memoscan
← all memos

Memo 0x7c9fc97d…15dfce on Ethereum

0xacfba7ce…4082·#25,751,071·view on Etherscan
nv;syncInv();drawChart()}; chTf.appendChild(invBtn); const syncChart=()=>{chSyncOrientation();syncInv();loadChart()}; chTog.onclick=()=>{chOpen=!chOpen;LS.ch=chOpen?"1":"0";syncChartUI()}; const lsecs=v=>{const m=/^(\d+)([mhdw]?)$/.exec((v||"").trim()); return m?+m[1]*(m[2]=="d"?86400:m[2]=="h"?3600:m[2]=="w"?604800:m[2]=="m"?60:1):NaN}; const symAddr=x=>{const m=TOKENS.filter(t=>t.sym.toLowerCase()===x); return m.length===1?m[0].addr.toLowerCase():""}; let lSeq=0; async function applyLink(){ const q=new URLSearchParams(location.hash.slice(1)); if(![...q.keys()].length)return; const my=++lSeq; const at=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(); } 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(); 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)&&quoteToken===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>