0xacfba7ce…4082·#25,751,346·view on Etherscan
atUnits(v,dec);
const pretty=n>=1?n.toLocaleString("en-US",{maximumFractionDigits:4}):n.toPrecision(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="";
TOKENS.forEach((t,i)=>{const o=`<option value="${i}">${t.sym}</option>`;
if(t.std==="nft")nft+=o;else ft+=o});
return ft
+(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 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()};
const adoptList=keep=>{
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 tkRows=()=>{
const q=tkFind.value.trim().toLowerCase();
tkList.textContent="";
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||"";
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.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 e=document.createElement("div");e.className="tkempty";e.textContent="No token matches that.";frag.appendChild(e)}
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();
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+scrollX,scrollX+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+scrollY-h-6:b.bottom+scrollY+6;
tkPanel.style.left=left+"px";
tkPanel.style.top=Math.max(scrollY+8,Math.min(top,scrollY+innerHeight-h-8))+"px";
pickBtn(sel).setAttribute("aria-expanded","true");
tkFind.focus();
};
const tkClose=()=>{
if(!tkSel)return;
pickBtn(tkSel)?.setAttribute("aria-expanded","false");
tkPanel.classList.add("hide");tkSel=null;
};
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"});
return;
}
if(e.key==="Enter"){
const r=tkCur>=0?rows[tkCur]:rows.length===1?rows[0]:null;
if(r){e.preventDefault();tkChoose(r.dataset.value)}
}
});
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=nftId.value.trim();
if(oneId&&!/^\d+$/.test(oneId)){
swap.textContent="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 ${t.sym} #${oneId}`
: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(nftId.value.trim())){
swap.textContent="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 ${f.sym} #${nftId.value.trim()}`;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||"");
async function connect(){
if(!window.ethereum){stat.textContent=NO_WALLET;return}
if(connecting)return;
connecting=true;
swap.textContent="Connecting…";swap.disabled=true;
lnGo.textContent="Connecting…";lnGo.disabled=true;
try{
let accs;
try{accs=await rpc("eth_requestAccounts",[])}
catch(e){err(e);return}
if(!accs[0])return;
let chainId=await rpc(I,[]);
if(+chainId!==1){
try{await rpc("wallet_switchEthereumChain",[{chainId:"0x1"}])}
catch(e){stat.textContent=isRejection(e)?"":"Switch to Ethereum mainnet.";return}
chainId=await rpc(I,[]);
if(+chainId!==1){stat.textContent="Switch to Ethereum mainnet.";return}
}
sessionStorage.removeItem("dc");
account=accs[0];
const keepPair=pairAddrs();
await loadTokenList();
adoptList(keepPair);
showAddr();
refreshBalance();
update();
loadPos();
syncChart();
if(lnMode)lnQuote();
}finally{connecting=false;swap.disabled=false;
lnGo.disabled=false;lnGo.textContent=account?"Launch":"Connect Wallet";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){
try{reserve=BigInt(await rpc("eth_gasPrice",[]))*lqReserveGas()}catch{}
}
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){
try{reserve=BigInt(await rpc("eth_gasPrice",[]))
*(lqMode?lqReserveGas():tab==="swap"?900000n:tab==="book"?350000n
:+dly.value?(tipCk.checked?200000n+TIP_GAS:200000n):30000n)}catch{}
}
const max=fromBalance>reserve?fromBalance-reserve:0n;
if(lqMode&&lqFillMax(f,max))return;
amt.value=formatUnits(max,f.dec);
fitFont(amt);
if(lqMode)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.",
"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 s=String(e?.data?.data||e?.data||e?.message||e||"");
const m=/0x([0-9a-fA-F]{8})\b/.exec(s);
const hit=m&&REVERTS[m[1].toLowerCase()];
return hit||("Error: "+s.slice(0,110));
};
const err=e=>stat.textContent=isRejection(e)?"":explain(e);
const settle=async tx=>{
if(!tx){stat.textContent="Done";return}
if(!/^0x[0-9a-f]{64}$/i.test(tx))throw Error("bad tx");
stat.innerHTML=txLink(tx,"Sent");await waitTx(tx);stat.innerHTML=txLink(tx,"Done")};
const txLink=(h,label)=>`${label} <a href="https://etherscan.io/tx/${h}" target="_blank" rel="noreferrer">${h.slice(0,10)}…${h.slice(-6)}</a>`;
let autoQ=0;
async function update(auto){
if(!auto)autoQ=0;
syncIcons();
if(lqMode)return loadLq();
if(tab==="book")return render();
if(tab==="send")return updateSend();
const my=++seq;
quoting=false;
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=/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);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,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,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 rpc("eth_blockNumber",[]);
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);
const v4Job=isIn?quoteV4Pools(f,t,swapAmt,quoteBlock).catch(()=>null):Promise.resolve(null);
const preJob=isIn?quotePrecision(f,t,swapAmt,PROUTE,quoteBlock).catch(()=>null):Promise.resolve(null);
const SRC_CURVE=5;
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