0x6857…67c7

All memos sent from and to 0x6857…67c7.

0x68575b07…67c7·#25,914,177·0x8c726265…db09c2
otal-40);--i)ids.push(i); if(ids.length){ const res=await mc3Deep(ids.map(id=>({to:FLOOR,data:"0x"+SEL_BIDS+encUint(id)}))); const now=BigInt(Math.floor(Date.now()/1e3)),me2=me(); for(let i=0;i<ids.length;i++){ const s=strip0x(res[i]||"0x");if(s.length<704)continue; const w=k=>BigInt("0x"+s.slice(k*64,(k+1)*64)),ad=k=>"0x"+s.slice(k*64+24,(k+1)*64); if(ad(0).toLowerCase()!==me2)continue; const exp=w(2)+w(3);if(!exp||now<=exp)continue; const rem=w(10);if(!rem)continue; rows.push({id:ids[i],board:FLOOR,floor:1,v2:0,dutch:0,maker:ad(0),pf:1,exp, nA:0,nB:0,cp:ZERO,tA:ad(6),aA:w(8),sA:null,dA:null,tB:ad(4),aB:rem,sB:null,dB:null}); } }}catch{} const uniq=new Set(),deduped=[]; deduped.capped=capped; for(const r of rows){ const k=`${(r.board||"").toLowerCase()}|${r.dutch?"d":r.floor?"f":"o"}|${r.id}`; if(uniq.has(k))continue; uniq.add(k);deduped.push(r); } return deduped; } const loadBook=()=>loadBookRun().catch(()=>{}); async function loadBookRun(){ const my=++bookSeq; if(tab!=="book"){book.innerHTML="";bookRows=[];syncBook(0);return} const live=(await Promise.all(BOARDS().map(b=> rpc("eth_getCode",[b.a,L]).then(c=>(c||"").length>4?b:null,()=>null)))).filter(Boolean); if(my!==bookSeq)return; if(!live.length){ bookRows=[];bookLegs=[]; book.innerHTML='<h6><span>Orderbook</span></h6><div class="o"><span class="l"><i>No Swapboard deployed on this network yet.</i></span></div>'; syncBook(0);book.classList.remove("hide");return} let rows=null; try{rows=await lensBookRows(my)}catch{rows=null} if(my!==bookSeq)return; bookTrunc=!!(rows&&rows.capped); if(!rows)rows=[]; for(const b of rows.length?[]:live){ let total=0; try{total=Number(BigInt(await rpc(C,[{to:b.a,data:"0x"+SEL_NEXTID},L])))}catch{continue} if(!total)continue; const hi=total,lo=Math.max(0,hi-40); if(lo>0)bookTrunc=true; const ids=[];for(let i=hi-1;i>=lo;--i)ids.push(i); let raw=""; try{raw=await rpc(C,[{to:b.a,data:"0x"+SEL_GETORDERS+encUint(32)+encUint(ids.length)+ids.map(i=>encUint(i)).join("")},L])}catch{continue} if(my!==bookSeq)return; const h=strip0x(raw);if(h.length<128)continue; const w=WORDS(b.v2),words=(h.length-128)/64; let n=Number(BigInt("0x"+h.slice(64,128))); if(!Number.isSafeInteger(n)||n*w>words)n=Math.floor(words/w); const at=(r,k)=>BigInt("0x"+h.slice(128+(r*w+k)*64,128+(r*w+k+1)*64)); const ad=(r,k)=>"0x"+h.slice(128+(r*w+k)*64+24,128+(r*w+k+1)*64); for(let r=0;r<n&&r<ids.length;++r){ if(at(r,1)!==1n)continue; const o=b.v2 ?{id:ids[r],board:b.a,v2:1,maker:ad(r,0),pf:at(r,2)===1n,exp:at(r,3), nA:at(r,4)===1n,nB:at(r,5)===1n,cp:ad(r,6), tA:ad(r,7),aA:at(r,8),tB:ad(r,9),aB:at(r,10)} :{id:ids[r],board:b.a,v2:0,maker:ad(r,0),pf:0,exp:0n,nA:0,nB:0, cp:ZERO,tA:ad(r,2),aA:at(r,3),tB:ad(r,4),aB:at(r,5)}; if(o.exp&&BigInt(Math.floor(Date.now()/1e3))>o.exp &&o.maker.toLowerCase()!==me())continue; if(o.cp!==ZERO &&o.cp.toLowerCase()!==me() &&o.maker.toLowerCase()!==me())continue; rows.push(o); } } if(my!==bookSeq)return; const cutoff=BigInt(Math.floor(Date.now()/1e3)); rows=rows.filter(o=>{ const mine=o.maker.toLowerCase()===me(); if(o.exp&&cutoff>o.exp&&!mine)return false; return o.cp===ZERO ||o.cp.toLowerCase()===me() ||o.maker.toLowerCase()===me(); }); const dutchRows=rows.filter(r=>r.dutch); const bidRows=rows.filter(r=>r.floor); const fillRows=rows.filter(r=>!r.dutch&&!r.floor&&r.v2&&r.pf&&!r.nA&&!r.nB); if(dutchRows.length||bidRows.length||fillRows.length){ const reqs=[ ...dutchRows.map(r=>({to:DUTCH,data:"0x"+SEL_DUTCH_LISTING+encUint(r.id)})), ...bidRows.map(r=>({to:FLOOR,data:"0x"+SEL_BIDS+encUint(r.id)})), ...fillRows.map(r=>({to:r.board,data:"0x"+SEL_INITIAL_B+encUint(r.id)})), ]; const raw=await mc3Deep(reqs); if(my!==bookSeq)return; let k=0; for(const r of dutchRows){const x=raw[k++];if(!x)continue; const h=strip0x(x),w=n=>BigInt("0x"+h.slice(n*64,(n+1)*64)); if(h.length>=640)Object.assign(r,{ start:w(2),duration:w(3),startPrice:w(5),endPrice:w(7),initial:w(8),remaining:w(9)}); } for(const r of bidRows){const x=raw[k++];if(!x)continue; const h=strip0x(x),w=n=>BigInt("0x"+h.slice(n*64,(n+1)*64)); if(h.length>=11*64)Object.assign(r,{start:w(2),duration:w(3),startPrice:w(5),endPrice:w(7)}); } for(const r of fillRows){const x=raw[k++];if(!x)continue; const h=strip0x(x);if(h.length<64)continue; const init=BigInt("0x"+h.slice(0,64)); if(init>r.aB)r.filled=(init-r.aB)*10000n/init; } } if(!rows.length){bookRows=[];bookLegs=[];book.innerHTML="";syncBook(0);return} const needMeta=[]; for(const r of rows){ if(!r.nA&&(r.sA==null||r.dA==null))needMeta.push(r.tA); if(!r.nB&&(r.sB==null||r.dB==null))needMeta.push(r.tB); } await Promise.all([loadLogos(rows.flatMap(r=>[r.tA,r.tB])),warmMeta(needMeta)]); if(my!==bookSeq)return; const legs=await Promise.all(rows.map(r=>Promise.all([ leg(r.tA,r.aA,r.nA,r.sA!=null&&r.dA!=null?{sym:r.sA,dec:r.dA}:null), leg(r.tB,r.aB,r.nB,r.sB!=null&&r.dB!=null?{sym:r.sB,dec:r.dB}:null) ]))); if(my!==bookSeq)return; rows.forEach((r,i)=>{const[a,b]=legs[i]; if(!r.nA&&r.dA==null&&a.dec!=null)r.dA=a.dec; if(!r.nB&&r.dB==null&&b.dec!=null)r.dB=b.dec}); bookRows=rows;bookLegs=legs; paintBook(); } let bookPairOnly=LS.bkp!=="0",bookLegs=[]; let bookType=/^(all|limit|dutch|bid|nft)$/.test(LS.bkt||"")?LS.bkt:"all"; const unitPx=r=>r.aA&&r.aB&&!r.nA&&!r.nB&&!r.isNFT ?r.aB*10n**BigInt(18+(r.dA??18))/(r.aA*10n**BigInt(r.dB??18)):null; const sameAsset=(a,b)=>{ const w=x=>{x=(x||"").toLowerCase();return x===ZERO.toLowerCase()?WETH.toLowerCase():x}; return w(a)===w(b); }; const rowInPair=r=>{ const f=TOKENS[fromSel.value]?.addr,t=TOKENS[toSel.value]?.addr; if(!f||!t)return true; return (sameAsset(r.tA,f)&&sameAsset(r.tB,t))||(sameAsset(r.tA,t)&&sameAsset(r.tB,f)); }; const rowIsType=r=>bookType==="all" ||(bookType==="dutch"&&r.dutch) ||(bookType==="bid"&&r.floor) ||(bookType==="nft"&&(r.nA||r.nB||r.isNFT)) ||(bookType==="limit"&&!r.dutch&&!r.floor); const bookOpenCards=()=>{const s=[]; for(const el of book.querySelectorAll('.o.ins[aria-expanded="true"]')) if(el.dataset.k)s.push(el.dataset.k); return s}; const bookReopen=ks=>{ for(const k of ks){ const el=book.querySelector('.o.ins[data-k="'+k+'"]'); if(!el)continue; const at=k.lastIndexOf(":"); const c=cardCache[k.slice(0,at).toLowerCase()+":"+k.slice(at+1)]; const box=el.parentNode.querySelector(".ic"); if(!c||!box)continue; el.setAttribute("aria-expanded","true"); box.innerHTML=cardHtml(c)||"No card for this order."; }}; const bookOrder=rows=>{ const idx=rows.map((r,i)=>i); const f=TOKENS[fromSel.value]?.addr,t=TOKENS[toSel.value]?.addr; if(!bookPairOnly||!f||!t)return idx; const k=rows.map(r=>{const d=sameAsset(r.tA,t)&&sameAsset(r.tB,f)?0:1; return[d,d?null:unitPx(r)]}); const px=(a,b)=>a===null||b===null?(a===b?0:a===null?1:-1):a<b?-1:a>b?1:0; return idx.sort((a,b)=>k[a][0]-k[b][0]||px(k[a][1],k[b][1])); }; function paintBook(){ const rows=bookRows,legs=bookLegs; if(!rows.length){book.innerHTML="";syncBook(0);return} const wasOpen=bookOpenCards(),wasAt=book.scrollTop; let own="",rest="",nOwn=0,nRest=0,hidden=0; const now=BigInt(Math.floor(Date.now()/1e3)); bookOrder(rows).forEach(i=>{ const r=rows[i],[a,bg]=legs[i]||[]; if(!a||!bg)return; const mine=r.maker.toLowerCase()===me(); const expired=!!(r.exp&&now>r.exp); const tags=(expired?'<span class="tg w" title="This order has lapsed and can no longer be filled, but its escrow is still held by the board. Reclaim returns it to you.">expired</span>':"") +(r.nA||r.nB||r.isNFT?'<span class="tg">NFT</span>':"") +(r.v2&&!r.pf&&!r.nA&&!r.nB?'<span class="tg">AON</span>':"") +(r.dutch?'<span class="tg">Dutch</span>':"") +(r.floor?'<span class="tg">Bid</span>':"") +(r.floor&&r.isNFT&&r.anyId?'<span class="tg">any id</span>':"") +(r.filled>0n?`<span class="tg">${r.filled<100n?"<1":Math.floor(Number(r.filled)/100)}% filled</span>`:"") +(r.cp!==ZERO?'<span class="tg">private</span>':"") +(!a.ok||!bg.ok?'<a class="tg w" href="'+ZLIST_URL+'" target="_blank" rel="noopener"' +' title="Not on the staked token list. Its symbol and decimals are read straight from the contract' +' and nothing has vouched for it - check the address before filling. Stake on zList to get a real' +' token listed.">unverified</a>':""); let when=expired?" · lapsed "+rel(Number(now-r.exp))+" ago — escrow still held" :r.exp?" · "+rel(Number(r.exp-now))+" left":""; if(!expired&&r.dutch&&r.duration){ const end=r.start+r.duration; const floor=r.initial?(r.endPrice*r.remaining+r.initial-1n)/r.initial:0n; when=now>=end?` · floor reached: ${trimAmt(floor,r.dB)} ${r.sB}` :` · floor ${trimAmt(floor,r.dB)} ${r.sB} in ${rel(Number(end-now))}`; } if(!expired&&r.floor&&r.duration){ const end=r.start+r.duration; when=now>=end?" · window closed" :` · climbs to ${trimAmt(r.initial?r.endPrice*r.aB/r.initial:r.endPrice,r.dA)} ${r.sA} in ${rel(Number(end-now))}`; } const dset=`data-i="${r.id}" data-b="${r.board}" data-v="${r.v2}" data-d="${r.dutch||0}" data-f="${r.floor||0}"`; const takeable=!r.floor||(!r.isNFT&&floorReady); const shrinkable=mine&&!expired&&((r.dutch&&!r.nA&&!r.nB)||r.floor); const editable=mine&&!expired&&r.v2&&!r.dutch&&!r.floor&&!r.nA&&!r.nB; const act=mine?`<button data-x="c" ${dset}>${expired?"Reclaim":"Cancel"}</button>` +(shrinkable?`<button data-x="w" ${dset}>Shrink</button>`:"") +(editable?`<button data-x="r" ${dset}>Edit</button>`:"") :takeable?`<button data-x="f" ${dset}>${r.floor?"Sell into":"Fill"}</button>` :`<button disabled>${r.isNFT?"NFT bid":"Not routable"}</button>`; const card=r.board.toLowerCase()!==SB1.toLowerCase(); const row=`<div class="ow"><div class="o${card?" ins":""}"${card?` data-k="${r.board}:${r.id}" role="button" tabindex="0" aria-expanded="false" title="Show the on-chain receipt"`:""}>` +`<span class="l">${a.ic}${bg.ic}<span><b>${a.txt} → ${bg.txt}</b>` +`<i>${pxOf(r,a,bg)}${a.link}${tags}${when}</i></span></span>${act}</div><div class="ic"></div></div>`; if(mine){own+=row;++nOwn;return} if(!(bookPairOnly?rowInPair(r):true)||!rowIsType(r)){++hidden;return} rest+=row;++nRest; }); let html=""; if(nOwn)html+=`<h6><span>Your orders</span><span>${nOwn}</span></h6>`+own; html+=bookFilters(hidden); if(nRest)html+=rest; else html+=`<div class="lqempty">${emptyBook()}${hidden?` ${hidden} hidden by the filters.`:""}</div>`; book.innerHTML=html; bookReopen(wasOpen); syncBook(nOwn+nRest,rows.length); book.scrollTop=wasAt; } const pxOf=(r,a,bg)=>{ const v=unitPx(r); return v?`<span class="px">${sigAmt(v,18,4)} ${bg.sym}/${a.sym}</span> \u00b7 `:""; }; const cardCache={}; async function loadCard(board,id){ const k=board.toLowerCase()+":"+id; if(cardCache[k])return cardCache[k]; const raw=await rpc(C,[{to:board,data:"0x"+SEL_TOKENURI+encUint(id)},L]); const uri=decodeString(raw); const i=uri.indexOf("base64,"); if(i<0)throw Error("card is not inline"); const j=JSON.parse(atob(uri.slice(i+7))); const out={attrs:Array.isArray(j.attributes)?j.attributes:[],img:safeUrl(j.image), name:safeSym(j.name).slice(0,40)}; cardCache[k]=out;capObj(cardCache,CARD_MAX);return out; } function cardHtml(c){ const rows=c.attrs.slice(0,10).map(a=>{ const t=safeSym(a&&a.trait_type).slice(0,20); let v=a&&a.value; if(a&&a.display_type==="date"&&+v)v=new Date(+v*1e3).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}); else if(a&&a.display_type==="number")v=String(v); return`<dt>${t}</dt><dd>${safeSym(v).slice(0,28)}</dd>`}).join(""); return(rows?`<dl>${rows}</dl>`:"")+(c.img?`<img src="${c.img}" alt="" loading="lazy">`:""); } let inspectSeq=0; async function inspect(el,k){ const box=el.parentNode.querySelector(".ic"); if(!box)return; if(box.innerHTML){box.innerHTML="";delete box.dataset.job;el.setAttribute("aria-expanded","false");return} el.setAttribute("aria-expanded","true"); const at=k.lastIndexOf(":"),board=k.slice(0,at),id=BigInt(k.slice(at+1)); const job=String(++inspectSeq);box.dataset.job=job; box.textContent="Reading the receipt\u2026"; let html; try{html=cardHtml(await loadCard(board,id))||"No card for this order."} catch{html=""} if(box.dataset.job!==job)return; if(html)box.innerHTML=html;else box.textContent="This receipt could not be read."; } const BOOK_TYPES=[["all","All"],["limit","Limit"],["dutch","Dutch"],["bid","Bids"],["nft","NFT"]]; const emptyBook=()=>{ const ty=BOOK_TYPES.find(([v])=>v===bookType); const what=bookType==="all"?"orders":`${ty?ty[1]:bookType} orders`; const where=bookPairOnly ?` on ${TOKENS[fromSel.value]?.sym||"?"}/${TOKENS[toSel.value]?.sym||"?"}`:""; return `No ${what.toLowerCase()}${where} from anyone else.`; }; function bookFilters(hidden){ const f=TOKENS[fromSel.value]?.sym||"?",t=TOKENS[toSel.value]?.sym||"?"; const chip=(on,attr,val,label,title)=>`<button type="button" class="bkc${on?" on":""}" ` +`aria-pressed="${on?"true":"false"}" data-${attr}="${val}"${title?` title="${title}"`:""}>${label}</button>`; const note=[hidden?hidden+" hidden":"",bookTrunc?"scan capped":""].filter(Boolean).join(" \u00b7 "); return `<div class="bkh"><h6><span>Orderbook</span><span title="${bookTrunc?"More orders exist than this scan returned \u2014 the newest are shown first.":""}">${note}</span></h6>` +`<div class="bkf">` +chip(bookPairOnly,"bf","1",`${f}/${t}`,"Only orders on the pair selected above") +chip(!bookPairOnly,"bf","0","All pairs","Every order on every board") +`</div><div class="bkf">` +BOOK_TYPES.map(([v,l])=>chip(bookType===v,"bt",v,l)).join("") +`</div></div>`; } let bookShown=0; function syncBook(n,total){ bookShown=n; total=total===undefined?n:total; const on=tab==="book"&&total>0; bkTog.classList.toggle("hide",!on); bkTog.firstChild.nodeValue=total ?`Orderbook (${n<total?`${n} of ${total}`:total}) `:"Orderbook "; bkTog.setAttribute("aria-expanded",on&&bkOpen?"true":"false"); book.classList.toggle("hide",!on||!bkOpen); } bkTog.onclick=()=>{bkOpen=!bkOpen;LS.bk=bkOpen?"1":"0";syncBook(bookShown,bookRows.length)}; book.addEventListener("keydown",e=>{ if(e.key!=="Enter"&&e.key!==" ")return; const row=e.target.closest(".o.ins"); if(!row||e.target!==row)return; e.preventDefault();inspect(row,row.dataset.k); }); book.addEventListener("click",async e=>{ const fc=e.target.closest("[data-bf]"),tc=e.target.closest("[data-bt]"); if(fc){bookPairOnly=fc.dataset.bf==="1";LS.bkp=bookPairOnly?"1":"0";paintBook();return} if(tc){bookType=tc.dataset.bt;LS.bkt=bookType;paintBook();return} const b=e.target.closest("button[data-i]"); if(!b){ const row=e.target.closest(".o.ins"); if(row&&!e.target.closest("a"))inspect(row,row.dataset.k); return; } b.disabled=true; ++busy; try{ await checkWallet(); const id=BigInt(b.dataset.i),to=b.dataset.b,dl=BigInt(Math.floor(Date.now()/1e3)+1800); const row=bookRows.find(x=>String(x.id)===b.dataset.i&&x.board.toLowerCase()===to.toLowerCase()); if(!row)throw Error("order changed — refresh and retry"); if(b.dataset.x==="c"){ const native=!row.nA&&row.tA.toLowerCase()===WETH.toLowerCase()&&wantsUnwrap(); const floorish=b.dataset.d==="1"||b.dataset.f==="1"; const sel=floorish ?(native?SEL_DUTCH_CANCEL_UNWRAP:SEL_DUTCH_CANCEL) :(native&&b.dataset.v==="1"?SEL_CANCEL_UNWRAP:SEL_CANCELORD); const data="0x"+sel+encUint(id); const lapsed=row.exp&&BigInt(Math.floor(Date.now()/1e3))>row.exp; stat.textContent=`${lapsed?"Reclaiming":"Cancelling"} — the ${b.dataset.f==="1"?"unspent escrow":"unfilled remainder"} returns as ${native&&(floorish||b.dataset.v==="1")?"ETH":"its escrow token"}`; const tx={from:account,to,data,value:"0x0"}; await rpc(C,[tx,L]); await settle(await sendTx([tx])); loadBook();refreshBalance();return; } if(b.dataset.x==="w"){ const refund=row.tA; const native=refund.toLowerCase()===WETH.toLowerCase()&&wantsUnwrap(); const dec=row.dutch?(row.dA??18):(row.dB??18),sym=row.dutch?(row.sA||"tokens"):(row.sB||"tokens"); const held=row.dutch?row.aA:row.aB; const answer=prompt(`Shrink this ${row.dutch?"lot":"bid"}.\n\nIt is escrowed for ${trimAmt(held,dec)} ${sym}.\n\nEnter the amount to stop ${row.dutch?"selling":"bidding for"} — the escrow behind it returns${native?" as ETH":""}. The rest stays live:`,trimAmt(held,dec)); if(answer===null){b.disabled=false;return} let asked; try{asked=parseUnits(String(answer).trim(),dec)}catch{throw Error("that is not an amount")} if(!asked)throw Error("enter an amount above zero"); if(asked>=held)throw Error(`that is the whole ${row.dutch?"lot":"bid"} — use Cancel instead`); const tx={from:account,to,data:"0x"+SEL_WITHDRAW+encUint(id)+encUint(asked)+encBool(native),value:"0x0"}; stat.textContent=`Shrinking — the escrow behind ${trimAmt(asked,dec)} ${sym} returns${native?" as ETH":""}...`; await rpc(C,[tx,L]); await settle(await sendTx([tx])); loadBook();refreshBalance();return; } if(b.dataset.x==="r"){ const sym=row.sB||"tokens",dec=row.dB??18; const answer=prompt(`Reprice this order without losing its place on the book.\n\nIt sells ${trimAmt(row.aA,row.dA??18)} ${row.sA||"tokens"} for ${trimAmt(row.aB,dec)} ${sym}.\n\nEnter the NEW total ${sym} you want:`,trimAmt(row.aB,dec)); if(answer===null){b.disabled=false;return} let asked; try{asked=parseUnits(String(answer).trim(),dec)}catch{throw Error("that is not an amount")} if(!asked)throw Error("enter an amount above zero"); if(asked===row.aB)throw Error("that is the same price"); const tx={from:account,to,data:"0x"+SEL_REPLACE+encUint(id)+encUint(row.aA)+encUint(asked)+encUint(row.exp||0n),value:"0x0"}; stat.textContent="Repricing..."; await rpc(C,[tx,L]); await settle(await sendTx([tx])); loadBook();refreshBalance();return; } const r=row; let payB=r.aB,getA=r.aA; if(r.pf&&!r.nA&&!r.nB&&!r.dutch&&!r.floor&&r.v2&&r.maker.toLowerCase()!==account.toLowerCase()){ const dB=r.dB??18,dA=r.dA??18,sB=r.sB||"tokens",sA=r.sA||"tokens"; let have=0n; try{ have=r.tB===ZERO||r.tB.toLowerCase()===WETH.toLowerCase() ?BigInt(await rpc("eth_getBalance",[account,L])) +(r.tB===ZERO?0n:BigInt(await rpc(C,[{to:WETH,data:"0x"+SEL_BALANCEOF+encAddr(account)},L]))) :BigInt(await rpc(C,[{to:r.tB,data:"0x"+SEL_BALANCEOF+encAddr(account)},L])); }catch{} const suggest=have&&have<r.aB?have:r.aB; const answer=prompt( `Fill how much of this order?\n\nIt sells ${trimAmt(r.aA,dA)} ${sA} for ${trimAmt(r.aB,dB)} ${sB} in total, ` +`and can be filled in part.\n\nEnter the ${sB} you want to spend:`, trimAmt(suggest,dB)); if(answer===null){b.disabled=false;return} let asked; try{asked=parseUnits(String(answer).trim(),dB)}catch{throw Error("that is not an amount")} if(!asked)throw Error("enter an amount above zero"); if(asked>r.aB)throw Error(`that is more than the order needs — it costs ${trimAmt(r.aB,dB)} ${sB} in full`); if(asked<r.aB){ const q=await rpc(C,[{to:r.board,data:"0x"+SEL_QUOTEFILL+encUint(id)+encUint(asked)},L]); const h=strip0x(q); if(h.length<128)throw Error("the board would not quote that amount"); getA=BigInt("0x"+h.slice(0,64)); payB=BigInt("0x"+h.slice(64,128)); if(!getA)throw Error("that amount buys nothing — try a larger one"); stat.textContent=`Filling ${trimAmt(payB,dB)} ${sB} of ${trimAmt(r.aB,dB)} for ${trimAmt(getA,dA)} ${sA}...`; } } const nativeOut=!r.nA&&r.tA.toLowerCase()===WETH.toLowerCase()&&wantsUnwrap(); const wethLeg=!r.nB&&r.tB.toLowerCase()===WETH.toLowerCase()&&payB>0n; const plan=wethLeg?await planWethPayment(payB):null; if(plan&&plan.rail==="short") throw Error(`Not enough ether to fill: it costs ${trimAmt(payB,18)} and you hold ` +`${trimAmt(plan.w,18)} WETH plus ${trimAmt(plan.e,18)} ETH. ` +(r.pf ?`This button takes the whole order — to buy part of it, use the Swap tab, which fills this order alongside the pools.` :`This order is all-or-nothing, so it cannot be filled in smaller pieces.`)); if(plan&&plan.wrap>0n){ stat.textContent=`Wrapping ${trimAmt(plan.wrap,18)} ETH to cover the rest...`; const wtx={from:account,to:WETH,data:"0xd0e30db0",value:toHex(plan.wrap)}; await rpc(C,[wtx,L]);await checkWallet();await settle(await sendTx([wtx])); } const nativeIn=plan?.rail==="eth"; const routeIn=nativeIn?ZERO:r.tB; const routeOut=nativeOut?ZERO:r.tA; const canonicalNative=routeIn.toLowerCase()===routeOut.toLowerCase() ||(routeOut===ZERO&&routeIn.toLowerCase()===WETH.toLowerCase()); if(!r.nB)await assertFunded(routeIn,payB, known(routeIn)||(routeIn===ZERO?{sym:"ETH",dec:18}:{sym:r.sB||"TOKEN",dec:r.dB??18})); if(!r.nA&&!r.nB&&r.cp===ZERO&&!canonicalNative){ if((await rpc("eth_getCode",[SWAPBOL,L])).length<5)throw Error("orderbook executor not deployed yet"); await preflightLeg(r,routeIn,routeOut,payB); const pay=payB,fillPlan=encFillPlan([ {id:r.id,board:r.board,pay,get:getA,part:false} ],routeIn,routeOut,account,account,dl); const planCall=encSnwap(pay?routeIn:ZERO,pay,account,routeOut,getA,SWAPBOL,fillPlan); const checkpoint=routeIn===ZERO?null:encCheckpoint(SWAPBOL,routeIn,account); const direct=checkpoint?encCalls([checkpoint,planCall]):planCall; const funded=routeIn===ZERO||!pay?direct:encCalls([ checkpoint, encSweep(routeIn,pay,SWAPBOL), encSnwap(ZERO,0n,account,routeOut,getA,SWAPBOL,fillPlan) ]); const meta=known(routeIn)||{addr:routeIn,sym:r.sB||"TOKEN",dec:r.dB??18}; await sendRouterFunded(direct,funded,meta,pay,routeIn===ZERO?pay:0n,"Filling order..."); loadBook();refreshBalance();return; } const data=r.dutch ?"0x"+SEL_DUTCH_FILL+encUint(id)+encUint(r.nA?0n:getA)+encAddr(account)+encUint(payB) :r.v2 ?"0x"+(nativeIn?SEL_FILL2_ETH:nativeOut?SEL_FILL2_UNWRAP:SEL_FILL2) +encUint(id)+encUint(dl)+(nativeIn?"":encUint(payB))+encUint(r.nA?0n:getA)+encAddr(ZERO) :"0x"+SEL_FILL1+encUint(id)+encUint(dl); const payNative=(nativeIn&&!r.dutch&&r.v2)||(r.dutch&&r.tB===ZERO); const wrap=nativeIn&&!payNative&&payB ?{to:WETH,data:"0xd0e30db0",value:toHex(payB)}:null; const tx={from:account,to,data,value:payNative?toHex(payB):"0x0"}; const approvals=[]; if(r.nB){ let ap=ZERO; try{ap="0x"+strip0x(await rpc(C,[{to:r.tB,data:"0x081812fc"+encUint(payB)},L])).slice(-40)}catch{} if(ap.toLowerCase()!==to.toLowerCase()){ approvals.push({to:r.tB,data:"0x"+SEL_APPROVE+encAddr(to)+encUint(payB),value:"0x0"}); } }else if(r.tB!==ZERO&&payB&&!(nativeIn&&!r.dutch&&r.v2)){ const al=BigInt(await rpc(C,[{to:r.tB,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(to)},L])); if(al<payB){ if(al)approvals.push({to:r.tB,data:"0x"+SEL_APPROVE+encAddr(to)+encUint(0n),value:"0x0"}); approvals.push({to:r.tB,data:"0x"+SEL_APPROVE+encAddr(to)+encUint(payB),value:"0x0"}); } } const revoke=r.dutch&&!r.nB&&r.tB!==ZERO ?{to:r.tB,data:"0x"+SEL_APPROVE+encAddr(to)+encUint(0n),value:"0x0"}:null; if((wrap||approvals.length||revoke)&&await canBatch(account)){ stat.textContent="Preparing and filling atomically..."; await settle(await sendBatch(account,[ ...(wrap?[wrap]:[]),...approvals,{to,data,value:tx.value},...(revoke?[revoke]:[]) ])); loadBook();refreshBalance();return; } if(wrap){ stat.textContent="Wrapping ETH..."; await waitTx(await sendTx([{from:account,...wrap}])); } for(let i=0;i<approvals.length;i++){ stat.textContent=`Approve${r.nB?" NFT":""}${approvals.length>1?` (${i+1}/${approvals.length})`:""}...`; await waitTx(await sendTx([{from:account,...approvals[i]}])); } await rpc(C,[tx,L]); await settle(await sendTx([tx])); if(revoke)try{ stat.textContent="Filled · revoking quote allowance..."; await waitTx(await sendTx([{
0x68575b07…67c7·#25,914,176·0xfa0819eb…6d1d26
sh and retry"); const addr=i=>"0x"+at(i).slice(24); if(/^0x0{40}$/i.test(addr(0)))throw Error("bid is closed — refresh and retry"); if(BigInt("0x"+at(1)))throw Error("NFT bids are not routable from here"); if(addr(4).toLowerCase()!==want.toLowerCase() ||addr(6).toLowerCase()!==gets.toLowerCase()) throw Error("bid changed — refresh and retry"); if(BigInt("0x"+at(10))<pay)throw Error("bid partly taken — refresh and retry"); } async function preflightFills(fills,tokenIn,tokenOut){ if(!fills||!fills.length)return; const inA=(tokenIn===ZERO?WETH:tokenIn).toLowerCase(); const outA=(tokenOut===ZERO?WETH:tokenOut).toLowerCase(); const reads=fills.map(f=>f.board.toLowerCase()===FLOOR.toLowerCase() ?{to:FLOOR,data:"0x"+SEL_BIDS+encUint(f.id)} :f.board.toLowerCase()===DUTCH.toLowerCase() ?{to:DUTCH,data:"0x"+SEL_DUTCH_LISTING+encUint(f.id)} :{to:f.board,data:"0x"+SEL_GETORDERS+encUint(32)+encUint(1)+encUint(f.id)}); const res=await mc3Deep(reads); const stale=()=>{throw Error("an order in this route just changed — refreshing")}; for(let i=0;i<fills.length;i++){ const f=fills[i],raw=res[i]; if(!raw)stale(); const s=strip0x(raw),at=k=>BigInt("0x"+s.slice(k*64,(k+1)*64)); if(f.board.toLowerCase()===FLOOR.toLowerCase()){ if(s.length<11*64)stale(); const ad=k=>"0x"+s.slice(k*64+24,(k+1)*64); if(/^0x0{40}$/i.test(ad(0))||at(1))stale(); if(ad(4).toLowerCase()!==inA||ad(6).toLowerCase()!==outA)stale(); if(at(10)<f.pay)stale(); }else if(f.board.toLowerCase()===DUTCH.toLowerCase()){ if(s.length<11*64||at(0)===0n)stale(); if(at(9)<f.get)stale(); if(at(10)&&BigInt(Math.floor(Date.now()/1e3))>at(10))stale(); }else{ const wds=WORDS(f.board.toLowerCase()===SB2.toLowerCase()?1:0); if(s.length<(2+wds)*64)stale(); const w=k=>at(2+k),ad=k=>"0x"+s.slice((2+k)*64+24,(3+k)*64); if(w(1)!==1n)stale(); const v2=wds===11,iA=v2?7:2,iB=v2?9:4; if(ad(iA).toLowerCase()!==outA||ad(iB).toLowerCase()!==inA)stale(); if(w(iB+1)<f.pay)stale(); if(v2&&w(iA+1)<f.get)stale(); if(v2&&w(3)&&BigInt(Math.floor(Date.now()/1e3))>w(3))stale(); } } } async function preflightAsk(row,pay){ if(row.dutch){ let h; try{h=await rpc(C,[{to:DUTCH,data:"0x"+SEL_DUTCH_LISTING+encUint(row.id)},L])} catch{throw Error("listing could not be read — refresh and retry")} const s=strip0x(h); if(s.length<640)throw Error("listing could not be read — refresh and retry"); if(BigInt("0x"+s.slice(0,64))===0n)throw Error("listing is closed — refresh and retry"); return; } let h; try{h=await rpc(C,[{to:row.board, data:"0x"+SEL_GETORDERS+encUint(32)+encUint(1)+encUint(row.id)},L])} catch{throw Error("order could not be read — refresh and retry")} const s=strip0x(h),wds=WORDS(row.v2); if(s.length<(2+wds)*64)throw Error("order could not be read — refresh and retry"); const at=k=>BigInt("0x"+s.slice(128+k*64,128+(k+1)*64)); const ad=k=>"0x"+s.slice(128+k*64+24,128+(k+1)*64); if(at(1)!==1n)throw Error("order is no longer active — refresh and retry"); const iA=row.v2?7:2,iB=row.v2?9:4; if(ad(iA).toLowerCase()!==row.tA.toLowerCase() ||ad(iB).toLowerCase()!==row.tB.toLowerCase()) throw Error("order changed — refresh and retry"); if(at(iA+1)>row.aA||at(iB+1)>row.aB)throw Error("order changed — refresh and retry"); if(pay!==undefined&&at(iB+1)<pay)throw Error("order partly taken — refresh and retry"); } const SEL_ORDER_FIXED="bcdb7936",SEL_ORDER_DUTCH="bbd94e1a",SEL_CHECKPOINT="a972985e"; const BOARDS=()=>[{a:SB2,v2:1},{a:SB1,v2:0}].filter(b=>b.a!==ZERO); const WORDS=b=>b?11:6; let bookSeq=0,bookRows=[],candCache={},bkOpen=LS.bk!=="0",bookTrunc=false; let busy=0; if(LS.wm==="weth"||LS.wm==="auto")ethMode.value=LS.wm; ethMode.onchange=()=>{LS.wm=ethMode.value}; const wantsUnwrap=()=>ethMode.value!=="weth"; const planWethPayment=async need=>{ let w=0n,e=0n; try{ const[wh,eh]=await Promise.all([ rpc(C,[{to:WETH,data:"0x"+SEL_BALANCEOF+encAddr(account)},L]), rpc("eth_getBalance",[account,L])]); w=BigInt(wh);e=BigInt(eh); }catch{return{rail:"weth",wrap:0n,w,e}} if(w>=need)return{rail:"weth",wrap:0n,w,e}; if(e>=need)return{rail:"eth",wrap:0n,w,e}; let gas=0n; try{gas=BigInt(await rpc("eth_gasPrice",[]))*400000n}catch{} const spendable=e>gas?e-gas:0n; if(w+spendable>=need)return{rail:"weth",wrap:need-w,w,e}; return{rail:"short",wrap:0n,w,e}; }; const assertFunded=async(token,need,meta)=>{ if(!need)return; let have; try{ have=token===ZERO ?BigInt(await rpc("eth_getBalance",[account,L])) :BigInt(await rpc(C,[{to:token,data:"0x"+SEL_BALANCEOF+encAddr(account)},L])); }catch{return} if(have>=need)return; const m=meta||known(token)||{sym:token===ZERO?"ETH":"tokens",dec:18}; throw Error(`Not enough ${m.sym} to fill: it costs ${trimAmt(need,m.dec)} ` +`and you hold ${trimAmt(have,m.dec)}.`); }; const known=a=>TOKENS.find(t=>t.addr.toLowerCase()===a.toLowerCase()); const sm=i=>i.replace('width="20" height="20"','width="15" height="15"'); const escan=(a,id)=>!isAddr(a)?"":`<a href="${chainCfg().explorer}/${id!=null?`nft/${a}/${id}`:`token/${a}`}" target="_blank" rel="noreferrer">${a.slice(0,6)}…${a.slice(-4)} ↗</a>`; const SEL_CONTRACTURI="e8a3d485"; const okDataImg=u=>typeof u==="string"&&u.length<=60000 &&/^data:image\/[a-z.+-]{2,20};base64,[A-Za-z0-9+/=]+$/i.test(u); const imgFromContractURI=raw=>{ const P="data:application/json;base64,"; const u=String(raw||"").trim(); if(!u.startsWith(P)||u.length>400000)return ""; let txt; try{ const bin=atob(u.slice(P.length)); txt=new TextDecoder().decode(Uint8Array.from(bin,c=>c.charCodeAt(0))); }catch{return ""} let j;try{j=JSON.parse(txt)}catch{return ""} return okDataImg(j&&j.image)?j.image:""; }; const capObj=(o,max)=>{const ks=Object.keys(o); for(let i=0;i<ks.length-max;i++)delete o[ks[i]]}; const capMap=(m,max)=>{while(m.size>max)m.delete(m.keys().next().value)}; const LOGO_MAX=192,CARD_MAX=64,META_MAX=256; const logoCache={}; async function loadLogos(addrs){ const want=[...new Set(addrs.map(a=>a.toLowerCase()))] .filter(a=>a!==ZERO&&!known(a)&&logoCache[a]===undefined); if(!want.length)return; let raw=[]; if(TOKENLIST!==ZERO)raw=await cfgBatch(want.map(a=>({to:TOKENLIST,data:"0x"+SEL_LOGOOF+encAddr(a)}))).catch(()=>[]); want.forEach((a,i)=>{let u="";try{if(raw[i])u=safeDataUrl(decodeString(raw[i]))}catch{}logoCache[a]=u}); capObj(logoCache,LOGO_MAX); const self=want.filter(a=>!logoCache[a]); if(!self.length)return; const cu=await mc3Deep(self.map(a=>({to:a,data:"0x"+SEL_CONTRACTURI}))).catch(()=>[]); self.forEach((a,i)=>{try{ const u=imgFromContractURI(cu[i]?decodeString(cu[i]):""); if(u)logoCache[a]=u; }catch{}}); } async function leg(addr,amt,isNft,meta){ const k=known(addr); if(isNft)return{ic:genIcon("#"),txt:`#${amt}`,ok:0,link:escan(addr,amt)}; const raw=meta&&meta.sym?meta:await tokMeta(addr),m={...raw,sym:safeSym(raw.sym)}; const lg=k?"":logoCache[addr.toLowerCase()]; const ic=k?sm(k.icon):lg?`<img src="${lg}" width="15" height="15">`:genIcon(m.sym); return{ic,txt:`${trimAmt(amt,m.dec)} ${k?safeSym(k.sym):'“'+m.sym+'”'}`,ok:!!k, sym:k?safeSym(k.sym):m.sym,dec:m.dec,link:addr===ZERO?"ETH":escan(addr)}; } let floorSeq=0; nftId.addEventListener("input",()=>{syncOrderType();showFloor();render()}); async function showFloor(){ const el=document.getElementById("floorNote");if(!el)return; const side=isNft(fromSel.value)?fromSel.value:isNft(toSel.value)?toSel.value:null; const live=tab==="book"&&side!==null&&!nftId.value.trim(); if(!live){el.classList.add("hide");el.textContent="";return} const seq=++floorSeq,t=TOKENS[side]; el.classList.remove("hide");el.textContent="Checking floor bids…"; const oth=isNft(fromSel.value)?toSel.value:fromSel.value; const qt=isNft(oth)?ZERO:(TOKENS[oth].addr===ZERO?WETH:TOKENS[oth].addr); const rows=await collectionFloor(t.addr,qt).catch(()=>[]); if(seq!==floorSeq)return; if(!rows.length){el.textContent="No standing bids on this collection.";return} const best=rows[0],q=known(best.quote); el.textContent=`${rows.length} collection bid${rows.length>1?"s":""} · best pays ` +`${trimAmt(best.price/best.initial,q?q.dec:best.qDec??18)} ${q?q.sym:safeSym(best.qSym||"?")} for any ${t.sym}`; } function decBidRows(hex){ const out=[]; try{ const h=strip0x(hex),bytes=h.length/2; const word=b=>{if(b<0||b+32>bytes)throw 0;return BigInt("0x"+h.slice(b*2,(b+32)*2))}; const num=b=>{const v=word(b);if(v>0xffffffffffffffffn)throw 0;return Number(v)}; const arr=num(0); if(arr<64||arr+32>bytes)throw 0; const n=num(arr),base=arr+32; if(n>256||base+n*32>bytes)throw 0; for(let i=0;i<n;i++){ try{ const row=base+num(base+i*32); if(row+17*32>bytes)throw 0; const f=k=>word(row+k*32); const txt=k=>{const p=row+num(row+k*32),len=num(p); if(p+32+len>bytes)throw 0; return safeSym(new TextDecoder().decode(hexToBytes(h.slice((p+32)*2,(p+32+Math.min(len,1024))*2))))}; const dec=k=>{const v=num(row+k*32);return v>0&&v<=37?v-1:null}; out.push({ id:f(0), bidder:"0x"+h.slice((row+1*32+12)*2,(row+2*32)*2), token:"0x"+h.slice((row+2*32+12)*2,(row+3*32)*2), quote:"0x"+h.slice((row+3*32+12)*2,(row+4*32)*2), isNFT:f(4)!==0n, anyId:f(5)!==0n, remaining:f(7), initial:f(8), price:f(9), proceeds:f(10), expiry:f(12), tDec:dec(13),qDec:dec(14), tSym:txt(15),qSym:txt(16), }); }catch{continue} } return out; }catch{return out} } async function collectionFloor(collection,quote=ZERO){ if(FLOORVIEW===ZERO||FLOOR===ZERO)return[]; const data="0x"+SEL_COLL_BIDS+encAddr(FLOOR)+encAddr(collection)+encAddr(quote) +encUint(0)+encUint(16)+encUint(256); const raw=await rpc(C,[{to:FLOORVIEW,data},L]).catch(()=>null); if(!raw)return[]; const rows=decBidRows(raw); return rows.filter(r=>r.anyId&&r.remaining>0n) .sort((a,b)=>{const l=b.proceeds*a.remaining,r=a.proceeds*b.remaining;return l>r?1:l<r?-1:0}); } async function floorCandidates(tokenIn,tokenOut,block){ const bin=tokenIn===ZERO?WETH:tokenIn,bout=tokenOut===ZERO?WETH:tokenOut; const data="0x"+SEL_FLOOR_CANDS+encAddr(FLOOR)+encAddr(bin)+encAddr(bout) +encUint(0)+encUint(64)+encUint(512); const raw=await rpc(C,[{to:FLOORVIEW,data},block]).catch(()=>null); if(!raw)return[]; const out=[]; for(const b of decBidRows(raw)){ if(b.isNFT||b.remaining<=0n||b.initial<=0n||b.price<=0n||b.proceeds<=0n)continue; if(b.token.toLowerCase()!==bin.toLowerCase())continue; if(b.quote.toLowerCase()!==bout.toLowerCase())continue; out.push({ id:b.id,board:FLOOR,floor:1,v2:0,dutch:0, maker:b.bidder,pf:1,exp:b.expiry,nA:0,nB:0,cp:ZERO, tA:b.quote,aA:b.proceeds,sA:b.qSym,dA:b.qDec, tB:b.token,aB:b.remaining,sB:b.tSym,dB:b.tDec, price:b.price,initial:b.initial, }); } return out; } function floorGet(o,pay){return o.price*pay/o.initial} function floorPay(o,want){ if(want<=0n)return 0n; const g=(want*o.initial+o.price-1n)/o.price; return g>o.aB?o.aB:g; } function rowGet(o,pay){return o.floor?floorGet(o,pay):o.aA*pay/o.aB} function decViewPage(hex){ const h=strip0x(hex),bytes=h.length/2; const word=b=>{if(b<0||b+32>bytes)throw Error("bad lens page");return BigInt("0x"+h.slice(b*2,(b+32)*2))}; const addr=(b)=>"0x"+h.slice((b+12)*2,(b+32)*2); const arr=Number(word(0)),next=word(32); if(!Number.isSafeInteger(arr)||arr<64||arr+32>bytes)throw Error("bad lens offset"); const n=Number(word(arr)),head=arr+32; if(!Number.isSafeInteger(n)||n>256||head+n*32>bytes)throw Error("bad lens length"); const text=(base,k)=>{const off=Number(word(base+k*32)),p=base+off,len=Number(word(p)); if(!Number.isSafeInteger(off)||!Number.isSafeInteger(len)||p+32+len>bytes)throw Error("bad lens string"); const cap=Math.min(len,1024),a=new Uint8Array(cap); for(let i=0;i<cap;i++)a[i]=parseInt(h.slice((p+32+i)*2,(p+33+i)*2),16); return new TextDecoder().decode(a)}; const rows=[]; const dec=v=>{const d=Number(v);return Number.isInteger(d)&&d>=0&&d<=36?d:null}; for(let i=0;i<n;i++){ try{ const off=Number(word(head+i*32)),b=head+off; if(!Number.isSafeInteger(off)||b+16*32>bytes)throw Error("bad lens row"); rows.push({id:word(b),maker:addr(b+32),pf:word(b+64)===1n,exp:word(b+96), nA:word(b+128)===1n,nB:word(b+160)===1n,cp:addr(b+192), tA:addr(b+224),aA:word(b+256),sA:safeSym(text(b,9)),dA:dec(word(b+320)), tB:addr(b+352),aB:word(b+384),sB:safeSym(text(b,13)),dB:dec(word(b+448)),board:addr(b+480)}); }catch{continue} } return{rows,next}; } const betterRate=(a,b)=>a.aA*b.aB>b.aA*a.aB; const bookShare=(h,amm)=>{ const rest=h.ammIn!==undefined?h.ammIn:h.ammOut; if(!rest)return"Orderbook"; const mine=h.ammIn!==undefined?h.bookIn:h.bookOut; const pct=Number(mine*100n/(mine+rest)); return`Orderbook ${pct<1?"<1":pct}% + ${amm}`; }; const MAX_BOOK_LEGS=32,AON_PAIR_SEEDS=24; function planBookExactIn(rows,amountIn,ammOut,ammIn){ if(!amountIn||!ammIn)return null; let c=rows.filter(o=>!o.nA&&!o.nB&&o.aA&&(o.aB||o.dutch)&&(o.pf||o.aB<=amountIn) &&(!o.floor||(o.price>0n&&o.initial>0n)) &&!(account&&o.maker&&o.maker.toLowerCase()===account.toLowerCase())); const candidateCapped=c.length>256; const aonCount=c.reduce((n,o)=>n+!o.pf,0); const aonBounded=aonCount>=3; c.sort((a,b)=>betterRate(a,b)?-1:betterRate(b,a)?1:0); if(c.length>256)c.length=256; const build=(seed=-1,seed2=-1)=>{ let rem=amountIn,bin=0n,bout=0n,fills=[],legCapped=false; const take=o=>{ if(o.aA*ammIn<=ammOut*o.aB)return; if(!o.pf&&o.aB>rem)return; if(fills.length>=MAX_BOOK_LEGS){legCapped=true;return} if(o.aB===0n){ fills.push({id:o.id,board:o.board,pay:0n,get:o.aA,part:false}); bout+=o.aA;return; } const pay=o.aB<rem?o.aB:rem; if(!pay)return; const get=o.pf?(pay===o.aB?o.aA:rowGet(o,pay)):o.aA; if(!get)return; fills.push({id:o.id,board:o.board,pay,get,part:o.pf&&pay<o.aB}); rem-=pay;bin+=pay;bout+=get; }; if(seed>=0)take(c[seed]); if(seed2>=0)take(c[seed2]); for(let i=0;i<c.length;i++)if(i!==seed&&i!==seed2)take(c[i]); return{fills,bookIn:bin,bookOut:bout,ammIn:rem,score:bout+rem*ammOut/ammIn, bounded:candidateCapped||aonBounded||legCapped}; }; let best=build(-1); for(let i=0;i<c.length;i++)if(!c[i].pf){ const p=build(i);if(p.score>best.score)best=p; } const aon=[];for(let i=0;i<c.length&&aon.length<AON_PAIR_SEEDS;i++)if(!c[i].pf)aon.push(i); for(let i=0;i<aon.length;i++)for(let j=i+1;j<aon.length;j++){ const p=build(aon[i],aon[j]);if(p.score>best.score)best=p; } return best.fills.length?best:null; } const cheaperRate=(a,b)=>a.aB*b.aA<b.aB*a.aA; function planBookExactOut(rows,amountOut,ammIn,ammOut){ if(!amountOut||!ammOut)return null; let c=rows.filter(o=>!o.nA&&!o.nB&&o.aA&&(o.aB||o.dutch)&&(o.pf||o.aA<=amountOut) &&(!o.floor||(o.price>0n&&o.initial>0n)) &&!(account&&o.maker&&o.maker.toLowerCase()===account.toLowerCase())); const candidateCapped=c.length>256; const aonCount=c.reduce((n,o)=>n+!o.pf,0); const aonBounded=aonCount>=3; c.sort((a,b)=>cheaperRate(a,b)?-1:cheaperRate(b,a)?1:0); if(c.length>256)c.length=256; const build=(seed=-1,seed2=-1)=>{ let left=amountOut,bin=0n,bout=0n,fills=[],legCapped=false; const take=o=>{ if(o.aB*ammOut>=ammIn*o.aA)return; if(!o.pf&&o.aA>left)return; if(fills.length>=MAX_BOOK_LEGS){legCapped=true;return} const want=o.aA<left?o.aA:left; let pay,get; if(o.aB===0n){pay=0n;get=want} else if(want===o.aA){pay=o.aB;get=o.aA} else if(o.floor){ pay=floorPay(o,want); get=floorGet(o,pay); } else{ pay=(want*o.aB+o.aA-1n)/o.aA; if(pay>o.aB)pay=o.aB; get=pay*o.aA/o.aB; } if(!get)return; fills.push({id:o.id,board:o.board,pay,get,part:want!==o.aA}); bin+=pay;bout+=get;left-=get<left?get:left; }; if(seed>=0)take(c[seed]); if(seed2>=0)take(c[seed2]); for(let i=0;i<c.length&&left;i++)if(i!==seed&&i!==seed2)take(c[i]); return{fills,bookIn:bin,bookOut:bout,ammOut:left,score:bin+left*ammIn/ammOut, bounded:candidateCapped||aonBounded||legCapped}; }; let best=build(-1); for(let i=0;i<c.length;i++)if(!c[i].pf){ const p=build(i);if(p.score<best.score)best=p; } const aon=[];for(let i=0;i<c.length&&aon.length<AON_PAIR_SEEDS;i++)if(!c[i].pf)aon.push(i); for(let i=0;i<aon.length;i++)for(let j=i+1;j<aon.length;j++){ const p=build(aon[i],aon[j]);if(p.score<best.score)best=p; } return best.fills.length?best:null; } function encFillPlan(fills,tokenIn,tokenOut,recipient,refundTo,deadline){ let tail=encUint(fills.length); for(const f of fills)tail+=encUint(f.id)+encAddr(f.board)+encUint(f.pay)+encUint(f.get)+encBool(f.part); return"0x"+SEL_FILLPLAN+encAddr(tokenIn)+encAddr(tokenOut)+encAddr(recipient)+encAddr(refundTo) +encUint(deadline)+encUint(192)+tail; } const BOL_OK=new Set(["6e0a4f98","afeae12b","21c0dad2","ea598cb0","de0e9a3e","8b64ad55","cb924a09","ad33b1d0","dc2c256f","47e7ef24"]); const bolOk=cd=>{if(chainCfg().sweep4)return true; const walk=h=>{h=strip0x(h||"");const sel=h.slice(0,8);if(sel!==SEL_MULTICALL)return BOL_OK.has(sel); try{const t=h.slice(8),n=Number(BigInt("0x"+t.slice(64,128)));if(!n)return false; for(let i=0;i<n;i++){const off=Number(BigInt("0x"+t.slice(128+i*64,192+i*64)))*2+128; const len=Number(BigInt("0x"+t.slice(off,off+64)))*2;if(!walk(t.slice(off+64,off+64+len)))return false} return true}catch{return false}}; return walk(cd)}; function encFillPlanAndSwap(fills,tokenIn,tokenOut,recipient,refundTo,deadline,ammIn,ammData){ let ft=encUint(fills.length); for(const f of fills)ft+=encUint(f.id)+encAddr(f.board)+encUint(f.pay)+encUint(f.get)+encBool(f.part); const d=strip0x(ammData||"0x"),dt=encUint(d.length/2)+d.padEnd(Math.ceil(d.length/64)*64,"0"); return"0x"+SEL_FILLPLAN_SWAP+encAddr(tokenIn)+encAddr(tokenOut)+encAddr(recipient)+encAddr(refundTo) +encUint(deadline)+encUint(256)+encUint(ammIn)+encUint(256+ft.length/2)+ft+dt; } function encSnwap(tokenIn,amountIn,recipient,tokenOut,minOut,executor,data){ const d=strip0x(data),body=encUint(d.length/2)+d.padEnd(Math.ceil(d.length/64)*64,"0"); return"0x"+SEL_SNWAP+encAddr(tokenIn)+encUint(amountIn)+encAddr(recipient)+encAddr(tokenOut) +encUint(minOut)+encAddr(executor)+encUint(224)+body; } const encCheckpoint=(executor,token,recipient)=>encSnwap( ZERO,0n,recipient,ZERO,0n,executor,"0x"+SEL_CHECKPOINT+encAddr(token) ); const SEL_SWEEP3="dc2c256f"; const encSweep=(token,amount,to)=>chainCfg().sweep4 ?"0x"+SEL_SWEEP+encAddr(token)+encUint(0)+encUint(amount)+encAddr(to) :"0x"+SEL_SWEEP3+encAddr(token)+encUint(amount)+encAddr(to); function encPermit2Hybrid(ammData,fills,tokenIn,tokenOut,recipient,refundTo,deadline,bookIn,bookOut){ const bookData=encFillPlan(fills,tokenIn,tokenOut,recipient,refundTo,deadline); const steps=[encCheckpoint(SWAPBOL,tokenIn,refundTo)]; if(bookIn)steps.push(encSweep(tokenIn,bookIn,SWAPBOL)); steps.push(encSnwap(ZERO,0n,recipient,tokenOut,bookOut,SWAPBOL,bookData)); if(ammData&&ammData!=="0x")steps.push(ammData); steps.push(encSweep(tokenIn,0n,refundTo)); return encCalls(steps); } async function swapCandidates(tokenIn,tokenOut,block){ if(tokenOut===ZERO&&tokenIn.toLowerCase()===WETH.toLowerCase())return[]; const key=tokenIn.toLowerCase()+":"+tokenOut.toLowerCase(),old=candCache[key]; if(old&&Date.now()-old.at<15000&&old.block===block){ old.rows.incomplete=old.incomplete;old.rows.unavailable=old.unavailable; return old.rows; } const snapshotAt=Date.now(); const boardIn=tokenIn===ZERO?WETH:tokenIn; const boardOut=tokenOut===ZERO?WETH:tokenOut; const [lc,le,ld]=await Promise.all([ hasCode(SBVIEW),hasCode(SWAPBOL), DUTCH!==ZERO?hasCode(DUTCH):false ]); if(!lc||!le){ const rows=[];rows.unavailable=true;return rows; } const specs=[...BOARDS()]; if(ld){ specs.push({a:DUTCH,dutch:1,ti:tokenIn}); if(tokenIn===ZERO)specs.push({a:DUTCH,dutch:1,ti:WETH}); } const groups=await Promise.all(specs.map(async b=>{ let cursor=0n,out=[],turns=0,incomplete=false,failed=false; try{do{ const ti=b.dutch?b.ti:boardIn,to=boardOut; const data="0x"+(b.dutch?SEL_DUTCH_CANDS:SEL_CANDS)+encAddr(b.a) +(b.dutch?"":encBool(b.v2))+encAddr(ti)+encAddr(to)+encAddr(SWAPBOL) +encUint(cursor)+encUint(512); const p=decViewPage(await rpc(C,[{to:SBVIEW,data},block])); out.push(...p.rows.map(r=>({...r,v2:b.v2||0,dutch:b.dutch||0}))); cursor=p.next; ++turns; }while(cursor&&turns<64)}catch{failed=true;incomplete=true} incomplete=incomplete||!!cursor; return{out,incomplete,failed}; })); let rows=groups.flatMap(g=>g.out); if(tokenIn===ZERO&&tokenOut.toLowerCase()===WETH.toLowerCase()){ rows=rows.filter(r=>r.dutch&&r.tA.toLowerCase()===WETH.toLowerCase()&&r.tB===ZERO); } else if(await floorRoutable(block)){ rows=rows.concat(await floorCandidates(tokenIn,tokenOut,block).catch(()=>[])); } const incomplete=groups.some(g=>g.incomplete); const unavailable=groups.every(g=>g.failed&&!g.out.length); rows.incomplete=incomplete; rows.unavailable=unavailable; const current=candCache[key],bn=BigInt(block),cn=current?BigInt(current.block):-1n; if(!current||bn>cn||(bn===cn&&snapshotAt>=current.at)){ candCache[key]={at:snapshotAt,block,rows,incomplete,unavailable}; } for(const k in candCache)if(k!==key&&snapshotAt-candCache[k].at>60000)delete candCache[k]; return rows; } async function lensBookRows(my){ const [sv,dv]=await Promise.all([hasCode(SBVIEW),DUTCH!==ZERO?hasCode(DUTCH):false]); if(!sv)return null; const specs=[...BOARDS()]; if(dv)specs.push({a:DUTCH,dutch:1}); const pages=await Promise.all(specs.map(async b=>{ let cursor=0n,out=[],turns=0; do{ const data="0x"+(b.dutch?SEL_RECENT_DUTCH:SEL_RECENT)+encAddr(b.a) +(b.dutch?"":encBool(b.v2))+encUint(cursor)+encUint(80)+encUint(256); const p=decViewPage(await rpc(C,[{to:SBVIEW,data},L])); out.push(...p.rows.map(r=>({...r,v2:b.v2||0,dutch:b.dutch||0}))); cursor=p.next; }while(cursor&&out.length<160&&++turns<64&&my===bookSeq); if(cursor)out.capped=true; return out; })); const rows=pages.flat(); let capped=pages.some(p=>p.capped); try{ for(const r of decViewPage(await rpc(C,[{to:SBVIEW, data:"0x"+SEL_EXPIRED+encAddr(SB2)+encUint(0)+encUint(80)+encUint(256)},L])).rows) if(r.maker&&r.maker.toLowerCase()===me())rows.push({...r,v2:1,dutch:0}); }catch{} if(FLOOR!==ZERO&&FLOORVIEW!==ZERO){ await floorRoutable(L); try{ const raw=await rpc(C,[{to:FLOORVIEW, data:"0x"+SEL_RECENT_FLOOR+encAddr(FLOOR)+encUint(0)+encUint(80)+encUint(256)},L]); for(const b of decBidRows(raw)){ if(b.remaining<=0n)continue; rows.push({ id:b.id,board:FLOOR,floor:1,v2:0,dutch:0, maker:b.bidder,pf:b.isNFT?0:1,exp:b.expiry, nA:0,nB:0,cp:ZERO, tA:b.quote,aA:b.proceeds,sA:b.qSym,dA:b.qDec, tB:b.token,aB:b.remaining,sB:b.tSym,dB:b.isNFT?0:b.tDec, price:b.price,initial:b.initial,anyId:b.anyId,isNFT:b.isNFT?1:0, }); } }catch{} } if(dv&&account)try{ const total=Number(BigInt(await rpc(C,[{to:DUTCH,data:"0x"+SEL_NEXTID2},L])||"0x0")); const ids=[];for(let i=total-1;i>=Math.max(0,total-40);--i)ids.push(i); if(ids.length){ const res=await mc3Deep(ids.map(id=>({to:DUTCH,data:"0x"+SEL_DUTCH_LISTING+encUint(id)}))); const now=BigInt(Math.floor(Date.now()/1e3)),me2=me(); for(let i=0;i<ids.length;i++){ const s=strip0x(res[i]||"0x");if(s.length<704)continue; const w=k=>BigInt("0x"+s.slice(k*64,(k+1)*64)),ad=k=>"0x"+s.slice(k*64+24,(k+1)*64); if(ad(0).toLowerCase()!==me2)continue; const exp=w(10);if(!exp||now<=exp)continue; const rem=w(9);if(!rem)continue; rows.push({id:ids[i],board:DUTCH,dutch:1,v2:0,maker:ad(0),pf:1,exp, nA:0,nB:0,cp:ZERO,tA:ad(4),aA:rem,sA:null,dA:null,tB:ad(6),aB:0n,sB:null,dB:null}); } }}catch{} if(FLOOR!==ZERO&&account)try{ const total=Number(BigInt(await rpc(C,[{to:FLOOR,data:"0x"+SEL_NEXTID2},L])||"0x0")); const ids=[];for(let i=total-1;i>=Math.max(0,t
0x68575b07…67c7·#25,914,175·0x9f92ab9b…4fa2e2
tes,swapAmt,amm.best.amountIn,rem); if(!next){p=null;break} const stable=(isIn?next.ammIn===p.ammIn:next.ammOut===p.ammOut); p=next;plannerBounded=plannerBounded||!!p.bounded;if(stable)break; } if(p){ const rem=isIn?p.ammIn:p.ammOut; if(!rem)amm=null; else if(!(amm&&ammFor===rem))amm=await quoteRem(rem,!isIn).catch(()=>null); if(amm&&!bolOk(amm.callData))amm=null; if(!rem||amm){ const recipient=rcv||acct; const ammBudget=amm?(isIn?rem:amm.amountLimit):0n; const totalBudget=p.bookIn+ammBudget; const expectedIn=p.bookIn+(amm?amm.best.amountIn:0n); const expectedOut=p.bookOut+(amm?amm.best.amountOut:0n); const saving=isIn ?(expectedOut>r.best.amountOut?expectedOut-r.best.amountOut:0n) :(expectedIn<r.best.amountIn?r.best.amountIn-expectedIn:0n); let gasFloor=null; if(saving){ const gasPrice=await gasPriceJob; if(gasPrice){ const extraWei=gasPrice*(120000n+105000n*BigInt(p.fills.length)); gasFloor=await gasValue(extraWei); } } const pricedGas=gasFloor!==null&&gasFloor>0n; const wins=saving>0n&&pricedGas&&saving*10n>gasFloor*12n; if(my!==seq)return; if(wins){ const fp=encFillPlanAndSwap( p.fills,f.addr,t.addr,recipient,acct,deadline,ammBudget,amm?amm.callData:"0x" ); const minOut=isIn?p.bookOut+(amm?amm.amountLimit:0n):swapAmt; const snwapIn=totalBudget===0n?ZERO:f.addr; const planCall=encSnwap(snwapIn,totalBudget,recipient,t.addr,minOut,SWAPBOL,fp); const callData=f.addr===ZERO?planCall:encCalls([ encCheckpoint(SWAPBOL,f.addr,acct),planCall ]); let fundedCallData=null; if(f.addr!==ZERO){ fundedCallData=encPermit2Hybrid( amm?amm.callData:"0x",p.fills,f.addr,t.addr,recipient,acct,deadline,p.bookIn,p.bookOut ); } r={...r,best:{...r.best,amountIn:expectedIn,amountOut:expectedOut},callData, fills:p.fills,pools:undefined, fundedCallData,amountLimit:isIn?minOut:totalBudget,msgValue:f.addr===ZERO?totalBudget:0n, slipBps:amm&&amm.slipBps, to:null,spender:f.addr===ZERO?null:ZROUTER, pool:null,wrap:0n,outDiffers:false,poolOut:undefined}; hybrid=isIn ?{bookIn:p.bookIn,bookOut:p.bookOut,ammIn:p.ammIn} :{bookIn:p.bookIn,bookOut:p.bookOut,ammOut:p.ammOut}; plannerBounded=plannerBounded||!!p.bounded; } } } } const pin=lanePin(); if(!isIn&&r&&r.amountLimit>0n){ try{ await loadSolvers(); const lanes=(solverLanes||[]).filter(x=>!laneDown(x.name)&&SOLVER_SHAPES_OUT[x.name]); if(lanes.length&&solverFill&&solverExec){ const dl=new Promise(res=>setTimeout(()=>res(null),SOLVER_DEADLINE)); const ck=`o|${f.addr}|${t.addr}|${swapAmt}|${slipBps}`; const tried=await Promise.all(lanes.map(x=>Promise.race([ laneCached(`${x.name}|${ck}`, ()=>SOLVER_SHAPES_OUT[x.name](x.endpoint,f.addr,t.addr,swapAmt,Number(slipBps),SOLVER_DEADLINE,f.dec,t.dec)) .then(q=>q?{...q,lane:x}:null).catch(()=>null),dl]))); const laneCeil=c=>{const d=c.inp+(c.inp*slipBps)/10000n; return c.cap&&c.cap>0n?bmin(c.cap,d):d}; const chainCeil=r.amountLimit; let win=null; for(const c of tried){ if(!laneOk(c)||!c.inp)continue; const ceil=laneCeil(c); if(pin){ if(c.lane.name!==pin)continue; win={...c,ceil,forced:1};break; } if(ceil*(10000n+c.lane.handicapBps)>=chainCeil*10000n)continue; if(!win||ceil<win.ceil)win={...c,ceil}; } rank=[{n:"On-chain",f:chainCeil}]; for(const c of tried){ if(!laneOk(c)||!c.inp)continue; rank.push({n:c.lane.name,f:laneCeil(c)}); } rank.sort((a,b)=>a.f>b.f?1:a.f<b.f?-1:0); if(my!==seq)return; if(win){ const ethIn=f.addr===ZERO; r={best:{source:SRC_SOLVER,amountIn:win.inp,amountOut:swapAmt,feeBps:0n, lane:win.lane.name,forced:win.forced}, callData:"0x"+SEL_FILL+encAddr(win.target)+encAddr(win.spender)+encAddr(f.addr) +encUint(win.ceil)+encAddr(t.addr)+encUint(swapAmt)+encAddr(rcv||acct||ZERO) +encUint(256n)+encUint(BigInt(strip0x(win.data).length/2)) +strip0x(win.data)+"0".repeat((64-(strip0x(win.data).length%64))%64), fundedCallData:null,amountLimit:win.ceil, msgValue:ethIn?win.ceil:0n, to:solverFill,spender:ethIn?null:solverFill}; hybrid=null; } } }catch{} } const pin2=lanePin(); if(isIn&&r&&r.amountLimit>0n){ try{ await loadSolvers(); const lanes=(solverLanes||[]).filter(x=>!laneDown(x.name)); if(lanes.length&&solverFill&&solverExec){ const deadline_=new Promise(res=>setTimeout(()=>res(null),SOLVER_DEADLINE)); const ck=`${f.addr}|${t.addr}|${swapAmt}|${slipBps}`; const tried=await Promise.all(lanes.map(x=>Promise.race([ laneCached(`${x.name}|${ck}`, ()=>SOLVER_SHAPES[x.name](x.endpoint,f.addr,t.addr,swapAmt,Number(slipBps),SOLVER_DEADLINE,f.dec,t.dec)) .then(q=>q?{...q,lane:x}:null).catch(()=>null), deadline_]))); const chainFloor=r.amountLimit; let win=null; for(const c of tried){ if(!laneOk(c)||!c.out)continue; const floor=c.out-(c.out*slipBps)/10000n; if(pin2){ if(c.lane.name!==pin2)continue; win={...c,floor,forced:1}; break; } if(floor*10000n<=chainFloor*(10000n+c.lane.handicapBps))continue; if(!win||floor>win.floor)win={...c,floor}; } rank=[{n:"On-chain",f:chainFloor}]; for(const c of tried){ if(!laneOk(c)||!c.out)continue; rank.push({n:c.lane.name,f:c.out-(c.out*slipBps)/10000n}); } rank.sort((a,b)=>a.f<b.f?1:a.f>b.f?-1:0); if(my!==seq)return; if(win){ const ethIn=f.addr===ZERO; r={best:{source:SRC_SOLVER,amountIn:swapAmt,amountOut:win.out,feeBps:0n,lane:win.lane.name,forced:win.forced}, callData:"0x"+SEL_FILL+encAddr(win.target)+encAddr(win.spender)+encAddr(f.addr) +encUint(swapAmt)+encAddr(t.addr)+encUint(win.floor)+encAddr(rcv||acct||ZERO) +encUint(256n)+encUint(BigInt(strip0x(win.data).length/2)) +strip0x(win.data)+"0".repeat((64-(strip0x(win.data).length%64))%64), fundedCallData:null,amountLimit:win.floor, msgValue:ethIn?swapAmt:0n, to:solverFill,spender:ethIn?null:solverFill}; hybrid=null; } } }catch{} } if(my!==seq)return; const finalIn=r.best.amountIn; const finalOut=r.best.amountOut; if(isIn){outAmt.value=trimAmt(finalOut,t.dec);fitFont(outAmt)} else{amt.value=trimAmt(finalIn,f.dec);fitFont(amt)} const rx=(+formatUnits(finalOut,t.dec))/(+formatUnits(finalIn,f.dec)); const s=r.best.source,raw=s===SRC_DEEP?"Deepstate":SOURCES[s]||"?"; const src=r.best.source===SRC_SOLVER?`${r.best.lane} solver${r.best.forced?" \u00b7 pinned":""}`:hybrid?bookShare(hybrid,raw):(s===3||s===4||s===SRC_PRECISION)&&r.best.feeBps?`${raw}\u00a0${Number(r.best.feeBps)/100}%`:raw; rate.textContent=isFinite(rx)?`1 ${f.sym} ≈ ${rx>=1?rx.toLocaleString("en-US",{maximumFractionDigits:4}):rx.toPrecision(4)} ${t.sym} · ${src}`:""; rankBox.textContent=""; if(rank&&rank.length>1){ const top=rank[0].f; const worse=isIn?"-":"+"; const used=r.best.source===SRC_SOLVER?r.best.lane:"On-chain"; for(const x of rank){ const row=document.createElement("i"); if(x.n===used)row.className="w"; const pinNow=lanePin(); if(x.n===pinNow)row.className+=" pin"; row.title=x.n===pinNow?"Pinned \u2014 click again for the best floor" :x.f===top&&x.n!==used?"Best floor \u2014 click to use it":"Use this venue"; const pick=()=>{const on=lanePin()===x.n;laneOverride=on?null:x.n;laneOverrideFor=on?"":laneScope();update()}; row.onclick=pick; row.setAttribute("role","button"); row.tabIndex=0; row.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();pick()}}; const n=document.createElement("span"); n.textContent=safeSym(x.n).slice(0,16); const v=document.createElement("u"); const gap=top>0n?Number((x.f>top?x.f-top:top-x.f)*10000n/top)/100:0; v.textContent=`${sigAmt(x.f,isIn?t.dec:f.dec)}${gap>0.004?` \u00b7 ${worse}${gap.toFixed(2)}%`:""}`; row.append(n,v); rankBox.appendChild(row); } } rankBox.classList.toggle("hide",!(rankOpen&&rankBox.firstChild)); const paysSym=r.poolOut===ZERO?"ETH":"WETH"; rcvHdr.textContent=r.outDiffers?`You receive \u2014 as ${paysSym}`:"You receive"; if(r.outDiffers&&rate.textContent)rate.textContent+=` \u00b7 pays ${paysSym}`; rate.title=""; if(venueLost||mcGaveUp>gaveUp0||(mcFailed>failed0&&mcOk===ok0)) rate.textContent+=" \u00b7 some venues unreachable"; if(isIn)rate.textContent+=` · Min ${trimAmt(r.amountLimit,t.dec)} ${t.sym}`; else{ const exactMax=formatUnits(r.amountLimit,f.dec); rate.textContent+=` · Max ${maxAmt(r.amountLimit,f.dec)} ${f.sym}`; rate.title=`Exact maximum: ${exactMax} ${f.sym}`; } if(r.slipBps>slipBps)rate.textContent+=` · Split — bound widened to ${Number(r.slipBps)/100}%`; if(bookIncomplete)rate.textContent+=" · Book scan capped"; if(plannerBounded)rate.textContent+=" · Book split approximate"; if(bookUnavailable)rate.textContent+=" · AMM only (books offline)"; if(pq&&pq.capped)rate.textContent+=` · Precision scan capped (${LQ_SCAN})`; if(isIn&&f.launched&&(t.addr===ZERO||t.addr.toLowerCase()===WETH.toLowerCase())){ const floorOut=await quoteFloor(f.addr,swapAmt,quoteBlock); if(my!==seq)return; if(floorOut>0n){ if(floorOut>finalOut){ rate.textContent+=` \u00b7 Floor pays more: ${trimAmt(floorOut,18)} ETH`; rate.title=`This coin can be burned back to the launcher for its share of the ether ` +`locked in its pool - ${trimAmt(floorOut,18)} ETH, against ${trimAmt(finalOut,18)} from selling. ` +`Redeem through the launcher at ${PLAUNCH} to take the larger.`; }else{ rate.textContent+=` \u00b7 Floor ${trimAmt(floorOut,18)} ETH`; rate.title=`The ether backing this amount if it were redeemed instead of sold. ` +`Selling pays more right now.`; } } } let refQ=iRef>=0&&dec[iRef]?dec[iRef].best:null; let refPair=null; if(!refQ&&isIn&&pq&&pq.out>0n&&refAmt>0n){ try{ const small=await quotePrecision(f,t,refAmt,PROUTE,quoteBlock); if(my!==seq)return; if(small&&small.out>0n)refPair={amountOut:small.out}; }catch{} } if(my!==seq)return; const impSrc=refQ||refPair; const imp=impSrc?impactBps(isIn?finalOut:finalIn,isIn?impSrc.amountOut:impSrc.amountIn,!isIn):null; const mkt=impSrc?(isIn?impSrc.amountOut:impSrc.amountIn)*REF_N:0n; const lossTok=imp!==null&&mkt?(isIn?(mkt>finalOut?mkt-finalOut:0n):(finalIn>mkt?finalIn-mkt:0n)):0n; if(imp!==null&&imp>=IMPACT_HIDE){ const pctTxt=(Number(imp)/100).toFixed(2); rate.textContent+=` · Impact ${pctTxt}%`; if(imp>=IMPACT_WARN)stat.textContent=`High price impact: ${pctTxt}% — about ${trimAmt(lossTok,isIn?t.dec:f.dec)} ${isIn?t.sym:f.sym} worse than the market rate.`; } if(rankBox.firstChild){ const car=document.createElement("span"); car.className="rk"; car.textContent="\u25be"; car.title="Compare venues"; car.setAttribute("role","button"); car.tabIndex=0; car.setAttribute("aria-expanded",rankBox.classList.contains("hide")?"false":"true"); const carToggle=()=>{const shut=rankBox.classList.toggle("hide"); rankOpen=!shut; car.setAttribute("aria-expanded",shut?"false":"true")}; car.onclick=carToggle; car.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();carToggle()}}; rate.appendChild(document.createTextNode(" ")); rate.appendChild(car); } const inApprove=isIn?swapAmt:r.msgValue||r.amountLimit; const shownPay=amt.value,shownGet=outAmt.value; if(acct)last={callData:r.callData,fundedCallData:r.fundedCallData,msgValue:r.msgValue, limit:r.amountLimit,shownPay,shownGet, to:r.to,spender:r.spender, amountIn:inApprove,from:f,recipient:rcv||"",exp:Date.now()+QUOTE_TTL,impact:imp, lossTok,lossSym:isIn?t.sym:f.sym,lossDec:isIn?t.dec:f.dec, fills:r.fills||null,tokenIn:f.addr,tokenOut:t.addr, source:r.best.source, wrap:r.wrap||0n,pools:r.pools||null, mkt,got:isIn?finalOut:finalIn,isIn}; quoting=false; render(); }catch(e){if(my===seq){ quoting=false; const transport=!!e?.noWallet||mcGaveUp>gaveUp0||(mcFailed>failed0&&mcOk===ok0); if(!transport&&!e?.exactOut&&advancePastDeadPair()){loadChart();update();return} outEl.value="";fitFont(outEl);rankBox.textContent="";rankBox.classList.add("hide");render(); stat.textContent=e?.noWallet?NO_WALLET :e&&e.exactOut?e.message :transport ?"Your RPC could not complete the quote — some venues were never asked. Try again, or switch networks in your wallet and back." :"No route: "+(e.message||e)}} } let debounceT=0; const showNet=()=>{ const e=document.getElementById("net"); if(!e)return; const c=chainCfg(); e.innerHTML=(c.icon||"")+"<span>"+c.short+"</span>"; e.title="Network: "+c.name+" — click to switch"; }; const switchNet=async()=>{ const ids=Object.keys(CHAINS).map(Number); const next=ids[(ids.indexOf(CHAIN_ID)+1)%ids.length]; if(!prov()){setChain(next);showNet();location.reload();return} try{ await rpc("wallet_switchEthereumChain",[{chainId:"0x"+next.toString(16)}]); }catch(e){ if(e&&e.code===4902){ const c=CHAINS[next]; try{await rpc("wallet_addEthereumChain",[{chainId:"0x"+next.toString(16),chainName:c.name, nativeCurrency:{name:"Ether",symbol:"ETH",decimals:18},rpcUrls:c.rpcs,blockExplorerUrls:[c.explorer]}])}catch{return} }else return; } setChain(next);showNet();location.reload(); }; const DEEPSTATE="0x6cf19308c22fc82ea620fa0b3e94948d20f27b96"; const SEL_POOLID="1338aa1d",SEL_POOLEPOCH="8fa79763",SEL_SWAPDEEP="ad33b1d0"; const OZ20="52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00"; const DEEP_TAKER="0x00000000000000000000000000000000deeb0000"; const MAXU=(1n<<256n)-1n; const slotKey=(k,sl)=>strip0x(keccak(hexToBytes(k+sl))); const deepSlots={}; const findSlot=async(tok,sel,keyw,probe)=>{ const ck=tok+sel; if(deepSlots[ck]!==undefined)return deepSlots[ck]; const cands=[OZ20,(BigInt("0x"+OZ20)+1n).toString(16).padStart(64,"0")]; for(let i=0;i<10;i++)cands.push(encUint(i)); for(const c of cands){ const sl=keyw.length>64?slotKey(keyw.slice(64),slotKey(keyw.slice(0,64),c)):slotKey(keyw,c); try{ const got=await rpc(C,[{to:tok,data:"0x"+sel+keyw},L,{[tok]:{stateDiff:{["0x"+sl]:"0x"+encUint(probe)}}}]); if(BigInt(got||0)===probe){deepSlots[ck]=c;return c} }catch{return null} } deepSlots[ck]=null;return null; }; const deepQuote=async(fa,ta,amt,expOut)=>{ if(CHAIN_ID!==4663||!amt||fa===ZERO||ta===ZERO)return null; const [t0,t1]=fa.toLowerCase()<ta.toLowerCase()?[fa,ta]:[ta,fa]; const isBid=ta.toLowerCase()===t0.toLowerCase(); try{ const pid=await rpc(C,[{to:DEEPSTATE,data:"0x"+SEL_POOLID+encAddr(t0)+encAddr(t1)},L]); if(!pid||pid==="0x")return null; const ep=await rpc(C,[{to:DEEPSTATE,data:"0x"+SEL_POOLEPOCH+strip0x(pid)},L]); const epoch=BigInt(ep||0); const bs=await findSlot(fa,"70a08231",encAddr(DEEP_TAKER),0x1234567n); if(bs===null)return null; const as=await findSlot(fa,"dd62ed3e",encAddr(DEEP_TAKER)+encAddr(ZROUTER),0x7654321n); if(as===null)return null; const ov={[fa]:{stateDiff:{ ["0x"+slotKey(encAddr(DEEP_TAKER),bs)]:"0x"+encUint(amt), ["0x"+slotKey(encAddr(ZROUTER),slotKey(encAddr(DEEP_TAKER),as))]:"0x"+encUint(MAXU)}}}; const price=isBid?0x7fffffffn:0x80000000n; const seed=isBid?(expOut||0n):amt; if(seed<=0n)return null; let best=null; for(let q=seed*4n,n=0;n<5&&q>0n;q/=4n,n++){ const order="0x"+encUint((price<<224n)|((q&((1n<<160n)-1n))<<64n)); const data="0x"+SEL_SWAPDEEP+encAddr(DEEP_TAKER)+encAddr(t0)+encAddr(t1)+encUint(epoch) +strip0x(order)+encUint(isBid?1:0)+encUint(amt)+encUint(0n)+encUint(MAXU); try{ const r=await rpc(C,[{from:DEEP_TAKER,to:ZROUTER,data},L,ov]); const b=strip0x(r||""); if(b.length<128)continue; const ai=BigInt("0x"+b.slice(0,64)),ao=BigInt("0x"+b.slice(64,128)); if(ao>0n&&(!best||ao>best.out))best={inUsed:ai,out:ao,t0,t1,isBid,epoch,order}; }catch{} } return best; }catch{return null} }; const showAddr=async()=>{const a=account,short=a.slice(0,6)+"..."+a.slice(-4); addr.textContent=short;addr.title=a+" - click to disconnect";addr.classList.add("on"); addr.setAttribute("role","button");addr.tabIndex=0; const n=await nameRev(a);if(n&&a===account)addr.textContent=n}; async function disconnect(){ sessionStorage.setItem("dc","1"); try{delete LS[WK]}catch{} location.reload(); } addr.onclick=()=>{account?disconnect():connect()} net.onclick=()=>{switchNet()} net.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();switchNet()}} showNet();; addr.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();account?disconnect():connect()}}; const AUTO_Q_MAX=5; const updateSoon=()=>{clearTimeout(debounceT);debounceT=setTimeout(()=>{debounceT=0;update()},250)}; const setMode=m=>{++seq;quoting=false;mode=m;const o=m==="in"?outAmt:amt;o.value="";fitFont(o);last=null;sendReady=null;render()}; amt.addEventListener("input",()=>{ if(lqMode){fitFont(amt);lqPreviewSoon();return} if(tab==="book"){fitFont(amt);render();return}setMode("in");fitFont(amt);updateSoon()}); outAmt.addEventListener("input",()=>{ if(lqMode){fitFont(outAmt);lqPreviewSoon();return} if(tab==="book"){fitFont(outAmt);seedFloor();render();return}setMode("out");fitFont(outAmt);updateSoon()}); const slipWarn=()=>slip.classList.toggle("warn",+slip.value>=3); if(LS.slip&&+LS.slip>=.01&&+LS.slip<=10)slip.value=LS.slip; slipWarn(); slip.addEventListener("input",()=>{last=null;slipWarn();render();updateSoon()}); rc.addEventListener("input",()=>{ if(lnMode)return; if(lqMode){clearTimeout(lqToT);lqToT=setTimeout(lqShowTo,300);return} last=null;sendReady=null;render();updateSoon()}); let lqToT; const onEnter=e=>{ if(e.key!=="Enter")return; const go=lnMode?lnGo:swap; if(!go||go.classList.contains("hide"))return; e.preventDefault(); if(debounceT){clearTimeout(debounceT);debounceT=0;update();return} if(!go.disabled)go.click(); }; for(const el of[amt,outAmt,rc,slip,floorAmt,nftId,lnName,lnSym,lnSupply,lnMcap,lnAlloc,lnGoal,lnDays]) if(el)el.addEventListener("keydown",onEnter); slip.addEventListener("change",()=>{slip.value=Math.min(10,Math.max(.01,+slip.value||.5));slipWarn();LS.slip=slip.value}); for(const sel of [fromSel,toSel]){ sel.dataset.prev=sel.value; sel.addEventListener("change",async()=>{ if(sel.value!=="__custom"){ sel.dataset.prev=sel.value; sel.dataset.addr=TOKENS[sel.value]?.addr||""; pairChosen=true; syncDisabled(sel); syncNftUI(); if(sel===fromSel)refreshBalance();else refreshBalance1(); loadChart(); if(tab==="book"&&bookRows.length)paintBook(); update(); return; } last=null;render(); const prev=sel.dataset.prev; let a; try{a=(prompt("Paste token address:")||"").trim()} catch{sel.value=prev;syncDisabled(); stat.textContent="This browser blocked the address prompt — add the token with a #token=0x… link instead."; return} if(!a){sel.value=prev;update();return} stat.textContent="Loading token..."; try{ const idx=await addCustomToken(a); rebuild(); const otherSel=sel===fromSel?toSel:fromSel; if(String(idx)===otherSel.value){otherSel.value=prev;otherSel.dataset.prev=prev} sel.value=String(idx); sel.dataset.prev=sel.value; pairChosen=true; stat.textContent=""; syncDisabled(sel);syncNftUI(); refreshBalance(); update(); }catch(e){ const msg="Couldn't load token: "+(e.message||e); sel.value=prev; syncDisabled();syncNftUI();refreshBalance();update(); stat.textContent=msg; } }); } flip.onclick=()=>{pairChosen=true;const a=fromSel.value,b=toSel.value,v=outAmt.value.replace(/,/g,"");fromSel.value=b;toSel.value=a; amt.value=(v&&!isNaN(+v))?v:"";setMode("in");fitFont(amt);syncDisabled();syncNftUI();refreshBalance();loadChart();update()}; const sameAcct=(a,b)=>!!a&&!!b&&a.toLowerCase()===b.toLowerCase(); const wkBind=eth=>{ if(!eth||eth.__zb)return; eth.__zb=1; eth.on?.("chainChanged",cid=>{if(connecting)return;if(CHAINS[+cid]&&account){setChain(+cid);location.reload();return}location.reload()}); eth.on?.("accountsChanged",accs=>{if(connecting||sameAcct(accs?.[0],account))return;location.reload()}); }; const wireWallet=eth=>{ if(!eth||wireWallet.done)return; wireWallet.done=true; wkBind(eth); (async()=>{ try{ const cid=await rpc(I,[]); wireWallet.probed=1; const hop=!!CHAINS[+cid]&&+cid!==CHAIN_ID; if(hop){setChain(+cid);showNet();loadEthUsd();burnTotal()} const accs=sessionStorage.getItem("dc")?[]:await rpc("eth_accounts",[]); if(accs[0]&&CHAINS[+cid]){account=accs[0];showAddr()} if(account||hop){ const keep2=pairAddrs(); await loadTokenList(); adoptList(keep2); if(hop&&location.hash.length>1)applyLink(); refreshBalance();update();if(account)loadPos();syncChart(); } }catch{} })(); (async()=>{ const self=selfFromUrl()||ZSWAP_SELF; if(!self)return; try{ if(+(await rpc(I,[]))!==CHAIN_ID)return; const ret=strip0x(await rpc(C,[{to:self,data:"0x"+SEL_LATEST},L])||""); if(ret.length!==64)return; let tip="0x"+ret.slice(24); const me=self.toLowerCase(); if(BigInt(tip)===0n||tip===me)return; const rd=async(to,sel)=>{const h=strip0x(await rpc(C,[{to,data:"0x"+sel},L])||"");return h.length===64?h:""}; const now=Date.now()/1000; for(let i=0;i<32&&tip!==me;i++){ const pv=await rd(tip,SEL_PREV); if(!pv)break; const prev="0x"+pv.slice(24); if(BigInt(prev)===0n)break; const at=await rd(prev,SEL_SUCCAT); const born=at?Number(BigInt("0x"+at)):0; if(!born||now>=born+MATURITY)break; tip=prev; } if(tip===me)return; const h=(location.hostname||"").split("."); const url=/^0x[0-9a-fA-F]{40}$/.test(h[0]||"") ?`${location.protocol}//${[tip,...h.slice(1)].join(".")}/` :`https://${tip}.w4eth.io/`; footAddr.innerHTML+=` · <a href="${url}" target="_blank" rel="noopener" ` +`title="A newer version exists on chain. This page is unchanged and always will be; the link opens the successor.">newer →</a>`; }catch{} })(); }; const wkGo=()=>{wireWallet(prov()); if(!prov())window.addEventListener("ethereum#initialized",()=>wireWallet(prov()),{once:true})}; if(!PROV&&LS[WK]&&LS[WK]!=="wc")wkRestore().then(wkGo,wkGo);else wkGo(); setTimeout(burnTotal,0); setTimeout(loadEthUsd,0); const SEL_TRANSFER="a9059cbb"; let SB2=MB.sb; let SB1=MB.s1; const SBVIEW="0x000000E0b25449F32f7D9259aC449bA88E78dFCE"; let SWAPBOL=MB.sw; let DUTCH=MB.du; let ORDERBOL=MB.ob; let FLOOR=MB.fl; const FLOORVIEW="0x0000004E376e9dB5D9EC28E6711E1a64997C6ba7"; const SEL_NEXTID="2a58b330",SEL_NEXTID2="61b8ce8c",SEL_GETORDERS="03652027",SEL_CANCELORD="514fcac7",SEL_EXPIRED="d6c3de07", SEL_WITHDRAW="ef3d3521",SEL_REPLACE="84343d3a"; const SEL_FILL1="c37dfc5b",SEL_FILL2="9d136c7f",SEL_FILL2_UNWRAP="3987baf4",SEL_FILL2_ETH="6f608bab"; const SEL_RECENT="6a9849c1",SEL_RECENT_DUTCH="98035c9a",SEL_DUTCH_FILL="ae7a8260",SEL_DUTCH_CANCEL="40e58ee5"; const SEL_CANCEL_UNWRAP="21dd76f9",SEL_DUTCH_CANCEL_UNWRAP="8382de65"; const SEL_DUTCH_LISTING="de74e57b"; const SEL_CANDS="5f452988",SEL_DUTCH_CANDS="eb33e466",SEL_FILLPLAN="c277f67c"; const SEL_FILLPLAN_SWAP="9090c8e5",SEL_SNWAP="5f3bd1c8"; const SEL_COLL_BIDS="16bb24eb"; const SEL_FLOOR_CANDS="c587d927",SEL_RECENT_FLOOR="a5edd13d"; const SEL_ORDER_FLOOR="23e93357"; const SEL_BOL_FLOOR="b732d224"; let floorReady=null; async function floorRoutable(block){ if(FLOOR===ZERO||FLOORVIEW===ZERO)return false; if(floorReady!==null)return floorReady; try{ const h=await rpc(C,[{to:SWAPBOL,data:"0x"+SEL_BOL_FLOOR},block||L]); floorReady=("0x"+strip0x(h).slice(-40)).toLowerCase()===FLOOR.toLowerCase(); }catch{return false} return floorReady; } const SEL_BIDS="4423c5f1"; const SEL_INITIAL_B="878ea250",SEL_TOKENURI="c87b56dd",SEL_LOGOOF="6ce273ac"; const SEL_QUOTEFILL="f325beda"; async function preflightLeg(row,routeIn,routeOut,pay){ if(!row.floor)return preflightAsk(row,pay); if(!await floorRoutable(L))throw Error("this executor cannot route bids yet"); const want=routeIn===ZERO?WETH:routeIn,gets=routeOut===ZERO?WETH:routeOut; let h; try{h=await rpc(C,[{to:FLOOR,data:"0x"+SEL_BIDS+encUint(row.id)},L])} catch{throw Error("bid could not be read — refresh and retry")} const at=i=>strip0x(h).slice(i*64,(i+1)*64); if(strip0x(h).length<11*64)throw Error("bid could not be read — refre
0x68575b07…67c7·#25,914,174·0xeaacf730…e824b4
ta","eth_signTypedData_v3","eth_signTypedData_v4"]; const wcToWallet=m=>m.indexOf("wallet_")===0||WC_SIGN.indexOf(m)>=0; async function wcRpc(method,params){ try{return await nodeRead(method,params)} catch(e){if(e&&e.code!=null)throw e; throw Error("Could not reach "+wcHost()+" for reads. Pick another node from the WalletConnect panel.")}} const wcRelay=WCCLI(WCU,u=>new WebSocket(u)); const qrSvg=m=>{let d=""; for(let r=0;r<m.size;r++)for(let c=0;c<m.size;c++)if(m.modules[r][c])d+="M"+c+" "+r+"h1v1h-1z"; return '<svg viewBox="-4 -4 '+(m.size+8)+" "+(m.size+8)+'" role="img" aria-label="WalletConnect pairing code" style="width:100%;height:auto;background:#fff;border-radius:.3em"><path fill="#000" d="'+d+'"/></svg>'}; const wcShow=uri=>{ wkList.textContent=""; const box=document.createElement("div"); box.className="wcq"; box.innerHTML=qrSvg(QR8.build(uri)); const cp=document.createElement("button"); cp.type="button";cp.className="fclink";cp.textContent="copy link"; cp.onclick=()=>{navigator.clipboard?navigator.clipboard.writeText(uri).then(()=>{cp.textContent="copied"}).catch(()=>{cp.textContent="copy blocked"}):cp.textContent="copy blocked"}; const note=document.createElement("div"); note.className="wcn"; note.textContent="Scan with a mobile wallet, or "; note.appendChild(cp); const nd=document.createElement("div"); nd.className="wcn wcnode"; nd.appendChild(document.createTextNode("Reads via "+wcHost()+" ")); const ch=document.createElement("button"); ch.type="button";ch.className="fclink";ch.textContent="change"; ch.onclick=()=>{ const v=prompt("HTTPS Ethereum RPC used for reads — it is pinned to the front of the read pool (the DAO-curated list, then the page's seeds)." +" Signing always goes to your wallet. Leave blank for the default.",wcNode()); if(v===null)return; const t=v.trim(); try{if(!t)localStorage.removeItem(RPC_STORE); else if(/^https:\/\/\S+$/i.test(t))localStorage.setItem(RPC_STORE,t); else return}catch{} nd.firstChild.nodeValue="Reads via "+wcHost()+" "}; nd.appendChild(ch); wkList.appendChild(box);wkList.appendChild(note);wkList.appendChild(nd); wkHdr.textContent="WalletConnect"; wkWrap.classList.remove("hide"); const back=document.activeElement; cp.focus({preventScroll:true}); return new Promise((res,rej)=>{ const esc=e=>{if(e.key==="Escape")stop()}; const off=()=>{removeEventListener("keydown",esc);removeEventListener("keydown",wkTrap,true); wkWrap.onclick=null;if(back&&back.focus)try{back.focus()}catch{}}; const stop=()=>{off();rej(Object.assign(Error(""),{code:4001}))}; wkWrap.onclick=e=>{if(e.target===wkWrap)stop()}; addEventListener("keydown",esc); addEventListener("keydown",wkTrap,true); wcShow.settle=()=>{off();res()}})}; async function wcConnect(){ const meta={name:"zSwap",description:"zSwap",url:location.origin,icons:[]}; const{uri,done}=await wcRelay.pair(WC_PID,meta); const bail=wcShow(uri); stat.textContent="Scan the code with your wallet."; let sess; try{sess=await Promise.race([done,bail])} catch(e){wcRelay.close();throw e} finally{if(wcShow.settle)wcShow.settle(); wkHdr.textContent="Choose a wallet"; wkWrap.classList.add("hide")} const accts=sess.accounts.filter(a=>a.slice(0,9)==="eip155:1:").map(a=>a.split(":").pop()); if(!accts.length){wcRelay.close();throw Error("That wallet settled no Ethereum mainnet account — switch it to Ethereum and reconnect.")} wcRead=sess.methods.indexOf("eth_call")>=0; const read=async(method,params)=>{ try{return await wcRpc(method,params)} catch(e){ if(e&&e.code!==undefined||!wcRead)throw e; return wcRelay.request(sess,method,params||[])}}; const evs=[]; wcRelay.on(ev=>{if(ev&&ev.t==="closed")location.reload(); if(!ev||ev.t!=="session")return; const s=ev.m; if(!s)return; if(s.method==="wc_sessionUpdate")return evs.forEach(f=>f("reload",null)); if(s.method!=="wc_sessionEvent")return; const e=s.params&&s.params.event||{};let d=e.data; if(e.name==="chainChanged"&&typeof d==="string"&&d.includes(":"))d=d.split(":").pop(); if(e.name==="accountsChanged"&&Array.isArray(d))d=d.map(a=>typeof a==="string"&&a.includes(":")?a.split(":").pop():a); if(e.name==="chainChanged"||e.name==="accountsChanged")evs.forEach(f=>f(e.name,d))}); return{request:({method,params})=> method==="eth_requestAccounts"||method==="eth_accounts"?Promise.resolve(accts): method==="eth_chainId"?Promise.resolve("0x1"): wcToWallet(method)?wcRelay.request(sess,method,params||[]): read(method,params), on:(t,f)=>{if(t==="chainChanged"||t==="accountsChanged")evs.push((n,d)=>{if(n==="reload"||n===t)try{f(d)}catch{}})}}} let wcRead=true; const wkWrap=document.getElementById("wkWrap"),wkList=document.getElementById("wkList"); const wkChoices=()=>W6963.map(d=>({name:d.info.name,icon:d.info.icon,rdns:d.info.rdns,get:()=>d.provider})) .concat([{name:"WalletConnect",svg:WC_ICON,rdns:"wc",get:wcConnect}]); const WK="zswap:wk"; const wkRestore=async()=>{ const id=LS[WK]; if(PROV||!id||id==="wc")return; wkScan(); for(let i=0;i<8;i++){ const m=W6963.filter(x=>x.info.rdns===id),d=m.length===1?m[0]:null; if(d){ try{const a=await Promise.race([d.provider.request({method:"eth_accounts"}), new Promise(r=>setTimeout(()=>r(null),1500))]); if(a&&a[0])PROV=d.provider}catch{} return} await new Promise(r=>setTimeout(r,50)); }}; const wkTrap=e=>{ if(e.key!=="Tab")return; const f=[...wkPanel.querySelectorAll('button,[href],input,select,textarea,[tabindex]:not([tabindex="-1"])')] .filter(x=>!x.disabled&&x.offsetParent!==null); if(!f.length)return; const a=f[0],z=f[f.length-1],cur=document.activeElement; if(e.shiftKey?cur===a||!wkPanel.contains(cur):cur===z){e.preventDefault();(e.shiftKey?z:a).focus()} }; const wkPick=cs=>new Promise(res=>{ wkList.textContent=""; let done=false; const back=document.activeElement; const end=v=>{if(done)return;done=true; wkWrap.classList.add("hide");removeEventListener("keydown",esc); removeEventListener("keydown",wkTrap,true); if(back&&back.focus)try{back.focus()}catch{} res(v)}; const esc=e=>{if(e.key==="Escape")end(null)}; cs.forEach(c=>{ const b=document.createElement("button"); b.type="button";b.className="tkr"; if(c.icon){const im=document.createElement("img");im.src=c.icon;im.alt="";b.appendChild(im)} else if(c.svg){const sp=document.createElement("span");sp.className="wki";sp.innerHTML=c.svg;b.appendChild(sp)} b.appendChild(document.createTextNode(c.name)); b.onclick=()=>end(c); wkList.appendChild(b)}); wkWrap.onclick=e=>{if(e.target===wkWrap)end(null)}; addEventListener("keydown",esc); addEventListener("keydown",wkTrap,true); wkWrap.classList.remove("hide"); wkList.firstChild?.focus()}); async function wkResolve(){ if(PROV)return true; wkScan(); const inj=window.ethereum; if(W6963.length<2&&inj)return true; const cs=wkChoices(); const c=await wkPick(cs); if(!c)return false; stat.textContent="Connecting…"; try{PROV=await c.get()} catch(e){if(!isRejection(e))err(e);return false} try{if(c.rdns)LS[WK]=c.rdns}catch{} wkBind(PROV); return true} async function connect(){ if(connecting)return; connecting=true; try{ if(!await wkResolve())return; if(!prov()){stat.textContent=NO_WALLET;return} swap.textContent="Connecting…";swap.disabled=true; lnGo.textContent="Connecting…";lnGo.disabled=true; wnGo.textContent="Connecting…";wnGo.disabled=true; let accs; try{accs=await rpc("eth_requestAccounts",[])} catch(e){err(e);return} if(!accs[0])return; let chainId=await rpc(I,[]); if(!CHAINS[+chainId]){ try{await rpc("wallet_switchEthereumChain",[{chainId:"0x1"}])} catch(e){stat.textContent=isRejection(e)?"":"Switch to a supported network.";return} chainId=await rpc(I,[]); if(!CHAINS[+chainId]){stat.textContent="Switch to a supported network.";return} } setChain(+chainId);showNet(); sessionStorage.removeItem("dc"); account=accs[0];sOpen(); const keepPair=pairAddrs(); showAddr(); if(!listLive){ swap.textContent="Loading tokens\u2026"; await loadTokenList(); } adoptList(keepPair); refreshBalance(); update(); fcMine(); loadPos(); syncChart(); if(lnMode)lnKindQuote(); }finally{connecting=false;swap.disabled=false; lnGo.disabled=false;lnGo.textContent=account?"Launch":"Connect Wallet"; wnGo.disabled=false; if(wnMode){wnPaint();wnQuote();wnRoll()}else wnGo.textContent="Register"; if(pvMode)pvWake();render()} } const lqReserveGas=()=>lqList.querySelector(".lqnew")?LQ_CREATE_GAS+300000n:400000n; const lqFillMax=(tok,max)=>{ const box=lqList.querySelector(".lqadd:not(.hide)"); const pair=chPair(); if(!box||!pair)return false; const side=pair[0].addr.toLowerCase()===tok.addr.toLowerCase()?"0":"1"; const el=box.querySelector(`.lqin[data-side="${side}"]`); if(!el)return false; el.value=formatUnits(max,tok.dec); el.dispatchEvent(new Event("input",{bubbles:true})); return true; }; bal1.addEventListener("click",async e=>{ if(e.target.tagName!=="A")return; e.preventDefault(); const t=TOKENS[toSel.value]; if(!t)return; let reserve=0n; if(t.addr===ZERO){ const g=lqReserveGas(); try{reserve=BigInt(await rpc("eth_gasPrice",[]))*g}catch{} if(reserve<=0n)reserve=GAS_FLOOR*g; } const max=toBalance>reserve?toBalance-reserve:0n; if(lqFillMax(t,max))return; outAmt.value=formatUnits(max,t.dec); fitFont(outAmt); lqPreviewSoon(); }); bal.addEventListener("click",async e=>{ if(e.target.tagName!=="A")return; e.preventDefault(); const f=TOKENS[fromSel.value]; if(!f)return; let reserve=0n; if(f.addr===ZERO){ const g=lqMode?lqReserveGas():tab==="swap"?900000n:tab==="book"?350000n :+dly.value?(tipCk.checked?200000n+TIP_GAS:200000n):30000n; try{reserve=BigInt(await rpc("eth_gasPrice",[]))*g}catch{} if(reserve<=0n)reserve=GAS_FLOOR*g; } const max=fromBalance>reserve?fromBalance-reserve:0n; if(lqMode&&lqFillMax(f,max))return; amt.value=formatUnits(max,f.dec); fitFont(amt); if(lqMode){lqPreviewSoon();return} setMode("in"); update(); }); let seq=0; const REVERTS={ "1ab7da6b":"That took too long and the deadline passed, so nothing was placed. Try again — you were not charged for an order.", "98c2c368":"The order was refused as malformed. Check the amounts and the expiry.", "7939f424":"The token transfer was refused — usually a missing or spent approval. Try again and approve first.", "90b8ec18":"The token transfer was refused by the token itself.", "3e3f8f73":"The approval was refused by the token.", "6fc70753":"That score name is not a valid label.", "9c9cef39":"That score is too long to record.", "7b2002ce":"Somebody already claimed that name — play again for a new one.", "845eadf1":"The score minter is not holding arcade.wei.", "b12d13eb":"Ether could not be sent to the recipient — a contract there may not accept it.", "ab143c06":"That call re-entered and was stopped.", }; const explain=e=>{ if(e?.noWallet)return NO_WALLET; const str=v=>typeof v==="string"?v :v&&typeof v.data==="string"?v.data :v&&typeof v.message==="string"?v.message:""; const s=str(e?.data)||str(e)||str(e?.error)||""; const m=/0x([0-9a-fA-F]{8})\b/.exec(s); const hit=m&&REVERTS[m[1].toLowerCase()]; if(hit)return hit; if(!s)return "The wallet returned an error with no message"+(e&&e.code!==undefined?" (code "+e.code+")":"")+"."; return "Error: "+s.slice(0,110); }; const err=e=>stat.textContent=isRejection(e)?"":explain(e); const settle=async(tx,quiet)=>{ if(!tx){stat.textContent="Done";return} if(!/^0x[0-9a-f]{64}$/i.test(tx))throw Error("bad tx"); stat.innerHTML=txLink(tx,"Sent"); try{await waitTx(tx)}catch(e){sFail();throw e} stat.innerHTML=txLink(tx,"Done");if(!quiet)sGot()}; const txLink=(h,label)=>`${label} <a href="${chainCfg().explorer}/tx/${h}" target="_blank" rel="noreferrer">${h.slice(0,10)}…${h.slice(-6)}</a>`; let autoQ=0,rankOpen=false; async function update(auto){ if(!auto)autoQ=0; syncIcons(); fcSync(); cbSync(); const my=++seq; quoting=false; if(wnMode||lnMode||pvMode)return; if(lqMode)return loadLq(); if(tab==="book")return render(); if(tab==="send")return updateSend(); last=null; rate.textContent=""; if(!lqMode&&!lnMode)rcvHdr.textContent="You receive"; render(); const f=TOKENS[fromSel.value], t=TOKENS[toSel.value]; if(!f||!t)return; if(f.addr===t.addr){stat.textContent="Pick different tokens.";return} stat.textContent=""; const isIn=mode==="in"; const inEl=isIn?amt:outAmt, outEl=isIn?outAmt:amt, inTok=isIn?f:t; outEl.value=""; fitFont(outEl); if(!inEl.value.trim())return; let swapAmt; try{swapAmt=parseUnits(inEl.value.trim(),inTok.dec)} catch(e){const q=inEl.value.trim(); if(q&&!/\.$/.test(q))stat.textContent=/^unclear comma/.test(e.message)?e.message:/decimals/.test(e.message)?`${inTok.sym} has ${inTok.dec} decimals`:"Invalid amount"; return} if(swapAmt<=0n)return; const rv=rc.value.trim(); let rcv=""; if(rv){ const fail=m=>{rc.classList.add("bad");rcvEl.textContent=""; outEl.value="";fitFont(outEl);rankBox.textContent="";rankBox.classList.add("hide");stat.textContent=m}; const bad=rcErr(rv); if(bad){fail(bad);return} if(isAddr(rv))rcv=rv; else{ rcvEl.textContent="resolving..."; try{rcv=await rcvOf(rv)} catch(e){if(my!==seq)return;fail(e&&e.offchain?e.message:"Name not registered");return} if(my!==seq)return; if(!rcv){fail("Name not registered");return} } rcvEl.textContent=rcv; }else rcvEl.textContent=""; rc.classList.remove("bad"); if(!rv&&f.addr===WETH&&t.addr===ZERO){ outEl.value=inEl.value;fitFont(outEl); rate.textContent=`1 ${f.sym} = 1 ${t.sym}`; last={callData:"0x2e1a7d4d"+encUint(swapAmt),msgValue:0n,isIn:true,amountIn:swapAmt,from:f,to:WETH,recipient:"",exp:Date.now()+QUOTE_TTL}; render();return; } if(!rv&&f.addr===ZERO&&t.addr===WETH){ outEl.value=inEl.value;fitFont(outEl); rate.textContent=`1 ${f.sym} = 1 ${t.sym}`; last={callData:"0xd0e30db0",msgValue:swapAmt,isIn:true,amountIn:swapAmt,from:f,to:WETH,recipient:"",exp:Date.now()+QUOTE_TTL}; render();return; } const pct=Math.min(10,Math.max(.01,+slip.value||.5)); const slipBps=BigInt(Math.round(pct*100)); const deadline=BigInt(Math.floor(Date.now()/1e3)+1800); outEl.value="...";fitFont(outEl);quoting=true;render(); const acct=account; const gaveUp0=mcGaveUp,ok0=mcOk,failed0=mcFailed; try{ const a=encAddr(rcv||acct||ZERO),rf=encAddr(acct||ZERO),tl=encBool(!isIn)+encAddr(f.addr)+encAddr(t.addr)+encUint(swapAmt)+encUint(slipBps)+encUint(deadline); const _rq=[]; const Q=(x,d,u,v,S,sb,mv)=>{_rq.push({to:ZQUOTER,data:"0x"+x+d,u,v,S,sb,mv});return _rq.length-1}; let r=null; const candKey=f.addr.toLowerCase()+":"+t.addr.toLowerCase(); const cachedCandidates=candCache[candKey]; const quoteBlock=cachedCandidates&&Date.now()-cachedCandidates.at<15000 ?cachedCandidates.block:await blockNow(); const candJob=acct?swapCandidates(f.addr,t.addr,quoteBlock).catch(()=>{ const x=[];x.unavailable=true;return x }):Promise.resolve([]); const gasPriceJob=acct?rpc("eth_gasPrice",[]).then(BigInt).catch(()=>0n):Promise.resolve(0n); let venueLost=false; const v4Job=isIn?quoteV4Pools(f,t,swapAmt,quoteBlock).catch(()=>{venueLost=true;return null}):Promise.resolve(null); const preJob=isIn?quotePrecision(f,t,swapAmt,PROUTE,quoteBlock).catch(()=>{venueLost=true;return null}):Promise.resolve(null); let curveOnlyOut=false; const exactOutSafe=y=>{if(isIn||!y.sources||!y.sources.includes(SRC_CURVE))return true;curveOnlyOut=true;return false}; const pick=y=>{if(!y)return;if(!exactOutSafe(y))return;const v=isIn?y.best.amountOut:y.best.amountIn;if(!v)return; if(!r||(isIn?v>r.best.amountOut:v<r.best.amountIn))r=y}; const tl0=encBool(!isIn)+encAddr(f.addr)+encAddr(t.addr)+encUint(swapAmt)+encUint(0n)+encUint(deadline); const jobs=[ [Q("e7798987",a+tl,0,4,0,0,6)], [Q("e453166e",a+rf+tl0,4,9),Q("e453166e",a+rf+tl,4,9)], [Q("4c464f59",a+tl0,8,13),Q("4c464f59",a+tl,8,13)]]; let splitSlip=0n; if(isIn){ const w=slipBps*3n,SS=w<150n?150n:w>500n?500n:w; splitSlip=SS; const ps=encAddr(f.addr)+encAddr(t.addr)+encUint(swapAmt)+encUint(SS)+encUint(deadline); for(const x of["892af013","85f86a90"])jobs.push([Q(x,a+ps,4,8,1,SS)]); } const tailStep=swapAmt/100n||1n,tailAmt=swapAmt>tailStep?swapAmt-tailStep:0n; const tailJobs=[]; if(tailAmt){ const z=encBool(!isIn)+encAddr(f.addr)+encAddr(t.addr)+encUint(tailAmt)+encUint(0n)+encUint(deadline); tailJobs.push(Q("e453166e",a+rf+z,4,9),Q("4c464f59",a+z,8,13)); if(isIn)for(const x of["892af013","85f86a90"]){ const ps=encAddr(f.addr)+encAddr(t.addr)+encUint(tailAmt)+encUint(splitSlip)+encUint(deadline); tailJobs.push(Q(x,a+ps,4,8,1,splitSlip)); } } const refAmt=swapAmt/REF_N; const iRef=refAmt>0n ?Q("e453166e",a+rf+encBool(!isIn)+encAddr(f.addr)+encAddr(t.addr)+encUint(refAmt)+encUint(0n)+encUint(deadline),4,9) :-1; const mcRaw=(await Promise.all(mc3Split(_rq,QPAIR) .map(g=>mc3Deep(g.map(q=>({to:q.to,data:q.data})),quoteBlock)))).flat(); if(my!==seq)return; const dec=_rq.map((q,i)=>{try{return mcRaw[i]?decQ(mcRaw[i],q.sb||slipBps,!isIn,q.u,q.v,q.S,q.mv):null}catch{return null}}); for(const g of jobs)pick(g.length>1?merge(dec[g[0]],dec[g[1]]):dec[g[0]]); const zr=r; if(isIn&&!(dec[jobs[1][0]]||dec[jobs[1][1]]||dec[jobs[2][0]]||dec[jobs[2][1]])){ const hq=await hubRoute(f,t,swapAmt,slipBps,deadline,rcv||acct||ZERO,acct||ZERO,quoteBlock).catch(()=>null); if(my!==seq)return; if(hq)pick(hq); } let tail=null; for(const i of tailJobs){const y=dec[i];if(!y)continue; const v=isIn?y.best.amountOut:y.best.amountIn; if(v&&(!tail||(isIn?v>tail.best.amountOut:v<tail.best.amountIn)))tail=y; } if(my!==seq)return; const v4q=await v4Job; let v4c=null; if(v4q&&v4q.amountOut>0n){ const v4Min=v4q.amountOut-(v4q.amountOut*slipBps)/10000n; const p=v4q.pool; v4c={best:{source:4,amountIn:v4q.amountIn,amountOut:v4q.amountOut,feeBps:p.fee>=8388608n?0n:BigInt(p.fee)/100n}, callData:"0x"+SEL_V4SWAP+encPoolKey(p)+encBool(p.zeroForOne)+encUint(v4q.amountIn) +encUint(v4Min)+encAddr(rcv||acct||ZERO)+encUint(deadline), fundedCallData:null,amountLimit:v4Min,msgValue:f.addr===ZERO?swapAmt:0n, to:V4PORT,spender:f.addr===ZERO?null:V4PORT}; if(!r)r=v4c; } const pq=await preJob; if(my!==seq)return; let pc=null; if(pq&&pq.out>0n){ const pMin=pq.out-(pq.out*slipBps)/10000n; const pNative=pq.tokenIn===ZERO; const pWethIn=pNative&&(pq.unwrap||0n)>0n; const pTo=rcv||acct||ZERO; const pFund=pWethIn?WETH:pq.tokenIn; const routeData=pWethIn ?encPRouteWeth(pq.pools,pq.tokenOut,swapAmt,pMin,pTo) :encPRoute(pq.pools,pq.tokenIn,pq.tokenOut,swapAmt,pMin,pTo); const routedLeg=encSnwap(pFund,swapAmt,pTo,pq.tokenOut,pMin,PROUTE,routeData); const checkpointLeg=encSnwap(ZERO,0n,acct||ZERO,ZERO,0n,PROUTE, "0x"+SEL_PCHECKPOINT+encAddr(pFund)+strip0x(keccak(hexToBytes(routeData)))+encAddr(acct||ZERO)); const pCall=(pNative&&!pWethIn)?routedLeg:encCalls([checkpointLeg,routedLeg]); const pFunded=(pNative&&!pWethIn)?null:encCalls([ checkpointLeg, encSweep(pFund,swapAmt,PROUTE), encSnwap(ZERO,0n,pTo,pq.tokenOut,pMin,PROUTE,routeData), ]); pc={best:{source:SRC_PRECISION,amountIn:swapAmt,amountOut:pq.out,feeBps:pq.fee/100n}, callData:pCall, fundedCallData:pFunded,amountLimit:pMin,msgValue:(pNative&&!pWethIn)?swapAmt:0n, wrap:pq.wrap||0n,outDiffers:!!pq.outDiffers,poolOut:pq.tokenOut, to:null,spender:(pNative&&!pWethIn)?null:ZROUTER,pools:pq.pools}; if(!r)r=pc; } if(!r&&curveOnlyOut)throw Object.assign(Error(`exact-output is unavailable for this pair — enter an amount to PAY instead`),{exactOut:1}); if(!r)throw Error("bad quote"); if(r.best.amountIn<=0n||r.best.amountOut<=0n||(!isIn&&r.amountLimit<r.best.amountIn))throw Error("bad quote"); let dc=null; if(isIn&&CHAIN_ID===4663&&f.addr!==ZERO&&t.addr!==ZERO){ const dq=await deepQuote(f.addr,t.addr,swapAmt,r&&r.best?r.best.amountOut:0n); if(my!==seq)return; if(dq&&dq.out>0n){ const dMin=dq.out-(dq.out*slipBps)/10000n; const dTo=rcv||acct||ZERO; const leg="0x"+SEL_SWAPDEEP+encAddr(dTo)+encAddr(dq.t0)+encAddr(dq.t1)+encUint(dq.epoch) +strip0x(dq.order)+encUint(dq.isBid?1:0)+encUint(swapAmt)+encUint(dMin)+encUint(deadline); dc={best:{source:SRC_DEEP,amountIn:dq.inUsed,amountOut:dq.out,feeBps:0n}, callData:encCalls([leg,encSweep(f.addr,0n,dTo)]), fundedCallData:null,amountLimit:dMin,msgValue:0n, wrap:0n,outDiffers:false,poolOut:t.addr,to:null,spender:ZROUTER}; } } for(const c of [v4c,pc,dc]){ if(!c||c===r)continue; if(isIn?c.best.amountOut>r.best.amountOut:c.best.amountIn<r.best.amountIn)r=c; } let hybrid=null,bookIncomplete=false,bookUnavailable=false,plannerBounded=false,rank=null; if(acct){ const candidates=await candJob; bookIncomplete=!!candidates.incomplete; bookUnavailable=!!candidates.unavailable; if(my!==seq)return; const quoteRem=async(x,eo)=>{ const qs=[],groups=[],add=(sel,data,u,v,S,sb)=>{ qs.push({data:"0x"+sel+data,u,v,S,sb});return qs.length-1}; const z0=encBool(eo)+encAddr(f.addr)+encAddr(t.addr)+encUint(x)+encUint(0n)+encUint(deadline); const zs=encBool(eo)+encAddr(f.addr)+encAddr(t.addr)+encUint(x)+encUint(slipBps)+encUint(deadline); groups.push([add("e453166e",a+rf+z0,4,9),add("e453166e",a+rf+zs,4,9)]); groups.push([add("4c464f59",a+z0,8,13),add("4c464f59",a+zs,8,13)]); if(!eo){ const w=slipBps*3n,ss=w<150n?150n:w>500n?500n:w; const ps=encAddr(f.addr)+encAddr(t.addr)+encUint(x)+encUint(ss)+encUint(deadline); for(const sel of["892af013","85f86a90"])groups.push([add(sel,a+ps,4,8,1,ss)]); } const raw=await mc3Deep(qs.map(q=>({to:ZQUOTER,data:q.data})),quoteBlock); const ds=qs.map((q,i)=>{try{return raw[i]?decQ(raw[i],q.sb||slipBps,eo,q.u,q.v,q.S):null}catch{return null}}); let best=null; for(const g of groups){ const y=g.length>1?merge(ds[g[0]],ds[g[1]]):ds[g[0]]; if(!y)continue; if(eo&&y.sources&&y.sources.includes(SRC_CURVE))continue; const v=eo?y.best.amountIn:y.best.amountOut; if(v&&(!best||(eo?v<best.best.amountIn:v>best.best.amountOut)))best=y; } return best; }; const gasValue=async wei=>{ const token=isIn?t.addr:f.addr; if(token===ZERO||token.toLowerCase()===WETH.toLowerCase())return wei; const eo=!isIn,ti=isIn?ZERO:token,to=isIn?token:ZERO; const z=encBool(eo)+encAddr(ti)+encAddr(to)+encUint(wei)+encUint(0n)+encUint(deadline); try{ const raw=await rpc(C,[{to:ZQUOTER,data:"0xe453166e"+a+rf+z},quoteBlock]); const q=decQ(raw,0n,eo,4,9); const v=isIn?q.best.amountOut:q.best.amountIn; if(v>0n)return v; }catch{} const ethSide=f.addr===ZERO||f.addr.toLowerCase()===WETH.toLowerCase(); const tokSide=t.addr===ZERO||t.addr.toLowerCase()===WETH.toLowerCase(); const inA=r.best.amountIn,outA=r.best.amountOut; if(inA>0n&&outA>0n){ if(isIn&&ethSide)return wei*outA/inA; if(!isIn&&tokSide)return wei*inA/outA; } return null; }; const tailDelta=swapAmt-tailAmt; const tailFits=!!tail&&r===zr; const seedOut=isIn&&tailFits&&r.best.amountOut>tail.best.amountOut ?r.best.amountOut-tail.best.amountOut:r.best.amountOut; const seedIn=!isIn&&tailFits&&r.best.amountIn>tail.best.amountIn ?r.best.amountIn-tail.best.amountIn:r.best.amountIn; let p=isIn ?planBookExactIn(candidates,swapAmt,seedOut,tailFits&&seedOut!==r.best.amountOut?tailDelta:r.best.amountIn) :planBookExactOut(candidates,swapAmt,seedIn,tailFits&&seedIn!==r.best.amountIn?tailDelta:r.best.amountOut); plannerBounded=!!(p&&p.bounded); let amm=null,ammFor=null; for(let round=0;p&&(isIn?p.ammIn:p.ammOut)&&round<2;round++){ const rem=isIn?p.ammIn:p.ammOut; amm=await quoteRem(rem,!isIn).catch(()=>null);ammFor=rem; if(!amm)break; const next=isIn ?planBookExactIn(candidates,swapAmt,amm.best.amountOut,rem) :planBookExactOut(candida
0x68575b07…67c7·#25,914,163·0x0de9483f…3b9cf7
r, or a .wei name":"Token id must be a number"; swap.disabled=true;return} const climbing=kind.value==="floor"; if(kind.value==="dutch"){ swap.textContent="A bid is fixed or climbing, not decaying";swap.disabled=true;return} if(oneId&&climbing){ swap.textContent="A climbing bid takes any id — clear the token id"; swap.disabled=true;return} if(!oneId){ const q=outAmt.value.trim(); if(q&&!/^\d+$/.test(q)){ swap.textContent="How many? a whole number";swap.disabled=true;return} if(q&&!BigInt(q)){swap.textContent="Bid for at least one";swap.disabled=true;return} if(q&&BigInt(q)>(1n<<128n)-1n){ swap.textContent="Too many to bid for at once";swap.disabled=true;return} } let bidAmt=0n; try{bidAmt=amt.value.trim()?parseUnits(amt.value.trim(),f.dec):0n}catch{} if(!bidAmt){ swap.textContent=climbing?"Set the most you will pay":"Set what you will pay"; swap.disabled=true;return} if(climbing){ let openAmt=0n; try{openAmt=floorAmt.value.trim()?parseUnits(floorAmt.value.trim(),f.dec):0n}catch{} if(!openAmt){swap.textContent="Set an opening bid";swap.disabled=true;return} if(openAmt>bidAmt){swap.textContent="Opening bid exceeds the maximum";swap.disabled=true;return} if(!+dly.value){swap.textContent="A climbing bid needs a window";swap.disabled=true;return} }else if(!oneId&&!+dly.value){ swap.textContent="A collection bid needs a window";swap.disabled=true;return} if(bidAmt>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} swap.textContent=oneId?`Bid for ${nftLabel(t.sym,t.addr,nftId.value)}` :climbing?`Climbing bid for any ${t.sym}`:`Bid for any ${t.sym}`; swap.disabled=false;return} if(f.std==="nft"){ if(kind.value!=="fixed"){ swap.textContent="An NFT sells at a fixed price";swap.disabled=true;return} if(!/^\d+$/.test(nftIdIn(f.addr,nftId.value))){ swap.textContent=isWns(f.addr)?"Name the .wei name, or the token id, to sell":"Name the token id to sell"; swap.disabled=true;return} let ask=0n; try{ask=outAmt.value.trim()?parseUnits(outAmt.value.trim(),t.dec):0n}catch{} if(!ask){swap.textContent="Set a price";swap.disabled=true;return} swap.textContent=`List ${nftLabel(f.sym,f.addr,nftId.value)}`;swap.disabled=false;return} const dutch=kind.value==="dutch",bid=kind.value==="floor"; let sell=0n,want=0n,floor=0n,badOrderAmount=false; try{if(amt.value.trim())sell=parseUnits(amt.value.trim(),f.dec); if(outAmt.value.trim())want=parseUnits(outAmt.value.trim(),t.dec); if((dutch||bid)&&floorAmt.value.trim())floor=parseUnits(floorAmt.value.trim(),bid?f.dec:t.dec)} catch{badOrderAmount=true} if(badOrderAmount){swap.textContent="Invalid order amount";swap.disabled=true;return} if(!sell||!want){swap.textContent=bid?"Place bid":"Place order";swap.disabled=true;return} if(bid){ if(t.addr===ZERO){swap.textContent="Pick a token to bid for";swap.disabled=true;return} if((f.addr===ZERO?WETH:f.addr).toLowerCase()===t.addr.toLowerCase()){ swap.textContent="Pick different token";swap.disabled=true;return} if(!floor){swap.textContent="Set an opening bid";swap.disabled=true;return} if(floor>sell){swap.textContent="Opening exceeds maximum";swap.disabled=true;return} if(+dly.value===0){swap.textContent="Choose a bid window";swap.disabled=true;return} if(sell>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} swap.textContent=`Bid ${trimAmt(floor,f.dec)} → ${trimAmt(sell,f.dec)} ${f.sym} for ${trimAmt(want,t.dec)} ${t.sym}`; swap.disabled=false;return; } if(f.addr===ZERO&&t.addr===WETH){swap.textContent="Pick different token";swap.disabled=true;return} if(!dutch&&f.addr===WETH&&t.addr===ZERO){swap.textContent="Pick different token";swap.disabled=true;return} if(dutch&&floor>want){swap.textContent="Floor exceeds start";swap.disabled=true;return} if(dutch&&+dly.value===0){swap.textContent="Choose decay duration";swap.disabled=true;return} if(sell>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} swap.textContent=dutch ?`Dutch ${trimAmt(sell,f.dec)} ${f.sym} · ${trimAmt(want,t.dec)} → ${trimAmt(floor,t.dec)} ${t.sym}` :`Place ${fill.value==="1"?"":"all-or-nothing "}order \u2014 ${trimAmt(sell,f.dec)} ${f.sym} \u2192 ${trimAmt(want,t.dec)} ${t.addr===ZERO?"WETH":t.sym}`; { const qs=t.addr===ZERO?"ETH":t.sym; rate.textContent=dutch ?`Falls from ${trimAmt(want,t.dec)} to ${trimAmt(floor,t.dec)} ${qs} over ${rel(+dly.value)}, then rests at ${trimAmt(floor,t.dec)} ${qs} ` +(+dRest.value?`for ${rel(+dRest.value)} and returns to you`:`until you cancel`) :`${fill.value==="1"?"Fills in any amount":"Fills all at once"} at ${trimAmt(want,t.dec)} ${t.addr===ZERO?"WETH":t.sym} for ${trimAmt(sell,f.dec)} ${f.sym}` +(+dly.value?`, expiring in ${rel(+dly.value)}`:", and never expires"); } swap.disabled=false;return; } if(tab==="send"){ if(!sendReady){ let n=0n;try{n=parseUnits(amt.value.trim(),TOKENS[fromSel.value].dec)}catch{} swap.textContent=n>0n?"Name a recipient":"Enter an amount"; swap.disabled=true;return} if(sendReady.amount>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} const a=`${trimAmt(sendReady.amount,sendReady.token.dec)} ${sendReady.token.sym}`,dv=+dly.value; if(dv&&sendTipWei>0n&&sendReady.token.addr===ZERO &&sendReady.amount+sendTipWei>fromBalance){ swap.textContent="Insufficient ETH for amount + keeper tip";swap.disabled=true;return} swap.textContent=(dv?`Lock ${a} for ${rel(dv)}`:`Send ${a}`)+` \u2192 ${sendReady.label}`; swap.disabled=false;return; } if(quoting){swap.textContent="Quoting…";swap.disabled=true;return} if(!last){swap.textContent="Swap";swap.disabled=true;return} if(last.amountIn>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} swap.textContent="Swap";swap.disabled=false; }; syncIcons(); render(); const isRejection=e=>e?.code===4001||/user\s+reject|user\s+denied/i.test(e?.message||""); const W6963=[]; addEventListener("eip6963:announceProvider",e=>{ const d=e.detail; if(!d||!d.provider||!d.info||W6963.some(x=>x.info.uuid===d.info.uuid))return; W6963.push(d)}); const wkScan=()=>{try{dispatchEvent(new Event("eip6963:requestProvider"))}catch{}}; wkScan(); const WC_PID="1e8390ef1c1d8a185e035912a1409749"; const WC_ICON='<svg viewBox="0 0 400 400" fill="none" aria-hidden="true"><circle cx="200" cy="200" r="200" fill="#3396ff"/><path fill="#fff" d="m122.519 148.965c42.791-41.729 112.171-41.729 154.962 0l5.15 5.022c2.14 2.086 2.14 5.469 0 7.555l-17.617 17.18c-1.07 1.043-2.804 1.043-3.874 0l-7.087-6.911c-29.853-29.111-78.253-29.111-108.106 0l-7.59 7.401c-1.07 1.043-2.804 1.043-3.874 0l-17.617-17.18c-2.14-2.086-2.14-5.469 0-7.555zm191.397 35.529 15.679 15.29c2.14 2.086 2.14 5.469 0 7.555l-70.7 68.944c-2.139 2.087-5.608 2.087-7.748 0l-50.178-48.931c-.535-.522-1.402-.522-1.937 0l-50.178 48.931c-2.139 2.087-5.608 2.087-7.748 0l-70.7015-68.945c-2.1396-2.086-2.1396-5.469 0-7.555l15.6795-15.29c2.1396-2.087 5.6085-2.087 7.7481 0l50.1789 48.932c.535.522 1.402.522 1.937 0l50.177-48.932c2.139-2.087 5.608-2.087 7.748 0l50.179 48.932c.535.522 1.402.522 1.937 0l50.179-48.931c2.139-2.087 5.608-2.087 7.748 0z"/></svg>'; const WCU=(()=>{ const rl=(v,c)=>(v<<c|v>>>(32-c))>>>0; const wds=b=>{const o=[];for(let i=0;i<b.length;i+=4)o.push((b[i]|b[i+1]<<8|b[i+2]<<16|b[i+3]<<24)>>>0);return o}; function cblk(x,k,n,c){ const s=[0x61707865,0x3320646e,0x79622d32,0x6b206574, k[0],k[1],k[2],k[3],k[4],k[5],k[6],k[7],c,n[0],n[1],n[2]]; for(let i=0;i<16;i++)x[i]=s[i]; for(let r=0;r<10;r++){ const q=(a,b,cc,d)=>{ x[a]=(x[a]+x[b])>>>0;x[d]=rl(x[d]^x[a],16); x[cc]=(x[cc]+x[d])>>>0;x[b]=rl(x[b]^x[cc],12); x[a]=(x[a]+x[b])>>>0;x[d]=rl(x[d]^x[a],8); x[cc]=(x[cc]+x[d])>>>0;x[b]=rl(x[b]^x[cc],7)}; q(0,4,8,12);q(1,5,9,13);q(2,6,10,14);q(3,7,11,15); q(0,5,10,15);q(1,6,11,12);q(2,7,8,13);q(3,4,9,14);} for(let i=0;i<16;i++)x[i]=(x[i]+s[i])>>>0;} function chacha(key,nonce,ctr,src){ const k=wds(key),n=wds(nonce),x=new Uint32Array(16),out=new Uint8Array(src.length); for(let p=0;p<src.length;p+=64){cblk(x,k,n,ctr++); for(let i=0;i<64&&p+i<src.length;i++)out[p+i]=src[p+i]^(x[i>>2]>>>((i&3)*8)&255);} return out} function poly(msg,key){ const P=(1n<<130n)-5n; let r=0n,sk=0n; for(let i=15;i>=0;i--)r=(r<<8n)|BigInt(key[i]); r&=0x0ffffffc0ffffffc0ffffffc0fffffffn; for(let i=31;i>=16;i--)sk=(sk<<8n)|BigInt(key[i]); let a=0n; for(let i=0;i<msg.length;i+=16){ const n=Math.min(16,msg.length-i); let b=0n; for(let j=n-1;j>=0;j--)b=(b<<8n)|BigInt(msg[i+j]); a=((a+b+(1n<<BigInt(8*n)))*r)%P;} a=(a+sk)&((1n<<128n)-1n); const o=new Uint8Array(16); for(let i=0;i<16;i++){o[i]=Number(a&255n);a>>=8n} return o} const p16=n=>n%16?16-n%16:0; const le64=n=>{const b=new Uint8Array(8);for(let i=0;i<8;i++){b[i]=n&255;n=Math.floor(n/256)}return b}; function tagOf(ct,key,nonce){ const otk=chacha(key,nonce,0,new Uint8Array(32)); const pad=new Uint8Array(p16(ct.length)); const m=new Uint8Array(ct.length+pad.length+16); m.set(ct);m.set(pad,ct.length); m.set(le64(0),ct.length+pad.length);m.set(le64(ct.length),ct.length+pad.length+8); return poly(m,otk)} const seal=(key,nonce,pt)=>{ const ct=chacha(key,nonce,1,pt),tg=tagOf(ct,key,nonce); const o=new Uint8Array(ct.length+16);o.set(ct);o.set(tg,ct.length);return o}; const open=(key,nonce,box)=>{ if(box.length<16)return null; const ct=box.subarray(0,box.length-16),tg=box.subarray(box.length-16); const w=tagOf(ct,key,nonce); let bad=0;for(let i=0;i<16;i++)bad|=w[i]^tg[i]; return bad?null:chacha(key,nonce,1,ct)}; const hex=b=>{let s="";for(const x of b)s+=x.toString(16).padStart(2,"0");return s}; const unhex=h=>{const b=new Uint8Array(h.length/2); for(let i=0;i<b.length;i++)b[i]=parseInt(h.substr(i*2,2),16);return b}; const bin=b=>{let s="";for(let i=0;i<b.length;i+=4096)s+=String.fromCharCode.apply(null,b.subarray(i,i+4096));return s}; const b64=b=>btoa(bin(b)); const unb64=s=>{const t=atob(s),b=new Uint8Array(t.length); for(let i=0;i<t.length;i++)b[i]=t.charCodeAt(i);return b}; const b64u=b=>b64(b).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,""); const utf8=s=>new TextEncoder().encode(s); const B58="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; const b58=b=>{let n=0n;for(const x of b)n=n*256n+BigInt(x); let s="";while(n>0n){s=B58[Number(n%58n)]+s;n/=58n} for(const x of b){if(x)break;s="1"+s}return s}; const rnd=n=>crypto.getRandomValues(new Uint8Array(n)); const sha256=async b=>new Uint8Array(await crypto.subtle.digest("SHA-256",b)); const topicOf=async k=>hex(await sha256(k)); const encode=(symKey,payload,senderPub)=>{ const iv=rnd(12),pt=utf8(JSON.stringify(payload)); const sealed=seal(symKey,iv,pt); const t=senderPub?1:0; const head=senderPub?1+32:1; const out=new Uint8Array(head+12+sealed.length); out[0]=t; if(senderPub)out.set(senderPub,1); out.set(iv,head);out.set(sealed,head+12); return b64(out)}; const decode=(symKey,msg)=>{ let b;try{b=unb64(msg)}catch{return null} if(!b.length)return null; const t=b[0]; if(t!==0&&t!==1)return null; const head=t===1?33:1; if(b.length<head+12+16)return null; const iv=b.subarray(head,head+12),box=b.subarray(head+12); const pt=open(symKey,iv,box); if(!pt)return null; try{return JSON.parse(new TextDecoder().decode(pt))}catch{return null}}; const didKey=pub=>{const b=new Uint8Array(34);b[0]=0xed;b[1]=0x01;b.set(pub,2); return"did:key:z"+b58(b)}; async function relayJwt(aud){ let kp; try{kp=await crypto.subtle.generateKey({name:"Ed25519"},true,["sign","verify"])} catch{throw Error("This browser cannot do Ed25519. WalletConnect needs a newer " +"browser; an extension wallet works here today.")} const pub=new Uint8Array(await crypto.subtle.exportKey("raw",kp.publicKey)); const iss=didKey(pub); const iat=Math.floor(Date.now()/1e3); const h=b64u(utf8(JSON.stringify({alg:"EdDSA",typ:"JWT"}))); const p=b64u(utf8(JSON.stringify({iss,sub:hex(rnd(32)),aud,iat,exp:iat+86400}))); const sig=new Uint8Array(await crypto.subtle.sign({name:"Ed25519"},kp.privateKey,utf8(h+"."+p))); return h+"."+p+"."+b64u(sig)} async function kex(){ let kp; try{kp=await crypto.subtle.generateKey({name:"X25519"},true,["deriveBits"])} catch{throw Error("This browser cannot do X25519. WalletConnect needs a newer " +"browser; an extension wallet works here today.")} const pub=new Uint8Array(await crypto.subtle.exportKey("raw",kp.publicKey)); return{priv:kp.privateKey,pub}} async function sessionKey(priv,peerHex){ const peer=await crypto.subtle.importKey("raw",unhex(peerHex),{name:"X25519"},false,[]); const bits=new Uint8Array(await crypto.subtle.deriveBits({name:"X25519",public:peer},priv,256)); const hk=await crypto.subtle.importKey("raw",bits,"HKDF",false,["deriveBits"]); const out=await crypto.subtle.deriveBits( {name:"HKDF",hash:"SHA-256",salt:new Uint8Array(0),info:new Uint8Array(0)},hk,256); return new Uint8Array(out)} return{seal,open,chacha,poly,hex,unhex,b64,unb64,b64u,b58,didKey,rnd,sha256, topicOf,encode,decode,relayJwt,kex,sessionKey}; })(); const WCCLI=(U,WSC)=>{ const RELAY="wss://relay.walletconnect.org"; const T_PROPOSE=1100,T_APPROVE=1101,T_SETTLE=1102,T_SETTLE_RES=1103,T_REQ=1108,T_RES=1109; const TTL=300,STTL=604800; let sock=null,nextId=1,waiters={},subs={},onEvent=null,alive=false,gone=""; const rpcId=()=>Date.now()*1000+(nextId++%1000); const sendRaw=o=>{ if(!alive)throw Error(gone||"The wallet connection is closed."); sock.send(JSON.stringify(o))}; function drop(why){ if(!alive&&gone)return; alive=false;gone=why; const ws=waiters;waiters={};subs={}; for(const k in ws)ws[k].rej(Error(why)); if(onEvent)onEvent({t:"closed",why})} function relayCall(method,params){ const id=rpcId(); return new Promise((res,rej)=>{ waiters[id]={res,rej}; sendRaw({id,jsonrpc:"2.0",method,params}); setTimeout(()=>{if(waiters[id]){delete waiters[id];rej(Error("relay timeout"))}},15000)})} function handle(raw){ let m;try{m=JSON.parse(raw)}catch{return} if(m.method==="irn_subscription"){ if(!alive)return; sendRaw({id:m.id,jsonrpc:"2.0",result:true}); const d=m.params&&m.params.data; if(d&&subs[d.topic])Promise.resolve(subs[d.topic](d.message)).catch(()=>{}); return} if(m.id&&waiters[m.id]){ const w=waiters[m.id];delete waiters[m.id]; if(m.error)w.rej(Error(m.error.message||"relay error"));else w.res(m.result)}} async function connect(projectId){ try{sock&&sock.close()}catch{} sock=null;alive=false;gone="";waiters={};subs={}; const jwt=await U.relayJwt(RELAY); const url=`${RELAY}/?auth=${jwt}&projectId=${projectId}&ua=zswap`; const ws=WSC(url); sock=ws; await new Promise((res,rej)=>{ let d=0; const t=setTimeout(()=>{if(!d){d=1;rej(Error("The relay did not answer."))}},15000); const fail=m=>{try{ws.close()}catch{}if(sock===ws)sock=null;rej(Error(m))}; const end=f=>x=>{if(d)return;d=1;clearTimeout(t);f(x)}; sock.onopen=end(()=>{alive=true;res()}); sock.onerror=end(()=>fail("The relay could not be reached.")); sock.onclose=end(e=>fail("The relay closed the connection" +(e&&e.code?" (code "+e.code+(e.reason?": "+e.reason:"")+")":"")+"."))}) sock.onclose=()=>drop("The relay connection dropped. Reconnect your wallet."); sock.onmessage=e=>handle(typeof e.data==="string"?e.data:""); } const subscribe=async(topic,fn)=>{subs[topic]=fn;await relayCall("irn_subscribe",{topic})}; const publish=(topic,message,tag,ttl)=> relayCall("irn_publish",{topic,message,ttl:ttl||TTL,tag,prompt:tag===T_PROPOSE||tag===T_REQ}); async function pair(projectId,meta){ await connect(projectId); const symKey=U.rnd(32); const pTopic=await U.topicOf(symKey); const kp=await U.kex(); const proposeId=rpcId(); let settle=null; const pKey="p"+proposeId; const done=new Promise((res,rej)=>{settle={ res:v=>{delete waiters[pKey];res(v)}, rej:e=>{delete waiters[pKey];rej(e)}}}); waiters[pKey]={res:v=>settle.res(v),rej:e=>settle.rej(e)}; await subscribe(pTopic,async msg=>{ try{ const m=U.decode(symKey,msg); if(!m||m.id!==proposeId)return; if(m.error)return settle.rej(Error(m.error.message||"rejected")); const rp=m.result&&m.result.responderPublicKey; if(!rp)return; const sKey=await U.sessionKey(kp.priv,rp); const sTopic=await U.topicOf(sKey); await subscribe(sTopic,async smsg=>{ const s=U.decode(sKey,smsg); if(!s)return; if(s.method==="wc_sessionSettle"){ await publish(sTopic,U.encode(sKey,{id:s.id,jsonrpc:"2.0",result:true}),T_SETTLE_RES,STTL); const ns=s.params&&s.params.namespaces||{}; const accts=[]; for(const k in ns)for(const a of ns[k].accounts||[])accts.push(a); settle.res({topic:sTopic,key:sKey,accounts:accts, methods:(ns.eip155&&ns.eip155.methods)||[]}); return} if(s.method==="wc_sessionDelete")return drop("The wallet ended the session."); if(s.method&&onEvent)onEvent({t:"session",m:s}); if(s.id&&waiters["s"+s.id]){ const w=waiters["s"+s.id];delete waiters["s"+s.id]; if(s.error)w.rej(Object.assign(Error(s.error.message||"rejected"),{code:s.error.code})); else w.res(s.result)}}); }catch(e){settle.rej(e instanceof Error?e:Error(String(e)))} }); const proposal={id:proposeId,jsonrpc:"2.0",method:"wc_sessionPropose",params:{ relays:[{protocol:"irn"}], proposer:{publicKey:U.hex(kp.pub),metadata:meta}, requiredNamespaces:{}, optionalNamespaces:{eip155:{chains:Object.keys(CHAINS).map(id=>"eip155:"+id), methods:["eth_sendTransaction","personal_sign","eth_signTypedData_v4", "eth_call","eth_chainId","eth_accounts","eth_getBalance", "wallet_switchEthereumChain","wallet_addEthereumChain"], events:["chainChanged","accountsChanged"]}}}}; await publish(pTopic,U.encode(symKey,proposal),T_PROPOSE); const uri=`wc:${pTopic}@2?relay-protocol=irn&symKey=${U.hex(symKey)}`; return{uri,done}} function request(sess,method,params){ const id=rpcId(); const body={id,jsonrpc:"2.0",method:"wc_sessionRequest", params:{request:{method,params:params||[]},chainId:"eip155:1"}}; const p=new Promise((res,rej)=>{ waiters["s"+id]={res,rej}; setTimeout(()=>{if(waiters["s"+id]){delete waiters["s"+id];rej(Error("wallet timed out"))}},300000)}); publish(sess.topic,U.encode(sess.key,body),T_REQ).catch(e=>{ const w=waiters["s"+id];if(!w)return;delete waiters["s"+id];w.rej(e)}); return p} return{pair,request,close:()=>{drop("The wallet connection was closed.");try{sock&&sock.close()}catch{}sock=null}, on:f=>{onEvent=f},_relayCall:relayCall}; }; const QR8=(()=>{ const EXP=new Uint8Array(512),LOG=new Uint8Array(256); (()=>{let x=1;for(let i=0;i<255;i++){EXP[i]=x;LOG[x]=i;x<<=1;if(x&256)x^=285} for(let i=255;i<512;i++)EXP[i]=EXP[i-255]})(); const mul=(a,b)=>a&&b?EXP[LOG[a]+LOG[b]]:0; const gen=n=>{let p=[1]; for(let i=0;i<n;i++){const q=new Array(p.length+1).fill(0); for(let j=0;j<p.length;j++){q[j]^=mul(p[j],EXP[i]);q[j+1]^=p[j]}p=q} return p}; const rs=(data,n)=>{ const g=gen(n),r=new Uint8Array(data.length+n); r.set(data); for(let i=0;i<data.length;i++){ const c=r[i];if(!c)continue; for(let j=0;j<=n;j++)r[i+j]^=mul(g[n-j],c)} return r.subarray(data.length)}; const SZ=49,NBLK=2,DPB=97,ECB=24,ALIGN=[6,24,42]; const bch=(v,g,n)=>{let d=v<<n; const bits=x=>{let c=0;while(x){c++;x>>>=1}return c}; const gl=bits(g); while(bits(d)>=gl)d^=g<<(bits(d)-gl); return d}; const fmtBits=mask=>{ const v=(1<<3)|mask; return ((v<<10)|bch(v,0x537,10))^0x5412}; const verBits=()=>(8<<12)|bch(8,0x1f25,12); function encode(str){ const bytes=new TextEncoder().encode(str); if(bytes.length>NBLK*DPB-2)throw Error("pairing code too long to draw"); const bits=[]; const push=(v,n)=>{for(let i=n-1;i>=0;i--)bits.push((v>>i)&1)}; push(4,4);push(bytes.length,8); for(const b of bytes)push(b,8); const cap=(NBLK*DPB)*8; for(let i=0;i<4&&bits.length<cap;i++)bits.push(0); while(bits.length%8)bits.push(0); const cw=[]; for(let i=0;i<bits.length;i+=8){let v=0;for(let j=0;j<8;j++)v=(v<<1)|bits[i+j];cw.push(v)} const pads=[0xec,0x11]; for(let i=0;cw.length<NBLK*DPB;i++)cw.push(pads[i%2]); const blocks=[],ecs=[]; for(let i=0;i<NBLK;i++){ const d=Uint8Array.from(cw.slice(i*DPB,(i+1)*DPB)); blocks.push(d);ecs.push(rs(d,ECB))} const out=[]; for(let i=0;i<DPB;i++)for(const b of blocks)out.push(b[i]); for(let i=0;i<ECB;i++)for(const e of ecs)out.push(e[i]); return out} function frame(){ const m=[],fn=[]; for(let i=0;i<SZ;i++){m.push(new Array(SZ).fill(0));fn.push(new Array(SZ).fill(0))} const put=(r,c,v)=>{m[r][c]=v;fn[r][c]=1}; const finder=(r,c)=>{ for(let i=-1;i<8;i++)for(let j=-1;j<8;j++){ const rr=r+i,cc=c+j; if(rr<0||cc<0||rr>=SZ||cc>=SZ)continue; const on=(i>=0&&i<=6&&(j===0||j===6))||(j>=0&&j<=6&&(i===0||i===6))||(i>=2&&i<=4&&j>=2&&j<=4); put(rr,cc,on?1:0)}}; finder(0,0);finder(0,SZ-7);finder(SZ-7,0); for(const r of ALIGN)for(const c of ALIGN){ if((r===6&&c===6)||(r===6&&c===ALIGN[ALIGN.length-1])||(r===ALIGN[ALIGN.length-1]&&c===6))continue; for(let i=-2;i<=2;i++)for(let j=-2;j<=2;j++) put(r+i,c+j,(Math.abs(i)===2||Math.abs(j)===2||(i===0&&j===0))?1:0)} for(let i=8;i<SZ-8;i++){put(6,i,i%2?0:1);put(i,6,i%2?0:1)} put(SZ-8,8,1); for(let i=0;i<9;i++){if(!fn[8][i])put(8,i,0);if(!fn[i][8])put(i,8,0)} for(let i=0;i<8;i++){if(!fn[8][SZ-1-i])put(8,SZ-1-i,0);if(!fn[SZ-1-i][8])put(SZ-1-i,8,0)} const vb=verBits(); for(let i=0;i<18;i++){ const b=(vb>>i)&1,r=Math.floor(i/3),c=i%3; put(SZ-11+c,r,b);put(r,SZ-11+c,b)} return{m,fn}} function place(m,fn,cw){ let bit=0,up=1; const get=i=>(cw[i>>3]>>(7-(i&7)))&1; for(let c=SZ-1;c>0;c-=2){ if(c===6)c--; for(let k=0;k<SZ;k++){ const r=up?SZ-1-k:k; for(let j=0;j<2;j++){ const cc=c-j; if(fn[r][cc])continue; m[r][cc]=bit<cw.length*8?get(bit):0;bit++}} up^=1}} const maskFn=(k,r,c)=>[ (r+c)%2===0,r%2===0,c%3===0,(r+c)%3===0, (((r/2)|0)+((c/3)|0))%2===0,((r*c)%2+(r*c)%3)===0, (((r*c)%2+(r*c)%3)%2)===0,(((r+c)%2+(r*c)%3)%2)===0][k]; function penalty(m){ let p=0; for(let i=0;i<SZ;i++){ let rc=1,cc=1; for(let j=1;j<SZ;j++){ rc=m[i][j]===m[i][j-1]?rc+1:1;if(rc===5)p+=3;else if(rc>5)p++; cc=m[j][i]===m[j-1][i]?cc+1:1;if(cc===5)p+=3;else if(cc>5)p++}} for(let i=0;i<SZ-1;i++)for(let j=0;j<SZ-1;j++){ const s=m[i][j]+m[i][j+1]+m[i+1][j]+m[i+1][j+1]; if(s===0||s===4)p+=3} const pat=[1,0,1,1,1,0,1]; const hit=(get)=>{ for(let i=0;i<SZ;i++)for(let j=0;j<SZ-6;j++){ let okk=1;for(let k=0;k<7;k++)if(get(i,j+k)!==pat[k]){okk=0;break} if(!okk)continue; let before=1,after=1; for(let k=j-4;k<j;k++)if(k<0||get(i,k)!==0){before=0;break} for(let k=j+7;k<j+11;k++)if(k>=SZ||get(i,k)!==0){after=0;break} if(before||after)p+=40}}; hit((i,j)=>m[i][j]);hit((i,j)=>m[j][i]); let dark=0;for(let i=0;i<SZ;i++)for(let j=0;j<SZ;j++)dark+=m[i][j]; p+=Math.floor(Math.abs(dark*100/(SZ*SZ)-50)/5)*10; return p} function build(str){ const cw=encode(str); let best=null,bestP=Infinity,bestK=0; for(let k=0;k<8;k++){ const{m,fn}=frame(); place(m,fn,cw); for(let r=0;r<SZ;r++)for(let c=0;c<SZ;c++) if(!fn[r][c]&&maskFn(k,r,c))m[r][c]^=1; const f=fmtBits(k); for(let i=0;i<8;i++){ const b=(f>>i)&1; if(i<6)m[i][8]=b;else if(i===6)m[7][8]=b;else m[8][8]=b; m[8][SZ-1-i]=b} for(let i=8;i<15;i++){ const b=(f>>i)&1,c=14-i; if(c<6)m[8][c]=b;else m[8][7]=b; m[SZ-1-c][8]=b} m[SZ-8][8]=1; const p=penalty(m); if(p<bestP){bestP=p;best=m;bestK=k}} return{size:SZ,modules:best,mask:bestK}} return{build,_encode:encode}; })(); const wcNode=()=>rpcPin()||rpcPool[rpcPi]; const wcHost=()=>{try{return new URL(wcNode()).host}catch{return wcNode()}}; const WC_SIGN=["eth_sendTransaction","eth_signTransaction","personal_sign","eth_sign", "eth_signTypedDa
0x68575b07…67c7·#25,914,162·0x3685cf66…59b49c
},{name:"chainId",type:"uint256"}, {name:"verifyingContract",type:"address"}], TokenPermissions:[{name:"token",type:"address"},{name:"amount",type:"uint256"}], PermitTransferFrom:[{name:"permitted",type:"TokenPermissions"},{name:"spender",type:"address"}, {name:"nonce",type:"uint256"},{name:"deadline",type:"uint256"}]}, primaryType:"PermitTransferFrom",domain:P2DOM(), message:{permitted:{token:tok,amount:amount.toString()},spender:ZROUTER, nonce:nonce.toString(),deadline:deadline.toString()}}; const sig=strip0x(await rpc("eth_signTypedData_v4",[owner,JSON.stringify(td)])); const n=sig.length/2;if(!Number.isInteger(n)||n===0||n>4096)throw Error("bad signature"); const norm=n===65&&parseInt(sig.slice(128,130),16)<27 ?sig.slice(0,128)+(parseInt(sig.slice(128,130),16)+27).toString(16).padStart(2,"0") :sig; const tail=encUint(n)+norm.padEnd(Math.ceil(norm.length/64)*64,"0"); return"0x"+SEL_P2TF+encAddr(tok)+encUint(amount)+encUint(nonce)+encUint(deadline)+pad32("a0")+tail}; const capsFor=(c,id)=>{ for(const key of Object.keys(c)){ try{if(BigInt(key)===id)return c[key]}catch{} } return undefined}; const hasAtomicBatch=c=>{ if(!c||typeof c!=="object")return false; const own=capsFor(c,BigInt(CHAIN_ID)); const k=own!==undefined?own:capsFor(c,0n); return!!(k&&k.atomic&&(k.atomic.status==="supported"||k.atomic.status==="ready"))}; const canBatch=async owner=>{ try{const c=await rpc("wallet_getCapabilities",[owner]); return hasAtomicBatch(c)}catch{return false}}; let snd=LS.snd!=="0"; let AC;const play=ns=>{if(!snd)return;try{AC=AC||new(window.AudioContext||window.webkitAudioContext);if(AC.state==="suspended")AC.resume();const t=AC.currentTime+.02;ns.forEach(([f,d,l,v])=>{const o=AC.createOscillator(),g=AC.createGain();o.type="triangle";o.frequency.value=f;o.connect(g);g.connect(AC.destination);g.gain.setValueAtTime(0,t+d);g.gain.linearRampToValueAtTime(v,t+d+.012);g.gain.exponentialRampToValueAtTime(1e-4,t+d+l);o.start(t+d);o.stop(t+d+l+.02)})}catch{}}; const sBlip=()=>play([[880,0,.07,.05]]); const sOpen=()=>play([[523.25,0,.3,.1],[783.99,.09,.3,.1]]); const sSend=()=>play([[392,0,.12,.07],[587.33,.07,.24,.07]]); const sGot=()=>play([[392,0,.11,.09],[493.88,.08,.11,.09],[587.33,.16,.11,.09],[783.99,.24,.6,.12]]); const sFail=()=>play([[311.13,0,.18,.07],[233.08,.13,.4,.07]]); const sPew=()=>play([[720,0,.04,.03]]); const sHit=()=>play([[440,0,.07,.05],[330,.04,.09,.04]]); const sendTx=req=>rpc(S,req).then(r=>{sSend();return r}); const sendBatch=async(owner,calls)=>{ const res=await rpc("wallet_sendCalls",[{version:"2.0.0",chainId:toHex(CHAIN_ID),from:owner,atomicRequired:true,calls}]); sSend(); const id=typeof res==="string"?res:res.id; for(let i=0;i<600;i++){ try{const st=await rpc("wallet_getCallsStatus",[id]); const rs=(st&&st.receipts)||[]; if(rs.some(r=>r&&r.status==="0x0"))throw Error("batch reverted"); const q=String(st&&st.status),done=q==="200"||/CONFIRMED|SUCCESS/i.test(q); const last=rs.length&&(done||rs.length>=calls.length)?rs[rs.length-1]:null,x=last&&(last.transactionHash||last.hash); if(x)return x; if(q==="400"||q==="500"||/fail|revert|reject/i.test(q))throw Error("batch failed"); if(done)throw Error("batch confirmed on chain, but the wallet returned no receipt — check your wallet's activity before retrying"); }catch(e){if(/^batch /.test(e.message))throw e} await new Promise(r=>setTimeout(r,1000))} throw Error("batch timed out")}; const encCalls=cs=>{const n=cs.length;let head="",tail="";let off=n*32; for(const c of cs){const d=strip0x(c),len=d.length/2; head+=encUint(off);const body=encUint(len)+d.padEnd(Math.ceil(d.length/64)*64,"0"); tail+=body;off+=body.length/2} return"0x"+SEL_MULTICALL+pad32("20")+encUint(n)+head+tail}; const MC3="0xcA11bde05977b3631167028862bE2a173976CA11"; const encAgg3=cs=>{const n=cs.length;let head="",tail="",off=n*32; for(const c of cs){const d=strip0x(c.data),len=d.length/2; const body=encAddr(c.to)+encBool(true)+encUint(96)+encUint(len)+d.padEnd(Math.ceil(d.length/64)*64,"0"); head+=encUint(off);tail+=body;off+=body.length/2} return"0x82ad56cb"+pad32("20")+encUint(n)+head+tail}; let mcOk=0,mcFailed=0; const decAgg3=hex=>{const h=strip0x(hex),at=i=>h.slice(i*64,(i+1)*64); const n=Number(BigInt("0x"+at(1))),out=[]; for(let i=0;i<n;i++){const rel=Number(BigInt("0x"+at(2+i)))/32,w=2+rel; const ok=BigInt("0x"+at(w))===1n,bo=Number(BigInt("0x"+at(w+1)))/32,lw=w+bo; const len=Number(BigInt("0x"+at(lw))); if(ok)mcOk++;else mcFailed++; out.push(ok?"0x"+h.slice((lw+1)*64,(lw+1)*64+len*2):null)} return out}; const mc3=async(cs,tag=L)=>cs.length?decAgg3(await rpc(C,[{to:MC3,data:encAgg3(cs)},tag])):[]; const MC3_MAX=16; const MC3_COLD=8; const mc3Clamp=n=>Number.isFinite(n)&&n>=2&&n<=MC3_MAX?Math.floor(n):MC3_COLD; let mc3Cap=mc3Clamp(Number(LS["zswap:mc3"])); let mc3Won=0; const mc3Save=()=>{try{LS["zswap:mc3"]=String(mc3Cap)}catch{}}; const cfgBatch=async(cs,tag)=>{ if(CHAIN_ID===1)return mc3Deep(cs,tag); if(!cs.length)return[]; try{return decAgg3(await l1Read("eth_call",[{to:MC3,data:encAgg3(cs)},tag||L]))} catch{return cs.map(()=>null)} }; const mc3Split=(cs,n)=>{const o=[];for(let i=0;i<cs.length;i+=n)o.push(cs.slice(i,i+n));return o}; const MC3_LANES=12; let mc3Live=0; const mc3Waiting=[]; const mc3Gate=async fn=>{ while(mc3Live>=MC3_LANES)await new Promise(r=>mc3Waiting.push(r)); mc3Live++; try{return await fn()} finally{mc3Live--;const next=mc3Waiting.shift();if(next)next()} }; let mcGaveUp=0; const mc3Deep=async(cs,tag=L,depth=0)=>{ if(!cs.length)return[]; const fan=async n=>(await Promise.all(mc3Split(cs,n).map(c=>mc3Deep(c,tag,depth+1)))).flat(); if(cs.length>mc3Cap&&depth<8)return fan(mc3Cap); try{ const r=await mc3Gate(()=>mc3(cs,tag)); if(cs.length>=mc3Cap&&mc3Cap<MC3_MAX&&r.every(x=>x!=null)&&++mc3Won>=2){mc3Cap++;mc3Won=0;mc3Save()} return r; } catch(e){ mc3Won=0; if(cs.length<2||depth>=8){mcGaveUp+=cs.length;return cs.map(()=>null)} const h=cs.length>>1; if(h<mc3Cap){mc3Cap=h;mc3Save()} await new Promise(r=>setTimeout(r,150*(depth+1))); return fan(h); } }; let bnVal="",bnAt=0,bnJob=null; const BN_TTL=12000; const bnFetch=()=>bnJob||(bnJob=rpc("eth_blockNumber",[]) .then(n=>{bnVal=n;bnAt=Date.now();return n}) .finally(()=>{bnJob=null})); const blockNow=()=>{ const age=Date.now()-bnAt; if(!bnVal||age>=BN_TTL)return bnFetch(); if(age>BN_TTL/2)bnFetch().catch(()=>{}); return Promise.resolve(bnVal); }; const codeSeen={}; const CODE_MISS_TTL=60000; const hasCode=async a=>{ if(codeSeen[a]===true)return true; const miss=codeSeen[a]; if(miss&&Date.now()-miss<CODE_MISS_TTL)return false; const h=await rpc("eth_getCode",[a,L]); const ok=(h||"").length>4; codeSeen[a]=ok||Date.now(); return ok; }; const REF_N=100n; const CH_WICK_CAP=3; const IMPACT_HIDE=50n, IMPACT_WARN=500n, IMPACT_CONFIRM=1500n, IMPACT_TYPED=3000n; const impactBps=(v,ref,eo)=>{if(!v||!ref)return null;const lin=ref*REF_N; if(lin===0n)return 0n; return eo?(v<=lin?0n:(v-lin)*10000n/lin):(v>=lin?0n:(lin-v)*10000n/lin)}; const safeSym=s=>String(s??"").replace(/[<>&"'`]/g,"") .replace(/[\u0000-\u001f\u007f]/g,"").trim().slice(0,16)||"?"; const safeUrl=u=>{u=String(u??"").trim(); return u.length<=32768&&/^(?:https:\/\/|data:image\/[a-z.+-]+;base64,)[^\s<>"'`\\]+$/i.test(u)?u:""}; const safeDataUrl=u=>{u=String(u??"").trim(); return u.length<=32768&&/^data:image\/[a-z.+-]+;base64,[A-Za-z0-9+/=]+$/i.test(u)?u:""}; const genIcon=sym=>`<svg width="20" height="20" viewBox="0 0 32 32"><circle cx="16" cy="16" r="16" fill="#999"/><text x="16" y="21" text-anchor="middle" fill="#fff" font-size="13" font-weight="600" font-family="system-ui">${(safeSym(sym)[0]||"?").toUpperCase()}</text></svg>`; const STORE=()=>CHAIN_ID===1?"zswap:custom":"zswap:custom:"+CHAIN_ID; function keepToken(t){ if(!t||!t.addr)return; const a=t.addr.toLowerCase(); try{const arr=JSON.parse(localStorage.getItem(STORE())||"[]") .filter(x=>String(x?.addr||"").toLowerCase()!==a); arr.push({sym:t.sym,addr:a,dec:t.dec,std:t.std,cause:t.cause?1:0}); localStorage.setItem(STORE(),JSON.stringify(arr))}catch{} } const SEL_SUPPORTS="01ffc9a7",IFACE_721="80ac58cd",IFACE_1155="d9b67a26"; const supports=id=>"0x"+SEL_SUPPORTS+id.padEnd(64,"0"); async function addCustomToken(a,keep=1){ a=a.toLowerCase(); if(!/^0x[0-9a-f]{40}$/.test(a))throw Error("invalid address"); const i=TOKENS.findIndex(t=>t.addr.toLowerCase()===a); if(i>=0)return i; if((await rpcRead("eth_getCode",[a,L],2)).length<5)throw Error("no contract at that address"); const[symRet,decRet,is721,is1155]=await mc3Deep([ {to:a,data:"0x95d89b41"},{to:a,data:"0x313ce567"}, {to:a,data:supports(IFACE_721)},{to:a,data:supports(IFACE_1155)}]); let uriRet=null; try{uriRet=await rpc(C,[{to:a,data:"0x"+SEL_CONTRACTURI},L])}catch{} const yes=h=>{try{return !!h&&BigInt(h)===1n}catch{return false}}; const dup=TOKENS.findIndex(t=>t.addr.toLowerCase()===a); if(dup>=0)return dup; let sym="?"; try{sym=decodeString(symRet||"0x").trim().replace(/[<>&"'`]/g,"").replace(/[\u0000-\u001f\u007f]/g,"").slice(0,16)||"?"}catch{} if(TOKENS.some(t=>t.sym.toLowerCase()===sym.toLowerCase()))sym+=" "+a.slice(0,6)+"\u2026"; if(yes(is1155)&&!yes(is721))throw Error("ERC-1155 is not supported"); const std=yes(is721)?"nft":"ft"; let dec=0; if(std==="ft"){ dec=Number(BigInt(decRet&&decRet!=="0x"?decRet:"0x0")); if(!decRet||decRet==="0x"||!Number.isInteger(dec)||dec<0||dec>36)throw Error("not an ERC-20 or ERC-721"); } let selfArt="";try{selfArt=imgFromContractURI(uriRet?decodeString(uriRet):"")}catch{} if(selfArt)logoCache[a]=selfArt; TOKENS.push({sym,addr:a,dec,std,cust:1, icon:selfArt?`<img width="20" height="20" style="border-radius:50%;object-fit:cover" src="${selfArt}" alt="">`:genIcon(sym)}); if(keep)keepToken(TOKENS[TOKENS.length-1]); return TOKENS.length-1; } function restoreCustom(){try{for(const t of JSON.parse(localStorage.getItem(STORE())||"[]")){ const addr=(t?.addr||"").toLowerCase(); const dec=+t?.dec; if(/^0x[0-9a-f]{40}$/.test(addr)&&Number.isInteger(dec)&&dec>=0&&dec<=36&&!TOKENS.some(x=>x.addr.toLowerCase()===addr)){ const sym=String(t.sym||"?").replace(/[<>&"'`]/g,"").replace(/[\u0000-\u001f\u007f]/g,"").slice(0,16)||"?"; TOKENS.push({sym,addr,dec,std:t.std==="nft"?"nft":"ft",cust:1,icon:genIcon(sym), ...(t.cause?{cause:1}:{})}); } }}catch{}} restoreCustom(); const QUOTE_TTL=45000; let account=null,connecting=false,last=null,fromBalance=0n,mode="in",bSeq=0,tab="swap",sendReady=null,sSeq=0,sendD="0",bookD="86400"; let quoting=false; let lqMode=false; const readBal=async t=>BigInt(t.addr===ZERO ?await rpc("eth_getBalance",[account,L]) :await rpc(C,[{to:t.addr,data:"0x"+SEL_BALANCEOF+encAddr(account)},L])); const showBal=(el,v,dec)=>{const n=+formatUnits(v,dec); const pretty=n>=1?n.toLocaleString("en-US",{maximumFractionDigits:4}):sigAmt(v,dec,4); el.innerHTML=`Balance: ${pretty} <a href="#" title="Use maximum (leaves gas for ETH)">Max</a>`}; async function refreshBalance(){ const f=TOKENS[fromSel.value]; if(!account||!f){bal.textContent="";fromBalance=0n;render();return} const my=++bSeq; try{ const v=await readBal(f); if(my!==bSeq)return; fromBalance=v;showBal(bal,v,f.dec); }catch{if(my===bSeq){bal.textContent="";fromBalance=0n}} refreshBalance1(); render(); } let toBalance=0n,b1Seq=0; async function refreshBalance1(){ const t=TOKENS[toSel.value]; bal1.classList.toggle("hide",!lqMode); if(!lqMode||!account||!t){bal1.textContent="";toBalance=0n;return} const my=++b1Seq; try{ const v=await readBal(t); if(my!==b1Seq)return; toBalance=v;showBal(bal1,v,t.dec); }catch{if(my===b1Seq){bal1.textContent="";toBalance=0n}} } const optsHtml=()=>{ let ft="",nft=""; let born=""; let cau=""; TOKENS.forEach((t,i)=>{const o=`<option value="${i}">${t.sym}</option>`; if(t.std==="nft")nft+=o;else if(t.cause)cau+=o;else if(t.launched)born+=o;else ft+=o}); return ft +(cau?`<optgroup label="Causes — backed and burned, never traded">${cau}</optgroup>`:"") +(born?`<optgroup label="Launched on zSwap — not curated">${born}</optgroup>`:"") +(nft?`<optgroup label="NFT collections — auction only">${nft}</optgroup>`:"") +`<option value="__custom">+ Custom token…</option>`; }; const rebuild=()=>{for(const s of[fromSel,toSel]){const v=s.value;s.innerHTML=optsHtml();s.value=v}}; const pairAddrs=()=>({fa:TOKENS[fromSel.value]?.addr,ta:TOKENS[toSel.value]?.addr, fi:String(fromSel.value),ti:String(toSel.value)}); const restorePair=k=>{ if(!k)return; const at=a=>a?TOKENS.findIndex(t=>t.addr.toLowerCase()===a.toLowerCase()):-1; const held=(cur,want)=>String(cur)===want||!TOKENS[cur]; if(held(fromSel.value,k.fi)){const i=at(k.fa);if(i>=0)fromSel.value=i} if(held(toSel.value,k.ti)){const i=at(k.ta);if(i>=0&&String(i)!==String(fromSel.value))toSel.value=i} for(const s of[fromSel,toSel]){ const want=s.dataset.addr; if(!want||held(s.value,s===fromSel?k.fi:k.ti))continue; const i=at(want); if(i>=0&&(s===fromSel||String(i)!==String(fromSel.value)))s.value=i; } }; const isNft=v=>TOKENS[v]?.std==="nft"; let pairChosen=false; const wrapTwin=(a,b)=>{ const x=(a||"").toLowerCase(),y=(b||"").toLowerCase(),w=WETH.toLowerCase(); return (x===ZERO&&y===w)||(x===w&&y===ZERO); }; const rankedOutputs=fi=>TOKENS.map((t,i)=>i) .filter(i=>i!==fi&&!isNft(String(i))&&!wrapTwin(TOKENS[i].addr,TOKENS[fi]?.addr)); const deadOutputs=new Set(); let pairAuto=false; const rankedDefaults=()=>{ if(TOKENS.length<2)return; const fi=TOKENS.findIndex((t,i)=>!isNft(String(i))); if(fi<0)return; const ti=rankedOutputs(fi).find(i=>!deadOutputs.has(TOKENS[i].addr.toLowerCase())); if(ti==null)return; fromSel.value=String(fi);toSel.value=String(ti); fromSel.dataset.prev=fromSel.value;toSel.dataset.prev=toSel.value; pairAuto=true; try{LS["zswap:pair"]=JSON.stringify([TOKENS[fi].addr,TOKENS[ti].addr])}catch{} }; const me=()=>(account||"").toLowerCase(); const advancePastDeadPair=()=>{ if(!pairAuto||pairChosen)return false; const cur=TOKENS[toSel.value]; if(!cur)return false; deadOutputs.add(cur.addr.toLowerCase()); const fi=Number(fromSel.value); const next=rankedOutputs(fi).find(i=>!deadOutputs.has(TOKENS[i].addr.toLowerCase())); if(next==null)return false; toSel.value=String(next);toSel.dataset.prev=toSel.value; syncDisabled();syncIcons(); return true; }; const settlePair=keep=>{if(pairChosen)restorePair(keep);else rankedDefaults()}; let listFresh=false; const adoptList=keep=>{ cbDisarm(); listFresh=true; rebuild(); settlePair(keep); if(!TOKENS[fromSel.value])fromSel.value=0; if(!TOKENS[toSel.value]||toSel.value===fromSel.value)toSel.value=TOKENS[1]?1:0; syncDisabled();syncIcons(); }; const syncDisabled=keep=>{ const pair=tab!=="send"; const nftOk=tab==="book"; if(pair&&fromSel.value===toSel.value){ const alt=TOKENS.findIndex((t,i)=>String(i)!==fromSel.value&&(nftOk||t.std!=="nft")); if(alt>=0){toSel.value=String(alt);toSel.dataset.prev=toSel.value} } if(pair&&nftOk&&isNft(fromSel.value)&&isNft(toSel.value)){ const give=keep===toSel?fromSel:toSel,stay=give===fromSel?toSel:fromSel; const alt=TOKENS.findIndex((t,i)=>t.std!=="nft"&&String(i)!==stay.value); if(alt>=0){give.value=String(alt);give.dataset.prev=give.value} } for(const[sel,other]of[[fromSel,toSel],[toSel,fromSel]]) for(const opt of sel.options) opt.disabled=opt.value!=="__custom"&&((pair&&opt.value===other.value)||(!nftOk&&isNft(opt.value))); }; fromSel.innerHTML=optsHtml();fromSel.value=0; toSel.innerHTML=optsHtml(); const LAST_PAIR="zswap:pair"; const seatPair=()=>{ try{ const [a,b]=JSON.parse(LS[LAST_PAIR]||"[]"); const fi=TOKENS.findIndex(t=>t.addr.toLowerCase()===String(a).toLowerCase()); const ti=TOKENS.findIndex(t=>t.addr.toLowerCase()===String(b).toLowerCase()); if(fi>=0&&ti>=0&&fi!==ti){fromSel.value=String(fi);toSel.value=String(ti);return true} }catch{} return false; }; if(!seatPair())toSel.value=String(Math.max(1,TOKENS.findIndex(t=>t.sym==="USDC"))); syncDisabled(); const iconShown=["",""]; const syncIcons=()=>{ [[fromIcon,fromSel],[toIcon,toSel]].forEach(([el,s],i)=>{ const t=TOKENS[s.value],ic=t?.icon||""; if(iconShown[i]!==ic){iconShown[i]=ic;el.innerHTML=ic} if(t)s.dataset.addr=t.addr}); syncPickers()}; const tkPanel=document.getElementById("tkPanel"),tkFind=document.getElementById("tkFind"), tkList=document.getElementById("tkList"); let tkSel=null,tkCur=-1; const pickBtn=sel=>document.getElementById(sel===fromSel?"fromPick":"toPick"); const syncPickers=()=>{ for(const sel of [fromSel,toSel]){ const b=pickBtn(sel);if(!b)continue; const t=TOKENS[sel.value]; b.classList.add("on");sel.classList.add("rich"); b.querySelector(".tkbs").textContent=t?t.sym:(sel.selectedOptions[0]?.textContent||""); b.setAttribute("aria-label",`Token: ${t?t.sym:"none"} - change`); const pill=b.closest(".pill"); if(pill){ const name=t?.name&&t.name.toLowerCase()!==t.sym.toLowerCase()?t.name:""; const extra=[name,t?.desc].filter(Boolean); if(extra.length)pill.title=[`${t.sym}${name?" — "+name:""}`,t.desc].filter(Boolean).join("\n\n"); else pill.removeAttribute("title"); } } }; const tkAct=id=>{if(id)tkFind.setAttribute("aria-activedescendant",id); else tkFind.removeAttribute("aria-activedescendant")}; const tkRows=()=>{ const q=tkFind.value.trim().toLowerCase(); tkList.textContent=""; tkCur=-1;tkAct(""); const frag=document.createDocumentFragment(); let n=0,group=""; for(const opt of tkSel.options){ const t=TOKENS[opt.value]; if(q){ const hay=[opt.textContent,t?.name,t?.addr].filter(Boolean).join(" ").toLowerCase(); if(!hay.includes(q))continue; } const g=opt.parentElement?.label||(opt.value==="__custom"?"": (listLive?(rankFellBack?"zList — listing order":"zList — ranked by conviction"):"Built-in token list")); if(g&&g!==group){const h=document.createElement("div");h.className="tkg";h.textContent=g;frag.appendChild(h);group=g} const r=document.createElement("button"); r.type="button";r.className="tkr";r.setAttribute("role","option"); r.id="tkr"+n; r.dataset.value=opt.value; r.setAttribute("aria-selected",String(opt.value===tkSel.value)); if(opt.disabled)r.setAttribute("aria-disabled","true"); const ic=document.createElement("span");ic.className="tki"; if(t?.icon)ic.innerHTML=t.icon; r.appendChild(ic); const tx=document.createElement("span");tx.className="tkt"; const top=document.createElement("span");top.className="tks"; const b=document.createElement("b");b.textContent=opt.textContent.trim();top.appendChild(b); if(t?.name&&t.name.toLowerCase()!==b.textContent.toLowerCase()){ const nm=document.createElement("span");nm.className="tkn";nm.textContent=t.name;top.appendChild(nm); } tx.appendChild(top); if(t?.desc){const d=document.createElement("span");d.className="tkd";d.textContent=t.desc;tx.appendChild(d)} r.appendChild(tx); if(opt.value===tkSel.value){const m=document.createElement("span");m.className="tkmark";m.textContent="✓";r.appendChild(m)} frag.appendChild(r);n++; } if(!n){ const a=/^0x[0-9a-f]{40}$/.test(q)?q:""; if(a){ const r=document.createElement("button"); r.type="button";r.className="tkr"; const tx=document.createElement("span");tx.className="tkt"; const top=document.createElement("span");top.className="tks"; const b=document.createElement("b");b.textContent="Use "+short(a);top.appendChild(b); tx.appendChild(top); const d=document.createElement("span");d.className="tkd"; d.textContent="Not listed \u2014 read it from chain"; tx.appendChild(d);r.appendChild(tx); r.onclick=async e=>{ e.stopPropagation(); if(r.dataset.busy)return; r.dataset.busy="1";b.textContent="Reading\u2026"; try{const i=await addCustomToken(a);rebuild();tkChoose(String(i))} catch(e){delete r.dataset.busy;b.textContent="Use "+short(a); d.textContent=(e&&e.message)||"could not read that address"}}; frag.appendChild(r); }else{const e=document.createElement("div");e.className="tkempty";e.textContent="No token matches that.";frag.appendChild(e)} } tkList.classList.toggle("fresh",listFresh); listFresh=false; tkList.appendChild(frag); tkCur=-1; }; const tkOpen=sel=>{ if(tkSel&&tkSel!==sel)pickBtn(tkSel)?.setAttribute("aria-expanded","false"); tkSel=sel;tkFind.value="";tkRows(); tkPanel.classList.remove("hide"); if(matchMedia("(pointer:coarse)").matches){ tkPanel.style.left=tkPanel.style.top=tkPanel.style.maxHeight=tkPanel.style.width=""; pickBtn(sel).setAttribute("aria-expanded","true"); tkFind.focus({preventScroll:true}); return; } const b=pickBtn(sel).getBoundingClientRect(); const card=document.querySelector(".card").getBoundingClientRect(); const w=Math.min(Math.max(card.width,240),innerWidth-16); tkPanel.style.width=w+"px"; const left=Math.max(8,Math.min(card.left,innerWidth-w-8)); const above=b.top,below=innerHeight-b.bottom; const flip=below<240&&above>below; const MENU_MAX=520; const room=Math.min(MENU_MAX,Math.max(140,(flip?above:below)-14)); tkPanel.style.maxHeight=room+"px"; const h=Math.min(tkPanel.offsetHeight,room); const top=flip?b.top-h-6:b.bottom+6; tkPanel.style.left=left+"px"; tkPanel.style.top=Math.max(8,Math.min(top,innerHeight-h-8))+"px"; pickBtn(sel).setAttribute("aria-expanded","true"); tkFind.focus({preventScroll:true}); }; const tkClose=()=>{ if(!tkSel)return; pickBtn(tkSel)?.setAttribute("aria-expanded","false"); tkPanel.classList.add("hide");tkSel=null;tkCur=-1;tkAct(""); }; const tkChoose=v=>{ const sel=tkSel;if(!sel)return; tkClose(); if(sel.value===v&&v!=="__custom")return; sel.value=v; sel.dispatchEvent(new Event("change",{bubbles:true})); }; for(const sel of [fromSel,toSel]){ const b=pickBtn(sel); if(b)b.addEventListener("click",e=>{e.stopPropagation();tkSel===sel?tkClose():tkOpen(sel)}); } tkList.addEventListener("click",e=>{ const r=e.target.closest(".tkr"); if(!r||r.getAttribute("aria-disabled")==="true")return; tkChoose(r.dataset.value); }); tkFind.addEventListener("input",tkRows); tkPanel.addEventListener("keydown",e=>{ const rows=[...tkList.querySelectorAll('.tkr:not([aria-disabled="true"])')]; if(e.key==="Escape"){e.preventDefault();const s=tkSel;tkClose();pickBtn(s)?.focus();return} if(e.key==="ArrowDown"||e.key==="ArrowUp"){ e.preventDefault(); if(!rows.length)return; tkCur=e.key==="ArrowDown"?(tkCur+1)%rows.length:(tkCur<=0?rows.length:tkCur)-1; rows.forEach((r,i)=>r.classList.toggle("cur",i===tkCur)); rows[tkCur].scrollIntoView({block:"nearest"}); tkAct(rows[tkCur].id); return; } if(e.key==="Enter"){ const r=tkCur>=0?rows[tkCur]:rows.length===1?rows[0]:null; if(r){e.preventDefault();r.dataset.value?tkChoose(r.dataset.value):r.click()} } }); tkPanel.addEventListener("click",e=>e.stopPropagation()); document.addEventListener("click",tkClose); addEventListener("resize",()=>{if(!matchMedia("(pointer:coarse)").matches)tkClose()}); addEventListener("scroll",e=>{if(tkSel&&!tkPanel.contains(e.target))tkClose()},true); const render=()=>{ if(!account){swap.textContent="Connect Wallet";swap.disabled=false;return} if(tab==="book"){ rate.textContent=""; const f=TOKENS[fromSel.value],t=TOKENS[toSel.value]; if(!f||!t){swap.textContent="Place order";swap.disabled=true;return} if(f.std==="nft"&&t.std==="nft"){ swap.textContent="One side must be a token";swap.disabled=true;return} if(t.std==="nft"){ const oneId=nftIdIn(t.addr,nftId.value); if(oneId&&!/^\d+$/.test(oneId)){ swap.textContent=isWns(t.addr)?"Token id must be a numbe
0x68575b07…67c7·#25,914,161·0xb0f72479…6d9f19
LLET="No wallet detected."; let PROV=null; const prov=()=>PROV||window.ethereum; const RPC_SEED=["https://ethereum-rpc.publicnode.com","https://eth-mainnet.public.blastapi.io"]; const RPC_STORE="zswap:rpc"; const rpcPin=()=>{try{const v=localStorage.getItem(RPC_STORE); if(v&&/^https:\/\/\S+$/i.test(v))return v}catch{}return""}; let rpcPool=RPC_SEED.slice(),rpcPi=0,curated=0,curating=0,curatedTry=0; const GAS_FLOOR=3000000000n; const SEL_LIST="d77e4c79"; const decStrArr=h=>{const b=strip0x(h||"");if(b.length<128)return[]; const W=i=>parseInt(b.slice(i*64,i*64+64),16); const off=W(0)/32;if(!Number.isInteger(off))return[]; const n=W(off);if(!(n>=0&&n<=64))return[]; const out=[]; for(let k=0;k<n;k++){ const rel=W(off+1+k)/32;if(!Number.isInteger(rel))return[]; const p=off+1+rel,len=W(p);if(!(len>=0&&len<=512))return[]; let str="";const base=(p+1)*64; for(let j=0;j<len;j++)str+=String.fromCharCode(parseInt(b.slice(base+j*2,base+j*2+2),16)); out.push(str)} return out}; const httpRead=async(u,method,params)=>{ const ac=new AbortController(),kill=setTimeout(()=>ac.abort(),15000); try{ let r; try{r=await fetch(u,{method:"POST",headers:{"content-type":"application/json"}, body:JSON.stringify({id:1,jsonrpc:"2.0",method,params:params||[]}),signal:ac.signal})} catch{throw Object.assign(Error("could not reach the read node"),{retry:1})} if(!r.ok)throw Object.assign(Error("a read node answered "+r.status),{retry:1}); let j;try{j=await r.json()}catch{throw Object.assign(Error("a read node sent an unreadable answer"),{retry:1})} if(j&&j.error){const c=j.error.code; throw Object.assign(Error(j.error.message||"the read node refused"),{code:c,data:j.error.data, retry:c==null||c===-32005||c===-32603||c===-32004||c===-32002||c===-32001||c===429})} return j?j.result:null; }finally{clearTimeout(kill)}}; const loadCurated=async()=>{ if(curating||Date.now()-curatedTry<60000)return; curating=1;curatedTry=Date.now(); try{ const a=RPCS_PIN; const alt=RPC_SEED.find(u=>u!==rpcPool[rpcPi])||RPC_SEED[0]; const [a1,a2]=await Promise.all([ httpRead(rpcPool[rpcPi],"eth_call",[{to:a,data:"0x"+SEL_LIST},L]), httpRead(alt,"eth_call",[{to:a,data:"0x"+SEL_LIST},L])]); if(!a1||!a2||strip0x(a1)!==strip0x(a2))return; const urls=decStrArr(a1).map(s=>s.trim()).filter(s=>/^https:\/\/\S+$/.test(s)); if(!urls.length)return; rpcPool=[...new Set(urls.concat(rpcPool,RPC_SEED))]; curated=1; }catch{} finally{curating=0}}; const OX_ETH="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; const OKX_APPROVE={1:"0x40aA958dd87FC8305b97f2BA922CDdCa374bcD7f",8453:"0x57df6092665eb6058DE53939612413ff4B09114E"}; const bmin=(a,b)=>a<b?a:b,bmax=(a,b)=>a>b?a:b; const SEL_SLIST="e3b06401",SEL_EXEC="495c73b0",SEL_FILL="0a1b4c9f"; const SOLVERS_PIN="0x1Dfbb2f41B596F72187370469074C46de60dA2e3"; const RPCS_PIN="0x8C7348D039f58C4e9cfA936EF410eec759213b12"; let SOLVER_FILL_PIN="0x7A2f21e476cA2ADde027BC868c5a083338EEfE54"; const SOLVER_ROW_TAG="0x7A2f21e476cA2ADde027BC868c5a083338EEfE54"; let SOLVER_EXEC_PIN="0x4f6633342045C80Efa6c8CAc41b9bdc6145168C4"; const SRC_SOLVER=9; const SRC_DEEP=10; const SOLVER_DEADLINE=1500,SOLVER_STRIKES=3,SOLVER_COOL=60000; let solverLanes=null,solverFill=null,solverExec=null,solversJob=null; const _strikes=new Map(); const laneHurt=n=>{const s=_strikes.get(n),t=Date.now();_strikes.set(n,{n:(s&&t-s.t<SOLVER_COOL?s.n:0)+1,t})}; const laneDown=n=>{const s=_strikes.get(n);return!!s&&Date.now()-s.t<SOLVER_COOL&&s.n>=SOLVER_STRIKES}; const laneSaw=(n,st)=>{if(st===401||st===403||st===429)laneHurt(n)}; const laneThrew=(n,e)=>{if(!e||e.name!=="AbortError")laneHurt(n)}; const laneOk=c=>!!c&&isAddr(c.target)&&(c.spender==null||isAddr(c.spender)) &&/^0x[0-9a-fA-F]*$/.test(c.data||"")&&strip0x(c.data||"").length%2===0; const decSolvers=h=>{const b=strip0x(h||"");if(b.length<128)return[]; const W=i=>parseInt(b.slice(i*64,i*64+64),16); const str=w=>{const n=W(w);if(!(n>=0&&n<=256))return null;let o="";const base=(w+1)*64; for(let j=0;j<n;j++)o+=String.fromCharCode(parseInt(b.slice(base+j*2,base+j*2+2),16));return o}; try{ const head=W(0)/32;if(!Number.isInteger(head))return[]; const n=W(head);if(!(n>=0&&n<=64))return[]; const out=[]; for(let k=0;k<n;k++){ const rel=W(head+1+k)/32;if(!Number.isInteger(rel))return[]; const t=head+1+rel; const nameOff=W(t)/32,epOff=W(t+1)/32; if(!Number.isInteger(nameOff)||!Number.isInteger(epOff))return[]; const name=str(t+nameOff),endpoint=str(t+epOff); if(name===null||endpoint===null)return[]; const hash=endpoint.indexOf("#"); const flag=hash<0?"":endpoint.slice(hash+1); const url=hash<0?endpoint:endpoint.slice(0,hash); const m=/(?:^|;)c=([0-9,]+)/.exec(flag); const chains=m?m[1].split(",").map(Number).filter(Boolean):[1]; out.push({name,endpoint:url,chains, adapter:"0x"+b.slice((t+2)*64+24,(t+3)*64), handicapBps:BigInt(W(t+3)), enabled:W(t+4)===1}); } return out}catch{return[]}}; const loadSolvers=()=>solversJob||(solversJob=(async()=>{ try{ const list=SOLVERS_PIN; solverFill=SOLVER_FILL_PIN; const ex=await nodeRead("eth_call",[{to:solverFill,data:"0x"+SEL_EXEC},L]).catch(()=>null); const exec="0x"+strip0x(ex||"0x").slice(24); if(!isAddr(exec)){solversJob=null;return} if(exec.toLowerCase()!==SOLVER_EXEC_PIN.toLowerCase()){solverLanes=[];return} solverExec=SOLVER_EXEC_PIN; const raw=await cfgRead("eth_call",[{to:list,data:"0x"+SEL_SLIST},L]); solverLanes=decSolvers(raw).filter(x=>x.enabled &&x.adapter.toLowerCase()===SOLVER_ROW_TAG.toLowerCase() &&/^https:\/\/\S+$/.test(x.endpoint) &&SOLVER_SHAPES[x.name] &&x.chains.includes(CHAIN_ID) &&chainCfg().slvId); }catch{solversJob=null} })()); const lanePost=async(name,url,body,ms)=>{ const ac=new AbortController(),k=setTimeout(()=>ac.abort(),ms); try{const r=await fetch(url,{method:"POST",headers:{"content-type":"application/json"}, body:JSON.stringify(body),signal:ac.signal});laneSaw(name,r.status); if(!r.ok)return null;return await r.json()} catch(e){laneThrew(name,e);return null}finally{clearTimeout(k)}}; const LANE_TTL=15000,LANE_MAX=64; const _laneHit=new Map(),_laneWait=new Map(); const laneCached=(k,go)=>{ const c=_laneHit.get(k); if(c&&Date.now()-c.t<LANE_TTL){_laneHit.delete(k);_laneHit.set(k,c);return Promise.resolve(c.v)} const w=_laneWait.get(k); if(w)return w; const pr=go().then(v=>{ _laneWait.delete(k); if(v!==null&&v!==undefined){_laneHit.set(k,{v,t:Date.now()}); if(_laneHit.size>LANE_MAX)_laneHit.delete(_laneHit.keys().next().value);} return v},e=>{_laneWait.delete(k);throw e}); _laneWait.set(k,pr);return pr}; let laneOverride=null,laneOverrideFor=""; const laneScope=()=>`${fromSel.value}|${toSel.value}|${tab}|${mode}`; const lanePin=()=>laneOverride&&laneOverrideFor===laneScope()?laneOverride:null; const laneGet=async(name,url,ms)=>{ const ac=new AbortController(),k=setTimeout(()=>ac.abort(),ms); try{const r=await fetch(url,{signal:ac.signal});laneSaw(name,r.status); if(!r.ok)return null;return await r.json()} catch(e){laneThrew(name,e);return null}finally{clearTimeout(k)}}; const SOLVER_SHAPES_OUT={ "0x":async(ep,f,t,amt,slip,ms)=>{ const q=new URLSearchParams({chainId:chainCfg().slvId,sellToken:f===ZERO?OX_ETH:f,buyToken:t===ZERO?OX_ETH:t, buyAmount:amt.toString(),taker:solverExec,slippageBps:String(slip)}); const j=await laneGet("0x",`${ep}/swap/allowance-holder/quote?${q}`,ms); if(!j||!j.transaction||!j.estimatedNetSellAmount)return null; return{inp:BigInt(j.estimatedNetSellAmount), cap:j.maxSellAmount?BigInt(j.maxSellAmount):0n, target:j.transaction.to,spender:j.allowanceTarget||j.transaction.to,data:j.transaction.data}}, "ParaSwap":async(ep,f,t,amt,slip,ms,fd,td)=>{ const q=new URLSearchParams({srcToken:f===ZERO?OX_ETH:f,destToken:t===ZERO?OX_ETH:t, amount:amt.toString(),srcDecimals:String(fd??18),destDecimals:String(td??18), side:"BUY",network:chainCfg().slvId,version:"6.2"}); const pr=await laneGet("ParaSwap",`${ep}/prices?${q}`,ms); const route=pr&&pr.priceRoute;if(!route||!route.srcAmount)return null; const j=await lanePost("ParaSwap",`${ep}/transactions/${chainCfg().slvId}?ignoreChecks=true`,{ srcToken:f===ZERO?OX_ETH:f,destToken:t===ZERO?OX_ETH:t, srcDecimals:route.srcDecimals,destDecimals:route.destDecimals, priceRoute:route,userAddress:solverExec,slippage:Number(slip)},ms); if(!j||!j.to||!j.data)return null; return{inp:bmax(BigInt(route.srcAmount),BigInt(route.srcAmountAfterFee||route.srcAmount)), target:j.to,spender:route.tokenTransferProxy||j.to,data:j.data}}, }; const SOLVER_SHAPES={ "0x":async(ep,f,t,amt,slip,ms)=>{ const q=new URLSearchParams({chainId:chainCfg().slvId,sellToken:f===ZERO?OX_ETH:f,buyToken:t===ZERO?OX_ETH:t, sellAmount:amt.toString(),taker:solverExec,slippageBps:String(slip)}); const j=await laneGet("0x",`${ep}/swap/allowance-holder/quote?${q}`,ms); if(!j||!j.transaction||!j.buyAmount)return null; return{out:BigInt(j.buyAmount),target:j.transaction.to, spender:j.allowanceTarget||j.transaction.to,data:j.transaction.data}}, "ParaSwap":async(ep,f,t,amt,slip,ms,fd,td)=>{ const q=new URLSearchParams({srcToken:f===ZERO?OX_ETH:f,destToken:t===ZERO?OX_ETH:t, amount:amt.toString(),srcDecimals:String(fd??18),destDecimals:String(td??18), side:"SELL",network:chainCfg().slvId,version:"6.2"}); const pr=await laneGet("ParaSwap",`${ep}/prices?${q}`,ms); const route=pr&&pr.priceRoute;if(!route||!route.destAmount)return null; const j=await lanePost("ParaSwap",`${ep}/transactions/${chainCfg().slvId}?ignoreChecks=true`,{ srcToken:f===ZERO?OX_ETH:f,destToken:t===ZERO?OX_ETH:t, srcDecimals:route.srcDecimals,destDecimals:route.destDecimals, priceRoute:route,userAddress:solverExec,slippage:Number(slip)},ms); if(!j||!j.to||!j.data)return null; return{out:bmin(BigInt(route.destAmount),BigInt(route.destAmountAfterFee||route.destAmount)), target:j.to,spender:route.tokenTransferProxy||j.to,data:j.data}}, "KyberSwap":async(ep,f,t,amt,slip,ms)=>{ const q=new URLSearchParams({tokenIn:f===ZERO?OX_ETH:f,tokenOut:t===ZERO?OX_ETH:t,amountIn:amt.toString()}); const pr=await laneGet("KyberSwap",`${ep}/${chainCfg().slvSlug}/api/v1/routes?${q}`,ms); const rs=pr&&pr.data&&pr.data.routeSummary;if(!rs||!rs.amountOut)return null; const j=await lanePost("KyberSwap",`${ep}/${chainCfg().slvSlug}/api/v1/route/build`, {routeSummary:rs,sender:solverExec,recipient:solverExec,slippageTolerance:Number(slip)},ms); const d=j&&j.data;if(!d||!d.data||!d.routerAddress)return null; return{out:BigInt(rs.amountOut),target:d.routerAddress,spender:d.routerAddress,data:d.data}}, "Enso":async(ep,f,t,amt,slip,ms)=>{ const q=new URLSearchParams({chainId:chainCfg().slvId,fromAddress:solverExec,receiver:solverExec, tokenIn:f===ZERO?OX_ETH:f,tokenOut:t===ZERO?OX_ETH:t,amountIn:amt.toString(), slippage:String(slip),routingStrategy:"router"}); const j=await laneGet("Enso",`${ep}?${q}`,ms); if(!j||!j.amountOut||!j.tx||!j.tx.to)return null; return{out:BigInt(j.amountOut),target:j.tx.to,spender:j.tx.to,data:j.tx.data}}, "OKX":async(ep,f,t,amt,slip,ms)=>{ const q=new URLSearchParams({chainIndex:chainCfg().slvId,fromTokenAddress:f===ZERO?OX_ETH:f, toTokenAddress:t===ZERO?OX_ETH:t,amount:amt.toString(), slippagePercent:(Number(slip)/100).toString(),userWalletAddress:solverExec,swapMode:"exactIn"}); const j=await laneGet("OKX",`${ep}/dex/aggregator/swap?${q}`,ms); const d=j&&j.code==="0"&&j.data&&j.data[0]; if(!d||!d.tx||!d.tx.to||!d.routerResult||!d.routerResult.toTokenAmount)return null; let sp=f===ZERO?d.tx.to:OKX_APPROVE[CHAIN_ID]; if(!sp){const a=await laneGet("OKX",`${ep}/dex/aggregator/approve-transaction?${new URLSearchParams({chainIndex:chainCfg().slvId,tokenContractAddress:f,approveAmount:amt.toString()})}`,ms); const x=a&&a.code==="0"&&a.data&&a.data[0]&&a.data[0].dexContractAddress;if(isAddr(x))sp=OKX_APPROVE[CHAIN_ID]=x} if(!sp)return null; return{out:BigInt(d.routerResult.toTokenAmount),target:d.tx.to,spender:sp,data:d.tx.data}}, "Bebop":async(ep,f,t,amt,slip,ms)=>{ if(f===ZERO||t===ZERO)return null; const q=new URLSearchParams({sell_tokens:f,buy_tokens:t,sell_amounts:amt.toString(), taker_address:solverExec,gasless:"false",approval_type:"Standard"}); const j=await laneGet("Bebop",`${ep}/quote?${q}`,ms); const rs=j&&j.routes;if(!rs||!rs.length)return null; const route=rs.find(x=>x.type===j.bestPrice)||rs[0]; const qq=route&&route.quote;if(!qq||!qq.tx||!qq.buyTokens)return null; const info=Object.values(qq.buyTokens)[0]; if(!info||!info.amount||!qq.tx.to)return null; const bebSp=qq.approvalTarget||qq.approval_target; if(!isAddr(bebSp||""))return null; return{out:BigInt(info.amount),target:qq.tx.to,spender:bebSp,data:qq.tx.data}}, "1inch":async(ep,f,t,amt,slip,ms)=>{ const q=new URLSearchParams({src:f===ZERO?OX_ETH:f,dst:t===ZERO?OX_ETH:t,amount:amt.toString(), from:solverExec,origin:solverExec,slippage:String(Number(slip)/100),disableEstimate:"true"}); const j=await laneGet("1inch",`${ep}/swap/v6.0/${chainCfg().slvId}/swap?${q}`,ms); if(!j||!j.tx||!j.dstAmount)return null; return{out:BigInt(j.dstAmount),target:j.tx.to,spender:j.tx.to,data:j.tx.data}}, }; const RPC_TRIES=4; const nodeRead=async(method,params)=>{ let last; const pin=CHAIN_ID===1?rpcPin():""; const tries=Math.min(rpcPool.length,RPC_TRIES); const p=rpcPi; for(let i=pin?-1:0;i<tries;i++){ const j=(p+i)%rpcPool.length; const u=i<0?pin:rpcPool[j]; try{const r=await httpRead(u,method,params);if(i>=0)rpcPi=j; if(!curated)loadCurated().catch(()=>{}); return r} catch(e){last=e;if(!e.retry)throw e}} throw last||Error("no read node answered")}; const isSign=m=>m===S||m.indexOf("wallet_")===0||m==="eth_requestAccounts"||m==="eth_accounts"||WC_SIGN.indexOf(m)>=0; const hexOk=r=>typeof r==="string"&&!/^0x[0-9a-fA-F]*$/.test(r)?Promise.reject(Error("bad hex from node")):r; const rpc=(method,params)=>{const e=prov(); const p=e?e.request({method,params}) :isSign(method)?Promise.reject(Object.assign(Error(NO_WALLET),{noWallet:true})):nodeRead(method,params); return method===C?p.then(hexOk):p}; const rpcRead=async(method,params,tries=4)=>{ let last; for(let i=0;i<tries;i++){ try{return await rpc(method,params)} catch(e){last=e;if(i+1<tries)await new Promise(r=>setTimeout(r,300*(i+1)))} } throw last; }; const okRet=h=>{h=strip0x(h);if(h&&BigInt("0x"+h)===0n)throw Error("token returned false")}; const waitTx=async hash=>{ const end=Date.now()+600000; let misses=0,wait=1500; while(Date.now()<end){ try{ const r=await rpc("eth_getTransactionReceipt",[hash]); misses=0; if(r){ if(r.status!=="0x1")throw Error("tx reverted: "+hash); return r; } wait=Math.min(wait+500,5000); }catch(e){ if(/tx reverted/.test(e.message||""))throw e; if(++misses>2)wait=Math.min(wait*2,15000); } await new Promise(res=>setTimeout(res,wait)); } throw Error("tx not mined in time"); }; const checkWallet=async()=>{if(!account)throw Error("Connect a wallet first.");const[c,a]=await Promise.all([rpc(I,[]),rpc("eth_accounts",[])]);if(+c!==CHAIN_ID)throw Error("Switch your wallet to "+chainCfg().name);if(a[0]?.toLowerCase()!==account.toLowerCase())throw Error("wallet changed")}; const hexToBytes=h=>{const s=strip0x(h);const o=new Uint8Array(s.length/2);for(let i=0;i<o.length;i++)o[i]=parseInt(s.slice(i*2,i*2+2),16);return o}; const KRC=[1n,0x8082n,0x800000000000808an,0x8000000080008000n,0x808bn,0x80000001n,0x8000000080008081n,0x8000000000008009n,0x8an,0x88n,0x80008009n,0x8000000an,0x8000808bn,0x800000000000008bn,0x8000000000008089n,0x8000000000008003n,0x8000000000008002n,0x8000000000000080n,0x800an,0x800000008000000an,0x8000000080008081n,0x8000000000008080n,0x80000001n,0x8000000080008008n]; const KR=[0,1,62,28,27,36,44,6,55,20,3,10,43,25,39,41,45,15,21,8,18,2,61,56,14]; const KM=(1n<<64n)-1n; const krol=(x,n)=>n?((x<<BigInt(n))|(x>>BigInt(64-n)))&KM:x; const KCc=new Array(5),KB=new Array(25); const keccak=b=>{const A=new Array(25).fill(0n),rate=136; const pad=rate-(b.length%rate),buf=new Uint8Array(b.length+pad);buf.set(b); buf[b.length]|=1;buf[buf.length-1]|=0x80; for(let o=0;o<buf.length;o+=rate){ for(let i=0;i<17;i++){let v=0n;for(let j=7;j>=0;j--)v=(v<<8n)|BigInt(buf[o+i*8+j]);A[i]^=v} for(let r=0;r<24;r++){ for(let x=0;x<5;x++)KCc[x]=A[x]^A[x+5]^A[x+10]^A[x+15]^A[x+20]; for(let x=0;x<5;x++){const D=KCc[(x+4)%5]^krol(KCc[(x+1)%5],1);for(let y=0;y<25;y+=5)A[x+y]^=D} for(let x=0;x<5;x++)for(let y=0;y<5;y++)KB[y+5*((2*x+3*y)%5)]=krol(A[x+5*y],KR[x+5*y]); for(let x=0;x<5;x++)for(let y=0;y<5;y++)A[x+5*y]=KB[x+5*y]^((~KB[(x+1)%5+5*y]&KM)&KB[(x+2)%5+5*y]); A[0]^=KRC[r]}} let out="";for(let i=0;i<4;i++){let v=A[i];for(let j=0;j<8;j++){out+=Number(v&0xffn).toString(16).padStart(2,"0");v>>=8n}} return "0x"+out}; const namehash=n=>{let nd="0x"+"0".repeat(64); if(n)for(const l of n.split(".").reverse())nd=keccak(new Uint8Array([...hexToBytes(nd),...hexToBytes(keccak(new TextEncoder().encode(l)))])); return nd}; const strTail=v=>{const b=new TextEncoder().encode(v);let h=""; for(const x of b)h+=x.toString(16).padStart(2,"0"); return encUint(b.length)+h.padEnd(Math.ceil(h.length/64)*64,"0")}; const encStr=v=>pad32("20")+strTail(v); const retAddr=h=>{const s=strip0x(h||""); if(s.length!==64||/[^0-9a-fA-F]/.test(s)||/[^0]/.test(s.slice(0,24)))return""; const a="0x"+s.slice(24).toLowerCase(); return /^0x0{40}$/.test(a)?"":a}; const nameOk=n=>/^[a-z0-9.-]+$/.test(n) &&n.split(".").every(l=>l.length>0&&l.length<64&&l[0]!=="-"&&l[l.length-1]!=="-"); const nsFwd=async(reg,n)=>{const id=await cfgRead(C,[{to:reg,data:"0x"+SEL_CID+encStr(n)},L]); if(strip0x(id||"").length!==64)return""; return retAddr(await cfgRead(C,[{to:reg,data:"0x"+SEL_RES+strip0x(id)},L]))}; const dnsEnc=n=>{let h=""; for(const l of n.split(".")){const b=new TextEncoder().encode(l); h+=b.length.toString(16).padStart(2,"0"); for(const x of b)h+=x.toString(16).padStart(2,"0")} return h+"00"}; const encBytes=h=>{h=strip0x(h);return encUint(h.length/2)+h.padEnd(Math.ceil(h.length/64)*64,"0")}; const encResolve=(dns,inner)=>{const a=encBytes(dns); return pad32("40")+encUint(64+a.length/2)+a+encBytes(inner)}; const decBytes=hex=>{const h=strip0x(hex||""); if(h.length<128)return""; const off=Number(BigInt("0x"+h.slice(0,64)))*2; if(!Number.isSafeInteger(off)||off+64>h.length)return""; const len=Number(BigInt("0x"+h.slice(off,off+64)))*2; if(!Number.isSafeInteger(len)||off+64+len>h.length)return""; return "0x"+h.slice(off+64,off+64+len)}; const ensFind=async n=>{const parts=n.split("."), nodes=parts.map((_,i)=>namehash(parts.slice(i).join("."))); const rs=await Promise.all(nodes.map(nd=> cfgRead(C,[{to:ENSREG,data:"0x"+SEL_RSLV+pad32(nd)},L]).catch(()=>null))); for(let i=0;i<nodes.length;i++){const r=retAddr(rs[i]); if(r)return{r,nd:nodes[i],exact:i===0}} return null}; const ensWild=async r=>{ try{const s=await cfgRead(C,[{to:r,data:supports(SEL_ERESOLVE)},L]); return strip0x(s||"").length===64&&BigInt(s)===1n}catch{return false}}; const ensFwd=async n=>{const f=await ensFind(n); if(!f)return""; const inner="0x"+SEL_EADDR+pad32(f.nd); if(f.exact)return retAddr(await cfgRead(C,[{to:f.r,data:inner},L])); if(!await ensWild(f.r))return""; try{return retAddr(decBytes(await cfgRead(C,[{to:f.r, data:"0x"+SEL_ERESOLVE+encResolve(dnsEnc(n),inner)},L])))} catch(e){ if(/556f1830/i.test((e&&e.data?JSON.stringify(e.data):"")+((e&&e.message)||""))) throw Object.assign(Error(NAME_OFFCHAIN),{offchain:true}); return""; }}; const ensRevName=async(nd,r)=>{ if(!r)return""; try{return decodeString(await cfgRead(C,[{to:r,data:"0x"+SEL_ENAME+pad32(nd)},L])).trim()}catch{return""}}; const nameFwd=n=>/\.gwei$/i.test(n)?nsFwd(GNS,n):/\.wei$/i.test(n)?nsFwd(WNS,n):/\.eth$/i.test(n)?ensFwd(n):Promise.resolve(""); const FWD_TTL=30000; const fwdCache=new Map(); const nameFwdCached=n=>{const hit=fwdCache.get(n); if(hit&&hit.exp>Date.now())return hit.p; const p=nameFwd(n); fwdCache.set(n,{p,exp:Date.now()+FWD_TTL}); p.catch(()=>fwdCache.delete(n)); return p}; const nameRev=async a=>{ const nd=namehash(strip0x(a).toLowerCase()+".addr.reverse"); const [w,g,er]=await Promise.all([ {to:WNS,data:"0x"+SEL_REV+encAddr(a)}, {to:GNS,data:"0x"+SEL_REV+encAddr(a)}, {to:ENSREG,data:"0x"+SEL_RSLV+pad32(nd)}] .map(c=>cfgRead(C,[c,L]).catch(()=>null))); const str=h=>{try{return h?decodeString(h).trim():""}catch{return""}}; const agrees=async n=>{ if(!n||!nameOk(n.toLowerCase()))return""; let f="";try{f=await nameFwdCached(n.toLowerCase())}catch{} return f&&f.toLowerCase()===a.toLowerCase()?n:""}; return await agrees(str(w))||await agrees(str(g)) ||await agrees(await ensRevName(nd,retAddr(er)))||""}; const decodeString=hex=>{ const h=strip0x(hex); if(h.length<=64)return new TextDecoder().decode(hexToBytes(h)).replace(/\0+$/,""); const off=Number(BigInt("0x"+h.slice(0,64)))*2; const len=Number(BigInt("0x"+h.slice(off,off+64)))*2; return new TextDecoder().decode(hexToBytes(h.slice(off+64,off+64+len))); }; const kecStr=v=>keccak(new TextEncoder().encode(v)); const ckAddr=a=>{const s=strip0x(a); if(s===s.toLowerCase()||s===s.toUpperCase())return true; const h=strip0x(kecStr(s.toLowerCase())); for(let i=0;i<40;i++){const c=s[i]; if(c>="a"&&c<="f"&&parseInt(h[i],16)>=8)return false; if(c>="A"&&c<="F"&&parseInt(h[i],16)<8)return false} return true}; const domainSep=(name,ver,tok)=>keccak(hexToBytes( kecStr(EIP712DOM)+pad32(kecStr(name))+pad32(kecStr(ver))+encUint(CHAIN_ID)+encAddr(tok))); const permitInfo=async(tok,owner)=>{ try{ const[ds,th,nc,nm]=await mc3Deep([ {to:tok,data:"0x"+SEL_DS},{to:tok,data:"0x"+SEL_PERMIT_TH}, {to:tok,data:"0x"+SEL_NONCES+encAddr(owner)},{to:tok,data:"0x"+SEL_NAME}]); if(!ds||ds==="0x")return null; if(th&&strip0x(th).length>=64&&strip0x(th).slice(0,64).toLowerCase()!==TH_2612)return null; if(!nc||!nm)return null; const nonce=BigInt(nc); const name=decodeString(nm); for(const ver of ["1","2","1.0","V1",""])if(domainSep(name,ver,tok).toLowerCase()===ds.toLowerCase()) return{name,ver,nonce}; }catch{} return null}; const signPermit=async(tok,info,owner,value,deadline)=>{ const td={types:{EIP712Domain:[{name:"name",type:"string"},{name:"version",type:"string"}, {name:"chainId",type:"uint256"},{name:"verifyingContract",type:"address"}], Permit:[{name:"owner",type:"address"},{name:"spender",type:"address"}, {name:"value",type:"uint256"},{name:"nonce",type:"uint256"},{name:"deadline",type:"uint256"}]}, primaryType:"Permit",domain:{name:info.name,version:info.ver,chainId:CHAIN_ID,verifyingContract:tok}, message:{owner,spender:ZROUTER,value:value.toString(),nonce:info.nonce.toString(),deadline:deadline.toString()}}; const sig=strip0x(await rpc("eth_signTypedData_v4",[owner,JSON.stringify(td)])); if(sig.length!==130)throw Error("bad signature"); let v=parseInt(sig.slice(128,130),16);if(v<27)v+=27; return"0x"+SEL_RPERMIT+encAddr(tok)+encUint(value)+encUint(deadline)+encUint(v)+sig.slice(0,64)+sig.slice(64,128)}; const P2DOM=()=>({name:"Permit2",chainId:CHAIN_ID,verifyingContract:PERMIT2}); const p2Ready=async(tok,owner)=>{ try{return BigInt(await rpc(C,[{to:tok,data:"0x"+SEL_ALLOWANCE+encAddr(owner)+encAddr(PERMIT2)},L]))}catch{return 0n}}; const signPermit2=async(tok,owner,amount,deadline)=>{ const nonce=BigInt("0x"+[...crypto.getRandomValues(new Uint8Array(31))].map(b=>b.toString(16).padStart(2,"0")).join("")); const td={types:{EIP712Domain:[{name:"name",type:"string"
0x68575b07…67c7·#25,914,160·0x4d7c96e5…0b80b7
6" d="M16.004 10.238l5.658 3.23-5.658-8.674v5.444z" fill="#FFF"/></g></svg>`; const RETH_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><circle cx="16" cy="16" r="16" fill="#ED5A37"/><path d="M16 5.75L9.89 15.85 16 19.45l6.08-3.6z" fill="#FFF"/><path d="M16 20.6l-6.11-3.59L16 25.58l6.08-8.57z" fill="#FFF"/></svg>`; const BOLD_ICON=`<svg width="20" height="20" viewBox="0 0 32 32" fill="none"><circle cx="16" cy="16" r="16" fill="#63D77D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M12.1719 4.56641H8.58203V26.1016H15.7617V25.2422C16.8398 25.793 18.0586 26.1055 19.3555 26.1055C23.7148 26.1055 27.25 22.5703 27.25 18.207C27.25 13.8438 23.7148 10.3086 19.3555 10.3086C18.0586 10.3086 16.8398 10.6211 15.7617 11.1719V4.56641H12.1719ZM15.7617 11.1719C13.207 12.4805 11.457 15.1406 11.457 18.207C11.457 21.2734 13.207 23.9336 15.7617 25.2422V11.1719Z" fill="#1C1D4F"/></svg>`; let TOKENS=[ {sym:"ETH", addr:ZERO, dec:18, icon:ETH_ICON}, {sym:"wstETH",addr:"0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0",dec:18,icon:WSTETH_ICON}, {sym:"WETH", addr:WETH, dec:18, icon:ETH_ICON}, {sym:"rETH", addr:"0xae78736cd615f374d3085123a210448e74fc6393",dec:18,icon:RETH_ICON}, {sym:"WBTC", addr:"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",dec:8, icon:WBTC_ICON}, {sym:"USDC", addr:"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",dec:6, icon:USDC_ICON}, {sym:"USDT", addr:"0xdac17f958d2ee523a2206206994597c13d831ec7",dec:6, icon:USDT_ICON}, {sym:"BOLD", addr:"0x6440f144b7e50d6a8439336510312d2f54beb01d",dec:18,icon:BOLD_ICON}, ]; let BUILTIN=TOKENS.slice(); CHAINS[1].tokens=TOKENS.slice(); CHAINS[8453].tokens=[ {sym:"ETH",addr:ZERO,dec:18,icon:ETH_ICON}, {sym:"WETH",addr:"0x4200000000000000000000000000000000000006",dec:18,icon:ETH_ICON}, {sym:"wstETH",addr:"0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452",dec:18,icon:WSTETH_ICON}, {sym:"rETH",addr:"0xb6fe221fe9eef5aba221c348ba20a1bf5e73624c",dec:18,icon:RETH_ICON}, {sym:"cbETH",addr:"0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22",dec:18}, {sym:"cbBTC",addr:"0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf",dec:8}, {sym:"USDC",addr:"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",dec:6,icon:USDC_ICON}, {sym:"USDT",addr:"0xfde4c96c8593536e31f229ea8f37b2ada2699bb2",dec:6,icon:USDT_ICON}, {sym:"DAI",addr:"0x50c5725949a6f0c72e6c4a641f24049a917db0cb",dec:18}, {sym:"AERO",addr:"0x940181a94a35a4569e4529a3cdfb74e38fd98631",dec:18}, ]; CHAINS[4663].tokens=[ {sym:"ETH",addr:ZERO,dec:18,icon:ETH_ICON}, {sym:"WETH",addr:"0x0bd7d308f8e1639fab988df18a8011f41eacad73",dec:18,icon:ETH_ICON}, {sym:"USDG",addr:"0x5fc5360d0400a0fd4f2af552add042d716f1d168",dec:6}, {sym:"NVDA",addr:"0xd0601ce157db5bdc3162bbac2a2c8af5320d9eec",dec:18}, {sym:"DEEP",addr:"0x1da24f6bb623b9d1afeae3f3146659a2662d6d27",dec:18}, {sym:"MARIAN",addr:"0x01637b14b7378b99de75a64d50656d98488d9a4d",dec:18}, ]; const CHAIN_STORE="zswap:chain"; function setChain(id){ id=+id; const c=CHAINS[id]; if(!c)return false; if(id===CHAIN_ID)return true; CHAIN_ID=id; WETH=c.wrapped; TOKENLIST=c.tokenlist; V4LENS=c.v4lens||ZERO; V4PORT=c.v4port||ZERO; const b=id===1?MB:L2B; SB2=b.sb||ZERO;SB1=b.s1||ZERO;SWAPBOL=b.sw||ZERO;DUTCH=b.du||ZERO;ORDERBOL=b.ob||ZERO;FLOOR=b.fl||ZERO;PROUTE=b.pr||ZERO;PLAUNCH=b.la||ZERO; floorReady=null;listLive=false;listJob=null; try{SOLVER_FILL_PIN=c.fill||ZERO;SOLVER_EXEC_PIN=c.exec||ZERO}catch{} try{if(c.sources)SOURCES=c.sources.slice();SRC_CURVE=(c.curve===undefined?5:c.curve)}catch{} if(c.tokens){TOKENS=c.tokens.slice();BUILTIN=TOKENS.slice();restoreCustom()} try{if(c.rpcs&&c.rpcs.length){rpcPool=[...new Set(c.rpcs)];rpcPi=0;curated=0;curatedTry=0}}catch{} try{localStorage[CHAIN_STORE]=String(id)}catch{} try{chainUi()}catch{} return true; } function parseV4Pools(extras,tokenAddr){ if(!Array.isArray(extras)||!/^0x[0-9a-f]{40}$/i.test(tokenAddr||""))return[]; const row=extras.find(x=>x&&typeof x.v==="string"&&String(x.k).toLowerCase()===V4POOL_KEY); if(!row)return[]; const out=[]; for(const spec of row.v.split(";")){ const p=spec.trim().split(":"); if(p.length!==5||p[0]!=="v1")continue; const other=p[1]==="0"?ZERO:p[1]; const hooks=p[4]==="0"?ZERO:p[4]; if(!/^0x[0-9a-f]{40}$/i.test(other)||!/^0x[0-9a-f]{40}$/i.test(hooks))continue; if(!/^\d{1,7}$/.test(p[2])||!/^\d{1,7}$/.test(p[3]))continue; const fee=Number(p[2]),ts=Number(p[3]); if(!Number.isInteger(fee)||!Number.isInteger(ts)||ts<1)continue; if(other.toLowerCase()===tokenAddr.toLowerCase())continue; const a=tokenAddr.toLowerCase(),b=other.toLowerCase(); const zeroFirst=BigInt(a)<BigInt(b); out.push({c0:zeroFirst?tokenAddr:other,c1:zeroFirst?other:tokenAddr,fee,ts,hooks}); if(out.length===8)break; } return out; } function v4PoolsFor(f,t){ const fa=f.addr.toLowerCase(),ta=t.addr.toLowerCase(); const seen=new Set(),out=[]; for(const tok of [f,t])for(const p of (tok.v4||[])){ const c0=p.c0.toLowerCase(),c1=p.c1.toLowerCase(); if(!((c0===fa&&c1===ta)||(c0===ta&&c1===fa)))continue; const id=`${c0}:${c1}:${p.fee}:${p.ts}:${p.hooks.toLowerCase()}`; if(seen.has(id))continue; seen.add(id); out.push({...p,zeroForOne:c0===fa}); } return out; } const encPoolKey=p=>encAddr(p.c0)+encAddr(p.c1)+encUint(p.fee)+encUint(p.ts)+encAddr(p.hooks); const SEL_QUOTE_BEST="2adaa389",SEL_EFFFEE="ef66de32"; let PROUTE=MB.pr; const SEL_PROUTE="5d6498e1",SEL_PCHECKPOINT="0b7c6c6c"; const SEL_PROUTEWETH="78fd78b0"; const SEL_ZAPIN="c98c2c0b"; const SEL_PREVIEW_ZAP="e7cddab0"; const encPRouteWeth=(pools,tokenOut,amountIn,minOut,to)=> "0x"+SEL_PROUTEWETH+encUint(160)+encAddr(tokenOut)+encUint(amountIn) +encUint(minOut)+encAddr(to)+encUint(pools.length)+pools.map(encAddr).join(""); const encPRoute=(pools,tokenIn,tokenOut,amountIn,minOut,to)=> "0x"+SEL_PROUTE+encUint(192)+encAddr(tokenIn)+encAddr(tokenOut)+encUint(amountIn) +encUint(minOut)+encAddr(to)+encUint(BigInt(pools.length))+pools.map(encAddr).join(""); const SEL_PAIR_COUNT="355da246"; const PPOLICY="0x00000045fc7b570Be4d71F67219508ebD295EC6D"; const SEL_ROUTABLE="9662c498",SEL_QUOTE_ALL="89941805"; const LQ_SCAN=128; const LQ_SHOW=24; const isEthLike=a=>a===ZERO||a.toLowerCase()===WETH.toLowerCase(); async function quotePrecision(f,t,amountIn,sender,block){ if(PPLENS===ZERO||PROUTE===ZERO||amountIn<=0n)return null; const nat=a=>a.toLowerCase()===WETH.toLowerCase()?ZERO:a; const vars=[]; const addVar=(a,b)=>{ const [x,y]=a.toLowerCase()<b.toLowerCase()?[a,b]:[b,a]; if(!vars.some(v=>v.c0===x&&v.c1===y&&v.tokenIn===a))vars.push({c0:x,c1:y,tokenIn:a}); }; const forms=a=>a===ZERO?[ZERO,WETH] :a.toLowerCase()===WETH.toLowerCase()?[WETH,ZERO]:[a]; for(const a of forms(f.addr))for(const b of forms(t.addr)){ if(nat(a)===nat(b))continue; addVar(a,b); } const reqs=[]; for(const v of vars)reqs.push( {to:PPLENS,data:"0x"+SEL_QUOTE_BEST+encAddr(v.c0)+encAddr(v.c1) +encAddr(sender)+encAddr(v.tokenIn)+encUint(amountIn)+encUint(0n)+encUint(BigInt(LQ_SCAN))}, {to:PFACTORY,data:"0x"+SEL_PAIR_COUNT+encAddr(v.c0)+encAddr(v.c1)}); const res=await mc3Deep(reqs,block); let pool="",out=0n,n=0,fA=f.addr,won=null; for(let i=0;i<vars.length;i++){ const q=res[i*2],c=res[i*2+1]; const b=decBest(q); if(!b)continue; if(b.out<=out)continue; pool=b.pool;out=b.out;fA=vars[i].tokenIn;won=vars[i]; try{n=c?Number(BigInt(c)):0}catch{n=0} } if(!pool||!won)return await hubHop(f,t,amountIn,sender,block); const tA=won.c0===fA?won.c1:won.c0; let fee=0n; const feeReq=p=>({to:PPLENS, data:"0x"+SEL_EFFFEE+encAddr(p)+encAddr(sender)+encAddr(fA)+encUint(amountIn)}); const [fx,rt]=await mc3Deep([feeReq(pool),routableReq([pool])],block); if(policyRefuses(rt,1)){ const alt=await bestRoutable(won.c0,won.c1,fA,amountIn,sender,block); if(!alt)return await hubHop(f,t,amountIn,sender,block); pool=alt.pool;out=alt.out; const [fx2]=await mc3Deep([feeReq(pool)],block); if(fx2)fee=BigInt(fx2); }else if(fx)fee=BigInt(fx); return {pools:[pool],out,fee,capped:n>LQ_SCAN,tokenIn:fA,tokenOut:tA, unwrap:fA===ZERO&&f.addr!==ZERO?amountIn:0n, wrap:fA!==ZERO&&f.addr===ZERO?amountIn:0n, outDiffers:(tA===ZERO)!==(t.addr===ZERO)}; } const routableReq=pools=>({to:PPOLICY, data:"0x"+SEL_ROUTABLE+encUint(32)+encUint(BigInt(pools.length))+pools.map(encAddr).join("")}); function policyRefuses(raw,n){ if(!raw)return false; const h=strip0x(raw); if(h.length<128+n*64)return false; const len=Number(BigInt("0x"+h.slice(64,128))); if(len!==n)return false; for(let i=0;i<n;i++)if(BigInt("0x"+h.slice(128+i*64,128+(i+1)*64))===0n)return true; return false; } async function bestRoutable(c0,c1,tokenIn,amountIn,sender,block){ const [raw]=await mc3Deep([{to:PPLENS,data:"0x"+SEL_QUOTE_ALL+encAddr(c0)+encAddr(c1) +encAddr(sender)+encAddr(tokenIn)+encUint(amountIn)+encUint(0n)+encUint(BigInt(LQ_SCAN))}],block); const cands=decAll(raw); if(!cands.length)return null; const [rt]=await mc3Deep([routableReq(cands.map(c=>c.pool))],block); if(!rt)return null; const h=strip0x(rt); if(h.length<128+cands.length*64)return null; let win=null; for(let i=0;i<cands.length;i++){ if(BigInt("0x"+h.slice(128+i*64,128+(i+1)*64))===0n)continue; if(!win||cands[i].out>win.out)win=cands[i]; } return win; } function decAll(raw){ if(!raw)return[]; const h=strip0x(raw); if(h.length<128)return[]; const n=Number(BigInt("0x"+h.slice(64,128))); const out=[]; for(let i=0;i<n;i++){ const b=128+i*192; if(h.length<b+192)break; const pool="0x"+h.slice(b+24,b+64),o=BigInt("0x"+h.slice(b+64,b+128)); if(o<=0n||/^0x0{40}$/i.test(pool))continue; out.push({pool,out:o}); } return out; } function decBest(raw){ if(!raw)return null; const h=strip0x(raw); if(h.length<128)return null; const pool="0x"+h.slice(24,64),out=BigInt("0x"+h.slice(64,128)); if(out<=0n||/^0x0{40}$/i.test(pool))return null; return {pool,out}; } async function hubHop(f,t,amountIn,sender,block){ if(isEthLike(f.addr)||isEthLike(t.addr))return null; const enc=(a,b,tokenIn,amt)=>{ const [c0,c1]=a.toLowerCase()<b.toLowerCase()?[a,b]:[b,a]; return [{to:PPLENS,data:"0x"+SEL_QUOTE_BEST+encAddr(c0)+encAddr(c1) +encAddr(sender)+encAddr(tokenIn)+encUint(amt)+encUint(0n)+encUint(BigInt(LQ_SCAN))}, {to:PFACTORY,data:"0x"+SEL_PAIR_COUNT+encAddr(c0)+encAddr(c1)}]}; const count=raw=>{try{return raw?Number(BigInt(raw)):0}catch{return 0}}; const hubs=[ZERO,WETH]; const legs=[]; const first=await mc3Deep(hubs.flatMap(h=>enc(f.addr,h,f.addr,amountIn)),block); for(let i=0;i<hubs.length;i++){ const b=decBest(first[i*2]); if(b)legs.push({hub:hubs[i],pool:b.pool,mid:b.out,n:count(first[i*2+1])}); } if(!legs.length)return null; const second=await mc3Deep(legs.flatMap(l=>enc(l.hub,t.addr,l.hub,l.mid)),block); let win=null; for(let i=0;i<legs.length;i++){ const b=decBest(second[i*2]); if(!b||(win&&b.out<=win.out))continue; win={pools:[legs[i].pool,b.pool],out:b.out,mid:legs[i].mid,hub:legs[i].hub, n:Math.max(legs[i].n,count(second[i*2+1]))}; } if(!win)return null; let fee=0n; const [a,b,rt]=await mc3Deep([ {to:PPLENS,data:"0x"+SEL_EFFFEE+encAddr(win.pools[0])+encAddr(sender)+encAddr(f.addr)+encUint(amountIn)}, {to:PPLENS,data:"0x"+SEL_EFFFEE+encAddr(win.pools[1])+encAddr(sender)+encAddr(win.hub)+encUint(win.mid)}, routableReq(win.pools), ],block); if(policyRefuses(rt,win.pools.length))return null; const fa=a?BigInt(a):0n,fb=b?BigInt(b):0n; fee=fa+fb-(fa*fb)/1000000n; return {pools:win.pools,out:win.out,fee,capped:win.n>LQ_SCAN, tokenIn:f.addr,tokenOut:t.addr,unwrap:0n,wrap:0n,outDiffers:false}; } const QPAIR=2; const SEL_BBS="e7798987"; const HUBS=()=>chainCfg().hubs||[WETH,...NUMERAIRES,"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0"]; async function hubRoute(f,t,amt,sb,dl,to,rf,block){ const nf=(f.addr===ZERO?WETH:f.addr).toLowerCase(),nt=(t.addr===ZERO?WETH:t.addr).toLowerCase(); const H=HUBS().filter(h=>h.toLowerCase()!==nf&&h.toLowerCase()!==nt); if(!H.length)return null; const E=(x,i,o,n)=>({to:ZQUOTER,data:"0x"+SEL_BBS+encAddr(x)+encBool(false)+encAddr(i)+encAddr(o)+encUint(n)+encUint(sb)+encUint(dl)}); const D=h=>{try{return decQ(h,sb,false,0,4,0,6)}catch{return null}}; const L=[],r1=await mc3Deep(H.map(h=>E(ZROUTER,f.addr,h,amt)),block); for(let i=0;i<H.length;i++){const a=r1[i]&&D(r1[i]);if(a&&a.amountLimit>0n)L.push({h:H[i],a})} if(!L.length)return null; const r2=await mc3Deep(L.map(x=>E(to,x.h,t.addr,x.a.amountLimit)),block); let B=null; for(let i=0;i<L.length;i++){const b=r2[i]&&D(r2[i]); if(b&&b.best.amountOut>0n&&(!B||b.best.amountOut>B.b.best.amountOut))B={...L[i],b}} if(!B)return null; const c=[B.a.callData,B.b.callData,encSweep(B.h,0n,rf),encSweep(ZERO,0n,rf)]; const feeOf=q=>(q.source===3||q.source===4)&&q.feeBps||0n;const ha=feeOf(B.a.best),hb=feeOf(B.b.best); return{best:{source:B.b.best.source,feeBps:ha+hb-ha*hb/10000n,amountIn:amt,amountOut:B.b.best.amountOut}, callData:encCalls(c),amountLimit:B.b.amountLimit, msgValue:f.addr===ZERO?amt:0n,spender:f.addr===ZERO?null:ZROUTER}; } async function quoteV4Pools(f,t,amountIn,block){ const pools=v4PoolsFor(f,t); if(!pools.length||amountIn<=0n||V4LENS===ZERO||V4PORT===ZERO)return null; const rs=(await mc3Deep(pools.map(p=>({to:V4LENS, data:"0x"+SEL_V4QUOTE+encBool(false)+encAddr(f.addr)+encAddr(t.addr) +encUint(p.fee)+encUint(p.ts)+encAddr(p.hooks)+encUint(amountIn)})),block)) .map(h=>{try{const x=strip0x(h||"");return x.length>=128?BigInt("0x"+x.slice(64,128)):0n}catch{return 0n}}); let best=null; for(let i=0;i<pools.length;i++)if(rs[i]>0n&&(!best||rs[i]>best.amountOut)) best={pool:pools[i],amountIn,amountOut:rs[i]}; return best; } const setListNote=t=>{ const el=document.getElementById("listNote"); if(!el)return; el.textContent=t||""; if(t&&/\u2026$/.test(t)){const s=document.createElement("span");s.className="spin";el.appendChild(s)} el.classList.toggle("hide",!t)}; const setListSource=w=>setListNote(w?"Built-in token list — "+w:""); let rankFellBack=false,listLive=false; const rankedIds=async()=>{ rankFellBack=false; try{ const ids=decUintArr(await cfgRead(C,[{to:ZLISTLENS,data:"0x"+SEL_RANKEDIDS},L])); if(ids.length)return ids; }catch{} try{ const ids=decUintArr(await cfgRead(C,[{to:TOKENLIST,data:"0x"+SEL_RANKEDIDS},L])); if(ids.length)rankFellBack=true; return ids; }catch{return[]} }; const KEEP="zswap:deep"; const stickyPools=()=>{try{ return JSON.parse(LS[KEEP]||"[]").filter(x=>/^0x[0-9a-f]{40}$/i.test(x)).slice(0,LAUNCH_SHOW); }catch{return[]}}; let listJob=null,listGen=0; const loadTokenList=()=>{if(listJob)return listJob;const g=++listGen; return listJob=loadTokenListRun().finally(()=>{if(!listLive&&g===listGen)listJob=null})}; async function loadTokenListRun(){ const cid=CHAIN_ID; if(TOKENLIST===ZERO)return setListSource("registry address not set"); setListNote("Built-in token list \u2014 checking zList\u2026"); try{ const cntJob=PLAUNCH===ZERO?Promise.resolve("0x0"):rpc(C,[{to:PFACTORY,data:"0x"+SEL_BYCREATOR_N+encAddr(PLAUNCH)},L]); cntJob.catch(()=>{}); const ids=await rankedIds();let dropNote=""; if(!ids.length)return setListSource("registry is empty"); const raw=await cfgBatch(ids.slice(0,64).map(id=>({to:TOKENLIST,data:"0x"+SEL_JSON+encUint(id)}))); const rows=raw.map(h=>{try{return h?decStr(h):null}catch{return null}}); const next=[]; for(const row of rows){ if(!row)continue; let t;try{t=JSON.parse(row)}catch{continue} if(t.k!=="eip155"||+t.c!==CHAIN_ID)continue; const std=t.p==="Native"||t.p==="ERC-20"?"ft":t.p==="ERC-721"?"nft":""; if(!std)continue; if(!/^0x[0-9a-f]{40}$/i.test(t.a||""))continue; if(!Number.isInteger(t.d)||t.d<0||t.d>36)continue; if(next.some(x=>x.addr.toLowerCase()===t.a.toLowerCase()))continue; const sym=safeSym(t.s).slice(0,12); const logo=safeDataUrl(t.l); next.push({sym,addr:t.a.toLowerCase(),dec:t.d,std,icon:logo?`<img src="${logo}" width="20" height="20" alt="">`:genIcon(sym), name:String(t.n??"").slice(0,64),desc:[String(t.desc??"").slice(0,240),(()=>{const o=(t.e||[]).find(x=>x&&String(x.k).toLowerCase()==="origin");return o?`${String(o.v).slice(0,24)} origin`:""})()].filter(Boolean).join(" \u00b7 "), v4:parseV4Pools(t.e,t.a)}); } if(CHAIN_ID!==1&&next.length){ const ft=next.filter(t=>t.std==="ft"); const dec=await mc3Deep(ft.map(t=>({to:t.addr,data:"0x313ce567"}))).catch(()=>null); if(dec){const bad=new Set();ft.forEach((t,i)=>{const h=strip0x(dec[i]||"");if(h.length<64||Number(BigInt("0x"+h.slice(0,64)))!==t.dec)bad.add(t.addr)}); if(bad.size){const n=next.length;next.splice(0,n,...next.filter(t=>!bad.has(t.addr)));dropNote=`${bad.size} listing${bad.size>1?"s":""} dropped: decimals differ on chain`}} } next.sort((a,b)=>(a.std==="nft")-(b.std==="nft")); if(!next.some(t=>t.std==="ft"))return setListSource("registry returned nothing swappable"); const mine=TOKENS.filter(t=>t.cust&&!next.some(x=>x.addr===t.addr.toLowerCase())); let born=[]; LAUNCHED.length=0; try{ const cnt=Number(BigInt(await cntJob||"0x0")); const take=Math.min(cnt,LAUNCH_SCAN); const raw=take?await rpc(C,[{to:PFACTORY,data:"0x"+SEL_BYCREATOR+encAddr(PLAUNCH)+encUint(BigInt(cnt-take))+encUint(BigInt(take))},L]):"0x"; const h=strip0x(raw||""),n=h.length>=128?Number(BigInt("0x"+h.slice(64,128)||"0")):0; let pools=[]; for(let i=0;i<n&&i<take;i++)pools.push("0x"+h.slice(128+i*64+24,128+(i+1)*64)); const fresh=new Set(pools.map(x=>x.toLowerCase())); const carried=stickyPools().filter(x=>!fresh.has(x.toLowerCase())); if(carried.length){ const fr=await mc3Deep(carried.map(a=>({to:a,data:"0x"+SEL_FEERECIP}))); carried.forEach((x,i)=>{let a="";try{a=("0x"+strip0x(fr[i]||"").slice(24)).toLowerCase()}catch{} if(a===PLAUNCH.toLowerCase())pools.push(x)}); } if(pools.length){ const depth=await mc3Deep(pools.map(a=>[{to:a,data:"0x"+SEL_RESERVE0}]).flat()); const byDepth=pools.map((p,i)=>{let d=0n;try{d=BigInt(depth[i]||"0x0")}catch{}return{p,d}}) .sort((x,y)=>x.d===y.d?0:(x.d<y.d?1:-1)).slice(0,LAUNCH_SHOW); pools=byDepth.map(x=>x.p); try{LS[KEEP]=JSON.stringify(byDepth.filter(x=>x.d>0n).map(x=>x.p))}catch{} const meta=await mc3Deep(pools.flatMap(a=>[{to:a,data:"0x"+SEL_TOKEN1}])); const toks=[]; meta.forEach((x,i)=>{const a=x?("0x"+strip0x(x).slice(24)).toLowerCase():""; if(/^0x[0-9a-f]{40}$/.test(a))toks.push({a,pi:i})}); const seen=new Set(next.map(x=>x.addr.toLowerCase())); const info=await mc3Deep(toks.flatMap(({a})=>[{to:a,data:"0x95d89b41"},{to:a,data:"0x"+SEL_CONTRACTURI}, {to:PLAUNCH,data:"0x"+SEL_CREATOROF+encAddr(a)}])); const taken=new Set(next.map(x=>x.sym.toLowerCase()).concat(BUILTIN.filter(b=>b.addr===ZERO||b.addr===WETH).map(x=>x.sym.toLowerCase()))); toks.forEach(({a,pi},i)=>{ if(seen.has(a))return; seen.add(a); let sym="?";try{sym=safeSym(decodeString(info[i*3]||"0x").trim()).slice(0,12)}catch{} if(!sym||sym==="?")return; if(taken.has(sym.toLowerCase()))sym+=" "+a.slice(0,6)+"…";else taken.add(sym.toLowerCase()); let art="";try{art=imgFromContractURI(decodeString(info[i*3+1]||"0x"))}catch{} if(art)logoCache[a]=art; const dep=byDepth[pi]?byDepth[pi].d:0n; let by="";try{const c="0x"+strip0x(info[i*3+2]||"").slice(24);if(/^0x[0-9a-f]{40}$/i.test(c)&&!/^0x0{40}$/i.test(c))by=short(c)}catch{} const desc=[dep>0n?`${(+formatUnits(dep,18)).toFixed(dep<10n**16n?5:3)} ETH liquidity`:"no liquidity yet", by?`by ${by}`:""].filter(Boolean).join(" \u00b7 "); born.push({sym,addr:a,dec:18,std:"ft",name:"",desc,launched:1,dep, icon:art?`<img width="20" height="20" style="border-radius:50%;object-fit:cover" src="${art}" alt="">`:genIcon(sym)}); LAUNCHED.push({addr:a,sym,pool:pools[pi]||""}); }); } }catch{} if(cid!==CHAIN_ID)return; const anchors=BUILTIN.filter(b=>(b.addr===ZERO||b.addr===WETH)&&!next.some(x=>x.addr===b.addr)); const bornAt=new Set(born.map(x=>x.addr.toLowerCase())); TOKENS.splice(0,TOKENS.length,...next,...anchors,...born, ...mine.filter(t=>!bornAt.has(t.addr.toLowerCase()))); listLive=true; setListNote([rankFellBack?"Ranked by listing order — conviction unavailable":"",dropNote].filter(Boolean).join(" \u00b7 ")); }catch(e){setListSource("could not be reached")} } const strip0x=h=>h.startsWith("0x")?h.slice(2):h; const pad32=h=>{const x=strip0x(h); if(x.length>64)throw Error("ABI word overflow"); return x.padStart(64,"0")}; const encUint=v=>{const b=BigInt(v); if(b<0n||b>=(1n<<256n))throw Error("uint256 out of range"); return pad32(b.toString(16))}; const encAddr=a=>{const x=strip0x(a||ZERO).toLowerCase(); if(!/^[0-9a-f]{40}$/.test(x))throw Error("not an address"); return pad32(x)}; const isAddr=a=>/^0x[0-9a-fA-F]{40}$/.test(a||""); const decStr=hex=>{const h=strip0x(hex);const n=parseInt(h.slice(64,128),16); return new TextDecoder().decode(hexToBytes(h.slice(128,128+n*2)))}; const decUintArr=hex=>{const h=strip0x(hex);const n=parseInt(h.slice(64,128),16);const o=[]; for(let i=0;i<n;i++)o.push(BigInt("0x"+h.slice(128+i*64,192+i*64)));return o}; const encBool=b=>pad32(b?"1":"0"); const toHex=v=>"0x"+BigInt(v).toString(16); const parseUnits=(s,dec)=>{ const r=s.trim(); if(r.includes(",")&&!/^\d{1,3}(?:,\d{3})+(?:\.\d*)?$/.test(r)) throw Error(r.indexOf(",")===r.lastIndexOf(",") ?`unclear comma — for decimals write ${r.replace(",",".")}` :"unclear commas — use . for decimals"); const t=r.replace(/,/g,""); if(!/^(?:\d+|\d*\.\d+|\d+\.)$/.test(t))throw Error("invalid amount"); const[ip,fp=""]=t.split("."); if(fp.length>dec)throw Error("too many decimals"); return BigInt((ip||"0")+fp.padEnd(dec,"0")); }; const formatUnits=(v,dec)=>{ if(!dec)return BigInt(v).toString(); v=BigInt(v); const sign=v<0n?"-":""; const s=(v<0n?-v:v).toString().padStart(dec+1,"0"); const ip=sign+s.slice(0,-dec); const fp=s.slice(-dec).replace(/0+$/,""); return fp?`${ip}.${fp}`:ip; }; const trimAmt=(v,dec)=>{const s=formatUnits(v,dec),i=s.indexOf(".");if(i<0)return s;const r=s.slice(0,i+7).replace(/\.?0+$/,"");return r==="0"&&v?s:r}; const sigAmt=(v,dec,sig=6)=>{ const s=formatUnits(v,dec),i=s.indexOf("."); if(i<0||v===0n)return s; const lead=s.slice(0,i).replace(/^0+/,""); if(lead.length)return s.slice(0,i+Math.max(1,sig-lead.length)+1).replace(/\.?0+$/,"")||s.slice(0,i); const frac=s.slice(i+1); let z=0;while(z<frac.length&&frac[z]==="0")z++; const cut=s.slice(0,i+1+z+sig).replace(/0+$/,""); return cut.endsWith(".")?cut.slice(0,-1):cut; }; const maxAmt=(v,dec)=>{v=BigInt(v);if(dec<=6)return formatUnits(v,dec); const u=10n**BigInt(dec-6);return formatUnits((v+u-1n)/u,6)}; const fitFont=el=>{ const l=el.value.length,s=l>18?".9em":l>14?"1.1em":l>10?"1.25em":"1.65em"; if(el.style.fontSize!==s)el.style.fontSize=s; }; const decQ=(hex,slip,eo,u,v,S,mv)=>{ const h=strip0x(hex),num=i=>BigInt("0x"+h.slice(i*64,(i+1)*64)),E=()=>{throw Error("bad quote")}; if(h.length<Math.max(v+2,(mv||v+1)+1)*64)E(); const o=Number(num(v))*2;if(o+64>h.length)E(); const n=Number(BigInt("0x"+h.slice(o,o+64)));if(!n||o+64+n*2>h.length)E(); const g=num(u+3),t=g>0n,k=S?(num(3)>0n?0:u):t?u:0,B=10000n,ai=S?num(2)+num(u+2):num(2),ao=S?num(3)+g:t?g:num(3),q=eo?ai:ao; const src=[];for(let w=0;w<=u;w+=4)if(num(w+2)>0n||num(w+3)>0n)src.push(Number(num(w))); return{best:{source:Number(num(k)),feeBps:num(k+1),amountIn:ai,amountOut:ao},sources:src,callData:"0x"+h.slice(o+64,o+64+n*2),amountLimit:eo?(q*(B+slip)+B-1n)/B:q*(B-slip)/B,msgValue:num(mv||v+1),slipBps:slip}; }; const merge=(est,exe)=>{if(!est)return exe;if(!exe)return null;return{...exe,best:est.best}}; const NO_WA
0x68575b07…67c7·#25,914,159·0x027aeff6…edc61f
fer and it comes back.</p></details> <details><summary>Orders</summary> <p><b>Fixed limit</b> &mdash; a price you name. It rests until someone fills it or you cancel.</p> <p><b>Dutch decay</b> &mdash; starts above your floor and falls to it over a window you choose. It has two clocks: the decay, then how long it rests at the floor before the escrow returns to you. Set that to <em>Forever</em> and it waits until you cancel.</p> <p><b>Climbing bid</b> &mdash; the mirror: a bid that rises toward a ceiling you name.</p> <p>Orders are escrowed on chain when placed, so a fill cannot fail for want of funds, and cancelling returns the escrow. Boards are denominated in WETH &mdash; an order asking for ETH is asking for WETH, and pays out as such.</p> <p>The <b>book</b> under the form is live and takeable. Yours sit at the top; everyone else&rsquo;s come cheapest first for the pair above, each row carrying its own unit price. <b>Fill</b> takes a single order, in part where it allows it. That same book is priced into every Swap quote &mdash; a swap that beats the pools on resting orders says <em>Orderbook</em> beside the rate, with the share it took.</p></details> <details><summary>Liquidity <em><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3.2c3.6 4 6 7.2 6 10.1a6 6 0 0 1-12 0c0-2.9 2.4-6.1 6-10.1z"/></svg></em></summary> <p>Precision pools are <b>bands</b>, not endless curves. A position earns fees only while the price is inside its band, and holds one asset or the other at the edges. A wider band is safer and earns less; a narrow one is the opposite.</p> <p>The bar on each row shows where the price sits in that band. <em>Out of range</em> means it is earning nothing right now.</p></details> <details><summary>Launching a coin <em><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/></svg></em></summary> <p>One transaction, and no ether needed aside from gas. A fixed supply is minted, your allocation is paid to you immediately, and <b>everything else is seeded into a fresh ETH pool</b> &mdash; one-sided, so the pool opens holding only your token.</p> <p><b>Starting market cap</b> is the pool's virtual ether reserve. It sets the opening price and how hard that price is to move, which are the same dial: roughly that much buying moves the market halfway. Small numbers mean the first buyer takes a large share of the supply, which is why the form says what one ether would buy.</p> <p><b>Creator keeps</b> is a share of the token SUPPLY, minted straight to you when the coin launches &mdash; tokens, not ether, and not a fee on trading. Keep 10% of a billion and 100,000,000 land in your wallet at once, unlocked, with nothing vesting.</p> <p>Whatever you keep does not go into the pool, and selling it later pulls ether back out of that pool &mdash; so a modest-looking percentage can be a claim on most of what early buyers put in. Anyone deciding whether to buy your coin can work that out too.</p> <p>A <b>logo</b> is stored as contract code on Ethereum &mdash; no IPFS, no pinning service, nothing to go dark. Upload anything; the page compresses it and shows the size before you sign. You can replace it later.</p></details> <details><summary>Launching a cause <em><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/></svg></em></summary> <p>The other half of the launcher, and a different thing entirely: no pool, no market, no trading. Ten million units are offered at a fixed price in ether &mdash; your <b>goal</b> divided by ten million &mdash; and what backers pay accumulates in a treasury that belongs to the cause, not to you.</p> <p>Two clocks, and they are deliberately different lengths. <b>Runs for</b> is how long backing stays open. <b>Released over</b> sets a fixed <b>rate</b>, not a fixed fraction: the tap pays out your <em>goal</em> divided by that window, every second, whatever was actually raised. Ten ether released over a year is under three hundredths of an ether a day.</p> <p><b>So a cause that raises less than its goal empties faster than the window suggests.</b> Raise a tenth of the goal and the treasury is drained in a tenth of the time &mdash; a tenth of a year, not a year. Since what has not been released is exactly what a backer can still burn back for, an under-subscribed cause can leave its backers with nothing well before the release window is over. Set the window long enough that a partial raise still leaves something behind, and read the daily figure below as the speed at full funding.</p> <p>The release clock starts the moment you launch and runs whether or not anyone has backed you yet, which is why it wants to be much longer than the raise. Release over the same thirty days you raise for and the whole treasury is yours the day backing closes &mdash; and somebody who backed you on the last day gets nothing back, because there is nothing left unreleased.</p> <p>That is what makes the token worth something on its own: <b>whatever has not been released yet can be burned back for</b>. A backer who changes their mind, or who watches a cause go quiet, redeems their share of the untouched treasury and takes their ether home. Nobody has to agree and nothing has to be voted on.</p> <p><b>The creator can close that exit, but not quietly and not quickly.</b> Burning calls <code>ragequit</code>, which the DAO can disable &mdash; and since the creator holds the only share, they alone can propose it and carry the vote. What they cannot do is act on it at once: the change has to go through the DAO, which queues it on chain and then waits out a <b>three-day timelock</b> before it can be executed. That queue is public from the moment it is entered, and burning back stays open the whole time. So the protection is not that nobody can take the exit away; it is that taking it away is visible, and slower than leaving.</p> <p>Backers hold <b>loot</b>, which carries that claim and no vote. You keep a single share and the vote that comes with it, and you buy that share at the same price everyone else pays &mdash; so your claim on the treasury is the same shape as theirs.</p> <p>Paste a cause’s token into the swap and the page prices the burn for you, against the treasury rather than against a market. It is the only price such a token has, and it is the honest one.</p></details> <details><summary>Fees</summary> <p>A launched market charges <b>1%</b> per swap. Half of that stays in the pool as reserves, which raises the floor for every holder. The other half is collectable, and splits <b>80% creator, 10% protocol, 10% tithe</b>.</p> <p>The <b>tithe</b> is burned to Ethereum itself. The ether goes to the BETH burner, which destroys it and mints a BETH receipt recording that it happened. Nobody can spend it and it cannot be redeemed &mdash; the ether no longer exists. What it buys is a smaller ether supply, which accrues to everyone holding ether rather than to us.</p> <p>The token side of collected fees is burned outright too &mdash; a launched coin's supply only ever falls.</p></details> <details><summary>What this page is</summary> <p>There is no server. This page is served from a contract, executes only its own bytes, and needs nothing but an Ethereum node to quote and swap. Those bytes cannot be changed by anyone, us included &mdash; a new version is a new address, not an edit. The token list is a contract too, artwork included; so are the node list, the solver roster, and the adapter an outside route settles through. It talks to your wallet, to Ethereum, and &mdash; when you ask for a quote &mdash; to the aggregators listed under Swapping. Which ones those are is itself recorded on chain and curated there, not decided by this page or by whoever answers a request; any of them can be switched off in a single transaction. They are asked for a price on a pair and a size, and <b>they are not told who is asking</b>: the address they are given is the shared adapter, the same one in every other person&rsquo;s request. They never see a key, and they cannot move anything, because what they return is bounded on chain before it settles. Pin <b>On-chain</b> in the venue list to skip them entirely. WalletConnect is the other outside party: reaching your phone needs its relay. Reads go through a public node you can point anywhere you like.</p> <p>Your keys never leave your wallet. Nothing here custodies your funds; every trade settles from your address to a contract you can read.</p> <p>The token list is <b>zList</b>, ranked on chain by conviction staking. Anything not on it can still be traded by pasting its address.</p></details> <div class="docsmore">Longer notes at <a href="https://docs.zswap.wei.limo" target="_blank" rel="noopener">docs.zswap.wei.limo</a></div> </div> <footer id="foot"><span id="footV"></span><span id="footAddr"></span><span id="footBurn"></span><span>&middot; <a id="footDoc" href="#" role="button">how it works</a></span><span>&middot; <a id="footList" target="_blank" rel="noopener" title="Token order here comes from zList, ranked by onchain conviction staking. Stake to move a token up the list - or to get one listed at all.">token list</a></span></footer> </div> <script>const IC0='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">', ICMOON=IC0+'<path d="M20.5 14.6A8.6 8.6 0 0 1 9.4 3.5a8.6 8.6 0 1 0 11.1 11.1z"/></svg>', ICSUN=IC0+'<circle cx="12" cy="12" r="4.2"/><path d="M12 2v2m0 16v2M4.2 4.2l1.5 1.5m12.6 12.6 1.5 1.5M2 12h2m16 0h2M4.2 19.8l1.5-1.5M18.3 5.7l1.5-1.5"/></svg>'; const ic=()=>{const d=H.className=='d';th.innerHTML=d?ICSUN:ICMOON;tc.content=d?'#0a0a0a':'#fff'};ic(); const ZSWAP_VERSION="0.3"; const ZSWAP_SELF=""; const ZSWAP_PREVIOUS="0xe686952842627A2cf81DF42CCaD54ef98046DB8D"; const ZLIST_URL="https://0x0000006d936ba3653b8854490e16e782cd32a9a8.w4eth.io/"; const selfFromUrl=()=>{ const h=(location.hostname||"").split("."); if(!/^0x[0-9a-fA-F]{40}$/.test(h[0]||""))return ""; if(/^\d+$/.test(h[1]||"")&&h[1]!=="1")return ""; return h[0]; }; const short=a=>a.slice(0,6)+"\u2026"+a.slice(-4); (()=>{ const self=selfFromUrl()||ZSWAP_SELF; footV.textContent=`zSwap v${ZSWAP_VERSION}`; footList.href=ZLIST_URL; if(self){ footAddr.innerHTML=`\u00b7 <a href="https://etherscan.io/address/${self}#code" target="_blank" rel="noopener" ` +`title="This exact build, on chain. Its bytes cannot change; a newer version is a different address.">${short(self)}</a>`; if(ZSWAP_PREVIOUS)footAddr.innerHTML+=` \u00b7 <a href="https://etherscan.io/address/${ZSWAP_PREVIOUS}#code" ` +`target="_blank" rel="noopener" title="The version this one succeeded. It still serves its own page, unchanged.">prev</a>`; } })(); th.onclick=()=>{H.classList.toggle('d');LS.t=H.className=='d'?'d':'l';ic()}; const ZQUOTER="0x000000bd2db80567c23e353ca95a251c573cbf9b"; const WNS="0x0000000000696760E15f265e828DB644A0c242EB"; const GNS="0x9D51D507BC7264d4fE8Ad1cf7Fe191933A0a81d6"; const ENSREG="0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; const WREG="0x53745292f0d30d68204a63002C17bDa16C772bf7"; const WROLL="0x0000C82AA4D72871568eF3859D2b0E7CF37e45f2"; const IDWEI="cea9efa56b7c8a673303d04b917a7119a2a68f8c4803d8e6fd1c3a1f0d2e4ebe"; const ZROUTER="0x000000000000FB114709235f1ccBFfb925F600e4"; const PFACTORY="0x000000Eb27B557aB426d9E99cFd54EC455799e81"; const PLQLENS="0x000000956bf20A41C54BaE4a4b6F5C8A166DAB4E"; const PPLENS="0x000000Bad3a2fa57ed74fa06000573ccddF6B7fB"; const PI_WORDS=19,PI_POOL=0,PI_LIQ=9,PI_HOOK=10,PI_SL=3,PI_SH=4, PI_FEE=5,PI_R0=6,PI_R1=7,PI_PX=8,PI_CBPS=12; let CHAIN_ID=1; const CHAIN_ICON_BASE=`<svg viewBox="0 0 32 32" width="20" height="20" aria-hidden="true"><rect width="32" height="32" fill="#0052FF"/><path d="M16 5.4A10.6 10.6 0 0 0 5.5 14.7h13.9v2.6H5.5A10.6 10.6 0 1 0 16 5.4Z" fill="#fff" transform="rotate(180 16 16)"/></svg>`; const CHAIN_ICON_RH=`<svg viewBox="0 0 32 32" width="20" height="20" aria-hidden="true"><rect width="32" height="32" fill="#CCFF00"/><g transform="translate(4 4)" fill="#110E08"><path d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/></g></svg>`; const CHAINS={ 1:{name:"Ethereum",short:"ETH",slow:true,sweep4:true,curve:5,fill:"0x7A2f21e476cA2ADde027BC868c5a083338EEfE54",exec:"0x4f6633342045C80Efa6c8CAc41b9bdc6145168C4",slvId:"1",slvSlug:"ethereum",sources:["UniV2","Sushi","zAMM","UniV3","UniV4","Curve","Lido","Wrap","Precision"],v4port:"0x000000dfb53Fa7f1c486470034741d5BCBE14BE9",v4lens:"0x000000c3aE1692983941495162A4AAB40660E65F",tokenlist:"0x0000006013dF75A31678B786061C2B54bf531524", wrapped:"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",explorer:"https://etherscan.io", rpcs:["https://ethereum-rpc.publicnode.com","https://eth-mainnet.public.blastapi.io"]}, 8453:{name:"Base",short:"BASE",sweep4:false,curve:-1,fill:"0x03d95ecAF7B2Ab6027b8541b19Cb0Ce5CEd9bC74",exec:"0x1434A2579DE1009aA12b7064A161E53a5be2C030",slvId:"8453",slvSlug:"base",sources:["UniV2","Aero","zAMM","UniV3","UniV4","AeroCL","Wrap","","Precision"],v4port:"0x508ad1b0ae31FaF295c5af8C5c2bE9e33E0D19C4",v4lens:"0x0000000000000000000000000000000000000000",tokenlist:"0x0000006013dF75A31678B786061C2B54bf531524", wrapped:"0x4200000000000000000000000000000000000006",explorer:"https://basescan.org",hubs:["0x4200000000000000000000000000000000000006","0x833589fcd6edb6e08f4c7c32d4f71b54bda02913","0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf","0x50c5725949a6f0c72e6c4a641f24049a917db0cb","0x940181a94a35a4569e4529a3cdfb74e38fd98631"], icon:CHAIN_ICON_BASE,rpcs:["https://mainnet.base.org","https://base-rpc.publicnode.com"]}, 4663:{name:"Robinhood",short:"RH",sweep4:false,curve:5,fill:"0x03d95ecAF7B2Ab6027b8541b19Cb0Ce5CEd9bC74",exec:"0x1434A2579DE1009aA12b7064A161E53a5be2C030",slvId:"4663",slvSlug:"robinhood",sources:["UniV2","Sushi","zAMM","UniV3","UniV4","Curve","Lido","Wrap","Precision"],v4port:"0x508ad1b0ae31FaF295c5af8C5c2bE9e33E0D19C4",v4lens:"0x0000000000000000000000000000000000000000",tokenlist:"0x0000006013dF75A31678B786061C2B54bf531524", wrapped:"0x0bd7d308f8e1639fab988df18a8011f41eacad73",explorer:"https://robinscan.io",hubs:["0x0bd7d308f8e1639fab988df18a8011f41eacad73"], rpcs:["https://rpc.mainnet.chain.robinhood.com"]}, }; CHAINS[4663].icon=CHAIN_ICON_RH; const MB={sb:"0x000000dA7bb4B2A9E3e80e9A4D4157E26CA6189b",s1:"0x000000fF3D7A2d373615141d7489Ca66683DbecF",sw:"0x00000087A6dc5071779Ed1F8274A39230768B976",du:"0x000000a213b430D14Bae6062c176289B05e04489",ob:"0x000000e6c7a12C80525ee74e7434aAb919447D95",fl:"0x00000080198137F790DA4C52bb902cf87c276748",pr:"0x0000007Be74558A1F8c9045301c6F44C8eD0c9eB",la:"0x0000002fC8E77585A008Aa45d78A71ad36293aEe"}; const L2B={sb:"0x0000001330435808A906432449D233d482Bc9b60",sw:"0x000000015d9428959A495E31A6999E1C61C64F00",du:"0x000000fa42d555173395323b2956e9c42EFaEFf2",ob:"0x0000007a1c93d42dD739E8d6cA9Dabf330934970",fl:"0x000000AC7d32e802B003a31F790eb28Ed3294Bac",pr:"0x000000D14E24e5FC8965bcDE58f21f704C944999"}; const chainCfg=()=>CHAINS[CHAIN_ID]||CHAINS[1]; const L1_RPCS=["https://ethereum-rpc.publicnode.com","https://eth-mainnet.public.blastapi.io"]; let l1i=0; const l1Read=async(method,params)=>{ for(let k=0;k<L1_RPCS.length*2;k++){ const u=L1_RPCS[l1i%L1_RPCS.length]; try{return await httpRead(u,method,params)}catch(e){l1i++} } throw Error("no mainnet read node"); }; const cfgRead=(method,params)=>CHAIN_ID===1?rpc(method,params):l1Read(method,params); const SHARE_HOST="new.zswap.wei.limo"; const ZERO="0x0000000000000000000000000000000000000000"; let WETH="0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"; let TOKENLIST="0x0000006013dF75A31678B786061C2B54bf531524"; const ZLISTLENS="0x000000cEa3AB048d59473F3fb116A8D7F1abd247"; const SEL_RANKEDIDS="df7ca268"; const SEL_JSON="74e18e96"; const SEL_CID="fb021939",SEL_RES="4f896d4f",SEL_REV="9af8b7aa"; const SCOREM="0x57Fae63f7c63732fc4B77247F0255b11b9458AE1",SEL_SCLAIM="5cc2f461"; const SEL_WAVAIL="8f8dc386",SEL_WFEE="fcee45f4",SEL_WPREM="1bf1fffb",SEL_WCOMMIT="f14fcbc8",SEL_WCOMMITS="839df945",SEL_WREVEAL="ea9384fa",SEL_WSUB="a00fd3c8"; const SEL_RSTATE="c19d93fb",SEL_RWEIGHT="0767d178",SEL_RTICKET="673b4784",SEL_RENTER="23972aef",SEL_WOWNER="6352211e"; const WN_KEY="zswap:wns",WN_MIN=60,WN_MAX=86400; const SEL_RSLV="0178b8bf",SEL_EADDR="3b3b57de",SEL_ENAME="691f3431"; const SEL_ERESOLVE="9061b923"; const NAME_OFFCHAIN="That name keeps its records off chain, and this page reads only on-chain state."; const SEL_ALLOWANCE="dd62ed3e"; const SEL_LATEST="52bfe789",SEL_PREV="247dfaa8",SEL_SUCCAT="451aae60"; const MATURITY=259200; const SEL_DS="3644e515",SEL_NONCES="7ecebe00",SEL_NAME="06fdde03",SEL_PERMIT_TH="30adf81f"; const TH_2612="6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9"; const SEL_RPERMIT="7ac2ff7b",SEL_MULTICALL="ac9650d8",SEL_SWEEP="cb019b84"; const PERMIT2="0x000000000022D473030F116dDEE9F6B43aC78BA3"; const SEL_P2TF="09d31579"; const EIP712DOM="EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; const SEL_APPROVE="095ea7b3"; const SEL_BALANCEOF="70a08231"; let V4PORT="0x000000dfb53Fa7f1c486470034741d5BCBE14BE9"; let V4LENS="0x000000c3aE1692983941495162A4AAB40660E65F"; const SEL_V4QUOTE="d500463c",SEL_V4SWAP="48e6f730"; const V4POOL_KEY="0x95a932c205571d4d1ca72715c642a2eca21dde79ffc28ff11509681f9383385f"; const C="eth_call",S="eth_sendTransaction",L="latest",I="eth_chainId"; let SOURCES=["UniV2","Sushi","zAMM","UniV3","UniV4","Curve","Lido","Wrap","Precision"]; const SRC_PRECISION=8; let SRC_CURVE=5; const ETH_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#627EEA"/><g fill="#FFF" fill-rule="nonzero"><path fill-opacity=".602" d="M16.498 4v8.87l7.497 3.35z"/><path d="M16.498 4L9 16.22l7.498-3.35z"/><path fill-opacity=".602" d="M16.498 21.968v6.027L24 17.616z"/><path d="M16.498 27.995v-6.028L9 17.616z"/><path fill-opacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/><path fill-opacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/></g></g></svg>`; const USDC_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none"><circle fill="#2775CA" cx="16" cy="16" r="16"/><g fill="#FFF"><path d="M20.022 18.124c0-2.124-1.28-2.852-3.84-3.156-1.828-.243-2.193-.728-2.193-1.578 0-.85.61-1.396 1.828-1.396 1.097 0 1.707.364 2.011 1.275a.458.458 0 00.427.303h.975a.416.416 0 00.427-.425v-.06a3.04 3.04 0 00-2.743-2.489V9.142c0-.243-.183-.425-.487-.486h-.915c-.243 0-.426.182-.487.486v1.396c-1.829.242-2.986 1.456-2.986 2.974 0 2.002 1.218 2.791 3.778 3.095 1.707.303 2.255.668 2.255 1.639 0 .97-.853 1.638-2.011 1.638-1.585 0-2.133-.667-2.316-1.578-.06-.242-.244-.364-.427-.364h-1.036a.416.416 0 00-.426.425v.06c.243 1.518 1.219 2.61 3.23 2.914v1.457c0 .242.183.425.487.485h.915c.243 0 .426-.182.487-.485V21.34c1.829-.303 3.047-1.578 3.047-3.217z"/><path d="M12.892 24.497c-4.754-1.7-7.192-6.98-5.424-11.653.914-2.55 2.925-4.491 5.424-5.402.244-.121.365-.303.365-.607v-.85c0-.242-.121-.424-.365-.485-.061 0-.183 0-.244.06a10.895 10.895 0 00-7.13 13.717c1.096 3.4 3.717 6.01 7.13 7.102.244.121.488 0 .548-.243.061-.06.061-.122.061-.243v-.85c0-.182-.182-.424-.365-.546zm6.46-18.936c-.244-.122-.488 0-.548.242-.061.061-.061.122-.061.243v.85c0 .243.182.485.365.607 4.754 1.7 7.192 6.98 5.424 11.653-.914 2.55-2.925 4.491-5.424 5.402-.244.121-.365.303-.365.607v.85c0 .242.121.424.365.485.061 0 .183 0 .244-.06a10.895 10.895 0 007.13-13.717c-1.096-3.46-3.778-6.07-7.13-7.162z"/></g></g></svg>`; const USDT_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#26A17B"/><path fill="#FFF" d="M17.922 17.383v-.002c-.11.008-.677.042-1.942.042-1.01 0-1.721-.03-1.971-.042v.003c-3.888-.171-6.79-.848-6.79-1.658 0-.809 2.902-1.486 6.79-1.66v2.644c.254.018.982.061 1.988.061 1.207 0 1.812-.05 1.925-.06v-2.643c3.88.173 6.775.85 6.775 1.658 0 .81-2.895 1.485-6.775 1.657m0-3.59v-2.366h5.414V7.819H8.595v3.608h5.414v2.365c-4.4.202-7.709 1.074-7.709 2.118 0 1.044 3.309 1.915 7.709 2.118v7.582h3.913v-7.584c4.393-.202 7.694-1.073 7.694-2.116 0-1.043-3.301-1.914-7.694-2.117"/></g></svg>`; const WBTC_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#F7931A"/><path fill="#FFF" fill-rule="nonzero" d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"/></g></svg>`; const WSTETH_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none"><circle fill="#00A3FF" cx="16" cy="16" r="16"/><path d="M9.437 14.864l-.181.275c-2.048 3.097-1.603 7.253 1.034 9.824 1.561 1.521 3.622 2.353 5.683 2.353 0 0 0 0-6.536-12.452z" fill="#FFF"/><path opacity=".6" d="M15.997 18.611l-6.56-3.747c6.56 12.452 6.56 12.452 6.56 12.452 0-2.683 0-5.623 0-8.705z" fill="#FFF"/><path opacity=".6" d="M22.563 14.864l.181.275c2.048 3.097 1.603 7.253-1.034 9.824-1.561 1.521-3.622 2.353-5.683 2.353 0 0 0 0 6.536-12.452z" fill="#FFF"/><path opacity=".2" d="M16.003 18.611l6.56-3.747c-6.56 12.452-6.56 12.452-6.56 12.452 0-2.683 0-5.623 0-8.705z" fill="#FFF"/><path opacity=".2" d="M16.004 10.239v6.459l5.654-3.23-5.654-3.229z" fill="#FFF"/><path opacity=".6" d="M16.005 10.239l-5.655 3.229 5.655 3.23v-6.46z" fill="#FFF"/><path d="M16.005 4.805l-5.655 8.668 5.655-3.233V4.805z" fill="#FFF"/><path opacity=".
0x68575b07…67c7·#25,914,158·0x46caf110…1c1dfb
ize:.82em;letter-spacing:.02em;margin-left:.15em} #chNote{display:block;font-size:.65em;color:var(--m);margin-top:.35em;text-align:right;font-variant-numeric:tabular-nums} .panel select.rich{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none} .tkb{display:none;align-items:center;gap:.25em;border:0;background:transparent;padding:0; font:inherit;font-size:.9em;font-weight:600;letter-spacing:.02em;color:var(--f);cursor:pointer} .tkb.on{display:inline-flex} .tkbc{font-size:.7em;color:var(--m)} .tkp{position:fixed;z-index:40;width:min(20em,calc(100vw - 2em));max-height:min(32em,calc(100vh - 5em)); display:flex;flex-direction:column;background:var(--c);border:1px solid var(--hd);overflow:hidden} #tkFind{border:0;border-bottom:1px solid var(--e);background:transparent;color:var(--f); font:inherit;font-size:.8em;padding:.7em .8em;outline:0;flex:none} #tkList{overflow-y:auto;overscroll-behavior:contain} #wkWrap{position:fixed;inset:0;z-index:60;background:#0006;display:flex;align-items:center;justify-content:center} #wkWrap.hide{display:none} #wkPanel{width:min(19em,calc(100vw - 2em));background:var(--c);border:1px solid var(--hd); overflow:hidden; max-height:min(32em,calc(100vh - 2em));display:flex;flex-direction:column} #wkHdr{flex:none} #wkList{overflow-y:auto;overscroll-behavior:contain} #wkHdr{padding:.7em .8em;border-bottom:1px solid var(--e);font-size:.72em; letter-spacing:.14em;text-transform:uppercase;color:var(--m)} #wkList img,#wkList .wki{width:20px;height:20px;border-radius:50%;flex:none} .wcq{padding:.7em .8em 0} .wcn{padding:.55em .8em .85em;font-size:.72em;color:var(--m)} .wcnode{padding-top:0;opacity:.85} #wkList .wki{display:block} #wkList .wki svg{width:100%;height:100%;display:block} .tkg{border-top:1px solid var(--e);padding:.7em .8em .3em;font-size:.62em;letter-spacing:.14em;text-transform:uppercase;color:var(--m)} .tkr{display:flex;align-items:flex-start;gap:.55em;width:100%;box-sizing:border-box; padding:.55em .8em;border:0;border-top:1px solid var(--e);background:transparent;color:var(--f);font:inherit; text-align:left;cursor:pointer} #tkList>.tkr:first-child,#tkList>.tkg:first-child,.tkg+.tkr{border-top:0} .tkr:hover,.tkr.cur{background:var(--fh)} .tkr[aria-disabled="true"]{opacity:.5;cursor:default} .tkr[aria-disabled="true"]:hover{background:transparent} .tkr img,.tkr svg{flex:none;border-radius:50%;margin-top:.1em} .tki{flex:none;width:20px;height:20px;display:flex;align-items:center;justify-content:center} .tkt{min-width:0;flex:1} .tks{display:flex;align-items:baseline;gap:.45em} .tks b{font-size:.85em;font-weight:600} .tkn{font-size:.7em;color:var(--m);overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .tkd{font-size:.66em;color:var(--m);line-height:1.45;margin-top:.15em; display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden} .tkmark{flex:none;font-size:.75em;color:var(--m)} .tkempty{padding:1.4em .8em;text-align:center;font-size:.72em;color:var(--m)} .sc{margin-top:.7em;padding:.7em .8em;border:1px solid var(--e);font-size:.72em;color:var(--m);line-height:1.55} .sc b{display:block;color:var(--f);font-size:1.15em;word-break:break-all} .sc .scw{color:var(--w)} .sc button{margin-top:.5em;padding:.4em .8em;font-size:1em} body:has(.tkp:not(.hide))::before{content:"";position:fixed;inset:0;z-index:39;pointer-events:none;background:#0006} @media(max-width:30em){#slipL span{display:none}} @media(pointer:coarse){ #th,#lk,#lq,#ln,#wn,#pv{width:36px;height:36px} #sd{width:44px;height:44px;top:.35em;right:.35em} #addr{padding:.55em 0;margin:-.55em 0} .tkb{padding:.7em 0 .7em .5em;margin:-.7em 0} .flip{width:2.4em;height:2.4em;margin:-1.2em auto} #slipL{padding:.5em 0;margin:-.5em .15em -.5em 0} .chtog{padding:1em .1em} .fclink{padding:.55em 0;margin:-.55em 0 -.55em .35em} .docs summary{padding:1em .2em} .tkp{position:fixed;left:0;right:0;bottom:0;top:auto;width:100%;max-width:none; box-sizing:border-box;border-left:0;border-right:0; max-height:82vh;border-bottom:0; padding-bottom:env(safe-area-inset-bottom,0)} .tkp::before{content:"";flex:none;width:2.2em;height:.25em; background:var(--e);margin:.5em auto .1em} .tkd{-webkit-line-clamp:1} .tkr{padding:.75em .9em} #tkFind{padding:.85em .9em;font-size:16px}.rcpt,.meta input,.dly input,.lqin{font-size:16px}.lnbtn{padding:.6em .8em}.p button,.o button,.lqbtn{padding:.6em .8em}.chtf button{padding:.5em} .tabs button{padding:.95em .5em} .dly{padding:.9em .8em} .wnbtn{padding:.95em .8em} #amt,#outAmt{padding:.3em 0} .invh{font-size:11px;top:6px;left:10px;right:10px;gap:.3em;flex-wrap:wrap;row-gap:3px} .invq{padding:10px 11px;margin:-10px -6px;opacity:1} .invm,.invsh{padding:10px 3px;margin:-10px -1px}} @media(pointer:coarse) and (max-width:24em){ .meta{gap:.12em}#th,#lk,#lq,#ln,#wn,#pv{width:32px;height:32px}#slip{width:2em}}</style> <script>const H=document.documentElement;let LS;try{LS=localStorage}catch{LS={}}if(LS.t?LS.t=='d':matchMedia('(prefers-color-scheme:dark)').matches)H.className='d'</script> <button id="sd" title="Sound" aria-label="Toggle sound"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 9.5v5h3.5L12 18V6L7.5 9.5H4z"/><path id="sdw" d="M15.5 9a4 4 0 0 1 0 6"/><path id="sdx" d="M16 9.5l4 4m0-4l-4 4"/></svg></button> <a id="logoLink" href="https://zfi.wei.is/dao/#/dao/1/0x5E58BA0e06ED0F5558f83bE732a4b899a674053E" target="_blank" rel="noopener" aria-label="The DAO that governs this page" title="zFi DAO — the governance that owns this page's lineage"><svg id="logo" viewBox="0 0 400 400" aria-hidden="true"><clipPath id="lgf"><rect width="400" height="400"/></clipPath><g clip-path="url(#lgf)"><rect width="400" height="400"/><path d="M-60-20L460-20L460 90L80 310L460 310L460 420L-60 420L-60 310L320 90L-60 90Z"/></g></svg></a> <svg id="fish" viewBox="0 0 24 14" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" aria-hidden="true"><path d="M2 12.2C7 .8 18 1.8 22 7"/><path d="M2 1.8C7 13.2 18 12.2 22 7"/></svg> <div class="card"> <div class="meta"><span id="net" role="button" tabindex="0" title="Network"></span><span id="addr" role="button" tabindex="0" title="Connect a wallet">Connect</span><label id="slipL" title="Slippage"><span>Slippage </span><input id="slip" type="number" value="0.5" min="0.01" max="10" step="0.1">%</label><button id="lq" title="Liquidity for this pair" aria-label="Toggle liquidity mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3.2c3.6 4 6 7.2 6 10.1a6 6 0 0 1-12 0c0-2.9 2.4-6.1 6-10.1z"/></svg></button><button id="ln" title="Launch a coin" aria-label="Toggle launch mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/></svg></button><button id="wn" title="Claim a .wei name" aria-label="Toggle names mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" fill-rule="evenodd" shape-rendering="crispEdges" aria-hidden="true"><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></button><button id="pv" title="Bridge privately to Base or Robinhood" aria-label="Toggle private bridge mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l7 3v5c0 4.5-3 8.2-7 10-4-1.8-7-5.5-7-10V6l7-3z"/><path d="M9 12l2 2 4-4"/></svg></button><button id="lk" title="Copy shareable link" aria-label="Copy shareable link"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2.5"/><path d="M5.5 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v.5"/></svg></button><button id="th" title="Toggle theme" aria-label="Toggle theme"></button></div> <div class="tabs" role="tablist"><button id="tabSwap" class="on" role="tab" aria-selected="true">Swap</button><button id="tabSend" role="tab" aria-selected="false">Send</button><button id="tabBook" role="tab" aria-selected="false">Orders</button></div> <div class="panel"><div class="hdr"><small id="payL">You pay</small><span class="pill"><span id="fromIcon"></span><select id="fromSel" aria-labelledby="payL"></select><button type="button" class="tkb" id="fromPick" aria-haspopup="listbox" aria-expanded="false"><span class="tkbs"></span><span class="tkbc">▾</span></button></span></div><div class="row" id="amtRow"><input id="amt" type="text" inputmode="decimal" placeholder="0.0" aria-label="Amount to pay" autocomplete="off" spellcheck="false"></div><small id="bal"></small></div> <button id="flip" class="flip" title="Flip tokens" aria-label="Flip tokens"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14m-5-5 5 5 5-5"/></svg></button> <div class="panel" id="rcvPanel"><div class="hdr"><small id="rcvHdr">You receive</small><span class="pill"><span id="toIcon"></span><select id="toSel" aria-labelledby="rcvHdr"></select><button type="button" class="tkb" id="toPick" aria-haspopup="listbox" aria-expanded="false"><span class="tkbs"></span><span class="tkbc">▾</span></button></span></div><div class="row" id="outRow"><input id="outAmt" type="text" inputmode="decimal" placeholder="0.0" aria-label="Amount to receive" autocomplete="off" spellcheck="false"></div><small id="bal1" class="hide"></small><small id="rate" role="status" aria-live="polite"></small><small id="rankBox" class="rank hide"></small></div> <div id="lqPanel" class="hide"><div class="hdr"><small>Liquidity</small><small id="lqSub"></small></div><div id="lqList"></div></div> <div id="lnPanel" class="hide"> <div class="hdr lnhdr"><small id="lnTitle">Launch a coin</small><small id="lnSub">0.4% of each trade goes to the creator</small></div> <label class="dly" id="lnKindL" title="A coin opens a market: supply is minted and seeded into a pool that trades from the first block. A cause opens a fund: backers pay ether for a token that can always be burned back for its share of whatever the cause has not spent.">Launch<select id="lnKind"><option value="coin">Coin</option><option value="cause">Cause</option></select></label> <label class="dly">Name<input id="lnName" maxlength="64" placeholder="Zero Cat" autocomplete="off" spellcheck="false"></label> <label class="dly">Symbol<input id="lnSym" maxlength="16" placeholder="ZCAT" autocomplete="off" spellcheck="false"></label> <label class="dly" id="lnSupplyL">Supply<input id="lnSupply" inputmode="decimal" value="1,000,000,000" autocomplete="off" spellcheck="false"></label> <div class="lngap" id="lnMktL">Market</div> <label class="dly" id="lnMcapL" title="The pool holds NO ether at launch - this is its virtual ether reserve, and it is the only thing deciding where on the curve your market opens. It sets the opening price and how hard that price is to move, which are the same knob: roughly this much buying moves the market halfway. Small numbers mean the first buyer takes a huge share of the supply.">Starting market cap<span class="lnu"><input id="lnMcap" inputmode="decimal" value="30" autocomplete="off" spellcheck="false"><i>ETH</i></span></label> <label class="dly" id="lnAllocL" title="The percentage of total supply paid to the creator at launch. Everything else is seeded into the pool. Capped at 20%.">Creator keeps<span class="lnu"><input id="lnAlloc" inputmode="decimal" value="0" autocomplete="off" spellcheck="false"><i>%</i></span></label> <label class="dly hide" id="lnGoalL" title="What the whole raise comes to. Ten million units are offered and the price is this divided by ten million, so a backer always knows what one unit costs and what it is a claim on.">Goal<span class="lnu"><input id="lnGoal" inputmode="decimal" value="10" autocomplete="off" spellcheck="false"><i>ETH</i></span></label> <label class="dly hide" id="lnDaysL" title="How long backing stays open. After this, the sale closes and nobody else can buy in.">Runs for<span class="lnu"><input id="lnDays" inputmode="decimal" value="30" autocomplete="off" spellcheck="false"><i>days</i></span></label> <label class="dly hide" id="lnVestL" title="How slowly the ether reaches you. The clock starts the moment you launch and runs whether or not anyone has backed you yet, so this wants to be well longer than the raise - what has not been released is exactly what a backer can still burn their token back for, and a release that finishes when backing closes leaves them nothing. The rate is your GOAL divided by this window, so a raise that falls short of the goal is emptied proportionally sooner - a tenth of the goal, a tenth of the window.">Released over<select id="lnVest"><option value="7889238">3 months</option><option value="15778476">6 months</option><option value="31556952" selected>12 months</option><option value="63113904">2 years</option></select></label> <div class="lngap">Optional</div> <label class="dly" id="lnArtRow" title="Held on-chain as contract code, not as a link. Nothing to pin and no gateway to go dark. Editable later, so a rough mark now costs nothing.">Logo<span><img id="lnPrev" alt=""><small id="lnArtNote">none</small><button type="button" id="lnPick" class="lnbtn">Choose</button></span></label> <input id="lnArt" type="file" accept="image/png,image/jpeg,image/webp,image/gif,image/svg+xml" class="hide"> </div> <div id="wnPanel" class="hide"> <div class="hdr lnhdr"><small>Claim a name</small><small id="wnSub">from 0.0005 ETH a year</small></div> <label class="dly">Name<input id="wnName" maxlength="63" placeholder="human" autocomplete="off" spellcheck="false"></label> <label class="dly" id="wnKindL">Ending<select id="wnTld"><option value="wei">.wei</option><option value="id">.id.wei — free</option></select></label> <small id="wnNote" role="status" aria-live="polite"></small> </div> <div id="pvPanel" class="hide"> <div class="hdr lnhdr"><small>Private bridge</small><small id="pvSub">Shielded ether on Ethereum: exit to an L2, withdraw, or pay a request</small></div> <label class="dly" title="Deposited into the confidential pool on Ethereum as a shielded note. Exit it later to an L2 address that nothing on-chain ties to this wallet. The amount, the L2 recipient and the timing of the exit are public; what is hidden is which deposit it came from, so the privacy is only as good as the number of other deposits in the pool.">Amount<span class="lnu"><input id="pvAmt" inputmode="decimal" placeholder="0.01" autocomplete="off" spellcheck="false"><i>ETH</i></span></label> <label class="dly" title="Split the deposit into equal notes that can be spent one at a time. A withdrawal or exit always spends a whole note, so several small notes are what makes a partial spend possible later.">Notes<select id="pvSplit"><option value="1">1</option><option value="2">2</option><option value="4">4</option></select></label> <label class="dly">Arrives on<select id="pvChain"><option value="8453">Base</option><option value="4663">Robinhood</option><option value="1">Ethereum (withdraw)</option></select></label> <label class="dly" title="The address credited on the chain chosen above. Leave blank for this wallet. On Robinhood the unused gas budget refunds here too, so a contract wallet gets its refund at an aliased address.">Recipient<input id="pvTo" placeholder="0x… on that chain, defaults to you" autocomplete="off" spellcheck="false"></label> <label class="dly" title="Through the relay: it proves and settles, paying its own gas; an exit costs a flat fee and this wallet never signs it. From this wallet: the relay only proves and you send the settle yourself, fee-free, but from this wallet. Applies to deposits, exits and payments.">Settle<select id="pvPath"><option value="relay">Through the relay</option><option value="self">From this wallet</option></select></label> <small id="pvKey" role="status"></small> <div id="pvList"></div> <small id="pvNote" role="status" aria-live="polite"></small> </div> <input id="rc" class="rcpt" placeholder="Recipient (optional) — 0x or .eth/.(g)wei" spellcheck="false" autocomplete="off"> <small id="rcvEl"></small> <small id="fcEl" class="hide" role="status" aria-live="polite"></small> <small id="cbEl" class="hide" role="status" aria-live="polite"></small> <small id="lnNote" class="hide" role="status" aria-live="polite"></small> <button id="lnGo" class="primary hide">Launch</button> <button id="wnGo" class="primary hide">Register</button> <button id="pvGo" class="primary hide">Deposit</button> <div id="wnRollPanel" class="hide"><div class="lngap">roll.wei</div><small id="wnRollEl" role="status" aria-live="polite"></small><button id="wnEnter" class="wnbtn hide">Enter the roll</button></div> <label class="dly hide" id="kindL">Order type<select id="kind"><option value="fixed">Fixed limit</option><option value="dutch">Dutch decay</option><option value="floor">Climbing bid</option></select></label> <label class="dly hide" id="dlyL">SLOW<select id="dly"><option value="0">Instant</option><option value="600">10 minutes</option><option value="3600">1 hour</option><option value="86400">1 day</option><option value="259200">3 days</option><option value="604800">7 days</option></select></label> <label class="dly hide" id="tipL" title="A time-locked transfer does not move on its own: once the lock expires somebody has to send the claim transaction. Tick this and the page posts a small ETH tip alongside the deposit, which pays a keeper to claim it for the recipient the moment it matures. The tip is refundable — if you reverse the transfer, the ether comes back to you.">Auto-claim<span class="tipr"><small id="tipNote"></small><input id="tipCk" type="checkbox" aria-describedby="tipNote"></span></label> <label class="dly hide" id="fillL">Fill<select id="fill"><option value="1">Any amount</option><option value="0">All or nothing</option></select></label> <label class="dly hide" id="floorL">Floor total<input id="floorAmt" inputmode="decimal" placeholder="0.0"></label> <label class="dly hide" id="dRestL" title="A Dutch lot has TWO clocks. It decays to your floor over the window above, and then - unless it is given an end - it sits there fillable forever, until you spend gas cancelling. This is how long it rests at the floor before the escrow simply comes back to you.">Rest at floor<select id="dRest"><option value="0">Forever</option><option value="86400">1 day</option><option value="604800">1 week</option><option value="2592000">30 days</option></select></label> <label class="dly hide" id="nftIdL">Token ID<input id="nftId" inputmode="numeric" placeholder="Any from collection" spellcheck="false" autocomplete="off"></label> <label class="dly hide" id="ethModeL" title="Applies when YOU fill an order from the book, or cancel one of yours and the escrow comes back wrapped. It does NOT change what an order you place pays you: boards are denominated in WETH, and a maker's proceeds are transferred as WETH with no unwrap available - so a limit order asking for ETH is an order asking for WETH, and fills as such. Paying is not a setting either: the page spends whichever of your ETH and WETH covers it, and wraps the difference if only the two together do.">Fills &amp; cancels pay<select id="ethMode"><option value="auto">ETH</option><option value="weth">WETH</option></select></label> <small id="floorNote" class="hide" role="status" aria-live="polite"></small> <button id="swap" class="primary">Connect Wallet</button> <div id="listNote" class="hide" role="status" aria-live="polite"></div> <div id="scEl" class="sc hide" role="status" aria-live="polite"></div> <div id="wkWrap" class="hide"><div id="wkPanel" role="dialog" aria-modal="true" aria-labelledby="wkHdr"><div id="wkHdr">Choose a wallet</div><div id="wkList"></div></div></div> <div id="tkPanel" class="tkp hide"><input id="tkFind" type="text" placeholder="Search name, symbol or address" autocomplete="off" spellcheck="false" aria-label="Filter tokens" role="combobox" aria-expanded="true" aria-controls="tkList" aria-autocomplete="list"><div id="tkList" role="listbox"></div></div> <div id="stat" role="status" aria-live="polite"></div> <button id="chTog" class="chtog hide" aria-expanded="false">Chart <span>▾</span></button> <div id="chBox" class="hide"><div class="chtf" id="chTf"></div><div id="chArt"></div><small id="chNote"></small></div> <div id="pos"></div> <button id="bkTog" class="chtog hide" aria-expanded="true">Orderbook <span>▾</span></button> <div id="book"></div> <div id="docPanel" class="docs hide"> <details><summary>Swapping</summary> <p>Every quote is compared across Uniswap v2, v3 and v4, Curve, Sushi, zAMM, Lido, our own Precision pools and the on-chain orderbook, and the best one wins. One route, one signature.</p> <p>A handful of outside aggregators are asked too &mdash; 0x, 1inch, ParaSwap, KyberSwap, Enso, Bebop, OKX. They are compared on what they can be <b>held to</b>, not on what they advertise: a quote less its slippage, against the same number for the chain. One only wins if it beats the chain by a margin wide enough to cover the extra gas, and if none of them answer, nothing changes. Tap the <b>&#9662;</b> beside the rate to see every venue that replied and what each was worth.</p> <p>An outside route is executed through a small adapter that <b>measures what actually arrives</b> and reverts if it is less than your minimum. The adapter is named in this page's own bytes, so a bad answer from a node cannot swap it for another one.</p> <p><b>Slippage</b> is the worst price you will accept &mdash; move past it and the trade reverts rather than filling badly. <b>Price impact</b> is how far your own size moves the price; the page warns as it grows and will not let a very bad one through quietly.</p> <p>ETH and WETH are interchangeable here. The page wraps or unwraps whichever way the pool needs, inside the same transaction.</p></details> <details><summary>Sending</summary> <p>A plain transfer, or a <b>SLOW</b> one: time-locked, and reversible by you at any point until it matures. Useful for large amounts and for addresses you have not paid before.</p> <p>A locked transfer does not deliver itself &mdash; someone must send the claim once it matures. Tick <b>auto-claim</b> and the page posts a small tip so a keeper does that for the recipient. The tip is refundable: reverse the trans
0x68575b07…67c7·#25,914,157·0xf4ef8aab…69927a
<!doctype html> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="referrer" content="no-referrer"> <meta http-equiv="Content-Security-Policy" content="object-src 'none';base-uri 'none';form-action 'none'"> <title>zFi</title> <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 400' width='400' height='400'%3E%3Crect width='400' height='400' fill='%23000'/%3E%3CclipPath id='frame'%3E%3Crect width='400' height='400'/%3E%3C/clipPath%3E%3Cg clip-path='url(%23frame)'%3E%3Cpath d='M-60-20L460-20L460 90L80 310L460 310L460 420L-60 420L-60 310L320 90L-60 90Z' fill='white'/%3E%3C/g%3E%3C/svg%3E" type="image/svg+xml"> <meta name="theme-color" id="tc" content="#fff"> <style>:root{color-scheme:light;--b:#fff;--c:#fff;--p:#fff;--f:#000;--m:#666;--n:#666;--e:#ddd;--k:#000;--kf:#fff;--kh:#333;--fh:#f5f5f5;--e2:#ddd;--w:#c33;--hd:#000;--dm:#767676} .d{color-scheme:dark;--b:#0a0a0a;--c:#0a0a0a;--p:#0a0a0a;--f:#e8e8e0;--m:#888;--n:#888;--e:#2a2a2a;--k:#e8e8e0;--kf:#0a0a0a;--kh:#ccc;--fh:#161616;--e2:#2a2a2a;--w:#e57373;--hd:#333;--dm:#7b7b7b} :focus-visible{outline:2px solid var(--f);outline-offset:2px} @media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}.flip:hover{transform:none}} body{background:var(--b);color:var(--f);font-family:Helvetica,Arial,sans-serif;min-height:100dvh;margin:0;padding:1em .85em;display:flex;flex-direction:column;box-sizing:border-box} .card{position:relative;border:1px solid var(--hd);padding:1em .95em;background:var(--c);max-width:22em;width:100%;margin:auto;box-sizing:border-box}.inv{position:absolute;inset:0;background:var(--c);overflow:hidden;outline:0;touch-action:none;z-index:5;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}#wn{-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}.invx{position:absolute;width:20px;height:20px;transition:opacity .12s;pointer-events:none}.invx.pop{opacity:0}.invs{position:absolute;width:22px;height:20px;display:flex;align-items:flex-start;justify-content:center;pointer-events:none} .invs svg{width:20px;height:20px} .invs::after{content:"";position:absolute;left:-4px;right:-4px;bottom:-4px;height:2px;background:var(--f)} .invs.pop::after{background:var(--w)}.invs.pop{opacity:.35}.invb{position:absolute;width:2px;height:7px;background:#16a34a;pointer-events:none}.invd{position:absolute;width:2px;height:6px;background:#c33;pointer-events:none}.invh{position:absolute;top:4px;left:8px;right:8px;font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--m);pointer-events:none;font-variant-numeric:tabular-nums;display:flex;justify-content:space-between;align-items:center;gap:.6em}.invq{pointer-events:auto;cursor:pointer;padding:1px 5px;border:1px solid var(--e);color:var(--f);opacity:.7}.invm{pointer-events:auto;cursor:pointer;color:var(--f);text-decoration:underline;text-underline-offset:2px}.invmsg{color:var(--m)}.invsh{pointer-events:auto;cursor:pointer;color:var(--m);text-decoration:underline;text-underline-offset:2px}.invq:hover{opacity:1} #addr{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer} #addr.on:hover{color:var(--f);text-decoration:line-through} #sd{position:fixed;top:.85em;right:.85em;width:1.85em;height:1.85em;display:inline-flex;align-items:center;justify-content:center;border:0;background:transparent;cursor:pointer;color:var(--m);opacity:.7;z-index:2;padding:0;transition:opacity .15s}#sd:hover,#sd:focus-visible{opacity:.9} #logoLink{position:fixed;top:.85em;left:.85em;width:1.75em;height:1.75em;z-index:1; display:block;opacity:.85;transition:opacity .15s} #logoLink:hover,#logoLink:focus-visible{opacity:1} #logo{display:block;width:100%;height:100%;pointer-events:none} #logo rect{fill:var(--f)} #logo path{fill:var(--b)} @media(max-width:32em){#logoLink{display:none}} #fish{position:fixed;z-index:1;pointer-events:none;color:var(--m);opacity:.12; left:calc(.9em + env(safe-area-inset-left,0px)); bottom:calc(.9em + env(safe-area-inset-bottom,0px)); width:clamp(1.15em,3.2vw,1.6em);height:auto} .meta{display:grid;grid-template-columns:minmax(0,1fr) auto auto auto auto auto auto auto auto;align-items:center;gap:.3em;font-size:.85em;color:var(--n);margin-bottom:.75em} #slipL{display:inline-flex;align-items:baseline;gap:.15em;font-size:.72em;color:var(--m); letter-spacing:.04em;white-space:nowrap;margin-right:.15em} .meta input{width:3em;font-size:.9em;padding:.15em .1em;border:0;border-bottom:1px solid var(--e2);transition:border-color .15s;background:transparent;text-align:right;outline:0;color:var(--f);-moz-appearance:textfield} .meta input::-webkit-outer-spin-button,.meta input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0} #slip{font-size:1.2em;width:2.4em;font-weight:600;font-variant-numeric:tabular-nums} #th,#lk,#lq,#ln,#wn,#pv{display:inline-flex;align-items:center;justify-content:center;border:0;background:transparent;cursor:pointer;color:var(--m);opacity:.8;width:1.8em;height:1.8em;line-height:1;transition:color .15s,opacity .15s,background .15s} #th:hover,#lk:hover,#lq:hover,#ln:hover,#wn:hover,#pv:hover,#lq.on,#ln.on,#wn.on,#pv.on{opacity:1}#wn{opacity:.55} #th:hover,#lk:hover,#lq:hover,#ln:hover,#wn:hover,#pv:hover{color:var(--f)} .meta input:hover{border-bottom-color:var(--m)} .meta input:focus{border-bottom-color:currentcolor} .panel{background:transparent;border:1px solid var(--e);padding:.95em .9em;margin:0;transition:border-color .15s} .panel+.panel,.flip+.panel{margin-top:-1px} .panel:focus-within,.dly:focus-within,.rcpt:focus-within,.lqin:focus-within{border-color:var(--f);position:relative;z-index:1} .panel small{color:var(--m);font-size:.8em} .hdr small{text-transform:uppercase;letter-spacing:.14em;font-size:.68em} .row{display:flex;gap:.4em;align-items:center;margin-top:.35em} .panel input{font-size:1.65em;font-weight:400;letter-spacing:-.015em;border:0;background:transparent;color:var(--f);outline:none;padding:0;flex:1;min-width:0;width:100%;font-variant-numeric:tabular-nums;transition:font-size .1s} .panel input::placeholder{color:var(--m);opacity:.32} .pill{display:inline-flex;align-items:center;gap:.4em;cursor:pointer;transition:opacity .15s} .pill:hover,.pill:focus-within{opacity:1;color:var(--f)} .panel select,.dly select{appearance:none;-webkit-appearance:none;border:0;background:transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5'%3E%3Cpath d='M0 0h8L4 5z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 0 center;padding:0 .85em 0 0;max-width:8em;font-size:.9em;font-weight:600;color:var(--f);letter-spacing:.02em;cursor:pointer;text-overflow:ellipsis} .panel select:focus{outline:0} option,optgroup{background-color:var(--c);color:var(--f)} svg{vertical-align:middle;flex-shrink:0} .flip{display:flex;align-items:center;justify-content:center;position:relative;z-index:2;margin:-.92em auto;width:1.85em;height:1.85em;border:1px solid var(--e);background:var(--c);cursor:pointer;color:var(--m);padding:0;transition:border-color .15s,color .15s,transform .25s} .flip:hover{border-color:var(--f);color:var(--f);transform:rotate(180deg)} .primary{width:100%;box-sizing:border-box;border:1px solid var(--f);padding:1.15em .9em;overflow-wrap:anywhere;line-height:1.3;font-size:.82em;background:transparent;color:var(--f);cursor:pointer;margin-top:.95em;font-weight:700;text-transform:uppercase;letter-spacing:.14em;transition:border-color .15s,color .15s,background .15s} .primary:hover:not(:disabled){background:var(--fh)} .primary:disabled{border-color:var(--e);color:var(--dm);cursor:not-allowed} .rcpt{width:100%;box-sizing:border-box;margin-top:-1px;padding:.66em .85em;border:1px solid var(--e);background:transparent;transition:border-color .15s;color:var(--f);font-size:.75em;outline:0;font-family:inherit} #lq.on,#ln.on,#wn.on,#pv.on{background:var(--k);color:var(--kf)} .cpr{display:flex;justify-content:space-between;align-items:center;gap:.5em;font-size:.75em;color:var(--n);padding:.6em .8em;border:1px solid var(--e);margin-top:-1px}.cpr b{font-weight:600;color:var(--f)}.cpr a{color:var(--m)}#pvList{margin-top:.6em}#pvKey{display:block;margin-top:.6em;color:var(--m)} .lqrow{padding:.62em .7em;border:1px solid var(--e);margin:-1px 0 0} .lqrow.out{opacity:.55} .lqband{display:flex;align-items:center;gap:.4em;font-size:.82em} .lqfee{color:var(--m);font-size:.72em} .lqtag{font-size:.62em;padding:.1em .4em;border:1px solid var(--e);color:var(--m);letter-spacing:.08em;text-transform:uppercase} .lqmeta{font-size:.7em;color:var(--m);margin-top:.25em;display:flex;align-items:center;gap:.4em} .lqacts{display:flex;gap:.4em;margin-top:.5em} .lqacts>.lqbtn{flex:1} .lqmine{font-size:.7em;margin-top:.25em;display:flex;align-items:center;gap:.4em} .lqempty{padding:.9em .7em;font-size:.75em;color:var(--m);text-align:center} .lqnewtog{width:100%;margin-top:.55em} .lqnew{margin-top:.5em;display:flex;flex-direction:column;gap:.35em;text-align:left} .lqnew .dly{margin-top:.4em;gap:.6em} .lqnew .dly>select{flex:1;min-width:0;max-width:none;text-align:right;text-align-last:right} .lqnew .dly .r{display:flex;gap:.4em;align-items:center} .lqnew .dly .r input{width:6.5em} .lqout{font-size:.72em;color:var(--m);line-height:1.55;min-height:1.1em} .lqout:empty{display:none} .lqbar{position:relative;height:9px;border:1px solid var(--e);margin:.5em 0 .35em} .lqbar>i{position:absolute;top:-4px;width:2px;height:15px;background:var(--f); left:var(--at,50%);transform:translateX(-1.5px)} .lqbar.out>i{background:var(--dm);width:1px} .lqbar>b{position:absolute;top:-1px;bottom:-1px;background:var(--m);opacity:.3; left:0;right:0} .lqnew .primary{margin-top:.5em;min-height:2.6em;font-size:.85em} #listNote{max-width:22em;margin:.5em auto 0;font-size:10px;color:var(--m);letter-spacing:.06em;text-align:center} .spin{display:inline-block;width:.75em;height:.75em;margin-left:.45em;vertical-align:-.1em; border:1px solid var(--e2);border-top-color:var(--m);border-radius:50%;animation:sp .7s linear infinite} @keyframes sp{to{transform:rotate(360deg)}} @keyframes fadein{from{opacity:0}to{opacity:1}} #tkList.fresh>*{animation:fadein .22s ease-out both} .lqbtn{font-size:.8em;padding:.45em .7em;cursor:pointer;border:1px solid var(--e);background:transparent;color:var(--f);font-family:inherit;font-weight:400;letter-spacing:.08em;text-transform:uppercase; white-space:nowrap;flex:0 0 auto;text-align:center;transition:background .15s,color .15s,border-color .15s} .lqbtn:hover:not(:disabled),.lqbtn:focus-visible:not(:disabled){border-color:var(--f)} .lqbtn:disabled{opacity:.4;cursor:not-allowed} .lqadd{margin-top:.45em;display:flex;flex-direction:column;gap:.35em} .lqin{width:100%;box-sizing:border-box;padding:.5em .65em;font:inherit;font-size:.75em;border:1px solid transparent;background:var(--p);transition:border-color .15s;color:var(--f);outline:0} .lqzap{display:flex;align-items:center;gap:.45em;font-size:.7em;color:var(--m);cursor:pointer;margin-bottom:.1em} .lqzap input{margin:0;cursor:pointer;accent-color:var(--f)} .lqpv{font-size:.7em;opacity:.7;min-height:1.2em;line-height:1.5} .lqadd .lqbtn{margin-left:0;align-self:stretch;font-size:.75em;padding:.5em .8em} .rcpt.bad{border-color:var(--w)} #rcvEl{display:block;font-size:.7em;color:var(--m);margin:.35em 0 .6em;word-break:break-all;text-align:center} #rcvEl:empty{display:none} #stat{font-size:.8em;color:var(--n);word-break:break-all;margin-top:.75em;text-align:center} #stat:not(:empty){margin-bottom:.5em}#stat:empty{margin-top:0} #stat a{color:var(--f);text-decoration:underline} .hdr{display:flex;justify-content:space-between;align-items:center;gap:.5em} #lqPanel,#lnPanel{margin-top:.85em} #lqPanel>.hdr{margin-bottom:.5em} .tabs{display:flex;gap:1.35em;margin-bottom:.85em;border-bottom:1px solid var(--e)} .tabs button{flex:1;padding:.5em;font-size:.75em;font-weight:600;text-transform:uppercase;letter-spacing:.08em;border:0;background:transparent;color:var(--m);cursor:pointer;transition:background .15s,color .15s} .tabs button.on{color:var(--f);font-weight:700;box-shadow:inset 0 -2px 0 var(--f)} .tabs button:hover:not(.on),.tabs button:focus-visible:not(.on){color:var(--f)} .hide{display:none!important} #book{margin-top:.4em;max-height:min(28em,58vh);overflow-y:auto;overscroll-behavior:contain} .px{color:var(--f);font-variant-numeric:tabular-nums} .bkh{position:sticky;top:0;z-index:1;background:var(--c);padding-top:.5em} .bkh h6{margin-top:0} #book:empty{display:none} #book h6{font-size:.7em;text-transform:uppercase;letter-spacing:.14em;color:var(--m);margin:.75em 0 .35em;font-weight:400;display:flex;justify-content:space-between} .o{display:flex;justify-content:space-between;align-items:center;flex-wrap:nowrap;gap:.5em;padding:.6em .75em;border:1px solid var(--e);margin-bottom:-1px;font-size:.75em} .o .l{display:flex;align-items:center;gap:.45em;min-width:0;flex:1 1 auto} .o .l>span{min-width:0;flex:1 1 auto} .o svg{flex-shrink:0} .o svg+svg{margin-left:-.5em} .o b{font-weight:600;font-variant-numeric:tabular-nums;display:block} .o i{font-style:normal;color:var(--m);display:block;font-size:.9em;overflow-wrap:break-word} .o i a{word-break:break-all} .o a{color:var(--m)} .o button,.p button{border:1px solid var(--e);padding:.4em .7em;font-size:.85em;font-weight:400;letter-spacing:.08em;text-transform:uppercase;background:transparent;color:var(--f);cursor:pointer;flex:0 0 auto;transition:background .15s,color .15s,border-color .15s} .o button:hover:not(:disabled),.o button:focus-visible:not(:disabled),.p button:hover:not(:disabled),.p button:focus-visible:not(:disabled){border-color:var(--f)} #foot{display:flex;justify-content:center;align-items:baseline;gap:.6em;flex-wrap:wrap;margin-top:1.3em;padding-top:.9em;border-top:1px solid var(--e);font-size:.6em;color:var(--m);letter-spacing:.1em;text-transform:uppercase} #foot span:empty{display:none} #foot a{color:var(--m);text-decoration:none} #foot a:hover{color:var(--f)} .o button:disabled,.p button:disabled{background:transparent;border-color:transparent;color:var(--m);cursor:default;font-weight:400} .bkf{display:flex;flex-wrap:wrap;gap:.3em;margin:.35em 0 .45em} .bkc{font-size:.68em;padding:.35em .6em;border:1px solid var(--e);letter-spacing:.1em;text-transform:uppercase; background:transparent;color:var(--m);cursor:pointer;font-family:inherit;font-weight:600; white-space:nowrap;transition:background .15s,color .15s} .bkc:hover:not(.on),.bkc:focus-visible:not(.on){color:var(--f);border-color:var(--f)} .bkc.on{background:var(--k);color:var(--kf);border-color:var(--k)} .bkc.on:hover{background:var(--kh)} .ow .o{margin-bottom:0} .o.ins{cursor:pointer} .o.ins:hover{background:var(--fh)} .ow{margin-bottom:0} .ic:empty{display:none} .ic{padding:.5em .75em;border:1px solid var(--e);border-top:0;margin-bottom:-1px;font-size:.72em;color:var(--n)} .ic img{display:block;width:100%;max-width:15em;margin:.5em auto 0} .ic dl{display:grid;grid-template-columns:auto 1fr;gap:.15em .6em;margin:0} .ic dt{color:var(--m)} .ic dd{margin:0;color:var(--f);font-variant-numeric:tabular-nums;text-align:right} .tg{display:inline-block;font-size:.85em;padding:.05em .35em;background:var(--e2);color:var(--f);margin-left:.3em} .tg.w{background:transparent;border:0;color:var(--w);font-weight:600; text-transform:uppercase;font-size:.62em;letter-spacing:.1em;padding:0; vertical-align:.1em;line-height:1;text-decoration:none;margin-left:.5em} .o a.tg.w{color:var(--w)} .dly{display:flex;justify-content:space-between;align-items:center;gap:.6em;font-size:.75em;color:var(--n);margin-top:-1px;padding:.7em .8em;border:1px solid var(--e);transition:border-color .15s} .dly select{font-size:1em} #lnArtNote{color:var(--n);font-variant-numeric:tabular-nums} #lnArtNote.set{color:var(--f)} .lnbtn{font:inherit;font-size:.85em;letter-spacing:.08em;text-transform:uppercase;color:var(--f);background:transparent;border:1px solid var(--e);padding:.4em .7em;cursor:pointer;transition:border-color .15s}.wnbtn{display:block;width:100%;box-sizing:border-box;font:inherit;font-size:.75em;color:var(--f);background:var(--p);border:1px solid var(--e);padding:.65em .8em;margin-top:.5em;cursor:pointer;text-align:center;transition:border-color .15s,color .15s}.wnbtn:hover{border-color:var(--f)}.wnbtn.mut{color:var(--m);cursor:default;border-color:transparent}.wnbtn.mut:hover{border-color:transparent} #fcEl{display:block;text-align:center;color:var(--m);font-size:.72em;letter-spacing:.03em;margin-top:.9em} .fclink{font:inherit;color:var(--m);background:none;border:0;border-bottom:1px solid var(--e);padding:0 0 .1em;margin-left:.35em;cursor:pointer;letter-spacing:inherit} .fclink:hover{color:var(--f);border-color:var(--f)} .fclink:disabled{cursor:default;opacity:.6} .lnbtn:hover{border-color:var(--f)} #lnArtRow{gap:.6em} #lnArtRow>span{display:flex;align-items:center;gap:.6em} #lnPrev{width:1.6em;height:1.6em;object-fit:cover;display:none} #lnPrev.set{display:block} .lqon{display:flex;flex-direction:column} .lqon>*{order:2} .lqon>.meta,.lqon>.tabs,.lqon>.panel,.lqon>.flip,.lqon>#rcvPanel{order:0} .lqon>#rc,.lqon>#rcvEl{order:1} .lnon>.tabs .on,.wnon>.tabs .on,.pvon>.tabs .on{color:var(--m);font-weight:600;box-shadow:none}.lnon #chTog,.lnon #chBox,.lnon #bkTog,.lnon #book,.lnon #pos,.pvon #chTog,.pvon #chBox,.pvon #bkTog,.pvon #book,.pvon #pos{display:none!important} .docs{margin-top:.5em} .docs details{border-top:1px solid var(--e)} .docs details:last-child{border-bottom:1px solid var(--e)} .docs summary{cursor:pointer;list-style:none;padding:.75em .2em;font-size:.7em;font-weight:700;text-transform:uppercase;letter-spacing:.02em;color:var(--f);display:flex;justify-content:space-between;align-items:center} .docs summary::-webkit-details-marker{display:none} .docs summary::after{content:"+";color:var(--m);font-weight:400} .docs details[open] summary::after{content:"\2013"} .docs p{margin:0 0 .7em;padding:0 .2em;font-size:.72em;line-height:1.6;color:var(--n)} .docs b{color:var(--f);font-weight:600} .docs em{font-style:normal;color:var(--m)} .docsmore{padding:.75em .2em .2em;font-size:.7em;color:var(--m)}.docsmore a{color:var(--f);text-decoration:underline;text-underline-offset:2px;text-decoration-color:var(--e2);transition:text-decoration-color .15s}.docsmore a:hover{text-decoration-color:var(--f)} .docs summary em{display:inline-flex;align-items:center;margin:0 auto 0 .5em;opacity:.75} .lngap{font-size:.58em;text-transform:uppercase;letter-spacing:.18em;color:var(--dm);margin:1.5em .1em .5em} .lngap+.dly{margin-top:0} .lnhdr{margin-bottom:.6em} .lnhdr>small:first-child{white-space:nowrap} .lqbar.fill>b{right:auto;width:var(--at);opacity:.9} #cbEl{display:block;font-size:.75em;line-height:1.5;color:var(--m);margin-top:.6em} .cbrow{display:flex;justify-content:space-between;gap:.6em;align-items:baseline} .cbact{display:flex;justify-content:space-between;align-items:center;gap:.7em; margin-top:.55em;padding-top:.55em;border-top:1px solid var(--e);color:var(--f)} .cbact>span{min-width:0} .cbmsg{display:block;margin-top:.5em} .lnhdr>small+small{color:var(--dm);text-transform:none;letter-spacing:.01em;font-size:.65em;font-weight:400;text-align:right;text-wrap:balance} #wnRollEl{display:block;font-size:.72em;line-height:1.55;color:var(--m)} #lnNote,#floorNote,#wnNote{display:block;font-size:.7em;line-height:1.45;color:var(--m);text-align:center;text-wrap:balance;margin:.55em .2em 0} #lnNote:empty,#floorNote:empty,#wnNote:empty{display:none} .lnu{display:flex;align-items:baseline;gap:.3em;color:var(--m);flex:0 1 auto;min-width:0} .lnu input{width:7em;flex:0 1 7em;min-width:0} .lnu i{font-style:normal;min-width:2.6em} .tipr{display:flex;align-items:center;gap:.5em} .tipr small{color:var(--m);font-size:.95em} .dly input{width:9em;flex:0 1 9em;min-width:0;border:0;border-bottom:1px solid var(--e2);transition:border-color .15s;background:transparent;color:var(--f);text-align:right;outline:0;font:inherit;font-variant-numeric:tabular-nums} .dly input[type=checkbox]{width:auto;border:0;cursor:pointer;accent-color:var(--f)} .dly input:hover{border-bottom-color:var(--e)} .dly input:focus{border-bottom-color:var(--f)} #pos{margin-top:.4em} #pos:empty{display:none} .ph{margin:1.5em .1em .5em;font-size:.58em;text-transform:uppercase;letter-spacing:.18em;color:var(--dm);font-weight:400} .p{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:.5em;padding:.6em .75em;border:1px solid var(--e);margin-bottom:-1px;font-size:.75em} .p span{flex:1;min-width:8em} .p b{display:block;font-weight:600;font-variant-numeric:tabular-nums;word-break:break-word} .p i{font-style:normal;color:var(--m);display:block;font-size:.9em} .p button{margin-left:auto} #rate{text-wrap:balance} #bal,#bal1,#rate{display:block;font-size:.75em;line-height:1.35;margin-top:.5em;text-align:right} #bal:empty,#bal1:empty,#rate:empty{display:none} #bal a,#bal1 a{color:var(--f);cursor:pointer;text-decoration:underline;font-weight:600} .meta input.warn{color:var(--w);border-bottom-color:var(--w)} #th:focus-visible,#lk:focus-visible,#lq:focus-visible,#ln:focus-visible,#wn:focus-visible,#pv:focus-visible{background:var(--p);color:var(--f)} .chtog{width:100%;margin-top:.5em;padding:.75em .1em;border:0;border-top:1px solid var(--e);background:transparent;color:var(--m);cursor:pointer;font:inherit;font-size:.7em;font-weight:400;text-transform:uppercase;letter-spacing:.08em;transition:color .15s,background .15s} .chtog:hover{color:var(--f)} .chtog span{display:inline-block;transition:transform .2s} .chtog[aria-expanded=true] span{transform:rotate(180deg)} .rank{display:block;margin-top:.45em;color:var(--m);font-size:.92em;line-height:1.5} .rank i{font-style:normal;display:flex;justify-content:space-between;gap:1em;cursor:pointer} .rank i.w{color:var(--f);font-weight:600} .rank i.pin u{text-decoration:underline} .rank u{text-decoration:none;color:var(--m)} #rate .rk{cursor:pointer;padding:0 .25em;opacity:.6} #rate .rk:hover{opacity:1} #chBox{margin-top:.4em} .chtf{display:flex;gap:1.1em;margin-bottom:.55em} .chtf button{flex:0 0 auto;padding:.2em 0;font:inherit;font-size:.65em;font-weight:400;text-transform:uppercase;letter-spacing:.12em;border:0;background:transparent;color:var(--m);cursor:pointer} .chtf button.on{color:var(--f);font-weight:700} .chtf button:disabled{opacity:.35;cursor:default} .chtf .chk{flex:0 0 auto;padding:.2em 0;margin-left:auto} .chtf .chk:hover{color:var(--f)} #chArt{touch-action:pan-y;position:relative;border:1px solid var(--e);overflow:hidden} #chArt svg{display:block;width:100%;height:auto} #chArt .hd{position:absolute;top:.4em;left:.55em;font-size:.7em;color:var(--m);font-variant-numeric:tabular-nums;pointer-events:none} #chArt .hd b{color:var(--f);font-size:1.25em;font-weight:600} #chArt .hd .ohlc{display:block;font-size:.9em;opacity:.85} #chArt .msg{padding:2.2em .8em;text-align:center;font-size:.72em;color:var(--m);line-height:1.6} #chArt .chflip{opacity:.55;font-s
_R` ` R` `@_ T`@Q` R`@_ `$5_R` R` `_R_` R` `@_ T`@Qa%IV[PPPPPPPa%OV[` R`@_ `$5_R` R` `@_ T`@Q"description":"Tokenized representation of a time-locked <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300"><path d="M0 23.7C0 15.6 0 11.5 1`v.5 8.4C3 5.4 5.4 3 8.4 1.5C11.5 `0 15.6 0 23.7 0H276.3C284.4 0 28`8.5 0 291.6 1.5C294.6 3 297 5.4 `ւ298.5 8.4C300 11.5 300 15.6 300 `23.7V276.3C300 284.4 300 288.5 2a98.5 291.6C297 294.6 294.6 297 2a91.6 298.5C288.5 300 284.4 300 2a76.3 300H23.7C15.6 300 11.5 300 a8.4 298.5C5.4 297 3 294.6 1.5 29a1.6C0 288.5 0 284.4 0 276.3V23.7aZ" fill="#0A0A0A" stroke="#fff" a<line x1="20" y1="60" x2="280" ya2="60" stroke="#fff" stroke-widta<text x="20" y="44" font-family=a"Helvetica,Arial,sans-serif" fonat-size="26" fill="#fff">SLOW</tea<g font-family="monospace" text-aanchor="middle" fill="#fff"><text x="150" y="150" font-size=a<text x="150" y="185" font-size=</text><text x="150" y="240" fondata:image/svg+xml;base64,m"attributes":[`{"trait_type":"Asset","value":"{"trait_type":"Delay","value":""},{"trait_type":"Delay (seconds,"display_type":"number"}]}data:application/json;base64,ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef`ghijklmnopqrstuvwxyz0123456789-_`@Ro0123456789abcdef` {"trait_type":"Token","value":"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef`ghijklmnopqrstuvwxyz0123456789-_PPPPPUaI2V[PPPPPaI2V[PPPPUaI2V[PPPPaI2V[PUPaI2V[PhaM7V[PPPPPPaMLV[PPPPPUaMLV[PPPPPaMLV[PPPPUaMLV[PPPPaMLV[PUPaMLV[PhaQSV[PPPPPPPaQYV[aU,W[PPaU#WPPV[aM{\u00000123456789abcdefbtn&quot;&amp;&#39;&lt;&gt;
0x68575b07…67c7·#25,913,808·0x751923f7…d72a9c
name){ const sym = escHTML(symbol), nm = escHTML(clipForDisplay(name||'', 28)); const label = formatDelay(delay); return '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 300">' + `<title>SLOW ${sym} · ${label}</title>` + `<path d="${PLATE_D}" fill="#0A0A0A" stroke="#fff" stroke-width="4"/>` + '<line x1="20" y1="60" x2="280" y2="60" stroke="#fff" stroke-width="2"/>' + '<text x="20" y="44" font-family="Helvetica,Arial,sans-serif" font-size="26" fill="#fff">SLOW</text>' + '<g font-family="monospace" text-anchor="middle" fill="#fff">' + `<text x="150" y="150" font-size="${fit(blen(sym),240,44,14)}">${sym}</text>` + `<text x="150" y="185" font-size="${fit(blen(nm),250,14,8)}" fill="#8A8A8A">${nm}</text>` + `<text x="150" y="240" font-size="${fit(blen(label),250,22,11)}">${label}</text>` + '</g></svg>'; } function paintPreview(){ el.fPreview.hidden = false; const meta = S.meta[(S.token||'').toLowerCase()]; const name = S.token===ZERO ? 'Ether' : (meta&&meta.name) || (S.symbol||''); const blank = !S.symbol && !S.delay; el.nftBox.classList.toggle('waiting', blank); el.nftBox.innerHTML = renderNFT(S.token, S.delay||0, S.symbol||'\u2014', S.symbol?name:''); el.previewHint.textContent = blank ? 'updates as you choose' : 'drawn on chain'; } function ready(){ return !!(S.resolved && S.token && S.symbol && S.amount && S.delay); } function missing(){ if(S.resolving) return 'Looking up that name…'; if(!S.resolved) return 'Enter who it goes to'; if(!S.token || !S.symbol) return 'Pick an asset'; if(!S.amount) return 'Enter an amount'; if(!S.delay) return 'Choose how long it stays locked'; return ''; } function paintFoot(){ const gap = S.sending ? '' : missing(); el.confirmBtn.disabled = S.sending || !!gap; el.footHint.textContent = gap || (!S.sending && !S.account ? 'Connects a wallet first' : ''); if(gap) el.confirmBtn.replaceChildren(document.createTextNode('Send')); } function renderReview(){ const show = ready(); paintFoot(); el.fReview.hidden = !show; if(!show) return; const who = S.resolvedName || shortAddr(S.resolved); el.summary.textContent = `${S.amount} ${S.symbol} → ${who}`; el.recipFull.replaceChildren(); if(S.resolved){ const full = document.createElement('b'); full.textContent = S.resolved; if(S.resolvedName){ el.recipFull.append(document.createTextNode(`${S.resolvedName} resolves to `), full); } else { el.recipFull.append(full); } } const t0=nw(), unlock=t0+S.delay, claw=unlock+GRACE; const dest = S.dest ? cfg(S.dest) : null; const rows= dest ? [ {cls:'now', when:'Now', what:`Leaves Ethereum. The timelock starts when it arrives on ${dest.name}, usually within a few minutes.`}, {cls:'', when:`+ ${fmtTime(S.delay)} after arrival`, what:`${S.resolvedName||shortAddr(S.resolved)} can claim it on ${dest.name}. Until then you can reverse it there.`}, {cls:'warn', when:'+ 30 days', what:`If it was never claimed, you can recover it on ${dest.name}.`}, ] : [ {cls:'now', when:'Now', what:`Locked. You can reverse it and take it back at any point until it unlocks.`}, {cls:'', when:fmtWhen(unlock), what:`Unlocks after ${fmtTime(S.delay)}. ${who} can claim it${S.autoClaim?', or a keeper will settle it for them':''}. You can no longer reverse it.`}, {cls:'warn', when:fmtWhen(claw), what:`If it was never claimed, you can recover it from here.`}, ]; el.timeline.replaceChildren(); for(const r of rows){ const li=document.createElement('li'); if(r.cls) li.className=r.cls; const w=document.createElement('span'); w.className='when'; w.textContent=r.when; const t=document.createElement('span'); t.className='what'; t.textContent=r.what; const box=document.createElement('div'); box.append(w,t); li.appendChild(box); el.timeline.appendChild(li); } el.confirmBtn.replaceChildren( document.createTextNode(`Send ${S.amount} `), Object.assign(document.createElement('span'),{className:'cs',textContent:S.symbol}), document.createTextNode(S.dest ? ` to ${cfg(S.dest).short}` : '')); previewPlan(); paintPreview(); } async function updateTipPreview(){ if(!S.autoClaim){ el.tipPre.textContent=''; return; } el.tipPre.textContent='Estimating…'; const tip=await estimateTip(S.token); if(!S.autoClaim) return; S.tip=tip; el.tipPre.textContent=`Keeper tip ≈ ${fmtAmt(tip,18)} ETH, refunded if ${S.resolvedName||'they'} settle it directly.`; } async function previewPlan(){ el.planNote.textContent=''; if(!S.account||!S.token||!S.amount) return; const c=cfg(); const prefix = S.chainId!=null && S.chainId!==c.id ? `On ${c.name} — your wallet will be asked to switch. ` : ''; try { const meta = S.token===ZERO?{decimals:18}:await tokenMeta(S.token); const wei = parseUnits(S.amount,meta.decimals); const plan = await planDeposit(wei); el.planNote.textContent = prefix + planLabel(plan); } catch(e){ el.planNote.textContent = prefix.trim(); } } async function maxSpendable(){ if(S.balance===null) return null; if(S.token!==ZERO) return S.balance; const units = S.dest ? 600_000n : 220_000n; let reserve=3_000_000_000_000_000n; try { const fh=await rpc('eth_feeHistory',['0x5','latest',[50]]); const base=BigInt(fh.baseFeePerGas.at(-1)); let prio=BigInt(fh.reward?.at(-1)?.[0]||0); if(prio<1_000_000_000n) prio=1_000_000_000n; reserve=units*(base*2n+prio); } catch(e){} if(S.autoClaim){ try { if(!S.tip) S.tip = await estimateTip(S.token); reserve += S.tip; } catch(e){} } if(S.dest && BRIDGES[S.dest]){ try { reserve += (await bridgeCosts(S.dest, S.resolved || S.account, S.delay || 0)).extra; } catch(e){} } return S.balance>reserve ? S.balance-reserve : 0n; } function setAmount(v){ S.amount = v || null; if(el.amountInput.value!==v) el.amountInput.value=v||''; if(v) S.step=Math.max(S.step,2); render(); renderReview(); } function validateAmount(){ const raw=el.amountInput.value.trim(); if(!raw){ S.amount=null; setNote(el.amountNote,''); render(); renderReview(); return; } try { const wei=parseUnits(raw,S.decimals); if(wei<=0n){ S.amount=null; setNote(el.amountNote,'Enter an amount above zero','err'); render(); renderReview(); return; } if(S.balance!==null && wei>S.balance){ S.amount=null; setNote(el.amountNote,`You have ${fmtAmt(S.balance,S.decimals)} ${S.symbol}`,'err'); render(); renderReview(); return; } setNote(el.amountNote,''); S.amount=raw; S.step=Math.max(S.step,2); } catch(e){ S.amount=null; setNote(el.amountNote,e.message,'err'); } render(); renderReview(); } function txVisible(on){ el.txbar.hidden=!on; document.body.classList.toggle('txlive', on); } function txShow(msg, step, hash){ txVisible(true); el.txSpin.hidden=false; el.txMsg.textContent=msg; el.txStep.textContent=step||''; el.txDismiss.hidden=true; if(hash){ el.txLink.hidden=false; el.txLink.href=`${cfg().explorer}/tx/${hash}`; el.txLink.textContent=hash.slice(0,10)+'…'; } else el.txLink.hidden=true; } function txDone(msg, hash){ txVisible(true); el.txSpin.hidden=true; el.txMsg.textContent=msg; el.txStep.textContent=''; el.txDismiss.hidden=false; if(hash){ el.txLink.hidden=false; el.txLink.href=`${cfg().explorer}/tx/${hash}`; el.txLink.textContent=hash.slice(0,10)+'…'; } clearTimeout(txDone.t); txDone.t=setTimeout(()=>txVisible(false), 12000); } const txHide = () => { txVisible(false); clearTimeout(txDone.t); }; async function handleConfirm(){ if(!ready()) return; if(!S.account && !await connect(true)) return; if(!await ensureChain()) return; S.sending=true; paintFoot(); try { if(S.slowDeployed[S.chain]===false) throw new Error(`SLOW is not deployed on ${cfg().name}`); const meta = S.token===ZERO?{decimals:18}:await tokenMeta(S.token); if(S.token!==ZERO && !meta.ok) throw new Error('That token did not answer symbol() or decimals()'); const wei=parseUnits(S.amount,meta.decimals); if(S.balance!==null && wei>S.balance) throw new Error(`You have ${fmtAmt(S.balance,meta.decimals)} ${S.symbol}`); await deposit(); const who=S.resolvedName||shortAddr(S.resolved); txDone(`Sent to ${who}. Locked for ${fmtTime(S.delay)}.`, S.lastHash); setView('send'); resetForm(); loadTransfers(true); } catch(e){ txHide(); fail(e); } finally { S.sending=false; paintFoot(); } } function linkBase(){ return location.origin + location.pathname; } function payLink(){ const q=new URLSearchParams(); if(S.resolvedName||S.resolved) q.set('to', S.resolvedName||S.resolved); if(S.symbol) q.set('asset', S.token===ZERO?'ETH':S.token); if(S.amount) q.set('amount', S.amount); if(S.delay) q.set('delay', String(S.delay)); q.set('chain', String(S.chain)); return `${linkBase()}#/pay?${q}`; } const txLink = id => `${linkBase()}#/tx/${id}?chain=${S.chain}`; async function copyLink(url, label){ try { if(navigator.clipboard?.writeText) await navigator.clipboard.writeText(url); else { const t=document.createElement('textarea'); t.value=url; t.style.position='fixed'; t.style.opacity='0'; document.body.appendChild(t); t.select(); document.execCommand('copy'); t.remove(); } toast(`${label} copied`); } catch(e){ toast(url, 9000); } } function parseRoute(){ const h=location.hash||''; const m=/^#\/(pay|tx)(?:\/([^?]*))?(?:\?(.*))?$/.exec(h); if(!m) return null; return {kind:m[1], arg:m[2]||'', q:new URLSearchParams(m[3]||'')}; } async function applyRoute(){ const r=parseRoute(); if(!r) return false; const want=Number(r.q.get('chain')); if(want && CHAINS[want] && want!==S.chain) switchChain(want); if(r.kind==='pay'){ setView('send'); const asset=(r.q.get('asset')||'').trim(); if(asset){ const known=cfg().tokens.find(t=> t.symbol.toLowerCase()===asset.toLowerCase() || t.address.toLowerCase()===asset.toLowerCase()); if(known){ S.token=known.address.toLowerCase(); S.symbol=known.symbol; S.decimals=known.decimals; S.step=Math.max(S.step,1); } else if(isAddr(asset)){ const m=await tokenMeta(asset); if(m.ok){ S.token=asset.toLowerCase(); S.symbol=m.symbol; S.decimals=m.decimals; S.step=Math.max(S.step,1); } } } const delay=parseInt(r.q.get('delay')||'',10); if(delay>0 && delay<=MAX_DELAY){ S.delay=delay; const preset=qs(`#timeRow [data-time="${delay}"]`); S.delayDisplay = preset ? preset.textContent : fmtTime(delay).toUpperCase(); } const amount=(r.q.get('amount')||'').trim(); if(amount){ el.amountInput.value=amount; } const to=(r.q.get('to')||'').trim(); if(to){ el.recipientInput.value=to; } paintPresets(); refreshBalance(); if(amount) validateAmount(); if(to) await resolveRecipient(to); render(); renderReview(); return true; } if(r.kind==='tx' && /^\d+$/.test(r.arg)){ await openTransferById(r.arg); return true; } return false; } async function openTransferById(id){ try { const [ph]=await mc([{to:SLOW,data:cd(SEL.pendingTransfers,['uint256'],[BigInt(id)])}]); if(!ph) throw new Error('Could not read that transfer'); const [ts,from,to,tid,amt]=decode(['uint256','address','address','uint256','uint256'],ph); if(ts===0n){ toast('That transfer has already been settled, reversed or recovered.',6000); setView('take'); return; } const {token,delay}=decodeId(tid); await tokenMeta(token).catch(()=>{}); const m=S.meta[token.toLowerCase()]||{symbol:'???',decimals:18}; const t={id:String(id), from, to, tokenId:tid.toString(), token, symbol:m.symbol, decimals:m.decimals, amountRaw:amt, amount:fmtAmt(amt,m.decimals), timestamp:Number(ts), delay, unlockTime:Number(ts)+delay}; const me=(S.account||'').toLowerCase(); S.tab = me===to.toLowerCase() ? 'inbound' : 'outbound'; setView('take'); openDetail(t, m.symbol==='???'?shortAddr(token):m.symbol); } catch(e){ fail(e); } } const THEMES=[ {key:'system', label:'System', sub:'follow the OS', glyph:'\u25d0'}, {key:'light', label:'Light', sub:'', glyph:'\u2600'}, {key:'dark', label:'Dark', sub:'', glyph:'\u263e'}, ]; const sysDark = () => matchMedia('(prefers-color-scheme: dark)').matches; function applyTheme(){ const light = S.theme==='light' || (S.theme==='system' && !sysDark()); document.body.classList.toggle('light',light); el.themeBtn.textContent = (THEMES.find(t=>t.key===S.theme)||THEMES[0]).glyph; el.themeBtn.title = `Appearance: ${S.theme}`; } function setTheme(k){ S.theme=k; try { k==='system' ? localStorage.removeItem('slow.theme') : localStorage.setItem('slow.theme',k); } catch(e){} applyTheme(); } function openPicker(modal, list, items, current){ return new Promise(resolve=>{ let done=false; const finish=v=>{ if(done) return; done=true; modal.__cancel=null; hideModal(modal); resolve(v); }; list.replaceChildren(); for(const it of items){ const b=document.createElement('button'); b.type='button'; b.className='pick'; b.setAttribute('aria-current',String(it.key===current)); const ico=document.createElement('span'); ico.className='ico'; if(it.icon){ const t=document.createElement('span'); t.innerHTML=it.icon; if(t.firstElementChild) ico.appendChild(t.firstElementChild); } else if(it.img){ const i=document.createElement('img'); i.src=it.img; i.alt=''; i.loading='lazy'; i.onerror=()=>{ i.remove(); ico.textContent=(it.label||'?').slice(0,1); }; ico.appendChild(i); } else if(it.glyph){ ico.textContent=it.glyph; } b.appendChild(ico); const lab=document.createElement('span'); lab.className='lbl2'; const nm=document.createElement('b'); nm.textContent=it.label; lab.appendChild(nm); if(it.sub){ const sm=document.createElement('small'); sm.textContent=it.sub; lab.appendChild(sm); } b.appendChild(lab); const tick=document.createElement('span'); tick.className='tick'; tick.textContent = it.key===current ? '\u2713' : ''; b.appendChild(tick); b.onclick=()=>finish(it.key); list.appendChild(b); } modal.__cancel=()=>finish(null); showModal(modal); }); } /* ══════════════════════════════════════════════════════════════════ Wiring ══════════════════════════════════════════════════════════════════ */ try { S.theme = localStorage.getItem('slow.theme') || 'system'; } catch(e){ S.theme='system'; } if(!THEMES.some(t=>t.key===S.theme)) S.theme='system'; applyTheme(); matchMedia('(prefers-color-scheme: dark)').addEventListener?.('change',()=>{ if(S.theme==='system') applyTheme(); }); el.themeBtn.onclick=async()=>{ const k=await openPicker(el.themeModal, el.themeList, THEMES.map(t=>({key:t.key,label:t.label,sub:t.sub,glyph:t.glyph})), S.theme); if(k) setTheme(k); }; el.themeCancel.onclick=()=>el.themeModal.__cancel?.(); el.chainBtn.onclick=async()=>{ const id=await openPicker(el.chainModal, el.chainList, CHAIN_IDS.map(id=>({ key:id, label:CHAINS[id].name, icon:ICONS[id], sub:CHAINS[id].tokens.map(t=>t.symbol).join(' · '), })), S.chain); if(id) switchChain(id); }; el.chainCancel.onclick=()=>el.chainModal.__cancel?.(); el.walletBtn.onclick=()=>S.account?disconnect():connect(true); el.walletCancel.onclick=()=>el.walletModal.__cancel?.(); el.homeBtn.onclick=()=>setView('home'); el.sendBox.onclick=()=>setView('send'); el.takeBox.onclick=async()=>{ setView('take'); if(!S.account) await connect(true); }; el.segSend.onclick=()=>setView('send'); el.segTake.onclick=async()=>{ setView('take'); if(!S.account) await connect(true); }; el.segGuard.onclick=async()=>{ setView('guard'); if(!S.account) await connect(true); }; el.gSet.onclick=()=>openGuardModal('set'); el.gRemove.onclick=()=>openGuardModal('remove'); el.gAddWard.onclick=()=>openGuardModal('ward'); el.gScan.onclick=scanWards; el.gCommit.onclick=commitGuardianTx; el.gCancel.onclick=cancelGuardianTx; el.guardApply.onclick=applyGuardModal; el.gaApply.onclick=()=>{ const w=S.guard.wards.find(x=>x.addr===el.gaModal.dataset.ward); if(w) guardApprove(w); }; el.gaCancel.onclick=()=>hideModal(el.gaModal); el.guardCancel.onclick=()=>hideModal(el.guardModal); el.guardInput.addEventListener('keydown',e=>{ if(e.key==='Enter') el.guardApply.click(); }); el.destRow.addEventListener('click',e=>{ const b=e.target.closest('.opt'); if(!b||b.disabled) return; const id=Number(b.dataset.dest); S.dest = id===MAINNET ? null : id; buildDestRow(); render(); renderReview(); }); el.cryptoGrid.addEventListener('click',e=>{ const b=e.target.closest('.crypto'); if(!b) return; S.token=b.dataset.token.toLowerCase(); S.symbol=b.dataset.symbol; S.decimals=Number(b.dataset.decimals)||18; S.step=Math.max(S.step,1); setNote(el.amountNote,''); tokenMeta(S.token).then(m=>{ if(S.token===b.dataset.token.toLowerCase()){ S.decimals=m.decimals; paintPresets(); } }); paintPresets(); refreshBalance(); buildDestRow(); if(S.amount) validateAmount(); }); el.amountRow.addEventListener('click',e=>{ const o=e.target.closest('.opt'); if(!o||o.disabled||!o.dataset.amount) return; setAmount(o.dataset.amount); setNote(el.amountNote,''); }); let amtTimer; el.amountInput.addEventListener('input',()=>{ clearTimeout(amtTimer); amtTimer=setTimeout(validateAmount,220); }); el.amountInput.addEventListener('blur',validateAmount); el.maxBtn.onclick=async()=>{ const max=await maxSpendable(); if(max===null){ toast('Connect a wallet to see your balance'); return; } if(max<=0n){ setNote(el.amountNote,'Not enough ETH left over to pay for the transaction','err'); return; } setAmount(formatUnits(max,S.decimals)); setNote(el.amountNote, S.token===ZERO ? 'Leaves enough ETH to pay for this transaction' : ''); }; el.timeRow.addEventListener('click',e=>{ const o=e.target.closest('.opt'); if(!o) return; if(o===el.customTimeOpt){ showModal(el.timeModal); return; } S.delay=parseInt(o.dataset.time); S.delayDisplay=o.textContent; render(); renderReview(); }); el.timeApply.onclick=()=>{ const r=fmtCustomTime({d:el.dInput.value,h:el.hInput.value,m:el.mInput.value,s:el.sInput.value}); if(!r.seconds){ toast('Enter a delay longer than zero'); return; } if(r.seconds>MAX_DELAY){ toast('The longest delay is 100 years'); return; } S.delay=r.seconds; S.delayDisplay=r.display; hideModal(el.timeModal); render(); renderReview(); }; el.customTokenOpt.onclick=()=>{ el.tokenInput.value=''; setNote(el.tokenStatus,''); showModal(el.tokenModal); }; el.tokenApply.onclick=async()=>{ const addr=el.tokenInput.value.trim(); if(!isAddr(addr)){ setNote(el.tokenStatus,'That is not a contract address','err'); return; } setNote(el.tokenStatus,'Reading the token…'); const meta=await tokenMeta(addr); if(!meta.ok){ setNote(el.tokenStatus,'That address did not answer symbol() or decimals()','err'); return; } S.token=addr.toLowerCase(); S.symbol=meta.symbol; S.decimals=meta.decimals; S.step=Math.max(S.step,1); hideModal(el.tokenModal); paintPresets(); refreshBalance(); buildDestRow(); if(S.amount) validateAmount(); }; el.tokenInput.addEventListener('keydown',e=>{ if(e.key==='Enter') el.tokenApply.click(); }); [el.dInput,el.hInput,el.mInput,el.sInput].forEach(i=>i.addEventListener('keydown',e=>{ if(e.key==='Enter') el.timeApply.click(); })); el.autoClaimChk.onchange=()=>{ S.autoClaim=el.autoClaimChk.checked; if(!S.autoClaim) S.tip=0n; updateTipPreview(); renderReview(); }; let ensTimer; el.recipientInput.addEventListener('input',()=>{ const v=el.recipientInput.value.trim(); S.resolved=null; S.resolvedName=null; S.resolveSeq++; renderReview(); clearTimeout(ensTimer); if(!v){ setNote(el.ens,''); return; } ensTimer=setTimeout(()=>resolveRecipient(v),450); }); el.recipientInput.addEventListener('keydown',e=>{ if(e.key!=='Enter') return; if(ready()) el.confirmBtn.click(); else el.amountInput.focus(); }); el.amountInput.addEventListener('keydown',e=>{ if(e.key==='Enter' && ready()) el.confirmBtn.click(); }); el.confirmBtn.onclick=handleConfirm; el.detailClose.onclick=()=>hideModal(el.detailModal); el.exitClose.onclick=()=>hideModal(el.exitModal); el.exitTo.oninput=el.exitData.oninput=updateExit; el.exitSend.onclick=doExit; el.exitCopy.onclick=()=>{ if(S.exitPlan&&!S.exitPlan.error) copyLink(exitText(S.exitPlan,S.exit),'Calls'); }; el.detailShare.onclick=()=>{ if(S.detail) copyLink(txLink(S.detail.t.id),'Link'); }; el.shareBtn.onclick=()=>copyLink(payLink(),'Request link'); el.recipeBtn.onclick=()=>{ const r=depositRecipe(); if(!r){ toast('Fill in the transfer first'); return; } copyLink(recipeText(r), 'Call'); }; el.txDismiss.onclick=txHide; el.helpBtn.onclick=()=>showModal(el.helpModal); el.helpClose.onclick=()=>hideModal(el.helpModal); qsa('.tab[data-tab]').forEach(t=>t.addEventListener('click',()=>{ S.tab=t.dataset.tab; paintTabs(); render(); renderList(); })); addEventListener('focus',()=>{ if(S.view==='take'&&S.account) loadTransfers(false); }); (()=>{ const parts=(location.hostname||'').split('.'); const fromHost = /^0x[0-9a-fA-F]{40}$/.test(parts[0]||'') && /^\d+$/.test(parts[1]||'') ? Number(parts[1]) : null; let saved=null; try { saved=Number(localStorage.getItem('slow.chain'))||null; } catch(e){} S.chain = (fromHost&&CHAINS[fromHost]) ? fromHost : (saved&&CHAINS[saved]) ? saved : MAINNET; })(); buildTokenGrid(); buildDestRow(); paintChain(); paintNet(); paintWallet(); setView('home'); checkDeployed(); addEventListener('hashchange', applyRoute); eagerConnect().then(applyRoute); })(); </script> </body> </html>
0x68575b07…67c7·#25,913,807·0x0ea674b2…fdf83d
`Awaiting recipient · recoverable in ${fmtTime(t.unlockTime+GRACE-n)}`, act:null}; } const progressOf = t => t.delay>0 ? Math.max(0,Math.min(100,((nw()-t.timestamp)/t.delay)*100)) : 100; const RANK = {ready:0, recover:1, pending:2, settling:3}; function paintTabs(){ qsa('.tab[data-tab]').forEach(t=>t.setAttribute('aria-selected',String(t.dataset.tab===S.tab))); } function renderList(){ const c = el.list; el.tabUnlocked.hidden = !S.unlocked.length; paintTabs(); if(S.tab==='unlocked' && !S.unlocked.length) S.tab='inbound'; const list = S.tab==='outbound' ? S.out : S.inb; c.replaceChildren(); if(S.account && S.tab==='unlocked'){ renderUnlocked(); return; } if(!S.account){ c.appendChild(emptyState('Connect a wallet to see your transfers.','Connect',()=>connect(true))); return; } if(S.loading){ const w=document.createElement('div'); w.className='empty'; w.appendChild(loaderNode()); const p=document.createElement('p'); p.textContent=`Reading ${cfg().name}…`; w.appendChild(p); c.appendChild(w); return; } if(S.slowDeployed[S.chain]===false){ c.appendChild(emptyState(`SLOW is not deployed on ${cfg().name} yet — there is nothing to read here.`, 'Switch network', ()=>el.chainBtn.click())); return; } if(S.listError){ c.appendChild(emptyState(S.listError, 'Try again', ()=>loadTransfers(true))); return; } if(!list.length){ c.appendChild(S.tab==='outbound' ? emptyState('Nothing sent yet.','Send one',()=>setView('send')) : emptyState('Nothing inbound.')); return; } const sorted=[...list].sort((a,b)=>{ const ra=RANK[statusOf(a).key], rb=RANK[statusOf(b).key]; return ra!==rb ? ra-rb : a.unlockTime-b.unlockTime; }); for(const t of sorted) c.appendChild(makeRow(t)); startTimer(); } function renderUnlocked(){ const c=el.list; c.replaceChildren(); if(!S.unlocked.length){ c.appendChild(emptyState('Nothing unlocked.')); return; } const note=document.createElement('div'); note.className='empty'; note.style.padding='0 0 6px'; note.textContent = S.hasGuardian ? 'Settled, and held as a wrapper. Your guardian approves each withdrawal before it can leave.' : 'Settled, and held as a wrapper. Withdraw to receive the underlying.'; c.appendChild(note); for(const u of S.unlocked){ const row=document.createElement('div'); row.className='tx'; const bar=document.createElement('span'); bar.className='tx-bar'; bar.style.background=railOf(u.symbol,u.token); const open=document.createElement('button'); open.type='button'; open.className='tx-open'; const l1=document.createElement('span'); l1.className='tx-line'; const amt=document.createElement('span'); amt.className='tx-amt'; amt.textContent=`${u.amount} ${u.symbol==='???'?shortAddr(u.token):u.symbol}`; const party=document.createElement('span'); party.className='tx-party'; party.textContent='unlocked'; l1.append(amt,party); const meta=document.createElement('span'); meta.className='tx-meta'; const st=document.createElement('span'); st.className='tx-status'; st.textContent = S.hasGuardian ? 'Needs a guardian-approved withdrawal' : 'Ready to withdraw'; meta.appendChild(st); open.append(l1,meta); open.onclick=()=>showExit(u); const act=document.createElement('button'); act.type='button'; act.className='tx-act'; act.textContent='Withdraw'; act.onclick=e=>{ e.stopPropagation(); doWithdraw(u); }; row.append(bar,open,act); c.appendChild(row); } } function emptyState(text, cta, fn){ const w=document.createElement('div'); w.className='empty'; const p=document.createElement('p'); p.textContent=text; w.appendChild(p); if(cta){ const b=document.createElement('button'); b.className='btn'; b.style.width='auto'; b.style.padding='0 22px'; b.textContent=cta; b.onclick=fn; w.appendChild(b); } return w; } function makeRow(t){ const st=statusOf(t); const sym = t.symbol==='???' ? shortAddr(t.token) : t.symbol; const row=document.createElement('div'); row.className='tx'+(st.key==='ready'?' act':st.key==='recover'?' act warn-row':''); row.dataset.tid=t.id; const bar=document.createElement('span'); bar.className='tx-bar'; bar.style.background=railOf(t.symbol,t.token); row.appendChild(bar); const open=document.createElement('button'); open.type='button'; open.className='tx-open'; open.setAttribute('aria-label',`${t.amount} ${sym} ${S.tab==='outbound'?'to':'from'} ${shortAddr(S.tab==='outbound'?t.to:t.from)} — ${st.text}. Details.`); const l1=document.createElement('span'); l1.className='tx-line'; const amt=document.createElement('span'); amt.className='tx-amt'; amt.textContent=`${t.amount} ${sym}`; const party=document.createElement('span'); party.className='tx-party'; party.textContent=(S.tab==='outbound'?'→ ':'← ')+shortAddr(S.tab==='outbound'?t.to:t.from); l1.append(amt,party); const track=document.createElement('span'); track.className='tx-track'; const fill=document.createElement('span'); fill.className='tx-fill'; fill.style.width=progressOf(t)+'%'; fill.dataset.tid=t.id; track.appendChild(fill); const l2=document.createElement('span'); l2.className='tx-meta'; const status=document.createElement('span'); status.className='tx-status'; status.textContent=st.text; const flags=document.createElement('span'); const f=[]; if(t.tipped) f.push('keeper'); if(t.guardianPending) f.push('guardian'); flags.textContent=f.join(' · '); l2.append(status,flags); open.append(l1,track,l2); open.onclick=()=>openDetail(t,sym); row.appendChild(open); if(st.act){ const act=document.createElement('button'); act.type='button'; act.className='tx-act'; act.textContent=st.act; act.setAttribute('aria-label',`${st.act} ${t.amount} ${sym}`); act.onclick=e=>{ e.stopPropagation(); runAction(t,st.key); }; row.appendChild(act); } return row; } function runAction(t,key){ if(key==='ready') return doClaim(t); if(key==='recover') return doClawback(t); if(key==='pending' && S.tab==='outbound') return doReverse(t); } function detailRows(t,sym){ const st=statusOf(t); const rows=[ ['Amount', `${t.amount} ${sym}`], [S.tab==='outbound'?'To':'From', S.tab==='outbound'?t.to:t.from], ['Status', st.text], ['Timelock', fmtTime(t.delay)], ['Sent', new Date(t.timestamp*1000).toLocaleString()], ]; if(t.guardianPending && S.tab==='outbound' && nw()<t.unlockTime) rows.push(['Guardian','Required to reclaim']); if(S.hasGuardian && S.tab==='inbound' && nw()>=t.unlockTime) rows.push(['Guardian','Required to withdraw']); if(t.tipped) rows.push(['Keeper tip', S.tab==='inbound'?'A keeper will settle this':'Attached, refundable']); return rows; } function openDetail(t,sym){ S.detail={t,sym}; el.detailShare.hidden=false; el.detailTitle.textContent = S.tab==='outbound'?'Outbound':'Inbound'; el.detailRows.replaceChildren(); for(const [k,v] of detailRows(t,sym)){ const row=document.createElement('div'); row.className='kv'; const dt=document.createElement('dt'), dd=document.createElement('dd'); dt.textContent=k; dd.textContent=v; row.append(dt,dd); el.detailRows.appendChild(row); } const mine = !!S.account && !!t.to && t.to.toLowerCase()===S.account.toLowerCase(); el.detailForget.hidden = !(S.tab==='inbound' && mine); el.detailForget.onclick = el.detailForget.hidden ? null : ()=>doForgetInbound(t); const st=statusOf(t), btn=el.detailAction; btn.className='btn'+(st.key==='recover'?' danger':''); if(st.act){ btn.hidden=false; btn.disabled=false; btn.textContent=`${st.act} ${t.amount} ${sym}`; btn.onclick=()=>{ hideModal(el.detailModal); runAction(t,st.key); }; } else { btn.hidden=true; btn.onclick=null; } showModal(el.detailModal); } function tick(){ if(S.view!=='take'||document.hidden) return; const list=S.tab==='outbound'?S.out:S.inb; const byId=new Map(list.map(t=>[t.id,t])); let restructure=false; for(const f of qsa('.tx-fill')){ const t=byId.get(f.dataset.tid); if(!t) continue; f.style.width=progressOf(t)+'%'; } for(const r of qsa('.tx')){ const t=byId.get(r.dataset.tid); if(!t) continue; const st=statusOf(t); const node=r.querySelector('.tx-status'); if(node && node.textContent!==st.text) node.textContent=st.text; const wasAct=r.classList.contains('act'); if(wasAct!==(st.key==='ready'||st.key==='recover')) restructure=true; } if(restructure) renderList(); } function startTimer(){ if(!S.timer) S.timer=setInterval(tick,5000); } function stopTimer(){ if(S.timer){ clearInterval(S.timer); S.timer=null; } } document.addEventListener('visibilitychange',()=>{ if(document.hidden){ stopTimer(); return; } if(S.view==='take'){ startTimer(); tick(); loadTransfers(false); } }); const wardsKey = () => `slow.wards.${S.chain}.${(S.account||'').toLowerCase()}`; function loadWards(){ try { return JSON.parse(localStorage.getItem(wardsKey())||'[]').filter(isAddr); } catch(e){ return []; } } function saveWards(list){ try { localStorage.setItem(wardsKey(), JSON.stringify([...new Set(list.map(a=>a.toLowerCase()))])); } catch(e){} } async function wardsFromIndex(){ const key='idx:'+S.chain; if(S.guard[key]===false) return []; try { if(S.guard[key]===undefined){ const code=await rpc('eth_getCode',[SLOW,'latest'],S.chain); S.guard[key] = typeof code==='string' && code.includes(SEL.wardsOf.slice(2)); if(!S.guard[key]) return []; } const [r]=await mc([{to:SLOW,data:cd(SEL.wardsOf,['address'],[S.account])}]); if(!r) return []; const d=strip(r), n=parseInt(d.slice(64,128),16); const out=[]; for(let i=0;i<n;i++) out.push('0x'+d.slice(128+i*64+24,128+(i+1)*64)); return out; } catch(e){ return []; } } async function wardsFromLogs(){ const c=cfg(); try { const logs=await rpc('eth_getLogs',[{ address:SLOW, topics:[TOPIC_GUARDIAN_SET, null, '0x'+word(S.account)], fromBlock:'0x'+(c.deployBlock||0).toString(16), toBlock:'latest', }],c.id); if(!Array.isArray(logs)) return []; return logs.map(l=>'0x'+String(l.topics?.[1]||'').slice(-40)).filter(isAddr); } catch(e){ return []; } } async function scanWards(){ if(!S.account) return; S.guard.scanning=true; renderGuard(); try { const [a,b]=await Promise.all([wardsFromIndex(),wardsFromLogs()]); const found=[...a,...b].map(x=>x.toLowerCase()).filter(isAddr); const before=loadWards(); const merged=[...new Set([...before,...found])]; saveWards(merged); const added=merged.length-before.length; toast(added ? `Found ${added} more.` : (found.length ? 'Nothing new.' : 'Nothing found — add an address instead.')); } finally { S.guard.scanning=false; await loadGuard(); } } async function loadGuard(){ if(!S.account) return; const acct=S.account, chain=S.chain; const stale=()=>S.account!==acct||S.chain!==chain; S.guard.loading=true; renderGuard(); try { const [gh,ph] = await mc([ {to:SLOW,data:cd(SEL.guardians,['address'],[acct])}, {to:SLOW,data:cd(SEL.pendingGuardian,['address'],[acct])}, ]); if(stale()) return; const guardian = (gh && strip(gh).length>=40) ? '0x'+gh.slice(-40) : ZERO; S.guard.guardian = (!isAddr(guardian) || guardian.toLowerCase()===ZERO) ? null : guardian; S.guard.pending = null; if(ph){ const [g,at] = decode(['address','uint256'], ph); if(at!==0n) S.guard.pending={guardian:g, at:Number(at)}; } const stored=loadWards(); if(stored.length){ const res=await mc(stored.map(w=>({to:SLOW,data:cd(SEL.guardians,['address'],[w])}))); if(stale()) return; S.guard.wards = stored.map((w,i)=>({ addr:w, mine: !!res[i] && ('0x'+res[i].slice(-40)).toLowerCase()===acct.toLowerCase(), txs:null, })); } else S.guard.wards=[]; } catch(e){ if(!stale()) toast('Could not read guardian state — '+errText(e),5000); } finally { if(!stale()){ S.guard.loading=false; renderGuard(); } } } async function loadWardTransfers(w){ const acct=S.account; try { const [oh, nh] = await mc([ {to:SLOW,data:cd(SEL.getOut,['address'],[w.addr])}, {to:SLOW,data:cd(SEL.guardianNonces,['address'],[w.addr])}, ]); w.nonce = nh ? decode(['uint256'],nh)[0] : undefined; const ids = oh ? decode(['uint256[]'],oh)[0] : []; const pts = ids.length ? await mc(ids.map(id=>({to:SLOW,data:cd(SEL.pendingTransfers,['uint256'],[id])}))) : []; const live=[]; ids.forEach((id,i)=>{ if(!pts[i]) return; try { const [ts,from,to,tid,amt]=decode(['uint256','address','address','uint256','uint256'],pts[i]); if(ts!==0n) live.push({id:id.toString(),from,to,tid,amt,ts:Number(ts)}); } catch(e){} }); const tokens=[...new Set(live.map(t=>decodeId(t.tid).token))]; await Promise.all(tokens.map(t=>tokenMeta(t).catch(()=>{}))); if(S.account!==acct) return; w.txs=live.map(t=>{ const {token,delay}=decodeId(t.tid); const m=S.meta[token.toLowerCase()]||{symbol:'???',decimals:18}; return {...t, token, symbol:m.symbol, decimals:m.decimals, amount:fmtAmt(t.amt,m.decimals), delay, unlockTime:t.ts+delay}; }); renderGuard(); } catch(e){ w.txs=[]; renderGuard(); toast('Could not read that account'); } } async function guardApprove(w){ if(!guardWritable()) return; const to=(el.gaTo.value||'').trim(); const idRaw=(el.gaId.value||'').trim(); const amtRaw=(el.gaAmt.value||'').trim(); if(!isAddr(to)){ setNote(el.gaNote,'Enter the address the funds will go to','err'); return; } if(!/^\d+$/.test(idRaw)){ setNote(el.gaNote,'Enter the position id they will draw from','err'); return; } let amount; try { amount=BigInt(amtRaw); if(amount<=0n) throw 0; } catch(e){ setNote(el.gaNote,'Enter the exact amount in base units','err'); return; } try { setNote(el.gaNote,'Deriving the operation id…'); const sel = el.gaKind.value==='transfer' ? SEL.predictTransferId : SEL.predictWithdrawalId; const [r] = await mc([{to:SLOW, data:cd(sel,['address','address','uint256','uint256'],[w.addr,to,BigInt(idRaw),amount])}]); if(!r) throw new Error('Could not derive the operation id'); const opId = decode(['uint256'],r)[0]; hideModal(el.gaModal); await slowTx(cd(SEL.approveTransfer,['address','uint256'],[w.addr,opId]), 'Approving one operation','Confirm in your wallet'); toast('Approved. Good for exactly this operation, once.',6000); loadGuard(); } catch(e){ fail(e); setNote(el.gaNote,''); } } function openApprove(w){ el.gaModal.dataset.ward=w.addr; el.gaTo.value=w.addr; el.gaId.value=''; el.gaAmt.value=''; setNote(el.gaNote,''); el.gaWard.textContent=shortAddr(w.addr); el.gaNonce.textContent = w.nonce!==undefined ? `their nonce is ${w.nonce}` : ''; showModal(el.gaModal); } function renderGuard(){ const g=S.guard; el.gNotDeployed.hidden = S.slowDeployed[S.chain]!==false; if(!el.gNotDeployed.hidden) el.gNotDeployed.textContent = `SLOW is not deployed on ${cfg().name} yet. Guardian settings there do not exist.`; el.gHint.textContent = g.scanning ? 'scanning…' : g.loading ? 'reading…' : ''; el.gScan.disabled = !!(g.loading||g.scanning); const cur=el.gCurrent; cur.replaceChildren(); cur.classList.toggle('none',!g.guardian); if(g.guardian){ const a=document.createElement('span'); a.textContent=g.guardian; const b=document.createElement('span'); b.className='sub'; b.textContent='Must co-sign every outflow from your account.'; cur.append(a,b); } else { const a=document.createElement('span'); a.textContent='None set'; const b=document.createElement('span'); b.className='sub'; b.textContent='Outflows need no second signature. Setting the first guardian takes effect immediately.'; cur.append(a,b); } el.gRemove.disabled=!g.guardian; const pf=el.gPendingField; pf.hidden=!g.pending; if(g.pending){ const ready = nw()>=g.pending.at; const p=el.gPending; p.replaceChildren(); const a=document.createElement('span'); a.textContent = g.pending.guardian.toLowerCase()===ZERO ? 'Remove the guardian' : `→ ${g.pending.guardian}`; const b=document.createElement('span'); b.className='sub'; b.textContent = ready ? 'Ready to commit. You or your current guardian can land it.' : `Commits in ${fmtTime(g.pending.at-nw())}. Either you or your current guardian can still cancel.`; p.append(a,b); el.gCommit.disabled=!ready; el.gCancel.disabled=ready; } const wrap=el.gWards; wrap.replaceChildren(); if(g.loading||g.scanning){ const w=document.createElement('div'); w.className='empty loader-sm'; w.appendChild(loaderNode()); const p=document.createElement('p'); p.textContent = g.scanning ? 'Looking for accounts that named you…' : `Reading ${cfg().name}…`; w.appendChild(p); wrap.appendChild(w); return; } el.gWardHint.textContent = g.wards.length ? `${g.wards.filter(w=>w.mine).length} active` : ''; if(!g.wards.length){ const e=document.createElement('div'); e.className='empty'; e.textContent='None. Add an address that named you as its guardian.'; wrap.appendChild(e); } for(const w of g.wards){ const box=document.createElement('div'); box.className='ward'; const h=document.createElement('button'); h.type='button'; h.className='ward-h'; const nm=document.createElement('span'); nm.textContent=shortAddr(w.addr); const cnt=document.createElement('span'); cnt.className='cnt'; cnt.textContent = !w.mine ? 'not guarded by you' : w.txs===null ? 'tap to load' : `${w.txs.length} pending`; h.append(nm,cnt); h.onclick=()=>{ if(!w.mine){ if(confirmDrop(w.addr)) return; return; } g.open = g.open===w.addr ? null : w.addr; renderGuard(); if(g.open===w.addr && w.txs===null) loadWardTransfers(w); }; box.appendChild(h); if(g.open===w.addr && w.mine && w.txs){ const b=document.createElement('div'); b.className='ward-b'; if(!w.txs.length){ const e=document.createElement('div'); e.className='empty'; e.style.padding='22px'; e.textContent='No pending transfers.'; b.appendChild(e); } for(const t of w.txs){ const r=document.createElement('div'); r.className='wtx'; const i=document.createElement('div'); i.className='wtx-i'; i.textContent=`${t.amount} ${t.symbol==='???'?shortAddr(t.token):t.symbol}`; const sub=document.createElement('span'); sub.textContent=`→ ${shortAddr(t.to)} · ${nw()>=t.unlockTime?'unlocked':fmtTime(t.unlockTime-nw())+' left'}`; i.appendChild(sub); r.appendChild(i); b.appendChild(r); } const foot=document.createElement('div'); foot.className='wtx'; const note=document.createElement('div'); note.className='wtx-i'; note.textContent='Approve an outflow'; const ns=document.createElement('span'); ns.textContent='Pre-authorises one operation. Their next one, in order.'; note.appendChild(ns); const ab=document.createElement('button'); ab.type='button'; ab.textContent='Approve…'; ab.onclick=()=>openApprove(w); foot.append(note,ab); b.appendChild(foot); box.appendChild(b); } wrap.appendChild(box); } } function confirmDrop(addr){ saveWards(loadWards().filter(a=>a!==addr.toLowerCase())); S.guard.wards=S.guard.wards.filter(w=>w.addr!==addr.toLowerCase()); toast('Removed — you are not that account’s guardian'); renderGuard(); return true; } function openGuardModal(mode){ el.guardModal.dataset.mode=mode; el.guardInput.value=''; el.guardInput.hidden = mode==='remove'; setNote(el.guardNote,''); el.guardTitle.textContent = mode==='ward' ? 'Account you guard' : mode==='remove' ? 'Remove guardian' : (S.guard.guardian?'Change guardian':'Set guardian'); el.guardApply.textContent = mode==='remove' ? 'Remove' : 'Apply'; const warn=el.guardWarn; if(mode==='ward') warn.textContent='The address must already have named you as its guardian on chain.'; else if(mode==='remove') warn.textContent=`Removal is staged for ${fmtTime(GUARD_DELAY)} and then has to be committed. Until it commits, your guardian can cancel it.`; else warn.textContent = S.guard.guardian ? `Changing an active guardian is staged for ${fmtTime(GUARD_DELAY)}. Either of you can cancel it in that window.` : 'The first guardian takes effect immediately. It cannot be your own address — a stolen key could approve its own withdrawals.'; showModal(el.guardModal); } async function applyGuardModal(){ const mode=el.guardModal.dataset.mode; if(mode==='remove'){ hideModal(el.guardModal); return setGuardianTx(ZERO); } const raw=el.guardInput.value.trim(); let addr=raw; if(!isAddr(raw)){ if(!raw.includes('.')){ setNote(el.guardNote,'Enter an address or a name','err'); return; } setNote(el.guardNote,'Resolving…'); addr=await resolveName(raw); if(!addr||!isAddr(addr)){ setNote(el.guardNote,`No address set for ${raw}`,'err'); return; } } if(mode==='ward'){ const [r]=await mc([{to:SLOW,data:cd(SEL.guardians,['address'],[addr])}]); const who = r ? '0x'+r.slice(-40) : ZERO; if(who.toLowerCase()!==(S.account||'').toLowerCase()){ setNote(el.guardNote,'That account has not named you as its guardian','err'); return; } saveWards([...loadWards(),addr]); hideModal(el.guardModal); loadGuard(); return; } if(addr.toLowerCase()===(S.account||'').toLowerCase()){ setNote(el.guardNote,'A guardian cannot be your own address','err'); return; } hideModal(el.guardModal); setGuardianTx(addr); } function guardWritable(){ if(S.slowDeployed[S.chain]===false){ toast(`SLOW is not deployed on ${cfg().name} — there is nothing to set a guardian on.`, 6000); return false; } return true; } async function setGuardianTx(addr){ if(!guardWritable()) return; const cur = (S.guard.guardian||'').toLowerCase(); const same = !!cur && addr.toLowerCase()===cur; const staged = !!S.guard.guardian && !same; const hadPending = !!S.guard.pending; try { await slowTx(cd(SEL.setGuardian,['address'],[addr]), same ? 'Keeping your guardian…' : staged ? 'Staging the change…' : 'Setting your guardian…', 'Confirm in your wallet'); toast(same ? (hadPending ? 'Kept. The pending change was cancelled.' : 'No change — that is already your guardian.') : staged ? `Staged. Commit it after ${fmtTime(GUARD_DELAY)}.` : (addr.toLowerCase()===ZERO?'Guardian removed.':'Guardian set.'), 6000); loadGuard(); } catch(e){ fail(e); } } async function commitGuardianTx(){ if(!guardWritable()) return; try { await slowTx(cd(SEL.commitGuardian,['address'],[S.account]),'Committing…','Confirm in your wallet'); toast('Committed.'); loadGuard(); } catch(e){ fail(e); } } async function cancelGuardianTx(){ if(!guardWritable()) return; try { await slowTx(cd(SEL.cancelGuardianChange,['address'],[S.account]),'Cancelling…','Confirm in your wallet'); toast('Change cancelled.'); loadGuard(); } catch(e){ fail(e); } } function setNote(node,text,cls){ node.textContent=text; node.className='note'+(cls?' '+cls:''); const owner=node.id && qs(`[aria-describedby="${node.id}"]`); if(owner) owner.setAttribute('aria-invalid', String(cls==='err')); } async function resolveRecipient(input){ const seq=++S.resolveSeq; const stale=()=>S.resolveSeq!==seq; setNote(el.ens,'Resolving…'); S.resolving=true; paintFoot(); try { if(isAddr(input)){ if(!checksumOk(input)){ S.resolved=null; S.resolvedName=null; setNote(el.ens, 'That address fails its checksum — a character is wrong. Re-copy it from the source.', 'err'); updateSummary(); return false; } const key=input.toLowerCase(); const name = key in S.ens ? S.ens[key] : await reverseAny(input); if(stale()) return false; S.ens[key]=name||null; S.resolved=toChecksum(input); S.resolvedName=name||null; setNote(el.ens, name?`${name} · ${shortAddr(S.resolved)}`:'Valid address','ok'); updateSummary(); return true; } if(input.includes('.')){ const key=input.toLowerCase(); const a = key in S.ens ? S.ens[key] : await resolveName(input); if(stale()) return false; S.ens[key]=a||null; if(a&&isAddr(a)){ S.resolved=toChecksum(a); S.resolvedName=input; setNote(el.ens,`${input} · ${shortAddr(S.resolved)}`,'ok'); updateSummary(); return true; } setNote(el.ens,`No address set for ${input}`,'err'); return false; } setNote(el.ens,'Enter an address, a .eth name, or a .wei name','err'); return false; } catch(e){ if(!stale()) setNote(el.ens,'Could not reach a name resolver','err'); return false; } finally { if(!stale()){ S.resolving=false; paintFoot(); } } } function updateSummary(){ renderReview(); paintPreview(); } const PLATE_D = 'M0 23.7C0 15.6 0 11.5 1.5 8.4C3 5.4 5.4 3 8.4 1.5C11.5 0 15.6 0 23.7 0H276.3C284.4 0 288.5 0 291.6 1.5C294.6 3 297 5.4 298.5 8.4C300 11.5 300 15.6 300 23.7V276.3C300 284.4 300 288.5 298.5 291.6C297 294.6 294.6 297 291.6 298.5C288.5 300 284.4 300 276.3 300H23.7C15.6 300 11.5 300 8.4 298.5C5.4 297 3 294.6 1.5 291.6C0 288.5 0 284.4 0 276.3V23.7Z'; const fit = (len, box, max, min) => { if(!len) return max; const size = Math.floor((box*10)/(len*6)); return size>max ? max : size<min ? min : size; }; const blen = s => new TextEncoder().encode(String(s)).length; function formatDelay(d){ d=BigInt(d); const u=(n,w)=>`${d/n} ${w}${d/n===1n?'':'s'}`; for(const [n,w] of [[31536000n,'year'],[2592000n,'month'],[86400n,'day'],[3600n,'hour'],[60n,'minute']]) if(d>=n && d%n===0n) return u(n,w); return `${d} second${d===1n?'':'s'}`; } const escHTML = t => String(t).replace(/&/g,'&amp;').replace(/'/g,'&#39;') .replace(/"/g,'&quot;').replace(/</g,'&lt;').replace(/>/g,'&gt;'); function clipForDisplay(str, maxBytes){ const b = new TextEncoder().encode(str); if(b.length<=maxBytes) return str; let cut=maxBytes; while(cut>0 && (b[cut]&0xc0)===0x80) cut--; return new TextDecoder().decode(b.slice(0,cut))+'...'; } function renderNFT(token, delay, symbol,
0x68575b07…67c7·#25,913,806·0x4937fc4d…b2bee5
waitTx(h); forgetUnlocked(u.id); toast(`Withdrew ${u.amount} ${u.symbol}.`); await loadUnlocked(); renderList(); } catch(e){ fail(e); } } async function refundTipIfAny(t){ if(!t.tipped) return ''; const gate=await getGate(); if(!gate) return ''; try { txShow('Refunding the keeper tip','Confirm in your wallet'); await waitTx(await sendTx({from:S.account,to:gate,data:c1(SEL.refundTip,t.id)})); return ' Tip refunded.'; } catch(e){ return isRejection(e) ? ' Tip left on the gate — refund it later with gate.refundTip.' : ' Tip is still on the gate; refund it later with gate.refundTip.'; } } async function doForgetInbound(t){ if(!guardWritable()) return; try { await slowTx(c1(SEL.forgetInbound, t.id), 'Dismissing…', 'Confirm in your wallet'); forgetUnlocked(t.id); toast('Dismissed from your list. The transfer itself is unchanged — its link still opens it.', 6000); hideModal(el.detailModal); loadTransfers(true); } catch(e){ fail(e); } } async function doClaim(t){ if(nw()<t.unlockTime){ toast(`${fmtTime(t.unlockTime-nw())} remaining`); return; } const guardian=S.hasGuardian, key=unlockedKey(); try { if(guardian) rememberUnlocked(t, key); await slowTx(c1(guardian?SEL.unlock:SEL.claim,t.id), guardian?'Unlocking…':'Claiming…','Confirm in your wallet'); toast(guardian ? 'Unlocked. It is now in Unlocked, awaiting a guardian-approved withdrawal.' : 'Claimed.', 6000); loadTransfers(true); } catch(e){ fail(e); } } async function doReverse(t){ if(nw()>=t.unlockTime){ toast("The timelock expired — this can no longer be reversed"); return; } const guarded=S.hasGuardian, key=unlockedKey(); try { const data = guarded ? c1(SEL.reverse,t.id) : cd(SEL.multicall,['bytes[]'],[[c1(SEL.reverse,t.id),wfCall(t)]]); if(guarded) rememberUnlocked(t, key); await slowTx(data,'Reversing the transfer…','Confirm in your wallet'); const tip=await refundTipIfAny(t); toast((guarded?'Reversed. It is now in Unlocked, awaiting a guardian-approved withdrawal.':'Reversed and returned.')+tip,6000); loadTransfers(true); } catch(e){ fail(e); } } async function doClawback(t){ const ready=t.unlockTime+GRACE; if(nw()<ready){ toast(`Recovery available in ${fmtTime(ready-nw())}`); return; } const guarded=S.hasGuardian, key=unlockedKey(); try { const data = guarded ? c1(SEL.clawback,t.id) : cd(SEL.multicall,['bytes[]'],[[c1(SEL.clawback,t.id),wfCall(t)]]); if(guarded) rememberUnlocked(t, key); await slowTx(data,'Recovering…','Confirm in your wallet'); const tip=await refundTipIfAny(t); toast((guarded?'Recovered. It is now in Unlocked, awaiting a guardian-approved withdrawal.':'Recovered.')+tip,6000); loadTransfers(true); } catch(e){ fail(e); } } const LOADER = '<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" class="loader" role="img" aria-label="Loading"><g opacity="1"><animate attributeName="opacity" values="1;0;0;0;0;0;0;0;0;0;0;0;0;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 155,160 200,135" fill="#FF6B9D" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,135 245,160" fill="#00D4FF" stroke="#000000" stroke-width="2"/><polygon points="155,160 200,135 200,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="200,135 245,160 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="155,160 200,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><polygon points="200,160 245,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="155" y1="160" x2="245" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;1;0;0;0;0;0;0;0;0;0;0;0;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 160,160 200,132" fill="#FF6B9D" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,132 240,160" fill="#00D4FF" stroke="#000000" stroke-width="2"/><polygon points="160,160 200,132 200,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="200,132 240,160 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="160,160 200,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><polygon points="200,160 240,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="160" y1="160" x2="240" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;1;0;0;0;0;0;0;0;0;0;0;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 170,160 200,130" fill="#FF6B9D" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,130 235,160" fill="#00D4FF" stroke="#000000" stroke-width="2"/><polygon points="200,130 235,160 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="170,160 200,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><polygon points="200,160 235,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="170" y1="160" x2="235" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;1;0;0;0;0;0;0;0;0;0;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 180,160 200,135" fill="#FF6B9D" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,135 220,160" fill="#00D4FF" stroke="#000000" stroke-width="2"/><polygon points="200,135 220,160 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="180,160 200,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><polygon points="200,160 220,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="180" y1="160" x2="220" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;0;1;0;0;0;0;0;0;0;0;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 195,160 200,140" fill="#00D4FF" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,140 205,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="195,160 200,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><polygon points="200,160 205,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="195" y1="160" x2="205" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;0;0;1;0;0;0;0;0;0;0;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 180,160 200,135" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,135 220,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="180,160 200,135 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="180,160 200,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><polygon points="200,160 220,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="180" y1="160" x2="220" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;0;0;0;1;0;0;0;0;0;0;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 165,160 200,130" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,130 230,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="165,160 200,130 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="165,160 200,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><polygon points="200,160 230,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="165" y1="160" x2="230" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;0;0;0;0;1;0;0;0;0;0;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 160,160 200,132" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,132 240,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="160,160 200,132 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="200,132 240,160 200,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="160,160 200,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><polygon points="200,160 240,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="160" y1="160" x2="240" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;0;0;0;0;0;1;0;0;0;0;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 155,160 200,135" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,135 245,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="155,160 200,135 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="200,135 245,160 200,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="155,160 200,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><polygon points="200,160 245,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="155" y1="160" x2="245" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;0;0;0;0;0;0;1;0;0;0;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 160,160 200,132" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,132 240,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="160,160 200,132 200,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="200,132 240,160 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="160,160 200,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><polygon points="200,160 240,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="160" y1="160" x2="240" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;0;0;0;0;0;0;0;1;0;0;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 170,160 200,130" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,130 235,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="200,130 235,160 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="170,160 200,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><polygon points="200,160 235,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="170" y1="160" x2="235" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;0;0;0;0;0;0;0;0;1;0;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 180,160 200,135" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,135 220,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="200,135 220,160 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="180,160 200,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><polygon points="200,160 220,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="180" y1="160" x2="220" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;0;0;0;0;0;0;0;0;0;1;0;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 195,160 200,140" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,140 205,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="195,160 200,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><polygon points="200,160 205,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="195" y1="160" x2="205" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;0;0;0;0;0;0;0;0;0;0;1;0;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 180,160 200,135" fill="#00D4FF" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,135 220,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="200,135 220,160 200,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="180,160 200,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><polygon points="200,160 220,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="180" y1="160" x2="220" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;0;0;0;0;0;0;0;0;0;0;0;1;0" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 170,160 200,130" fill="#00D4FF" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,130 235,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="200,130 235,160 200,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="170,160 200,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><polygon points="200,160 235,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="170" y1="160" x2="235" y2="160" stroke="#000000" stroke-width="2"/></g><g opacity="0"><animate attributeName="opacity" values="0;0;0;0;0;0;0;0;0;0;0;0;0;0;0;1" dur="3.2s" repeatCount="indefinite"/><polygon points="200,80 160,160 200,132" fill="#00D4FF" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,132 240,160" fill="#FF6B9D" stroke="#000000" stroke-width="2"/><polygon points="160,160 200,132 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="200,132 240,160 200,160" fill="#FF6B9D" stroke="#000000" stroke-width="2"/><polygon points="160,160 200,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><polygon points="200,160 240,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="160" y1="160" x2="240" y2="160" stroke="#000000" stroke-width="2"/></g></svg>'; const LOADER_STILL = '<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg" class="loader" role="img" aria-label="Loading"><g opacity="1"><polygon points="200,80 155,160 200,135" fill="#FF6B9D" stroke="#000000" stroke-width="2"/><polygon points="200,80 200,135 245,160" fill="#00D4FF" stroke="#000000" stroke-width="2"/><polygon points="155,160 200,135 200,160" fill="#FFE066" stroke="#000000" stroke-width="2"/><polygon points="200,135 245,160 200,160" fill="#66D9A6" stroke="#000000" stroke-width="2"/><polygon points="155,160 200,160 200,240" fill="#FF9F40" stroke="#000000" stroke-width="2"/><polygon points="200,160 245,160 200,240" fill="#B967DB" stroke="#000000" stroke-width="2"/><line x1="200" y1="80" x2="200" y2="240" stroke="#000000" stroke-width="2"/><line x1="155" y1="160" x2="245" y2="160" stroke="#000000" stroke-width="2"/></g></svg>'; function loaderNode(){ const d=document.createElement('div'); d.className='loading-wrap'; d.innerHTML = matchMedia('(prefers-reduced-motion: reduce)').matches ? LOADER_STILL : LOADER; return d; } const ICONS = { 1:'<svg viewBox="0 0 32 32" aria-hidden="true">' +'<rect width="32" height="32" fill="#627EEA"/>' +'<g transform="translate(4 4)" fill="#fff">' +'<path d="M11.944 17.97 4.58 13.62 11.943 24l7.37-10.38-7.372 4.35h.003zM12.056 0 4.69 12.223l7.365 4.354 7.365-4.35L12.056 0z"/>' +'</g></svg>', 4663:'<svg viewBox="0 0 32 32" aria-hidden="true">' +'<rect width="32" height="32" fill="#CCFF00"/>' +'<g transform="translate(4 4)" fill="#110E08">' +'<path d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/>' +'</g></svg>', 8453:'<svg viewBox="0 0 32 32" aria-hidden="true">' +'<rect width="32" height="32" fill="#0052FF"/>' +'<path d="M16 5.4A10.6 10.6 0 0 0 5.5 14.7h13.9v2.6H5.5A10.6 10.6 0 1 0 16 5.4Z"' +' fill="#fff" transform="rotate(180 16 16)"/>' +'</svg>', }; function destinations(){ if(S.chain!==MAINNET || S.token!==ZERO) return []; return CHAIN_IDS.filter(id => id!==MAINNET && BRIDGES[id] && BRIDGES[id].from===MAINNET); } function buildDestRow(){ const opts = destinations(); el.fDest.hidden = !opts.length; if(!opts.length){ S.dest=null; return; } const row=el.destRow; row.replaceChildren(); const mk=(id,label,live)=>{ const b=document.createElement('button'); b.type='button'; b.className='opt'; b.dataset.dest=String(id); b.textContent=label; b.disabled=!live; b.title = live ? '' : S.slowDeployed[id]!==true ? `SLOW is not deployed on ${cfg(id).name} yet` : (S.account && S.accountIsContract===null) ? `Still checking whether this account can be reversed from on ${cfg(id).name}` : `A transfer bridged to ${cfg(id).name} from this account could not be reversed or recovered`; b.setAttribute('aria-pressed', String((S.dest||MAINNET)===id)); row.appendChild(b); }; mk(MAINNET, 'Ethereum', true); for(const id of opts) mk(id, cfg(id).short, canBridge(S.chain, id)); const live = opts.filter(id=>canBridge(S.chain, id)).length; const checking = !!S.account && S.accountIsContract===null; if(S.dest && !canBridge(S.chain, S.dest)) S.dest=null; el.destHint.textContent = live ? '' : checking ? 'checking…' : 'awaiting deployment'; } function buildTokenGrid(){ const g=el.cryptoGrid, c=cfg(); el.assetHint.textContent=c.name; g.replaceChildren(); for(const t of cfg().tokens){ const b=document.createElement('button'); b.type='button'; b.className='crypto'; b.dataset.symbol=t.symbol; b.dataset.token=t.address.toLowerCase(); b.dataset.decimals=String(t.decimals); b.textContent=t.symbol; b.style.borderLeftColor=railOf(t.symbol,t.address); b.setAttribute('aria-pressed','false'); g.appendChild(b); } } function presetsFor(sym){ return PRESETS[sym]||PRESETS.default; } function paintPresets(){ const p=presetsFor(S.symbol); const btns=[...el.amountRow.querySelectorAll('[data-amount]')]; btns.forEach((b,i)=>{ b.dataset.amount=p[i]; b.textContent=p[i]; }); if(S.balance!==null && S.token && S.symbol){ el.balHint.textContent=`${fmtAmt(S.balance,S.decimals)} ${S.symbol}`; for(const b of btns){ let over=false; try { over = parseUnits(b.dataset.amount,S.decimals) > S.balance; } catch(e){} b.disabled=over; b.title=over?`More than your ${S.symbol} balance`:''; } } else { el.balHint.textContent = !S.token ? 'pick an asset first' : !S.account ? 'connect to see balance' : ''; for(const b of btns){ b.disabled=false; b.title=''; } } el.maxBtn.disabled = S.balance===null; render(); renderReview(); } function paintChain(){ const c=cfg(); el.chainIcon.innerHTML=ICONS[c.id]||''; el.chainName.textContent=c.name; el.chainBtn.title=`${c.name} · chain ${c.id}`; el.chainBtn.setAttribute('aria-label',`Network: ${c.name}. Change.`); } function paintNet(){ el.netDot.hidden = S.readOk; el.netDot.title = S.readOk ? '' : `No RPC reachable for ${cfg().name}`; } function paintWallet(){ const on=!!S.account; el.walletBtn.querySelector('.lbl').textContent = on ? (S.ensName||shortAddr(S.account)) : 'Connect'; el.walletBtn.classList.toggle('on',on); el.walletBtn.title = on ? `${S.account}${S.walletName?' · '+S.walletName:''} — disconnect` : 'Connect a wallet'; } function paintDeployed(){ const live=S.slowDeployed[S.chain]; if(live===undefined && S.slowUnknown===S.chain){ el.chainWarn.hidden=false; el.chainWarn.textContent=`Could not reach ${cfg().name} to check SLOW is there. Reads may be incomplete.`; render(); return; } el.chainWarn.hidden = live!==false; if(live===false) el.chainWarn.textContent = S.slowMismatch===S.chain ? `The contract at this address on ${cfg().name} is not the build this page speaks to.` : `SLOW is not deployed on ${cfg().name} yet.`; render(); } function render(){ const b=document.body; b.classList.toggle('v-home',S.view==='home'); b.classList.toggle('v-send',S.view==='send'); b.classList.toggle('v-take',S.view==='take'); b.classList.toggle('v-guard',S.view==='guard'); el.segSend.setAttribute('aria-selected',String(S.view==='send')); el.segTake.setAttribute('aria-selected',String(S.view==='take')); el.segGuard.setAttribute('aria-selected',String(S.view==='guard')); el.fAmount.classList.toggle('locked',S.step<1); el.fAmount.inert = S.step<1; el.fTime.classList.toggle('locked',S.step<2); el.fTime.inert = S.step<2; const isCustom = S.token && !qs(`.crypto[data-token="${S.token}" i]`); el.customTokenOpt.setAttribute('aria-pressed',String(!!isCustom)); el.customTokenOpt.textContent = isCustom?S.symbol:'+ Custom token'; el.customTokenOpt.style.borderLeft = isCustom?`5px solid ${tokenColor(S.token)}`:''; el.cryptoGrid.classList.toggle('dim',!!isCustom); qsa('.crypto').forEach(c=>c.setAttribute('aria-pressed',String(c.dataset.token.toLowerCase()===S.token))); el.amountRow.querySelectorAll('.opt').forEach(o=> o.setAttribute('aria-pressed',String(o.dataset.amount===S.amount))); const customDly = S.delay && !qs(`#timeRow [data-time="${S.delay}"]`); el.timeRow.querySelectorAll('.opt').forEach(o=>{ const on = o===el.customTimeOpt ? !!customDly : parseInt(o.dataset.time)===S.delay; o.setAttribute('aria-pressed',String(!!on)); }); el.customTimeOpt.textContent = customDly?S.delayDisplay:'Custom'; paintFoot(); el.timeHint.textContent = S.step<2 ? 'enter an amount first' : ''; el.tabOut.setAttribute('aria-selected',String(S.tab==='outbound')); el.tabIn.setAttribute('aria-selected',String(S.tab==='inbound')); } function resetForm(){ S.token=S.symbol=null; S.decimals=18; S.amount=S.delay=S.delayDisplay=null; S.resolved=S.resolvedName=null; S.resolving=false; S.step=0; S.autoClaim=false; S.tip=0n; S.balance=null; el.recipientInput.value=''; setNote(el.ens,''); el.amountInput.value=''; setNote(el.amountNote,''); el.planNote.textContent=''; el.dInput.value=el.hInput.value=el.mInput.value=el.sInput.value=''; el.autoClaimChk.checked=false; el.tipPre.textContent=''; el.balHint.textContent=''; el.fReview.hidden=true; paintPresets(); } function setView(v){ if(v==='send') paintPreview(); S.view=v; if(v==='home') resetForm(); if(v!=='take'){ stopTimer(); } if(v!=='send') hideAllModals(); render(); if(v==='take'){ renderList(); loadTransfers(true); } if(v==='guard'){ renderGuard(); loadGuard(); } } const isSender = t => !!S.account && !!t.from && t.from.toLowerCase()===S.account.toLowerCase(); function statusOf(t){ const n=nw(); const expired = n>=t.unlockTime; const inbound = S.tab==='inbound'; if(inbound) return expired ? {key:'ready', text:'Ready to claim', act:'Claim'} : {key:'pending', text:`Unlocks in ${fmtTime(t.unlockTime-n)}`, act:null}; if(!isSender(t)){ const via = S.account && t.from && t.from.toLowerCase()===aliasOf(S.account).toLowerCase(); return expired ? {key:'settling', text: via ? 'Bridged in — awaiting the recipient' : 'Awaiting the recipient', act:null} : {key:'pending', text:`Unlocks in ${fmtTime(t.unlockTime-n)}`+(via?' · bridged in':''), act:null}; } if(!expired) return {key:'pending', text:`Unlocks in ${fmtTime(t.unlockTime-n)}`, act:'Reverse'}; if(n>=t.unlockTime+GRACE) return {key:'recover', text:'Unclaimed — recoverable', act:'Recover'}; return {key:'settling', text:
0x68575b07…67c7·#25,913,805·0xce85d9b7…3b8250
onst symbol = sh ? decodeStringLoose(sh) : ''; const name = nh ? decodeStringLoose(nh) : ''; let decimals = 18, ok=false; if(dh){ try { decimals=Number(decode(['uint256'],dh)[0]); ok=true; } catch(e){} } if(!(decimals>=0&&decimals<=36)) { decimals=18; ok=false; } return S.meta[k]={symbol:symbol||'???', name:name||symbol||'???', decimals, ok:ok&&!!symbol}; } async function refreshBalance(){ if(!S.account||!S.token){ el.balHint.textContent=''; return; } const tok=S.token, acct=S.account; try { let raw; if(tok===ZERO) raw=BigInt(await rpc('eth_getBalance',[acct,'latest'])); else { const [h]=await mc([{to:tok,data:cd(SEL.balanceOf,['address'],[acct])}]); raw = h ? decode(['uint256'],h)[0] : 0n; } if(S.token!==tok||S.account!==acct) return; S.balance=raw; paintPresets(); } catch(e){ } } function hasAtomic(caps){ if(!caps||typeof caps!=='object') return false; const pick = id => { for(const k of Object.keys(caps)){ try { if(BigInt(k)===id) return caps[k]; } catch(e){} } return undefined; }; const c = pick(BigInt(S.chain)) ?? pick(0n); const st = c?.atomic?.status; return st==='supported'||st==='ready'; } async function canBatch(){ if(S.batchCap!==null) return S.batchCap; if(!S.account) return (S.batchCap=false); try { const caps=await rpc('wallet_getCapabilities',[S.account]); return (S.batchCap=hasAtomic(caps)); } catch(e){ return (S.batchCap=false); } } async function sendBatch(calls){ const res = await rpc('wallet_sendCalls',[{ version:'2.0.0', chainId:cfg().hex, from:S.account, atomicRequired:true, calls: calls.map(c=>({to:c.to, data:c.data, value:c.value||'0x0'})), }]); const id = typeof res==='string' ? res : res?.id; if(!id) throw new Error('Wallet returned no batch id'); const end=Date.now()+600000; let wait=1200; while(Date.now()<end){ try { const st=await rpc('wallet_getCallsStatus',[id]); const rs=(st&&st.receipts)||[]; if(rs.some(r=>r&&r.status==='0x0')) throw new Error('BATCH: the batch reverted on chain'); const last=rs[rs.length-1], h=last&&(last.transactionHash||last.hash); if(h) return h; const q=String(st&&st.status); if(q==='400'||q==='500'||/fail|revert|reject/i.test(q)) throw new Error('BATCH: the wallet reported the batch failed'); } catch(e){ if(/^BATCH: /.test(e.message)) throw new Error(e.message.slice(7)); } await sleep(wait); wait=Math.min(wait+400,4000); } throw new Error('Batch not confirmed in time — check your wallet activity before retrying'); } const EIP712_DOMAIN_TH = keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'); function domainSeparator(name, version, verifying){ return keccak256('0x'+strip(EIP712_DOMAIN_TH) + strip(keccak256(name)) + strip(keccak256(version)) + word(S.chain) + word(verifying)); } async function permitInfo(token, owner){ const k=token.toLowerCase()+':'+owner.toLowerCase(); const cached = S.permitCache[k]; if(cached === null) return null; if(cached){ const fresh = await permitNonce(token, owner); return fresh===null ? null : {...cached, nonce:fresh}; } let out=null; try { const [ds,th,nc,nm] = await mc([ {to:token,data:SEL.domainSeparator}, {to:token,data:SEL.permitTypehash}, {to:token,data:cd(SEL.nonces,['address'],[owner])}, {to:token,data:SEL.name}, ]); if(ds && ds!=='0x' && nc && nm){ const typehashOk = !th || strip(th).length<64 || strip(th).slice(0,64).toLowerCase()===TYPEHASH_2612; if(typehashOk){ const name=decodeStringLoose(nm); const nonce=decode(['uint256'],nc)[0]; if(name){ for(const ver of ['1','2','1.0','1.1','V1','']){ if(domainSeparator(name,ver,token).toLowerCase()===ds.toLowerCase()){ out={name,version:ver,nonce}; break; } } } } } } catch(e){} S.permitCache[k]=out; return out; } async function permitNonce(token, owner){ try { const [r]=await mc([{to:token,data:cd(SEL.nonces,['address'],[owner])}]); return r ? decode(['uint256'],r)[0] : null; } catch(e){ return null; } } async function signPermit(token, info, owner, spender, value, deadline){ const td = { types:{ EIP712Domain:[{name:'name',type:'string'},{name:'version',type:'string'}, {name:'chainId',type:'uint256'},{name:'verifyingContract',type:'address'}], Permit:[{name:'owner',type:'address'},{name:'spender',type:'address'}, {name:'value',type:'uint256'},{name:'nonce',type:'uint256'},{name:'deadline',type:'uint256'}], }, primaryType:'Permit', domain:{name:info.name,version:info.version,chainId:S.chain,verifyingContract:token}, message:{owner,spender,value:value.toString(),nonce:info.nonce.toString(),deadline:deadline.toString()}, }; const sig = strip(await rpc('eth_signTypedData_v4',[owner,JSON.stringify(td)])); if(sig.length!==130) throw new Error('Wallet returned a malformed signature'); let v=parseInt(sig.slice(128,130),16); if(v<27) v+=27; return {v:BigInt(v), r:'0x'+sig.slice(0,64), s:'0x'+sig.slice(64,128)}; } async function slowHasPermit(){ if(S.slowPermit!==null) return S.slowPermit; try { const code = await rpc('eth_getCode',[SLOW,'latest']); S.slowPermit = typeof code==='string' && code.includes(SEL.depositToWithPermit.slice(2)); } catch(e){ S.slowPermit=false; } return S.slowPermit; } async function sendTx(tx){ if(!await ensureChain()) throw new Error(`Switch to ${cfg().name}`); return rpc('eth_sendTransaction',[tx]); } async function waitTx(hash){ const end=Date.now()+600000; let wait=1500, misses=0; while(Date.now()<end){ try { const r=await rpc('eth_getTransactionReceipt',[hash]); misses=0; if(r){ if(r.status!=='0x1') throw new Error('REVERT'); return r; } wait=Math.min(wait+500,6000); } catch(e){ if(e.message==='REVERT') throw new Error('The transaction reverted on chain'); if(++misses>2) wait=Math.min(wait*2,15000); } await sleep(wait); } throw new Error('Not confirmed within 10 minutes — check your wallet before retrying'); } async function slowTx(data, msg, step, value){ txShow(msg, step); const tx={from:S.account,to:SLOW,data}; if(value&&value!=='0x0') tx.value=value; const h=await sendTx(tx); S.lastHash=h; txShow(msg,'Waiting for confirmation',h); await waitTx(h); return h; } const c1 = (sel,id) => cd(sel,['uint256'],[BigInt(id)]); const wfCall = t => cd(SEL.withdrawFrom,['address','address','uint256','uint256'],[t.from,S.account,BigInt(t.tokenId),t.amountRaw]); function depositCalldata(wei, permitSig, deadline){ const isETH = S.token===ZERO; const tokenArg = isETH?ZERO:S.token; const amountArg = isETH?0n:wei; if(permitSig){ return S.autoClaim ? cd(SEL.depositToWithTipAndPermit, ['address','address','uint256','uint96','uint256','bytes','uint256','uint256','uint256','uint256'], [tokenArg,S.resolved,wei,BigInt(S.delay),S.tip,'0x',deadline,permitSig.v,BigInt(permitSig.r),BigInt(permitSig.s)]) : cd(SEL.depositToWithPermit, ['address','address','uint256','uint96','bytes','uint256','uint256','uint256','uint256'], [tokenArg,S.resolved,wei,BigInt(S.delay),'0x',deadline,permitSig.v,BigInt(permitSig.r),BigInt(permitSig.s)]); } return S.autoClaim ? cd(SEL.depositToWithTip,['address','address','uint256','uint96','uint256','bytes'], [tokenArg,S.resolved,wei,BigInt(S.delay),S.tip,'0x']) : cd(SEL.depositTo,['address','address','uint256','uint96','bytes'], [tokenArg,S.resolved,amountArg,BigInt(S.delay),'0x']); } async function planDeposit(wei){ const isETH=S.token===ZERO; if(isETH) return {kind:'direct', calls:1}; let allowance=0n; try { const [h]=await mc([{to:S.token,data:cd(SEL.allowance,['address','address'],[S.account,SLOW])}]); if(h) allowance=decode(['uint256'],h)[0]; } catch(e){} if(allowance>=wei) return {kind:'direct', calls:1, allowance}; if(await canBatch()) return {kind:'batch', calls:allowance>0n?3:2, allowance}; if(await slowHasPermit() && await permitInfo(S.token,S.account)) return {kind:'permit', calls:1, allowance}; return {kind:'approve', calls:allowance>0n?3:2, allowance}; } function planLabel(p){ if(!p) return ''; switch(p.kind){ case 'direct': return 'One transaction.'; case 'batch': return 'One confirmation — your wallet batches the approval and the deposit atomically.'; case 'permit': return 'One transaction — you sign the approval instead of paying for it.'; case 'approve': return `Two transactions: an exact-amount approval${p.allowance>0n?' (reset first, this token requires it)':''}, then the deposit.`; } return ''; } async function deposit(){ const isETH=S.token===ZERO; const meta = isETH?{decimals:18}:await tokenMeta(S.token); const wei = parseUnits(S.amount, meta.decimals); if(!S.autoClaim) S.tip = 0n; else if(!S.tip) S.tip = await estimateTip(S.token); const value = isETH ? wei + S.tip : S.tip; const valueHex = value>0n ? '0x'+value.toString(16) : '0x0'; const shortfall = need => S.balance!==null && need>S.balance ? `This needs ${fmtAmt(need,18)} ETH and you have ${fmtAmt(S.balance,18)}. ` + `Lower the amount — Max leaves room for the fees.` : null; if(S.dest){ if(S.token!==ZERO) throw new Error(`${S.symbol} cannot be bridged — send it on ${cfg().name}`); if(!canBridge(S.chain, S.dest)) throw new Error(`A transfer to ${cfg(S.dest).short} could not be reversed or recovered from this account`); const b = await bridgePlan(S.dest, S.resolved, S.delay, wei); const short = shortfall(b.value); if(short) throw new Error(short); txShow(`Sending to ${cfg(S.dest).short}`, 'Confirm in your wallet'); const h = await sendTx({from:S.account, to:b.to, data:b.data, value:'0x'+b.value.toString(16)}); S.lastHash=h; txShow(`Sending to ${cfg(S.dest).short}`, 'Waiting for the L1 confirmation', h); await waitTx(h); return; } if(isETH){ const short = shortfall(value); if(short) throw new Error(short); } const plan = await planDeposit(wei); if(plan.kind==='direct'){ await slowTx(depositCalldata(wei),'Sending','Confirm in your wallet',valueHex); return; } if(plan.kind==='batch'){ txShow('Sending','Approve the batch in your wallet'); const calls=[]; if(plan.allowance>0n) calls.push({to:S.token,data:cd(SEL.approve,['address','uint256'],[SLOW,0n])}); calls.push({to:S.token,data:cd(SEL.approve,['address','uint256'],[SLOW,wei])}); calls.push({to:SLOW,data:depositCalldata(wei),value:valueHex}); if(!await ensureChain()) throw new Error(`Switch to ${cfg().name}`); const h=await sendBatch(calls); S.lastHash=h; txShow('Sending','Waiting for confirmation',h); await waitTx(h); return; } if(plan.kind==='permit'){ txShow('Sending','Sign the approval in your wallet'); const info=await permitInfo(S.token,S.account); const deadline=BigInt(nw()+1800); const sig=await signPermit(S.token,info,S.account,SLOW,wei,deadline); txShow('Sending','Confirm the transfer in your wallet'); const h=await sendTx({from:S.account,to:SLOW,data:depositCalldata(wei,sig,deadline),value:valueHex}); S.lastHash=h; txShow('Sending','Waiting for confirmation',h); await waitTx(h); return; } const steps = plan.allowance>0n ? [0n,wei] : [wei]; for(let i=0;i<steps.length;i++){ txShow(`Approving ${S.symbol}`, `Step ${i+1} of ${steps.length+1}`); const h=await sendTx({from:S.account,to:S.token,data:cd(SEL.approve,['address','uint256'],[SLOW,steps[i]])}); txShow(`Approving ${S.symbol}`, `Step ${i+1} of ${steps.length+1}`, h); await waitTx(h); } const [fresh]=await mc([{to:S.token,data:cd(SEL.allowance,['address','address'],[S.account,SLOW])}]); if(!fresh||decode(['uint256'],fresh)[0]<wei) throw new Error('The approval did not take effect'); txShow('Sending',`Step ${steps.length+1} of ${steps.length+1}`); const h=await sendTx({from:S.account,to:SLOW,data:depositCalldata(wei),value:valueHex}); S.lastHash=h; txShow('Sending','Waiting for confirmation',h); await waitTx(h); } const KEEPER_GAS = {eth:150_000n, erc20:220_000n}; async function estimateTip(token){ const c=cfg(); const units = token===ZERO ? KEEPER_GAS.eth : KEEPER_GAS.erc20; let price; try { price = BigInt(await rpc('eth_gasPrice',[],c.id)); } catch(e){ price = c.fallbackGasPrice; } if(!price || price < c.minGasPrice) price = c.minGasPrice; let tip = units * price * 3n / 2n; if(tip < c.minTip) tip = c.minTip; return tip; } async function getGate(){ if(S.gate!==undefined) return S.gate; const [h]=await mc([{to:SLOW,data:SEL.gateAddr}]); S.gate = h ? '0x'+h.slice(-40) : null; if(S.gate===ZERO) S.gate=null; return S.gate; } const LIST_PAGE = 100; const LIST_CAP = 600; const LIST_KIND = { out: {all:SEL.getOut, count:SEL.outCount, at:SEL.outAt}, in: {all:SEL.getIn, count:SEL.inCount, at:SEL.inAt}, }; async function readIdList(kind, who){ const k = LIST_KIND[kind]; const [whole] = await mc([{to:SLOW, data:cd(k.all,['address'],[who])}]); if(whole){ try { return decode(['uint256[]'],whole)[0]; } catch(e){} } const [ch] = await mc([{to:SLOW, data:cd(k.count,['address'],[who])}]); if(!ch) return null; let n; try { n = Number(decode(['uint256'],ch)[0]); } catch(e){ return null; } if(!Number.isFinite(n) || n < 0) return null; if(n === 0) return []; if(n > LIST_CAP) return {overflow:n}; const out=[]; for(let i=0;i<n;i+=LIST_PAGE){ const calls=[]; for(let j=i;j<Math.min(i+LIST_PAGE,n);j++) calls.push({to:SLOW, data:cd(k.at,['address','uint256'],[who,BigInt(j)])}); const res=await mc(calls); for(const r of res){ if(!r) return null; try { out.push(decode(['uint256'],r)[0]); } catch(e){ return null; } } } return out; } const LOAD_COOLDOWN = 12000; async function loadTransfers(force){ if(!S.account) return; if(!force && Date.now()-S.lastLoad < LOAD_COOLDOWN) return; S.lastLoad=Date.now(); const seq=++S.loadSeq, account=S.account; const stale = () => S.loadSeq!==seq || S.account!==account; S.loading=true; renderList(); try { const alias = S.chain!==MAINNET ? aliasOf(account) : null; const [gRes, own, iRaw, viaBridge] = await Promise.all([ mc([{to:SLOW,data:cd(SEL.guardians,['address'],[account])}]), readIdList('out', account), readIdList('in', account), alias ? readIdList('out', alias) : Promise.resolve([]), ]); if(stale()) return; const gh = gRes[0]; S.listError = null; const lists = alias ? [own, iRaw, viaBridge] : [own, iRaw]; const overflow = lists.find(x => x && x.overflow); if(overflow){ S.loading=false; S.listError = `This account has ${group(String(overflow.overflow))} pending entries — more than ` + `this page can list. Anyone can add one with a dust deposit, so a very large count is ` + `usually somebody stuffing the list rather than transfers you are owed. ` + `Open a transfer by its link and dismiss it to take it off this list; dismissing removes ` + `the listing only, never the transfer.`; renderList(); return; } if(lists.some(x => x === null)){ S.loading=false; S.listError = `Could not read your transfers on ${cfg().name}. This is a failed read, not an ` + `empty list — do not treat it as proof that nothing is pending.`; renderList(); return; } S.aliasHasTransfers = viaBridge.length>0; const oIds = [...own, ...viaBridge]; const iIds = iRaw; if(gh==null){ S.loading=false; S.guardUnknown=true; renderList(); toast('Could not read your guardian setting — reload before sending'); return; } S.guardUnknown=false; const gAddr = strip(gh).length>=40 ? '0x'+gh.slice(-40) : ZERO; const hasGuardian = isAddr(gAddr) && gAddr.toLowerCase()!==ZERO; const all=[...new Map([...oIds,...iIds].map(id=>[id.toString(),id])).values()]; const pts = await mc(all.map(id=>({to:SLOW,data:cd(SEL.pendingTransfers,['uint256'],[id])}))); if(stale()) return; const map=new Map(); all.forEach((id,i)=>{ if(!pts[i]) return; try { const [ts,from,to,tid,amt]=decode(['uint256','address','address','uint256','uint256'],pts[i]); if(ts!==0n) map.set(id.toString(),{ts,from,to,tid,amt}); } catch(e){} }); const tokens=[...new Set([...map.values()].map(v=>decodeId(v.tid).token))]; await Promise.all(tokens.map(t=>tokenMeta(t).catch(()=>{}))); if(stale()) return; const build = id => { const k=id.toString(), pt=map.get(k); if(!pt) return null; const {token,delay}=decodeId(pt.tid); const m=S.meta[token.toLowerCase()]||{symbol:'???',decimals:18}; const timestamp=Number(pt.ts); return { id:k, from:pt.from, to:pt.to, tokenId:pt.tid.toString(), token, symbol:m.symbol, decimals:m.decimals, amountRaw:pt.amt, amount:fmtAmt(pt.amt,m.decimals), timestamp, delay, unlockTime:timestamp+delay, }; }; const outArr=oIds.map(build).filter(Boolean).sort((a,b)=>a.unlockTime-b.unlockTime); const inArr =iIds.map(build).filter(Boolean).sort((a,b)=>a.unlockTime-b.unlockTime); const extra=[]; if(hasGuardian) for(const t of outArr) extra.push({kind:'g',t,call:{to:SLOW, data:cd(SEL.isWithdrawalApprovalNeeded,['address','address','uint256','uint256'], [account,account,BigInt(t.tokenId),t.amountRaw])}}); const gate=await getGate(); if(gate) for(const t of [...inArr,...outArr]) extra.push({kind:'t',t,call:{to:gate,data:c1(SEL.tips,t.id)}}); if(extra.length){ const res=await mc(extra.map(x=>x.call)); if(stale()) return; res.forEach((r,i)=>{ if(!r) return; const {kind,t}=extra[i]; try { const v=decode(['uint256'],r)[0]; if(kind==='g') t.guardianPending = v!==0n; else if(v>0n) t.tipped=true; } catch(e){} }); } if(stale()) return; S.out=outArr; S.inb=inArr; S.hasGuardian=hasGuardian; S.listError=null; await loadUnlocked(); if(stale()) return; } catch(e){ if(!stale()) toast('Could not load transfers — '+errText(e), 5000); } finally { if(!stale()){ S.loading=false; renderList(); } } } const unlockedKey = () => `slow.unlocked.${S.chain}.${(S.account||'').toLowerCase()}`; function rememberUnlocked(t, key){ try { const k=key||unlockedKey(); const cur=JSON.parse(localStorage.getItem(k)||'[]'); if(!cur.some(x=>x.id===t.tokenId)){ cur.push({id:t.tokenId, token:t.token, symbol:t.symbol, decimals:t.decimals}); localStorage.setItem(k, JSON.stringify(cur)); } } catch(e){} } function forgetUnlocked(id){ try { const k=unlockedKey(); const cur=JSON.parse(localStorage.getItem(k)||'[]').filter(x=>x.id!==id); localStorage.setItem(k, JSON.stringify(cur)); } catch(e){} } async function loadUnlocked(){ if(!S.account){ S.unlocked=[]; return; } let notes=[]; try { notes=JSON.parse(localStorage.getItem(unlockedKey())||'[]'); } catch(e){} if(!notes.length){ S.unlocked=[]; return; } const acct=S.account; const res=await mc(notes.map(n=>({to:SLOW, data:cd(SEL.unlockedBalances,['address','uint256'],[acct,BigInt(n.id)])}))); if(S.account!==acct) return; const live=[]; notes.forEach((n,i)=>{ if(!res[i]) return; let bal=0n; try { bal=decode(['uint256'],res[i])[0]; } catch(e){ return; } if(bal>0n){ live.push({...n, raw:bal, amount:fmtAmt(bal,n.decimals), zero:0}); return; } const seen=(n.zero||0)+1; if(seen<3) live.push({...n, zero:seen, raw:0n, amount:'0', pending:true}); else forgetUnlocked(n.id); }); try { localStorage.setItem(unlockedKey(), JSON.stringify( live.map(({id,token,symbol,decimals,zero})=>({id,token,symbol,decimals,zero})))); } catch(e){} S.unlocked=live.filter(u=>!u.pending); } async function doWithdraw(u, to){ if(!guardWritable()) return; to = to || S.account; try { if(S.hasGuardian){ const [r]=await mc([{to:SLOW, data:cd(SEL.isWithdrawalApprovalNeeded,['address','address','uint256','uint256'], [S.account,to,BigInt(u.id),u.raw])}]); if(r && decode(['uint256'],r)[0]!==0n){ showApprovalNeeded(u, await withdrawOpId(u, to), to); return; } } await slowTx(cd(SEL.withdrawFrom,['address','address','uint256','uint256'], [S.account,to,BigInt(u.id),u.raw]), `Withdrawing ${u.amount} ${u.symbol}`, 'Confirm in your wallet'); toast(`Withdrew ${u.amount} ${u.symbol}.`); await loadUnlocked(); renderList(); } catch(e){ fail(e); } } async function withdrawOpId(u, to){ try { const [p]=await mc([{to:SLOW, data:cd(SEL.predictWithdrawalId,['address','address','uint256','uint256'], [S.account,to,BigInt(u.id),u.raw])}]); return p ? decode(['uint256'],p)[0].toString() : '(could not derive)'; } catch(e){ return '(could not derive)'; } } function showApprovalNeeded(u, opId, to){ to = to || S.account; S.detail=null; el.detailShare.hidden=true; el.detailForget.hidden=true; el.detailForget.onclick=null; el.detailTitle.textContent='Guardian approval needed'; el.detailRows.replaceChildren(); const rows=[ ['Amount', `${u.amount} ${u.symbol}`], ['To', to], ['Position id', u.id], ['Operation id', opId], ['Guardian', S.guard.guardian || '(unknown)'], ]; for(const [k,v] of rows){ const row=document.createElement('div'); row.className='kv'; const dt=document.createElement('dt'), dd=document.createElement('dd'); dt.textContent=k; dd.textContent=v; row.append(dt,dd); el.detailRows.appendChild(row); } const btn=el.detailAction; btn.hidden=false; btn.disabled=false; btn.className='btn alt'; btn.textContent='Copy the details for your guardian'; btn.onclick=()=>copyLink( `SLOW withdrawal awaiting your approval\nchain: ${cfg().name} (${S.chain})\n`+ `account: ${S.account}\nto: ${to}\nposition id: ${u.id}\namount: ${u.raw}\n`+ `operation id: ${opId}\napprove with: approveTransfer(${S.account}, ${opId})`, 'Details'); showModal(el.detailModal); } const HEXDATA = /^0x([0-9a-fA-F]{2})*$/; function exitRecipe(u, toRaw, dataRaw){ const raw=(dataRaw||'').trim(); const data = raw==='0x' ? '' : raw; const to=((toRaw||'').trim())||S.account; if(!isAddr(to)) return {error:'Enter a destination address'}; if(to.toLowerCase()===SLOW.toLowerCase()) return {error:'SLOW itself cannot be the destination'}; if(to===ZERO) return {error:'The zero address cannot be the destination'}; if(S.gate && to.toLowerCase()===String(S.gate).toLowerCase()) return {error:'The keeper gate cannot be the destination'}; if(data && !HEXDATA.test(data)) return {error:'Call data must be 0x followed by whole bytes'}; const isETH=u.token===ZERO; const wf = dest => ({to:SLOW, value:0n, data:cd(SEL.withdrawFrom,['address','address','uint256','uint256'],[S.account,dest,BigInt(u.id),u.raw])}); const ethCall = !!data && isETH; const settleTo = ethCall ? S.account : to; const calls=[wf(settleTo)]; if(data) calls.push({to, data, value: ethCall ? u.raw : 0n}); return {chainId:S.chain, chainName:cfg().name, calls, settleTo, target:to, needsAtomic:calls.length>1, gap: !!data && !isETH}; } function exitText(r, u){ const lines=[`# SLOW withdrawal — ${r.chainName} (chain ${r.chainId})`, `# ${u.amount} ${u.symbol} · position ${u.id}`, '']; r.calls.forEach((c,i)=>lines.push(`call ${i+1}`, ` to: ${c.to}`, ` value: ${c.value} wei`, ` data: ${c.data}`, '')); if(r.needsAtomic) lines.push('note: send these as one atomic batch. Run apart, the funds sit at the', ' destination between them and anyone who can call it may take them.'); return lines.join('\n'); } function showExit(u){ S.exit=u; el.exitTitle.textContent='Withdraw'; el.exitAmt.textContent=`${u.amount} ${u.symbol==='???'?shortAddr(u.token):u.symbol} · position ${u.id}`; el.exitTo.value=''; el.exitData.value=''; updateExit(); showModal(el.exitModal); } async function updateExit(){ const u=S.exit; if(!u) return; const r=exitRecipe(u, el.exitTo.value, el.exitData.value); S.exitPlan=r; if(r.error){ setNote(el.exitNote, r.error, 'err'); el.exitSend.disabled=true; el.exitCopy.disabled=true; return; } el.exitCopy.disabled=false; const dest = r.target.toLowerCase()===(S.account||'').toLowerCase() ? 'yourself' : shortAddr(r.target); if(!r.needsAtomic){ setNote(el.exitNote, `One transaction: the underlying goes to ${dest}.`,'ok'); el.exitSend.disabled=false; el.exitSend.textContent='Withdraw'; return; } const batch=await canBatch(); if(S.exitPlan!==r) return; el.exitSend.disabled=!batch; el.exitSend.textContent = batch?'Withdraw and call':'Withdraw'; if(batch){ setNote(el.exitNote, r.gap ? `One confirmation: the token lands at ${dest} and the call runs in the same batch.` : `One confirmation: you receive the ETH and it is forwarded to ${dest} with your call.`,'ok'); } else { setNote(el.exitNote, 'Your wallet cannot batch, so these two calls would run apart — ' +'the funds would sit at the destination unattributed in between. Copy them and send them atomically.','err'); } } async function doExit(){ const u=S.exit, r=S.exitPlan; if(!u||!r||r.error) return; if(!guardWritable()) return; if(!r.needsAtomic){ hideModal(el.exitModal); await doWithdraw(u, r.settleTo); return; } try { if(S.hasGuardian){ const [q]=await mc([{to:SLOW, data:cd(SEL.isWithdrawalApprovalNeeded,['address','address','uint256','uint256'], [S.account,r.settleTo,BigInt(u.id),u.raw])}]); if(q && decode(['uint256'],q)[0]!==0n){ hideModal(el.exitModal); showApprovalNeeded(u, await withdrawOpId(u, r.settleTo), r.settleTo); return; } } if(!await ensureChain()) throw new Error(`Switch to ${cfg().name}`); hideModal(el.exitModal); txShow(`Withdrawing ${u.amount} ${u.symbol}`,'Confirm in your wallet'); const h=await sendBatch(r.calls.map(c=>({to:c.to,data:c.data,value:'0x'+c.value.toString(16)}))); S.lastHash=h; txShow(`Withdrawing ${u.amount} ${u.symbol}`,'Waiting for confirmation',h); await
0x68575b07…67c7·#25,913,803·0xcb65d57e…ffeceb
k="false" aria-describedby="ens"> <p class="note" id="ens" aria-live="polite"></p> </div> <div class="field locked" id="fAmount"> <span class="flabel">Amount <em id="balHint"></em></span> <div class="amt"> <input type="text" class="input" id="amountInput" inputmode="decimal" placeholder="0" autocomplete="off" aria-label="Amount" aria-describedby="amountNote"> <button class="opt" id="maxBtn">Max</button> </div> <div class="row" id="amountRow" role="group" aria-label="Quick amounts"> <button class="opt" data-amount="1">1</button> <button class="opt" data-amount="10">10</button> <button class="opt" data-amount="100">100</button> </div> <p class="note" id="amountNote" aria-live="polite"></p> </div> <div class="field" id="fReview" hidden> <span class="flabel">What happens</span> <p class="summary" id="summary"></p> <p class="recipFull" id="recipFull"></p> <ol class="tl" id="timeline"></ol> <label class="check" for="autoClaimChk"><input type="checkbox" id="autoClaimChk"> Pay a keeper to auto-claim it</label> <p class="note" id="tipPre"></p> <p class="note" id="planNote" aria-live="polite"></p> <div class="alts"> <button class="btn alt" id="shareBtn">Copy request link</button> <button class="btn alt" id="recipeBtn">Copy the call</button> </div> </div> </div> <div class="col" id="colB"> <div class="field"> <span class="flabel">Asset <em id="assetHint"></em></span> <div class="grid" id="cryptoGrid" role="group" aria-label="Asset"></div> <button class="link" id="customTokenOpt">+ Custom token</button> </div> <div class="field" id="fDest" hidden> <span class="flabel">Deliver on <em id="destHint"></em></span> <div class="row" id="destRow" role="group" aria-label="Destination chain"></div> </div> <div class="field locked" id="fTime"> <span class="flabel">Unlocks after <em id="timeHint"></em></span> <div class="row" id="timeRow" role="group" aria-label="Timelock"> <button class="opt" data-time="600">10 MIN</button> <button class="opt" data-time="3600">1 HR</button> <button class="opt" data-time="86400">1 DAY</button> <button class="opt" id="customTimeOpt">Custom</button> </div> </div> <div class="field" id="fPreview" hidden> <span class="flabel">The position <em id="previewHint">updates as you choose</em></span> <div class="nftbox" id="nftBox"></div> </div> </div> </div> <div class="sendFoot"> <p class="foothint" id="footHint" aria-live="polite"></p> <button class="btn" id="confirmBtn">Send</button> </div> <div class="guardPane"> <p class="warn" id="gNotDeployed" hidden></p> <div class="field"> <span class="flabel">Your guardian <em id="gHint"></em></span> <div class="gcard" id="gCurrent">—</div> <div class="row2"> <button class="opt" id="gSet">Set</button> <button class="opt" id="gRemove">Remove</button> </div> </div> <div class="field" id="gPendingField" hidden> <span class="flabel">Pending change</span> <div class="gcard" id="gPending">—</div> <div class="row2"> <button class="opt" id="gCommit">Commit</button> <button class="opt" id="gCancel">Cancel</button> </div> </div> <div class="field"> <span class="flabel">Accounts you guard <em id="gWardHint"></em></span> <div class="list" id="gWards"></div> <div class="row2"> <button class="link" id="gAddWard">+ Add an account</button> <button class="link" id="gScan">Scan for mine</button> </div> </div> </div> <div class="takePane"> <div class="tabs" role="tablist" aria-label="Transfers"> <button class="tab" id="tabOut" role="tab" aria-selected="true" data-tab="outbound">Outbound</button> <button class="tab" id="tabIn" role="tab" aria-selected="false" data-tab="inbound">Inbound</button> <button class="tab" id="tabUnlocked" role="tab" aria-selected="false" data-tab="unlocked" hidden>Unlocked</button> </div> <div class="list" id="list" role="tabpanel" tabindex="0"></div> </div> </div> </main> <div id="tipLayer"></div> <div class="modal" id="themeModal" role="dialog" aria-modal="true" aria-labelledby="themeTitle"><div class="card"> <h3 id="themeTitle">Appearance</h3> <div class="picks" id="themeList"></div> <button class="btn alt" id="themeCancel">Cancel</button> </div></div> <div class="modal" id="chainModal" role="dialog" aria-modal="true" aria-labelledby="chainTitle"><div class="card"> <h3 id="chainTitle">Network</h3> <div class="picks" id="chainList"></div> <p class="note">SLOW is at the same address on each. Names always resolve on Ethereum.</p> <button class="btn alt" id="chainCancel">Cancel</button> </div></div> <div class="modal" id="walletModal" role="dialog" aria-modal="true" aria-labelledby="walletTitle"><div class="card"> <h3 id="walletTitle">Wallet</h3> <div class="picks" id="walletList"></div> <button class="btn alt" id="walletCancel">Cancel</button> </div></div> <div class="modal" id="timeModal" role="dialog" aria-modal="true" aria-labelledby="timeTitle"><div class="card"> <h3 id="timeTitle">Timelock</h3> <div class="tgrid"> <div><input type="number" id="dInput" min="0" placeholder="0" inputmode="numeric"><label for="dInput">Days</label></div> <div><input type="number" id="hInput" min="0" placeholder="0" inputmode="numeric"><label for="hInput">Hrs</label></div> <div><input type="number" id="mInput" min="0" placeholder="0" inputmode="numeric"><label for="mInput">Min</label></div> <div><input type="number" id="sInput" min="0" placeholder="0" inputmode="numeric"><label for="sInput">Sec</label></div> </div> <button class="btn" id="timeApply">Apply</button> </div></div> <div class="modal" id="tokenModal" role="dialog" aria-modal="true" aria-labelledby="tokenTitle"><div class="card"> <h3 id="tokenTitle">Custom token</h3> <input type="text" class="input" id="tokenInput" placeholder="0x\u2026 contract address" autocomplete="off" spellcheck="false"> <p class="note" id="tokenStatus"></p> <p class="note">Fee-on-transfer or rebasing tokens may strand funds.</p> <button class="btn" id="tokenApply">Apply</button> </div></div> <div class="modal" id="guardModal" role="dialog" aria-modal="true" aria-labelledby="guardTitle"><div class="card"> <h3 id="guardTitle">Guardian</h3> <input type="text" class="input" id="guardInput" placeholder="Address, .eth, .wei, or .gwei" autocomplete="off" spellcheck="false"> <p class="note" id="guardNote" aria-live="polite"></p> <p class="note" id="guardWarn"></p> <button class="btn" id="guardApply">Apply</button> <button class="btn alt" id="guardCancel">Cancel</button> </div></div> <div class="modal" id="gaModal" role="dialog" aria-modal="true" aria-labelledby="gaTitle"><div class="card"> <h3 id="gaTitle">Approve one outflow</h3> <p class="note">Pre-authorises a single operation for <b id="gaWard"></b> — <span id="gaNonce"></span>. It is consumed when they run it, and voided if their guardian changes.</p> <select class="input" id="gaKind" aria-label="Operation"> <option value="withdraw">Withdraw the underlying</option> <option value="transfer">Transfer the position</option> </select> <input type="text" class="input" id="gaTo" placeholder="Recipient address" autocomplete="off" spellcheck="false" aria-label="Recipient"> <input type="text" class="input" id="gaId" placeholder="Position id" inputmode="numeric" autocomplete="off" aria-label="Position id"> <input type="text" class="input" id="gaAmt" placeholder="Amount, in base units" inputmode="numeric" autocomplete="off" aria-label="Amount"> <p class="note" id="gaNote" aria-live="polite"></p> <button class="btn" id="gaApply">Approve</button> <button class="btn alt" id="gaCancel">Cancel</button> </div></div> <div class="modal" id="detailModal" role="dialog" aria-modal="true" aria-labelledby="detailTitle"><div class="card" tabindex="-1"> <h3 id="detailTitle">Transfer</h3> <dl class="rows" id="detailRows"></dl> <button class="btn" id="detailAction"></button> <button class="btn alt" id="detailShare">Copy link to this transfer</button> <button class="btn alt" id="detailForget" hidden>Dismiss from my list</button> <button class="btn alt" id="detailClose">Close</button> </div></div> <div class="modal" id="exitModal" role="dialog" aria-modal="true" aria-labelledby="exitTitle"><div class="card"> <h3 id="exitTitle">Withdraw</h3> <p class="note" id="exitAmt"></p> <input type="text" class="input" id="exitTo" placeholder="Destination \u2014 blank means yourself" autocomplete="off" spellcheck="false" aria-label="Destination"> <input type="text" class="input" id="exitData" placeholder="Then call it with this data (optional, 0x\u2026)" autocomplete="off" spellcheck="false" aria-label="Call data"> <p class="note" id="exitNote" aria-live="polite"></p> <button class="btn" id="exitSend">Withdraw</button> <button class="btn alt" id="exitCopy">Copy the calls</button> <button class="btn alt" id="exitClose">Close</button> </div></div> <div class="modal" id="helpModal" role="dialog" aria-modal="true" aria-labelledby="helpTitle"><div class="card"> <h3 id="helpTitle">How SLOW works</h3> <dl class="rows"> <div class="kv"><dt>Wrap</dt><dd>Your token becomes a SLOW position with a timelock you pick.</dd></div> <div class="kv"><dt>Reverse</dt><dd>Until the lock expires, you can pull it back. Wrong address, changed mind, stolen key.</dd></div> <div class="kv"><dt>Claim</dt><dd>After it expires, the recipient takes the underlying. A keeper can do it for them if you tip.</dd></div> <div class="kv"><dt>Recover</dt><dd>If nobody ever claims, you can take it back 30 days after expiry.</dd></div> <div class="kv"><dt>Guardian</dt><dd>Optional cosigner who must approve every outflow from your account.</dd></div> </dl> <p class="note">Every transfer is an ERC-1155 position that draws its own picture on chain.</p> <button class="btn alt" id="helpClose">Close</button> </div></div> <div class="txbar" id="txbar" role="status" aria-live="polite" hidden> <span class="spinner sm" id="txSpin"></span> <span class="txmsg"><b id="txMsg"></b><em id="txStep"></em></span> <a id="txLink" target="_blank" rel="noopener" hidden>View</a> <button id="txDismiss" aria-label="Dismiss" hidden>×</button> </div> <div class="toast" id="toast" role="status" aria-live="polite"></div> <script> (()=>{ 'use strict'; const SLOW = '0x000000006513B7821171C8447ec7ECdfa3b956Fd'; const ZERO = '0x0000000000000000000000000000000000000000'; const ENS_REG = '0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e'; const WNS = '0x0000000000696760E15f265e828DB644A0c242EB'; const GNS = '0x9D51D507BC7264d4fE8Ad1cf7Fe191933A0a81d6'; const MC3 = '0xcA11bde05977b3631167028862bE2a173976CA11'; const MAINNET = 1; const GRACE = 2592000; const GUARD_DELAY = 86400; const TOPIC_GUARDIAN_SET = '0xc3ce29e3ab42e524b6f6f1b4d3674898d503ee3577a64ac87b555904ebc14138'; const MAX_DELAY = 3155760000; const CHAINS = { 1: { id:1, hex:'0x1', name:'Ethereum', short:'Mainnet', explorer:'https://etherscan.io', deployBlock:24986598, minGasPrice:50_000_000n, fallbackGasPrice:2_000_000_000n, minTip:20_000_000_000_000n, rpcs:[ 'https://ethereum-rpc.publicnode.com', 'https://eth.llamarpc.com', 'https://1rpc.io/eth', 'https://eth-pokt.nodies.app', 'https://gateway.tenderly.co/public/mainnet', ], tokens:[ {symbol:'ETH', address:'0x0000000000000000000000000000000000000000', decimals:18}, {symbol:'USDC', address:'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', decimals:6}, {symbol:'USDe', address:'0x4c9edd5852cd905f086c759e8383e09bff1e68b3', decimals:18}, {symbol:'cbBTC', address:'0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf', decimals:8}, {symbol:'wstETH',address:'0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0', decimals:18}, {symbol:'WBTC', address:'0x2260fac5e5542a773aa44fbcfedf7c193bc2c599', decimals:8}, {symbol:'USDT', address:'0xdac17f958d2ee523a2206206994597c13d831ec7', decimals:6}, {symbol:'BOLD', address:'0x6440f144b7e50d6a8439336510312d2f54beb01d', decimals:18}, ], }, 8453: { id:8453, hex:'0x2105', name:'Base', short:'Base', explorer:'https://basescan.org', deployBlock:0, minGasPrice:1_000_000n, fallbackGasPrice:50_000_000n, minTip:2_000_000_000_000n, rpcs:[ 'https://base-rpc.publicnode.com', 'https://mainnet.base.org', 'https://base.drpc.org', 'https://1rpc.io/base', ], tokens:[ {symbol:'ETH', address:'0x0000000000000000000000000000000000000000', decimals:18}, {symbol:'USDC', address:'0x833589fcd6edb6e08f4c7c32d4f71b54bda02913', decimals:6}, {symbol:'USDe', address:'0x5d3a1ff2b6bab83b63cd9ad0787074081a52ef34', decimals:18}, {symbol:'cbBTC', address:'0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf', decimals:8}, {symbol:'wstETH',address:'0xc1cba3fcea344f92d9239c08c0568f6f2f0ee452', decimals:18}, {symbol:'cbETH', address:'0x2ae3f1ec7f1f5012cfeab0185bfc7aa3cf0dec22', decimals:18}, {symbol:'USDT', address:'0xfde4c96c8593536e31f229ea8f37b2ada2699bb2', decimals:6}, {symbol:'AERO', address:'0x940181a94a35a4569e4529a3cdfb74e38fd98631', decimals:18}, ], addChain:{ chainId:'0x2105', chainName:'Base', nativeCurrency:{name:'Ether', symbol:'ETH', decimals:18}, rpcUrls:['https://mainnet.base.org'], blockExplorerUrls:['https://basescan.org'], }, }, 4663: { id:4663, hex:'0x1237', name:'Robinhood Chain', short:'Robinhood', explorer:'https://robinscan.io', deployBlock:0, minGasPrice:10_000_000n, fallbackGasPrice:500_000_000n, minTip:5_000_000_000_000n, rpcs:['https://rpc.mainnet.chain.robinhood.com'], tokens:[ {symbol:'ETH', address:'0x0000000000000000000000000000000000000000', decimals:18}, {symbol:'USDG', address:'0x5fc5360d0400a0fd4f2af552add042d716f1d168', decimals:6}, {symbol:'USDe', address:'0x5d3a1ff2b6bab83b63cd9ad0787074081a52ef34', decimals:18}, {symbol:'cbBTC',address:'0xcec185eb182c47d1ba1efc84e6959e18cd620be4', decimals:8}, {symbol:'NVDA', address:'0xd0601ce157db5bdc3162bbac2a2c8af5320d9eec', decimals:18}, {symbol:'SPY', address:'0x117cc2133c37b721f49de2a7a74833232b3b4c0c', decimals:18}, {symbol:'SPCX', address:'0x4a0e65a3eccec6dbe60ae065f2e7bb85fae35eea', decimals:18}, {symbol:'GME', address:'0x1b0e319c6a659f002271b69db8a7df2f911c153e', decimals:18}, ], addChain:{ chainId:'0x1237', chainName:'Robinhood Chain', nativeCurrency:{name:'Ether', symbol:'ETH', decimals:18}, rpcUrls:['https://rpc.mainnet.chain.robinhood.com'], blockExplorerUrls:['https://robinscan.io'], }, }, }; const CHAIN_IDS = [1, 8453, 4663]; const L1_ALIAS = 0x1111000000000000000000000000000000001111n; const ADDR_MASK = (1n<<160n)-1n; const aliasOf = a => '0x'+(((BigInt(a)+L1_ALIAS)&ADDR_MASK).toString(16).padStart(40,'0')); const unaliasOf = a => '0x'+(((BigInt(a)-L1_ALIAS)&ADDR_MASK).toString(16).padStart(40,'0')); const BRIDGES = { 8453: { kind:'op', from:1, entry:'0x49048044D57e1C92A77f79988d21Fa8fAF74E97e', l2GasLimit:600_000n, l2GasLimitContract:2_500_000n, }, 4663: { kind:'arb', from:1, entry:'0x1A07cc4BD17E0118BdB54D70990D2158AbAD7a2D', l2GasLimit:800_000n, l2GasLimitContract:2_500_000n, nodeInterface:'0x00000000000000000000000000000000000000C8', }, }; const bridgeTo = id => BRIDGES[id]; const BRIDGE_REGISTRY = null; const SLOW_ARRIVAL = '0x9F8D89D298caBDC0D64cbA3888D0DA85Dc95097f'; const KIND_BY_ID = {1:'op', 2:'arb'}; async function loadBridgeRoutes(){ if(!BRIDGE_REGISTRY || S.routesLoaded) return; S.routesLoaded = true; try { const [r] = await mc([{to:BRIDGE_REGISTRY, data:SEL.allRoutes}], 0, MAINNET); if(!r) return; const d=strip(r); const idsOff=parseInt(d.slice(0,64),16)*2, n=parseInt(d.slice(idsOff,idsOff+64),16); const rtOff=parseInt(d.slice(64,128),16)*2; for(let i=0;i<n;i++){ const id=Number(BigInt('0x'+d.slice(idsOff+64+i*64, idsOff+128+i*64))); const base=rtOff+64+i*4*64; const entry='0x'+d.slice(base+24, base+64); const kind=KIND_BY_ID[parseInt(d.slice(base+64, base+128),16)]; const gas=BigInt('0x'+d.slice(base+128, base+192)); if(!kind || BRIDGES[id] || !CHAINS[id]) continue; BRIDGES[id]={kind, from:MAINNET, entry, l2GasLimit:gas, registry:true}; } } catch(e){ } } function depositRecipe(){ if(!ready()) return null; const isETH = S.token===ZERO; let wei; try { wei = parseUnits(S.amount, S.decimals); } catch(e){ return null; } const chain = S.dest || S.chain; const via = S.dest ? bridgeVia(S.dest) : null; return { chainId: chain, chainName: cfg(chain).name, to: isETH && via ? via : SLOW, data: isETH ? innerDepositCalldata(S.resolved, S.delay, S.dest || undefined) : cd(SEL.depositTo, ['address','address','uint256','uint96','bytes'], [S.token, S.resolved, wei, BigInt(S.delay), '0x']), value: isETH ? wei : 0n, note: isETH ? null : `Requires an allowance of ${S.amount} ${S.symbol} to ${SLOW} first.`, via: isETH ? via : null, origin: isETH && via ? (S.account || ZERO) : null, }; } function recipeText(r){ return [ `# SLOW deposit — ${r.chainName} (chain ${r.chainId})`, `to: ${r.to}`, `value: ${r.value} wei`, `data: ${r.data}`, r.note ? `note: ${r.note}` : null, r.via ? `note: Through SlowArrival ${r.via}, so the reverse survives the crossing.` : null, r.via ? ` Held for ${r.origin} — the originHint in the calldata, and the only` : null, r.via ? ` address SlowArrival.reverse/.clawback answer to. Send via a bridge.` : null, ].filter(Boolean).join('\n'); } function innerDepositCalldata(recipient, delay, destChain){ if(bridgeVia(destChain)){ return cd(SEL.arrive, ['address','uint96','address','uint256'], [recipient, BigInt(delay), S.account || ZERO, 0n]); } return cd(SEL.depositTo, ['address','address','uint256','uint96','bytes'], [ZERO, recipient, 0n, BigInt(delay), '0x']); } const bookArrival = destChain => (BRIDGES[destChain] && BRIDGES[destChain].registry && S.book[destChain]) ? S.book[destChain] : SLOW_ARRIVAL; const bridgeVia = destChain => (SLOW_ARRIVAL && S.arrivalDeployed[destChain] === true) ? bookArrival(destChain) : null; const bridgeTarget = destChain => bridgeVia(destChain) || SLOW; const ARRIVAL_NAME = '0x536c6f774172726976616c000000000000000000000000000000000000000000'; async function loadArrivalAddress(id){ if(!BRIDGE_REGISTRY || !BRIDGES[id] || !BRIDGES[id].registry) return; if(S.book[id] !== undefined) return; S.book[id] = null; try { const [r] = await mc([{to:BRIDGE_REGISTRY, data: cd(SEL.deployments, ['bytes32','uint256'], [ARRIVAL_NAME, BigInt(id)])}], 0, MAINNET); if(!r) return; const at = '0x' + strip(r).slice(24, 64); if(at !== ZERO) S.book[id] = at; } catch(e){ } } async function destGasLimit(destChain, recipient){ const b = BRIDGES[destChain]; const big = b.l2GasLimitContract || b.l2GasLimit; try { const code = await rpc('eth_getCode', [recipient, 'latest'], destChain); if(typeof code !== 'string') return big; return code.length > 4 ? big : b.l2GasLimit; } catch(e){ return big; } } async function bridgeCosts(destChain, recipient, delay){ const b = BRIDGES[destChain]; const inner = innerDepositCalldata(recipient, delay, destChain); const l2Gas = await destGasLimit(destChain, recipient); if(b.kind!=='arb') return {inner, l2Gas, submission:0n, maxFee:0n, prepaid:0n, extra:0n}; const l1Base = await l1BaseFee(); const bytesLen = BigInt(strip(inner).length/2); let submission; try { const [f] = await mc([{to:b.entry, data: cd(SEL.calcSubmissionFee, ['uint256','uint256'], [bytesLen, l1Base])}], 0, MAINNET); submission = f ? decode(['uint256'], f)[0] : 0n; } catch(e){ submission = 0n; } if(!submission) submission = 100_000_000_000_000n; submission = submission * 3n / 2n; let maxFee; try { maxFee = BigInt(await rpc('eth_gasPrice', [], destChain)) * 2n; } catch(e){ maxFee = cfg(destChain).fallbackGasPrice * 2n; } if(maxFee < cfg(destChain).minGasPrice) maxFee = cfg(destChain).minGasPrice; const prepaid = l2Gas * maxFee; return {inner, l2Gas, submission, maxFee, prepaid, extra: submission + prepaid}; } async function bridgePlan(destChain, recipient, delay, weiAmount){ const b = BRIDGES[destChain]; if(!b) throw new Error(`No bridge route to ${cfg(destChain).name}`); const target = bridgeTarget(destChain); const {inner, l2Gas, submission, maxFee, prepaid} = await bridgeCosts(destChain, recipient, delay); if(b.kind==='op'){ return { to:b.entry, data: cd(SEL.depositTransaction, ['address','uint256','uint256','bool','bytes'], [target, weiAmount, l2Gas, false, inner]), value: weiAmount, parts: [['Bridged', weiAmount]], }; } return { to:b.entry, data: cd(SEL.createRetryableTicket, ['address','uint256','uint256','address','address','uint256','uint256','bytes'], [target, weiAmount, submission, S.account, S.account, l2Gas, maxFee, inner]), value: weiAmount + submission + prepaid, parts: [['Bridged', weiAmount], ['Ticket', submission], ['Destination gas', prepaid]], }; } async function l1BaseFee(){ try { const b = await rpc('eth_getBlockByNumber', ['latest', false], MAINNET); return BigInt(b.baseFeePerGas || '0x0'); } catch(e){ return 2_000_000_000n; } } const canBridge = (fromChain, toChain) => { const b = BRIDGES[toChain]; if(!b || b.from !== fromChain || S.slowDeployed[toChain] !== true) return false; const notProvenEOA = !!S.account && S.accountIsContract !== false; if(S.arrivalDeployed[toChain] !== true && (b.kind === 'arb' || notProvenEOA)) return false; if(b.kind === 'arb' && notProvenEOA) return false; return true; }; const cfg = id => CHAINS[id ?? S.chain] || CHAINS[MAINNET]; const SEL = { depositTo:'0x94eeaec9', depositToWithTip:'0x75f92e42', depositToWithPermit:'0x2f19167b', depositToWithTipAndPermit:'0x9cdec3f5', reverse:'0x97d15425', claim:'0x379607f5', clawback:'0xfcc36bc9', unlock:'0x6198e339', withdrawFrom:'0xd4fdc309', multicall:'0xac9650d8', pendingTransfers:'0x6577b86a', getOut:'0xd40d4bc6', getIn:'0xe3993ee7', outCount:'0x73bfdce5', inCount:'0xea712c4f', forgetInbound:'0x03ca0e44', outAt:'0xb5f8348b', inAt:'0x8b40f4c5', guardians:'0x0633b14a', isWithdrawalApprovalNeeded:'0xabe616b8', setGuardian:'0x8a0dac4a', commitGuardian:'0xa952a15f', cancelGuardianChange:'0xdb6c927d', pendingGuardian:'0x3b0a6875', approveTransfer:'0xfa02c4b7', revokeApproval:'0x47d07c4c', guardianApproved:'0x8d4a1b58', isGuardianApprovalNeeded:'0xaade934f', predictWithdrawalId:'0x2d0eae84', predictTransferId:'0xd8a29acf', wardsOf:'0xb266b777', arrive:'0x24eb6264', depositTransaction:'0xe9e05c42', createRetryableTicket:'0x679b6ded', calcSubmissionFee:'0xa66b327d', allRoutes:'0x71da4170', deployments:'0x24495881', unlockedBalances:'0x5b96484e', guardianNonces:'0xeac43b4a', gateAddr:'0x7a0ebc88', tips:'0xa5c68c59', refundTip:'0xd27e1e72', approve:'0x095ea7b3', allowance:'0xdd62ed3e', decimals:'0x313ce567', symbol:'0x95d89b41', name:'0x06fdde03', balanceOf:'0x70a08231', permit:'0xd505accf', nonces:'0x7ecebe00', domainSeparator:'0x3644e515', permitTypehash:'0x30adf81f', resolver:'0x0178b8bf', addr:'0x3b3b57de', ensName:'0x691f3431', wnsReverse:'0x9af8b7aa', aggregate3:'0x82ad56cb', }; const TYPEHASH_2612 = '6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9'; const TOKEN_COLORS = { ETH:'#ff69b4', WETH:'#ff69b4', USDC:'#2775ca', USDT:'#26a17b', BOLD:'#c026d3', USDe:'#6d28d9', USDG:'#ccff00', NVDA:'#4d7c0f', wstETH:'#38bdf8', WBTC:'#a0522d', cbETH:'#71f26d', AERO:'#5d7222', cbBTC:'#f7931a', SPY:'#0f766e', SPCX:'#b9bec7', GME:'#dc2626', }; const PRESETS = { ETH:['0.01','0.1','1'], WETH:['0.01','0.1','1'], cbBTC:['0.001','0.01','0.1'], WBTC:['0.001','0.01','0.1'], wstETH:['0.01','0.1','1'], cbETH:['0.01','0.1','1'], AERO:['1','10','100'], NVDA:['0.1','1','10'], SPY:['0.1','1','10'], SPCX:['0.1','1','10'], GME:['0.1','1','10'], default:['10','100','1000'], }; const M64 = (1n<<64n)-1n; const RC = [1n,32898n,9223372036854808714n,9223372039002292224n,32907n,2147483649n,9223372039002292353n,9223372036854808585n,138n,136n,2147516425n,2147483658n,2147516555n,9223372036854775947n,9223372036854808713n,9223372036854808579n,9223372036854808578n,9223372036854775936n,32778n,9223372039002259466n,9223372039002292353n,92233
0x68575b07…67c7·#25,913,801·0x8adf0d34…a5d3ff
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover"> <link rel="icon" type="image/svg+xml" sizes="any" href="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Cpath%20d%3D%22M0%205.06C0%203.32%200%202.46%20.33%201.79C.64%201.15%201.15%20.64%201.79%20.33C2.46%200%203.32%200%205.06%200H58.94C6.68%200%2061.54%200%2062.21%20.33C62.85%20.64%2063.36%201.15%2063.67%201.79C64%202.46%2064%203.32%2064%205.06V58.94C64%206.68%2064%2061.54%2063.67%2062.21C63.36%2062.85%2062.85%2063.36%2062.21%2063.67C61.54%2064%206.68%2064%2058.94%2064H5.06C3.32%2064%202.46%2064%201.79%2063.67C1.15%2063.36%20.64%2062.85%20.33%2062.21C0%2061.54%200%206.68%200%2058.94V5.06Z%22%20fill%3D%22%230A0A0A%22%20stroke%3D%22%23FFFFFF%22%20stroke-width%3D%222%22%2F%3E%3Cpath%20d%3D%22M48%2C16%20H16%20V35%20H42%20V42%20H16%20V48%20H48%20V29%20H22%20V22%20H48%20Z%22%20fill%3D%22%23FFFFFF%22%2F%3E%3C%2Fsvg%3E"> <title>SLOW</title> <style> :root{ --bg:#000;--fg:#fff; --bg2:#0d0d0d;--bg3:#1c1c1c;--bd:#5c5c5c;--bd2:#8a8a8a;--dim:#9e9e9e; --rule:#1e1e1e; --ok:#3ddc84;--warn:#ffb020;--err:#ff6b6b; --t:120ms linear; --sans:'Helvetica Neue',Helvetica,Arial,sans-serif; --mono:ui-monospace,'SF Mono',SFMono-Regular,Menlo,Consolas,monospace; } body.light{ --bg:#fff;--fg:#000; --bg2:#f2f2f2;--bg3:#e4e4e4;--bd:#8e8e8e;--bd2:#5f5f5f;--dim:#585858; --rule:#e6e6e6; --ok:#0a7f3f;--warn:#9a5b00;--err:#c62828; } *{box-sizing:border-box;margin:0;padding:0} [hidden]{display:none!important} html,body{height:100%} :root{color-scheme:dark} body.light,:root:has(body.light){color-scheme:light} body{ min-height:100dvh;background:var(--bg);color:var(--fg); font-family:var(--sans);font-size:15px;line-height:1.4; -webkit-font-smoothing:antialiased; display:flex;flex-direction:column;overflow:hidden; } button,input{font:inherit;color:inherit;border-radius:0} :focus-visible{outline:2px solid var(--fg);outline-offset:2px} .txbar :focus-visible,.btn:focus-visible,.tab[aria-selected=true]:focus-visible, .seg button[aria-selected=true]:focus-visible,.crypto[aria-pressed=true]:focus-visible, .opt[aria-pressed=true]:focus-visible{outline-color:var(--bg);outline-offset:-4px;box-shadow:0 0 0 2px var(--fg)} @media (prefers-reduced-motion:reduce){*,*::before,*::after{transition-duration:.001ms!important;animation-duration:.001ms!important}} .sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap} .mono{font-family:var(--mono);font-variant-numeric:tabular-nums} .bar{ flex:0 0 auto;display:flex;justify-content:space-between;align-items:stretch;gap:8px; padding:12px 14px;padding-top:max(12px,env(safe-area-inset-top));z-index:20; } .bar-r{display:flex;gap:8px;align-items:stretch;min-width:0} .chip{ display:inline-flex;align-items:center;gap:7px; background:transparent;color:var(--fg);border:1px solid var(--bd); padding:0 12px;height:36px;font-size:13px;cursor:pointer; letter-spacing:.02em;white-space:nowrap; transition:border-color var(--t),background var(--t); } @media (hover:hover){.chip:hover{border-color:var(--fg);background:var(--bg2)}} .chip svg{width:17px;height:17px;flex:0 0 auto;display:block} .chip .lbl{overflow:hidden;text-overflow:ellipsis;max-width:34vw} #themeBtn,#helpBtn{width:36px;padding:0;justify-content:center;font-size:14px} .wordmark{ display:inline-flex;align-items:center;gap:10px; background:transparent;border:none;cursor:pointer;color:var(--fg); font-size:15px;font-weight:700;letter-spacing:.26em;padding:0 2px; } .wordmark .mk{width:26px;height:26px;flex:0 0 auto} @media (hover:hover){.wordmark:hover{opacity:.65}} #walletBtn.on{border-color:var(--fg)} #netDot{width:6px;height:6px;background:var(--err);flex:0 0 auto} .warn{ flex:0 0 auto;width:100%;max-width:420px;margin:0 auto 14px;padding:8px 11px; font-size:12.5px;border:1px solid var(--warn);color:var(--fg);background:var(--bg2); } main{ flex:1 1 auto;min-height:0;display:flex;flex-direction:column;align-items:center; padding:0 14px 14px;padding-bottom:max(14px,env(safe-area-inset-bottom)); } .home{display:none;flex-direction:column;align-items:center;gap:20px;margin:auto 0} body.v-home .home{display:flex} .frame{display:flex;flex-direction:column;width:190px;border:2px solid var(--fg);user-select:none} .box{ height:92px;display:flex;justify-content:center;align-items:center; font-weight:700;font-size:22px;letter-spacing:.14em;cursor:pointer;width:100%;border:none; background:var(--bg);color:var(--fg);transition:background var(--t),color var(--t); } @media (hover:hover){.box:hover{background:var(--fg);color:var(--bg)}} #sendBox{background:var(--fg);color:var(--bg)} @media (hover:hover){#sendBox:hover{background:var(--bg);color:var(--fg)}} .divider{height:2px;background:var(--fg);flex:0 0 auto} .tag{font-size:12.5px;color:var(--dim);text-align:center;max-width:230px} .panel{ display:none;flex-direction:column;width:100%;max-width:420px; flex:1 1 auto;min-height:0;margin:0 auto;padding-top:4px; } body.v-send .panel,body.v-take .panel{display:flex} body.v-send .panel{flex:0 1 auto} body.v-take .panel{max-width:640px} .seg{display:flex;border:1px solid var(--bd);flex:0 0 auto;margin-bottom:22px} .seg button{ flex:1 1 0;height:44px;background:transparent;border:none;cursor:pointer; font-size:12px;font-weight:700;letter-spacing:.14em;color:var(--dim); transition:background var(--t),color var(--t); } .seg button + button{border-left:1px solid var(--bd)} @media (hover:hover){.seg button:hover{color:var(--fg)}} .seg button[aria-selected=true]{background:var(--fg);color:var(--bg)} .sendPane{display:none;flex-direction:column;gap:20px;overflow-y:auto;min-height:0;flex:1 1 auto} .col{display:contents} #colA > .field:nth-child(1){order:1} #colB > .field:nth-child(1){order:2} #fAmount{order:3} #fDest{order:4} #fTime{order:5} #fPreview{order:6} #fReview{order:7} body.v-send .sendPane{display:flex} .field{display:flex;flex-direction:column;gap:8px;transition:opacity var(--t)} .sendPane > .field + .field, .guardPane > .field + .field{padding-top:20px;border-top:1px solid var(--rule)} .tl li:last-child{padding-bottom:0} .field.locked{pointer-events:none} .field.locked > :not(.flabel){opacity:.55;transition:opacity var(--t)} .field.locked .opt:disabled,.field.locked .input:disabled,.field.locked .crypto:disabled{opacity:1} .flabel{ display:flex;justify-content:space-between;align-items:baseline;gap:10px; font-size:10.5px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:var(--dim); } .flabel em{font-style:normal;font-family:var(--mono);letter-spacing:0;text-transform:none;font-weight:400} .grid{display:grid;grid-template-columns:repeat(4,1fr);gap:6px} .crypto{ display:flex;justify-content:center;align-items:center; height:52px;padding:0 4px 0 0; border-left-width:5px;border-left-style:solid; font-family:var(--mono);font-size:13px;letter-spacing:.02em; background:transparent;color:var(--fg); cursor:pointer;border:1px solid var(--bd); transition:border-color var(--t),background var(--t),color var(--t),opacity var(--t); overflow:hidden;white-space:nowrap; } .grid.dim .crypto{opacity:.45} @media (hover:hover){.crypto:hover:not([aria-pressed=true]){border-color:var(--fg)}} .crypto[aria-pressed=true]{background:var(--fg);color:var(--bg);border-color:var(--fg);opacity:1!important} .row{display:grid;grid-template-columns:repeat(4,1fr);gap:6px} .opt{ height:44px;display:flex;align-items:center;justify-content:center; font-family:var(--mono);font-size:13px;font-variant-numeric:tabular-nums; background:transparent;color:var(--fg);cursor:pointer;border:1px solid var(--bd); transition:border-color var(--t),background var(--t),color var(--t); white-space:nowrap;overflow:hidden;padding:0 4px; } @media (hover:hover){.opt:hover:not(:disabled){border-color:var(--fg)}} .opt[aria-pressed=true]{background:var(--fg);color:var(--bg);border-color:var(--fg)} .opt:disabled{opacity:1;background:var(--bg2);color:var(--dim);border-color:var(--bd);cursor:not-allowed} .amt{display:grid;grid-template-columns:1fr auto;gap:6px} .amt .input{font-size:19px;padding:12px 13px} #maxBtn{padding:0 16px} .row{display:grid;grid-template-columns:repeat(4,1fr);gap:6px} #amountRow{grid-template-columns:repeat(3,1fr)} .tl{list-style:none;display:flex;flex-direction:column;margin:2px 0 4px} .tl li{display:grid;grid-template-columns:13px 1fr;gap:12px;padding-bottom:16px;position:relative} .tl li:last-child{padding-bottom:0} .tl li::before{ content:'';grid-column:1;width:9px;height:9px;margin-top:4px; background:var(--fg);border-radius:50%; } .tl li:not(:last-child)::after{ content:'';position:absolute;left:4px;top:15px;bottom:-1px;width:1px;background:var(--bd); } .tl li.now::before{background:var(--ok)} .tl li.warn::before{background:var(--warn)} .tl .when{display:block;font-family:var(--mono);font-size:12px;color:var(--fg)} .tl .what{display:block;font-size:12.5px;color:var(--dim);margin-top:2px} .txbar{ position:fixed;left:50%;transform:translateX(-50%); bottom:max(16px,env(safe-area-inset-bottom));z-index:65; display:flex;align-items:center;gap:12px; background:var(--fg);color:var(--bg);padding:11px 14px; max-width:min(94vw,520px);font-size:12.5px; } .txbar .txmsg{display:flex;flex-direction:column;min-width:0} .txbar b{font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis} .txbar em{font-style:normal;font-size:10.5px;opacity:.7;letter-spacing:.1em;text-transform:uppercase} .txbar a{color:var(--bg);font-family:var(--mono);font-size:11px;text-decoration:underline;white-space:nowrap} .txbar button{background:transparent;border:none;color:var(--bg);cursor:pointer;font-size:17px;line-height:1;padding:0 2px} .spinner.sm{width:15px;height:15px;border-width:2px;border-color:rgba(0,0,0,.25);border-top-color:var(--bg);flex:0 0 auto} body.light .spinner.sm{border-color:rgba(255,255,255,.3);border-top-color:var(--bg)} .skel{height:52px;border:1px solid var(--bd);background:linear-gradient(90deg,transparent,var(--bg2),transparent); background-size:200% 100%;animation:sk 1.1s linear infinite} @keyframes sk{to{background-position:-200% 0}} .link{ background:transparent;color:var(--dim);font-size:11px;cursor:pointer; letter-spacing:.14em;text-transform:uppercase;border:1px dashed var(--bd); height:44px;transition:color var(--t),border-color var(--t); } @media (hover:hover){.link:hover:not(:disabled){color:var(--fg);border-color:var(--fg)}} .link:disabled{background:var(--bg2);color:var(--dim);border-color:var(--bd);border-style:solid;cursor:not-allowed} .link[aria-pressed=true]{background:var(--fg);color:var(--bg);border:1px solid var(--fg);border-style:solid;font-weight:700} .sendFoot{display:none;flex:0 0 auto;flex-direction:column;gap:9px;padding-top:13px;border-top:1px solid var(--bd)} body.v-send .sendFoot{display:flex} .foothint{font-size:11.5px;color:var(--dim);text-align:center;min-height:16px;line-height:1.35} .takePane{display:none;flex-direction:column;gap:14px;flex:1 1 auto;min-height:0} body.v-take .takePane{display:flex} .guardPane{display:none;flex-direction:column;gap:20px;overflow-y:auto;min-height:0} body.v-guard .guardPane{display:flex} body.v-guard .panel,body.v-take .panel{display:flex} .gcard{ border:1px solid var(--bd);padding:13px;font-family:var(--mono);font-size:13px; overflow-wrap:anywhere;display:flex;flex-direction:column;gap:5px; } .gcard .sub{font-size:11px;color:var(--dim)} .gcard.none{color:var(--dim)} .row2{display:grid;grid-template-columns:1fr 1fr;gap:6px} .ward{border:1px solid var(--bd);display:flex;flex-direction:column} .ward-h{ display:flex;justify-content:space-between;align-items:center;gap:10px; padding:12px 13px;background:transparent;border:none;cursor:pointer; font-family:var(--mono);font-size:12.5px;color:var(--fg);text-align:left; } @media (hover:hover){.ward-h:hover{background:var(--bg2)}} .ward-h .cnt{font-size:11px;color:var(--dim)} .ward-b{border-top:1px solid var(--bd);display:flex;flex-direction:column} .wtx{display:grid;grid-template-columns:1fr auto;gap:10px;padding:10px 13px;align-items:center} .wtx+.wtx{border-top:1px solid var(--bd)} .wtx-i{font-family:var(--mono);font-size:12px;min-width:0} .wtx-i span{display:block;font-size:10.5px;color:var(--dim)} .wtx button{ background:transparent;border:1px solid var(--bd);color:var(--fg);cursor:pointer; font-size:10px;font-weight:700;letter-spacing:.12em;text-transform:uppercase; padding:7px 12px;white-space:nowrap; } @media (hover:hover){.wtx button:hover{border-color:var(--fg)}} .wtx button.on{background:var(--ok);border-color:var(--ok);color:#000} .tabs{display:flex;gap:0;flex:0 0 auto;border-bottom:1px solid var(--bd)} .tab{ flex:1 1 0;height:36px;background:transparent;border:none;cursor:pointer; font-size:11px;font-weight:400;letter-spacing:.14em;text-transform:uppercase;color:var(--dim); transition:background var(--t),color var(--t); } @media (hover:hover){.tab:hover{color:var(--fg)}} .tab[aria-selected=true]{background:transparent;color:var(--fg);font-weight:700;box-shadow:inset 0 -3px 0 var(--fg)} .list{ flex:1 1 auto;min-height:0;overflow-y:auto;display:flex;flex-direction:column;gap:1px; scrollbar-width:thin;scrollbar-color:var(--bd) transparent; } .list::-webkit-scrollbar{width:6px} .list::-webkit-scrollbar-thumb{background:var(--bd)} .tx{display:grid;grid-template-columns:3px 1fr auto;border:1px solid var(--bd);flex:0 0 auto} .tx.act{border-color:var(--fg)} .tx-bar{background:var(--dim)} .tx-open{ display:flex;flex-direction:column;gap:7px;padding:11px 13px; background:transparent;border:none;cursor:pointer;text-align:left;min-width:0; } @media (hover:hover){.tx-open:hover{background:var(--bg2)}} .tx-line{display:flex;justify-content:space-between;align-items:baseline;gap:12px;min-width:0} .tx-amt{font-family:var(--mono);font-size:14px;font-variant-numeric:tabular-nums;white-space:nowrap} .tx-party{font-family:var(--mono);font-size:11.5px;color:var(--dim);overflow:hidden;text-overflow:ellipsis} .tx-track{height:2px;background:var(--bd);position:relative;overflow:hidden} .tx-fill{position:absolute;inset:0 auto 0 0;background:var(--fg);transition:width .6s linear} .tx.act .tx-fill{background:var(--ok)} .tx-meta{display:flex;justify-content:space-between;gap:10px;font-size:11px;color:var(--dim)} .tx.act .tx-status{color:var(--ok);font-weight:700} .tx.warn-row .tx-status{color:var(--warn);font-weight:700} .tx-act{ padding:0 16px;background:transparent;border:none;border-left:1px solid var(--bd); cursor:pointer;font-size:11px;font-weight:700;letter-spacing:.12em;color:var(--fg); transition:background var(--t),color var(--t);white-space:nowrap; } @media (hover:hover){.tx-act:hover{background:var(--fg);color:var(--bg)}} .tx.act .tx-act{border-left-color:var(--fg)} .empty{ display:flex;flex-direction:column;align-items:center;gap:16px; padding:52px 12px;color:var(--dim);font-size:13px;text-align:center; } .modal{display:none;position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:80;overflow-y:auto;padding:14px} body.light .modal{background:rgba(0,0,0,.4)} .modal.on{display:flex;align-items:center;justify-content:center} .card:focus{outline:none} .card{ background:var(--bg);border:2px solid var(--fg);color:var(--fg); padding:22px;width:min(400px,100%);margin:auto; display:flex;flex-direction:column;gap:14px;overflow-wrap:break-word; } .card h3{font-size:11px;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:var(--dim)} .summary{font-family:var(--mono);font-size:19px;line-height:1.35} .recipFull{font-family:var(--mono);font-size:12px;line-height:1.5;color:var(--dim); overflow-wrap:anywhere;margin:6px 0 0} .recipFull b{color:var(--fg);font-weight:400} .input{ width:100%;background:transparent;color:var(--fg);border:1px solid var(--bd); padding:11px;font-family:var(--mono);font-size:14px; } .input:focus{outline:none;border-color:var(--fg)} .input[aria-invalid=true]{border-color:var(--err);border-width:2px} .btn{ background:var(--fg);color:var(--bg);border:1px solid var(--fg); height:44px;font-size:12px;font-weight:700;letter-spacing:.14em;text-transform:uppercase; cursor:pointer;width:100%;transition:opacity var(--t); } .cs{text-transform:none} .alts{display:grid;grid-template-columns:1fr 1fr;gap:6px} .alts .btn{font-size:11px;padding:0 10px} @media (hover:hover){.btn:hover:not(:disabled){opacity:.82}} .btn:disabled{background:var(--bg2);color:var(--dim);border-color:var(--bd);cursor:not-allowed} .btn.alt{background:transparent;color:var(--fg);border-color:var(--bd)} @media (hover:hover){.btn.alt:hover{border-color:var(--fg);opacity:1}} .btn.danger{background:var(--warn);border-color:var(--warn);color:#000} .note{font-size:12px;line-height:1.5;color:var(--dim);min-height:16px} .note.ok{color:var(--fg)} .note.err{color:var(--err)} .tgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:6px} .tgrid div{display:flex;flex-direction:column;gap:5px} .tgrid input{width:100%;background:transparent;color:var(--fg);border:1px solid var(--bd);padding:10px 2px;text-align:center;font-family:var(--mono);font-size:14px} .tgrid label{font-size:9.5px;letter-spacing:.12em;text-transform:uppercase;color:var(--dim);text-align:center} .check{display:flex;align-items:center;gap:9px;font-size:12.5px;cursor:pointer;user-select:none;color:var(--dim)} .check input{width:15px;height:15px;accent-color:var(--fg);cursor:pointer} .rows{display:flex;flex-direction:column;gap:0;border:1px solid var(--bd)} .kv{display:flex;justify-content:space-between;gap:14px;font-size:12.5px;padding:9px 12px} .kv+.kv{border-top:1px solid var(--bd)} .kv dt{color:var(--dim);white-space:nowrap} .kv dd{font-family:var(--mono);font-variant-numeric:tabular-nums;text-align:right;overflow-wrap:anywhere} #helpModal .kv{flex-direction:column;gap:4px;align-items:start} #helpModal .kv dd{font-family:var(--sans);text-align:left;font-variant-numeric:normal} #helpModal .kv dt{font-size:10.5px;font-weight:700;letter-spacing:.16em;text-transform:uppercase} .picks{display:flex;flex-direction:column;gap:6px} .pick{ display:grid;grid-template-columns:24px 1fr auto;align-items:center;gap:13px; padding:12px 13px;min-height:52px;width:100%; background:transparent;border:1px solid var(--bd);cursor:pointer;text-align:left; font-size:13.5px;color:var(--fg); transition:border-color var(--t),background var(--t); } @media (hover:hover){.pick:hover{border-color:var(--fg);background:var(--bg2)}} .pick[aria-current=true]{border-color:var(--fg);background:var(--bg2)} .pick .ico{ grid-column:1;width:24px;height:24px;display:flex;align-items:center;justify-content:center; overflow:hidden;font-size:15px;line-height:1; } .pick .ico > *{max-width:24px;max-height:24px;display:block} .pick .ico img{width:24px;height:24px;object-fit:contain} .pick .ico svg{width:24px;height:24px} .pick .lbl2{grid-column:2;min-width:0} .pick .lbl2 b{display:block;font-weight:400;overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .pick small{ display:block;color:var(--dim);font-size:10px;letter-spacing:.06em; margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap; } .pick .tick{grid-column:3;color:var(--fg);font-size:13px} @media (max-width:520px){ .modal.on{align-items:flex-end} .modal{padding:0} .card{width:100%;margin:0;border-width:2px 0 0;padding-bottom:max(22px,env(safe-area-inset-bottom))} } @media (min-width:900px){ body.v-send .panel{max-width:880px} body.v-send .seg{width:422px;align-self:start} body.v-send .sendPane{ display:grid;grid-template-columns:1fr 1fr;column-gap:36px;row-gap:0; align-items:start; } .col{display:flex;flex-direction:column;gap:20px;min-width:0} #colA{grid-column:1;grid-row:1} #colB{grid-column:2;grid-row:1} .sendPane > .field + .field{padding-top:0;border-top:0} .col > .field + .field{padding-top:20px;border-top:1px solid var(--rule)} body.v-send .sendFoot{width:422px;align-self:start} } .spinner{width:30px;height:30px;border:2px solid var(--bd);border-top-color:var(--fg);animation:sp .8s linear infinite} .loading-wrap{display:flex;align-items:center;justify-content:center} .loader{width:96px;height:96px;display:block} .loader-sm .loader{width:40px;height:40px} @keyframes sp{to{transform:rotate(360deg)}} .toast{ position:fixed;left:50%;transform:translateX(-50%);bottom:max(18px,env(safe-area-inset-bottom)); background:var(--fg);color:var(--bg);padding:11px 16px;font-size:12.5px;z-index:70; opacity:0;transition:opacity .2s;max-width:min(92vw,440px);text-align:center;pointer-events:none; } .toast.on{opacity:1} .nftbox{width:100%;max-width:264px;aspect-ratio:1/1;transition:opacity var(--t)} .nftbox.waiting{opacity:.45} .nftbox svg{width:100%;height:100%;display:block} #tipLayer{position:fixed;inset:0;pointer-events:none;z-index:30} @media (pointer:coarse){ .input,.tgrid input{font-size:16px} .tgrid input{padding:12px 2px} } @media (hover:none){ .box:active{background:var(--fg);color:var(--bg)} #sendBox:active{background:var(--bg);color:var(--fg)} .btn:active:not(:disabled){opacity:.75} .chip:active,.opt:active:not(:disabled),.pick:active,.tab:active, .seg button:active,.tx-open:active,.ward-h:active,.link:active{background:var(--bg2)} .tx-act:active{background:var(--fg);color:var(--bg)} } @media (pointer:coarse){ button,a,input,label,.tx-open,.ward-h{-webkit-tap-highlight-color:transparent} button,a,label,summary{touch-action:manipulation} .chip{height:44px} #themeBtn,#helpBtn{width:44px} .tab,.seg button,.link{height:44px} .opt{height:46px} .wtx button{padding:12px 14px;min-height:44px} .txbar button{padding:0 8px;font-size:20px;min-height:44px} .ward-h{min-height:44px} .wordmark{min-height:44px} .check input{width:20px;height:20px} .btn{height:48px} } @media (max-width:520px){ .bar{gap:6px;padding-left:max(12px,env(safe-area-inset-left));padding-right:max(12px,env(safe-area-inset-right))} .bar-r{gap:6px} .chip{padding:0 10px} .chip .lbl{max-width:26vw} main{padding-left:max(12px,env(safe-area-inset-left));padding-right:max(12px,env(safe-area-inset-right))} .card{max-height:88dvh;overflow-y:auto;overscroll-behavior:contain} .kv{flex-direction:column;gap:3px} .kv dd{text-align:left} } @media (max-width:380px){ #chainName{display:none} .chip .lbl{max-width:34vw} .wordmark{letter-spacing:.18em;font-size:14px;gap:8px} .wordmark .mk{width:22px;height:22px} } body.txlive .toast{bottom:calc(max(18px,env(safe-area-inset-bottom)) + 62px)} body{overscroll-behavior:none} .list,.sendPane,.guardPane,.modal{overscroll-behavior:contain} </style> </head> <body> <div class="bar"> <button class="wordmark" id="homeBtn" aria-label="Home"> <svg class="mk" viewBox="0 0 64 64" aria-hidden="true"><path d="M0 5.06C0 3.32 0 2.46 .33 1.79C.64 1.15 1.15 .64 1.79 .33C2.46 0 3.32 0 5.06 0H58.94C6.68 0 61.54 0 62.21 .33C62.85 .64 63.36 1.15 63.67 1.79C64 2.46 64 3.32 64 5.06V58.94C64 6.68 64 61.54 63.67 62.21C63.36 62.85 62.85 63.36 62.21 63.67C61.54 64 6.68 64 58.94 64H5.06C3.32 64 2.46 64 1.79 63.67C1.15 63.36 .64 62.85 .33 62.21C0 61.54 0 6.68 0 58.94V5.06Z" fill="#0A0A0A" stroke="#FFFFFF" stroke-width="2"/><path d="M48,16 H16 V35 H42 V42 H16 V48 H48 V29 H22 V22 H48 Z" fill="#FFFFFF"/></svg><span>SLOW</span> </button> <div class="bar-r"> <button class="chip" id="helpBtn" aria-label="How SLOW works">?</button> <button class="chip" id="themeBtn" aria-label="Appearance"></button> <button class="chip" id="chainBtn" aria-label="Network"> <span id="chainIcon" aria-hidden="true"></span><span class="lbl" id="chainName">Ethereum</span><span id="netDot" hidden></span> </button> <button class="chip" id="walletBtn"><span class="lbl">Connect</span></button> </div> </div> <p class="warn" id="chainWarn" role="status" hidden></p> <main> <div class="home"> <div class="frame"> <button class="box" id="sendBox">SEND</button> <div class="divider"></div> <button class="box" id="takeBox">TAKE</button> </div> <p class="tag">Wrap once. Send with a timelock you can reverse.</p> </div> <div class="panel"> <div class="seg" role="tablist" aria-label="Mode"> <button id="segSend" role="tab" aria-selected="true">Send</button> <button id="segTake" role="tab" aria-selected="false">Take</button> <button id="segGuard" role="tab" aria-selected="false">Guard</button> </div> <div class="sendPane"> <div class="col" id="colA"> <div class="field"> <span class="flabel">To</span> <input type="text" class="input" id="recipientInput" placeholder="Address, .eth, .wei, or .gwei" autocomplete="off" spellchec
0x68575b07…67c7·#25,913,389·0xd66eb6a8…172bc9
nce();const upd=update(); loadChart(); if(t==="book")loadBook(); if(listReady){ const miss=["token","out"].map(k=>(q.get(k)||"").trim()) .filter(v=>v&&!/^0x[0-9a-f]{40}$/i.test(v)&&!symAddr(v.toLowerCase())).map(v=>v.slice(0,24)); if(miss.length){try{await upd}catch{} if(my===lSeq)stat.textContent=`Couldn\u2019t find ${miss.join(" or ")} \u2014 link it by address instead.`;} } } addEventListener("hashchange",applyLink); const tname=t=>t.cust||t.launched||/\s/.test(t.sym)||!symAddr(t.sym.toLowerCase())?t.addr:t.sym; lk.onclick=async()=>{ const f=TOKENS[fromSel.value],t=TOKENS[toSel.value],p=new URLSearchParams(); p.set("token",tname(f)); if(CHAIN_ID!==1)p.set("chain",String(CHAIN_ID)); const rcv=rc.value.trim();if(rcv)p.set("to",rcv); if(tab==="send"){ p.set("tab","send"); if(amt.value.trim())p.set("amount",amt.value.trim()); if(+dly.value)p.set("lock",dly.value); }else{ p.set("out",tname(t)); if(lqMode)p.set("lq","1"); if(tab==="book"){p.set("tab","book");if(+dly.value)p.set("lock",dly.value)} if(mode==="out"&&outAmt.value.trim()){p.set("amount",outAmt.value.trim());p.set("exactOut","1")} else if(amt.value.trim())p.set("amount",amt.value.trim()); } const u=(location.origin&&location.origin!=="null"?location.origin+location.pathname:location.href.split("#")[0])+"#"+p; try{await navigator.clipboard.writeText(u);stat.textContent="Link copied"} catch{stat.textContent=u} }; tabSwap.onclick=()=>{sBlip();setTab("swap")}; tabSend.onclick=()=>{sBlip();setTab("send")}; {const TABS=[tabSwap,tabSend,tabBook],NAMES=["swap","send","book"]; for(const el of TABS)el.addEventListener("keydown",e=>{ const i=TABS.indexOf(e.target); const j=e.key==="ArrowRight"?(i+1)%TABS.length:e.key==="ArrowLeft"?(i+TABS.length-1)%TABS.length :e.key==="Home"?0:e.key==="End"?TABS.length-1:-1; if(j<0)return; e.preventDefault();TABS[j].focus();sBlip();setTab(NAMES[j]); }); syncStops=()=>TABS.forEach(el=>el.tabIndex=el.classList.contains("on")?0:-1); syncStops();for(const el of TABS)el.addEventListener("click",syncStops); for(const el of TABS)el.addEventListener("keyup",syncStops);} applyLink(); loadTokenList().then(()=>{ listReady=true; adoptList(pairAddrs()); return applyLink(); }).catch(()=>{ listReady=true; applyLink().catch(()=>{}); }); async function doSend(){ if(!sendReady)return; swap.disabled=true;++busy; try{ const{to,amount,token}=sendReady; const fresh=await rcvOf(rc.value.trim(),true); if(fresh.toLowerCase()!==to.toLowerCase())throw Error("recipient changed \u2014 check and retry"); if(amount>fromBalance)throw Error("insufficient balance"); await checkWallet(); const dsec=chainCfg().slow?BigInt(dly.value||0):0n; let txReq,batch=null; if(dsec>0n){ const isE=token.addr===ZERO; if(tipCk.checked){await syncTip();swap.disabled=true} const tip=tipCk.checked?sendTipWei:0n; const val=isE?amount+tip:tip; if(isE&&val>fromBalance)throw Error("insufficient ETH for amount + keeper tip"); const dep=tip>0n ?"0x"+SEL_DEPOSITTIP+encAddr(isE?ZERO:token.addr)+encAddr(to) +encUint(amount)+encUint(dsec)+encUint(tip)+encUint(192n)+encUint(0n) :"0x"+SEL_DEPOSITTO+encAddr(isE?ZERO:token.addr)+encAddr(to) +encUint(isE?0n:amount)+encUint(dsec)+encUint(160n)+encUint(0n); txReq={from:account,to:SLOW,data:dep,value:toHex(val)}; if(!isE){ const aHex=await rpc(C,[{to:token.addr,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(SLOW)},L]); const cur=BigInt(aHex); if(cur<amount){ const pre=cur>0n?[{to:token.addr,data:"0x"+SEL_APPROVE+encAddr(SLOW)+encUint(0),value:"0x0"}]:[]; const ap={to:token.addr,data:"0x"+SEL_APPROVE+encAddr(SLOW)+encUint(amount),value:"0x0"}; if(await canBatch(account))batch=[...pre,ap,{to:SLOW,data:dep,value:toHex(val)}]; else{stat.textContent="Approve "+token.sym+"..."; for(const c of[...pre,ap]){const at=await sendTx([{from:account,...c}]);await waitTx(at)}} }} }else{ txReq=token.addr===ZERO ?{from:account,to,value:toHex(amount)} :{from:account,to:token.addr,data:"0x"+SEL_TRANSFER+encAddr(to)+encUint(amount),value:"0x0"}; } let tx; if(batch){await checkWallet();tx=await sendBatch(account,batch)} else{const ret=await rpc(C,[txReq,L]);if(dsec===0n&&token.addr!==ZERO)okRet(ret);tx=await sendTx([txReq])} await settle(tx); amt.value="";fitFont(amt);sendReady=null; refreshBalance();render();loadPos(); }catch(e){err(e);render()} finally{--busy} } async function sendRouterFunded(callData,fundedCallData,token,amount,msgValue,verb){ await checkWallet(); let data=callData,batch=null; if(token.addr!==ZERO){ const allowData="0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(ZROUTER); const allowance=BigInt(await rpc(C,[{to:token.addr,data:allowData},L])); if(allowance<amount){ let ready=false; const pi=await permitInfo(token.addr,account); if(pi)try{ stat.textContent="Sign approval..."; const pd=BigInt(Math.floor(Date.now()/1e3)+1800); data=encCalls([await signPermit(token.addr,pi,account,amount,pd),callData]); ready=true; }catch(e){if(isRejection(e))throw e} const selected=TOKENS[fromSel.value]; const walletBal=selected&&selected.addr.toLowerCase()===token.addr.toLowerCase() ?fromBalance:BigInt(await rpc(C,[{to:token.addr,data:"0x"+SEL_BALANCEOF+encAddr(account)},L])); if(!ready&&walletBal>=amount&&await p2Ready(token.addr,account)>=amount)try{ stat.textContent="Sign approval..."; const pd=BigInt(Math.floor(Date.now()/1e3)+1800); data=encCalls([await signPermit2(token.addr,account,amount,pd),fundedCallData||callData]); ready=true; }catch(e){if(isRejection(e))throw e} if(!ready&&await canBatch(account)){ const pre=allowance>0n ?[{to:token.addr,data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(0n),value:"0x0"}]:[]; batch=[...pre, {to:token.addr,data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(amount),value:"0x0"}, {to:ZROUTER,data:callData,value:toHex(msgValue)}]; ready=true; } if(!ready){ const approvals=allowance>0n?[0n,amount]:[amount]; for(let i=0;i<approvals.length;i++){ stat.textContent=`Approve ${token.sym}${approvals.length>1?` (${i+1}/${approvals.length})`:""}...`; const h=await sendTx([{from:account,to:token.addr, data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(approvals[i]),value:"0x0"}]); await waitTx(h); } const fresh=BigInt(await rpc(C,[{to:token.addr,data:allowData},L])); if(fresh<amount)throw Error("approval failed"); } } } stat.textContent=verb; let tx; if(batch){await checkWallet();tx=await sendBatch(account,batch)} else{ const req={from:account,to:ZROUTER,data,value:toHex(msgValue)}; await rpc(C,[req,L]);await checkWallet();tx=await sendTx([req]); } await settle(tx); } const PUSH_MAGIC="381bcb9ccaaab09ca206deb73834ed7b483c58bb3face9b43e8c7b50193253e2"; const SEL_SAFE_TRANSFER_DATA="b88d4fde",SEL_OWNEROF="6352211e"; const isWns=a=>(a||"").toLowerCase()===WNS.toLowerCase(); const weiName=v=>{const n=(v||"").trim().toLowerCase();return n.endsWith(".wei")?n:n+".wei"}; const nftIdIn=(addr,v)=>{v=(v||"").trim(); if(!v||/^\d+$/.test(v)||!isWns(addr))return v; const n=weiName(v); return /^[a-z0-9-]+(\.[a-z0-9-]+)*\.wei$/.test(n)?BigInt(namehash(n)).toString():v}; const nftLabel=(sym,addr,v)=>isWns(addr)&&!/^\d+$/.test((v||"").trim()) ?weiName(v):`${sym} #${(v||"").trim()}`; async function pushNftOrder(f,t){ const id=nftIdIn(f.addr,nftId.value); if(!/^\d+$/.test(id))throw Error(isWns(f.addr)?"name the .wei name, or the token id, you are selling":"name the token id you are selling"); const tokenId=BigInt(id); if(kind.value!=="fixed")throw Error("an NFT is indivisible, so it sells at a fixed price"); const want=parseUnits(outAmt.value.trim()||"0",t.dec); if(!want)throw Error("name a price"); const quote=t.addr===ZERO?WETH:t.addr; if(quote.toLowerCase()===f.addr.toLowerCase())throw Error("pick different tokens"); if((await rpc("eth_getCode",[SB2,L])).length<5)throw Error("fixed orderbook not deployed yet"); let cp=ZERO; const v=rc.value.trim(); if(v){cp=await rcvOf(v,true);if(!cp)throw Error("Private recipient not resolved")} let owner=ZERO; try{owner="0x"+strip0x(await rpc(C,[{to:f.addr,data:"0x"+SEL_OWNEROF+encUint(tokenId)},L])).slice(-40)} catch{throw Error(`${f.sym} #${id} does not exist`)} if(owner.toLowerCase()!==account.toLowerCase())throw Error(`you do not own ${f.sym} #${id}`); const exp=+dly.value?BigInt(Math.floor(Date.now()/1e3)+ +dly.value):0n; const terms=PUSH_MAGIC+encAddr(quote)+encUint(want)+encUint(exp)+encBool(false)+encAddr(cp); const data="0x"+SEL_SAFE_TRANSFER_DATA+encAddr(account)+encAddr(SB2)+encUint(tokenId) +encUint(128n)+encUint(192n)+terms; const req={from:account,to:f.addr,data,value:"0x0"}; await rpc(C,[req,L]); await checkWallet(); await settle(await sendTx([req])); } const SEL_CREATE_ETH="0e8e31d3",SEL_CREATE="8e14f9f8",SEL_FLOOR_BID="3d8d260b"; async function bidCollection(f,t,climbing){ if(nftId.value.trim())throw Error("clear the token id to bid on the whole collection"); const ceiling=parseUnits(amt.value.trim()||"0",f.dec); if(!ceiling)throw Error("name the most you will pay in total"); const open=climbing?parseUnits(floorAmt.value.trim()||"0",f.dec):ceiling; if(climbing&&!open)throw Error("set an opening bid"); if(open>ceiling)throw Error("the opening bid exceeds the maximum"); if(ceiling>(1n<<96n)-1n)throw Error("bid amount too large"); const wantRaw=outAmt.value.trim(); if(wantRaw&&!/^\d+$/.test(wantRaw))throw Error("how many tokens? a whole number"); const want=wantRaw?BigInt(wantRaw):1n; if(!want)throw Error("bid for at least one"); if(want>(1n<<128n)-1n)throw Error("too many to bid for at once"); const duration=BigInt(dly.value||0); if(!duration)throw Error("a bid needs a window — pick an expiry"); if((await rpc("eth_getCode",[FLOOR,L])).length<5)throw Error("bid board not deployed yet"); const terms=encAddr(t.addr)+encAddr(f.addr===ZERO?ZERO:f.addr)+encUint(want) +encUint(open)+encUint(ceiling)+encUint(0n)+encUint(duration)+encBool(true) +encUint(288n)+encUint(0n); const data="0x"+SEL_FLOOR_BID+encUint(32n)+terms; if(f.addr===ZERO){ const req={from:account,to:FLOOR,data,value:toHex(ceiling)}; await rpc(C,[req,L]);await checkWallet();await settle(await sendTx([req])); return; } const tx={from:account,to:FLOOR,data,value:"0x0"}; const al=BigInt(await rpc(C,[{to:f.addr,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(FLOOR)},L])); const approvals=[]; if(al<ceiling){ if(al)approvals.push({to:f.addr,data:"0x"+SEL_APPROVE+encAddr(FLOOR)+encUint(0n),value:"0x0"}); approvals.push({to:f.addr,data:"0x"+SEL_APPROVE+encAddr(FLOOR)+encUint(ceiling),value:"0x0"}); } if(approvals.length&&await canBatch(account)){ await checkWallet(); await settle(await sendBatch(account,[...approvals,{to:FLOOR,data,value:"0x0"}])); return; } for(const a of approvals){await checkWallet();await settle(await sendTx([{from:account,...a}]))} await rpc(C,[tx,L]);await checkWallet();await settle(await sendTx([tx])); } async function buyNftOrder(f,t){ const id=nftIdIn(t.addr,nftId.value); if(!/^\d+$/.test(id))throw Error(isWns(t.addr)?"name the .wei name, or the token id, you want to buy":"name the token id you want to buy"); const tokenId=BigInt(id); if(kind.value!=="fixed")throw Error("a bid for one token is a fixed offer"); const price=parseUnits(amt.value.trim()||"0",f.dec); if(!price)throw Error("name what you will pay"); if((await rpc("eth_getCode",[SB2,L])).length<5)throw Error("fixed orderbook not deployed yet"); let cp=ZERO; const v=rc.value.trim(); if(v){cp=await rcvOf(v,true);if(!cp)throw Error("Private recipient not resolved")} try{await rpc(C,[{to:t.addr,data:"0x"+SEL_OWNEROF+encUint(tokenId)},L])} catch{throw Error(`${t.sym} #${id} does not exist`)} const exp=+dly.value?BigInt(Math.floor(Date.now()/1e3)+ +dly.value):0n; if(f.addr===ZERO){ const data="0x"+SEL_CREATE_ETH+encAddr(t.addr)+encUint(tokenId)+encBool(false) +encUint(exp)+encBool(true)+encAddr(cp); const req={from:account,to:SB2,data,value:toHex(price)}; await rpc(C,[req,L]);await checkWallet();await settle(await sendTx([req])); return; } const data="0x"+SEL_CREATE+encAddr(f.addr)+encUint(price)+encAddr(t.addr)+encUint(tokenId) +encBool(false)+encUint(exp)+encBool(false)+encBool(true)+encAddr(cp); const tx={from:account,to:SB2,data,value:"0x0"}; const al=BigInt(await rpc(C,[{to:f.addr,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(SB2)},L])); const approvals=[]; if(al<price){ if(al)approvals.push({to:f.addr,data:"0x"+SEL_APPROVE+encAddr(SB2)+encUint(0n),value:"0x0"}); approvals.push({to:f.addr,data:"0x"+SEL_APPROVE+encAddr(SB2)+encUint(price),value:"0x0"}); } if(approvals.length&&await canBatch(account)){ await checkWallet(); await settle(await sendBatch(account,[...approvals,{to:SB2,data,value:"0x0"}])); return; } for(const a of approvals){await checkWallet();await settle(await sendTx([{from:account,...a}]))} await rpc(C,[tx,L]);await checkWallet();await settle(await sendTx([tx])); } async function placeOrder(){ const f=TOKENS[fromSel.value],t=TOKENS[toSel.value]; swap.disabled=true;++busy; try{ if(f.std==="nft"||t.std==="nft"){ if(f.std==="nft")await pushNftOrder(f,t); else if(nftId.value.trim())await buyNftOrder(f,t); else await bidCollection(f,t,kind.value==="floor"); amt.value="";outAmt.value="";nftId.value="";floorAmt.value=""; floorTouched=false; fitFont(amt);fitFont(outAmt); stat.textContent=f.std==="nft"?"Listed.":"Bid placed."; loadBook();refreshBalance();render(); return; } const sell=parseUnits(amt.value.trim(),f.dec),want=parseUnits(outAmt.value.trim(),t.dec); const dutch=kind.value==="dutch",bid=kind.value==="floor"; const floor=(dutch||bid)&&floorAmt.value.trim()?parseUnits(floorAmt.value.trim(),bid?f.dec:t.dec):0n; if(dutch&&floor===0n){ try{ if(!confirm(`This lot decays to ZERO.\n\nOnce the window ends, anyone can take all ${trimAmt(sell,f.dec)} ${f.sym} for nothing — and it stays that way until you cancel.\n\nThat is how a "come and take it" launch works. If you meant to keep a reserve, set a Floor total instead.\n\nContinue with a zero floor?`))return; }catch{ stat.textContent="This lot would decay to zero and this browser blocked the confirmation. Set a Floor total, or open the page outside the wallet's in-app browser."; return; } } const board=bid?FLOOR:dutch?DUTCH:SB2; const quoteToken=!dutch&&!bid&&t.addr===ZERO?WETH:t.addr; const [bc,oc]=await Promise.all([rpc("eth_getCode",[board,L]),rpc("eth_getCode",[ORDERBOL,L])]); if(bc.length<5)throw Error(`${bid?"bid board":dutch?"Dutch":"fixed"} orderbook not deployed yet`); if(oc.length<5)throw Error("routed order adapter not deployed yet"); if(!bid&&f.addr===ZERO&&t.addr===WETH)throw Error("pick a non-WETH quote token"); if(!dutch&&!bid&&(f.addr===WETH||f.addr===ZERO)&&quoteToken===WETH)throw Error("pick different tokens"); if(dutch&&floor>want)throw Error("floor exceeds start price"); let cp=ZERO; const v=rc.value.trim(); if(!dutch&&!bid&&v){cp=await rcvOf(v,true);if(!cp)throw Error("Private recipient not resolved")} const pf=fill.value==="1"?1n:0n; const placementDeadline=BigInt(Math.floor(Date.now()/1e3)+1800); let orderData; if(bid){ const duration=BigInt(dly.value||0); if(!duration)throw Error("choose a bid window"); if(!floor)throw Error("set an opening bid"); if(floor>sell)throw Error("opening bid exceeds the maximum"); if(want>(1n<<128n)-1n||sell>(1n<<96n)-1n)throw Error("bid amount too large"); if(t.addr===ZERO)throw Error("a bid buys a token — pick one to bid for"); if((f.addr===ZERO?WETH:f.addr).toLowerCase()===t.addr.toLowerCase()) throw Error("pick different tokens"); orderData="0x"+SEL_ORDER_FLOOR+encAddr(FLOOR)+encAddr(account)+encAddr(account) +encAddr(t.addr)+encAddr(f.addr)+encUint(want)+encUint(floor)+encUint(sell) +encUint(0n)+encUint(duration)+encUint(placementDeadline); }else if(dutch){ const duration=BigInt(dly.value||0); if(!duration)throw Error("choose a decay duration"); if(sell>(1n<<128n)-1n||want>(1n<<96n)-1n||floor>(1n<<96n)-1n)throw Error("Dutch amount too large"); const dutchExpiry=dutchEnds(); orderData="0x"+SEL_ORDER_DUTCH+encAddr(DUTCH)+encAddr(account)+encAddr(account) +encAddr(f.addr)+encAddr(t.addr)+encUint(sell)+encUint(want)+encUint(floor) +encUint(0n)+encUint(duration)+encUint(dutchExpiry)+encUint(placementDeadline); }else{ const exp=+dly.value?BigInt(Math.floor(Date.now()/1e3)+ +dly.value):0n; orderData="0x"+SEL_ORDER_FIXED+encAddr(SB2)+encAddr(account)+encAddr(account) +encAddr(f.addr)+encUint(sell)+encAddr(quoteToken)+encUint(want)+encUint(pf) +encUint(exp)+encAddr(cp)+encUint(placementDeadline); } let direct,funded; if(f.addr===ZERO){ direct=funded=encSnwap(ZERO,0n,account,ZERO,0n,ORDERBOL,orderData); }else{ const checkpoint=encCheckpoint(ORDERBOL,f.addr,account); direct=encCalls([ checkpoint, encSnwap(f.addr,sell,account,ZERO,0n,ORDERBOL,orderData) ]); funded=encCalls([ checkpoint, encSweep(f.addr,sell,ORDERBOL), encSnwap(ZERO,0n,account,ZERO,0n,ORDERBOL,orderData) ]); } await sendRouterFunded(direct,funded,f,sell,f.addr===ZERO?sell:0n, bid?"Placing bid (escrowing the ceiling)...":dutch?"Placing Dutch order...":t.addr===ZERO?"Placing limit order (ETH quote settles as WETH)...":"Placing limit order..."); amt.value="";outAmt.value="";floorAmt.value=""; floorTouched=false; fitFont(amt);fitFont(outAmt); loadBook();refreshBalance();render(); }catch(e){err(e);render()}finally{--busy} } swap.onclick=async()=>{ if(tab==="book"){if(!account)return connect();return placeOrder()} if(tab==="send"){if(!account)return connect();return doSend()} if(!account) return connect(); if(!last)return; if(Date.now()>last.exp){stat.textContent="Quote expired — refreshing...";updateSoon();return} if(last.impact!==null&&last.impact!==undefined&&last.impact>=IMPACT_CONFIRM){ const pct=(Number(last.impact)/100).toFixed(2); const worse=`about ${trimAmt(last.lossTok,last.lossDec)} ${last.lossSym} ${last.isIn?"less than":"more than"} the market rate`; try{ if(last.impact>=IMPACT_TYPED){ const need=String(Math.floor(Number(last.impact)/100)); if(prompt(`STOP — this trade is priced ${pct}% away from the market.\n\nYou would ${last.isIn?"receive":"pay"} ${worse}.\n\nUsually this means the pool is illiquid or the amount is wrong.\n\nType ${need} to accept this loss:`)!==need)return; }else if(!confirm(`Price impact ${pct}%.\n\nYou will ${last.isIn?"receive":"pay"} ${worse}.\n\nContinue?`))return; }catch{ stat.textContent=`This trade is ${pct}% away from the market and this browser blocked the confirmation. Reduce the amount, or open the page outside the wallet's in-app browser.`; return; } } swap.disabled=true;++busy; try{ const q=last; const {callData,fundedCallData,msgValue,amountIn,from,to}=q; const wantsValue=q.wrap>0n?0n:(from.addr===ZERO?(q.isIn?amountIn:q.limit):0n); if(msgValue!==wantsValue)throw Error("bad value"); if((q.shownPay!==undefined&&q.shownPay!==amt.value)|| (q.shownGet!==undefined&&q.shownGet!==outAmt.value)) throw Error("the quote on screen changed \u2014 check the amounts and retry"); const rvNow=rc.value.trim(); if(((rvNow?await rcvOf(rvNow,true):"")||"").toLowerCase()!==(q.recipient||"").toLowerCase()) throw Error("recipient changed — check and retry"); await checkWallet(); try{await preflightFills(q.fills,q.tokenIn,q.tokenOut)} catch(e){stat.textContent=e.message;swap.disabled=false;last=null;updateSoon();return} let cd2=callData,batchCalls=null,prep=[]; const spender=q.spender!==undefined?q.spender:(to?null:ZROUTER); const target=to||ZROUTER; const fund=q.wrap>0n?{addr:WETH,sym:"WETH",dec:18}:from; if(q.source===SRC_PRECISION&&q.pools&&q.pools.length){ const ok=await mc3(q.pools.map(p=>({to:PFACTORY,data:"0x"+SEL_ISPOOL+encAddr(p)}))) .then(rs=>rs.length===q.pools.length&&rs.every(r=>!!r&&BigInt(r)===1n)).catch(()=>false); if(!ok)throw Error("that pool is not one the factory made — refusing to send"); if(q.wrap>0n){ const have=BigInt(await rpcRead("eth_getBalance",[account,L])); if(have<q.wrap)throw Error(`Not enough ETH: this needs ${trimAmt(q.wrap,18)} and you hold ${trimAmt(have,18)}.`); prep.push({to:WETH,data:"0xd0e30db0",value:toHex(q.wrap), note:`Wrapping ${trimAmt(q.wrap,18)} ETH — this pool holds WETH`}); } } const prepBatchable=prep.length?await canBatch(account):false; if(prep.length&&!prepBatchable){ for(const c of prep){ stat.textContent=c.note+"..."; const ctx={from:account,to:c.to,data:c.data,value:c.value}; await rpc(C,[ctx,L]);await checkWallet(); await settle(await sendTx([ctx]),1); } prep=[]; } if(fund.addr!==ZERO&&spender&&spender!==ZROUTER){ const allowData="0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(spender); const a=BigInt(await rpc(C,[{to:fund.addr,data:allowData},L])); if(a<amountIn){ if(await canBatch(account)){ const pre=a>0n?[{to:fund.addr,data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(0n),value:"0x0"}]:[]; batchCalls=[...pre,{to:fund.addr,data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(amountIn),value:"0x0"}, {to:target,data:callData,value:toHex(msgValue)}]; }else{ const n=a>0n?2:1; if(a>0n){ stat.textContent=`Approving (1/${n})...`; await waitTx(await sendTx([{from:account,to:fund.addr, data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(0)}])); } stat.textContent=n>1?"Approving (2/2)...":"Approving..."; await waitTx(await sendTx([{from:account,to:fund.addr, data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(amountIn)}])); if(BigInt(await rpcRead(C,[{to:fund.addr,data:allowData},L]))<amountIn)throw Error("approval failed"); } } }else if(fund.addr!==ZERO&&!to&&!msgValue){ const allowData="0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(ZROUTER); const aHex=await rpc(C,[{to:fund.addr,data:allowData},L]); const a=BigInt(aHex); if(a<amountIn){ let permitted=false; const sameAsQuote=fund===from; const pi=sameAsQuote?await permitInfo(fund.addr,account):null; if(pi){try{ stat.textContent="Sign approval..."; const pd=BigInt(Math.floor(Date.now()/1e3)+1800); cd2=encCalls([await signPermit(fund.addr,pi,account,amountIn,pd),callData]); permitted=true; }catch(e){if(isRejection(e)){stat.textContent="";swap.disabled=false;return}}} const p2Amount=amountIn; const p2Fundable=!!fundedCallData||q.source!==SRC_PRECISION; if(!permitted&&p2Fundable&&sameAsQuote&&fromBalance>=p2Amount&&await p2Ready(fund.addr,account)>=p2Amount){try{ stat.textContent="Sign approval..."; const pd=BigInt(Math.floor(Date.now()/1e3)+1800); const p2Call=await signPermit2(fund.addr,account,p2Amount,pd); cd2=fundedCallData?encCalls([p2Call,fundedCallData]) :encCalls(q.isIn?[p2Call,callData]:[p2Call,callData,encSweep(fund.addr,0n,account)]); permitted=true; }catch(e){if(isRejection(e)){stat.textContent="";swap.disabled=false;return}}} if(!permitted&&await canBatch(account)){ const pre=a>0n?[{to:fund.addr,data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(0n),value:"0x0"}]:[]; batchCalls=[...pre,{to:fund.addr,data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(amountIn),value:"0x0"}, {to:target,data:callData,value:toHex(msgValue)}]; permitted=true; } if(!permitted){ const n=a>0n?2:1; if(a>0n){ stat.textContent=`Approving (1/${n})...`; const zeroData="0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(0); const h1=await sendTx([{from:account,to:fund.addr,data:zeroData}]); await waitTx(h1); } stat.textContent=n>1?`Approving (2/2)...`:"Approving..."; const apprData="0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(amountIn); const h2=await sendTx([{from:account,to:fund.addr,data:apprData}]); await waitTx(h2); const nHex=await rpc(C,[{to:fund.addr,data:allowData},L]); if(BigInt(nHex)<amountIn)throw Error("approval failed"); } } } if(prep.length&&batchCalls){ batchCalls=[...prep.map(({to,data,value})=>({to,data,value})),...batchCalls]; prep=[]; } let tx; const mainTx={from:account,to:target,data:cd2,value:toHex(msgValue)}; if(prep.length&&!batchCalls&&await canBatch(account)){ stat.textContent="Swapping..."; await checkWallet(); tx=await sendBatch(account,[...prep.map(({to,data,value})=>({to,data,value})), {to:target,data:cd2,value:toHex(msgValue)}]); }else{ for(const c of prep){ stat.textContent=c.note+"..."; const ctx={from:account,to:c.to,data:c.data,value:c.value}; await rpc(C,[ctx,L]);await checkWallet(); await settle(await sendTx([ctx]),1); } stat.textContent="Swapping..."; if(batchCalls){ await checkWallet(); tx=await sendBatch(account,batchCalls); }else{ await rpc(C,[mainTx,L]); await checkWallet(); tx=await sendTx([mainTx]); } } await settle(tx); last=null; chCache={};loadChart(); refreshBalance(); render(); }catch(e){err(e);render()} finally{--busy} };</script>
0x68575b07…67c7·#25,913,388·0x7bd11465…e1aa01
er,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,data:"0x"+SEL_CREATOROF+encAddr(candidate)},L]); if(c&&"0x"+strip0x(c).slice(24)===owner.toLowerCase())token=candidate; }catch{} } if(token){ try{ const idx=await addCustomToken(token); TOKENS[idx].launched=1; rebuild(); fromSel.value=String(Math.max(0,TOKENS.findIndex(t=>t.addr===ZERO))); fromSel.dataset.prev=fromSel.value; toSel.value=String(idx); toSel.dataset.prev=toSel.value; pairChosen=true; syncDisabled();syncNftUI(); refreshBalance(); update(); }catch{} } stat.innerHTML=token?`${safeSym(sym)} is live at ` +`<a href="${chainCfg().explorer}/token/${token}" target="_blank" rel="noreferrer">${short(token)}</a>` +` \u00b7 0.4% of each trade goes to you \u2014 collect it from the coin panel` :`${safeSym(sym)} is live \u00b7 0.4% of each trade goes to you`; lnArtBytes=null;lnArtNote.textContent="none";lnArtNote.classList.remove("set"); lnPrev.classList.remove("set");lnArt.value=""; }catch(e){err(e)} finally{lnGo.disabled=false} }; const SEL_MARKETS="29c21083",SEL_TAPE="29a65241"; const SEL_BYCREATOR="7d78be2b",SEL_TOKEN1="d21220a7",SEL_BYCREATOR_N="aa5e6b5b",SEL_RESERVE0="443cb4bc",SEL_FEERECIP="46904840"; const LAUNCHED=[]; const LAUNCH_SHOW=24; const LAUNCH_SCAN=256; const FINE=300,COARSE=14400,TFS=[["5m",300],["1h",3600],["4h",14400],["1d",86400]]; const CH_POOLS=8; const srcFor=tf=>tf>=COARSE?COARSE:FINE; let chFloor=0,chSupply=0,chFloorKey="",chCoin1=0; let chKind=LS.ck==="line"?"line":"candle",chInv=false,chInvKey="",chPeriod=300,chBars=null,chSeq=0,chPools=null,chOpen=LS.ch==="1",chCache={}; let chDraw=null; const chKeep=(key,val)=>{ chCache[key]=val; for(const k in chCache)if(k!==key&&Date.now()-chCache[k].at>60000)delete chCache[k]; }; const unf=p=>(p&0xffffff)*2**(p>>>24); const decBar=w=>{if(!w)return null;const f=s=>Number((w>>BigInt(s))&0xffffffffn); return{b:Number(w&0xffffffffn),o:unf(f(32)),h:unf(f(64)),l:unf(f(96)),c:unf(f(128)),v:unf(f(160)),n:Number((w>>192n)&0xffffn)}}; const rollUp=(bars,src,dst)=>{if(dst<=src)return bars.filter(Boolean); const out=[];let g=null,k=null; for(let i=bars.length-1;i>=0;i--){const b=bars[i];if(!b)continue; const key=Math.floor(b.b*src/dst); if(key!==k){if(g)out.push(g);k=key;g={b:key,o:b.o,h:b.h,l:b.l,c:b.c,v:b.v,n:b.n}} else{g.h=Math.max(g.h,b.h);g.l=Math.min(g.l,b.l);g.c=b.c;g.v+=b.v;g.n+=b.n}} if(g)out.push(g);return out.reverse()}; const CH_WICK_SHARE=.01; const mergeTapes=tapes=>{const m=new Map(); for(const bars of tapes)for(const b of bars||[]){if(!b)continue; let e=m.get(b.b); if(!e)m.set(b.b,e={b:b.b,v:0,n:0,ov:0,cv:0,parts:[]}); e.v+=b.v;e.n+=b.n;e.ov+=b.o*b.v;e.cv+=b.c*b.v;e.parts.push(b)} const out=[...m.values()].sort((x,y)=>y.b-x.b); for(const e of out){ const big=e.v>0?e.parts.filter(x=>x.v>=e.v*CH_WICK_SHARE):[]; const src=big.length?big:e.parts; let h=-Infinity,l=Infinity; for(const x of src){if(x.h>h)h=x.h;if(x.l<l)l=x.l} e.h=h;e.l=l; if(e.v>0){e.o=e.ov/e.v;e.c=e.cv/e.v}else{e.o=src[0].o;e.c=src[0].c} delete e.ov;delete e.cv;delete e.parts} return out}; const chPair=()=>{const f=TOKENS[fromSel.value],t=TOKENS[toSel.value]; if(!f||!t||f.addr===t.addr)return null; return f.addr.toLowerCase()<t.addr.toLowerCase()?[f,t]:[t,f]}; let chLoading=""; async function loadChart(){ const p0=chPair(); const k0=chPairKey(p0); if(k0&&chLoading===k0)return; chBars=null;chPools=null; const p=p0; if(tab!=="swap"||!p){syncChartUI();return} const my=++chSeq,key=k0; const hit=chCache[key]; if(hit&&Date.now()-hit.at<30000){chBars=hit.bars;chPools=hit.pools;syncChartUI();return} chLoading=key; try{ if(PPLENS===ZERO||(await rpc("eth_getCode",[PPLENS,L])).length<5)return syncChartUI(); const marketsFor=async pair=>{ const [raw]=await mc3([{to:PPLENS,data:"0x"+SEL_MARKETS+encAddr(pair[0].addr)+encAddr(pair[1].addr) +encAddr(ZERO)+encUint(0)+encUint(LQ_SCAN)+encUint(0)}]); const found=[]; if(raw){const h=strip0x(raw),n=Number(BigInt("0x"+h.slice(64,128))),base=128; for(let i=0;i<n&&i<LQ_SCAN;i++){const at=k=>BigInt("0x"+h.slice(base+(i*PI_WORDS+k)*64,base+(i*PI_WORDS+k+1)*64)); if(at(PI_HOOK)!==0n||at(PI_LIQ)===0n)continue; found.push({a:"0x"+h.slice(base+(i*PI_WORDS+PI_POOL)*64+24,base+(i*PI_WORDS+PI_POOL+1)*64),liq:at(PI_LIQ)})}} found.sort((x,y)=>y.liq>x.liq?1:y.liq<x.liq?-1:0); return found.slice(0,CH_POOLS).map(x=>x.a); }; let eff=p,pools=await marketsFor(p); if(my!==chSeq)return; if(!pools.length){ const alt=chOtherForm(p); if(alt){ const altPools=await marketsFor(alt); if(my!==chSeq)return; if(altPools.length){eff=alt;pools=altPools} } } const [t0,t1]=eff; if(!pools.length){chKeep(key,{at:Date.now(),bars:null,pools:null,pair:null});return syncChartUI()} const reqs=[]; for(const a of pools)reqs.push({to:a,data:"0x"+SEL_TAPE+encUint(FINE)+encUint(256)}, {to:a,data:"0x"+SEL_TAPE+encUint(COARSE)+encUint(256)}); const tapes=await mc3Deep(reqs); if(my!==chSeq)return; const dec1Tape=hex=>{if(!hex)return null; const h=strip0x(hex),n=Number(BigInt("0x"+h.slice(64,128))),bars=[]; for(let j=0;j<n;j++)bars.push(decBar(BigInt("0x"+h.slice(128+j*64,128+(j+1)*64)))); return bars}; const decoded=tapes.map(dec1Tape); const fine=[],coarse=[]; for(let i=0;i<pools.length;i++){fine.push(decoded[i*2]);coarse.push(decoded[i*2+1])} const sc=10**(t0.dec-t1.dec)/1e18; const build=set=>{const m=mergeTapes(set); for(const b of m){b.o*=sc;b.h*=sc;b.l*=sc;b.c*=sc}return m.length?m:null}; const f=build(fine); let c=build(coarse); if(f&&c){const lead=rollUp(f,FINE,COARSE).filter(b=>b.b>c[0].b); if(lead.length)c=lead.concat(c)} chBars=f||c?{[FINE]:f,[COARSE]:c}:null; const live=set=>set.filter(d=>d&&d.some(Boolean)).length; chPools=chBars?{[FINE]:live(fine),[COARSE]:live(coarse)}:null; chKeep(key,{at:Date.now(),bars:chBars,pools:chPools,pair:eff===p?null:eff}); }catch{chBars=null;chPools=null} finally{if(chLoading===k0)chLoading=""} if(my===chSeq)syncChartUI(); } const chPoolsAt=step=>chPools&&chPools[step]||0; const chLabel=p=>chInv?`${p[0].sym} per ${p[1].sym}`:`${p[1].sym} per ${p[0].sym}`; async function chFloorFor(p){ const key=chPairKey(p); if(key===chFloorKey)return; chFloorKey=key;chFloor=0;chSupply=0;chCoin1=0; const tok=p&&p[1]&&p[1].launched?p[1].addr:p&&p[0]&&p[0].launched?p[0].addr:""; if(!tok)return; chCoin1=p[1]&&p[1].launched?1:0; try{ const [fp,sup]=await mc3Deep([{to:PLAUNCH,data:"0x"+SEL_FLOORPRICE+encAddr(tok)}, {to:tok,data:"0x"+SEL_TOTALSUPPLY}]); if(key!==chFloorKey)return; chFloor=fp?+formatUnits(BigInt(fp),18):0; chSupply=sup?+formatUnits(BigInt(sup),18):0; }catch{} } function syncChartUI(){ const on=tab==="swap"&&!!chBars; chTog.classList.toggle("hide",!on); chBox.classList.toggle("hide",!on||!chOpen); chTog.setAttribute("aria-expanded",on&&chOpen?"true":"false"); if(!on||!chOpen)return; const p=chEff(); chNote.textContent=p?chLabel(p):""; drawChart();syncInv(); } const NUMERAIRES=new Set([ "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "0xdac17f958d2ee523a2206206994597c13d831ec7", "0x6b175474e89094c44da98b954eedeac495271d0f", ]); const moneyRank=a=>{a=(a||"").toLowerCase(); return NUMERAIRES.has(a)?2:(a===ZERO||a===WETH.toLowerCase())?1:0}; const chPairKey=p=>p?p[0].addr.toLowerCase()+":"+p[1].addr.toLowerCase():""; const chNaturalInv=p=>!!p&&moneyRank(p[0].addr)>moneyRank(p[1].addr); const chOtherForm=p=>{ const eth=p.filter(x=>isEthLike(x.addr)); if(eth.length!==1)return null; const cur=eth[0],rest=p[0]===cur?p[1]:p[0]; const sub=TOKENS.find(x=>x.addr.toLowerCase()===(cur.addr===ZERO?WETH:ZERO).toLowerCase()); if(!sub)return null; return sub.addr.toLowerCase()<rest.addr.toLowerCase()?[sub,rest]:[rest,sub]; }; const chEff=()=>{const p=chPair();if(!p)return p; const hit=chCache[chPairKey(p)]; return (hit&&hit.pair)||p;}; const CH_SLOTS=Math.floor((340-2-42)/3),CH_FILL=.34; const chFinest=()=>chBars&&chBars[FINE]?FINE:COARSE; const chSeries=secs=>{ if(!chBars)return null; const src=srcFor(secs),step=chBars[src]?src:chFinest(),base=chBars[step]; if(!base||!base.length)return null; const per=Math.max(secs,step); let pts=rollUp(base,step,per).slice().reverse(); pts=pts.map(b=>{const{o,c}=b; if(!(o>0)||!(c>0)||!isFinite(o)||!isFinite(c))return null; const h=b.h>0&&isFinite(b.h)?b.h:0,l=b.l>0&&isFinite(b.l)?b.l:Infinity; return{...b,h:Math.max(h,o,c),l:Math.min(l,o,c)}}).filter(Boolean); return pts.length?{pts,per,step}:null; }; const chAutoPeriod=()=>{ if(!chBars)return null; const fit=secs=>{const s=chSeries(secs); if(!s||s.per!==secs)return null; const pts=s.pts,newest=pts[pts.length-1].b; const shown=pts.filter(b=>newest-b.b<CH_SLOTS); if(!shown.length)return null; const fill=shown.length/(newest-shown[0].b+1); const whole=newest-pts[0].b+1<=CH_SLOTS; return{score:shown.length*fill,ok:shown.length>=6&&fill>=CH_FILL&&whole}}; let best=null,bestS=-1; for(const[,secs] of TFS){const f=fit(secs); if(!f)continue; if(f.ok)return secs; if(f.score>bestS){best=secs;bestS=f.score}} return best==null?chFinest():best; }; let chTfKey=""; const chSyncPeriod=()=>{ const k=chPairKey(chEff()); if(k===chTfKey)return; const auto=chAutoPeriod(); if(auto==null)return; chTfKey=k;chPeriod=auto; }; const chSyncTfs=()=>{ const finest=chFinest(); if(chPeriod<finest)chPeriod=finest; for(const c of chTf.children){if(!c.dataset.secs)continue; const secs=Number(c.dataset.secs); c.disabled=!!chBars&&secs<finest; c.title=c.disabled?"This pair has no five-minute tape to draw":""; c.classList.toggle("on",secs===chPeriod)} }; const chSyncOrientation=()=>{ const p=chEff(),k=chPairKey(p); if(k===chInvKey)return; chInvKey=k;chInv=chNaturalInv(p); }; const hudFor=i=>{ if(!chDraw)return; const{pts,num,UP,DN,ch,per,hd}=chDraw; if(!hd)return; if(i<0){const b=pts[pts.length-1]; let note="",flip=""; if(chInv&&isFinite(ch)&&ch>-100){ const other=(1/(1+ch/100)-1)*100; const pr=chEff&&chEff(); const base=pr&&pr[0]?pr[0].sym:"the token"; note=` title="${safeSym(base)} is ${other>=0?"+":""}${other.toFixed(2)}% over this window. ` +`This chart prices the pair the other way up, so its line moves the opposite way."`; flip=` <span class="chflip">${safeSym(base)} ${other>=0?"+":""}${other.toFixed(2)}%</span>`; } hd.innerHTML=`<b>${num(b.c)}</b> <span style="color:${ch>=0?UP:DN}"${note}>${ch>=0?"+":""}${ch.toFixed(2)}%</span>${flip}`; return} const b=pts[i],up=b.c>=b.o,d=new Date(b.b*per*1e3); const when=per>=86400 ?d.toLocaleDateString("en-US",{month:"short",day:"numeric"}) :d.toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",hour12:false}); hd.innerHTML=`<b style="color:${up?UP:DN}">${num(b.c)}</b> <span>${when}</span>` +`<span class="ohlc">H ${num(b.h)} L ${num(b.l)} · ${b.n} trade${b.n===1?"":"s"}</span>`; }; const chPoint=e=>{ if(!chDraw)return; const r=chArt.getBoundingClientRect(); if(!r.width)return; const{pts,W,PL,st,b0}=chDraw; const x=(e.clientX-r.left)/r.width*W; const at=b0+Math.round((x-PL)/st-.5); let i=0,near=Infinity; for(let j=0;j<pts.length;j++){const d=Math.abs(pts[j].b-at); if(d<near){near=d;i=j}} hudFor(i); if(!chDraw.svg)return; let el=chDraw.cross; if(!el){ el=document.createElementNS("http://www.w3.org/2000/svg","line"); el.setAttribute("class","cross"); el.setAttribute("y1","0");el.setAttribute("y2","150"); el.setAttribute("stroke","currentColor");el.setAttribute("stroke-opacity",".35"); el.setAttribute("stroke-width",".8"); chDraw.svg.appendChild(el);chDraw.cross=el; } const cx=chDraw.X(i).toFixed(1); el.setAttribute("x1",cx);el.setAttribute("x2",cx); }; chArt.addEventListener("pointermove",chPoint); chArt.addEventListener("pointerdown",chPoint); chArt.addEventListener("pointerleave",()=>{ if(chDraw&&chDraw.cross){chDraw.cross.remove();chDraw.cross=null} hudFor(-1)}); function drawChart(){ if(!chBars)return; const W=340,H=150,PL=2,PR=42,PT=10,PB=14,VH=22; const pw=W-PL-PR,ph=H-PT-PB-VH; chSyncPeriod();chSyncOrientation();chSyncTfs(); const ser=chSeries(chPeriod); if(!ser){chArt.innerHTML='<div class="msg">No trades in this window.</div>';chDraw=null;return} const per=ser.per; let pts=ser.pts; const newest=pts[pts.length-1].b,cap=Math.floor(pw/3); const kept=pts.filter(b=>newest-b.b<cap); const clipped=kept.length<pts.length; pts=kept; if(chInv)pts=pts.map(b=>({...b,o:1/b.o,h:1/b.l,l:1/b.h,c:1/b.c})) .filter(b=>isFinite(b.o)&&isFinite(b.h)&&isFinite(b.l)&&isFinite(b.c)); if(!pts.length){chArt.innerHTML='<div class="msg">No trades in this window.</div>';chDraw=null;return} let hi=-Infinity,lo=Infinity,vm=0; for(const b of pts){if(b.v>vm)vm=b.v} if(chKind==="line"){ for(const b of pts){if(b.c>hi)hi=b.c;if(b.c<lo)lo=b.c} }else{ let bh=-Infinity,bl=Infinity; for(const b of pts){const t=Math.max(b.o,b.c),u=Math.min(b.o,b.c); if(t>bh)bh=t;if(u<bl)bl=u} const span=bh>bl?bh-bl:bh*.1; const cutHi=Math.min(bh+span*CH_WICK_CAP,bh*2), cutLo=Math.max(bl-span*CH_WICK_CAP,bl/2); for(const b of pts){ const h=Math.min(b.h,cutHi),l=Math.max(b.l,cutLo); if(h>hi)hi=h;if(l<lo)lo=l} } if(hi===lo){hi*=1.001;lo*=0.999} const rawLo=lo; let sp=hi-lo;hi+=sp*.09; lo-=Math.min(sp*.09,rawLo>0?rawLo*.1:0); if(lo<=0)lo=rawLo>0?rawLo*.9:0; sp=hi-lo; const n=pts.length,b0=pts[0].b,slots=pts[n-1].b-b0+1; const st=pw/slots,bw=Math.max(1,Math.min(9,st*.68)); const X=i=>PL+st*(pts[i].b-b0+.5),Y=p=>PT+ph*(1-(p-lo)/sp),vt=PT+ph+6,VY=v=>vt+(VH-6)*(1-(vm?v/vm:0)); const num=v=>v>=1000?v.toLocaleString("en-US",{maximumFractionDigits:2}):v>=1?v.toFixed(3):fmtPx(v); const UP="#16a34a",DN="#c33"; const p0=chEff(),lbl=p0?chLabel(p0):"Price"; let s=`<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" role="img" aria-label="${lbl}, ${n} bars, ${num(pts[n-1].c)} now, range ${num(lo)} to ${num(hi)}">`; for(let g=0;g<=3;g++){const p=lo+sp*g/3,y=Y(p).toFixed(1); s+=`<line x1="${PL}" y1="${y}" x2="${W-PR}" y2="${y}" stroke="currentColor" stroke-opacity=".1"/>` +`<text x="${W-PR+4}" y="${(Y(p)+2.5).toFixed(1)}" font-size="7" fill="currentColor" fill-opacity=".5">${p>0?num(p):"0"}</text>`} for(let i=0;i<n;i++){const b=pts[i],y=VY(b.v); s+=`<rect x="${(X(i)-bw/2).toFixed(1)}" y="${y.toFixed(1)}" width="${bw.toFixed(1)}" height="${Math.max(.4,vt+VH-y).toFixed(1)}" fill="currentColor" fill-opacity=".18"/>`} const chNum=p0&&(chCoin1?p0[0]:p0[1]); const ethNum=!!chNum&&(chNum.addr===ZERO||chNum.addr===WETH); const fl=chFloor>0&&ethNum?(chCoin1?(chInv?chFloor:1/chFloor):(chInv?1/chFloor:chFloor)):0; if(fl>0&&fl>=lo&&fl<=hi){ const fy=Y(fl).toFixed(1); s+=`<line x1="${PL}" y1="${fy}" x2="${W-PR}" y2="${fy}" stroke="currentColor" stroke-opacity=".45" stroke-width=".7" stroke-dasharray="3 2"/>` +`<text x="${PL+3}" y="${(Y(fl)-2.5).toFixed(1)}" font-size="6.5" fill="currentColor" fill-opacity=".6">floor</text>`; } const rise=pts[n-1].c>=pts[0].o; if(chKind==="line"){ const d=pts.map((b,i)=>`${i?"L":"M"}${X(i).toFixed(1)} ${Y(b.c).toFixed(1)}`).join(" "); const col=rise?UP:DN; s+=`<path d="${d} L${X(n-1).toFixed(1)} ${(PT+ph).toFixed(1)} L${X(0).toFixed(1)} ${(PT+ph).toFixed(1)} Z" fill="${col}" fill-opacity=".1"/>` +`<path d="${d}" fill="none" stroke="${col}" stroke-width="1.2" stroke-linejoin="round"/>` +`<circle cx="${X(n-1).toFixed(1)}" cy="${Y(pts[n-1].c).toFixed(1)}" r="1.8" fill="${col}"/>`; }else{ for(let i=0;i<n;i++){const b=pts[i],col=b.c>=b.o?UP:DN,x=X(i); const t=Y(Math.max(b.o,b.c)),bo=Y(Math.min(b.o,b.c)); s+=`<line x1="${x.toFixed(1)}" y1="${Y(b.h).toFixed(1)}" x2="${x.toFixed(1)}" y2="${Y(b.l).toFixed(1)}" stroke="${col}" stroke-width=".8"/>` +`<rect x="${(x-bw/2).toFixed(1)}" y="${t.toFixed(1)}" width="${bw.toFixed(1)}" height="${Math.max(.8,bo-t).toFixed(1)}" fill="${col}"/>`} } const spanS=(pts[n-1].b-pts[0].b+1)*per; const tAt=b=>{const d=new Date(b*per*1000); return spanS>129600?d.toLocaleDateString("en-US",{month:"short",day:"numeric"}) :d.toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",hour12:false})}; const ticks=n>=3?[[0,"start"],[n>>1,"middle"],[n-1,"end"]]:[[0,"start"],[n-1,"end"]]; const tLbl=ticks.map(([i])=>i>=0&&i<n?tAt(pts[i].b):""); ticks.forEach(([i,anc],k)=>{if(i<0||i>=n)return; if(anc==="middle"&&(tLbl[k]===tLbl[0]||tLbl[k]===tLbl[tLbl.length-1]))return; s+=`<text x="${X(i).toFixed(1)}" y="${H-3.5}" font-size="6.5" text-anchor="${anc}" ` +`fill="currentColor" fill-opacity=".45">${tLbl[k]}</text>`}); s+="</svg>"; const last=pts[n-1],first=pts[0],ch=((last.c-first.o)/first.o)*100; chArt.innerHTML=s+'<div class="hd"></div>'; chDraw={pts,X,W,PL,st,b0,num,UP,DN,ch,per, hd:chArt.querySelector(".hd"),svg:chArt.querySelector("svg"),cross:null}; hudFor(-1); const took=rel(Math.max(per,(slots-1)*per)); const np=chPoolsAt(ser.step); const fine=(chBars&&chBars[chFinest()])||null; const newestBar=fine?fine.find(b=>b&&b.c>0):null; const latest=newestBar?newestBar.c:pts[n-1].c; const canon=latest>0?1/latest:0; const px=chCoin1?canon:latest; const mcap=chSupply>0&&px>0?chSupply*px:0; chNote.textContent=p0?[chLabel(p0), mcap>0?`${mcap>=1000?Math.round(mcap).toLocaleString("en-US"):mcap.toFixed(mcap>=1?1:3)} ${ethNum?"ETH":chCoin1?p0[0].sym:p0[1].sym} mcap${ ethNum&&ethUsd>0?` (${fmtUsd(mcap*ethUsd)})`:""}`:"", `${np} pool${np===1?"":"s"}`,`${clipped?"last ":""}${took}`].filter(Boolean).join(" · "):""; if(p0){const was=chFloor+":"+chSupply+":"+chCoin1; chFloorFor(p0).then(()=>{if(chFloor+":"+chSupply+":"+chCoin1!==was)drawChart()});} } for(const[label,secs]of TFS){const b=document.createElement("button"); b.textContent=label;b.dataset.secs=String(secs);if(secs===chPeriod)b.className="on"; b.onclick=()=>{chPeriod=secs;chTfKey=chPairKey(chEff());drawChart()}; chTf.appendChild(b)} const kindBtn=document.createElement("button"); kindBtn.className="chk"; const syncKind=()=>{kindBtn.textContent=chKind==="candle"?"Line":"Candle"; kindBtn.title=`Switch to ${kindBtn.textContent.toLowerCase()} chart`}; syncKind(); kindBtn.onclick=()=>{chKind=chKind==="candle"?"line":"candle";LS.ck=chKind;syncKind();drawChart()}; chTf.appendChild(kindBtn); const invBtn=document.createElement("button"); invBtn.className="chk"; invBtn.textContent="⇅"; const syncInv=()=>{const p=chEff(); invBtn.title=p?`Price ${chInv?p[1].sym:p[0].sym} instead — currently ${chInv?p[0].sym:p[1].sym} per ${chInv?p[1].sym:p[0].sym}`:"Invert the price"; invBtn.setAttribute("aria-pressed",chInv?"true":"false")}; syncInv(); invBtn.onclick=()=>{chInv=!chInv;syncInv();drawChart()}; chTf.appendChild(invBtn); const syncChart=()=>{chSyncOrientation();syncInv();loadChart()}; chTog.onclick=()=>{chOpen=!chOpen;LS.ch=chOpen?"1":"0";syncChartUI()}; const lsecs=v=>{const m=/^(\d+)([mhdw]?)$/.exec((v||"").trim()); return m?+m[1]*(m[2]=="d"?86400:m[2]=="h"?3600:m[2]=="w"?604800:m[2]=="m"?60:1):NaN}; const symAddr=x=>{const m=TOKENS.filter(t=>t.sym.toLowerCase()===x); return m.length===1?m[0].addr.toLowerCase():""}; const ARCADE=".arcade.wei",SC_OWNER="6352211e",SC_TEXT="308e3386"; const scEl=document.getElementById("scEl"); let scSeq=0; const scShow=async name=>{ const my=++scSeq; const n=String(name||"").trim().toLowerCase(); if(!/^[a-z0-9-]{1,63}\.arcade\.wei$/.test(n)){scEl.classList.add("hide");return} const lbl=n.slice(0,-ARCADE.length); scEl.classList.remove("hide"); scEl.textContent="Reading "+n+"\u2026"; let owner="",rec=""; try{ const id=namehash(n); const[o,t]=await cfgBatch([{to:WNS,data:"0x"+SC_OWNER+pad32(id)}, {to:WNS,data:"0x"+SC_TEXT+pad32(id)+encUint(64)+strTail("score")}]); if(my!==scSeq)return; const a="0x"+strip0x(o||"").slice(24); if(!/^0x[0-9a-f]{40}$/i.test(a)||/^0x0{40}$/i.test(a))throw Error("gone"); owner=a; try{rec=decStr(t)}catch{} }catch{if(my===scSeq)scEl.textContent="No such name \u2014 "+n+" has never been minted.";return} const m=/^(\d+)(?:-w(\d+))?-[0-9a-z]+$/.exec(lbl); scEl.textContent=""; const b=document.createElement("b");b.textContent=n;scEl.appendChild(b); const line=t=>{const d=document.createElement("div");d.textContent=t;scEl.appendChild(d);return d}; if(m)line(`Minted score ${(+m[1]).toLocaleString("en-US")}`+(m[2]?` \u00b7 reached wave ${m[2]}`:"")); else line("Not a score name \u2014 the label carries no run."); line("Held by "+short(owner)); if(m)line("Anyone can mint a name with any number \u2014 this says it was claimed, not earned.").className="scw"; if(m&&rec&&rec!==m[1])line(`The score record now reads ${rec}, not ${m[1]} \u2014 the holder changed it.`).className="scw"; const go=document.createElement("button");go.type="button";go.textContent="Play invaders"; go.onclick=()=>{sBlip();invPlay()}; scEl.appendChild(go); }; let lSeq=0,listReady=false; const lkWrote=Object.create(null); const lkKeep=(el,v)=>{const w=lkWrote[el.id]; if(el.value!==""&&el.value!==w&&(w!==undefined||el.type!=="select-one"))return false; el.value=v;lkWrote[el.id]=v;return true; }; async function applyLink(e){ const q=new URLSearchParams(location.hash.slice(1)); const my=++lSeq; if(![...q.keys()].length){ if(e){ if(typeof amt!=="undefined"&&amt){amt.value="";fitFont(amt)} if(typeof outAmt!=="undefined"&&outAmt){outAmt.value="";fitFont(outAmt)} if(typeof rc!=="undefined"&&rc){rc.value="";rc.classList.remove("bad")} if(typeof rcvEl!=="undefined"&&rcvEl)rcvEl.textContent=""; if(typeof scEl!=="undefined"&&scEl)scEl.classList.add("hide"); setMode("in"); last=null; try{update()}catch{} } return;} const wantChain=+q.get("chain")||0; if(CHAINS[wantChain]&&wantChain!==CHAIN_ID){ if(prov()){if(wireWallet.probed)stat.textContent=`This link is for ${CHAINS[wantChain].name} \u2014 switch your wallet there to use it.`;return} setChain(wantChain);showNet();await loadTokenList();if(my!==lSeq)return; } const at=a=>a?TOKENS.findIndex(t=>t.addr.toLowerCase()===a):-1; const tok=async v=>{if(!v)return "";const x=v.trim().toLowerCase(); if(!/^0x[0-9a-f]{40}$/.test(x))return symAddr(x); if(at(x)<0)try{await addCustomToken(x,0)}catch{} return at(x)<0?"":x}; if(symAddr((q.get("token")||"").trim().toLowerCase())||symAddr((q.get("out")||"").trim().toLowerCase()))pairChosen=true; const both=async()=>[await tok(q.get("token")),await tok(q.get("out"))]; let[fa,ta]=await both(); if(my!==lSeq)return; if((fa&&at(fa)<0)||(ta&&at(ta)<0)){[fa,ta]=await both();if(my!==lSeq)return} if(!listReady){ const named=["token","out"].map(k=>(q.get(k)||"").trim()).filter(Boolean); if(named.some(v=>!/^0x[0-9a-f]{40}$/i.test(v)&&!symAddr(v.toLowerCase())))return; } const fi=at(fa),oi=at(ta); if(fi>=0||oi>=0)pairChosen=true; rebuild(); if(fi>=0){fromSel.value=fi;fromSel.dataset.prev=fromSel.value} if(oi>=0&&oi!==fi){toSel.value=oi;toSel.dataset.prev=toSel.value} if(q.get("score"))scShow(q.get("score"));else scEl.classList.add("hide"); const to=q.get("to"),lk=lsecs(q.get("lock")),want=q.get("tab"),n=q.get("amount"); const t=want==="swap"||want==="send"||want==="book"?want :q.get("out")?"swap":(to||!isNaN(lk))?"send":tab; if(fromSel.value===toSel.value){ const alt=String(TOKENS.findIndex((x,i)=>String(i)!==fromSel.value&&(t==="book"||x.std!=="nft"))); if(alt!=="-1"){if(oi>=0)fromSel.value=alt;else toSel.value=alt} } const rcMine=rc.value!==""&&rc.value!==lkWrote.rc; const wantLq=q.get("lq")==="1"; const linkWantsCard=fi>=0||oi>=0||to||n||!isNaN(lk)||q.get("token")||q.get("out"); if(!wantLq&&lqMode&&linkWantsCard)lqSet(false); if(linkWantsCard){if(lnMode)lnSet(false);if(wnMode)wnSet(false);if(pvMode)pvSet(false)} setTab(t); if(wantLq&&t==="swap"&&!lqMode)lqSet(true); if(to&&!rcMine)lkKeep(rc,to); if(!isNaN(lk)){const os=[...dly.options].map(x=>+x.value).sort((a,b)=>a-b); const v=String(os.find(o=>o>=lk)??os[os.length-1]); if(t==="book")bookD=v;else if(t==="send")sendD=v; if(t!=="swap")lkKeep(dly,v)} if(n&&/^\d*\.?\d+$/.test(n)){ if(q.get("exactOut")==="1"&&q.get("out")){if(lkKeep(outAmt,n)){setMode("out");fitFont(outAmt)}} else{if(lkKeep(amt,n)){setMode("in");fitFont(amt)}} } refreshBala
0x68575b07…67c7·#25,913,387·0x062925ec…5eab1f
ance(); stat.textContent="Paid. The relay is settling the recipient's note.";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]&&ch!==1)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=ch===1?ZERO: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 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}; let esc=to.toLowerCase(),warn=""; if(ch!==1){const code=await cfgRead("eth_getCode",[to,L]); 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; 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";x.rb=cpRelayBase();cpSaveNotes(); stat.textContent=(self?"Proving. The wallet will ask you to send it when the proof is ready.":ch===1?"The relay is settling the withdrawal.":"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=x.ch===1?{from:account,to:CP_POOL,value:"0x0",data:cpSettleData(x.pv,x.pr),gas:toHex(700000)} :{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=x.ch===1?"Withdrawn to "+x.to+".":"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=>!n.p&&BigInt(n.v)===v&&cpNoteOf(n).dep===id))continue; for(let i=0;i<64;i++){const k=cpNoteOf({i,v:String(v)}); if(k.dep===id){if(!cpNotes.some(n=>!n.p&&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="request">request</button><button type="button" class="fclink" data-a="pay">pay</button><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><button type="button" class="fclink" data-a="relay">relay</button>`; pvGo.textContent=!account?"Connect Wallet":!on?"Unlock key":"Deposit"; pvGo.disabled=!!cpBusy; if(!on){pvList.innerHTML="";return} const rows=cpNotes.map((n,j)=>{const{s,k}=cpStatus(n),x=n.ex,ch=x?CHAINS[x.ch].name:"",B=(a,l)=>`<button type="button" class="fclink" data-a="${a}" data-j="${j}">${l}</button>`; const paid=n.inv&&!!cpPool&&cpPool.wraps.some(w=>w[0]===k.dep); const act=s==="ready"?B("exit","exit") :s==="pending"?(n.job?(n.js==="failed"?"relay failed"+B("settle","retry"):"settling…"):n.inv&&!paid?"awaiting payment":B("settle","settle")) :s==="exiting"?(!x.job||x.js==="failed"?"relay failed"+B("exit","retry") :x.self?(x.ptx?"sent…":x.pv?B("send","send"):"proving…"):"relaying…") :s==="funded"?B("activate","activate") :s==="expired"?B("reclaim","reclaim") :s==="bridged"||s==="withdrawn"?`<a href="${CHAINS[x.ch].explorer}/address/${x.to}" target="_blank" rel="noreferrer">on ${ch}</a>` :s==="paying"?(n.job?"settling…":B("settle","settle")):s==="payfailed"?"relay failed"+B("settle","retry") :s==="paid"?`<a href="${CHAINS[1].explorer}/tx/${n.tx}" target="_blank" rel="noreferrer">paid</a>`:s; return`<div class="cpr"><span><b>${cpFmt(n.v)} ETH</b> ${n.p?"paid":n.inv?"request #"+n.i:"#"+n.i}${x&&s!=="bridged"&&s!=="withdrawn"?" → "+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(n.inv&&!n.job&&!n.p&&cpPool&&cpPool.wraps.some(w=>w[0]===cpNoteOf(n).dep))try{await cpSettleWrap(n)}catch{}} 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=b.dataset.j!==undefined?cpNotes[+b.dataset.j]:null; 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==="relay"){const u=(prompt("Relay to prove and settle through. Leave empty for the default ("+CP_RELAY+").",cpRelayBase())||"").trim(); try{if(!u||u===CP_RELAY)delete LS["zswap:cprelay"];else if(/^https:\/\/[\w.-]+(:\d+)?(\/[\w./-]*)?$/.test(u))LS["zswap:cprelay"]=u.replace(/\/$/,"");else return void(stat.textContent="A relay is an https URL.")}catch{} stat.textContent="Relay: "+cpRelayBase();return} if(a==="request")return pvRun(cpRequest,"Building the request…"); if(a==="pay")return pvRun(cpPay,"Checking the request…"); 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,own
0x68575b07…67c7·#25,913,386·0x4377e355…b82c45
— click to unmute"}; sd.onclick=()=>{snd=!snd;try{LS.snd=snd?"1":"0"}catch{}syncSnd();if(snd)sBlip()}; syncSnd(); wn.onclick=()=>{sBlip();wnSet(!wnMode)}; const cpHex=b=>{let s="";for(const x of b)s+=x.toString(16).padStart(2,"0");return s},cpU8=s=>new TextEncoder().encode(s); const CP_POOL="0x0000000000047DD77CeCEfE5Dc015EB7bFa9C677",CP_ROUTER="0x000000004c5BF191225F9049b385d6F3820E09BC", CP_ETH="0x3cba71e1114af183cdeacc6b8457a474d17529fd28704480ca799d0d03126f34",CP_SCALE=10000000000n,CP_BLOCK=25892003, CP_RELAY="https://api.tacit.finance",CP_LOGS=["https://mainnet.gateway.tenderly.co","https://eth.drpc.org"], CP_NI="0x00000000000000000000000000000000000000C8",CP_KEY="zswap:cpk:",CP_NOTES="zswap:cpn:",CP_CACHE="zswap:cppool"; const CP_L2={8453:{bridge:"0x3154Cf16ccdb4C6d922629664174b904d80F2C35",peer:"0x4200000000000000000000000000000000000010",gas:800000n}, 4663:{inbox:"0x1A07cc4BD17E0118BdB54D70990D2158AbAD7a2D",peer:"0xDf8755334ce7A73cCF6b581C02eA649AE3E864b3",gas:1000000n}}; const SEL_CPESCROW="2bf0cda2",SEL_CPACT="1699fd5b",SEL_CPRECL="02edf635",SEL_CPEXIT="acad0634",SEL_CPWRAP="859a9cee", SEL_CPDEP="7da9874f",SEL_CPDEPTO="9a2ac6d5",SEL_CPRETRY="679b6ded",SEL_CPSUBFEE="a66b327d",SEL_CPESTRT="c3dc5879", SEL_CPIMPL="93228617",SEL_CPSETTLE="717fd7f2",SEL_CPNEXT="0be4f422",SEL_CPASSET="9fda5b66",SEL_CPOTHER="7f46ddb2",SEL_CPBRIDGE="e78cea92"; const CP_T_LEAVES="0x7783fb256f5b4e1d4d8b79583488756286326ae15d9997d4098ce5432ed2708b", CP_T_SPENT="0x576d91547505afce99e7ebe2baf1a0948b5915598105a55f40ba72fea86e875e", CP_T_WRAP="0xf5d1711d21af6f42622ab6237626933cefc42cc9f0663d81b7c4c7bc5ce99e44"; const CP_MSG="zSwap private bridge\n\nSign to derive the key that owns your shielded deposits. This costs nothing and moves nothing.\n\nAccount: "; const S2K=[0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2]; const sha2=m=>{const l=m.length,n=((l+9+63)>>6)<<6,b=new Uint8Array(n);b.set(m);b[l]=128; const dv=new DataView(b.buffer);dv.setUint32(n-4,(l*8)>>>0);dv.setUint32(n-8,Math.floor(l/536870912)); const h=[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],w=new Uint32Array(64),rr=(x,k)=>(x>>>k)|(x<<(32-k)); for(let o=0;o<n;o+=64){for(let i=0;i<16;i++)w[i]=dv.getUint32(o+i*4); for(let i=16;i<64;i++){const p=w[i-15],q=w[i-2];w[i]=w[i-16]+(rr(p,7)^rr(p,18)^(p>>>3))+w[i-7]+(rr(q,17)^rr(q,19)^(q>>>10))} let[a0,a1,a2,a3,a4,a5,a6,a7]=h; for(let i=0;i<64;i++){const t1=(a7+(rr(a4,6)^rr(a4,11)^rr(a4,25))+((a4&a5)^(~a4&a6))+S2K[i]+w[i])>>>0,t2=((rr(a0,2)^rr(a0,13)^rr(a0,22))+((a0&a1)^(a0&a2)^(a1&a2)))>>>0; a7=a6;a6=a5;a5=a4;a4=(a3+t1)>>>0;a3=a2;a2=a1;a1=a0;a0=(t1+t2)>>>0} h[0]=(h[0]+a0)>>>0;h[1]=(h[1]+a1)>>>0;h[2]=(h[2]+a2)>>>0;h[3]=(h[3]+a3)>>>0;h[4]=(h[4]+a4)>>>0;h[5]=(h[5]+a5)>>>0;h[6]=(h[6]+a6)>>>0;h[7]=(h[7]+a7)>>>0} const o=new Uint8Array(32);for(let i=0;i<8;i++){o[i*4]=h[i]>>>24;o[i*4+1]=(h[i]>>>16)&255;o[i*4+2]=(h[i]>>>8)&255;o[i*4+3]=h[i]&255}return o}; const FP=0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn,FN=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n; const GP=[0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n,0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n,1n],PZ=[0n,1n,0n]; const fmod=(a,m)=>{a%=m;return a<0n?a+m:a}; const fpow=(b,e,m)=>{let r=1n;b=fmod(b,m);for(;e>0n;e>>=1n){if(e&1n)r=r*b%m;b=b*b%m}return r}; const pdbl=([x,y,z])=>{if(!z||!y)return PZ;const ys=y*y%FP,s=4n*x*ys%FP,m=3n*x*x%FP,x3=fmod(m*m-2n*s,FP); return[x3,fmod(m*(s-x3)-8n*ys*ys,FP),2n*y*z%FP]}; const padd=(P,Q)=>{if(!P[2])return Q;if(!Q[2])return P; const[x1,y1,z1]=P,[x2,y2,z2]=Q,z1s=z1*z1%FP,z2s=z2*z2%FP,u1=x1*z2s%FP,u2=x2*z1s%FP,s1=y1*z2s%FP*z2%FP,s2=y2*z1s%FP*z1%FP; if(u1===u2)return s1===s2?pdbl(P):PZ; const h=fmod(u2-u1,FP),r=fmod(s2-s1,FP),h2=h*h%FP,h3=h2*h%FP,v=u1*h2%FP,x3=fmod(r*r-h3-2n*v,FP); return[x3,fmod(r*(v-x3)-s1*h3,FP),h*z1%FP*z2%FP]}; const pmul=(P,k)=>{let R=PZ,Q=P;for(k=fmod(k,FN);k>0n;k>>=1n){if(k&1n)R=padd(R,Q);Q=pdbl(Q)}return R}; const paff=([x,y,z])=>{if(!z)return[0n,0n];const zi=fpow(z,FP-2n,FP),z2=zi*zi%FP;return[x*z2%FP,y*z2%FP*zi%FP]}; const pcomp=P=>{const[x,y]=paff(P);return(y&1n?"03":"02")+x.toString(16).padStart(64,"0")}; const pfromx=(x,odd)=>{if(x>=FP)return null;const y2=(x*x%FP*x+7n)%FP,y=fpow(y2,(FP+1n)/4n,FP); return y*y%FP===y2?[x,Number(y&1n)!==(odd?1:0)?FP-y:y,1n]:null}; const cpCat=(...a)=>{const o=new Uint8Array(a.reduce((n,x)=>n+x.length,0));let p=0;for(const x of a){o.set(x,p);p+=x.length}return o}; const b32=v=>hexToBytes(typeof v==="bigint"?v.toString(16).padStart(64,"0"):strip0x(String(v)).padStart(64,"0")); const beN=(n,len)=>{const o=new Uint8Array(len);let v=BigInt(n);for(let i=len-1;i>=0;i--){o[i]=Number(v&255n);v>>=8n}return o}; const big=b=>BigInt("0x"+cpHex(b)); const cpH=(()=>{const s=sha2(cpU8("tacit-generator-H-v1"));for(let c=0;c<256;c++){const P=pfromx(big(sha2(cpCat(s,[c]))),0);if(P)return P}})(); const cpCommit=(v,r)=>padd(pmul(cpH,BigInt(v)),pmul(GP,BigInt(r))); const cpXY=P=>{const[x,y]=paff(P);return{cx:"0x"+x.toString(16).padStart(64,"0"),cy:"0x"+y.toString(16).padStart(64,"0")}}; const cpDerive=(seed,i)=>{const base=cpCat(b32(seed),cpU8("tacit-evm-cnote-v1"),b32(CP_ETH),beN(i,8)); return{secret:"0x"+cpHex(sha2(cpCat(base,[0]))),blinding:fmod(big(sha2(cpCat(base,[1]))),FN)||1n}}; const cpOwner=nk=>keccak(cpCat(b32(nk),cpU8("tacit-native-owner-v1"))); const cpLeaf=(cx,cy,o)=>keccak(cpCat(b32(CP_ETH),b32(cx),b32(cy),b32(o))); const cpDepCommit=(cx,cy,o)=>keccak(cpCat(b32(cx),b32(cy),b32(o))); const cpDepId=(v,c)=>keccak(cpCat(b32(CP_ETH),beN(v,32),b32(c))); const cpNu=(nk,leaf)=>keccak(cpCat(b32(nk),b32(leaf),cpU8("tacit-native-nullifier-v1"))); const cpBinding=()=>keccak(hexToBytes(pad32("1")+strip0x(CP_POOL).toLowerCase())); const cpCtx=(tag,cb,a,b,notes,amts)=>keccak(cpCat(cpU8(tag),b32(cb),b32(a),b32(b),...notes.flatMap(n=>n.map(b32)),...amts.map(x=>beN(x,8)))); const cpNonce=(r,ctx,role)=>fmod(big(hexToBytes(keccak(cpCat(cpU8("tacit-open-nonce-v1"),b32(fmod(BigInt(r),FN)),b32(ctx),cpU8(role))))),FN)||1n; const cpSigma=(v,r,ctx,k)=>{const rs=fmod(BigInt(r),FN),ks=fmod(BigInt(k),FN),Cc=cpCommit(v,rs),R=pmul(GP,ks); const e=fmod(big(hexToBytes(keccak(cpCat(cpU8("tacit-open-sigma-v1"),beN(v,8),b32(ctx),hexToBytes(pcomp(Cc)),hexToBytes(pcomp(R)))))),FN); return{R:"0x"+pcomp(R),z:"0x"+fmod(ks+e*rs,FN).toString(16).padStart(64,"0")}}; const cpSeal=(pub,n,e)=>{const P=pfromx(BigInt("0x"+pub.slice(2)),pub.slice(0,2)==="03"); const ss=sha2(hexToBytes(pcomp(pmul(P,e)))),plain=cpCat(beN(n.value,8),b32(n.blinding),b32(n.secret),b32(CP_ETH),b32(n.owner)),o=new Uint8Array(plain.length); for(let i=0;i<plain.length;i+=32){const k=sha2(cpCat(ss,[i/32]));for(let j=0;j<32&&i+j<plain.length;j++)o[i+j]=plain[i+j]^k[j]} return"0x"+pcomp(pmul(GP,e))+cpHex(o)}; const cpZeros=(()=>{const z=[new Uint8Array(32)];for(let i=1;i<32;i++)z.push(hexToBytes(keccak(cpCat(z[i-1],z[i-1]))));return z})(); const cpTree=(leaves,idx)=>{let lv=leaves.map(hexToBytes);const path=[]; for(let i=0;i<32;i++){const sib=(idx>>>i)^1;path.push("0x"+cpHex(sib<lv.length?lv[sib]:cpZeros[i]));const nx=[]; for(let k=0;k*2<lv.length;k++)nx.push(hexToBytes(keccak(cpCat(lv[2*k],2*k+1<lv.length?lv[2*k+1]:cpZeros[i])))); lv=nx.length?nx:[cpZeros[i+1]||cpZeros[0]]} return{root:"0x"+cpHex(lv[0]),path}}; const cpRandom=()=>{for(let t=0;t<32;t++){const b=new Uint8Array(32);crypto.getRandomValues(b);const s=big(b);if(s&&s<FN)return s}throw Error("no randomness")}; const cpEncCall=c=>encAddr(c.target)+encUint(c.value)+encAddr(c.token)+encUint(c.amount)+encBool(c.push)+pad32("c0")+encBytes(c.data||"0x"); const cpEncRecipe=r=>{const calls=r.calls.map(cpEncCall);let off=calls.length*32,head=encUint(calls.length); for(const c of calls){head+=encUint(off);off+=c.length/2} const ca=head+calls.join(""),sw=encUint(r.sweepTokens.length)+r.sweepTokens.map(encAddr).join(""),mo=encUint(r.minOuts.length)+r.minOuts.map(encUint).join(""); return pad32(r.exitedAsset)+encAddr(r.feeAsset)+encAddr(r.finalRecipient)+encUint(r.deadline)+encUint(r.nonce) +encUint(256)+encUint(256+ca.length/2)+encUint(256+(ca.length+sw.length)/2)+ca+sw+mo}; const cpEscrow=(impl,r)=>{const salt=keccak(hexToBytes(pad32("20")+cpEncRecipe(r))); const ih=keccak(hexToBytes("602d5f8160095f39f35f5f365f5f37365f73"+strip0x(impl).toLowerCase()+"5af43d5f5f3e6029573d5ffd5b3d5ff3")); return"0x"+strip0x(keccak(hexToBytes("ff"+strip0x(CP_ROUTER)+strip0x(salt)+strip0x(ih)))).slice(24)}; const cpActData=r=>"0x"+SEL_CPACT+pad32("20")+cpEncRecipe(r); const cpReclData=r=>{const b=cpEncRecipe(r);return"0x"+SEL_CPRECL+pad32("40")+encUint(64+b.length/2)+b+pad32("0")}; const cpExitData=(pv,pr,r)=>{const a=encBytes(pv),b=encBytes(pr),c=pad32("0"),d=cpEncRecipe(r); return"0x"+SEL_CPEXIT+pad32("80")+encUint(128+a.length/2)+encUint(128+(a.length+b.length)/2)+encUint(128+(a.length+b.length+c.length)/2)+a+b+c+d}; const cpRecipe=x=>{const l2=CP_L2[x.ch],r={exitedAsset:CP_ETH,feeAsset:ZERO,finalRecipient:x.fin,deadline:BigInt(x.dl),nonce:BigInt(x.nonce), sweepTokens:[ZERO],minOuts:[0n]}; r.calls=[x.ch===8453?{target:l2.bridge,value:BigInt(x.wei),token:ZERO,amount:0n,push:false, data:"0x"+SEL_CPDEPTO+encAddr(x.to)+encUint(200000)+pad32("60")+pad32("0")} :{target:l2.inbox,value:BigInt(x.wei),token:ZERO,amount:0n,push:false, data:"0x"+SEL_CPRETRY+encAddr(x.to)+encUint(x.l2v)+encUint(x.sc)+encAddr(x.to)+encAddr(x.to)+encUint(x.gl)+encUint(x.mf)+pad32("100")+pad32("0")}]; return r}; const cpRelayBase=()=>{try{const u=LS["zswap:cprelay"];if(/^https:\/\/[\w.-]+(:\d+)?(\/[\w./-]*)?$/.test(u||""))return u.replace(/\/$/,"")}catch{}return CP_RELAY}; const relayFetch=async(path,body,base)=>{const ac=new AbortController(),k=setTimeout(()=>ac.abort(),20000); try{const r=await fetch((base||cpRelayBase())+path,body?{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(body),signal:ac.signal}:{signal:ac.signal}); let j=null;try{j=await r.json()}catch{} if(r.status===404&&!body)return{status:"unknown"}; if(!r.ok)throw Error((j&&j.error)||"The relay answered "+r.status+"."); return j||{}} catch(e){if(e&&e.name==="AbortError")throw Error("The relay did not answer."); if(e instanceof TypeError)throw Error("The relay could not be reached from this page.");throw e} finally{clearTimeout(k)}}; let cpSeed="",cpFp="",cpPub="",cpNotes=[],cpPool=null,cpSyncAt=0,cpBusy=0,pvMode=false,pvTimer=0,pvTick=0; const cpNoteOf=n=>{const{secret,blinding}=cpDerive(cpSeed,n.i),owner=cpOwner(secret),{cx,cy}=cpXY(cpCommit(n.v,blinding)),leaf=cpLeaf(cx,cy,owner); return{secret,blinding,owner,cx,cy,leaf,nu:cpNu(secret,leaf),value:BigInt(n.v),dep:cpDepId(n.v,cpDepCommit(cx,cy,owner))}}; const pneg=([x,y,z])=>[x,fmod(-y,FP),z]; const cpOnCurve=(x,y)=>x<FP&&y<FP&&y*y%FP===(x*x%FP*x+7n)%FP; const cpVerifySigma=(cx,cy,v,Rh,zh,ctx)=>{const x=BigInt(cx),y=BigInt(cy);if(!cpOnCurve(x,y))return false; const R=pfromx(BigInt("0x"+strip0x(Rh).slice(2)),strip0x(Rh).slice(0,2)==="03");if(!R||!/^0x0[23][0-9a-fA-F]{64}$/.test(Rh))return false; const Cc=[x,y,1n],e=fmod(big(hexToBytes(keccak(cpCat(cpU8("tacit-open-sigma-v1"),beN(v,8),b32(ctx),hexToBytes(pcomp(Cc)),hexToBytes(pcomp(R)))))),FN); const X=padd(Cc,pneg(pmul(cpH,BigInt(v))));return pcomp(pmul(GP,BigInt(zh)))===pcomp(padd(R,pmul(X,e)))}; const cpSettleData=(pv,pr)=>{const a=encBytes(pv),b=encBytes(pr);return"0x"+SEL_CPSETTLE+pad32("60")+encUint(96+a.length/2)+encUint(96+(a.length+b.length)/2)+a+b+pad32("0")}; const cpSaveNotes=()=>{try{LS[CP_NOTES+cpFp]=JSON.stringify(cpNotes)}catch{}}; const cpLoadNotes=()=>{try{const a=JSON.parse(LS[CP_NOTES+cpFp]||"[]");return Array.isArray(a)?a.filter(n=>Number.isInteger(n.i)&&/^\d+$/.test(n.v||"")):[]}catch{return[]}}; const cpKeyFor=a=>{try{return LS[CP_KEY+a.toLowerCase()]}catch{return""}}; const cpUse=s=>{cpSeed=s;cpFp=cpHex(sha2(cpU8("zswap-cp-v1:"+s))).slice(0,16);cpPub=pcomp(pmul(GP,BigInt(s)));cpNotes=cpLoadNotes()}; const cpUnlock=async()=>{let s=cpKeyFor(account); if(!/^0x[0-9a-f]{64}$/i.test(s||"")){ const sig=await rpc("personal_sign",["0x"+cpHex(cpU8(CP_MSG+account.toLowerCase()+"\nVersion: 1")),account]); if(!/^0x[0-9a-fA-F]{130}$/.test(sig||""))throw Error("The wallet returned an unusable signature."); s="0x"+(fmod(BigInt(keccak(hexToBytes(sig))),FN)||1n).toString(16).padStart(64,"0"); try{LS[CP_KEY+account.toLowerCase()]=s}catch{}} cpUse(s)}; const cpRescue=i=>{const k=fmod(big(sha2(cpCat(cpU8("zswap-exit-rescue-v1"),b32(cpSeed),beN(i,8)))),FN)||1n,[x,y]=paff(pmul(GP,k)); return{key:"0x"+k.toString(16).padStart(64,"0"),addr:"0x"+strip0x(keccak(cpCat(beN(x,32),beN(y,32)))).slice(24)}}; const cpLogsOk=r=>Array.isArray(r)&&r.every(l=>l&&Array.isArray(l.topics)&&l.topics.every(t=>/^0x[0-9a-f]{64}$/i.test(t)) &&/^0x([0-9a-f]{2})*$/i.test(l.data||"0x")&&/^0x[0-9a-f]+$/i.test(l.blockNumber||"")&&/^0x[0-9a-f]+$/i.test(l.logIndex||"")); const cpGetLogs=async(from,to,depth)=>{ const f={address:CP_POOL,fromBlock:toHex(from),toBlock:toHex(to),topics:[[CP_T_LEAVES,CP_T_SPENT,CP_T_WRAP]]}; try{const r=await cfgRead("eth_getLogs",[f]);if(cpLogsOk(r))return r}catch{} for(const u of CP_LOGS){try{const r=await httpRead(u,"eth_getLogs",[f]);if(cpLogsOk(r))return r}catch{}} if(to-from<2000||depth>12)throw Error("No node would serve the pool's history."); const m=from+((to-from)>>1);return(await cpGetLogs(from,m,depth+1)).concat(await cpGetLogs(m+1,to,depth+1))}; const cpWord=(d,i)=>d.slice(i*64,i*64+64); const cpArr=(d,off)=>{const n=Number(BigInt("0x"+cpWord(d,off)||"0x0")),o=[];for(let i=0;i<n;i++)o.push("0x"+cpWord(d,off+1+i));return o}; const cpSync=async(force)=>{ if(!force&&cpPool&&Date.now()-cpSyncAt<60000)return cpPool; let c=cpPool;if(!c)try{c=JSON.parse(LS[CP_CACHE]||"null")}catch{} if(!c||!Array.isArray(c.leaves)||!Array.isArray(c.spent)||!Array.isArray(c.wraps)||!(c.to>=CP_BLOCK))c={to:CP_BLOCK-1,leaves:[],spent:[],wraps:[]}; const cached=c.to>=CP_BLOCK; const head=parseInt(await cfgRead("eth_blockNumber",[]),16); if(head>c.to){const logs=await cpGetLogs(c.to+1,head,0); logs.sort((a,b)=>(parseInt(a.blockNumber,16)-parseInt(b.blockNumber,16))||(parseInt(a.logIndex,16)-parseInt(b.logIndex,16))); for(const l of logs){const t=l.topics[0].toLowerCase(),d=strip0x(l.data||""); if(t===CP_T_LEAVES&&l.topics[1]){const first=Number(BigInt(l.topics[1])),off=Number(BigInt("0x"+cpWord(d,0)))/32; cpArr(d,off).forEach((x,i)=>{c.leaves[first+i]=x})} else if(t===CP_T_SPENT)c.spent.push(...cpArr(d,Number(BigInt("0x"+cpWord(d,0)))/32)); else if(t===CP_T_WRAP&&l.topics[1])c.wraps.push([l.topics[1].toLowerCase(),"0x"+cpWord(d,0)])} c.to=head} const next=Number(BigInt(await cfgRead(C,[{to:CP_POOL,data:"0x"+SEL_CPNEXT},L])||"0x0")); if(c.leaves.length<next||c.leaves.slice(0,next).some(x=>!x)){ if(cached){cpPool=null;try{delete LS[CP_CACHE]}catch{}return cpSync(true)} throw Error("The pool's history came back incomplete.")} cpPool=c;cpSyncAt=Date.now();try{LS[CP_CACHE]=JSON.stringify(c)}catch{}return c}; const cpLadder=v=>{let x=BigInt(v);if(x<=0n)return 0n;const d=x.toString().length;if(d<=2)return x;const s=10n**BigInt(d-2);return(x+s-1n)/s*s}; const cpFee=async v=>{const gp=BigInt(await cfgRead("eth_gasPrice",[])||"0x0"); let fee=cpLadder((450000n*gp+40000000000000n)*135n/100n/CP_SCALE);if(fee>v)fee=v;return fee}; const cpPins=async ch=>{ const impl=retAddr(await cfgRead(C,[{to:CP_ROUTER,data:"0x"+SEL_CPIMPL},L])); if(!impl||impl===ZERO)throw Error("The router did not answer."); const a=strip0x(await cfgRead(C,[{to:CP_POOL,data:"0x"+SEL_CPASSET+pad32(CP_ETH)},L])||""); if(BigInt("0x"+(cpWord(a,0)||"0"))!==1n||BigInt("0x"+(cpWord(a,1)||"0"))!==0n)throw Error("Native ETH is not registered in the pool."); const l2=CP_L2[ch]; const peer=retAddr(await cfgRead(C,[{to:l2.bridge||l2.inbox,data:"0x"+(l2.bridge?SEL_CPOTHER:SEL_CPBRIDGE)},L])); if(peer!==l2.peer.toLowerCase())throw Error("The "+CHAINS[ch].name+" bridge did not answer as expected — refusing to continue."); if(ch===4663){const id=await httpRead(CHAINS[4663].rpcs[0],I,[]);if(parseInt(id,16)!==4663)throw Error("The Robinhood node answered for another chain.")} return impl}; const cpRhGas=async to=>{const rh=CHAINS[4663].rpcs[0],data="0x"+SEL_CPESTRT+encAddr(CP_ROUTER)+encUint(10n**18n+1n)+encAddr(to)+encUint(1)+encAddr(to)+encAddr(to)+pad32("e0")+pad32("0"); const est=BigInt(await httpRead(rh,"eth_estimateGas",[{to:CP_NI,data}])||"0x0"); const l1=BigInt(await cfgRead("eth_gasPrice",[])||"0x0"); const sc=BigInt(await cfgRead(C,[{to:CP_L2[4663].inbox,data:"0x"+SEL_CPSUBFEE+pad32("0")+encUint(l1*2n)},L])||"0x0"); const l2=BigInt(await httpRead(rh,"eth_gasPrice",[])||"0x0"); if(!est||!sc||!l2)throw Error("Robinhood gas could not be quoted."); return{gl:est*3n/2n,sc,mf:l2*2n}}; const cpDry=async(x,r,esc)=>{const data=cpActData(r),req={to:CP_ROUTER,data,gas:toHex(CP_L2[x.ch].gas)},ov={[esc]:{balance:toHex(BigInt(x.wei))}}; const tries=[()=>cfgRead(C,[req,L,ov]),...CP_LOGS.map(u=>()=>httpRead(u,C,[req,L,ov]))];let last=""; for(const t of tries){try{if(strip0x(await t()||"")==="")return"";last=""}catch(e){const m=String(e&&e.message||"");if(/revert|execution|out of gas|BadTarget|Expired/i.test(m))throw Error("The bridge call would fail: "+m.slice(0,80));last=m}} return"Could not dry-run the bridge call ("+(last||"no node")+")."}; const cpEnsureBalance=async(esc,need)=>BigInt(await cfgRead("eth_getBalance",[esc,L])||"0x0")>=need; const cpStatus=n=>{if(n.p)return{k:null,li:-1,s:n.js==="settled"?"paid":n.js==="failed"?"payfailed":"paying"}; const k=cpNoteOf(n),c=cpPool; const li=c?c.leaves.indexOf(k.leaf):-1,spent=!!c&&c.spent.includes(k.nu); const x=n.ex,dl=x?Number(x.dl):0,now=Math.floor(Date.now()/1000); if(x&&x.ch===1)return{k,li,s:spent?"withdrawn":"exiting"}; if(x&&(x.atx||x.ptx||x.rtx))return{k,li,s:x.rtx?"reclaimed":"bridged"}; if(x&&spent)return{k,li,s:now>dl?"expired":"funded"}; if(x)return{k,li,s:"exiting"}; if(spent)return{k,li,s:"spent"}; if(li>=0)return{k,li,s:"ready"}; return{k,li,s:"pending"}}; const cpRelayPoll=async n=>{const job=n.ex?n.ex.job:n.job;if(!job)return; const tgt=n.ex||n,st=await relayFetch("/confidential/status?id="+encodeURIComponent(job),null,tgt.rb);tgt.js=st.status||"unknown"; if(st.txHash)tgt.jtx=st.txHash;if(st.error)tgt.jerr=String(st.error).slice(0,120); const pr=st.proof||st.proofBytes;if(st.status==="proven"&&/^0x[0-9a-fA-F]+$/.test(st.publicValues||"")&&/^0x[0-9a-fA-F]+$/.test(pr||"")){tgt.pv=st.publicValues;tgt.pr=pr} cpSaveNotes()}; const cpSettleWrap=async n=>{const r=await relayFetch("/confidential/submit",{type:"wrap",op:n.op,memos:[n.memo],mode:"settle"}); if(!r.jobId)throw Error("The relay did not take the deposit.");n.job=r.jobId;n.js=r.status||"pending";n.rb=cpRelayBase();cpSaveNotes()}; const cpAmount=parts=>{const wei=parseUnits(pvAmt.value||"",18); if(wei<=0n)throw Error("How much ether?"); if(wei%(CP_SCALE*BigInt(parts)))throw Error(parts>1?"That amount does not split evenly into "+parts+" notes.":"The pool keeps eight decimals — round the amount."); const v=wei/CP_SCALE/BigInt(parts);if(v>=1n<<64n)throw Error("Too large for one note.");return{wei,v}}; const cpFresh=async v=>{let i=cpNotes.reduce((m,n)=>Number.isInteger(n.i)&&n.i>=0?Math.max(m,n.i+1):m,0),k; for(let t=0;;i++){k=cpNoteOf({i,v:String(v)}); const st=await cfgRead(C,[{to:CP_POOL,data:"0x"+SEL_CPDEP+pad32(k.dep)},L]); if(BigInt(st||"0x0")===0n)return{i,k};if(++t>20)throw Error("Could not find a fresh note slot.")}}; const cpNote=(i,k)=>{const v=k.value,cb=cpBinding(),ctx=cpCtx("tacit-wrap-intent-v1",cb,CP_ETH,k.dep,[[k.cx,k.cy,k.owner]],[v]),sig=cpSigma(v,k.blinding,ctx,cpNonce(k.blinding,ctx,"wrap")); return{i,v:String(v),at:Math.floor(Date.now()/1000),memo:cpSeal(cpPub,k,cpRandom()), op:{chainBinding:cb,asset:CP_ETH,value:String(v),cx:k.cx,cy:k.cy,owner:k.owner,sigR:sig.R,sigZ:sig.z}}}; const cpDepositOne=async(v,label)=>{const{i,k}=await cpFresh(v),n=cpNote(i,k); const req={from:account,to:CP_ROUTER,value:toHex(v*CP_SCALE),data:"0x"+SEL_CPWRAP+pad32(cpDepCommit(k.cx,k.cy,k.owner))}; await rpc(C,[req,L]); n.tx=await sendTx([req]);cpNotes.push(n);cpSaveNotes();cpPaint(); await settle(n.tx,true); stat.textContent=label+" Asking the relay to settle it…"; await cpSettleWrap(n);return n}; const cpDeposit=async()=>{ const parts=+pvSplit.value||1,{wei,v}=cpAmount(parts); if(wei>fromBalance)throw Error("Not enough ETH."); for(let p=1;p<=parts;p++)await cpDepositOne(v,parts>1?"Deposit "+p+" of "+parts+" sent.":"Deposited."); pvAmt.value="";refreshBalance(); stat.textContent=(parts>1?"Deposited as "+parts+" notes.":"Deposited.")+" The relay is settling them into the pool.";sGot()}; const cpRequest=async()=>{const{v}=cpAmount(1),{i,k}=await cpFresh(v),n=cpNote(i,k);n.inv=1; cpNotes.push(n);cpSaveNotes();cpPaint(); const inv={v:1,chainBinding:n.op.chainBinding,assetId:CP_ETH,underlying:ZERO,ticker:"cETH",amount:String(v*CP_SCALE),value:String(v), cx:k.cx,cy:k.cy,owner:k.owner,commit:cpDepCommit(k.cx,k.cy,k.owner),depositId:k.dep,leaf:k.leaf,memo:n.memo,witness:n.op}; pvAmt.value="";stat.textContent="Request built. Send it to whoever is paying; the note settles once they pay."; prompt("Your payment request. Anyone can pay it; only this key can spend the result.",JSON.stringify(inv))}; const cpPay=async()=>{let inv;try{inv=JSON.parse((prompt("Paste the payment request (JSON).")||"").trim())}catch{} const h32=x=>/^0x[0-9a-fA-F]{64}$/.test(x||""); if(!inv||inv.v!==1||!inv.witness)throw Error("That is not a payment request."); if(String(inv.assetId).toLowerCase()!==CP_ETH||String(inv.underlying||ZERO).toLowerCase()!==ZERO)throw Error("This request is not for ether."); if(![inv.cx,inv.cy,inv.owner,inv.commit,inv.depositId,inv.leaf,inv.chainBinding].every(h32)||!/^\d+$/.test(inv.value||"")||!/^\d+$/.test(inv.amount||""))throw Error("The request is malformed."); const v=BigInt(inv.value),wei=BigInt(inv.amount),w=inv.witness; if(wei!==v*CP_SCALE||v<=0n||v>=1n<<64n)throw Error("The request's amounts disagree."); if(String(inv.chainBinding).toLowerCase()!==cpBinding()||w.chainBinding!==inv.chainBinding||String(w.asset).toLowerCase()!==CP_ETH||w.value!==inv.value||w.cx!==inv.cx||w.cy!==inv.cy||w.owner!==inv.owner)throw Error("The request is for another pool."); const commit=cpDepCommit(inv.cx,inv.cy,inv.owner); if(commit!==String(inv.commit).toLowerCase()||cpLeaf(inv.cx,inv.cy,inv.owner)!==String(inv.leaf).toLowerCase()||cpDepId(v,commit)!==String(inv.depositId).toLowerCase())throw Error("The request does not add up — refusing to pay it."); const ctx=cpCtx("tacit-wrap-intent-v1",inv.chainBinding,CP_ETH,inv.depositId,[[inv.cx,inv.cy,inv.owner]],[v]); if(!cpVerifySigma(inv.cx,inv.cy,v,w.sigR,w.sigZ,ctx))throw Error("The request is not claimable by its author — refusing to pay it."); if(!/^0x0[23][0-9a-fA-F]{64}[0-9a-fA-F]{272}$/.test(inv.memo||""))throw Error("The request carries no recovery memo."); const st=await cfgRead(C,[{to:CP_POOL,data:"0x"+SEL_CPDEP+pad32(inv.depositId)},L]); if(BigInt(st||"0x0")!==0n)throw Error("This request was already paid."); if(wei>fromBalance)throw Error("Not enough ETH."); const n={p:1,i:-1,v:String(v),dep:inv.depositId,at:Math.floor(Date.now()/1000),op:w,memo:inv.memo}; const req={from:account,to:CP_ROUTER,value:toHex(wei),data:"0x"+SEL_CPWRAP+pad32(commit)}; await rpc(C,[req,L]); n.tx=await sendTx([req]);cpNotes.unshift(n);cpSaveNotes();cpPaint(); await settle(n.tx,true); stat.textContent="Paid. Asking the relay to settle it for the recipient…"; await cpSettleWrap(n);refreshBal
0x68575b07…67c7·#25,913,385·0x34d3a364…e70aaf
async function cbAct(){ if(!cbDao||cbAmt<=0n)return; const b=document.getElementById("cbGo");if(!b||b.disabled)return; const label=b.textContent; try{ if(!account)return connect(); b.disabled=true;b.textContent=cbBuy?"Backing…":"Burning…"; const req=cbBuy ?{from:account,to:COFF,value:"0x"+cbCost.toString(16), data:"0x"+SEL_BUY+encAddr(cbDao)+encUint(cbAmt)} :{from:account,to:cbDao,value:"0x0", data:"0x"+SEL_RAGEQUIT+encUint(96)+encUint(0)+encUint(cbAmt)+encUint(1)+encAddr(ZERO)}; if(!cbBuy&&cbShares&&cbTok){ let now2=null; try{ const [b2,r2]=await Promise.all([ rpc("eth_getBalance",[cbDao,L]), mc3Deep([{to:cbShares,data:"0x"+SEL_TSUP},{to:cbTok,data:"0x"+SEL_TSUP}])]); const e2=BigInt(b2||"0x0"),s2=BigInt(r2[0]||"0x0")+BigInt(r2[1]||"0x0"); if(s2>0n)now2=e2*cbAmt/s2; }catch{} if(now2!==null&&now2<cbDue){ const lost=cbDue-now2; if(now2<=0n)throw Error("The treasury has been drawn down since this was quoted \u2014 burning now would pay nothing."); if(lost*100n>cbDue)throw Error(`This burn now pays ${trimAmt(now2,18)} ETH, not ${trimAmt(cbDue,18)} ` +`\u2014 the treasury was drawn down since it was quoted. Check the line again before burning.`); } } await rpc(C,[req,L]); await waitTx(await sendTx([req]));sGot(); if(cbBuy&&cbTok){ const kt=TOKENS.find(x=>x.addr&&x.addr.toLowerCase()===cbTok.toLowerCase()); if(kt){kt.cause=1;if(kt.cust)keepToken(kt)} } stat.textContent=cbBuy?"Backed · burn it back any time for what is left." :"Burned · the ether is in your wallet."; refreshBalance();cbSync(); }catch(e){err(e);b.disabled=false;b.textContent=label} } const LN_MIME={"image/png":0,"image/webp":1,"image/svg+xml":2,"image/gif":3,"image/jpeg":4}; const MIME_NAME=["image/png","image/webp","image/svg+xml","image/gif","image/jpeg","image/avif"]; const LN_MAX_ART=24575; const LN_BUDGET=8192; const lnCause=()=>lnKind.value==="cause"; const lnCap=()=>lnCause()?CAU_MAX_ART:LN_MAX_ART; const lnBud=()=>lnCause()?CAU_MAX_ART:LN_BUDGET; const LN_MAX_ALLOC=2000; const LN_MIN_POOLED=2000000000000n; const LN_MIN_MCAP=1000000000000n; const lnHex=b=>{let h="";for(const x of b)h+=x.toString(16).padStart(2,"0");return h}; const lnTail=b=>{const h=lnHex(b);return encUint(b.length)+h.padEnd(Math.ceil(h.length/64)*64,"0")}; const encLaunch=(n,y,u,sup,alloc,mcap,own,art,mime)=>{ const T=new TextEncoder(); const dyn=[T.encode(n),T.encode(y),T.encode(u)]; const statics=[encUint(sup),encUint(alloc),encUint(mcap),encAddr(own)]; if(art){dyn.push(art);statics.push(encUint(mime))} const words=statics.length+(art?4:3); let off=words*32,head="",tail=""; const heads=[]; for(const b of dyn){heads.push(encUint(off));off+=lnTail(b).length/2;tail+=lnTail(b)} head=heads.slice(0,3).join("")+statics.slice(0,4).join("") +(art?heads[3]+statics[4]:""); return "0x"+(art?SEL_LAUNCHART:SEL_LAUNCH)+head+tail; }; async function lnOwner(){ const rv=rc.value.trim(); if(!rv)return account; if(/^0x[0-9a-fA-F]{40}$/.test(rv)){ if(/^0x0{40}$/i.test(rv))throw Error("That cannot be the zero address."); return rv; } if(!/\.(g?wei|eth)$/i.test(rv))throw Error("That must be an address, or a .eth / .wei name."); rcvEl.textContent="resolving\u2026"; let r="";try{r=await nameFwd(rv.toLowerCase())}catch{r=""} if(!r||/^0x0{40}$/i.test(r)){rcvEl.textContent="";throw Error("That name is not registered.")} if(!isAddr(r))throw Error("That name resolved to something that is not an address."); rcvEl.textContent=r; return r; } const CSUM="0x00000000004473e1f31C8266612e7FD5504e6f2a"; const CMSUM="0x0000000000330B8df9E3bc5E553074DA58eE9138"; const CIMPL="643a45b599d81be3f3a68f37eb3de55ff10673c1"; const CRND="0x000000000011C799980827F52d3137b4abD6E654"; const COFF="0x000000A4Ad929C9E108aD2B1D2fBeDe0C2Ae57e1"; const CTAP="0x0000000060cdD33cbE020fAE696E70E7507bF56D"; const CLOOT="0x00000000000000000000000000000000000003ef"; const SEL_DAICO="4e1e3b11",SEL_SETALLOW="da46098c",SEL_CONFIGURE="fca53be5"; const CUNITS=10000000n,CONE=10n**18n,CMAX=(1n<<256n)-1n; const CAU_MAX_ART=4096; const CAU_MAX_DAYS=3650; const cPad=h=>h.padEnd(Math.ceil(h.length/64)*64,"0"); const cTail=h=>{h=strip0x(h);return encUint(h.length/2)+cPad(h)}; const cCall=(to,data)=>encAddr(to)+encUint(0)+encUint(96)+cTail(data); const cCalls=cs=>{let off=cs.length*32,head="",tail=""; for(const c of cs){head+=encUint(off);off+=c.length/2;tail+=c} return encUint(cs.length)+head+tail}; const CLIMPL="6f1f2af76a3add953277e9f369242697c87bc6a5"; const cProxy=impl=>strip0x(keccak(hexToBytes( "602d5f8160095f39f35f5f365f5f37365f73"+impl+"5af43d5f5f3e6029573d5ffd5b3d5ff3"))); const cLoot=dao=>{const d=strip0x(dao).toLowerCase(); return "0x"+keccak(hexToBytes("ff"+d+d+"0".repeat(24)+cProxy(CLIMPL))).slice(26)}; const cPredict=(owner,salt)=>{ const inner=encUint(96)+encUint(160)+strip0x(salt) +encUint(1)+encAddr(owner)+encUint(1)+encUint(CONE); const ss=strip0x(keccak(hexToBytes(inner))); const ih=cProxy(CIMPL); return "0x"+keccak(hexToBytes("ff"+strip0x(CMSUM)+ss+ih)).slice(26); }; const encCause=(name,sym,uri,salt,owner,ben,dao,goal,rate,price,deadline,cap)=>{ const T=new TextEncoder(),Z=encUint(0); const dyn=[lnTail(T.encode(name)),lnTail(T.encode(sym)),lnTail(T.encode(uri)), encUint(1)+encAddr(owner),encUint(1)+encUint(CONE),encUint(0), cCalls([ cCall(dao,SEL_SETALLOW+encAddr(COFF)+encAddr(CLOOT)+encUint(CMAX)), cCall(COFF,SEL_CONFIGURE+encAddr(CLOOT)+encAddr(ZERO)+encUint(price) +encUint(deadline)+encUint(cap))])]; const cfg=encUint(CONE)+encUint(604800)+encUint(259200)+encUint(CONE)+Z.repeat(17); const tap=encAddr(CTAP)+Z+encUint(goal)+encAddr(ben)+encUint(rate); let off=52*32,heads=[],tail=""; for(const d of dyn){heads.push(encUint(off));off+=d.length/2;tail+=d} return "0x"+SEL_DAICO +heads[0]+heads[1]+heads[2] +encUint(1000)+encUint(1)+encAddr(CRND)+strip0x(salt) +heads[3]+heads[4]+heads[5] +cfg+Z.repeat(7)+tap+Z.repeat(8)+heads[6]+tail; }; const cauURI=(name,sym)=>{ let img=""; if(lnArtBytes){let s="";for(const x of lnArtBytes)s+=String.fromCharCode(x); img="data:"+MIME_NAME[lnArtMime]+";base64,"+btoa(s)} return "data:application/json,"+encodeURIComponent( JSON.stringify({name,symbol:sym,image:img,launchType:"cause"})); }; function cauTerms(){ const g=String(lnGoal.value||"").replace(/[, ]/g,"").trim(); const d=String(lnDays.value||"").replace(/[, ]/g,"").trim(); if(!/^\d+(\.\d+)?$/.test(g)||!/^\d+(\.\d+)?$/.test(d))return null; let goal;try{goal=parseUnits(g,18)}catch{return null} const secs=BigInt(Math.round(Number(d)*86400)); if(goal<=0n||secs<3600n||Number(d)>CAU_MAX_DAYS)return null; const price=goal/CUNITS; if(price<=0n)return null; const vest=BigInt(lnVest.value); if(vest<=secs)return{err:`Backing runs for ${trimNum(Number(secs)/86400)} days but the ether is ` +`fully released in ${trimNum(Number(vest)/86400)} days — the treasury would empty before backing closes. ` +`Shorten the raise, or release over longer.`}; const rate=goal/vest||1n; return{goal,secs,price,rate,vest,cap:(CUNITS-1n)*CONE}; } const lnKindQuote=()=>lnKind.value==="cause"?cauQuote():lnQuote(); function cauQuote(){ lnGo.textContent=account?"Launch":"Connect Wallet"; const t=cauTerms(); if(!t){lnNote.textContent="";return} if(t.err){lnNote.textContent=t.err;lnNote.style.color="var(--w)";return} const per=(+formatUnits(t.price,18)); const tenthDays=t.rate>0n ?Number((t.goal/10n)/t.rate)/86400 :0; lnNote.textContent=`1 ETH backs ${trimNum(1/per)} units` +` · ${trimNum(+formatUnits(t.rate*86400n,18))} ETH a day at full funding, over ` +lnVest.options[lnVest.selectedIndex].textContent +(tenthDays>0?` · at a tenth of the goal it empties in ${trimNum(Math.round(tenthDays*10)/10)} days`:"") +` · backers burn back for the rest` +(lnArtBytes?` · logo ${(lnArtBytes.length/1024).toFixed(1)} KB` +(lnArtBytes.length>CAU_MAX_ART?` — over the 4 KB a cause allows, pick something simpler`:""):""); lnNote.style.color=lnArtBytes&&lnArtBytes.length>CAU_MAX_ART?"var(--w)":""; } function cauKind(){ const on=lnKind.value==="cause"; for(const el of [lnMktL,lnSupplyL,lnMcapL,lnAllocL])el.classList.toggle("hide",on); for(const el of [lnGoalL,lnDaysL,lnVestL])el.classList.toggle("hide",!on); lnTitle.textContent=on?"Launch a cause":"Launch a coin"; lnSub.textContent=on?"backers burn back for whatever you have not drawn" :"0.4% of each trade goes to the creator"; lnName.placeholder=on?"Feed Ducks":"Zero Cat"; lnSym.placeholder=on?"DUCK":"ZCAT"; rc.placeholder=on?"Beneficiary — defaults to you":"Creator — defaults to you"; rc.title=on?"Who the ether streams to. Leave blank and it is you. Worth setting to a multisig if the cause has one." :"Who the token belongs to: receives the creator allocation, collects the creator share of trading fees for the life of the token, and may edit its name, symbol and logo. Leave blank and it is you. Worth setting to a multisig if the project has one."; stat.textContent=""; on?cauQuote():lnQuote(); } async function 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.`); if(lnArtBytes&&lnArtBytes.length>CAU_MAX_ART) throw Error(`A cause's logo is stored in the DAO itself, so it is capped at 4 KB. ` +`That one is ${(lnArtBytes.length/1024).toFixed(1)} KB — pick something simpler.`); const t=cauTerms(); if(!t)throw Error("A goal in ether, and how long it runs for — at least an hour, at most ten years."); if(t.err)throw Error(t.err); const ben=await lnOwner(); const owner=account; const salt=wnSecret(); const dao=cPredict(owner,salt); const deadline=BigInt(Math.floor(Date.now()/1000))+t.secs; stat.textContent="Launching…"; const req={from:account,to:CSUM, value:"0x"+t.price.toString(16), data:encCause(name,sym,cauURI(name,sym),salt,owner,ben,dao,t.goal,t.rate,t.price,deadline,t.cap)}; await rpc(C,[req,L]); await waitTx(await sendTx([req]));sGot(); lnArtBytes=null;lnArtNote.textContent="none";lnArtNote.classList.remove("set"); lnPrev.classList.remove("set");lnArt.value=""; let loot=cLoot(dao); try{ const said=cbWord(await rpc(C,[{to:dao,data:"0x"+SEL_LOOTOF},L])); if(isAddr(said||"")&&said.toLowerCase()!==loot.toLowerCase())loot=said; }catch{} try{const ix=await addCustomToken(loot);TOKENS[ix].cause=1;rebuild()}catch{} stat.innerHTML=`${safeSym(sym)} is raising at ` +`<a href="${chainCfg().explorer}/token/${loot}" target="_blank" rel="noreferrer">${short(loot)}</a>` +` · ${trimNum(+formatUnits(t.goal,18))} ETH over ${trimNum(Number(t.secs)/86400)} days` +` · backers can burn back for whatever you have not drawn`; } let lnMode=false,lnArtBytes=null,lnArtMime=0; function lnSet(on){ if(on&&PLAUNCH===ZERO)return; if(on)cbDisarm(); ++seq;quoting=false; lnMode=on; if(on&&lqMode)lqSet(false); if(on&&wnMode)wnSet(false); if(on&&pvMode)pvSet(false); ln.setAttribute("aria-pressed",on?"true":"false"); ln.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); document.querySelector(".card").classList.toggle("lnon",on); for(const el of [lnPanel,lnNote,lnGo])el.classList.toggle("hide",!on); rc.value="";rc.classList.remove("bad");rcvEl.textContent=""; rc.title=on?"Who the token belongs to: receives the creator allocation, collects the creator share of trading fees for the life of the token, and may edit its name, symbol and logo. Leave blank and it is you. Worth setting to a multisig if the project has one.":""; rc.placeholder=on?"Creator — defaults to you" :"Recipient (optional) — 0x or .eth/.(g)wei"; stat.textContent="";lnNote.textContent=""; if(on)cauKind(); fcSync(); } function lnQuote(){ lnGo.textContent=account?"Launch":"Connect Wallet"; const ns=v=>v.replace(/[, ]/g,""); const sup=ns(lnSupply.value),mc=+ns(lnMcap.value),al=+ns(lnAlloc.value); lnNote.textContent=""; if(!(Number(sup)>0)||!(mc>0))return; const alBps=al>0?Math.round(al*100):0; const pooled=Number(sup)*(1-alBps/10000); if(!(pooled>0))return; const sym=lnSym.value.trim().toUpperCase(); const poolFrac=99/(mc+.99); const firstEth=poolFrac*pooled/Number(sup); const firstTokens=poolFrac*pooled/100; lnNote.textContent=`1 ETH buys ${firstEth>=10?Math.round(firstEth):firstEth.toFixed(firstEth<1?2:1)}% of all ${sym||"tokens"}` +` \u00b7 opens at 1 ETH \u2248 ${trimNum(firstTokens)} ${sym||"tokens"}` +(al>0&&alBps===0?` \u00b7 that keep is below one basis point \u2014 the smallest is 0.01%`:"") +(alBps>0?` \u00b7 creator keeps ${trimNum(Number(sup)*alBps/10000)} (${alBps/100}%)` +` \u00b7 fully diluted ${trimNum(mc*Number(sup)/pooled)} ETH`:"") +(alBps>=1000?` \u00b7 that much can sell for most of the ether early buyers put in`:"") +(lnArtBytes?` \u00b7 logo ${(lnArtBytes.length/1024).toFixed(1)} KB`:""); lnNote.style.color=poolFrac>=10?"var(--w)":""; } const trimNum=n=>n>=1000?Math.round(n).toLocaleString("en-US") :n>=1?n.toLocaleString("en-US",{maximumFractionDigits:4}):String(Number(n.toPrecision(4))); const blobText=b=>b.text?b.text():new Promise((res,rej)=>{ const r=new FileReader();r.onload=()=>res(String(r.result));r.onerror=()=>rej(r.error);r.readAsText(b)}); const blobBytes=async b=>b.arrayBuffer?new Uint8Array(await b.arrayBuffer()):await new Promise((res,rej)=>{ const r=new FileReader();r.onload=()=>res(new Uint8Array(r.result));r.onerror=()=>rej(r.error);r.readAsArrayBuffer(b)}); async function lnPrepare(file){ if(/svg/.test(file.type)){ const b=new TextEncoder().encode((await blobText(file)) .replace(/<\?xml[^>]*\?>/g,"").replace(/<!--[\s\S]*?-->/g,"").replace(/<!DOCTYPE[^>]*>/gi,"") .replace(/<script[\s\S]*?<\/script\s*>/gi,"").replace(/<script[\s\S]*$/gi,"") .replace(/<foreignObject[\s\S]*?<\/foreignObject\s*>/gi,"").replace(/<foreignObject[\s\S]*$/gi,"") .replace(/\son[a-z-]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]*)/gi,"") .replace(/\s(?:[a-z]+:)?href\s*=\s*("\s*javascript:[^"]*"|'\s*javascript:[^']*'|javascript:[^\s>]*)/gi,"") .replace(/>\s+</g,"><").trim()); if(b.length>lnCap())throw Error(`That SVG is ${(b.length/1024).toFixed(1)} KB and the on-chain limit is ${Math.round(lnCap()/1024)} KB.`); return{bytes:b,mime:LN_MIME["image/svg+xml"]}; } const bmp=await createImageBitmap(file); let smallest=null; for(const side of [512,384,256,192,128,96,64]){ for(const [type,q] of [["image/webp",1],["image/png",undefined],["image/webp",0.85],["image/webp",0.6]]){ const sc=Math.min(1,side/Math.max(bmp.width,bmp.height)); const cv=document.createElement("canvas"); cv.width=Math.max(1,Math.round(bmp.width*sc));cv.height=Math.max(1,Math.round(bmp.height*sc)); const cx=cv.getContext("2d");cx.imageSmoothingQuality="high"; cx.drawImage(bmp,0,0,cv.width,cv.height); const blob=await new Promise(r=>cv.toBlob(r,type,q)); if(!blob||blob.type!==type)continue; const bytes=await blobBytes(blob); if(!smallest||bytes.length<smallest.bytes.length)smallest={bytes,mime:LN_MIME[type]}; if(bytes.length<=lnBud())return{bytes,mime:LN_MIME[type]}; } } if(smallest&&smallest.bytes.length<=lnCap())return smallest; throw Error("That image will not compress small enough. Flat colours and simple shapes do far better than photographs."); } lnPick.onclick=()=>lnArt.click(); lnArt.onchange=async()=>{ const f=lnArt.files&&lnArt.files[0]; if(!f)return; lnArtNote.textContent="reading…"; try{ const r=await lnPrepare(f); lnArtBytes=r.bytes;lnArtMime=r.mime; lnArtNote.textContent=`${(r.bytes.length/1024).toFixed(1)} KB`; lnArtNote.classList.add("set"); let b64="";for(const x of r.bytes)b64+=String.fromCharCode(x); lnPrev.src="data:"+MIME_NAME[r.mime]+";base64,"+btoa(b64); lnPrev.classList.add("set"); }catch(e){ lnArtBytes=null;lnArtNote.classList.remove("set");lnPrev.classList.remove("set"); lnArtNote.textContent="none";stat.textContent=e.message||String(e); } lnKindQuote(); }; const lnGroup=el=>{ if(!/^[\d, ]*(\.[\d ]*)?$/.test(el.value))return; const dot=el.value.indexOf("."); const intRaw=(dot<0?el.value:el.value.slice(0,dot)).replace(/\D/g,""); const frac=dot<0?"":"."+el.value.slice(dot+1).replace(/\D/g,""); const digitsBefore=el.value.slice(0,el.selectionStart??el.value.length).replace(/\D/g,"").length; const out=intRaw.replace(/\B(?=(\d{3})+(?!\d))/g,",")+frac; if(out===el.value)return; el.value=out; let seen=0,pos=digitsBefore?out.length:0; if(digitsBefore)for(let i=0;i<out.length;i++){ if(/\d/.test(out[i])&&++seen===digitsBefore){pos=i+1;break} } try{el.setSelectionRange(pos,pos)}catch{} }; lnSupply.oninput=()=>{lnGroup(lnSupply);lnQuote()}; for(const el of [lnName,lnSym,lnMcap,lnAlloc])el.oninput=lnKindQuote; lnKind.onchange=cauKind; for(const el of [lnGoal,lnDays])el.oninput=cauQuote; lnVest.onchange=cauQuote; ln.onclick=()=>{sBlip();lnSet(!lnMode)}; let wnMode=false,wnQ=null,wnSeq=0,wnRollSeq=0,wnTimer=0,wnRound=0n,wnStampTry=0,wnRollAt=0; const wnOk=l=>/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(l)&&new TextEncoder().encode(l).length<256; const wnFree=()=>wnTld.value==="id"; const wnOffer=(t,id)=>{wnEnter.classList.remove("hide");wnEnter.textContent=t; wnEnter.dataset.id=id||"";wnEnter.classList.toggle("mut",!id&&!!account)}; const wnFull=l=>l+(wnFree()?".id.wei":".wei"); const wnSecret=()=>{const a=new Uint8Array(32);crypto.getRandomValues(a);let h="";for(const x of a)h+=x.toString(16).padStart(2,"0");return"0x"+h}; const wnHash=(l,o,sec)=>keccak(hexToBytes(pad32("60")+encAddr(o)+pad32(sec)+strTail(l))); const wnKey=()=>WN_KEY+":"+(account||"").toLowerCase(); const wnStamp=async()=>{const p=wnPend(); if(!p||p.at||!account)return; try{const r=await rpc(C,[{to:WNS,data:"0x"+SEL_WCOMMITS+pad32(wnHash(p.label,account,p.secret))},L]); const t=Number(dec1(r||"0x0",0)); if(t)wnSave({...p,at:t})}catch{}}; const wnPend=()=>{try{const p=JSON.parse(LS[wnKey()]||"null");return p&&p.label&&p.secret&&Number.isFinite(p.at)?p:null}catch{return null}}; const wnSave=p=>{try{if(p)LS[wnKey()]=JSON.stringify(p);else delete LS[wnKey()]}catch{}}; const wnId=n=>namehash(/\.wei$/.test(n)?n:n+".wei"); const wnCostCall=l=>[{to:WNS,data:"0x"+SEL_WFEE+encUint(new TextEncoder().encode(l).length)}, {to:WNS,data:"0x"+SEL_WPREM+pad32(wnId(l))}]; const wnCost=async l=>{const [f,pr]=await mc3(wnCostCall(l));return dec1(f,0)+dec1(pr,0)}; const wnSync=()=>{ if(!wnMode)return; const p=wnPend(); if(p&&!p.at){ if(!p.since){wnSave({...p,since:Math.floor(Date.now()/1000)});wnGo.disabled=true;wnGo.textContent="Confirming\u2026";return} if(Math.floor(Date.now()/1000)-p.since>600){wnGo.disabled=false;wnGo.textContent="Commit again";return} wnGo.disabled=true;wnGo.textContent="Confirming\u2026";return} if(p){ const left=WN_MIN-(Math.floor(Date.now()/1000)-p.at); wnGo.disabled=left>0; wnGo.textContent=left>0?`Ready in ${left}s`:`Register ${p.label}.wei`; if(Math.floor(Date.now()/1000)-p.at>=WN_MAX){wnGo.disabled=false;wnGo.textContent="Commit again"} return} wnGo.disabled=!wnQ; wnGo.textContent=wnQ?(wnQ.free?`Claim ${wnQ.label}.id.wei`:"Commit"):"Register"}; const wnTicketOffer=async(l,seq,round)=>{ try{const[t]=await mc3([{to:WROLL,data:"0x"+SEL_RTICKET+encUint(round)+pad32(wnId(l))}]); if(seq!==wnSeq)return; const inAlready=!!dec1(t,0); wnOffer(inAlready?`${l}.wei is already in`:`Enter ${l}.wei in the roll`,inAlready?"":pad32(wnId(l))); }catch{if(seq===wnSeq)wnOffer(`Could not read the roll for ${l}.wei`)}}; const wnQuote=async()=>{ const my=++wnSeq; wnQ=null; wnOffer(account?"Enter a name you own":"Connect to enter"); wnSub.textContent=wnFree()?"free":"from 0.0005 ETH a year"; const l=wnName.value.trim().toLowerCase(); const p=wnPend(); if(p){wnNote.textContent=p.at?`${p.label}.wei is held for you — register it within 24 hours or the commitment lapses.` :`Waiting for the commitment on ${p.label}.wei to confirm.`;wnSync();return} if(!l){wnNote.textContent="";wnSync();return} if(!wnOk(l)){wnNote.textContent="Letters, digits and hyphens only, and not starting or ending with a hyphen.";wnSync();return} wnNote.textContent="checking…"; wnSync(); try{ const free=wnFree(); const rc=!free&&account?[{to:WNS,data:"0x"+SEL_WOWNER+pad32(wnId(l))}, {to:WROLL,data:"0x"+SEL_RWEIGHT+pad32(wnId(l))}, {to:WROLL,data:"0x"+SEL_RSTATE}]:[]; const r=await mc3([{to:WNS,data:"0x"+SEL_WAVAIL+pad32("40")+pad32(free?IDWEI:"0")+strTail(l)}, ...(free?[]:wnCostCall(l)),...rc]); if(my!==wnSeq)return; const av=r[0],f=r[1],pr=r[2]; if(!account)wnOffer("Connect to enter"); else if(free)wnOffer("A free name cannot enter the roll"); else if(dec1(av,0)===1n)wnOffer(`${l}.wei is not registered yet`); else{const ow=retAddr(r[3]),mine=!!ow&&ow.toLowerCase()===account.toLowerCase(); const wt=r[4]&&dec1(r[4],0); if(r[5])wnRound=dec1(r[5],1); if(!mine||!wt)wnOffer(!mine?`${l}.wei is not yours`:`${l}.wei does not weigh`); else wnTicketOffer(l,my,wnRound)} if(dec1(av,0)!==1n){wnNote.textContent=`${wnFull(l)} is taken.`;wnSync();return} if(free){wnQ={label:l,free:true}; wnNote.textContent=`${l}.id.wei is free, in one transaction. A free name does not weigh in the roll.`;wnSync();return} const cost=dec1(f,0)+dec1(pr,0); wnQ={label:l,free:false,cost}; wnNote.textContent=`${l}.wei costs ${trimAmt(cost,18)} ETH for a year, in two transactions a minute apart.`; }catch(e){if(my===wnSeq)wnNote.textContent="Could not price that name."} wnSync()}; const wnRoll=async()=>{ if(!wnMode)return; const my=++wnRollSeq; wnRollAt=Date.now(); try{ const [st,rv]=await mc3([{to:WROLL,data:"0x"+SEL_RSTATE}, ...(account?[{to:WNS,data:"0x"+SEL_REV+encAddr(account)}]:[])]); const round=dec1(st,1),end=Number(dec1(st,2)),pot=dec1(st,3),tix=dec1(st,5),tw=dec1(st,6); if(my!==wnRollSeq)return; const days=Math.max(0,Math.ceil((end-Date.now()/1000)/86400)); let line=`A monthly draw among paid .wei names. ${(+formatUnits(pot,18)).toFixed(3)} ETH staked, ${tix} entered, ${days} day${days===1?"":"s"} left.`; wnRound=round; const typed=wnName.value.trim(); if(!typed)wnOffer(account?"Enter a name you own":"Connect to enter"); if(account&&!typed){ const nm=decodeString(rv||"0x").trim(); if(!nm)line+=" Any paid .wei name you own can enter, free — type one above."; else{ const id=wnId(nm); const [w,t]=await mc3([{to:WROLL,data:"0x"+SEL_RWEIGHT+pad32(id)}, {to:WROLL,data:"0x"+SEL_RTICKET+encUint(round)+pad32(id)}]); if(my!==wnRollSeq||wnName.value.trim()){wnRollEl.textContent=line;return} const weight=dec1(w,0); if(!weight){line+=` ${nm} does not weigh — only a paid .wei name does.`; wnOffer("Enter a name you own")} else if(dec1(t,0)){const odds=tw?Number(weight*10000n/tw)/100:0; line+=` ${nm} is in, at about ${odds<0.01?"<0.01":odds.toFixed(2)}%.`} else{line+=` ${nm} can enter, free.`;wnOffer(`Enter ${nm} in the roll`,pad32(id))}}} wnRollEl.textContent=line; }catch{wnRollEl.textContent="Could not read the roll."}}; const wnPaint=()=>{const on=wnMode; wn.setAttribute("aria-pressed",on?"true":"false"); wn.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 [wnPanel,wnGo,wnRollPanel])el.classList.toggle("hide",!on); for(const el of [rc,rcvEl])if(el)el.classList.toggle("hide",on);document.querySelector(".card").classList.toggle("wnon",on)}; function wnSet(on){ if(on&&CHAIN_ID!==1)return; if(on)cbDisarm(); ++seq;quoting=false; wnMode=on; if(on&&lqMode)lqSet(false); if(on&&lnMode)lnSet(false); if(on&&pvMode)pvSet(false); wnPaint(); stat.textContent=""; clearInterval(wnTimer); if(on){wnTimer=setInterval(()=>{ if(document.hidden)return; wnSync(); if(Date.now()-wnRollAt>30000)wnRoll(); const q=wnPend(); if(q&&!q.at&&Date.now()-wnStampTry>5000){wnStampTry=Date.now(); wnStamp().then(()=>{const r=wnPend();if(r&&r.at)wnQuote()})} },1000); wnStampTry=0; const pp=wnPend();if(pp&&!pp.at)wnStamp().then(wnQuote); wnQuote();wnRoll()} else{wnNote.textContent="";wnRollEl.textContent=""} fcSync();} const syncSnd=()=>{sdw.style.display=snd?"":"none";sdx.style.display=snd?"none":""; sd.style.opacity=snd?"":".5";sd.setAttribute("aria-pressed",snd?"true":"false"); sd.title=snd?"Sound on — click to mute":"Sound off
0x68575b07…67c7·#25,913,382·0x9412e642…a72f9c
t0.dec); }catch{} } lqList.addEventListener("input",e=>{ const box=e.target.closest(".lqadd"); if(box&&e.target.classList.contains("lqin")){ box.querySelector('[data-act="ac"]').disabled=true; lqMirror(box,e.target);clearTimeout(lqPvT);lqPvT=setTimeout(()=>lqPreview(box),300);return} const nb=e.target.closest(".lqnew"); if(nb){nb.querySelector("#lqCreate").disabled=true; clearTimeout(lqPvT);lqPvT=setTimeout(lqNewPreview,300)} }); lqList.addEventListener("change",e=>{ if(e.target.id==="lqFee"||e.target.id==="lqRange")return lqNewPreview(); if(!e.target.classList.contains("lqz"))return; const box=e.target.closest(".lqadd"),ins=box.querySelectorAll(".lqin"); const on=e.target.checked; const mir=box.dataset.mir; if(on&&mir&&ins[0].value.trim()&&ins[1].value.trim())ins[+mir].value=""; if(!on)lqMirror(box,ins[0].value.trim()||!ins[1].value.trim()?ins[0]:ins[1]); box.querySelector('[data-act="ac"]').textContent=on?"Zap in":"Add liquidity"; lqPreview(box); }); let lqNewSeq=0; let lqPvT2; const lqPreviewSoon=()=>{const g=lqList.querySelector("#lqCreate");if(g)g.disabled=true; clearTimeout(lqPvT2);lqPvT2=setTimeout(lqNewPreview,320)}; async function lqNewPreview(){ const box=lqList.querySelector(".lqnew"); if(!box)return; const out=box.querySelector("#lqPv"),go=box.querySelector("#lqCreate"); const bandOut=box.querySelector("#lqRangeOut"); const my=++lqNewSeq; go.disabled=true; const pair=chPair(); if(!pair){out.textContent="";return} const [t0,t1]=pair; const range=box.querySelector("#lqRange").value; box.querySelector("#lqCustom").classList.toggle("hide",range!=="custom"); const payTok=TOKENS[fromSel.value]; const payIs0=!!payTok&&payTok.addr.toLowerCase()===t0.addr.toLowerCase(); const el0=payIs0?amt:outAmt,el1=payIs0?outAmt:amt; let a0=0n,a1=0n; try{ const v0=el0.value.trim(),v1=el1.value.trim(); a0=v0?parseUnits(v0,t0.dec):0n;a1=v1?parseUnits(v1,t1.dec):0n; }catch{out.textContent="Check the amounts.";bandOut.textContent="";return} const oneSided=(a0&&!a1)?0:(a1&&!a0)?1:-1; box.querySelector("#lqPxRow").classList.toggle("hide",oneSided<0); if(!a0&&!a1){ bandOut.textContent=""; out.textContent=`Enter how much ${t0.sym} and ${t1.sym} to deposit — the ratio sets the opening price.`; return} const pxIn=box.querySelector("#lqPx").value.trim(); const px=oneSided<0 ?(Number(a1)/10**t1.dec)/(Number(a0)/10**t0.dec) :(pxIn?+pxIn:NaN); if(!(px>0)){ bandOut.textContent=""; const only=oneSided===0?t0.sym:t1.sym; out.textContent=`${/^[AEIOU]/i.test(only)?"An":"A"} ${only}-only band has no ratio to` +` read a price from — name one.`; return} const fee=BigInt(box.querySelector("#lqFee").value); let lo,hi; if(range==="custom"){ const n=id=>{const v=box.querySelector("#"+id).value.trim();return v?+v:NaN}; lo=n("lqLo");hi=n("lqHi"); if(!(lo>0)||!(hi>0)){bandOut.textContent="";out.textContent="Name both prices.";return} if(lo>=hi){out.textContent="The low price must be below the high one.";bandOut.textContent="";return} if(oneSided<0&&(px<lo||px>hi)){ out.textContent=`These amounts imply ${fmtPx(px)}, which is outside that band.`;return} }else{ let mult=+range; if(range==="full"){ const found=lqFullWidth(t0.dec,t1.dec,a0,a1,px,fee); if(!found){ bandOut.textContent=""; out.textContent="Even the narrowest range needs more than this deposit.";return} mult=found; } [lo,hi]=lqBandFor(px,mult,a0,a1); } const sl=pxToSqrt(lo,t0.dec,t1.dec),sh=pxToSqrt(hi,t0.dec,t1.dec); if(!sl||!sh||sh<=sl){out.textContent="Check the prices.";bandOut.textContent="";return} const sp=oneSided===0?sl:oneSided===1?sh:lqSeedPrice(sl,sh,a0,a1); if(!sp){out.textContent="Those amounts cannot open a band that wide.";return} const openAt=bandPrice(sp,t0.dec,t1.dec); bandOut.innerHTML=lqBar(lo,hi,openAt)+`<span></span>`; bandOut.querySelector("span").textContent= `${fmtPx(lo)} – ${fmtPx(hi)} · opens at ${fmtPx(openAt)} ${t1.sym} per ${t0.sym}` +(oneSided===0?` · ${t0.sym} only`:oneSided===1?` · ${t1.sym} only`:""); box.querySelector("#lqOwnNote").textContent= `Unowned, like any AMM pool: anyone may add, and no creator fee can ever be` +` charged. Prices are ${t1.sym} per ${t0.sym}.`; lqGasNote(box); const market=lqMarket(t0.addr,t1.addr,sl,sh,fee,ZERO); box.dataset.sl=sl;box.dataset.sh=sh;box.dataset.sp=sp;box.dataset.fee=fee; box.dataset.a0=a0;box.dataset.a1=a1;box.dataset.lp=0; const q=lqSeedQuote(sl,sh,sp,a0,a1); if(!q.ok){out.textContent="This band cannot open at that price with those amounts.";return} if(!lqSeedAdmits(sl,sh,sp,fee,q)){ out.textContent="A band this size could not be traded once opened —" +" widen the range, or deposit more.";return} out.textContent=`Seeds ${trimAmt(q.used0,t0.dec)} ${t0.sym} + ${trimAmt(q.used1,t1.dec)} ${t1.sym}` +((q.used0<a0||q.used1<a1)?" — the rest is returned":""); box.dataset.lp=q.lp; try{ const [addr]=await mc3([{to:PFACTORY,data:"0x"+SEL_POOLFOR+market}]); if(my!==lqNewSeq)return; const pool="0x"+strip0x(addr).slice(24); const [sup]=await mc3([{to:pool,data:"0x"+SEL_TOTALSUPPLY}]).catch(()=>[null]); if(my!==lqNewSeq)return; if(sup&&strip0x(sup).length>=64&&dec1(sup,0)>0n){ out.textContent="That exact band already exists — add to it from the list above."; return} go.disabled=!account; }catch{ if(my!==lqNewSeq)return; out.textContent="Could not reach the factory."; } } const LQ_CREATE_GAS=4700000n; async function lqGasNote(box){ const el=box.querySelector("#lqGas"); if(!el)return; el.textContent=`Creating deploys the pool contract — about ` +`${(Number(LQ_CREATE_GAS)/1e6).toFixed(1)}M gas.`; try{ const wei=BigInt(await rpc("eth_gasPrice",[]))*LQ_CREATE_GAS; el.textContent=`Creating deploys the pool contract — about ` +`${(Number(LQ_CREATE_GAS)/1e6).toFixed(1)}M gas, roughly ` +`${trimAmt(wei,18)} ETH at the moment. Adding to a band that already ` +`exists costs a fraction of it, so check the fee your wallet offers.`; }catch{} } async function lqCreate(){ const box=lqList.querySelector(".lqnew"); if(!box||busy||!account)return; const pair=chPair(); if(!pair)return; const [t0,t1]=pair; const go=box.querySelector("#lqCreate"),out=box.querySelector("#lqPv"); const sl=BigInt(box.dataset.sl||0),sh=BigInt(box.dataset.sh||0),sp=BigInt(box.dataset.sp||0); const a0=BigInt(box.dataset.a0||0),a1=BigInt(box.dataset.a1||0),fee=BigInt(box.dataset.fee||0); if(!sl||!sh||!sp){out.textContent="Preview the band first.";return} const to=await lqTo(); if(!to)return; ++busy;go.disabled=true; try{ const bps=BigInt(Math.round(Math.min(.5,Math.max(.01,+slip.value||.5))*100)); const lp=BigInt(box.dataset.lp||0); if(lp<=0n){out.textContent="Preview the band first.";return} const minLP=lp*(10000n-bps)/10000n; const market=lqMarket(t0.addr,t1.addr,sl,sh,fee,ZERO); const native=t0.addr===ZERO; if(!native&&a0)await lqApprove(t0.addr,a0); if(a1)await lqApprove(t1.addr,a1); const data="0x"+SEL_CREATE_SEED+market+encUint(sp)+encUint(a0)+encUint(a1) +encUint(minLP)+encAddr(to); const tx={from:account,to:PFACTORY,data,value:native?toHex(a0):"0x0"}; stat.textContent="Creating the band..."; await rpc(C,[tx,L]);await checkWallet();await settle(await sendTx([tx])); stat.textContent="Band created."; loadLq();refreshBalance(); }catch(e){err(e);out.textContent="";} finally{--busy;go.disabled=false} } async function lqApprove(token,need){ const al=BigInt(await rpc(C,[{to:token,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(PFACTORY)},L])); if(al>=need)return; if(al)await settle(await sendTx([{from:account,to:token, data:"0x"+SEL_APPROVE+encAddr(PFACTORY)+encUint(0n),value:"0x0"}])); await settle(await sendTx([{from:account,to:token, data:"0x"+SEL_APPROVE+encAddr(PFACTORY)+encUint(need),value:"0x0"}])); } lqList.onclick=async ev=>{ if(ev.target.id==="lqCreate"){await lqCreate();return} if(ev.target.id==="lqNewTog"){ const pr=chPair();if(!pr)return; ev.target.outerHTML=lqNewForm(pr[0],pr[1]); lqSyncAmounts(); lqNewPreview(); lqList.querySelector(".lqnew")?.scrollIntoView?.({block:"nearest"}); return} const b=ev.target.closest("[data-act]"); if(!b||busy)return; const p=chPair(); if(!p)return; const [t0,t1]=p,pool=b.dataset.pool; const row=lqRows.find(x=>x.pool.toLowerCase()===pool.toLowerCase()); if(!row)return; const act=b.dataset.act; if(act!=="a"&&!account)return void(stat.textContent="Connect a wallet first."); if(act==="a"){ const box=b.closest(".lqrow").querySelector(".lqadd"); box.classList.toggle("hide"); b.textContent=box.classList.contains("hide")?"Add":"Cancel"; if(!box.classList.contains("hide"))box.querySelector(".lqin").focus(); return; } if(act==="w"){ if(row.shares===0n)return; ++busy;b.disabled=true; try{await doRemove(pool,row.shares,t0,t1)}finally{--busy;b.disabled=false} return; } if(act==="ac"){ const box=b.closest(".lqadd"); const ins=box.querySelectorAll(".lqin"); if(isZap(box)){ const z=zapSide(box,t0,t1); if(z.err){stat.textContent=z.err;return} if(!z.amt)return; ++busy;b.disabled=true; try{await doZap(pool,z.tok,z.amt)}finally{--busy;b.disabled=false} return; } let a0,a1; try{ a0=parseUnits(ins[0].value.trim(),t0.dec);a1=parseUnits(ins[1].value.trim(),t1.dec); }catch{stat.textContent="Invalid amount";return} if(!a0||!a1)return; ++busy;b.disabled=true; try{await doAdd(pool,a0,a1,t0,t1)}finally{--busy;b.disabled=false} } }; footDoc.setAttribute("aria-expanded","false"); footDoc.onclick=e=>{ e.preventDefault(); const on=docPanel.classList.contains("hide"); footDoc.setAttribute("aria-expanded",on?"true":"false"); docPanel.classList.toggle("hide",!on); footDoc.textContent=on?"hide":"how it works"; if(on)docPanel.scrollIntoView({behavior:"smooth",block:"nearest"}); }; lq.onclick=()=>{sBlip();lqSet(!lqMode)}; let PLAUNCH=MB.la; const SEL_LAUNCH="6a648dc6"; const SEL_LAUNCHART="72cf7bd8"; const SEL_CREATOROF="dea5c2e0"; const SEL_FLOORPRICE="2aad9987"; const ETHUSD_FEED="0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419"; const ETHUSD_STALE=3600; let ethUsd=0; async function loadEthUsd(){ try{ const c=JSON.parse(sessionStorage.getItem("ethusd")||"null"); if(c&&Date.now()-c.t<300000){ethUsd=c.v;return} }catch{} try{ const r=await cfgRead(C,[{to:ETHUSD_FEED,data:"0xfeaf968c"},L]); const h=strip0x(r||""); if(h.length<5*64)return; const answer=BigInt("0x"+h.slice(64,128)),updated=Number(BigInt("0x"+h.slice(192,256))); if(answer<=0n)return; if(Math.floor(Date.now()/1000)-updated>ETHUSD_STALE)return; ethUsd=Number(answer)/1e8; try{sessionStorage.setItem("ethusd",JSON.stringify({t:Date.now(),v:ethUsd}))}catch{} }catch{} } const fmtUsd=v=>{ if(!(v>0))return ""; if(v>=1000)return "$"+Math.round(v).toLocaleString("en-US"); if(v>=1)return "$"+v.toFixed(2); if(v>=0.01)return "$"+v.toFixed(4); return "$"+fmtPx(v); }; const BETH="0x2cb662Ec360C34a45d7cA0126BCd53C9a1fd48F9"; const TITHE_RECORD="0x5E58BA0e06ED0F5558f83bE732a4b899a674053E"; const BURN_TTL=600000; async function burnTotal(){ try{ const c=JSON.parse(sessionStorage.getItem("burn")||"null"); if(c&&Date.now()-c.t<BURN_TTL)return void showBurn(BigInt(c.v)); }catch{} try{ const r=await cfgRead(C,[{to:BETH,data:"0x70a08231"+encAddr(TITHE_RECORD)},L]); const v=BigInt(r||"0x0"); if(v<=0n)return; try{sessionStorage.setItem("burn",JSON.stringify({t:Date.now(),v:v.toString()}))}catch{} showBurn(v); }catch{} } function showBurn(v){ if(v<=0n)return; footBurn.innerHTML=` \u00b7 <a href="https://etherscan.io/token/${BETH}?a=${TITHE_RECORD}" ` +`target="_blank" rel="noopener" title="A tenth of every fee a launched coin earns is burned to Ethereum itself. ` +`This is the running total, held as the BETH receipt minted to the DAO - the ether it stands for no longer exists.">` +`${(+formatUnits(v,18)).toFixed(4)} ETH burned</a>`; } async function quoteFloor(tok,amount,block){ if(!tok||tok===ZERO||amount<=0n)return 0n; const key=tok.toLowerCase(); const at=block||L; if(!fcFloorPx[key]||fcFloorPx[key].blk!==at){ try{ const r=await rpc(C,[{to:PLAUNCH,data:"0x"+SEL_FLOORPRICE+encAddr(tok)},at]); fcFloorPx[key]={v:BigInt(r||"0x0"),blk:at}; }catch{delete fcFloorPx[key];return 0n} } const px=fcFloorPx[key].v; return px>0n?px*amount/(10n**18n):0n; } const SEL_POOLOF="988b1fa7",SEL_OWED0="56ea33ee",SEL_OWED1="3ad57b72",SEL_COLLECT="a480ca79"; const SEL_COLLECTMANY="c296057e"; let fcMineToks=[],fcSeq=0,fcMineSeq=0; async function fcMine(){ const my=++fcMineSeq; fcMineToks=[]; if(!account||!LAUNCHED.length||PLAUNCH===ZERO)return fcSync(); try{ const reqs=[]; for(const x of LAUNCHED)reqs.push( {to:PLAUNCH,data:"0x"+SEL_CREATOROF+encAddr(x.addr)}, {to:x.pool,data:"0x"+SEL_OWED0}); const res=await mc3Deep(reqs); if(my!==fcMineSeq)return; const acct=account.toLowerCase(); let total=0n;const mine=[]; for(let i=0;i<LAUNCHED.length;i++){ const c=res[i*2],o=res[i*2+1]; if(!c||!o)continue; if(("0x"+strip0x(c).slice(24)).toLowerCase()!==acct)continue; let owed=0n;try{owed=BigInt(o)}catch{continue} if(owed<FC_DUST)continue; total+=owed;mine.push(LAUNCHED[i]); } if(!mine.length)return fcSync(); fcMineToks=mine.map(x=>x.addr); const cut=(+formatUnits(total*8n/10n,18)).toFixed(4); fcEl.innerHTML=(mine.length===1?`${safeSym(mine[0].sym)} \u00b7 `:`${mine.length} of your coins \u00b7 `) +`${cut} ETH in creator fees ` +`<button type="button" id="fcGo" class="fclink">${mine.length===1?"Collect":"Collect all"}</button>`; fcEl.title="Trading fees your launched coins have earned and nobody has swept yet. " +"The ether comes to you; the token side is burned, which raises the floor for your holders."; fcEl.classList.toggle("hide",lnMode||lqMode||wnMode||pvMode||tab!=="swap"); fcGo.onclick=fcCollectMine; }catch{if(my===fcMineSeq)fcSync()} } async function fcCollectMine(){ if(!fcMineToks.length)return; const b=fcGo;if(b.disabled)return; const label=b.textContent; try{ b.disabled=true;b.textContent="Collecting\u2026"; const req={from:account,to:PLAUNCH,value:"0x0", data:"0x"+SEL_COLLECTMANY+encUint(32)+encUint(BigInt(fcMineToks.length)) +fcMineToks.map(encAddr).join("")}; await rpc(C,[req,L]); await waitTx(await sendTx([req]));sGot(); stat.textContent="Fees collected."; for(const k of Object.keys(fcFloorPx))delete fcFloorPx[k]; fcMine(); }catch(e){err(e);b.disabled=false;b.textContent=label} } const FC_DUST=10000000000000n; let fcTok="",fcOwed=0n,fcOwner=""; const fcPool={},fcFloorPx={}; async function fcSync(){ const my=++fcSeq; const t=TOKENS[toSel.value],f=TOKENS[fromSel.value]; const cand=[t,f].find(x=>x&&x.addr&&x.addr!==ZERO&&x.std!=="nft"); fcTok="";fcOwed=0n; if(lnMode||lqMode||wnMode||pvMode||tab!=="swap"||PLAUNCH===ZERO){fcEl.classList.add("hide");return} if(fcMineToks.length){fcEl.classList.remove("hide");return} if(!cand){fcEl.classList.add("hide");return} try{ const key=cand.addr.toLowerCase(); let pool=fcPool[key]; if(pool===undefined){ const pr=await rpc(C,[{to:PLAUNCH,data:"0x"+SEL_POOLOF+encAddr(cand.addr)},L]); if(my!==fcSeq)return; pool=fcPool[key]="0x"+strip0x(pr||"").slice(24); } if(!/^0x[0-9a-f]{40}$/i.test(pool)||/^0x0{40}$/i.test(pool)){fcEl.classList.add("hide");return} const [o0,cr]=await mc3Deep([{to:pool,data:"0x"+SEL_OWED0}, {to:PLAUNCH,data:"0x"+SEL_CREATOROF+encAddr(cand.addr)}]); if(my!==fcSeq)return; fcOwner=cr?"0x"+strip0x(cr).slice(24):""; const owed=BigInt(o0||"0x0"); if(owed<FC_DUST){fcEl.classList.add("hide");return} fcTok=cand.addr;fcOwed=owed; const mine=account&&fcOwner&&account.toLowerCase()===fcOwner.toLowerCase(); fcEl.innerHTML=`${(+formatUnits(owed*8n/10n,18)).toFixed(4)} ETH in ` +`${mine?"creator fees":"fees for this coin's creator"} ` +`<button type="button" id="fcGo" class="fclink">Collect</button>`; fcEl.title=mine?"Trading fees this coin has earned. Anyone may collect them and the ether comes to you." :"Trading fees this coin has earned. Anyone may collect them; the ether goes to the coin's creator " +"either way, and the token side is burned, which raises the floor for holders."; fcEl.classList.remove("hide"); fcGo.onclick=fcCollect; }catch{if(my===fcSeq)fcEl.classList.add("hide")} } async function fcCollect(){ if(!fcTok)return; const b=fcGo;if(b.disabled)return; try{ if(!account)return connect(); b.disabled=true;b.textContent="Collecting\u2026"; const req={from:account,to:PLAUNCH,value:"0x0",data:"0x"+SEL_COLLECT+encAddr(fcTok)}; await rpc(C,[req,L]); await waitTx(await sendTx([req]));sGot(); stat.textContent="Fees sent to the creator."; for(const k of Object.keys(fcFloorPx))delete fcFloorPx[k]; fcSync(); }catch(e){err(e);b.disabled=false;b.textContent="Collect"} } const SEL_DAOOF="98fabd3a",SEL_LOOTOF="9b7b2ab0",SEL_SHARESOF="03314efa", SEL_TSUP="18160ddd",SEL_RAGEQUIT="29f64d1a"; const SEL_SALES="c6b9f06a",SEL_REMAINING="b399b0bc",SEL_BUY="cce7ec13"; const SEL_TAPS="6144452a",SEL_TAPCLAIM="1e83409a",SEL_DAOALLOW="dd62ed3e"; const SEL_RQABLE="14a6d7de"; const cbSeen={}; let cbDao="",cbAmt=0n,cbCost=0n,cbBuy=false,cbDrawn=0,cbSold=0n,cbReady=0n,cbSeq=0; let cbDue=0n,cbShares="",cbTok=""; const cbDisarm=()=>{cbSeq++;cbDao="";cbAmt=0n;cbReady=0n;cbDue=0n;cbShares="";cbTok=""; if(typeof cbEl!=="undefined"&&cbEl)cbEl.classList.add("hide")}; const cbWord=h=>{const b=strip0x(h||"");return b.length>=64?"0x"+b.slice(24,64):""}; async function cbLook(tok){ const key=tok.toLowerCase(); if(cbSeen[key])return cbSeen[key]; let out={dao:"",shares:""}; try{ const dao=cbWord(await rpc(C,[{to:tok,data:"0x"+SEL_DAOOF},L])); if(/^0x[0-9a-f]{40}$/i.test(dao)&&!/^0x0{40}$/i.test(dao)){ const [lt,sh]=await mc3Deep([{to:dao,data:"0x"+SEL_LOOTOF},{to:dao,data:"0x"+SEL_SHARESOF}]); if(lt==null||sh==null)return out; if(cbWord(lt).toLowerCase()===key){out={dao,shares:cbWord(sh)}; const ix=TOKENS.findIndex(t=>t.addr&&t.addr.toLowerCase()===key); if(ix>=0&&!TOKENS[ix].cause){TOKENS[ix].cause=1;rebuild()}} } }catch{ return out; } return cbSeen[key]=out; } async function cbSync(){ const my=++cbSeq; cbDao="";cbAmt=0n;cbBuy=false; const f=TOKENS[fromSel.value],t=TOKENS[toSel.value]; if(lnMode||lqMode||wnMode||pvMode||tab!=="swap"||!f||!t){cbEl.classList.add("hide");return} const real=x=>x&&x.addr&&x.addr!==ZERO&&x.std!=="nft"; const cbEth=f.addr===ZERO; let hit=null,tok=null,buy=false; if(real(t)){const h=await cbLook(t.addr);if(my!==cbSeq)return; if(h.dao){hit=h;tok=t;buy=true}} if(!hit&&real(f)){const h=await cbLook(f.addr);if(my!==cbSeq)return; if(h.dao){hit=h,tok=f}} if(!hit||!hit.shares||!tok){cbEl.classList.add("hide");return} let eth=0n,sup=0n,price=0n,dl=0n,left=0n,cap=0n; let rqOff=false; const now2=BigInt(Math.floor(Date.now()/1000)); cbReady=0n; try{ const [b,r]=await Promise.all([ rpc("eth_getBalance",[hit.dao,L]), mc3Deep([{to:hit.shares,data:"0x"+SEL_TSUP},{to:tok.addr,data:"0x"+SEL_TSUP}, {to:COFF,data:"0x"+SEL_SALES+encAddr(hit.dao)}, {to:COFF,data:"0x"+SEL_REMAINING+encAddr(hit.dao)}, {to:CTAP,data:"0x"+SEL_TAPS+encAddr(hit.dao)}, {to:hit.dao,data:"0x"+SEL_DAOALLOW+encAddr(ZERO)+encAddr(CTAP)}, {to:hit.dao,data:"0x"+SEL_RQABLE}])]); eth=BigInt(b||"0x0"); if(my!==cbSeq)return; if(r[0]==null||r[1]==null||r[2]==null||r[3]==null){cbEl.classList.add("hide");return} const shr=BigInt(r[0]),lot=BigInt(r[1]); sup=shr+lot; const sale=strip0x(r[2]||""); if(sale.length>=320 &&("0x"+sale.slice(24,64)).toLowerCase()===CLOOT &&/^0+$/.test(sale.slice(64,128))){ dl=BigInt("0x"+sale.slice(128,192));price=BigInt("0x"+sale.slice(192,256)); cap=BigInt("0x"+sale.slice(256,320))} cbSold=lot; left=BigInt(r[3]||"0x0"); const tp=strip0x(r[4]||""); if(tp.length>=256){ const rate=BigInt("0x"+tp.slice(128,192)),lastC=BigInt("0x"+tp.slice(192,256)); const allow=BigInt(r[5]||"0x0"); if(rate>0n&&now2>lastC){ let av=(now2-lastC)*rate; if(av>allow)av=allow; if(av>eth)av=eth; cbReady=av/rate*rate; }} rqOff=r[6]!=null&&BigInt(r[6])===0n; cbDrawn=lot>0n&&price>0n?(()=>{const raised=(lot*price+CONE-1n)/CONE; return raised>eth?Number((raised-eth)*100n/raised):0})():0; }catch{} if(my!==cbSeq)return; if(sup<=0n){cbEl.classList.add("hide");return} const now=BigInt(Math.floor(Date.now()/1000)); const open=price>0n&&(dl===0n||dl>now); const days=dl>now?Number((dl-now+86399n)/86400n):0; cbDao=hit.dao;cbBuy=buy; const pct=cap>0n?Number(cbSold*1000n/cap)/10:0; const goal=cap>0n&&price>0n?cap*price/CONE:0n; const head=goal>0n ?`<span>${trimAmt(cbSold*price/CONE,18)} of ${trimAmt(goal,18)} ETH backed</span>` +`<span>${open?days+" days left":"closed"}${cbDrawn>0?" · "+cbDrawn+"% released":""}</span>` :""; const bar=goal>0n ?`<div class="lqbar fill" style="--at:${Math.max(0,Math.min(100,pct)).toFixed(1)}%" aria-hidden="true"><b></b></div>` :""; const rel=cbReady>0n ?`<span class="cbrow"><span>${trimAmt(cbReady,18)} ETH vested and unreleased</span>` +`<button type="button" id="cbRel" class="fclink">Release</button></span>` :""; const top=(head?`<span class="cbrow">${head}</span>${bar}`:"")+rel; const act=(text,label)=>`<span class="cbact"><span>${text}</span>` +`<button type="button" id="cbGo" class="lqbtn">${label}</button></span>`; const msg=t=>`<span class="cbmsg">${t}</span>`; const paint=(html,tip)=>{ cbEl.innerHTML=top+html; cbEl.title=tip; cbEl.classList.remove("hide"); const gi=i=>document.getElementById(i); const g=gi("cbGo"),r=gi("cbRel"); if(g)g.onclick=cbAct; if(r)r.onclick=cbRelease; }; if(buy){ if(!open||left<=0n) return rqOff ?paint(msg(`Backing for ${safeSym(tok.sym)} has closed, and burn-back is switched off \u2014 it cannot be redeemed`), "This DAO has set ragequittable to false, so ragequit reverts and the treasury cannot be claimed against. Only the DAO can switch it back on.") :paint(msg(`Backing for ${safeSym(tok.sym)} has closed — it can still be burned back`),CB_TIP); if(rqOff) return paint(msg(`This cause has switched burn-back off \u2014 backing it cannot be redeemed`), "Backing pays ether for loot whose only value is the right to burn it back. This DAO has turned that right off, so there is no way out of the position."); if(!cbEth) return paint(msg(`Back ${safeSym(tok.sym)} with ether — switch the top token to ETH`),CB_TIP); let pay=0n;try{pay=parseUnits(amt.value.trim()||"0",18)}catch{} if(pay<=0n) return paint(msg(`1 ETH backs ${trimAmt(CONE*CONE/price,tok.dec)} ${safeSym(tok.sym)}`),CB_TIP); let units=pay*CONE/price; if(units>left)units=left; if(units<=0n){cbEl.classList.add("hide");return} cbAmt=units; cbTok=tok.addr; cbCost=(units*price+CONE-1n)/CONE; return paint(act(`Back with ${trimAmt(cbCost,18)} ETH for ` +`${trimAmt(units,tok.dec)} ${safeSym(tok.sym)}`,"Back"),CB_TIP); } if(rqOff) return paint(msg(`Burn-back has been switched off by this cause \u2014 ${safeSym(tok.sym)} cannot be redeemed`), "This DAO has set ragequittable to false, so ragequit reverts and the treasury " +"cannot be claimed against. Only the DAO can switch it back on."); let want=0n;try{want=parseUnits(amt.value.trim()||"0",tok.dec)}catch{} if(want<=0n)want=fromBalance; if(want<=0n){ return paint(msg(`1 ${safeSym(tok.sym)} redeems for ${trimAmt(eth*CONE/sup,18)} ETH`),CB_TIP);} cbAmt=want; const due=eth*want/sup; cbDue=due;cbShares=hit.shares;cbTok=tok.addr; if(due<=0n) return paint(msg(eth<=0n ?`This cause has drawn down its treasury — burning ${safeSym(tok.sym)} would pay nothing` :`Burning ${trimAmt(want,tok.dec)} ${safeSym(tok.sym)} would pay less than a wei`),CB_TIP); return paint(act(`Burn ${trimAmt(want,tok.dec)} ${safeSym(tok.sym)} ` +`for ${trimAmt(due,18)} ETH`,"Burn"), "This token is a claim on a cause's treasury, not a position in a market. " +"Burning it pays your share of the ETHER the cause has not drawn down yet, counted " +"pro rata across every share and unit of loot. Any other token the cause holds is " +"not paid out by this burn. It cannot be undone."); } const CB_TIP="Backing a cause is not a trade and there is no market to leave through. " +"What you get is a claim: burn it back at any time for your share of the ether the cause " +"has not drawn down yet, which is the number this line shows once you hold some."; async function cbRelease(){ if(!cbDao||cbReady<=0n)return; const b=document.getElementById("cbRel");if(!b||b.disabled)return; try{ if(!account)return connect(); b.disabled=true;b.textContent="Releasing…"; const req={from:account,to:CTAP,value:"0x0",data:"0x"+SEL_TAPCLAIM+encAddr(cbDao)}; await rpc(C,[req,L]); await waitTx(await sendTx([req]));sGot(); stat.textContent="Released to the cause."; cbSync(); }catch(e){err(e);b.disabled=false;b.textContent="Release"} }
0x68575b07…67c7·#25,913,379·0xb6a8459b…4e7381
airs","Every order on every board") +`</div><div class="bkf">` +BOOK_TYPES.map(([v,l])=>chip(bookType===v,"bt",v,l)).join("") +`</div></div>`; } let bookShown=0; function syncBook(n,total){ bookShown=n; total=total===undefined?n:total; const on=tab==="book"&&total>0; bkTog.classList.toggle("hide",!on); bkTog.firstChild.nodeValue=total ?`Orderbook (${n<total?`${n} of ${total}`:total}) `:"Orderbook "; bkTog.setAttribute("aria-expanded",on&&bkOpen?"true":"false"); book.classList.toggle("hide",!on||!bkOpen); } bkTog.onclick=()=>{bkOpen=!bkOpen;LS.bk=bkOpen?"1":"0";syncBook(bookShown,bookRows.length)}; book.addEventListener("keydown",e=>{ if(e.key!=="Enter"&&e.key!==" ")return; const row=e.target.closest(".o.ins"); if(!row||e.target!==row)return; e.preventDefault();inspect(row,row.dataset.k); }); book.addEventListener("click",async e=>{ const fc=e.target.closest("[data-bf]"),tc=e.target.closest("[data-bt]"); if(fc){bookPairOnly=fc.dataset.bf==="1";LS.bkp=bookPairOnly?"1":"0";paintBook();return} if(tc){bookType=tc.dataset.bt;LS.bkt=bookType;paintBook();return} const b=e.target.closest("button[data-i]"); if(!b){ const row=e.target.closest(".o.ins"); if(row&&!e.target.closest("a"))inspect(row,row.dataset.k); return; } b.disabled=true; ++busy; try{ await checkWallet(); const id=BigInt(b.dataset.i),to=b.dataset.b,dl=BigInt(Math.floor(Date.now()/1e3)+1800); const row=bookRows.find(x=>String(x.id)===b.dataset.i&&x.board.toLowerCase()===to.toLowerCase()); if(!row)throw Error("order changed — refresh and retry"); if(b.dataset.x==="c"){ const native=!row.nA&&row.tA.toLowerCase()===WETH.toLowerCase()&&wantsUnwrap(); const floorish=b.dataset.d==="1"||b.dataset.f==="1"; const sel=floorish ?(native?SEL_DUTCH_CANCEL_UNWRAP:SEL_DUTCH_CANCEL) :(native&&b.dataset.v==="1"?SEL_CANCEL_UNWRAP:SEL_CANCELORD); const data="0x"+sel+encUint(id); const lapsed=row.exp&&BigInt(Math.floor(Date.now()/1e3))>row.exp; stat.textContent=`${lapsed?"Reclaiming":"Cancelling"} — the ${b.dataset.f==="1"?"unspent escrow":"unfilled remainder"} returns as ${native&&(floorish||b.dataset.v==="1")?"ETH":"its escrow token"}`; const tx={from:account,to,data,value:"0x0"}; await rpc(C,[tx,L]); await settle(await sendTx([tx])); loadBook();refreshBalance();return; } if(b.dataset.x==="w"){ const refund=row.tA; const native=refund.toLowerCase()===WETH.toLowerCase()&&wantsUnwrap(); const dec=row.dutch?(row.dA??18):(row.dB??18),sym=row.dutch?(row.sA||"tokens"):(row.sB||"tokens"); const held=row.dutch?row.aA:row.aB; const answer=prompt(`Shrink this ${row.dutch?"lot":"bid"}.\n\nIt is escrowed for ${trimAmt(held,dec)} ${sym}.\n\nEnter the amount to stop ${row.dutch?"selling":"bidding for"} — the escrow behind it returns${native?" as ETH":""}. The rest stays live:`,trimAmt(held,dec)); if(answer===null){b.disabled=false;return} let asked; try{asked=parseUnits(String(answer).trim(),dec)}catch{throw Error("that is not an amount")} if(!asked)throw Error("enter an amount above zero"); if(asked>=held)throw Error(`that is the whole ${row.dutch?"lot":"bid"} — use Cancel instead`); const tx={from:account,to,data:"0x"+SEL_WITHDRAW+encUint(id)+encUint(asked)+encBool(native),value:"0x0"}; stat.textContent=`Shrinking — the escrow behind ${trimAmt(asked,dec)} ${sym} returns${native?" as ETH":""}...`; await rpc(C,[tx,L]); await settle(await sendTx([tx])); loadBook();refreshBalance();return; } if(b.dataset.x==="r"){ const sym=row.sB||"tokens",dec=row.dB??18; const answer=prompt(`Reprice this order without losing its place on the book.\n\nIt sells ${trimAmt(row.aA,row.dA??18)} ${row.sA||"tokens"} for ${trimAmt(row.aB,dec)} ${sym}.\n\nEnter the NEW total ${sym} you want:`,trimAmt(row.aB,dec)); if(answer===null){b.disabled=false;return} let asked; try{asked=parseUnits(String(answer).trim(),dec)}catch{throw Error("that is not an amount")} if(!asked)throw Error("enter an amount above zero"); if(asked===row.aB)throw Error("that is the same price"); const tx={from:account,to,data:"0x"+SEL_REPLACE+encUint(id)+encUint(row.aA)+encUint(asked)+encUint(row.exp||0n),value:"0x0"}; stat.textContent="Repricing..."; await rpc(C,[tx,L]); await settle(await sendTx([tx])); loadBook();refreshBalance();return; } const r=row; let payB=r.aB,getA=r.aA; if(r.pf&&!r.nA&&!r.nB&&!r.dutch&&!r.floor&&r.v2&&r.maker.toLowerCase()!==account.toLowerCase()){ const dB=r.dB??18,dA=r.dA??18,sB=r.sB||"tokens",sA=r.sA||"tokens"; let have=0n; try{ have=r.tB===ZERO||r.tB.toLowerCase()===WETH.toLowerCase() ?BigInt(await rpc("eth_getBalance",[account,L])) +(r.tB===ZERO?0n:BigInt(await rpc(C,[{to:WETH,data:"0x"+SEL_BALANCEOF+encAddr(account)},L]))) :BigInt(await rpc(C,[{to:r.tB,data:"0x"+SEL_BALANCEOF+encAddr(account)},L])); }catch{} const suggest=have&&have<r.aB?have:r.aB; const answer=prompt( `Fill how much of this order?\n\nIt sells ${trimAmt(r.aA,dA)} ${sA} for ${trimAmt(r.aB,dB)} ${sB} in total, ` +`and can be filled in part.\n\nEnter the ${sB} you want to spend:`, trimAmt(suggest,dB)); if(answer===null){b.disabled=false;return} let asked; try{asked=parseUnits(String(answer).trim(),dB)}catch{throw Error("that is not an amount")} if(!asked)throw Error("enter an amount above zero"); if(asked>r.aB)throw Error(`that is more than the order needs — it costs ${trimAmt(r.aB,dB)} ${sB} in full`); if(asked<r.aB){ const q=await rpc(C,[{to:r.board,data:"0x"+SEL_QUOTEFILL+encUint(id)+encUint(asked)},L]); const h=strip0x(q); if(h.length<128)throw Error("the board would not quote that amount"); getA=BigInt("0x"+h.slice(0,64)); payB=BigInt("0x"+h.slice(64,128)); if(!getA)throw Error("that amount buys nothing — try a larger one"); stat.textContent=`Filling ${trimAmt(payB,dB)} ${sB} of ${trimAmt(r.aB,dB)} for ${trimAmt(getA,dA)} ${sA}...`; } } const nativeOut=!r.nA&&r.tA.toLowerCase()===WETH.toLowerCase()&&wantsUnwrap(); const wethLeg=!r.nB&&r.tB.toLowerCase()===WETH.toLowerCase()&&payB>0n; const plan=wethLeg?await planWethPayment(payB):null; if(plan&&plan.rail==="short") throw Error(`Not enough ether to fill: it costs ${trimAmt(payB,18)} and you hold ` +`${trimAmt(plan.w,18)} WETH plus ${trimAmt(plan.e,18)} ETH. ` +(r.pf ?`This button takes the whole order — to buy part of it, use the Swap tab, which fills this order alongside the pools.` :`This order is all-or-nothing, so it cannot be filled in smaller pieces.`)); if(plan&&plan.wrap>0n){ stat.textContent=`Wrapping ${trimAmt(plan.wrap,18)} ETH to cover the rest...`; const wtx={from:account,to:WETH,data:"0xd0e30db0",value:toHex(plan.wrap)}; await rpc(C,[wtx,L]);await checkWallet();await settle(await sendTx([wtx])); } const nativeIn=plan?.rail==="eth"; const routeIn=nativeIn?ZERO:r.tB; const routeOut=nativeOut?ZERO:r.tA; const canonicalNative=routeIn.toLowerCase()===routeOut.toLowerCase() ||(routeOut===ZERO&&routeIn.toLowerCase()===WETH.toLowerCase()); if(!r.nB)await assertFunded(routeIn,payB, known(routeIn)||(routeIn===ZERO?{sym:"ETH",dec:18}:{sym:r.sB||"TOKEN",dec:r.dB??18})); if(!r.nA&&!r.nB&&r.cp===ZERO&&!canonicalNative){ if((await rpc("eth_getCode",[SWAPBOL,L])).length<5)throw Error("orderbook executor not deployed yet"); await preflightLeg(r,routeIn,routeOut,payB); const pay=payB,fillPlan=encFillPlan([ {id:r.id,board:r.board,pay,get:getA,part:false} ],routeIn,routeOut,account,account,dl); const planCall=encSnwap(pay?routeIn:ZERO,pay,account,routeOut,getA,SWAPBOL,fillPlan); const checkpoint=routeIn===ZERO?null:encCheckpoint(SWAPBOL,routeIn,account); const direct=checkpoint?encCalls([checkpoint,planCall]):planCall; const funded=routeIn===ZERO||!pay?direct:encCalls([ checkpoint, encSweep(routeIn,pay,SWAPBOL), encSnwap(ZERO,0n,account,routeOut,getA,SWAPBOL,fillPlan) ]); const meta=known(routeIn)||{addr:routeIn,sym:r.sB||"TOKEN",dec:r.dB??18}; await sendRouterFunded(direct,funded,meta,pay,routeIn===ZERO?pay:0n,"Filling order..."); loadBook();refreshBalance();return; } const data=r.dutch ?"0x"+SEL_DUTCH_FILL+encUint(id)+encUint(r.nA?0n:getA)+encAddr(account)+encUint(payB) :r.v2 ?"0x"+(nativeIn?SEL_FILL2_ETH:nativeOut?SEL_FILL2_UNWRAP:SEL_FILL2) +encUint(id)+encUint(dl)+(nativeIn?"":encUint(payB))+encUint(r.nA?0n:getA)+encAddr(ZERO) :"0x"+SEL_FILL1+encUint(id)+encUint(dl); const payNative=(nativeIn&&!r.dutch&&r.v2)||(r.dutch&&r.tB===ZERO); const wrap=nativeIn&&!payNative&&payB ?{to:WETH,data:"0xd0e30db0",value:toHex(payB)}:null; const tx={from:account,to,data,value:payNative?toHex(payB):"0x0"}; const approvals=[]; if(r.nB){ let ap=ZERO; try{ap="0x"+strip0x(await rpc(C,[{to:r.tB,data:"0x081812fc"+encUint(payB)},L])).slice(-40)}catch{} if(ap.toLowerCase()!==to.toLowerCase()){ approvals.push({to:r.tB,data:"0x"+SEL_APPROVE+encAddr(to)+encUint(payB),value:"0x0"}); } }else if(r.tB!==ZERO&&payB&&!(nativeIn&&!r.dutch&&r.v2)){ const al=BigInt(await rpc(C,[{to:r.tB,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(to)},L])); if(al<payB){ if(al)approvals.push({to:r.tB,data:"0x"+SEL_APPROVE+encAddr(to)+encUint(0n),value:"0x0"}); approvals.push({to:r.tB,data:"0x"+SEL_APPROVE+encAddr(to)+encUint(payB),value:"0x0"}); } } const revoke=r.dutch&&!r.nB&&r.tB!==ZERO ?{to:r.tB,data:"0x"+SEL_APPROVE+encAddr(to)+encUint(0n),value:"0x0"}:null; if((wrap||approvals.length||revoke)&&await canBatch(account)){ stat.textContent="Preparing and filling atomically..."; await settle(await sendBatch(account,[ ...(wrap?[wrap]:[]),...approvals,{to,data,value:tx.value},...(revoke?[revoke]:[]) ])); loadBook();refreshBalance();return; } if(wrap){ stat.textContent="Wrapping ETH..."; await waitTx(await sendTx([{from:account,...wrap}])); } for(let i=0;i<approvals.length;i++){ stat.textContent=`Approve${r.nB?" NFT":""}${approvals.length>1?` (${i+1}/${approvals.length})`:""}...`; await waitTx(await sendTx([{from:account,...approvals[i]}])); } await rpc(C,[tx,L]); await settle(await sendTx([tx])); if(revoke)try{ stat.textContent="Filled · revoking quote allowance..."; await waitTx(await sendTx([{from:account,...revoke}])); stat.textContent="Done"; }catch{stat.textContent="Filled · quote allowance remains; revoke it in your wallet"} loadBook();refreshBalance(); }catch(x){err(x);b.disabled=false} finally{--busy} }); const SLOW="0x000000000000888741B254d37e1b27128AfEAaBC"; const SEL_DEPOSITTO="94eeaec9",SEL_CLAIM="379607f5",SEL_REVERSE="97d15425",SEL_WITHDRAWFROM="d4fdc309"; const SEL_UNLOCK="6198e339",SEL_CLAWBACK="fcc36bc9",SEL_GUARDIAN="0633b14a"; const GRACE=2592000n; const SLOW_GATE="0xb8B546b93a82f4Aa6f0345142dF5679B659ef3D4"; const SEL_DEPOSITTIP="75f92e42",SEL_TIPS="a5c68c59",SEL_REFUNDTIP="d27e1e72"; const TIP_GAS=180000n; let sendTipWei=0n,tipSeq=0; async function syncTip(){ const my=++tipSeq; const on=tab==="send"&&+dly.value>0; tipL.classList.toggle("hide",!on); if(!on||!tipCk.checked){sendTipWei=0n;tipNote.textContent="";return} tipNote.textContent="estimating…"; let wei=0n; try{wei=BigInt(await rpc("eth_gasPrice",[]))*TIP_GAS}catch{} if(my!==tipSeq)return; sendTipWei=wei>0n?wei:10n**15n/2n; tipNote.textContent="tip ≈ "+trimAmt(sendTipWei,18)+" ETH"; render(); } const SEL_OUT="d40d4bc6",SEL_IN="e3993ee7",SEL_PENDING="6577b86a"; const idTok=i=>"0x"+(i&((1n<<160n)-1n)).toString(16).padStart(40,"0"); const idDelay=i=>i>>160n; const w=(h,n)=>BigInt("0x"+(strip0x(h).slice(n*64,n*64+64)||"0")); const metaCache=new Map(); async function tokMeta(a){ a=a.toLowerCase(); if(a===ZERO)return{sym:"ETH",dec:18}; const k=TOKENS.find(t=>t.addr.toLowerCase()===a); if(k)return k; if(metaCache.has(a))return metaCache.get(a); let m={sym:a.slice(0,6)+"…",dec:18},ok=0; try{const[sr,dr]=await Promise.all([rpc(C,[{to:a,data:"0x95d89b41"},L]),rpc(C,[{to:a,data:"0x313ce567"},L])]); const d=Number(BigInt(dr)); if(Number.isInteger(d)&&d>=0&&d<=36){m={sym:decodeString(sr).trim().replace(/[<>&"'`]/g,"").slice(0,10)||m.sym,dec:d};ok=1} }catch{} if(ok){metaCache.set(a,m);capMap(metaCache,META_MAX)} return m; } async function warmMeta(addrs){ const want=[...new Set(addrs.map(a=>(a||"").toLowerCase()))] .filter(a=>/^0x[0-9a-f]{40}$/.test(a)&&a!==ZERO&&!known(a)&&!metaCache.has(a)); if(!want.length)return; const res=await mc3Deep(want.flatMap(a=>[{to:a,data:"0x95d89b41"},{to:a,data:"0x313ce567"}])).catch(()=>[]); want.forEach((a,i)=>{ const sr=res[i*2],dr=res[i*2+1]; if(!sr||!dr)return; let d;try{d=Number(BigInt(dr))}catch{return} if(!Number.isInteger(d)||d<0||d>36)return; let sym="";try{sym=decodeString(sr).trim().replace(/[<>&"'`]/g,"").slice(0,10)}catch{} metaCache.set(a,{sym:sym||a.slice(0,6)+"…",dec:d}); }); capMap(metaCache,META_MAX); } const rel=s=>{const a=Math.abs(s),u=[[86400,"d"],[3600,"h"],[60,"m"]]; for(const[n,l]of u)if(a>=n)return Math.floor(a/n)+l;return a+"s"}; let posSeq=0,posShown=25,slowGuard=false; const POSPAGE=25; async function loadPos(){ if(!account){pos.innerHTML="";tabSend.textContent="Send";slowGuard=false;return} const my=++posSeq; let outIds=[],inIds=[]; try{ const[oh,ih,gd]=await Promise.all([ rpc(C,[{to:SLOW,data:"0x"+SEL_OUT+encAddr(account)},L]), rpc(C,[{to:SLOW,data:"0x"+SEL_IN+encAddr(account)},L]), rpc(C,[{to:SLOW,data:"0x"+SEL_GUARDIAN+encAddr(account)},L]).catch(()=>"0x")]); outIds=decUintArr(oh);inIds=decUintArr(ih); if(my===posSeq)slowGuard=w(gd,0)!==0n; }catch{return} if(my!==posSeq)return; const dirs=new Map(); for(const[t,d]of[...outIds.map(i=>[i,"out"]),...inIds.map(i=>[i,"in"])]){ const k=t.toString();dirs.set(k,(dirs.get(k)||"")+d)} const allKeys=[...dirs.keys()].sort((a,b)=>a.length-b.length||(a<b?1:-1)); const keys=allKeys.slice(0,posShown); const more=allKeys.length-keys.length; const ps=[]; for(let i=0;i<keys.length;i+=10){ ps.push(...await Promise.all(keys.slice(i,i+10).map(k=> rpc(C,[{to:SLOW,data:"0x"+SEL_PENDING+encUint(BigInt(k))},L]).catch(()=>0)))); if(my!==posSeq)return; } if(my!==posSeq)return; const rows=[],settled=[]; keys.forEach((k,i)=>{const p=ps[i];if(!p)return; const ts=w(p,0); if(ts===0n){if(dirs.get(k).includes("out"))settled.push(k);return} rows.push({tid:BigInt(k),dir:dirs.get(k),ts,id:w(p,3),amount:w(p,4)})}); const stale=[]; for(let i=0;i<settled.length;i+=10){ const ts=await Promise.all(settled.slice(i,i+10).map(k=> rpc(C,[{to:SLOW_GATE,data:"0x"+SEL_TIPS+encUint(BigInt(k))},L]).catch(()=>0))); if(my!==posSeq)return; ts.forEach((t,j)=>{if(t&&w(t,0)>0n&&idTok(w(t,1))===account.toLowerCase()) stale.push({tid:settled[i+j],amount:w(t,0)})}); } const now=BigInt(Math.floor(Date.now()/1e3)); const ready=rows.filter(r=>r.dir.includes("in")&&now>=r.ts+idDelay(r.id)).length; tabSend.textContent=ready?`Send (${ready}${more?"+":""})`:"Send"; if(tab!=="send"||!(rows.length||stale.length)){pos.innerHTML="";return} rows.sort((a,b)=>Number((a.ts+idDelay(a.id))-(b.ts+idDelay(b.id)))); const metas=await Promise.all(rows.map(r=>tokMeta(idTok(r.id)))); if(my!==posSeq)return; let html=`<div class="ph">Time-locked</div>`; rows.forEach((r,n)=>{ const m=metas[n],exp=r.ts+idDelay(r.id),left=Number(exp-now); const ripe=now>=exp; const amt=`${trimAmt(r.amount,m.dec)} ${m.sym}`; const io=r.dir.includes("in"),oo=r.dir.includes("out"); const held=`data-t="${r.tid}" data-i="${r.id}" data-m="${r.amount}"`; const cb=oo&&ripe&&now>=exp+GRACE; let act,note; if(cb)note=slowGuard?"Unclaimed past grace · unlock credits SLOW; withdraw needs guardian approval":"Unclaimed past grace · recoverable"; else if(ripe)note=io?(slowGuard?"Matured · unlock credits SLOW; withdraw needs guardian approval":"Ready to claim"):"Matured · recipient can claim, you in "+rel(Number(exp+GRACE-now)); else note=(io&&!oo?"Arrives in ":"Reversible · sends in ")+rel(left); if(io&&ripe)act=`<button data-a="c" data-t="${r.tid}">${slowGuard?"Unlock":"Claim"}</button>`; else if(oo&&!ripe)act=`<button data-a="r" ${held}>Reverse</button>`; else if(cb)act=`<button data-a="k" ${held}>Clawback</button>`; else act=`<button disabled>${io?"In":"Out"}</button>`; html+=`<div class="p"><span><b>${oo&&io?"↺":oo?"→":"←"} ${amt}</b><i>${note}</i></span>${act}</div>`; }); stale.forEach(s=>{html+=`<div class="p"><span><b>↩ ${trimAmt(s.amount,18)} ETH</b>` +`<i>Keeper tip on a settled transfer · unspent</i></span>` +`<button data-a="t" data-t="${s.tid}">Reclaim tip</button></div>`}); if(more)html+=`<div class="p" style="justify-content:center"><button data-a="m">Show ${Math.min(more,POSPAGE)} more of ${more}</button></div>`; pos.innerHTML=html; } pos.addEventListener("click",async e=>{ const m=e.target.closest('button[data-a="m"]'); if(m){posShown+=POSPAGE;m.disabled=true;Promise.resolve(loadPos()).finally(()=>{if(m.isConnected)m.disabled=false});return} const b=e.target.closest("button[data-t]"); if(!b)return; b.disabled=true; ++busy; try{ await checkWallet(); const a=b.dataset.a; if(a==="t"){if(!await refundTipBestEffort(b.dataset.t,"Tip"))throw Error("the tip is no longer refundable"); refreshBalance();loadPos();return} const back=a==="r"||a==="k"; let data="0x"+(a==="r"?SEL_REVERSE:a==="k"?SEL_CLAWBACK:slowGuard?SEL_UNLOCK:SEL_CLAIM)+encUint(BigInt(b.dataset.t)); if(back&&!slowGuard)data=encCalls([data,"0x"+SEL_WITHDRAWFROM+encAddr(account)+encAddr(account) +encUint(BigInt(b.dataset.i))+encUint(BigInt(b.dataset.m))]); const txReq={from:account,to:SLOW,data,value:"0x0"}; await rpc(C,[txReq,L]); await settle(await sendTx([txReq])); if(back)await refundTipBestEffort(b.dataset.t,a==="r"?"Reversed":"Recovered"); refreshBalance();loadPos(); }catch(x){err(x);b.disabled=false} finally{--busy} }); async function refundTipBestEffort(tid,verb="Reversed"){ try{ const t=await rpc(C,[{to:SLOW_GATE,data:"0x"+SEL_TIPS+encUint(BigInt(tid))},L]); if(w(t,0)===0n)return false; const req={from:account,to:SLOW_GATE,data:"0x"+SEL_REFUNDTIP+encUint(BigInt(tid)),value:"0x0"}; await rpc(C,[req,L]); stat.textContent=verb+" · reclaiming keeper tip..."; await settle(await sendTx([req])); stat.textContent=verb+" · keeper tip refunded"; return true; }catch{return false} } const rcErr=v=>isAddr(v) ?(/^0x0{40}$/i.test(v)?"Recipient cannot be the zero address" :!ckAddr(v)?"That address fails its checksum — a character is wrong. Paste it again.":"") :!/\.(g?wei|eth)$/i.test(v)?"Recipient must be an address or a .wei / .gwei / .eth name" :!nameOk(v.toLowerCase())?"That name uses characters this page cannot resolve.":""; const rcvOf=async(v,fresh)=>{ if(isAddr(v))return /^0x0{40}$/i.test(v)||!ckAddr(v)?"":v; if(!/\.(g?wei|eth)$/i.test(v))return ""; const n=v.toLowerCase(); if(!nameOk(n))return ""; let a=""; try{a=fresh?await nameFwd(n):await nameFwdCached(n)} catch(e){if(e&&e.offchain)throw e} return !isAddr(a)||/^0x0{40}$/i.test(a)?"":a; }; async function updateSend(){ const my=++sSeq; sendReady=null; const f=TOKENS[fromSel.value]; const v=rc.value.trim(); let amount=0n,amtErr=""; const q=amt.value.trim(); try{amount=parseUnits(q,f.dec)} catch(e){if(q&&!/\.$/.test(q))amtErr=/^unclear comma/.test(e.message)?e.message:/decimals/.test(e.message)?`${f.sym} has ${f.dec} decimals`:"Invalid amount"} if(!v){rc.classList.remove("bad");rcvEl.textContent="";stat.textContent=amtErr;render();return} const bad=rcErr(v); if(bad){rc.classList.add("bad");rcvEl.textContent=""; stat.textContent=bad;render();return} if(!/^0x/.test(v))rcvEl.textContent="resolving..."; let to="",toErr=""; try{to=await rcvOf(v)}catch(e){toErr=e&&e.offchain?e.message:""} if(my!==sSeq)return; if(!to){rc.classList.add("bad");rcvEl.textContent=""; stat.textContent=toErr||"Name not registered";render();return} rc.classList.remove("bad"); rcvEl.textContent=to; stat.textContent=amtErr; if(amount>0n)sendReady={to,amount,token:f,label:/^0x/.test(v)?to.slice(0,6)+"\u2026"+to.slice(-4):v}; render(); } const syncNftUI=()=>{ const anyNft=tab==="book"&&(isNft(fromSel.value)||isNft(toSel.value)); nftIdL.classList.toggle("hide",!anyNft); if(!anyNft)nftId.value=""; nftId.placeholder=isWns(TOKENS[fromSel.value]?.addr)||isWns(TOKENS[toSel.value]?.addr) ?(isNft(fromSel.value)?"Which .wei name you are selling":"Any, or a .wei name") :isNft(fromSel.value)?"Which one you are selling":"Any from collection"; syncOrderType(); showFloor(); }; let syncStops=()=>{}; function chainUi(){ lq.classList.toggle("hide",tab!=="swap"||PROUTE===ZERO); ln.classList.toggle("hide",tab!=="swap"||PLAUNCH===ZERO); wn.classList.toggle("hide",tab!=="swap"||CHAIN_ID!==1); pv.classList.toggle("hide",tab!=="swap"||CHAIN_ID!==1); tabBook.classList.toggle("hide",SB2===ZERO); if(lqMode&&PROUTE===ZERO)lqSet(false); if(lnMode&&PLAUNCH===ZERO)lnSet(false); if(wnMode&&CHAIN_ID!==1)wnSet(false); if(pvMode&&CHAIN_ID!==1)pvSet(false); if(tab==="book"&&SB2===ZERO)setTab("swap"); } const setTab=t=>{ if(t==="book"&&SB2===ZERO)t="swap"; if(tab==="send")sendD=dly.value;else if(tab==="book")bookD=dly.value; if(t!=="swap"&&lqMode)lqSet(false); if(t!=="swap"&&lnMode)lnSet(false); if(t!=="swap"&&wnMode)wnSet(false); if(t!=="swap"&&pvMode)pvSet(false); lq.classList.toggle("hide",t!=="swap"||PROUTE===ZERO); ln.classList.toggle("hide",t!=="swap"||PLAUNCH===ZERO); wn.classList.toggle("hide",t!=="swap"||CHAIN_ID!==1); pv.classList.toggle("hide",t!=="swap"||CHAIN_ID!==1); tabBook.classList.toggle("hide",SB2===ZERO); if(t!==tab){rc.value="";rc.classList.remove("bad");rcvEl.textContent=""} tab=t;last=null;sendReady=null;stat.textContent="";posShown=POSPAGE; const send=t==="send",bk=t==="book"; tabSwap.classList.toggle("on",t==="swap"); tabSend.classList.toggle("on",send); tabBook.classList.toggle("on",bk); for(const[el,on]of[[tabSwap,t==="swap"],[tabSend,send],[tabBook,bk]])el.setAttribute("aria-selected",on?"true":"false"); syncStops(); rcvPanel.classList.toggle("hide",send);flip.classList.toggle("hide",send||lqMode); if(!bk)for(const[sel,other]of[[fromSel,toSel],[toSel,fromSel]]){ if(!isNft(sel.value))continue; const alt=TOKENS.findIndex((t,i)=>t.std!=="nft"&&String(i)!==other.value); if(alt>=0)sel.value=String(alt); } if(!send&&fromSel.value===toSel.value){ const alt=TOKENS.findIndex((t,i)=>String(i)!==fromSel.value&&(bk||t.std!=="nft")); if(alt>=0)toSel.value=String(alt); } syncDisabled(); syncNftUI(); slipL.classList.toggle("hide",t!=="swap"); dlyL.classList.toggle("hide",t==="swap"||(send&&!chainCfg().slow)); kindL.classList.toggle("hide",!bk); ethModeL.classList.toggle("hide",!bk); dlyL.firstChild.nodeValue=bk?"Expires":"Time lock"; dly.options[0].textContent=bk?"Never":"Instant"; if(send)dly.value=sendD;else if(bk)dly.value=bookD; payL.textContent=send?"Amount":bk?"You sell":lqMode?"Liquidity for":"You pay"; rcvHdr.textContent=bk&&kind.value==="dutch"?"Start ask total":bk?"You want":lqMode?"and":"You receive"; rc.placeholder=send?"Recipient \u2014 0x or .eth/.(g)wei" :bk?"Private to (optional) \u2014 0x or .eth/.(g)wei" :lqMode?"LP shares to (optional) \u2014 0x or .eth/.(g)wei":"Recipient (optional) \u2014 0x or .eth/.(g)wei"; syncOrderType();syncChart();syncTip(); loadPos();loadBook(); outAmt.value="";mode="in"; if(wnMode)wnPaint(); if(pvMode)pvPaint(); update(); }; dly.onchange=()=>{if(tab==="book")bookD=dly.value;else sendD=dly.value;syncTip();render()}; tipCk.onchange=()=>{syncTip();render()}; fill.onchange=render; dRest.onchange=render; const syncOrderType=()=>{ const dutch=tab==="book"&&kind.value==="dutch"; const bid=tab==="book"&&kind.value==="floor"; const sched=dutch||bid; const nftLeg=tab==="book"&&(isNft(fromSel.value)||isNft(toSel.value)); dRestL.classList.toggle("hide",!dutch); const collectionBid=nftLeg&&isNft(toSel.value)&&!nftId.value.trim(); floorL.classList.toggle("hide",!sched); floorL.firstChild.nodeValue=bid?"Opening bid":"Floor total"; if(tab==="book"){ dlyL.firstChild.nodeValue=dutch?"Decays over":bid||collectionBid?"Bid window":"Expires"; dly.options[0].textContent=sched||collectionBid?"—":"Never"; } fillL.classList.toggle("hide",tab!=="book"||sched||nftLeg); rc.classList.toggle("hide",sched); rcvEl.classList.toggle("hide",sched); if((sched||collectionBid)&&+dly.value===0){dly.value="86400";if(tab==="book")bookD=dly.value} if(tab==="book"){ payL.textContent=bid?"You escrow (max)":"You sell"; rcvHdr.textContent=bid?"You want to buy":dutch?"Start ask total":"You want"; } }; kind.onchange=()=>{syncOrderType();seedFloor();render()}; let floorTouched=false; const dutchEnds=()=>{ const rest=BigInt(dRest.value||0); if(!rest)return 0n; return BigInt(Math.floor(Date.now()/1e3))+BigInt(dly.value||0)+rest;
0x68575b07…67c7·#25,913,378·0x627466e2…80a109
p+33+i)*2),16); return new TextDecoder().decode(a)}; const rows=[]; const dec=v=>{const d=Number(v);return Number.isInteger(d)&&d>=0&&d<=36?d:null}; for(let i=0;i<n;i++){ try{ const off=Number(word(head+i*32)),b=head+off; if(!Number.isSafeInteger(off)||b+16*32>bytes)throw Error("bad lens row"); rows.push({id:word(b),maker:addr(b+32),pf:word(b+64)===1n,exp:word(b+96), nA:word(b+128)===1n,nB:word(b+160)===1n,cp:addr(b+192), tA:addr(b+224),aA:word(b+256),sA:safeSym(text(b,9)),dA:dec(word(b+320)), tB:addr(b+352),aB:word(b+384),sB:safeSym(text(b,13)),dB:dec(word(b+448)),board:addr(b+480)}); }catch{continue} } return{rows,next}; } const betterRate=(a,b)=>a.aA*b.aB>b.aA*a.aB; const bookShare=(h,amm)=>{ const rest=h.ammIn!==undefined?h.ammIn:h.ammOut; if(!rest)return"Orderbook"; const mine=h.ammIn!==undefined?h.bookIn:h.bookOut; const pct=Number(mine*100n/(mine+rest)); return`Orderbook ${pct<1?"<1":pct}% + ${amm}`; }; const MAX_BOOK_LEGS=32,AON_PAIR_SEEDS=24; function planBookExactIn(rows,amountIn,ammOut,ammIn){ if(!amountIn||!ammIn)return null; let c=rows.filter(o=>!o.nA&&!o.nB&&o.aA&&(o.aB||o.dutch)&&(o.pf||o.aB<=amountIn) &&(!o.floor||(o.price>0n&&o.initial>0n)) &&!(account&&o.maker&&o.maker.toLowerCase()===account.toLowerCase())); const candidateCapped=c.length>256; const aonCount=c.reduce((n,o)=>n+!o.pf,0); const aonBounded=aonCount>=3; c.sort((a,b)=>betterRate(a,b)?-1:betterRate(b,a)?1:0); if(c.length>256)c.length=256; const build=(seed=-1,seed2=-1)=>{ let rem=amountIn,bin=0n,bout=0n,fills=[],legCapped=false; const take=o=>{ if(o.aA*ammIn<=ammOut*o.aB)return; if(!o.pf&&o.aB>rem)return; if(fills.length>=MAX_BOOK_LEGS){legCapped=true;return} if(o.aB===0n){ fills.push({id:o.id,board:o.board,pay:0n,get:o.aA,part:false}); bout+=o.aA;return; } const pay=o.aB<rem?o.aB:rem; if(!pay)return; const get=o.pf?(pay===o.aB?o.aA:rowGet(o,pay)):o.aA; if(!get)return; fills.push({id:o.id,board:o.board,pay,get,part:o.pf&&pay<o.aB}); rem-=pay;bin+=pay;bout+=get; }; if(seed>=0)take(c[seed]); if(seed2>=0)take(c[seed2]); for(let i=0;i<c.length;i++)if(i!==seed&&i!==seed2)take(c[i]); return{fills,bookIn:bin,bookOut:bout,ammIn:rem,score:bout+rem*ammOut/ammIn, bounded:candidateCapped||aonBounded||legCapped}; }; let best=build(-1); for(let i=0;i<c.length;i++)if(!c[i].pf){ const p=build(i);if(p.score>best.score)best=p; } const aon=[];for(let i=0;i<c.length&&aon.length<AON_PAIR_SEEDS;i++)if(!c[i].pf)aon.push(i); for(let i=0;i<aon.length;i++)for(let j=i+1;j<aon.length;j++){ const p=build(aon[i],aon[j]);if(p.score>best.score)best=p; } return best.fills.length?best:null; } const cheaperRate=(a,b)=>a.aB*b.aA<b.aB*a.aA; function planBookExactOut(rows,amountOut,ammIn,ammOut){ if(!amountOut||!ammOut)return null; let c=rows.filter(o=>!o.nA&&!o.nB&&o.aA&&(o.aB||o.dutch)&&(o.pf||o.aA<=amountOut) &&(!o.floor||(o.price>0n&&o.initial>0n)) &&!(account&&o.maker&&o.maker.toLowerCase()===account.toLowerCase())); const candidateCapped=c.length>256; const aonCount=c.reduce((n,o)=>n+!o.pf,0); const aonBounded=aonCount>=3; c.sort((a,b)=>cheaperRate(a,b)?-1:cheaperRate(b,a)?1:0); if(c.length>256)c.length=256; const build=(seed=-1,seed2=-1)=>{ let left=amountOut,bin=0n,bout=0n,fills=[],legCapped=false; const take=o=>{ if(o.aB*ammOut>=ammIn*o.aA)return; if(!o.pf&&o.aA>left)return; if(fills.length>=MAX_BOOK_LEGS){legCapped=true;return} const want=o.aA<left?o.aA:left; let pay,get; if(o.aB===0n){pay=0n;get=want} else if(want===o.aA){pay=o.aB;get=o.aA} else if(o.floor){ pay=floorPay(o,want); get=floorGet(o,pay); } else{ pay=(want*o.aB+o.aA-1n)/o.aA; if(pay>o.aB)pay=o.aB; get=pay*o.aA/o.aB; } if(!get)return; fills.push({id:o.id,board:o.board,pay,get,part:want!==o.aA}); bin+=pay;bout+=get;left-=get<left?get:left; }; if(seed>=0)take(c[seed]); if(seed2>=0)take(c[seed2]); for(let i=0;i<c.length&&left;i++)if(i!==seed&&i!==seed2)take(c[i]); return{fills,bookIn:bin,bookOut:bout,ammOut:left,score:bin+left*ammIn/ammOut, bounded:candidateCapped||aonBounded||legCapped}; }; let best=build(-1); for(let i=0;i<c.length;i++)if(!c[i].pf){ const p=build(i);if(p.score<best.score)best=p; } const aon=[];for(let i=0;i<c.length&&aon.length<AON_PAIR_SEEDS;i++)if(!c[i].pf)aon.push(i); for(let i=0;i<aon.length;i++)for(let j=i+1;j<aon.length;j++){ const p=build(aon[i],aon[j]);if(p.score<best.score)best=p; } return best.fills.length?best:null; } function encFillPlan(fills,tokenIn,tokenOut,recipient,refundTo,deadline){ let tail=encUint(fills.length); for(const f of fills)tail+=encUint(f.id)+encAddr(f.board)+encUint(f.pay)+encUint(f.get)+encBool(f.part); return"0x"+SEL_FILLPLAN+encAddr(tokenIn)+encAddr(tokenOut)+encAddr(recipient)+encAddr(refundTo) +encUint(deadline)+encUint(192)+tail; } function encFillPlanAndSwap(fills,tokenIn,tokenOut,recipient,refundTo,deadline,ammIn,ammData){ let ft=encUint(fills.length); for(const f of fills)ft+=encUint(f.id)+encAddr(f.board)+encUint(f.pay)+encUint(f.get)+encBool(f.part); const d=strip0x(ammData||"0x"),dt=encUint(d.length/2)+d.padEnd(Math.ceil(d.length/64)*64,"0"); return"0x"+SEL_FILLPLAN_SWAP+encAddr(tokenIn)+encAddr(tokenOut)+encAddr(recipient)+encAddr(refundTo) +encUint(deadline)+encUint(256)+encUint(ammIn)+encUint(256+ft.length/2)+ft+dt; } function encSnwap(tokenIn,amountIn,recipient,tokenOut,minOut,executor,data){ const d=strip0x(data),body=encUint(d.length/2)+d.padEnd(Math.ceil(d.length/64)*64,"0"); return"0x"+SEL_SNWAP+encAddr(tokenIn)+encUint(amountIn)+encAddr(recipient)+encAddr(tokenOut) +encUint(minOut)+encAddr(executor)+encUint(224)+body; } const encCheckpoint=(executor,token,recipient)=>encSnwap( ZERO,0n,recipient,ZERO,0n,executor,"0x"+SEL_CHECKPOINT+encAddr(token) ); const SEL_SWEEP3="dc2c256f"; const encSweep=(token,amount,to)=>chainCfg().sweep4 ?"0x"+SEL_SWEEP+encAddr(token)+encUint(0)+encUint(amount)+encAddr(to) :"0x"+SEL_SWEEP3+encAddr(token)+encUint(amount)+encAddr(to); function encPermit2Hybrid(ammData,fills,tokenIn,tokenOut,recipient,refundTo,deadline,bookIn,bookOut){ const bookData=encFillPlan(fills,tokenIn,tokenOut,recipient,refundTo,deadline); const steps=[encCheckpoint(SWAPBOL,tokenIn,refundTo)]; if(bookIn)steps.push(encSweep(tokenIn,bookIn,SWAPBOL)); steps.push(encSnwap(ZERO,0n,recipient,tokenOut,bookOut,SWAPBOL,bookData)); if(ammData&&ammData!=="0x")steps.push(ammData); steps.push(encSweep(tokenIn,0n,refundTo)); return encCalls(steps); } async function swapCandidates(tokenIn,tokenOut,block){ if(tokenOut===ZERO&&tokenIn.toLowerCase()===WETH.toLowerCase())return[]; const key=tokenIn.toLowerCase()+":"+tokenOut.toLowerCase(),old=candCache[key]; if(old&&Date.now()-old.at<15000&&old.block===block){ old.rows.incomplete=old.incomplete;old.rows.unavailable=old.unavailable; return old.rows; } const snapshotAt=Date.now(); const boardIn=tokenIn===ZERO?WETH:tokenIn; const boardOut=tokenOut===ZERO?WETH:tokenOut; const [lc,le,ld]=await Promise.all([ hasCode(SBVIEW),hasCode(SWAPBOL), DUTCH!==ZERO?hasCode(DUTCH):false ]); if(!lc||!le){ const rows=[];rows.unavailable=true;return rows; } const specs=[...BOARDS()]; if(ld){ specs.push({a:DUTCH,dutch:1,ti:tokenIn}); if(tokenIn===ZERO)specs.push({a:DUTCH,dutch:1,ti:WETH}); } const groups=await Promise.all(specs.map(async b=>{ let cursor=0n,out=[],turns=0,incomplete=false,failed=false; try{do{ const ti=b.dutch?b.ti:boardIn,to=boardOut; const data="0x"+(b.dutch?SEL_DUTCH_CANDS:SEL_CANDS)+encAddr(b.a) +(b.dutch?"":encBool(b.v2))+encAddr(ti)+encAddr(to)+encAddr(SWAPBOL) +encUint(cursor)+encUint(512); const p=decViewPage(await rpc(C,[{to:SBVIEW,data},block])); out.push(...p.rows.map(r=>({...r,v2:b.v2||0,dutch:b.dutch||0}))); cursor=p.next; ++turns; }while(cursor&&turns<64)}catch{failed=true;incomplete=true} incomplete=incomplete||!!cursor; return{out,incomplete,failed}; })); let rows=groups.flatMap(g=>g.out); if(tokenIn===ZERO&&tokenOut.toLowerCase()===WETH.toLowerCase()){ rows=rows.filter(r=>r.dutch&&r.tA.toLowerCase()===WETH.toLowerCase()&&r.tB===ZERO); } else if(await floorRoutable(block)){ rows=rows.concat(await floorCandidates(tokenIn,tokenOut,block).catch(()=>[])); } const incomplete=groups.some(g=>g.incomplete); const unavailable=groups.every(g=>g.failed&&!g.out.length); rows.incomplete=incomplete; rows.unavailable=unavailable; const current=candCache[key],bn=BigInt(block),cn=current?BigInt(current.block):-1n; if(!current||bn>cn||(bn===cn&&snapshotAt>=current.at)){ candCache[key]={at:snapshotAt,block,rows,incomplete,unavailable}; } for(const k in candCache)if(k!==key&&snapshotAt-candCache[k].at>60000)delete candCache[k]; return rows; } async function lensBookRows(my){ const [sv,dv]=await Promise.all([hasCode(SBVIEW),DUTCH!==ZERO?hasCode(DUTCH):false]); if(!sv)return null; const specs=[...BOARDS()]; if(dv)specs.push({a:DUTCH,dutch:1}); const pages=await Promise.all(specs.map(async b=>{ let cursor=0n,out=[],turns=0; do{ const data="0x"+(b.dutch?SEL_RECENT_DUTCH:SEL_RECENT)+encAddr(b.a) +(b.dutch?"":encBool(b.v2))+encUint(cursor)+encUint(80)+encUint(256); const p=decViewPage(await rpc(C,[{to:SBVIEW,data},L])); out.push(...p.rows.map(r=>({...r,v2:b.v2||0,dutch:b.dutch||0}))); cursor=p.next; }while(cursor&&out.length<160&&++turns<64&&my===bookSeq); if(cursor)out.capped=true; return out; })); const rows=pages.flat(); let capped=pages.some(p=>p.capped); try{ for(const r of decViewPage(await rpc(C,[{to:SBVIEW, data:"0x"+SEL_EXPIRED+encAddr(SB2)+encUint(0)+encUint(80)+encUint(256)},L])).rows) if(r.maker&&r.maker.toLowerCase()===me())rows.push({...r,v2:1,dutch:0}); }catch{} if(FLOOR!==ZERO&&FLOORVIEW!==ZERO){ await floorRoutable(L); try{ const raw=await rpc(C,[{to:FLOORVIEW, data:"0x"+SEL_RECENT_FLOOR+encAddr(FLOOR)+encUint(0)+encUint(80)+encUint(256)},L]); for(const b of decBidRows(raw)){ if(b.remaining<=0n)continue; rows.push({ id:b.id,board:FLOOR,floor:1,v2:0,dutch:0, maker:b.bidder,pf:b.isNFT?0:1,exp:b.expiry, nA:0,nB:0,cp:ZERO, tA:b.quote,aA:b.proceeds,sA:b.qSym,dA:b.qDec, tB:b.token,aB:b.remaining,sB:b.tSym,dB:b.isNFT?0:b.tDec, price:b.price,initial:b.initial,anyId:b.anyId,isNFT:b.isNFT?1:0, }); } }catch{} } if(dv&&account)try{ const total=Number(BigInt(await rpc(C,[{to:DUTCH,data:"0x"+SEL_NEXTID2},L])||"0x0")); const ids=[];for(let i=total-1;i>=Math.max(0,total-40);--i)ids.push(i); if(ids.length){ const res=await mc3Deep(ids.map(id=>({to:DUTCH,data:"0x"+SEL_DUTCH_LISTING+encUint(id)}))); const now=BigInt(Math.floor(Date.now()/1e3)),me2=me(); for(let i=0;i<ids.length;i++){ const s=strip0x(res[i]||"0x");if(s.length<704)continue; const w=k=>BigInt("0x"+s.slice(k*64,(k+1)*64)),ad=k=>"0x"+s.slice(k*64+24,(k+1)*64); if(ad(0).toLowerCase()!==me2)continue; const exp=w(10);if(!exp||now<=exp)continue; const rem=w(9);if(!rem)continue; rows.push({id:ids[i],board:DUTCH,dutch:1,v2:0,maker:ad(0),pf:1,exp, nA:0,nB:0,cp:ZERO,tA:ad(4),aA:rem,sA:null,dA:null,tB:ad(6),aB:0n,sB:null,dB:null}); } }}catch{} if(FLOOR!==ZERO&&account)try{ const total=Number(BigInt(await rpc(C,[{to:FLOOR,data:"0x"+SEL_NEXTID2},L])||"0x0")); const ids=[];for(let i=total-1;i>=Math.max(0,total-40);--i)ids.push(i); if(ids.length){ const res=await mc3Deep(ids.map(id=>({to:FLOOR,data:"0x"+SEL_BIDS+encUint(id)}))); const now=BigInt(Math.floor(Date.now()/1e3)),me2=me(); for(let i=0;i<ids.length;i++){ const s=strip0x(res[i]||"0x");if(s.length<704)continue; const w=k=>BigInt("0x"+s.slice(k*64,(k+1)*64)),ad=k=>"0x"+s.slice(k*64+24,(k+1)*64); if(ad(0).toLowerCase()!==me2)continue; const exp=w(2)+w(3);if(!exp||now<=exp)continue; const rem=w(10);if(!rem)continue; rows.push({id:ids[i],board:FLOOR,floor:1,v2:0,dutch:0,maker:ad(0),pf:1,exp, nA:0,nB:0,cp:ZERO,tA:ad(6),aA:w(8),sA:null,dA:null,tB:ad(4),aB:rem,sB:null,dB:null}); } }}catch{} const uniq=new Set(),deduped=[]; deduped.capped=capped; for(const r of rows){ const k=`${(r.board||"").toLowerCase()}|${r.dutch?"d":r.floor?"f":"o"}|${r.id}`; if(uniq.has(k))continue; uniq.add(k);deduped.push(r); } return deduped; } const loadBook=()=>loadBookRun().catch(()=>{}); async function loadBookRun(){ const my=++bookSeq; if(tab!=="book"){book.innerHTML="";bookRows=[];syncBook(0);return} const live=(await Promise.all(BOARDS().map(b=> rpc("eth_getCode",[b.a,L]).then(c=>(c||"").length>4?b:null,()=>null)))).filter(Boolean); if(my!==bookSeq)return; if(!live.length){ bookRows=[];bookLegs=[]; book.innerHTML='<h6><span>Orderbook</span></h6><div class="o"><span class="l"><i>No Swapboard deployed on this network yet.</i></span></div>'; syncBook(0);book.classList.remove("hide");return} let rows=null; try{rows=await lensBookRows(my)}catch{rows=null} if(my!==bookSeq)return; bookTrunc=!!(rows&&rows.capped); if(!rows)rows=[]; for(const b of rows.length?[]:live){ let total=0; try{total=Number(BigInt(await rpc(C,[{to:b.a,data:"0x"+SEL_NEXTID},L])))}catch{continue} if(!total)continue; const hi=total,lo=Math.max(0,hi-40); if(lo>0)bookTrunc=true; const ids=[];for(let i=hi-1;i>=lo;--i)ids.push(i); let raw=""; try{raw=await rpc(C,[{to:b.a,data:"0x"+SEL_GETORDERS+encUint(32)+encUint(ids.length)+ids.map(i=>encUint(i)).join("")},L])}catch{continue} if(my!==bookSeq)return; const h=strip0x(raw);if(h.length<128)continue; const w=WORDS(b.v2),words=(h.length-128)/64; let n=Number(BigInt("0x"+h.slice(64,128))); if(!Number.isSafeInteger(n)||n*w>words)n=Math.floor(words/w); const at=(r,k)=>BigInt("0x"+h.slice(128+(r*w+k)*64,128+(r*w+k+1)*64)); const ad=(r,k)=>"0x"+h.slice(128+(r*w+k)*64+24,128+(r*w+k+1)*64); for(let r=0;r<n&&r<ids.length;++r){ if(at(r,1)!==1n)continue; const o=b.v2 ?{id:ids[r],board:b.a,v2:1,maker:ad(r,0),pf:at(r,2)===1n,exp:at(r,3), nA:at(r,4)===1n,nB:at(r,5)===1n,cp:ad(r,6), tA:ad(r,7),aA:at(r,8),tB:ad(r,9),aB:at(r,10)} :{id:ids[r],board:b.a,v2:0,maker:ad(r,0),pf:0,exp:0n,nA:0,nB:0, cp:ZERO,tA:ad(r,2),aA:at(r,3),tB:ad(r,4),aB:at(r,5)}; if(o.exp&&BigInt(Math.floor(Date.now()/1e3))>o.exp &&o.maker.toLowerCase()!==me())continue; if(o.cp!==ZERO &&o.cp.toLowerCase()!==me() &&o.maker.toLowerCase()!==me())continue; rows.push(o); } } if(my!==bookSeq)return; const cutoff=BigInt(Math.floor(Date.now()/1e3)); rows=rows.filter(o=>{ const mine=o.maker.toLowerCase()===me(); if(o.exp&&cutoff>o.exp&&!mine)return false; return o.cp===ZERO ||o.cp.toLowerCase()===me() ||o.maker.toLowerCase()===me(); }); const dutchRows=rows.filter(r=>r.dutch); const bidRows=rows.filter(r=>r.floor); const fillRows=rows.filter(r=>!r.dutch&&!r.floor&&r.v2&&r.pf&&!r.nA&&!r.nB); if(dutchRows.length||bidRows.length||fillRows.length){ const reqs=[ ...dutchRows.map(r=>({to:DUTCH,data:"0x"+SEL_DUTCH_LISTING+encUint(r.id)})), ...bidRows.map(r=>({to:FLOOR,data:"0x"+SEL_BIDS+encUint(r.id)})), ...fillRows.map(r=>({to:r.board,data:"0x"+SEL_INITIAL_B+encUint(r.id)})), ]; const raw=await mc3Deep(reqs); if(my!==bookSeq)return; let k=0; for(const r of dutchRows){const x=raw[k++];if(!x)continue; const h=strip0x(x),w=n=>BigInt("0x"+h.slice(n*64,(n+1)*64)); if(h.length>=640)Object.assign(r,{ start:w(2),duration:w(3),startPrice:w(5),endPrice:w(7),initial:w(8),remaining:w(9)}); } for(const r of bidRows){const x=raw[k++];if(!x)continue; const h=strip0x(x),w=n=>BigInt("0x"+h.slice(n*64,(n+1)*64)); if(h.length>=11*64)Object.assign(r,{start:w(2),duration:w(3),startPrice:w(5),endPrice:w(7)}); } for(const r of fillRows){const x=raw[k++];if(!x)continue; const h=strip0x(x);if(h.length<64)continue; const init=BigInt("0x"+h.slice(0,64)); if(init>r.aB)r.filled=(init-r.aB)*10000n/init; } } if(!rows.length){bookRows=[];bookLegs=[];book.innerHTML="";syncBook(0);return} const needMeta=[]; for(const r of rows){ if(!r.nA&&(r.sA==null||r.dA==null))needMeta.push(r.tA); if(!r.nB&&(r.sB==null||r.dB==null))needMeta.push(r.tB); } await Promise.all([loadLogos(rows.flatMap(r=>[r.tA,r.tB])),warmMeta(needMeta)]); if(my!==bookSeq)return; const legs=await Promise.all(rows.map(r=>Promise.all([ leg(r.tA,r.aA,r.nA,r.sA!=null&&r.dA!=null?{sym:r.sA,dec:r.dA}:null), leg(r.tB,r.aB,r.nB,r.sB!=null&&r.dB!=null?{sym:r.sB,dec:r.dB}:null) ]))); if(my!==bookSeq)return; rows.forEach((r,i)=>{const[a,b]=legs[i]; if(!r.nA&&r.dA==null&&a.dec!=null)r.dA=a.dec; if(!r.nB&&r.dB==null&&b.dec!=null)r.dB=b.dec}); bookRows=rows;bookLegs=legs; paintBook(); } let bookPairOnly=LS.bkp!=="0",bookLegs=[]; let bookType=/^(all|limit|dutch|bid|nft)$/.test(LS.bkt||"")?LS.bkt:"all"; const unitPx=r=>r.aA&&r.aB&&!r.nA&&!r.nB&&!r.isNFT ?r.aB*10n**BigInt(18+(r.dA??18))/(r.aA*10n**BigInt(r.dB??18)):null; const sameAsset=(a,b)=>{ const w=x=>{x=(x||"").toLowerCase();return x===ZERO.toLowerCase()?WETH.toLowerCase():x}; return w(a)===w(b); }; const rowInPair=r=>{ const f=TOKENS[fromSel.value]?.addr,t=TOKENS[toSel.value]?.addr; if(!f||!t)return true; return (sameAsset(r.tA,f)&&sameAsset(r.tB,t))||(sameAsset(r.tA,t)&&sameAsset(r.tB,f)); }; const rowIsType=r=>bookType==="all" ||(bookType==="dutch"&&r.dutch) ||(bookType==="bid"&&r.floor) ||(bookType==="nft"&&(r.nA||r.nB||r.isNFT)) ||(bookType==="limit"&&!r.dutch&&!r.floor); const bookOpenCards=()=>{const s=[]; for(const el of book.querySelectorAll('.o.ins[aria-expanded="true"]')) if(el.dataset.k)s.push(el.dataset.k); return s}; const bookReopen=ks=>{ for(const k of ks){ const el=book.querySelector('.o.ins[data-k="'+k+'"]'); if(!el)continue; const at=k.lastIndexOf(":"); const c=cardCache[k.slice(0,at).toLowerCase()+":"+k.slice(at+1)]; const box=el.parentNode.querySelector(".ic"); if(!c||!box)continue; el.setAttribute("aria-expanded","true"); box.innerHTML=cardHtml(c)||"No card for this order."; }}; const bookOrder=rows=>{ const idx=rows.map((r,i)=>i); const f=TOKENS[fromSel.value]?.addr,t=TOKENS[toSel.value]?.addr; if(!bookPairOnly||!f||!t)return idx; const k=rows.map(r=>{const d=sameAsset(r.tA,t)&&sameAsset(r.tB,f)?0:1; return[d,d?null:unitPx(r)]}); const px=(a,b)=>a===null||b===null?(a===b?0:a===null?1:-1):a<b?-1:a>b?1:0; return idx.sort((a,b)=>k[a][0]-k[b][0]||px(k[a][1],k[b][1])); }; function paintBook(){ const rows=bookRows,legs=bookLegs; if(!rows.length){book.innerHTML="";syncBook(0);return} const wasOpen=bookOpenCards(),wasAt=book.scrollTop; let own="",rest="",nOwn=0,nRest=0,hidden=0; const now=BigInt(Math.floor(Date.now()/1e3)); bookOrder(rows).forEach(i=>{ const r=rows[i],[a,bg]=legs[i]||[]; if(!a||!bg)return; const mine=r.maker.toLowerCase()===me(); const expired=!!(r.exp&&now>r.exp); const tags=(expired?'<span class="tg w" title="This order has lapsed and can no longer be filled, but its escrow is still held by the board. Reclaim returns it to you.">expired</span>':"") +(r.nA||r.nB||r.isNFT?'<span class="tg">NFT</span>':"") +(r.v2&&!r.pf&&!r.nA&&!r.nB?'<span class="tg">AON</span>':"") +(r.dutch?'<span class="tg">Dutch</span>':"") +(r.floor?'<span class="tg">Bid</span>':"") +(r.floor&&r.isNFT&&r.anyId?'<span class="tg">any id</span>':"") +(r.filled>0n?`<span class="tg">${r.filled<100n?"<1":Math.floor(Number(r.filled)/100)}% filled</span>`:"") +(r.cp!==ZERO?'<span class="tg">private</span>':"") +(!a.ok||!bg.ok?'<a class="tg w" href="'+ZLIST_URL+'" target="_blank" rel="noopener"' +' title="Not on the staked token list. Its symbol and decimals are read straight from the contract' +' and nothing has vouched for it - check the address before filling. Stake on zList to get a real' +' token listed.">unverified</a>':""); let when=expired?" · lapsed "+rel(Number(now-r.exp))+" ago — escrow still held" :r.exp?" · "+rel(Number(r.exp-now))+" left":""; if(!expired&&r.dutch&&r.duration){ const end=r.start+r.duration; const floor=r.initial?(r.endPrice*r.remaining+r.initial-1n)/r.initial:0n; when=now>=end?` · floor reached: ${trimAmt(floor,r.dB)} ${r.sB}` :` · floor ${trimAmt(floor,r.dB)} ${r.sB} in ${rel(Number(end-now))}`; } if(!expired&&r.floor&&r.duration){ const end=r.start+r.duration; when=now>=end?" · window closed" :` · climbs to ${trimAmt(r.initial?r.endPrice*r.aB/r.initial:r.endPrice,r.dA)} ${r.sA} in ${rel(Number(end-now))}`; } const dset=`data-i="${r.id}" data-b="${r.board}" data-v="${r.v2}" data-d="${r.dutch||0}" data-f="${r.floor||0}"`; const takeable=!r.floor||(!r.isNFT&&floorReady); const shrinkable=mine&&!expired&&((r.dutch&&!r.nA&&!r.nB)||r.floor); const editable=mine&&!expired&&r.v2&&!r.dutch&&!r.floor&&!r.nA&&!r.nB; const act=mine?`<button data-x="c" ${dset}>${expired?"Reclaim":"Cancel"}</button>` +(shrinkable?`<button data-x="w" ${dset}>Shrink</button>`:"") +(editable?`<button data-x="r" ${dset}>Edit</button>`:"") :takeable?`<button data-x="f" ${dset}>${r.floor?"Sell into":"Fill"}</button>` :`<button disabled>${r.isNFT?"NFT bid":"Not routable"}</button>`; const card=r.board.toLowerCase()!==SB1.toLowerCase(); const row=`<div class="ow"><div class="o${card?" ins":""}"${card?` data-k="${r.board}:${r.id}" role="button" tabindex="0" aria-expanded="false" title="Show the on-chain receipt"`:""}>` +`<span class="l">${a.ic}${bg.ic}<span><b>${a.txt} → ${bg.txt}</b>` +`<i>${pxOf(r,a,bg)}${a.link}${tags}${when}</i></span></span>${act}</div><div class="ic"></div></div>`; if(mine){own+=row;++nOwn;return} if(!(bookPairOnly?rowInPair(r):true)||!rowIsType(r)){++hidden;return} rest+=row;++nRest; }); let html=""; if(nOwn)html+=`<h6><span>Your orders</span><span>${nOwn}</span></h6>`+own; html+=bookFilters(hidden); if(nRest)html+=rest; else html+=`<div class="lqempty">${emptyBook()}${hidden?` ${hidden} hidden by the filters.`:""}</div>`; book.innerHTML=html; bookReopen(wasOpen); syncBook(nOwn+nRest,rows.length); book.scrollTop=wasAt; } const pxOf=(r,a,bg)=>{ const v=unitPx(r); return v?`<span class="px">${sigAmt(v,18,4)} ${bg.sym}/${a.sym}</span> \u00b7 `:""; }; const cardCache={}; async function loadCard(board,id){ const k=board.toLowerCase()+":"+id; if(cardCache[k])return cardCache[k]; const raw=await rpc(C,[{to:board,data:"0x"+SEL_TOKENURI+encUint(id)},L]); const uri=decodeString(raw); const i=uri.indexOf("base64,"); if(i<0)throw Error("card is not inline"); const j=JSON.parse(atob(uri.slice(i+7))); const out={attrs:Array.isArray(j.attributes)?j.attributes:[],img:safeUrl(j.image), name:safeSym(j.name).slice(0,40)}; cardCache[k]=out;capObj(cardCache,CARD_MAX);return out; } function cardHtml(c){ const rows=c.attrs.slice(0,10).map(a=>{ const t=safeSym(a&&a.trait_type).slice(0,20); let v=a&&a.value; if(a&&a.display_type==="date"&&+v)v=new Date(+v*1e3).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}); else if(a&&a.display_type==="number")v=String(v); return`<dt>${t}</dt><dd>${safeSym(v).slice(0,28)}</dd>`}).join(""); return(rows?`<dl>${rows}</dl>`:"")+(c.img?`<img src="${c.img}" alt="" loading="lazy">`:""); } let inspectSeq=0; async function inspect(el,k){ const box=el.parentNode.querySelector(".ic"); if(!box)return; if(box.innerHTML){box.innerHTML="";delete box.dataset.job;el.setAttribute("aria-expanded","false");return} el.setAttribute("aria-expanded","true"); const at=k.lastIndexOf(":"),board=k.slice(0,at),id=BigInt(k.slice(at+1)); const job=String(++inspectSeq);box.dataset.job=job; box.textContent="Reading the receipt\u2026"; let html; try{html=cardHtml(await loadCard(board,id))||"No card for this order."} catch{html=""} if(box.dataset.job!==job)return; if(html)box.innerHTML=html;else box.textContent="This receipt could not be read."; } const BOOK_TYPES=[["all","All"],["limit","Limit"],["dutch","Dutch"],["bid","Bids"],["nft","NFT"]]; const emptyBook=()=>{ const ty=BOOK_TYPES.find(([v])=>v===bookType); const what=bookType==="all"?"orders":`${ty?ty[1]:bookType} orders`; const where=bookPairOnly ?` on ${TOKENS[fromSel.value]?.sym||"?"}/${TOKENS[toSel.value]?.sym||"?"}`:""; return `No ${what.toLowerCase()}${where} from anyone else.`; }; function bookFilters(hidden){ const f=TOKENS[fromSel.value]?.sym||"?",t=TOKENS[toSel.value]?.sym||"?"; const chip=(on,attr,val,label,title)=>`<button type="button" class="bkc${on?" on":""}" ` +`aria-pressed="${on?"true":"false"}" data-${attr}="${val}"${title?` title="${title}"`:""}>${label}</button>`; const note=[hidden?hidden+" hidden":"",bookTrunc?"scan capped":""].filter(Boolean).join(" \u00b7 "); return `<div class="bkh"><h6><span>Orderbook</span><span title="${bookTrunc?"More orders exist than this scan returned \u2014 the newest are shown first.":""}">${note}</span></h6>` +`<div class="bkf">` +chip(bookPairOnly,"bf","1",`${f}/${t}`,"Only orders on the pair selected above") +chip(!bookPairOnly,"bf","0","All p
0x68575b07…67c7·#25,913,377·0xea557877…278133
gle("hide"); rankOpen=!shut; car.setAttribute("aria-expanded",shut?"false":"true")}; car.onclick=carToggle; car.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();carToggle()}}; rate.appendChild(document.createTextNode(" ")); rate.appendChild(car); } const inApprove=isIn?swapAmt:r.msgValue||r.amountLimit; const shownPay=amt.value,shownGet=outAmt.value; if(acct)last={callData:r.callData,fundedCallData:r.fundedCallData,msgValue:r.msgValue, limit:r.amountLimit,shownPay,shownGet, to:r.to,spender:r.spender, amountIn:inApprove,from:f,recipient:rcv||"",exp:Date.now()+QUOTE_TTL,impact:imp, lossTok,lossSym:isIn?t.sym:f.sym,lossDec:isIn?t.dec:f.dec, fills:r.fills||null,tokenIn:f.addr,tokenOut:t.addr, source:r.best.source, wrap:r.wrap||0n,pools:r.pools||null, mkt,got:isIn?finalOut:finalIn,isIn}; quoting=false; render(); }catch(e){if(my===seq){ quoting=false; const transport=!!e?.noWallet||mcGaveUp>gaveUp0||(mcFailed>failed0&&mcOk===ok0); if(!transport&&!e?.exactOut&&advancePastDeadPair()){loadChart();update();return} outEl.value="";fitFont(outEl);rankBox.textContent="";rankBox.classList.add("hide");render(); stat.textContent=e?.noWallet?NO_WALLET :e&&e.exactOut?e.message :transport ?"Your RPC could not complete the quote — some venues were never asked. Try again, or switch networks in your wallet and back." :"No route: "+(e.message||e)}} } let debounceT=0; const showNet=()=>{ const e=document.getElementById("net"); if(!e)return; const c=chainCfg(); e.innerHTML=(c.icon||"")+"<span>"+c.short+"</span>"; e.title="Network: "+c.name+" — click to switch"; }; const switchNet=async()=>{ const ids=Object.keys(CHAINS).map(Number); const next=ids[(ids.indexOf(CHAIN_ID)+1)%ids.length]; try{ await rpc("wallet_switchEthereumChain",[{chainId:"0x"+next.toString(16)}]); }catch(e){ if(e&&e.code===4902){ const c=CHAINS[next]; try{await rpc("wallet_addEthereumChain",[{chainId:"0x"+next.toString(16),chainName:c.name, nativeCurrency:{name:"Ether",symbol:"ETH",decimals:18},rpcUrls:c.rpcs,blockExplorerUrls:[c.explorer]}])}catch{return} }else return; } setChain(next);showNet();location.reload(); }; const DEEPSTATE="0x6cf19308c22fc82ea620fa0b3e94948d20f27b96"; const SEL_POOLID="1338aa1d",SEL_POOLEPOCH="8fa79763",SEL_SWAPDEEP="ad33b1d0"; const OZ20="52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00"; const DEEP_TAKER="0x00000000000000000000000000000000deeb0000"; const MAXU=(1n<<256n)-1n; const slotKey=(k,sl)=>strip0x(keccak(hexToBytes(k+sl))); const deepSlots={}; const findSlot=async(tok,sel,keyw,probe)=>{ const ck=tok+sel; if(deepSlots[ck]!==undefined)return deepSlots[ck]; const cands=[OZ20,(BigInt("0x"+OZ20)+1n).toString(16).padStart(64,"0")]; for(let i=0;i<10;i++)cands.push(encUint(i)); for(const c of cands){ const sl=keyw.length>64?slotKey(keyw.slice(64),slotKey(keyw.slice(0,64),c)):slotKey(keyw,c); try{ const got=await rpc(C,[{to:tok,data:"0x"+sel+keyw},L,{[tok]:{stateDiff:{["0x"+sl]:"0x"+encUint(probe)}}}]); if(BigInt(got||0)===probe){deepSlots[ck]=c;return c} }catch{return null} } deepSlots[ck]=null;return null; }; const deepQuote=async(fa,ta,amt,expOut)=>{ if(CHAIN_ID!==4663||!amt||fa===ZERO||ta===ZERO)return null; const [t0,t1]=fa.toLowerCase()<ta.toLowerCase()?[fa,ta]:[ta,fa]; const isBid=ta.toLowerCase()===t0.toLowerCase(); try{ const pid=await rpc(C,[{to:DEEPSTATE,data:"0x"+SEL_POOLID+encAddr(t0)+encAddr(t1)},L]); if(!pid||pid==="0x")return null; const ep=await rpc(C,[{to:DEEPSTATE,data:"0x"+SEL_POOLEPOCH+strip0x(pid)},L]); const epoch=BigInt(ep||0); const bs=await findSlot(fa,"70a08231",encAddr(DEEP_TAKER),0x1234567n); if(bs===null)return null; const as=await findSlot(fa,"dd62ed3e",encAddr(DEEP_TAKER)+encAddr(ZROUTER),0x7654321n); if(as===null)return null; const ov={[fa]:{stateDiff:{ ["0x"+slotKey(encAddr(DEEP_TAKER),bs)]:"0x"+encUint(amt), ["0x"+slotKey(encAddr(ZROUTER),slotKey(encAddr(DEEP_TAKER),as))]:"0x"+encUint(MAXU)}}}; const price=isBid?0x7fffffffn:0x80000000n; const seed=isBid?(expOut||0n):amt; if(seed<=0n)return null; let best=null; for(let q=seed*4n,n=0;n<5&&q>0n;q/=4n,n++){ const order="0x"+encUint((price<<224n)|((q&((1n<<160n)-1n))<<64n)); const data="0x"+SEL_SWAPDEEP+encAddr(DEEP_TAKER)+encAddr(t0)+encAddr(t1)+encUint(epoch) +strip0x(order)+encUint(isBid?1:0)+encUint(amt)+encUint(0n)+encUint(MAXU); try{ const r=await rpc(C,[{from:DEEP_TAKER,to:ZROUTER,data},L,ov]); const b=strip0x(r||""); if(b.length<128)continue; const ai=BigInt("0x"+b.slice(0,64)),ao=BigInt("0x"+b.slice(64,128)); if(ao>0n&&(!best||ao>best.out))best={inUsed:ai,out:ao,t0,t1,isBid,epoch,order}; }catch{} } return best; }catch{return null} }; const showAddr=async()=>{const a=account,short=a.slice(0,6)+"..."+a.slice(-4); addr.textContent=short;addr.title=a+" - click to disconnect";addr.classList.add("on"); addr.setAttribute("role","button");addr.tabIndex=0; const n=await nameRev(a);if(n&&a===account)addr.textContent=n}; async function disconnect(){ sessionStorage.setItem("dc","1"); try{delete LS[WK]}catch{} location.reload(); } addr.onclick=()=>{account?disconnect():connect()} net.onclick=()=>{switchNet()} net.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();switchNet()}} showNet();; addr.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();account?disconnect():connect()}}; const AUTO_Q_MAX=5; const updateSoon=()=>{clearTimeout(debounceT);debounceT=setTimeout(()=>{debounceT=0;update()},250)}; const setMode=m=>{++seq;quoting=false;mode=m;const o=m==="in"?outAmt:amt;o.value="";fitFont(o);last=null;sendReady=null;render()}; amt.addEventListener("input",()=>{ if(lqMode){fitFont(amt);lqPreviewSoon();return} if(tab==="book"){fitFont(amt);render();return}setMode("in");fitFont(amt);updateSoon()}); outAmt.addEventListener("input",()=>{ if(lqMode){fitFont(outAmt);lqPreviewSoon();return} if(tab==="book"){fitFont(outAmt);seedFloor();render();return}setMode("out");fitFont(outAmt);updateSoon()}); const slipWarn=()=>slip.classList.toggle("warn",+slip.value>=3); if(LS.slip&&+LS.slip>=.01&&+LS.slip<=10)slip.value=LS.slip; slipWarn(); slip.addEventListener("input",()=>{last=null;slipWarn();render();updateSoon()}); rc.addEventListener("input",()=>{ if(lnMode)return; if(lqMode){clearTimeout(lqToT);lqToT=setTimeout(lqShowTo,300);return} last=null;sendReady=null;render();updateSoon()}); let lqToT; const onEnter=e=>{ if(e.key!=="Enter")return; const go=lnMode?lnGo:swap; if(!go||go.classList.contains("hide"))return; e.preventDefault(); if(debounceT){clearTimeout(debounceT);debounceT=0;update();return} if(!go.disabled)go.click(); }; for(const el of[amt,outAmt,rc,slip,floorAmt,nftId,lnName,lnSym,lnSupply,lnMcap,lnAlloc,lnGoal,lnDays]) if(el)el.addEventListener("keydown",onEnter); slip.addEventListener("change",()=>{slip.value=Math.min(10,Math.max(.01,+slip.value||.5));slipWarn();LS.slip=slip.value}); for(const sel of [fromSel,toSel]){ sel.dataset.prev=sel.value; sel.addEventListener("change",async()=>{ if(sel.value!=="__custom"){ sel.dataset.prev=sel.value; sel.dataset.addr=TOKENS[sel.value]?.addr||""; pairChosen=true; syncDisabled(sel); syncNftUI(); if(sel===fromSel)refreshBalance();else refreshBalance1(); loadChart(); if(tab==="book"&&bookRows.length)paintBook(); update(); return; } last=null;render(); const prev=sel.dataset.prev; let a; try{a=(prompt("Paste token address:")||"").trim()} catch{sel.value=prev;syncDisabled(); stat.textContent="This browser blocked the address prompt — add the token with a #token=0x… link instead."; return} if(!a){sel.value=prev;update();return} stat.textContent="Loading token..."; try{ const idx=await addCustomToken(a); rebuild(); const otherSel=sel===fromSel?toSel:fromSel; if(String(idx)===otherSel.value){otherSel.value=prev;otherSel.dataset.prev=prev} sel.value=String(idx); sel.dataset.prev=sel.value; pairChosen=true; stat.textContent=""; syncDisabled(sel);syncNftUI(); refreshBalance(); update(); }catch(e){ const msg="Couldn't load token: "+(e.message||e); sel.value=prev; syncDisabled();syncNftUI();refreshBalance();update(); stat.textContent=msg; } }); } flip.onclick=()=>{pairChosen=true;const a=fromSel.value,b=toSel.value,v=outAmt.value.replace(/,/g,"");fromSel.value=b;toSel.value=a; amt.value=(v&&!isNaN(+v))?v:"";setMode("in");fitFont(amt);syncDisabled();syncNftUI();refreshBalance();loadChart();update()}; const sameAcct=(a,b)=>!!a&&!!b&&a.toLowerCase()===b.toLowerCase(); const wkBind=eth=>{ if(!eth||eth.__zb)return; eth.__zb=1; eth.on?.("chainChanged",cid=>{if(connecting)return;if(CHAINS[+cid]&&account){setChain(+cid);location.reload();return}location.reload()}); eth.on?.("accountsChanged",accs=>{if(connecting||sameAcct(accs?.[0],account))return;location.reload()}); }; const wireWallet=eth=>{ if(!eth||wireWallet.done)return; wireWallet.done=true; wkBind(eth); (async()=>{ if(sessionStorage.getItem("dc"))return; try{ const accs=await rpc("eth_accounts",[]); const cid=await rpc(I,[]); wireWallet.probed=1; const hop=!!CHAINS[+cid]&&+cid!==CHAIN_ID; if(hop){setChain(+cid);showNet()} if(accs[0]&&CHAINS[+cid]){account=accs[0];showAddr()} if(account||hop){ const keep2=pairAddrs(); await loadTokenList(); adoptList(keep2); if(hop&&location.hash.length>1)applyLink(); refreshBalance();update();if(account)loadPos();syncChart(); } }catch{} })(); (async()=>{ const self=selfFromUrl()||ZSWAP_SELF; if(!self)return; try{ if(+(await rpc(I,[]))!==CHAIN_ID)return; const ret=strip0x(await rpc(C,[{to:self,data:"0x"+SEL_LATEST},L])||""); if(ret.length!==64)return; let tip="0x"+ret.slice(24); const me=self.toLowerCase(); if(BigInt(tip)===0n||tip===me)return; const rd=async(to,sel)=>{const h=strip0x(await rpc(C,[{to,data:"0x"+sel},L])||"");return h.length===64?h:""}; const now=Date.now()/1000; for(let i=0;i<32&&tip!==me;i++){ const pv=await rd(tip,SEL_PREV); if(!pv)break; const prev="0x"+pv.slice(24); if(BigInt(prev)===0n)break; const at=await rd(prev,SEL_SUCCAT); const born=at?Number(BigInt("0x"+at)):0; if(!born||now>=born+MATURITY)break; tip=prev; } if(tip===me)return; const h=(location.hostname||"").split("."); const url=/^0x[0-9a-fA-F]{40}$/.test(h[0]||"") ?`${location.protocol}//${[tip,...h.slice(1)].join(".")}/` :`https://${tip}.w4eth.io/`; footAddr.innerHTML+=` · <a href="${url}" target="_blank" rel="noopener" ` +`title="A newer version exists on chain. This page is unchanged and always will be; the link opens the successor.">newer →</a>`; }catch{} })(); }; const wkGo=()=>{wireWallet(prov()); if(!prov())window.addEventListener("ethereum#initialized",()=>wireWallet(prov()),{once:true})}; if(!PROV&&LS[WK]&&LS[WK]!=="wc")wkRestore().then(wkGo,wkGo);else wkGo(); setTimeout(burnTotal,0); setTimeout(loadEthUsd,0); const SEL_TRANSFER="a9059cbb"; let SB2=MB.sb; let SB1=MB.s1; const SBVIEW="0x000000E0b25449F32f7D9259aC449bA88E78dFCE"; let SWAPBOL=MB.sw; let DUTCH=MB.du; let ORDERBOL=MB.ob; let FLOOR=MB.fl; const FLOORVIEW="0x0000004E376e9dB5D9EC28E6711E1a64997C6ba7"; const SEL_NEXTID="2a58b330",SEL_NEXTID2="61b8ce8c",SEL_GETORDERS="03652027",SEL_CANCELORD="514fcac7",SEL_EXPIRED="d6c3de07", SEL_WITHDRAW="ef3d3521",SEL_REPLACE="84343d3a"; const SEL_FILL1="c37dfc5b",SEL_FILL2="9d136c7f",SEL_FILL2_UNWRAP="3987baf4",SEL_FILL2_ETH="6f608bab"; const SEL_RECENT="6a9849c1",SEL_RECENT_DUTCH="98035c9a",SEL_DUTCH_FILL="ae7a8260",SEL_DUTCH_CANCEL="40e58ee5"; const SEL_CANCEL_UNWRAP="21dd76f9",SEL_DUTCH_CANCEL_UNWRAP="8382de65"; const SEL_DUTCH_LISTING="de74e57b"; const SEL_CANDS="5f452988",SEL_DUTCH_CANDS="eb33e466",SEL_FILLPLAN="c277f67c"; const SEL_FILLPLAN_SWAP="9090c8e5",SEL_SNWAP="5f3bd1c8"; const SEL_COLL_BIDS="16bb24eb"; const SEL_FLOOR_CANDS="c587d927",SEL_RECENT_FLOOR="a5edd13d"; const SEL_ORDER_FLOOR="23e93357"; const SEL_BOL_FLOOR="b732d224"; let floorReady=null; async function floorRoutable(block){ if(FLOOR===ZERO||FLOORVIEW===ZERO)return false; if(floorReady!==null)return floorReady; try{ const h=await rpc(C,[{to:SWAPBOL,data:"0x"+SEL_BOL_FLOOR},block||L]); floorReady=("0x"+strip0x(h).slice(-40)).toLowerCase()===FLOOR.toLowerCase(); }catch{return false} return floorReady; } const SEL_BIDS="4423c5f1"; const SEL_INITIAL_B="878ea250",SEL_TOKENURI="c87b56dd",SEL_LOGOOF="6ce273ac"; const SEL_QUOTEFILL="f325beda"; async function preflightLeg(row,routeIn,routeOut,pay){ if(!row.floor)return preflightAsk(row,pay); if(!await floorRoutable(L))throw Error("this executor cannot route bids yet"); const want=routeIn===ZERO?WETH:routeIn,gets=routeOut===ZERO?WETH:routeOut; let h; try{h=await rpc(C,[{to:FLOOR,data:"0x"+SEL_BIDS+encUint(row.id)},L])} catch{throw Error("bid could not be read — refresh and retry")} const at=i=>strip0x(h).slice(i*64,(i+1)*64); if(strip0x(h).length<11*64)throw Error("bid could not be read — refresh and retry"); const addr=i=>"0x"+at(i).slice(24); if(/^0x0{40}$/i.test(addr(0)))throw Error("bid is closed — refresh and retry"); if(BigInt("0x"+at(1)))throw Error("NFT bids are not routable from here"); if(addr(4).toLowerCase()!==want.toLowerCase() ||addr(6).toLowerCase()!==gets.toLowerCase()) throw Error("bid changed — refresh and retry"); if(BigInt("0x"+at(10))<pay)throw Error("bid partly taken — refresh and retry"); } async function preflightFills(fills,tokenIn,tokenOut){ if(!fills||!fills.length)return; const inA=(tokenIn===ZERO?WETH:tokenIn).toLowerCase(); const outA=(tokenOut===ZERO?WETH:tokenOut).toLowerCase(); const reads=fills.map(f=>f.board.toLowerCase()===FLOOR.toLowerCase() ?{to:FLOOR,data:"0x"+SEL_BIDS+encUint(f.id)} :f.board.toLowerCase()===DUTCH.toLowerCase() ?{to:DUTCH,data:"0x"+SEL_DUTCH_LISTING+encUint(f.id)} :{to:f.board,data:"0x"+SEL_GETORDERS+encUint(32)+encUint(1)+encUint(f.id)}); const res=await mc3Deep(reads); const stale=()=>{throw Error("an order in this route just changed — refreshing")}; for(let i=0;i<fills.length;i++){ const f=fills[i],raw=res[i]; if(!raw)stale(); const s=strip0x(raw),at=k=>BigInt("0x"+s.slice(k*64,(k+1)*64)); if(f.board.toLowerCase()===FLOOR.toLowerCase()){ if(s.length<11*64)stale(); const ad=k=>"0x"+s.slice(k*64+24,(k+1)*64); if(/^0x0{40}$/i.test(ad(0))||at(1))stale(); if(ad(4).toLowerCase()!==inA||ad(6).toLowerCase()!==outA)stale(); if(at(10)<f.pay)stale(); }else if(f.board.toLowerCase()===DUTCH.toLowerCase()){ if(s.length<11*64||at(0)===0n)stale(); if(at(9)<f.get)stale(); if(at(10)&&BigInt(Math.floor(Date.now()/1e3))>at(10))stale(); }else{ const wds=WORDS(f.board.toLowerCase()===SB2.toLowerCase()?1:0); if(s.length<(2+wds)*64)stale(); const w=k=>at(2+k),ad=k=>"0x"+s.slice((2+k)*64+24,(3+k)*64); if(w(1)!==1n)stale(); const v2=wds===11,iA=v2?7:2,iB=v2?9:4; if(ad(iA).toLowerCase()!==outA||ad(iB).toLowerCase()!==inA)stale(); if(w(iB+1)<f.pay)stale(); if(v2&&w(iA+1)<f.get)stale(); if(v2&&w(3)&&BigInt(Math.floor(Date.now()/1e3))>w(3))stale(); } } } async function preflightAsk(row,pay){ if(row.dutch){ let h; try{h=await rpc(C,[{to:DUTCH,data:"0x"+SEL_DUTCH_LISTING+encUint(row.id)},L])} catch{throw Error("listing could not be read — refresh and retry")} const s=strip0x(h); if(s.length<640)throw Error("listing could not be read — refresh and retry"); if(BigInt("0x"+s.slice(0,64))===0n)throw Error("listing is closed — refresh and retry"); return; } let h; try{h=await rpc(C,[{to:row.board, data:"0x"+SEL_GETORDERS+encUint(32)+encUint(1)+encUint(row.id)},L])} catch{throw Error("order could not be read — refresh and retry")} const s=strip0x(h),wds=WORDS(row.v2); if(s.length<(2+wds)*64)throw Error("order could not be read — refresh and retry"); const at=k=>BigInt("0x"+s.slice(128+k*64,128+(k+1)*64)); const ad=k=>"0x"+s.slice(128+k*64+24,128+(k+1)*64); if(at(1)!==1n)throw Error("order is no longer active — refresh and retry"); const iA=row.v2?7:2,iB=row.v2?9:4; if(ad(iA).toLowerCase()!==row.tA.toLowerCase() ||ad(iB).toLowerCase()!==row.tB.toLowerCase()) throw Error("order changed — refresh and retry"); if(at(iA+1)>row.aA||at(iB+1)>row.aB)throw Error("order changed — refresh and retry"); if(pay!==undefined&&at(iB+1)<pay)throw Error("order partly taken — refresh and retry"); } const SEL_ORDER_FIXED="bcdb7936",SEL_ORDER_DUTCH="bbd94e1a",SEL_CHECKPOINT="a972985e"; const BOARDS=()=>[{a:SB2,v2:1},{a:SB1,v2:0}].filter(b=>b.a!==ZERO); const WORDS=b=>b?11:6; let bookSeq=0,bookRows=[],candCache={},bkOpen=LS.bk!=="0",bookTrunc=false; let busy=0; if(LS.wm==="weth"||LS.wm==="auto")ethMode.value=LS.wm; ethMode.onchange=()=>{LS.wm=ethMode.value}; const wantsUnwrap=()=>ethMode.value!=="weth"; const planWethPayment=async need=>{ let w=0n,e=0n; try{ const[wh,eh]=await Promise.all([ rpc(C,[{to:WETH,data:"0x"+SEL_BALANCEOF+encAddr(account)},L]), rpc("eth_getBalance",[account,L])]); w=BigInt(wh);e=BigInt(eh); }catch{return{rail:"weth",wrap:0n,w,e}} if(w>=need)return{rail:"weth",wrap:0n,w,e}; if(e>=need)return{rail:"eth",wrap:0n,w,e}; let gas=0n; try{gas=BigInt(await rpc("eth_gasPrice",[]))*400000n}catch{} const spendable=e>gas?e-gas:0n; if(w+spendable>=need)return{rail:"weth",wrap:need-w,w,e}; return{rail:"short",wrap:0n,w,e}; }; const assertFunded=async(token,need,meta)=>{ if(!need)return; let have; try{ have=token===ZERO ?BigInt(await rpc("eth_getBalance",[account,L])) :BigInt(await rpc(C,[{to:token,data:"0x"+SEL_BALANCEOF+encAddr(account)},L])); }catch{return} if(have>=need)return; const m=meta||known(token)||{sym:token===ZERO?"ETH":"tokens",dec:18}; throw Error(`Not enough ${m.sym} to fill: it costs ${trimAmt(need,m.dec)} ` +`and you hold ${trimAmt(have,m.dec)}.`); }; const known=a=>TOKENS.find(t=>t.addr.toLowerCase()===a.toLowerCase()); const sm=i=>i.replace('width="20" height="20"','width="15" height="15"'); const escan=(a,id)=>!isAddr(a)?"":`<a href="${chainCfg().explorer}/${id!=null?`nft/${a}/${id}`:`token/${a}`}" target="_blank" rel="noreferrer">${a.slice(0,6)}…${a.slice(-4)} ↗</a>`; const SEL_CONTRACTURI="e8a3d485"; const okDataImg=u=>typeof u==="string"&&u.length<=60000 &&/^data:image\/[a-z.+-]{2,20};base64,[A-Za-z0-9+/=]+$/i.test(u); const imgFromContractURI=raw=>{ const P="data:application/json;base64,"; const u=String(raw||"").trim(); if(!u.startsWith(P)||u.length>400000)return ""; let txt; try{ const bin=atob(u.slice(P.length)); txt=new TextDecoder().decode(Uint8Array.from(bin,c=>c.charCodeAt(0))); }catch{return ""} let j;try{j=JSON.parse(txt)}catch{return ""} return okDataImg(j&&j.image)?j.image:""; }; const capObj=(o,max)=>{const ks=Object.keys(o); for(let i=0;i<ks.length-max;i++)delete o[ks[i]]}; const capMap=(m,max)=>{while(m.size>max)m.delete(m.keys().next().value)}; const LOGO_MAX=192,CARD_MAX=64,META_MAX=256; const logoCache={}; async function loadLogos(addrs){ const want=[...new Set(addrs.map(a=>a.toLowerCase()))] .filter(a=>a!==ZERO&&!known(a)&&logoCache[a]===undefined); if(!want.length)return; let raw=[]; if(TOKENLIST!==ZERO)raw=await cfgBatch(want.map(a=>({to:TOKENLIST,data:"0x"+SEL_LOGOOF+encAddr(a)}))).catch(()=>[]); want.forEach((a,i)=>{let u="";try{if(raw[i])u=safeDataUrl(decodeString(raw[i]))}catch{}logoCache[a]=u}); capObj(logoCache,LOGO_MAX); const self=want.filter(a=>!logoCache[a]); if(!self.length)return; const cu=await mc3Deep(self.map(a=>({to:a,data:"0x"+SEL_CONTRACTURI}))).catch(()=>[]); self.forEach((a,i)=>{try{ const u=imgFromContractURI(cu[i]?decodeString(cu[i]):""); if(u)logoCache[a]=u; }catch{}}); } async function leg(addr,amt,isNft,meta){ const k=known(addr); if(isNft)return{ic:genIcon("#"),txt:`#${amt}`,ok:0,link:escan(addr,amt)}; const raw=meta&&meta.sym?meta:await tokMeta(addr),m={...raw,sym:safeSym(raw.sym)}; const lg=k?"":logoCache[addr.toLowerCase()]; const ic=k?sm(k.icon):lg?`<img src="${lg}" width="15" height="15">`:genIcon(m.sym); return{ic,txt:`${trimAmt(amt,m.dec)} ${k?safeSym(k.sym):'“'+m.sym+'”'}`,ok:!!k, sym:k?safeSym(k.sym):m.sym,dec:m.dec,link:addr===ZERO?"ETH":escan(addr)}; } let floorSeq=0; nftId.addEventListener("input",()=>{syncOrderType();showFloor();render()}); async function showFloor(){ const el=document.getElementById("floorNote");if(!el)return; const side=isNft(fromSel.value)?fromSel.value:isNft(toSel.value)?toSel.value:null; const live=tab==="book"&&side!==null&&!nftId.value.trim(); if(!live){el.classList.add("hide");el.textContent="";return} const seq=++floorSeq,t=TOKENS[side]; el.classList.remove("hide");el.textContent="Checking floor bids…"; const oth=isNft(fromSel.value)?toSel.value:fromSel.value; const qt=isNft(oth)?ZERO:(TOKENS[oth].addr===ZERO?WETH:TOKENS[oth].addr); const rows=await collectionFloor(t.addr,qt).catch(()=>[]); if(seq!==floorSeq)return; if(!rows.length){el.textContent="No standing bids on this collection.";return} const best=rows[0],q=known(best.quote); el.textContent=`${rows.length} collection bid${rows.length>1?"s":""} · best pays ` +`${trimAmt(best.price/best.initial,q?q.dec:best.qDec??18)} ${q?q.sym:safeSym(best.qSym||"?")} for any ${t.sym}`; } function decBidRows(hex){ const out=[]; try{ const h=strip0x(hex),bytes=h.length/2; const word=b=>{if(b<0||b+32>bytes)throw 0;return BigInt("0x"+h.slice(b*2,(b+32)*2))}; const num=b=>{const v=word(b);if(v>0xffffffffffffffffn)throw 0;return Number(v)}; const arr=num(0); if(arr<64||arr+32>bytes)throw 0; const n=num(arr),base=arr+32; if(n>256||base+n*32>bytes)throw 0; for(let i=0;i<n;i++){ try{ const row=base+num(base+i*32); if(row+17*32>bytes)throw 0; const f=k=>word(row+k*32); const txt=k=>{const p=row+num(row+k*32),len=num(p); if(p+32+len>bytes)throw 0; return safeSym(new TextDecoder().decode(hexToBytes(h.slice((p+32)*2,(p+32+Math.min(len,1024))*2))))}; const dec=k=>{const v=num(row+k*32);return v>0&&v<=37?v-1:null}; out.push({ id:f(0), bidder:"0x"+h.slice((row+1*32+12)*2,(row+2*32)*2), token:"0x"+h.slice((row+2*32+12)*2,(row+3*32)*2), quote:"0x"+h.slice((row+3*32+12)*2,(row+4*32)*2), isNFT:f(4)!==0n, anyId:f(5)!==0n, remaining:f(7), initial:f(8), price:f(9), proceeds:f(10), expiry:f(12), tDec:dec(13),qDec:dec(14), tSym:txt(15),qSym:txt(16), }); }catch{continue} } return out; }catch{return out} } async function collectionFloor(collection,quote=ZERO){ if(FLOORVIEW===ZERO||FLOOR===ZERO)return[]; const data="0x"+SEL_COLL_BIDS+encAddr(FLOOR)+encAddr(collection)+encAddr(quote) +encUint(0)+encUint(16)+encUint(256); const raw=await rpc(C,[{to:FLOORVIEW,data},L]).catch(()=>null); if(!raw)return[]; const rows=decBidRows(raw); return rows.filter(r=>r.anyId&&r.remaining>0n) .sort((a,b)=>{const l=b.proceeds*a.remaining,r=a.proceeds*b.remaining;return l>r?1:l<r?-1:0}); } async function floorCandidates(tokenIn,tokenOut,block){ const bin=tokenIn===ZERO?WETH:tokenIn,bout=tokenOut===ZERO?WETH:tokenOut; const data="0x"+SEL_FLOOR_CANDS+encAddr(FLOOR)+encAddr(bin)+encAddr(bout) +encUint(0)+encUint(64)+encUint(512); const raw=await rpc(C,[{to:FLOORVIEW,data},block]).catch(()=>null); if(!raw)return[]; const out=[]; for(const b of decBidRows(raw)){ if(b.isNFT||b.remaining<=0n||b.initial<=0n||b.price<=0n||b.proceeds<=0n)continue; if(b.token.toLowerCase()!==bin.toLowerCase())continue; if(b.quote.toLowerCase()!==bout.toLowerCase())continue; out.push({ id:b.id,board:FLOOR,floor:1,v2:0,dutch:0, maker:b.bidder,pf:1,exp:b.expiry,nA:0,nB:0,cp:ZERO, tA:b.quote,aA:b.proceeds,sA:b.qSym,dA:b.qDec, tB:b.token,aB:b.remaining,sB:b.tSym,dB:b.tDec, price:b.price,initial:b.initial, }); } return out; } function floorGet(o,pay){return o.price*pay/o.initial} function floorPay(o,want){ if(want<=0n)return 0n; const g=(want*o.initial+o.price-1n)/o.price; return g>o.aB?o.aB:g; } function rowGet(o,pay){return o.floor?floorGet(o,pay):o.aA*pay/o.aB} function decViewPage(hex){ const h=strip0x(hex),bytes=h.length/2; const word=b=>{if(b<0||b+32>bytes)throw Error("bad lens page");return BigInt("0x"+h.slice(b*2,(b+32)*2))}; const addr=(b)=>"0x"+h.slice((b+12)*2,(b+32)*2); const arr=Number(word(0)),next=word(32); if(!Number.isSafeInteger(arr)||arr<64||arr+32>bytes)throw Error("bad lens offset"); const n=Number(word(arr)),head=arr+32; if(!Number.isSafeInteger(n)||n>256||head+n*32>bytes)throw Error("bad lens length"); const text=(base,k)=>{const off=Number(word(base+k*32)),p=base+off,len=Number(word(p)); if(!Number.isSafeInteger(off)||!Number.isSafeInteger(len)||p+32+len>bytes)throw Error("bad lens string"); const cap=Math.min(len,1024),a=new Uint8Array(cap); for(let i=0;i<cap;i++)a[i]=parseInt(h.slice((p+32+i)*2,(
0x68575b07…67c7·#25,913,376·0xba4a6884…556355
{ if(e.target.tagName!=="A")return; e.preventDefault(); const f=TOKENS[fromSel.value]; if(!f)return; let reserve=0n; if(f.addr===ZERO){ const g=lqMode?lqReserveGas():tab==="swap"?900000n:tab==="book"?350000n :+dly.value?(tipCk.checked?200000n+TIP_GAS:200000n):30000n; try{reserve=BigInt(await rpc("eth_gasPrice",[]))*g}catch{} if(reserve<=0n)reserve=GAS_FLOOR*g; } const max=fromBalance>reserve?fromBalance-reserve:0n; if(lqMode&&lqFillMax(f,max))return; amt.value=formatUnits(max,f.dec); fitFont(amt); if(lqMode){lqPreviewSoon();return} setMode("in"); update(); }); let seq=0; const REVERTS={ "1ab7da6b":"That took too long and the deadline passed, so nothing was placed. Try again — you were not charged for an order.", "98c2c368":"The order was refused as malformed. Check the amounts and the expiry.", "7939f424":"The token transfer was refused — usually a missing or spent approval. Try again and approve first.", "90b8ec18":"The token transfer was refused by the token itself.", "3e3f8f73":"The approval was refused by the token.", "6fc70753":"That score name is not a valid label.", "9c9cef39":"That score is too long to record.", "7b2002ce":"Somebody already claimed that name — play again for a new one.", "845eadf1":"The score minter is not holding arcade.wei.", "b12d13eb":"Ether could not be sent to the recipient — a contract there may not accept it.", "ab143c06":"That call re-entered and was stopped.", }; const explain=e=>{ if(e?.noWallet)return NO_WALLET; const str=v=>typeof v==="string"?v :v&&typeof v.data==="string"?v.data :v&&typeof v.message==="string"?v.message:""; const s=str(e?.data)||str(e)||str(e?.error)||""; const m=/0x([0-9a-fA-F]{8})\b/.exec(s); const hit=m&&REVERTS[m[1].toLowerCase()]; if(hit)return hit; if(!s)return "The wallet returned an error with no message"+(e&&e.code!==undefined?" (code "+e.code+")":"")+"."; return "Error: "+s.slice(0,110); }; const err=e=>stat.textContent=isRejection(e)?"":explain(e); const settle=async(tx,quiet)=>{ if(!tx){stat.textContent="Done";return} if(!/^0x[0-9a-f]{64}$/i.test(tx))throw Error("bad tx"); stat.innerHTML=txLink(tx,"Sent"); try{await waitTx(tx)}catch(e){sFail();throw e} stat.innerHTML=txLink(tx,"Done");if(!quiet)sGot()}; const txLink=(h,label)=>`${label} <a href="${chainCfg().explorer}/tx/${h}" target="_blank" rel="noreferrer">${h.slice(0,10)}…${h.slice(-6)}</a>`; let autoQ=0,rankOpen=false; async function update(auto){ if(!auto)autoQ=0; syncIcons(); fcSync(); cbSync(); const my=++seq; quoting=false; if(wnMode||lnMode||pvMode)return; if(lqMode)return loadLq(); if(tab==="book")return render(); if(tab==="send")return updateSend(); last=null; rate.textContent=""; if(!lqMode&&!lnMode)rcvHdr.textContent="You receive"; render(); const f=TOKENS[fromSel.value], t=TOKENS[toSel.value]; if(!f||!t)return; if(f.addr===t.addr){stat.textContent="Pick different tokens.";return} stat.textContent=""; const isIn=mode==="in"; const inEl=isIn?amt:outAmt, outEl=isIn?outAmt:amt, inTok=isIn?f:t; outEl.value=""; fitFont(outEl); if(!inEl.value.trim())return; let swapAmt; try{swapAmt=parseUnits(inEl.value.trim(),inTok.dec)} catch(e){const q=inEl.value.trim(); if(q&&!/\.$/.test(q))stat.textContent=/^unclear comma/.test(e.message)?e.message:/decimals/.test(e.message)?`${inTok.sym} has ${inTok.dec} decimals`:"Invalid amount"; return} if(swapAmt<=0n)return; const rv=rc.value.trim(); let rcv=""; if(rv){ const fail=m=>{rc.classList.add("bad");rcvEl.textContent=""; outEl.value="";fitFont(outEl);rankBox.textContent="";rankBox.classList.add("hide");stat.textContent=m}; const bad=rcErr(rv); if(bad){fail(bad);return} if(isAddr(rv))rcv=rv; else{ rcvEl.textContent="resolving..."; try{rcv=await rcvOf(rv)} catch(e){if(my!==seq)return;fail(e&&e.offchain?e.message:"Name not registered");return} if(my!==seq)return; if(!rcv){fail("Name not registered");return} } rcvEl.textContent=rcv; }else rcvEl.textContent=""; rc.classList.remove("bad"); if(!rv&&f.addr===WETH&&t.addr===ZERO){ outEl.value=inEl.value;fitFont(outEl); rate.textContent=`1 ${f.sym} = 1 ${t.sym}`; last={callData:"0x2e1a7d4d"+encUint(swapAmt),msgValue:0n,isIn:true,amountIn:swapAmt,from:f,to:WETH,recipient:"",exp:Date.now()+QUOTE_TTL}; render();return; } if(!rv&&f.addr===ZERO&&t.addr===WETH){ outEl.value=inEl.value;fitFont(outEl); rate.textContent=`1 ${f.sym} = 1 ${t.sym}`; last={callData:"0xd0e30db0",msgValue:swapAmt,isIn:true,amountIn:swapAmt,from:f,to:WETH,recipient:"",exp:Date.now()+QUOTE_TTL}; render();return; } const pct=Math.min(10,Math.max(.01,+slip.value||.5)); const slipBps=BigInt(Math.round(pct*100)); const deadline=BigInt(Math.floor(Date.now()/1e3)+1800); outEl.value="...";fitFont(outEl);quoting=true;render(); const acct=account; const gaveUp0=mcGaveUp,ok0=mcOk,failed0=mcFailed; try{ const a=encAddr(rcv||acct||ZERO),rf=encAddr(acct||ZERO),tl=encBool(!isIn)+encAddr(f.addr)+encAddr(t.addr)+encUint(swapAmt)+encUint(slipBps)+encUint(deadline); const _rq=[]; const Q=(x,d,u,v,S,sb,mv)=>{_rq.push({to:ZQUOTER,data:"0x"+x+d,u,v,S,sb,mv});return _rq.length-1}; let r=null; const candKey=f.addr.toLowerCase()+":"+t.addr.toLowerCase(); const cachedCandidates=candCache[candKey]; const quoteBlock=cachedCandidates&&Date.now()-cachedCandidates.at<15000 ?cachedCandidates.block:await blockNow(); const candJob=acct?swapCandidates(f.addr,t.addr,quoteBlock).catch(()=>{ const x=[];x.unavailable=true;return x }):Promise.resolve([]); const gasPriceJob=acct?rpc("eth_gasPrice",[]).then(BigInt).catch(()=>0n):Promise.resolve(0n); let venueLost=false; const v4Job=isIn?quoteV4Pools(f,t,swapAmt,quoteBlock).catch(()=>{venueLost=true;return null}):Promise.resolve(null); const preJob=isIn?quotePrecision(f,t,swapAmt,PROUTE,quoteBlock).catch(()=>{venueLost=true;return null}):Promise.resolve(null); let curveOnlyOut=false; const exactOutSafe=y=>{if(isIn||!y.sources||!y.sources.includes(SRC_CURVE))return true;curveOnlyOut=true;return false}; const pick=y=>{if(!y)return;if(!exactOutSafe(y))return;const v=isIn?y.best.amountOut:y.best.amountIn;if(!v)return; if(!r||(isIn?v>r.best.amountOut:v<r.best.amountIn))r=y}; const tl0=encBool(!isIn)+encAddr(f.addr)+encAddr(t.addr)+encUint(swapAmt)+encUint(0n)+encUint(deadline); const jobs=[ [Q("e7798987",a+tl,0,4,0,0,6)], [Q("e453166e",a+rf+tl0,4,9),Q("e453166e",a+rf+tl,4,9)], [Q("4c464f59",a+tl0,8,13),Q("4c464f59",a+tl,8,13)]]; let splitSlip=0n; if(isIn){ const w=slipBps*3n,SS=w<150n?150n:w>500n?500n:w; splitSlip=SS; const ps=encAddr(f.addr)+encAddr(t.addr)+encUint(swapAmt)+encUint(SS)+encUint(deadline); for(const x of["892af013","85f86a90"])jobs.push([Q(x,a+ps,4,8,1,SS)]); } const tailStep=swapAmt/100n||1n,tailAmt=swapAmt>tailStep?swapAmt-tailStep:0n; const tailJobs=[]; if(tailAmt){ const z=encBool(!isIn)+encAddr(f.addr)+encAddr(t.addr)+encUint(tailAmt)+encUint(0n)+encUint(deadline); tailJobs.push(Q("e453166e",a+rf+z,4,9),Q("4c464f59",a+z,8,13)); if(isIn)for(const x of["892af013","85f86a90"]){ const ps=encAddr(f.addr)+encAddr(t.addr)+encUint(tailAmt)+encUint(splitSlip)+encUint(deadline); tailJobs.push(Q(x,a+ps,4,8,1,splitSlip)); } } const refAmt=swapAmt/REF_N; const iRef=refAmt>0n ?Q("e453166e",a+rf+encBool(!isIn)+encAddr(f.addr)+encAddr(t.addr)+encUint(refAmt)+encUint(0n)+encUint(deadline),4,9) :-1; const mcRaw=(await Promise.all(mc3Split(_rq,QPAIR) .map(g=>mc3Deep(g.map(q=>({to:q.to,data:q.data})),quoteBlock)))).flat(); if(my!==seq)return; const dec=_rq.map((q,i)=>{try{return mcRaw[i]?decQ(mcRaw[i],q.sb||slipBps,!isIn,q.u,q.v,q.S,q.mv):null}catch{return null}}); for(const g of jobs)pick(g.length>1?merge(dec[g[0]],dec[g[1]]):dec[g[0]]); const zr=r; if(isIn&&!(dec[jobs[1][0]]||dec[jobs[1][1]]||dec[jobs[2][0]]||dec[jobs[2][1]])){ const hq=await hubRoute(f,t,swapAmt,slipBps,deadline,rcv||acct||ZERO,acct||ZERO,quoteBlock).catch(()=>null); if(my!==seq)return; if(hq)pick(hq); } let tail=null; for(const i of tailJobs){const y=dec[i];if(!y)continue; const v=isIn?y.best.amountOut:y.best.amountIn; if(v&&(!tail||(isIn?v>tail.best.amountOut:v<tail.best.amountIn)))tail=y; } if(my!==seq)return; const v4q=await v4Job; let v4c=null; if(v4q&&v4q.amountOut>0n){ const v4Min=v4q.amountOut-(v4q.amountOut*slipBps)/10000n; const p=v4q.pool; v4c={best:{source:4,amountIn:v4q.amountIn,amountOut:v4q.amountOut,feeBps:p.fee>=8388608n?0n:BigInt(p.fee)/100n}, callData:"0x"+SEL_V4SWAP+encPoolKey(p)+encBool(p.zeroForOne)+encUint(v4q.amountIn) +encUint(v4Min)+encAddr(rcv||acct||ZERO)+encUint(deadline), fundedCallData:null,amountLimit:v4Min,msgValue:f.addr===ZERO?swapAmt:0n, to:V4PORT,spender:f.addr===ZERO?null:V4PORT}; if(!r)r=v4c; } const pq=await preJob; if(my!==seq)return; let pc=null; if(pq&&pq.out>0n){ const pMin=pq.out-(pq.out*slipBps)/10000n; const pNative=pq.tokenIn===ZERO; const pWethIn=pNative&&(pq.unwrap||0n)>0n; const pTo=rcv||acct||ZERO; const pFund=pWethIn?WETH:pq.tokenIn; const routeData=pWethIn ?encPRouteWeth(pq.pools,pq.tokenOut,swapAmt,pMin,pTo) :encPRoute(pq.pools,pq.tokenIn,pq.tokenOut,swapAmt,pMin,pTo); const routedLeg=encSnwap(pFund,swapAmt,pTo,pq.tokenOut,pMin,PROUTE,routeData); const checkpointLeg=encSnwap(ZERO,0n,acct||ZERO,ZERO,0n,PROUTE, "0x"+SEL_PCHECKPOINT+encAddr(pFund)+strip0x(keccak(hexToBytes(routeData)))+encAddr(acct||ZERO)); const pCall=(pNative&&!pWethIn)?routedLeg:encCalls([checkpointLeg,routedLeg]); const pFunded=(pNative&&!pWethIn)?null:encCalls([ checkpointLeg, encSweep(pFund,swapAmt,PROUTE), encSnwap(ZERO,0n,pTo,pq.tokenOut,pMin,PROUTE,routeData), ]); pc={best:{source:SRC_PRECISION,amountIn:swapAmt,amountOut:pq.out,feeBps:pq.fee/100n}, callData:pCall, fundedCallData:pFunded,amountLimit:pMin,msgValue:(pNative&&!pWethIn)?swapAmt:0n, wrap:pq.wrap||0n,outDiffers:!!pq.outDiffers,poolOut:pq.tokenOut, to:null,spender:(pNative&&!pWethIn)?null:ZROUTER,pools:pq.pools}; if(!r)r=pc; } if(!r&&curveOnlyOut)throw Object.assign(Error(`exact-output is unavailable for this pair — enter an amount to PAY instead`),{exactOut:1}); if(!r)throw Error("bad quote"); if(r.best.amountIn<=0n||r.best.amountOut<=0n||(!isIn&&r.amountLimit<r.best.amountIn))throw Error("bad quote"); let dc=null; if(isIn&&CHAIN_ID===4663&&f.addr!==ZERO&&t.addr!==ZERO){ const dq=await deepQuote(f.addr,t.addr,swapAmt,r&&r.best?r.best.amountOut:0n); if(my!==seq)return; if(dq&&dq.out>0n){ const dMin=dq.out-(dq.out*slipBps)/10000n; const dTo=rcv||acct||ZERO; const leg="0x"+SEL_SWAPDEEP+encAddr(dTo)+encAddr(dq.t0)+encAddr(dq.t1)+encUint(dq.epoch) +strip0x(dq.order)+encUint(dq.isBid?1:0)+encUint(swapAmt)+encUint(dMin)+encUint(deadline); dc={best:{source:SRC_DEEP,amountIn:dq.inUsed,amountOut:dq.out,feeBps:0n}, callData:encCalls([leg,encSweep(f.addr,0n,dTo)]), fundedCallData:null,amountLimit:dMin,msgValue:0n, wrap:0n,outDiffers:false,poolOut:t.addr,to:null,spender:ZROUTER}; } } for(const c of [v4c,pc,dc]){ if(!c||c===r)continue; if(isIn?c.best.amountOut>r.best.amountOut:c.best.amountIn<r.best.amountIn)r=c; } let hybrid=null,bookIncomplete=false,bookUnavailable=false,plannerBounded=false,rank=null; if(acct){ const candidates=await candJob; bookIncomplete=!!candidates.incomplete; bookUnavailable=!!candidates.unavailable; if(my!==seq)return; const quoteRem=async(x,eo)=>{ const qs=[],groups=[],add=(sel,data,u,v,S,sb)=>{ qs.push({data:"0x"+sel+data,u,v,S,sb});return qs.length-1}; const z0=encBool(eo)+encAddr(f.addr)+encAddr(t.addr)+encUint(x)+encUint(0n)+encUint(deadline); const zs=encBool(eo)+encAddr(f.addr)+encAddr(t.addr)+encUint(x)+encUint(slipBps)+encUint(deadline); groups.push([add("e453166e",a+rf+z0,4,9),add("e453166e",a+rf+zs,4,9)]); groups.push([add("4c464f59",a+z0,8,13),add("4c464f59",a+zs,8,13)]); if(!eo){ const w=slipBps*3n,ss=w<150n?150n:w>500n?500n:w; const ps=encAddr(f.addr)+encAddr(t.addr)+encUint(x)+encUint(ss)+encUint(deadline); for(const sel of["892af013","85f86a90"])groups.push([add(sel,a+ps,4,8,1,ss)]); } const raw=await mc3Deep(qs.map(q=>({to:ZQUOTER,data:q.data})),quoteBlock); const ds=qs.map((q,i)=>{try{return raw[i]?decQ(raw[i],q.sb||slipBps,eo,q.u,q.v,q.S):null}catch{return null}}); let best=null; for(const g of groups){ const y=g.length>1?merge(ds[g[0]],ds[g[1]]):ds[g[0]]; if(!y)continue; if(eo&&y.sources&&y.sources.includes(SRC_CURVE))continue; const v=eo?y.best.amountIn:y.best.amountOut; if(v&&(!best||(eo?v<best.best.amountIn:v>best.best.amountOut)))best=y; } return best; }; const gasValue=async wei=>{ const token=isIn?t.addr:f.addr; if(token===ZERO||token.toLowerCase()===WETH.toLowerCase())return wei; const eo=!isIn,ti=isIn?ZERO:token,to=isIn?token:ZERO; const z=encBool(eo)+encAddr(ti)+encAddr(to)+encUint(wei)+encUint(0n)+encUint(deadline); try{ const raw=await rpc(C,[{to:ZQUOTER,data:"0xe453166e"+a+rf+z},quoteBlock]); const q=decQ(raw,0n,eo,4,9); const v=isIn?q.best.amountOut:q.best.amountIn; if(v>0n)return v; }catch{} const ethSide=f.addr===ZERO||f.addr.toLowerCase()===WETH.toLowerCase(); const tokSide=t.addr===ZERO||t.addr.toLowerCase()===WETH.toLowerCase(); const inA=r.best.amountIn,outA=r.best.amountOut; if(inA>0n&&outA>0n){ if(isIn&&ethSide)return wei*outA/inA; if(!isIn&&tokSide)return wei*inA/outA; } return null; }; const tailDelta=swapAmt-tailAmt; const tailFits=!!tail&&r===zr; const seedOut=isIn&&tailFits&&r.best.amountOut>tail.best.amountOut ?r.best.amountOut-tail.best.amountOut:r.best.amountOut; const seedIn=!isIn&&tailFits&&r.best.amountIn>tail.best.amountIn ?r.best.amountIn-tail.best.amountIn:r.best.amountIn; let p=isIn ?planBookExactIn(candidates,swapAmt,seedOut,tailFits&&seedOut!==r.best.amountOut?tailDelta:r.best.amountIn) :planBookExactOut(candidates,swapAmt,seedIn,tailFits&&seedIn!==r.best.amountIn?tailDelta:r.best.amountOut); plannerBounded=!!(p&&p.bounded); let amm=null,ammFor=null; for(let round=0;p&&(isIn?p.ammIn:p.ammOut)&&round<2;round++){ const rem=isIn?p.ammIn:p.ammOut; amm=await quoteRem(rem,!isIn).catch(()=>null);ammFor=rem; if(!amm)break; const next=isIn ?planBookExactIn(candidates,swapAmt,amm.best.amountOut,rem) :planBookExactOut(candidates,swapAmt,amm.best.amountIn,rem); if(!next){p=null;break} const stable=(isIn?next.ammIn===p.ammIn:next.ammOut===p.ammOut); p=next;plannerBounded=plannerBounded||!!p.bounded;if(stable)break; } if(p){ const rem=isIn?p.ammIn:p.ammOut; if(!rem)amm=null; else if(!(amm&&ammFor===rem))amm=await quoteRem(rem,!isIn).catch(()=>null); if(!rem||amm){ const recipient=rcv||acct; const ammBudget=amm?(isIn?rem:amm.amountLimit):0n; const totalBudget=p.bookIn+ammBudget; const expectedIn=p.bookIn+(amm?amm.best.amountIn:0n); const expectedOut=p.bookOut+(amm?amm.best.amountOut:0n); const saving=isIn ?(expectedOut>r.best.amountOut?expectedOut-r.best.amountOut:0n) :(expectedIn<r.best.amountIn?r.best.amountIn-expectedIn:0n); let gasFloor=null; if(saving){ const gasPrice=await gasPriceJob; if(gasPrice){ const extraWei=gasPrice*(120000n+105000n*BigInt(p.fills.length)); gasFloor=await gasValue(extraWei); } } const pricedGas=gasFloor!==null&&gasFloor>0n; const wins=saving>0n&&pricedGas&&saving*10n>gasFloor*12n; if(my!==seq)return; if(wins){ const fp=encFillPlanAndSwap( p.fills,f.addr,t.addr,recipient,acct,deadline,ammBudget,amm?amm.callData:"0x" ); const minOut=isIn?p.bookOut+(amm?amm.amountLimit:0n):swapAmt; const snwapIn=totalBudget===0n?ZERO:f.addr; const planCall=encSnwap(snwapIn,totalBudget,recipient,t.addr,minOut,SWAPBOL,fp); const callData=f.addr===ZERO?planCall:encCalls([ encCheckpoint(SWAPBOL,f.addr,acct),planCall ]); let fundedCallData=null; if(f.addr!==ZERO){ fundedCallData=encPermit2Hybrid( amm?amm.callData:"0x",p.fills,f.addr,t.addr,recipient,acct,deadline,p.bookIn,p.bookOut ); } r={...r,best:{...r.best,amountIn:expectedIn,amountOut:expectedOut},callData, fills:p.fills,pools:undefined, fundedCallData,amountLimit:isIn?minOut:totalBudget,msgValue:f.addr===ZERO?totalBudget:0n, slipBps:amm&&amm.slipBps, to:null,spender:f.addr===ZERO?null:ZROUTER, pool:null,wrap:0n,outDiffers:false,poolOut:undefined}; hybrid=isIn ?{bookIn:p.bookIn,bookOut:p.bookOut,ammIn:p.ammIn} :{bookIn:p.bookIn,bookOut:p.bookOut,ammOut:p.ammOut}; plannerBounded=plannerBounded||!!p.bounded; } } } } const pin=lanePin(); if(!isIn&&r&&r.amountLimit>0n){ try{ await loadSolvers(); const lanes=(solverLanes||[]).filter(x=>!laneDown(x.name)&&SOLVER_SHAPES_OUT[x.name]); if(lanes.length&&solverFill&&solverExec){ const dl=new Promise(res=>setTimeout(()=>res(null),SOLVER_DEADLINE)); const ck=`o|${f.addr}|${t.addr}|${swapAmt}|${slipBps}`; const tried=await Promise.all(lanes.map(x=>Promise.race([ laneCached(`${x.name}|${ck}`, ()=>SOLVER_SHAPES_OUT[x.name](x.endpoint,f.addr,t.addr,swapAmt,Number(slipBps),SOLVER_DEADLINE,f.dec,t.dec)) .then(q=>q?{...q,lane:x}:null).catch(()=>null),dl]))); const laneCeil=c=>{const d=c.inp+(c.inp*slipBps)/10000n; return c.cap&&c.cap>0n?bmin(c.cap,d):d}; const chainCeil=r.amountLimit; let win=null; for(const c of tried){ if(!laneOk(c)||!c.inp)continue; const ceil=laneCeil(c); if(pin){ if(c.lane.name!==pin)continue; win={...c,ceil,forced:1};break; } if(ceil*(10000n+c.lane.handicapBps)>=chainCeil*10000n)continue; if(!win||ceil<win.ceil)win={...c,ceil}; } rank=[{n:"On-chain",f:chainCeil}]; for(const c of tried){ if(!laneOk(c)||!c.inp)continue; rank.push({n:c.lane.name,f:laneCeil(c)}); } rank.sort((a,b)=>a.f>b.f?1:a.f<b.f?-1:0); if(my!==seq)return; if(win){ const ethIn=f.addr===ZERO; r={best:{source:SRC_SOLVER,amountIn:win.inp,amountOut:swapAmt,feeBps:0n, lane:win.lane.name,forced:win.forced}, callData:"0x"+SEL_FILL+encAddr(win.target)+encAddr(win.spender)+encAddr(f.addr) +encUint(win.ceil)+encAddr(t.addr)+encUint(swapAmt)+encAddr(rcv||acct||ZERO) +encUint(256n)+encUint(BigInt(strip0x(win.data).length/2)) +strip0x(win.data)+"0".repeat((64-(strip0x(win.data).length%64))%64), fundedCallData:null,amountLimit:win.ceil, msgValue:ethIn?win.ceil:0n, to:solverFill,spender:ethIn?null:solverFill}; hybrid=null; } } }catch{} } const pin2=lanePin(); if(isIn&&r&&r.amountLimit>0n){ try{ await loadSolvers(); const lanes=(solverLanes||[]).filter(x=>!laneDown(x.name)); if(lanes.length&&solverFill&&solverExec){ const deadline_=new Promise(res=>setTimeout(()=>res(null),SOLVER_DEADLINE)); const ck=`${f.addr}|${t.addr}|${swapAmt}|${slipBps}`; const tried=await Promise.all(lanes.map(x=>Promise.race([ laneCached(`${x.name}|${ck}`, ()=>SOLVER_SHAPES[x.name](x.endpoint,f.addr,t.addr,swapAmt,Number(slipBps),SOLVER_DEADLINE,f.dec,t.dec)) .then(q=>q?{...q,lane:x}:null).catch(()=>null), deadline_]))); const chainFloor=r.amountLimit; let win=null; for(const c of tried){ if(!laneOk(c)||!c.out)continue; const floor=c.out-(c.out*slipBps)/10000n; if(pin2){ if(c.lane.name!==pin2)continue; win={...c,floor,forced:1}; break; } if(floor*10000n<=chainFloor*(10000n+c.lane.handicapBps))continue; if(!win||floor>win.floor)win={...c,floor}; } rank=[{n:"On-chain",f:chainFloor}]; for(const c of tried){ if(!laneOk(c)||!c.out)continue; rank.push({n:c.lane.name,f:c.out-(c.out*slipBps)/10000n}); } rank.sort((a,b)=>a.f<b.f?1:a.f>b.f?-1:0); if(my!==seq)return; if(win){ const ethIn=f.addr===ZERO; r={best:{source:SRC_SOLVER,amountIn:swapAmt,amountOut:win.out,feeBps:0n,lane:win.lane.name,forced:win.forced}, callData:"0x"+SEL_FILL+encAddr(win.target)+encAddr(win.spender)+encAddr(f.addr) +encUint(swapAmt)+encAddr(t.addr)+encUint(win.floor)+encAddr(rcv||acct||ZERO) +encUint(256n)+encUint(BigInt(strip0x(win.data).length/2)) +strip0x(win.data)+"0".repeat((64-(strip0x(win.data).length%64))%64), fundedCallData:null,amountLimit:win.floor, msgValue:ethIn?swapAmt:0n, to:solverFill,spender:ethIn?null:solverFill}; hybrid=null; } } }catch{} } if(my!==seq)return; const finalIn=r.best.amountIn; const finalOut=r.best.amountOut; if(isIn){outAmt.value=trimAmt(finalOut,t.dec);fitFont(outAmt)} else{amt.value=trimAmt(finalIn,f.dec);fitFont(amt)} const rx=(+formatUnits(finalOut,t.dec))/(+formatUnits(finalIn,f.dec)); const s=r.best.source,raw=SOURCES[s]||"?"; const src=r.best.source===SRC_SOLVER?`${r.best.lane} solver${r.best.forced?" \u00b7 pinned":""}`:hybrid?bookShare(hybrid,raw):(s===3||s===4||s===SRC_PRECISION)&&r.best.feeBps?`${raw}\u00a0${Number(r.best.feeBps)/100}%`:raw; rate.textContent=isFinite(rx)?`1 ${f.sym} ≈ ${rx>=1?rx.toLocaleString("en-US",{maximumFractionDigits:4}):rx.toPrecision(4)} ${t.sym} · ${src}`:""; rankBox.textContent=""; if(rank&&rank.length>1){ const top=rank[0].f; const worse=isIn?"-":"+"; const used=r.best.source===SRC_SOLVER?r.best.lane:"On-chain"; for(const x of rank){ const row=document.createElement("i"); if(x.n===used)row.className="w"; const pinNow=lanePin(); if(x.n===pinNow)row.className+=" pin"; row.title=x.n===pinNow?"Pinned \u2014 click again for the best floor" :x.f===top&&x.n!==used?"Best floor \u2014 click to use it":"Use this venue"; const pick=()=>{const on=lanePin()===x.n;laneOverride=on?null:x.n;laneOverrideFor=on?"":laneScope();update()}; row.onclick=pick; row.setAttribute("role","button"); row.tabIndex=0; row.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();pick()}}; const n=document.createElement("span"); n.textContent=safeSym(x.n).slice(0,16); const v=document.createElement("u"); const gap=top>0n?Number((x.f>top?x.f-top:top-x.f)*10000n/top)/100:0; v.textContent=`${sigAmt(x.f,isIn?t.dec:f.dec)}${gap>0.004?` \u00b7 ${worse}${gap.toFixed(2)}%`:""}`; row.append(n,v); rankBox.appendChild(row); } } rankBox.classList.toggle("hide",!(rankOpen&&rankBox.firstChild)); const paysSym=r.poolOut===ZERO?"ETH":"WETH"; rcvHdr.textContent=r.outDiffers?`You receive \u2014 as ${paysSym}`:"You receive"; if(r.outDiffers&&rate.textContent)rate.textContent+=` \u00b7 pays ${paysSym}`; rate.title=""; if(venueLost||mcGaveUp>gaveUp0||(mcFailed>failed0&&mcOk===ok0)) rate.textContent+=" \u00b7 some venues unreachable"; if(isIn)rate.textContent+=` · Min ${trimAmt(r.amountLimit,t.dec)} ${t.sym}`; else{ const exactMax=formatUnits(r.amountLimit,f.dec); rate.textContent+=` · Max ${maxAmt(r.amountLimit,f.dec)} ${f.sym}`; rate.title=`Exact maximum: ${exactMax} ${f.sym}`; } if(r.slipBps>slipBps)rate.textContent+=` · Split — bound widened to ${Number(r.slipBps)/100}%`; if(bookIncomplete)rate.textContent+=" · Book scan capped"; if(plannerBounded)rate.textContent+=" · Book split approximate"; if(bookUnavailable)rate.textContent+=" · AMM only (books offline)"; if(pq&&pq.capped)rate.textContent+=` · Precision scan capped (${LQ_SCAN})`; if(isIn&&f.launched&&(t.addr===ZERO||t.addr.toLowerCase()===WETH.toLowerCase())){ const floorOut=await quoteFloor(f.addr,swapAmt,quoteBlock); if(my!==seq)return; if(floorOut>0n){ if(floorOut>finalOut){ rate.textContent+=` \u00b7 Floor pays more: ${trimAmt(floorOut,18)} ETH`; rate.title=`This coin can be burned back to the launcher for its share of the ether ` +`locked in its pool - ${trimAmt(floorOut,18)} ETH, against ${trimAmt(finalOut,18)} from selling. ` +`Redeem through the launcher at ${PLAUNCH} to take the larger.`; }else{ rate.textContent+=` \u00b7 Floor ${trimAmt(floorOut,18)} ETH`; rate.title=`The ether backing this amount if it were redeemed instead of sold. ` +`Selling pays more right now.`; } } } let refQ=iRef>=0&&dec[iRef]?dec[iRef].best:null; let refPair=null; if(!refQ&&isIn&&pq&&pq.out>0n&&refAmt>0n){ try{ const small=await quotePrecision(f,t,refAmt,PROUTE,quoteBlock); if(my!==seq)return; if(small&&small.out>0n)refPair={amountOut:small.out}; }catch{} } if(my!==seq)return; const impSrc=refQ||refPair; const imp=impSrc?impactBps(isIn?finalOut:finalIn,isIn?impSrc.amountOut:impSrc.amountIn,!isIn):null; const mkt=impSrc?(isIn?impSrc.amountOut:impSrc.amountIn)*REF_N:0n; const lossTok=imp!==null&&mkt?(isIn?(mkt>finalOut?mkt-finalOut:0n):(finalIn>mkt?finalIn-mkt:0n)):0n; if(imp!==null&&imp>=IMPACT_HIDE){ const pctTxt=(Number(imp)/100).toFixed(2); rate.textContent+=` · Impact ${pctTxt}%`; if(imp>=IMPACT_WARN)stat.textContent=`High price impact: ${pctTxt}% — about ${trimAmt(lossTok,isIn?t.dec:f.dec)} ${isIn?t.sym:f.sym} worse than the market rate.`; } if(rankBox.firstChild){ const car=document.createElement("span"); car.className="rk"; car.textContent="\u25be"; car.title="Compare venues"; car.setAttribute("role","button"); car.tabIndex=0; car.setAttribute("aria-expanded",rankBox.classList.contains("hide")?"false":"true"); const carToggle=()=>{const shut=rankBox.classList.tog
0x68575b07…67c7·#25,913,375·0xf30e1022…662254
on poly(msg,key){ const P=(1n<<130n)-5n; let r=0n,sk=0n; for(let i=15;i>=0;i--)r=(r<<8n)|BigInt(key[i]); r&=0x0ffffffc0ffffffc0ffffffc0fffffffn; for(let i=31;i>=16;i--)sk=(sk<<8n)|BigInt(key[i]); let a=0n; for(let i=0;i<msg.length;i+=16){ const n=Math.min(16,msg.length-i); let b=0n; for(let j=n-1;j>=0;j--)b=(b<<8n)|BigInt(msg[i+j]); a=((a+b+(1n<<BigInt(8*n)))*r)%P;} a=(a+sk)&((1n<<128n)-1n); const o=new Uint8Array(16); for(let i=0;i<16;i++){o[i]=Number(a&255n);a>>=8n} return o} const p16=n=>n%16?16-n%16:0; const le64=n=>{const b=new Uint8Array(8);for(let i=0;i<8;i++){b[i]=n&255;n=Math.floor(n/256)}return b}; function tagOf(ct,key,nonce){ const otk=chacha(key,nonce,0,new Uint8Array(32)); const pad=new Uint8Array(p16(ct.length)); const m=new Uint8Array(ct.length+pad.length+16); m.set(ct);m.set(pad,ct.length); m.set(le64(0),ct.length+pad.length);m.set(le64(ct.length),ct.length+pad.length+8); return poly(m,otk)} const seal=(key,nonce,pt)=>{ const ct=chacha(key,nonce,1,pt),tg=tagOf(ct,key,nonce); const o=new Uint8Array(ct.length+16);o.set(ct);o.set(tg,ct.length);return o}; const open=(key,nonce,box)=>{ if(box.length<16)return null; const ct=box.subarray(0,box.length-16),tg=box.subarray(box.length-16); const w=tagOf(ct,key,nonce); let bad=0;for(let i=0;i<16;i++)bad|=w[i]^tg[i]; return bad?null:chacha(key,nonce,1,ct)}; const hex=b=>{let s="";for(const x of b)s+=x.toString(16).padStart(2,"0");return s}; const unhex=h=>{const b=new Uint8Array(h.length/2); for(let i=0;i<b.length;i++)b[i]=parseInt(h.substr(i*2,2),16);return b}; const bin=b=>{let s="";for(let i=0;i<b.length;i+=4096)s+=String.fromCharCode.apply(null,b.subarray(i,i+4096));return s}; const b64=b=>btoa(bin(b)); const unb64=s=>{const t=atob(s),b=new Uint8Array(t.length); for(let i=0;i<t.length;i++)b[i]=t.charCodeAt(i);return b}; const b64u=b=>b64(b).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,""); const utf8=s=>new TextEncoder().encode(s); const B58="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; const b58=b=>{let n=0n;for(const x of b)n=n*256n+BigInt(x); let s="";while(n>0n){s=B58[Number(n%58n)]+s;n/=58n} for(const x of b){if(x)break;s="1"+s}return s}; const rnd=n=>crypto.getRandomValues(new Uint8Array(n)); const sha256=async b=>new Uint8Array(await crypto.subtle.digest("SHA-256",b)); const topicOf=async k=>hex(await sha256(k)); const encode=(symKey,payload,senderPub)=>{ const iv=rnd(12),pt=utf8(JSON.stringify(payload)); const sealed=seal(symKey,iv,pt); const t=senderPub?1:0; const head=senderPub?1+32:1; const out=new Uint8Array(head+12+sealed.length); out[0]=t; if(senderPub)out.set(senderPub,1); out.set(iv,head);out.set(sealed,head+12); return b64(out)}; const decode=(symKey,msg)=>{ let b;try{b=unb64(msg)}catch{return null} if(!b.length)return null; const t=b[0]; if(t!==0&&t!==1)return null; const head=t===1?33:1; if(b.length<head+12+16)return null; const iv=b.subarray(head,head+12),box=b.subarray(head+12); const pt=open(symKey,iv,box); if(!pt)return null; try{return JSON.parse(new TextDecoder().decode(pt))}catch{return null}}; const didKey=pub=>{const b=new Uint8Array(34);b[0]=0xed;b[1]=0x01;b.set(pub,2); return"did:key:z"+b58(b)}; async function relayJwt(aud){ let kp; try{kp=await crypto.subtle.generateKey({name:"Ed25519"},true,["sign","verify"])} catch{throw Error("This browser cannot do Ed25519. WalletConnect needs a newer " +"browser; an extension wallet works here today.")} const pub=new Uint8Array(await crypto.subtle.exportKey("raw",kp.publicKey)); const iss=didKey(pub); const iat=Math.floor(Date.now()/1e3); const h=b64u(utf8(JSON.stringify({alg:"EdDSA",typ:"JWT"}))); const p=b64u(utf8(JSON.stringify({iss,sub:hex(rnd(32)),aud,iat,exp:iat+86400}))); const sig=new Uint8Array(await crypto.subtle.sign({name:"Ed25519"},kp.privateKey,utf8(h+"."+p))); return h+"."+p+"."+b64u(sig)} async function kex(){ let kp; try{kp=await crypto.subtle.generateKey({name:"X25519"},true,["deriveBits"])} catch{throw Error("This browser cannot do X25519. WalletConnect needs a newer " +"browser; an extension wallet works here today.")} const pub=new Uint8Array(await crypto.subtle.exportKey("raw",kp.publicKey)); return{priv:kp.privateKey,pub}} async function sessionKey(priv,peerHex){ const peer=await crypto.subtle.importKey("raw",unhex(peerHex),{name:"X25519"},false,[]); const bits=new Uint8Array(await crypto.subtle.deriveBits({name:"X25519",public:peer},priv,256)); const hk=await crypto.subtle.importKey("raw",bits,"HKDF",false,["deriveBits"]); const out=await crypto.subtle.deriveBits( {name:"HKDF",hash:"SHA-256",salt:new Uint8Array(0),info:new Uint8Array(0)},hk,256); return new Uint8Array(out)} return{seal,open,chacha,poly,hex,unhex,b64,unb64,b64u,b58,didKey,rnd,sha256, topicOf,encode,decode,relayJwt,kex,sessionKey}; })(); const WCCLI=(U,WSC)=>{ const RELAY="wss://relay.walletconnect.org"; const T_PROPOSE=1100,T_APPROVE=1101,T_SETTLE=1102,T_SETTLE_RES=1103,T_REQ=1108,T_RES=1109; const TTL=300,STTL=604800; let sock=null,nextId=1,waiters={},subs={},onEvent=null,alive=false,gone=""; const rpcId=()=>Date.now()*1000+(nextId++%1000); const sendRaw=o=>{ if(!alive)throw Error(gone||"The wallet connection is closed."); sock.send(JSON.stringify(o))}; function drop(why){ if(!alive&&gone)return; alive=false;gone=why; const ws=waiters;waiters={};subs={}; for(const k in ws)ws[k].rej(Error(why)); if(onEvent)onEvent({t:"closed",why})} function relayCall(method,params){ const id=rpcId(); return new Promise((res,rej)=>{ waiters[id]={res,rej}; sendRaw({id,jsonrpc:"2.0",method,params}); setTimeout(()=>{if(waiters[id]){delete waiters[id];rej(Error("relay timeout"))}},15000)})} function handle(raw){ let m;try{m=JSON.parse(raw)}catch{return} if(m.method==="irn_subscription"){ if(!alive)return; sendRaw({id:m.id,jsonrpc:"2.0",result:true}); const d=m.params&&m.params.data; if(d&&subs[d.topic])Promise.resolve(subs[d.topic](d.message)).catch(()=>{}); return} if(m.id&&waiters[m.id]){ const w=waiters[m.id];delete waiters[m.id]; if(m.error)w.rej(Error(m.error.message||"relay error"));else w.res(m.result)}} async function connect(projectId){ try{sock&&sock.close()}catch{} sock=null;alive=false;gone="";waiters={};subs={}; const jwt=await U.relayJwt(RELAY); const url=`${RELAY}/?auth=${jwt}&projectId=${projectId}&ua=zswap`; const ws=WSC(url); sock=ws; await new Promise((res,rej)=>{ let d=0; const t=setTimeout(()=>{if(!d){d=1;rej(Error("The relay did not answer."))}},15000); const fail=m=>{try{ws.close()}catch{}if(sock===ws)sock=null;rej(Error(m))}; const end=f=>x=>{if(d)return;d=1;clearTimeout(t);f(x)}; sock.onopen=end(()=>{alive=true;res()}); sock.onerror=end(()=>fail("The relay could not be reached.")); sock.onclose=end(e=>fail("The relay closed the connection" +(e&&e.code?" (code "+e.code+(e.reason?": "+e.reason:"")+")":"")+"."))}) sock.onclose=()=>drop("The relay connection dropped. Reconnect your wallet."); sock.onmessage=e=>handle(typeof e.data==="string"?e.data:""); } const subscribe=async(topic,fn)=>{subs[topic]=fn;await relayCall("irn_subscribe",{topic})}; const publish=(topic,message,tag,ttl)=> relayCall("irn_publish",{topic,message,ttl:ttl||TTL,tag,prompt:tag===T_PROPOSE||tag===T_REQ}); async function pair(projectId,meta){ await connect(projectId); const symKey=U.rnd(32); const pTopic=await U.topicOf(symKey); const kp=await U.kex(); const proposeId=rpcId(); let settle=null; const pKey="p"+proposeId; const done=new Promise((res,rej)=>{settle={ res:v=>{delete waiters[pKey];res(v)}, rej:e=>{delete waiters[pKey];rej(e)}}}); waiters[pKey]={res:v=>settle.res(v),rej:e=>settle.rej(e)}; await subscribe(pTopic,async msg=>{ try{ const m=U.decode(symKey,msg); if(!m||m.id!==proposeId)return; if(m.error)return settle.rej(Error(m.error.message||"rejected")); const rp=m.result&&m.result.responderPublicKey; if(!rp)return; const sKey=await U.sessionKey(kp.priv,rp); const sTopic=await U.topicOf(sKey); await subscribe(sTopic,async smsg=>{ const s=U.decode(sKey,smsg); if(!s)return; if(s.method==="wc_sessionSettle"){ await publish(sTopic,U.encode(sKey,{id:s.id,jsonrpc:"2.0",result:true}),T_SETTLE_RES,STTL); const ns=s.params&&s.params.namespaces||{}; const accts=[]; for(const k in ns)for(const a of ns[k].accounts||[])accts.push(a); settle.res({topic:sTopic,key:sKey,accounts:accts, methods:(ns.eip155&&ns.eip155.methods)||[]}); return} if(s.method==="wc_sessionDelete")return drop("The wallet ended the session."); if(s.method&&onEvent)onEvent({t:"session",m:s}); if(s.id&&waiters["s"+s.id]){ const w=waiters["s"+s.id];delete waiters["s"+s.id]; if(s.error)w.rej(Object.assign(Error(s.error.message||"rejected"),{code:s.error.code})); else w.res(s.result)}}); }catch(e){settle.rej(e instanceof Error?e:Error(String(e)))} }); const proposal={id:proposeId,jsonrpc:"2.0",method:"wc_sessionPropose",params:{ relays:[{protocol:"irn"}], proposer:{publicKey:U.hex(kp.pub),metadata:meta}, requiredNamespaces:{}, optionalNamespaces:{eip155:{chains:Object.keys(CHAINS).map(id=>"eip155:"+id), methods:["eth_sendTransaction","personal_sign","eth_signTypedData_v4", "eth_call","eth_chainId","eth_accounts","eth_getBalance", "wallet_switchEthereumChain","wallet_addEthereumChain"], events:["chainChanged","accountsChanged"]}}}}; await publish(pTopic,U.encode(symKey,proposal),T_PROPOSE); const uri=`wc:${pTopic}@2?relay-protocol=irn&symKey=${U.hex(symKey)}`; return{uri,done}} function request(sess,method,params){ const id=rpcId(); const body={id,jsonrpc:"2.0",method:"wc_sessionRequest", params:{request:{method,params:params||[]},chainId:"eip155:1"}}; const p=new Promise((res,rej)=>{ waiters["s"+id]={res,rej}; setTimeout(()=>{if(waiters["s"+id]){delete waiters["s"+id];rej(Error("wallet timed out"))}},300000)}); publish(sess.topic,U.encode(sess.key,body),T_REQ).catch(e=>{ const w=waiters["s"+id];if(!w)return;delete waiters["s"+id];w.rej(e)}); return p} return{pair,request,close:()=>{drop("The wallet connection was closed.");try{sock&&sock.close()}catch{}sock=null}, on:f=>{onEvent=f},_relayCall:relayCall}; }; const QR8=(()=>{ const EXP=new Uint8Array(512),LOG=new Uint8Array(256); (()=>{let x=1;for(let i=0;i<255;i++){EXP[i]=x;LOG[x]=i;x<<=1;if(x&256)x^=285} for(let i=255;i<512;i++)EXP[i]=EXP[i-255]})(); const mul=(a,b)=>a&&b?EXP[LOG[a]+LOG[b]]:0; const gen=n=>{let p=[1]; for(let i=0;i<n;i++){const q=new Array(p.length+1).fill(0); for(let j=0;j<p.length;j++){q[j]^=mul(p[j],EXP[i]);q[j+1]^=p[j]}p=q} return p}; const rs=(data,n)=>{ const g=gen(n),r=new Uint8Array(data.length+n); r.set(data); for(let i=0;i<data.length;i++){ const c=r[i];if(!c)continue; for(let j=0;j<=n;j++)r[i+j]^=mul(g[n-j],c)} return r.subarray(data.length)}; const SZ=49,NBLK=2,DPB=97,ECB=24,ALIGN=[6,24,42]; const bch=(v,g,n)=>{let d=v<<n; const bits=x=>{let c=0;while(x){c++;x>>>=1}return c}; const gl=bits(g); while(bits(d)>=gl)d^=g<<(bits(d)-gl); return d}; const fmtBits=mask=>{ const v=(1<<3)|mask; return ((v<<10)|bch(v,0x537,10))^0x5412}; const verBits=()=>(8<<12)|bch(8,0x1f25,12); function encode(str){ const bytes=new TextEncoder().encode(str); if(bytes.length>NBLK*DPB-2)throw Error("pairing code too long to draw"); const bits=[]; const push=(v,n)=>{for(let i=n-1;i>=0;i--)bits.push((v>>i)&1)}; push(4,4);push(bytes.length,8); for(const b of bytes)push(b,8); const cap=(NBLK*DPB)*8; for(let i=0;i<4&&bits.length<cap;i++)bits.push(0); while(bits.length%8)bits.push(0); const cw=[]; for(let i=0;i<bits.length;i+=8){let v=0;for(let j=0;j<8;j++)v=(v<<1)|bits[i+j];cw.push(v)} const pads=[0xec,0x11]; for(let i=0;cw.length<NBLK*DPB;i++)cw.push(pads[i%2]); const blocks=[],ecs=[]; for(let i=0;i<NBLK;i++){ const d=Uint8Array.from(cw.slice(i*DPB,(i+1)*DPB)); blocks.push(d);ecs.push(rs(d,ECB))} const out=[]; for(let i=0;i<DPB;i++)for(const b of blocks)out.push(b[i]); for(let i=0;i<ECB;i++)for(const e of ecs)out.push(e[i]); return out} function frame(){ const m=[],fn=[]; for(let i=0;i<SZ;i++){m.push(new Array(SZ).fill(0));fn.push(new Array(SZ).fill(0))} const put=(r,c,v)=>{m[r][c]=v;fn[r][c]=1}; const finder=(r,c)=>{ for(let i=-1;i<8;i++)for(let j=-1;j<8;j++){ const rr=r+i,cc=c+j; if(rr<0||cc<0||rr>=SZ||cc>=SZ)continue; const on=(i>=0&&i<=6&&(j===0||j===6))||(j>=0&&j<=6&&(i===0||i===6))||(i>=2&&i<=4&&j>=2&&j<=4); put(rr,cc,on?1:0)}}; finder(0,0);finder(0,SZ-7);finder(SZ-7,0); for(const r of ALIGN)for(const c of ALIGN){ if((r===6&&c===6)||(r===6&&c===ALIGN[ALIGN.length-1])||(r===ALIGN[ALIGN.length-1]&&c===6))continue; for(let i=-2;i<=2;i++)for(let j=-2;j<=2;j++) put(r+i,c+j,(Math.abs(i)===2||Math.abs(j)===2||(i===0&&j===0))?1:0)} for(let i=8;i<SZ-8;i++){put(6,i,i%2?0:1);put(i,6,i%2?0:1)} put(SZ-8,8,1); for(let i=0;i<9;i++){if(!fn[8][i])put(8,i,0);if(!fn[i][8])put(i,8,0)} for(let i=0;i<8;i++){if(!fn[8][SZ-1-i])put(8,SZ-1-i,0);if(!fn[SZ-1-i][8])put(SZ-1-i,8,0)} const vb=verBits(); for(let i=0;i<18;i++){ const b=(vb>>i)&1,r=Math.floor(i/3),c=i%3; put(SZ-11+c,r,b);put(r,SZ-11+c,b)} return{m,fn}} function place(m,fn,cw){ let bit=0,up=1; const get=i=>(cw[i>>3]>>(7-(i&7)))&1; for(let c=SZ-1;c>0;c-=2){ if(c===6)c--; for(let k=0;k<SZ;k++){ const r=up?SZ-1-k:k; for(let j=0;j<2;j++){ const cc=c-j; if(fn[r][cc])continue; m[r][cc]=bit<cw.length*8?get(bit):0;bit++}} up^=1}} const maskFn=(k,r,c)=>[ (r+c)%2===0,r%2===0,c%3===0,(r+c)%3===0, (((r/2)|0)+((c/3)|0))%2===0,((r*c)%2+(r*c)%3)===0, (((r*c)%2+(r*c)%3)%2)===0,(((r+c)%2+(r*c)%3)%2)===0][k]; function penalty(m){ let p=0; for(let i=0;i<SZ;i++){ let rc=1,cc=1; for(let j=1;j<SZ;j++){ rc=m[i][j]===m[i][j-1]?rc+1:1;if(rc===5)p+=3;else if(rc>5)p++; cc=m[j][i]===m[j-1][i]?cc+1:1;if(cc===5)p+=3;else if(cc>5)p++}} for(let i=0;i<SZ-1;i++)for(let j=0;j<SZ-1;j++){ const s=m[i][j]+m[i][j+1]+m[i+1][j]+m[i+1][j+1]; if(s===0||s===4)p+=3} const pat=[1,0,1,1,1,0,1]; const hit=(get)=>{ for(let i=0;i<SZ;i++)for(let j=0;j<SZ-6;j++){ let okk=1;for(let k=0;k<7;k++)if(get(i,j+k)!==pat[k]){okk=0;break} if(!okk)continue; let before=1,after=1; for(let k=j-4;k<j;k++)if(k<0||get(i,k)!==0){before=0;break} for(let k=j+7;k<j+11;k++)if(k>=SZ||get(i,k)!==0){after=0;break} if(before||after)p+=40}}; hit((i,j)=>m[i][j]);hit((i,j)=>m[j][i]); let dark=0;for(let i=0;i<SZ;i++)for(let j=0;j<SZ;j++)dark+=m[i][j]; p+=Math.floor(Math.abs(dark*100/(SZ*SZ)-50)/5)*10; return p} function build(str){ const cw=encode(str); let best=null,bestP=Infinity,bestK=0; for(let k=0;k<8;k++){ const{m,fn}=frame(); place(m,fn,cw); for(let r=0;r<SZ;r++)for(let c=0;c<SZ;c++) if(!fn[r][c]&&maskFn(k,r,c))m[r][c]^=1; const f=fmtBits(k); for(let i=0;i<8;i++){ const b=(f>>i)&1; if(i<6)m[i][8]=b;else if(i===6)m[7][8]=b;else m[8][8]=b; m[8][SZ-1-i]=b} for(let i=8;i<15;i++){ const b=(f>>i)&1,c=14-i; if(c<6)m[8][c]=b;else m[8][7]=b; m[SZ-1-c][8]=b} m[SZ-8][8]=1; const p=penalty(m); if(p<bestP){bestP=p;best=m;bestK=k}} return{size:SZ,modules:best,mask:bestK}} return{build,_encode:encode}; })(); const wcNode=()=>rpcPin()||rpcPool[rpcPi]; const wcHost=()=>{try{return new URL(wcNode()).host}catch{return wcNode()}}; const WC_SIGN=["eth_sendTransaction","eth_signTransaction","personal_sign","eth_sign", "eth_signTypedData","eth_signTypedData_v3","eth_signTypedData_v4"]; const wcToWallet=m=>m.indexOf("wallet_")===0||WC_SIGN.indexOf(m)>=0; async function wcRpc(method,params){ try{return await nodeRead(method,params)} catch(e){if(e&&e.code!=null)throw e; throw Error("Could not reach "+wcHost()+" for reads. Pick another node from the WalletConnect panel.")}} const wcRelay=WCCLI(WCU,u=>new WebSocket(u)); const qrSvg=m=>{let d=""; for(let r=0;r<m.size;r++)for(let c=0;c<m.size;c++)if(m.modules[r][c])d+="M"+c+" "+r+"h1v1h-1z"; return '<svg viewBox="-4 -4 '+(m.size+8)+" "+(m.size+8)+'" role="img" aria-label="WalletConnect pairing code" style="width:100%;height:auto;background:#fff;border-radius:.3em"><path fill="#000" d="'+d+'"/></svg>'}; const wcShow=uri=>{ wkList.textContent=""; const box=document.createElement("div"); box.className="wcq"; box.innerHTML=qrSvg(QR8.build(uri)); const cp=document.createElement("button"); cp.type="button";cp.className="fclink";cp.textContent="copy link"; cp.onclick=()=>{navigator.clipboard?navigator.clipboard.writeText(uri).then(()=>{cp.textContent="copied"}).catch(()=>{cp.textContent="copy blocked"}):cp.textContent="copy blocked"}; const note=document.createElement("div"); note.className="wcn"; note.textContent="Scan with a mobile wallet, or "; note.appendChild(cp); const nd=document.createElement("div"); nd.className="wcn wcnode"; nd.appendChild(document.createTextNode("Reads via "+wcHost()+" ")); const ch=document.createElement("button"); ch.type="button";ch.className="fclink";ch.textContent="change"; ch.onclick=()=>{ const v=prompt("HTTPS Ethereum RPC used for reads — it is pinned to the front of the read pool (the DAO-curated list, then the page's seeds)." +" Signing always goes to your wallet. Leave blank for the default.",wcNode()); if(v===null)return; const t=v.trim(); try{if(!t)localStorage.removeItem(RPC_STORE); else if(/^https:\/\/\S+$/i.test(t))localStorage.setItem(RPC_STORE,t); else return}catch{} nd.firstChild.nodeValue="Reads via "+wcHost()+" "}; nd.appendChild(ch); wkList.appendChild(box);wkList.appendChild(note);wkList.appendChild(nd); wkHdr.textContent="WalletConnect"; wkWrap.classList.remove("hide"); const back=document.activeElement; cp.focus({preventScroll:true}); return new Promise((res,rej)=>{ const esc=e=>{if(e.key==="Escape")stop()}; const off=()=>{removeEventListener("keydown",esc);removeEventListener("keydown",wkTrap,true); wkWrap.onclick=null;if(back&&back.focus)try{back.focus()}catch{}}; const stop=()=>{off();rej(Object.assign(Error(""),{code:4001}))}; wkWrap.onclick=e=>{if(e.target===wkWrap)stop()}; addEventListener("keydown",esc); addEventListener("keydown",wkTrap,true); wcShow.settle=()=>{off();res()}})}; async function wcConnect(){ const meta={name:"zSwap",description:"zSwap",url:location.origin,icons:[]}; const{uri,done}=await wcRelay.pair(WC_PID,meta); const bail=wcShow(uri); stat.textContent="Scan the code with your wallet."; let sess; try{sess=await Promise.race([done,bail])} catch(e){wcRelay.close();throw e} finally{if(wcShow.settle)wcShow.settle(); wkHdr.textContent="Choose a wallet"; wkWrap.classList.add("hide")} const accts=sess.accounts.filter(a=>a.slice(0,9)==="eip155:1:").map(a=>a.split(":").pop()); if(!accts.length){wcRelay.close();throw Error("That wallet settled no Ethereum mainnet account — switch it to Ethereum and reconnect.")} wcRead=sess.methods.indexOf("eth_call")>=0; const read=async(method,params)=>{ try{return await wcRpc(method,params)} catch(e){ if(e&&e.code!==undefined||!wcRead)throw e; return wcRelay.request(sess,method,params||[])}}; const evs=[]; wcRelay.on(ev=>{if(ev&&ev.t==="closed")location.reload(); if(!ev||ev.t!=="session")return; const s=ev.m; if(!s)return; if(s.method==="wc_sessionUpdate")return evs.forEach(f=>f("reload",null)); if(s.method!=="wc_sessionEvent")return; const e=s.params&&s.params.event||{};let d=e.data; if(e.name==="chainChanged"&&typeof d==="string"&&d.includes(":"))d=d.split(":").pop(); if(e.name==="accountsChanged"&&Array.isArray(d))d=d.map(a=>typeof a==="string"&&a.includes(":")?a.split(":").pop():a); if(e.name==="chainChanged"||e.name==="accountsChanged")evs.forEach(f=>f(e.name,d))}); return{request:({method,params})=> method==="eth_requestAccounts"||method==="eth_accounts"?Promise.resolve(accts): method==="eth_chainId"?Promise.resolve("0x1"): wcToWallet(method)?wcRelay.request(sess,method,params||[]): read(method,params), on:(t,f)=>{if(t==="chainChanged"||t==="accountsChanged")evs.push((n,d)=>{if(n==="reload"||n===t)try{f(d)}catch{}})}}} let wcRead=true; const wkWrap=document.getElementById("wkWrap"),wkList=document.getElementById("wkList"); const wkChoices=()=>W6963.map(d=>({name:d.info.name,icon:d.info.icon,rdns:d.info.rdns,get:()=>d.provider})) .concat([{name:"WalletConnect",svg:WC_ICON,rdns:"wc",get:wcConnect}]); const WK="zswap:wk"; const wkRestore=async()=>{ const id=LS[WK]; if(PROV||!id||id==="wc")return; wkScan(); for(let i=0;i<8;i++){ const m=W6963.filter(x=>x.info.rdns===id),d=m.length===1?m[0]:null; if(d){ try{const a=await Promise.race([d.provider.request({method:"eth_accounts"}), new Promise(r=>setTimeout(()=>r(null),1500))]); if(a&&a[0])PROV=d.provider}catch{} return} await new Promise(r=>setTimeout(r,50)); }}; const wkTrap=e=>{ if(e.key!=="Tab")return; const f=[...wkPanel.querySelectorAll('button,[href],input,select,textarea,[tabindex]:not([tabindex="-1"])')] .filter(x=>!x.disabled&&x.offsetParent!==null); if(!f.length)return; const a=f[0],z=f[f.length-1],cur=document.activeElement; if(e.shiftKey?cur===a||!wkPanel.contains(cur):cur===z){e.preventDefault();(e.shiftKey?z:a).focus()} }; const wkPick=cs=>new Promise(res=>{ wkList.textContent=""; let done=false; const back=document.activeElement; const end=v=>{if(done)return;done=true; wkWrap.classList.add("hide");removeEventListener("keydown",esc); removeEventListener("keydown",wkTrap,true); if(back&&back.focus)try{back.focus()}catch{} res(v)}; const esc=e=>{if(e.key==="Escape")end(null)}; cs.forEach(c=>{ const b=document.createElement("button"); b.type="button";b.className="tkr"; if(c.icon){const im=document.createElement("img");im.src=c.icon;im.alt="";b.appendChild(im)} else if(c.svg){const sp=document.createElement("span");sp.className="wki";sp.innerHTML=c.svg;b.appendChild(sp)} b.appendChild(document.createTextNode(c.name)); b.onclick=()=>end(c); wkList.appendChild(b)}); wkWrap.onclick=e=>{if(e.target===wkWrap)end(null)}; addEventListener("keydown",esc); addEventListener("keydown",wkTrap,true); wkWrap.classList.remove("hide"); wkList.firstChild?.focus()}); async function wkResolve(){ if(PROV)return true; wkScan(); const inj=window.ethereum; if(W6963.length<2&&inj)return true; const cs=wkChoices(); const c=await wkPick(cs); if(!c)return false; stat.textContent="Connecting…"; try{PROV=await c.get()} catch(e){if(!isRejection(e))err(e);return false} try{if(c.rdns)LS[WK]=c.rdns}catch{} wkBind(PROV); return true} async function connect(){ if(connecting)return; connecting=true; try{ if(!await wkResolve())return; if(!prov()){stat.textContent=NO_WALLET;return} swap.textContent="Connecting…";swap.disabled=true; lnGo.textContent="Connecting…";lnGo.disabled=true; wnGo.textContent="Connecting…";wnGo.disabled=true; let accs; try{accs=await rpc("eth_requestAccounts",[])} catch(e){err(e);return} if(!accs[0])return; let chainId=await rpc(I,[]); if(!CHAINS[+chainId]){ try{await rpc("wallet_switchEthereumChain",[{chainId:"0x1"}])} catch(e){stat.textContent=isRejection(e)?"":"Switch to a supported network.";return} chainId=await rpc(I,[]); if(!CHAINS[+chainId]){stat.textContent="Switch to a supported network.";return} } setChain(+chainId);showNet(); sessionStorage.removeItem("dc"); account=accs[0];sOpen(); const keepPair=pairAddrs(); showAddr(); if(!listLive){ swap.textContent="Loading tokens\u2026"; await loadTokenList(); } adoptList(keepPair); refreshBalance(); update(); fcMine(); loadPos(); syncChart(); if(lnMode)lnKindQuote(); }finally{connecting=false;swap.disabled=false; lnGo.disabled=false;lnGo.textContent=account?"Launch":"Connect Wallet"; wnGo.disabled=false; if(wnMode){wnPaint();wnQuote();wnRoll()}else wnGo.textContent="Register"; if(pvMode)pvWake();render()} } const lqReserveGas=()=>lqList.querySelector(".lqnew")?LQ_CREATE_GAS+300000n:400000n; const lqFillMax=(tok,max)=>{ const box=lqList.querySelector(".lqadd:not(.hide)"); const pair=chPair(); if(!box||!pair)return false; const side=pair[0].addr.toLowerCase()===tok.addr.toLowerCase()?"0":"1"; const el=box.querySelector(`.lqin[data-side="${side}"]`); if(!el)return false; el.value=formatUnits(max,tok.dec); el.dispatchEvent(new Event("input",{bubbles:true})); return true; }; bal1.addEventListener("click",async e=>{ if(e.target.tagName!=="A")return; e.preventDefault(); const t=TOKENS[toSel.value]; if(!t)return; let reserve=0n; if(t.addr===ZERO){ const g=lqReserveGas(); try{reserve=BigInt(await rpc("eth_gasPrice",[]))*g}catch{} if(reserve<=0n)reserve=GAS_FLOOR*g; } const max=toBalance>reserve?toBalance-reserve:0n; if(lqFillMax(t,max))return; outAmt.value=formatUnits(max,t.dec); fitFont(outAmt); lqPreviewSoon(); }); bal.addEventListener("click",async e=
0x68575b07…67c7·#25,913,372·0xc2a76288…3b41c4
feSym=s=>String(s??"").replace(/[<>&"'`]/g,"") .replace(/[\u0000-\u001f\u007f]/g,"").trim().slice(0,16)||"?"; const safeUrl=u=>{u=String(u??"").trim(); return u.length<=32768&&/^(?:https:\/\/|data:image\/[a-z.+-]+;base64,)[^\s<>"'`\\]+$/i.test(u)?u:""}; const safeDataUrl=u=>{u=String(u??"").trim(); return u.length<=32768&&/^data:image\/[a-z.+-]+;base64,[A-Za-z0-9+/=]+$/i.test(u)?u:""}; const genIcon=sym=>`<svg width="20" height="20" viewBox="0 0 32 32"><circle cx="16" cy="16" r="16" fill="#999"/><text x="16" y="21" text-anchor="middle" fill="#fff" font-size="13" font-weight="600" font-family="system-ui">${(safeSym(sym)[0]||"?").toUpperCase()}</text></svg>`; const STORE=()=>CHAIN_ID===1?"zswap:custom":"zswap:custom:"+CHAIN_ID; function keepToken(t){ if(!t||!t.addr)return; const a=t.addr.toLowerCase(); try{const arr=JSON.parse(localStorage.getItem(STORE())||"[]") .filter(x=>String(x?.addr||"").toLowerCase()!==a); arr.push({sym:t.sym,addr:a,dec:t.dec,std:t.std,cause:t.cause?1:0}); localStorage.setItem(STORE(),JSON.stringify(arr))}catch{} } const SEL_SUPPORTS="01ffc9a7",IFACE_721="80ac58cd",IFACE_1155="d9b67a26"; const supports=id=>"0x"+SEL_SUPPORTS+id.padEnd(64,"0"); async function addCustomToken(a,keep=1){ a=a.toLowerCase(); if(!/^0x[0-9a-f]{40}$/.test(a))throw Error("invalid address"); const i=TOKENS.findIndex(t=>t.addr.toLowerCase()===a); if(i>=0)return i; if((await rpcRead("eth_getCode",[a,L],2)).length<5)throw Error("no contract at that address"); const[symRet,decRet,is721,is1155]=await mc3Deep([ {to:a,data:"0x95d89b41"},{to:a,data:"0x313ce567"}, {to:a,data:supports(IFACE_721)},{to:a,data:supports(IFACE_1155)}]); let uriRet=null; try{uriRet=await rpc(C,[{to:a,data:"0x"+SEL_CONTRACTURI},L])}catch{} const yes=h=>{try{return !!h&&BigInt(h)===1n}catch{return false}}; const dup=TOKENS.findIndex(t=>t.addr.toLowerCase()===a); if(dup>=0)return dup; let sym="?"; try{sym=decodeString(symRet||"0x").trim().replace(/[<>&"'`]/g,"").replace(/[\u0000-\u001f\u007f]/g,"").slice(0,16)||"?"}catch{} if(TOKENS.some(t=>t.sym.toLowerCase()===sym.toLowerCase()))sym+=" "+a.slice(0,6)+"\u2026"; if(yes(is1155)&&!yes(is721))throw Error("ERC-1155 is not supported"); const std=yes(is721)?"nft":"ft"; let dec=0; if(std==="ft"){ dec=Number(BigInt(decRet&&decRet!=="0x"?decRet:"0x0")); if(!decRet||decRet==="0x"||!Number.isInteger(dec)||dec<0||dec>36)throw Error("not an ERC-20 or ERC-721"); } let selfArt="";try{selfArt=imgFromContractURI(uriRet?decodeString(uriRet):"")}catch{} if(selfArt)logoCache[a]=selfArt; TOKENS.push({sym,addr:a,dec,std,cust:1, icon:selfArt?`<img width="20" height="20" style="border-radius:50%;object-fit:cover" src="${selfArt}" alt="">`:genIcon(sym)}); if(keep)keepToken(TOKENS[TOKENS.length-1]); return TOKENS.length-1; } function restoreCustom(){try{for(const t of JSON.parse(localStorage.getItem(STORE())||"[]")){ const addr=(t?.addr||"").toLowerCase(); const dec=+t?.dec; if(/^0x[0-9a-f]{40}$/.test(addr)&&Number.isInteger(dec)&&dec>=0&&dec<=36&&!TOKENS.some(x=>x.addr.toLowerCase()===addr)){ const sym=String(t.sym||"?").replace(/[<>&"'`]/g,"").replace(/[\u0000-\u001f\u007f]/g,"").slice(0,16)||"?"; TOKENS.push({sym,addr,dec,std:t.std==="nft"?"nft":"ft",cust:1,icon:genIcon(sym), ...(t.cause?{cause:1}:{})}); } }}catch{}} restoreCustom(); const QUOTE_TTL=45000; let account=null,connecting=false,last=null,fromBalance=0n,mode="in",bSeq=0,tab="swap",sendReady=null,sSeq=0,sendD="0",bookD="86400"; let quoting=false; let lqMode=false; const readBal=async t=>BigInt(t.addr===ZERO ?await rpc("eth_getBalance",[account,L]) :await rpc(C,[{to:t.addr,data:"0x"+SEL_BALANCEOF+encAddr(account)},L])); const showBal=(el,v,dec)=>{const n=+formatUnits(v,dec); const pretty=n>=1?n.toLocaleString("en-US",{maximumFractionDigits:4}):sigAmt(v,dec,4); el.innerHTML=`Balance: ${pretty} <a href="#" title="Use maximum (leaves gas for ETH)">Max</a>`}; async function refreshBalance(){ const f=TOKENS[fromSel.value]; if(!account||!f){bal.textContent="";fromBalance=0n;render();return} const my=++bSeq; try{ const v=await readBal(f); if(my!==bSeq)return; fromBalance=v;showBal(bal,v,f.dec); }catch{if(my===bSeq){bal.textContent="";fromBalance=0n}} refreshBalance1(); render(); } let toBalance=0n,b1Seq=0; async function refreshBalance1(){ const t=TOKENS[toSel.value]; bal1.classList.toggle("hide",!lqMode); if(!lqMode||!account||!t){bal1.textContent="";toBalance=0n;return} const my=++b1Seq; try{ const v=await readBal(t); if(my!==b1Seq)return; toBalance=v;showBal(bal1,v,t.dec); }catch{if(my===b1Seq){bal1.textContent="";toBalance=0n}} } const optsHtml=()=>{ let ft="",nft=""; let born=""; let cau=""; TOKENS.forEach((t,i)=>{const o=`<option value="${i}">${t.sym}</option>`; if(t.std==="nft")nft+=o;else if(t.cause)cau+=o;else if(t.launched)born+=o;else ft+=o}); return ft +(cau?`<optgroup label="Causes — backed and burned, never traded">${cau}</optgroup>`:"") +(born?`<optgroup label="Launched on zSwap — not curated">${born}</optgroup>`:"") +(nft?`<optgroup label="NFT collections — auction only">${nft}</optgroup>`:"") +`<option value="__custom">+ Custom token…</option>`; }; const rebuild=()=>{for(const s of[fromSel,toSel]){const v=s.value;s.innerHTML=optsHtml();s.value=v}}; const pairAddrs=()=>({fa:TOKENS[fromSel.value]?.addr,ta:TOKENS[toSel.value]?.addr, fi:String(fromSel.value),ti:String(toSel.value)}); const restorePair=k=>{ if(!k)return; const at=a=>a?TOKENS.findIndex(t=>t.addr.toLowerCase()===a.toLowerCase()):-1; const held=(cur,want)=>String(cur)===want||!TOKENS[cur]; if(held(fromSel.value,k.fi)){const i=at(k.fa);if(i>=0)fromSel.value=i} if(held(toSel.value,k.ti)){const i=at(k.ta);if(i>=0&&String(i)!==String(fromSel.value))toSel.value=i} for(const s of[fromSel,toSel]){ const want=s.dataset.addr; if(!want||held(s.value,s===fromSel?k.fi:k.ti))continue; const i=at(want); if(i>=0&&(s===fromSel||String(i)!==String(fromSel.value)))s.value=i; } }; const isNft=v=>TOKENS[v]?.std==="nft"; let pairChosen=false; const wrapTwin=(a,b)=>{ const x=(a||"").toLowerCase(),y=(b||"").toLowerCase(),w=WETH.toLowerCase(); return (x===ZERO&&y===w)||(x===w&&y===ZERO); }; const rankedOutputs=fi=>TOKENS.map((t,i)=>i) .filter(i=>i!==fi&&!isNft(String(i))&&!wrapTwin(TOKENS[i].addr,TOKENS[fi]?.addr)); const deadOutputs=new Set(); let pairAuto=false; const rankedDefaults=()=>{ if(TOKENS.length<2)return; const fi=TOKENS.findIndex((t,i)=>!isNft(String(i))); if(fi<0)return; const ti=rankedOutputs(fi).find(i=>!deadOutputs.has(TOKENS[i].addr.toLowerCase())); if(ti==null)return; fromSel.value=String(fi);toSel.value=String(ti); fromSel.dataset.prev=fromSel.value;toSel.dataset.prev=toSel.value; pairAuto=true; try{LS["zswap:pair"]=JSON.stringify([TOKENS[fi].addr,TOKENS[ti].addr])}catch{} }; const me=()=>(account||"").toLowerCase(); const advancePastDeadPair=()=>{ if(!pairAuto||pairChosen)return false; const cur=TOKENS[toSel.value]; if(!cur)return false; deadOutputs.add(cur.addr.toLowerCase()); const fi=Number(fromSel.value); const next=rankedOutputs(fi).find(i=>!deadOutputs.has(TOKENS[i].addr.toLowerCase())); if(next==null)return false; toSel.value=String(next);toSel.dataset.prev=toSel.value; syncDisabled();syncIcons(); return true; }; const settlePair=keep=>{if(pairChosen)restorePair(keep);else rankedDefaults()}; let listFresh=false; const adoptList=keep=>{ cbDisarm(); listFresh=true; rebuild(); settlePair(keep); if(!TOKENS[fromSel.value])fromSel.value=0; if(!TOKENS[toSel.value]||toSel.value===fromSel.value)toSel.value=TOKENS[1]?1:0; syncDisabled();syncIcons(); }; const syncDisabled=keep=>{ const pair=tab!=="send"; const nftOk=tab==="book"; if(pair&&fromSel.value===toSel.value){ const alt=TOKENS.findIndex((t,i)=>String(i)!==fromSel.value&&(nftOk||t.std!=="nft")); if(alt>=0){toSel.value=String(alt);toSel.dataset.prev=toSel.value} } if(pair&&nftOk&&isNft(fromSel.value)&&isNft(toSel.value)){ const give=keep===toSel?fromSel:toSel,stay=give===fromSel?toSel:fromSel; const alt=TOKENS.findIndex((t,i)=>t.std!=="nft"&&String(i)!==stay.value); if(alt>=0){give.value=String(alt);give.dataset.prev=give.value} } for(const[sel,other]of[[fromSel,toSel],[toSel,fromSel]]) for(const opt of sel.options) opt.disabled=opt.value!=="__custom"&&((pair&&opt.value===other.value)||(!nftOk&&isNft(opt.value))); }; fromSel.innerHTML=optsHtml();fromSel.value=0; toSel.innerHTML=optsHtml(); const LAST_PAIR="zswap:pair"; const seatPair=()=>{ try{ const [a,b]=JSON.parse(LS[LAST_PAIR]||"[]"); const fi=TOKENS.findIndex(t=>t.addr.toLowerCase()===String(a).toLowerCase()); const ti=TOKENS.findIndex(t=>t.addr.toLowerCase()===String(b).toLowerCase()); if(fi>=0&&ti>=0&&fi!==ti){fromSel.value=String(fi);toSel.value=String(ti);return true} }catch{} return false; }; if(!seatPair())toSel.value=String(Math.max(1,TOKENS.findIndex(t=>t.sym==="USDC"))); syncDisabled(); const iconShown=["",""]; const syncIcons=()=>{ [[fromIcon,fromSel],[toIcon,toSel]].forEach(([el,s],i)=>{ const t=TOKENS[s.value],ic=t?.icon||""; if(iconShown[i]!==ic){iconShown[i]=ic;el.innerHTML=ic} if(t)s.dataset.addr=t.addr}); syncPickers()}; const tkPanel=document.getElementById("tkPanel"),tkFind=document.getElementById("tkFind"), tkList=document.getElementById("tkList"); let tkSel=null,tkCur=-1; const pickBtn=sel=>document.getElementById(sel===fromSel?"fromPick":"toPick"); const syncPickers=()=>{ for(const sel of [fromSel,toSel]){ const b=pickBtn(sel);if(!b)continue; const t=TOKENS[sel.value]; b.classList.add("on");sel.classList.add("rich"); b.querySelector(".tkbs").textContent=t?t.sym:(sel.selectedOptions[0]?.textContent||""); b.setAttribute("aria-label",`Token: ${t?t.sym:"none"} - change`); const pill=b.closest(".pill"); if(pill){ const name=t?.name&&t.name.toLowerCase()!==t.sym.toLowerCase()?t.name:""; const extra=[name,t?.desc].filter(Boolean); if(extra.length)pill.title=[`${t.sym}${name?" — "+name:""}`,t.desc].filter(Boolean).join("\n\n"); else pill.removeAttribute("title"); } } }; const tkAct=id=>{if(id)tkFind.setAttribute("aria-activedescendant",id); else tkFind.removeAttribute("aria-activedescendant")}; const tkRows=()=>{ const q=tkFind.value.trim().toLowerCase(); tkList.textContent=""; tkCur=-1;tkAct(""); const frag=document.createDocumentFragment(); let n=0,group=""; for(const opt of tkSel.options){ const t=TOKENS[opt.value]; if(q){ const hay=[opt.textContent,t?.name,t?.addr].filter(Boolean).join(" ").toLowerCase(); if(!hay.includes(q))continue; } const g=opt.parentElement?.label||(opt.value==="__custom"?"": (listLive?(rankFellBack?"zList — listing order":"zList — ranked by conviction"):"Built-in token list")); if(g&&g!==group){const h=document.createElement("div");h.className="tkg";h.textContent=g;frag.appendChild(h);group=g} const r=document.createElement("button"); r.type="button";r.className="tkr";r.setAttribute("role","option"); r.id="tkr"+n; r.dataset.value=opt.value; r.setAttribute("aria-selected",String(opt.value===tkSel.value)); if(opt.disabled)r.setAttribute("aria-disabled","true"); const ic=document.createElement("span");ic.className="tki"; if(t?.icon)ic.innerHTML=t.icon; r.appendChild(ic); const tx=document.createElement("span");tx.className="tkt"; const top=document.createElement("span");top.className="tks"; const b=document.createElement("b");b.textContent=opt.textContent.trim();top.appendChild(b); if(t?.name&&t.name.toLowerCase()!==b.textContent.toLowerCase()){ const nm=document.createElement("span");nm.className="tkn";nm.textContent=t.name;top.appendChild(nm); } tx.appendChild(top); if(t?.desc){const d=document.createElement("span");d.className="tkd";d.textContent=t.desc;tx.appendChild(d)} r.appendChild(tx); if(opt.value===tkSel.value){const m=document.createElement("span");m.className="tkmark";m.textContent="✓";r.appendChild(m)} frag.appendChild(r);n++; } if(!n){ const a=/^0x[0-9a-f]{40}$/.test(q)?q:""; if(a){ const r=document.createElement("button"); r.type="button";r.className="tkr"; const tx=document.createElement("span");tx.className="tkt"; const top=document.createElement("span");top.className="tks"; const b=document.createElement("b");b.textContent="Use "+short(a);top.appendChild(b); tx.appendChild(top); const d=document.createElement("span");d.className="tkd"; d.textContent="Not listed \u2014 read it from chain"; tx.appendChild(d);r.appendChild(tx); r.onclick=async e=>{ e.stopPropagation(); if(r.dataset.busy)return; r.dataset.busy="1";b.textContent="Reading\u2026"; try{const i=await addCustomToken(a);rebuild();tkChoose(String(i))} catch(e){delete r.dataset.busy;b.textContent="Use "+short(a); d.textContent=(e&&e.message)||"could not read that address"}}; frag.appendChild(r); }else{const e=document.createElement("div");e.className="tkempty";e.textContent="No token matches that.";frag.appendChild(e)} } tkList.classList.toggle("fresh",listFresh); listFresh=false; tkList.appendChild(frag); tkCur=-1; }; const tkOpen=sel=>{ if(tkSel&&tkSel!==sel)pickBtn(tkSel)?.setAttribute("aria-expanded","false"); tkSel=sel;tkFind.value="";tkRows(); tkPanel.classList.remove("hide"); if(matchMedia("(pointer:coarse)").matches){ tkPanel.style.left=tkPanel.style.top=tkPanel.style.maxHeight=tkPanel.style.width=""; pickBtn(sel).setAttribute("aria-expanded","true"); tkFind.focus({preventScroll:true}); return; } const b=pickBtn(sel).getBoundingClientRect(); const card=document.querySelector(".card").getBoundingClientRect(); const w=Math.min(Math.max(card.width,240),innerWidth-16); tkPanel.style.width=w+"px"; const left=Math.max(8,Math.min(card.left,innerWidth-w-8)); const above=b.top,below=innerHeight-b.bottom; const flip=below<240&&above>below; const MENU_MAX=520; const room=Math.min(MENU_MAX,Math.max(140,(flip?above:below)-14)); tkPanel.style.maxHeight=room+"px"; const h=Math.min(tkPanel.offsetHeight,room); const top=flip?b.top-h-6:b.bottom+6; tkPanel.style.left=left+"px"; tkPanel.style.top=Math.max(8,Math.min(top,innerHeight-h-8))+"px"; pickBtn(sel).setAttribute("aria-expanded","true"); tkFind.focus({preventScroll:true}); }; const tkClose=()=>{ if(!tkSel)return; pickBtn(tkSel)?.setAttribute("aria-expanded","false"); tkPanel.classList.add("hide");tkSel=null;tkCur=-1;tkAct(""); }; const tkChoose=v=>{ const sel=tkSel;if(!sel)return; tkClose(); if(sel.value===v&&v!=="__custom")return; sel.value=v; sel.dispatchEvent(new Event("change",{bubbles:true})); }; for(const sel of [fromSel,toSel]){ const b=pickBtn(sel); if(b)b.addEventListener("click",e=>{e.stopPropagation();tkSel===sel?tkClose():tkOpen(sel)}); } tkList.addEventListener("click",e=>{ const r=e.target.closest(".tkr"); if(!r||r.getAttribute("aria-disabled")==="true")return; tkChoose(r.dataset.value); }); tkFind.addEventListener("input",tkRows); tkPanel.addEventListener("keydown",e=>{ const rows=[...tkList.querySelectorAll('.tkr:not([aria-disabled="true"])')]; if(e.key==="Escape"){e.preventDefault();const s=tkSel;tkClose();pickBtn(s)?.focus();return} if(e.key==="ArrowDown"||e.key==="ArrowUp"){ e.preventDefault(); if(!rows.length)return; tkCur=e.key==="ArrowDown"?(tkCur+1)%rows.length:(tkCur<=0?rows.length:tkCur)-1; rows.forEach((r,i)=>r.classList.toggle("cur",i===tkCur)); rows[tkCur].scrollIntoView({block:"nearest"}); tkAct(rows[tkCur].id); return; } if(e.key==="Enter"){ const r=tkCur>=0?rows[tkCur]:rows.length===1?rows[0]:null; if(r){e.preventDefault();r.dataset.value?tkChoose(r.dataset.value):r.click()} } }); tkPanel.addEventListener("click",e=>e.stopPropagation()); document.addEventListener("click",tkClose); addEventListener("resize",()=>{if(!matchMedia("(pointer:coarse)").matches)tkClose()}); addEventListener("scroll",e=>{if(tkSel&&!tkPanel.contains(e.target))tkClose()},true); const render=()=>{ if(!account){swap.textContent="Connect Wallet";swap.disabled=false;return} if(tab==="book"){ rate.textContent=""; const f=TOKENS[fromSel.value],t=TOKENS[toSel.value]; if(!f||!t){swap.textContent="Place order";swap.disabled=true;return} if(f.std==="nft"&&t.std==="nft"){ swap.textContent="One side must be a token";swap.disabled=true;return} if(t.std==="nft"){ const oneId=nftIdIn(t.addr,nftId.value); if(oneId&&!/^\d+$/.test(oneId)){ swap.textContent=isWns(t.addr)?"Token id must be a number, or a .wei name":"Token id must be a number"; swap.disabled=true;return} const climbing=kind.value==="floor"; if(kind.value==="dutch"){ swap.textContent="A bid is fixed or climbing, not decaying";swap.disabled=true;return} if(oneId&&climbing){ swap.textContent="A climbing bid takes any id — clear the token id"; swap.disabled=true;return} if(!oneId){ const q=outAmt.value.trim(); if(q&&!/^\d+$/.test(q)){ swap.textContent="How many? a whole number";swap.disabled=true;return} if(q&&!BigInt(q)){swap.textContent="Bid for at least one";swap.disabled=true;return} if(q&&BigInt(q)>(1n<<128n)-1n){ swap.textContent="Too many to bid for at once";swap.disabled=true;return} } let bidAmt=0n; try{bidAmt=amt.value.trim()?parseUnits(amt.value.trim(),f.dec):0n}catch{} if(!bidAmt){ swap.textContent=climbing?"Set the most you will pay":"Set what you will pay"; swap.disabled=true;return} if(climbing){ let openAmt=0n; try{openAmt=floorAmt.value.trim()?parseUnits(floorAmt.value.trim(),f.dec):0n}catch{} if(!openAmt){swap.textContent="Set an opening bid";swap.disabled=true;return} if(openAmt>bidAmt){swap.textContent="Opening bid exceeds the maximum";swap.disabled=true;return} if(!+dly.value){swap.textContent="A climbing bid needs a window";swap.disabled=true;return} }else if(!oneId&&!+dly.value){ swap.textContent="A collection bid needs a window";swap.disabled=true;return} if(bidAmt>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} swap.textContent=oneId?`Bid for ${nftLabel(t.sym,t.addr,nftId.value)}` :climbing?`Climbing bid for any ${t.sym}`:`Bid for any ${t.sym}`; swap.disabled=false;return} if(f.std==="nft"){ if(kind.value!=="fixed"){ swap.textContent="An NFT sells at a fixed price";swap.disabled=true;return} if(!/^\d+$/.test(nftIdIn(f.addr,nftId.value))){ swap.textContent=isWns(f.addr)?"Name the .wei name, or the token id, to sell":"Name the token id to sell"; swap.disabled=true;return} let ask=0n; try{ask=outAmt.value.trim()?parseUnits(outAmt.value.trim(),t.dec):0n}catch{} if(!ask){swap.textContent="Set a price";swap.disabled=true;return} swap.textContent=`List ${nftLabel(f.sym,f.addr,nftId.value)}`;swap.disabled=false;return} const dutch=kind.value==="dutch",bid=kind.value==="floor"; let sell=0n,want=0n,floor=0n,badOrderAmount=false; try{if(amt.value.trim())sell=parseUnits(amt.value.trim(),f.dec); if(outAmt.value.trim())want=parseUnits(outAmt.value.trim(),t.dec); if((dutch||bid)&&floorAmt.value.trim())floor=parseUnits(floorAmt.value.trim(),bid?f.dec:t.dec)} catch{badOrderAmount=true} if(badOrderAmount){swap.textContent="Invalid order amount";swap.disabled=true;return} if(!sell||!want){swap.textContent=bid?"Place bid":"Place order";swap.disabled=true;return} if(bid){ if(t.addr===ZERO){swap.textContent="Pick a token to bid for";swap.disabled=true;return} if((f.addr===ZERO?WETH:f.addr).toLowerCase()===t.addr.toLowerCase()){ swap.textContent="Pick different token";swap.disabled=true;return} if(!floor){swap.textContent="Set an opening bid";swap.disabled=true;return} if(floor>sell){swap.textContent="Opening exceeds maximum";swap.disabled=true;return} if(+dly.value===0){swap.textContent="Choose a bid window";swap.disabled=true;return} if(sell>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} swap.textContent=`Bid ${trimAmt(floor,f.dec)} → ${trimAmt(sell,f.dec)} ${f.sym} for ${trimAmt(want,t.dec)} ${t.sym}`; swap.disabled=false;return; } if(f.addr===ZERO&&t.addr===WETH){swap.textContent="Pick different token";swap.disabled=true;return} if(!dutch&&f.addr===WETH&&t.addr===ZERO){swap.textContent="Pick different token";swap.disabled=true;return} if(dutch&&floor>want){swap.textContent="Floor exceeds start";swap.disabled=true;return} if(dutch&&+dly.value===0){swap.textContent="Choose decay duration";swap.disabled=true;return} if(sell>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} swap.textContent=dutch ?`Dutch ${trimAmt(sell,f.dec)} ${f.sym} · ${trimAmt(want,t.dec)} → ${trimAmt(floor,t.dec)} ${t.sym}` :`Place ${fill.value==="1"?"":"all-or-nothing "}order \u2014 ${trimAmt(sell,f.dec)} ${f.sym} \u2192 ${trimAmt(want,t.dec)} ${t.addr===ZERO?"WETH":t.sym}`; { const qs=t.addr===ZERO?"ETH":t.sym; rate.textContent=dutch ?`Falls from ${trimAmt(want,t.dec)} to ${trimAmt(floor,t.dec)} ${qs} over ${rel(+dly.value)}, then rests at ${trimAmt(floor,t.dec)} ${qs} ` +(+dRest.value?`for ${rel(+dRest.value)} and returns to you`:`until you cancel`) :`${fill.value==="1"?"Fills in any amount":"Fills all at once"} at ${trimAmt(want,t.dec)} ${t.addr===ZERO?"WETH":t.sym} for ${trimAmt(sell,f.dec)} ${f.sym}` +(+dly.value?`, expiring in ${rel(+dly.value)}`:", and never expires"); } swap.disabled=false;return; } if(tab==="send"){ if(!sendReady){ let n=0n;try{n=parseUnits(amt.value.trim(),TOKENS[fromSel.value].dec)}catch{} swap.textContent=n>0n?"Name a recipient":"Enter an amount"; swap.disabled=true;return} if(sendReady.amount>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} const a=`${trimAmt(sendReady.amount,sendReady.token.dec)} ${sendReady.token.sym}`,dv=+dly.value; if(dv&&sendTipWei>0n&&sendReady.token.addr===ZERO &&sendReady.amount+sendTipWei>fromBalance){ swap.textContent="Insufficient ETH for amount + keeper tip";swap.disabled=true;return} swap.textContent=(dv?`Lock ${a} for ${rel(dv)}`:`Send ${a}`)+` \u2192 ${sendReady.label}`; swap.disabled=false;return; } if(quoting){swap.textContent="Quoting…";swap.disabled=true;return} if(!last){swap.textContent="Swap";swap.disabled=true;return} if(last.amountIn>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} swap.textContent="Swap";swap.disabled=false; }; syncIcons(); render(); const isRejection=e=>e?.code===4001||/user\s+reject|user\s+denied/i.test(e?.message||""); const W6963=[]; addEventListener("eip6963:announceProvider",e=>{ const d=e.detail; if(!d||!d.provider||!d.info||W6963.some(x=>x.info.uuid===d.info.uuid))return; W6963.push(d)}); const wkScan=()=>{try{dispatchEvent(new Event("eip6963:requestProvider"))}catch{}}; wkScan(); const WC_PID="1e8390ef1c1d8a185e035912a1409749"; const WC_ICON='<svg viewBox="0 0 400 400" fill="none" aria-hidden="true"><circle cx="200" cy="200" r="200" fill="#3396ff"/><path fill="#fff" d="m122.519 148.965c42.791-41.729 112.171-41.729 154.962 0l5.15 5.022c2.14 2.086 2.14 5.469 0 7.555l-17.617 17.18c-1.07 1.043-2.804 1.043-3.874 0l-7.087-6.911c-29.853-29.111-78.253-29.111-108.106 0l-7.59 7.401c-1.07 1.043-2.804 1.043-3.874 0l-17.617-17.18c-2.14-2.086-2.14-5.469 0-7.555zm191.397 35.529 15.679 15.29c2.14 2.086 2.14 5.469 0 7.555l-70.7 68.944c-2.139 2.087-5.608 2.087-7.748 0l-50.178-48.931c-.535-.522-1.402-.522-1.937 0l-50.178 48.931c-2.139 2.087-5.608 2.087-7.748 0l-70.7015-68.945c-2.1396-2.086-2.1396-5.469 0-7.555l15.6795-15.29c2.1396-2.087 5.6085-2.087 7.7481 0l50.1789 48.932c.535.522 1.402.522 1.937 0l50.177-48.932c2.139-2.087 5.608-2.087 7.748 0l50.179 48.932c.535.522 1.402.522 1.937 0l50.179-48.931c2.139-2.087 5.608-2.087 7.748 0z"/></svg>'; const WCU=(()=>{ const rl=(v,c)=>(v<<c|v>>>(32-c))>>>0; const wds=b=>{const o=[];for(let i=0;i<b.length;i+=4)o.push((b[i]|b[i+1]<<8|b[i+2]<<16|b[i+3]<<24)>>>0);return o}; function cblk(x,k,n,c){ const s=[0x61707865,0x3320646e,0x79622d32,0x6b206574, k[0],k[1],k[2],k[3],k[4],k[5],k[6],k[7],c,n[0],n[1],n[2]]; for(let i=0;i<16;i++)x[i]=s[i]; for(let r=0;r<10;r++){ const q=(a,b,cc,d)=>{ x[a]=(x[a]+x[b])>>>0;x[d]=rl(x[d]^x[a],16); x[cc]=(x[cc]+x[d])>>>0;x[b]=rl(x[b]^x[cc],12); x[a]=(x[a]+x[b])>>>0;x[d]=rl(x[d]^x[a],8); x[cc]=(x[cc]+x[d])>>>0;x[b]=rl(x[b]^x[cc],7)}; q(0,4,8,12);q(1,5,9,13);q(2,6,10,14);q(3,7,11,15); q(0,5,10,15);q(1,6,11,12);q(2,7,8,13);q(3,4,9,14);} for(let i=0;i<16;i++)x[i]=(x[i]+s[i])>>>0;} function chacha(key,nonce,ctr,src){ const k=wds(key),n=wds(nonce),x=new Uint32Array(16),out=new Uint8Array(src.length); for(let p=0;p<src.length;p+=64){cblk(x,k,n,ctr++); for(let i=0;i<64&&p+i<src.length;i++)out[p+i]=src[p+i]^(x[i>>2]>>>((i&3)*8)&255);} return out} functi
0x68575b07…67c7·#25,913,371·0x63d3880d…1ed076
urn await r.json()} catch(e){laneThrew(name,e);return null}finally{clearTimeout(k)}}; const LANE_TTL=15000,LANE_MAX=64; const _laneHit=new Map(),_laneWait=new Map(); const laneCached=(k,go)=>{ const c=_laneHit.get(k); if(c&&Date.now()-c.t<LANE_TTL){_laneHit.delete(k);_laneHit.set(k,c);return Promise.resolve(c.v)} const w=_laneWait.get(k); if(w)return w; const pr=go().then(v=>{ _laneWait.delete(k); if(v!==null&&v!==undefined){_laneHit.set(k,{v,t:Date.now()}); if(_laneHit.size>LANE_MAX)_laneHit.delete(_laneHit.keys().next().value);} return v},e=>{_laneWait.delete(k);throw e}); _laneWait.set(k,pr);return pr}; let laneOverride=null,laneOverrideFor=""; const laneScope=()=>`${fromSel.value}|${toSel.value}|${tab}|${mode}`; const lanePin=()=>laneOverride&&laneOverrideFor===laneScope()?laneOverride:null; const laneGet=async(name,url,ms)=>{ const ac=new AbortController(),k=setTimeout(()=>ac.abort(),ms); try{const r=await fetch(url,{signal:ac.signal});laneSaw(name,r.status); if(!r.ok)return null;return await r.json()} catch(e){laneThrew(name,e);return null}finally{clearTimeout(k)}}; const SOLVER_SHAPES_OUT={ "0x":async(ep,f,t,amt,slip,ms)=>{ const q=new URLSearchParams({chainId:chainCfg().slvId,sellToken:f===ZERO?OX_ETH:f,buyToken:t===ZERO?OX_ETH:t, buyAmount:amt.toString(),taker:solverExec,slippageBps:String(slip)}); const j=await laneGet("0x",`${ep}/swap/allowance-holder/quote?${q}`,ms); if(!j||!j.transaction||!j.estimatedNetSellAmount)return null; return{inp:BigInt(j.estimatedNetSellAmount), cap:j.maxSellAmount?BigInt(j.maxSellAmount):0n, target:j.transaction.to,spender:j.allowanceTarget||j.transaction.to,data:j.transaction.data}}, "ParaSwap":async(ep,f,t,amt,slip,ms,fd,td)=>{ const q=new URLSearchParams({srcToken:f===ZERO?OX_ETH:f,destToken:t===ZERO?OX_ETH:t, amount:amt.toString(),srcDecimals:String(fd??18),destDecimals:String(td??18), side:"BUY",network:chainCfg().slvId,version:"6.2"}); const pr=await laneGet("ParaSwap",`${ep}/prices?${q}`,ms); const route=pr&&pr.priceRoute;if(!route||!route.srcAmount)return null; const j=await lanePost("ParaSwap",`${ep}/transactions/${chainCfg().slvId}?ignoreChecks=true`,{ srcToken:f===ZERO?OX_ETH:f,destToken:t===ZERO?OX_ETH:t, srcDecimals:route.srcDecimals,destDecimals:route.destDecimals, priceRoute:route,userAddress:solverExec,slippage:Number(slip)},ms); if(!j||!j.to||!j.data)return null; return{inp:bmax(BigInt(route.srcAmount),BigInt(route.srcAmountAfterFee||route.srcAmount)), target:j.to,spender:route.tokenTransferProxy||j.to,data:j.data}}, }; const SOLVER_SHAPES={ "0x":async(ep,f,t,amt,slip,ms)=>{ const q=new URLSearchParams({chainId:chainCfg().slvId,sellToken:f===ZERO?OX_ETH:f,buyToken:t===ZERO?OX_ETH:t, sellAmount:amt.toString(),taker:solverExec,slippageBps:String(slip)}); const j=await laneGet("0x",`${ep}/swap/allowance-holder/quote?${q}`,ms); if(!j||!j.transaction||!j.buyAmount)return null; return{out:BigInt(j.buyAmount),target:j.transaction.to, spender:j.allowanceTarget||j.transaction.to,data:j.transaction.data}}, "ParaSwap":async(ep,f,t,amt,slip,ms,fd,td)=>{ const q=new URLSearchParams({srcToken:f===ZERO?OX_ETH:f,destToken:t===ZERO?OX_ETH:t, amount:amt.toString(),srcDecimals:String(fd??18),destDecimals:String(td??18), side:"SELL",network:chainCfg().slvId,version:"6.2"}); const pr=await laneGet("ParaSwap",`${ep}/prices?${q}`,ms); const route=pr&&pr.priceRoute;if(!route||!route.destAmount)return null; const j=await lanePost("ParaSwap",`${ep}/transactions/${chainCfg().slvId}?ignoreChecks=true`,{ srcToken:f===ZERO?OX_ETH:f,destToken:t===ZERO?OX_ETH:t, srcDecimals:route.srcDecimals,destDecimals:route.destDecimals, priceRoute:route,userAddress:solverExec,slippage:Number(slip)},ms); if(!j||!j.to||!j.data)return null; return{out:bmin(BigInt(route.destAmount),BigInt(route.destAmountAfterFee||route.destAmount)), target:j.to,spender:route.tokenTransferProxy||j.to,data:j.data}}, "KyberSwap":async(ep,f,t,amt,slip,ms)=>{ const q=new URLSearchParams({tokenIn:f===ZERO?OX_ETH:f,tokenOut:t===ZERO?OX_ETH:t,amountIn:amt.toString()}); const pr=await laneGet("KyberSwap",`${ep}/${chainCfg().slvSlug}/api/v1/routes?${q}`,ms); const rs=pr&&pr.data&&pr.data.routeSummary;if(!rs||!rs.amountOut)return null; const j=await lanePost("KyberSwap",`${ep}/${chainCfg().slvSlug}/api/v1/route/build`, {routeSummary:rs,sender:solverExec,recipient:solverExec,slippageTolerance:Number(slip)},ms); const d=j&&j.data;if(!d||!d.data||!d.routerAddress)return null; return{out:BigInt(rs.amountOut),target:d.routerAddress,spender:d.routerAddress,data:d.data}}, "Enso":async(ep,f,t,amt,slip,ms)=>{ const q=new URLSearchParams({chainId:chainCfg().slvId,fromAddress:solverExec,receiver:solverExec, tokenIn:f===ZERO?OX_ETH:f,tokenOut:t===ZERO?OX_ETH:t,amountIn:amt.toString(), slippage:String(slip),routingStrategy:"router"}); const j=await laneGet("Enso",`${ep}?${q}`,ms); if(!j||!j.amountOut||!j.tx||!j.tx.to)return null; return{out:BigInt(j.amountOut),target:j.tx.to,spender:j.tx.to,data:j.tx.data}}, "OKX":async(ep,f,t,amt,slip,ms)=>{ const q=new URLSearchParams({chainIndex:chainCfg().slvId,fromTokenAddress:f===ZERO?OX_ETH:f, toTokenAddress:t===ZERO?OX_ETH:t,amount:amt.toString(), slippagePercent:(Number(slip)/100).toString(),userWalletAddress:solverExec,swapMode:"exactIn"}); const j=await laneGet("OKX",`${ep}/dex/aggregator/swap?${q}`,ms); const d=j&&j.code==="0"&&j.data&&j.data[0]; if(!d||!d.tx||!d.tx.to||!d.routerResult||!d.routerResult.toTokenAmount)return null; let sp=f===ZERO?d.tx.to:OKX_APPROVE[CHAIN_ID]; if(!sp){const a=await laneGet("OKX",`${ep}/dex/aggregator/approve-transaction?${new URLSearchParams({chainIndex:chainCfg().slvId,tokenContractAddress:f,approveAmount:amt.toString()})}`,ms); const x=a&&a.code==="0"&&a.data&&a.data[0]&&a.data[0].dexContractAddress;if(isAddr(x))sp=OKX_APPROVE[CHAIN_ID]=x} if(!sp)return null; return{out:BigInt(d.routerResult.toTokenAmount),target:d.tx.to,spender:sp,data:d.tx.data}}, "Bebop":async(ep,f,t,amt,slip,ms)=>{ if(f===ZERO||t===ZERO)return null; const q=new URLSearchParams({sell_tokens:f,buy_tokens:t,sell_amounts:amt.toString(), taker_address:solverExec,gasless:"false",approval_type:"Standard"}); const j=await laneGet("Bebop",`${ep}/quote?${q}`,ms); const rs=j&&j.routes;if(!rs||!rs.length)return null; const route=rs.find(x=>x.type===j.bestPrice)||rs[0]; const qq=route&&route.quote;if(!qq||!qq.tx||!qq.buyTokens)return null; const info=Object.values(qq.buyTokens)[0]; if(!info||!info.amount||!qq.tx.to)return null; const bebSp=qq.approvalTarget||qq.approval_target; if(!isAddr(bebSp||""))return null; return{out:BigInt(info.amount),target:qq.tx.to,spender:bebSp,data:qq.tx.data}}, "1inch":async(ep,f,t,amt,slip,ms)=>{ const q=new URLSearchParams({src:f===ZERO?OX_ETH:f,dst:t===ZERO?OX_ETH:t,amount:amt.toString(), from:solverExec,origin:solverExec,slippage:String(Number(slip)/100),disableEstimate:"true"}); const j=await laneGet("1inch",`${ep}/swap/v6.0/${chainCfg().slvId}/swap?${q}`,ms); if(!j||!j.tx||!j.dstAmount)return null; return{out:BigInt(j.dstAmount),target:j.tx.to,spender:j.tx.to,data:j.tx.data}}, }; const RPC_TRIES=4; const nodeRead=async(method,params)=>{ let last; const pin=rpcPin(); const tries=Math.min(rpcPool.length,RPC_TRIES); const p=rpcPi; for(let i=pin?-1:0;i<tries;i++){ const j=(p+i)%rpcPool.length; const u=i<0?pin:rpcPool[j]; try{const r=await httpRead(u,method,params);if(i>=0)rpcPi=j; if(!curated)loadCurated().catch(()=>{}); return r} catch(e){last=e;if(!e.retry)throw e}} throw last||Error("no read node answered")}; const isSign=m=>m===S||m.indexOf("wallet_")===0||m==="eth_requestAccounts"||m==="eth_accounts"||WC_SIGN.indexOf(m)>=0; const hexOk=r=>typeof r==="string"&&!/^0x[0-9a-fA-F]*$/.test(r)?Promise.reject(Error("bad hex from node")):r; const rpc=(method,params)=>{const e=prov(); const p=e?e.request({method,params}) :isSign(method)?Promise.reject(Object.assign(Error(NO_WALLET),{noWallet:true})):nodeRead(method,params); return method===C?p.then(hexOk):p}; const rpcRead=async(method,params,tries=4)=>{ let last; for(let i=0;i<tries;i++){ try{return await rpc(method,params)} catch(e){last=e;if(i+1<tries)await new Promise(r=>setTimeout(r,300*(i+1)))} } throw last; }; const okRet=h=>{h=strip0x(h);if(h&&BigInt("0x"+h)===0n)throw Error("token returned false")}; const waitTx=async hash=>{ const end=Date.now()+600000; let misses=0,wait=1500; while(Date.now()<end){ try{ const r=await rpc("eth_getTransactionReceipt",[hash]); misses=0; if(r){ if(r.status!=="0x1")throw Error("tx reverted: "+hash); return r; } wait=Math.min(wait+500,5000); }catch(e){ if(/tx reverted/.test(e.message||""))throw e; if(++misses>2)wait=Math.min(wait*2,15000); } await new Promise(res=>setTimeout(res,wait)); } throw Error("tx not mined in time"); }; const checkWallet=async()=>{if(!account)throw Error("Connect a wallet first.");const[c,a]=await Promise.all([rpc(I,[]),rpc("eth_accounts",[])]);if(+c!==CHAIN_ID)throw Error("Switch your wallet to "+chainCfg().name);if(a[0]?.toLowerCase()!==account.toLowerCase())throw Error("wallet changed")}; const hexToBytes=h=>{const s=strip0x(h);const o=new Uint8Array(s.length/2);for(let i=0;i<o.length;i++)o[i]=parseInt(s.slice(i*2,i*2+2),16);return o}; const KRC=[1n,0x8082n,0x800000000000808an,0x8000000080008000n,0x808bn,0x80000001n,0x8000000080008081n,0x8000000000008009n,0x8an,0x88n,0x80008009n,0x8000000an,0x8000808bn,0x800000000000008bn,0x8000000000008089n,0x8000000000008003n,0x8000000000008002n,0x8000000000000080n,0x800an,0x800000008000000an,0x8000000080008081n,0x8000000000008080n,0x80000001n,0x8000000080008008n]; const KR=[0,1,62,28,27,36,44,6,55,20,3,10,43,25,39,41,45,15,21,8,18,2,61,56,14]; const KM=(1n<<64n)-1n; const krol=(x,n)=>n?((x<<BigInt(n))|(x>>BigInt(64-n)))&KM:x; const KCc=new Array(5),KB=new Array(25); const keccak=b=>{const A=new Array(25).fill(0n),rate=136; const pad=rate-(b.length%rate),buf=new Uint8Array(b.length+pad);buf.set(b); buf[b.length]|=1;buf[buf.length-1]|=0x80; for(let o=0;o<buf.length;o+=rate){ for(let i=0;i<17;i++){let v=0n;for(let j=7;j>=0;j--)v=(v<<8n)|BigInt(buf[o+i*8+j]);A[i]^=v} for(let r=0;r<24;r++){ for(let x=0;x<5;x++)KCc[x]=A[x]^A[x+5]^A[x+10]^A[x+15]^A[x+20]; for(let x=0;x<5;x++){const D=KCc[(x+4)%5]^krol(KCc[(x+1)%5],1);for(let y=0;y<25;y+=5)A[x+y]^=D} for(let x=0;x<5;x++)for(let y=0;y<5;y++)KB[y+5*((2*x+3*y)%5)]=krol(A[x+5*y],KR[x+5*y]); for(let x=0;x<5;x++)for(let y=0;y<5;y++)A[x+5*y]=KB[x+5*y]^((~KB[(x+1)%5+5*y]&KM)&KB[(x+2)%5+5*y]); A[0]^=KRC[r]}} let out="";for(let i=0;i<4;i++){let v=A[i];for(let j=0;j<8;j++){out+=Number(v&0xffn).toString(16).padStart(2,"0");v>>=8n}} return "0x"+out}; const namehash=n=>{let nd="0x"+"0".repeat(64); if(n)for(const l of n.split(".").reverse())nd=keccak(new Uint8Array([...hexToBytes(nd),...hexToBytes(keccak(new TextEncoder().encode(l)))])); return nd}; const strTail=v=>{const b=new TextEncoder().encode(v);let h=""; for(const x of b)h+=x.toString(16).padStart(2,"0"); return encUint(b.length)+h.padEnd(Math.ceil(h.length/64)*64,"0")}; const encStr=v=>pad32("20")+strTail(v); const retAddr=h=>{const s=strip0x(h||""); if(s.length!==64||/[^0-9a-fA-F]/.test(s)||/[^0]/.test(s.slice(0,24)))return""; const a="0x"+s.slice(24).toLowerCase(); return /^0x0{40}$/.test(a)?"":a}; const nameOk=n=>/^[a-z0-9.-]+$/.test(n) &&n.split(".").every(l=>l.length>0&&l.length<64&&l[0]!=="-"&&l[l.length-1]!=="-"); const nsFwd=async(reg,n)=>{const id=await cfgRead(C,[{to:reg,data:"0x"+SEL_CID+encStr(n)},L]); if(strip0x(id||"").length!==64)return""; return retAddr(await cfgRead(C,[{to:reg,data:"0x"+SEL_RES+strip0x(id)},L]))}; const dnsEnc=n=>{let h=""; for(const l of n.split(".")){const b=new TextEncoder().encode(l); h+=b.length.toString(16).padStart(2,"0"); for(const x of b)h+=x.toString(16).padStart(2,"0")} return h+"00"}; const encBytes=h=>{h=strip0x(h);return encUint(h.length/2)+h.padEnd(Math.ceil(h.length/64)*64,"0")}; const encResolve=(dns,inner)=>{const a=encBytes(dns); return pad32("40")+encUint(64+a.length/2)+a+encBytes(inner)}; const decBytes=hex=>{const h=strip0x(hex||""); if(h.length<128)return""; const off=Number(BigInt("0x"+h.slice(0,64)))*2; if(!Number.isSafeInteger(off)||off+64>h.length)return""; const len=Number(BigInt("0x"+h.slice(off,off+64)))*2; if(!Number.isSafeInteger(len)||off+64+len>h.length)return""; return "0x"+h.slice(off+64,off+64+len)}; const ensFind=async n=>{const parts=n.split("."), nodes=parts.map((_,i)=>namehash(parts.slice(i).join("."))); const rs=await Promise.all(nodes.map(nd=> cfgRead(C,[{to:ENSREG,data:"0x"+SEL_RSLV+pad32(nd)},L]).catch(()=>null))); for(let i=0;i<nodes.length;i++){const r=retAddr(rs[i]); if(r)return{r,nd:nodes[i],exact:i===0}} return null}; const ensWild=async r=>{ try{const s=await cfgRead(C,[{to:r,data:supports(SEL_ERESOLVE)},L]); return strip0x(s||"").length===64&&BigInt(s)===1n}catch{return false}}; const ensFwd=async n=>{const f=await ensFind(n); if(!f)return""; const inner="0x"+SEL_EADDR+pad32(f.nd); if(f.exact)return retAddr(await cfgRead(C,[{to:f.r,data:inner},L])); if(!await ensWild(f.r))return""; try{return retAddr(decBytes(await cfgRead(C,[{to:f.r, data:"0x"+SEL_ERESOLVE+encResolve(dnsEnc(n),inner)},L])))} catch(e){ if(/556f1830/i.test((e&&e.data?JSON.stringify(e.data):"")+((e&&e.message)||""))) throw Object.assign(Error(NAME_OFFCHAIN),{offchain:true}); return""; }}; const ensRevName=async(nd,r)=>{ if(!r)return""; try{return decodeString(await cfgRead(C,[{to:r,data:"0x"+SEL_ENAME+pad32(nd)},L])).trim()}catch{return""}}; const nameFwd=n=>/\.gwei$/i.test(n)?nsFwd(GNS,n):/\.wei$/i.test(n)?nsFwd(WNS,n):/\.eth$/i.test(n)?ensFwd(n):Promise.resolve(""); const FWD_TTL=30000; const fwdCache=new Map(); const nameFwdCached=n=>{const hit=fwdCache.get(n); if(hit&&hit.exp>Date.now())return hit.p; const p=nameFwd(n); fwdCache.set(n,{p,exp:Date.now()+FWD_TTL}); p.catch(()=>fwdCache.delete(n)); return p}; const nameRev=async a=>{ const nd=namehash(strip0x(a).toLowerCase()+".addr.reverse"); const [w,g,er]=await Promise.all([ {to:WNS,data:"0x"+SEL_REV+encAddr(a)}, {to:GNS,data:"0x"+SEL_REV+encAddr(a)}, {to:ENSREG,data:"0x"+SEL_RSLV+pad32(nd)}] .map(c=>cfgRead(C,[c,L]).catch(()=>null))); const str=h=>{try{return h?decodeString(h).trim():""}catch{return""}}; const agrees=async n=>{ if(!n||!nameOk(n.toLowerCase()))return""; let f="";try{f=await nameFwdCached(n.toLowerCase())}catch{} return f&&f.toLowerCase()===a.toLowerCase()?n:""}; return await agrees(str(w))||await agrees(str(g)) ||await agrees(await ensRevName(nd,retAddr(er)))||""}; const decodeString=hex=>{ const h=strip0x(hex); if(h.length<=64)return new TextDecoder().decode(hexToBytes(h)).replace(/\0+$/,""); const off=Number(BigInt("0x"+h.slice(0,64)))*2; const len=Number(BigInt("0x"+h.slice(off,off+64)))*2; return new TextDecoder().decode(hexToBytes(h.slice(off+64,off+64+len))); }; const kecStr=v=>keccak(new TextEncoder().encode(v)); const ckAddr=a=>{const s=strip0x(a); if(s===s.toLowerCase()||s===s.toUpperCase())return true; const h=strip0x(kecStr(s.toLowerCase())); for(let i=0;i<40;i++){const c=s[i]; if(c>="a"&&c<="f"&&parseInt(h[i],16)>=8)return false; if(c>="A"&&c<="F"&&parseInt(h[i],16)<8)return false} return true}; const domainSep=(name,ver,tok)=>keccak(hexToBytes( kecStr(EIP712DOM)+pad32(kecStr(name))+pad32(kecStr(ver))+encUint(CHAIN_ID)+encAddr(tok))); const permitInfo=async(tok,owner)=>{ try{ const[ds,th,nc,nm]=await mc3Deep([ {to:tok,data:"0x"+SEL_DS},{to:tok,data:"0x"+SEL_PERMIT_TH}, {to:tok,data:"0x"+SEL_NONCES+encAddr(owner)},{to:tok,data:"0x"+SEL_NAME}]); if(!ds||ds==="0x")return null; if(th&&strip0x(th).length>=64&&strip0x(th).slice(0,64).toLowerCase()!==TH_2612)return null; if(!nc||!nm)return null; const nonce=BigInt(nc); const name=decodeString(nm); for(const ver of ["1","2","1.0","V1",""])if(domainSep(name,ver,tok).toLowerCase()===ds.toLowerCase()) return{name,ver,nonce}; }catch{} return null}; const signPermit=async(tok,info,owner,value,deadline)=>{ const td={types:{EIP712Domain:[{name:"name",type:"string"},{name:"version",type:"string"}, {name:"chainId",type:"uint256"},{name:"verifyingContract",type:"address"}], Permit:[{name:"owner",type:"address"},{name:"spender",type:"address"}, {name:"value",type:"uint256"},{name:"nonce",type:"uint256"},{name:"deadline",type:"uint256"}]}, primaryType:"Permit",domain:{name:info.name,version:info.ver,chainId:CHAIN_ID,verifyingContract:tok}, message:{owner,spender:ZROUTER,value:value.toString(),nonce:info.nonce.toString(),deadline:deadline.toString()}}; const sig=strip0x(await rpc("eth_signTypedData_v4",[owner,JSON.stringify(td)])); if(sig.length!==130)throw Error("bad signature"); let v=parseInt(sig.slice(128,130),16);if(v<27)v+=27; return"0x"+SEL_RPERMIT+encAddr(tok)+encUint(value)+encUint(deadline)+encUint(v)+sig.slice(0,64)+sig.slice(64,128)}; const P2DOM=()=>({name:"Permit2",chainId:CHAIN_ID,verifyingContract:PERMIT2}); const p2Ready=async(tok,owner)=>{ try{return BigInt(await rpc(C,[{to:tok,data:"0x"+SEL_ALLOWANCE+encAddr(owner)+encAddr(PERMIT2)},L]))}catch{return 0n}}; const signPermit2=async(tok,owner,amount,deadline)=>{ const nonce=BigInt("0x"+[...crypto.getRandomValues(new Uint8Array(31))].map(b=>b.toString(16).padStart(2,"0")).join("")); const td={types:{EIP712Domain:[{name:"name",type:"string"},{name:"chainId",type:"uint256"}, {name:"verifyingContract",type:"address"}], TokenPermissions:[{name:"token",type:"address"},{name:"amount",type:"uint256"}], PermitTransferFrom:[{name:"permitted",type:"TokenPermissions"},{name:"spender",type:"address"}, {name:"nonce",type:"uint256"},{name:"deadline",type:"uint256"}]}, primaryType:"PermitTransferFrom",domain:P2DOM(), message:{permitted:{token:tok,amount:amount.toString()},spender:ZROUTER, nonce:nonce.toString(),deadline:deadline.toString()}}; const sig=strip0x(await rpc("eth_signTypedData_v4",[owner,JSON.stringify(td)])); const n=sig.length/2;if(!Number.isInteger(n)||n===0||n>4096)throw Error("bad signature"); const norm=n===65&&parseInt(sig.slice(128,130),16)<27 ?sig.slice(0,128)+(parseInt(sig.slice(128,130),16)+27).toString(16).padStart(2,"0") :sig; const tail=encUint(n)+norm.padEnd(Math.ceil(norm.length/64)*64,"0"); return"0x"+SEL_P2TF+encAddr(tok)+encUint(amount)+encUint(nonce)+encUint(deadline)+pad32("a0")+tail}; const capsFor=(c,id)=>{ for(const key of Object.keys(c)){ try{if(BigInt(key)===id)return c[key]}catch{} } return undefined}; const hasAtomicBatch=c=>{ if(!c||typeof c!=="object")return false; const own=capsFor(c,BigInt(CHAIN_ID)); const k=own!==undefined?own:capsFor(c,0n); return!!(k&&k.atomic&&(k.atomic.status==="supported"||k.atomic.status==="ready"))}; const canBatch=async owner=>{ try{const c=await rpc("wallet_getCapabilities",[owner]); return hasAtomicBatch(c)}catch{return false}}; let snd=LS.snd!=="0"; let AC;const play=ns=>{if(!snd)return;try{AC=AC||new(window.AudioContext||window.webkitAudioContext);if(AC.state==="suspended")AC.resume();const t=AC.currentTime+.02;ns.forEach(([f,d,l,v])=>{const o=AC.createOscillator(),g=AC.createGain();o.type="triangle";o.frequency.value=f;o.connect(g);g.connect(AC.destination);g.gain.setValueAtTime(0,t+d);g.gain.linearRampToValueAtTime(v,t+d+.012);g.gain.exponentialRampToValueAtTime(1e-4,t+d+l);o.start(t+d);o.stop(t+d+l+.02)})}catch{}}; const sBlip=()=>play([[880,0,.07,.05]]); const sOpen=()=>play([[523.25,0,.3,.1],[783.99,.09,.3,.1]]); const sSend=()=>play([[392,0,.12,.07],[587.33,.07,.24,.07]]); const sGot=()=>play([[392,0,.11,.09],[493.88,.08,.11,.09],[587.33,.16,.11,.09],[783.99,.24,.6,.12]]); const sFail=()=>play([[311.13,0,.18,.07],[233.08,.13,.4,.07]]); const sPew=()=>play([[720,0,.04,.03]]); const sHit=()=>play([[440,0,.07,.05],[330,.04,.09,.04]]); const sendTx=req=>rpc(S,req).then(r=>{sSend();return r}); const sendBatch=async(owner,calls)=>{ const res=await rpc("wallet_sendCalls",[{version:"2.0.0",chainId:toHex(CHAIN_ID),from:owner,atomicRequired:true,calls}]); sSend(); const id=typeof res==="string"?res:res.id; for(let i=0;i<600;i++){ try{const st=await rpc("wallet_getCallsStatus",[id]); const rs=(st&&st.receipts)||[]; if(rs.some(r=>r&&r.status==="0x0"))throw Error("batch reverted"); const q=String(st&&st.status),done=q==="200"||/CONFIRMED|SUCCESS/i.test(q); const last=rs.length&&(done||rs.length>=calls.length)?rs[rs.length-1]:null,x=last&&(last.transactionHash||last.hash); if(x)return x; if(q==="400"||q==="500"||/fail|revert|reject/i.test(q))throw Error("batch failed"); if(done)throw Error("batch confirmed on chain, but the wallet returned no receipt — check your wallet's activity before retrying"); }catch(e){if(/^batch /.test(e.message))throw e} await new Promise(r=>setTimeout(r,1000))} throw Error("batch timed out")}; const encCalls=cs=>{const n=cs.length;let head="",tail="";let off=n*32; for(const c of cs){const d=strip0x(c),len=d.length/2; head+=encUint(off);const body=encUint(len)+d.padEnd(Math.ceil(d.length/64)*64,"0"); tail+=body;off+=body.length/2} return"0x"+SEL_MULTICALL+pad32("20")+encUint(n)+head+tail}; const MC3="0xcA11bde05977b3631167028862bE2a173976CA11"; const encAgg3=cs=>{const n=cs.length;let head="",tail="",off=n*32; for(const c of cs){const d=strip0x(c.data),len=d.length/2; const body=encAddr(c.to)+encBool(true)+encUint(96)+encUint(len)+d.padEnd(Math.ceil(d.length/64)*64,"0"); head+=encUint(off);tail+=body;off+=body.length/2} return"0x82ad56cb"+pad32("20")+encUint(n)+head+tail}; let mcOk=0,mcFailed=0; const decAgg3=hex=>{const h=strip0x(hex),at=i=>h.slice(i*64,(i+1)*64); const n=Number(BigInt("0x"+at(1))),out=[]; for(let i=0;i<n;i++){const rel=Number(BigInt("0x"+at(2+i)))/32,w=2+rel; const ok=BigInt("0x"+at(w))===1n,bo=Number(BigInt("0x"+at(w+1)))/32,lw=w+bo; const len=Number(BigInt("0x"+at(lw))); if(ok)mcOk++;else mcFailed++; out.push(ok?"0x"+h.slice((lw+1)*64,(lw+1)*64+len*2):null)} return out}; const mc3=async(cs,tag=L)=>cs.length?decAgg3(await rpc(C,[{to:MC3,data:encAgg3(cs)},tag])):[]; const MC3_MAX=16; const MC3_COLD=8; const mc3Clamp=n=>Number.isFinite(n)&&n>=2&&n<=MC3_MAX?Math.floor(n):MC3_COLD; let mc3Cap=mc3Clamp(Number(LS["zswap:mc3"])); let mc3Won=0; const mc3Save=()=>{try{LS["zswap:mc3"]=String(mc3Cap)}catch{}}; const cfgBatch=async(cs,tag)=>{ if(CHAIN_ID===1)return mc3Deep(cs,tag); if(!cs.length)return[]; try{return decAgg3(await l1Read("eth_call",[{to:MC3,data:encAgg3(cs)},tag||L]))} catch{return cs.map(()=>null)} }; const mc3Split=(cs,n)=>{const o=[];for(let i=0;i<cs.length;i+=n)o.push(cs.slice(i,i+n));return o}; const MC3_LANES=12; let mc3Live=0; const mc3Waiting=[]; const mc3Gate=async fn=>{ while(mc3Live>=MC3_LANES)await new Promise(r=>mc3Waiting.push(r)); mc3Live++; try{return await fn()} finally{mc3Live--;const next=mc3Waiting.shift();if(next)next()} }; let mcGaveUp=0; const mc3Deep=async(cs,tag=L,depth=0)=>{ if(!cs.length)return[]; const fan=async n=>(await Promise.all(mc3Split(cs,n).map(c=>mc3Deep(c,tag,depth+1)))).flat(); if(cs.length>mc3Cap&&depth<8)return fan(mc3Cap); try{ const r=await mc3Gate(()=>mc3(cs,tag)); if(cs.length>=mc3Cap&&mc3Cap<MC3_MAX&&r.every(x=>x!=null)&&++mc3Won>=2){mc3Cap++;mc3Won=0;mc3Save()} return r; } catch(e){ mc3Won=0; if(cs.length<2||depth>=8){mcGaveUp+=cs.length;return cs.map(()=>null)} const h=cs.length>>1; if(h<mc3Cap){mc3Cap=h;mc3Save()} await new Promise(r=>setTimeout(r,150*(depth+1))); return fan(h); } }; let bnVal="",bnAt=0,bnJob=null; const BN_TTL=12000; const bnFetch=()=>bnJob||(bnJob=rpc("eth_blockNumber",[]) .then(n=>{bnVal=n;bnAt=Date.now();return n}) .finally(()=>{bnJob=null})); const blockNow=()=>{ const age=Date.now()-bnAt; if(!bnVal||age>=BN_TTL)return bnFetch(); if(age>BN_TTL/2)bnFetch().catch(()=>{}); return Promise.resolve(bnVal); }; const codeSeen={}; const CODE_MISS_TTL=60000; const hasCode=async a=>{ if(codeSeen[a]===true)return true; const miss=codeSeen[a]; if(miss&&Date.now()-miss<CODE_MISS_TTL)return false; const h=await rpc("eth_getCode",[a,L]); const ok=(h||"").length>4; codeSeen[a]=ok||Date.now(); return ok; }; const REF_N=100n; const CH_WICK_CAP=3; const IMPACT_HIDE=50n, IMPACT_WARN=500n, IMPACT_CONFIRM=1500n, IMPACT_TYPED=3000n; const impactBps=(v,ref,eo)=>{if(!v||!ref)return null;const lin=ref*REF_N; if(lin===0n)return 0n; return eo?(v<=lin?0n:(v-lin)*10000n/lin):(v>=lin?0n:(lin-v)*10000n/lin)}; const sa
0x68575b07…67c7·#25,913,369·0x34895439…58b5f4
t(":"); if(p.length!==5||p[0]!=="v1")continue; const other=p[1]==="0"?ZERO:p[1]; const hooks=p[4]==="0"?ZERO:p[4]; if(!/^0x[0-9a-f]{40}$/i.test(other)||!/^0x[0-9a-f]{40}$/i.test(hooks))continue; if(!/^\d{1,7}$/.test(p[2])||!/^\d{1,7}$/.test(p[3]))continue; const fee=Number(p[2]),ts=Number(p[3]); if(!Number.isInteger(fee)||!Number.isInteger(ts)||ts<1)continue; if(other.toLowerCase()===tokenAddr.toLowerCase())continue; const a=tokenAddr.toLowerCase(),b=other.toLowerCase(); const zeroFirst=BigInt(a)<BigInt(b); out.push({c0:zeroFirst?tokenAddr:other,c1:zeroFirst?other:tokenAddr,fee,ts,hooks}); if(out.length===8)break; } return out; } function v4PoolsFor(f,t){ const fa=f.addr.toLowerCase(),ta=t.addr.toLowerCase(); const seen=new Set(),out=[]; for(const tok of [f,t])for(const p of (tok.v4||[])){ const c0=p.c0.toLowerCase(),c1=p.c1.toLowerCase(); if(!((c0===fa&&c1===ta)||(c0===ta&&c1===fa)))continue; const id=`${c0}:${c1}:${p.fee}:${p.ts}:${p.hooks.toLowerCase()}`; if(seen.has(id))continue; seen.add(id); out.push({...p,zeroForOne:c0===fa}); } return out; } const encPoolKey=p=>encAddr(p.c0)+encAddr(p.c1)+encUint(p.fee)+encUint(p.ts)+encAddr(p.hooks); const SEL_QUOTE_BEST="2adaa389",SEL_EFFFEE="ef66de32"; let PROUTE=MB.pr; const SEL_PROUTE="5d6498e1",SEL_PCHECKPOINT="0b7c6c6c"; const SEL_PROUTEWETH="78fd78b0"; const SEL_ZAPIN="c98c2c0b"; const SEL_PREVIEW_ZAP="e7cddab0"; const encPRouteWeth=(pools,tokenOut,amountIn,minOut,to)=> "0x"+SEL_PROUTEWETH+encUint(160)+encAddr(tokenOut)+encUint(amountIn) +encUint(minOut)+encAddr(to)+encUint(pools.length)+pools.map(encAddr).join(""); const encPRoute=(pools,tokenIn,tokenOut,amountIn,minOut,to)=> "0x"+SEL_PROUTE+encUint(192)+encAddr(tokenIn)+encAddr(tokenOut)+encUint(amountIn) +encUint(minOut)+encAddr(to)+encUint(BigInt(pools.length))+pools.map(encAddr).join(""); const SEL_PAIR_COUNT="355da246"; const PPOLICY="0x00000045fc7b570Be4d71F67219508ebD295EC6D"; const SEL_ROUTABLE="9662c498",SEL_QUOTE_ALL="89941805"; const LQ_SCAN=128; const LQ_SHOW=24; const isEthLike=a=>a===ZERO||a.toLowerCase()===WETH.toLowerCase(); async function quotePrecision(f,t,amountIn,sender,block){ if(PPLENS===ZERO||PROUTE===ZERO||amountIn<=0n)return null; const nat=a=>a.toLowerCase()===WETH.toLowerCase()?ZERO:a; const vars=[]; const addVar=(a,b)=>{ const [x,y]=a.toLowerCase()<b.toLowerCase()?[a,b]:[b,a]; if(!vars.some(v=>v.c0===x&&v.c1===y&&v.tokenIn===a))vars.push({c0:x,c1:y,tokenIn:a}); }; const forms=a=>a===ZERO?[ZERO,WETH] :a.toLowerCase()===WETH.toLowerCase()?[WETH,ZERO]:[a]; for(const a of forms(f.addr))for(const b of forms(t.addr)){ if(nat(a)===nat(b))continue; addVar(a,b); } const reqs=[]; for(const v of vars)reqs.push( {to:PPLENS,data:"0x"+SEL_QUOTE_BEST+encAddr(v.c0)+encAddr(v.c1) +encAddr(sender)+encAddr(v.tokenIn)+encUint(amountIn)+encUint(0n)+encUint(BigInt(LQ_SCAN))}, {to:PFACTORY,data:"0x"+SEL_PAIR_COUNT+encAddr(v.c0)+encAddr(v.c1)}); const res=await mc3Deep(reqs,block); let pool="",out=0n,n=0,fA=f.addr,won=null; for(let i=0;i<vars.length;i++){ const q=res[i*2],c=res[i*2+1]; const b=decBest(q); if(!b)continue; if(b.out<=out)continue; pool=b.pool;out=b.out;fA=vars[i].tokenIn;won=vars[i]; try{n=c?Number(BigInt(c)):0}catch{n=0} } if(!pool||!won)return await hubHop(f,t,amountIn,sender,block); const tA=won.c0===fA?won.c1:won.c0; let fee=0n; const feeReq=p=>({to:PPLENS, data:"0x"+SEL_EFFFEE+encAddr(p)+encAddr(sender)+encAddr(fA)+encUint(amountIn)}); const [fx,rt]=await mc3Deep([feeReq(pool),routableReq([pool])],block); if(policyRefuses(rt,1)){ const alt=await bestRoutable(won.c0,won.c1,fA,amountIn,sender,block); if(!alt)return await hubHop(f,t,amountIn,sender,block); pool=alt.pool;out=alt.out; const [fx2]=await mc3Deep([feeReq(pool)],block); if(fx2)fee=BigInt(fx2); }else if(fx)fee=BigInt(fx); return {pools:[pool],out,fee,capped:n>LQ_SCAN,tokenIn:fA,tokenOut:tA, unwrap:fA===ZERO&&f.addr!==ZERO?amountIn:0n, wrap:fA!==ZERO&&f.addr===ZERO?amountIn:0n, outDiffers:(tA===ZERO)!==(t.addr===ZERO)}; } const routableReq=pools=>({to:PPOLICY, data:"0x"+SEL_ROUTABLE+encUint(32)+encUint(BigInt(pools.length))+pools.map(encAddr).join("")}); function policyRefuses(raw,n){ if(!raw)return false; const h=strip0x(raw); if(h.length<128+n*64)return false; const len=Number(BigInt("0x"+h.slice(64,128))); if(len!==n)return false; for(let i=0;i<n;i++)if(BigInt("0x"+h.slice(128+i*64,128+(i+1)*64))===0n)return true; return false; } async function bestRoutable(c0,c1,tokenIn,amountIn,sender,block){ const [raw]=await mc3Deep([{to:PPLENS,data:"0x"+SEL_QUOTE_ALL+encAddr(c0)+encAddr(c1) +encAddr(sender)+encAddr(tokenIn)+encUint(amountIn)+encUint(0n)+encUint(BigInt(LQ_SCAN))}],block); const cands=decAll(raw); if(!cands.length)return null; const [rt]=await mc3Deep([routableReq(cands.map(c=>c.pool))],block); if(!rt)return null; const h=strip0x(rt); if(h.length<128+cands.length*64)return null; let win=null; for(let i=0;i<cands.length;i++){ if(BigInt("0x"+h.slice(128+i*64,128+(i+1)*64))===0n)continue; if(!win||cands[i].out>win.out)win=cands[i]; } return win; } function decAll(raw){ if(!raw)return[]; const h=strip0x(raw); if(h.length<128)return[]; const n=Number(BigInt("0x"+h.slice(64,128))); const out=[]; for(let i=0;i<n;i++){ const b=128+i*192; if(h.length<b+192)break; const pool="0x"+h.slice(b+24,b+64),o=BigInt("0x"+h.slice(b+64,b+128)); if(o<=0n||/^0x0{40}$/i.test(pool))continue; out.push({pool,out:o}); } return out; } function decBest(raw){ if(!raw)return null; const h=strip0x(raw); if(h.length<128)return null; const pool="0x"+h.slice(24,64),out=BigInt("0x"+h.slice(64,128)); if(out<=0n||/^0x0{40}$/i.test(pool))return null; return {pool,out}; } async function hubHop(f,t,amountIn,sender,block){ if(isEthLike(f.addr)||isEthLike(t.addr))return null; const enc=(a,b,tokenIn,amt)=>{ const [c0,c1]=a.toLowerCase()<b.toLowerCase()?[a,b]:[b,a]; return [{to:PPLENS,data:"0x"+SEL_QUOTE_BEST+encAddr(c0)+encAddr(c1) +encAddr(sender)+encAddr(tokenIn)+encUint(amt)+encUint(0n)+encUint(BigInt(LQ_SCAN))}, {to:PFACTORY,data:"0x"+SEL_PAIR_COUNT+encAddr(c0)+encAddr(c1)}]}; const count=raw=>{try{return raw?Number(BigInt(raw)):0}catch{return 0}}; const hubs=[ZERO,WETH]; const legs=[]; const first=await mc3Deep(hubs.flatMap(h=>enc(f.addr,h,f.addr,amountIn)),block); for(let i=0;i<hubs.length;i++){ const b=decBest(first[i*2]); if(b)legs.push({hub:hubs[i],pool:b.pool,mid:b.out,n:count(first[i*2+1])}); } if(!legs.length)return null; const second=await mc3Deep(legs.flatMap(l=>enc(l.hub,t.addr,l.hub,l.mid)),block); let win=null; for(let i=0;i<legs.length;i++){ const b=decBest(second[i*2]); if(!b||(win&&b.out<=win.out))continue; win={pools:[legs[i].pool,b.pool],out:b.out,mid:legs[i].mid,hub:legs[i].hub, n:Math.max(legs[i].n,count(second[i*2+1]))}; } if(!win)return null; let fee=0n; const [a,b,rt]=await mc3Deep([ {to:PPLENS,data:"0x"+SEL_EFFFEE+encAddr(win.pools[0])+encAddr(sender)+encAddr(f.addr)+encUint(amountIn)}, {to:PPLENS,data:"0x"+SEL_EFFFEE+encAddr(win.pools[1])+encAddr(sender)+encAddr(win.hub)+encUint(win.mid)}, routableReq(win.pools), ],block); if(policyRefuses(rt,win.pools.length))return null; const fa=a?BigInt(a):0n,fb=b?BigInt(b):0n; fee=fa+fb-(fa*fb)/1000000n; return {pools:win.pools,out:win.out,fee,capped:win.n>LQ_SCAN, tokenIn:f.addr,tokenOut:t.addr,unwrap:0n,wrap:0n,outDiffers:false}; } const QPAIR=2; const SEL_BBS="e7798987"; const HUBS=()=>[WETH,...NUMERAIRES,"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599","0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0"]; async function hubRoute(f,t,amt,sb,dl,to,rf,block){ const nf=(f.addr===ZERO?WETH:f.addr).toLowerCase(),nt=(t.addr===ZERO?WETH:t.addr).toLowerCase(); const H=HUBS().filter(h=>h.toLowerCase()!==nf&&h.toLowerCase()!==nt); if(!H.length)return null; const E=(x,i,o,n)=>({to:ZQUOTER,data:"0x"+SEL_BBS+encAddr(x)+encBool(false)+encAddr(i)+encAddr(o)+encUint(n)+encUint(sb)+encUint(dl)}); const D=h=>{try{return decQ(h,sb,false,0,4,0,6)}catch{return null}}; const L=[],r1=await mc3Deep(H.map(h=>E(ZROUTER,f.addr,h,amt)),block); for(let i=0;i<H.length;i++){const a=r1[i]&&D(r1[i]);if(a&&a.amountLimit>0n)L.push({h:H[i],a})} if(!L.length)return null; const r2=await mc3Deep(L.map(x=>E(to,x.h,t.addr,x.a.amountLimit)),block); let B=null; for(let i=0;i<L.length;i++){const b=r2[i]&&D(r2[i]); if(b&&b.best.amountOut>0n&&(!B||b.best.amountOut>B.b.best.amountOut))B={...L[i],b}} if(!B)return null; const c=[B.a.callData,B.b.callData,encSweep(B.h,0n,rf),encSweep(ZERO,0n,rf)]; const ha=B.a.best.feeBps||0n,hb=B.b.best.feeBps||0n; return{best:{source:B.b.best.source,feeBps:ha+hb-ha*hb/10000n,amountIn:amt,amountOut:B.b.best.amountOut}, callData:encCalls(c),amountLimit:B.b.amountLimit, msgValue:f.addr===ZERO?amt:0n,spender:f.addr===ZERO?null:ZROUTER}; } async function quoteV4Pools(f,t,amountIn,block){ const pools=v4PoolsFor(f,t); if(!pools.length||amountIn<=0n||V4LENS===ZERO||V4PORT===ZERO)return null; const rs=(await mc3Deep(pools.map(p=>({to:V4LENS, data:"0x"+SEL_V4QUOTE+encBool(false)+encAddr(f.addr)+encAddr(t.addr) +encUint(p.fee)+encUint(p.ts)+encAddr(p.hooks)+encUint(amountIn)})),block)) .map(h=>{try{const x=strip0x(h||"");return x.length>=128?BigInt("0x"+x.slice(64,128)):0n}catch{return 0n}}); let best=null; for(let i=0;i<pools.length;i++)if(rs[i]>0n&&(!best||rs[i]>best.amountOut)) best={pool:pools[i],amountIn,amountOut:rs[i]}; return best; } const setListNote=t=>{ const el=document.getElementById("listNote"); if(!el)return; el.textContent=t||""; if(t&&/\u2026$/.test(t)){const s=document.createElement("span");s.className="spin";el.appendChild(s)} el.classList.toggle("hide",!t)}; const setListSource=w=>setListNote(w?"Built-in token list — "+w:""); let rankFellBack=false,listLive=false; const rankedIds=async()=>{ rankFellBack=false; try{ const ids=decUintArr(await cfgRead(C,[{to:ZLISTLENS,data:"0x"+SEL_RANKEDIDS},L])); if(ids.length)return ids; }catch{} try{ const ids=decUintArr(await cfgRead(C,[{to:TOKENLIST,data:"0x"+SEL_RANKEDIDS},L])); if(ids.length)rankFellBack=true; return ids; }catch{return[]} }; const KEEP="zswap:deep"; const stickyPools=()=>{try{ return JSON.parse(LS[KEEP]||"[]").filter(x=>/^0x[0-9a-f]{40}$/i.test(x)).slice(0,LAUNCH_SHOW); }catch{return[]}}; let listJob=null,listGen=0; const loadTokenList=()=>{if(listJob)return listJob;const g=++listGen; return listJob=loadTokenListRun().finally(()=>{if(!listLive&&g===listGen)listJob=null})}; async function loadTokenListRun(){ const cid=CHAIN_ID; if(TOKENLIST===ZERO)return setListSource("registry address not set"); setListNote("Built-in token list \u2014 checking zList\u2026"); try{ const cntJob=PLAUNCH===ZERO?Promise.resolve("0x0"):rpc(C,[{to:PFACTORY,data:"0x"+SEL_BYCREATOR_N+encAddr(PLAUNCH)},L]); cntJob.catch(()=>{}); const ids=await rankedIds(); if(!ids.length)return setListSource("registry is empty"); const raw=await cfgBatch(ids.slice(0,64).map(id=>({to:TOKENLIST,data:"0x"+SEL_JSON+encUint(id)}))); const rows=raw.map(h=>{try{return h?decStr(h):null}catch{return null}}); const next=[]; for(const row of rows){ if(!row)continue; let t;try{t=JSON.parse(row)}catch{continue} if(t.k!=="eip155"||+t.c!==CHAIN_ID)continue; const std=t.p==="Native"||t.p==="ERC-20"?"ft":t.p==="ERC-721"?"nft":""; if(!std)continue; if(!/^0x[0-9a-f]{40}$/i.test(t.a||""))continue; if(!Number.isInteger(t.d)||t.d<0||t.d>36)continue; if(next.some(x=>x.addr.toLowerCase()===t.a.toLowerCase()))continue; const sym=safeSym(t.s).slice(0,12); const logo=safeDataUrl(t.l); next.push({sym,addr:t.a.toLowerCase(),dec:t.d,std,icon:logo?`<img src="${logo}" width="20" height="20" alt="">`:genIcon(sym), name:String(t.n??"").slice(0,64),desc:String(t.desc??"").slice(0,240), v4:parseV4Pools(t.e,t.a)}); } next.sort((a,b)=>(a.std==="nft")-(b.std==="nft")); if(!next.some(t=>t.std==="ft"))return setListSource("registry returned nothing swappable"); const mine=TOKENS.filter(t=>t.cust&&!next.some(x=>x.addr===t.addr.toLowerCase())); let born=[]; LAUNCHED.length=0; try{ const cnt=Number(BigInt(await cntJob||"0x0")); const take=Math.min(cnt,LAUNCH_SCAN); const raw=take?await rpc(C,[{to:PFACTORY,data:"0x"+SEL_BYCREATOR+encAddr(PLAUNCH)+encUint(BigInt(cnt-take))+encUint(BigInt(take))},L]):"0x"; const h=strip0x(raw||""),n=h.length>=128?Number(BigInt("0x"+h.slice(64,128)||"0")):0; let pools=[]; for(let i=0;i<n&&i<take;i++)pools.push("0x"+h.slice(128+i*64+24,128+(i+1)*64)); const fresh=new Set(pools.map(x=>x.toLowerCase())); const carried=stickyPools().filter(x=>!fresh.has(x.toLowerCase())); if(carried.length){ const fr=await mc3Deep(carried.map(a=>({to:a,data:"0x"+SEL_FEERECIP}))); carried.forEach((x,i)=>{let a="";try{a=("0x"+strip0x(fr[i]||"").slice(24)).toLowerCase()}catch{} if(a===PLAUNCH.toLowerCase())pools.push(x)}); } if(pools.length){ const depth=await mc3Deep(pools.map(a=>[{to:a,data:"0x"+SEL_RESERVE0}]).flat()); const byDepth=pools.map((p,i)=>{let d=0n;try{d=BigInt(depth[i]||"0x0")}catch{}return{p,d}}) .sort((x,y)=>x.d===y.d?0:(x.d<y.d?1:-1)).slice(0,LAUNCH_SHOW); pools=byDepth.map(x=>x.p); try{LS[KEEP]=JSON.stringify(byDepth.filter(x=>x.d>0n).map(x=>x.p))}catch{} const meta=await mc3Deep(pools.flatMap(a=>[{to:a,data:"0x"+SEL_TOKEN1}])); const toks=[]; meta.forEach((x,i)=>{const a=x?("0x"+strip0x(x).slice(24)).toLowerCase():""; if(/^0x[0-9a-f]{40}$/.test(a))toks.push({a,pi:i})}); const seen=new Set(next.map(x=>x.addr.toLowerCase())); const info=await mc3Deep(toks.flatMap(({a})=>[{to:a,data:"0x95d89b41"},{to:a,data:"0x"+SEL_CONTRACTURI}, {to:PLAUNCH,data:"0x"+SEL_CREATOROF+encAddr(a)}])); const taken=new Set(next.map(x=>x.sym.toLowerCase()).concat(BUILTIN.filter(b=>b.addr===ZERO||b.addr===WETH).map(x=>x.sym.toLowerCase()))); toks.forEach(({a,pi},i)=>{ if(seen.has(a))return; seen.add(a); let sym="?";try{sym=safeSym(decodeString(info[i*3]||"0x").trim()).slice(0,12)}catch{} if(!sym||sym==="?")return; if(taken.has(sym.toLowerCase()))sym+=" "+a.slice(0,6)+"…";else taken.add(sym.toLowerCase()); let art="";try{art=imgFromContractURI(decodeString(info[i*3+1]||"0x"))}catch{} if(art)logoCache[a]=art; const dep=byDepth[pi]?byDepth[pi].d:0n; let by="";try{const c="0x"+strip0x(info[i*3+2]||"").slice(24);if(/^0x[0-9a-f]{40}$/i.test(c)&&!/^0x0{40}$/i.test(c))by=short(c)}catch{} const desc=[dep>0n?`${(+formatUnits(dep,18)).toFixed(dep<10n**16n?5:3)} ETH liquidity`:"no liquidity yet", by?`by ${by}`:""].filter(Boolean).join(" \u00b7 "); born.push({sym,addr:a,dec:18,std:"ft",name:"",desc,launched:1,dep, icon:art?`<img width="20" height="20" style="border-radius:50%;object-fit:cover" src="${art}" alt="">`:genIcon(sym)}); LAUNCHED.push({addr:a,sym,pool:pools[pi]||""}); }); } }catch{} if(cid!==CHAIN_ID)return; const anchors=BUILTIN.filter(b=>(b.addr===ZERO||b.addr===WETH)&&!next.some(x=>x.addr===b.addr)); const bornAt=new Set(born.map(x=>x.addr.toLowerCase())); TOKENS.splice(0,TOKENS.length,...next,...anchors,...born, ...mine.filter(t=>!bornAt.has(t.addr.toLowerCase()))); listLive=true; setListNote(rankFellBack?"Ranked by listing order — conviction unavailable":""); }catch(e){setListSource("could not be reached")} } const strip0x=h=>h.startsWith("0x")?h.slice(2):h; const pad32=h=>{const x=strip0x(h); if(x.length>64)throw Error("ABI word overflow"); return x.padStart(64,"0")}; const encUint=v=>{const b=BigInt(v); if(b<0n||b>=(1n<<256n))throw Error("uint256 out of range"); return pad32(b.toString(16))}; const encAddr=a=>{const x=strip0x(a||ZERO).toLowerCase(); if(!/^[0-9a-f]{40}$/.test(x))throw Error("not an address"); return pad32(x)}; const isAddr=a=>/^0x[0-9a-fA-F]{40}$/.test(a||""); const decStr=hex=>{const h=strip0x(hex);const n=parseInt(h.slice(64,128),16); return new TextDecoder().decode(hexToBytes(h.slice(128,128+n*2)))}; const decUintArr=hex=>{const h=strip0x(hex);const n=parseInt(h.slice(64,128),16);const o=[]; for(let i=0;i<n;i++)o.push(BigInt("0x"+h.slice(128+i*64,192+i*64)));return o}; const encBool=b=>pad32(b?"1":"0"); const toHex=v=>"0x"+BigInt(v).toString(16); const parseUnits=(s,dec)=>{ const r=s.trim(); if(r.includes(",")&&!/^\d{1,3}(?:,\d{3})+(?:\.\d*)?$/.test(r)) throw Error(r.indexOf(",")===r.lastIndexOf(",") ?`unclear comma — for decimals write ${r.replace(",",".")}` :"unclear commas — use . for decimals"); const t=r.replace(/,/g,""); if(!/^(?:\d+|\d*\.\d+|\d+\.)$/.test(t))throw Error("invalid amount"); const[ip,fp=""]=t.split("."); if(fp.length>dec)throw Error("too many decimals"); return BigInt((ip||"0")+fp.padEnd(dec,"0")); }; const formatUnits=(v,dec)=>{ if(!dec)return BigInt(v).toString(); v=BigInt(v); const sign=v<0n?"-":""; const s=(v<0n?-v:v).toString().padStart(dec+1,"0"); const ip=sign+s.slice(0,-dec); const fp=s.slice(-dec).replace(/0+$/,""); return fp?`${ip}.${fp}`:ip; }; const trimAmt=(v,dec)=>{const s=formatUnits(v,dec),i=s.indexOf(".");if(i<0)return s;const r=s.slice(0,i+7).replace(/\.?0+$/,"");return r==="0"&&v?s:r}; const sigAmt=(v,dec,sig=6)=>{ const s=formatUnits(v,dec),i=s.indexOf("."); if(i<0||v===0n)return s; const lead=s.slice(0,i).replace(/^0+/,""); if(lead.length)return s.slice(0,i+Math.max(1,sig-lead.length)+1).replace(/\.?0+$/,"")||s.slice(0,i); const frac=s.slice(i+1); let z=0;while(z<frac.length&&frac[z]==="0")z++; const cut=s.slice(0,i+1+z+sig).replace(/0+$/,""); return cut.endsWith(".")?cut.slice(0,-1):cut; }; const maxAmt=(v,dec)=>{v=BigInt(v);if(dec<=6)return formatUnits(v,dec); const u=10n**BigInt(dec-6);return formatUnits((v+u-1n)/u,6)}; const fitFont=el=>{ const l=el.value.length,s=l>18?".9em":l>14?"1.1em":l>10?"1.25em":"1.65em"; if(el.style.fontSize!==s)el.style.fontSize=s; }; const decQ=(hex,slip,eo,u,v,S,mv)=>{ const h=strip0x(hex),num=i=>BigInt("0x"+h.slice(i*64,(i+1)*64)),E=()=>{throw Error("bad quote")}; if(h.length<Math.max(v+2,(mv||v+1)+1)*64)E(); const o=Number(num(v))*2;if(o+64>h.length)E(); const n=Number(BigInt("0x"+h.slice(o,o+64)));if(!n||o+64+n*2>h.length)E(); const g=num(u+3),t=g>0n,k=S?(num(3)>0n?0:u):t?u:0,B=10000n,ai=S?num(2)+num(u+2):num(2),ao=S?num(3)+g:t?g:num(3),q=eo?ai:ao; const src=[];for(let w=0;w<=u;w+=4)if(num(w+2)>0n||num(w+3)>0n)src.push(Number(num(w))); return{best:{source:Number(num(k)),feeBps:num(k+1),amountIn:ai,amountOut:ao},sources:src,callData:"0x"+h.slice(o+64,o+64+n*2),amountLimit:eo?(q*(B+slip)+B-1n)/B:q*(B-slip)/B,msgValue:num(mv||v+1),slipBps:slip}; }; const merge=(est,exe)=>{if(!est)return exe;if(!exe)return null;return{...exe,best:est.best}}; const NO_WALLET="No wallet detected."; let PROV=null; const prov=()=>PROV||window.ethereum; const RPC_SEED=["https://ethereum-rpc.publicnode.com","https://eth-mainnet.public.blastapi.io"]; const RPC_STORE="zswap:rpc"; const rpcPin=()=>{try{const v=localStorage.getItem(RPC_STORE); if(v&&/^https:\/\/\S+$/i.test(v))return v}catch{}return""}; let rpcPool=RPC_SEED.slice(),rpcPi=0,curated=0,curating=0,curatedTry=0; const GAS_FLOOR=3000000000n; const SEL_LIST="d77e4c79"; const decStrArr=h=>{const b=strip0x(h||"");if(b.length<128)return[]; const W=i=>parseInt(b.slice(i*64,i*64+64),16); const off=W(0)/32;if(!Number.isInteger(off))return[]; const n=W(off);if(!(n>=0&&n<=64))return[]; const out=[]; for(let k=0;k<n;k++){ const rel=W(off+1+k)/32;if(!Number.isInteger(rel))return[]; const p=off+1+rel,len=W(p);if(!(len>=0&&len<=512))return[]; let str="";const base=(p+1)*64; for(let j=0;j<len;j++)str+=String.fromCharCode(parseInt(b.slice(base+j*2,base+j*2+2),16)); out.push(str)} return out}; const httpRead=async(u,method,params)=>{ const ac=new AbortController(),kill=setTimeout(()=>ac.abort(),15000); try{ let r; try{r=await fetch(u,{method:"POST",headers:{"content-type":"application/json"}, body:JSON.stringify({id:1,jsonrpc:"2.0",method,params:params||[]}),signal:ac.signal})} catch{throw Object.assign(Error("could not reach the read node"),{retry:1})} if(!r.ok)throw Object.assign(Error("a read node answered "+r.status),{retry:1}); let j;try{j=await r.json()}catch{throw Object.assign(Error("a read node sent an unreadable answer"),{retry:1})} if(j&&j.error){const c=j.error.code; throw Object.assign(Error(j.error.message||"the read node refused"),{code:c,data:j.error.data, retry:c==null||c===-32005||c===-32603||c===-32004||c===-32002||c===-32001||c===429})} return j?j.result:null; }finally{clearTimeout(kill)}}; const loadCurated=async()=>{ if(curating||Date.now()-curatedTry<60000)return; curating=1;curatedTry=Date.now(); try{ const a=RPCS_PIN; const alt=RPC_SEED.find(u=>u!==rpcPool[rpcPi])||RPC_SEED[0]; const [a1,a2]=await Promise.all([ httpRead(rpcPool[rpcPi],"eth_call",[{to:a,data:"0x"+SEL_LIST},L]), httpRead(alt,"eth_call",[{to:a,data:"0x"+SEL_LIST},L])]); if(!a1||!a2||strip0x(a1)!==strip0x(a2))return; const urls=decStrArr(a1).map(s=>s.trim()).filter(s=>/^https:\/\/\S+$/.test(s)); if(!urls.length)return; rpcPool=[...new Set(urls.concat(rpcPool,RPC_SEED))]; curated=1; }catch{} finally{curating=0}}; const OX_ETH="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; const OKX_APPROVE={1:"0x40aA958dd87FC8305b97f2BA922CDdCa374bcD7f",8453:"0x57df6092665eb6058DE53939612413ff4B09114E"}; const bmin=(a,b)=>a<b?a:b,bmax=(a,b)=>a>b?a:b; const SEL_SLIST="e3b06401",SEL_EXEC="495c73b0",SEL_FILL="0a1b4c9f"; const SOLVERS_PIN="0x1Dfbb2f41B596F72187370469074C46de60dA2e3"; const RPCS_PIN="0x8C7348D039f58C4e9cfA936EF410eec759213b12"; let SOLVER_FILL_PIN="0x7A2f21e476cA2ADde027BC868c5a083338EEfE54"; const SOLVER_ROW_TAG="0x7A2f21e476cA2ADde027BC868c5a083338EEfE54"; let SOLVER_EXEC_PIN="0x4f6633342045C80Efa6c8CAc41b9bdc6145168C4"; const SRC_SOLVER=9; const SRC_DEEP=10; const SOLVER_DEADLINE=1500,SOLVER_STRIKES=3,SOLVER_COOL=60000; let solverLanes=null,solverFill=null,solverExec=null,solversJob=null; const _strikes=new Map(); const laneHurt=n=>{const s=_strikes.get(n),t=Date.now();_strikes.set(n,{n:(s&&t-s.t<SOLVER_COOL?s.n:0)+1,t})}; const laneDown=n=>{const s=_strikes.get(n);return!!s&&Date.now()-s.t<SOLVER_COOL&&s.n>=SOLVER_STRIKES}; const laneSaw=(n,st)=>{if(st===401||st===403||st===429)laneHurt(n)}; const laneThrew=(n,e)=>{if(!e||e.name!=="AbortError")laneHurt(n)}; const laneOk=c=>!!c&&isAddr(c.target)&&(c.spender==null||isAddr(c.spender)) &&/^0x[0-9a-fA-F]*$/.test(c.data||"")&&strip0x(c.data||"").length%2===0; const decSolvers=h=>{const b=strip0x(h||"");if(b.length<128)return[]; const W=i=>parseInt(b.slice(i*64,i*64+64),16); const str=w=>{const n=W(w);if(!(n>=0&&n<=256))return null;let o="";const base=(w+1)*64; for(let j=0;j<n;j++)o+=String.fromCharCode(parseInt(b.slice(base+j*2,base+j*2+2),16));return o}; try{ const head=W(0)/32;if(!Number.isInteger(head))return[]; const n=W(head);if(!(n>=0&&n<=64))return[]; const out=[]; for(let k=0;k<n;k++){ const rel=W(head+1+k)/32;if(!Number.isInteger(rel))return[]; const t=head+1+rel; const nameOff=W(t)/32,epOff=W(t+1)/32; if(!Number.isInteger(nameOff)||!Number.isInteger(epOff))return[]; const name=str(t+nameOff),endpoint=str(t+epOff); if(name===null||endpoint===null)return[]; const hash=endpoint.indexOf("#"); const flag=hash<0?"":endpoint.slice(hash+1); const url=hash<0?endpoint:endpoint.slice(0,hash); const m=/(?:^|;)c=([0-9,]+)/.exec(flag); const chains=m?m[1].split(",").map(Number).filter(Boolean):[1]; out.push({name,endpoint:url,chains, adapter:"0x"+b.slice((t+2)*64+24,(t+3)*64), handicapBps:BigInt(W(t+3)), enabled:W(t+4)===1}); } return out}catch{return[]}}; const loadSolvers=()=>solversJob||(solversJob=(async()=>{ try{ const list=SOLVERS_PIN; solverFill=SOLVER_FILL_PIN; const ex=await nodeRead("eth_call",[{to:solverFill,data:"0x"+SEL_EXEC},L]).catch(()=>null); const exec="0x"+strip0x(ex||"0x").slice(24); if(!isAddr(exec)){solversJob=null;return} if(exec.toLowerCase()!==SOLVER_EXEC_PIN.toLowerCase()){solverLanes=[];return} solverExec=SOLVER_EXEC_PIN; const raw=await cfgRead("eth_call",[{to:list,data:"0x"+SEL_SLIST},L]); solverLanes=decSolvers(raw).filter(x=>x.enabled &&x.adapter.toLowerCase()===SOLVER_ROW_TAG.toLowerCase() &&/^https:\/\/\S+$/.test(x.endpoint) &&SOLVER_SHAPES[x.name] &&x.chains.includes(CHAIN_ID) &&chainCfg().slvId); }catch{solversJob=null} })()); const lanePost=async(name,url,body,ms)=>{ const ac=new AbortController(),k=setTimeout(()=>ac.abort(),ms); try{const r=await fetch(url,{method:"POST",headers:{"content-type":"application/json"}, body:JSON.stringify(body),signal:ac.signal});laneSaw(name,r.status); if(!r.ok)return null;ret
0x68575b07…67c7·#25,913,368·0x97832916…a366f7
is minted, your allocation is paid to you immediately, and <b>everything else is seeded into a fresh ETH pool</b> &mdash; one-sided, so the pool opens holding only your token.</p> <p><b>Starting market cap</b> is the pool's virtual ether reserve. It sets the opening price and how hard that price is to move, which are the same dial: roughly that much buying moves the market halfway. Small numbers mean the first buyer takes a large share of the supply, which is why the form says what one ether would buy.</p> <p><b>Creator keeps</b> is a share of the token SUPPLY, minted straight to you when the coin launches &mdash; tokens, not ether, and not a fee on trading. Keep 10% of a billion and 100,000,000 land in your wallet at once, unlocked, with nothing vesting.</p> <p>Whatever you keep does not go into the pool, and selling it later pulls ether back out of that pool &mdash; so a modest-looking percentage can be a claim on most of what early buyers put in. Anyone deciding whether to buy your coin can work that out too.</p> <p>A <b>logo</b> is stored as contract code on Ethereum &mdash; no IPFS, no pinning service, nothing to go dark. Upload anything; the page compresses it and shows the size before you sign. You can replace it later.</p></details> <details><summary>Launching a cause <em><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/></svg></em></summary> <p>The other half of the launcher, and a different thing entirely: no pool, no market, no trading. Ten million units are offered at a fixed price in ether &mdash; your <b>goal</b> divided by ten million &mdash; and what backers pay accumulates in a treasury that belongs to the cause, not to you.</p> <p>Two clocks, and they are deliberately different lengths. <b>Runs for</b> is how long backing stays open. <b>Released over</b> sets a fixed <b>rate</b>, not a fixed fraction: the tap pays out your <em>goal</em> divided by that window, every second, whatever was actually raised. Ten ether released over a year is under three hundredths of an ether a day.</p> <p><b>So a cause that raises less than its goal empties faster than the window suggests.</b> Raise a tenth of the goal and the treasury is drained in a tenth of the time &mdash; a tenth of a year, not a year. Since what has not been released is exactly what a backer can still burn back for, an under-subscribed cause can leave its backers with nothing well before the release window is over. Set the window long enough that a partial raise still leaves something behind, and read the daily figure below as the speed at full funding.</p> <p>The release clock starts the moment you launch and runs whether or not anyone has backed you yet, which is why it wants to be much longer than the raise. Release over the same thirty days you raise for and the whole treasury is yours the day backing closes &mdash; and somebody who backed you on the last day gets nothing back, because there is nothing left unreleased.</p> <p>That is what makes the token worth something on its own: <b>whatever has not been released yet can be burned back for</b>. A backer who changes their mind, or who watches a cause go quiet, redeems their share of the untouched treasury and takes their ether home. Nobody has to agree and nothing has to be voted on.</p> <p><b>The creator can close that exit, but not quietly and not quickly.</b> Burning calls <code>ragequit</code>, which the DAO can disable &mdash; and since the creator holds the only share, they alone can propose it and carry the vote. What they cannot do is act on it at once: the change has to go through the DAO, which queues it on chain and then waits out a <b>three-day timelock</b> before it can be executed. That queue is public from the moment it is entered, and burning back stays open the whole time. So the protection is not that nobody can take the exit away; it is that taking it away is visible, and slower than leaving.</p> <p>Backers hold <b>loot</b>, which carries that claim and no vote. You keep a single share and the vote that comes with it, and you buy that share at the same price everyone else pays &mdash; so your claim on the treasury is the same shape as theirs.</p> <p>Paste a cause’s token into the swap and the page prices the burn for you, against the treasury rather than against a market. It is the only price such a token has, and it is the honest one.</p></details> <details><summary>Fees</summary> <p>A launched market charges <b>1%</b> per swap. Half of that stays in the pool as reserves, which raises the floor for every holder. The other half is collectable, and splits <b>80% creator, 10% protocol, 10% tithe</b>.</p> <p>The <b>tithe</b> is burned to Ethereum itself. The ether goes to the BETH burner, which destroys it and mints a BETH receipt recording that it happened. Nobody can spend it and it cannot be redeemed &mdash; the ether no longer exists. What it buys is a smaller ether supply, which accrues to everyone holding ether rather than to us.</p> <p>The token side of collected fees is burned outright too &mdash; a launched coin's supply only ever falls.</p></details> <details><summary>What this page is</summary> <p>There is no server. This page is served from a contract, executes only its own bytes, and needs nothing but an Ethereum node to quote and swap. Those bytes cannot be changed by anyone, us included &mdash; a new version is a new address, not an edit. The token list is a contract too, artwork included; so are the node list, the solver roster, and the adapter an outside route settles through. It talks to your wallet, to Ethereum, and &mdash; when you ask for a quote &mdash; to the aggregators listed under Swapping. Which ones those are is itself recorded on chain and curated there, not decided by this page or by whoever answers a request; any of them can be switched off in a single transaction. They are asked for a price on a pair and a size, and <b>they are not told who is asking</b>: the address they are given is the shared adapter, the same one in every other person&rsquo;s request. They never see a key, and they cannot move anything, because what they return is bounded on chain before it settles. Pin <b>On-chain</b> in the venue list to skip them entirely. WalletConnect is the other outside party: reaching your phone needs its relay. Reads go through a public node you can point anywhere you like.</p> <p>Your keys never leave your wallet. Nothing here custodies your funds; every trade settles from your address to a contract you can read.</p> <p>The token list is <b>zList</b>, ranked on chain by conviction staking. Anything not on it can still be traded by pasting its address.</p></details> <div class="docsmore">Longer notes at <a href="https://docs.zswap.wei.limo" target="_blank" rel="noopener">docs.zswap.wei.limo</a></div> </div> <footer id="foot"><span id="footV"></span><span id="footAddr"></span><span id="footBurn"></span><span>&middot; <a id="footDoc" href="#" role="button">how it works</a></span><span>&middot; <a id="footList" target="_blank" rel="noopener" title="Token order here comes from zList, ranked by onchain conviction staking. Stake to move a token up the list - or to get one listed at all.">token list</a></span></footer> </div> <script>const IC0='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">', ICMOON=IC0+'<path d="M20.5 14.6A8.6 8.6 0 0 1 9.4 3.5a8.6 8.6 0 1 0 11.1 11.1z"/></svg>', ICSUN=IC0+'<circle cx="12" cy="12" r="4.2"/><path d="M12 2v2m0 16v2M4.2 4.2l1.5 1.5m12.6 12.6 1.5 1.5M2 12h2m16 0h2M4.2 19.8l1.5-1.5M18.3 5.7l1.5-1.5"/></svg>'; const ic=()=>{const d=H.className=='d';th.innerHTML=d?ICSUN:ICMOON;tc.content=d?'#0a0a0a':'#fff'};ic(); const ZSWAP_VERSION="0.3"; const ZSWAP_SELF=""; const ZSWAP_PREVIOUS="0xe686952842627A2cf81DF42CCaD54ef98046DB8D"; const ZLIST_URL="https://0x0000006d936ba3653b8854490e16e782cd32a9a8.w4eth.io/"; const selfFromUrl=()=>{ const h=(location.hostname||"").split("."); if(!/^0x[0-9a-fA-F]{40}$/.test(h[0]||""))return ""; if(/^\d+$/.test(h[1]||"")&&h[1]!=="1")return ""; return h[0]; }; const short=a=>a.slice(0,6)+"\u2026"+a.slice(-4); (()=>{ const self=selfFromUrl()||ZSWAP_SELF; footV.textContent=`zSwap v${ZSWAP_VERSION}`; footList.href=ZLIST_URL; if(self){ footAddr.innerHTML=`\u00b7 <a href="https://etherscan.io/address/${self}#code" target="_blank" rel="noopener" ` +`title="This exact build, on chain. Its bytes cannot change; a newer version is a different address.">${short(self)}</a>`; if(ZSWAP_PREVIOUS)footAddr.innerHTML+=` \u00b7 <a href="https://etherscan.io/address/${ZSWAP_PREVIOUS}#code" ` +`target="_blank" rel="noopener" title="The version this one succeeded. It still serves its own page, unchanged.">prev</a>`; } })(); th.onclick=()=>{H.classList.toggle('d');LS.t=H.className=='d'?'d':'l';ic()}; const ZQUOTER="0x000000bd2db80567c23e353ca95a251c573cbf9b"; const WNS="0x0000000000696760E15f265e828DB644A0c242EB"; const GNS="0x9D51D507BC7264d4fE8Ad1cf7Fe191933A0a81d6"; const ENSREG="0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e"; const WREG="0x53745292f0d30d68204a63002C17bDa16C772bf7"; const WROLL="0x0000C82AA4D72871568eF3859D2b0E7CF37e45f2"; const IDWEI="cea9efa56b7c8a673303d04b917a7119a2a68f8c4803d8e6fd1c3a1f0d2e4ebe"; const ZROUTER="0x000000000000FB114709235f1ccBFfb925F600e4"; const PFACTORY="0x000000Eb27B557aB426d9E99cFd54EC455799e81"; const PLQLENS="0x000000956bf20A41C54BaE4a4b6F5C8A166DAB4E"; const PPLENS="0x000000Bad3a2fa57ed74fa06000573ccddF6B7fB"; const PI_WORDS=19,PI_POOL=0,PI_LIQ=9,PI_HOOK=10,PI_SL=3,PI_SH=4, PI_FEE=5,PI_R0=6,PI_R1=7,PI_PX=8,PI_CBPS=12; let CHAIN_ID=1; const CHAIN_ICON_BASE=`<svg viewBox="0 0 32 32" width="20" height="20" aria-hidden="true"><rect width="32" height="32" fill="#0052FF"/><path d="M16 5.4A10.6 10.6 0 0 0 5.5 14.7h13.9v2.6H5.5A10.6 10.6 0 1 0 16 5.4Z" fill="#fff" transform="rotate(180 16 16)"/></svg>`; const CHAIN_ICON_RH=`<svg viewBox="0 0 32 32" width="20" height="20" aria-hidden="true"><rect width="32" height="32" fill="#CCFF00"/><g transform="translate(4 4)" fill="#110E08"><path d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/></g></svg>`; const CHAINS={ 1:{name:"Ethereum",short:"ETH",slow:true,sweep4:true,curve:5,fill:"0x7A2f21e476cA2ADde027BC868c5a083338EEfE54",exec:"0x4f6633342045C80Efa6c8CAc41b9bdc6145168C4",slvId:"1",slvSlug:"ethereum",sources:["UniV2","Sushi","zAMM","UniV3","UniV4","Curve","Lido","Wrap","Precision"],v4port:"0x000000dfb53Fa7f1c486470034741d5BCBE14BE9",v4lens:"0x000000c3aE1692983941495162A4AAB40660E65F",tokenlist:"0x0000006013dF75A31678B786061C2B54bf531524", wrapped:"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",explorer:"https://etherscan.io", rpcs:["https://ethereum-rpc.publicnode.com","https://eth-mainnet.public.blastapi.io"]}, 8453:{name:"Base",short:"BASE",sweep4:false,curve:-1,fill:"0x03d95ecAF7B2Ab6027b8541b19Cb0Ce5CEd9bC74",exec:"0x1434A2579DE1009aA12b7064A161E53a5be2C030",slvId:"8453",slvSlug:"base",sources:["UniV2","Aero","zAMM","UniV3","UniV4","AeroCL","Wrap","","Precision"],v4port:"0x508ad1b0ae31FaF295c5af8C5c2bE9e33E0D19C4",v4lens:"0x0000000000000000000000000000000000000000",tokenlist:"0x0000006013dF75A31678B786061C2B54bf531524", wrapped:"0x4200000000000000000000000000000000000006",explorer:"https://basescan.org", icon:CHAIN_ICON_BASE,rpcs:["https://mainnet.base.org","https://base-rpc.publicnode.com"]}, 4663:{name:"Robinhood",short:"RH",sweep4:false,curve:5,fill:"0x03d95ecAF7B2Ab6027b8541b19Cb0Ce5CEd9bC74",exec:"0x1434A2579DE1009aA12b7064A161E53a5be2C030",slvId:"4663",slvSlug:"robinhood",sources:["UniV2","Sushi","zAMM","UniV3","UniV4","Curve","Lido","Wrap","Precision"],v4port:"0x508ad1b0ae31FaF295c5af8C5c2bE9e33E0D19C4",v4lens:"0x0000000000000000000000000000000000000000",tokenlist:"0x0000006013dF75A31678B786061C2B54bf531524", wrapped:"0x0bd7d308f8e1639fab988df18a8011f41eacad73",explorer:"https://robinscan.io", rpcs:["https://rpc.mainnet.chain.robinhood.com"]}, }; CHAINS[4663].icon=CHAIN_ICON_RH; const MB={sb:"0x000000dA7bb4B2A9E3e80e9A4D4157E26CA6189b",s1:"0x000000fF3D7A2d373615141d7489Ca66683DbecF",sw:"0x00000087A6dc5071779Ed1F8274A39230768B976",du:"0x000000a213b430D14Bae6062c176289B05e04489",ob:"0x000000e6c7a12C80525ee74e7434aAb919447D95",fl:"0x00000080198137F790DA4C52bb902cf87c276748",pr:"0x0000007Be74558A1F8c9045301c6F44C8eD0c9eB",la:"0x0000002fC8E77585A008Aa45d78A71ad36293aEe"}; const L2B={sb:"0x0000001330435808A906432449D233d482Bc9b60",sw:"0x00000032100E634903378DAEED6bA448f35F541a",du:"0x000000fa42d555173395323b2956e9c42EFaEFf2",ob:"0x0000007a1c93d42dD739E8d6cA9Dabf330934970",fl:"0x000000AC7d32e802B003a31F790eb28Ed3294Bac",pr:"0x000000D14E24e5FC8965bcDE58f21f704C944999"}; const chainCfg=()=>CHAINS[CHAIN_ID]||CHAINS[1]; const L1_RPCS=["https://ethereum-rpc.publicnode.com","https://eth-mainnet.public.blastapi.io"]; let l1i=0; const l1Read=async(method,params)=>{ for(let k=0;k<L1_RPCS.length*2;k++){ const u=L1_RPCS[l1i%L1_RPCS.length]; try{return await httpRead(u,method,params)}catch(e){l1i++} } throw Error("no mainnet read node"); }; const cfgRead=(method,params)=>CHAIN_ID===1?rpc(method,params):l1Read(method,params); const SHARE_HOST="new.zswap.wei.limo"; const ZERO="0x0000000000000000000000000000000000000000"; let WETH="0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2"; let TOKENLIST="0x0000006013dF75A31678B786061C2B54bf531524"; const ZLISTLENS="0x000000cEa3AB048d59473F3fb116A8D7F1abd247"; const SEL_RANKEDIDS="df7ca268"; const SEL_JSON="74e18e96"; const SEL_CID="fb021939",SEL_RES="4f896d4f",SEL_REV="9af8b7aa"; const SCOREM="0x57Fae63f7c63732fc4B77247F0255b11b9458AE1",SEL_SCLAIM="5cc2f461"; const SEL_WAVAIL="8f8dc386",SEL_WFEE="fcee45f4",SEL_WPREM="1bf1fffb",SEL_WCOMMIT="f14fcbc8",SEL_WCOMMITS="839df945",SEL_WREVEAL="ea9384fa",SEL_WSUB="a00fd3c8"; const SEL_RSTATE="c19d93fb",SEL_RWEIGHT="0767d178",SEL_RTICKET="673b4784",SEL_RENTER="23972aef",SEL_WOWNER="6352211e"; const WN_KEY="zswap:wns",WN_MIN=60,WN_MAX=86400; const SEL_RSLV="0178b8bf",SEL_EADDR="3b3b57de",SEL_ENAME="691f3431"; const SEL_ERESOLVE="9061b923"; const NAME_OFFCHAIN="That name keeps its records off chain, and this page reads only on-chain state."; const SEL_ALLOWANCE="dd62ed3e"; const SEL_LATEST="52bfe789",SEL_PREV="247dfaa8",SEL_SUCCAT="451aae60"; const MATURITY=259200; const SEL_DS="3644e515",SEL_NONCES="7ecebe00",SEL_NAME="06fdde03",SEL_PERMIT_TH="30adf81f"; const TH_2612="6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9"; const SEL_RPERMIT="7ac2ff7b",SEL_MULTICALL="ac9650d8",SEL_SWEEP="cb019b84"; const PERMIT2="0x000000000022D473030F116dDEE9F6B43aC78BA3"; const SEL_P2TF="09d31579"; const EIP712DOM="EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"; const SEL_APPROVE="095ea7b3"; const SEL_BALANCEOF="70a08231"; let V4PORT="0x000000dfb53Fa7f1c486470034741d5BCBE14BE9"; let V4LENS="0x000000c3aE1692983941495162A4AAB40660E65F"; const SEL_V4QUOTE="d500463c",SEL_V4SWAP="48e6f730"; const V4POOL_KEY="0x95a932c205571d4d1ca72715c642a2eca21dde79ffc28ff11509681f9383385f"; const C="eth_call",S="eth_sendTransaction",L="latest",I="eth_chainId"; let SOURCES=["UniV2","Sushi","zAMM","UniV3","UniV4","Curve","Lido","Wrap","Precision"]; const SRC_PRECISION=8; let SRC_CURVE=5; const ETH_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#627EEA"/><g fill="#FFF" fill-rule="nonzero"><path fill-opacity=".602" d="M16.498 4v8.87l7.497 3.35z"/><path d="M16.498 4L9 16.22l7.498-3.35z"/><path fill-opacity=".602" d="M16.498 21.968v6.027L24 17.616z"/><path d="M16.498 27.995v-6.028L9 17.616z"/><path fill-opacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/><path fill-opacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/></g></g></svg>`; const USDC_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none"><circle fill="#2775CA" cx="16" cy="16" r="16"/><g fill="#FFF"><path d="M20.022 18.124c0-2.124-1.28-2.852-3.84-3.156-1.828-.243-2.193-.728-2.193-1.578 0-.85.61-1.396 1.828-1.396 1.097 0 1.707.364 2.011 1.275a.458.458 0 00.427.303h.975a.416.416 0 00.427-.425v-.06a3.04 3.04 0 00-2.743-2.489V9.142c0-.243-.183-.425-.487-.486h-.915c-.243 0-.426.182-.487.486v1.396c-1.829.242-2.986 1.456-2.986 2.974 0 2.002 1.218 2.791 3.778 3.095 1.707.303 2.255.668 2.255 1.639 0 .97-.853 1.638-2.011 1.638-1.585 0-2.133-.667-2.316-1.578-.06-.242-.244-.364-.427-.364h-1.036a.416.416 0 00-.426.425v.06c.243 1.518 1.219 2.61 3.23 2.914v1.457c0 .242.183.425.487.485h.915c.243 0 .426-.182.487-.485V21.34c1.829-.303 3.047-1.578 3.047-3.217z"/><path d="M12.892 24.497c-4.754-1.7-7.192-6.98-5.424-11.653.914-2.55 2.925-4.491 5.424-5.402.244-.121.365-.303.365-.607v-.85c0-.242-.121-.424-.365-.485-.061 0-.183 0-.244.06a10.895 10.895 0 00-7.13 13.717c1.096 3.4 3.717 6.01 7.13 7.102.244.121.488 0 .548-.243.061-.06.061-.122.061-.243v-.85c0-.182-.182-.424-.365-.546zm6.46-18.936c-.244-.122-.488 0-.548.242-.061.061-.061.122-.061.243v.85c0 .243.182.485.365.607 4.754 1.7 7.192 6.98 5.424 11.653-.914 2.55-2.925 4.491-5.424 5.402-.244.121-.365.303-.365.607v.85c0 .242.121.424.365.485.061 0 .183 0 .244-.06a10.895 10.895 0 007.13-13.717c-1.096-3.46-3.778-6.07-7.13-7.162z"/></g></g></svg>`; const USDT_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#26A17B"/><path fill="#FFF" d="M17.922 17.383v-.002c-.11.008-.677.042-1.942.042-1.01 0-1.721-.03-1.971-.042v.003c-3.888-.171-6.79-.848-6.79-1.658 0-.809 2.902-1.486 6.79-1.66v2.644c.254.018.982.061 1.988.061 1.207 0 1.812-.05 1.925-.06v-2.643c3.88.173 6.775.85 6.775 1.658 0 .81-2.895 1.485-6.775 1.657m0-3.59v-2.366h5.414V7.819H8.595v3.608h5.414v2.365c-4.4.202-7.709 1.074-7.709 2.118 0 1.044 3.309 1.915 7.709 2.118v7.582h3.913v-7.584c4.393-.202 7.694-1.073 7.694-2.116 0-1.043-3.301-1.914-7.694-2.117"/></g></svg>`; const WBTC_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#F7931A"/><path fill="#FFF" fill-rule="nonzero" d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"/></g></svg>`; const WSTETH_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none"><circle fill="#00A3FF" cx="16" cy="16" r="16"/><path d="M9.437 14.864l-.181.275c-2.048 3.097-1.603 7.253 1.034 9.824 1.561 1.521 3.622 2.353 5.683 2.353 0 0 0 0-6.536-12.452z" fill="#FFF"/><path opacity=".6" d="M15.997 18.611l-6.56-3.747c6.56 12.452 6.56 12.452 6.56 12.452 0-2.683 0-5.623 0-8.705z" fill="#FFF"/><path opacity=".6" d="M22.563 14.864l.181.275c2.048 3.097 1.603 7.253-1.034 9.824-1.561 1.521-3.622 2.353-5.683 2.353 0 0 0 0 6.536-12.452z" fill="#FFF"/><path opacity=".2" d="M16.003 18.611l6.56-3.747c-6.56 12.452-6.56 12.452-6.56 12.452 0-2.683 0-5.623 0-8.705z" fill="#FFF"/><path opacity=".2" d="M16.004 10.239v6.459l5.654-3.23-5.654-3.229z" fill="#FFF"/><path opacity=".6" d="M16.005 10.239l-5.655 3.229 5.655 3.23v-6.46z" fill="#FFF"/><path d="M16.005 4.805l-5.655 8.668 5.655-3.233V4.805z" fill="#FFF"/><path opacity=".6" d="M16.004 10.238l5.658 3.23-5.658-8.674v5.444z" fill="#FFF"/></g></svg>`; const RETH_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><circle cx="16" cy="16" r="16" fill="#ED5A37"/><path d="M16 5.75L9.89 15.85 16 19.45l6.08-3.6z" fill="#FFF"/><path d="M16 20.6l-6.11-3.59L16 25.58l6.08-8.57z" fill="#FFF"/></svg>`; const BOLD_ICON=`<svg width="20" height="20" viewBox="0 0 32 32" fill="none"><circle cx="16" cy="16" r="16" fill="#63D77D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M12.1719 4.56641H8.58203V26.1016H15.7617V25.2422C16.8398 25.793 18.0586 26.1055 19.3555 26.1055C23.7148 26.1055 27.25 22.5703 27.25 18.207C27.25 13.8438 23.7148 10.3086 19.3555 10.3086C18.0586 10.3086 16.8398 10.6211 15.7617 11.1719V4.56641H12.1719ZM15.7617 11.1719C13.207 12.4805 11.457 15.1406 11.457 18.207C11.457 21.2734 13.207 23.9336 15.7617 25.2422V11.1719Z" fill="#1C1D4F"/></svg>`; let TOKENS=[ {sym:"ETH", addr:ZERO, dec:18, icon:ETH_ICON}, {sym:"wstETH",addr:"0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0",dec:18,icon:WSTETH_ICON}, {sym:"WETH", addr:WETH, dec:18, icon:ETH_ICON}, {sym:"rETH", addr:"0xae78736cd615f374d3085123a210448e74fc6393",dec:18,icon:RETH_ICON}, {sym:"WBTC", addr:"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",dec:8, icon:WBTC_ICON}, {sym:"USDC", addr:"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",dec:6, icon:USDC_ICON}, {sym:"USDT", addr:"0xdac17f958d2ee523a2206206994597c13d831ec7",dec:6, icon:USDT_ICON}, {sym:"BOLD", addr:"0x6440f144b7e50d6a8439336510312d2f54beb01d",dec:18,icon:BOLD_ICON}, ]; let BUILTIN=TOKENS.slice(); CHAINS[1].tokens=TOKENS.slice(); CHAINS[8453].tokens=[ {sym:"ETH",addr:ZERO,dec:18,icon:ETH_ICON}, {sym:"WETH",addr:"0x4200000000000000000000000000000000000006",dec:18,icon:ETH_ICON}, {sym:"USDC",addr:"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",dec:6,icon:USDC_ICON}, {sym:"cbBTC",addr:"0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf",dec:8}, {sym:"DAI",addr:"0x50c5725949a6f0c72e6c4a641f24049a917db0cb",dec:18}, {sym:"AERO",addr:"0x940181a94a35a4569e4529a3cdfb74e38fd98631",dec:18}, ]; CHAINS[4663].tokens=[ {sym:"ETH",addr:ZERO,dec:18,icon:ETH_ICON}, {sym:"WETH",addr:"0x0bd7d308f8e1639fab988df18a8011f41eacad73",dec:18,icon:ETH_ICON}, {sym:"USDG",addr:"0x5fc5360d0400a0fd4f2af552add042d716f1d168",dec:6}, {sym:"NVDA",addr:"0xd0601ce157db5bdc3162bbac2a2c8af5320d9eec",dec:18}, {sym:"DEEP",addr:"0x1da24f6bb623b9d1afeae3f3146659a2662d6d27",dec:18}, ]; const CHAIN_STORE="zswap:chain"; function setChain(id){ id=+id; const c=CHAINS[id]; if(!c)return false; if(id===CHAIN_ID)return true; CHAIN_ID=id; WETH=c.wrapped; TOKENLIST=c.tokenlist; V4LENS=c.v4lens||ZERO; V4PORT=c.v4port||ZERO; const b=id===1?MB:L2B; SB2=b.sb||ZERO;SB1=b.s1||ZERO;SWAPBOL=b.sw||ZERO;DUTCH=b.du||ZERO;ORDERBOL=b.ob||ZERO;FLOOR=b.fl||ZERO;PROUTE=b.pr||ZERO;PLAUNCH=b.la||ZERO; floorReady=null;listLive=false;listJob=null; try{SOLVER_FILL_PIN=c.fill||ZERO;SOLVER_EXEC_PIN=c.exec||ZERO}catch{} try{if(c.sources)SOURCES=c.sources.slice();SRC_CURVE=(c.curve===undefined?5:c.curve)}catch{} if(c.tokens){TOKENS=c.tokens.slice();BUILTIN=TOKENS.slice();restoreCustom()} try{if(c.rpcs&&c.rpcs.length){rpcPool=[...new Set(c.rpcs)];rpcPi=0;curated=0;curatedTry=0}}catch{} try{localStorage[CHAIN_STORE]=String(id)}catch{} try{chainUi()}catch{} return true; } function parseV4Pools(extras,tokenAddr){ if(!Array.isArray(extras)||!/^0x[0-9a-f]{40}$/i.test(tokenAddr||""))return[]; const row=extras.find(x=>x&&typeof x.v==="string"&&String(x.k).toLowerCase()===V4POOL_KEY); if(!row)return[]; const out=[]; for(const spec of row.v.split(";")){ const p=spec.trim().spli
0x68575b07…67c7·#25,913,367·0x395498c6…f4745f
,calc(100vw - 2em));background:var(--c);border:1px solid var(--hd); overflow:hidden; max-height:min(32em,calc(100vh - 2em));display:flex;flex-direction:column} #wkHdr{flex:none} #wkList{overflow-y:auto;overscroll-behavior:contain} #wkHdr{padding:.7em .8em;border-bottom:1px solid var(--e);font-size:.72em; letter-spacing:.14em;text-transform:uppercase;color:var(--m)} #wkList img,#wkList .wki{width:20px;height:20px;border-radius:50%;flex:none} .wcq{padding:.7em .8em 0} .wcn{padding:.55em .8em .85em;font-size:.72em;color:var(--m)} .wcnode{padding-top:0;opacity:.85} #wkList .wki{display:block} #wkList .wki svg{width:100%;height:100%;display:block} .tkg{border-top:1px solid var(--e);padding:.7em .8em .3em;font-size:.62em;letter-spacing:.14em;text-transform:uppercase;color:var(--m)} .tkr{display:flex;align-items:flex-start;gap:.55em;width:100%;box-sizing:border-box; padding:.55em .8em;border:0;border-top:1px solid var(--e);background:transparent;color:var(--f);font:inherit; text-align:left;cursor:pointer} #tkList>.tkr:first-child,#tkList>.tkg:first-child,.tkg+.tkr{border-top:0} .tkr:hover,.tkr.cur{background:var(--fh)} .tkr[aria-disabled="true"]{opacity:.5;cursor:default} .tkr[aria-disabled="true"]:hover{background:transparent} .tkr img,.tkr svg{flex:none;border-radius:50%;margin-top:.1em} .tki{flex:none;width:20px;height:20px;display:flex;align-items:center;justify-content:center} .tkt{min-width:0;flex:1} .tks{display:flex;align-items:baseline;gap:.45em} .tks b{font-size:.85em;font-weight:600} .tkn{font-size:.7em;color:var(--m);overflow:hidden;text-overflow:ellipsis;white-space:nowrap} .tkd{font-size:.66em;color:var(--m);line-height:1.45;margin-top:.15em; display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden} .tkmark{flex:none;font-size:.75em;color:var(--m)} .tkempty{padding:1.4em .8em;text-align:center;font-size:.72em;color:var(--m)} .sc{margin-top:.7em;padding:.7em .8em;border:1px solid var(--e);font-size:.72em;color:var(--m);line-height:1.55} .sc b{display:block;color:var(--f);font-size:1.15em;word-break:break-all} .sc .scw{color:var(--w)} .sc button{margin-top:.5em;padding:.4em .8em;font-size:1em} body:has(.tkp:not(.hide))::before{content:"";position:fixed;inset:0;z-index:39;pointer-events:none;background:#0006} @media(max-width:30em){#slipL span{display:none}} @media(pointer:coarse){ #th,#lk,#lq,#ln,#wn,#pv{width:36px;height:36px} #sd{width:44px;height:44px;top:.35em;right:.35em} #addr{padding:.55em 0;margin:-.55em 0} .tkb{padding:.7em 0 .7em .5em;margin:-.7em 0} .flip{width:2.4em;height:2.4em;margin:-1.2em auto} #slipL{padding:.5em 0;margin:-.5em .15em -.5em 0} .chtog{padding:1em .1em} .fclink{padding:.55em 0;margin:-.55em 0 -.55em .35em} .docs summary{padding:1em .2em} .tkp{position:fixed;left:0;right:0;bottom:0;top:auto;width:100%;max-width:none; box-sizing:border-box;border-left:0;border-right:0; max-height:82vh;border-bottom:0; padding-bottom:env(safe-area-inset-bottom,0)} .tkp::before{content:"";flex:none;width:2.2em;height:.25em; background:var(--e);margin:.5em auto .1em} .tkd{-webkit-line-clamp:1} .tkr{padding:.75em .9em} #tkFind{padding:.85em .9em;font-size:16px}.rcpt,.meta input,.dly input,.lqin{font-size:16px}.lnbtn{padding:.6em .8em}.p button,.o button,.lqbtn{padding:.6em .8em}.chtf button{padding:.5em} .tabs button{padding:.95em .5em} .dly{padding:.9em .8em} .wnbtn{padding:.95em .8em} #amt,#outAmt{padding:.3em 0} .invh{font-size:11px;top:6px;left:10px;right:10px;gap:.3em;flex-wrap:wrap;row-gap:3px} .invq{padding:10px 11px;margin:-10px -6px;opacity:1} .invm,.invsh{padding:10px 3px;margin:-10px -1px}} @media(pointer:coarse) and (max-width:24em){ .meta{gap:.12em}#th,#lk,#lq,#ln,#wn,#pv{width:32px;height:32px}#slip{width:2em}}</style> <script>const H=document.documentElement;let LS;try{LS=localStorage}catch{LS={}}if(LS.t?LS.t=='d':matchMedia('(prefers-color-scheme:dark)').matches)H.className='d'</script> <button id="sd" title="Sound" aria-label="Toggle sound"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 9.5v5h3.5L12 18V6L7.5 9.5H4z"/><path id="sdw" d="M15.5 9a4 4 0 0 1 0 6"/><path id="sdx" d="M16 9.5l4 4m0-4l-4 4"/></svg></button> <a id="logoLink" href="https://zfi.wei.is/dao/#/dao/1/0x5E58BA0e06ED0F5558f83bE732a4b899a674053E" target="_blank" rel="noopener" aria-label="The DAO that governs this page" title="zFi DAO — the governance that owns this page's lineage"><svg id="logo" viewBox="0 0 400 400" aria-hidden="true"><clipPath id="lgf"><rect width="400" height="400"/></clipPath><g clip-path="url(#lgf)"><rect width="400" height="400"/><path d="M-60-20L460-20L460 90L80 310L460 310L460 420L-60 420L-60 310L320 90L-60 90Z"/></g></svg></a> <svg id="fish" viewBox="0 0 24 14" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" aria-hidden="true"><path d="M2 12.2C7 .8 18 1.8 22 7"/><path d="M2 1.8C7 13.2 18 12.2 22 7"/></svg> <div class="card"> <div class="meta"><span id="net" role="button" tabindex="0" title="Network"></span><span id="addr" role="button" tabindex="0" title="Connect a wallet">Connect</span><label id="slipL" title="Slippage"><span>Slippage </span><input id="slip" type="number" value="0.5" min="0.01" max="10" step="0.1">%</label><button id="lq" title="Liquidity for this pair" aria-label="Toggle liquidity mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3.2c3.6 4 6 7.2 6 10.1a6 6 0 0 1-12 0c0-2.9 2.4-6.1 6-10.1z"/></svg></button><button id="ln" title="Launch a coin" aria-label="Toggle launch mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/></svg></button><button id="wn" title="Claim a .wei name" aria-label="Toggle names mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" fill-rule="evenodd" shape-rendering="crispEdges" aria-hidden="true"><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></button><button id="pv" title="Bridge privately to Base or Robinhood" aria-label="Toggle private bridge mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l7 3v5c0 4.5-3 8.2-7 10-4-1.8-7-5.5-7-10V6l7-3z"/><path d="M9 12l2 2 4-4"/></svg></button><button id="lk" title="Copy shareable link" aria-label="Copy shareable link"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2.5"/><path d="M5.5 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v.5"/></svg></button><button id="th" title="Toggle theme" aria-label="Toggle theme"></button></div> <div class="tabs" role="tablist"><button id="tabSwap" class="on" role="tab" aria-selected="true">Swap</button><button id="tabSend" role="tab" aria-selected="false">Send</button><button id="tabBook" role="tab" aria-selected="false">Orders</button></div> <div class="panel"><div class="hdr"><small id="payL">You pay</small><span class="pill"><span id="fromIcon"></span><select id="fromSel" aria-labelledby="payL"></select><button type="button" class="tkb" id="fromPick" aria-haspopup="listbox" aria-expanded="false"><span class="tkbs"></span><span class="tkbc">▾</span></button></span></div><div class="row" id="amtRow"><input id="amt" type="text" inputmode="decimal" placeholder="0.0" aria-label="Amount to pay" autocomplete="off" spellcheck="false"></div><small id="bal"></small></div> <button id="flip" class="flip" title="Flip tokens" aria-label="Flip tokens"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14m-5-5 5 5 5-5"/></svg></button> <div class="panel" id="rcvPanel"><div class="hdr"><small id="rcvHdr">You receive</small><span class="pill"><span id="toIcon"></span><select id="toSel" aria-labelledby="rcvHdr"></select><button type="button" class="tkb" id="toPick" aria-haspopup="listbox" aria-expanded="false"><span class="tkbs"></span><span class="tkbc">▾</span></button></span></div><div class="row" id="outRow"><input id="outAmt" type="text" inputmode="decimal" placeholder="0.0" aria-label="Amount to receive" autocomplete="off" spellcheck="false"></div><small id="bal1" class="hide"></small><small id="rate" role="status" aria-live="polite"></small><small id="rankBox" class="rank hide"></small></div> <div id="lqPanel" class="hide"><div class="hdr"><small>Liquidity</small><small id="lqSub"></small></div><div id="lqList"></div></div> <div id="lnPanel" class="hide"> <div class="hdr lnhdr"><small id="lnTitle">Launch a coin</small><small id="lnSub">0.4% of each trade goes to the creator</small></div> <label class="dly" id="lnKindL" title="A coin opens a market: supply is minted and seeded into a pool that trades from the first block. A cause opens a fund: backers pay ether for a token that can always be burned back for its share of whatever the cause has not spent.">Launch<select id="lnKind"><option value="coin">Coin</option><option value="cause">Cause</option></select></label> <label class="dly">Name<input id="lnName" maxlength="64" placeholder="Zero Cat" autocomplete="off" spellcheck="false"></label> <label class="dly">Symbol<input id="lnSym" maxlength="16" placeholder="ZCAT" autocomplete="off" spellcheck="false"></label> <label class="dly" id="lnSupplyL">Supply<input id="lnSupply" inputmode="decimal" value="1,000,000,000" autocomplete="off" spellcheck="false"></label> <div class="lngap" id="lnMktL">Market</div> <label class="dly" id="lnMcapL" title="The pool holds NO ether at launch - this is its virtual ether reserve, and it is the only thing deciding where on the curve your market opens. It sets the opening price and how hard that price is to move, which are the same knob: roughly this much buying moves the market halfway. Small numbers mean the first buyer takes a huge share of the supply.">Starting market cap<span class="lnu"><input id="lnMcap" inputmode="decimal" value="30" autocomplete="off" spellcheck="false"><i>ETH</i></span></label> <label class="dly" id="lnAllocL" title="The percentage of total supply paid to the creator at launch. Everything else is seeded into the pool. Capped at 20%.">Creator keeps<span class="lnu"><input id="lnAlloc" inputmode="decimal" value="0" autocomplete="off" spellcheck="false"><i>%</i></span></label> <label class="dly hide" id="lnGoalL" title="What the whole raise comes to. Ten million units are offered and the price is this divided by ten million, so a backer always knows what one unit costs and what it is a claim on.">Goal<span class="lnu"><input id="lnGoal" inputmode="decimal" value="10" autocomplete="off" spellcheck="false"><i>ETH</i></span></label> <label class="dly hide" id="lnDaysL" title="How long backing stays open. After this, the sale closes and nobody else can buy in.">Runs for<span class="lnu"><input id="lnDays" inputmode="decimal" value="30" autocomplete="off" spellcheck="false"><i>days</i></span></label> <label class="dly hide" id="lnVestL" title="How slowly the ether reaches you. The clock starts the moment you launch and runs whether or not anyone has backed you yet, so this wants to be well longer than the raise - what has not been released is exactly what a backer can still burn their token back for, and a release that finishes when backing closes leaves them nothing. The rate is your GOAL divided by this window, so a raise that falls short of the goal is emptied proportionally sooner - a tenth of the goal, a tenth of the window.">Released over<select id="lnVest"><option value="7889238">3 months</option><option value="15778476">6 months</option><option value="31556952" selected>12 months</option><option value="63113904">2 years</option></select></label> <div class="lngap">Optional</div> <label class="dly" id="lnArtRow" title="Held on-chain as contract code, not as a link. Nothing to pin and no gateway to go dark. Editable later, so a rough mark now costs nothing.">Logo<span><img id="lnPrev" alt=""><small id="lnArtNote">none</small><button type="button" id="lnPick" class="lnbtn">Choose</button></span></label> <input id="lnArt" type="file" accept="image/png,image/jpeg,image/webp,image/gif,image/svg+xml" class="hide"> </div> <div id="wnPanel" class="hide"> <div class="hdr lnhdr"><small>Claim a name</small><small id="wnSub">from 0.0005 ETH a year</small></div> <label class="dly">Name<input id="wnName" maxlength="63" placeholder="human" autocomplete="off" spellcheck="false"></label> <label class="dly" id="wnKindL">Ending<select id="wnTld"><option value="wei">.wei</option><option value="id">.id.wei — free</option></select></label> <small id="wnNote" role="status" aria-live="polite"></small> </div> <div id="pvPanel" class="hide"> <div class="hdr lnhdr"><small>Private bridge</small><small id="pvSub">Shielded ether on Ethereum: exit to an L2, withdraw, or pay a request</small></div> <label class="dly" title="Deposited into the confidential pool on Ethereum as a shielded note. Exit it later to an L2 address that nothing on-chain ties to this wallet. The amount, the L2 recipient and the timing of the exit are public; what is hidden is which deposit it came from, so the privacy is only as good as the number of other deposits in the pool.">Amount<span class="lnu"><input id="pvAmt" inputmode="decimal" placeholder="0.01" autocomplete="off" spellcheck="false"><i>ETH</i></span></label> <label class="dly" title="Split the deposit into equal notes that can be spent one at a time. A withdrawal or exit always spends a whole note, so several small notes are what makes a partial spend possible later.">Notes<select id="pvSplit"><option value="1">1</option><option value="2">2</option><option value="4">4</option></select></label> <label class="dly">Arrives on<select id="pvChain"><option value="8453">Base</option><option value="4663">Robinhood</option><option value="1">Ethereum (withdraw)</option></select></label> <label class="dly" title="The address credited on the chain chosen above. Leave blank for this wallet. On Robinhood the unused gas budget refunds here too, so a contract wallet gets its refund at an aliased address.">Recipient<input id="pvTo" placeholder="0x… on that chain, defaults to you" autocomplete="off" spellcheck="false"></label> <label class="dly" title="Relayed: the relay proves and settles the exit, paying its own gas out of a flat fee, so this wallet never signs the exit. Yourself: the relay only proves; you send one atomic transaction that exits and bridges, fee-free, but from this wallet.">Exit<select id="pvPath"><option value="relay">Relayed, for a fee</option><option value="self">Yourself, one tx</option></select></label> <small id="pvKey" role="status"></small> <div id="pvList"></div> <small id="pvNote" role="status" aria-live="polite"></small> </div> <input id="rc" class="rcpt" placeholder="Recipient (optional) — 0x or .eth/.(g)wei" spellcheck="false" autocomplete="off"> <small id="rcvEl"></small> <small id="fcEl" class="hide" role="status" aria-live="polite"></small> <small id="cbEl" class="hide" role="status" aria-live="polite"></small> <small id="lnNote" class="hide" role="status" aria-live="polite"></small> <button id="lnGo" class="primary hide">Launch</button> <button id="wnGo" class="primary hide">Register</button> <button id="pvGo" class="primary hide">Deposit</button> <div id="wnRollPanel" class="hide"><div class="lngap">roll.wei</div><small id="wnRollEl" role="status" aria-live="polite"></small><button id="wnEnter" class="wnbtn hide">Enter the roll</button></div> <label class="dly hide" id="kindL">Order type<select id="kind"><option value="fixed">Fixed limit</option><option value="dutch">Dutch decay</option><option value="floor">Climbing bid</option></select></label> <label class="dly hide" id="dlyL">SLOW<select id="dly"><option value="0">Instant</option><option value="600">10 minutes</option><option value="3600">1 hour</option><option value="86400">1 day</option><option value="259200">3 days</option><option value="604800">7 days</option></select></label> <label class="dly hide" id="tipL" title="A time-locked transfer does not move on its own: once the lock expires somebody has to send the claim transaction. Tick this and the page posts a small ETH tip alongside the deposit, which pays a keeper to claim it for the recipient the moment it matures. The tip is refundable — if you reverse the transfer, the ether comes back to you.">Auto-claim<span class="tipr"><small id="tipNote"></small><input id="tipCk" type="checkbox" aria-describedby="tipNote"></span></label> <label class="dly hide" id="fillL">Fill<select id="fill"><option value="1">Any amount</option><option value="0">All or nothing</option></select></label> <label class="dly hide" id="floorL">Floor total<input id="floorAmt" inputmode="decimal" placeholder="0.0"></label> <label class="dly hide" id="dRestL" title="A Dutch lot has TWO clocks. It decays to your floor over the window above, and then - unless it is given an end - it sits there fillable forever, until you spend gas cancelling. This is how long it rests at the floor before the escrow simply comes back to you.">Rest at floor<select id="dRest"><option value="0">Forever</option><option value="86400">1 day</option><option value="604800">1 week</option><option value="2592000">30 days</option></select></label> <label class="dly hide" id="nftIdL">Token ID<input id="nftId" inputmode="numeric" placeholder="Any from collection" spellcheck="false" autocomplete="off"></label> <label class="dly hide" id="ethModeL" title="Applies when YOU fill an order from the book, or cancel one of yours and the escrow comes back wrapped. It does NOT change what an order you place pays you: boards are denominated in WETH, and a maker's proceeds are transferred as WETH with no unwrap available - so a limit order asking for ETH is an order asking for WETH, and fills as such. Paying is not a setting either: the page spends whichever of your ETH and WETH covers it, and wraps the difference if only the two together do.">Fills &amp; cancels pay<select id="ethMode"><option value="auto">ETH</option><option value="weth">WETH</option></select></label> <small id="floorNote" class="hide" role="status" aria-live="polite"></small> <button id="swap" class="primary">Connect Wallet</button> <div id="listNote" class="hide" role="status" aria-live="polite"></div> <div id="scEl" class="sc hide" role="status" aria-live="polite"></div> <div id="wkWrap" class="hide"><div id="wkPanel" role="dialog" aria-modal="true" aria-labelledby="wkHdr"><div id="wkHdr">Choose a wallet</div><div id="wkList"></div></div></div> <div id="tkPanel" class="tkp hide"><input id="tkFind" type="text" placeholder="Search name, symbol or address" autocomplete="off" spellcheck="false" aria-label="Filter tokens" role="combobox" aria-expanded="true" aria-controls="tkList" aria-autocomplete="list"><div id="tkList" role="listbox"></div></div> <div id="stat" role="status" aria-live="polite"></div> <button id="chTog" class="chtog hide" aria-expanded="false">Chart <span>▾</span></button> <div id="chBox" class="hide"><div class="chtf" id="chTf"></div><div id="chArt"></div><small id="chNote"></small></div> <div id="pos"></div> <button id="bkTog" class="chtog hide" aria-expanded="true">Orderbook <span>▾</span></button> <div id="book"></div> <div id="docPanel" class="docs hide"> <details><summary>Swapping</summary> <p>Every quote is compared across Uniswap v2, v3 and v4, Curve, Sushi, zAMM, Lido, our own Precision pools and the on-chain orderbook, and the best one wins. One route, one signature.</p> <p>A handful of outside aggregators are asked too &mdash; 0x, 1inch, ParaSwap, KyberSwap, Enso, Bebop, OKX. They are compared on what they can be <b>held to</b>, not on what they advertise: a quote less its slippage, against the same number for the chain. One only wins if it beats the chain by a margin wide enough to cover the extra gas, and if none of them answer, nothing changes. Tap the <b>&#9662;</b> beside the rate to see every venue that replied and what each was worth.</p> <p>An outside route is executed through a small adapter that <b>measures what actually arrives</b> and reverts if it is less than your minimum. The adapter is named in this page's own bytes, so a bad answer from a node cannot swap it for another one.</p> <p><b>Slippage</b> is the worst price you will accept &mdash; move past it and the trade reverts rather than filling badly. <b>Price impact</b> is how far your own size moves the price; the page warns as it grows and will not let a very bad one through quietly.</p> <p>ETH and WETH are interchangeable here. The page wraps or unwraps whichever way the pool needs, inside the same transaction.</p></details> <details><summary>Sending</summary> <p>A plain transfer, or a <b>SLOW</b> one: time-locked, and reversible by you at any point until it matures. Useful for large amounts and for addresses you have not paid before.</p> <p>A locked transfer does not deliver itself &mdash; someone must send the claim once it matures. Tick <b>auto-claim</b> and the page posts a small tip so a keeper does that for the recipient. The tip is refundable: reverse the transfer and it comes back.</p></details> <details><summary>Orders</summary> <p><b>Fixed limit</b> &mdash; a price you name. It rests until someone fills it or you cancel.</p> <p><b>Dutch decay</b> &mdash; starts above your floor and falls to it over a window you choose. It has two clocks: the decay, then how long it rests at the floor before the escrow returns to you. Set that to <em>Forever</em> and it waits until you cancel.</p> <p><b>Climbing bid</b> &mdash; the mirror: a bid that rises toward a ceiling you name.</p> <p>Orders are escrowed on chain when placed, so a fill cannot fail for want of funds, and cancelling returns the escrow. Boards are denominated in WETH &mdash; an order asking for ETH is asking for WETH, and pays out as such.</p> <p>The <b>book</b> under the form is live and takeable. Yours sit at the top; everyone else&rsquo;s come cheapest first for the pair above, each row carrying its own unit price. <b>Fill</b> takes a single order, in part where it allows it. That same book is priced into every Swap quote &mdash; a swap that beats the pools on resting orders says <em>Orderbook</em> beside the rate, with the share it took.</p></details> <details><summary>Liquidity <em><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3.2c3.6 4 6 7.2 6 10.1a6 6 0 0 1-12 0c0-2.9 2.4-6.1 6-10.1z"/></svg></em></summary> <p>Precision pools are <b>bands</b>, not endless curves. A position earns fees only while the price is inside its band, and holds one asset or the other at the edges. A wider band is safer and earns less; a narrow one is the opposite.</p> <p>The bar on each row shows where the price sits in that band. <em>Out of range</em> means it is earning nothing right now.</p></details> <details><summary>Launching a coin <em><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/></svg></em></summary> <p>One transaction, and no ether needed aside from gas. A fixed supply
0x68575b07…67c7·#25,913,366·0x1aaba21d…6de9bb
<!doctype html> <meta charset="utf-8"> <meta name="viewport" content="width=device-width,initial-scale=1"> <meta name="referrer" content="no-referrer"> <meta http-equiv="Content-Security-Policy" content="object-src 'none';base-uri 'none';form-action 'none'"> <title>zFi</title> <link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 400' width='400' height='400'%3E%3Crect width='400' height='400' fill='%23000'/%3E%3CclipPath id='frame'%3E%3Crect width='400' height='400'/%3E%3C/clipPath%3E%3Cg clip-path='url(%23frame)'%3E%3Cpath d='M-60-20L460-20L460 90L80 310L460 310L460 420L-60 420L-60 310L320 90L-60 90Z' fill='white'/%3E%3C/g%3E%3C/svg%3E" type="image/svg+xml"> <meta name="theme-color" id="tc" content="#fff"> <style>:root{color-scheme:light;--b:#fff;--c:#fff;--p:#fff;--f:#000;--m:#666;--n:#666;--e:#ddd;--k:#000;--kf:#fff;--kh:#333;--fh:#f5f5f5;--e2:#ddd;--w:#c33;--hd:#000;--dm:#767676} .d{color-scheme:dark;--b:#0a0a0a;--c:#0a0a0a;--p:#0a0a0a;--f:#e8e8e0;--m:#888;--n:#888;--e:#2a2a2a;--k:#e8e8e0;--kf:#0a0a0a;--kh:#ccc;--fh:#161616;--e2:#2a2a2a;--w:#e57373;--hd:#333;--dm:#7b7b7b} :focus-visible{outline:2px solid var(--f);outline-offset:2px} @media(prefers-reduced-motion:reduce){*{transition:none!important;animation:none!important}.flip:hover{transform:none}} body{background:var(--b);color:var(--f);font-family:Helvetica,Arial,sans-serif;min-height:100dvh;margin:0;padding:1em .85em;display:flex;flex-direction:column;box-sizing:border-box} .card{position:relative;border:1px solid var(--hd);padding:1em .95em;background:var(--c);max-width:22em;width:100%;margin:auto;box-sizing:border-box}.inv{position:absolute;inset:0;background:var(--c);overflow:hidden;outline:0;touch-action:none;z-index:5;-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}#wn{-webkit-user-select:none;user-select:none;-webkit-touch-callout:none}.invx{position:absolute;width:20px;height:20px;transition:opacity .12s;pointer-events:none}.invx.pop{opacity:0}.invs{position:absolute;width:22px;height:20px;display:flex;align-items:flex-start;justify-content:center;pointer-events:none} .invs svg{width:20px;height:20px} .invs::after{content:"";position:absolute;left:-4px;right:-4px;bottom:-4px;height:2px;background:var(--f)} .invs.pop::after{background:var(--w)}.invs.pop{opacity:.35}.invb{position:absolute;width:2px;height:7px;background:#16a34a;pointer-events:none}.invd{position:absolute;width:2px;height:6px;background:#c33;pointer-events:none}.invh{position:absolute;top:4px;left:8px;right:8px;font-size:9px;letter-spacing:.12em;text-transform:uppercase;color:var(--m);pointer-events:none;font-variant-numeric:tabular-nums;display:flex;justify-content:space-between;align-items:center;gap:.6em}.invq{pointer-events:auto;cursor:pointer;padding:1px 5px;border:1px solid var(--e);color:var(--f);opacity:.7}.invm{pointer-events:auto;cursor:pointer;color:var(--f);text-decoration:underline;text-underline-offset:2px}.invmsg{color:var(--m)}.invsh{pointer-events:auto;cursor:pointer;color:var(--m);text-decoration:underline;text-underline-offset:2px}.invq:hover{opacity:1} #addr{min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;cursor:pointer} #addr.on:hover{color:var(--f);text-decoration:line-through} #sd{position:fixed;top:.85em;right:.85em;width:1.85em;height:1.85em;display:inline-flex;align-items:center;justify-content:center;border:0;background:transparent;cursor:pointer;color:var(--m);opacity:.7;z-index:2;padding:0;transition:opacity .15s}#sd:hover,#sd:focus-visible{opacity:.9} #logoLink{position:fixed;top:.85em;left:.85em;width:1.75em;height:1.75em;z-index:1; display:block;opacity:.85;transition:opacity .15s} #logoLink:hover,#logoLink:focus-visible{opacity:1} #logo{display:block;width:100%;height:100%;pointer-events:none} #logo rect{fill:var(--f)} #logo path{fill:var(--b)} @media(max-width:32em){#logoLink{display:none}} #fish{position:fixed;z-index:1;pointer-events:none;color:var(--m);opacity:.12; left:calc(.9em + env(safe-area-inset-left,0px)); bottom:calc(.9em + env(safe-area-inset-bottom,0px)); width:clamp(1.15em,3.2vw,1.6em);height:auto} .meta{display:grid;grid-template-columns:minmax(0,1fr) auto auto auto auto auto auto auto auto;align-items:center;gap:.3em;font-size:.85em;color:var(--n);margin-bottom:.75em} #slipL{display:inline-flex;align-items:baseline;gap:.15em;font-size:.72em;color:var(--m); letter-spacing:.04em;white-space:nowrap;margin-right:.15em} .meta input{width:3em;font-size:.9em;padding:.15em .1em;border:0;border-bottom:1px solid var(--e2);transition:border-color .15s;background:transparent;text-align:right;outline:0;color:var(--f);-moz-appearance:textfield} .meta input::-webkit-outer-spin-button,.meta input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0} #slip{font-size:1.2em;width:2.4em;font-weight:600;font-variant-numeric:tabular-nums} #th,#lk,#lq,#ln,#wn,#pv{display:inline-flex;align-items:center;justify-content:center;border:0;background:transparent;cursor:pointer;color:var(--m);opacity:.8;width:1.8em;height:1.8em;line-height:1;transition:color .15s,opacity .15s,background .15s} #th:hover,#lk:hover,#lq:hover,#ln:hover,#wn:hover,#pv:hover,#lq.on,#ln.on,#wn.on,#pv.on{opacity:1}#wn{opacity:.55} #th:hover,#lk:hover,#lq:hover,#ln:hover,#wn:hover,#pv:hover{color:var(--f)} .meta input:hover{border-bottom-color:var(--m)} .meta input:focus{border-bottom-color:currentcolor} .panel{background:transparent;border:1px solid var(--e);padding:.95em .9em;margin:0;transition:border-color .15s} .panel+.panel,.flip+.panel{margin-top:-1px} .panel:focus-within,.dly:focus-within,.rcpt:focus-within,.lqin:focus-within{border-color:var(--f);position:relative;z-index:1} .panel small{color:var(--m);font-size:.8em} .hdr small{text-transform:uppercase;letter-spacing:.14em;font-size:.68em} .row{display:flex;gap:.4em;align-items:center;margin-top:.35em} .panel input{font-size:1.65em;font-weight:400;letter-spacing:-.015em;border:0;background:transparent;color:var(--f);outline:none;padding:0;flex:1;min-width:0;width:100%;font-variant-numeric:tabular-nums;transition:font-size .1s} .panel input::placeholder{color:var(--m);opacity:.32} .pill{display:inline-flex;align-items:center;gap:.4em;cursor:pointer;transition:opacity .15s} .pill:hover,.pill:focus-within{opacity:1;color:var(--f)} .panel select,.dly select{appearance:none;-webkit-appearance:none;border:0;background:transparent url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='8' height='5' viewBox='0 0 8 5'%3E%3Cpath d='M0 0h8L4 5z' fill='%23999'/%3E%3C/svg%3E") no-repeat right 0 center;padding:0 .85em 0 0;max-width:8em;font-size:.9em;font-weight:600;color:var(--f);letter-spacing:.02em;cursor:pointer;text-overflow:ellipsis} .panel select:focus{outline:0} option,optgroup{background-color:var(--c);color:var(--f)} svg{vertical-align:middle;flex-shrink:0} .flip{display:flex;align-items:center;justify-content:center;position:relative;z-index:2;margin:-.92em auto;width:1.85em;height:1.85em;border:1px solid var(--e);background:var(--c);cursor:pointer;color:var(--m);padding:0;transition:border-color .15s,color .15s,transform .25s} .flip:hover{border-color:var(--f);color:var(--f);transform:rotate(180deg)} .primary{width:100%;box-sizing:border-box;border:1px solid var(--f);padding:1.15em .9em;overflow-wrap:anywhere;line-height:1.3;font-size:.82em;background:transparent;color:var(--f);cursor:pointer;margin-top:.95em;font-weight:700;text-transform:uppercase;letter-spacing:.14em;transition:border-color .15s,color .15s,background .15s} .primary:hover:not(:disabled){background:var(--fh)} .primary:disabled{border-color:var(--e);color:var(--dm);cursor:not-allowed} .rcpt{width:100%;box-sizing:border-box;margin-top:-1px;padding:.66em .85em;border:1px solid var(--e);background:transparent;transition:border-color .15s;color:var(--f);font-size:.75em;outline:0;font-family:inherit} #lq.on,#ln.on,#wn.on,#pv.on{background:var(--k);color:var(--kf)} .cpr{display:flex;justify-content:space-between;align-items:center;gap:.5em;font-size:.75em;color:var(--n);padding:.6em .8em;border:1px solid var(--e);margin-top:-1px}.cpr b{font-weight:600;color:var(--f)}.cpr a{color:var(--m)}#pvList{margin-top:.6em}#pvKey{display:block;margin-top:.6em;color:var(--m)} .lqrow{padding:.62em .7em;border:1px solid var(--e);margin:-1px 0 0} .lqrow.out{opacity:.55} .lqband{display:flex;align-items:center;gap:.4em;font-size:.82em} .lqfee{color:var(--m);font-size:.72em} .lqtag{font-size:.62em;padding:.1em .4em;border:1px solid var(--e);color:var(--m);letter-spacing:.08em;text-transform:uppercase} .lqmeta{font-size:.7em;color:var(--m);margin-top:.25em;display:flex;align-items:center;gap:.4em} .lqacts{display:flex;gap:.4em;margin-top:.5em} .lqacts>.lqbtn{flex:1} .lqmine{font-size:.7em;margin-top:.25em;display:flex;align-items:center;gap:.4em} .lqempty{padding:.9em .7em;font-size:.75em;color:var(--m);text-align:center} .lqnewtog{width:100%;margin-top:.55em} .lqnew{margin-top:.5em;display:flex;flex-direction:column;gap:.35em;text-align:left} .lqnew .dly{margin-top:.4em;gap:.6em} .lqnew .dly>select{flex:1;min-width:0;max-width:none;text-align:right;text-align-last:right} .lqnew .dly .r{display:flex;gap:.4em;align-items:center} .lqnew .dly .r input{width:6.5em} .lqout{font-size:.72em;color:var(--m);line-height:1.55;min-height:1.1em} .lqout:empty{display:none} .lqbar{position:relative;height:9px;border:1px solid var(--e);margin:.5em 0 .35em} .lqbar>i{position:absolute;top:-4px;width:2px;height:15px;background:var(--f); left:var(--at,50%);transform:translateX(-1.5px)} .lqbar.out>i{background:var(--dm);width:1px} .lqbar>b{position:absolute;top:-1px;bottom:-1px;background:var(--m);opacity:.3; left:0;right:0} .lqnew .primary{margin-top:.5em;min-height:2.6em;font-size:.85em} #listNote{max-width:22em;margin:.5em auto 0;font-size:10px;color:var(--m);letter-spacing:.06em;text-align:center} .spin{display:inline-block;width:.75em;height:.75em;margin-left:.45em;vertical-align:-.1em; border:1px solid var(--e2);border-top-color:var(--m);border-radius:50%;animation:sp .7s linear infinite} @keyframes sp{to{transform:rotate(360deg)}} @keyframes fadein{from{opacity:0}to{opacity:1}} #tkList.fresh>*{animation:fadein .22s ease-out both} .lqbtn{font-size:.8em;padding:.45em .7em;cursor:pointer;border:1px solid var(--e);background:transparent;color:var(--f);font-family:inherit;font-weight:400;letter-spacing:.08em;text-transform:uppercase; white-space:nowrap;flex:0 0 auto;text-align:center;transition:background .15s,color .15s,border-color .15s} .lqbtn:hover:not(:disabled),.lqbtn:focus-visible:not(:disabled){border-color:var(--f)} .lqbtn:disabled{opacity:.4;cursor:not-allowed} .lqadd{margin-top:.45em;display:flex;flex-direction:column;gap:.35em} .lqin{width:100%;box-sizing:border-box;padding:.5em .65em;font:inherit;font-size:.75em;border:1px solid transparent;background:var(--p);transition:border-color .15s;color:var(--f);outline:0} .lqzap{display:flex;align-items:center;gap:.45em;font-size:.7em;color:var(--m);cursor:pointer;margin-bottom:.1em} .lqzap input{margin:0;cursor:pointer;accent-color:var(--f)} .lqpv{font-size:.7em;opacity:.7;min-height:1.2em;line-height:1.5} .lqadd .lqbtn{margin-left:0;align-self:stretch;font-size:.75em;padding:.5em .8em} .rcpt.bad{border-color:var(--w)} #rcvEl{display:block;font-size:.7em;color:var(--m);margin:.35em 0 .6em;word-break:break-all;text-align:center} #rcvEl:empty{display:none} #stat{font-size:.8em;color:var(--n);word-break:break-all;margin-top:.75em;text-align:center} #stat:not(:empty){margin-bottom:.5em}#stat:empty{margin-top:0} #stat a{color:var(--f);text-decoration:underline} .hdr{display:flex;justify-content:space-between;align-items:center;gap:.5em} #lqPanel,#lnPanel{margin-top:.85em} #lqPanel>.hdr{margin-bottom:.5em} .tabs{display:flex;gap:1.35em;margin-bottom:.85em;border-bottom:1px solid var(--e)} .tabs button{flex:1;padding:.5em;font-size:.75em;font-weight:600;text-transform:uppercase;letter-spacing:.08em;border:0;background:transparent;color:var(--m);cursor:pointer;transition:background .15s,color .15s} .tabs button.on{color:var(--f);font-weight:700;box-shadow:inset 0 -2px 0 var(--f)} .tabs button:hover:not(.on),.tabs button:focus-visible:not(.on){color:var(--f)} .hide{display:none!important} #book{margin-top:.4em;max-height:min(28em,58vh);overflow-y:auto;overscroll-behavior:contain} .px{color:var(--f);font-variant-numeric:tabular-nums} .bkh{position:sticky;top:0;z-index:1;background:var(--c);padding-top:.5em} .bkh h6{margin-top:0} #book:empty{display:none} #book h6{font-size:.7em;text-transform:uppercase;letter-spacing:.14em;color:var(--m);margin:.75em 0 .35em;font-weight:400;display:flex;justify-content:space-between} .o{display:flex;justify-content:space-between;align-items:center;flex-wrap:nowrap;gap:.5em;padding:.6em .75em;border:1px solid var(--e);margin-bottom:-1px;font-size:.75em} .o .l{display:flex;align-items:center;gap:.45em;min-width:0;flex:1 1 auto} .o .l>span{min-width:0;flex:1 1 auto} .o svg{flex-shrink:0} .o svg+svg{margin-left:-.5em} .o b{font-weight:600;font-variant-numeric:tabular-nums;display:block} .o i{font-style:normal;color:var(--m);display:block;font-size:.9em;overflow-wrap:break-word} .o i a{word-break:break-all} .o a{color:var(--m)} .o button,.p button{border:1px solid var(--e);padding:.4em .7em;font-size:.85em;font-weight:400;letter-spacing:.08em;text-transform:uppercase;background:transparent;color:var(--f);cursor:pointer;flex:0 0 auto;transition:background .15s,color .15s,border-color .15s} .o button:hover:not(:disabled),.o button:focus-visible:not(:disabled),.p button:hover:not(:disabled),.p button:focus-visible:not(:disabled){border-color:var(--f)} #foot{display:flex;justify-content:center;align-items:baseline;gap:.6em;flex-wrap:wrap;margin-top:1.3em;padding-top:.9em;border-top:1px solid var(--e);font-size:.6em;color:var(--m);letter-spacing:.1em;text-transform:uppercase} #foot span:empty{display:none} #foot a{color:var(--m);text-decoration:none} #foot a:hover{color:var(--f)} .o button:disabled,.p button:disabled{background:transparent;border-color:transparent;color:var(--m);cursor:default;font-weight:400} .bkf{display:flex;flex-wrap:wrap;gap:.3em;margin:.35em 0 .45em} .bkc{font-size:.68em;padding:.35em .6em;border:1px solid var(--e);letter-spacing:.1em;text-transform:uppercase; background:transparent;color:var(--m);cursor:pointer;font-family:inherit;font-weight:600; white-space:nowrap;transition:background .15s,color .15s} .bkc:hover:not(.on),.bkc:focus-visible:not(.on){color:var(--f);border-color:var(--f)} .bkc.on{background:var(--k);color:var(--kf);border-color:var(--k)} .bkc.on:hover{background:var(--kh)} .ow .o{margin-bottom:0} .o.ins{cursor:pointer} .o.ins:hover{background:var(--fh)} .ow{margin-bottom:0} .ic:empty{display:none} .ic{padding:.5em .75em;border:1px solid var(--e);border-top:0;margin-bottom:-1px;font-size:.72em;color:var(--n)} .ic img{display:block;width:100%;max-width:15em;margin:.5em auto 0} .ic dl{display:grid;grid-template-columns:auto 1fr;gap:.15em .6em;margin:0} .ic dt{color:var(--m)} .ic dd{margin:0;color:var(--f);font-variant-numeric:tabular-nums;text-align:right} .tg{display:inline-block;font-size:.85em;padding:.05em .35em;background:var(--e2);color:var(--f);margin-left:.3em} .tg.w{background:transparent;border:0;color:var(--w);font-weight:600; text-transform:uppercase;font-size:.62em;letter-spacing:.1em;padding:0; vertical-align:.1em;line-height:1;text-decoration:none;margin-left:.5em} .o a.tg.w{color:var(--w)} .dly{display:flex;justify-content:space-between;align-items:center;gap:.6em;font-size:.75em;color:var(--n);margin-top:-1px;padding:.7em .8em;border:1px solid var(--e);transition:border-color .15s} .dly select{font-size:1em} #lnArtNote{color:var(--n);font-variant-numeric:tabular-nums} #lnArtNote.set{color:var(--f)} .lnbtn{font:inherit;font-size:.85em;letter-spacing:.08em;text-transform:uppercase;color:var(--f);background:transparent;border:1px solid var(--e);padding:.4em .7em;cursor:pointer;transition:border-color .15s}.wnbtn{display:block;width:100%;box-sizing:border-box;font:inherit;font-size:.75em;color:var(--f);background:var(--p);border:1px solid var(--e);padding:.65em .8em;margin-top:.5em;cursor:pointer;text-align:center;transition:border-color .15s,color .15s}.wnbtn:hover{border-color:var(--f)}.wnbtn.mut{color:var(--m);cursor:default;border-color:transparent}.wnbtn.mut:hover{border-color:transparent} #fcEl{display:block;text-align:center;color:var(--m);font-size:.72em;letter-spacing:.03em;margin-top:.9em} .fclink{font:inherit;color:var(--m);background:none;border:0;border-bottom:1px solid var(--e);padding:0 0 .1em;margin-left:.35em;cursor:pointer;letter-spacing:inherit} .fclink:hover{color:var(--f);border-color:var(--f)} .fclink:disabled{cursor:default;opacity:.6} .lnbtn:hover{border-color:var(--f)} #lnArtRow{gap:.6em} #lnArtRow>span{display:flex;align-items:center;gap:.6em} #lnPrev{width:1.6em;height:1.6em;object-fit:cover;display:none} #lnPrev.set{display:block} .lqon{display:flex;flex-direction:column} .lqon>*{order:2} .lqon>.meta,.lqon>.tabs,.lqon>.panel,.lqon>.flip,.lqon>#rcvPanel{order:0} .lqon>#rc,.lqon>#rcvEl{order:1} .lnon>.tabs .on,.wnon>.tabs .on,.pvon>.tabs .on{color:var(--m);font-weight:600;box-shadow:none}.lnon #chTog,.lnon #chBox,.lnon #bkTog,.lnon #book,.lnon #pos,.pvon #chTog,.pvon #chBox,.pvon #bkTog,.pvon #book,.pvon #pos{display:none!important} .docs{margin-top:.5em} .docs details{border-top:1px solid var(--e)} .docs details:last-child{border-bottom:1px solid var(--e)} .docs summary{cursor:pointer;list-style:none;padding:.75em .2em;font-size:.7em;font-weight:700;text-transform:uppercase;letter-spacing:.02em;color:var(--f);display:flex;justify-content:space-between;align-items:center} .docs summary::-webkit-details-marker{display:none} .docs summary::after{content:"+";color:var(--m);font-weight:400} .docs details[open] summary::after{content:"\2013"} .docs p{margin:0 0 .7em;padding:0 .2em;font-size:.72em;line-height:1.6;color:var(--n)} .docs b{color:var(--f);font-weight:600} .docs em{font-style:normal;color:var(--m)} .docsmore{padding:.75em .2em .2em;font-size:.7em;color:var(--m)}.docsmore a{color:var(--f);text-decoration:underline;text-underline-offset:2px;text-decoration-color:var(--e2);transition:text-decoration-color .15s}.docsmore a:hover{text-decoration-color:var(--f)} .docs summary em{display:inline-flex;align-items:center;margin:0 auto 0 .5em;opacity:.75} .lngap{font-size:.58em;text-transform:uppercase;letter-spacing:.18em;color:var(--dm);margin:1.5em .1em .5em} .lngap+.dly{margin-top:0} .lnhdr{margin-bottom:.6em} .lnhdr>small:first-child{white-space:nowrap} .lqbar.fill>b{right:auto;width:var(--at);opacity:.9} #cbEl{display:block;font-size:.75em;line-height:1.5;color:var(--m);margin-top:.6em} .cbrow{display:flex;justify-content:space-between;gap:.6em;align-items:baseline} .cbact{display:flex;justify-content:space-between;align-items:center;gap:.7em; margin-top:.55em;padding-top:.55em;border-top:1px solid var(--e);color:var(--f)} .cbact>span{min-width:0} .cbmsg{display:block;margin-top:.5em} .lnhdr>small+small{color:var(--dm);text-transform:none;letter-spacing:.01em;font-size:.65em;font-weight:400;text-align:right;text-wrap:balance} #wnRollEl{display:block;font-size:.72em;line-height:1.55;color:var(--m)} #lnNote,#floorNote,#wnNote{display:block;font-size:.7em;line-height:1.45;color:var(--m);text-align:center;text-wrap:balance;margin:.55em .2em 0} #lnNote:empty,#floorNote:empty,#wnNote:empty{display:none} .lnu{display:flex;align-items:baseline;gap:.3em;color:var(--m);flex:0 1 auto;min-width:0} .lnu input{width:7em;flex:0 1 7em;min-width:0} .lnu i{font-style:normal;min-width:2.6em} .tipr{display:flex;align-items:center;gap:.5em} .tipr small{color:var(--m);font-size:.95em} .dly input{width:9em;flex:0 1 9em;min-width:0;border:0;border-bottom:1px solid var(--e2);transition:border-color .15s;background:transparent;color:var(--f);text-align:right;outline:0;font:inherit;font-variant-numeric:tabular-nums} .dly input[type=checkbox]{width:auto;border:0;cursor:pointer;accent-color:var(--f)} .dly input:hover{border-bottom-color:var(--e)} .dly input:focus{border-bottom-color:var(--f)} #pos{margin-top:.4em} #pos:empty{display:none} .ph{margin:1.5em .1em .5em;font-size:.58em;text-transform:uppercase;letter-spacing:.18em;color:var(--dm);font-weight:400} .p{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:.5em;padding:.6em .75em;border:1px solid var(--e);margin-bottom:-1px;font-size:.75em} .p span{flex:1;min-width:8em} .p b{display:block;font-weight:600;font-variant-numeric:tabular-nums;word-break:break-word} .p i{font-style:normal;color:var(--m);display:block;font-size:.9em} .p button{margin-left:auto} #rate{text-wrap:balance} #bal,#bal1,#rate{display:block;font-size:.75em;line-height:1.35;margin-top:.5em;text-align:right} #bal:empty,#bal1:empty,#rate:empty{display:none} #bal a,#bal1 a{color:var(--f);cursor:pointer;text-decoration:underline;font-weight:600} .meta input.warn{color:var(--w);border-bottom-color:var(--w)} #th:focus-visible,#lk:focus-visible,#lq:focus-visible,#ln:focus-visible,#wn:focus-visible,#pv:focus-visible{background:var(--p);color:var(--f)} .chtog{width:100%;margin-top:.5em;padding:.75em .1em;border:0;border-top:1px solid var(--e);background:transparent;color:var(--m);cursor:pointer;font:inherit;font-size:.7em;font-weight:400;text-transform:uppercase;letter-spacing:.08em;transition:color .15s,background .15s} .chtog:hover{color:var(--f)} .chtog span{display:inline-block;transition:transform .2s} .chtog[aria-expanded=true] span{transform:rotate(180deg)} .rank{display:block;margin-top:.45em;color:var(--m);font-size:.92em;line-height:1.5} .rank i{font-style:normal;display:flex;justify-content:space-between;gap:1em;cursor:pointer} .rank i.w{color:var(--f);font-weight:600} .rank i.pin u{text-decoration:underline} .rank u{text-decoration:none;color:var(--m)} #rate .rk{cursor:pointer;padding:0 .25em;opacity:.6} #rate .rk:hover{opacity:1} #chBox{margin-top:.4em} .chtf{display:flex;gap:1.1em;margin-bottom:.55em} .chtf button{flex:0 0 auto;padding:.2em 0;font:inherit;font-size:.65em;font-weight:400;text-transform:uppercase;letter-spacing:.12em;border:0;background:transparent;color:var(--m);cursor:pointer} .chtf button.on{color:var(--f);font-weight:700} .chtf button:disabled{opacity:.35;cursor:default} .chtf .chk{flex:0 0 auto;padding:.2em 0;margin-left:auto} .chtf .chk:hover{color:var(--f)} #chArt{touch-action:pan-y;position:relative;border:1px solid var(--e);overflow:hidden} #chArt svg{display:block;width:100%;height:auto} #chArt .hd{position:absolute;top:.4em;left:.55em;font-size:.7em;color:var(--m);font-variant-numeric:tabular-nums;pointer-events:none} #chArt .hd b{color:var(--f);font-size:1.25em;font-weight:600} #chArt .hd .ohlc{display:block;font-size:.9em;opacity:.85} #chArt .msg{padding:2.2em .8em;text-align:center;font-size:.72em;color:var(--m);line-height:1.6} #chArt .chflip{opacity:.55;font-size:.82em;letter-spacing:.02em;margin-left:.15em} #chNote{display:block;font-size:.65em;color:var(--m);margin-top:.35em;text-align:right;font-variant-numeric:tabular-nums} .panel select.rich{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none} .tkb{display:none;align-items:center;gap:.25em;border:0;background:transparent;padding:0; font:inherit;font-size:.9em;font-weight:600;letter-spacing:.02em;color:var(--f);cursor:pointer} .tkb.on{display:inline-flex} .tkbc{font-size:.7em;color:var(--m)} .tkp{position:fixed;z-index:40;width:min(20em,calc(100vw - 2em));max-height:min(32em,calc(100vh - 5em)); display:flex;flex-direction:column;background:var(--c);border:1px solid var(--hd);overflow:hidden} #tkFind{border:0;border-bottom:1px solid var(--e);background:transparent;color:var(--f); font:inherit;font-size:.8em;padding:.7em .8em;outline:0;flex:none} #tkList{overflow-y:auto;overscroll-behavior:contain} #wkWrap{position:fixed;inset:0;z-index:60;background:#0006;display:flex;align-items:center;justify-content:center} #wkWrap.hide{display:none} #wkPanel{width:min(19em
0x68575b07…67c7·#25,913,259·0x20cbeca6…a99e23
u2014 link it by address instead.`;} } } addEventListener("hashchange",applyLink); const tname=t=>t.cust||t.launched||/\s/.test(t.sym)||!symAddr(t.sym.toLowerCase())?t.addr:t.sym; lk.onclick=async()=>{ const f=TOKENS[fromSel.value],t=TOKENS[toSel.value],p=new URLSearchParams(); p.set("token",tname(f)); if(CHAIN_ID!==1)p.set("chain",String(CHAIN_ID)); const rcv=rc.value.trim();if(rcv)p.set("to",rcv); if(tab==="send"){ p.set("tab","send"); if(amt.value.trim())p.set("amount",amt.value.trim()); if(+dly.value)p.set("lock",dly.value); }else{ p.set("out",tname(t)); if(lqMode)p.set("lq","1"); if(tab==="book"){p.set("tab","book");if(+dly.value)p.set("lock",dly.value)} if(mode==="out"&&outAmt.value.trim()){p.set("amount",outAmt.value.trim());p.set("exactOut","1")} else if(amt.value.trim())p.set("amount",amt.value.trim()); } const u=(location.origin&&location.origin!=="null"?location.origin+location.pathname:location.href.split("#")[0])+"#"+p; try{await navigator.clipboard.writeText(u);stat.textContent="Link copied"} catch{stat.textContent=u} }; tabSwap.onclick=()=>{sBlip();setTab("swap")}; tabSend.onclick=()=>{sBlip();setTab("send")}; {const TABS=[tabSwap,tabSend,tabBook],NAMES=["swap","send","book"]; for(const el of TABS)el.addEventListener("keydown",e=>{ const i=TABS.indexOf(e.target); const j=e.key==="ArrowRight"?(i+1)%TABS.length:e.key==="ArrowLeft"?(i+TABS.length-1)%TABS.length :e.key==="Home"?0:e.key==="End"?TABS.length-1:-1; if(j<0)return; e.preventDefault();TABS[j].focus();sBlip();setTab(NAMES[j]); }); syncStops=()=>TABS.forEach(el=>el.tabIndex=el.classList.contains("on")?0:-1); syncStops();for(const el of TABS)el.addEventListener("click",syncStops); for(const el of TABS)el.addEventListener("keyup",syncStops);} applyLink(); loadTokenList().then(()=>{ listReady=true; adoptList(pairAddrs()); return applyLink(); }).catch(()=>{ listReady=true; applyLink().catch(()=>{}); }); async function doSend(){ if(!sendReady)return; swap.disabled=true;++busy; try{ const{to,amount,token}=sendReady; const fresh=await rcvOf(rc.value.trim(),true); if(fresh.toLowerCase()!==to.toLowerCase())throw Error("recipient changed \u2014 check and retry"); if(amount>fromBalance)throw Error("insufficient balance"); await checkWallet(); const dsec=chainCfg().slow?BigInt(dly.value||0):0n; let txReq,batch=null; if(dsec>0n){ const isE=token.addr===ZERO; if(tipCk.checked){await syncTip();swap.disabled=true} const tip=tipCk.checked?sendTipWei:0n; const val=isE?amount+tip:tip; if(isE&&val>fromBalance)throw Error("insufficient ETH for amount + keeper tip"); const dep=tip>0n ?"0x"+SEL_DEPOSITTIP+encAddr(isE?ZERO:token.addr)+encAddr(to) +encUint(amount)+encUint(dsec)+encUint(tip)+encUint(192n)+encUint(0n) :"0x"+SEL_DEPOSITTO+encAddr(isE?ZERO:token.addr)+encAddr(to) +encUint(isE?0n:amount)+encUint(dsec)+encUint(160n)+encUint(0n); txReq={from:account,to:SLOW,data:dep,value:toHex(val)}; if(!isE){ const aHex=await rpc(C,[{to:token.addr,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(SLOW)},L]); const cur=BigInt(aHex); if(cur<amount){ const pre=cur>0n?[{to:token.addr,data:"0x"+SEL_APPROVE+encAddr(SLOW)+encUint(0),value:"0x0"}]:[]; const ap={to:token.addr,data:"0x"+SEL_APPROVE+encAddr(SLOW)+encUint(amount),value:"0x0"}; if(await canBatch(account))batch=[...pre,ap,{to:SLOW,data:dep,value:toHex(val)}]; else{stat.textContent="Approve "+token.sym+"..."; for(const c of[...pre,ap]){const at=await sendTx([{from:account,...c}]);await waitTx(at)}} }} }else{ txReq=token.addr===ZERO ?{from:account,to,value:toHex(amount)} :{from:account,to:token.addr,data:"0x"+SEL_TRANSFER+encAddr(to)+encUint(amount),value:"0x0"}; } let tx; if(batch){await checkWallet();tx=await sendBatch(account,batch)} else{const ret=await rpc(C,[txReq,L]);if(dsec===0n&&token.addr!==ZERO)okRet(ret);tx=await sendTx([txReq])} await settle(tx); amt.value="";fitFont(amt);sendReady=null; refreshBalance();render();loadPos(); }catch(e){err(e);render()} finally{--busy} } async function sendRouterFunded(callData,fundedCallData,token,amount,msgValue,verb){ await checkWallet(); let data=callData,batch=null; if(token.addr!==ZERO){ const allowData="0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(ZROUTER); const allowance=BigInt(await rpc(C,[{to:token.addr,data:allowData},L])); if(allowance<amount){ let ready=false; const pi=await permitInfo(token.addr,account); if(pi)try{ stat.textContent="Sign approval..."; const pd=BigInt(Math.floor(Date.now()/1e3)+1800); data=encCalls([await signPermit(token.addr,pi,account,amount,pd),callData]); ready=true; }catch(e){if(isRejection(e))throw e} const selected=TOKENS[fromSel.value]; const walletBal=selected&&selected.addr.toLowerCase()===token.addr.toLowerCase() ?fromBalance:BigInt(await rpc(C,[{to:token.addr,data:"0x"+SEL_BALANCEOF+encAddr(account)},L])); if(!ready&&walletBal>=amount&&await p2Ready(token.addr,account)>=amount)try{ stat.textContent="Sign approval..."; const pd=BigInt(Math.floor(Date.now()/1e3)+1800); data=encCalls([await signPermit2(token.addr,account,amount,pd),fundedCallData||callData]); ready=true; }catch(e){if(isRejection(e))throw e} if(!ready&&await canBatch(account)){ const pre=allowance>0n ?[{to:token.addr,data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(0n),value:"0x0"}]:[]; batch=[...pre, {to:token.addr,data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(amount),value:"0x0"}, {to:ZROUTER,data:callData,value:toHex(msgValue)}]; ready=true; } if(!ready){ const approvals=allowance>0n?[0n,amount]:[amount]; for(let i=0;i<approvals.length;i++){ stat.textContent=`Approve ${token.sym}${approvals.length>1?` (${i+1}/${approvals.length})`:""}...`; const h=await sendTx([{from:account,to:token.addr, data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(approvals[i]),value:"0x0"}]); await waitTx(h); } const fresh=BigInt(await rpc(C,[{to:token.addr,data:allowData},L])); if(fresh<amount)throw Error("approval failed"); } } } stat.textContent=verb; let tx; if(batch){await checkWallet();tx=await sendBatch(account,batch)} else{ const req={from:account,to:ZROUTER,data,value:toHex(msgValue)}; await rpc(C,[req,L]);await checkWallet();tx=await sendTx([req]); } await settle(tx); } const PUSH_MAGIC="381bcb9ccaaab09ca206deb73834ed7b483c58bb3face9b43e8c7b50193253e2"; const SEL_SAFE_TRANSFER_DATA="b88d4fde",SEL_OWNEROF="6352211e"; const isWns=a=>(a||"").toLowerCase()===WNS.toLowerCase(); const weiName=v=>{const n=(v||"").trim().toLowerCase();return n.endsWith(".wei")?n:n+".wei"}; const nftIdIn=(addr,v)=>{v=(v||"").trim(); if(!v||/^\d+$/.test(v)||!isWns(addr))return v; const n=weiName(v); return /^[a-z0-9-]+(\.[a-z0-9-]+)*\.wei$/.test(n)?BigInt(namehash(n)).toString():v}; const nftLabel=(sym,addr,v)=>isWns(addr)&&!/^\d+$/.test((v||"").trim()) ?weiName(v):`${sym} #${(v||"").trim()}`; async function pushNftOrder(f,t){ const id=nftIdIn(f.addr,nftId.value); if(!/^\d+$/.test(id))throw Error(isWns(f.addr)?"name the .wei name, or the token id, you are selling":"name the token id you are selling"); const tokenId=BigInt(id); if(kind.value!=="fixed")throw Error("an NFT is indivisible, so it sells at a fixed price"); const want=parseUnits(outAmt.value.trim()||"0",t.dec); if(!want)throw Error("name a price"); const quote=t.addr===ZERO?WETH:t.addr; if(quote.toLowerCase()===f.addr.toLowerCase())throw Error("pick different tokens"); if((await rpc("eth_getCode",[SB2,L])).length<5)throw Error("fixed orderbook not deployed yet"); let cp=ZERO; const v=rc.value.trim(); if(v){cp=await rcvOf(v,true);if(!cp)throw Error("Private recipient not resolved")} let owner=ZERO; try{owner="0x"+strip0x(await rpc(C,[{to:f.addr,data:"0x"+SEL_OWNEROF+encUint(tokenId)},L])).slice(-40)} catch{throw Error(`${f.sym} #${id} does not exist`)} if(owner.toLowerCase()!==account.toLowerCase())throw Error(`you do not own ${f.sym} #${id}`); const exp=+dly.value?BigInt(Math.floor(Date.now()/1e3)+ +dly.value):0n; const terms=PUSH_MAGIC+encAddr(quote)+encUint(want)+encUint(exp)+encBool(false)+encAddr(cp); const data="0x"+SEL_SAFE_TRANSFER_DATA+encAddr(account)+encAddr(SB2)+encUint(tokenId) +encUint(128n)+encUint(192n)+terms; const req={from:account,to:f.addr,data,value:"0x0"}; await rpc(C,[req,L]); await checkWallet(); await settle(await sendTx([req])); } const SEL_CREATE_ETH="0e8e31d3",SEL_CREATE="8e14f9f8",SEL_FLOOR_BID="3d8d260b"; async function bidCollection(f,t,climbing){ if(nftId.value.trim())throw Error("clear the token id to bid on the whole collection"); const ceiling=parseUnits(amt.value.trim()||"0",f.dec); if(!ceiling)throw Error("name the most you will pay in total"); const open=climbing?parseUnits(floorAmt.value.trim()||"0",f.dec):ceiling; if(climbing&&!open)throw Error("set an opening bid"); if(open>ceiling)throw Error("the opening bid exceeds the maximum"); if(ceiling>(1n<<96n)-1n)throw Error("bid amount too large"); const wantRaw=outAmt.value.trim(); if(wantRaw&&!/^\d+$/.test(wantRaw))throw Error("how many tokens? a whole number"); const want=wantRaw?BigInt(wantRaw):1n; if(!want)throw Error("bid for at least one"); if(want>(1n<<128n)-1n)throw Error("too many to bid for at once"); const duration=BigInt(dly.value||0); if(!duration)throw Error("a bid needs a window — pick an expiry"); if((await rpc("eth_getCode",[FLOOR,L])).length<5)throw Error("bid board not deployed yet"); const terms=encAddr(t.addr)+encAddr(f.addr===ZERO?ZERO:f.addr)+encUint(want) +encUint(open)+encUint(ceiling)+encUint(0n)+encUint(duration)+encBool(true) +encUint(288n)+encUint(0n); const data="0x"+SEL_FLOOR_BID+encUint(32n)+terms; if(f.addr===ZERO){ const req={from:account,to:FLOOR,data,value:toHex(ceiling)}; await rpc(C,[req,L]);await checkWallet();await settle(await sendTx([req])); return; } const tx={from:account,to:FLOOR,data,value:"0x0"}; const al=BigInt(await rpc(C,[{to:f.addr,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(FLOOR)},L])); const approvals=[]; if(al<ceiling){ if(al)approvals.push({to:f.addr,data:"0x"+SEL_APPROVE+encAddr(FLOOR)+encUint(0n),value:"0x0"}); approvals.push({to:f.addr,data:"0x"+SEL_APPROVE+encAddr(FLOOR)+encUint(ceiling),value:"0x0"}); } if(approvals.length&&await canBatch(account)){ await checkWallet(); await settle(await sendBatch(account,[...approvals,{to:FLOOR,data,value:"0x0"}])); return; } for(const a of approvals){await checkWallet();await settle(await sendTx([{from:account,...a}]))} await rpc(C,[tx,L]);await checkWallet();await settle(await sendTx([tx])); } async function buyNftOrder(f,t){ const id=nftIdIn(t.addr,nftId.value); if(!/^\d+$/.test(id))throw Error(isWns(t.addr)?"name the .wei name, or the token id, you want to buy":"name the token id you want to buy"); const tokenId=BigInt(id); if(kind.value!=="fixed")throw Error("a bid for one token is a fixed offer"); const price=parseUnits(amt.value.trim()||"0",f.dec); if(!price)throw Error("name what you will pay"); if((await rpc("eth_getCode",[SB2,L])).length<5)throw Error("fixed orderbook not deployed yet"); let cp=ZERO; const v=rc.value.trim(); if(v){cp=await rcvOf(v,true);if(!cp)throw Error("Private recipient not resolved")} try{await rpc(C,[{to:t.addr,data:"0x"+SEL_OWNEROF+encUint(tokenId)},L])} catch{throw Error(`${t.sym} #${id} does not exist`)} const exp=+dly.value?BigInt(Math.floor(Date.now()/1e3)+ +dly.value):0n; if(f.addr===ZERO){ const data="0x"+SEL_CREATE_ETH+encAddr(t.addr)+encUint(tokenId)+encBool(false) +encUint(exp)+encBool(true)+encAddr(cp); const req={from:account,to:SB2,data,value:toHex(price)}; await rpc(C,[req,L]);await checkWallet();await settle(await sendTx([req])); return; } const data="0x"+SEL_CREATE+encAddr(f.addr)+encUint(price)+encAddr(t.addr)+encUint(tokenId) +encBool(false)+encUint(exp)+encBool(false)+encBool(true)+encAddr(cp); const tx={from:account,to:SB2,data,value:"0x0"}; const al=BigInt(await rpc(C,[{to:f.addr,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(SB2)},L])); const approvals=[]; if(al<price){ if(al)approvals.push({to:f.addr,data:"0x"+SEL_APPROVE+encAddr(SB2)+encUint(0n),value:"0x0"}); approvals.push({to:f.addr,data:"0x"+SEL_APPROVE+encAddr(SB2)+encUint(price),value:"0x0"}); } if(approvals.length&&await canBatch(account)){ await checkWallet(); await settle(await sendBatch(account,[...approvals,{to:SB2,data,value:"0x0"}])); return; } for(const a of approvals){await checkWallet();await settle(await sendTx([{from:account,...a}]))} await rpc(C,[tx,L]);await checkWallet();await settle(await sendTx([tx])); } async function placeOrder(){ const f=TOKENS[fromSel.value],t=TOKENS[toSel.value]; swap.disabled=true;++busy; try{ if(f.std==="nft"||t.std==="nft"){ if(f.std==="nft")await pushNftOrder(f,t); else if(nftId.value.trim())await buyNftOrder(f,t); else await bidCollection(f,t,kind.value==="floor"); amt.value="";outAmt.value="";nftId.value="";floorAmt.value=""; floorTouched=false; fitFont(amt);fitFont(outAmt); stat.textContent=f.std==="nft"?"Listed.":"Bid placed."; loadBook();refreshBalance();render(); return; } const sell=parseUnits(amt.value.trim(),f.dec),want=parseUnits(outAmt.value.trim(),t.dec); const dutch=kind.value==="dutch",bid=kind.value==="floor"; const floor=(dutch||bid)&&floorAmt.value.trim()?parseUnits(floorAmt.value.trim(),bid?f.dec:t.dec):0n; if(dutch&&floor===0n){ try{ if(!confirm(`This lot decays to ZERO.\n\nOnce the window ends, anyone can take all ${trimAmt(sell,f.dec)} ${f.sym} for nothing — and it stays that way until you cancel.\n\nThat is how a "come and take it" launch works. If you meant to keep a reserve, set a Floor total instead.\n\nContinue with a zero floor?`))return; }catch{ stat.textContent="This lot would decay to zero and this browser blocked the confirmation. Set a Floor total, or open the page outside the wallet's in-app browser."; return; } } const board=bid?FLOOR:dutch?DUTCH:SB2; const quoteToken=!dutch&&!bid&&t.addr===ZERO?WETH:t.addr; const [bc,oc]=await Promise.all([rpc("eth_getCode",[board,L]),rpc("eth_getCode",[ORDERBOL,L])]); if(bc.length<5)throw Error(`${bid?"bid board":dutch?"Dutch":"fixed"} orderbook not deployed yet`); if(oc.length<5)throw Error("routed order adapter not deployed yet"); if(!bid&&f.addr===ZERO&&t.addr===WETH)throw Error("pick a non-WETH quote token"); if(!dutch&&!bid&&(f.addr===WETH||f.addr===ZERO)&&quoteToken===WETH)throw Error("pick different tokens"); if(dutch&&floor>want)throw Error("floor exceeds start price"); let cp=ZERO; const v=rc.value.trim(); if(!dutch&&!bid&&v){cp=await rcvOf(v,true);if(!cp)throw Error("Private recipient not resolved")} const pf=fill.value==="1"?1n:0n; const placementDeadline=BigInt(Math.floor(Date.now()/1e3)+1800); let orderData; if(bid){ const duration=BigInt(dly.value||0); if(!duration)throw Error("choose a bid window"); if(!floor)throw Error("set an opening bid"); if(floor>sell)throw Error("opening bid exceeds the maximum"); if(want>(1n<<128n)-1n||sell>(1n<<96n)-1n)throw Error("bid amount too large"); if(t.addr===ZERO)throw Error("a bid buys a token — pick one to bid for"); if((f.addr===ZERO?WETH:f.addr).toLowerCase()===t.addr.toLowerCase()) throw Error("pick different tokens"); orderData="0x"+SEL_ORDER_FLOOR+encAddr(FLOOR)+encAddr(account)+encAddr(account) +encAddr(t.addr)+encAddr(f.addr)+encUint(want)+encUint(floor)+encUint(sell) +encUint(0n)+encUint(duration)+encUint(placementDeadline); }else if(dutch){ const duration=BigInt(dly.value||0); if(!duration)throw Error("choose a decay duration"); if(sell>(1n<<128n)-1n||want>(1n<<96n)-1n||floor>(1n<<96n)-1n)throw Error("Dutch amount too large"); const dutchExpiry=dutchEnds(); orderData="0x"+SEL_ORDER_DUTCH+encAddr(DUTCH)+encAddr(account)+encAddr(account) +encAddr(f.addr)+encAddr(t.addr)+encUint(sell)+encUint(want)+encUint(floor) +encUint(0n)+encUint(duration)+encUint(dutchExpiry)+encUint(placementDeadline); }else{ const exp=+dly.value?BigInt(Math.floor(Date.now()/1e3)+ +dly.value):0n; orderData="0x"+SEL_ORDER_FIXED+encAddr(SB2)+encAddr(account)+encAddr(account) +encAddr(f.addr)+encUint(sell)+encAddr(quoteToken)+encUint(want)+encUint(pf) +encUint(exp)+encAddr(cp)+encUint(placementDeadline); } let direct,funded; if(f.addr===ZERO){ direct=funded=encSnwap(ZERO,0n,account,ZERO,0n,ORDERBOL,orderData); }else{ const checkpoint=encCheckpoint(ORDERBOL,f.addr,account); direct=encCalls([ checkpoint, encSnwap(f.addr,sell,account,ZERO,0n,ORDERBOL,orderData) ]); funded=encCalls([ checkpoint, encSweep(f.addr,sell,ORDERBOL), encSnwap(ZERO,0n,account,ZERO,0n,ORDERBOL,orderData) ]); } await sendRouterFunded(direct,funded,f,sell,f.addr===ZERO?sell:0n, bid?"Placing bid (escrowing the ceiling)...":dutch?"Placing Dutch order...":t.addr===ZERO?"Placing limit order (ETH quote settles as WETH)...":"Placing limit order..."); amt.value="";outAmt.value="";floorAmt.value=""; floorTouched=false; fitFont(amt);fitFont(outAmt); loadBook();refreshBalance();render(); }catch(e){err(e);render()}finally{--busy} } swap.onclick=async()=>{ if(tab==="book"){if(!account)return connect();return placeOrder()} if(tab==="send"){if(!account)return connect();return doSend()} if(!account) return connect(); if(!last)return; if(Date.now()>last.exp){stat.textContent="Quote expired — refreshing...";updateSoon();return} if(last.impact!==null&&last.impact!==undefined&&last.impact>=IMPACT_CONFIRM){ const pct=(Number(last.impact)/100).toFixed(2); const worse=`about ${trimAmt(last.lossTok,last.lossDec)} ${last.lossSym} ${last.isIn?"less than":"more than"} the market rate`; try{ if(last.impact>=IMPACT_TYPED){ const need=String(Math.floor(Number(last.impact)/100)); if(prompt(`STOP — this trade is priced ${pct}% away from the market.\n\nYou would ${last.isIn?"receive":"pay"} ${worse}.\n\nUsually this means the pool is illiquid or the amount is wrong.\n\nType ${need} to accept this loss:`)!==need)return; }else if(!confirm(`Price impact ${pct}%.\n\nYou will ${last.isIn?"receive":"pay"} ${worse}.\n\nContinue?`))return; }catch{ stat.textContent=`This trade is ${pct}% away from the market and this browser blocked the confirmation. Reduce the amount, or open the page outside the wallet's in-app browser.`; return; } } swap.disabled=true;++busy; try{ const q=last; const {callData,fundedCallData,msgValue,amountIn,from,to}=q; const wantsValue=q.wrap>0n?0n:(from.addr===ZERO?(q.isIn?amountIn:q.limit):0n); if(msgValue!==wantsValue)throw Error("bad value"); if((q.shownPay!==undefined&&q.shownPay!==amt.value)|| (q.shownGet!==undefined&&q.shownGet!==outAmt.value)) throw Error("the quote on screen changed \u2014 check the amounts and retry"); const rvNow=rc.value.trim(); if(((rvNow?await rcvOf(rvNow,true):"")||"").toLowerCase()!==(q.recipient||"").toLowerCase()) throw Error("recipient changed — check and retry"); await checkWallet(); try{await preflightFills(q.fills,q.tokenIn,q.tokenOut)} catch(e){stat.textContent=e.message;swap.disabled=false;last=null;updateSoon();return} let cd2=callData,batchCalls=null,prep=[]; const spender=q.spender!==undefined?q.spender:(to?null:ZROUTER); const target=to||ZROUTER; const fund=q.wrap>0n?{addr:WETH,sym:"WETH",dec:18}:from; if(q.source===SRC_PRECISION&&q.pools&&q.pools.length){ const ok=await mc3(q.pools.map(p=>({to:PFACTORY,data:"0x"+SEL_ISPOOL+encAddr(p)}))) .then(rs=>rs.length===q.pools.length&&rs.every(r=>!!r&&BigInt(r)===1n)).catch(()=>false); if(!ok)throw Error("that pool is not one the factory made — refusing to send"); if(q.wrap>0n){ const have=BigInt(await rpcRead("eth_getBalance",[account,L])); if(have<q.wrap)throw Error(`Not enough ETH: this needs ${trimAmt(q.wrap,18)} and you hold ${trimAmt(have,18)}.`); prep.push({to:WETH,data:"0xd0e30db0",value:toHex(q.wrap), note:`Wrapping ${trimAmt(q.wrap,18)} ETH — this pool holds WETH`}); } } const prepBatchable=prep.length?await canBatch(account):false; if(prep.length&&!prepBatchable){ for(const c of prep){ stat.textContent=c.note+"..."; const ctx={from:account,to:c.to,data:c.data,value:c.value}; await rpc(C,[ctx,L]);await checkWallet(); await settle(await sendTx([ctx]),1); } prep=[]; } if(fund.addr!==ZERO&&spender&&spender!==ZROUTER){ const allowData="0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(spender); const a=BigInt(await rpc(C,[{to:fund.addr,data:allowData},L])); if(a<amountIn){ if(await canBatch(account)){ const pre=a>0n?[{to:fund.addr,data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(0n),value:"0x0"}]:[]; batchCalls=[...pre,{to:fund.addr,data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(amountIn),value:"0x0"}, {to:target,data:callData,value:toHex(msgValue)}]; }else{ const n=a>0n?2:1; if(a>0n){ stat.textContent=`Approving (1/${n})...`; await waitTx(await sendTx([{from:account,to:fund.addr, data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(0)}])); } stat.textContent=n>1?"Approving (2/2)...":"Approving..."; await waitTx(await sendTx([{from:account,to:fund.addr, data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(amountIn)}])); if(BigInt(await rpcRead(C,[{to:fund.addr,data:allowData},L]))<amountIn)throw Error("approval failed"); } } }else if(fund.addr!==ZERO&&!to&&!msgValue){ const allowData="0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(ZROUTER); const aHex=await rpc(C,[{to:fund.addr,data:allowData},L]); const a=BigInt(aHex); if(a<amountIn){ let permitted=false; const sameAsQuote=fund===from; const pi=sameAsQuote?await permitInfo(fund.addr,account):null; if(pi){try{ stat.textContent="Sign approval..."; const pd=BigInt(Math.floor(Date.now()/1e3)+1800); cd2=encCalls([await signPermit(fund.addr,pi,account,amountIn,pd),callData]); permitted=true; }catch(e){if(isRejection(e)){stat.textContent="";swap.disabled=false;return}}} const p2Amount=amountIn; const p2Fundable=!!fundedCallData||q.source!==SRC_PRECISION; if(!permitted&&p2Fundable&&sameAsQuote&&fromBalance>=p2Amount&&await p2Ready(fund.addr,account)>=p2Amount){try{ stat.textContent="Sign approval..."; const pd=BigInt(Math.floor(Date.now()/1e3)+1800); const p2Call=await signPermit2(fund.addr,account,p2Amount,pd); cd2=fundedCallData?encCalls([p2Call,fundedCallData]) :encCalls(q.isIn?[p2Call,callData]:[p2Call,callData,encSweep(fund.addr,0n,account)]); permitted=true; }catch(e){if(isRejection(e)){stat.textContent="";swap.disabled=false;return}}} if(!permitted&&await canBatch(account)){ const pre=a>0n?[{to:fund.addr,data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(0n),value:"0x0"}]:[]; batchCalls=[...pre,{to:fund.addr,data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(amountIn),value:"0x0"}, {to:target,data:callData,value:toHex(msgValue)}]; permitted=true; } if(!permitted){ const n=a>0n?2:1; if(a>0n){ stat.textContent=`Approving (1/${n})...`; const zeroData="0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(0); const h1=await sendTx([{from:account,to:fund.addr,data:zeroData}]); await waitTx(h1); } stat.textContent=n>1?`Approving (2/2)...`:"Approving..."; const apprData="0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(amountIn); const h2=await sendTx([{from:account,to:fund.addr,data:apprData}]); await waitTx(h2); const nHex=await rpc(C,[{to:fund.addr,data:allowData},L]); if(BigInt(nHex)<amountIn)throw Error("approval failed"); } } } if(prep.length&&batchCalls){ batchCalls=[...prep.map(({to,data,value})=>({to,data,value})),...batchCalls]; prep=[]; } let tx; const mainTx={from:account,to:target,data:cd2,value:toHex(msgValue)}; if(prep.length&&!batchCalls&&await canBatch(account)){ stat.textContent="Swapping..."; await checkWallet(); tx=await sendBatch(account,[...prep.map(({to,data,value})=>({to,data,value})), {to:target,data:cd2,value:toHex(msgValue)}]); }else{ for(const c of prep){ stat.textContent=c.note+"..."; const ctx={from:account,to:c.to,data:c.data,value:c.value}; await rpc(C,[ctx,L]);await checkWallet(); await settle(await sendTx([ctx]),1); } stat.textContent="Swapping..."; if(batchCalls){ await checkWallet(); tx=await sendBatch(account,batchCalls); }else{ await rpc(C,[mainTx,L]); await checkWallet(); tx=await sendTx([mainTx]); } } await settle(tx); last=null; chCache={};loadChart(); refreshBalance(); render(); }catch(e){err(e);render()} finally{--busy} };</script>
0x68575b07…67c7·#25,913,258·0x32a48407…bd0934
data:"0x"+SEL_CREATOROF+encAddr(candidate)},L]); if(c&&"0x"+strip0x(c).slice(24)===owner.toLowerCase())token=candidate; }catch{} } if(token){ try{ const idx=await addCustomToken(token); TOKENS[idx].launched=1; rebuild(); fromSel.value=String(Math.max(0,TOKENS.findIndex(t=>t.addr===ZERO))); fromSel.dataset.prev=fromSel.value; toSel.value=String(idx); toSel.dataset.prev=toSel.value; pairChosen=true; syncDisabled();syncNftUI(); refreshBalance(); update(); }catch{} } stat.innerHTML=token?`${safeSym(sym)} is live at ` +`<a href="${chainCfg().explorer}/token/${token}" target="_blank" rel="noreferrer">${short(token)}</a>` +` \u00b7 0.4% of each trade goes to you \u2014 collect it from the coin panel` :`${safeSym(sym)} is live \u00b7 0.4% of each trade goes to you`; lnArtBytes=null;lnArtNote.textContent="none";lnArtNote.classList.remove("set"); lnPrev.classList.remove("set");lnArt.value=""; }catch(e){err(e)} finally{lnGo.disabled=false} }; const SEL_MARKETS="29c21083",SEL_TAPE="29a65241"; const SEL_BYCREATOR="7d78be2b",SEL_TOKEN1="d21220a7",SEL_BYCREATOR_N="aa5e6b5b",SEL_RESERVE0="443cb4bc",SEL_FEERECIP="46904840"; const LAUNCHED=[]; const LAUNCH_SHOW=24; const LAUNCH_SCAN=256; const FINE=300,COARSE=14400,TFS=[["5m",300],["1h",3600],["4h",14400],["1d",86400]]; const CH_POOLS=8; const srcFor=tf=>tf>=COARSE?COARSE:FINE; let chFloor=0,chSupply=0,chFloorKey="",chCoin1=0; let chKind=LS.ck==="line"?"line":"candle",chInv=false,chInvKey="",chPeriod=300,chBars=null,chSeq=0,chPools=null,chOpen=LS.ch==="1",chCache={}; let chDraw=null; const chKeep=(key,val)=>{ chCache[key]=val; for(const k in chCache)if(k!==key&&Date.now()-chCache[k].at>60000)delete chCache[k]; }; const unf=p=>(p&0xffffff)*2**(p>>>24); const decBar=w=>{if(!w)return null;const f=s=>Number((w>>BigInt(s))&0xffffffffn); return{b:Number(w&0xffffffffn),o:unf(f(32)),h:unf(f(64)),l:unf(f(96)),c:unf(f(128)),v:unf(f(160)),n:Number((w>>192n)&0xffffn)}}; const rollUp=(bars,src,dst)=>{if(dst<=src)return bars.filter(Boolean); const out=[];let g=null,k=null; for(let i=bars.length-1;i>=0;i--){const b=bars[i];if(!b)continue; const key=Math.floor(b.b*src/dst); if(key!==k){if(g)out.push(g);k=key;g={b:key,o:b.o,h:b.h,l:b.l,c:b.c,v:b.v,n:b.n}} else{g.h=Math.max(g.h,b.h);g.l=Math.min(g.l,b.l);g.c=b.c;g.v+=b.v;g.n+=b.n}} if(g)out.push(g);return out.reverse()}; const CH_WICK_SHARE=.01; const mergeTapes=tapes=>{const m=new Map(); for(const bars of tapes)for(const b of bars||[]){if(!b)continue; let e=m.get(b.b); if(!e)m.set(b.b,e={b:b.b,v:0,n:0,ov:0,cv:0,parts:[]}); e.v+=b.v;e.n+=b.n;e.ov+=b.o*b.v;e.cv+=b.c*b.v;e.parts.push(b)} const out=[...m.values()].sort((x,y)=>y.b-x.b); for(const e of out){ const big=e.v>0?e.parts.filter(x=>x.v>=e.v*CH_WICK_SHARE):[]; const src=big.length?big:e.parts; let h=-Infinity,l=Infinity; for(const x of src){if(x.h>h)h=x.h;if(x.l<l)l=x.l} e.h=h;e.l=l; if(e.v>0){e.o=e.ov/e.v;e.c=e.cv/e.v}else{e.o=src[0].o;e.c=src[0].c} delete e.ov;delete e.cv;delete e.parts} return out}; const chPair=()=>{const f=TOKENS[fromSel.value],t=TOKENS[toSel.value]; if(!f||!t||f.addr===t.addr)return null; return f.addr.toLowerCase()<t.addr.toLowerCase()?[f,t]:[t,f]}; let chLoading=""; async function loadChart(){ const p0=chPair(); const k0=chPairKey(p0); if(k0&&chLoading===k0)return; chBars=null;chPools=null; const p=p0; if(tab!=="swap"||!p){syncChartUI();return} const my=++chSeq,key=k0; const hit=chCache[key]; if(hit&&Date.now()-hit.at<30000){chBars=hit.bars;chPools=hit.pools;syncChartUI();return} chLoading=key; try{ if(PPLENS===ZERO||(await rpc("eth_getCode",[PPLENS,L])).length<5)return syncChartUI(); const marketsFor=async pair=>{ const [raw]=await mc3([{to:PPLENS,data:"0x"+SEL_MARKETS+encAddr(pair[0].addr)+encAddr(pair[1].addr) +encAddr(ZERO)+encUint(0)+encUint(LQ_SCAN)+encUint(0)}]); const found=[]; if(raw){const h=strip0x(raw),n=Number(BigInt("0x"+h.slice(64,128))),base=128; for(let i=0;i<n&&i<LQ_SCAN;i++){const at=k=>BigInt("0x"+h.slice(base+(i*PI_WORDS+k)*64,base+(i*PI_WORDS+k+1)*64)); if(at(PI_HOOK)!==0n||at(PI_LIQ)===0n)continue; found.push({a:"0x"+h.slice(base+(i*PI_WORDS+PI_POOL)*64+24,base+(i*PI_WORDS+PI_POOL+1)*64),liq:at(PI_LIQ)})}} found.sort((x,y)=>y.liq>x.liq?1:y.liq<x.liq?-1:0); return found.slice(0,CH_POOLS).map(x=>x.a); }; let eff=p,pools=await marketsFor(p); if(my!==chSeq)return; if(!pools.length){ const alt=chOtherForm(p); if(alt){ const altPools=await marketsFor(alt); if(my!==chSeq)return; if(altPools.length){eff=alt;pools=altPools} } } const [t0,t1]=eff; if(!pools.length){chKeep(key,{at:Date.now(),bars:null,pools:null,pair:null});return syncChartUI()} const reqs=[]; for(const a of pools)reqs.push({to:a,data:"0x"+SEL_TAPE+encUint(FINE)+encUint(256)}, {to:a,data:"0x"+SEL_TAPE+encUint(COARSE)+encUint(256)}); const tapes=await mc3Deep(reqs); if(my!==chSeq)return; const dec1Tape=hex=>{if(!hex)return null; const h=strip0x(hex),n=Number(BigInt("0x"+h.slice(64,128))),bars=[]; for(let j=0;j<n;j++)bars.push(decBar(BigInt("0x"+h.slice(128+j*64,128+(j+1)*64)))); return bars}; const decoded=tapes.map(dec1Tape); const fine=[],coarse=[]; for(let i=0;i<pools.length;i++){fine.push(decoded[i*2]);coarse.push(decoded[i*2+1])} const sc=10**(t0.dec-t1.dec)/1e18; const build=set=>{const m=mergeTapes(set); for(const b of m){b.o*=sc;b.h*=sc;b.l*=sc;b.c*=sc}return m.length?m:null}; const f=build(fine); let c=build(coarse); if(f&&c){const lead=rollUp(f,FINE,COARSE).filter(b=>b.b>c[0].b); if(lead.length)c=lead.concat(c)} chBars=f||c?{[FINE]:f,[COARSE]:c}:null; const live=set=>set.filter(d=>d&&d.some(Boolean)).length; chPools=chBars?{[FINE]:live(fine),[COARSE]:live(coarse)}:null; chKeep(key,{at:Date.now(),bars:chBars,pools:chPools,pair:eff===p?null:eff}); }catch{chBars=null;chPools=null} finally{if(chLoading===k0)chLoading=""} if(my===chSeq)syncChartUI(); } const chPoolsAt=step=>chPools&&chPools[step]||0; const chLabel=p=>chInv?`${p[0].sym} per ${p[1].sym}`:`${p[1].sym} per ${p[0].sym}`; async function chFloorFor(p){ const key=chPairKey(p); if(key===chFloorKey)return; chFloorKey=key;chFloor=0;chSupply=0;chCoin1=0; const tok=p&&p[1]&&p[1].launched?p[1].addr:p&&p[0]&&p[0].launched?p[0].addr:""; if(!tok)return; chCoin1=p[1]&&p[1].launched?1:0; try{ const [fp,sup]=await mc3Deep([{to:PLAUNCH,data:"0x"+SEL_FLOORPRICE+encAddr(tok)}, {to:tok,data:"0x"+SEL_TOTALSUPPLY}]); if(key!==chFloorKey)return; chFloor=fp?+formatUnits(BigInt(fp),18):0; chSupply=sup?+formatUnits(BigInt(sup),18):0; }catch{} } function syncChartUI(){ const on=tab==="swap"&&!!chBars; chTog.classList.toggle("hide",!on); chBox.classList.toggle("hide",!on||!chOpen); chTog.setAttribute("aria-expanded",on&&chOpen?"true":"false"); if(!on||!chOpen)return; const p=chEff(); chNote.textContent=p?chLabel(p):""; drawChart();syncInv(); } const NUMERAIRES=new Set([ "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", "0xdac17f958d2ee523a2206206994597c13d831ec7", "0x6b175474e89094c44da98b954eedeac495271d0f", ]); const moneyRank=a=>{a=(a||"").toLowerCase(); return NUMERAIRES.has(a)?2:(a===ZERO||a===WETH.toLowerCase())?1:0}; const chPairKey=p=>p?p[0].addr.toLowerCase()+":"+p[1].addr.toLowerCase():""; const chNaturalInv=p=>!!p&&moneyRank(p[0].addr)>moneyRank(p[1].addr); const chOtherForm=p=>{ const eth=p.filter(x=>isEthLike(x.addr)); if(eth.length!==1)return null; const cur=eth[0],rest=p[0]===cur?p[1]:p[0]; const sub=TOKENS.find(x=>x.addr.toLowerCase()===(cur.addr===ZERO?WETH:ZERO).toLowerCase()); if(!sub)return null; return sub.addr.toLowerCase()<rest.addr.toLowerCase()?[sub,rest]:[rest,sub]; }; const chEff=()=>{const p=chPair();if(!p)return p; const hit=chCache[chPairKey(p)]; return (hit&&hit.pair)||p;}; const CH_SLOTS=Math.floor((340-2-42)/3),CH_FILL=.34; const chFinest=()=>chBars&&chBars[FINE]?FINE:COARSE; const chSeries=secs=>{ if(!chBars)return null; const src=srcFor(secs),step=chBars[src]?src:chFinest(),base=chBars[step]; if(!base||!base.length)return null; const per=Math.max(secs,step); let pts=rollUp(base,step,per).slice().reverse(); pts=pts.map(b=>{const{o,c}=b; if(!(o>0)||!(c>0)||!isFinite(o)||!isFinite(c))return null; const h=b.h>0&&isFinite(b.h)?b.h:0,l=b.l>0&&isFinite(b.l)?b.l:Infinity; return{...b,h:Math.max(h,o,c),l:Math.min(l,o,c)}}).filter(Boolean); return pts.length?{pts,per,step}:null; }; const chAutoPeriod=()=>{ if(!chBars)return null; const fit=secs=>{const s=chSeries(secs); if(!s||s.per!==secs)return null; const pts=s.pts,newest=pts[pts.length-1].b; const shown=pts.filter(b=>newest-b.b<CH_SLOTS); if(!shown.length)return null; const fill=shown.length/(newest-shown[0].b+1); const whole=newest-pts[0].b+1<=CH_SLOTS; return{score:shown.length*fill,ok:shown.length>=6&&fill>=CH_FILL&&whole}}; let best=null,bestS=-1; for(const[,secs] of TFS){const f=fit(secs); if(!f)continue; if(f.ok)return secs; if(f.score>bestS){best=secs;bestS=f.score}} return best==null?chFinest():best; }; let chTfKey=""; const chSyncPeriod=()=>{ const k=chPairKey(chEff()); if(k===chTfKey)return; const auto=chAutoPeriod(); if(auto==null)return; chTfKey=k;chPeriod=auto; }; const chSyncTfs=()=>{ const finest=chFinest(); if(chPeriod<finest)chPeriod=finest; for(const c of chTf.children){if(!c.dataset.secs)continue; const secs=Number(c.dataset.secs); c.disabled=!!chBars&&secs<finest; c.title=c.disabled?"This pair has no five-minute tape to draw":""; c.classList.toggle("on",secs===chPeriod)} }; const chSyncOrientation=()=>{ const p=chEff(),k=chPairKey(p); if(k===chInvKey)return; chInvKey=k;chInv=chNaturalInv(p); }; const hudFor=i=>{ if(!chDraw)return; const{pts,num,UP,DN,ch,per,hd}=chDraw; if(!hd)return; if(i<0){const b=pts[pts.length-1]; let note="",flip=""; if(chInv&&isFinite(ch)&&ch>-100){ const other=(1/(1+ch/100)-1)*100; const pr=chEff&&chEff(); const base=pr&&pr[0]?pr[0].sym:"the token"; note=` title="${safeSym(base)} is ${other>=0?"+":""}${other.toFixed(2)}% over this window. ` +`This chart prices the pair the other way up, so its line moves the opposite way."`; flip=` <span class="chflip">${safeSym(base)} ${other>=0?"+":""}${other.toFixed(2)}%</span>`; } hd.innerHTML=`<b>${num(b.c)}</b> <span style="color:${ch>=0?UP:DN}"${note}>${ch>=0?"+":""}${ch.toFixed(2)}%</span>${flip}`; return} const b=pts[i],up=b.c>=b.o,d=new Date(b.b*per*1e3); const when=per>=86400 ?d.toLocaleDateString("en-US",{month:"short",day:"numeric"}) :d.toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",hour12:false}); hd.innerHTML=`<b style="color:${up?UP:DN}">${num(b.c)}</b> <span>${when}</span>` +`<span class="ohlc">H ${num(b.h)} L ${num(b.l)} · ${b.n} trade${b.n===1?"":"s"}</span>`; }; const chPoint=e=>{ if(!chDraw)return; const r=chArt.getBoundingClientRect(); if(!r.width)return; const{pts,W,PL,st,b0}=chDraw; const x=(e.clientX-r.left)/r.width*W; const at=b0+Math.round((x-PL)/st-.5); let i=0,near=Infinity; for(let j=0;j<pts.length;j++){const d=Math.abs(pts[j].b-at); if(d<near){near=d;i=j}} hudFor(i); if(!chDraw.svg)return; let el=chDraw.cross; if(!el){ el=document.createElementNS("http://www.w3.org/2000/svg","line"); el.setAttribute("class","cross"); el.setAttribute("y1","0");el.setAttribute("y2","150"); el.setAttribute("stroke","currentColor");el.setAttribute("stroke-opacity",".35"); el.setAttribute("stroke-width",".8"); chDraw.svg.appendChild(el);chDraw.cross=el; } const cx=chDraw.X(i).toFixed(1); el.setAttribute("x1",cx);el.setAttribute("x2",cx); }; chArt.addEventListener("pointermove",chPoint); chArt.addEventListener("pointerdown",chPoint); chArt.addEventListener("pointerleave",()=>{ if(chDraw&&chDraw.cross){chDraw.cross.remove();chDraw.cross=null} hudFor(-1)}); function drawChart(){ if(!chBars)return; const W=340,H=150,PL=2,PR=42,PT=10,PB=14,VH=22; const pw=W-PL-PR,ph=H-PT-PB-VH; chSyncPeriod();chSyncOrientation();chSyncTfs(); const ser=chSeries(chPeriod); if(!ser){chArt.innerHTML='<div class="msg">No trades in this window.</div>';chDraw=null;return} const per=ser.per; let pts=ser.pts; const newest=pts[pts.length-1].b,cap=Math.floor(pw/3); const kept=pts.filter(b=>newest-b.b<cap); const clipped=kept.length<pts.length; pts=kept; if(chInv)pts=pts.map(b=>({...b,o:1/b.o,h:1/b.l,l:1/b.h,c:1/b.c})) .filter(b=>isFinite(b.o)&&isFinite(b.h)&&isFinite(b.l)&&isFinite(b.c)); if(!pts.length){chArt.innerHTML='<div class="msg">No trades in this window.</div>';chDraw=null;return} let hi=-Infinity,lo=Infinity,vm=0; for(const b of pts){if(b.v>vm)vm=b.v} if(chKind==="line"){ for(const b of pts){if(b.c>hi)hi=b.c;if(b.c<lo)lo=b.c} }else{ let bh=-Infinity,bl=Infinity; for(const b of pts){const t=Math.max(b.o,b.c),u=Math.min(b.o,b.c); if(t>bh)bh=t;if(u<bl)bl=u} const span=bh>bl?bh-bl:bh*.1; const cutHi=Math.min(bh+span*CH_WICK_CAP,bh*2), cutLo=Math.max(bl-span*CH_WICK_CAP,bl/2); for(const b of pts){ const h=Math.min(b.h,cutHi),l=Math.max(b.l,cutLo); if(h>hi)hi=h;if(l<lo)lo=l} } if(hi===lo){hi*=1.001;lo*=0.999} const rawLo=lo; let sp=hi-lo;hi+=sp*.09; lo-=Math.min(sp*.09,rawLo>0?rawLo*.1:0); if(lo<=0)lo=rawLo>0?rawLo*.9:0; sp=hi-lo; const n=pts.length,b0=pts[0].b,slots=pts[n-1].b-b0+1; const st=pw/slots,bw=Math.max(1,Math.min(9,st*.68)); const X=i=>PL+st*(pts[i].b-b0+.5),Y=p=>PT+ph*(1-(p-lo)/sp),vt=PT+ph+6,VY=v=>vt+(VH-6)*(1-(vm?v/vm:0)); const num=v=>v>=1000?v.toLocaleString("en-US",{maximumFractionDigits:2}):v>=1?v.toFixed(3):fmtPx(v); const UP="#16a34a",DN="#c33"; const p0=chEff(),lbl=p0?chLabel(p0):"Price"; let s=`<svg viewBox="0 0 ${W} ${H}" preserveAspectRatio="none" role="img" aria-label="${lbl}, ${n} bars, ${num(pts[n-1].c)} now, range ${num(lo)} to ${num(hi)}">`; for(let g=0;g<=3;g++){const p=lo+sp*g/3,y=Y(p).toFixed(1); s+=`<line x1="${PL}" y1="${y}" x2="${W-PR}" y2="${y}" stroke="currentColor" stroke-opacity=".1"/>` +`<text x="${W-PR+4}" y="${(Y(p)+2.5).toFixed(1)}" font-size="7" fill="currentColor" fill-opacity=".5">${p>0?num(p):"0"}</text>`} for(let i=0;i<n;i++){const b=pts[i],y=VY(b.v); s+=`<rect x="${(X(i)-bw/2).toFixed(1)}" y="${y.toFixed(1)}" width="${bw.toFixed(1)}" height="${Math.max(.4,vt+VH-y).toFixed(1)}" fill="currentColor" fill-opacity=".18"/>`} const chNum=p0&&(chCoin1?p0[0]:p0[1]); const ethNum=!!chNum&&(chNum.addr===ZERO||chNum.addr===WETH); const fl=chFloor>0&&ethNum?(chCoin1?(chInv?chFloor:1/chFloor):(chInv?1/chFloor:chFloor)):0; if(fl>0&&fl>=lo&&fl<=hi){ const fy=Y(fl).toFixed(1); s+=`<line x1="${PL}" y1="${fy}" x2="${W-PR}" y2="${fy}" stroke="currentColor" stroke-opacity=".45" stroke-width=".7" stroke-dasharray="3 2"/>` +`<text x="${PL+3}" y="${(Y(fl)-2.5).toFixed(1)}" font-size="6.5" fill="currentColor" fill-opacity=".6">floor</text>`; } const rise=pts[n-1].c>=pts[0].o; if(chKind==="line"){ const d=pts.map((b,i)=>`${i?"L":"M"}${X(i).toFixed(1)} ${Y(b.c).toFixed(1)}`).join(" "); const col=rise?UP:DN; s+=`<path d="${d} L${X(n-1).toFixed(1)} ${(PT+ph).toFixed(1)} L${X(0).toFixed(1)} ${(PT+ph).toFixed(1)} Z" fill="${col}" fill-opacity=".1"/>` +`<path d="${d}" fill="none" stroke="${col}" stroke-width="1.2" stroke-linejoin="round"/>` +`<circle cx="${X(n-1).toFixed(1)}" cy="${Y(pts[n-1].c).toFixed(1)}" r="1.8" fill="${col}"/>`; }else{ for(let i=0;i<n;i++){const b=pts[i],col=b.c>=b.o?UP:DN,x=X(i); const t=Y(Math.max(b.o,b.c)),bo=Y(Math.min(b.o,b.c)); s+=`<line x1="${x.toFixed(1)}" y1="${Y(b.h).toFixed(1)}" x2="${x.toFixed(1)}" y2="${Y(b.l).toFixed(1)}" stroke="${col}" stroke-width=".8"/>` +`<rect x="${(x-bw/2).toFixed(1)}" y="${t.toFixed(1)}" width="${bw.toFixed(1)}" height="${Math.max(.8,bo-t).toFixed(1)}" fill="${col}"/>`} } const spanS=(pts[n-1].b-pts[0].b+1)*per; const tAt=b=>{const d=new Date(b*per*1000); return spanS>129600?d.toLocaleDateString("en-US",{month:"short",day:"numeric"}) :d.toLocaleTimeString("en-US",{hour:"2-digit",minute:"2-digit",hour12:false})}; const ticks=n>=3?[[0,"start"],[n>>1,"middle"],[n-1,"end"]]:[[0,"start"],[n-1,"end"]]; const tLbl=ticks.map(([i])=>i>=0&&i<n?tAt(pts[i].b):""); ticks.forEach(([i,anc],k)=>{if(i<0||i>=n)return; if(anc==="middle"&&(tLbl[k]===tLbl[0]||tLbl[k]===tLbl[tLbl.length-1]))return; s+=`<text x="${X(i).toFixed(1)}" y="${H-3.5}" font-size="6.5" text-anchor="${anc}" ` +`fill="currentColor" fill-opacity=".45">${tLbl[k]}</text>`}); s+="</svg>"; const last=pts[n-1],first=pts[0],ch=((last.c-first.o)/first.o)*100; chArt.innerHTML=s+'<div class="hd"></div>'; chDraw={pts,X,W,PL,st,b0,num,UP,DN,ch,per, hd:chArt.querySelector(".hd"),svg:chArt.querySelector("svg"),cross:null}; hudFor(-1); const took=rel(Math.max(per,(slots-1)*per)); const np=chPoolsAt(ser.step); const fine=(chBars&&chBars[chFinest()])||null; const newestBar=fine?fine.find(b=>b&&b.c>0):null; const latest=newestBar?newestBar.c:pts[n-1].c; const canon=latest>0?1/latest:0; const px=chCoin1?canon:latest; const mcap=chSupply>0&&px>0?chSupply*px:0; chNote.textContent=p0?[chLabel(p0), mcap>0?`${mcap>=1000?Math.round(mcap).toLocaleString("en-US"):mcap.toFixed(mcap>=1?1:3)} ${ethNum?"ETH":chCoin1?p0[0].sym:p0[1].sym} mcap${ ethNum&&ethUsd>0?` (${fmtUsd(mcap*ethUsd)})`:""}`:"", `${np} pool${np===1?"":"s"}`,`${clipped?"last ":""}${took}`].filter(Boolean).join(" · "):""; if(p0){const was=chFloor+":"+chSupply+":"+chCoin1; chFloorFor(p0).then(()=>{if(chFloor+":"+chSupply+":"+chCoin1!==was)drawChart()});} } for(const[label,secs]of TFS){const b=document.createElement("button"); b.textContent=label;b.dataset.secs=String(secs);if(secs===chPeriod)b.className="on"; b.onclick=()=>{chPeriod=secs;chTfKey=chPairKey(chEff());drawChart()}; chTf.appendChild(b)} const kindBtn=document.createElement("button"); kindBtn.className="chk"; const syncKind=()=>{kindBtn.textContent=chKind==="candle"?"Line":"Candle"; kindBtn.title=`Switch to ${kindBtn.textContent.toLowerCase()} chart`}; syncKind(); kindBtn.onclick=()=>{chKind=chKind==="candle"?"line":"candle";LS.ck=chKind;syncKind();drawChart()}; chTf.appendChild(kindBtn); const invBtn=document.createElement("button"); invBtn.className="chk"; invBtn.textContent="⇅"; const syncInv=()=>{const p=chEff(); invBtn.title=p?`Price ${chInv?p[1].sym:p[0].sym} instead — currently ${chInv?p[0].sym:p[1].sym} per ${chInv?p[1].sym:p[0].sym}`:"Invert the price"; invBtn.setAttribute("aria-pressed",chInv?"true":"false")}; syncInv(); invBtn.onclick=()=>{chInv=!chInv;syncInv();drawChart()}; chTf.appendChild(invBtn); const syncChart=()=>{chSyncOrientation();syncInv();loadChart()}; chTog.onclick=()=>{chOpen=!chOpen;LS.ch=chOpen?"1":"0";syncChartUI()}; const lsecs=v=>{const m=/^(\d+)([mhdw]?)$/.exec((v||"").trim()); return m?+m[1]*(m[2]=="d"?86400:m[2]=="h"?3600:m[2]=="w"?604800:m[2]=="m"?60:1):NaN}; const symAddr=x=>{const m=TOKENS.filter(t=>t.sym.toLowerCase()===x); return m.length===1?m[0].addr.toLowerCase():""}; const ARCADE=".arcade.wei",SC_OWNER="6352211e",SC_TEXT="308e3386"; const scEl=document.getElementById("scEl"); let scSeq=0; const scShow=async name=>{ const my=++scSeq; const n=String(name||"").trim().toLowerCase(); if(!/^[a-z0-9-]{1,63}\.arcade\.wei$/.test(n)){scEl.classList.add("hide");return} const lbl=n.slice(0,-ARCADE.length); scEl.classList.remove("hide"); scEl.textContent="Reading "+n+"\u2026"; let owner="",rec=""; try{ const id=namehash(n); const[o,t]=await cfgBatch([{to:WNS,data:"0x"+SC_OWNER+pad32(id)}, {to:WNS,data:"0x"+SC_TEXT+pad32(id)+encUint(64)+strTail("score")}]); if(my!==scSeq)return; const a="0x"+strip0x(o||"").slice(24); if(!/^0x[0-9a-f]{40}$/i.test(a)||/^0x0{40}$/i.test(a))throw Error("gone"); owner=a; try{rec=decStr(t)}catch{} }catch{if(my===scSeq)scEl.textContent="No such name \u2014 "+n+" has never been minted.";return} const m=/^(\d+)(?:-w(\d+))?-[0-9a-z]+$/.exec(lbl); scEl.textContent=""; const b=document.createElement("b");b.textContent=n;scEl.appendChild(b); const line=t=>{const d=document.createElement("div");d.textContent=t;scEl.appendChild(d);return d}; if(m)line(`Minted score ${(+m[1]).toLocaleString("en-US")}`+(m[2]?` \u00b7 reached wave ${m[2]}`:"")); else line("Not a score name \u2014 the label carries no run."); line("Held by "+short(owner)); if(m)line("Anyone can mint a name with any number \u2014 this says it was claimed, not earned.").className="scw"; if(m&&rec&&rec!==m[1])line(`The score record now reads ${rec}, not ${m[1]} \u2014 the holder changed it.`).className="scw"; const go=document.createElement("button");go.type="button";go.textContent="Play invaders"; go.onclick=()=>{sBlip();invPlay()}; scEl.appendChild(go); }; let lSeq=0,listReady=false; const lkWrote=Object.create(null); const lkKeep=(el,v)=>{const w=lkWrote[el.id]; if(el.value!==""&&el.value!==w&&(w!==undefined||el.type!=="select-one"))return false; el.value=v;lkWrote[el.id]=v;return true; }; async function applyLink(e){ const q=new URLSearchParams(location.hash.slice(1)); const my=++lSeq; if(![...q.keys()].length){ if(e){ if(typeof amt!=="undefined"&&amt){amt.value="";fitFont(amt)} if(typeof outAmt!=="undefined"&&outAmt){outAmt.value="";fitFont(outAmt)} if(typeof rc!=="undefined"&&rc){rc.value="";rc.classList.remove("bad")} if(typeof rcvEl!=="undefined"&&rcvEl)rcvEl.textContent=""; if(typeof scEl!=="undefined"&&scEl)scEl.classList.add("hide"); setMode("in"); last=null; try{update()}catch{} } return;} const wantChain=+q.get("chain")||0; if(CHAINS[wantChain]&&wantChain!==CHAIN_ID){ if(prov()){if(wireWallet.probed)stat.textContent=`This link is for ${CHAINS[wantChain].name} \u2014 switch your wallet there to use it.`;return} setChain(wantChain);showNet();await loadTokenList();if(my!==lSeq)return; } const at=a=>a?TOKENS.findIndex(t=>t.addr.toLowerCase()===a):-1; const tok=async v=>{if(!v)return "";const x=v.trim().toLowerCase(); if(!/^0x[0-9a-f]{40}$/.test(x))return symAddr(x); if(at(x)<0)try{await addCustomToken(x,0)}catch{} return at(x)<0?"":x}; if(symAddr((q.get("token")||"").trim().toLowerCase())||symAddr((q.get("out")||"").trim().toLowerCase()))pairChosen=true; const both=async()=>[await tok(q.get("token")),await tok(q.get("out"))]; let[fa,ta]=await both(); if(my!==lSeq)return; if((fa&&at(fa)<0)||(ta&&at(ta)<0)){[fa,ta]=await both();if(my!==lSeq)return} if(!listReady){ const named=["token","out"].map(k=>(q.get(k)||"").trim()).filter(Boolean); if(named.some(v=>!/^0x[0-9a-f]{40}$/i.test(v)&&!symAddr(v.toLowerCase())))return; } const fi=at(fa),oi=at(ta); if(fi>=0||oi>=0)pairChosen=true; rebuild(); if(fi>=0){fromSel.value=fi;fromSel.dataset.prev=fromSel.value} if(oi>=0&&oi!==fi){toSel.value=oi;toSel.dataset.prev=toSel.value} if(q.get("score"))scShow(q.get("score"));else scEl.classList.add("hide"); const to=q.get("to"),lk=lsecs(q.get("lock")),want=q.get("tab"),n=q.get("amount"); const t=want==="swap"||want==="send"||want==="book"?want :q.get("out")?"swap":(to||!isNaN(lk))?"send":tab; if(fromSel.value===toSel.value){ const alt=String(TOKENS.findIndex((x,i)=>String(i)!==fromSel.value&&(t==="book"||x.std!=="nft"))); if(alt!=="-1"){if(oi>=0)fromSel.value=alt;else toSel.value=alt} } const rcMine=rc.value!==""&&rc.value!==lkWrote.rc; const wantLq=q.get("lq")==="1"; const linkWantsCard=fi>=0||oi>=0||to||n||!isNaN(lk)||q.get("token")||q.get("out"); if(!wantLq&&lqMode&&linkWantsCard)lqSet(false); if(linkWantsCard){if(lnMode)lnSet(false);if(wnMode)wnSet(false);if(pvMode)pvSet(false)} setTab(t); if(wantLq&&t==="swap"&&!lqMode)lqSet(true); if(to&&!rcMine)lkKeep(rc,to); if(!isNaN(lk)){const os=[...dly.options].map(x=>+x.value).sort((a,b)=>a-b); const v=String(os.find(o=>o>=lk)??os[os.length-1]); if(t==="book")bookD=v;else if(t==="send")sendD=v; if(t!=="swap")lkKeep(dly,v)} if(n&&/^\d*\.?\d+$/.test(n)){ if(q.get("exactOut")==="1"&&q.get("out")){if(lkKeep(outAmt,n)){setMode("out");fitFont(outAmt)}} else{if(lkKeep(amt,n)){setMode("in");fitFont(amt)}} } refreshBalance();const upd=update(); loadChart(); if(t==="book")loadBook(); if(listReady){ const miss=["token","out"].map(k=>(q.get(k)||"").trim()) .filter(v=>v&&!/^0x[0-9a-f]{40}$/i.test(v)&&!symAddr(v.toLowerCase())).map(v=>v.slice(0,24)); if(miss.length){try{await upd}catch{} if(my===lSeq)stat.textContent=`Couldn\u2019t find ${miss.join(" or ")} \
0x68575b07…67c7·#25,913,257·0xa01a2a37…ee7392
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,
0x68575b07…67c7·#25,913,256·0x044e2d08…0eb990
l){wnNote.textContent="";wnSync();return} if(!wnOk(l)){wnNote.textContent="Letters, digits and hyphens only, and not starting or ending with a hyphen.";wnSync();return} wnNote.textContent="checking…"; wnSync(); try{ const free=wnFree(); const rc=!free&&account?[{to:WNS,data:"0x"+SEL_WOWNER+pad32(wnId(l))}, {to:WROLL,data:"0x"+SEL_RWEIGHT+pad32(wnId(l))}, {to:WROLL,data:"0x"+SEL_RSTATE}]:[]; const r=await mc3([{to:WNS,data:"0x"+SEL_WAVAIL+pad32("40")+pad32(free?IDWEI:"0")+strTail(l)}, ...(free?[]:wnCostCall(l)),...rc]); if(my!==wnSeq)return; const av=r[0],f=r[1],pr=r[2]; if(!account)wnOffer("Connect to enter"); else if(free)wnOffer("A free name cannot enter the roll"); else if(dec1(av,0)===1n)wnOffer(`${l}.wei is not registered yet`); else{const ow=retAddr(r[3]),mine=!!ow&&ow.toLowerCase()===account.toLowerCase(); const wt=r[4]&&dec1(r[4],0); if(r[5])wnRound=dec1(r[5],1); if(!mine||!wt)wnOffer(!mine?`${l}.wei is not yours`:`${l}.wei does not weigh`); else wnTicketOffer(l,my,wnRound)} if(dec1(av,0)!==1n){wnNote.textContent=`${wnFull(l)} is taken.`;wnSync();return} if(free){wnQ={label:l,free:true}; wnNote.textContent=`${l}.id.wei is free, in one transaction. A free name does not weigh in the roll.`;wnSync();return} const cost=dec1(f,0)+dec1(pr,0); wnQ={label:l,free:false,cost}; wnNote.textContent=`${l}.wei costs ${trimAmt(cost,18)} ETH for a year, in two transactions a minute apart.`; }catch(e){if(my===wnSeq)wnNote.textContent="Could not price that name."} wnSync()}; const wnRoll=async()=>{ if(!wnMode)return; const my=++wnRollSeq; wnRollAt=Date.now(); try{ const [st,rv]=await mc3([{to:WROLL,data:"0x"+SEL_RSTATE}, ...(account?[{to:WNS,data:"0x"+SEL_REV+encAddr(account)}]:[])]); const round=dec1(st,1),end=Number(dec1(st,2)),pot=dec1(st,3),tix=dec1(st,5),tw=dec1(st,6); if(my!==wnRollSeq)return; const days=Math.max(0,Math.ceil((end-Date.now()/1000)/86400)); let line=`A monthly draw among paid .wei names. ${(+formatUnits(pot,18)).toFixed(3)} ETH staked, ${tix} entered, ${days} day${days===1?"":"s"} left.`; wnRound=round; const typed=wnName.value.trim(); if(!typed)wnOffer(account?"Enter a name you own":"Connect to enter"); if(account&&!typed){ const nm=decodeString(rv||"0x").trim(); if(!nm)line+=" Any paid .wei name you own can enter, free — type one above."; else{ const id=wnId(nm); const [w,t]=await mc3([{to:WROLL,data:"0x"+SEL_RWEIGHT+pad32(id)}, {to:WROLL,data:"0x"+SEL_RTICKET+encUint(round)+pad32(id)}]); if(my!==wnRollSeq||wnName.value.trim()){wnRollEl.textContent=line;return} const weight=dec1(w,0); if(!weight){line+=` ${nm} does not weigh — only a paid .wei name does.`; wnOffer("Enter a name you own")} else if(dec1(t,0)){const odds=tw?Number(weight*10000n/tw)/100:0; line+=` ${nm} is in, at about ${odds<0.01?"<0.01":odds.toFixed(2)}%.`} else{line+=` ${nm} can enter, free.`;wnOffer(`Enter ${nm} in the roll`,pad32(id))}}} wnRollEl.textContent=line; }catch{wnRollEl.textContent="Could not read the roll."}}; const wnPaint=()=>{const on=wnMode; wn.setAttribute("aria-pressed",on?"true":"false"); wn.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 [wnPanel,wnGo,wnRollPanel])el.classList.toggle("hide",!on); for(const el of [rc,rcvEl])if(el)el.classList.toggle("hide",on);document.querySelector(".card").classList.toggle("wnon",on)}; function wnSet(on){ if(on&&CHAIN_ID!==1)return; if(on)cbDisarm(); ++seq;quoting=false; wnMode=on; if(on&&lqMode)lqSet(false); if(on&&lnMode)lnSet(false); if(on&&pvMode)pvSet(false); wnPaint(); stat.textContent=""; clearInterval(wnTimer); if(on){wnTimer=setInterval(()=>{ if(document.hidden)return; wnSync(); if(Date.now()-wnRollAt>30000)wnRoll(); const q=wnPend(); if(q&&!q.at&&Date.now()-wnStampTry>5000){wnStampTry=Date.now(); wnStamp().then(()=>{const r=wnPend();if(r&&r.at)wnQuote()})} },1000); wnStampTry=0; const pp=wnPend();if(pp&&!pp.at)wnStamp().then(wnQuote); wnQuote();wnRoll()} else{wnNote.textContent="";wnRollEl.textContent=""} fcSync();} const syncSnd=()=>{sdw.style.display=snd?"":"none";sdx.style.display=snd?"none":""; sd.style.opacity=snd?"":".5";sd.setAttribute("aria-pressed",snd?"true":"false"); sd.title=snd?"Sound on — click to mute":"Sound off — click to unmute"}; sd.onclick=()=>{snd=!snd;try{LS.snd=snd?"1":"0"}catch{}syncSnd();if(snd)sBlip()}; syncSnd(); wn.onclick=()=>{sBlip();wnSet(!wnMode)}; const cpHex=b=>{let s="";for(const x of b)s+=x.toString(16).padStart(2,"0");return s},cpU8=s=>new TextEncoder().encode(s); const CP_POOL="0x0000000000047DD77CeCEfE5Dc015EB7bFa9C677",CP_ROUTER="0x000000004c5BF191225F9049b385d6F3820E09BC", CP_ETH="0x3cba71e1114af183cdeacc6b8457a474d17529fd28704480ca799d0d03126f34",CP_SCALE=10000000000n,CP_BLOCK=25892003, CP_RELAY="https://api.tacit.finance",CP_LOGS=["https://mainnet.gateway.tenderly.co","https://eth.drpc.org"], CP_NI="0x00000000000000000000000000000000000000C8",CP_KEY="zswap:cpk:",CP_NOTES="zswap:cpn:",CP_CACHE="zswap:cppool"; const CP_L2={8453:{bridge:"0x3154Cf16ccdb4C6d922629664174b904d80F2C35",peer:"0x4200000000000000000000000000000000000010",gas:800000n}, 4663:{inbox:"0x1A07cc4BD17E0118BdB54D70990D2158AbAD7a2D",peer:"0xDf8755334ce7A73cCF6b581C02eA649AE3E864b3",gas:1000000n}}; const SEL_CPESCROW="2bf0cda2",SEL_CPACT="1699fd5b",SEL_CPRECL="02edf635",SEL_CPEXIT="acad0634",SEL_CPWRAP="859a9cee", SEL_CPDEP="7da9874f",SEL_CPDEPTO="9a2ac6d5",SEL_CPRETRY="679b6ded",SEL_CPSUBFEE="a66b327d",SEL_CPESTRT="c3dc5879", SEL_CPIMPL="93228617",SEL_CPNEXT="0be4f422",SEL_CPASSET="9fda5b66",SEL_CPOTHER="7f46ddb2",SEL_CPBRIDGE="e78cea92"; const CP_T_LEAVES="0x7783fb256f5b4e1d4d8b79583488756286326ae15d9997d4098ce5432ed2708b", CP_T_SPENT="0x576d91547505afce99e7ebe2baf1a0948b5915598105a55f40ba72fea86e875e", CP_T_WRAP="0xf5d1711d21af6f42622ab6237626933cefc42cc9f0663d81b7c4c7bc5ce99e44"; const CP_MSG="zSwap private bridge\n\nSign to derive the key that owns your shielded deposits. This costs nothing and moves nothing.\n\nAccount: "; const S2K=[0x428a2f98,0x71374491,0xb5c0fbcf,0xe9b5dba5,0x3956c25b,0x59f111f1,0x923f82a4,0xab1c5ed5,0xd807aa98,0x12835b01,0x243185be,0x550c7dc3,0x72be5d74,0x80deb1fe,0x9bdc06a7,0xc19bf174,0xe49b69c1,0xefbe4786,0x0fc19dc6,0x240ca1cc,0x2de92c6f,0x4a7484aa,0x5cb0a9dc,0x76f988da,0x983e5152,0xa831c66d,0xb00327c8,0xbf597fc7,0xc6e00bf3,0xd5a79147,0x06ca6351,0x14292967,0x27b70a85,0x2e1b2138,0x4d2c6dfc,0x53380d13,0x650a7354,0x766a0abb,0x81c2c92e,0x92722c85,0xa2bfe8a1,0xa81a664b,0xc24b8b70,0xc76c51a3,0xd192e819,0xd6990624,0xf40e3585,0x106aa070,0x19a4c116,0x1e376c08,0x2748774c,0x34b0bcb5,0x391c0cb3,0x4ed8aa4a,0x5b9cca4f,0x682e6ff3,0x748f82ee,0x78a5636f,0x84c87814,0x8cc70208,0x90befffa,0xa4506ceb,0xbef9a3f7,0xc67178f2]; const sha2=m=>{const l=m.length,n=((l+9+63)>>6)<<6,b=new Uint8Array(n);b.set(m);b[l]=128; const dv=new DataView(b.buffer);dv.setUint32(n-4,(l*8)>>>0);dv.setUint32(n-8,Math.floor(l/536870912)); const h=[0x6a09e667,0xbb67ae85,0x3c6ef372,0xa54ff53a,0x510e527f,0x9b05688c,0x1f83d9ab,0x5be0cd19],w=new Uint32Array(64),rr=(x,k)=>(x>>>k)|(x<<(32-k)); for(let o=0;o<n;o+=64){for(let i=0;i<16;i++)w[i]=dv.getUint32(o+i*4); for(let i=16;i<64;i++){const p=w[i-15],q=w[i-2];w[i]=w[i-16]+(rr(p,7)^rr(p,18)^(p>>>3))+w[i-7]+(rr(q,17)^rr(q,19)^(q>>>10))} let[a0,a1,a2,a3,a4,a5,a6,a7]=h; for(let i=0;i<64;i++){const t1=(a7+(rr(a4,6)^rr(a4,11)^rr(a4,25))+((a4&a5)^(~a4&a6))+S2K[i]+w[i])>>>0,t2=((rr(a0,2)^rr(a0,13)^rr(a0,22))+((a0&a1)^(a0&a2)^(a1&a2)))>>>0; a7=a6;a6=a5;a5=a4;a4=(a3+t1)>>>0;a3=a2;a2=a1;a1=a0;a0=(t1+t2)>>>0} h[0]=(h[0]+a0)>>>0;h[1]=(h[1]+a1)>>>0;h[2]=(h[2]+a2)>>>0;h[3]=(h[3]+a3)>>>0;h[4]=(h[4]+a4)>>>0;h[5]=(h[5]+a5)>>>0;h[6]=(h[6]+a6)>>>0;h[7]=(h[7]+a7)>>>0} const o=new Uint8Array(32);for(let i=0;i<8;i++){o[i*4]=h[i]>>>24;o[i*4+1]=(h[i]>>>16)&255;o[i*4+2]=(h[i]>>>8)&255;o[i*4+3]=h[i]&255}return o}; const FP=0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2fn,FN=0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141n; const GP=[0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798n,0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8n,1n],PZ=[0n,1n,0n]; const fmod=(a,m)=>{a%=m;return a<0n?a+m:a}; const fpow=(b,e,m)=>{let r=1n;b=fmod(b,m);for(;e>0n;e>>=1n){if(e&1n)r=r*b%m;b=b*b%m}return r}; const pdbl=([x,y,z])=>{if(!z||!y)return PZ;const ys=y*y%FP,s=4n*x*ys%FP,m=3n*x*x%FP,x3=fmod(m*m-2n*s,FP); return[x3,fmod(m*(s-x3)-8n*ys*ys,FP),2n*y*z%FP]}; const padd=(P,Q)=>{if(!P[2])return Q;if(!Q[2])return P; const[x1,y1,z1]=P,[x2,y2,z2]=Q,z1s=z1*z1%FP,z2s=z2*z2%FP,u1=x1*z2s%FP,u2=x2*z1s%FP,s1=y1*z2s%FP*z2%FP,s2=y2*z1s%FP*z1%FP; if(u1===u2)return s1===s2?pdbl(P):PZ; const h=fmod(u2-u1,FP),r=fmod(s2-s1,FP),h2=h*h%FP,h3=h2*h%FP,v=u1*h2%FP,x3=fmod(r*r-h3-2n*v,FP); return[x3,fmod(r*(v-x3)-s1*h3,FP),h*z1%FP*z2%FP]}; const pmul=(P,k)=>{let R=PZ,Q=P;for(k=fmod(k,FN);k>0n;k>>=1n){if(k&1n)R=padd(R,Q);Q=pdbl(Q)}return R}; const paff=([x,y,z])=>{if(!z)return[0n,0n];const zi=fpow(z,FP-2n,FP),z2=zi*zi%FP;return[x*z2%FP,y*z2%FP*zi%FP]}; const pcomp=P=>{const[x,y]=paff(P);return(y&1n?"03":"02")+x.toString(16).padStart(64,"0")}; const pfromx=(x,odd)=>{if(x>=FP)return null;const y2=(x*x%FP*x+7n)%FP,y=fpow(y2,(FP+1n)/4n,FP); return y*y%FP===y2?[x,Number(y&1n)!==(odd?1:0)?FP-y:y,1n]:null}; const cpCat=(...a)=>{const o=new Uint8Array(a.reduce((n,x)=>n+x.length,0));let p=0;for(const x of a){o.set(x,p);p+=x.length}return o}; const b32=v=>hexToBytes(typeof v==="bigint"?v.toString(16).padStart(64,"0"):strip0x(String(v)).padStart(64,"0")); const beN=(n,len)=>{const o=new Uint8Array(len);let v=BigInt(n);for(let i=len-1;i>=0;i--){o[i]=Number(v&255n);v>>=8n}return o}; const big=b=>BigInt("0x"+cpHex(b)); const cpH=(()=>{const s=sha2(cpU8("tacit-generator-H-v1"));for(let c=0;c<256;c++){const P=pfromx(big(sha2(cpCat(s,[c]))),0);if(P)return P}})(); const cpCommit=(v,r)=>padd(pmul(cpH,BigInt(v)),pmul(GP,BigInt(r))); const cpXY=P=>{const[x,y]=paff(P);return{cx:"0x"+x.toString(16).padStart(64,"0"),cy:"0x"+y.toString(16).padStart(64,"0")}}; const cpDerive=(seed,i)=>{const base=cpCat(b32(seed),cpU8("tacit-evm-cnote-v1"),b32(CP_ETH),beN(i,8)); return{secret:"0x"+cpHex(sha2(cpCat(base,[0]))),blinding:fmod(big(sha2(cpCat(base,[1]))),FN)||1n}}; const cpOwner=nk=>keccak(cpCat(b32(nk),cpU8("tacit-native-owner-v1"))); const cpLeaf=(cx,cy,o)=>keccak(cpCat(b32(CP_ETH),b32(cx),b32(cy),b32(o))); const cpDepCommit=(cx,cy,o)=>keccak(cpCat(b32(cx),b32(cy),b32(o))); const cpDepId=(v,c)=>keccak(cpCat(b32(CP_ETH),beN(v,32),b32(c))); const cpNu=(nk,leaf)=>keccak(cpCat(b32(nk),b32(leaf),cpU8("tacit-native-nullifier-v1"))); const cpBinding=()=>keccak(hexToBytes(pad32("1")+strip0x(CP_POOL).toLowerCase())); const cpCtx=(tag,cb,a,b,notes,amts)=>keccak(cpCat(cpU8(tag),b32(cb),b32(a),b32(b),...notes.flatMap(n=>n.map(b32)),...amts.map(x=>beN(x,8)))); const cpNonce=(r,ctx,role)=>fmod(big(hexToBytes(keccak(cpCat(cpU8("tacit-open-nonce-v1"),b32(fmod(BigInt(r),FN)),b32(ctx),cpU8(role))))),FN)||1n; const cpSigma=(v,r,ctx,k)=>{const rs=fmod(BigInt(r),FN),ks=fmod(BigInt(k),FN),Cc=cpCommit(v,rs),R=pmul(GP,ks); const e=fmod(big(hexToBytes(keccak(cpCat(cpU8("tacit-open-sigma-v1"),beN(v,8),b32(ctx),hexToBytes(pcomp(Cc)),hexToBytes(pcomp(R)))))),FN); return{R:"0x"+pcomp(R),z:"0x"+fmod(ks+e*rs,FN).toString(16).padStart(64,"0")}}; const cpSeal=(pub,n,e)=>{const P=pfromx(BigInt("0x"+pub.slice(2)),pub.slice(0,2)==="03"); const ss=sha2(hexToBytes(pcomp(pmul(P,e)))),plain=cpCat(beN(n.value,8),b32(n.blinding),b32(n.secret),b32(CP_ETH),b32(n.owner)),o=new Uint8Array(plain.length); for(let i=0;i<plain.length;i+=32){const k=sha2(cpCat(ss,[i/32]));for(let j=0;j<32&&i+j<plain.length;j++)o[i+j]=plain[i+j]^k[j]} return"0x"+pcomp(pmul(GP,e))+cpHex(o)}; const cpZeros=(()=>{const z=[new Uint8Array(32)];for(let i=1;i<32;i++)z.push(hexToBytes(keccak(cpCat(z[i-1],z[i-1]))));return z})(); const cpTree=(leaves,idx)=>{let lv=leaves.map(hexToBytes);const path=[]; for(let i=0;i<32;i++){const sib=(idx>>>i)^1;path.push("0x"+cpHex(sib<lv.length?lv[sib]:cpZeros[i]));const nx=[]; for(let k=0;k*2<lv.length;k++)nx.push(hexToBytes(keccak(cpCat(lv[2*k],2*k+1<lv.length?lv[2*k+1]:cpZeros[i])))); lv=nx.length?nx:[cpZeros[i+1]||cpZeros[0]]} return{root:"0x"+cpHex(lv[0]),path}}; const cpRandom=()=>{for(let t=0;t<32;t++){const b=new Uint8Array(32);crypto.getRandomValues(b);const s=big(b);if(s&&s<FN)return s}throw Error("no randomness")}; const cpEncCall=c=>encAddr(c.target)+encUint(c.value)+encAddr(c.token)+encUint(c.amount)+encBool(c.push)+pad32("c0")+encBytes(c.data||"0x"); const cpEncRecipe=r=>{const calls=r.calls.map(cpEncCall);let off=calls.length*32,head=encUint(calls.length); for(const c of calls){head+=encUint(off);off+=c.length/2} const ca=head+calls.join(""),sw=encUint(r.sweepTokens.length)+r.sweepTokens.map(encAddr).join(""),mo=encUint(r.minOuts.length)+r.minOuts.map(encUint).join(""); return pad32(r.exitedAsset)+encAddr(r.feeAsset)+encAddr(r.finalRecipient)+encUint(r.deadline)+encUint(r.nonce) +encUint(256)+encUint(256+ca.length/2)+encUint(256+(ca.length+sw.length)/2)+ca+sw+mo}; const cpEscrow=(impl,r)=>{const salt=keccak(hexToBytes(pad32("20")+cpEncRecipe(r))); const ih=keccak(hexToBytes("602d5f8160095f39f35f5f365f5f37365f73"+strip0x(impl).toLowerCase()+"5af43d5f5f3e6029573d5ffd5b3d5ff3")); return"0x"+strip0x(keccak(hexToBytes("ff"+strip0x(CP_ROUTER)+strip0x(salt)+strip0x(ih)))).slice(24)}; const cpActData=r=>"0x"+SEL_CPACT+pad32("20")+cpEncRecipe(r); const cpReclData=r=>{const b=cpEncRecipe(r);return"0x"+SEL_CPRECL+pad32("40")+encUint(64+b.length/2)+b+pad32("0")}; const cpExitData=(pv,pr,r)=>{const a=encBytes(pv),b=encBytes(pr),c=pad32("0"),d=cpEncRecipe(r); return"0x"+SEL_CPEXIT+pad32("80")+encUint(128+a.length/2)+encUint(128+(a.length+b.length)/2)+encUint(128+(a.length+b.length+c.length)/2)+a+b+c+d}; const cpRecipe=x=>{const l2=CP_L2[x.ch],r={exitedAsset:CP_ETH,feeAsset:ZERO,finalRecipient:x.fin,deadline:BigInt(x.dl),nonce:BigInt(x.nonce), sweepTokens:[ZERO],minOuts:[0n]}; r.calls=[x.ch===8453?{target:l2.bridge,value:BigInt(x.wei),token:ZERO,amount:0n,push:false, data:"0x"+SEL_CPDEPTO+encAddr(x.to)+encUint(200000)+pad32("60")+pad32("0")} :{target:l2.inbox,value:BigInt(x.wei),token:ZERO,amount:0n,push:false, data:"0x"+SEL_CPRETRY+encAddr(x.to)+encUint(x.l2v)+encUint(x.sc)+encAddr(x.to)+encAddr(x.to)+encUint(x.gl)+encUint(x.mf)+pad32("100")+pad32("0")}]; return r}; const relayFetch=async(path,body)=>{const ac=new AbortController(),k=setTimeout(()=>ac.abort(),20000); try{const r=await fetch(CP_RELAY+path,body?{method:"POST",headers:{"content-type":"application/json"},body:JSON.stringify(body),signal:ac.signal}:{signal:ac.signal}); let j=null;try{j=await r.json()}catch{} if(r.status===404&&!body)return{status:"unknown"}; if(!r.ok)throw Error((j&&j.error)||"The relay answered "+r.status+"."); return j||{}} catch(e){if(e&&e.name==="AbortError")throw Error("The relay did not answer."); if(e instanceof TypeError)throw Error("The relay could not be reached from this page.");throw e} finally{clearTimeout(k)}}; let cpSeed="",cpFp="",cpPub="",cpNotes=[],cpPool=null,cpSyncAt=0,cpBusy=0,pvMode=false,pvTimer=0,pvTick=0; const cpNoteOf=n=>{const{secret,blinding}=cpDerive(cpSeed,n.i),owner=cpOwner(secret),{cx,cy}=cpXY(cpCommit(n.v,blinding)),leaf=cpLeaf(cx,cy,owner); return{secret,blinding,owner,cx,cy,leaf,nu:cpNu(secret,leaf),value:BigInt(n.v)}}; const cpSaveNotes=()=>{try{LS[CP_NOTES+cpFp]=JSON.stringify(cpNotes)}catch{}}; const cpLoadNotes=()=>{try{const a=JSON.parse(LS[CP_NOTES+cpFp]||"[]");return Array.isArray(a)?a.filter(n=>Number.isInteger(n.i)&&/^\d+$/.test(n.v||"")):[]}catch{return[]}}; const cpKeyFor=a=>{try{return LS[CP_KEY+a.toLowerCase()]}catch{return""}}; const cpUse=s=>{cpSeed=s;cpFp=cpHex(sha2(cpU8("zswap-cp-v1:"+s))).slice(0,16);cpPub=pcomp(pmul(GP,BigInt(s)));cpNotes=cpLoadNotes()}; const cpUnlock=async()=>{let s=cpKeyFor(account); if(!/^0x[0-9a-f]{64}$/i.test(s||"")){ const sig=await rpc("personal_sign",["0x"+cpHex(cpU8(CP_MSG+account.toLowerCase()+"\nVersion: 1")),account]); if(!/^0x[0-9a-fA-F]{130}$/.test(sig||""))throw Error("The wallet returned an unusable signature."); s="0x"+(fmod(BigInt(keccak(hexToBytes(sig))),FN)||1n).toString(16).padStart(64,"0"); try{LS[CP_KEY+account.toLowerCase()]=s}catch{}} cpUse(s)}; const cpRescue=i=>{const k=fmod(big(sha2(cpCat(cpU8("zswap-exit-rescue-v1"),b32(cpSeed),beN(i,8)))),FN)||1n,[x,y]=paff(pmul(GP,k)); return{key:"0x"+k.toString(16).padStart(64,"0"),addr:"0x"+strip0x(keccak(cpCat(beN(x,32),beN(y,32)))).slice(24)}}; const cpLogsOk=r=>Array.isArray(r)&&r.every(l=>l&&Array.isArray(l.topics)&&l.topics.every(t=>/^0x[0-9a-f]{64}$/i.test(t)) &&/^0x([0-9a-f]{2})*$/i.test(l.data||"0x")&&/^0x[0-9a-f]+$/i.test(l.blockNumber||"")&&/^0x[0-9a-f]+$/i.test(l.logIndex||"")); const cpGetLogs=async(from,to,depth)=>{ const f={address:CP_POOL,fromBlock:toHex(from),toBlock:toHex(to),topics:[[CP_T_LEAVES,CP_T_SPENT,CP_T_WRAP]]}; try{const r=await cfgRead("eth_getLogs",[f]);if(cpLogsOk(r))return r}catch{} for(const u of CP_LOGS){try{const r=await httpRead(u,"eth_getLogs",[f]);if(cpLogsOk(r))return r}catch{}} if(to-from<2000||depth>12)throw Error("No node would serve the pool's history."); const m=from+((to-from)>>1);return(await cpGetLogs(from,m,depth+1)).concat(await cpGetLogs(m+1,to,depth+1))}; const cpWord=(d,i)=>d.slice(i*64,i*64+64); const cpArr=(d,off)=>{const n=Number(BigInt("0x"+cpWord(d,off)||"0x0")),o=[];for(let i=0;i<n;i++)o.push("0x"+cpWord(d,off+1+i));return o}; const cpSync=async(force)=>{ if(!force&&cpPool&&Date.now()-cpSyncAt<60000)return cpPool; let c=cpPool;if(!c)try{c=JSON.parse(LS[CP_CACHE]||"null")}catch{} if(!c||!Array.isArray(c.leaves)||!Array.isArray(c.spent)||!Array.isArray(c.wraps)||!(c.to>=CP_BLOCK))c={to:CP_BLOCK-1,leaves:[],spent:[],wraps:[]}; const cached=c.to>=CP_BLOCK; const head=parseInt(await cfgRead("eth_blockNumber",[]),16); if(head>c.to){const logs=await cpGetLogs(c.to+1,head,0); logs.sort((a,b)=>(parseInt(a.blockNumber,16)-parseInt(b.blockNumber,16))||(parseInt(a.logIndex,16)-parseInt(b.logIndex,16))); for(const l of logs){const t=l.topics[0].toLowerCase(),d=strip0x(l.data||""); if(t===CP_T_LEAVES&&l.topics[1]){const first=Number(BigInt(l.topics[1])),off=Number(BigInt("0x"+cpWord(d,0)))/32; cpArr(d,off).forEach((x,i)=>{c.leaves[first+i]=x})} else if(t===CP_T_SPENT)c.spent.push(...cpArr(d,Number(BigInt("0x"+cpWord(d,0)))/32)); else if(t===CP_T_WRAP&&l.topics[1])c.wraps.push([l.topics[1].toLowerCase(),"0x"+cpWord(d,0)])} c.to=head} const next=Number(BigInt(await cfgRead(C,[{to:CP_POOL,data:"0x"+SEL_CPNEXT},L])||"0x0")); if(c.leaves.length<next||c.leaves.slice(0,next).some(x=>!x)){ if(cached){cpPool=null;try{delete LS[CP_CACHE]}catch{}return cpSync(true)} throw Error("The pool's history came back incomplete.")} cpPool=c;cpSyncAt=Date.now();try{LS[CP_CACHE]=JSON.stringify(c)}catch{}return c}; const cpLadder=v=>{let x=BigInt(v);if(x<=0n)return 0n;const d=x.toString().length;if(d<=2)return x;const s=10n**BigInt(d-2);return(x+s-1n)/s*s}; const cpFee=async v=>{const gp=BigInt(await cfgRead("eth_gasPrice",[])||"0x0"); let fee=cpLadder((450000n*gp+40000000000000n)*135n/100n/CP_SCALE);if(fee>v)fee=v;return fee}; const cpPins=async ch=>{ const impl=retAddr(await cfgRead(C,[{to:CP_ROUTER,data:"0x"+SEL_CPIMPL},L])); if(!impl||impl===ZERO)throw Error("The router did not answer."); const a=strip0x(await cfgRead(C,[{to:CP_POOL,data:"0x"+SEL_CPASSET+pad32(CP_ETH)},L])||""); if(BigInt("0x"+(cpWord(a,0)||"0"))!==1n||BigInt("0x"+(cpWord(a,1)||"0"))!==0n)throw Error("Native ETH is not registered in the pool."); const l2=CP_L2[ch]; const peer=retAddr(await cfgRead(C,[{to:l2.bridge||l2.inbox,data:"0x"+(l2.bridge?SEL_CPOTHER:SEL_CPBRIDGE)},L])); if(peer!==l2.peer.toLowerCase())throw Error("The "+CHAINS[ch].name+" bridge did not answer as expected — refusing to continue."); if(ch===4663){const id=await httpRead(CHAINS[4663].rpcs[0],I,[]);if(parseInt(id,16)!==4663)throw Error("The Robinhood node answered for another chain.")} return impl}; const cpRhGas=async to=>{const rh=CHAINS[4663].rpcs[0],data="0x"+SEL_CPESTRT+encAddr(CP_ROUTER)+encUint(10n**18n+1n)+encAddr(to)+encUint(1)+encAddr(to)+encAddr(to)+pad32("e0")+pad32("0"); const est=BigInt(await httpRead(rh,"eth_estimateGas",[{to:CP_NI,data}])||"0x0"); const l1=BigInt(await cfgRead("eth_gasPrice",[])||"0x0"); const sc=BigInt(await cfgRead(C,[{to:CP_L2[4663].inbox,data:"0x"+SEL_CPSUBFEE+pad32("0")+encUint(l1*2n)},L])||"0x0"); const l2=BigInt(await httpRead(rh,"eth_gasPrice",[])||"0x0"); if(!est||!sc||!l2)throw Error("Robinhood gas could not be quoted."); return{gl:est*3n/2n,sc,mf:l2*2n}}; const cpDry=async(x,r,esc)=>{const data=cpActData(r),req={to:CP_ROUTER,data,gas:toHex(CP_L2[x.ch].gas)},ov={[esc]:{balance:toHex(BigInt(x.wei))}}; const tries=[()=>cfgRead(C,[req,L,ov]),...CP_LOGS.map(u=>()=>httpRead(u,C,[req,L,ov]))];let last=""; for(const t of tries){try{if(strip0x(await t()||"")==="")return"";last=""}catch(e){const m=String(e&&e.message||"");if(/revert|execution|out of gas|BadTarget|Expired/i.test(m))throw Error("The bridge call would fail: "+m.slice(0,80));last=m}} return"Could not dry-run the bridge call ("+(last||"no node")+")."}; const cpEnsureBalance=async(esc,need)=>BigInt(await cfgRead("eth_getBalance",[esc,L])||"0x0")>=need; const cpStatus=n=>{const k=cpNoteOf(n),c=cpPool; const li=c?c.leaves.indexOf(k.leaf):-1,spent=!!c&&c.spent.includes(k.nu); const x=n.ex,dl=x?Number(x.dl):0,now=Math.floor(Date.now()/1000); if(x&&(x.atx||x.ptx||x.rtx))return{k,li,s:x.rtx?"reclaimed":"bridged"}; if(x&&spent)return{k,li,s:now>dl?"expired":"funded"}; if(x)return{k,li,s:"exiting"}; if(spent)return{k,li,s:"spent"}; if(li>=0)return{k,li,s:"ready"}; return{k,li,s:"pending"}}; const cpRelayPoll=async n=>{const job=n.ex?n.ex.job:n.job;if(!job)return; const st=await relayFetch("/confidential/status?id="+encodeURIComponent(job)); const tgt=n.ex||n;tgt.js=st.status||"unknown"; if(st.txHash)tgt.jtx=st.txHash;if(st.error)tgt.jerr=String(st.error).slice(0,120); const pr=st.proof||st.proofBytes;if(st.status==="proven"&&/^0x[0-9a-fA-F]+$/.test(st.publicValues||"")&&/^0x[0-9a-fA-F]+$/.test(pr||"")){tgt.pv=st.publicValues;tgt.pr=pr} cpSaveNotes()}; const cpSettleWrap=async n=>{const r=await relayFetch("/confidential/submit",{type:"wrap",op:n.op,memos:[n.memo],mode:"settle"}); if(!r.jobId)throw Error("The relay did not take the deposit.");n.job=r.jobId;n.js=r.status||"pending";cpSaveNotes()}; const cpDeposit=async()=>{ const wei=parseUnits(pvAmt.value||"",18); if(wei<=0n)throw Error("How much ether?"); if(wei%CP_SCALE)throw Error("The pool keeps eight decimals — round the amount."); const v=wei/CP_SCALE;if(v>=1n<<64n)throw Error("Too large for one note."); if(wei>fromBalance)throw Error("Not enough ETH."); let i=cpNotes.reduce((m,n)=>Math.max(m,n.i+1),0),k; for(let t=0;;i++){k=cpNoteOf({i,v:String(v)}); const st=await cfgRead(C,[{to:CP_POOL,data:"0x"+SEL_CPDEP+pad32(cpDepId(v,cpDepCommit(k.cx,k.cy,k.owner)))},L]); if(BigInt(st||"0x0")===0n)break;if(++t>20)throw Error("Could not find a fresh note slot.")} const cb=cpBinding(),commit=cpDepCommit(k.cx,k.cy,k.owner),dep=cpDepId(v,commit); const ctx=cpCtx("tacit-wrap-intent-v1",cb,CP_ETH,dep,[[k.cx,k.cy,k.owner]],[v]),sig=cpSigma(v,k.blinding,ctx,cpNonce(k.blinding,ctx,"wrap")); const n={i,v:String(v),at:Math.floor(Date.now()/1000),memo:cpSeal(cpPub,k,cpRandom()), op:{chainBinding:cb,asset:CP_ETH,value:String(v),cx:k.cx,cy:k.cy,owner:k.owner,sigR:sig.R,sigZ:sig.z}}; const req={from:account,to:CP_ROUTER,value:toHex(wei),data:"0x"+SEL_CPWRAP+pad32(commit)}; await rpc(C,[req,L]); n.tx=await sendTx([req]);cpNotes.push(n);cpSaveNotes();cpPaint(); await settle(n.tx,true); stat.textContent="Deposited. Asking the relay to settle it…"; awai
0x68575b07…67c7·#25,913,255·0xc7cd2144…9f1648
""; const bar=goal>0n ?`<div class="lqbar fill" style="--at:${Math.max(0,Math.min(100,pct)).toFixed(1)}%" aria-hidden="true"><b></b></div>` :""; const rel=cbReady>0n ?`<span class="cbrow"><span>${trimAmt(cbReady,18)} ETH vested and unreleased</span>` +`<button type="button" id="cbRel" class="fclink">Release</button></span>` :""; const top=(head?`<span class="cbrow">${head}</span>${bar}`:"")+rel; const act=(text,label)=>`<span class="cbact"><span>${text}</span>` +`<button type="button" id="cbGo" class="lqbtn">${label}</button></span>`; const msg=t=>`<span class="cbmsg">${t}</span>`; const paint=(html,tip)=>{ cbEl.innerHTML=top+html; cbEl.title=tip; cbEl.classList.remove("hide"); const gi=i=>document.getElementById(i); const g=gi("cbGo"),r=gi("cbRel"); if(g)g.onclick=cbAct; if(r)r.onclick=cbRelease; }; if(buy){ if(!open||left<=0n) return rqOff ?paint(msg(`Backing for ${safeSym(tok.sym)} has closed, and burn-back is switched off \u2014 it cannot be redeemed`), "This DAO has set ragequittable to false, so ragequit reverts and the treasury cannot be claimed against. Only the DAO can switch it back on.") :paint(msg(`Backing for ${safeSym(tok.sym)} has closed — it can still be burned back`),CB_TIP); if(rqOff) return paint(msg(`This cause has switched burn-back off \u2014 backing it cannot be redeemed`), "Backing pays ether for loot whose only value is the right to burn it back. This DAO has turned that right off, so there is no way out of the position."); if(!cbEth) return paint(msg(`Back ${safeSym(tok.sym)} with ether — switch the top token to ETH`),CB_TIP); let pay=0n;try{pay=parseUnits(amt.value.trim()||"0",18)}catch{} if(pay<=0n) return paint(msg(`1 ETH backs ${trimAmt(CONE*CONE/price,tok.dec)} ${safeSym(tok.sym)}`),CB_TIP); let units=pay*CONE/price; if(units>left)units=left; if(units<=0n){cbEl.classList.add("hide");return} cbAmt=units; cbTok=tok.addr; cbCost=(units*price+CONE-1n)/CONE; return paint(act(`Back with ${trimAmt(cbCost,18)} ETH for ` +`${trimAmt(units,tok.dec)} ${safeSym(tok.sym)}`,"Back"),CB_TIP); } if(rqOff) return paint(msg(`Burn-back has been switched off by this cause \u2014 ${safeSym(tok.sym)} cannot be redeemed`), "This DAO has set ragequittable to false, so ragequit reverts and the treasury " +"cannot be claimed against. Only the DAO can switch it back on."); let want=0n;try{want=parseUnits(amt.value.trim()||"0",tok.dec)}catch{} if(want<=0n)want=fromBalance; if(want<=0n){ return paint(msg(`1 ${safeSym(tok.sym)} redeems for ${trimAmt(eth*CONE/sup,18)} ETH`),CB_TIP);} cbAmt=want; const due=eth*want/sup; cbDue=due;cbShares=hit.shares;cbTok=tok.addr; if(due<=0n) return paint(msg(eth<=0n ?`This cause has drawn down its treasury — burning ${safeSym(tok.sym)} would pay nothing` :`Burning ${trimAmt(want,tok.dec)} ${safeSym(tok.sym)} would pay less than a wei`),CB_TIP); return paint(act(`Burn ${trimAmt(want,tok.dec)} ${safeSym(tok.sym)} ` +`for ${trimAmt(due,18)} ETH`,"Burn"), "This token is a claim on a cause's treasury, not a position in a market. " +"Burning it pays your share of the ETHER the cause has not drawn down yet, counted " +"pro rata across every share and unit of loot. Any other token the cause holds is " +"not paid out by this burn. It cannot be undone."); } const CB_TIP="Backing a cause is not a trade and there is no market to leave through. " +"What you get is a claim: burn it back at any time for your share of the ether the cause " +"has not drawn down yet, which is the number this line shows once you hold some."; async function cbRelease(){ if(!cbDao||cbReady<=0n)return; const b=document.getElementById("cbRel");if(!b||b.disabled)return; try{ if(!account)return connect(); b.disabled=true;b.textContent="Releasing…"; const req={from:account,to:CTAP,value:"0x0",data:"0x"+SEL_TAPCLAIM+encAddr(cbDao)}; await rpc(C,[req,L]); await waitTx(await sendTx([req]));sGot(); stat.textContent="Released to the cause."; cbSync(); }catch(e){err(e);b.disabled=false;b.textContent="Release"} } async function cbAct(){ if(!cbDao||cbAmt<=0n)return; const b=document.getElementById("cbGo");if(!b||b.disabled)return; const label=b.textContent; try{ if(!account)return connect(); b.disabled=true;b.textContent=cbBuy?"Backing…":"Burning…"; const req=cbBuy ?{from:account,to:COFF,value:"0x"+cbCost.toString(16), data:"0x"+SEL_BUY+encAddr(cbDao)+encUint(cbAmt)} :{from:account,to:cbDao,value:"0x0", data:"0x"+SEL_RAGEQUIT+encUint(96)+encUint(0)+encUint(cbAmt)+encUint(1)+encAddr(ZERO)}; if(!cbBuy&&cbShares&&cbTok){ let now2=null; try{ const [b2,r2]=await Promise.all([ rpc("eth_getBalance",[cbDao,L]), mc3Deep([{to:cbShares,data:"0x"+SEL_TSUP},{to:cbTok,data:"0x"+SEL_TSUP}])]); const e2=BigInt(b2||"0x0"),s2=BigInt(r2[0]||"0x0")+BigInt(r2[1]||"0x0"); if(s2>0n)now2=e2*cbAmt/s2; }catch{} if(now2!==null&&now2<cbDue){ const lost=cbDue-now2; if(now2<=0n)throw Error("The treasury has been drawn down since this was quoted \u2014 burning now would pay nothing."); if(lost*100n>cbDue)throw Error(`This burn now pays ${trimAmt(now2,18)} ETH, not ${trimAmt(cbDue,18)} ` +`\u2014 the treasury was drawn down since it was quoted. Check the line again before burning.`); } } await rpc(C,[req,L]); await waitTx(await sendTx([req]));sGot(); if(cbBuy&&cbTok){ const kt=TOKENS.find(x=>x.addr&&x.addr.toLowerCase()===cbTok.toLowerCase()); if(kt){kt.cause=1;if(kt.cust)keepToken(kt)} } stat.textContent=cbBuy?"Backed · burn it back any time for what is left." :"Burned · the ether is in your wallet."; refreshBalance();cbSync(); }catch(e){err(e);b.disabled=false;b.textContent=label} } const LN_MIME={"image/png":0,"image/webp":1,"image/svg+xml":2,"image/gif":3,"image/jpeg":4}; const MIME_NAME=["image/png","image/webp","image/svg+xml","image/gif","image/jpeg","image/avif"]; const LN_MAX_ART=24575; const LN_BUDGET=8192; const lnCause=()=>lnKind.value==="cause"; const lnCap=()=>lnCause()?CAU_MAX_ART:LN_MAX_ART; const lnBud=()=>lnCause()?CAU_MAX_ART:LN_BUDGET; const LN_MAX_ALLOC=2000; const LN_MIN_POOLED=2000000000000n; const LN_MIN_MCAP=1000000000000n; const lnHex=b=>{let h="";for(const x of b)h+=x.toString(16).padStart(2,"0");return h}; const lnTail=b=>{const h=lnHex(b);return encUint(b.length)+h.padEnd(Math.ceil(h.length/64)*64,"0")}; const encLaunch=(n,y,u,sup,alloc,mcap,own,art,mime)=>{ const T=new TextEncoder(); const dyn=[T.encode(n),T.encode(y),T.encode(u)]; const statics=[encUint(sup),encUint(alloc),encUint(mcap),encAddr(own)]; if(art){dyn.push(art);statics.push(encUint(mime))} const words=statics.length+(art?4:3); let off=words*32,head="",tail=""; const heads=[]; for(const b of dyn){heads.push(encUint(off));off+=lnTail(b).length/2;tail+=lnTail(b)} head=heads.slice(0,3).join("")+statics.slice(0,4).join("") +(art?heads[3]+statics[4]:""); return "0x"+(art?SEL_LAUNCHART:SEL_LAUNCH)+head+tail; }; async function lnOwner(){ const rv=rc.value.trim(); if(!rv)return account; if(/^0x[0-9a-fA-F]{40}$/.test(rv)){ if(/^0x0{40}$/i.test(rv))throw Error("That cannot be the zero address."); return rv; } if(!/\.(g?wei|eth)$/i.test(rv))throw Error("That must be an address, or a .eth / .wei name."); rcvEl.textContent="resolving\u2026"; let r="";try{r=await nameFwd(rv.toLowerCase())}catch{r=""} if(!r||/^0x0{40}$/i.test(r)){rcvEl.textContent="";throw Error("That name is not registered.")} if(!isAddr(r))throw Error("That name resolved to something that is not an address."); rcvEl.textContent=r; return r; } const CSUM="0x00000000004473e1f31C8266612e7FD5504e6f2a"; const CMSUM="0x0000000000330B8df9E3bc5E553074DA58eE9138"; const CIMPL="643a45b599d81be3f3a68f37eb3de55ff10673c1"; const CRND="0x000000000011C799980827F52d3137b4abD6E654"; const COFF="0x000000A4Ad929C9E108aD2B1D2fBeDe0C2Ae57e1"; const CTAP="0x0000000060cdD33cbE020fAE696E70E7507bF56D"; const CLOOT="0x00000000000000000000000000000000000003ef"; const SEL_DAICO="4e1e3b11",SEL_SETALLOW="da46098c",SEL_CONFIGURE="fca53be5"; const CUNITS=10000000n,CONE=10n**18n,CMAX=(1n<<256n)-1n; const CAU_MAX_ART=4096; const CAU_MAX_DAYS=3650; const cPad=h=>h.padEnd(Math.ceil(h.length/64)*64,"0"); const cTail=h=>{h=strip0x(h);return encUint(h.length/2)+cPad(h)}; const cCall=(to,data)=>encAddr(to)+encUint(0)+encUint(96)+cTail(data); const cCalls=cs=>{let off=cs.length*32,head="",tail=""; for(const c of cs){head+=encUint(off);off+=c.length/2;tail+=c} return encUint(cs.length)+head+tail}; const CLIMPL="6f1f2af76a3add953277e9f369242697c87bc6a5"; const cProxy=impl=>strip0x(keccak(hexToBytes( "602d5f8160095f39f35f5f365f5f37365f73"+impl+"5af43d5f5f3e6029573d5ffd5b3d5ff3"))); const cLoot=dao=>{const d=strip0x(dao).toLowerCase(); return "0x"+keccak(hexToBytes("ff"+d+d+"0".repeat(24)+cProxy(CLIMPL))).slice(26)}; const cPredict=(owner,salt)=>{ const inner=encUint(96)+encUint(160)+strip0x(salt) +encUint(1)+encAddr(owner)+encUint(1)+encUint(CONE); const ss=strip0x(keccak(hexToBytes(inner))); const ih=cProxy(CIMPL); return "0x"+keccak(hexToBytes("ff"+strip0x(CMSUM)+ss+ih)).slice(26); }; const encCause=(name,sym,uri,salt,owner,ben,dao,goal,rate,price,deadline,cap)=>{ const T=new TextEncoder(),Z=encUint(0); const dyn=[lnTail(T.encode(name)),lnTail(T.encode(sym)),lnTail(T.encode(uri)), encUint(1)+encAddr(owner),encUint(1)+encUint(CONE),encUint(0), cCalls([ cCall(dao,SEL_SETALLOW+encAddr(COFF)+encAddr(CLOOT)+encUint(CMAX)), cCall(COFF,SEL_CONFIGURE+encAddr(CLOOT)+encAddr(ZERO)+encUint(price) +encUint(deadline)+encUint(cap))])]; const cfg=encUint(CONE)+encUint(604800)+encUint(259200)+encUint(CONE)+Z.repeat(17); const tap=encAddr(CTAP)+Z+encUint(goal)+encAddr(ben)+encUint(rate); let off=52*32,heads=[],tail=""; for(const d of dyn){heads.push(encUint(off));off+=d.length/2;tail+=d} return "0x"+SEL_DAICO +heads[0]+heads[1]+heads[2] +encUint(1000)+encUint(1)+encAddr(CRND)+strip0x(salt) +heads[3]+heads[4]+heads[5] +cfg+Z.repeat(7)+tap+Z.repeat(8)+heads[6]+tail; }; const cauURI=(name,sym)=>{ let img=""; if(lnArtBytes){let s="";for(const x of lnArtBytes)s+=String.fromCharCode(x); img="data:"+MIME_NAME[lnArtMime]+";base64,"+btoa(s)} return "data:application/json,"+encodeURIComponent( JSON.stringify({name,symbol:sym,image:img,launchType:"cause"})); }; function cauTerms(){ const g=String(lnGoal.value||"").replace(/[, ]/g,"").trim(); const d=String(lnDays.value||"").replace(/[, ]/g,"").trim(); if(!/^\d+(\.\d+)?$/.test(g)||!/^\d+(\.\d+)?$/.test(d))return null; let goal;try{goal=parseUnits(g,18)}catch{return null} const secs=BigInt(Math.round(Number(d)*86400)); if(goal<=0n||secs<3600n||Number(d)>CAU_MAX_DAYS)return null; const price=goal/CUNITS; if(price<=0n)return null; const vest=BigInt(lnVest.value); if(vest<=secs)return{err:`Backing runs for ${trimNum(Number(secs)/86400)} days but the ether is ` +`fully released in ${trimNum(Number(vest)/86400)} days — the treasury would empty before backing closes. ` +`Shorten the raise, or release over longer.`}; const rate=goal/vest||1n; return{goal,secs,price,rate,vest,cap:(CUNITS-1n)*CONE}; } const lnKindQuote=()=>lnKind.value==="cause"?cauQuote():lnQuote(); function cauQuote(){ lnGo.textContent=account?"Launch":"Connect Wallet"; const t=cauTerms(); if(!t){lnNote.textContent="";return} if(t.err){lnNote.textContent=t.err;lnNote.style.color="var(--w)";return} const per=(+formatUnits(t.price,18)); const tenthDays=t.rate>0n ?Number((t.goal/10n)/t.rate)/86400 :0; lnNote.textContent=`1 ETH backs ${trimNum(1/per)} units` +` · ${trimNum(+formatUnits(t.rate*86400n,18))} ETH a day at full funding, over ` +lnVest.options[lnVest.selectedIndex].textContent +(tenthDays>0?` · at a tenth of the goal it empties in ${trimNum(Math.round(tenthDays*10)/10)} days`:"") +` · backers burn back for the rest` +(lnArtBytes?` · logo ${(lnArtBytes.length/1024).toFixed(1)} KB` +(lnArtBytes.length>CAU_MAX_ART?` — over the 4 KB a cause allows, pick something simpler`:""):""); lnNote.style.color=lnArtBytes&&lnArtBytes.length>CAU_MAX_ART?"var(--w)":""; } function cauKind(){ const on=lnKind.value==="cause"; for(const el of [lnMktL,lnSupplyL,lnMcapL,lnAllocL])el.classList.toggle("hide",on); for(const el of [lnGoalL,lnDaysL,lnVestL])el.classList.toggle("hide",!on); lnTitle.textContent=on?"Launch a cause":"Launch a coin"; lnSub.textContent=on?"backers burn back for whatever you have not drawn" :"0.4% of each trade goes to the creator"; lnName.placeholder=on?"Feed Ducks":"Zero Cat"; lnSym.placeholder=on?"DUCK":"ZCAT"; rc.placeholder=on?"Beneficiary — defaults to you":"Creator — defaults to you"; rc.title=on?"Who the ether streams to. Leave blank and it is you. Worth setting to a multisig if the cause has one." :"Who the token belongs to: receives the creator allocation, collects the creator share of trading fees for the life of the token, and may edit its name, symbol and logo. Leave blank and it is you. Worth setting to a multisig if the project has one."; stat.textContent=""; on?cauQuote():lnQuote(); } async function 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.`); if(lnArtBytes&&lnArtBytes.length>CAU_MAX_ART) throw Error(`A cause's logo is stored in the DAO itself, so it is capped at 4 KB. ` +`That one is ${(lnArtBytes.length/1024).toFixed(1)} KB — pick something simpler.`); const t=cauTerms(); if(!t)throw Error("A goal in ether, and how long it runs for — at least an hour, at most ten years."); if(t.err)throw Error(t.err); const ben=await lnOwner(); const owner=account; const salt=wnSecret(); const dao=cPredict(owner,salt); const deadline=BigInt(Math.floor(Date.now()/1000))+t.secs; stat.textContent="Launching…"; const req={from:account,to:CSUM, value:"0x"+t.price.toString(16), data:encCause(name,sym,cauURI(name,sym),salt,owner,ben,dao,t.goal,t.rate,t.price,deadline,t.cap)}; await rpc(C,[req,L]); await waitTx(await sendTx([req]));sGot(); lnArtBytes=null;lnArtNote.textContent="none";lnArtNote.classList.remove("set"); lnPrev.classList.remove("set");lnArt.value=""; let loot=cLoot(dao); try{ const said=cbWord(await rpc(C,[{to:dao,data:"0x"+SEL_LOOTOF},L])); if(isAddr(said||"")&&said.toLowerCase()!==loot.toLowerCase())loot=said; }catch{} try{const ix=await addCustomToken(loot);TOKENS[ix].cause=1;rebuild()}catch{} stat.innerHTML=`${safeSym(sym)} is raising at ` +`<a href="${chainCfg().explorer}/token/${loot}" target="_blank" rel="noreferrer">${short(loot)}</a>` +` · ${trimNum(+formatUnits(t.goal,18))} ETH over ${trimNum(Number(t.secs)/86400)} days` +` · backers can burn back for whatever you have not drawn`; } let lnMode=false,lnArtBytes=null,lnArtMime=0; function lnSet(on){ if(on&&PLAUNCH===ZERO)return; if(on)cbDisarm(); ++seq;quoting=false; lnMode=on; if(on&&lqMode)lqSet(false); if(on&&wnMode)wnSet(false); if(on&&pvMode)pvSet(false); ln.setAttribute("aria-pressed",on?"true":"false"); ln.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); document.querySelector(".card").classList.toggle("lnon",on); for(const el of [lnPanel,lnNote,lnGo])el.classList.toggle("hide",!on); rc.value="";rc.classList.remove("bad");rcvEl.textContent=""; rc.title=on?"Who the token belongs to: receives the creator allocation, collects the creator share of trading fees for the life of the token, and may edit its name, symbol and logo. Leave blank and it is you. Worth setting to a multisig if the project has one.":""; rc.placeholder=on?"Creator — defaults to you" :"Recipient (optional) — 0x or .eth/.(g)wei"; stat.textContent="";lnNote.textContent=""; if(on)cauKind(); fcSync(); } function lnQuote(){ lnGo.textContent=account?"Launch":"Connect Wallet"; const ns=v=>v.replace(/[, ]/g,""); const sup=ns(lnSupply.value),mc=+ns(lnMcap.value),al=+ns(lnAlloc.value); lnNote.textContent=""; if(!(Number(sup)>0)||!(mc>0))return; const alBps=al>0?Math.round(al*100):0; const pooled=Number(sup)*(1-alBps/10000); if(!(pooled>0))return; const sym=lnSym.value.trim().toUpperCase(); const poolFrac=99/(mc+.99); const firstEth=poolFrac*pooled/Number(sup); const firstTokens=poolFrac*pooled/100; lnNote.textContent=`1 ETH buys ${firstEth>=10?Math.round(firstEth):firstEth.toFixed(firstEth<1?2:1)}% of all ${sym||"tokens"}` +` \u00b7 opens at 1 ETH \u2248 ${trimNum(firstTokens)} ${sym||"tokens"}` +(al>0&&alBps===0?` \u00b7 that keep is below one basis point \u2014 the smallest is 0.01%`:"") +(alBps>0?` \u00b7 creator keeps ${trimNum(Number(sup)*alBps/10000)} (${alBps/100}%)` +` \u00b7 fully diluted ${trimNum(mc*Number(sup)/pooled)} ETH`:"") +(alBps>=1000?` \u00b7 that much can sell for most of the ether early buyers put in`:"") +(lnArtBytes?` \u00b7 logo ${(lnArtBytes.length/1024).toFixed(1)} KB`:""); lnNote.style.color=poolFrac>=10?"var(--w)":""; } const trimNum=n=>n>=1000?Math.round(n).toLocaleString("en-US") :n>=1?n.toLocaleString("en-US",{maximumFractionDigits:4}):String(Number(n.toPrecision(4))); const blobText=b=>b.text?b.text():new Promise((res,rej)=>{ const r=new FileReader();r.onload=()=>res(String(r.result));r.onerror=()=>rej(r.error);r.readAsText(b)}); const blobBytes=async b=>b.arrayBuffer?new Uint8Array(await b.arrayBuffer()):await new Promise((res,rej)=>{ const r=new FileReader();r.onload=()=>res(new Uint8Array(r.result));r.onerror=()=>rej(r.error);r.readAsArrayBuffer(b)}); async function lnPrepare(file){ if(/svg/.test(file.type)){ const b=new TextEncoder().encode((await blobText(file)) .replace(/<\?xml[^>]*\?>/g,"").replace(/<!--[\s\S]*?-->/g,"").replace(/<!DOCTYPE[^>]*>/gi,"") .replace(/<script[\s\S]*?<\/script\s*>/gi,"").replace(/<script[\s\S]*$/gi,"") .replace(/<foreignObject[\s\S]*?<\/foreignObject\s*>/gi,"").replace(/<foreignObject[\s\S]*$/gi,"") .replace(/\son[a-z-]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]*)/gi,"") .replace(/\s(?:[a-z]+:)?href\s*=\s*("\s*javascript:[^"]*"|'\s*javascript:[^']*'|javascript:[^\s>]*)/gi,"") .replace(/>\s+</g,"><").trim()); if(b.length>lnCap())throw Error(`That SVG is ${(b.length/1024).toFixed(1)} KB and the on-chain limit is ${Math.round(lnCap()/1024)} KB.`); return{bytes:b,mime:LN_MIME["image/svg+xml"]}; } const bmp=await createImageBitmap(file); let smallest=null; for(const side of [512,384,256,192,128,96,64]){ for(const [type,q] of [["image/webp",1],["image/png",undefined],["image/webp",0.85],["image/webp",0.6]]){ const sc=Math.min(1,side/Math.max(bmp.width,bmp.height)); const cv=document.createElement("canvas"); cv.width=Math.max(1,Math.round(bmp.width*sc));cv.height=Math.max(1,Math.round(bmp.height*sc)); const cx=cv.getContext("2d");cx.imageSmoothingQuality="high"; cx.drawImage(bmp,0,0,cv.width,cv.height); const blob=await new Promise(r=>cv.toBlob(r,type,q)); if(!blob||blob.type!==type)continue; const bytes=await blobBytes(blob); if(!smallest||bytes.length<smallest.bytes.length)smallest={bytes,mime:LN_MIME[type]}; if(bytes.length<=lnBud())return{bytes,mime:LN_MIME[type]}; } } if(smallest&&smallest.bytes.length<=lnCap())return smallest; throw Error("That image will not compress small enough. Flat colours and simple shapes do far better than photographs."); } lnPick.onclick=()=>lnArt.click(); lnArt.onchange=async()=>{ const f=lnArt.files&&lnArt.files[0]; if(!f)return; lnArtNote.textContent="reading…"; try{ const r=await lnPrepare(f); lnArtBytes=r.bytes;lnArtMime=r.mime; lnArtNote.textContent=`${(r.bytes.length/1024).toFixed(1)} KB`; lnArtNote.classList.add("set"); let b64="";for(const x of r.bytes)b64+=String.fromCharCode(x); lnPrev.src="data:"+MIME_NAME[r.mime]+";base64,"+btoa(b64); lnPrev.classList.add("set"); }catch(e){ lnArtBytes=null;lnArtNote.classList.remove("set");lnPrev.classList.remove("set"); lnArtNote.textContent="none";stat.textContent=e.message||String(e); } lnKindQuote(); }; const lnGroup=el=>{ if(!/^[\d, ]*(\.[\d ]*)?$/.test(el.value))return; const dot=el.value.indexOf("."); const intRaw=(dot<0?el.value:el.value.slice(0,dot)).replace(/\D/g,""); const frac=dot<0?"":"."+el.value.slice(dot+1).replace(/\D/g,""); const digitsBefore=el.value.slice(0,el.selectionStart??el.value.length).replace(/\D/g,"").length; const out=intRaw.replace(/\B(?=(\d{3})+(?!\d))/g,",")+frac; if(out===el.value)return; el.value=out; let seen=0,pos=digitsBefore?out.length:0; if(digitsBefore)for(let i=0;i<out.length;i++){ if(/\d/.test(out[i])&&++seen===digitsBefore){pos=i+1;break} } try{el.setSelectionRange(pos,pos)}catch{} }; lnSupply.oninput=()=>{lnGroup(lnSupply);lnQuote()}; for(const el of [lnName,lnSym,lnMcap,lnAlloc])el.oninput=lnKindQuote; lnKind.onchange=cauKind; for(const el of [lnGoal,lnDays])el.oninput=cauQuote; lnVest.onchange=cauQuote; ln.onclick=()=>{sBlip();lnSet(!lnMode)}; let wnMode=false,wnQ=null,wnSeq=0,wnRollSeq=0,wnTimer=0,wnRound=0n,wnStampTry=0,wnRollAt=0; const wnOk=l=>/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(l)&&new TextEncoder().encode(l).length<256; const wnFree=()=>wnTld.value==="id"; const wnOffer=(t,id)=>{wnEnter.classList.remove("hide");wnEnter.textContent=t; wnEnter.dataset.id=id||"";wnEnter.classList.toggle("mut",!id&&!!account)}; const wnFull=l=>l+(wnFree()?".id.wei":".wei"); const wnSecret=()=>{const a=new Uint8Array(32);crypto.getRandomValues(a);let h="";for(const x of a)h+=x.toString(16).padStart(2,"0");return"0x"+h}; const wnHash=(l,o,sec)=>keccak(hexToBytes(pad32("60")+encAddr(o)+pad32(sec)+strTail(l))); const wnKey=()=>WN_KEY+":"+(account||"").toLowerCase(); const wnStamp=async()=>{const p=wnPend(); if(!p||p.at||!account)return; try{const r=await rpc(C,[{to:WNS,data:"0x"+SEL_WCOMMITS+pad32(wnHash(p.label,account,p.secret))},L]); const t=Number(dec1(r||"0x0",0)); if(t)wnSave({...p,at:t})}catch{}}; const wnPend=()=>{try{const p=JSON.parse(LS[wnKey()]||"null");return p&&p.label&&p.secret&&Number.isFinite(p.at)?p:null}catch{return null}}; const wnSave=p=>{try{if(p)LS[wnKey()]=JSON.stringify(p);else delete LS[wnKey()]}catch{}}; const wnId=n=>namehash(/\.wei$/.test(n)?n:n+".wei"); const wnCostCall=l=>[{to:WNS,data:"0x"+SEL_WFEE+encUint(new TextEncoder().encode(l).length)}, {to:WNS,data:"0x"+SEL_WPREM+pad32(wnId(l))}]; const wnCost=async l=>{const [f,pr]=await mc3(wnCostCall(l));return dec1(f,0)+dec1(pr,0)}; const wnSync=()=>{ if(!wnMode)return; const p=wnPend(); if(p&&!p.at){ if(!p.since){wnSave({...p,since:Math.floor(Date.now()/1000)});wnGo.disabled=true;wnGo.textContent="Confirming\u2026";return} if(Math.floor(Date.now()/1000)-p.since>600){wnGo.disabled=false;wnGo.textContent="Commit again";return} wnGo.disabled=true;wnGo.textContent="Confirming\u2026";return} if(p){ const left=WN_MIN-(Math.floor(Date.now()/1000)-p.at); wnGo.disabled=left>0; wnGo.textContent=left>0?`Ready in ${left}s`:`Register ${p.label}.wei`; if(Math.floor(Date.now()/1000)-p.at>=WN_MAX){wnGo.disabled=false;wnGo.textContent="Commit again"} return} wnGo.disabled=!wnQ; wnGo.textContent=wnQ?(wnQ.free?`Claim ${wnQ.label}.id.wei`:"Commit"):"Register"}; const wnTicketOffer=async(l,seq,round)=>{ try{const[t]=await mc3([{to:WROLL,data:"0x"+SEL_RTICKET+encUint(round)+pad32(wnId(l))}]); if(seq!==wnSeq)return; const inAlready=!!dec1(t,0); wnOffer(inAlready?`${l}.wei is already in`:`Enter ${l}.wei in the roll`,inAlready?"":pad32(wnId(l))); }catch{if(seq===wnSeq)wnOffer(`Could not read the roll for ${l}.wei`)}}; const wnQuote=async()=>{ const my=++wnSeq; wnQ=null; wnOffer(account?"Enter a name you own":"Connect to enter"); wnSub.textContent=wnFree()?"free":"from 0.0005 ETH a year"; const l=wnName.value.trim().toLowerCase(); const p=wnPend(); if(p){wnNote.textContent=p.at?`${p.label}.wei is held for you — register it within 24 hours or the commitment lapses.` :`Waiting for the commitment on ${p.label}.wei to confirm.`;wnSync();return} if(!
0x68575b07…67c7·#25,913,254·0x44d4347d…5bdefa
const minLp=lessSlip(pv.lp); const zap="0x"+SEL_ZAPIN+encAddr(pool)+encAddr(tok.addr)+encUint(amountIn) +encUint(pv.portion)+encUint(minLp)+encAddr(to)+encAddr(account); const leg=encSnwap(tok.addr,amountIn,to,pool,minLp,PROUTE,zap); let data,value; if(tok.addr===ZERO){data=leg;value=toHex(amountIn)} else{ stat.textContent="Approving…"; await ensureAllowance(tok.addr,ZROUTER,amountIn); data=encCalls([ encSnwap(ZERO,0n,account,ZERO,0n,PROUTE, "0x"+SEL_PCHECKPOINT+encAddr(tok.addr)+strip0x(keccak(hexToBytes(zap)))+encAddr(account)), leg]); value="0x0"; } stat.textContent="Zapping…"; const req={from:account,to:ZROUTER,value,data}; await rpc(C,[req,L]); await settle(await sendTx([req])); await loadLq();refreshBalance(); }catch(e){err(e)} finally{--busy} } let lqPvT,lqPvSeq=0; async function lqPreview(box){ const pool=box.closest(".lqrow")?.dataset.pool||""; const row=lqRows.find(x=>x.pool.toLowerCase()===pool.toLowerCase()); const p=chPair(); const out=box.querySelector(".lqpv"),go=box.querySelector('[data-act="ac"]'); if(!row||!p){out.textContent="";go.disabled=true;return} const [t0,t1]=p,ins=box.querySelectorAll(".lqin"); let a0,a1; try{ a0=ins[0].value.trim()?parseUnits(ins[0].value.trim(),t0.dec):0n; a1=ins[1].value.trim()?parseUnits(ins[1].value.trim(),t1.dec):0n; }catch{out.textContent="Invalid amount.";go.disabled=true;return} if(isZap(box)){ const z=zapSide(box,t0,t1); if(z.err){out.textContent=z.err;go.disabled=true;return} if(!z.amt){out.textContent=`Enter an amount of ${t0.sym} or ${t1.sym}.`;go.disabled=true;return} const mz=++lqPvSeq; out.textContent="Checking…";go.disabled=true; const zp=await previewZap(row.pool,z.tok.addr,z.amt).catch(()=>null); if(mz!==lqPvSeq)return; if(!zp||zp.lp===0n){out.textContent="The pool would refuse this deposit.";go.disabled=true;return} out.textContent=`swaps ${trimAmt(zp.portion,z.tok.dec)} of ${trimAmt(z.amt,z.tok.dec)} ${z.tok.sym}` +` for ${(z.tok===t0?t1:t0).sym}, then deposits both · mints ~${trimAmt(zp.lp,18)} LP`; go.disabled=false;return; } if(!a0||!a1){ out.textContent="Enter both amounts.";go.disabled=true;return} const my=++lqPvSeq; out.textContent="Checking…";go.disabled=true; const pv=await previewAdd(row.pool,a0,a1).catch(()=>null); if(my!==lqPvSeq)return; if(!pv){out.textContent="The pool would refuse this deposit.";go.disabled=true;return} const ref=(pv.ref0>0n?` · refunds ${trimAmt(pv.ref0,t0.dec)} ${t0.sym}`:"") +(pv.ref1>0n?` · refunds ${trimAmt(pv.ref1,t1.dec)} ${t1.sym}`:""); out.textContent=`deposits ${trimAmt(pv.used0,t0.dec)} ${t0.sym} + ${trimAmt(pv.used1,t1.dec)} ${t1.sym}${ref}`; go.disabled=false; } const isZap=box=>!!box.querySelector(".lqz")?.checked; function zapSide(box,t0,t1){ const ins=box.querySelectorAll(".lqin"); const v0=ins[0].value.trim(),v1=ins[1].value.trim(); if(v0&&v1)return{err:`One side only — clear the ${t1.sym} field, or turn one-sided off.`}; if(!v0&&!v1)return{err:`Enter an amount of ${t0.sym} or ${t1.sym}.`}; const tok=v0?t0:t1; try{return{tok,amt:parseUnits(v0||v1,tok.dec)}}catch{return{err:"Invalid amount."}} } function lqMirror(box,src){ if(isZap(box))return; const pool=box.closest(".lqrow")?.dataset.pool||""; const row=lqRows.find(x=>x.pool.toLowerCase()===pool.toLowerCase()); const p=chPair(); if(!row||!p||!row.r0||!row.r1)return; const [t0,t1]=p,ins=box.querySelectorAll(".lqin"); const i=src===ins[0]?0:src===ins[1]?1:-1; if(i<0)return; const other=ins[1-i],v=src.value.trim(); box.dataset.mir=String(1-i); if(!v){other.value="";return} try{ const raw=parseUnits(v,i===0?t0.dec:t1.dec); other.value=formatUnits(i===0?raw*row.r1/row.r0:raw*row.r0/row.r1,i===0?t1.dec:t0.dec); }catch{} } lqList.addEventListener("input",e=>{ const box=e.target.closest(".lqadd"); if(box&&e.target.classList.contains("lqin")){ box.querySelector('[data-act="ac"]').disabled=true; lqMirror(box,e.target);clearTimeout(lqPvT);lqPvT=setTimeout(()=>lqPreview(box),300);return} const nb=e.target.closest(".lqnew"); if(nb){nb.querySelector("#lqCreate").disabled=true; clearTimeout(lqPvT);lqPvT=setTimeout(lqNewPreview,300)} }); lqList.addEventListener("change",e=>{ if(e.target.id==="lqFee"||e.target.id==="lqRange")return lqNewPreview(); if(!e.target.classList.contains("lqz"))return; const box=e.target.closest(".lqadd"),ins=box.querySelectorAll(".lqin"); const on=e.target.checked; const mir=box.dataset.mir; if(on&&mir&&ins[0].value.trim()&&ins[1].value.trim())ins[+mir].value=""; if(!on)lqMirror(box,ins[0].value.trim()||!ins[1].value.trim()?ins[0]:ins[1]); box.querySelector('[data-act="ac"]').textContent=on?"Zap in":"Add liquidity"; lqPreview(box); }); let lqNewSeq=0; let lqPvT2; const lqPreviewSoon=()=>{const g=lqList.querySelector("#lqCreate");if(g)g.disabled=true; clearTimeout(lqPvT2);lqPvT2=setTimeout(lqNewPreview,320)}; async function lqNewPreview(){ const box=lqList.querySelector(".lqnew"); if(!box)return; const out=box.querySelector("#lqPv"),go=box.querySelector("#lqCreate"); const bandOut=box.querySelector("#lqRangeOut"); const my=++lqNewSeq; go.disabled=true; const pair=chPair(); if(!pair){out.textContent="";return} const [t0,t1]=pair; const range=box.querySelector("#lqRange").value; box.querySelector("#lqCustom").classList.toggle("hide",range!=="custom"); const payTok=TOKENS[fromSel.value]; const payIs0=!!payTok&&payTok.addr.toLowerCase()===t0.addr.toLowerCase(); const el0=payIs0?amt:outAmt,el1=payIs0?outAmt:amt; let a0=0n,a1=0n; try{ const v0=el0.value.trim(),v1=el1.value.trim(); a0=v0?parseUnits(v0,t0.dec):0n;a1=v1?parseUnits(v1,t1.dec):0n; }catch{out.textContent="Check the amounts.";bandOut.textContent="";return} const oneSided=(a0&&!a1)?0:(a1&&!a0)?1:-1; box.querySelector("#lqPxRow").classList.toggle("hide",oneSided<0); if(!a0&&!a1){ bandOut.textContent=""; out.textContent=`Enter how much ${t0.sym} and ${t1.sym} to deposit — the ratio sets the opening price.`; return} const pxIn=box.querySelector("#lqPx").value.trim(); const px=oneSided<0 ?(Number(a1)/10**t1.dec)/(Number(a0)/10**t0.dec) :(pxIn?+pxIn:NaN); if(!(px>0)){ bandOut.textContent=""; const only=oneSided===0?t0.sym:t1.sym; out.textContent=`${/^[AEIOU]/i.test(only)?"An":"A"} ${only}-only band has no ratio to` +` read a price from — name one.`; return} const fee=BigInt(box.querySelector("#lqFee").value); let lo,hi; if(range==="custom"){ const n=id=>{const v=box.querySelector("#"+id).value.trim();return v?+v:NaN}; lo=n("lqLo");hi=n("lqHi"); if(!(lo>0)||!(hi>0)){bandOut.textContent="";out.textContent="Name both prices.";return} if(lo>=hi){out.textContent="The low price must be below the high one.";bandOut.textContent="";return} if(oneSided<0&&(px<lo||px>hi)){ out.textContent=`These amounts imply ${fmtPx(px)}, which is outside that band.`;return} }else{ let mult=+range; if(range==="full"){ const found=lqFullWidth(t0.dec,t1.dec,a0,a1,px,fee); if(!found){ bandOut.textContent=""; out.textContent="Even the narrowest range needs more than this deposit.";return} mult=found; } [lo,hi]=lqBandFor(px,mult,a0,a1); } const sl=pxToSqrt(lo,t0.dec,t1.dec),sh=pxToSqrt(hi,t0.dec,t1.dec); if(!sl||!sh||sh<=sl){out.textContent="Check the prices.";bandOut.textContent="";return} const sp=oneSided===0?sl:oneSided===1?sh:lqSeedPrice(sl,sh,a0,a1); if(!sp){out.textContent="Those amounts cannot open a band that wide.";return} const openAt=bandPrice(sp,t0.dec,t1.dec); bandOut.innerHTML=lqBar(lo,hi,openAt)+`<span></span>`; bandOut.querySelector("span").textContent= `${fmtPx(lo)} – ${fmtPx(hi)} · opens at ${fmtPx(openAt)} ${t1.sym} per ${t0.sym}` +(oneSided===0?` · ${t0.sym} only`:oneSided===1?` · ${t1.sym} only`:""); box.querySelector("#lqOwnNote").textContent= `Unowned, like any AMM pool: anyone may add, and no creator fee can ever be` +` charged. Prices are ${t1.sym} per ${t0.sym}.`; lqGasNote(box); const market=lqMarket(t0.addr,t1.addr,sl,sh,fee,ZERO); box.dataset.sl=sl;box.dataset.sh=sh;box.dataset.sp=sp;box.dataset.fee=fee; box.dataset.a0=a0;box.dataset.a1=a1;box.dataset.lp=0; const q=lqSeedQuote(sl,sh,sp,a0,a1); if(!q.ok){out.textContent="This band cannot open at that price with those amounts.";return} if(!lqSeedAdmits(sl,sh,sp,fee,q)){ out.textContent="A band this size could not be traded once opened —" +" widen the range, or deposit more.";return} out.textContent=`Seeds ${trimAmt(q.used0,t0.dec)} ${t0.sym} + ${trimAmt(q.used1,t1.dec)} ${t1.sym}` +((q.used0<a0||q.used1<a1)?" — the rest is returned":""); box.dataset.lp=q.lp; try{ const [addr]=await mc3([{to:PFACTORY,data:"0x"+SEL_POOLFOR+market}]); if(my!==lqNewSeq)return; const pool="0x"+strip0x(addr).slice(24); const [sup]=await mc3([{to:pool,data:"0x"+SEL_TOTALSUPPLY}]).catch(()=>[null]); if(my!==lqNewSeq)return; if(sup&&strip0x(sup).length>=64&&dec1(sup,0)>0n){ out.textContent="That exact band already exists — add to it from the list above."; return} go.disabled=!account; }catch{ if(my!==lqNewSeq)return; out.textContent="Could not reach the factory."; } } const LQ_CREATE_GAS=4700000n; async function lqGasNote(box){ const el=box.querySelector("#lqGas"); if(!el)return; el.textContent=`Creating deploys the pool contract — about ` +`${(Number(LQ_CREATE_GAS)/1e6).toFixed(1)}M gas.`; try{ const wei=BigInt(await rpc("eth_gasPrice",[]))*LQ_CREATE_GAS; el.textContent=`Creating deploys the pool contract — about ` +`${(Number(LQ_CREATE_GAS)/1e6).toFixed(1)}M gas, roughly ` +`${trimAmt(wei,18)} ETH at the moment. Adding to a band that already ` +`exists costs a fraction of it, so check the fee your wallet offers.`; }catch{} } async function lqCreate(){ const box=lqList.querySelector(".lqnew"); if(!box||busy||!account)return; const pair=chPair(); if(!pair)return; const [t0,t1]=pair; const go=box.querySelector("#lqCreate"),out=box.querySelector("#lqPv"); const sl=BigInt(box.dataset.sl||0),sh=BigInt(box.dataset.sh||0),sp=BigInt(box.dataset.sp||0); const a0=BigInt(box.dataset.a0||0),a1=BigInt(box.dataset.a1||0),fee=BigInt(box.dataset.fee||0); if(!sl||!sh||!sp){out.textContent="Preview the band first.";return} const to=await lqTo(); if(!to)return; ++busy;go.disabled=true; try{ const bps=BigInt(Math.round(Math.min(.5,Math.max(.01,+slip.value||.5))*100)); const lp=BigInt(box.dataset.lp||0); if(lp<=0n){out.textContent="Preview the band first.";return} const minLP=lp*(10000n-bps)/10000n; const market=lqMarket(t0.addr,t1.addr,sl,sh,fee,ZERO); const native=t0.addr===ZERO; if(!native&&a0)await lqApprove(t0.addr,a0); if(a1)await lqApprove(t1.addr,a1); const data="0x"+SEL_CREATE_SEED+market+encUint(sp)+encUint(a0)+encUint(a1) +encUint(minLP)+encAddr(to); const tx={from:account,to:PFACTORY,data,value:native?toHex(a0):"0x0"}; stat.textContent="Creating the band..."; await rpc(C,[tx,L]);await checkWallet();await settle(await sendTx([tx])); stat.textContent="Band created."; loadLq();refreshBalance(); }catch(e){err(e);out.textContent="";} finally{--busy;go.disabled=false} } async function lqApprove(token,need){ const al=BigInt(await rpc(C,[{to:token,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(PFACTORY)},L])); if(al>=need)return; if(al)await settle(await sendTx([{from:account,to:token, data:"0x"+SEL_APPROVE+encAddr(PFACTORY)+encUint(0n),value:"0x0"}])); await settle(await sendTx([{from:account,to:token, data:"0x"+SEL_APPROVE+encAddr(PFACTORY)+encUint(need),value:"0x0"}])); } lqList.onclick=async ev=>{ if(ev.target.id==="lqCreate"){await lqCreate();return} if(ev.target.id==="lqNewTog"){ const pr=chPair();if(!pr)return; ev.target.outerHTML=lqNewForm(pr[0],pr[1]); lqSyncAmounts(); lqNewPreview(); lqList.querySelector(".lqnew")?.scrollIntoView?.({block:"nearest"}); return} const b=ev.target.closest("[data-act]"); if(!b||busy)return; const p=chPair(); if(!p)return; const [t0,t1]=p,pool=b.dataset.pool; const row=lqRows.find(x=>x.pool.toLowerCase()===pool.toLowerCase()); if(!row)return; const act=b.dataset.act; if(act!=="a"&&!account)return void(stat.textContent="Connect a wallet first."); if(act==="a"){ const box=b.closest(".lqrow").querySelector(".lqadd"); box.classList.toggle("hide"); b.textContent=box.classList.contains("hide")?"Add":"Cancel"; if(!box.classList.contains("hide"))box.querySelector(".lqin").focus(); return; } if(act==="w"){ if(row.shares===0n)return; ++busy;b.disabled=true; try{await doRemove(pool,row.shares,t0,t1)}finally{--busy;b.disabled=false} return; } if(act==="ac"){ const box=b.closest(".lqadd"); const ins=box.querySelectorAll(".lqin"); if(isZap(box)){ const z=zapSide(box,t0,t1); if(z.err){stat.textContent=z.err;return} if(!z.amt)return; ++busy;b.disabled=true; try{await doZap(pool,z.tok,z.amt)}finally{--busy;b.disabled=false} return; } let a0,a1; try{ a0=parseUnits(ins[0].value.trim(),t0.dec);a1=parseUnits(ins[1].value.trim(),t1.dec); }catch{stat.textContent="Invalid amount";return} if(!a0||!a1)return; ++busy;b.disabled=true; try{await doAdd(pool,a0,a1,t0,t1)}finally{--busy;b.disabled=false} } }; footDoc.setAttribute("aria-expanded","false"); footDoc.onclick=e=>{ e.preventDefault(); const on=docPanel.classList.contains("hide"); footDoc.setAttribute("aria-expanded",on?"true":"false"); docPanel.classList.toggle("hide",!on); footDoc.textContent=on?"hide":"how it works"; if(on)docPanel.scrollIntoView({behavior:"smooth",block:"nearest"}); }; lq.onclick=()=>{sBlip();lqSet(!lqMode)}; let PLAUNCH=MB.la; const SEL_LAUNCH="6a648dc6"; const SEL_LAUNCHART="72cf7bd8"; const SEL_CREATOROF="dea5c2e0"; const SEL_FLOORPRICE="2aad9987"; const ETHUSD_FEED="0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419"; const ETHUSD_STALE=3600; let ethUsd=0; async function loadEthUsd(){ try{ const c=JSON.parse(sessionStorage.getItem("ethusd")||"null"); if(c&&Date.now()-c.t<300000){ethUsd=c.v;return} }catch{} try{ const r=await cfgRead(C,[{to:ETHUSD_FEED,data:"0xfeaf968c"},L]); const h=strip0x(r||""); if(h.length<5*64)return; const answer=BigInt("0x"+h.slice(64,128)),updated=Number(BigInt("0x"+h.slice(192,256))); if(answer<=0n)return; if(Math.floor(Date.now()/1000)-updated>ETHUSD_STALE)return; ethUsd=Number(answer)/1e8; try{sessionStorage.setItem("ethusd",JSON.stringify({t:Date.now(),v:ethUsd}))}catch{} }catch{} } const fmtUsd=v=>{ if(!(v>0))return ""; if(v>=1000)return "$"+Math.round(v).toLocaleString("en-US"); if(v>=1)return "$"+v.toFixed(2); if(v>=0.01)return "$"+v.toFixed(4); return "$"+fmtPx(v); }; const BETH="0x2cb662Ec360C34a45d7cA0126BCd53C9a1fd48F9"; const TITHE_RECORD="0x5E58BA0e06ED0F5558f83bE732a4b899a674053E"; const BURN_TTL=600000; async function burnTotal(){ try{ const c=JSON.parse(sessionStorage.getItem("burn")||"null"); if(c&&Date.now()-c.t<BURN_TTL)return void showBurn(BigInt(c.v)); }catch{} try{ const r=await cfgRead(C,[{to:BETH,data:"0x70a08231"+encAddr(TITHE_RECORD)},L]); const v=BigInt(r||"0x0"); if(v<=0n)return; try{sessionStorage.setItem("burn",JSON.stringify({t:Date.now(),v:v.toString()}))}catch{} showBurn(v); }catch{} } function showBurn(v){ if(v<=0n)return; footBurn.innerHTML=` \u00b7 <a href="https://etherscan.io/token/${BETH}?a=${TITHE_RECORD}" ` +`target="_blank" rel="noopener" title="A tenth of every fee a launched coin earns is burned to Ethereum itself. ` +`This is the running total, held as the BETH receipt minted to the DAO - the ether it stands for no longer exists.">` +`${(+formatUnits(v,18)).toFixed(4)} ETH burned</a>`; } async function quoteFloor(tok,amount,block){ if(!tok||tok===ZERO||amount<=0n)return 0n; const key=tok.toLowerCase(); const at=block||L; if(!fcFloorPx[key]||fcFloorPx[key].blk!==at){ try{ const r=await rpc(C,[{to:PLAUNCH,data:"0x"+SEL_FLOORPRICE+encAddr(tok)},at]); fcFloorPx[key]={v:BigInt(r||"0x0"),blk:at}; }catch{delete fcFloorPx[key];return 0n} } const px=fcFloorPx[key].v; return px>0n?px*amount/(10n**18n):0n; } const SEL_POOLOF="988b1fa7",SEL_OWED0="56ea33ee",SEL_OWED1="3ad57b72",SEL_COLLECT="a480ca79"; const SEL_COLLECTMANY="c296057e"; let fcMineToks=[],fcSeq=0,fcMineSeq=0; async function fcMine(){ const my=++fcMineSeq; fcMineToks=[]; if(!account||!LAUNCHED.length||PLAUNCH===ZERO)return fcSync(); try{ const reqs=[]; for(const x of LAUNCHED)reqs.push( {to:PLAUNCH,data:"0x"+SEL_CREATOROF+encAddr(x.addr)}, {to:x.pool,data:"0x"+SEL_OWED0}); const res=await mc3Deep(reqs); if(my!==fcMineSeq)return; const acct=account.toLowerCase(); let total=0n;const mine=[]; for(let i=0;i<LAUNCHED.length;i++){ const c=res[i*2],o=res[i*2+1]; if(!c||!o)continue; if(("0x"+strip0x(c).slice(24)).toLowerCase()!==acct)continue; let owed=0n;try{owed=BigInt(o)}catch{continue} if(owed<FC_DUST)continue; total+=owed;mine.push(LAUNCHED[i]); } if(!mine.length)return fcSync(); fcMineToks=mine.map(x=>x.addr); const cut=(+formatUnits(total*8n/10n,18)).toFixed(4); fcEl.innerHTML=(mine.length===1?`${safeSym(mine[0].sym)} \u00b7 `:`${mine.length} of your coins \u00b7 `) +`${cut} ETH in creator fees ` +`<button type="button" id="fcGo" class="fclink">${mine.length===1?"Collect":"Collect all"}</button>`; fcEl.title="Trading fees your launched coins have earned and nobody has swept yet. " +"The ether comes to you; the token side is burned, which raises the floor for your holders."; fcEl.classList.toggle("hide",lnMode||lqMode||wnMode||pvMode||tab!=="swap"); fcGo.onclick=fcCollectMine; }catch{if(my===fcMineSeq)fcSync()} } async function fcCollectMine(){ if(!fcMineToks.length)return; const b=fcGo;if(b.disabled)return; const label=b.textContent; try{ b.disabled=true;b.textContent="Collecting\u2026"; const req={from:account,to:PLAUNCH,value:"0x0", data:"0x"+SEL_COLLECTMANY+encUint(32)+encUint(BigInt(fcMineToks.length)) +fcMineToks.map(encAddr).join("")}; await rpc(C,[req,L]); await waitTx(await sendTx([req]));sGot(); stat.textContent="Fees collected."; for(const k of Object.keys(fcFloorPx))delete fcFloorPx[k]; fcMine(); }catch(e){err(e);b.disabled=false;b.textContent=label} } const FC_DUST=10000000000000n; let fcTok="",fcOwed=0n,fcOwner=""; const fcPool={},fcFloorPx={}; async function fcSync(){ const my=++fcSeq; const t=TOKENS[toSel.value],f=TOKENS[fromSel.value]; const cand=[t,f].find(x=>x&&x.addr&&x.addr!==ZERO&&x.std!=="nft"); fcTok="";fcOwed=0n; if(lnMode||lqMode||wnMode||pvMode||tab!=="swap"||PLAUNCH===ZERO){fcEl.classList.add("hide");return} if(fcMineToks.length){fcEl.classList.remove("hide");return} if(!cand){fcEl.classList.add("hide");return} try{ const key=cand.addr.toLowerCase(); let pool=fcPool[key]; if(pool===undefined){ const pr=await rpc(C,[{to:PLAUNCH,data:"0x"+SEL_POOLOF+encAddr(cand.addr)},L]); if(my!==fcSeq)return; pool=fcPool[key]="0x"+strip0x(pr||"").slice(24); } if(!/^0x[0-9a-f]{40}$/i.test(pool)||/^0x0{40}$/i.test(pool)){fcEl.classList.add("hide");return} const [o0,cr]=await mc3Deep([{to:pool,data:"0x"+SEL_OWED0}, {to:PLAUNCH,data:"0x"+SEL_CREATOROF+encAddr(cand.addr)}]); if(my!==fcSeq)return; fcOwner=cr?"0x"+strip0x(cr).slice(24):""; const owed=BigInt(o0||"0x0"); if(owed<FC_DUST){fcEl.classList.add("hide");return} fcTok=cand.addr;fcOwed=owed; const mine=account&&fcOwner&&account.toLowerCase()===fcOwner.toLowerCase(); fcEl.innerHTML=`${(+formatUnits(owed*8n/10n,18)).toFixed(4)} ETH in ` +`${mine?"creator fees":"fees for this coin's creator"} ` +`<button type="button" id="fcGo" class="fclink">Collect</button>`; fcEl.title=mine?"Trading fees this coin has earned. Anyone may collect them and the ether comes to you." :"Trading fees this coin has earned. Anyone may collect them; the ether goes to the coin's creator " +"either way, and the token side is burned, which raises the floor for holders."; fcEl.classList.remove("hide"); fcGo.onclick=fcCollect; }catch{if(my===fcSeq)fcEl.classList.add("hide")} } async function fcCollect(){ if(!fcTok)return; const b=fcGo;if(b.disabled)return; try{ if(!account)return connect(); b.disabled=true;b.textContent="Collecting\u2026"; const req={from:account,to:PLAUNCH,value:"0x0",data:"0x"+SEL_COLLECT+encAddr(fcTok)}; await rpc(C,[req,L]); await waitTx(await sendTx([req]));sGot(); stat.textContent="Fees sent to the creator."; for(const k of Object.keys(fcFloorPx))delete fcFloorPx[k]; fcSync(); }catch(e){err(e);b.disabled=false;b.textContent="Collect"} } const SEL_DAOOF="98fabd3a",SEL_LOOTOF="9b7b2ab0",SEL_SHARESOF="03314efa", SEL_TSUP="18160ddd",SEL_RAGEQUIT="29f64d1a"; const SEL_SALES="c6b9f06a",SEL_REMAINING="b399b0bc",SEL_BUY="cce7ec13"; const SEL_TAPS="6144452a",SEL_TAPCLAIM="1e83409a",SEL_DAOALLOW="dd62ed3e"; const SEL_RQABLE="14a6d7de"; const cbSeen={}; let cbDao="",cbAmt=0n,cbCost=0n,cbBuy=false,cbDrawn=0,cbSold=0n,cbReady=0n,cbSeq=0; let cbDue=0n,cbShares="",cbTok=""; const cbDisarm=()=>{cbSeq++;cbDao="";cbAmt=0n;cbReady=0n;cbDue=0n;cbShares="";cbTok=""; if(typeof cbEl!=="undefined"&&cbEl)cbEl.classList.add("hide")}; const cbWord=h=>{const b=strip0x(h||"");return b.length>=64?"0x"+b.slice(24,64):""}; async function cbLook(tok){ const key=tok.toLowerCase(); if(cbSeen[key])return cbSeen[key]; let out={dao:"",shares:""}; try{ const dao=cbWord(await rpc(C,[{to:tok,data:"0x"+SEL_DAOOF},L])); if(/^0x[0-9a-f]{40}$/i.test(dao)&&!/^0x0{40}$/i.test(dao)){ const [lt,sh]=await mc3Deep([{to:dao,data:"0x"+SEL_LOOTOF},{to:dao,data:"0x"+SEL_SHARESOF}]); if(lt==null||sh==null)return out; if(cbWord(lt).toLowerCase()===key){out={dao,shares:cbWord(sh)}; const ix=TOKENS.findIndex(t=>t.addr&&t.addr.toLowerCase()===key); if(ix>=0&&!TOKENS[ix].cause){TOKENS[ix].cause=1;rebuild()}} } }catch{ return out; } return cbSeen[key]=out; } async function cbSync(){ const my=++cbSeq; cbDao="";cbAmt=0n;cbBuy=false; const f=TOKENS[fromSel.value],t=TOKENS[toSel.value]; if(lnMode||lqMode||wnMode||pvMode||tab!=="swap"||!f||!t){cbEl.classList.add("hide");return} const real=x=>x&&x.addr&&x.addr!==ZERO&&x.std!=="nft"; const cbEth=f.addr===ZERO; let hit=null,tok=null,buy=false; if(real(t)){const h=await cbLook(t.addr);if(my!==cbSeq)return; if(h.dao){hit=h;tok=t;buy=true}} if(!hit&&real(f)){const h=await cbLook(f.addr);if(my!==cbSeq)return; if(h.dao){hit=h,tok=f}} if(!hit||!hit.shares||!tok){cbEl.classList.add("hide");return} let eth=0n,sup=0n,price=0n,dl=0n,left=0n,cap=0n; let rqOff=false; const now2=BigInt(Math.floor(Date.now()/1000)); cbReady=0n; try{ const [b,r]=await Promise.all([ rpc("eth_getBalance",[hit.dao,L]), mc3Deep([{to:hit.shares,data:"0x"+SEL_TSUP},{to:tok.addr,data:"0x"+SEL_TSUP}, {to:COFF,data:"0x"+SEL_SALES+encAddr(hit.dao)}, {to:COFF,data:"0x"+SEL_REMAINING+encAddr(hit.dao)}, {to:CTAP,data:"0x"+SEL_TAPS+encAddr(hit.dao)}, {to:hit.dao,data:"0x"+SEL_DAOALLOW+encAddr(ZERO)+encAddr(CTAP)}, {to:hit.dao,data:"0x"+SEL_RQABLE}])]); eth=BigInt(b||"0x0"); if(my!==cbSeq)return; if(r[0]==null||r[1]==null||r[2]==null||r[3]==null){cbEl.classList.add("hide");return} const shr=BigInt(r[0]),lot=BigInt(r[1]); sup=shr+lot; const sale=strip0x(r[2]||""); if(sale.length>=320 &&("0x"+sale.slice(24,64)).toLowerCase()===CLOOT &&/^0+$/.test(sale.slice(64,128))){ dl=BigInt("0x"+sale.slice(128,192));price=BigInt("0x"+sale.slice(192,256)); cap=BigInt("0x"+sale.slice(256,320))} cbSold=lot; left=BigInt(r[3]||"0x0"); const tp=strip0x(r[4]||""); if(tp.length>=256){ const rate=BigInt("0x"+tp.slice(128,192)),lastC=BigInt("0x"+tp.slice(192,256)); const allow=BigInt(r[5]||"0x0"); if(rate>0n&&now2>lastC){ let av=(now2-lastC)*rate; if(av>allow)av=allow; if(av>eth)av=eth; cbReady=av/rate*rate; }} rqOff=r[6]!=null&&BigInt(r[6])===0n; cbDrawn=lot>0n&&price>0n?(()=>{const raised=(lot*price+CONE-1n)/CONE; return raised>eth?Number((raised-eth)*100n/raised):0})():0; }catch{} if(my!==cbSeq)return; if(sup<=0n){cbEl.classList.add("hide");return} const now=BigInt(Math.floor(Date.now()/1000)); const open=price>0n&&(dl===0n||dl>now); const days=dl>now?Number((dl-now+86399n)/86400n):0; cbDao=hit.dao;cbBuy=buy; const pct=cap>0n?Number(cbSold*1000n/cap)/10:0; const goal=cap>0n&&price>0n?cap*price/CONE:0n; const head=goal>0n ?`<span>${trimAmt(cbSold*price/CONE,18)} of ${trimAmt(goal,18)} ETH backed</span>` +`<span>${open?days+" days left":"closed"}${cbDrawn>0?" · "+cbDrawn+"% released":""}</span>` :
0x68575b07…67c7·#25,913,252·0x2793c174…9bdf8e
en(wasOpen); syncBook(nOwn+nRest,rows.length); book.scrollTop=wasAt; } const pxOf=(r,a,bg)=>{ const v=unitPx(r); return v?`<span class="px">${sigAmt(v,18,4)} ${bg.sym}/${a.sym}</span> \u00b7 `:""; }; const cardCache={}; async function loadCard(board,id){ const k=board.toLowerCase()+":"+id; if(cardCache[k])return cardCache[k]; const raw=await rpc(C,[{to:board,data:"0x"+SEL_TOKENURI+encUint(id)},L]); const uri=decodeString(raw); const i=uri.indexOf("base64,"); if(i<0)throw Error("card is not inline"); const j=JSON.parse(atob(uri.slice(i+7))); const out={attrs:Array.isArray(j.attributes)?j.attributes:[],img:safeUrl(j.image), name:safeSym(j.name).slice(0,40)}; cardCache[k]=out;capObj(cardCache,CARD_MAX);return out; } function cardHtml(c){ const rows=c.attrs.slice(0,10).map(a=>{ const t=safeSym(a&&a.trait_type).slice(0,20); let v=a&&a.value; if(a&&a.display_type==="date"&&+v)v=new Date(+v*1e3).toLocaleDateString("en-US",{month:"short",day:"numeric",year:"numeric"}); else if(a&&a.display_type==="number")v=String(v); return`<dt>${t}</dt><dd>${safeSym(v).slice(0,28)}</dd>`}).join(""); return(rows?`<dl>${rows}</dl>`:"")+(c.img?`<img src="${c.img}" alt="" loading="lazy">`:""); } let inspectSeq=0; async function inspect(el,k){ const box=el.parentNode.querySelector(".ic"); if(!box)return; if(box.innerHTML){box.innerHTML="";delete box.dataset.job;el.setAttribute("aria-expanded","false");return} el.setAttribute("aria-expanded","true"); const at=k.lastIndexOf(":"),board=k.slice(0,at),id=BigInt(k.slice(at+1)); const job=String(++inspectSeq);box.dataset.job=job; box.textContent="Reading the receipt\u2026"; let html; try{html=cardHtml(await loadCard(board,id))||"No card for this order."} catch{html=""} if(box.dataset.job!==job)return; if(html)box.innerHTML=html;else box.textContent="This receipt could not be read."; } const BOOK_TYPES=[["all","All"],["limit","Limit"],["dutch","Dutch"],["bid","Bids"],["nft","NFT"]]; const emptyBook=()=>{ const ty=BOOK_TYPES.find(([v])=>v===bookType); const what=bookType==="all"?"orders":`${ty?ty[1]:bookType} orders`; const where=bookPairOnly ?` on ${TOKENS[fromSel.value]?.sym||"?"}/${TOKENS[toSel.value]?.sym||"?"}`:""; return `No ${what.toLowerCase()}${where} from anyone else.`; }; function bookFilters(hidden){ const f=TOKENS[fromSel.value]?.sym||"?",t=TOKENS[toSel.value]?.sym||"?"; const chip=(on,attr,val,label,title)=>`<button type="button" class="bkc${on?" on":""}" ` +`aria-pressed="${on?"true":"false"}" data-${attr}="${val}"${title?` title="${title}"`:""}>${label}</button>`; const note=[hidden?hidden+" hidden":"",bookTrunc?"scan capped":""].filter(Boolean).join(" \u00b7 "); return `<div class="bkh"><h6><span>Orderbook</span><span title="${bookTrunc?"More orders exist than this scan returned \u2014 the newest are shown first.":""}">${note}</span></h6>` +`<div class="bkf">` +chip(bookPairOnly,"bf","1",`${f}/${t}`,"Only orders on the pair selected above") +chip(!bookPairOnly,"bf","0","All pairs","Every order on every board") +`</div><div class="bkf">` +BOOK_TYPES.map(([v,l])=>chip(bookType===v,"bt",v,l)).join("") +`</div></div>`; } let bookShown=0; function syncBook(n,total){ bookShown=n; total=total===undefined?n:total; const on=tab==="book"&&total>0; bkTog.classList.toggle("hide",!on); bkTog.firstChild.nodeValue=total ?`Orderbook (${n<total?`${n} of ${total}`:total}) `:"Orderbook "; bkTog.setAttribute("aria-expanded",on&&bkOpen?"true":"false"); book.classList.toggle("hide",!on||!bkOpen); } bkTog.onclick=()=>{bkOpen=!bkOpen;LS.bk=bkOpen?"1":"0";syncBook(bookShown,bookRows.length)}; book.addEventListener("keydown",e=>{ if(e.key!=="Enter"&&e.key!==" ")return; const row=e.target.closest(".o.ins"); if(!row||e.target!==row)return; e.preventDefault();inspect(row,row.dataset.k); }); book.addEventListener("click",async e=>{ const fc=e.target.closest("[data-bf]"),tc=e.target.closest("[data-bt]"); if(fc){bookPairOnly=fc.dataset.bf==="1";LS.bkp=bookPairOnly?"1":"0";paintBook();return} if(tc){bookType=tc.dataset.bt;LS.bkt=bookType;paintBook();return} const b=e.target.closest("button[data-i]"); if(!b){ const row=e.target.closest(".o.ins"); if(row&&!e.target.closest("a"))inspect(row,row.dataset.k); return; } b.disabled=true; ++busy; try{ await checkWallet(); const id=BigInt(b.dataset.i),to=b.dataset.b,dl=BigInt(Math.floor(Date.now()/1e3)+1800); const row=bookRows.find(x=>String(x.id)===b.dataset.i&&x.board.toLowerCase()===to.toLowerCase()); if(!row)throw Error("order changed — refresh and retry"); if(b.dataset.x==="c"){ const native=!row.nA&&row.tA.toLowerCase()===WETH.toLowerCase()&&wantsUnwrap(); const floorish=b.dataset.d==="1"||b.dataset.f==="1"; const sel=floorish ?(native?SEL_DUTCH_CANCEL_UNWRAP:SEL_DUTCH_CANCEL) :(native&&b.dataset.v==="1"?SEL_CANCEL_UNWRAP:SEL_CANCELORD); const data="0x"+sel+encUint(id); const lapsed=row.exp&&BigInt(Math.floor(Date.now()/1e3))>row.exp; stat.textContent=`${lapsed?"Reclaiming":"Cancelling"} — the ${b.dataset.f==="1"?"unspent escrow":"unfilled remainder"} returns as ${native&&(floorish||b.dataset.v==="1")?"ETH":"its escrow token"}`; const tx={from:account,to,data,value:"0x0"}; await rpc(C,[tx,L]); await settle(await sendTx([tx])); loadBook();refreshBalance();return; } if(b.dataset.x==="w"){ const refund=row.tA; const native=refund.toLowerCase()===WETH.toLowerCase()&&wantsUnwrap(); const dec=row.dutch?(row.dA??18):(row.dB??18),sym=row.dutch?(row.sA||"tokens"):(row.sB||"tokens"); const held=row.dutch?row.aA:row.aB; const answer=prompt(`Shrink this ${row.dutch?"lot":"bid"}.\n\nIt is escrowed for ${trimAmt(held,dec)} ${sym}.\n\nEnter the amount to stop ${row.dutch?"selling":"bidding for"} — the escrow behind it returns${native?" as ETH":""}. The rest stays live:`,trimAmt(held,dec)); if(answer===null){b.disabled=false;return} let asked; try{asked=parseUnits(String(answer).trim(),dec)}catch{throw Error("that is not an amount")} if(!asked)throw Error("enter an amount above zero"); if(asked>=held)throw Error(`that is the whole ${row.dutch?"lot":"bid"} — use Cancel instead`); const tx={from:account,to,data:"0x"+SEL_WITHDRAW+encUint(id)+encUint(asked)+encBool(native),value:"0x0"}; stat.textContent=`Shrinking — the escrow behind ${trimAmt(asked,dec)} ${sym} returns${native?" as ETH":""}...`; await rpc(C,[tx,L]); await settle(await sendTx([tx])); loadBook();refreshBalance();return; } if(b.dataset.x==="r"){ const sym=row.sB||"tokens",dec=row.dB??18; const answer=prompt(`Reprice this order without losing its place on the book.\n\nIt sells ${trimAmt(row.aA,row.dA??18)} ${row.sA||"tokens"} for ${trimAmt(row.aB,dec)} ${sym}.\n\nEnter the NEW total ${sym} you want:`,trimAmt(row.aB,dec)); if(answer===null){b.disabled=false;return} let asked; try{asked=parseUnits(String(answer).trim(),dec)}catch{throw Error("that is not an amount")} if(!asked)throw Error("enter an amount above zero"); if(asked===row.aB)throw Error("that is the same price"); const tx={from:account,to,data:"0x"+SEL_REPLACE+encUint(id)+encUint(row.aA)+encUint(asked)+encUint(row.exp||0n),value:"0x0"}; stat.textContent="Repricing..."; await rpc(C,[tx,L]); await settle(await sendTx([tx])); loadBook();refreshBalance();return; } const r=row; let payB=r.aB,getA=r.aA; if(r.pf&&!r.nA&&!r.nB&&!r.dutch&&!r.floor&&r.v2&&r.maker.toLowerCase()!==account.toLowerCase()){ const dB=r.dB??18,dA=r.dA??18,sB=r.sB||"tokens",sA=r.sA||"tokens"; let have=0n; try{ have=r.tB===ZERO||r.tB.toLowerCase()===WETH.toLowerCase() ?BigInt(await rpc("eth_getBalance",[account,L])) +(r.tB===ZERO?0n:BigInt(await rpc(C,[{to:WETH,data:"0x"+SEL_BALANCEOF+encAddr(account)},L]))) :BigInt(await rpc(C,[{to:r.tB,data:"0x"+SEL_BALANCEOF+encAddr(account)},L])); }catch{} const suggest=have&&have<r.aB?have:r.aB; const answer=prompt( `Fill how much of this order?\n\nIt sells ${trimAmt(r.aA,dA)} ${sA} for ${trimAmt(r.aB,dB)} ${sB} in total, ` +`and can be filled in part.\n\nEnter the ${sB} you want to spend:`, trimAmt(suggest,dB)); if(answer===null){b.disabled=false;return} let asked; try{asked=parseUnits(String(answer).trim(),dB)}catch{throw Error("that is not an amount")} if(!asked)throw Error("enter an amount above zero"); if(asked>r.aB)throw Error(`that is more than the order needs — it costs ${trimAmt(r.aB,dB)} ${sB} in full`); if(asked<r.aB){ const q=await rpc(C,[{to:r.board,data:"0x"+SEL_QUOTEFILL+encUint(id)+encUint(asked)},L]); const h=strip0x(q); if(h.length<128)throw Error("the board would not quote that amount"); getA=BigInt("0x"+h.slice(0,64)); payB=BigInt("0x"+h.slice(64,128)); if(!getA)throw Error("that amount buys nothing — try a larger one"); stat.textContent=`Filling ${trimAmt(payB,dB)} ${sB} of ${trimAmt(r.aB,dB)} for ${trimAmt(getA,dA)} ${sA}...`; } } const nativeOut=!r.nA&&r.tA.toLowerCase()===WETH.toLowerCase()&&wantsUnwrap(); const wethLeg=!r.nB&&r.tB.toLowerCase()===WETH.toLowerCase()&&payB>0n; const plan=wethLeg?await planWethPayment(payB):null; if(plan&&plan.rail==="short") throw Error(`Not enough ether to fill: it costs ${trimAmt(payB,18)} and you hold ` +`${trimAmt(plan.w,18)} WETH plus ${trimAmt(plan.e,18)} ETH. ` +(r.pf ?`This button takes the whole order — to buy part of it, use the Swap tab, which fills this order alongside the pools.` :`This order is all-or-nothing, so it cannot be filled in smaller pieces.`)); if(plan&&plan.wrap>0n){ stat.textContent=`Wrapping ${trimAmt(plan.wrap,18)} ETH to cover the rest...`; const wtx={from:account,to:WETH,data:"0xd0e30db0",value:toHex(plan.wrap)}; await rpc(C,[wtx,L]);await checkWallet();await settle(await sendTx([wtx])); } const nativeIn=plan?.rail==="eth"; const routeIn=nativeIn?ZERO:r.tB; const routeOut=nativeOut?ZERO:r.tA; const canonicalNative=routeIn.toLowerCase()===routeOut.toLowerCase() ||(routeOut===ZERO&&routeIn.toLowerCase()===WETH.toLowerCase()); if(!r.nB)await assertFunded(routeIn,payB, known(routeIn)||(routeIn===ZERO?{sym:"ETH",dec:18}:{sym:r.sB||"TOKEN",dec:r.dB??18})); if(!r.nA&&!r.nB&&r.cp===ZERO&&!canonicalNative){ if((await rpc("eth_getCode",[SWAPBOL,L])).length<5)throw Error("orderbook executor not deployed yet"); await preflightLeg(r,routeIn,routeOut,payB); const pay=payB,fillPlan=encFillPlan([ {id:r.id,board:r.board,pay,get:getA,part:false} ],routeIn,routeOut,account,account,dl); const planCall=encSnwap(pay?routeIn:ZERO,pay,account,routeOut,getA,SWAPBOL,fillPlan); const checkpoint=routeIn===ZERO?null:encCheckpoint(SWAPBOL,routeIn,account); const direct=checkpoint?encCalls([checkpoint,planCall]):planCall; const funded=routeIn===ZERO||!pay?direct:encCalls([ checkpoint, encSweep(routeIn,pay,SWAPBOL), encSnwap(ZERO,0n,account,routeOut,getA,SWAPBOL,fillPlan) ]); const meta=known(routeIn)||{addr:routeIn,sym:r.sB||"TOKEN",dec:r.dB??18}; await sendRouterFunded(direct,funded,meta,pay,routeIn===ZERO?pay:0n,"Filling order..."); loadBook();refreshBalance();return; } const data=r.dutch ?"0x"+SEL_DUTCH_FILL+encUint(id)+encUint(r.nA?0n:getA)+encAddr(account)+encUint(payB) :r.v2 ?"0x"+(nativeIn?SEL_FILL2_ETH:nativeOut?SEL_FILL2_UNWRAP:SEL_FILL2) +encUint(id)+encUint(dl)+(nativeIn?"":encUint(payB))+encUint(r.nA?0n:getA)+encAddr(ZERO) :"0x"+SEL_FILL1+encUint(id)+encUint(dl); const payNative=(nativeIn&&!r.dutch&&r.v2)||(r.dutch&&r.tB===ZERO); const wrap=nativeIn&&!payNative&&payB ?{to:WETH,data:"0xd0e30db0",value:toHex(payB)}:null; const tx={from:account,to,data,value:payNative?toHex(payB):"0x0"}; const approvals=[]; if(r.nB){ let ap=ZERO; try{ap="0x"+strip0x(await rpc(C,[{to:r.tB,data:"0x081812fc"+encUint(payB)},L])).slice(-40)}catch{} if(ap.toLowerCase()!==to.toLowerCase()){ approvals.push({to:r.tB,data:"0x"+SEL_APPROVE+encAddr(to)+encUint(payB),value:"0x0"}); } }else if(r.tB!==ZERO&&payB&&!(nativeIn&&!r.dutch&&r.v2)){ const al=BigInt(await rpc(C,[{to:r.tB,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(to)},L])); if(al<payB){ if(al)approvals.push({to:r.tB,data:"0x"+SEL_APPROVE+encAddr(to)+encUint(0n),value:"0x0"}); approvals.push({to:r.tB,data:"0x"+SEL_APPROVE+encAddr(to)+encUint(payB),value:"0x0"}); } } const revoke=r.dutch&&!r.nB&&r.tB!==ZERO ?{to:r.tB,data:"0x"+SEL_APPROVE+encAddr(to)+encUint(0n),value:"0x0"}:null; if((wrap||approvals.length||revoke)&&await canBatch(account)){ stat.textContent="Preparing and filling atomically..."; await settle(await sendBatch(account,[ ...(wrap?[wrap]:[]),...approvals,{to,data,value:tx.value},...(revoke?[revoke]:[]) ])); loadBook();refreshBalance();return; } if(wrap){ stat.textContent="Wrapping ETH..."; await waitTx(await sendTx([{from:account,...wrap}])); } for(let i=0;i<approvals.length;i++){ stat.textContent=`Approve${r.nB?" NFT":""}${approvals.length>1?` (${i+1}/${approvals.length})`:""}...`; await waitTx(await sendTx([{from:account,...approvals[i]}])); } await rpc(C,[tx,L]); await settle(await sendTx([tx])); if(revoke)try{ stat.textContent="Filled · revoking quote allowance..."; await waitTx(await sendTx([{from:account,...revoke}])); stat.textContent="Done"; }catch{stat.textContent="Filled · quote allowance remains; revoke it in your wallet"} loadBook();refreshBalance(); }catch(x){err(x);b.disabled=false} finally{--busy} }); const SLOW="0x000000000000888741B254d37e1b27128AfEAaBC"; const SEL_DEPOSITTO="94eeaec9",SEL_CLAIM="379607f5",SEL_REVERSE="97d15425",SEL_WITHDRAWFROM="d4fdc309"; const SEL_UNLOCK="6198e339",SEL_CLAWBACK="fcc36bc9",SEL_GUARDIAN="0633b14a"; const GRACE=2592000n; const SLOW_GATE="0xb8B546b93a82f4Aa6f0345142dF5679B659ef3D4"; const SEL_DEPOSITTIP="75f92e42",SEL_TIPS="a5c68c59",SEL_REFUNDTIP="d27e1e72"; const TIP_GAS=180000n; let sendTipWei=0n,tipSeq=0; async function syncTip(){ const my=++tipSeq; const on=tab==="send"&&+dly.value>0; tipL.classList.toggle("hide",!on); if(!on||!tipCk.checked){sendTipWei=0n;tipNote.textContent="";return} tipNote.textContent="estimating…"; let wei=0n; try{wei=BigInt(await rpc("eth_gasPrice",[]))*TIP_GAS}catch{} if(my!==tipSeq)return; sendTipWei=wei>0n?wei:10n**15n/2n; tipNote.textContent="tip ≈ "+trimAmt(sendTipWei,18)+" ETH"; render(); } const SEL_OUT="d40d4bc6",SEL_IN="e3993ee7",SEL_PENDING="6577b86a"; const idTok=i=>"0x"+(i&((1n<<160n)-1n)).toString(16).padStart(40,"0"); const idDelay=i=>i>>160n; const w=(h,n)=>BigInt("0x"+(strip0x(h).slice(n*64,n*64+64)||"0")); const metaCache=new Map(); async function tokMeta(a){ a=a.toLowerCase(); if(a===ZERO)return{sym:"ETH",dec:18}; const k=TOKENS.find(t=>t.addr.toLowerCase()===a); if(k)return k; if(metaCache.has(a))return metaCache.get(a); let m={sym:a.slice(0,6)+"…",dec:18},ok=0; try{const[sr,dr]=await Promise.all([rpc(C,[{to:a,data:"0x95d89b41"},L]),rpc(C,[{to:a,data:"0x313ce567"},L])]); const d=Number(BigInt(dr)); if(Number.isInteger(d)&&d>=0&&d<=36){m={sym:decodeString(sr).trim().replace(/[<>&"'`]/g,"").slice(0,10)||m.sym,dec:d};ok=1} }catch{} if(ok){metaCache.set(a,m);capMap(metaCache,META_MAX)} return m; } async function warmMeta(addrs){ const want=[...new Set(addrs.map(a=>(a||"").toLowerCase()))] .filter(a=>/^0x[0-9a-f]{40}$/.test(a)&&a!==ZERO&&!known(a)&&!metaCache.has(a)); if(!want.length)return; const res=await mc3Deep(want.flatMap(a=>[{to:a,data:"0x95d89b41"},{to:a,data:"0x313ce567"}])).catch(()=>[]); want.forEach((a,i)=>{ const sr=res[i*2],dr=res[i*2+1]; if(!sr||!dr)return; let d;try{d=Number(BigInt(dr))}catch{return} if(!Number.isInteger(d)||d<0||d>36)return; let sym="";try{sym=decodeString(sr).trim().replace(/[<>&"'`]/g,"").slice(0,10)}catch{} metaCache.set(a,{sym:sym||a.slice(0,6)+"…",dec:d}); }); capMap(metaCache,META_MAX); } const rel=s=>{const a=Math.abs(s),u=[[86400,"d"],[3600,"h"],[60,"m"]]; for(const[n,l]of u)if(a>=n)return Math.floor(a/n)+l;return a+"s"}; let posSeq=0,posShown=25,slowGuard=false; const POSPAGE=25; async function loadPos(){ if(!account){pos.innerHTML="";tabSend.textContent="Send";slowGuard=false;return} const my=++posSeq; let outIds=[],inIds=[]; try{ const[oh,ih,gd]=await Promise.all([ rpc(C,[{to:SLOW,data:"0x"+SEL_OUT+encAddr(account)},L]), rpc(C,[{to:SLOW,data:"0x"+SEL_IN+encAddr(account)},L]), rpc(C,[{to:SLOW,data:"0x"+SEL_GUARDIAN+encAddr(account)},L]).catch(()=>"0x")]); outIds=decUintArr(oh);inIds=decUintArr(ih); if(my===posSeq)slowGuard=w(gd,0)!==0n; }catch{return} if(my!==posSeq)return; const dirs=new Map(); for(const[t,d]of[...outIds.map(i=>[i,"out"]),...inIds.map(i=>[i,"in"])]){ const k=t.toString();dirs.set(k,(dirs.get(k)||"")+d)} const allKeys=[...dirs.keys()].sort((a,b)=>a.length-b.length||(a<b?1:-1)); const keys=allKeys.slice(0,posShown); const more=allKeys.length-keys.length; const ps=[]; for(let i=0;i<keys.length;i+=10){ ps.push(...await Promise.all(keys.slice(i,i+10).map(k=> rpc(C,[{to:SLOW,data:"0x"+SEL_PENDING+encUint(BigInt(k))},L]).catch(()=>0)))); if(my!==posSeq)return; } if(my!==posSeq)return; const rows=[],settled=[]; keys.forEach((k,i)=>{const p=ps[i];if(!p)return; const ts=w(p,0); if(ts===0n){if(dirs.get(k).includes("out"))settled.push(k);return} rows.push({tid:BigInt(k),dir:dirs.get(k),ts,id:w(p,3),amount:w(p,4)})}); const stale=[]; for(let i=0;i<settled.length;i+=10){ const ts=await Promise.all(settled.slice(i,i+10).map(k=> rpc(C,[{to:SLOW_GATE,data:"0x"+SEL_TIPS+encUint(BigInt(k))},L]).catch(()=>0))); if(my!==posSeq)return; ts.forEach((t,j)=>{if(t&&w(t,0)>0n&&idTok(w(t,1))===account.toLowerCase()) stale.push({tid:settled[i+j],amount:w(t,0)})}); } const now=BigInt(Math.floor(Date.now()/1e3)); const ready=rows.filter(r=>r.dir.includes("in")&&now>=r.ts+idDelay(r.id)).length; tabSend.textContent=ready?`Send (${ready}${more?"+":""})`:"Send"; if(tab!=="send"||!(rows.length||stale.length)){pos.innerHTML="";return} rows.sort((a,b)=>Number((a.ts+idDelay(a.id))-(b.ts+idDelay(b.id)))); const metas=await Promise.all(rows.map(r=>tokMeta(idTok(r.id)))); if(my!==posSeq)return; let html=`<div class="ph">Time-locked</div>`; rows.forEach((r,n)=>{ const m=metas[n],exp=r.ts+idDelay(r.id),left=Number(exp-now); const ripe=now>=exp; const amt=`${trimAmt(r.amount,m.dec)} ${m.sym}`; const io=r.dir.includes("in"),oo=r.dir.includes("out"); const held=`data-t="${r.tid}" data-i="${r.id}" data-m="${r.amount}"`; const cb=oo&&ripe&&now>=exp+GRACE; let act,note; if(cb)note=slowGuard?"Unclaimed past grace · unlock credits SLOW; withdraw needs guardian approval":"Unclaimed past grace · recoverable"; else if(ripe)note=io?(slowGuard?"Matured · unlock credits SLOW; withdraw needs guardian approval":"Ready to claim"):"Matured · recipient can claim, you in "+rel(Number(exp+GRACE-now)); else note=(io&&!oo?"Arrives in ":"Reversible · sends in ")+rel(left); if(io&&ripe)act=`<button data-a="c" data-t="${r.tid}">${slowGuard?"Unlock":"Claim"}</button>`; else if(oo&&!ripe)act=`<button data-a="r" ${held}>Reverse</button>`; else if(cb)act=`<button data-a="k" ${held}>Clawback</button>`; else act=`<button disabled>${io?"In":"Out"}</button>`; html+=`<div class="p"><span><b>${oo&&io?"↺":oo?"→":"←"} ${amt}</b><i>${note}</i></span>${act}</div>`; }); stale.forEach(s=>{html+=`<div class="p"><span><b>↩ ${trimAmt(s.amount,18)} ETH</b>` +`<i>Keeper tip on a settled transfer · unspent</i></span>` +`<button data-a="t" data-t="${s.tid}">Reclaim tip</button></div>`}); if(more)html+=`<div class="p" style="justify-content:center"><button data-a="m">Show ${Math.min(more,POSPAGE)} more of ${more}</button></div>`; pos.innerHTML=html; } pos.addEventListener("click",async e=>{ const m=e.target.closest('button[data-a="m"]'); if(m){posShown+=POSPAGE;m.disabled=true;Promise.resolve(loadPos()).finally(()=>{if(m.isConnected)m.disabled=false});return} const b=e.target.closest("button[data-t]"); if(!b)return; b.disabled=true; ++busy; try{ await checkWallet(); const a=b.dataset.a; if(a==="t"){if(!await refundTipBestEffort(b.dataset.t,"Tip"))throw Error("the tip is no longer refundable"); refreshBalance();loadPos();return} const back=a==="r"||a==="k"; let data="0x"+(a==="r"?SEL_REVERSE:a==="k"?SEL_CLAWBACK:slowGuard?SEL_UNLOCK:SEL_CLAIM)+encUint(BigInt(b.dataset.t)); if(back&&!slowGuard)data=encCalls([data,"0x"+SEL_WITHDRAWFROM+encAddr(account)+encAddr(account) +encUint(BigInt(b.dataset.i))+encUint(BigInt(b.dataset.m))]); const txReq={from:account,to:SLOW,data,value:"0x0"}; await rpc(C,[txReq,L]); await settle(await sendTx([txReq])); if(back)await refundTipBestEffort(b.dataset.t,a==="r"?"Reversed":"Recovered"); refreshBalance();loadPos(); }catch(x){err(x);b.disabled=false} finally{--busy} }); async function refundTipBestEffort(tid,verb="Reversed"){ try{ const t=await rpc(C,[{to:SLOW_GATE,data:"0x"+SEL_TIPS+encUint(BigInt(tid))},L]); if(w(t,0)===0n)return false; const req={from:account,to:SLOW_GATE,data:"0x"+SEL_REFUNDTIP+encUint(BigInt(tid)),value:"0x0"}; await rpc(C,[req,L]); stat.textContent=verb+" · reclaiming keeper tip..."; await settle(await sendTx([req])); stat.textContent=verb+" · keeper tip refunded"; return true; }catch{return false} } const rcErr=v=>isAddr(v) ?(/^0x0{40}$/i.test(v)?"Recipient cannot be the zero address" :!ckAddr(v)?"That address fails its checksum — a character is wrong. Paste it again.":"") :!/\.(g?wei|eth)$/i.test(v)?"Recipient must be an address or a .wei / .gwei / .eth name" :!nameOk(v.toLowerCase())?"That name uses characters this page cannot resolve.":""; const rcvOf=async(v,fresh)=>{ if(isAddr(v))return /^0x0{40}$/i.test(v)||!ckAddr(v)?"":v; if(!/\.(g?wei|eth)$/i.test(v))return ""; const n=v.toLowerCase(); if(!nameOk(n))return ""; let a=""; try{a=fresh?await nameFwd(n):await nameFwdCached(n)} catch(e){if(e&&e.offchain)throw e} return !isAddr(a)||/^0x0{40}$/i.test(a)?"":a; }; async function updateSend(){ const my=++sSeq; sendReady=null; const f=TOKENS[fromSel.value]; const v=rc.value.trim(); let amount=0n,amtErr=""; const q=amt.value.trim(); try{amount=parseUnits(q,f.dec)} catch(e){if(q&&!/\.$/.test(q))amtErr=/^unclear comma/.test(e.message)?e.message:/decimals/.test(e.message)?`${f.sym} has ${f.dec} decimals`:"Invalid amount"} if(!v){rc.classList.remove("bad");rcvEl.textContent="";stat.textContent=amtErr;render();return} const bad=rcErr(v); if(bad){rc.classList.add("bad");rcvEl.textContent=""; stat.textContent=bad;render();return} if(!/^0x/.test(v))rcvEl.textContent="resolving..."; let to="",toErr=""; try{to=await rcvOf(v)}catch(e){toErr=e&&e.offchain?e.message:""} if(my!==sSeq)return; if(!to){rc.classList.add("bad");rcvEl.textContent=""; stat.textContent=toErr||"Name not registered";render();return} rc.classList.remove("bad"); rcvEl.textContent=to; stat.textContent=amtErr; if(amount>0n)sendReady={to,amount,token:f,label:/^0x/.test(v)?to.slice(0,6)+"\u2026"+to.slice(-4):v}; render(); } const syncNftUI=()=>{ const anyNft=tab==="book"&&(isNft(fromSel.value)||isNft(toSel.value)); nftIdL.classList.toggle("hide",!anyNft); if(!anyNft)nftId.value=""; nftId.placeholder=isWns(TOKENS[fromSel.value]?.addr)||isWns(TOKENS[toSel.value]?.addr) ?(isNft(fromSel.value)?"Which .wei name you are selling":"Any, or a .wei name") :isNft(fromSel.value)?"Which one you are selling":"Any from collection"; syncOrderType(); showFloor(); }; let syncStops=()=>{}; function chainUi(){ lq.classList.toggle("hide",tab!=="swap"||PROUTE===ZERO); ln.classList.toggle("hide",tab!=="swap"||PLAUNCH===ZERO); wn.classList.toggle("hide",tab!=="swap"||CHAIN_ID!==1); pv.classList.toggle("hide",tab!=="swap"||CHAIN_ID!==1); tabBook.classList.toggle("hide",SB2===ZERO); if(lqMode&&PROUTE===ZERO)lqSet(false); if(lnMode&&PLAUNCH===ZERO)lnSet(false); if(wnMode&&CHAIN_ID!==1)wnSet(false); if(pvMode&&CHAIN_ID!==1)pvSet(false); if(tab==="book"&&SB2===ZERO)setTab("swap"); } const setTab=t=>{ if(t==="book"&&SB2===ZERO)t="swap"; if(tab==="send")sendD=dly.value;else if(tab==="book")bookD=dly.value; if(t!=="swap"&&lqMode)lqSet(false); if(t!=="swap"&&lnMode)lnSet(false); if(t!=="swap"&&wnMode)wnSet(false); if(t!=="swap"&&pvMode)pvSet(false); lq.classList.toggle("hide",t!=="swap"||PROUTE===ZERO); ln.classList.toggle("hide",t!=="swap"||PLAUNCH===ZERO); wn.classList.toggle("hide",t
0x68575b07…67c7·#25,913,251·0x80212662…7accec
ken:"0x"+h.slice((row+2*32+12)*2,(row+3*32)*2), quote:"0x"+h.slice((row+3*32+12)*2,(row+4*32)*2), isNFT:f(4)!==0n, anyId:f(5)!==0n, remaining:f(7), initial:f(8), price:f(9), proceeds:f(10), expiry:f(12), tDec:dec(13),qDec:dec(14), tSym:txt(15),qSym:txt(16), }); }catch{continue} } return out; }catch{return out} } async function collectionFloor(collection,quote=ZERO){ if(FLOORVIEW===ZERO||FLOOR===ZERO)return[]; const data="0x"+SEL_COLL_BIDS+encAddr(FLOOR)+encAddr(collection)+encAddr(quote) +encUint(0)+encUint(16)+encUint(256); const raw=await rpc(C,[{to:FLOORVIEW,data},L]).catch(()=>null); if(!raw)return[]; const rows=decBidRows(raw); return rows.filter(r=>r.anyId&&r.remaining>0n) .sort((a,b)=>{const l=b.proceeds*a.remaining,r=a.proceeds*b.remaining;return l>r?1:l<r?-1:0}); } async function floorCandidates(tokenIn,tokenOut,block){ const bin=tokenIn===ZERO?WETH:tokenIn,bout=tokenOut===ZERO?WETH:tokenOut; const data="0x"+SEL_FLOOR_CANDS+encAddr(FLOOR)+encAddr(bin)+encAddr(bout) +encUint(0)+encUint(64)+encUint(512); const raw=await rpc(C,[{to:FLOORVIEW,data},block]).catch(()=>null); if(!raw)return[]; const out=[]; for(const b of decBidRows(raw)){ if(b.isNFT||b.remaining<=0n||b.initial<=0n||b.price<=0n||b.proceeds<=0n)continue; if(b.token.toLowerCase()!==bin.toLowerCase())continue; if(b.quote.toLowerCase()!==bout.toLowerCase())continue; out.push({ id:b.id,board:FLOOR,floor:1,v2:0,dutch:0, maker:b.bidder,pf:1,exp:b.expiry,nA:0,nB:0,cp:ZERO, tA:b.quote,aA:b.proceeds,sA:b.qSym,dA:b.qDec, tB:b.token,aB:b.remaining,sB:b.tSym,dB:b.tDec, price:b.price,initial:b.initial, }); } return out; } function floorGet(o,pay){return o.price*pay/o.initial} function floorPay(o,want){ if(want<=0n)return 0n; const g=(want*o.initial+o.price-1n)/o.price; return g>o.aB?o.aB:g; } function rowGet(o,pay){return o.floor?floorGet(o,pay):o.aA*pay/o.aB} function decViewPage(hex){ const h=strip0x(hex),bytes=h.length/2; const word=b=>{if(b<0||b+32>bytes)throw Error("bad lens page");return BigInt("0x"+h.slice(b*2,(b+32)*2))}; const addr=(b)=>"0x"+h.slice((b+12)*2,(b+32)*2); const arr=Number(word(0)),next=word(32); if(!Number.isSafeInteger(arr)||arr<64||arr+32>bytes)throw Error("bad lens offset"); const n=Number(word(arr)),head=arr+32; if(!Number.isSafeInteger(n)||n>256||head+n*32>bytes)throw Error("bad lens length"); const text=(base,k)=>{const off=Number(word(base+k*32)),p=base+off,len=Number(word(p)); if(!Number.isSafeInteger(off)||!Number.isSafeInteger(len)||p+32+len>bytes)throw Error("bad lens string"); const cap=Math.min(len,1024),a=new Uint8Array(cap); for(let i=0;i<cap;i++)a[i]=parseInt(h.slice((p+32+i)*2,(p+33+i)*2),16); return new TextDecoder().decode(a)}; const rows=[]; const dec=v=>{const d=Number(v);return Number.isInteger(d)&&d>=0&&d<=36?d:null}; for(let i=0;i<n;i++){ try{ const off=Number(word(head+i*32)),b=head+off; if(!Number.isSafeInteger(off)||b+16*32>bytes)throw Error("bad lens row"); rows.push({id:word(b),maker:addr(b+32),pf:word(b+64)===1n,exp:word(b+96), nA:word(b+128)===1n,nB:word(b+160)===1n,cp:addr(b+192), tA:addr(b+224),aA:word(b+256),sA:safeSym(text(b,9)),dA:dec(word(b+320)), tB:addr(b+352),aB:word(b+384),sB:safeSym(text(b,13)),dB:dec(word(b+448)),board:addr(b+480)}); }catch{continue} } return{rows,next}; } const betterRate=(a,b)=>a.aA*b.aB>b.aA*a.aB; const bookShare=(h,amm)=>{ const rest=h.ammIn!==undefined?h.ammIn:h.ammOut; if(!rest)return"Orderbook"; const mine=h.ammIn!==undefined?h.bookIn:h.bookOut; const pct=Number(mine*100n/(mine+rest)); return`Orderbook ${pct<1?"<1":pct}% + ${amm}`; }; const MAX_BOOK_LEGS=32,AON_PAIR_SEEDS=24; function planBookExactIn(rows,amountIn,ammOut,ammIn){ if(!amountIn||!ammIn)return null; let c=rows.filter(o=>!o.nA&&!o.nB&&o.aA&&(o.aB||o.dutch)&&(o.pf||o.aB<=amountIn) &&(!o.floor||(o.price>0n&&o.initial>0n)) &&!(account&&o.maker&&o.maker.toLowerCase()===account.toLowerCase())); const candidateCapped=c.length>256; const aonCount=c.reduce((n,o)=>n+!o.pf,0); const aonBounded=aonCount>=3; c.sort((a,b)=>betterRate(a,b)?-1:betterRate(b,a)?1:0); if(c.length>256)c.length=256; const build=(seed=-1,seed2=-1)=>{ let rem=amountIn,bin=0n,bout=0n,fills=[],legCapped=false; const take=o=>{ if(o.aA*ammIn<=ammOut*o.aB)return; if(!o.pf&&o.aB>rem)return; if(fills.length>=MAX_BOOK_LEGS){legCapped=true;return} if(o.aB===0n){ fills.push({id:o.id,board:o.board,pay:0n,get:o.aA,part:false}); bout+=o.aA;return; } const pay=o.aB<rem?o.aB:rem; if(!pay)return; const get=o.pf?(pay===o.aB?o.aA:rowGet(o,pay)):o.aA; if(!get)return; fills.push({id:o.id,board:o.board,pay,get,part:o.pf&&pay<o.aB}); rem-=pay;bin+=pay;bout+=get; }; if(seed>=0)take(c[seed]); if(seed2>=0)take(c[seed2]); for(let i=0;i<c.length;i++)if(i!==seed&&i!==seed2)take(c[i]); return{fills,bookIn:bin,bookOut:bout,ammIn:rem,score:bout+rem*ammOut/ammIn, bounded:candidateCapped||aonBounded||legCapped}; }; let best=build(-1); for(let i=0;i<c.length;i++)if(!c[i].pf){ const p=build(i);if(p.score>best.score)best=p; } const aon=[];for(let i=0;i<c.length&&aon.length<AON_PAIR_SEEDS;i++)if(!c[i].pf)aon.push(i); for(let i=0;i<aon.length;i++)for(let j=i+1;j<aon.length;j++){ const p=build(aon[i],aon[j]);if(p.score>best.score)best=p; } return best.fills.length?best:null; } const cheaperRate=(a,b)=>a.aB*b.aA<b.aB*a.aA; function planBookExactOut(rows,amountOut,ammIn,ammOut){ if(!amountOut||!ammOut)return null; let c=rows.filter(o=>!o.nA&&!o.nB&&o.aA&&(o.aB||o.dutch)&&(o.pf||o.aA<=amountOut) &&(!o.floor||(o.price>0n&&o.initial>0n)) &&!(account&&o.maker&&o.maker.toLowerCase()===account.toLowerCase())); const candidateCapped=c.length>256; const aonCount=c.reduce((n,o)=>n+!o.pf,0); const aonBounded=aonCount>=3; c.sort((a,b)=>cheaperRate(a,b)?-1:cheaperRate(b,a)?1:0); if(c.length>256)c.length=256; const build=(seed=-1,seed2=-1)=>{ let left=amountOut,bin=0n,bout=0n,fills=[],legCapped=false; const take=o=>{ if(o.aB*ammOut>=ammIn*o.aA)return; if(!o.pf&&o.aA>left)return; if(fills.length>=MAX_BOOK_LEGS){legCapped=true;return} const want=o.aA<left?o.aA:left; let pay,get; if(o.aB===0n){pay=0n;get=want} else if(want===o.aA){pay=o.aB;get=o.aA} else if(o.floor){ pay=floorPay(o,want); get=floorGet(o,pay); } else{ pay=(want*o.aB+o.aA-1n)/o.aA; if(pay>o.aB)pay=o.aB; get=pay*o.aA/o.aB; } if(!get)return; fills.push({id:o.id,board:o.board,pay,get,part:want!==o.aA}); bin+=pay;bout+=get;left-=get<left?get:left; }; if(seed>=0)take(c[seed]); if(seed2>=0)take(c[seed2]); for(let i=0;i<c.length&&left;i++)if(i!==seed&&i!==seed2)take(c[i]); return{fills,bookIn:bin,bookOut:bout,ammOut:left,score:bin+left*ammIn/ammOut, bounded:candidateCapped||aonBounded||legCapped}; }; let best=build(-1); for(let i=0;i<c.length;i++)if(!c[i].pf){ const p=build(i);if(p.score<best.score)best=p; } const aon=[];for(let i=0;i<c.length&&aon.length<AON_PAIR_SEEDS;i++)if(!c[i].pf)aon.push(i); for(let i=0;i<aon.length;i++)for(let j=i+1;j<aon.length;j++){ const p=build(aon[i],aon[j]);if(p.score<best.score)best=p; } return best.fills.length?best:null; } function encFillPlan(fills,tokenIn,tokenOut,recipient,refundTo,deadline){ let tail=encUint(fills.length); for(const f of fills)tail+=encUint(f.id)+encAddr(f.board)+encUint(f.pay)+encUint(f.get)+encBool(f.part); return"0x"+SEL_FILLPLAN+encAddr(tokenIn)+encAddr(tokenOut)+encAddr(recipient)+encAddr(refundTo) +encUint(deadline)+encUint(192)+tail; } function encFillPlanAndSwap(fills,tokenIn,tokenOut,recipient,refundTo,deadline,ammIn,ammData){ let ft=encUint(fills.length); for(const f of fills)ft+=encUint(f.id)+encAddr(f.board)+encUint(f.pay)+encUint(f.get)+encBool(f.part); const d=strip0x(ammData||"0x"),dt=encUint(d.length/2)+d.padEnd(Math.ceil(d.length/64)*64,"0"); return"0x"+SEL_FILLPLAN_SWAP+encAddr(tokenIn)+encAddr(tokenOut)+encAddr(recipient)+encAddr(refundTo) +encUint(deadline)+encUint(256)+encUint(ammIn)+encUint(256+ft.length/2)+ft+dt; } function encSnwap(tokenIn,amountIn,recipient,tokenOut,minOut,executor,data){ const d=strip0x(data),body=encUint(d.length/2)+d.padEnd(Math.ceil(d.length/64)*64,"0"); return"0x"+SEL_SNWAP+encAddr(tokenIn)+encUint(amountIn)+encAddr(recipient)+encAddr(tokenOut) +encUint(minOut)+encAddr(executor)+encUint(224)+body; } const encCheckpoint=(executor,token,recipient)=>encSnwap( ZERO,0n,recipient,ZERO,0n,executor,"0x"+SEL_CHECKPOINT+encAddr(token) ); const SEL_SWEEP3="dc2c256f"; const encSweep=(token,amount,to)=>chainCfg().sweep4 ?"0x"+SEL_SWEEP+encAddr(token)+encUint(0)+encUint(amount)+encAddr(to) :"0x"+SEL_SWEEP3+encAddr(token)+encUint(amount)+encAddr(to); function encPermit2Hybrid(ammData,fills,tokenIn,tokenOut,recipient,refundTo,deadline,bookIn,bookOut){ const bookData=encFillPlan(fills,tokenIn,tokenOut,recipient,refundTo,deadline); const steps=[encCheckpoint(SWAPBOL,tokenIn,refundTo)]; if(bookIn)steps.push(encSweep(tokenIn,bookIn,SWAPBOL)); steps.push(encSnwap(ZERO,0n,recipient,tokenOut,bookOut,SWAPBOL,bookData)); if(ammData&&ammData!=="0x")steps.push(ammData); steps.push(encSweep(tokenIn,0n,refundTo)); return encCalls(steps); } async function swapCandidates(tokenIn,tokenOut,block){ if(tokenOut===ZERO&&tokenIn.toLowerCase()===WETH.toLowerCase())return[]; const key=tokenIn.toLowerCase()+":"+tokenOut.toLowerCase(),old=candCache[key]; if(old&&Date.now()-old.at<15000&&old.block===block){ old.rows.incomplete=old.incomplete;old.rows.unavailable=old.unavailable; return old.rows; } const snapshotAt=Date.now(); const boardIn=tokenIn===ZERO?WETH:tokenIn; const boardOut=tokenOut===ZERO?WETH:tokenOut; const [lc,le,ld]=await Promise.all([ hasCode(SBVIEW),hasCode(SWAPBOL), DUTCH!==ZERO?hasCode(DUTCH):false ]); if(!lc||!le){ const rows=[];rows.unavailable=true;return rows; } const specs=[...BOARDS()]; if(ld){ specs.push({a:DUTCH,dutch:1,ti:tokenIn}); if(tokenIn===ZERO)specs.push({a:DUTCH,dutch:1,ti:WETH}); } const groups=await Promise.all(specs.map(async b=>{ let cursor=0n,out=[],turns=0,incomplete=false,failed=false; try{do{ const ti=b.dutch?b.ti:boardIn,to=boardOut; const data="0x"+(b.dutch?SEL_DUTCH_CANDS:SEL_CANDS)+encAddr(b.a) +(b.dutch?"":encBool(b.v2))+encAddr(ti)+encAddr(to)+encAddr(SWAPBOL) +encUint(cursor)+encUint(512); const p=decViewPage(await rpc(C,[{to:SBVIEW,data},block])); out.push(...p.rows.map(r=>({...r,v2:b.v2||0,dutch:b.dutch||0}))); cursor=p.next; ++turns; }while(cursor&&turns<64)}catch{failed=true;incomplete=true} incomplete=incomplete||!!cursor; return{out,incomplete,failed}; })); let rows=groups.flatMap(g=>g.out); if(tokenIn===ZERO&&tokenOut.toLowerCase()===WETH.toLowerCase()){ rows=rows.filter(r=>r.dutch&&r.tA.toLowerCase()===WETH.toLowerCase()&&r.tB===ZERO); } else if(await floorRoutable(block)){ rows=rows.concat(await floorCandidates(tokenIn,tokenOut,block).catch(()=>[])); } const incomplete=groups.some(g=>g.incomplete); const unavailable=groups.every(g=>g.failed&&!g.out.length); rows.incomplete=incomplete; rows.unavailable=unavailable; const current=candCache[key],bn=BigInt(block),cn=current?BigInt(current.block):-1n; if(!current||bn>cn||(bn===cn&&snapshotAt>=current.at)){ candCache[key]={at:snapshotAt,block,rows,incomplete,unavailable}; } for(const k in candCache)if(k!==key&&snapshotAt-candCache[k].at>60000)delete candCache[k]; return rows; } async function lensBookRows(my){ const [sv,dv]=await Promise.all([hasCode(SBVIEW),DUTCH!==ZERO?hasCode(DUTCH):false]); if(!sv)return null; const specs=[...BOARDS()]; if(dv)specs.push({a:DUTCH,dutch:1}); const pages=await Promise.all(specs.map(async b=>{ let cursor=0n,out=[],turns=0; do{ const data="0x"+(b.dutch?SEL_RECENT_DUTCH:SEL_RECENT)+encAddr(b.a) +(b.dutch?"":encBool(b.v2))+encUint(cursor)+encUint(80)+encUint(256); const p=decViewPage(await rpc(C,[{to:SBVIEW,data},L])); out.push(...p.rows.map(r=>({...r,v2:b.v2||0,dutch:b.dutch||0}))); cursor=p.next; }while(cursor&&out.length<160&&++turns<64&&my===bookSeq); if(cursor)out.capped=true; return out; })); const rows=pages.flat(); let capped=pages.some(p=>p.capped); try{ for(const r of decViewPage(await rpc(C,[{to:SBVIEW, data:"0x"+SEL_EXPIRED+encAddr(SB2)+encUint(0)+encUint(80)+encUint(256)},L])).rows) if(r.maker&&r.maker.toLowerCase()===me())rows.push({...r,v2:1,dutch:0}); }catch{} if(FLOOR!==ZERO&&FLOORVIEW!==ZERO){ await floorRoutable(L); try{ const raw=await rpc(C,[{to:FLOORVIEW, data:"0x"+SEL_RECENT_FLOOR+encAddr(FLOOR)+encUint(0)+encUint(80)+encUint(256)},L]); for(const b of decBidRows(raw)){ if(b.remaining<=0n)continue; rows.push({ id:b.id,board:FLOOR,floor:1,v2:0,dutch:0, maker:b.bidder,pf:b.isNFT?0:1,exp:b.expiry, nA:0,nB:0,cp:ZERO, tA:b.quote,aA:b.proceeds,sA:b.qSym,dA:b.qDec, tB:b.token,aB:b.remaining,sB:b.tSym,dB:b.isNFT?0:b.tDec, price:b.price,initial:b.initial,anyId:b.anyId,isNFT:b.isNFT?1:0, }); } }catch{} } if(dv&&account)try{ const total=Number(BigInt(await rpc(C,[{to:DUTCH,data:"0x"+SEL_NEXTID2},L])||"0x0")); const ids=[];for(let i=total-1;i>=Math.max(0,total-40);--i)ids.push(i); if(ids.length){ const res=await mc3Deep(ids.map(id=>({to:DUTCH,data:"0x"+SEL_DUTCH_LISTING+encUint(id)}))); const now=BigInt(Math.floor(Date.now()/1e3)),me2=me(); for(let i=0;i<ids.length;i++){ const s=strip0x(res[i]||"0x");if(s.length<704)continue; const w=k=>BigInt("0x"+s.slice(k*64,(k+1)*64)),ad=k=>"0x"+s.slice(k*64+24,(k+1)*64); if(ad(0).toLowerCase()!==me2)continue; const exp=w(10);if(!exp||now<=exp)continue; const rem=w(9);if(!rem)continue; rows.push({id:ids[i],board:DUTCH,dutch:1,v2:0,maker:ad(0),pf:1,exp, nA:0,nB:0,cp:ZERO,tA:ad(4),aA:rem,sA:null,dA:null,tB:ad(6),aB:0n,sB:null,dB:null}); } }}catch{} if(FLOOR!==ZERO&&account)try{ const total=Number(BigInt(await rpc(C,[{to:FLOOR,data:"0x"+SEL_NEXTID2},L])||"0x0")); const ids=[];for(let i=total-1;i>=Math.max(0,total-40);--i)ids.push(i); if(ids.length){ const res=await mc3Deep(ids.map(id=>({to:FLOOR,data:"0x"+SEL_BIDS+encUint(id)}))); const now=BigInt(Math.floor(Date.now()/1e3)),me2=me(); for(let i=0;i<ids.length;i++){ const s=strip0x(res[i]||"0x");if(s.length<704)continue; const w=k=>BigInt("0x"+s.slice(k*64,(k+1)*64)),ad=k=>"0x"+s.slice(k*64+24,(k+1)*64); if(ad(0).toLowerCase()!==me2)continue; const exp=w(2)+w(3);if(!exp||now<=exp)continue; const rem=w(10);if(!rem)continue; rows.push({id:ids[i],board:FLOOR,floor:1,v2:0,dutch:0,maker:ad(0),pf:1,exp, nA:0,nB:0,cp:ZERO,tA:ad(6),aA:w(8),sA:null,dA:null,tB:ad(4),aB:rem,sB:null,dB:null}); } }}catch{} const uniq=new Set(),deduped=[]; deduped.capped=capped; for(const r of rows){ const k=`${(r.board||"").toLowerCase()}|${r.dutch?"d":r.floor?"f":"o"}|${r.id}`; if(uniq.has(k))continue; uniq.add(k);deduped.push(r); } return deduped; } const loadBook=()=>loadBookRun().catch(()=>{}); async function loadBookRun(){ const my=++bookSeq; if(tab!=="book"){book.innerHTML="";bookRows=[];syncBook(0);return} const live=(await Promise.all(BOARDS().map(b=> rpc("eth_getCode",[b.a,L]).then(c=>(c||"").length>4?b:null,()=>null)))).filter(Boolean); if(my!==bookSeq)return; if(!live.length){ bookRows=[];bookLegs=[]; book.innerHTML='<h6><span>Orderbook</span></h6><div class="o"><span class="l"><i>No Swapboard deployed on this network yet.</i></span></div>'; syncBook(0);book.classList.remove("hide");return} let rows=null; try{rows=await lensBookRows(my)}catch{rows=null} if(my!==bookSeq)return; bookTrunc=!!(rows&&rows.capped); if(!rows)rows=[]; for(const b of rows.length?[]:live){ let total=0; try{total=Number(BigInt(await rpc(C,[{to:b.a,data:"0x"+SEL_NEXTID},L])))}catch{continue} if(!total)continue; const hi=total,lo=Math.max(0,hi-40); if(lo>0)bookTrunc=true; const ids=[];for(let i=hi-1;i>=lo;--i)ids.push(i); let raw=""; try{raw=await rpc(C,[{to:b.a,data:"0x"+SEL_GETORDERS+encUint(32)+encUint(ids.length)+ids.map(i=>encUint(i)).join("")},L])}catch{continue} if(my!==bookSeq)return; const h=strip0x(raw);if(h.length<128)continue; const w=WORDS(b.v2),words=(h.length-128)/64; let n=Number(BigInt("0x"+h.slice(64,128))); if(!Number.isSafeInteger(n)||n*w>words)n=Math.floor(words/w); const at=(r,k)=>BigInt("0x"+h.slice(128+(r*w+k)*64,128+(r*w+k+1)*64)); const ad=(r,k)=>"0x"+h.slice(128+(r*w+k)*64+24,128+(r*w+k+1)*64); for(let r=0;r<n&&r<ids.length;++r){ if(at(r,1)!==1n)continue; const o=b.v2 ?{id:ids[r],board:b.a,v2:1,maker:ad(r,0),pf:at(r,2)===1n,exp:at(r,3), nA:at(r,4)===1n,nB:at(r,5)===1n,cp:ad(r,6), tA:ad(r,7),aA:at(r,8),tB:ad(r,9),aB:at(r,10)} :{id:ids[r],board:b.a,v2:0,maker:ad(r,0),pf:0,exp:0n,nA:0,nB:0, cp:ZERO,tA:ad(r,2),aA:at(r,3),tB:ad(r,4),aB:at(r,5)}; if(o.exp&&BigInt(Math.floor(Date.now()/1e3))>o.exp &&o.maker.toLowerCase()!==me())continue; if(o.cp!==ZERO &&o.cp.toLowerCase()!==me() &&o.maker.toLowerCase()!==me())continue; rows.push(o); } } if(my!==bookSeq)return; const cutoff=BigInt(Math.floor(Date.now()/1e3)); rows=rows.filter(o=>{ const mine=o.maker.toLowerCase()===me(); if(o.exp&&cutoff>o.exp&&!mine)return false; return o.cp===ZERO ||o.cp.toLowerCase()===me() ||o.maker.toLowerCase()===me(); }); const dutchRows=rows.filter(r=>r.dutch); const bidRows=rows.filter(r=>r.floor); const fillRows=rows.filter(r=>!r.dutch&&!r.floor&&r.v2&&r.pf&&!r.nA&&!r.nB); if(dutchRows.length||bidRows.length||fillRows.length){ const reqs=[ ...dutchRows.map(r=>({to:DUTCH,data:"0x"+SEL_DUTCH_LISTING+encUint(r.id)})), ...bidRows.map(r=>({to:FLOOR,data:"0x"+SEL_BIDS+encUint(r.id)})), ...fillRows.map(r=>({to:r.board,data:"0x"+SEL_INITIAL_B+encUint(r.id)})), ]; const raw=await mc3Deep(reqs); if(my!==bookSeq)return; let k=0; for(const r of dutchRows){const x=raw[k++];if(!x)continue; const h=strip0x(x),w=n=>BigInt("0x"+h.slice(n*64,(n+1)*64)); if(h.length>=640)Object.assign(r,{ start:w(2),duration:w(3),startPrice:w(5),endPrice:w(7),initial:w(8),remaining:w(9)}); } for(const r of bidRows){const x=raw[k++];if(!x)continue; const h=strip0x(x),w=n=>BigInt("0x"+h.slice(n*64,(n+1)*64)); if(h.length>=11*64)Object.assign(r,{start:w(2),duration:w(3),startPrice:w(5),endPrice:w(7)}); } for(const r of fillRows){const x=raw[k++];if(!x)continue; const h=strip0x(x);if(h.length<64)continue; const init=BigInt("0x"+h.slice(0,64)); if(init>r.aB)r.filled=(init-r.aB)*10000n/init; } } if(!rows.length){bookRows=[];bookLegs=[];book.innerHTML="";syncBook(0);return} const needMeta=[]; for(const r of rows){ if(!r.nA&&(r.sA==null||r.dA==null))needMeta.push(r.tA); if(!r.nB&&(r.sB==null||r.dB==null))needMeta.push(r.tB); } await Promise.all([loadLogos(rows.flatMap(r=>[r.tA,r.tB])),warmMeta(needMeta)]); if(my!==bookSeq)return; const legs=await Promise.all(rows.map(r=>Promise.all([ leg(r.tA,r.aA,r.nA,r.sA!=null&&r.dA!=null?{sym:r.sA,dec:r.dA}:null), leg(r.tB,r.aB,r.nB,r.sB!=null&&r.dB!=null?{sym:r.sB,dec:r.dB}:null) ]))); if(my!==bookSeq)return; rows.forEach((r,i)=>{const[a,b]=legs[i]; if(!r.nA&&r.dA==null&&a.dec!=null)r.dA=a.dec; if(!r.nB&&r.dB==null&&b.dec!=null)r.dB=b.dec}); bookRows=rows;bookLegs=legs; paintBook(); } let bookPairOnly=LS.bkp!=="0",bookLegs=[]; let bookType=/^(all|limit|dutch|bid|nft)$/.test(LS.bkt||"")?LS.bkt:"all"; const unitPx=r=>r.aA&&r.aB&&!r.nA&&!r.nB&&!r.isNFT ?r.aB*10n**BigInt(18+(r.dA??18))/(r.aA*10n**BigInt(r.dB??18)):null; const sameAsset=(a,b)=>{ const w=x=>{x=(x||"").toLowerCase();return x===ZERO.toLowerCase()?WETH.toLowerCase():x}; return w(a)===w(b); }; const rowInPair=r=>{ const f=TOKENS[fromSel.value]?.addr,t=TOKENS[toSel.value]?.addr; if(!f||!t)return true; return (sameAsset(r.tA,f)&&sameAsset(r.tB,t))||(sameAsset(r.tA,t)&&sameAsset(r.tB,f)); }; const rowIsType=r=>bookType==="all" ||(bookType==="dutch"&&r.dutch) ||(bookType==="bid"&&r.floor) ||(bookType==="nft"&&(r.nA||r.nB||r.isNFT)) ||(bookType==="limit"&&!r.dutch&&!r.floor); const bookOpenCards=()=>{const s=[]; for(const el of book.querySelectorAll('.o.ins[aria-expanded="true"]')) if(el.dataset.k)s.push(el.dataset.k); return s}; const bookReopen=ks=>{ for(const k of ks){ const el=book.querySelector('.o.ins[data-k="'+k+'"]'); if(!el)continue; const at=k.lastIndexOf(":"); const c=cardCache[k.slice(0,at).toLowerCase()+":"+k.slice(at+1)]; const box=el.parentNode.querySelector(".ic"); if(!c||!box)continue; el.setAttribute("aria-expanded","true"); box.innerHTML=cardHtml(c)||"No card for this order."; }}; const bookOrder=rows=>{ const idx=rows.map((r,i)=>i); const f=TOKENS[fromSel.value]?.addr,t=TOKENS[toSel.value]?.addr; if(!bookPairOnly||!f||!t)return idx; const k=rows.map(r=>{const d=sameAsset(r.tA,t)&&sameAsset(r.tB,f)?0:1; return[d,d?null:unitPx(r)]}); const px=(a,b)=>a===null||b===null?(a===b?0:a===null?1:-1):a<b?-1:a>b?1:0; return idx.sort((a,b)=>k[a][0]-k[b][0]||px(k[a][1],k[b][1])); }; function paintBook(){ const rows=bookRows,legs=bookLegs; if(!rows.length){book.innerHTML="";syncBook(0);return} const wasOpen=bookOpenCards(),wasAt=book.scrollTop; let own="",rest="",nOwn=0,nRest=0,hidden=0; const now=BigInt(Math.floor(Date.now()/1e3)); bookOrder(rows).forEach(i=>{ const r=rows[i],[a,bg]=legs[i]||[]; if(!a||!bg)return; const mine=r.maker.toLowerCase()===me(); const expired=!!(r.exp&&now>r.exp); const tags=(expired?'<span class="tg w" title="This order has lapsed and can no longer be filled, but its escrow is still held by the board. Reclaim returns it to you.">expired</span>':"") +(r.nA||r.nB||r.isNFT?'<span class="tg">NFT</span>':"") +(r.v2&&!r.pf&&!r.nA&&!r.nB?'<span class="tg">AON</span>':"") +(r.dutch?'<span class="tg">Dutch</span>':"") +(r.floor?'<span class="tg">Bid</span>':"") +(r.floor&&r.isNFT&&r.anyId?'<span class="tg">any id</span>':"") +(r.filled>0n?`<span class="tg">${r.filled<100n?"<1":Math.floor(Number(r.filled)/100)}% filled</span>`:"") +(r.cp!==ZERO?'<span class="tg">private</span>':"") +(!a.ok||!bg.ok?'<a class="tg w" href="'+ZLIST_URL+'" target="_blank" rel="noopener"' +' title="Not on the staked token list. Its symbol and decimals are read straight from the contract' +' and nothing has vouched for it - check the address before filling. Stake on zList to get a real' +' token listed.">unverified</a>':""); let when=expired?" · lapsed "+rel(Number(now-r.exp))+" ago — escrow still held" :r.exp?" · "+rel(Number(r.exp-now))+" left":""; if(!expired&&r.dutch&&r.duration){ const end=r.start+r.duration; const floor=r.initial?(r.endPrice*r.remaining+r.initial-1n)/r.initial:0n; when=now>=end?` · floor reached: ${trimAmt(floor,r.dB)} ${r.sB}` :` · floor ${trimAmt(floor,r.dB)} ${r.sB} in ${rel(Number(end-now))}`; } if(!expired&&r.floor&&r.duration){ const end=r.start+r.duration; when=now>=end?" · window closed" :` · climbs to ${trimAmt(r.initial?r.endPrice*r.aB/r.initial:r.endPrice,r.dA)} ${r.sA} in ${rel(Number(end-now))}`; } const dset=`data-i="${r.id}" data-b="${r.board}" data-v="${r.v2}" data-d="${r.dutch||0}" data-f="${r.floor||0}"`; const takeable=!r.floor||(!r.isNFT&&floorReady); const shrinkable=mine&&!expired&&((r.dutch&&!r.nA&&!r.nB)||r.floor); const editable=mine&&!expired&&r.v2&&!r.dutch&&!r.floor&&!r.nA&&!r.nB; const act=mine?`<button data-x="c" ${dset}>${expired?"Reclaim":"Cancel"}</button>` +(shrinkable?`<button data-x="w" ${dset}>Shrink</button>`:"") +(editable?`<button data-x="r" ${dset}>Edit</button>`:"") :takeable?`<button data-x="f" ${dset}>${r.floor?"Sell into":"Fill"}</button>` :`<button disabled>${r.isNFT?"NFT bid":"Not routable"}</button>`; const card=r.board.toLowerCase()!==SB1.toLowerCase(); const row=`<div class="ow"><div class="o${card?" ins":""}"${card?` data-k="${r.board}:${r.id}" role="button" tabindex="0" aria-expanded="false" title="Show the on-chain receipt"`:""}>` +`<span class="l">${a.ic}${bg.ic}<span><b>${a.txt} → ${bg.txt}</b>` +`<i>${pxOf(r,a,bg)}${a.link}${tags}${when}</i></span></span>${act}</div><div class="ic"></div></div>`; if(mine){own+=row;++nOwn;return} if(!(bookPairOnly?rowInPair(r):true)||!rowIsType(r)){++hidden;return} rest+=row;++nRest; }); let html=""; if(nOwn)html+=`<h6><span>Your orders</span><span>${nOwn}</span></h6>`+own; html+=bookFilters(hidden); if(nRest)html+=rest; else html+=`<div class="lqempty">${emptyBook()}${hidden?` ${hidden} hidden by the filters.`:""}</div>`; book.innerHTML=html; bookReop
0x68575b07…67c7·#25,913,250·0x1cb542cc…c10cbf
lipBps>slipBps)rate.textContent+=` · Split — bound widened to ${Number(r.slipBps)/100}%`; if(bookIncomplete)rate.textContent+=" · Book scan capped"; if(plannerBounded)rate.textContent+=" · Book split approximate"; if(bookUnavailable)rate.textContent+=" · AMM only (books offline)"; if(pq&&pq.capped)rate.textContent+=` · Precision scan capped (${LQ_SCAN})`; if(isIn&&f.launched&&(t.addr===ZERO||t.addr.toLowerCase()===WETH.toLowerCase())){ const floorOut=await quoteFloor(f.addr,swapAmt,quoteBlock); if(my!==seq)return; if(floorOut>0n){ if(floorOut>finalOut){ rate.textContent+=` \u00b7 Floor pays more: ${trimAmt(floorOut,18)} ETH`; rate.title=`This coin can be burned back to the launcher for its share of the ether ` +`locked in its pool - ${trimAmt(floorOut,18)} ETH, against ${trimAmt(finalOut,18)} from selling. ` +`Redeem through the launcher at ${PLAUNCH} to take the larger.`; }else{ rate.textContent+=` \u00b7 Floor ${trimAmt(floorOut,18)} ETH`; rate.title=`The ether backing this amount if it were redeemed instead of sold. ` +`Selling pays more right now.`; } } } let refQ=iRef>=0&&dec[iRef]?dec[iRef].best:null; let refPair=null; if(!refQ&&isIn&&pq&&pq.out>0n&&refAmt>0n){ try{ const small=await quotePrecision(f,t,refAmt,PROUTE,quoteBlock); if(my!==seq)return; if(small&&small.out>0n)refPair={amountOut:small.out}; }catch{} } if(my!==seq)return; const impSrc=refQ||refPair; const imp=impSrc?impactBps(isIn?finalOut:finalIn,isIn?impSrc.amountOut:impSrc.amountIn,!isIn):null; const mkt=impSrc?(isIn?impSrc.amountOut:impSrc.amountIn)*REF_N:0n; const lossTok=imp!==null&&mkt?(isIn?(mkt>finalOut?mkt-finalOut:0n):(finalIn>mkt?finalIn-mkt:0n)):0n; if(imp!==null&&imp>=IMPACT_HIDE){ const pctTxt=(Number(imp)/100).toFixed(2); rate.textContent+=` · Impact ${pctTxt}%`; if(imp>=IMPACT_WARN)stat.textContent=`High price impact: ${pctTxt}% — about ${trimAmt(lossTok,isIn?t.dec:f.dec)} ${isIn?t.sym:f.sym} worse than the market rate.`; } if(rankBox.firstChild){ const car=document.createElement("span"); car.className="rk"; car.textContent="\u25be"; car.title="Compare venues"; car.setAttribute("role","button"); car.tabIndex=0; car.setAttribute("aria-expanded",rankBox.classList.contains("hide")?"false":"true"); const carToggle=()=>{const shut=rankBox.classList.toggle("hide"); rankOpen=!shut; car.setAttribute("aria-expanded",shut?"false":"true")}; car.onclick=carToggle; car.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();carToggle()}}; rate.appendChild(document.createTextNode(" ")); rate.appendChild(car); } const inApprove=isIn?swapAmt:r.msgValue||r.amountLimit; const shownPay=amt.value,shownGet=outAmt.value; if(acct)last={callData:r.callData,fundedCallData:r.fundedCallData,msgValue:r.msgValue, limit:r.amountLimit,shownPay,shownGet, to:r.to,spender:r.spender, amountIn:inApprove,from:f,recipient:rcv||"",exp:Date.now()+QUOTE_TTL,impact:imp, lossTok,lossSym:isIn?t.sym:f.sym,lossDec:isIn?t.dec:f.dec, fills:r.fills||null,tokenIn:f.addr,tokenOut:t.addr, source:r.best.source, wrap:r.wrap||0n,pools:r.pools||null, mkt,got:isIn?finalOut:finalIn,isIn}; quoting=false; render(); }catch(e){if(my===seq){ quoting=false; const transport=!!e?.noWallet||mcGaveUp>gaveUp0||(mcFailed>failed0&&mcOk===ok0); if(!transport&&!e?.exactOut&&advancePastDeadPair()){loadChart();update();return} outEl.value="";fitFont(outEl);rankBox.textContent="";rankBox.classList.add("hide");render(); stat.textContent=e?.noWallet?NO_WALLET :e&&e.exactOut?e.message :transport ?"Your RPC could not complete the quote — some venues were never asked. Try again, or switch networks in your wallet and back." :"No route: "+(e.message||e)}} } let debounceT=0; const showNet=()=>{ const e=document.getElementById("net"); if(!e)return; const c=chainCfg(); e.innerHTML=(c.icon||"")+"<span>"+c.short+"</span>"; e.title="Network: "+c.name+" — click to switch"; }; const switchNet=async()=>{ const ids=Object.keys(CHAINS).map(Number); const next=ids[(ids.indexOf(CHAIN_ID)+1)%ids.length]; try{ await rpc("wallet_switchEthereumChain",[{chainId:"0x"+next.toString(16)}]); }catch(e){ if(e&&e.code===4902){ const c=CHAINS[next]; try{await rpc("wallet_addEthereumChain",[{chainId:"0x"+next.toString(16),chainName:c.name, nativeCurrency:{name:"Ether",symbol:"ETH",decimals:18},rpcUrls:c.rpcs,blockExplorerUrls:[c.explorer]}])}catch{return} }else return; } setChain(next);showNet();location.reload(); }; const DEEPSTATE="0x6cf19308c22fc82ea620fa0b3e94948d20f27b96"; const SEL_POOLID="1338aa1d",SEL_POOLEPOCH="8fa79763",SEL_SWAPDEEP="ad33b1d0"; const OZ20="52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00"; const DEEP_TAKER="0x00000000000000000000000000000000deeb0000"; const MAXU=(1n<<256n)-1n; const slotKey=(k,sl)=>strip0x(keccak(hexToBytes(k+sl))); const deepSlots={}; const findSlot=async(tok,sel,keyw,probe)=>{ const ck=tok+sel; if(deepSlots[ck]!==undefined)return deepSlots[ck]; const cands=[OZ20,(BigInt("0x"+OZ20)+1n).toString(16).padStart(64,"0")]; for(let i=0;i<10;i++)cands.push(encUint(i)); for(const c of cands){ const sl=keyw.length>64?slotKey(keyw.slice(64),slotKey(keyw.slice(0,64),c)):slotKey(keyw,c); try{ const got=await rpc(C,[{to:tok,data:"0x"+sel+keyw},L,{[tok]:{stateDiff:{["0x"+sl]:"0x"+encUint(probe)}}}]); if(BigInt(got||0)===probe){deepSlots[ck]=c;return c} }catch{return null} } deepSlots[ck]=null;return null; }; const deepQuote=async(fa,ta,amt,expOut)=>{ if(CHAIN_ID!==4663||!amt||fa===ZERO||ta===ZERO)return null; const [t0,t1]=fa.toLowerCase()<ta.toLowerCase()?[fa,ta]:[ta,fa]; const isBid=ta.toLowerCase()===t0.toLowerCase(); try{ const pid=await rpc(C,[{to:DEEPSTATE,data:"0x"+SEL_POOLID+encAddr(t0)+encAddr(t1)},L]); if(!pid||pid==="0x")return null; const ep=await rpc(C,[{to:DEEPSTATE,data:"0x"+SEL_POOLEPOCH+strip0x(pid)},L]); const epoch=BigInt(ep||0); const bs=await findSlot(fa,"70a08231",encAddr(DEEP_TAKER),0x1234567n); if(bs===null)return null; const as=await findSlot(fa,"dd62ed3e",encAddr(DEEP_TAKER)+encAddr(ZROUTER),0x7654321n); if(as===null)return null; const ov={[fa]:{stateDiff:{ ["0x"+slotKey(encAddr(DEEP_TAKER),bs)]:"0x"+encUint(amt), ["0x"+slotKey(encAddr(ZROUTER),slotKey(encAddr(DEEP_TAKER),as))]:"0x"+encUint(MAXU)}}}; const price=isBid?0x7fffffffn:0x80000000n; const seed=isBid?(expOut||0n):amt; if(seed<=0n)return null; let best=null; for(let q=seed*4n,n=0;n<5&&q>0n;q/=4n,n++){ const order="0x"+encUint((price<<224n)|((q&((1n<<160n)-1n))<<64n)); const data="0x"+SEL_SWAPDEEP+encAddr(DEEP_TAKER)+encAddr(t0)+encAddr(t1)+encUint(epoch) +strip0x(order)+encUint(isBid?1:0)+encUint(amt)+encUint(0n)+encUint(MAXU); try{ const r=await rpc(C,[{from:DEEP_TAKER,to:ZROUTER,data},L,ov]); const b=strip0x(r||""); if(b.length<128)continue; const ai=BigInt("0x"+b.slice(0,64)),ao=BigInt("0x"+b.slice(64,128)); if(ao>0n&&(!best||ao>best.out))best={inUsed:ai,out:ao,t0,t1,isBid,epoch,order}; }catch{} } return best; }catch{return null} }; const showAddr=async()=>{const a=account,short=a.slice(0,6)+"..."+a.slice(-4); addr.textContent=short;addr.title=a+" - click to disconnect";addr.classList.add("on"); addr.setAttribute("role","button");addr.tabIndex=0; const n=await nameRev(a);if(n&&a===account)addr.textContent=n}; async function disconnect(){ sessionStorage.setItem("dc","1"); try{delete LS[WK]}catch{} location.reload(); } addr.onclick=()=>{account?disconnect():connect()} net.onclick=()=>{switchNet()} net.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();switchNet()}} showNet();; addr.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();account?disconnect():connect()}}; const AUTO_Q_MAX=5; const updateSoon=()=>{clearTimeout(debounceT);debounceT=setTimeout(()=>{debounceT=0;update()},250)}; const setMode=m=>{++seq;quoting=false;mode=m;const o=m==="in"?outAmt:amt;o.value="";fitFont(o);last=null;sendReady=null;render()}; amt.addEventListener("input",()=>{ if(lqMode){fitFont(amt);lqPreviewSoon();return} if(tab==="book"){fitFont(amt);render();return}setMode("in");fitFont(amt);updateSoon()}); outAmt.addEventListener("input",()=>{ if(lqMode){fitFont(outAmt);lqPreviewSoon();return} if(tab==="book"){fitFont(outAmt);seedFloor();render();return}setMode("out");fitFont(outAmt);updateSoon()}); const slipWarn=()=>slip.classList.toggle("warn",+slip.value>=3); if(LS.slip&&+LS.slip>=.01&&+LS.slip<=10)slip.value=LS.slip; slipWarn(); slip.addEventListener("input",()=>{last=null;slipWarn();render();updateSoon()}); rc.addEventListener("input",()=>{ if(lnMode)return; if(lqMode){clearTimeout(lqToT);lqToT=setTimeout(lqShowTo,300);return} last=null;sendReady=null;render();updateSoon()}); let lqToT; const onEnter=e=>{ if(e.key!=="Enter")return; const go=lnMode?lnGo:swap; if(!go||go.classList.contains("hide"))return; e.preventDefault(); if(debounceT){clearTimeout(debounceT);debounceT=0;update();return} if(!go.disabled)go.click(); }; for(const el of[amt,outAmt,rc,slip,floorAmt,nftId,lnName,lnSym,lnSupply,lnMcap,lnAlloc,lnGoal,lnDays]) if(el)el.addEventListener("keydown",onEnter); slip.addEventListener("change",()=>{slip.value=Math.min(10,Math.max(.01,+slip.value||.5));slipWarn();LS.slip=slip.value}); for(const sel of [fromSel,toSel]){ sel.dataset.prev=sel.value; sel.addEventListener("change",async()=>{ if(sel.value!=="__custom"){ sel.dataset.prev=sel.value; sel.dataset.addr=TOKENS[sel.value]?.addr||""; pairChosen=true; syncDisabled(sel); syncNftUI(); if(sel===fromSel)refreshBalance();else refreshBalance1(); loadChart(); if(tab==="book"&&bookRows.length)paintBook(); update(); return; } last=null;render(); const prev=sel.dataset.prev; let a; try{a=(prompt("Paste token address:")||"").trim()} catch{sel.value=prev;syncDisabled(); stat.textContent="This browser blocked the address prompt — add the token with a #token=0x… link instead."; return} if(!a){sel.value=prev;update();return} stat.textContent="Loading token..."; try{ const idx=await addCustomToken(a); rebuild(); const otherSel=sel===fromSel?toSel:fromSel; if(String(idx)===otherSel.value){otherSel.value=prev;otherSel.dataset.prev=prev} sel.value=String(idx); sel.dataset.prev=sel.value; pairChosen=true; stat.textContent=""; syncDisabled(sel);syncNftUI(); refreshBalance(); update(); }catch(e){ const msg="Couldn't load token: "+(e.message||e); sel.value=prev; syncDisabled();syncNftUI();refreshBalance();update(); stat.textContent=msg; } }); } flip.onclick=()=>{pairChosen=true;const a=fromSel.value,b=toSel.value,v=outAmt.value.replace(/,/g,"");fromSel.value=b;toSel.value=a; amt.value=(v&&!isNaN(+v))?v:"";setMode("in");fitFont(amt);syncDisabled();syncNftUI();refreshBalance();loadChart();update()}; const sameAcct=(a,b)=>!!a&&!!b&&a.toLowerCase()===b.toLowerCase(); const wkBind=eth=>{ if(!eth||eth.__zb)return; eth.__zb=1; eth.on?.("chainChanged",cid=>{if(connecting)return;if(CHAINS[+cid]&&account){setChain(+cid);location.reload();return}location.reload()}); eth.on?.("accountsChanged",accs=>{if(connecting||sameAcct(accs?.[0],account))return;location.reload()}); }; const wireWallet=eth=>{ if(!eth||wireWallet.done)return; wireWallet.done=true; wkBind(eth); (async()=>{ if(sessionStorage.getItem("dc"))return; try{ const accs=await rpc("eth_accounts",[]); const cid=await rpc(I,[]); wireWallet.probed=1; const hop=!!CHAINS[+cid]&&+cid!==CHAIN_ID; if(hop){setChain(+cid);showNet()} if(accs[0]&&CHAINS[+cid]){account=accs[0];showAddr()} if(account||hop){ const keep2=pairAddrs(); await loadTokenList(); adoptList(keep2); if(hop&&location.hash.length>1)applyLink(); refreshBalance();update();if(account)loadPos();syncChart(); } }catch{} })(); (async()=>{ const self=selfFromUrl()||ZSWAP_SELF; if(!self)return; try{ if(+(await rpc(I,[]))!==CHAIN_ID)return; const ret=strip0x(await rpc(C,[{to:self,data:"0x"+SEL_LATEST},L])||""); if(ret.length!==64)return; let tip="0x"+ret.slice(24); const me=self.toLowerCase(); if(BigInt(tip)===0n||tip===me)return; const rd=async(to,sel)=>{const h=strip0x(await rpc(C,[{to,data:"0x"+sel},L])||"");return h.length===64?h:""}; const now=Date.now()/1000; for(let i=0;i<32&&tip!==me;i++){ const pv=await rd(tip,SEL_PREV); if(!pv)break; const prev="0x"+pv.slice(24); if(BigInt(prev)===0n)break; const at=await rd(prev,SEL_SUCCAT); const born=at?Number(BigInt("0x"+at)):0; if(!born||now>=born+MATURITY)break; tip=prev; } if(tip===me)return; const h=(location.hostname||"").split("."); const url=/^0x[0-9a-fA-F]{40}$/.test(h[0]||"") ?`${location.protocol}//${[tip,...h.slice(1)].join(".")}/` :`https://${tip}.w4eth.io/`; footAddr.innerHTML+=` · <a href="${url}" target="_blank" rel="noopener" ` +`title="A newer version exists on chain. This page is unchanged and always will be; the link opens the successor.">newer →</a>`; }catch{} })(); }; const wkGo=()=>{wireWallet(prov()); if(!prov())window.addEventListener("ethereum#initialized",()=>wireWallet(prov()),{once:true})}; if(!PROV&&LS[WK]&&LS[WK]!=="wc")wkRestore().then(wkGo,wkGo);else wkGo(); setTimeout(burnTotal,0); setTimeout(loadEthUsd,0); const SEL_TRANSFER="a9059cbb"; let SB2=MB.sb; let SB1=MB.s1; const SBVIEW="0x000000E0b25449F32f7D9259aC449bA88E78dFCE"; let SWAPBOL=MB.sw; let DUTCH=MB.du; let ORDERBOL=MB.ob; let FLOOR=MB.fl; const FLOORVIEW="0x0000004E376e9dB5D9EC28E6711E1a64997C6ba7"; const SEL_NEXTID="2a58b330",SEL_NEXTID2="61b8ce8c",SEL_GETORDERS="03652027",SEL_CANCELORD="514fcac7",SEL_EXPIRED="d6c3de07", SEL_WITHDRAW="ef3d3521",SEL_REPLACE="84343d3a"; const SEL_FILL1="c37dfc5b",SEL_FILL2="9d136c7f",SEL_FILL2_UNWRAP="3987baf4",SEL_FILL2_ETH="6f608bab"; const SEL_RECENT="6a9849c1",SEL_RECENT_DUTCH="98035c9a",SEL_DUTCH_FILL="ae7a8260",SEL_DUTCH_CANCEL="40e58ee5"; const SEL_CANCEL_UNWRAP="21dd76f9",SEL_DUTCH_CANCEL_UNWRAP="8382de65"; const SEL_DUTCH_LISTING="de74e57b"; const SEL_CANDS="5f452988",SEL_DUTCH_CANDS="eb33e466",SEL_FILLPLAN="c277f67c"; const SEL_FILLPLAN_SWAP="9090c8e5",SEL_SNWAP="5f3bd1c8"; const SEL_COLL_BIDS="16bb24eb"; const SEL_FLOOR_CANDS="c587d927",SEL_RECENT_FLOOR="a5edd13d"; const SEL_ORDER_FLOOR="23e93357"; const SEL_BOL_FLOOR="b732d224"; let floorReady=null; async function floorRoutable(block){ if(FLOOR===ZERO||FLOORVIEW===ZERO)return false; if(floorReady!==null)return floorReady; try{ const h=await rpc(C,[{to:SWAPBOL,data:"0x"+SEL_BOL_FLOOR},block||L]); floorReady=("0x"+strip0x(h).slice(-40)).toLowerCase()===FLOOR.toLowerCase(); }catch{return false} return floorReady; } const SEL_BIDS="4423c5f1"; const SEL_INITIAL_B="878ea250",SEL_TOKENURI="c87b56dd",SEL_LOGOOF="6ce273ac"; const SEL_QUOTEFILL="f325beda"; async function preflightLeg(row,routeIn,routeOut,pay){ if(!row.floor)return preflightAsk(row,pay); if(!await floorRoutable(L))throw Error("this executor cannot route bids yet"); const want=routeIn===ZERO?WETH:routeIn,gets=routeOut===ZERO?WETH:routeOut; let h; try{h=await rpc(C,[{to:FLOOR,data:"0x"+SEL_BIDS+encUint(row.id)},L])} catch{throw Error("bid could not be read — refresh and retry")} const at=i=>strip0x(h).slice(i*64,(i+1)*64); if(strip0x(h).length<11*64)throw Error("bid could not be read — refresh and retry"); const addr=i=>"0x"+at(i).slice(24); if(/^0x0{40}$/i.test(addr(0)))throw Error("bid is closed — refresh and retry"); if(BigInt("0x"+at(1)))throw Error("NFT bids are not routable from here"); if(addr(4).toLowerCase()!==want.toLowerCase() ||addr(6).toLowerCase()!==gets.toLowerCase()) throw Error("bid changed — refresh and retry"); if(BigInt("0x"+at(10))<pay)throw Error("bid partly taken — refresh and retry"); } async function preflightFills(fills,tokenIn,tokenOut){ if(!fills||!fills.length)return; const inA=(tokenIn===ZERO?WETH:tokenIn).toLowerCase(); const outA=(tokenOut===ZERO?WETH:tokenOut).toLowerCase(); const reads=fills.map(f=>f.board.toLowerCase()===FLOOR.toLowerCase() ?{to:FLOOR,data:"0x"+SEL_BIDS+encUint(f.id)} :f.board.toLowerCase()===DUTCH.toLowerCase() ?{to:DUTCH,data:"0x"+SEL_DUTCH_LISTING+encUint(f.id)} :{to:f.board,data:"0x"+SEL_GETORDERS+encUint(32)+encUint(1)+encUint(f.id)}); const res=await mc3Deep(reads); const stale=()=>{throw Error("an order in this route just changed — refreshing")}; for(let i=0;i<fills.length;i++){ const f=fills[i],raw=res[i]; if(!raw)stale(); const s=strip0x(raw),at=k=>BigInt("0x"+s.slice(k*64,(k+1)*64)); if(f.board.toLowerCase()===FLOOR.toLowerCase()){ if(s.length<11*64)stale(); const ad=k=>"0x"+s.slice(k*64+24,(k+1)*64); if(/^0x0{40}$/i.test(ad(0))||at(1))stale(); if(ad(4).toLowerCase()!==inA||ad(6).toLowerCase()!==outA)stale(); if(at(10)<f.pay)stale(); }else if(f.board.toLowerCase()===DUTCH.toLowerCase()){ if(s.length<11*64||at(0)===0n)stale(); if(at(9)<f.get)stale(); if(at(10)&&BigInt(Math.floor(Date.now()/1e3))>at(10))stale(); }else{ const wds=WORDS(f.board.toLowerCase()===SB2.toLowerCase()?1:0); if(s.length<(2+wds)*64)stale(); const w=k=>at(2+k),ad=k=>"0x"+s.slice((2+k)*64+24,(3+k)*64); if(w(1)!==1n)stale(); const v2=wds===11,iA=v2?7:2,iB=v2?9:4; if(ad(iA).toLowerCase()!==outA||ad(iB).toLowerCase()!==inA)stale(); if(w(iB+1)<f.pay)stale(); if(v2&&w(iA+1)<f.get)stale(); if(v2&&w(3)&&BigInt(Math.floor(Date.now()/1e3))>w(3))stale(); } } } async function preflightAsk(row,pay){ if(row.dutch){ let h; try{h=await rpc(C,[{to:DUTCH,data:"0x"+SEL_DUTCH_LISTING+encUint(row.id)},L])} catch{throw Error("listing could not be read — refresh and retry")} const s=strip0x(h); if(s.length<640)throw Error("listing could not be read — refresh and retry"); if(BigInt("0x"+s.slice(0,64))===0n)throw Error("listing is closed — refresh and retry"); return; } let h; try{h=await rpc(C,[{to:row.board, data:"0x"+SEL_GETORDERS+encUint(32)+encUint(1)+encUint(row.id)},L])} catch{throw Error("order could not be read — refresh and retry")} const s=strip0x(h),wds=WORDS(row.v2); if(s.length<(2+wds)*64)throw Error("order could not be read — refresh and retry"); const at=k=>BigInt("0x"+s.slice(128+k*64,128+(k+1)*64)); const ad=k=>"0x"+s.slice(128+k*64+24,128+(k+1)*64); if(at(1)!==1n)throw Error("order is no longer active — refresh and retry"); const iA=row.v2?7:2,iB=row.v2?9:4; if(ad(iA).toLowerCase()!==row.tA.toLowerCase() ||ad(iB).toLowerCase()!==row.tB.toLowerCase()) throw Error("order changed — refresh and retry"); if(at(iA+1)>row.aA||at(iB+1)>row.aB)throw Error("order changed — refresh and retry"); if(pay!==undefined&&at(iB+1)<pay)throw Error("order partly taken — refresh and retry"); } const SEL_ORDER_FIXED="bcdb7936",SEL_ORDER_DUTCH="bbd94e1a",SEL_CHECKPOINT="a972985e"; const BOARDS=()=>[{a:SB2,v2:1},{a:SB1,v2:0}].filter(b=>b.a!==ZERO); const WORDS=b=>b?11:6; let bookSeq=0,bookRows=[],candCache={},bkOpen=LS.bk!=="0",bookTrunc=false; let busy=0; if(LS.wm==="weth"||LS.wm==="auto")ethMode.value=LS.wm; ethMode.onchange=()=>{LS.wm=ethMode.value}; const wantsUnwrap=()=>ethMode.value!=="weth"; const planWethPayment=async need=>{ let w=0n,e=0n; try{ const[wh,eh]=await Promise.all([ rpc(C,[{to:WETH,data:"0x"+SEL_BALANCEOF+encAddr(account)},L]), rpc("eth_getBalance",[account,L])]); w=BigInt(wh);e=BigInt(eh); }catch{return{rail:"weth",wrap:0n,w,e}} if(w>=need)return{rail:"weth",wrap:0n,w,e}; if(e>=need)return{rail:"eth",wrap:0n,w,e}; let gas=0n; try{gas=BigInt(await rpc("eth_gasPrice",[]))*400000n}catch{} const spendable=e>gas?e-gas:0n; if(w+spendable>=need)return{rail:"weth",wrap:need-w,w,e}; return{rail:"short",wrap:0n,w,e}; }; const assertFunded=async(token,need,meta)=>{ if(!need)return; let have; try{ have=token===ZERO ?BigInt(await rpc("eth_getBalance",[account,L])) :BigInt(await rpc(C,[{to:token,data:"0x"+SEL_BALANCEOF+encAddr(account)},L])); }catch{return} if(have>=need)return; const m=meta||known(token)||{sym:token===ZERO?"ETH":"tokens",dec:18}; throw Error(`Not enough ${m.sym} to fill: it costs ${trimAmt(need,m.dec)} ` +`and you hold ${trimAmt(have,m.dec)}.`); }; const known=a=>TOKENS.find(t=>t.addr.toLowerCase()===a.toLowerCase()); const sm=i=>i.replace('width="20" height="20"','width="15" height="15"'); const escan=(a,id)=>!isAddr(a)?"":`<a href="${chainCfg().explorer}/${id!=null?`nft/${a}/${id}`:`token/${a}`}" target="_blank" rel="noreferrer">${a.slice(0,6)}…${a.slice(-4)} ↗</a>`; const SEL_CONTRACTURI="e8a3d485"; const okDataImg=u=>typeof u==="string"&&u.length<=60000 &&/^data:image\/[a-z.+-]{2,20};base64,[A-Za-z0-9+/=]+$/i.test(u); const imgFromContractURI=raw=>{ const P="data:application/json;base64,"; const u=String(raw||"").trim(); if(!u.startsWith(P)||u.length>400000)return ""; let txt; try{ const bin=atob(u.slice(P.length)); txt=new TextDecoder().decode(Uint8Array.from(bin,c=>c.charCodeAt(0))); }catch{return ""} let j;try{j=JSON.parse(txt)}catch{return ""} return okDataImg(j&&j.image)?j.image:""; }; const capObj=(o,max)=>{const ks=Object.keys(o); for(let i=0;i<ks.length-max;i++)delete o[ks[i]]}; const capMap=(m,max)=>{while(m.size>max)m.delete(m.keys().next().value)}; const LOGO_MAX=192,CARD_MAX=64,META_MAX=256; const logoCache={}; async function loadLogos(addrs){ const want=[...new Set(addrs.map(a=>a.toLowerCase()))] .filter(a=>a!==ZERO&&!known(a)&&logoCache[a]===undefined); if(!want.length)return; let raw=[]; if(TOKENLIST!==ZERO)raw=await cfgBatch(want.map(a=>({to:TOKENLIST,data:"0x"+SEL_LOGOOF+encAddr(a)}))).catch(()=>[]); want.forEach((a,i)=>{let u="";try{if(raw[i])u=safeDataUrl(decodeString(raw[i]))}catch{}logoCache[a]=u}); capObj(logoCache,LOGO_MAX); const self=want.filter(a=>!logoCache[a]); if(!self.length)return; const cu=await mc3Deep(self.map(a=>({to:a,data:"0x"+SEL_CONTRACTURI}))).catch(()=>[]); self.forEach((a,i)=>{try{ const u=imgFromContractURI(cu[i]?decodeString(cu[i]):""); if(u)logoCache[a]=u; }catch{}}); } async function leg(addr,amt,isNft,meta){ const k=known(addr); if(isNft)return{ic:genIcon("#"),txt:`#${amt}`,ok:0,link:escan(addr,amt)}; const raw=meta&&meta.sym?meta:await tokMeta(addr),m={...raw,sym:safeSym(raw.sym)}; const lg=k?"":logoCache[addr.toLowerCase()]; const ic=k?sm(k.icon):lg?`<img src="${lg}" width="15" height="15">`:genIcon(m.sym); return{ic,txt:`${trimAmt(amt,m.dec)} ${k?safeSym(k.sym):'“'+m.sym+'”'}`,ok:!!k, sym:k?safeSym(k.sym):m.sym,dec:m.dec,link:addr===ZERO?"ETH":escan(addr)}; } let floorSeq=0; nftId.addEventListener("input",()=>{syncOrderType();showFloor();render()}); async function showFloor(){ const el=document.getElementById("floorNote");if(!el)return; const side=isNft(fromSel.value)?fromSel.value:isNft(toSel.value)?toSel.value:null; const live=tab==="book"&&side!==null&&!nftId.value.trim(); if(!live){el.classList.add("hide");el.textContent="";return} const seq=++floorSeq,t=TOKENS[side]; el.classList.remove("hide");el.textContent="Checking floor bids…"; const oth=isNft(fromSel.value)?toSel.value:fromSel.value; const qt=isNft(oth)?ZERO:(TOKENS[oth].addr===ZERO?WETH:TOKENS[oth].addr); const rows=await collectionFloor(t.addr,qt).catch(()=>[]); if(seq!==floorSeq)return; if(!rows.length){el.textContent="No standing bids on this collection.";return} const best=rows[0],q=known(best.quote); el.textContent=`${rows.length} collection bid${rows.length>1?"s":""} · best pays ` +`${trimAmt(best.price/best.initial,q?q.dec:best.qDec??18)} ${q?q.sym:safeSym(best.qSym||"?")} for any ${t.sym}`; } function decBidRows(hex){ const out=[]; try{ const h=strip0x(hex),bytes=h.length/2; const word=b=>{if(b<0||b+32>bytes)throw 0;return BigInt("0x"+h.slice(b*2,(b+32)*2))}; const num=b=>{const v=word(b);if(v>0xffffffffffffffffn)throw 0;return Number(v)}; const arr=num(0); if(arr<64||arr+32>bytes)throw 0; const n=num(arr),base=arr+32; if(n>256||base+n*32>bytes)throw 0; for(let i=0;i<n;i++){ try{ const row=base+num(base+i*32); if(row+17*32>bytes)throw 0; const f=k=>word(row+k*32); const txt=k=>{const p=row+num(row+k*32),len=num(p); if(p+32+len>bytes)throw 0; return safeSym(new TextDecoder().decode(hexToBytes(h.slice((p+32)*2,(p+32+Math.min(len,1024))*2))))}; const dec=k=>{const v=num(row+k*32);return v>0&&v<=37?v-1:null}; out.push({ id:f(0), bidder:"0x"+h.slice((row+1*32+12)*2,(row+2*32)*2), to
0x68575b07…67c7·#25,913,248·0x5fc60587…ede2da
isabled=true; let accs; try{accs=await rpc("eth_requestAccounts",[])} catch(e){err(e);return} if(!accs[0])return; let chainId=await rpc(I,[]); if(!CHAINS[+chainId]){ try{await rpc("wallet_switchEthereumChain",[{chainId:"0x1"}])} catch(e){stat.textContent=isRejection(e)?"":"Switch to a supported network.";return} chainId=await rpc(I,[]); if(!CHAINS[+chainId]){stat.textContent="Switch to a supported network.";return} } setChain(+chainId);showNet(); sessionStorage.removeItem("dc"); account=accs[0];sOpen(); const keepPair=pairAddrs(); showAddr(); if(!listLive){ swap.textContent="Loading tokens\u2026"; await loadTokenList(); } adoptList(keepPair); refreshBalance(); update(); fcMine(); loadPos(); syncChart(); if(lnMode)lnKindQuote(); }finally{connecting=false;swap.disabled=false; lnGo.disabled=false;lnGo.textContent=account?"Launch":"Connect Wallet"; wnGo.disabled=false; if(wnMode){wnPaint();wnQuote();wnRoll()}else wnGo.textContent="Register"; if(pvMode)pvWake();render()} } const lqReserveGas=()=>lqList.querySelector(".lqnew")?LQ_CREATE_GAS+300000n:400000n; const lqFillMax=(tok,max)=>{ const box=lqList.querySelector(".lqadd:not(.hide)"); const pair=chPair(); if(!box||!pair)return false; const side=pair[0].addr.toLowerCase()===tok.addr.toLowerCase()?"0":"1"; const el=box.querySelector(`.lqin[data-side="${side}"]`); if(!el)return false; el.value=formatUnits(max,tok.dec); el.dispatchEvent(new Event("input",{bubbles:true})); return true; }; bal1.addEventListener("click",async e=>{ if(e.target.tagName!=="A")return; e.preventDefault(); const t=TOKENS[toSel.value]; if(!t)return; let reserve=0n; if(t.addr===ZERO){ const g=lqReserveGas(); try{reserve=BigInt(await rpc("eth_gasPrice",[]))*g}catch{} if(reserve<=0n)reserve=GAS_FLOOR*g; } const max=toBalance>reserve?toBalance-reserve:0n; if(lqFillMax(t,max))return; outAmt.value=formatUnits(max,t.dec); fitFont(outAmt); lqPreviewSoon(); }); bal.addEventListener("click",async e=>{ if(e.target.tagName!=="A")return; e.preventDefault(); const f=TOKENS[fromSel.value]; if(!f)return; let reserve=0n; if(f.addr===ZERO){ const g=lqMode?lqReserveGas():tab==="swap"?900000n:tab==="book"?350000n :+dly.value?(tipCk.checked?200000n+TIP_GAS:200000n):30000n; try{reserve=BigInt(await rpc("eth_gasPrice",[]))*g}catch{} if(reserve<=0n)reserve=GAS_FLOOR*g; } const max=fromBalance>reserve?fromBalance-reserve:0n; if(lqMode&&lqFillMax(f,max))return; amt.value=formatUnits(max,f.dec); fitFont(amt); if(lqMode){lqPreviewSoon();return} setMode("in"); update(); }); let seq=0; const REVERTS={ "1ab7da6b":"That took too long and the deadline passed, so nothing was placed. Try again — you were not charged for an order.", "98c2c368":"The order was refused as malformed. Check the amounts and the expiry.", "7939f424":"The token transfer was refused — usually a missing or spent approval. Try again and approve first.", "90b8ec18":"The token transfer was refused by the token itself.", "3e3f8f73":"The approval was refused by the token.", "6fc70753":"That score name is not a valid label.", "9c9cef39":"That score is too long to record.", "7b2002ce":"Somebody already claimed that name — play again for a new one.", "845eadf1":"The score minter is not holding arcade.wei.", "b12d13eb":"Ether could not be sent to the recipient — a contract there may not accept it.", "ab143c06":"That call re-entered and was stopped.", }; const explain=e=>{ if(e?.noWallet)return NO_WALLET; const str=v=>typeof v==="string"?v :v&&typeof v.data==="string"?v.data :v&&typeof v.message==="string"?v.message:""; const s=str(e?.data)||str(e)||str(e?.error)||""; const m=/0x([0-9a-fA-F]{8})\b/.exec(s); const hit=m&&REVERTS[m[1].toLowerCase()]; if(hit)return hit; if(!s)return "The wallet returned an error with no message"+(e&&e.code!==undefined?" (code "+e.code+")":"")+"."; return "Error: "+s.slice(0,110); }; const err=e=>stat.textContent=isRejection(e)?"":explain(e); const settle=async(tx,quiet)=>{ if(!tx){stat.textContent="Done";return} if(!/^0x[0-9a-f]{64}$/i.test(tx))throw Error("bad tx"); stat.innerHTML=txLink(tx,"Sent"); try{await waitTx(tx)}catch(e){sFail();throw e} stat.innerHTML=txLink(tx,"Done");if(!quiet)sGot()}; const txLink=(h,label)=>`${label} <a href="${chainCfg().explorer}/tx/${h}" target="_blank" rel="noreferrer">${h.slice(0,10)}…${h.slice(-6)}</a>`; let autoQ=0,rankOpen=false; async function update(auto){ if(!auto)autoQ=0; syncIcons(); fcSync(); cbSync(); const my=++seq; quoting=false; if(wnMode||lnMode||pvMode)return; if(lqMode)return loadLq(); if(tab==="book")return render(); if(tab==="send")return updateSend(); last=null; rate.textContent=""; if(!lqMode&&!lnMode)rcvHdr.textContent="You receive"; render(); const f=TOKENS[fromSel.value], t=TOKENS[toSel.value]; if(!f||!t)return; if(f.addr===t.addr){stat.textContent="Pick different tokens.";return} stat.textContent=""; const isIn=mode==="in"; const inEl=isIn?amt:outAmt, outEl=isIn?outAmt:amt, inTok=isIn?f:t; outEl.value=""; fitFont(outEl); if(!inEl.value.trim())return; let swapAmt; try{swapAmt=parseUnits(inEl.value.trim(),inTok.dec)} catch(e){const q=inEl.value.trim(); if(q&&!/\.$/.test(q))stat.textContent=/^unclear comma/.test(e.message)?e.message:/decimals/.test(e.message)?`${inTok.sym} has ${inTok.dec} decimals`:"Invalid amount"; return} if(swapAmt<=0n)return; const rv=rc.value.trim(); let rcv=""; if(rv){ const fail=m=>{rc.classList.add("bad");rcvEl.textContent=""; outEl.value="";fitFont(outEl);rankBox.textContent="";rankBox.classList.add("hide");stat.textContent=m}; const bad=rcErr(rv); if(bad){fail(bad);return} if(isAddr(rv))rcv=rv; else{ rcvEl.textContent="resolving..."; try{rcv=await rcvOf(rv)} catch(e){if(my!==seq)return;fail(e&&e.offchain?e.message:"Name not registered");return} if(my!==seq)return; if(!rcv){fail("Name not registered");return} } rcvEl.textContent=rcv; }else rcvEl.textContent=""; rc.classList.remove("bad"); if(!rv&&f.addr===WETH&&t.addr===ZERO){ outEl.value=inEl.value;fitFont(outEl); rate.textContent=`1 ${f.sym} = 1 ${t.sym}`; last={callData:"0x2e1a7d4d"+encUint(swapAmt),msgValue:0n,isIn:true,amountIn:swapAmt,from:f,to:WETH,recipient:"",exp:Date.now()+QUOTE_TTL}; render();return; } if(!rv&&f.addr===ZERO&&t.addr===WETH){ outEl.value=inEl.value;fitFont(outEl); rate.textContent=`1 ${f.sym} = 1 ${t.sym}`; last={callData:"0xd0e30db0",msgValue:swapAmt,isIn:true,amountIn:swapAmt,from:f,to:WETH,recipient:"",exp:Date.now()+QUOTE_TTL}; render();return; } const pct=Math.min(10,Math.max(.01,+slip.value||.5)); const slipBps=BigInt(Math.round(pct*100)); const deadline=BigInt(Math.floor(Date.now()/1e3)+1800); outEl.value="...";fitFont(outEl);quoting=true;render(); const acct=account; const gaveUp0=mcGaveUp,ok0=mcOk,failed0=mcFailed; try{ const a=encAddr(rcv||acct||ZERO),rf=encAddr(acct||ZERO),tl=encBool(!isIn)+encAddr(f.addr)+encAddr(t.addr)+encUint(swapAmt)+encUint(slipBps)+encUint(deadline); const _rq=[]; const Q=(x,d,u,v,S,sb,mv)=>{_rq.push({to:ZQUOTER,data:"0x"+x+d,u,v,S,sb,mv});return _rq.length-1}; let r=null; const candKey=f.addr.toLowerCase()+":"+t.addr.toLowerCase(); const cachedCandidates=candCache[candKey]; const quoteBlock=cachedCandidates&&Date.now()-cachedCandidates.at<15000 ?cachedCandidates.block:await blockNow(); const candJob=acct?swapCandidates(f.addr,t.addr,quoteBlock).catch(()=>{ const x=[];x.unavailable=true;return x }):Promise.resolve([]); const gasPriceJob=acct?rpc("eth_gasPrice",[]).then(BigInt).catch(()=>0n):Promise.resolve(0n); let venueLost=false; const v4Job=isIn?quoteV4Pools(f,t,swapAmt,quoteBlock).catch(()=>{venueLost=true;return null}):Promise.resolve(null); const preJob=isIn?quotePrecision(f,t,swapAmt,PROUTE,quoteBlock).catch(()=>{venueLost=true;return null}):Promise.resolve(null); let curveOnlyOut=false; const exactOutSafe=y=>{if(isIn||!y.sources||!y.sources.includes(SRC_CURVE))return true;curveOnlyOut=true;return false}; const pick=y=>{if(!y)return;if(!exactOutSafe(y))return;const v=isIn?y.best.amountOut:y.best.amountIn;if(!v)return; if(!r||(isIn?v>r.best.amountOut:v<r.best.amountIn))r=y}; const tl0=encBool(!isIn)+encAddr(f.addr)+encAddr(t.addr)+encUint(swapAmt)+encUint(0n)+encUint(deadline); const jobs=[ [Q("e7798987",a+tl,0,4,0,0,6)], [Q("e453166e",a+rf+tl0,4,9),Q("e453166e",a+rf+tl,4,9)], [Q("4c464f59",a+tl0,8,13),Q("4c464f59",a+tl,8,13)]]; let splitSlip=0n; if(isIn){ const w=slipBps*3n,SS=w<150n?150n:w>500n?500n:w; splitSlip=SS; const ps=encAddr(f.addr)+encAddr(t.addr)+encUint(swapAmt)+encUint(SS)+encUint(deadline); for(const x of["892af013","85f86a90"])jobs.push([Q(x,a+ps,4,8,1,SS)]); } const tailStep=swapAmt/100n||1n,tailAmt=swapAmt>tailStep?swapAmt-tailStep:0n; const tailJobs=[]; if(tailAmt){ const z=encBool(!isIn)+encAddr(f.addr)+encAddr(t.addr)+encUint(tailAmt)+encUint(0n)+encUint(deadline); tailJobs.push(Q("e453166e",a+rf+z,4,9),Q("4c464f59",a+z,8,13)); if(isIn)for(const x of["892af013","85f86a90"]){ const ps=encAddr(f.addr)+encAddr(t.addr)+encUint(tailAmt)+encUint(splitSlip)+encUint(deadline); tailJobs.push(Q(x,a+ps,4,8,1,splitSlip)); } } const refAmt=swapAmt/REF_N; const iRef=refAmt>0n ?Q("e453166e",a+rf+encBool(!isIn)+encAddr(f.addr)+encAddr(t.addr)+encUint(refAmt)+encUint(0n)+encUint(deadline),4,9) :-1; const mcRaw=(await Promise.all(mc3Split(_rq,QPAIR) .map(g=>mc3Deep(g.map(q=>({to:q.to,data:q.data})),quoteBlock)))).flat(); if(my!==seq)return; const dec=_rq.map((q,i)=>{try{return mcRaw[i]?decQ(mcRaw[i],q.sb||slipBps,!isIn,q.u,q.v,q.S,q.mv):null}catch{return null}}); for(const g of jobs)pick(g.length>1?merge(dec[g[0]],dec[g[1]]):dec[g[0]]); const zr=r; if(isIn&&!(dec[jobs[1][0]]||dec[jobs[1][1]]||dec[jobs[2][0]]||dec[jobs[2][1]])){ const hq=await hubRoute(f,t,swapAmt,slipBps,deadline,rcv||acct||ZERO,acct||ZERO,quoteBlock).catch(()=>null); if(my!==seq)return; if(hq)pick(hq); } let tail=null; for(const i of tailJobs){const y=dec[i];if(!y)continue; const v=isIn?y.best.amountOut:y.best.amountIn; if(v&&(!tail||(isIn?v>tail.best.amountOut:v<tail.best.amountIn)))tail=y; } if(my!==seq)return; const v4q=await v4Job; let v4c=null; if(v4q&&v4q.amountOut>0n){ const v4Min=v4q.amountOut-(v4q.amountOut*slipBps)/10000n; const p=v4q.pool; v4c={best:{source:4,amountIn:v4q.amountIn,amountOut:v4q.amountOut,feeBps:p.fee>=8388608n?0n:BigInt(p.fee)/100n}, callData:"0x"+SEL_V4SWAP+encPoolKey(p)+encBool(p.zeroForOne)+encUint(v4q.amountIn) +encUint(v4Min)+encAddr(rcv||acct||ZERO)+encUint(deadline), fundedCallData:null,amountLimit:v4Min,msgValue:f.addr===ZERO?swapAmt:0n, to:V4PORT,spender:f.addr===ZERO?null:V4PORT}; if(!r)r=v4c; } const pq=await preJob; if(my!==seq)return; let pc=null; if(pq&&pq.out>0n){ const pMin=pq.out-(pq.out*slipBps)/10000n; const pNative=pq.tokenIn===ZERO; const pWethIn=pNative&&(pq.unwrap||0n)>0n; const pTo=rcv||acct||ZERO; const pFund=pWethIn?WETH:pq.tokenIn; const routeData=pWethIn ?encPRouteWeth(pq.pools,pq.tokenOut,swapAmt,pMin,pTo) :encPRoute(pq.pools,pq.tokenIn,pq.tokenOut,swapAmt,pMin,pTo); const routedLeg=encSnwap(pFund,swapAmt,pTo,pq.tokenOut,pMin,PROUTE,routeData); const checkpointLeg=encSnwap(ZERO,0n,acct||ZERO,ZERO,0n,PROUTE, "0x"+SEL_PCHECKPOINT+encAddr(pFund)+strip0x(keccak(hexToBytes(routeData)))+encAddr(acct||ZERO)); const pCall=(pNative&&!pWethIn)?routedLeg:encCalls([checkpointLeg,routedLeg]); const pFunded=(pNative&&!pWethIn)?null:encCalls([ checkpointLeg, encSweep(pFund,swapAmt,PROUTE), encSnwap(ZERO,0n,pTo,pq.tokenOut,pMin,PROUTE,routeData), ]); pc={best:{source:SRC_PRECISION,amountIn:swapAmt,amountOut:pq.out,feeBps:pq.fee/100n}, callData:pCall, fundedCallData:pFunded,amountLimit:pMin,msgValue:(pNative&&!pWethIn)?swapAmt:0n, wrap:pq.wrap||0n,outDiffers:!!pq.outDiffers,poolOut:pq.tokenOut, to:null,spender:(pNative&&!pWethIn)?null:ZROUTER,pools:pq.pools}; if(!r)r=pc; } if(!r&&curveOnlyOut)throw Object.assign(Error(`exact-output is unavailable for this pair — enter an amount to PAY instead`),{exactOut:1}); if(!r)throw Error("bad quote"); if(r.best.amountIn<=0n||r.best.amountOut<=0n||(!isIn&&r.amountLimit<r.best.amountIn))throw Error("bad quote"); let dc=null; if(isIn&&CHAIN_ID===4663&&f.addr!==ZERO&&t.addr!==ZERO){ const dq=await deepQuote(f.addr,t.addr,swapAmt,r&&r.best?r.best.amountOut:0n); if(my!==seq)return; if(dq&&dq.out>0n){ const dMin=dq.out-(dq.out*slipBps)/10000n; const dTo=rcv||acct||ZERO; const leg="0x"+SEL_SWAPDEEP+encAddr(dTo)+encAddr(dq.t0)+encAddr(dq.t1)+encUint(dq.epoch) +strip0x(dq.order)+encUint(dq.isBid?1:0)+encUint(swapAmt)+encUint(dMin)+encUint(deadline); dc={best:{source:SRC_DEEP,amountIn:dq.inUsed,amountOut:dq.out,feeBps:0n}, callData:encCalls([leg,encSweep(f.addr,0n,dTo)]), fundedCallData:null,amountLimit:dMin,msgValue:0n, wrap:0n,outDiffers:false,poolOut:t.addr,to:null,spender:ZROUTER}; } } for(const c of [v4c,pc,dc]){ if(!c||c===r)continue; if(isIn?c.best.amountOut>r.best.amountOut:c.best.amountIn<r.best.amountIn)r=c; } let hybrid=null,bookIncomplete=false,bookUnavailable=false,plannerBounded=false,rank=null; if(acct){ const candidates=await candJob; bookIncomplete=!!candidates.incomplete; bookUnavailable=!!candidates.unavailable; if(my!==seq)return; const quoteRem=async(x,eo)=>{ const qs=[],groups=[],add=(sel,data,u,v,S,sb)=>{ qs.push({data:"0x"+sel+data,u,v,S,sb});return qs.length-1}; const z0=encBool(eo)+encAddr(f.addr)+encAddr(t.addr)+encUint(x)+encUint(0n)+encUint(deadline); const zs=encBool(eo)+encAddr(f.addr)+encAddr(t.addr)+encUint(x)+encUint(slipBps)+encUint(deadline); groups.push([add("e453166e",a+rf+z0,4,9),add("e453166e",a+rf+zs,4,9)]); groups.push([add("4c464f59",a+z0,8,13),add("4c464f59",a+zs,8,13)]); if(!eo){ const w=slipBps*3n,ss=w<150n?150n:w>500n?500n:w; const ps=encAddr(f.addr)+encAddr(t.addr)+encUint(x)+encUint(ss)+encUint(deadline); for(const sel of["892af013","85f86a90"])groups.push([add(sel,a+ps,4,8,1,ss)]); } const raw=await mc3Deep(qs.map(q=>({to:ZQUOTER,data:q.data})),quoteBlock); const ds=qs.map((q,i)=>{try{return raw[i]?decQ(raw[i],q.sb||slipBps,eo,q.u,q.v,q.S):null}catch{return null}}); let best=null; for(const g of groups){ const y=g.length>1?merge(ds[g[0]],ds[g[1]]):ds[g[0]]; if(!y)continue; if(eo&&y.sources&&y.sources.includes(SRC_CURVE))continue; const v=eo?y.best.amountIn:y.best.amountOut; if(v&&(!best||(eo?v<best.best.amountIn:v>best.best.amountOut)))best=y; } return best; }; const gasValue=async wei=>{ const token=isIn?t.addr:f.addr; if(token===ZERO||token.toLowerCase()===WETH.toLowerCase())return wei; const eo=!isIn,ti=isIn?ZERO:token,to=isIn?token:ZERO; const z=encBool(eo)+encAddr(ti)+encAddr(to)+encUint(wei)+encUint(0n)+encUint(deadline); try{ const raw=await rpc(C,[{to:ZQUOTER,data:"0xe453166e"+a+rf+z},quoteBlock]); const q=decQ(raw,0n,eo,4,9); const v=isIn?q.best.amountOut:q.best.amountIn; if(v>0n)return v; }catch{} const ethSide=f.addr===ZERO||f.addr.toLowerCase()===WETH.toLowerCase(); const tokSide=t.addr===ZERO||t.addr.toLowerCase()===WETH.toLowerCase(); const inA=r.best.amountIn,outA=r.best.amountOut; if(inA>0n&&outA>0n){ if(isIn&&ethSide)return wei*outA/inA; if(!isIn&&tokSide)return wei*inA/outA; } return null; }; const tailDelta=swapAmt-tailAmt; const tailFits=!!tail&&r===zr; const seedOut=isIn&&tailFits&&r.best.amountOut>tail.best.amountOut ?r.best.amountOut-tail.best.amountOut:r.best.amountOut; const seedIn=!isIn&&tailFits&&r.best.amountIn>tail.best.amountIn ?r.best.amountIn-tail.best.amountIn:r.best.amountIn; let p=isIn ?planBookExactIn(candidates,swapAmt,seedOut,tailFits&&seedOut!==r.best.amountOut?tailDelta:r.best.amountIn) :planBookExactOut(candidates,swapAmt,seedIn,tailFits&&seedIn!==r.best.amountIn?tailDelta:r.best.amountOut); plannerBounded=!!(p&&p.bounded); let amm=null,ammFor=null; for(let round=0;p&&(isIn?p.ammIn:p.ammOut)&&round<2;round++){ const rem=isIn?p.ammIn:p.ammOut; amm=await quoteRem(rem,!isIn).catch(()=>null);ammFor=rem; if(!amm)break; const next=isIn ?planBookExactIn(candidates,swapAmt,amm.best.amountOut,rem) :planBookExactOut(candidates,swapAmt,amm.best.amountIn,rem); if(!next){p=null;break} const stable=(isIn?next.ammIn===p.ammIn:next.ammOut===p.ammOut); p=next;plannerBounded=plannerBounded||!!p.bounded;if(stable)break; } if(p){ const rem=isIn?p.ammIn:p.ammOut; if(!rem)amm=null; else if(!(amm&&ammFor===rem))amm=await quoteRem(rem,!isIn).catch(()=>null); if(!rem||amm){ const recipient=rcv||acct; const ammBudget=amm?(isIn?rem:amm.amountLimit):0n; const totalBudget=p.bookIn+ammBudget; const expectedIn=p.bookIn+(amm?amm.best.amountIn:0n); const expectedOut=p.bookOut+(amm?amm.best.amountOut:0n); const saving=isIn ?(expectedOut>r.best.amountOut?expectedOut-r.best.amountOut:0n) :(expectedIn<r.best.amountIn?r.best.amountIn-expectedIn:0n); let gasFloor=null; if(saving){ const gasPrice=await gasPriceJob; if(gasPrice){ const extraWei=gasPrice*(120000n+105000n*BigInt(p.fills.length)); gasFloor=await gasValue(extraWei); } } const pricedGas=gasFloor!==null&&gasFloor>0n; const wins=saving>0n&&pricedGas&&saving*10n>gasFloor*12n; if(my!==seq)return; if(wins){ const fp=encFillPlanAndSwap( p.fills,f.addr,t.addr,recipient,acct,deadline,ammBudget,amm?amm.callData:"0x" ); const minOut=isIn?p.bookOut+(amm?amm.amountLimit:0n):swapAmt; const snwapIn=totalBudget===0n?ZERO:f.addr; const planCall=encSnwap(snwapIn,totalBudget,recipient,t.addr,minOut,SWAPBOL,fp); const callData=f.addr===ZERO?planCall:encCalls([ encCheckpoint(SWAPBOL,f.addr,acct),planCall ]); let fundedCallData=null; if(f.addr!==ZERO){ fundedCallData=encPermit2Hybrid( amm?amm.callData:"0x",p.fills,f.addr,t.addr,recipient,acct,deadline,p.bookIn,p.bookOut ); } r={...r,best:{...r.best,amountIn:expectedIn,amountOut:expectedOut},callData, fills:p.fills,pools:undefined, fundedCallData,amountLimit:isIn?minOut:totalBudget,msgValue:f.addr===ZERO?totalBudget:0n, slipBps:amm&&amm.slipBps, to:null,spender:f.addr===ZERO?null:ZROUTER, pool:null,wrap:0n,outDiffers:false,poolOut:undefined}; hybrid=isIn ?{bookIn:p.bookIn,bookOut:p.bookOut,ammIn:p.ammIn} :{bookIn:p.bookIn,bookOut:p.bookOut,ammOut:p.ammOut}; plannerBounded=plannerBounded||!!p.bounded; } } } } const pin=lanePin(); if(!isIn&&r&&r.amountLimit>0n){ try{ await loadSolvers(); const lanes=(solverLanes||[]).filter(x=>!laneDown(x.name)&&SOLVER_SHAPES_OUT[x.name]); if(lanes.length&&solverFill&&solverExec){ const dl=new Promise(res=>setTimeout(()=>res(null),SOLVER_DEADLINE)); const ck=`o|${f.addr}|${t.addr}|${swapAmt}|${slipBps}`; const tried=await Promise.all(lanes.map(x=>Promise.race([ laneCached(`${x.name}|${ck}`, ()=>SOLVER_SHAPES_OUT[x.name](x.endpoint,f.addr,t.addr,swapAmt,Number(slipBps),SOLVER_DEADLINE,f.dec,t.dec)) .then(q=>q?{...q,lane:x}:null).catch(()=>null),dl]))); const laneCeil=c=>{const d=c.inp+(c.inp*slipBps)/10000n; return c.cap&&c.cap>0n?bmin(c.cap,d):d}; const chainCeil=r.amountLimit; let win=null; for(const c of tried){ if(!laneOk(c)||!c.inp)continue; const ceil=laneCeil(c); if(pin){ if(c.lane.name!==pin)continue; win={...c,ceil,forced:1};break; } if(ceil*(10000n+c.lane.handicapBps)>=chainCeil*10000n)continue; if(!win||ceil<win.ceil)win={...c,ceil}; } rank=[{n:"On-chain",f:chainCeil}]; for(const c of tried){ if(!laneOk(c)||!c.inp)continue; rank.push({n:c.lane.name,f:laneCeil(c)}); } rank.sort((a,b)=>a.f>b.f?1:a.f<b.f?-1:0); if(my!==seq)return; if(win){ const ethIn=f.addr===ZERO; r={best:{source:SRC_SOLVER,amountIn:win.inp,amountOut:swapAmt,feeBps:0n, lane:win.lane.name,forced:win.forced}, callData:"0x"+SEL_FILL+encAddr(win.target)+encAddr(win.spender)+encAddr(f.addr) +encUint(win.ceil)+encAddr(t.addr)+encUint(swapAmt)+encAddr(rcv||acct||ZERO) +encUint(256n)+encUint(BigInt(strip0x(win.data).length/2)) +strip0x(win.data)+"0".repeat((64-(strip0x(win.data).length%64))%64), fundedCallData:null,amountLimit:win.ceil, msgValue:ethIn?win.ceil:0n, to:solverFill,spender:ethIn?null:solverFill}; hybrid=null; } } }catch{} } const pin2=lanePin(); if(isIn&&r&&r.amountLimit>0n){ try{ await loadSolvers(); const lanes=(solverLanes||[]).filter(x=>!laneDown(x.name)); if(lanes.length&&solverFill&&solverExec){ const deadline_=new Promise(res=>setTimeout(()=>res(null),SOLVER_DEADLINE)); const ck=`${f.addr}|${t.addr}|${swapAmt}|${slipBps}`; const tried=await Promise.all(lanes.map(x=>Promise.race([ laneCached(`${x.name}|${ck}`, ()=>SOLVER_SHAPES[x.name](x.endpoint,f.addr,t.addr,swapAmt,Number(slipBps),SOLVER_DEADLINE,f.dec,t.dec)) .then(q=>q?{...q,lane:x}:null).catch(()=>null), deadline_]))); const chainFloor=r.amountLimit; let win=null; for(const c of tried){ if(!laneOk(c)||!c.out)continue; const floor=c.out-(c.out*slipBps)/10000n; if(pin2){ if(c.lane.name!==pin2)continue; win={...c,floor,forced:1}; break; } if(floor*10000n<=chainFloor*(10000n+c.lane.handicapBps))continue; if(!win||floor>win.floor)win={...c,floor}; } rank=[{n:"On-chain",f:chainFloor}]; for(const c of tried){ if(!laneOk(c)||!c.out)continue; rank.push({n:c.lane.name,f:c.out-(c.out*slipBps)/10000n}); } rank.sort((a,b)=>a.f<b.f?1:a.f>b.f?-1:0); if(my!==seq)return; if(win){ const ethIn=f.addr===ZERO; r={best:{source:SRC_SOLVER,amountIn:swapAmt,amountOut:win.out,feeBps:0n,lane:win.lane.name,forced:win.forced}, callData:"0x"+SEL_FILL+encAddr(win.target)+encAddr(win.spender)+encAddr(f.addr) +encUint(swapAmt)+encAddr(t.addr)+encUint(win.floor)+encAddr(rcv||acct||ZERO) +encUint(256n)+encUint(BigInt(strip0x(win.data).length/2)) +strip0x(win.data)+"0".repeat((64-(strip0x(win.data).length%64))%64), fundedCallData:null,amountLimit:win.floor, msgValue:ethIn?swapAmt:0n, to:solverFill,spender:ethIn?null:solverFill}; hybrid=null; } } }catch{} } if(my!==seq)return; const finalIn=r.best.amountIn; const finalOut=r.best.amountOut; if(isIn){outAmt.value=trimAmt(finalOut,t.dec);fitFont(outAmt)} else{amt.value=trimAmt(finalIn,f.dec);fitFont(amt)} const rx=(+formatUnits(finalOut,t.dec))/(+formatUnits(finalIn,f.dec)); const s=r.best.source,raw=SOURCES[s]||"?"; const src=r.best.source===SRC_SOLVER?`${r.best.lane} solver${r.best.forced?" \u00b7 pinned":""}`:hybrid?bookShare(hybrid,raw):(s===3||s===4||s===SRC_PRECISION)&&r.best.feeBps?`${raw}\u00a0${Number(r.best.feeBps)/100}%`:raw; rate.textContent=isFinite(rx)?`1 ${f.sym} ≈ ${rx>=1?rx.toLocaleString("en-US",{maximumFractionDigits:4}):rx.toPrecision(4)} ${t.sym} · ${src}`:""; rankBox.textContent=""; if(rank&&rank.length>1){ const top=rank[0].f; const worse=isIn?"-":"+"; const used=r.best.source===SRC_SOLVER?r.best.lane:"On-chain"; for(const x of rank){ const row=document.createElement("i"); if(x.n===used)row.className="w"; const pinNow=lanePin(); if(x.n===pinNow)row.className+=" pin"; row.title=x.n===pinNow?"Pinned \u2014 click again for the best floor" :x.f===top&&x.n!==used?"Best floor \u2014 click to use it":"Use this venue"; const pick=()=>{const on=lanePin()===x.n;laneOverride=on?null:x.n;laneOverrideFor=on?"":laneScope();update()}; row.onclick=pick; row.setAttribute("role","button"); row.tabIndex=0; row.onkeydown=e=>{if(e.key==="Enter"||e.key===" "){e.preventDefault();pick()}}; const n=document.createElement("span"); n.textContent=safeSym(x.n).slice(0,16); const v=document.createElement("u"); const gap=top>0n?Number((x.f>top?x.f-top:top-x.f)*10000n/top)/100:0; v.textContent=`${sigAmt(x.f,isIn?t.dec:f.dec)}${gap>0.004?` \u00b7 ${worse}${gap.toFixed(2)}%`:""}`; row.append(n,v); rankBox.appendChild(row); } } rankBox.classList.toggle("hide",!(rankOpen&&rankBox.firstChild)); const paysSym=r.poolOut===ZERO?"ETH":"WETH"; rcvHdr.textContent=r.outDiffers?`You receive \u2014 as ${paysSym}`:"You receive"; if(r.outDiffers&&rate.textContent)rate.textContent+=` \u00b7 pays ${paysSym}`; rate.title=""; if(venueLost||mcGaveUp>gaveUp0||(mcFailed>failed0&&mcOk===ok0)) rate.textContent+=" \u00b7 some venues unreachable"; if(isIn)rate.textContent+=` · Min ${trimAmt(r.amountLimit,t.dec)} ${t.sym}`; else{ const exactMax=formatUnits(r.amountLimit,f.dec); rate.textContent+=` · Max ${maxAmt(r.amountLimit,f.dec)} ${f.sym}`; rate.title=`Exact maximum: ${exactMax} ${f.sym}`; } if(r.s
0x68575b07…67c7·#25,913,247·0xf25dc429…5b30c4
c-29.853-29.111-78.253-29.111-108.106 0l-7.59 7.401c-1.07 1.043-2.804 1.043-3.874 0l-17.617-17.18c-2.14-2.086-2.14-5.469 0-7.555zm191.397 35.529 15.679 15.29c2.14 2.086 2.14 5.469 0 7.555l-70.7 68.944c-2.139 2.087-5.608 2.087-7.748 0l-50.178-48.931c-.535-.522-1.402-.522-1.937 0l-50.178 48.931c-2.139 2.087-5.608 2.087-7.748 0l-70.7015-68.945c-2.1396-2.086-2.1396-5.469 0-7.555l15.6795-15.29c2.1396-2.087 5.6085-2.087 7.7481 0l50.1789 48.932c.535.522 1.402.522 1.937 0l50.177-48.932c2.139-2.087 5.608-2.087 7.748 0l50.179 48.932c.535.522 1.402.522 1.937 0l50.179-48.931c2.139-2.087 5.608-2.087 7.748 0z"/></svg>'; const WCU=(()=>{ const rl=(v,c)=>(v<<c|v>>>(32-c))>>>0; const wds=b=>{const o=[];for(let i=0;i<b.length;i+=4)o.push((b[i]|b[i+1]<<8|b[i+2]<<16|b[i+3]<<24)>>>0);return o}; function cblk(x,k,n,c){ const s=[0x61707865,0x3320646e,0x79622d32,0x6b206574, k[0],k[1],k[2],k[3],k[4],k[5],k[6],k[7],c,n[0],n[1],n[2]]; for(let i=0;i<16;i++)x[i]=s[i]; for(let r=0;r<10;r++){ const q=(a,b,cc,d)=>{ x[a]=(x[a]+x[b])>>>0;x[d]=rl(x[d]^x[a],16); x[cc]=(x[cc]+x[d])>>>0;x[b]=rl(x[b]^x[cc],12); x[a]=(x[a]+x[b])>>>0;x[d]=rl(x[d]^x[a],8); x[cc]=(x[cc]+x[d])>>>0;x[b]=rl(x[b]^x[cc],7)}; q(0,4,8,12);q(1,5,9,13);q(2,6,10,14);q(3,7,11,15); q(0,5,10,15);q(1,6,11,12);q(2,7,8,13);q(3,4,9,14);} for(let i=0;i<16;i++)x[i]=(x[i]+s[i])>>>0;} function chacha(key,nonce,ctr,src){ const k=wds(key),n=wds(nonce),x=new Uint32Array(16),out=new Uint8Array(src.length); for(let p=0;p<src.length;p+=64){cblk(x,k,n,ctr++); for(let i=0;i<64&&p+i<src.length;i++)out[p+i]=src[p+i]^(x[i>>2]>>>((i&3)*8)&255);} return out} function poly(msg,key){ const P=(1n<<130n)-5n; let r=0n,sk=0n; for(let i=15;i>=0;i--)r=(r<<8n)|BigInt(key[i]); r&=0x0ffffffc0ffffffc0ffffffc0fffffffn; for(let i=31;i>=16;i--)sk=(sk<<8n)|BigInt(key[i]); let a=0n; for(let i=0;i<msg.length;i+=16){ const n=Math.min(16,msg.length-i); let b=0n; for(let j=n-1;j>=0;j--)b=(b<<8n)|BigInt(msg[i+j]); a=((a+b+(1n<<BigInt(8*n)))*r)%P;} a=(a+sk)&((1n<<128n)-1n); const o=new Uint8Array(16); for(let i=0;i<16;i++){o[i]=Number(a&255n);a>>=8n} return o} const p16=n=>n%16?16-n%16:0; const le64=n=>{const b=new Uint8Array(8);for(let i=0;i<8;i++){b[i]=n&255;n=Math.floor(n/256)}return b}; function tagOf(ct,key,nonce){ const otk=chacha(key,nonce,0,new Uint8Array(32)); const pad=new Uint8Array(p16(ct.length)); const m=new Uint8Array(ct.length+pad.length+16); m.set(ct);m.set(pad,ct.length); m.set(le64(0),ct.length+pad.length);m.set(le64(ct.length),ct.length+pad.length+8); return poly(m,otk)} const seal=(key,nonce,pt)=>{ const ct=chacha(key,nonce,1,pt),tg=tagOf(ct,key,nonce); const o=new Uint8Array(ct.length+16);o.set(ct);o.set(tg,ct.length);return o}; const open=(key,nonce,box)=>{ if(box.length<16)return null; const ct=box.subarray(0,box.length-16),tg=box.subarray(box.length-16); const w=tagOf(ct,key,nonce); let bad=0;for(let i=0;i<16;i++)bad|=w[i]^tg[i]; return bad?null:chacha(key,nonce,1,ct)}; const hex=b=>{let s="";for(const x of b)s+=x.toString(16).padStart(2,"0");return s}; const unhex=h=>{const b=new Uint8Array(h.length/2); for(let i=0;i<b.length;i++)b[i]=parseInt(h.substr(i*2,2),16);return b}; const bin=b=>{let s="";for(let i=0;i<b.length;i+=4096)s+=String.fromCharCode.apply(null,b.subarray(i,i+4096));return s}; const b64=b=>btoa(bin(b)); const unb64=s=>{const t=atob(s),b=new Uint8Array(t.length); for(let i=0;i<t.length;i++)b[i]=t.charCodeAt(i);return b}; const b64u=b=>b64(b).replace(/\+/g,"-").replace(/\//g,"_").replace(/=+$/,""); const utf8=s=>new TextEncoder().encode(s); const B58="123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; const b58=b=>{let n=0n;for(const x of b)n=n*256n+BigInt(x); let s="";while(n>0n){s=B58[Number(n%58n)]+s;n/=58n} for(const x of b){if(x)break;s="1"+s}return s}; const rnd=n=>crypto.getRandomValues(new Uint8Array(n)); const sha256=async b=>new Uint8Array(await crypto.subtle.digest("SHA-256",b)); const topicOf=async k=>hex(await sha256(k)); const encode=(symKey,payload,senderPub)=>{ const iv=rnd(12),pt=utf8(JSON.stringify(payload)); const sealed=seal(symKey,iv,pt); const t=senderPub?1:0; const head=senderPub?1+32:1; const out=new Uint8Array(head+12+sealed.length); out[0]=t; if(senderPub)out.set(senderPub,1); out.set(iv,head);out.set(sealed,head+12); return b64(out)}; const decode=(symKey,msg)=>{ let b;try{b=unb64(msg)}catch{return null} if(!b.length)return null; const t=b[0]; if(t!==0&&t!==1)return null; const head=t===1?33:1; if(b.length<head+12+16)return null; const iv=b.subarray(head,head+12),box=b.subarray(head+12); const pt=open(symKey,iv,box); if(!pt)return null; try{return JSON.parse(new TextDecoder().decode(pt))}catch{return null}}; const didKey=pub=>{const b=new Uint8Array(34);b[0]=0xed;b[1]=0x01;b.set(pub,2); return"did:key:z"+b58(b)}; async function relayJwt(aud){ let kp; try{kp=await crypto.subtle.generateKey({name:"Ed25519"},true,["sign","verify"])} catch{throw Error("This browser cannot do Ed25519. WalletConnect needs a newer " +"browser; an extension wallet works here today.")} const pub=new Uint8Array(await crypto.subtle.exportKey("raw",kp.publicKey)); const iss=didKey(pub); const iat=Math.floor(Date.now()/1e3); const h=b64u(utf8(JSON.stringify({alg:"EdDSA",typ:"JWT"}))); const p=b64u(utf8(JSON.stringify({iss,sub:hex(rnd(32)),aud,iat,exp:iat+86400}))); const sig=new Uint8Array(await crypto.subtle.sign({name:"Ed25519"},kp.privateKey,utf8(h+"."+p))); return h+"."+p+"."+b64u(sig)} async function kex(){ let kp; try{kp=await crypto.subtle.generateKey({name:"X25519"},true,["deriveBits"])} catch{throw Error("This browser cannot do X25519. WalletConnect needs a newer " +"browser; an extension wallet works here today.")} const pub=new Uint8Array(await crypto.subtle.exportKey("raw",kp.publicKey)); return{priv:kp.privateKey,pub}} async function sessionKey(priv,peerHex){ const peer=await crypto.subtle.importKey("raw",unhex(peerHex),{name:"X25519"},false,[]); const bits=new Uint8Array(await crypto.subtle.deriveBits({name:"X25519",public:peer},priv,256)); const hk=await crypto.subtle.importKey("raw",bits,"HKDF",false,["deriveBits"]); const out=await crypto.subtle.deriveBits( {name:"HKDF",hash:"SHA-256",salt:new Uint8Array(0),info:new Uint8Array(0)},hk,256); return new Uint8Array(out)} return{seal,open,chacha,poly,hex,unhex,b64,unb64,b64u,b58,didKey,rnd,sha256, topicOf,encode,decode,relayJwt,kex,sessionKey}; })(); const WCCLI=(U,WSC)=>{ const RELAY="wss://relay.walletconnect.org"; const T_PROPOSE=1100,T_APPROVE=1101,T_SETTLE=1102,T_SETTLE_RES=1103,T_REQ=1108,T_RES=1109; const TTL=300,STTL=604800; let sock=null,nextId=1,waiters={},subs={},onEvent=null,alive=false,gone=""; const rpcId=()=>Date.now()*1000+(nextId++%1000); const sendRaw=o=>{ if(!alive)throw Error(gone||"The wallet connection is closed."); sock.send(JSON.stringify(o))}; function drop(why){ if(!alive&&gone)return; alive=false;gone=why; const ws=waiters;waiters={};subs={}; for(const k in ws)ws[k].rej(Error(why)); if(onEvent)onEvent({t:"closed",why})} function relayCall(method,params){ const id=rpcId(); return new Promise((res,rej)=>{ waiters[id]={res,rej}; sendRaw({id,jsonrpc:"2.0",method,params}); setTimeout(()=>{if(waiters[id]){delete waiters[id];rej(Error("relay timeout"))}},15000)})} function handle(raw){ let m;try{m=JSON.parse(raw)}catch{return} if(m.method==="irn_subscription"){ if(!alive)return; sendRaw({id:m.id,jsonrpc:"2.0",result:true}); const d=m.params&&m.params.data; if(d&&subs[d.topic])Promise.resolve(subs[d.topic](d.message)).catch(()=>{}); return} if(m.id&&waiters[m.id]){ const w=waiters[m.id];delete waiters[m.id]; if(m.error)w.rej(Error(m.error.message||"relay error"));else w.res(m.result)}} async function connect(projectId){ try{sock&&sock.close()}catch{} sock=null;alive=false;gone="";waiters={};subs={}; const jwt=await U.relayJwt(RELAY); const url=`${RELAY}/?auth=${jwt}&projectId=${projectId}&ua=zswap`; const ws=WSC(url); sock=ws; await new Promise((res,rej)=>{ let d=0; const t=setTimeout(()=>{if(!d){d=1;rej(Error("The relay did not answer."))}},15000); const fail=m=>{try{ws.close()}catch{}if(sock===ws)sock=null;rej(Error(m))}; const end=f=>x=>{if(d)return;d=1;clearTimeout(t);f(x)}; sock.onopen=end(()=>{alive=true;res()}); sock.onerror=end(()=>fail("The relay could not be reached.")); sock.onclose=end(e=>fail("The relay closed the connection" +(e&&e.code?" (code "+e.code+(e.reason?": "+e.reason:"")+")":"")+"."))}) sock.onclose=()=>drop("The relay connection dropped. Reconnect your wallet."); sock.onmessage=e=>handle(typeof e.data==="string"?e.data:""); } const subscribe=async(topic,fn)=>{subs[topic]=fn;await relayCall("irn_subscribe",{topic})}; const publish=(topic,message,tag,ttl)=> relayCall("irn_publish",{topic,message,ttl:ttl||TTL,tag,prompt:tag===T_PROPOSE||tag===T_REQ}); async function pair(projectId,meta){ await connect(projectId); const symKey=U.rnd(32); const pTopic=await U.topicOf(symKey); const kp=await U.kex(); const proposeId=rpcId(); let settle=null; const pKey="p"+proposeId; const done=new Promise((res,rej)=>{settle={ res:v=>{delete waiters[pKey];res(v)}, rej:e=>{delete waiters[pKey];rej(e)}}}); waiters[pKey]={res:v=>settle.res(v),rej:e=>settle.rej(e)}; await subscribe(pTopic,async msg=>{ try{ const m=U.decode(symKey,msg); if(!m||m.id!==proposeId)return; if(m.error)return settle.rej(Error(m.error.message||"rejected")); const rp=m.result&&m.result.responderPublicKey; if(!rp)return; const sKey=await U.sessionKey(kp.priv,rp); const sTopic=await U.topicOf(sKey); await subscribe(sTopic,async smsg=>{ const s=U.decode(sKey,smsg); if(!s)return; if(s.method==="wc_sessionSettle"){ await publish(sTopic,U.encode(sKey,{id:s.id,jsonrpc:"2.0",result:true}),T_SETTLE_RES,STTL); const ns=s.params&&s.params.namespaces||{}; const accts=[]; for(const k in ns)for(const a of ns[k].accounts||[])accts.push(a); settle.res({topic:sTopic,key:sKey,accounts:accts, methods:(ns.eip155&&ns.eip155.methods)||[]}); return} if(s.method==="wc_sessionDelete")return drop("The wallet ended the session."); if(s.method&&onEvent)onEvent({t:"session",m:s}); if(s.id&&waiters["s"+s.id]){ const w=waiters["s"+s.id];delete waiters["s"+s.id]; if(s.error)w.rej(Object.assign(Error(s.error.message||"rejected"),{code:s.error.code})); else w.res(s.result)}}); }catch(e){settle.rej(e instanceof Error?e:Error(String(e)))} }); const proposal={id:proposeId,jsonrpc:"2.0",method:"wc_sessionPropose",params:{ relays:[{protocol:"irn"}], proposer:{publicKey:U.hex(kp.pub),metadata:meta}, requiredNamespaces:{}, optionalNamespaces:{eip155:{chains:Object.keys(CHAINS).map(id=>"eip155:"+id), methods:["eth_sendTransaction","personal_sign","eth_signTypedData_v4", "eth_call","eth_chainId","eth_accounts","eth_getBalance", "wallet_switchEthereumChain","wallet_addEthereumChain"], events:["chainChanged","accountsChanged"]}}}}; await publish(pTopic,U.encode(symKey,proposal),T_PROPOSE); const uri=`wc:${pTopic}@2?relay-protocol=irn&symKey=${U.hex(symKey)}`; return{uri,done}} function request(sess,method,params){ const id=rpcId(); const body={id,jsonrpc:"2.0",method:"wc_sessionRequest", params:{request:{method,params:params||[]},chainId:"eip155:1"}}; const p=new Promise((res,rej)=>{ waiters["s"+id]={res,rej}; setTimeout(()=>{if(waiters["s"+id]){delete waiters["s"+id];rej(Error("wallet timed out"))}},300000)}); publish(sess.topic,U.encode(sess.key,body),T_REQ).catch(e=>{ const w=waiters["s"+id];if(!w)return;delete waiters["s"+id];w.rej(e)}); return p} return{pair,request,close:()=>{drop("The wallet connection was closed.");try{sock&&sock.close()}catch{}sock=null}, on:f=>{onEvent=f},_relayCall:relayCall}; }; const QR8=(()=>{ const EXP=new Uint8Array(512),LOG=new Uint8Array(256); (()=>{let x=1;for(let i=0;i<255;i++){EXP[i]=x;LOG[x]=i;x<<=1;if(x&256)x^=285} for(let i=255;i<512;i++)EXP[i]=EXP[i-255]})(); const mul=(a,b)=>a&&b?EXP[LOG[a]+LOG[b]]:0; const gen=n=>{let p=[1]; for(let i=0;i<n;i++){const q=new Array(p.length+1).fill(0); for(let j=0;j<p.length;j++){q[j]^=mul(p[j],EXP[i]);q[j+1]^=p[j]}p=q} return p}; const rs=(data,n)=>{ const g=gen(n),r=new Uint8Array(data.length+n); r.set(data); for(let i=0;i<data.length;i++){ const c=r[i];if(!c)continue; for(let j=0;j<=n;j++)r[i+j]^=mul(g[n-j],c)} return r.subarray(data.length)}; const SZ=49,NBLK=2,DPB=97,ECB=24,ALIGN=[6,24,42]; const bch=(v,g,n)=>{let d=v<<n; const bits=x=>{let c=0;while(x){c++;x>>>=1}return c}; const gl=bits(g); while(bits(d)>=gl)d^=g<<(bits(d)-gl); return d}; const fmtBits=mask=>{ const v=(1<<3)|mask; return ((v<<10)|bch(v,0x537,10))^0x5412}; const verBits=()=>(8<<12)|bch(8,0x1f25,12); function encode(str){ const bytes=new TextEncoder().encode(str); if(bytes.length>NBLK*DPB-2)throw Error("pairing code too long to draw"); const bits=[]; const push=(v,n)=>{for(let i=n-1;i>=0;i--)bits.push((v>>i)&1)}; push(4,4);push(bytes.length,8); for(const b of bytes)push(b,8); const cap=(NBLK*DPB)*8; for(let i=0;i<4&&bits.length<cap;i++)bits.push(0); while(bits.length%8)bits.push(0); const cw=[]; for(let i=0;i<bits.length;i+=8){let v=0;for(let j=0;j<8;j++)v=(v<<1)|bits[i+j];cw.push(v)} const pads=[0xec,0x11]; for(let i=0;cw.length<NBLK*DPB;i++)cw.push(pads[i%2]); const blocks=[],ecs=[]; for(let i=0;i<NBLK;i++){ const d=Uint8Array.from(cw.slice(i*DPB,(i+1)*DPB)); blocks.push(d);ecs.push(rs(d,ECB))} const out=[]; for(let i=0;i<DPB;i++)for(const b of blocks)out.push(b[i]); for(let i=0;i<ECB;i++)for(const e of ecs)out.push(e[i]); return out} function frame(){ const m=[],fn=[]; for(let i=0;i<SZ;i++){m.push(new Array(SZ).fill(0));fn.push(new Array(SZ).fill(0))} const put=(r,c,v)=>{m[r][c]=v;fn[r][c]=1}; const finder=(r,c)=>{ for(let i=-1;i<8;i++)for(let j=-1;j<8;j++){ const rr=r+i,cc=c+j; if(rr<0||cc<0||rr>=SZ||cc>=SZ)continue; const on=(i>=0&&i<=6&&(j===0||j===6))||(j>=0&&j<=6&&(i===0||i===6))||(i>=2&&i<=4&&j>=2&&j<=4); put(rr,cc,on?1:0)}}; finder(0,0);finder(0,SZ-7);finder(SZ-7,0); for(const r of ALIGN)for(const c of ALIGN){ if((r===6&&c===6)||(r===6&&c===ALIGN[ALIGN.length-1])||(r===ALIGN[ALIGN.length-1]&&c===6))continue; for(let i=-2;i<=2;i++)for(let j=-2;j<=2;j++) put(r+i,c+j,(Math.abs(i)===2||Math.abs(j)===2||(i===0&&j===0))?1:0)} for(let i=8;i<SZ-8;i++){put(6,i,i%2?0:1);put(i,6,i%2?0:1)} put(SZ-8,8,1); for(let i=0;i<9;i++){if(!fn[8][i])put(8,i,0);if(!fn[i][8])put(i,8,0)} for(let i=0;i<8;i++){if(!fn[8][SZ-1-i])put(8,SZ-1-i,0);if(!fn[SZ-1-i][8])put(SZ-1-i,8,0)} const vb=verBits(); for(let i=0;i<18;i++){ const b=(vb>>i)&1,r=Math.floor(i/3),c=i%3; put(SZ-11+c,r,b);put(r,SZ-11+c,b)} return{m,fn}} function place(m,fn,cw){ let bit=0,up=1; const get=i=>(cw[i>>3]>>(7-(i&7)))&1; for(let c=SZ-1;c>0;c-=2){ if(c===6)c--; for(let k=0;k<SZ;k++){ const r=up?SZ-1-k:k; for(let j=0;j<2;j++){ const cc=c-j; if(fn[r][cc])continue; m[r][cc]=bit<cw.length*8?get(bit):0;bit++}} up^=1}} const maskFn=(k,r,c)=>[ (r+c)%2===0,r%2===0,c%3===0,(r+c)%3===0, (((r/2)|0)+((c/3)|0))%2===0,((r*c)%2+(r*c)%3)===0, (((r*c)%2+(r*c)%3)%2)===0,(((r+c)%2+(r*c)%3)%2)===0][k]; function penalty(m){ let p=0; for(let i=0;i<SZ;i++){ let rc=1,cc=1; for(let j=1;j<SZ;j++){ rc=m[i][j]===m[i][j-1]?rc+1:1;if(rc===5)p+=3;else if(rc>5)p++; cc=m[j][i]===m[j-1][i]?cc+1:1;if(cc===5)p+=3;else if(cc>5)p++}} for(let i=0;i<SZ-1;i++)for(let j=0;j<SZ-1;j++){ const s=m[i][j]+m[i][j+1]+m[i+1][j]+m[i+1][j+1]; if(s===0||s===4)p+=3} const pat=[1,0,1,1,1,0,1]; const hit=(get)=>{ for(let i=0;i<SZ;i++)for(let j=0;j<SZ-6;j++){ let okk=1;for(let k=0;k<7;k++)if(get(i,j+k)!==pat[k]){okk=0;break} if(!okk)continue; let before=1,after=1; for(let k=j-4;k<j;k++)if(k<0||get(i,k)!==0){before=0;break} for(let k=j+7;k<j+11;k++)if(k>=SZ||get(i,k)!==0){after=0;break} if(before||after)p+=40}}; hit((i,j)=>m[i][j]);hit((i,j)=>m[j][i]); let dark=0;for(let i=0;i<SZ;i++)for(let j=0;j<SZ;j++)dark+=m[i][j]; p+=Math.floor(Math.abs(dark*100/(SZ*SZ)-50)/5)*10; return p} function build(str){ const cw=encode(str); let best=null,bestP=Infinity,bestK=0; for(let k=0;k<8;k++){ const{m,fn}=frame(); place(m,fn,cw); for(let r=0;r<SZ;r++)for(let c=0;c<SZ;c++) if(!fn[r][c]&&maskFn(k,r,c))m[r][c]^=1; const f=fmtBits(k); for(let i=0;i<8;i++){ const b=(f>>i)&1; if(i<6)m[i][8]=b;else if(i===6)m[7][8]=b;else m[8][8]=b; m[8][SZ-1-i]=b} for(let i=8;i<15;i++){ const b=(f>>i)&1,c=14-i; if(c<6)m[8][c]=b;else m[8][7]=b; m[SZ-1-c][8]=b} m[SZ-8][8]=1; const p=penalty(m); if(p<bestP){bestP=p;best=m;bestK=k}} return{size:SZ,modules:best,mask:bestK}} return{build,_encode:encode}; })(); const wcNode=()=>rpcPin()||rpcPool[rpcPi]; const wcHost=()=>{try{return new URL(wcNode()).host}catch{return wcNode()}}; const WC_SIGN=["eth_sendTransaction","eth_signTransaction","personal_sign","eth_sign", "eth_signTypedData","eth_signTypedData_v3","eth_signTypedData_v4"]; const wcToWallet=m=>m.indexOf("wallet_")===0||WC_SIGN.indexOf(m)>=0; async function wcRpc(method,params){ try{return await nodeRead(method,params)} catch(e){if(e&&e.code!=null)throw e; throw Error("Could not reach "+wcHost()+" for reads. Pick another node from the WalletConnect panel.")}} const wcRelay=WCCLI(WCU,u=>new WebSocket(u)); const qrSvg=m=>{let d=""; for(let r=0;r<m.size;r++)for(let c=0;c<m.size;c++)if(m.modules[r][c])d+="M"+c+" "+r+"h1v1h-1z"; return '<svg viewBox="-4 -4 '+(m.size+8)+" "+(m.size+8)+'" role="img" aria-label="WalletConnect pairing code" style="width:100%;height:auto;background:#fff;border-radius:.3em"><path fill="#000" d="'+d+'"/></svg>'}; const wcShow=uri=>{ wkList.textContent=""; const box=document.createElement("div"); box.className="wcq"; box.innerHTML=qrSvg(QR8.build(uri)); const cp=document.createElement("button"); cp.type="button";cp.className="fclink";cp.textContent="copy link"; cp.onclick=()=>{navigator.clipboard?navigator.clipboard.writeText(uri).then(()=>{cp.textContent="copied"}).catch(()=>{cp.textContent="copy blocked"}):cp.textContent="copy blocked"}; const note=document.createElement("div"); note.className="wcn"; note.textContent="Scan with a mobile wallet, or "; note.appendChild(cp); const nd=document.createElement("div"); nd.className="wcn wcnode"; nd.appendChild(document.createTextNode("Reads via "+wcHost()+" ")); const ch=document.createElement("button"); ch.type="button";ch.className="fclink";ch.textContent="change"; ch.onclick=()=>{ const v=prompt("HTTPS Ethereum RPC used for reads — it is pinned to the front of the read pool (the DAO-curated list, then the page's seeds)." +" Signing always goes to your wallet. Leave blank for the default.",wcNode()); if(v===null)return; const t=v.trim(); try{if(!t)localStorage.removeItem(RPC_STORE); else if(/^https:\/\/\S+$/i.test(t))localStorage.setItem(RPC_STORE,t); else return}catch{} nd.firstChild.nodeValue="Reads via "+wcHost()+" "}; nd.appendChild(ch); wkList.appendChild(box);wkList.appendChild(note);wkList.appendChild(nd); wkHdr.textContent="WalletConnect"; wkWrap.classList.remove("hide"); const back=document.activeElement; cp.focus({preventScroll:true}); return new Promise((res,rej)=>{ const esc=e=>{if(e.key==="Escape")stop()}; const off=()=>{removeEventListener("keydown",esc);removeEventListener("keydown",wkTrap,true); wkWrap.onclick=null;if(back&&back.focus)try{back.focus()}catch{}}; const stop=()=>{off();rej(Object.assign(Error(""),{code:4001}))}; wkWrap.onclick=e=>{if(e.target===wkWrap)stop()}; addEventListener("keydown",esc); addEventListener("keydown",wkTrap,true); wcShow.settle=()=>{off();res()}})}; async function wcConnect(){ const meta={name:"zSwap",description:"zSwap",url:location.origin,icons:[]}; const{uri,done}=await wcRelay.pair(WC_PID,meta); const bail=wcShow(uri); stat.textContent="Scan the code with your wallet."; let sess; try{sess=await Promise.race([done,bail])} catch(e){wcRelay.close();throw e} finally{if(wcShow.settle)wcShow.settle(); wkHdr.textContent="Choose a wallet"; wkWrap.classList.add("hide")} const accts=sess.accounts.filter(a=>a.slice(0,9)==="eip155:1:").map(a=>a.split(":").pop()); if(!accts.length){wcRelay.close();throw Error("That wallet settled no Ethereum mainnet account — switch it to Ethereum and reconnect.")} wcRead=sess.methods.indexOf("eth_call")>=0; const read=async(method,params)=>{ try{return await wcRpc(method,params)} catch(e){ if(e&&e.code!==undefined||!wcRead)throw e; return wcRelay.request(sess,method,params||[])}}; const evs=[]; wcRelay.on(ev=>{if(ev&&ev.t==="closed")location.reload(); if(!ev||ev.t!=="session")return; const s=ev.m; if(!s)return; if(s.method==="wc_sessionUpdate")return evs.forEach(f=>f("reload",null)); if(s.method!=="wc_sessionEvent")return; const e=s.params&&s.params.event||{};let d=e.data; if(e.name==="chainChanged"&&typeof d==="string"&&d.includes(":"))d=d.split(":").pop(); if(e.name==="accountsChanged"&&Array.isArray(d))d=d.map(a=>typeof a==="string"&&a.includes(":")?a.split(":").pop():a); if(e.name==="chainChanged"||e.name==="accountsChanged")evs.forEach(f=>f(e.name,d))}); return{request:({method,params})=> method==="eth_requestAccounts"||method==="eth_accounts"?Promise.resolve(accts): method==="eth_chainId"?Promise.resolve("0x1"): wcToWallet(method)?wcRelay.request(sess,method,params||[]): read(method,params), on:(t,f)=>{if(t==="chainChanged"||t==="accountsChanged")evs.push((n,d)=>{if(n==="reload"||n===t)try{f(d)}catch{}})}}} let wcRead=true; const wkWrap=document.getElementById("wkWrap"),wkList=document.getElementById("wkList"); const wkChoices=()=>W6963.map(d=>({name:d.info.name,icon:d.info.icon,rdns:d.info.rdns,get:()=>d.provider})) .concat([{name:"WalletConnect",svg:WC_ICON,rdns:"wc",get:wcConnect}]); const WK="zswap:wk"; const wkRestore=async()=>{ const id=LS[WK]; if(PROV||!id||id==="wc")return; wkScan(); for(let i=0;i<8;i++){ const m=W6963.filter(x=>x.info.rdns===id),d=m.length===1?m[0]:null; if(d){ try{const a=await Promise.race([d.provider.request({method:"eth_accounts"}), new Promise(r=>setTimeout(()=>r(null),1500))]); if(a&&a[0])PROV=d.provider}catch{} return} await new Promise(r=>setTimeout(r,50)); }}; const wkTrap=e=>{ if(e.key!=="Tab")return; const f=[...wkPanel.querySelectorAll('button,[href],input,select,textarea,[tabindex]:not([tabindex="-1"])')] .filter(x=>!x.disabled&&x.offsetParent!==null); if(!f.length)return; const a=f[0],z=f[f.length-1],cur=document.activeElement; if(e.shiftKey?cur===a||!wkPanel.contains(cur):cur===z){e.preventDefault();(e.shiftKey?z:a).focus()} }; const wkPick=cs=>new Promise(res=>{ wkList.textContent=""; let done=false; const back=document.activeElement; const end=v=>{if(done)return;done=true; wkWrap.classList.add("hide");removeEventListener("keydown",esc); removeEventListener("keydown",wkTrap,true); if(back&&back.focus)try{back.focus()}catch{} res(v)}; const esc=e=>{if(e.key==="Escape")end(null)}; cs.forEach(c=>{ const b=document.createElement("button"); b.type="button";b.className="tkr"; if(c.icon){const im=document.createElement("img");im.src=c.icon;im.alt="";b.appendChild(im)} else if(c.svg){const sp=document.createElement("span");sp.className="wki";sp.innerHTML=c.svg;b.appendChild(sp)} b.appendChild(document.createTextNode(c.name)); b.onclick=()=>end(c); wkList.appendChild(b)}); wkWrap.onclick=e=>{if(e.target===wkWrap)end(null)}; addEventListener("keydown",esc); addEventListener("keydown",wkTrap,true); wkWrap.classList.remove("hide"); wkList.firstChild?.focus()}); async function wkResolve(){ if(PROV)return true; wkScan(); const inj=window.ethereum; if(W6963.length<2&&inj)return true; const cs=wkChoices(); const c=await wkPick(cs); if(!c)return false; stat.textContent="Connecting…"; try{PROV=await c.get()} catch(e){if(!isRejection(e))err(e);return false} try{if(c.rdns)LS[WK]=c.rdns}catch{} wkBind(PROV); return true} async function connect(){ if(connecting)return; connecting=true; try{ if(!await wkResolve())return; if(!prov()){stat.textContent=NO_WALLET;return} swap.textContent="Connecting…";swap.disabled=true; lnGo.textContent="Connecting…";lnGo.disabled=true; wnGo.textContent="Connecting…";wnGo.d
0x68575b07…67c7·#25,913,246·0xb6f4ad5f…183b4c
>=mc3Cap&&mc3Cap<MC3_MAX&&r.every(x=>x!=null)&&++mc3Won>=2){mc3Cap++;mc3Won=0;mc3Save()} return r; } catch(e){ mc3Won=0; if(cs.length<2||depth>=8){mcGaveUp+=cs.length;return cs.map(()=>null)} const h=cs.length>>1; if(h<mc3Cap){mc3Cap=h;mc3Save()} await new Promise(r=>setTimeout(r,150*(depth+1))); return fan(h); } }; let bnVal="",bnAt=0,bnJob=null; const BN_TTL=12000; const bnFetch=()=>bnJob||(bnJob=rpc("eth_blockNumber",[]) .then(n=>{bnVal=n;bnAt=Date.now();return n}) .finally(()=>{bnJob=null})); const blockNow=()=>{ const age=Date.now()-bnAt; if(!bnVal||age>=BN_TTL)return bnFetch(); if(age>BN_TTL/2)bnFetch().catch(()=>{}); return Promise.resolve(bnVal); }; const codeSeen={}; const CODE_MISS_TTL=60000; const hasCode=async a=>{ if(codeSeen[a]===true)return true; const miss=codeSeen[a]; if(miss&&Date.now()-miss<CODE_MISS_TTL)return false; const h=await rpc("eth_getCode",[a,L]); const ok=(h||"").length>4; codeSeen[a]=ok||Date.now(); return ok; }; const REF_N=100n; const CH_WICK_CAP=3; const IMPACT_HIDE=50n, IMPACT_WARN=500n, IMPACT_CONFIRM=1500n, IMPACT_TYPED=3000n; const impactBps=(v,ref,eo)=>{if(!v||!ref)return null;const lin=ref*REF_N; if(lin===0n)return 0n; return eo?(v<=lin?0n:(v-lin)*10000n/lin):(v>=lin?0n:(lin-v)*10000n/lin)}; const safeSym=s=>String(s??"").replace(/[<>&"'`]/g,"") .replace(/[\u0000-\u001f\u007f]/g,"").trim().slice(0,16)||"?"; const safeUrl=u=>{u=String(u??"").trim(); return u.length<=32768&&/^(?:https:\/\/|data:image\/[a-z.+-]+;base64,)[^\s<>"'`\\]+$/i.test(u)?u:""}; const safeDataUrl=u=>{u=String(u??"").trim(); return u.length<=32768&&/^data:image\/[a-z.+-]+;base64,[A-Za-z0-9+/=]+$/i.test(u)?u:""}; const genIcon=sym=>`<svg width="20" height="20" viewBox="0 0 32 32"><circle cx="16" cy="16" r="16" fill="#999"/><text x="16" y="21" text-anchor="middle" fill="#fff" font-size="13" font-weight="600" font-family="system-ui">${(safeSym(sym)[0]||"?").toUpperCase()}</text></svg>`; const STORE=()=>CHAIN_ID===1?"zswap:custom":"zswap:custom:"+CHAIN_ID; function keepToken(t){ if(!t||!t.addr)return; const a=t.addr.toLowerCase(); try{const arr=JSON.parse(localStorage.getItem(STORE())||"[]") .filter(x=>String(x?.addr||"").toLowerCase()!==a); arr.push({sym:t.sym,addr:a,dec:t.dec,std:t.std,cause:t.cause?1:0}); localStorage.setItem(STORE(),JSON.stringify(arr))}catch{} } const SEL_SUPPORTS="01ffc9a7",IFACE_721="80ac58cd",IFACE_1155="d9b67a26"; const supports=id=>"0x"+SEL_SUPPORTS+id.padEnd(64,"0"); async function addCustomToken(a,keep=1){ a=a.toLowerCase(); if(!/^0x[0-9a-f]{40}$/.test(a))throw Error("invalid address"); const i=TOKENS.findIndex(t=>t.addr.toLowerCase()===a); if(i>=0)return i; if((await rpcRead("eth_getCode",[a,L],2)).length<5)throw Error("no contract at that address"); const[symRet,decRet,is721,is1155]=await mc3Deep([ {to:a,data:"0x95d89b41"},{to:a,data:"0x313ce567"}, {to:a,data:supports(IFACE_721)},{to:a,data:supports(IFACE_1155)}]); let uriRet=null; try{uriRet=await rpc(C,[{to:a,data:"0x"+SEL_CONTRACTURI},L])}catch{} const yes=h=>{try{return !!h&&BigInt(h)===1n}catch{return false}}; const dup=TOKENS.findIndex(t=>t.addr.toLowerCase()===a); if(dup>=0)return dup; let sym="?"; try{sym=decodeString(symRet||"0x").trim().replace(/[<>&"'`]/g,"").replace(/[\u0000-\u001f\u007f]/g,"").slice(0,16)||"?"}catch{} if(TOKENS.some(t=>t.sym.toLowerCase()===sym.toLowerCase()))sym+=" "+a.slice(0,6)+"\u2026"; if(yes(is1155)&&!yes(is721))throw Error("ERC-1155 is not supported"); const std=yes(is721)?"nft":"ft"; let dec=0; if(std==="ft"){ dec=Number(BigInt(decRet&&decRet!=="0x"?decRet:"0x0")); if(!decRet||decRet==="0x"||!Number.isInteger(dec)||dec<0||dec>36)throw Error("not an ERC-20 or ERC-721"); } let selfArt="";try{selfArt=imgFromContractURI(uriRet?decodeString(uriRet):"")}catch{} if(selfArt)logoCache[a]=selfArt; TOKENS.push({sym,addr:a,dec,std,cust:1, icon:selfArt?`<img width="20" height="20" style="border-radius:50%;object-fit:cover" src="${selfArt}" alt="">`:genIcon(sym)}); if(keep)keepToken(TOKENS[TOKENS.length-1]); return TOKENS.length-1; } function restoreCustom(){try{for(const t of JSON.parse(localStorage.getItem(STORE())||"[]")){ const addr=(t?.addr||"").toLowerCase(); const dec=+t?.dec; if(/^0x[0-9a-f]{40}$/.test(addr)&&Number.isInteger(dec)&&dec>=0&&dec<=36&&!TOKENS.some(x=>x.addr.toLowerCase()===addr)){ const sym=String(t.sym||"?").replace(/[<>&"'`]/g,"").replace(/[\u0000-\u001f\u007f]/g,"").slice(0,16)||"?"; TOKENS.push({sym,addr,dec,std:t.std==="nft"?"nft":"ft",cust:1,icon:genIcon(sym), ...(t.cause?{cause:1}:{})}); } }}catch{}} restoreCustom(); const QUOTE_TTL=45000; let account=null,connecting=false,last=null,fromBalance=0n,mode="in",bSeq=0,tab="swap",sendReady=null,sSeq=0,sendD="0",bookD="86400"; let quoting=false; let lqMode=false; const readBal=async t=>BigInt(t.addr===ZERO ?await rpc("eth_getBalance",[account,L]) :await rpc(C,[{to:t.addr,data:"0x"+SEL_BALANCEOF+encAddr(account)},L])); const showBal=(el,v,dec)=>{const n=+formatUnits(v,dec); const pretty=n>=1?n.toLocaleString("en-US",{maximumFractionDigits:4}):sigAmt(v,dec,4); el.innerHTML=`Balance: ${pretty} <a href="#" title="Use maximum (leaves gas for ETH)">Max</a>`}; async function refreshBalance(){ const f=TOKENS[fromSel.value]; if(!account||!f){bal.textContent="";fromBalance=0n;render();return} const my=++bSeq; try{ const v=await readBal(f); if(my!==bSeq)return; fromBalance=v;showBal(bal,v,f.dec); }catch{if(my===bSeq){bal.textContent="";fromBalance=0n}} refreshBalance1(); render(); } let toBalance=0n,b1Seq=0; async function refreshBalance1(){ const t=TOKENS[toSel.value]; bal1.classList.toggle("hide",!lqMode); if(!lqMode||!account||!t){bal1.textContent="";toBalance=0n;return} const my=++b1Seq; try{ const v=await readBal(t); if(my!==b1Seq)return; toBalance=v;showBal(bal1,v,t.dec); }catch{if(my===b1Seq){bal1.textContent="";toBalance=0n}} } const optsHtml=()=>{ let ft="",nft=""; let born=""; let cau=""; TOKENS.forEach((t,i)=>{const o=`<option value="${i}">${t.sym}</option>`; if(t.std==="nft")nft+=o;else if(t.cause)cau+=o;else if(t.launched)born+=o;else ft+=o}); return ft +(cau?`<optgroup label="Causes — backed and burned, never traded">${cau}</optgroup>`:"") +(born?`<optgroup label="Launched on zSwap — not curated">${born}</optgroup>`:"") +(nft?`<optgroup label="NFT collections — auction only">${nft}</optgroup>`:"") +`<option value="__custom">+ Custom token…</option>`; }; const rebuild=()=>{for(const s of[fromSel,toSel]){const v=s.value;s.innerHTML=optsHtml();s.value=v}}; const pairAddrs=()=>({fa:TOKENS[fromSel.value]?.addr,ta:TOKENS[toSel.value]?.addr, fi:String(fromSel.value),ti:String(toSel.value)}); const restorePair=k=>{ if(!k)return; const at=a=>a?TOKENS.findIndex(t=>t.addr.toLowerCase()===a.toLowerCase()):-1; const held=(cur,want)=>String(cur)===want||!TOKENS[cur]; if(held(fromSel.value,k.fi)){const i=at(k.fa);if(i>=0)fromSel.value=i} if(held(toSel.value,k.ti)){const i=at(k.ta);if(i>=0&&String(i)!==String(fromSel.value))toSel.value=i} for(const s of[fromSel,toSel]){ const want=s.dataset.addr; if(!want||held(s.value,s===fromSel?k.fi:k.ti))continue; const i=at(want); if(i>=0&&(s===fromSel||String(i)!==String(fromSel.value)))s.value=i; } }; const isNft=v=>TOKENS[v]?.std==="nft"; let pairChosen=false; const wrapTwin=(a,b)=>{ const x=(a||"").toLowerCase(),y=(b||"").toLowerCase(),w=WETH.toLowerCase(); return (x===ZERO&&y===w)||(x===w&&y===ZERO); }; const rankedOutputs=fi=>TOKENS.map((t,i)=>i) .filter(i=>i!==fi&&!isNft(String(i))&&!wrapTwin(TOKENS[i].addr,TOKENS[fi]?.addr)); const deadOutputs=new Set(); let pairAuto=false; const rankedDefaults=()=>{ if(TOKENS.length<2)return; const fi=TOKENS.findIndex((t,i)=>!isNft(String(i))); if(fi<0)return; const ti=rankedOutputs(fi).find(i=>!deadOutputs.has(TOKENS[i].addr.toLowerCase())); if(ti==null)return; fromSel.value=String(fi);toSel.value=String(ti); fromSel.dataset.prev=fromSel.value;toSel.dataset.prev=toSel.value; pairAuto=true; try{LS["zswap:pair"]=JSON.stringify([TOKENS[fi].addr,TOKENS[ti].addr])}catch{} }; const me=()=>(account||"").toLowerCase(); const advancePastDeadPair=()=>{ if(!pairAuto||pairChosen)return false; const cur=TOKENS[toSel.value]; if(!cur)return false; deadOutputs.add(cur.addr.toLowerCase()); const fi=Number(fromSel.value); const next=rankedOutputs(fi).find(i=>!deadOutputs.has(TOKENS[i].addr.toLowerCase())); if(next==null)return false; toSel.value=String(next);toSel.dataset.prev=toSel.value; syncDisabled();syncIcons(); return true; }; const settlePair=keep=>{if(pairChosen)restorePair(keep);else rankedDefaults()}; let listFresh=false; const adoptList=keep=>{ cbDisarm(); listFresh=true; rebuild(); settlePair(keep); if(!TOKENS[fromSel.value])fromSel.value=0; if(!TOKENS[toSel.value]||toSel.value===fromSel.value)toSel.value=TOKENS[1]?1:0; syncDisabled();syncIcons(); }; const syncDisabled=keep=>{ const pair=tab!=="send"; const nftOk=tab==="book"; if(pair&&fromSel.value===toSel.value){ const alt=TOKENS.findIndex((t,i)=>String(i)!==fromSel.value&&(nftOk||t.std!=="nft")); if(alt>=0){toSel.value=String(alt);toSel.dataset.prev=toSel.value} } if(pair&&nftOk&&isNft(fromSel.value)&&isNft(toSel.value)){ const give=keep===toSel?fromSel:toSel,stay=give===fromSel?toSel:fromSel; const alt=TOKENS.findIndex((t,i)=>t.std!=="nft"&&String(i)!==stay.value); if(alt>=0){give.value=String(alt);give.dataset.prev=give.value} } for(const[sel,other]of[[fromSel,toSel],[toSel,fromSel]]) for(const opt of sel.options) opt.disabled=opt.value!=="__custom"&&((pair&&opt.value===other.value)||(!nftOk&&isNft(opt.value))); }; fromSel.innerHTML=optsHtml();fromSel.value=0; toSel.innerHTML=optsHtml(); const LAST_PAIR="zswap:pair"; const seatPair=()=>{ try{ const [a,b]=JSON.parse(LS[LAST_PAIR]||"[]"); const fi=TOKENS.findIndex(t=>t.addr.toLowerCase()===String(a).toLowerCase()); const ti=TOKENS.findIndex(t=>t.addr.toLowerCase()===String(b).toLowerCase()); if(fi>=0&&ti>=0&&fi!==ti){fromSel.value=String(fi);toSel.value=String(ti);return true} }catch{} return false; }; if(!seatPair())toSel.value=String(Math.max(1,TOKENS.findIndex(t=>t.sym==="USDC"))); syncDisabled(); const iconShown=["",""]; const syncIcons=()=>{ [[fromIcon,fromSel],[toIcon,toSel]].forEach(([el,s],i)=>{ const t=TOKENS[s.value],ic=t?.icon||""; if(iconShown[i]!==ic){iconShown[i]=ic;el.innerHTML=ic} if(t)s.dataset.addr=t.addr}); syncPickers()}; const tkPanel=document.getElementById("tkPanel"),tkFind=document.getElementById("tkFind"), tkList=document.getElementById("tkList"); let tkSel=null,tkCur=-1; const pickBtn=sel=>document.getElementById(sel===fromSel?"fromPick":"toPick"); const syncPickers=()=>{ for(const sel of [fromSel,toSel]){ const b=pickBtn(sel);if(!b)continue; const t=TOKENS[sel.value]; b.classList.add("on");sel.classList.add("rich"); b.querySelector(".tkbs").textContent=t?t.sym:(sel.selectedOptions[0]?.textContent||""); b.setAttribute("aria-label",`Token: ${t?t.sym:"none"} - change`); const pill=b.closest(".pill"); if(pill){ const name=t?.name&&t.name.toLowerCase()!==t.sym.toLowerCase()?t.name:""; const extra=[name,t?.desc].filter(Boolean); if(extra.length)pill.title=[`${t.sym}${name?" — "+name:""}`,t.desc].filter(Boolean).join("\n\n"); else pill.removeAttribute("title"); } } }; const tkAct=id=>{if(id)tkFind.setAttribute("aria-activedescendant",id); else tkFind.removeAttribute("aria-activedescendant")}; const tkRows=()=>{ const q=tkFind.value.trim().toLowerCase(); tkList.textContent=""; tkCur=-1;tkAct(""); const frag=document.createDocumentFragment(); let n=0,group=""; for(const opt of tkSel.options){ const t=TOKENS[opt.value]; if(q){ const hay=[opt.textContent,t?.name,t?.addr].filter(Boolean).join(" ").toLowerCase(); if(!hay.includes(q))continue; } const g=opt.parentElement?.label||(opt.value==="__custom"?"": (listLive?(rankFellBack?"zList — listing order":"zList — ranked by conviction"):"Built-in token list")); if(g&&g!==group){const h=document.createElement("div");h.className="tkg";h.textContent=g;frag.appendChild(h);group=g} const r=document.createElement("button"); r.type="button";r.className="tkr";r.setAttribute("role","option"); r.id="tkr"+n; r.dataset.value=opt.value; r.setAttribute("aria-selected",String(opt.value===tkSel.value)); if(opt.disabled)r.setAttribute("aria-disabled","true"); const ic=document.createElement("span");ic.className="tki"; if(t?.icon)ic.innerHTML=t.icon; r.appendChild(ic); const tx=document.createElement("span");tx.className="tkt"; const top=document.createElement("span");top.className="tks"; const b=document.createElement("b");b.textContent=opt.textContent.trim();top.appendChild(b); if(t?.name&&t.name.toLowerCase()!==b.textContent.toLowerCase()){ const nm=document.createElement("span");nm.className="tkn";nm.textContent=t.name;top.appendChild(nm); } tx.appendChild(top); if(t?.desc){const d=document.createElement("span");d.className="tkd";d.textContent=t.desc;tx.appendChild(d)} r.appendChild(tx); if(opt.value===tkSel.value){const m=document.createElement("span");m.className="tkmark";m.textContent="✓";r.appendChild(m)} frag.appendChild(r);n++; } if(!n){ const a=/^0x[0-9a-f]{40}$/.test(q)?q:""; if(a){ const r=document.createElement("button"); r.type="button";r.className="tkr"; const tx=document.createElement("span");tx.className="tkt"; const top=document.createElement("span");top.className="tks"; const b=document.createElement("b");b.textContent="Use "+short(a);top.appendChild(b); tx.appendChild(top); const d=document.createElement("span");d.className="tkd"; d.textContent="Not listed \u2014 read it from chain"; tx.appendChild(d);r.appendChild(tx); r.onclick=async e=>{ e.stopPropagation(); if(r.dataset.busy)return; r.dataset.busy="1";b.textContent="Reading\u2026"; try{const i=await addCustomToken(a);rebuild();tkChoose(String(i))} catch(e){delete r.dataset.busy;b.textContent="Use "+short(a); d.textContent=(e&&e.message)||"could not read that address"}}; frag.appendChild(r); }else{const e=document.createElement("div");e.className="tkempty";e.textContent="No token matches that.";frag.appendChild(e)} } tkList.classList.toggle("fresh",listFresh); listFresh=false; tkList.appendChild(frag); tkCur=-1; }; const tkOpen=sel=>{ if(tkSel&&tkSel!==sel)pickBtn(tkSel)?.setAttribute("aria-expanded","false"); tkSel=sel;tkFind.value="";tkRows(); tkPanel.classList.remove("hide"); if(matchMedia("(pointer:coarse)").matches){ tkPanel.style.left=tkPanel.style.top=tkPanel.style.maxHeight=tkPanel.style.width=""; pickBtn(sel).setAttribute("aria-expanded","true"); tkFind.focus({preventScroll:true}); return; } const b=pickBtn(sel).getBoundingClientRect(); const card=document.querySelector(".card").getBoundingClientRect(); const w=Math.min(Math.max(card.width,240),innerWidth-16); tkPanel.style.width=w+"px"; const left=Math.max(8,Math.min(card.left,innerWidth-w-8)); const above=b.top,below=innerHeight-b.bottom; const flip=below<240&&above>below; const MENU_MAX=520; const room=Math.min(MENU_MAX,Math.max(140,(flip?above:below)-14)); tkPanel.style.maxHeight=room+"px"; const h=Math.min(tkPanel.offsetHeight,room); const top=flip?b.top-h-6:b.bottom+6; tkPanel.style.left=left+"px"; tkPanel.style.top=Math.max(8,Math.min(top,innerHeight-h-8))+"px"; pickBtn(sel).setAttribute("aria-expanded","true"); tkFind.focus({preventScroll:true}); }; const tkClose=()=>{ if(!tkSel)return; pickBtn(tkSel)?.setAttribute("aria-expanded","false"); tkPanel.classList.add("hide");tkSel=null;tkCur=-1;tkAct(""); }; const tkChoose=v=>{ const sel=tkSel;if(!sel)return; tkClose(); if(sel.value===v&&v!=="__custom")return; sel.value=v; sel.dispatchEvent(new Event("change",{bubbles:true})); }; for(const sel of [fromSel,toSel]){ const b=pickBtn(sel); if(b)b.addEventListener("click",e=>{e.stopPropagation();tkSel===sel?tkClose():tkOpen(sel)}); } tkList.addEventListener("click",e=>{ const r=e.target.closest(".tkr"); if(!r||r.getAttribute("aria-disabled")==="true")return; tkChoose(r.dataset.value); }); tkFind.addEventListener("input",tkRows); tkPanel.addEventListener("keydown",e=>{ const rows=[...tkList.querySelectorAll('.tkr:not([aria-disabled="true"])')]; if(e.key==="Escape"){e.preventDefault();const s=tkSel;tkClose();pickBtn(s)?.focus();return} if(e.key==="ArrowDown"||e.key==="ArrowUp"){ e.preventDefault(); if(!rows.length)return; tkCur=e.key==="ArrowDown"?(tkCur+1)%rows.length:(tkCur<=0?rows.length:tkCur)-1; rows.forEach((r,i)=>r.classList.toggle("cur",i===tkCur)); rows[tkCur].scrollIntoView({block:"nearest"}); tkAct(rows[tkCur].id); return; } if(e.key==="Enter"){ const r=tkCur>=0?rows[tkCur]:rows.length===1?rows[0]:null; if(r){e.preventDefault();r.dataset.value?tkChoose(r.dataset.value):r.click()} } }); tkPanel.addEventListener("click",e=>e.stopPropagation()); document.addEventListener("click",tkClose); addEventListener("resize",()=>{if(!matchMedia("(pointer:coarse)").matches)tkClose()}); addEventListener("scroll",e=>{if(tkSel&&!tkPanel.contains(e.target))tkClose()},true); const render=()=>{ if(!account){swap.textContent="Connect Wallet";swap.disabled=false;return} if(tab==="book"){ rate.textContent=""; const f=TOKENS[fromSel.value],t=TOKENS[toSel.value]; if(!f||!t){swap.textContent="Place order";swap.disabled=true;return} if(f.std==="nft"&&t.std==="nft"){ swap.textContent="One side must be a token";swap.disabled=true;return} if(t.std==="nft"){ const oneId=nftIdIn(t.addr,nftId.value); if(oneId&&!/^\d+$/.test(oneId)){ swap.textContent=isWns(t.addr)?"Token id must be a number, or a .wei name":"Token id must be a number"; swap.disabled=true;return} const climbing=kind.value==="floor"; if(kind.value==="dutch"){ swap.textContent="A bid is fixed or climbing, not decaying";swap.disabled=true;return} if(oneId&&climbing){ swap.textContent="A climbing bid takes any id — clear the token id"; swap.disabled=true;return} if(!oneId){ const q=outAmt.value.trim(); if(q&&!/^\d+$/.test(q)){ swap.textContent="How many? a whole number";swap.disabled=true;return} if(q&&!BigInt(q)){swap.textContent="Bid for at least one";swap.disabled=true;return} if(q&&BigInt(q)>(1n<<128n)-1n){ swap.textContent="Too many to bid for at once";swap.disabled=true;return} } let bidAmt=0n; try{bidAmt=amt.value.trim()?parseUnits(amt.value.trim(),f.dec):0n}catch{} if(!bidAmt){ swap.textContent=climbing?"Set the most you will pay":"Set what you will pay"; swap.disabled=true;return} if(climbing){ let openAmt=0n; try{openAmt=floorAmt.value.trim()?parseUnits(floorAmt.value.trim(),f.dec):0n}catch{} if(!openAmt){swap.textContent="Set an opening bid";swap.disabled=true;return} if(openAmt>bidAmt){swap.textContent="Opening bid exceeds the maximum";swap.disabled=true;return} if(!+dly.value){swap.textContent="A climbing bid needs a window";swap.disabled=true;return} }else if(!oneId&&!+dly.value){ swap.textContent="A collection bid needs a window";swap.disabled=true;return} if(bidAmt>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} swap.textContent=oneId?`Bid for ${nftLabel(t.sym,t.addr,nftId.value)}` :climbing?`Climbing bid for any ${t.sym}`:`Bid for any ${t.sym}`; swap.disabled=false;return} if(f.std==="nft"){ if(kind.value!=="fixed"){ swap.textContent="An NFT sells at a fixed price";swap.disabled=true;return} if(!/^\d+$/.test(nftIdIn(f.addr,nftId.value))){ swap.textContent=isWns(f.addr)?"Name the .wei name, or the token id, to sell":"Name the token id to sell"; swap.disabled=true;return} let ask=0n; try{ask=outAmt.value.trim()?parseUnits(outAmt.value.trim(),t.dec):0n}catch{} if(!ask){swap.textContent="Set a price";swap.disabled=true;return} swap.textContent=`List ${nftLabel(f.sym,f.addr,nftId.value)}`;swap.disabled=false;return} const dutch=kind.value==="dutch",bid=kind.value==="floor"; let sell=0n,want=0n,floor=0n,badOrderAmount=false; try{if(amt.value.trim())sell=parseUnits(amt.value.trim(),f.dec); if(outAmt.value.trim())want=parseUnits(outAmt.value.trim(),t.dec); if((dutch||bid)&&floorAmt.value.trim())floor=parseUnits(floorAmt.value.trim(),bid?f.dec:t.dec)} catch{badOrderAmount=true} if(badOrderAmount){swap.textContent="Invalid order amount";swap.disabled=true;return} if(!sell||!want){swap.textContent=bid?"Place bid":"Place order";swap.disabled=true;return} if(bid){ if(t.addr===ZERO){swap.textContent="Pick a token to bid for";swap.disabled=true;return} if((f.addr===ZERO?WETH:f.addr).toLowerCase()===t.addr.toLowerCase()){ swap.textContent="Pick different token";swap.disabled=true;return} if(!floor){swap.textContent="Set an opening bid";swap.disabled=true;return} if(floor>sell){swap.textContent="Opening exceeds maximum";swap.disabled=true;return} if(+dly.value===0){swap.textContent="Choose a bid window";swap.disabled=true;return} if(sell>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} swap.textContent=`Bid ${trimAmt(floor,f.dec)} → ${trimAmt(sell,f.dec)} ${f.sym} for ${trimAmt(want,t.dec)} ${t.sym}`; swap.disabled=false;return; } if(f.addr===ZERO&&t.addr===WETH){swap.textContent="Pick different token";swap.disabled=true;return} if(!dutch&&f.addr===WETH&&t.addr===ZERO){swap.textContent="Pick different token";swap.disabled=true;return} if(dutch&&floor>want){swap.textContent="Floor exceeds start";swap.disabled=true;return} if(dutch&&+dly.value===0){swap.textContent="Choose decay duration";swap.disabled=true;return} if(sell>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} swap.textContent=dutch ?`Dutch ${trimAmt(sell,f.dec)} ${f.sym} · ${trimAmt(want,t.dec)} → ${trimAmt(floor,t.dec)} ${t.sym}` :`Place ${fill.value==="1"?"":"all-or-nothing "}order \u2014 ${trimAmt(sell,f.dec)} ${f.sym} \u2192 ${trimAmt(want,t.dec)} ${t.addr===ZERO?"WETH":t.sym}`; { const qs=t.addr===ZERO?"ETH":t.sym; rate.textContent=dutch ?`Falls from ${trimAmt(want,t.dec)} to ${trimAmt(floor,t.dec)} ${qs} over ${rel(+dly.value)}, then rests at ${trimAmt(floor,t.dec)} ${qs} ` +(+dRest.value?`for ${rel(+dRest.value)} and returns to you`:`until you cancel`) :`${fill.value==="1"?"Fills in any amount":"Fills all at once"} at ${trimAmt(want,t.dec)} ${t.addr===ZERO?"WETH":t.sym} for ${trimAmt(sell,f.dec)} ${f.sym}` +(+dly.value?`, expiring in ${rel(+dly.value)}`:", and never expires"); } swap.disabled=false;return; } if(tab==="send"){ if(!sendReady){ let n=0n;try{n=parseUnits(amt.value.trim(),TOKENS[fromSel.value].dec)}catch{} swap.textContent=n>0n?"Name a recipient":"Enter an amount"; swap.disabled=true;return} if(sendReady.amount>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} const a=`${trimAmt(sendReady.amount,sendReady.token.dec)} ${sendReady.token.sym}`,dv=+dly.value; if(dv&&sendTipWei>0n&&sendReady.token.addr===ZERO &&sendReady.amount+sendTipWei>fromBalance){ swap.textContent="Insufficient ETH for amount + keeper tip";swap.disabled=true;return} swap.textContent=(dv?`Lock ${a} for ${rel(dv)}`:`Send ${a}`)+` \u2192 ${sendReady.label}`; swap.disabled=false;return; } if(quoting){swap.textContent="Quoting…";swap.disabled=true;return} if(!last){swap.textContent="Swap";swap.disabled=true;return} if(last.amountIn>fromBalance){swap.textContent="Insufficient balance";swap.disabled=true;return} swap.textContent="Swap";swap.disabled=false; }; syncIcons(); render(); const isRejection=e=>e?.code===4001||/user\s+reject|user\s+denied/i.test(e?.message||""); const W6963=[]; addEventListener("eip6963:announceProvider",e=>{ const d=e.detail; if(!d||!d.provider||!d.info||W6963.some(x=>x.info.uuid===d.info.uuid))return; W6963.push(d)}); const wkScan=()=>{try{dispatchEvent(new Event("eip6963:requestProvider"))}catch{}}; wkScan(); const WC_PID="1e8390ef1c1d8a185e035912a1409749"; const WC_ICON='<svg viewBox="0 0 400 400" fill="none" aria-hidden="true"><circle cx="200" cy="200" r="200" fill="#3396ff"/><path fill="#fff" d="m122.519 148.965c42.791-41.729 112.171-41.729 154.962 0l5.15 5.022c2.14 2.086 2.14 5.469 0 7.555l-17.617 17.18c-1.07 1.043-2.804 1.043-3.874 0l-7.087-6.911
1-50 of 135