0xacfba7ce…4082·#25,784,651·view on Etherscan
v;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;if(v&&!isNaN(+v))amt.value=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||(+cid===1&&account))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,[]);
if(accs[0]&&+cid===1){
account=accs[0];
showAddr();
const keep2=pairAddrs();
await loadTokenList();
adoptList(keep2);
refreshBalance();update();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<4&&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{}
})();
};
wireWallet(prov());
if(!prov())window.addEventListener("ethereum#initialized",()=>wireWallet(prov()),{once:true});
setTimeout(burnTotal,0);
setTimeout(loadEthUsd,0);
const SEL_TRANSFER="a9059cbb";
const SB2="0x000000dA7bb4B2A9E3e80e9A4D4157E26CA6189b";
const SB1="0x000000fF3D7A2d373615141d7489Ca66683DbecF";
const SBVIEW="0x000000E0b25449F32f7D9259aC449bA88E78dFCE";
const SWAPBOL="0x00000087A6dc5071779Ed1F8274A39230768B976";
const DUTCH="0x000000a213b430D14Bae6062c176289B05e04489";
const ORDERBOL="0x000000e6c7a12C80525ee74e7434aAb919447D95";
const FLOOR="0x00000080198137F790DA4C52bb902cf87c276748";
const FLOORVIEW="0x0000004E376e9dB5D9EC28E6711E1a64997C6ba7";
const SEL_NEXTID="2a58b330",SEL_GETORDERS="03652027",SEL_CANCELORD="514fcac7";
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);
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<640||at(9)===0n)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();
}
}
}
async function preflightAsk(row){
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(9*64,10*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");
}
const SEL_ORDER_FIXED="bcdb7936",SEL_ORDER_DUTCH="bbd94e1a",SEL_CHECKPOINT="a972985e";
const BOARDS=[{a:SB2,v2:1},{a:SB1,v2:0}];
const WORDS=b=>b?11:6;
let bookSeq=0,bookRows=[],candCache={},bkOpen=LS.bk!=="0";
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)=>`<a href="https://etherscan.io/${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 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 mc3Deep(want.map(a=>({to:TOKENLIST,data:"0x"+SEL_LOGOOF+encAddr(a)}))).catch(()=>[]);
want.forEach((a,i)=>{let u="";try{if(raw[i])u=safeUrl(decodeString(raw[i]))}catch{}logoCache[a]=u});
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,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 rows=await collectionFloor(t.addr).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.proceeds,q?q.dec:18)} ${q?q.sym:"?"} 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){
if(FLOORVIEW===ZERO||FLOOR===ZERO)return[];
const data="0x"+SEL_COLL_BIDS+encAddr(FLOOR)+encAddr(collection)+encAddr(ZERO)
+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 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 encSweep=(token,amount,to)=>"0x"+SEL_SWEEP+encAddr(token)+encUint(0)+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){
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,block),hasCode(SWAPBOL,block),
DUTCH!==ZERO?hasCode(DUTCH,block):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(