0xacfba7ce…4082·#25,784,650·view on Etherscan
ethod,params||[]):
read(method,params),
on:()=>{}}}
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,get:()=>d.provider}))
.concat([{name:"WalletConnect",svg:WC_ICON,get:wcConnect}]);
const wkPick=cs=>new Promise(res=>{
wkList.textContent="";
let done=false;
const end=v=>{if(done)return;done=true;
wkWrap.classList.add("hide");removeEventListener("keydown",esc);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";b.setAttribute("role","option");
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);
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}
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;
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();
fcMine();
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){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.",
"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();
fcSync();
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 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);
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+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]]);
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: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 Error(`exact-output is unavailable for this pair — enter an amount to PAY instead`);
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 hybrid=null,bookIncomplete=false,bookUnavailable=false,plannerBounded=false;
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);
return isIn?q.best.amountOut:q.best.amountIn;
}catch{return null}
};
const tailDelta=swapAmt-tailAmt;
const seedOut=isIn&&tail&&r.best.amountOut>tail.best.amountOut
?r.best.amountOut-tail.best.amountOut:r.best.amountOut;
const seedIn=!isIn&&tail&&r.best.amountIn>tail.best.amountIn
?r.best.amountIn-tail.best.amountIn:r.best.amountIn;
let p=isIn
?planBookExactIn(candidates,swapAmt,seedOut,tail&&seedOut!==r.best.amountOut?tailDelta:r.best.amountIn)
:planBookExactOut(candidates,swapAmt,seedIn,tail&&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 edgeBps=5n+2n*BigInt(p.fills.length);
const fallbackWin=isIn
?expectedOut*10000n>r.best.amountOut*(10000n+edgeBps)
:expectedIn*(10000n+edgeBps)<r.best.amountIn*10000n;
const wins=saving>0n&&(gasFloor===null?fallbackWin: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,
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;
}
}
}
}
for(const c of [v4c,pc])
if(c&&c!==r&&c.best.amountOut>r.best.amountOut){r=c;hybrid=null}
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=hybrid?"Orderbook + AMM":(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}`:"";
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(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+=" · Planner heuristic/capped";
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);
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.`;
}
const inApprove=isIn?swapAmt:r.msgValue||r.amountLimit;
if(acct)last={callData:r.callData,fundedCallData:r.fundedCallData,msgValue:r.msgValue,
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;
if(advancePastDeadPair()){loadChart();update();return}
outEl.value="";fitFont(outEl);render();
stat.textContent=e?.noWallet?NO_WALLET
:mcGaveUp>gaveUp0||(mcFailed>failed0&&mcOk===ok0)
?"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 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{await rpc("wallet_revokePermissions",[{eth_accounts:{}}])}catch{}
location.reload();
}
addr.onclick=()=>{if(account)disconnect()};
addr.onkeydown=e=>{if(account&&(e.key==="Enter"||e.key===" ")){e.preventDefault();disconnect()}};
const AUTO_Q_MAX=5;
const updateSoon=()=>{clearTimeout(debounceT);debounceT=setTimeout(()=>{debounceT=0;update()},250)};
const setMode=m=>{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])
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=pre