0x68575b07…67c7·#25,913,254·view on Etherscan
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 sendTx([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 ${(z.tok===t0?t1:t0).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();
box.dataset.mir=String(1-i);
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=>{
const box=e.target.closest(".lqadd");
if(box&&e.target.classList.contains("lqin")){
box.querySelector('[data-act="ac"]').disabled=true;
lqMirror(box,e.target);clearTimeout(lqPvT);lqPvT=setTimeout(()=>lqPreview(box),300);return}
const nb=e.target.closest(".lqnew");
if(nb){nb.querySelector("#lqCreate").disabled=true;
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;
const mir=box.dataset.mir;
if(on&&mir&&ins[0].value.trim()&&ins[1].value.trim())ins[+mir].value="";
if(!on)lqMirror(box,ins[0].value.trim()||!ins[1].value.trim()?ins[0]:ins[1]);
box.querySelector('[data-act="ac"]').textContent=on?"Zap in":"Add liquidity";
lqPreview(box);
});
let lqNewSeq=0;
let lqPvT2;
const lqPreviewSoon=()=>{const g=lqList.querySelector("#lqCreate");if(g)g.disabled=true;
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(.5,Math.max(.01,+slip.value||.5))*100));
const lp=BigInt(box.dataset.lp||0);
if(lp<=0n){out.textContent="Preview the band first.";return}
const minLP=lp*(10000n-bps)/10000n;
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 sendTx([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 sendTx([{from:account,to:token,
data:"0x"+SEL_APPROVE+encAddr(PFACTORY)+encUint(0n),value:"0x0"}]));
await settle(await sendTx([{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;
ev.target.outerHTML=lqNewForm(pr[0],pr[1]);
lqSyncAmounts();
lqNewPreview();
lqList.querySelector(".lqnew")?.scrollIntoView?.({block:"nearest"});
return}
const b=ev.target.closest("[data-act]");
if(!b||busy)return;
const p=chPair(); if(!p)return;
const [t0,t1]=p,pool=b.dataset.pool;
const row=lqRows.find(x=>x.pool.toLowerCase()===pool.toLowerCase());
if(!row)return;
const act=b.dataset.act;
if(act!=="a"&&!account)return void(stat.textContent="Connect a wallet first.");
if(act==="a"){
const box=b.closest(".lqrow").querySelector(".lqadd");
box.classList.toggle("hide");
b.textContent=box.classList.contains("hide")?"Add":"Cancel";
if(!box.classList.contains("hide"))box.querySelector(".lqin").focus();
return;
}
if(act==="w"){
if(row.shares===0n)return;
++busy;b.disabled=true;
try{await doRemove(pool,row.shares,t0,t1)}finally{--busy;b.disabled=false}
return;
}
if(act==="ac"){
const box=b.closest(".lqadd");
const ins=box.querySelectorAll(".lqin");
if(isZap(box)){
const z=zapSide(box,t0,t1);
if(z.err){stat.textContent=z.err;return}
if(!z.amt)return;
++busy;b.disabled=true;
try{await doZap(pool,z.tok,z.amt)}finally{--busy;b.disabled=false}
return;
}
let a0,a1;
try{
a0=parseUnits(ins[0].value.trim(),t0.dec);a1=parseUnits(ins[1].value.trim(),t1.dec);
}catch{stat.textContent="Invalid amount";return}
if(!a0||!a1)return;
++busy;b.disabled=true;
try{await doAdd(pool,a0,a1,t0,t1)}finally{--busy;b.disabled=false}
}
};
footDoc.setAttribute("aria-expanded","false");
footDoc.onclick=e=>{
e.preventDefault();
const on=docPanel.classList.contains("hide");
footDoc.setAttribute("aria-expanded",on?"true":"false");
docPanel.classList.toggle("hide",!on);
footDoc.textContent=on?"hide":"how it works";
if(on)docPanel.scrollIntoView({behavior:"smooth",block:"nearest"});
};
lq.onclick=()=>{sBlip();lqSet(!lqMode)};
let PLAUNCH=MB.la;
const SEL_LAUNCH="6a648dc6";
const SEL_LAUNCHART="72cf7bd8";
const SEL_CREATOROF="dea5c2e0";
const SEL_FLOORPRICE="2aad9987";
const ETHUSD_FEED="0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419";
const ETHUSD_STALE=3600;
let ethUsd=0;
async function loadEthUsd(){
try{
const c=JSON.parse(sessionStorage.getItem("ethusd")||"null");
if(c&&Date.now()-c.t<300000){ethUsd=c.v;return}
}catch{}
try{
const r=await cfgRead(C,[{to:ETHUSD_FEED,data:"0xfeaf968c"},L]);
const h=strip0x(r||"");
if(h.length<5*64)return;
const answer=BigInt("0x"+h.slice(64,128)),updated=Number(BigInt("0x"+h.slice(192,256)));
if(answer<=0n)return;
if(Math.floor(Date.now()/1000)-updated>ETHUSD_STALE)return;
ethUsd=Number(answer)/1e8;
try{sessionStorage.setItem("ethusd",JSON.stringify({t:Date.now(),v:ethUsd}))}catch{}
}catch{}
}
const fmtUsd=v=>{
if(!(v>0))return "";
if(v>=1000)return "$"+Math.round(v).toLocaleString("en-US");
if(v>=1)return "$"+v.toFixed(2);
if(v>=0.01)return "$"+v.toFixed(4);
return "$"+fmtPx(v);
};
const BETH="0x2cb662Ec360C34a45d7cA0126BCd53C9a1fd48F9";
const TITHE_RECORD="0x5E58BA0e06ED0F5558f83bE732a4b899a674053E";
const BURN_TTL=600000;
async function burnTotal(){
try{
const c=JSON.parse(sessionStorage.getItem("burn")||"null");
if(c&&Date.now()-c.t<BURN_TTL)return void showBurn(BigInt(c.v));
}catch{}
try{
const r=await cfgRead(C,[{to:BETH,data:"0x70a08231"+encAddr(TITHE_RECORD)},L]);
const v=BigInt(r||"0x0");
if(v<=0n)return;
try{sessionStorage.setItem("burn",JSON.stringify({t:Date.now(),v:v.toString()}))}catch{}
showBurn(v);
}catch{}
}
function showBurn(v){
if(v<=0n)return;
footBurn.innerHTML=` \u00b7 <a href="https://etherscan.io/token/${BETH}?a=${TITHE_RECORD}" `
+`target="_blank" rel="noopener" title="A tenth of every fee a launched coin earns is burned to Ethereum itself. `
+`This is the running total, held as the BETH receipt minted to the DAO - the ether it stands for no longer exists.">`
+`${(+formatUnits(v,18)).toFixed(4)} ETH burned</a>`;
}
async function quoteFloor(tok,amount,block){
if(!tok||tok===ZERO||amount<=0n)return 0n;
const key=tok.toLowerCase();
const at=block||L;
if(!fcFloorPx[key]||fcFloorPx[key].blk!==at){
try{
const r=await rpc(C,[{to:PLAUNCH,data:"0x"+SEL_FLOORPRICE+encAddr(tok)},at]);
fcFloorPx[key]={v:BigInt(r||"0x0"),blk:at};
}catch{delete fcFloorPx[key];return 0n}
}
const px=fcFloorPx[key].v;
return px>0n?px*amount/(10n**18n):0n;
}
const SEL_POOLOF="988b1fa7",SEL_OWED0="56ea33ee",SEL_OWED1="3ad57b72",SEL_COLLECT="a480ca79";
const SEL_COLLECTMANY="c296057e";
let fcMineToks=[],fcSeq=0,fcMineSeq=0;
async function fcMine(){
const my=++fcMineSeq;
fcMineToks=[];
if(!account||!LAUNCHED.length||PLAUNCH===ZERO)return fcSync();
try{
const reqs=[];
for(const x of LAUNCHED)reqs.push(
{to:PLAUNCH,data:"0x"+SEL_CREATOROF+encAddr(x.addr)},
{to:x.pool,data:"0x"+SEL_OWED0});
const res=await mc3Deep(reqs);
if(my!==fcMineSeq)return;
const acct=account.toLowerCase();
let total=0n;const mine=[];
for(let i=0;i<LAUNCHED.length;i++){
const c=res[i*2],o=res[i*2+1];
if(!c||!o)continue;
if(("0x"+strip0x(c).slice(24)).toLowerCase()!==acct)continue;
let owed=0n;try{owed=BigInt(o)}catch{continue}
if(owed<FC_DUST)continue;
total+=owed;mine.push(LAUNCHED[i]);
}
if(!mine.length)return fcSync();
fcMineToks=mine.map(x=>x.addr);
const cut=(+formatUnits(total*8n/10n,18)).toFixed(4);
fcEl.innerHTML=(mine.length===1?`${safeSym(mine[0].sym)} \u00b7 `:`${mine.length} of your coins \u00b7 `)
+`${cut} ETH in creator fees `
+`<button type="button" id="fcGo" class="fclink">${mine.length===1?"Collect":"Collect all"}</button>`;
fcEl.title="Trading fees your launched coins have earned and nobody has swept yet. "
+"The ether comes to you; the token side is burned, which raises the floor for your holders.";
fcEl.classList.toggle("hide",lnMode||lqMode||wnMode||pvMode||tab!=="swap");
fcGo.onclick=fcCollectMine;
}catch{if(my===fcMineSeq)fcSync()}
}
async function fcCollectMine(){
if(!fcMineToks.length)return;
const b=fcGo;if(b.disabled)return;
const label=b.textContent;
try{
b.disabled=true;b.textContent="Collecting\u2026";
const req={from:account,to:PLAUNCH,value:"0x0",
data:"0x"+SEL_COLLECTMANY+encUint(32)+encUint(BigInt(fcMineToks.length))
+fcMineToks.map(encAddr).join("")};
await rpc(C,[req,L]);
await waitTx(await sendTx([req]));sGot();
stat.textContent="Fees collected.";
for(const k of Object.keys(fcFloorPx))delete fcFloorPx[k];
fcMine();
}catch(e){err(e);b.disabled=false;b.textContent=label}
}
const FC_DUST=10000000000000n;
let fcTok="",fcOwed=0n,fcOwner="";
const fcPool={},fcFloorPx={};
async function fcSync(){
const my=++fcSeq;
const t=TOKENS[toSel.value],f=TOKENS[fromSel.value];
const cand=[t,f].find(x=>x&&x.addr&&x.addr!==ZERO&&x.std!=="nft");
fcTok="";fcOwed=0n;
if(lnMode||lqMode||wnMode||pvMode||tab!=="swap"||PLAUNCH===ZERO){fcEl.classList.add("hide");return}
if(fcMineToks.length){fcEl.classList.remove("hide");return}
if(!cand){fcEl.classList.add("hide");return}
try{
const key=cand.addr.toLowerCase();
let pool=fcPool[key];
if(pool===undefined){
const pr=await rpc(C,[{to:PLAUNCH,data:"0x"+SEL_POOLOF+encAddr(cand.addr)},L]);
if(my!==fcSeq)return;
pool=fcPool[key]="0x"+strip0x(pr||"").slice(24);
}
if(!/^0x[0-9a-f]{40}$/i.test(pool)||/^0x0{40}$/i.test(pool)){fcEl.classList.add("hide");return}
const [o0,cr]=await mc3Deep([{to:pool,data:"0x"+SEL_OWED0},
{to:PLAUNCH,data:"0x"+SEL_CREATOROF+encAddr(cand.addr)}]);
if(my!==fcSeq)return;
fcOwner=cr?"0x"+strip0x(cr).slice(24):"";
const owed=BigInt(o0||"0x0");
if(owed<FC_DUST){fcEl.classList.add("hide");return}
fcTok=cand.addr;fcOwed=owed;
const mine=account&&fcOwner&&account.toLowerCase()===fcOwner.toLowerCase();
fcEl.innerHTML=`${(+formatUnits(owed*8n/10n,18)).toFixed(4)} ETH in `
+`${mine?"creator fees":"fees for this coin's creator"} `
+`<button type="button" id="fcGo" class="fclink">Collect</button>`;
fcEl.title=mine?"Trading fees this coin has earned. Anyone may collect them and the ether comes to you."
:"Trading fees this coin has earned. Anyone may collect them; the ether goes to the coin's creator "
+"either way, and the token side is burned, which raises the floor for holders.";
fcEl.classList.remove("hide");
fcGo.onclick=fcCollect;
}catch{if(my===fcSeq)fcEl.classList.add("hide")}
}
async function fcCollect(){
if(!fcTok)return;
const b=fcGo;if(b.disabled)return;
try{
if(!account)return connect();
b.disabled=true;b.textContent="Collecting\u2026";
const req={from:account,to:PLAUNCH,value:"0x0",data:"0x"+SEL_COLLECT+encAddr(fcTok)};
await rpc(C,[req,L]);
await waitTx(await sendTx([req]));sGot();
stat.textContent="Fees sent to the creator.";
for(const k of Object.keys(fcFloorPx))delete fcFloorPx[k];
fcSync();
}catch(e){err(e);b.disabled=false;b.textContent="Collect"}
}
const SEL_DAOOF="98fabd3a",SEL_LOOTOF="9b7b2ab0",SEL_SHARESOF="03314efa",
SEL_TSUP="18160ddd",SEL_RAGEQUIT="29f64d1a";
const SEL_SALES="c6b9f06a",SEL_REMAINING="b399b0bc",SEL_BUY="cce7ec13";
const SEL_TAPS="6144452a",SEL_TAPCLAIM="1e83409a",SEL_DAOALLOW="dd62ed3e";
const SEL_RQABLE="14a6d7de";
const cbSeen={};
let cbDao="",cbAmt=0n,cbCost=0n,cbBuy=false,cbDrawn=0,cbSold=0n,cbReady=0n,cbSeq=0;
let cbDue=0n,cbShares="",cbTok="";
const cbDisarm=()=>{cbSeq++;cbDao="";cbAmt=0n;cbReady=0n;cbDue=0n;cbShares="";cbTok="";
if(typeof cbEl!=="undefined"&&cbEl)cbEl.classList.add("hide")};
const cbWord=h=>{const b=strip0x(h||"");return b.length>=64?"0x"+b.slice(24,64):""};
async function cbLook(tok){
const key=tok.toLowerCase();
if(cbSeen[key])return cbSeen[key];
let out={dao:"",shares:""};
try{
const dao=cbWord(await rpc(C,[{to:tok,data:"0x"+SEL_DAOOF},L]));
if(/^0x[0-9a-f]{40}$/i.test(dao)&&!/^0x0{40}$/i.test(dao)){
const [lt,sh]=await mc3Deep([{to:dao,data:"0x"+SEL_LOOTOF},{to:dao,data:"0x"+SEL_SHARESOF}]);
if(lt==null||sh==null)return out;
if(cbWord(lt).toLowerCase()===key){out={dao,shares:cbWord(sh)};
const ix=TOKENS.findIndex(t=>t.addr&&t.addr.toLowerCase()===key);
if(ix>=0&&!TOKENS[ix].cause){TOKENS[ix].cause=1;rebuild()}}
}
}catch{
return out;
}
return cbSeen[key]=out;
}
async function cbSync(){
const my=++cbSeq;
cbDao="";cbAmt=0n;cbBuy=false;
const f=TOKENS[fromSel.value],t=TOKENS[toSel.value];
if(lnMode||lqMode||wnMode||pvMode||tab!=="swap"||!f||!t){cbEl.classList.add("hide");return}
const real=x=>x&&x.addr&&x.addr!==ZERO&&x.std!=="nft";
const cbEth=f.addr===ZERO;
let hit=null,tok=null,buy=false;
if(real(t)){const h=await cbLook(t.addr);if(my!==cbSeq)return;
if(h.dao){hit=h;tok=t;buy=true}}
if(!hit&&real(f)){const h=await cbLook(f.addr);if(my!==cbSeq)return;
if(h.dao){hit=h,tok=f}}
if(!hit||!hit.shares||!tok){cbEl.classList.add("hide");return}
let eth=0n,sup=0n,price=0n,dl=0n,left=0n,cap=0n;
let rqOff=false;
const now2=BigInt(Math.floor(Date.now()/1000));
cbReady=0n;
try{
const [b,r]=await Promise.all([
rpc("eth_getBalance",[hit.dao,L]),
mc3Deep([{to:hit.shares,data:"0x"+SEL_TSUP},{to:tok.addr,data:"0x"+SEL_TSUP},
{to:COFF,data:"0x"+SEL_SALES+encAddr(hit.dao)},
{to:COFF,data:"0x"+SEL_REMAINING+encAddr(hit.dao)},
{to:CTAP,data:"0x"+SEL_TAPS+encAddr(hit.dao)},
{to:hit.dao,data:"0x"+SEL_DAOALLOW+encAddr(ZERO)+encAddr(CTAP)},
{to:hit.dao,data:"0x"+SEL_RQABLE}])]);
eth=BigInt(b||"0x0");
if(my!==cbSeq)return;
if(r[0]==null||r[1]==null||r[2]==null||r[3]==null){cbEl.classList.add("hide");return}
const shr=BigInt(r[0]),lot=BigInt(r[1]);
sup=shr+lot;
const sale=strip0x(r[2]||"");
if(sale.length>=320
&&("0x"+sale.slice(24,64)).toLowerCase()===CLOOT
&&/^0+$/.test(sale.slice(64,128))){
dl=BigInt("0x"+sale.slice(128,192));price=BigInt("0x"+sale.slice(192,256));
cap=BigInt("0x"+sale.slice(256,320))}
cbSold=lot;
left=BigInt(r[3]||"0x0");
const tp=strip0x(r[4]||"");
if(tp.length>=256){
const rate=BigInt("0x"+tp.slice(128,192)),lastC=BigInt("0x"+tp.slice(192,256));
const allow=BigInt(r[5]||"0x0");
if(rate>0n&&now2>lastC){
let av=(now2-lastC)*rate;
if(av>allow)av=allow;
if(av>eth)av=eth;
cbReady=av/rate*rate;
}}
rqOff=r[6]!=null&&BigInt(r[6])===0n;
cbDrawn=lot>0n&&price>0n?(()=>{const raised=(lot*price+CONE-1n)/CONE;
return raised>eth?Number((raised-eth)*100n/raised):0})():0;
}catch{}
if(my!==cbSeq)return;
if(sup<=0n){cbEl.classList.add("hide");return}
const now=BigInt(Math.floor(Date.now()/1000));
const open=price>0n&&(dl===0n||dl>now);
const days=dl>now?Number((dl-now+86399n)/86400n):0;
cbDao=hit.dao;cbBuy=buy;
const pct=cap>0n?Number(cbSold*1000n/cap)/10:0;
const goal=cap>0n&&price>0n?cap*price/CONE:0n;
const head=goal>0n
?`<span>${trimAmt(cbSold*price/CONE,18)} of ${trimAmt(goal,18)} ETH backed</span>`
+`<span>${open?days+" days left":"closed"}${cbDrawn>0?" · "+cbDrawn+"% released":""}</span>`
: