0x68575b07…67c7·#25,914,177·view on Etherscan
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([{