0x68575b07…67c7·#25,913,382·view on Etherscan
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>`
:"";
const bar=goal>0n
?`<div class="lqbar fill" style="--at:${Math.max(0,Math.min(100,pct)).toFixed(1)}%" aria-hidden="true"><b></b></div>`
:"";
const rel=cbReady>0n
?`<span class="cbrow"><span>${trimAmt(cbReady,18)} ETH vested and unreleased</span>`
+`<button type="button" id="cbRel" class="fclink">Release</button></span>`
:"";
const top=(head?`<span class="cbrow">${head}</span>${bar}`:"")+rel;
const act=(text,label)=>`<span class="cbact"><span>${text}</span>`
+`<button type="button" id="cbGo" class="lqbtn">${label}</button></span>`;
const msg=t=>`<span class="cbmsg">${t}</span>`;
const paint=(html,tip)=>{
cbEl.innerHTML=top+html;
cbEl.title=tip;
cbEl.classList.remove("hide");
const gi=i=>document.getElementById(i);
const g=gi("cbGo"),r=gi("cbRel");
if(g)g.onclick=cbAct;
if(r)r.onclick=cbRelease;
};
if(buy){
if(!open||left<=0n)
return rqOff
?paint(msg(`Backing for ${safeSym(tok.sym)} has closed, and burn-back is switched off \u2014 it cannot be redeemed`),
"This DAO has set ragequittable to false, so ragequit reverts and the treasury cannot be claimed against. Only the DAO can switch it back on.")
:paint(msg(`Backing for ${safeSym(tok.sym)} has closed — it can still be burned back`),CB_TIP);
if(rqOff)
return paint(msg(`This cause has switched burn-back off \u2014 backing it cannot be redeemed`),
"Backing pays ether for loot whose only value is the right to burn it back. This DAO has turned that right off, so there is no way out of the position.");
if(!cbEth)
return paint(msg(`Back ${safeSym(tok.sym)} with ether — switch the top token to ETH`),CB_TIP);
let pay=0n;try{pay=parseUnits(amt.value.trim()||"0",18)}catch{}
if(pay<=0n)
return paint(msg(`1 ETH backs ${trimAmt(CONE*CONE/price,tok.dec)} ${safeSym(tok.sym)}`),CB_TIP);
let units=pay*CONE/price;
if(units>left)units=left;
if(units<=0n){cbEl.classList.add("hide");return}
cbAmt=units;
cbTok=tok.addr;
cbCost=(units*price+CONE-1n)/CONE;
return paint(act(`Back with ${trimAmt(cbCost,18)} ETH for `
+`${trimAmt(units,tok.dec)} ${safeSym(tok.sym)}`,"Back"),CB_TIP);
}
if(rqOff)
return paint(msg(`Burn-back has been switched off by this cause \u2014 ${safeSym(tok.sym)} cannot be redeemed`),
"This DAO has set ragequittable to false, so ragequit reverts and the treasury "
+"cannot be claimed against. Only the DAO can switch it back on.");
let want=0n;try{want=parseUnits(amt.value.trim()||"0",tok.dec)}catch{}
if(want<=0n)want=fromBalance;
if(want<=0n){
return paint(msg(`1 ${safeSym(tok.sym)} redeems for ${trimAmt(eth*CONE/sup,18)} ETH`),CB_TIP);}
cbAmt=want;
const due=eth*want/sup;
cbDue=due;cbShares=hit.shares;cbTok=tok.addr;
if(due<=0n)
return paint(msg(eth<=0n
?`This cause has drawn down its treasury — burning ${safeSym(tok.sym)} would pay nothing`
:`Burning ${trimAmt(want,tok.dec)} ${safeSym(tok.sym)} would pay less than a wei`),CB_TIP);
return paint(act(`Burn ${trimAmt(want,tok.dec)} ${safeSym(tok.sym)} `
+`for ${trimAmt(due,18)} ETH`,"Burn"),
"This token is a claim on a cause's treasury, not a position in a market. "
+"Burning it pays your share of the ETHER the cause has not drawn down yet, counted "
+"pro rata across every share and unit of loot. Any other token the cause holds is "
+"not paid out by this burn. It cannot be undone.");
}
const CB_TIP="Backing a cause is not a trade and there is no market to leave through. "
+"What you get is a claim: burn it back at any time for your share of the ether the cause "
+"has not drawn down yet, which is the number this line shows once you hold some.";
async function cbRelease(){
if(!cbDao||cbReady<=0n)return;
const b=document.getElementById("cbRel");if(!b||b.disabled)return;
try{
if(!account)return connect();
b.disabled=true;b.textContent="Releasing…";
const req={from:account,to:CTAP,value:"0x0",data:"0x"+SEL_TAPCLAIM+encAddr(cbDao)};
await rpc(C,[req,L]);
await waitTx(await sendTx([req]));sGot();
stat.textContent="Released to the cause.";
cbSync();
}catch(e){err(e);b.disabled=false;b.textContent="Release"}
}