0xacfba7ce…4082·#25,751,354·view on Etherscan
=a=>a?TOKENS.findIndex(t=>t.addr.toLowerCase()===a):-1;
const tok=async v=>{if(!v)return "";const x=v.trim().toLowerCase();
if(!/^0x[0-9a-f]{40}$/.test(x))return symAddr(x);
if(at(x)<0)try{await addCustomToken(x,0)}catch{}
return at(x)<0?"":x};
if(symAddr((q.get("token")||"").trim().toLowerCase())||symAddr((q.get("out")||"").trim().toLowerCase()))pairChosen=true;
const both=async()=>[await tok(q.get("token")),await tok(q.get("out"))];
let[fa,ta]=await both();
if(my!==lSeq)return;
if((fa&&at(fa)<0)||(ta&&at(ta)<0)){[fa,ta]=await both();if(my!==lSeq)return}
const fi=at(fa),oi=at(ta);
if(fi>=0||oi>=0)pairChosen=true;
rebuild();
if(fi>=0)fromSel.value=fi;
if(oi>=0&&oi!==fi)toSel.value=oi;
const to=q.get("to"),lk=lsecs(q.get("lock")),want=q.get("tab"),n=q.get("amount");
const t=want==="swap"||want==="send"||want==="book"?want
:q.get("out")?"swap":(to||!isNaN(lk))?"send":tab;
if(fromSel.value===toSel.value){
const alt=String(TOKENS.findIndex((x,i)=>String(i)!==fromSel.value&&(t==="book"||x.std!=="nft")));
if(alt!=="-1"){if(oi>=0)fromSel.value=alt;else toSel.value=alt}
}
if(lqMode&&(fi>=0||oi>=0||to||n||!isNaN(lk)))lqSet(false);
setTab(t);
if(to)rc.value=to;
if(!isNaN(lk)){const os=[...dly.options].map(x=>+x.value).sort((a,b)=>a-b);
const v=String(os.find(o=>o>=lk)??os[os.length-1]);
if(t==="book")bookD=v;else sendD=v;
if(t!=="swap")dly.value=v}
if(n&&/^\d*\.?\d+$/.test(n)){
if(q.get("exactOut")==="1"&&q.get("out")){outAmt.value=n;setMode("out");fitFont(outAmt)}
else{amt.value=n;setMode("in");fitFont(amt)}
}
refreshBalance();update();
loadChart();
if(t==="book")loadBook();
}
addEventListener("hashchange",applyLink);
const tname=t=>/\s/.test(t.sym)||!symAddr(t.sym.toLowerCase())?t.addr:t.sym;
lk.onclick=async()=>{
const f=TOKENS[fromSel.value],t=TOKENS[toSel.value],p=new URLSearchParams();
p.set("token",tname(f));
if(tab==="send"){
const r=rc.value.trim();if(r)p.set("to",r);
if(amt.value.trim())p.set("amount",amt.value.trim());
if(+dly.value)p.set("lock",dly.value);
}else{
p.set("out",tname(t));
if(tab==="book"){p.set("tab","book");if(+dly.value)p.set("lock",dly.value)}
if(mode==="out"&&outAmt.value.trim()){p.set("amount",outAmt.value.trim());p.set("exactOut","1")}
else if(amt.value.trim())p.set("amount",amt.value.trim());
}
const u=location.origin+location.pathname+"#"+p;
try{await navigator.clipboard.writeText(u);stat.textContent="Link copied"}
catch{stat.textContent=u}
};
tabSwap.onclick=()=>setTab("swap");
tabSend.onclick=()=>setTab("send");
{const TABS=[tabSwap,tabSend,tabBook],NAMES=["swap","send","book"];
for(const el of TABS)el.addEventListener("keydown",e=>{
const i=TABS.indexOf(e.target);
const j=e.key==="ArrowRight"?(i+1)%TABS.length:e.key==="ArrowLeft"?(i+TABS.length-1)%TABS.length
:e.key==="Home"?0:e.key==="End"?TABS.length-1:-1;
if(j<0)return;
e.preventDefault();TABS[j].focus();setTab(NAMES[j]);
});
const syncStops=()=>TABS.forEach(el=>el.tabIndex=el.classList.contains("on")?0:-1);
syncStops();for(const el of TABS)el.addEventListener("click",syncStops);
for(const el of TABS)el.addEventListener("keyup",syncStops);}
applyLink();
if(window.ethereum)loadTokenList().then(()=>{
adoptList(pairAddrs());
return applyLink();
}).catch(()=>{});
async function doSend(){
if(!sendReady)return;
swap.disabled=true;
try{
const{to,amount,token}=sendReady;
const fresh=await rcvOf(rc.value.trim(),true);
if(fresh.toLowerCase()!==to.toLowerCase())throw Error("recipient changed \u2014 check and retry");
if(amount>fromBalance)throw Error("insufficient balance");
await checkWallet();
const dsec=BigInt(dly.value||0);
let txReq,batch=null;
if(dsec>0n){
const isE=token.addr===ZERO;
if(tipCk.checked)await syncTip();
const tip=tipCk.checked?sendTipWei:0n;
const val=isE?amount+tip:tip;
if(isE&&val>fromBalance)throw Error("insufficient ETH for amount + keeper tip");
const dep=tip>0n
?"0x"+SEL_DEPOSITTIP+encAddr(isE?ZERO:token.addr)+encAddr(to)
+encUint(amount)+encUint(dsec)+encUint(tip)+encUint(192n)+encUint(0n)
:"0x"+SEL_DEPOSITTO+encAddr(isE?ZERO:token.addr)+encAddr(to)
+encUint(isE?0n:amount)+encUint(dsec)+encUint(160n)+encUint(0n);
txReq={from:account,to:SLOW,data:dep,value:toHex(val)};
if(!isE){
const aHex=await rpc(C,[{to:token.addr,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(SLOW)},L]);
const cur=BigInt(aHex);
if(cur<amount){
const pre=cur>0n?[{to:token.addr,data:"0x"+SEL_APPROVE+encAddr(SLOW)+encUint(0),value:"0x0"}]:[];
const ap={to:token.addr,data:"0x"+SEL_APPROVE+encAddr(SLOW)+encUint(amount),value:"0x0"};
if(await canBatch(account))batch=[...pre,ap,{to:SLOW,data:dep,value:toHex(val)}];
else{stat.textContent="Approve "+token.sym+"...";
for(const c of[...pre,ap]){const at=await rpc(S,[{from:account,...c}]);await waitTx(at)}}
}}
}else{
txReq=token.addr===ZERO
?{from:account,to,value:toHex(amount)}
:{from:account,to:token.addr,data:"0x"+SEL_TRANSFER+encAddr(to)+encUint(amount),value:"0x0"};
}
let tx;
if(batch){await checkWallet();tx=await sendBatch(account,batch)}
else{const ret=await rpc(C,[txReq,L]);if(dsec===0n&&token.addr!==ZERO)okRet(ret);tx=await rpc(S,[txReq])}
await settle(tx);
amt.value="";fitFont(amt);sendReady=null;
refreshBalance();render();loadPos();
}catch(e){err(e);render()}
}
async function sendRouterFunded(callData,fundedCallData,token,amount,msgValue,verb){
await checkWallet();
let data=callData,batch=null;
if(token.addr!==ZERO){
const allowData="0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(ZROUTER);
const allowance=BigInt(await rpc(C,[{to:token.addr,data:allowData},L]));
if(allowance<amount){
let ready=false;
const pi=await permitInfo(token.addr,account);
if(pi)try{
stat.textContent="Sign approval...";
const pd=BigInt(Math.floor(Date.now()/1e3)+1800);
data=encCalls([await signPermit(token.addr,pi,account,amount,pd),callData]);
ready=true;
}catch(e){if(isRejection(e))throw e}
const selected=TOKENS[fromSel.value];
const walletBal=selected&&selected.addr.toLowerCase()===token.addr.toLowerCase()
?fromBalance:BigInt(await rpc(C,[{to:token.addr,data:"0x"+SEL_BALANCEOF+encAddr(account)},L]));
if(!ready&&walletBal>=amount&&await p2Ready(token.addr,account)>=amount)try{
stat.textContent="Sign approval...";
const pd=BigInt(Math.floor(Date.now()/1e3)+1800);
data=encCalls([await signPermit2(token.addr,account,amount,pd),fundedCallData||callData]);
ready=true;
}catch(e){if(isRejection(e))throw e}
if(!ready&&await canBatch(account)){
const pre=allowance>0n
?[{to:token.addr,data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(0n),value:"0x0"}]:[];
batch=[...pre,
{to:token.addr,data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(amount),value:"0x0"},
{to:ZROUTER,data:callData,value:toHex(msgValue)}];
ready=true;
}
if(!ready){
const approvals=allowance>0n?[0n,amount]:[amount];
for(let i=0;i<approvals.length;i++){
stat.textContent=`Approve ${token.sym}${approvals.length>1?` (${i+1}/${approvals.length})`:""}...`;
const h=await rpc(S,[{from:account,to:token.addr,
data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(approvals[i]),value:"0x0"}]);
await waitTx(h);
}
const fresh=BigInt(await rpc(C,[{to:token.addr,data:allowData},L]));
if(fresh<amount)throw Error("approval failed");
}
}
}
stat.textContent=verb;
let tx;
if(batch){await checkWallet();tx=await sendBatch(account,batch)}
else{
const req={from:account,to:ZROUTER,data,value:toHex(msgValue)};
await rpc(C,[req,L]);await checkWallet();tx=await rpc(S,[req]);
}
await settle(tx);
}
const PUSH_MAGIC="381bcb9ccaaab09ca206deb73834ed7b483c58bb3face9b43e8c7b50193253e2";
const SEL_SAFE_TRANSFER_DATA="b88d4fde",SEL_OWNEROF="6352211e";
async function pushNftOrder(f,t){
const id=nftId.value.trim();
if(!/^\d+$/.test(id))throw Error("name the token id you are selling");
const tokenId=BigInt(id);
if(kind.value!=="fixed")throw Error("an NFT is indivisible, so it sells at a fixed price");
const want=parseUnits(outAmt.value.trim()||"0",t.dec);
if(!want)throw Error("name a price");
const quote=t.addr===ZERO?WETH:t.addr;
if(quote.toLowerCase()===f.addr.toLowerCase())throw Error("pick different tokens");
if((await rpc("eth_getCode",[SB2,L])).length<5)throw Error("fixed orderbook not deployed yet");
let cp=ZERO;
const v=rc.value.trim();
if(v){cp=await rcvOf(v,true);if(!cp)throw Error("Private recipient not resolved")}
let owner=ZERO;
try{owner="0x"+strip0x(await rpc(C,[{to:f.addr,data:"0x"+SEL_OWNEROF+encUint(tokenId)},L])).slice(-40)}
catch{throw Error(`${f.sym} #${id} does not exist`)}
if(owner.toLowerCase()!==account.toLowerCase())throw Error(`you do not own ${f.sym} #${id}`);
const exp=+dly.value?BigInt(Math.floor(Date.now()/1e3)+ +dly.value):0n;
const terms=PUSH_MAGIC+encAddr(quote)+encUint(want)+encUint(exp)+encBool(false)+encAddr(cp);
const data="0x"+SEL_SAFE_TRANSFER_DATA+encAddr(account)+encAddr(SB2)+encUint(tokenId)
+encUint(128n)+encUint(192n)+terms;
const req={from:account,to:f.addr,data,value:"0x0"};
await rpc(C,[req,L]);
await checkWallet();
await settle(await rpc(S,[req]));
}
const SEL_CREATE_ETH="0e8e31d3",SEL_CREATE="8e14f9f8",SEL_FLOOR_BID="3d8d260b";
async function bidCollection(f,t,climbing){
if(nftId.value.trim())throw Error("clear the token id to bid on the whole collection");
const ceiling=parseUnits(amt.value.trim()||"0",f.dec);
if(!ceiling)throw Error("name the most you will pay in total");
const open=climbing?parseUnits(floorAmt.value.trim()||"0",f.dec):ceiling;
if(climbing&&!open)throw Error("set an opening bid");
if(open>ceiling)throw Error("the opening bid exceeds the maximum");
if(ceiling>(1n<<96n)-1n)throw Error("bid amount too large");
const wantRaw=outAmt.value.trim();
if(wantRaw&&!/^\d+$/.test(wantRaw))throw Error("how many tokens? a whole number");
const want=wantRaw?BigInt(wantRaw):1n;
if(!want)throw Error("bid for at least one");
if(want>(1n<<128n)-1n)throw Error("too many to bid for at once");
const duration=BigInt(dly.value||0);
if(!duration)throw Error("a bid needs a window — pick an expiry");
if((await rpc("eth_getCode",[FLOOR,L])).length<5)throw Error("bid board not deployed yet");
const terms=encAddr(t.addr)+encAddr(f.addr===ZERO?ZERO:f.addr)+encUint(want)
+encUint(open)+encUint(ceiling)+encUint(0n)+encUint(duration)+encBool(true)
+encUint(288n)+encUint(0n);
const data="0x"+SEL_FLOOR_BID+encUint(32n)+terms;
if(f.addr===ZERO){
const req={from:account,to:FLOOR,data,value:toHex(ceiling)};
await rpc(C,[req,L]);await checkWallet();await settle(await rpc(S,[req]));
return;
}
const tx={from:account,to:FLOOR,data,value:"0x0"};
const al=BigInt(await rpc(C,[{to:f.addr,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(FLOOR)},L]));
const approvals=[];
if(al<ceiling){
if(al)approvals.push({to:f.addr,data:"0x"+SEL_APPROVE+encAddr(FLOOR)+encUint(0n),value:"0x0"});
approvals.push({to:f.addr,data:"0x"+SEL_APPROVE+encAddr(FLOOR)+encUint(ceiling),value:"0x0"});
}
if(approvals.length&&await canBatch(account)){
await checkWallet();
await settle(await sendBatch(account,[...approvals,{to:FLOOR,data,value:"0x0"}]));
return;
}
for(const a of approvals){await checkWallet();await settle(await rpc(S,[{from:account,...a}]))}
await rpc(C,[tx,L]);await checkWallet();await settle(await rpc(S,[tx]));
}
async function buyNftOrder(f,t){
const id=nftId.value.trim();
if(!/^\d+$/.test(id))throw Error("name the token id you want to buy");
const tokenId=BigInt(id);
if(kind.value!=="fixed")throw Error("a bid for one token is a fixed offer");
const price=parseUnits(amt.value.trim()||"0",f.dec);
if(!price)throw Error("name what you will pay");
if((await rpc("eth_getCode",[SB2,L])).length<5)throw Error("fixed orderbook not deployed yet");
let cp=ZERO;
const v=rc.value.trim();
if(v){cp=await rcvOf(v,true);if(!cp)throw Error("Private recipient not resolved")}
try{await rpc(C,[{to:t.addr,data:"0x"+SEL_OWNEROF+encUint(tokenId)},L])}
catch{throw Error(`${t.sym} #${id} does not exist`)}
const exp=+dly.value?BigInt(Math.floor(Date.now()/1e3)+ +dly.value):0n;
if(f.addr===ZERO){
const data="0x"+SEL_CREATE_ETH+encAddr(t.addr)+encUint(tokenId)+encBool(false)
+encUint(exp)+encBool(true)+encAddr(cp);
const req={from:account,to:SB2,data,value:toHex(price)};
await rpc(C,[req,L]);await checkWallet();await settle(await rpc(S,[req]));
return;
}
const data="0x"+SEL_CREATE+encAddr(f.addr)+encUint(price)+encAddr(t.addr)+encUint(tokenId)
+encBool(false)+encUint(exp)+encBool(false)+encBool(true)+encAddr(cp);
const tx={from:account,to:SB2,data,value:"0x0"};
const al=BigInt(await rpc(C,[{to:f.addr,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(SB2)},L]));
const approvals=[];
if(al<price){
if(al)approvals.push({to:f.addr,data:"0x"+SEL_APPROVE+encAddr(SB2)+encUint(0n),value:"0x0"});
approvals.push({to:f.addr,data:"0x"+SEL_APPROVE+encAddr(SB2)+encUint(price),value:"0x0"});
}
if(approvals.length&&await canBatch(account)){
await checkWallet();
await settle(await sendBatch(account,[...approvals,{to:SB2,data,value:"0x0"}]));
return;
}
for(const a of approvals){await checkWallet();await settle(await rpc(S,[{from:account,...a}]))}
await rpc(C,[tx,L]);await checkWallet();await settle(await rpc(S,[tx]));
}
async function placeOrder(){
const f=TOKENS[fromSel.value],t=TOKENS[toSel.value];
swap.disabled=true;
try{
if(f.std==="nft"||t.std==="nft"){
if(f.std==="nft")await pushNftOrder(f,t);
else if(nftId.value.trim())await buyNftOrder(f,t);
else await bidCollection(f,t,kind.value==="floor");
amt.value="";outAmt.value="";nftId.value="";floorAmt.value="";
floorTouched=false;
fitFont(amt);fitFont(outAmt);fitFont(floorAmt);
stat.textContent=f.std==="nft"?"Listed.":"Bid placed.";
loadBook();refreshBalance();render();
return;
}
const sell=parseUnits(amt.value.trim(),f.dec),want=parseUnits(outAmt.value.trim(),t.dec);
const dutch=kind.value==="dutch",bid=kind.value==="floor";
const floor=(dutch||bid)&&floorAmt.value.trim()?parseUnits(floorAmt.value.trim(),bid?f.dec:t.dec):0n;
if(dutch&&floor===0n){
try{
if(!confirm(`This lot decays to ZERO.\n\nOnce the window ends, anyone can take all ${trimAmt(sell,f.dec)} ${f.sym} for nothing — and it stays that way until you cancel.\n\nThat is how a "come and take it" launch works. If you meant to keep a reserve, set a Floor total instead.\n\nContinue with a zero floor?`))return;
}catch{
stat.textContent="This lot would decay to zero and this browser blocked the confirmation. Set a Floor total, or open the page outside the wallet's in-app browser.";
return;
}
}
const board=bid?FLOOR:dutch?DUTCH:SB2;
const quoteToken=!dutch&&!bid&&t.addr===ZERO?WETH:t.addr;
const [bc,oc]=await Promise.all([rpc("eth_getCode",[board,L]),rpc("eth_getCode",[ORDERBOL,L])]);
if(bc.length<5)throw Error(`${bid?"bid board":dutch?"Dutch":"fixed"} orderbook not deployed yet`);
if(oc.length<5)throw Error("routed order adapter not deployed yet");
if(!bid&&f.addr===ZERO&&t.addr===WETH)throw Error("pick a non-WETH quote token");
if(!dutch&&!bid&&(f.addr===WETH||f.addr===ZERO)&"eToken===WETH)throw Error("pick different tokens");
if(dutch&&floor>want)throw Error("floor exceeds start price");
let cp=ZERO;
const v=rc.value.trim();
if(!dutch&&!bid&&v){cp=await rcvOf(v,true);if(!cp)throw Error("Private recipient not resolved")}
const pf=fill.value==="1"?1n:0n;
const placementDeadline=BigInt(Math.floor(Date.now()/1e3)+1800);
let orderData;
if(bid){
const duration=BigInt(dly.value||0);
if(!duration)throw Error("choose a bid window");
if(!floor)throw Error("set an opening bid");
if(floor>sell)throw Error("opening bid exceeds the maximum");
if(want>(1n<<128n)-1n||sell>(1n<<96n)-1n)throw Error("bid amount too large");
if(t.addr===ZERO)throw Error("a bid buys a token — pick one to bid for");
if((f.addr===ZERO?WETH:f.addr).toLowerCase()===t.addr.toLowerCase())
throw Error("pick different tokens");
orderData="0x"+SEL_ORDER_FLOOR+encAddr(FLOOR)+encAddr(account)+encAddr(account)
+encAddr(t.addr)+encAddr(f.addr)+encUint(want)+encUint(floor)+encUint(sell)
+encUint(0n)+encUint(duration)+encUint(placementDeadline);
}else if(dutch){
const duration=BigInt(dly.value||0);
if(!duration)throw Error("choose a decay duration");
if(sell>(1n<<128n)-1n||want>(1n<<96n)-1n||floor>(1n<<96n)-1n)throw Error("Dutch amount too large");
const dutchExpiry=dutchEnds();
orderData="0x"+SEL_ORDER_DUTCH+encAddr(DUTCH)+encAddr(account)+encAddr(account)
+encAddr(f.addr)+encAddr(t.addr)+encUint(sell)+encUint(want)+encUint(floor)
+encUint(0n)+encUint(duration)+encUint(dutchExpiry)+encUint(placementDeadline);
}else{
const exp=+dly.value?BigInt(Math.floor(Date.now()/1e3)+ +dly.value):0n;
orderData="0x"+SEL_ORDER_FIXED+encAddr(SB2)+encAddr(account)+encAddr(account)
+encAddr(f.addr)+encUint(sell)+encAddr(quoteToken)+encUint(want)+encUint(pf)
+encUint(exp)+encAddr(cp)+encUint(placementDeadline);
}
let direct,funded;
if(f.addr===ZERO){
direct=funded=encSnwap(ZERO,0n,account,ZERO,0n,ORDERBOL,orderData);
}else{
const checkpoint=encCheckpoint(ORDERBOL,f.addr,account);
direct=encCalls([
checkpoint,
encSnwap(f.addr,sell,account,ZERO,0n,ORDERBOL,orderData)
]);
funded=encCalls([
checkpoint,
encSweep(f.addr,sell,ORDERBOL),
encSnwap(ZERO,0n,account,ZERO,0n,ORDERBOL,orderData)
]);
}
await sendRouterFunded(direct,funded,f,sell,f.addr===ZERO?sell:0n,
bid?"Placing bid (escrowing the ceiling)...":dutch?"Placing Dutch order...":t.addr===ZERO?"Placing limit order (ETH quote settles as WETH)...":"Placing limit order...");
amt.value="";outAmt.value="";floorAmt.value="";
floorTouched=false;
fitFont(amt);fitFont(outAmt);fitFont(floorAmt);
loadBook();refreshBalance();render();
}catch(e){err(e);render()}
}
swap.onclick=async()=>{
if(tab==="book"){if(!account)return connect();return placeOrder()}
if(tab==="send"){if(!account)return connect();return doSend()}
if(!account) return connect();
if(!last)return;
if(Date.now()>last.exp){stat.textContent="Quote expired — refreshing...";updateSoon();return}
if(last.impact!==null&&last.impact!==undefined&&last.impact>=IMPACT_CONFIRM){
const pct=(Number(last.impact)/100).toFixed(2);
const worse=`about ${trimAmt(last.lossTok,last.lossDec)} ${last.lossSym} ${last.isIn?"less than":"more than"} the market rate`;
try{
if(last.impact>=IMPACT_TYPED){
const need=String(Math.floor(Number(last.impact)/100));
if(prompt(`STOP — this trade is priced ${pct}% away from the market.\n\nYou would ${last.isIn?"receive":"pay"} ${worse}.\n\nUsually this means the pool is illiquid or the amount is wrong.\n\nType ${need} to accept this loss:`)!==need)return;
}else if(!confirm(`Price impact ${pct}%.\n\nYou will ${last.isIn?"receive":"pay"} ${worse}.\n\nContinue?`))return;
}catch{
stat.textContent=`This trade is ${pct}% away from the market and this browser blocked the confirmation. Reduce the amount, or open the page outside the wallet's in-app browser.`;
return;
}
}
swap.disabled=true;
try{
const q=last;
const {callData,fundedCallData,msgValue,amountIn,from,to}=q;
const wantsValue=q.wrap>0n?0n:(from.addr===ZERO?amountIn:0n);
if(msgValue!==wantsValue)throw Error("bad value");
const rvNow=rc.value.trim();
if(((rvNow?await rcvOf(rvNow,true):"")||"").toLowerCase()!==(q.recipient||"").toLowerCase())
throw Error("recipient changed — check and retry");
await checkWallet();
try{await preflightFills(q.fills,q.tokenIn,q.tokenOut)}
catch(e){stat.textContent=e.message;swap.disabled=false;last=null;updateSoon();return}
let cd2=callData,batchCalls=null,prep=[];
const spender=q.spender!==undefined?q.spender:(to?null:ZROUTER);
const target=to||ZROUTER;
const fund=q.wrap>0n?{addr:WETH,sym:"WETH",dec:18}:from;
if(q.source===SRC_PRECISION&&q.pool){
const ok=await mc3([{to:PFACTORY,data:"0x"+SEL_ISPOOL+encAddr(q.pool)}])
.then(([r])=>!!r&&BigInt(r)===1n).catch(()=>false);
if(!ok)throw Error("that pool is not one the factory made — refusing to send");
if(q.wrap>0n){
const have=BigInt(await rpcRead("eth_getBalance",[account,L]));
if(have<q.wrap)throw Error(`Not enough ETH: this needs ${trimAmt(q.wrap,18)} and you hold ${trimAmt(have,18)}.`);
prep.push({to:WETH,data:"0xd0e30db0",value:toHex(q.wrap),
note:`Wrapping ${trimAmt(q.wrap,18)} ETH — this pool holds WETH`});
}
}
const prepBatchable=prep.length?await canBatch(account):false;
if(prep.length&&!prepBatchable){
for(const c of prep){
stat.textContent=c.note+"...";
const ctx={from:account,to:c.to,data:c.data,value:c.value};
await rpc(C,[ctx,L]);await checkWallet();
await settle(await rpc(S,[ctx]));
}
prep=[];
}
if(fund.addr!==ZERO&&spender&&spender!==ZROUTER){
const allowData="0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(spender);
const a=BigInt(await rpc(C,[{to:fund.addr,data:allowData},L]));
if(a<amountIn){
if(await canBatch(account)){
const pre=a>0n?[{to:fund.addr,data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(0n),value:"0x0"}]:[];
batchCalls=[...pre,{to:fund.addr,data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(amountIn),value:"0x0"},
{to:target,data:callData,value:toHex(msgValue)}];
}else{
const n=a>0n?2:1;
if(a>0n){
stat.textContent=`Approving (1/${n})...`;
await waitTx(await rpc(S,[{from:account,to:fund.addr,
data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(0)}]));
}
stat.textContent=n>1?"Approving (2/2)...":"Approving...";
await waitTx(await rpc(S,[{from:account,to:fund.addr,
data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(amountIn)}]));
if(BigInt(await rpcRead(C,[{to:fund.addr,data:allowData},L]))<amountIn)throw Error("approval failed");
}
}
}else if(fund.addr!==ZERO&&!to&&!msgValue){
const allowData="0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(ZROUTER);
const aHex=await rpc(C,[{to:fund.addr,data:allowData},L]);
const a=BigInt(aHex);
if(a<amountIn){
let permitted=false;
const sameAsQuote=fund===from;
const pi=sameAsQuote?await permitInfo(fund.addr,account):null;
if(pi){try{
stat.textContent="Sign approval...";
const pd=BigInt(Math.floor(Date.now()/1e3)+1800);
cd2=encCalls([await signPermit(fund.addr,pi,account,amountIn,pd),callData]);
permitted=true;
}catch(e){if(isRejection(e)){stat.textContent="";swap.disabled=false;return}}}
const p2Amount=amountIn;
const p2Fundable=!!fundedCallData||q.source!==SRC_PRECISION;
if(!permitted&&p2Fundable&&sameAsQuote&&fromBalance>=p2Amount&&await p2Ready(fund.addr,account)>=p2Amount){try{
stat.textContent="Sign approval...";
const pd=BigInt(Math.floor(Date.now()/1e3)+1800);
const p2Call=await signPermit2(fund.addr,account,p2Amount,pd);
cd2=fundedCallData?encCalls([p2Call,fundedCallData])
:encCalls(q.isIn?[p2Call,callData]:[p2Call,callData,encSweep(fund.addr,0n,account)]);
permitted=true;
}catch(e){if(isRejection(e)){stat.textContent="";swap.disabled=false;return}}}
if(!permitted&&await canBatch(account)){
const pre=a>0n?[{to:fund.addr,data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(0n),value:"0x0"}]:[];
batchCalls=[...pre,{to:fund.addr,data:"0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(amountIn),value:"0x0"},
{to:target,data:callData,value:toHex(msgValue)}];
permitted=true;
}
if(!permitted){
const n=a>0n?2:1;
if(a>0n){
stat.textContent=`Approving (1/${n})...`;
const zeroData="0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(0);
const h1=await rpc(S,[{from:account,to:fund.addr,data:zeroData}]);
await waitTx(h1);
}
stat.textContent=n>1?`Approving (2/2)...`:"Approving...";
const apprData="0x"+SEL_APPROVE+encAddr(ZROUTER)+encUint(amountIn);
const h2=await rpc(S,[{from:account,to:fund.addr,data:apprData}]);
await waitTx(h2);
const nHex=await rpc(C,[{to:fund.addr,data:allowData},L]);
if(BigInt(nHex)<amountIn)throw Error("approval failed");
}
}
}
if(prep.length&&batchCalls){
batchCalls=[...prep.map(({to,data,value})=>({to,data,value})),...batchCalls];
prep=[];
}
let tx;
const mainTx={from:account,to:target,data:cd2,value:toHex(msgValue)};
if(prep.length&&!batchCalls&&await canBatch(account)){
stat.textContent="Swapping...";
await checkWallet();
tx=await sendBatch(account,[...prep.map(({to,data,value})=>({to,data,value})),
{to:target,data:cd2,value:toHex(msgValue)}]);
}else{
for(const c of prep){
stat.textContent=c.note+"...";
const ctx={from:account,to:c.to,data:c.data,value:c.value};
await rpc(C,[ctx,L]);await checkWallet();
await settle(await rpc(S,[ctx]));
}
stat.textContent="Swapping...";
if(batchCalls){
await checkWallet();
tx=await sendBatch(account,batchCalls);
}else{
await rpc(C,[mainTx,L]);
await checkWallet();
tx=await rpc(S,[mainTx]);
}
}
await settle(tx);
last=null;
chCache={};loadChart();
refreshBalance();
render();
}catch(e){err(e);render()}
};</script>