memoscan
← all memos

Memo 0x6b849b5d…0f9d11 on Ethereum

0xacfba7ce…4082·#25,751,351·view on Etherscan
class="lqin" data-side="0" inputmode="decimal" autocomplete="off" spellcheck="false" placeholder="0.0 ${t0.sym}" aria-label="${t0.sym} to deposit">` +`<input class="lqin" data-side="1" inputmode="decimal" autocomplete="off" spellcheck="false" placeholder="0.0 ${t1.sym}" aria-label="${t1.sym} to deposit">` +`<div class="lqpv"></div>` +`<button class="lqbtn" data-act="ac" ${p} disabled>Add liquidity</button></div>` +`</div>`; } const LQ_FEES=[[500,"0.05%","pairs that track each other"], [3000,"0.30%","most pairs"],[10000,"1.00%","thin or volatile"]]; const LQ_RANGES=[["full","Full range"],["100","Wide — 100×"],["10","10×"], ["2","2×"],["1.25","Tight — 25%"],["custom","Custom prices"]]; function lqNewForm(t0,t1){ return `<div class="lqnew" data-t0="${t0.addr}" data-t1="${t1.addr}">` +`<label class="dly" for="lqRange">Range<select id="lqRange">` +LQ_RANGES.map(([v,l])=>`<option value="${v}"${v==="full"?" selected":""}>${l}</option>`).join("") +`</select></label>` +`<label class="dly hide" id="lqCustom">Prices<span class="r">` +`<input id="lqLo" inputmode="decimal" autocomplete="off" spellcheck="false" placeholder="Low" aria-label="Lowest price in the band">` +`<input id="lqHi" inputmode="decimal" autocomplete="off" spellcheck="false" placeholder="High" aria-label="Highest price in the band"></span></label>` +`<label class="dly hide" id="lqPxRow" for="lqPx">Opening price` +`<input id="lqPx" inputmode="decimal" autocomplete="off" spellcheck="false" placeholder="${t1.sym} per ${t0.sym}" aria-label="Opening price"></label>` +`<label class="dly" for="lqFee">Fee<select id="lqFee">` +LQ_FEES.map(([v,l,h])=>`<option value="${v}"${v===3000?" selected":""}>${l} — ${h}</option>`).join("") +`</select></label>` +`<div class="lqout" id="lqRangeOut"></div>` +`<div class="lqout" id="lqPv"></div>` +`<div class="lqout" id="lqOwnNote"></div>` +`<div class="lqout" id="lqGas"></div>` +`<button class="primary" id="lqCreate" disabled>Create band</button></div>`; } const pxToSqrt=(px,d0,d1)=>{ const raw=px*10**(d1-d0); if(!isFinite(raw)||raw<=0)return 0n; return BigInt(Math.round(Math.sqrt(raw)*1e9))*10n**9n; }; const lqMarket=(t0,t1,lo,hi,fee,owner)=>encAddr(t0)+encAddr(t1)+encUint(lo)+encUint(hi) +encUint(fee)+encAddr(ZERO)+encAddr(owner)+encUint(0n); const lqSeedPrice=(sl,sh,in0,in1)=>{ const L=Number(sl),H=Number(sh),x=Number(in0),y=Number(in1),W=1e18; const g=s=>(x*s*H*(s-L))/((H-s)*W*y*W); let lo=L,hi=H; for(let i=0;i<90;i++){const m=(lo+hi)/2;if(g(m)<1)lo=m;else hi=m} const s=(lo+hi)/2; if(!(s>L&&s<H))return 0n; return BigInt(Math.round(s)); }; const lqBandFor=(px,mult,a0,a1)=>{ if(a0&&!a1)return[px,px*mult]; if(a1&&!a0)return[px/mult,px]; return[px/mult,px*mult]; }; const LQ_MIN_LIQUIDITY=1000n,LQ_MIN_RESOLUTION=1000000n,LQ_WAD=10n**18n, LQ_MAX_LIQUIDITY=(1n<<128n)-1n,LQ_MAX_SQRT=10n**36n,LQ_SEED_TOLERANCE=100n; const lqUp=(a,b,c)=>(a*b+c-1n)/c; const LQ_NOSEED={ok:false,lp:0n,used0:0n,used1:0n}; function lqSeedQuote(sl,sh,s,a0,a1){ if(!(sl>0n)||!(sh>sl)||sh>LQ_MAX_SQRT||s<sl||s>sh)return LQ_NOSEED; const f0=()=>a0*s/(sh-s)*sh/LQ_WAD,f1=()=>a1*LQ_WAD/(s-sl); let total; if(s===sl)total=f0(); else if(s===sh)total=f1(); else{const x=f0(),y=f1();total=x<y?x:y} if(total===0n||total>LQ_MAX_LIQUIDITY)return LQ_NOSEED; let used0=lqUp(total,(sh-s)*LQ_WAD,sh*s),used1=lqUp(total,s-sl,LQ_WAD); const v0=total*LQ_WAD/sh,v1=total*sl/LQ_WAD; if(v0<LQ_MIN_RESOLUTION||v1<LQ_MIN_RESOLUTION)return LQ_NOSEED; let x=v0+used0,y=v1+used1; const maxX=y*LQ_WAD*LQ_WAD/(sl*sl); if(x>maxX){used0=maxX>v0?maxX-v0:0n;x=v0+used0} const maxY=x*sh*sh/(LQ_WAD*LQ_WAD); if(y>maxY){used1=maxY>v1?maxY-v1:0n;y=v1+used1} if(used0>a0||used1>a1||total<=LQ_MIN_LIQUIDITY)return LQ_NOSEED; const wantSq=s*s,gotSq=x===0n?0n:y*LQ_WAD*LQ_WAD/x,slack=wantSq/LQ_SEED_TOLERANCE; if(gotSq<wantSq-slack||gotSq>wantSq+slack)return LQ_NOSEED; return {ok:true,lp:total-LQ_MIN_LIQUIDITY,used0,used1}; } const LQ_MAX128=(1n<<128n)-1n,LQ_FEE_DENOM=1000000n; const lqInBand=(sl,sh,vX,vY)=>{ if(vX<=0n)return false; const ratio=vY*LQ_WAD*LQ_WAD/vX,hi=sh+1n; return ratio>=sl*sl&&ratio<hi*hi; }; function lqExecutable(sl,sh,fee,augIn,augOut,rIn,rOut,zeroForOne){ if(augOut<=1n)return false; let need=lqUp(augIn,1n,augOut-1n); if(need===0n)need=1n; if(need>LQ_MAX128)return false; const net=lqUp(need,LQ_FEE_DENOM,LQ_FEE_DENOM-fee); if(net>LQ_MAX128)return false; const priced=net*(LQ_FEE_DENOM-fee)/LQ_FEE_DENOM; if(priced===0n)return false; if(net>LQ_MAX128-rIn)return false; const out=priced*augOut/(augIn+priced); if(out===0n||out>rOut)return false; return zeroForOne?lqInBand(sl,sh,augIn+net,augOut-out):lqInBand(sl,sh,augOut-out,augIn+net); } const lqTradeable=(sl,sh,fee,x,y,r0,r1)=> (r1!==0n&&lqExecutable(sl,sh,fee,x,y,r0,r1,true)) ||(r0!==0n&&lqExecutable(sl,sh,fee,y,x,r1,r0,false)); function lqSeedAdmits(sl,sh,s,fee,q){ if(!q||!q.ok)return false; const total=q.lp+LQ_MIN_LIQUIDITY; if(lqUp(total,(sh-s)*LQ_WAD,sh*s)>LQ_MAX128)return false; if(lqUp(total,s-sl,LQ_WAD)>LQ_MAX128)return false; const x=total*LQ_WAD/sh+q.used0,y=total*sl/LQ_WAD+q.used1; if(!lqInBand(sl,sh,x,y))return false; return lqTradeable(sl,sh,fee,x,y,q.used0,q.used1); } const lqBandFits=(sl,sh,sp,fee,in0,in1)=> lqSeedAdmits(sl,sh,sp,fee,lqSeedQuote(sl,sh,sp,in0,in1)); const LQ_WIDTHS=[1000,100,10,2]; function lqFullWidth(d0,d1,a0,a1,px,fee){ for(const mult of LQ_WIDTHS){ const [lo,hi]=lqBandFor(px,mult,a0,a1); const sl=pxToSqrt(lo,d0,d1),sh=pxToSqrt(hi,d0,d1); if(!sl||!sh||sh<=sl)continue; const sp=a0&&!a1?sl:a1&&!a0?sh:lqSeedPrice(sl,sh,a0,a1); if(sp&&lqBandFits(sl,sh,sp,fee,a0,a1))return mult; } return null; } async function loadLq(){ const p=chPair(); const my=++lqSeq; if(!p){lqList.innerHTML=`<div class="lqempty">Pick two different tokens.</div>`;lqSub.textContent="";lqRows=[];return}lqSyncAmounts(); const [t0,t1]=p; lqSub.textContent=`${t0.sym} / ${t1.sym}`; lqList.innerHTML=`<div class="lqempty">Loading…</div>`; try{ const who=account||ZERO; const [raw,c]=await mc3([ {to:PPLENS,data:"0x"+SEL_MARKETS+encAddr(t0.addr)+encAddr(t1.addr) +encAddr(who)+encUint(0)+encUint(LQ_SCAN)+encUint(0)}, {to:PFACTORY,data:"0x"+SEL_PAIR_COUNT+encAddr(t0.addr)+encAddr(t1.addr)}, ]); if(my!==lqSeq)return; let total=0;try{if(c)total=Number(BigInt(c))}catch{} const rows=[]; if(raw){const h=strip0x(raw),n=Number(BigInt("0x"+h.slice(64,128))),base=128; for(let i=0;i<n&&i<LQ_SCAN;i++){ const at=k=>BigInt("0x"+h.slice(base+(i*PI_WORDS+k)*64,base+(i*PI_WORDS+k+1)*64)); rows.push({pool:"0x"+h.slice(base+(i*PI_WORDS+PI_POOL)*64+24,base+(i*PI_WORDS+PI_POOL+1)*64), sl:at(PI_SL),sh:at(PI_SH),fee:at(PI_FEE),r0:at(PI_R0),r1:at(PI_R1),px:at(PI_PX), liq:at(PI_LIQ),hook:"0x"+h.slice(base+(i*PI_WORDS+PI_HOOK)*64+24,base+(i*PI_WORDS+PI_HOOK+1)*64), shares:0n,mine0:0n,mine1:0n})}} if(account&&rows.length){ const bs=await mc3Deep(rows.map(r=>({to:r.pool,data:"0x"+SEL_BALANCEOF+encAddr(account)}))); if(my!==lqSeq)return; rows.forEach((r,i)=>{ if(!bs[i]||r.liq===0n)return; const s=dec1(bs[i],0); r.shares=s;r.mine0=s*r.r0/r.liq;r.mine1=s*r.r1/r.liq; }); } if(my!==lqSeq)return; if(!rows.length){ lqList.innerHTML=`<div class="lqempty">No Precision band for this pair yet.</div>`+lqNewForm(t0,t1);lqSyncAmounts(); lqRows=[]; lqNewPreview(); return} rows.sort((a,b)=>(b.shares>0n)-(a.shares>0n)||(b.liq>a.liq?1:b.liq<a.liq?-1:0)); const shown=rows.slice(0,LQ_SHOW); if(total>shown.length)lqSub.textContent+=` · showing ${shown.length} of ${total}` +(total>LQ_SCAN?` (first ${LQ_SCAN} scanned)`:""); lqRows=shown; lqList.innerHTML=shown.map(o=>lqRow(o,t0,t1)).join("") +`<button class="lqbtn lqnewtog" id="lqNewTog">Create a new band</button>`; lqSyncAmounts(); }catch(e){ if(my!==lqSeq)return; lqRows=[]; lqList.innerHTML=`<div class="lqempty">Could not read pools.</div>`;lqSyncAmounts(); } } const SEL_REMOVE="e39b0eb5",SEL_ADDEXACT="cc0025e4",SEL_PREVIEW_REMOVE="a2eaee07", SEL_PREVIEW_ADD="e03ec807"; const dec1=(h,i)=>BigInt("0x"+strip0x(h).slice(i*64,(i+1)*64)); async function previewRemove(pool,shares){ const [r]=await mc3([{to:PLQLENS,data:"0x"+SEL_PREVIEW_REMOVE+encAddr(pool)+encUint(shares)}]); if(!r)return null; return dec1(r,0)===1n?{a0:dec1(r,1),a1:dec1(r,2)}:null; } async function previewAdd(pool,a0,a1){ const [r]=await mc3([{to:PLQLENS,data:"0x"+SEL_PREVIEW_ADD+encAddr(pool)+encUint(a0)+encUint(a1)}]); if(!r)return null; return dec1(r,0)===1n?{lp:dec1(r,1),used0:dec1(r,2),used1:dec1(r,3),ref0:dec1(r,4),ref1:dec1(r,5)}:null; } async function lqTo(){ const v=rc.value.trim(); if(!v)return account; const to=await rcvOf(v).catch(()=>""); if(!to){rc.classList.add("bad"); stat.textContent="Recipient must be an address or a .wei / .gwei / .eth name that resolves."; return null} rc.classList.remove("bad"); return to; } let lqToSeq=0; async function lqShowTo(){ const my=++lqToSeq,v=rc.value.trim(); if(!v){rc.classList.remove("bad");rcvEl.textContent="";return} rcvEl.textContent="resolving..."; const to=await rcvOf(v).catch(()=>""); if(my!==lqToSeq)return; rc.classList.toggle("bad",!to); rcvEl.textContent=to||""; } async function previewZap(pool,tokenIn,amountIn){ const [r]=await mc3([{to:PLQLENS,data:"0x"+SEL_PREVIEW_ZAP+encAddr(pool)+encAddr(tokenIn)+encUint(amountIn)}]); if(!r)return null; return dec1(r,0)===1n?{portion:dec1(r,1),lp:dec1(r,2)}:null; } const lessSlip=v=>{const b=BigInt(Math.round(Math.min(10,Math.max(.01,+slip.value||.5))*100)); return v-(v*b)/10000n}; async function ensureAllowance(token,spender,need){ if(token===ZERO||need===0n)return; const [r]=await mc3([{to:token,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(spender)}]); if(r&&dec1(r,0)>=need)return; stat.textContent="Approving…"; if(r&&dec1(r,0)>0n)await waitTx(await rpc(S,[{from:account,to:token, data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(0),value:"0x0"}])); await waitTx(await rpc(S,[{from:account,to:token, data:"0x"+SEL_APPROVE+encAddr(spender)+encUint(need),value:"0x0"}])); } async function factoryPool(pool){ const [r]=await mc3([{to:PFACTORY,data:"0x"+SEL_ISPOOL+encAddr(pool)}]).catch(()=>[null]); return !!r&&dec1(r,0)===1n; } const SEL_REMOVE_LOSSY="0cc55f06"; const encLossy=(shares,to,take0,take1)=>"0x"+SEL_REMOVE_LOSSY+encUint(shares) +encUint(0n)+encUint(0n)+encAddr(to)+encBool(take0)+encBool(take1); async function lossyExit(pool,shares,t0,t1){ const attempt=async(take0,take1)=>{ const req={from:account,to:pool,value:"0x0",data:encLossy(shares,account,take0,take1)}; try{await rpc(C,[req,L]);return req}catch{return null} }; const both=await attempt(true,true); if(both)return{req:both,note:`This pool cannot pay out in full — one of its` +` tokens is short, or takes a fee on transfer.\n\nYou would receive your share` +` of what it can still pay, on both sides, which is less than the amounts` +` shown.\n\nContinue?`}; for(const [take0,take1,keep,drop] of [[true,false,t0,t1],[false,true,t1,t0]]){ const req=await attempt(take0,take1); if(!req)continue; return{req,note:`The ${drop.sym} side of this pool cannot be transferred.\n\n` +`You can still leave through ${keep.sym} alone — but doing so GIVES UP your` +` ${drop.sym} claim permanently, to the holders who remain.\n\nContinue?`}; } return null; } async function doRemove(pool,shares,t0,t1){ const pv=await previewRemove(pool,shares); if(!pv){stat.textContent="This amount redeems nothing — the pool would refuse it.";return} if(!await factoryPool(pool)){stat.textContent="Not a Precision pool — refusing to send.";return} ++busy; try{ await checkWallet(); stat.textContent="Withdrawing…"; let req={from:account,to:pool,value:"0x0", data:"0x"+SEL_REMOVE+encUint(shares)+encUint(lessSlip(pv.a0))+encUint(lessSlip(pv.a1))+encAddr(account)}; try{await rpc(C,[req,L])} catch(strict){ const alt=await lossyExit(pool,shares,t0,t1); if(!alt)throw strict; let go=false; try{go=confirm(alt.note)} catch{go=false} if(!go){stat.textContent="Withdrawal cancelled.";return} req=alt.req; stat.textContent="Withdrawing what the pool can still pay…"; } await settle(await rpc(S,[req])); await loadLq();refreshBalance(); }catch(e){err(e)} finally{--busy} } async function doAdd(pool,a0,a1,t0,t1){ const to=await lqTo(); if(!to)return; const pv=await previewAdd(pool,a0,a1); if(!pv){stat.textContent="The pool would refuse this deposit.";return} if(!await factoryPool(pool)){stat.textContent="Not a Precision pool — refusing to send.";return} ++busy; try{ await checkWallet(); await ensureAllowance(t0.addr,pool,a0); await ensureAllowance(t1.addr,pool,a1); stat.textContent="Adding…"; const value=t0.addr===ZERO?a0:0n; const req={from:account,to:pool,value:toHex(value), data:"0x"+SEL_ADDEXACT+encUint(0)+encUint(a0)+encUint(a1)+encUint(lessSlip(pv.lp))+encAddr(to)}; await rpc(C,[req,L]); await settle(await rpc(S,[req])); await loadLq();refreshBalance(); }catch(e){err(e)} finally{--busy} } async function doZap(pool,tok,amountIn){ const to=await lqTo(); if(!to)return; const pv=await previewZap(pool,tok.addr,amountIn); if(!pv||pv.lp===0n){stat.textContent="The pool would refuse this deposit.";return} if(!await factoryPool(pool)){stat.textContent="Not a Precision pool — refusing to send.";return} ++busy; try{ await checkWallet(); const minLp=lessSlip(pv.lp); const zap="0x"+SEL_ZAPIN+encAddr(pool)+encAddr(tok.addr)+encUint(amountIn) +encUint(pv.portion)+encUint(minLp)+encAddr(to)+encAddr(account); const leg=encSnwap(tok.addr,amountIn,to,pool,minLp,PROUTE,zap); let data,value; if(tok.addr===ZERO){data=leg;value=toHex(amountIn)} else{ stat.textContent="Approving…"; await ensureAllowance(tok.addr,ZROUTER,amountIn); data=encCalls([ encSnwap(ZERO,0n,account,ZERO,0n,PROUTE, "0x"+SEL_PCHECKPOINT+encAddr(tok.addr)+strip0x(keccak(hexToBytes(zap)))+encAddr(account)), leg]); value="0x0"; } stat.textContent="Zapping…"; const req={from:account,to:ZROUTER,value,data}; await rpc(C,[req,L]); await settle(await rpc(S,[req])); await loadLq();refreshBalance(); }catch(e){err(e)} finally{--busy} } let lqPvT,lqPvSeq=0; async function lqPreview(box){ const pool=box.closest(".lqrow")?.dataset.pool||""; const row=lqRows.find(x=>x.pool.toLowerCase()===pool.toLowerCase()); const p=chPair(); const out=box.querySelector(".lqpv"),go=box.querySelector('[data-act="ac"]'); if(!row||!p){out.textContent="";go.disabled=true;return} const [t0,t1]=p,ins=box.querySelectorAll(".lqin"); let a0,a1; try{ a0=ins[0].value.trim()?parseUnits(ins[0].value.trim(),t0.dec):0n; a1=ins[1].value.trim()?parseUnits(ins[1].value.trim(),t1.dec):0n; }catch{out.textContent="Invalid amount.";go.disabled=true;return} if(isZap(box)){ const z=zapSide(box,t0,t1); if(z.err){out.textContent=z.err;go.disabled=true;return} if(!z.amt){out.textContent=`Enter an amount of ${t0.sym} or ${t1.sym}.`;go.disabled=true;return} const mz=++lqPvSeq; out.textContent="Checking…";go.disabled=true; const zp=await previewZap(row.pool,z.tok.addr,z.amt).catch(()=>null); if(mz!==lqPvSeq)return; if(!zp||zp.lp===0n){out.textContent="The pool would refuse this deposit.";go.disabled=true;return} out.textContent=`swaps ${trimAmt(zp.portion,z.tok.dec)} of ${trimAmt(z.amt,z.tok.dec)} ${z.tok.sym}` +` for ${t1.sym}, then deposits both · mints ~${trimAmt(zp.lp,18)} LP`; go.disabled=false;return; } if(!a0||!a1){ out.textContent="Enter both amounts.";go.disabled=true;return} const my=++lqPvSeq; out.textContent="Checking…";go.disabled=true; const pv=await previewAdd(row.pool,a0,a1).catch(()=>null); if(my!==lqPvSeq)return; if(!pv){out.textContent="The pool would refuse this deposit.";go.disabled=true;return} const ref=(pv.ref0>0n?` · refunds ${trimAmt(pv.ref0,t0.dec)} ${t0.sym}`:"") +(pv.ref1>0n?` · refunds ${trimAmt(pv.ref1,t1.dec)} ${t1.sym}`:""); out.textContent=`deposits ${trimAmt(pv.used0,t0.dec)} ${t0.sym} + ${trimAmt(pv.used1,t1.dec)} ${t1.sym}${ref}`; go.disabled=false; } const isZap=box=>!!box.querySelector(".lqz")?.checked; function zapSide(box,t0,t1){ const ins=box.querySelectorAll(".lqin"); const v0=ins[0].value.trim(),v1=ins[1].value.trim(); if(v0&&v1)return{err:`One side only — clear the ${t1.sym} field, or turn one-sided off.`}; if(!v0&&!v1)return{err:`Enter an amount of ${t0.sym} or ${t1.sym}.`}; const tok=v0?t0:t1; try{return{tok,amt:parseUnits(v0||v1,tok.dec)}}catch{return{err:"Invalid amount."}} } function lqMirror(box,src){ if(isZap(box))return; const pool=box.closest(".lqrow")?.dataset.pool||""; const row=lqRows.find(x=>x.pool.toLowerCase()===pool.toLowerCase()); const p=chPair(); if(!row||!p||!row.r0||!row.r1)return; const [t0,t1]=p,ins=box.querySelectorAll(".lqin"); const i=src===ins[0]?0:src===ins[1]?1:-1; if(i<0)return; const other=ins[1-i],v=src.value.trim(); if(!v){other.value="";return} try{ const raw=parseUnits(v,i===0?t0.dec:t1.dec); other.value=formatUnits(i===0?raw*row.r1/row.r0:raw*row.r0/row.r1,i===0?t1.dec:t0.dec); }catch{} } lqList.addEventListener("input",e=>{ if(!e.target.classList.contains("lqin"))return; const box=e.target.closest(".lqadd"); if(box){lqMirror(box,e.target);clearTimeout(lqPvT);lqPvT=setTimeout(()=>lqPreview(box),300);return} if(e.target.closest(".lqnew")){clearTimeout(lqPvT);lqPvT=setTimeout(lqNewPreview,300)} }); lqList.addEventListener("change",e=>{ if(e.target.id==="lqFee"||e.target.id==="lqRange")return lqNewPreview(); if(!e.target.classList.contains("lqz"))return; const box=e.target.closest(".lqadd"),ins=box.querySelectorAll(".lqin"); const on=e.target.checked; if(!on)lqMirror(box,ins[0]); box.querySelector('[data-act="ac"]').textContent=on?"Zap in":"Add liquidity"; lqPreview(box); }); let lqNewSeq=0; let lqPvT2; const lqPreviewSoon=()=>{clearTimeout(lqPvT2);lqPvT2=setTimeout(lqNewPreview,320)}; async function lqNewPreview(){ const box=lqList.querySelector(".lqnew"); if(!box)return; const out=box.querySelector("#lqPv"),go=box.querySelector("#lqCreate"); const bandOut=box.querySelector("#lqRangeOut"); const my=++lqNewSeq; go.disabled=true; const pair=chPair(); if(!pair){out.textContent="";return} const [t0,t1]=pair; const range=box.querySelector("#lqRange").value; box.querySelector("#lqCustom").classList.toggle("hide",range!=="custom"); const payTok=TOKENS[fromSel.value]; const payIs0=!!payTok&&payTok.addr.toLowerCase()===t0.addr.toLowerCase(); const el0=payIs0?amt:outAmt,el1=payIs0?outAmt:amt; let a0=0n,a1=0n; try{ const v0=el0.value.trim(),v1=el1.value.trim(); a0=v0?parseUnits(v0,t0.dec):0n;a1=v1?parseUnits(v1,t1.dec):0n; }catch{out.textContent="Check the amounts.";bandOut.textContent="";return} const oneSided=(a0&&!a1)?0:(a1&&!a0)?1:-1; box.querySelector("#lqPxRow").classList.toggle("hide",oneSided<0); if(!a0&&!a1){ bandOut.textContent=""; out.textContent=`Enter how much ${t0.sym} and ${t1.sym} to deposit — the ratio sets the opening price.`; return} const pxIn=box.querySelector("#lqPx").value.trim(); const px=oneSided<0 ?(Number(a1)/10**t1.dec)/(Number(a0)/10**t0.dec) :(pxIn?+pxIn:NaN); if(!(px>0)){ bandOut.textContent=""; const only=oneSided===0?t0.sym:t1.sym; out.textContent=`${/^[AEIOU]/i.test(only)?"An":"A"} ${only}-only band has no ratio to` +` read a price from — name one.`; return} const fee=BigInt(box.querySelector("#lqFee").value); let lo,hi; if(range==="custom"){ const n=id=>{const v=box.querySelector("#"+id).value.trim();return v?+v:NaN}; lo=n("lqLo");hi=n("lqHi"); if(!(lo>0)||!(hi>0)){bandOut.textContent="";out.textContent="Name both prices.";return} if(lo>=hi){out.textContent="The low price must be below the high one.";bandOut.textContent="";return} if(oneSided<0&&(px<lo||px>hi)){ out.textContent=`These amounts imply ${fmtPx(px)}, which is outside that band.`;return} }else{ let mult=+range; if(range==="full"){ const found=lqFullWidth(t0.dec,t1.dec,a0,a1,px,fee); if(!found){ bandOut.textContent=""; out.textContent="Even the narrowest range needs more than this deposit.";return} mult=found; } [lo,hi]=lqBandFor(px,mult,a0,a1); } const sl=pxToSqrt(lo,t0.dec,t1.dec),sh=pxToSqrt(hi,t0.dec,t1.dec); if(!sl||!sh||sh<=sl){out.textContent="Check the prices.";bandOut.textContent="";return} const sp=oneSided===0?sl:oneSided===1?sh:lqSeedPrice(sl,sh,a0,a1); if(!sp){out.textContent="Those amounts cannot open a band that wide.";return} const openAt=bandPrice(sp,t0.dec,t1.dec); bandOut.innerHTML=lqBar(lo,hi,openAt)+`<span></span>`; bandOut.querySelector("span").textContent= `${fmtPx(lo)} – ${fmtPx(hi)} · opens at ${fmtPx(openAt)} ${t1.sym} per ${t0.sym}` +(oneSided===0?` · ${t0.sym} only`:oneSided===1?` · ${t1.sym} only`:""); box.querySelector("#lqOwnNote").textContent= `Unowned, like any AMM pool: anyone may add, and no creator fee can ever be` +` charged. Prices are ${t1.sym} per ${t0.sym}.`; lqGasNote(box); const market=lqMarket(t0.addr,t1.addr,sl,sh,fee,ZERO); box.dataset.sl=sl;box.dataset.sh=sh;box.dataset.sp=sp;box.dataset.fee=fee; box.dataset.a0=a0;box.dataset.a1=a1;box.dataset.lp=0; const q=lqSeedQuote(sl,sh,sp,a0,a1); if(!q.ok){out.textContent="This band cannot open at that price with those amounts.";return} if(!lqSeedAdmits(sl,sh,sp,fee,q)){ out.textContent="A band this size could not be traded once opened —" +" widen the range, or deposit more.";return} out.textContent=`Seeds ${trimAmt(q.used0,t0.dec)} ${t0.sym} + ${trimAmt(q.used1,t1.dec)} ${t1.sym}` +((q.used0<a0||q.used1<a1)?" — the rest is returned":""); box.dataset.lp=q.lp; try{ const [addr]=await mc3([{to:PFACTORY,data:"0x"+SEL_POOLFOR+market}]); if(my!==lqNewSeq)return; const pool="0x"+strip0x(addr).slice(24); const [sup]=await mc3([{to:pool,data:"0x"+SEL_TOTALSUPPLY}]).catch(()=>[null]); if(my!==lqNewSeq)return; if(sup&&strip0x(sup).length>=64&&dec1(sup,0)>0n){ out.textContent="That exact band already exists — add to it from the list above."; return} go.disabled=!account; }catch{ if(my!==lqNewSeq)return; out.textContent="Could not reach the factory."; } } const LQ_CREATE_GAS=4700000n; async function lqGasNote(box){ const el=box.querySelector("#lqGas"); if(!el)return; el.textContent=`Creating deploys the pool contract — about ` +`${(Number(LQ_CREATE_GAS)/1e6).toFixed(1)}M gas.`; try{ const wei=BigInt(await rpc("eth_gasPrice",[]))*LQ_CREATE_GAS; el.textContent=`Creating deploys the pool contract — about ` +`${(Number(LQ_CREATE_GAS)/1e6).toFixed(1)}M gas, roughly ` +`${trimAmt(wei,18)} ETH at the moment. Adding to a band that already ` +`exists costs a fraction of it, so check the fee your wallet offers.`; }catch{} } async function lqCreate(){ const box=lqList.querySelector(".lqnew"); if(!box||busy||!account)return; const pair=chPair(); if(!pair)return; const [t0,t1]=pair; const go=box.querySelector("#lqCreate"),out=box.querySelector("#lqPv"); const sl=BigInt(box.dataset.sl||0),sh=BigInt(box.dataset.sh||0),sp=BigInt(box.dataset.sp||0); const a0=BigInt(box.dataset.a0||0),a1=BigInt(box.dataset.a1||0),fee=BigInt(box.dataset.fee||0); if(!sl||!sh||!sp){out.textContent="Preview the band first.";return} const to=await lqTo(); if(!to)return; ++busy;go.disabled=true; try{ const bps=BigInt(Math.round(Math.min(10,Math.max(.01,+slip.value||.5))*100)); const lp=BigInt(box.dataset.lp||0); const minLP=lp>0n?lp*(10000n-bps)/10000n:0n; const market=lqMarket(t0.addr,t1.addr,sl,sh,fee,ZERO); const native=t0.addr===ZERO; if(!native&&a0)await lqApprove(t0.addr,a0); if(a1)await lqApprove(t1.addr,a1); const data="0x"+SEL_CREATE_SEED+market+encUint(sp)+encUint(a0)+encUint(a1) +encUint(minLP)+encAddr(to); const tx={from:account,to:PFACTORY,data,value:native?toHex(a0):"0x0"}; stat.textContent="Creating the band..."; await rpc(C,[tx,L]);await checkWallet();await settle(await rpc(S,[tx])); stat.textContent="Band created."; loadLq();refreshBalance(); }catch(e){err(e);out.textContent="";} finally{--busy;go.disabled=false} } async function lqApprove(token,need){ const al=BigInt(await rpc(C,[{to:token,data:"0x"+SEL_ALLOWANCE+encAddr(account)+encAddr(PFACTORY)},L])); if(al>=need)return; if(al)await settle(await rpc(S,[{from:account,to:token, data:"0x"+SEL_APPROVE+encAddr(PFACTORY)+encUint(0n),value:"0x0"}])); await settle(await rpc(S,[{from:account,to:token, data:"0x"+SEL_APPROVE+encAddr(PFACTORY)+encUint(need),value:"0x0"}])); } lqList.onclick=async ev=>{ if(ev.target.id==="lqCreate"){await lqCreate();return} if(ev.target.id==="lqNewTog"){ const pr=chPair();if(!pr)return