fer and it comes back.</p></details>
<details><summary>Orders</summary>
<p><b>Fixed limit</b> — a price you name. It rests until someone fills it or you cancel.</p>
<p><b>Dutch decay</b> — starts above your floor and falls to it over a window you choose. It has two clocks: the decay, then how long it rests at the floor before the escrow returns to you. Set that to <em>Forever</em> and it waits until you cancel.</p>
<p><b>Climbing bid</b> — the mirror: a bid that rises toward a ceiling you name.</p>
<p>Orders are escrowed on chain when placed, so a fill cannot fail for want of funds, and cancelling returns the escrow. Boards are denominated in WETH — an order asking for ETH is asking for WETH, and pays out as such.</p>
<p>The <b>book</b> under the form is live and takeable. Yours sit at the top; everyone else’s come cheapest first for the pair above, each row carrying its own unit price. <b>Fill</b> takes a single order, in part where it allows it. That same book is priced into every Swap quote — a swap that beats the pools on resting orders says <em>Orderbook</em> beside the rate, with the share it took.</p></details>
<details><summary>Liquidity <em><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3.2c3.6 4 6 7.2 6 10.1a6 6 0 0 1-12 0c0-2.9 2.4-6.1 6-10.1z"/></svg></em></summary>
<p>Precision pools are <b>bands</b>, not endless curves. A position earns fees only while the price is inside its band, and holds one asset or the other at the edges. A wider band is safer and earns less; a narrow one is the opposite.</p>
<p>The bar on each row shows where the price sits in that band. <em>Out of range</em> means it is earning nothing right now.</p></details>
<details><summary>Launching a coin <em><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/></svg></em></summary>
<p>One transaction, and no ether needed aside from gas. A fixed supply is minted, your allocation is paid to you immediately, and <b>everything else is seeded into a fresh ETH pool</b> — one-sided, so the pool opens holding only your token.</p>
<p><b>Starting market cap</b> is the pool's virtual ether reserve. It sets the opening price and how hard that price is to move, which are the same dial: roughly that much buying moves the market halfway. Small numbers mean the first buyer takes a large share of the supply, which is why the form says what one ether would buy.</p>
<p><b>Creator keeps</b> is a share of the token SUPPLY, minted straight to you when the coin launches — tokens, not ether, and not a fee on trading. Keep 10% of a billion and 100,000,000 land in your wallet at once, unlocked, with nothing vesting.</p>
<p>Whatever you keep does not go into the pool, and selling it later pulls ether back out of that pool — so a modest-looking percentage can be a claim on most of what early buyers put in. Anyone deciding whether to buy your coin can work that out too.</p>
<p>A <b>logo</b> is stored as contract code on Ethereum — no IPFS, no pinning service, nothing to go dark. Upload anything; the page compresses it and shows the size before you sign. You can replace it later.</p></details>
<details><summary>Launching a cause <em><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/></svg></em></summary>
<p>The other half of the launcher, and a different thing entirely: no pool, no market, no trading. Ten million units are offered at a fixed price in ether — your <b>goal</b> divided by ten million — and what backers pay accumulates in a treasury that belongs to the cause, not to you.</p>
<p>Two clocks, and they are deliberately different lengths. <b>Runs for</b> is how long backing stays open. <b>Released over</b> sets a fixed <b>rate</b>, not a fixed fraction: the tap pays out your <em>goal</em> divided by that window, every second, whatever was actually raised. Ten ether released over a year is under three hundredths of an ether a day.</p>
<p><b>So a cause that raises less than its goal empties faster than the window suggests.</b> Raise a tenth of the goal and the treasury is drained in a tenth of the time — a tenth of a year, not a year. Since what has not been released is exactly what a backer can still burn back for, an under-subscribed cause can leave its backers with nothing well before the release window is over. Set the window long enough that a partial raise still leaves something behind, and read the daily figure below as the speed at full funding.</p>
<p>The release clock starts the moment you launch and runs whether or not anyone has backed you yet, which is why it wants to be much longer than the raise. Release over the same thirty days you raise for and the whole treasury is yours the day backing closes — and somebody who backed you on the last day gets nothing back, because there is nothing left unreleased.</p>
<p>That is what makes the token worth something on its own: <b>whatever has not been released yet can be burned back for</b>. A backer who changes their mind, or who watches a cause go quiet, redeems their share of the untouched treasury and takes their ether home. Nobody has to agree and nothing has to be voted on.</p>
<p><b>The creator can close that exit, but not quietly and not quickly.</b> Burning calls <code>ragequit</code>, which the DAO can disable — and since the creator holds the only share, they alone can propose it and carry the vote. What they cannot do is act on it at once: the change has to go through the DAO, which queues it on chain and then waits out a <b>three-day timelock</b> before it can be executed. That queue is public from the moment it is entered, and burning back stays open the whole time. So the protection is not that nobody can take the exit away; it is that taking it away is visible, and slower than leaving.</p>
<p>Backers hold <b>loot</b>, which carries that claim and no vote. You keep a single share and the vote that comes with it, and you buy that share at the same price everyone else pays — so your claim on the treasury is the same shape as theirs.</p>
<p>Paste a cause’s token into the swap and the page prices the burn for you, against the treasury rather than against a market. It is the only price such a token has, and it is the honest one.</p></details>
<details><summary>Fees</summary>
<p>A launched market charges <b>1%</b> per swap. Half of that stays in the pool as reserves, which raises the floor for every holder. The other half is collectable, and splits <b>80% creator, 10% protocol, 10% tithe</b>.</p>
<p>The <b>tithe</b> is burned to Ethereum itself. The ether goes to the BETH burner, which destroys it and mints a BETH receipt recording that it happened. Nobody can spend it and it cannot be redeemed — the ether no longer exists. What it buys is a smaller ether supply, which accrues to everyone holding ether rather than to us.</p>
<p>The token side of collected fees is burned outright too — a launched coin's supply only ever falls.</p></details>
<details><summary>What this page is</summary>
<p>There is no server. This page is served from a contract, executes only its own bytes, and needs nothing but an Ethereum node to quote and swap. Those bytes cannot be changed by anyone, us included — a new version is a new address, not an edit. The token list is a contract too, artwork included; so are the node list, the solver roster, and the adapter an outside route settles through. It talks to your wallet, to Ethereum, and — when you ask for a quote — to the aggregators listed under Swapping. Which ones those are is itself recorded on chain and curated there, not decided by this page or by whoever answers a request; any of them can be switched off in a single transaction. They are asked for a price on a pair and a size, and <b>they are not told who is asking</b>: the address they are given is the shared adapter, the same one in every other person’s request. They never see a key, and they cannot move anything, because what they return is bounded on chain before it settles. Pin <b>On-chain</b> in the venue list to skip them entirely. WalletConnect is the other outside party: reaching your phone needs its relay. Reads go through a public node you can point anywhere you like.</p>
<p>Your keys never leave your wallet. Nothing here custodies your funds; every trade settles from your address to a contract you can read.</p>
<p>The token list is <b>zList</b>, ranked on chain by conviction staking. Anything not on it can still be traded by pasting its address.</p></details>
<div class="docsmore">Longer notes at <a href="https://docs.zswap.wei.limo" target="_blank" rel="noopener">docs.zswap.wei.limo</a></div>
</div>
<footer id="foot"><span id="footV"></span><span id="footAddr"></span><span id="footBurn"></span><span>· <a id="footDoc" href="#" role="button">how it works</a></span><span>· <a id="footList" target="_blank" rel="noopener" title="Token order here comes from zList, ranked by onchain conviction staking. Stake to move a token up the list - or to get one listed at all.">token list</a></span></footer>
</div>
<script>const IC0='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">',
ICMOON=IC0+'<path d="M20.5 14.6A8.6 8.6 0 0 1 9.4 3.5a8.6 8.6 0 1 0 11.1 11.1z"/></svg>',
ICSUN=IC0+'<circle cx="12" cy="12" r="4.2"/><path d="M12 2v2m0 16v2M4.2 4.2l1.5 1.5m12.6 12.6 1.5 1.5M2 12h2m16 0h2M4.2 19.8l1.5-1.5M18.3 5.7l1.5-1.5"/></svg>';
const ic=()=>{const d=H.className=='d';th.innerHTML=d?ICSUN:ICMOON;tc.content=d?'#0a0a0a':'#fff'};ic();
const ZSWAP_VERSION="0.3";
const ZSWAP_SELF="";
const ZSWAP_PREVIOUS="0xe686952842627A2cf81DF42CCaD54ef98046DB8D";
const ZLIST_URL="https://0x0000006d936ba3653b8854490e16e782cd32a9a8.w4eth.io/";
const selfFromUrl=()=>{
const h=(location.hostname||"").split(".");
if(!/^0x[0-9a-fA-F]{40}$/.test(h[0]||""))return "";
if(/^\d+$/.test(h[1]||"")&&h[1]!=="1")return "";
return h[0];
};
const short=a=>a.slice(0,6)+"\u2026"+a.slice(-4);
(()=>{
const self=selfFromUrl()||ZSWAP_SELF;
footV.textContent=`zSwap v${ZSWAP_VERSION}`;
footList.href=ZLIST_URL;
if(self){
footAddr.innerHTML=`\u00b7 <a href="https://etherscan.io/address/${self}#code" target="_blank" rel="noopener" `
+`title="This exact build, on chain. Its bytes cannot change; a newer version is a different address.">${short(self)}</a>`;
if(ZSWAP_PREVIOUS)footAddr.innerHTML+=` \u00b7 <a href="https://etherscan.io/address/${ZSWAP_PREVIOUS}#code" `
+`target="_blank" rel="noopener" title="The version this one succeeded. It still serves its own page, unchanged.">prev</a>`;
}
})();
th.onclick=()=>{H.classList.toggle('d');LS.t=H.className=='d'?'d':'l';ic()};
const ZQUOTER="0x000000bd2db80567c23e353ca95a251c573cbf9b";
const WNS="0x0000000000696760E15f265e828DB644A0c242EB";
const GNS="0x9D51D507BC7264d4fE8Ad1cf7Fe191933A0a81d6";
const ENSREG="0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e";
const WREG="0x53745292f0d30d68204a63002C17bDa16C772bf7";
const WROLL="0x0000C82AA4D72871568eF3859D2b0E7CF37e45f2";
const IDWEI="cea9efa56b7c8a673303d04b917a7119a2a68f8c4803d8e6fd1c3a1f0d2e4ebe";
const ZROUTER="0x000000000000FB114709235f1ccBFfb925F600e4";
const PFACTORY="0x000000Eb27B557aB426d9E99cFd54EC455799e81";
const PLQLENS="0x000000956bf20A41C54BaE4a4b6F5C8A166DAB4E";
const PPLENS="0x000000Bad3a2fa57ed74fa06000573ccddF6B7fB";
const PI_WORDS=19,PI_POOL=0,PI_LIQ=9,PI_HOOK=10,PI_SL=3,PI_SH=4,
PI_FEE=5,PI_R0=6,PI_R1=7,PI_PX=8,PI_CBPS=12;
let CHAIN_ID=1;
const CHAIN_ICON_BASE=`<svg viewBox="0 0 32 32" width="20" height="20" aria-hidden="true"><rect width="32" height="32" fill="#0052FF"/><path d="M16 5.4A10.6 10.6 0 0 0 5.5 14.7h13.9v2.6H5.5A10.6 10.6 0 1 0 16 5.4Z" fill="#fff" transform="rotate(180 16 16)"/></svg>`;
const CHAIN_ICON_RH=`<svg viewBox="0 0 32 32" width="20" height="20" aria-hidden="true"><rect width="32" height="32" fill="#CCFF00"/><g transform="translate(4 4)" fill="#110E08"><path d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/></g></svg>`;
const CHAINS={
1:{name:"Ethereum",short:"ETH",slow:true,sweep4:true,curve:5,fill:"0x7A2f21e476cA2ADde027BC868c5a083338EEfE54",exec:"0x4f6633342045C80Efa6c8CAc41b9bdc6145168C4",slvId:"1",slvSlug:"ethereum",sources:["UniV2","Sushi","zAMM","UniV3","UniV4","Curve","Lido","Wrap","Precision"],v4port:"0x000000dfb53Fa7f1c486470034741d5BCBE14BE9",v4lens:"0x000000c3aE1692983941495162A4AAB40660E65F",tokenlist:"0x0000006013dF75A31678B786061C2B54bf531524",
wrapped:"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",explorer:"https://etherscan.io",
rpcs:["https://ethereum-rpc.publicnode.com","https://eth-mainnet.public.blastapi.io"]},
8453:{name:"Base",short:"BASE",sweep4:false,curve:-1,fill:"0x03d95ecAF7B2Ab6027b8541b19Cb0Ce5CEd9bC74",exec:"0x1434A2579DE1009aA12b7064A161E53a5be2C030",slvId:"8453",slvSlug:"base",sources:["UniV2","Aero","zAMM","UniV3","UniV4","AeroCL","Wrap","","Precision"],v4port:"0x508ad1b0ae31FaF295c5af8C5c2bE9e33E0D19C4",v4lens:"0x0000000000000000000000000000000000000000",tokenlist:"0x0000006013dF75A31678B786061C2B54bf531524",
wrapped:"0x4200000000000000000000000000000000000006",explorer:"https://basescan.org",hubs:["0x4200000000000000000000000000000000000006","0x833589fcd6edb6e08f4c7c32d4f71b54bda02913","0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf","0x50c5725949a6f0c72e6c4a641f24049a917db0cb","0x940181a94a35a4569e4529a3cdfb74e38fd98631"],
icon:CHAIN_ICON_BASE,rpcs:["https://mainnet.base.org","https://base-rpc.publicnode.com"]},
4663:{name:"Robinhood",short:"RH",sweep4:false,curve:5,fill:"0x03d95ecAF7B2Ab6027b8541b19Cb0Ce5CEd9bC74",exec:"0x1434A2579DE1009aA12b7064A161E53a5be2C030",slvId:"4663",slvSlug:"robinhood",sources:["UniV2","Sushi","zAMM","UniV3","UniV4","Curve","Lido","Wrap","Precision"],v4port:"0x508ad1b0ae31FaF295c5af8C5c2bE9e33E0D19C4",v4lens:"0x0000000000000000000000000000000000000000",tokenlist:"0x0000006013dF75A31678B786061C2B54bf531524",
wrapped:"0x0bd7d308f8e1639fab988df18a8011f41eacad73",explorer:"https://robinscan.io",hubs:["0x0bd7d308f8e1639fab988df18a8011f41eacad73"],
rpcs:["https://rpc.mainnet.chain.robinhood.com"]},
};
CHAINS[4663].icon=CHAIN_ICON_RH;
const MB={sb:"0x000000dA7bb4B2A9E3e80e9A4D4157E26CA6189b",s1:"0x000000fF3D7A2d373615141d7489Ca66683DbecF",sw:"0x00000087A6dc5071779Ed1F8274A39230768B976",du:"0x000000a213b430D14Bae6062c176289B05e04489",ob:"0x000000e6c7a12C80525ee74e7434aAb919447D95",fl:"0x00000080198137F790DA4C52bb902cf87c276748",pr:"0x0000007Be74558A1F8c9045301c6F44C8eD0c9eB",la:"0x0000002fC8E77585A008Aa45d78A71ad36293aEe"};
const L2B={sb:"0x0000001330435808A906432449D233d482Bc9b60",sw:"0x000000015d9428959A495E31A6999E1C61C64F00",du:"0x000000fa42d555173395323b2956e9c42EFaEFf2",ob:"0x0000007a1c93d42dD739E8d6cA9Dabf330934970",fl:"0x000000AC7d32e802B003a31F790eb28Ed3294Bac",pr:"0x000000D14E24e5FC8965bcDE58f21f704C944999"};
const chainCfg=()=>CHAINS[CHAIN_ID]||CHAINS[1];
const L1_RPCS=["https://ethereum-rpc.publicnode.com","https://eth-mainnet.public.blastapi.io"];
let l1i=0;
const l1Read=async(method,params)=>{
for(let k=0;k<L1_RPCS.length*2;k++){
const u=L1_RPCS[l1i%L1_RPCS.length];
try{return await httpRead(u,method,params)}catch(e){l1i++}
}
throw Error("no mainnet read node");
};
const cfgRead=(method,params)=>CHAIN_ID===1?rpc(method,params):l1Read(method,params);
const SHARE_HOST="new.zswap.wei.limo";
const ZERO="0x0000000000000000000000000000000000000000";
let WETH="0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";
let TOKENLIST="0x0000006013dF75A31678B786061C2B54bf531524";
const ZLISTLENS="0x000000cEa3AB048d59473F3fb116A8D7F1abd247";
const SEL_RANKEDIDS="df7ca268";
const SEL_JSON="74e18e96";
const SEL_CID="fb021939",SEL_RES="4f896d4f",SEL_REV="9af8b7aa";
const SCOREM="0x57Fae63f7c63732fc4B77247F0255b11b9458AE1",SEL_SCLAIM="5cc2f461";
const SEL_WAVAIL="8f8dc386",SEL_WFEE="fcee45f4",SEL_WPREM="1bf1fffb",SEL_WCOMMIT="f14fcbc8",SEL_WCOMMITS="839df945",SEL_WREVEAL="ea9384fa",SEL_WSUB="a00fd3c8";
const SEL_RSTATE="c19d93fb",SEL_RWEIGHT="0767d178",SEL_RTICKET="673b4784",SEL_RENTER="23972aef",SEL_WOWNER="6352211e";
const WN_KEY="zswap:wns",WN_MIN=60,WN_MAX=86400;
const SEL_RSLV="0178b8bf",SEL_EADDR="3b3b57de",SEL_ENAME="691f3431";
const SEL_ERESOLVE="9061b923";
const NAME_OFFCHAIN="That name keeps its records off chain, and this page reads only on-chain state.";
const SEL_ALLOWANCE="dd62ed3e";
const SEL_LATEST="52bfe789",SEL_PREV="247dfaa8",SEL_SUCCAT="451aae60";
const MATURITY=259200;
const SEL_DS="3644e515",SEL_NONCES="7ecebe00",SEL_NAME="06fdde03",SEL_PERMIT_TH="30adf81f";
const TH_2612="6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9";
const SEL_RPERMIT="7ac2ff7b",SEL_MULTICALL="ac9650d8",SEL_SWEEP="cb019b84";
const PERMIT2="0x000000000022D473030F116dDEE9F6B43aC78BA3";
const SEL_P2TF="09d31579";
const EIP712DOM="EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)";
const SEL_APPROVE="095ea7b3";
const SEL_BALANCEOF="70a08231";
let V4PORT="0x000000dfb53Fa7f1c486470034741d5BCBE14BE9";
let V4LENS="0x000000c3aE1692983941495162A4AAB40660E65F";
const SEL_V4QUOTE="d500463c",SEL_V4SWAP="48e6f730";
const V4POOL_KEY="0x95a932c205571d4d1ca72715c642a2eca21dde79ffc28ff11509681f9383385f";
const C="eth_call",S="eth_sendTransaction",L="latest",I="eth_chainId";
let SOURCES=["UniV2","Sushi","zAMM","UniV3","UniV4","Curve","Lido","Wrap","Precision"];
const SRC_PRECISION=8;
let SRC_CURVE=5;
const ETH_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#627EEA"/><g fill="#FFF" fill-rule="nonzero"><path fill-opacity=".602" d="M16.498 4v8.87l7.497 3.35z"/><path d="M16.498 4L9 16.22l7.498-3.35z"/><path fill-opacity=".602" d="M16.498 21.968v6.027L24 17.616z"/><path d="M16.498 27.995v-6.028L9 17.616z"/><path fill-opacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/><path fill-opacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/></g></g></svg>`;
const USDC_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none"><circle fill="#2775CA" cx="16" cy="16" r="16"/><g fill="#FFF"><path d="M20.022 18.124c0-2.124-1.28-2.852-3.84-3.156-1.828-.243-2.193-.728-2.193-1.578 0-.85.61-1.396 1.828-1.396 1.097 0 1.707.364 2.011 1.275a.458.458 0 00.427.303h.975a.416.416 0 00.427-.425v-.06a3.04 3.04 0 00-2.743-2.489V9.142c0-.243-.183-.425-.487-.486h-.915c-.243 0-.426.182-.487.486v1.396c-1.829.242-2.986 1.456-2.986 2.974 0 2.002 1.218 2.791 3.778 3.095 1.707.303 2.255.668 2.255 1.639 0 .97-.853 1.638-2.011 1.638-1.585 0-2.133-.667-2.316-1.578-.06-.242-.244-.364-.427-.364h-1.036a.416.416 0 00-.426.425v.06c.243 1.518 1.219 2.61 3.23 2.914v1.457c0 .242.183.425.487.485h.915c.243 0 .426-.182.487-.485V21.34c1.829-.303 3.047-1.578 3.047-3.217z"/><path d="M12.892 24.497c-4.754-1.7-7.192-6.98-5.424-11.653.914-2.55 2.925-4.491 5.424-5.402.244-.121.365-.303.365-.607v-.85c0-.242-.121-.424-.365-.485-.061 0-.183 0-.244.06a10.895 10.895 0 00-7.13 13.717c1.096 3.4 3.717 6.01 7.13 7.102.244.121.488 0 .548-.243.061-.06.061-.122.061-.243v-.85c0-.182-.182-.424-.365-.546zm6.46-18.936c-.244-.122-.488 0-.548.242-.061.061-.061.122-.061.243v.85c0 .243.182.485.365.607 4.754 1.7 7.192 6.98 5.424 11.653-.914 2.55-2.925 4.491-5.424 5.402-.244.121-.365.303-.365.607v.85c0 .242.121.424.365.485.061 0 .183 0 .244-.06a10.895 10.895 0 007.13-13.717c-1.096-3.46-3.778-6.07-7.13-7.162z"/></g></g></svg>`;
const USDT_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#26A17B"/><path fill="#FFF" d="M17.922 17.383v-.002c-.11.008-.677.042-1.942.042-1.01 0-1.721-.03-1.971-.042v.003c-3.888-.171-6.79-.848-6.79-1.658 0-.809 2.902-1.486 6.79-1.66v2.644c.254.018.982.061 1.988.061 1.207 0 1.812-.05 1.925-.06v-2.643c3.88.173 6.775.85 6.775 1.658 0 .81-2.895 1.485-6.775 1.657m0-3.59v-2.366h5.414V7.819H8.595v3.608h5.414v2.365c-4.4.202-7.709 1.074-7.709 2.118 0 1.044 3.309 1.915 7.709 2.118v7.582h3.913v-7.584c4.393-.202 7.694-1.073 7.694-2.116 0-1.043-3.301-1.914-7.694-2.117"/></g></svg>`;
const WBTC_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#F7931A"/><path fill="#FFF" fill-rule="nonzero" d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"/></g></svg>`;
const WSTETH_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none"><circle fill="#00A3FF" cx="16" cy="16" r="16"/><path d="M9.437 14.864l-.181.275c-2.048 3.097-1.603 7.253 1.034 9.824 1.561 1.521 3.622 2.353 5.683 2.353 0 0 0 0-6.536-12.452z" fill="#FFF"/><path opacity=".6" d="M15.997 18.611l-6.56-3.747c6.56 12.452 6.56 12.452 6.56 12.452 0-2.683 0-5.623 0-8.705z" fill="#FFF"/><path opacity=".6" d="M22.563 14.864l.181.275c2.048 3.097 1.603 7.253-1.034 9.824-1.561 1.521-3.622 2.353-5.683 2.353 0 0 0 0 6.536-12.452z" fill="#FFF"/><path opacity=".2" d="M16.003 18.611l6.56-3.747c-6.56 12.452-6.56 12.452-6.56 12.452 0-2.683 0-5.623 0-8.705z" fill="#FFF"/><path opacity=".2" d="M16.004 10.239v6.459l5.654-3.23-5.654-3.229z" fill="#FFF"/><path opacity=".6" d="M16.005 10.239l-5.655 3.229 5.655 3.23v-6.46z" fill="#FFF"/><path d="M16.005 4.805l-5.655 8.668 5.655-3.233V4.805z" fill="#FFF"/><path opacity=".
ize:.82em;letter-spacing:.02em;margin-left:.15em}
#chNote{display:block;font-size:.65em;color:var(--m);margin-top:.35em;text-align:right;font-variant-numeric:tabular-nums}
.panel select.rich{position:absolute;width:1px;height:1px;opacity:0;pointer-events:none}
.tkb{display:none;align-items:center;gap:.25em;border:0;background:transparent;padding:0;
font:inherit;font-size:.9em;font-weight:600;letter-spacing:.02em;color:var(--f);cursor:pointer}
.tkb.on{display:inline-flex}
.tkbc{font-size:.7em;color:var(--m)}
.tkp{position:fixed;z-index:40;width:min(20em,calc(100vw - 2em));max-height:min(32em,calc(100vh - 5em));
display:flex;flex-direction:column;background:var(--c);border:1px solid var(--hd);overflow:hidden}
#tkFind{border:0;border-bottom:1px solid var(--e);background:transparent;color:var(--f);
font:inherit;font-size:.8em;padding:.7em .8em;outline:0;flex:none}
#tkList{overflow-y:auto;overscroll-behavior:contain}
#wkWrap{position:fixed;inset:0;z-index:60;background:#0006;display:flex;align-items:center;justify-content:center}
#wkWrap.hide{display:none}
#wkPanel{width:min(19em,calc(100vw - 2em));background:var(--c);border:1px solid var(--hd);
overflow:hidden;
max-height:min(32em,calc(100vh - 2em));display:flex;flex-direction:column}
#wkHdr{flex:none}
#wkList{overflow-y:auto;overscroll-behavior:contain}
#wkHdr{padding:.7em .8em;border-bottom:1px solid var(--e);font-size:.72em;
letter-spacing:.14em;text-transform:uppercase;color:var(--m)}
#wkList img,#wkList .wki{width:20px;height:20px;border-radius:50%;flex:none}
.wcq{padding:.7em .8em 0}
.wcn{padding:.55em .8em .85em;font-size:.72em;color:var(--m)}
.wcnode{padding-top:0;opacity:.85}
#wkList .wki{display:block}
#wkList .wki svg{width:100%;height:100%;display:block}
.tkg{border-top:1px solid var(--e);padding:.7em .8em .3em;font-size:.62em;letter-spacing:.14em;text-transform:uppercase;color:var(--m)}
.tkr{display:flex;align-items:flex-start;gap:.55em;width:100%;box-sizing:border-box;
padding:.55em .8em;border:0;border-top:1px solid var(--e);background:transparent;color:var(--f);font:inherit;
text-align:left;cursor:pointer}
#tkList>.tkr:first-child,#tkList>.tkg:first-child,.tkg+.tkr{border-top:0}
.tkr:hover,.tkr.cur{background:var(--fh)}
.tkr[aria-disabled="true"]{opacity:.5;cursor:default}
.tkr[aria-disabled="true"]:hover{background:transparent}
.tkr img,.tkr svg{flex:none;border-radius:50%;margin-top:.1em}
.tki{flex:none;width:20px;height:20px;display:flex;align-items:center;justify-content:center}
.tkt{min-width:0;flex:1}
.tks{display:flex;align-items:baseline;gap:.45em}
.tks b{font-size:.85em;font-weight:600}
.tkn{font-size:.7em;color:var(--m);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tkd{font-size:.66em;color:var(--m);line-height:1.45;margin-top:.15em;
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.tkmark{flex:none;font-size:.75em;color:var(--m)}
.tkempty{padding:1.4em .8em;text-align:center;font-size:.72em;color:var(--m)}
.sc{margin-top:.7em;padding:.7em .8em;border:1px solid var(--e);font-size:.72em;color:var(--m);line-height:1.55}
.sc b{display:block;color:var(--f);font-size:1.15em;word-break:break-all}
.sc .scw{color:var(--w)}
.sc button{margin-top:.5em;padding:.4em .8em;font-size:1em}
body:has(.tkp:not(.hide))::before{content:"";position:fixed;inset:0;z-index:39;pointer-events:none;background:#0006}
@media(max-width:30em){#slipL span{display:none}}
@media(pointer:coarse){
#th,#lk,#lq,#ln,#wn,#pv{width:36px;height:36px}
#sd{width:44px;height:44px;top:.35em;right:.35em}
#addr{padding:.55em 0;margin:-.55em 0}
.tkb{padding:.7em 0 .7em .5em;margin:-.7em 0}
.flip{width:2.4em;height:2.4em;margin:-1.2em auto}
#slipL{padding:.5em 0;margin:-.5em .15em -.5em 0}
.chtog{padding:1em .1em}
.fclink{padding:.55em 0;margin:-.55em 0 -.55em .35em}
.docs summary{padding:1em .2em}
.tkp{position:fixed;left:0;right:0;bottom:0;top:auto;width:100%;max-width:none;
box-sizing:border-box;border-left:0;border-right:0;
max-height:82vh;border-bottom:0;
padding-bottom:env(safe-area-inset-bottom,0)}
.tkp::before{content:"";flex:none;width:2.2em;height:.25em;
background:var(--e);margin:.5em auto .1em}
.tkd{-webkit-line-clamp:1}
.tkr{padding:.75em .9em}
#tkFind{padding:.85em .9em;font-size:16px}.rcpt,.meta input,.dly input,.lqin{font-size:16px}.lnbtn{padding:.6em .8em}.p button,.o button,.lqbtn{padding:.6em .8em}.chtf button{padding:.5em}
.tabs button{padding:.95em .5em}
.dly{padding:.9em .8em}
.wnbtn{padding:.95em .8em}
#amt,#outAmt{padding:.3em 0}
.invh{font-size:11px;top:6px;left:10px;right:10px;gap:.3em;flex-wrap:wrap;row-gap:3px}
.invq{padding:10px 11px;margin:-10px -6px;opacity:1}
.invm,.invsh{padding:10px 3px;margin:-10px -1px}}
@media(pointer:coarse) and (max-width:24em){
.meta{gap:.12em}#th,#lk,#lq,#ln,#wn,#pv{width:32px;height:32px}#slip{width:2em}}</style>
<script>const H=document.documentElement;let LS;try{LS=localStorage}catch{LS={}}if(LS.t?LS.t=='d':matchMedia('(prefers-color-scheme:dark)').matches)H.className='d'</script>
<button id="sd" title="Sound" aria-label="Toggle sound"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 9.5v5h3.5L12 18V6L7.5 9.5H4z"/><path id="sdw" d="M15.5 9a4 4 0 0 1 0 6"/><path id="sdx" d="M16 9.5l4 4m0-4l-4 4"/></svg></button>
<a id="logoLink" href="https://zfi.wei.is/dao/#/dao/1/0x5E58BA0e06ED0F5558f83bE732a4b899a674053E" target="_blank" rel="noopener" aria-label="The DAO that governs this page" title="zFi DAO — the governance that owns this page's lineage"><svg id="logo" viewBox="0 0 400 400" aria-hidden="true"><clipPath id="lgf"><rect width="400" height="400"/></clipPath><g clip-path="url(#lgf)"><rect width="400" height="400"/><path d="M-60-20L460-20L460 90L80 310L460 310L460 420L-60 420L-60 310L320 90L-60 90Z"/></g></svg></a>
<svg id="fish" viewBox="0 0 24 14" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" aria-hidden="true"><path d="M2 12.2C7 .8 18 1.8 22 7"/><path d="M2 1.8C7 13.2 18 12.2 22 7"/></svg>
<div class="card">
<div class="meta"><span id="net" role="button" tabindex="0" title="Network"></span><span id="addr" role="button" tabindex="0" title="Connect a wallet">Connect</span><label id="slipL" title="Slippage"><span>Slippage </span><input id="slip" type="number" value="0.5" min="0.01" max="10" step="0.1">%</label><button id="lq" title="Liquidity for this pair" aria-label="Toggle liquidity mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3.2c3.6 4 6 7.2 6 10.1a6 6 0 0 1-12 0c0-2.9 2.4-6.1 6-10.1z"/></svg></button><button id="ln" title="Launch a coin" aria-label="Toggle launch mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/></svg></button><button id="wn" title="Claim a .wei name" aria-label="Toggle names mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" fill-rule="evenodd" shape-rendering="crispEdges" aria-hidden="true"><path d="M3 1h1v1H3zM5 2h1v1H5zM10 2h1v1h-1zM12 1h1v1h-1zM4 4h8v1H4zM3 5h10v1H3zM2 6h12v3H2zM3 9h10v1H3zM4 10h8v1H4zM5 11h2v1H5zM9 11h2v1H9zM2 12h3v1H2zM7 12h2v1H7zM11 12h3v1h-3zM1 13h2v1H1zM13 13h2v1h-2zM3 6h3v2H3zM10 6h3v2h-3z"/></svg></button><button id="pv" title="Bridge privately to Base or Robinhood" aria-label="Toggle private bridge mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l7 3v5c0 4.5-3 8.2-7 10-4-1.8-7-5.5-7-10V6l7-3z"/><path d="M9 12l2 2 4-4"/></svg></button><button id="lk" title="Copy shareable link" aria-label="Copy shareable link"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2.5"/><path d="M5.5 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v.5"/></svg></button><button id="th" title="Toggle theme" aria-label="Toggle theme"></button></div>
<div class="tabs" role="tablist"><button id="tabSwap" class="on" role="tab" aria-selected="true">Swap</button><button id="tabSend" role="tab" aria-selected="false">Send</button><button id="tabBook" role="tab" aria-selected="false">Orders</button></div>
<div class="panel"><div class="hdr"><small id="payL">You pay</small><span class="pill"><span id="fromIcon"></span><select id="fromSel" aria-labelledby="payL"></select><button type="button" class="tkb" id="fromPick" aria-haspopup="listbox" aria-expanded="false"><span class="tkbs"></span><span class="tkbc">▾</span></button></span></div><div class="row" id="amtRow"><input id="amt" type="text" inputmode="decimal" placeholder="0.0" aria-label="Amount to pay" autocomplete="off" spellcheck="false"></div><small id="bal"></small></div>
<button id="flip" class="flip" title="Flip tokens" aria-label="Flip tokens"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14m-5-5 5 5 5-5"/></svg></button>
<div class="panel" id="rcvPanel"><div class="hdr"><small id="rcvHdr">You receive</small><span class="pill"><span id="toIcon"></span><select id="toSel" aria-labelledby="rcvHdr"></select><button type="button" class="tkb" id="toPick" aria-haspopup="listbox" aria-expanded="false"><span class="tkbs"></span><span class="tkbc">▾</span></button></span></div><div class="row" id="outRow"><input id="outAmt" type="text" inputmode="decimal" placeholder="0.0" aria-label="Amount to receive" autocomplete="off" spellcheck="false"></div><small id="bal1" class="hide"></small><small id="rate" role="status" aria-live="polite"></small><small id="rankBox" class="rank hide"></small></div>
<div id="lqPanel" class="hide"><div class="hdr"><small>Liquidity</small><small id="lqSub"></small></div><div id="lqList"></div></div>
<div id="lnPanel" class="hide">
<div class="hdr lnhdr"><small id="lnTitle">Launch a coin</small><small id="lnSub">0.4% of each trade goes to the creator</small></div>
<label class="dly" id="lnKindL" title="A coin opens a market: supply is minted and seeded into a pool that trades from the first block. A cause opens a fund: backers pay ether for a token that can always be burned back for its share of whatever the cause has not spent.">Launch<select id="lnKind"><option value="coin">Coin</option><option value="cause">Cause</option></select></label>
<label class="dly">Name<input id="lnName" maxlength="64" placeholder="Zero Cat" autocomplete="off" spellcheck="false"></label>
<label class="dly">Symbol<input id="lnSym" maxlength="16" placeholder="ZCAT" autocomplete="off" spellcheck="false"></label>
<label class="dly" id="lnSupplyL">Supply<input id="lnSupply" inputmode="decimal" value="1,000,000,000" autocomplete="off" spellcheck="false"></label>
<div class="lngap" id="lnMktL">Market</div>
<label class="dly" id="lnMcapL" title="The pool holds NO ether at launch - this is its virtual ether reserve, and it is the only thing deciding where on the curve your market opens. It sets the opening price and how hard that price is to move, which are the same knob: roughly this much buying moves the market halfway. Small numbers mean the first buyer takes a huge share of the supply.">Starting market cap<span class="lnu"><input id="lnMcap" inputmode="decimal" value="30" autocomplete="off" spellcheck="false"><i>ETH</i></span></label>
<label class="dly" id="lnAllocL" title="The percentage of total supply paid to the creator at launch. Everything else is seeded into the pool. Capped at 20%.">Creator keeps<span class="lnu"><input id="lnAlloc" inputmode="decimal" value="0" autocomplete="off" spellcheck="false"><i>%</i></span></label>
<label class="dly hide" id="lnGoalL" title="What the whole raise comes to. Ten million units are offered and the price is this divided by ten million, so a backer always knows what one unit costs and what it is a claim on.">Goal<span class="lnu"><input id="lnGoal" inputmode="decimal" value="10" autocomplete="off" spellcheck="false"><i>ETH</i></span></label>
<label class="dly hide" id="lnDaysL" title="How long backing stays open. After this, the sale closes and nobody else can buy in.">Runs for<span class="lnu"><input id="lnDays" inputmode="decimal" value="30" autocomplete="off" spellcheck="false"><i>days</i></span></label>
<label class="dly hide" id="lnVestL" title="How slowly the ether reaches you. The clock starts the moment you launch and runs whether or not anyone has backed you yet, so this wants to be well longer than the raise - what has not been released is exactly what a backer can still burn their token back for, and a release that finishes when backing closes leaves them nothing. The rate is your GOAL divided by this window, so a raise that falls short of the goal is emptied proportionally sooner - a tenth of the goal, a tenth of the window.">Released over<select id="lnVest"><option value="7889238">3 months</option><option value="15778476">6 months</option><option value="31556952" selected>12 months</option><option value="63113904">2 years</option></select></label>
<div class="lngap">Optional</div>
<label class="dly" id="lnArtRow" title="Held on-chain as contract code, not as a link. Nothing to pin and no gateway to go dark. Editable later, so a rough mark now costs nothing.">Logo<span><img id="lnPrev" alt=""><small id="lnArtNote">none</small><button type="button" id="lnPick" class="lnbtn">Choose</button></span></label>
<input id="lnArt" type="file" accept="image/png,image/jpeg,image/webp,image/gif,image/svg+xml" class="hide">
</div>
<div id="wnPanel" class="hide">
<div class="hdr lnhdr"><small>Claim a name</small><small id="wnSub">from 0.0005 ETH a year</small></div>
<label class="dly">Name<input id="wnName" maxlength="63" placeholder="human" autocomplete="off" spellcheck="false"></label>
<label class="dly" id="wnKindL">Ending<select id="wnTld"><option value="wei">.wei</option><option value="id">.id.wei — free</option></select></label>
<small id="wnNote" role="status" aria-live="polite"></small>
</div>
<div id="pvPanel" class="hide">
<div class="hdr lnhdr"><small>Private bridge</small><small id="pvSub">Shielded ether on Ethereum: exit to an L2, withdraw, or pay a request</small></div>
<label class="dly" title="Deposited into the confidential pool on Ethereum as a shielded note. Exit it later to an L2 address that nothing on-chain ties to this wallet. The amount, the L2 recipient and the timing of the exit are public; what is hidden is which deposit it came from, so the privacy is only as good as the number of other deposits in the pool.">Amount<span class="lnu"><input id="pvAmt" inputmode="decimal" placeholder="0.01" autocomplete="off" spellcheck="false"><i>ETH</i></span></label>
<label class="dly" title="Split the deposit into equal notes that can be spent one at a time. A withdrawal or exit always spends a whole note, so several small notes are what makes a partial spend possible later.">Notes<select id="pvSplit"><option value="1">1</option><option value="2">2</option><option value="4">4</option></select></label>
<label class="dly">Arrives on<select id="pvChain"><option value="8453">Base</option><option value="4663">Robinhood</option><option value="1">Ethereum (withdraw)</option></select></label>
<label class="dly" title="The address credited on the chain chosen above. Leave blank for this wallet. On Robinhood the unused gas budget refunds here too, so a contract wallet gets its refund at an aliased address.">Recipient<input id="pvTo" placeholder="0x… on that chain, defaults to you" autocomplete="off" spellcheck="false"></label>
<label class="dly" title="Through the relay: it proves and settles, paying its own gas; an exit costs a flat fee and this wallet never signs it. From this wallet: the relay only proves and you send the settle yourself, fee-free, but from this wallet. Applies to deposits, exits and payments.">Settle<select id="pvPath"><option value="relay">Through the relay</option><option value="self">From this wallet</option></select></label>
<small id="pvKey" role="status"></small>
<div id="pvList"></div>
<small id="pvNote" role="status" aria-live="polite"></small>
</div>
<input id="rc" class="rcpt" placeholder="Recipient (optional) — 0x or .eth/.(g)wei" spellcheck="false" autocomplete="off">
<small id="rcvEl"></small>
<small id="fcEl" class="hide" role="status" aria-live="polite"></small>
<small id="cbEl" class="hide" role="status" aria-live="polite"></small>
<small id="lnNote" class="hide" role="status" aria-live="polite"></small>
<button id="lnGo" class="primary hide">Launch</button>
<button id="wnGo" class="primary hide">Register</button>
<button id="pvGo" class="primary hide">Deposit</button>
<div id="wnRollPanel" class="hide"><div class="lngap">roll.wei</div><small id="wnRollEl" role="status" aria-live="polite"></small><button id="wnEnter" class="wnbtn hide">Enter the roll</button></div>
<label class="dly hide" id="kindL">Order type<select id="kind"><option value="fixed">Fixed limit</option><option value="dutch">Dutch decay</option><option value="floor">Climbing bid</option></select></label>
<label class="dly hide" id="dlyL">SLOW<select id="dly"><option value="0">Instant</option><option value="600">10 minutes</option><option value="3600">1 hour</option><option value="86400">1 day</option><option value="259200">3 days</option><option value="604800">7 days</option></select></label>
<label class="dly hide" id="tipL" title="A time-locked transfer does not move on its own: once the lock expires somebody has to send the claim transaction. Tick this and the page posts a small ETH tip alongside the deposit, which pays a keeper to claim it for the recipient the moment it matures. The tip is refundable — if you reverse the transfer, the ether comes back to you.">Auto-claim<span class="tipr"><small id="tipNote"></small><input id="tipCk" type="checkbox" aria-describedby="tipNote"></span></label>
<label class="dly hide" id="fillL">Fill<select id="fill"><option value="1">Any amount</option><option value="0">All or nothing</option></select></label>
<label class="dly hide" id="floorL">Floor total<input id="floorAmt" inputmode="decimal" placeholder="0.0"></label>
<label class="dly hide" id="dRestL" title="A Dutch lot has TWO clocks. It decays to your floor over the window above, and then - unless it is given an end - it sits there fillable forever, until you spend gas cancelling. This is how long it rests at the floor before the escrow simply comes back to you.">Rest at floor<select id="dRest"><option value="0">Forever</option><option value="86400">1 day</option><option value="604800">1 week</option><option value="2592000">30 days</option></select></label>
<label class="dly hide" id="nftIdL">Token ID<input id="nftId" inputmode="numeric" placeholder="Any from collection" spellcheck="false" autocomplete="off"></label>
<label class="dly hide" id="ethModeL" title="Applies when YOU fill an order from the book, or cancel one of yours and the escrow comes back wrapped. It does NOT change what an order you place pays you: boards are denominated in WETH, and a maker's proceeds are transferred as WETH with no unwrap available - so a limit order asking for ETH is an order asking for WETH, and fills as such. Paying is not a setting either: the page spends whichever of your ETH and WETH covers it, and wraps the difference if only the two together do.">Fills & cancels pay<select id="ethMode"><option value="auto">ETH</option><option value="weth">WETH</option></select></label>
<small id="floorNote" class="hide" role="status" aria-live="polite"></small>
<button id="swap" class="primary">Connect Wallet</button>
<div id="listNote" class="hide" role="status" aria-live="polite"></div>
<div id="scEl" class="sc hide" role="status" aria-live="polite"></div>
<div id="wkWrap" class="hide"><div id="wkPanel" role="dialog" aria-modal="true" aria-labelledby="wkHdr"><div id="wkHdr">Choose a wallet</div><div id="wkList"></div></div></div>
<div id="tkPanel" class="tkp hide"><input id="tkFind" type="text" placeholder="Search name, symbol or address" autocomplete="off" spellcheck="false" aria-label="Filter tokens" role="combobox" aria-expanded="true" aria-controls="tkList" aria-autocomplete="list"><div id="tkList" role="listbox"></div></div>
<div id="stat" role="status" aria-live="polite"></div>
<button id="chTog" class="chtog hide" aria-expanded="false">Chart <span>▾</span></button>
<div id="chBox" class="hide"><div class="chtf" id="chTf"></div><div id="chArt"></div><small id="chNote"></small></div>
<div id="pos"></div>
<button id="bkTog" class="chtog hide" aria-expanded="true">Orderbook <span>▾</span></button>
<div id="book"></div>
<div id="docPanel" class="docs hide">
<details><summary>Swapping</summary>
<p>Every quote is compared across Uniswap v2, v3 and v4, Curve, Sushi, zAMM, Lido, our own Precision pools and the on-chain orderbook, and the best one wins. One route, one signature.</p>
<p>A handful of outside aggregators are asked too — 0x, 1inch, ParaSwap, KyberSwap, Enso, Bebop, OKX. They are compared on what they can be <b>held to</b>, not on what they advertise: a quote less its slippage, against the same number for the chain. One only wins if it beats the chain by a margin wide enough to cover the extra gas, and if none of them answer, nothing changes. Tap the <b>▾</b> beside the rate to see every venue that replied and what each was worth.</p>
<p>An outside route is executed through a small adapter that <b>measures what actually arrives</b> and reverts if it is less than your minimum. The adapter is named in this page's own bytes, so a bad answer from a node cannot swap it for another one.</p>
<p><b>Slippage</b> is the worst price you will accept — move past it and the trade reverts rather than filling badly. <b>Price impact</b> is how far your own size moves the price; the page warns as it grows and will not let a very bad one through quietly.</p>
<p>ETH and WETH are interchangeable here. The page wraps or unwraps whichever way the pool needs, inside the same transaction.</p></details>
<details><summary>Sending</summary>
<p>A plain transfer, or a <b>SLOW</b> one: time-locked, and reversible by you at any point until it matures. Useful for large amounts and for addresses you have not paid before.</p>
<p>A locked transfer does not deliver itself — someone must send the claim once it matures. Tick <b>auto-claim</b> and the page posts a small tip so a keeper does that for the recipient. The tip is refundable: reverse the trans
ance();
stat.textContent="Paid. The relay is settling the recipient's note.";sGot()};
const cpExit=async n=>{
const ch=+pvChain.value,to=pvTo.value.trim()||account,self=pvPath.value==="self",k=cpNoteOf(n);
if(n.ex&&n.ex.job&&n.ex.js!=="failed")throw Error("This note is already exiting.");
if(!isAddr(to))throw Error("The recipient must be a 0x address on "+CHAINS[ch].name+".");
if(!CP_L2[ch]&&ch!==1)throw Error("Unknown chain.");
const c=await cpSync(true),li=c.leaves.indexOf(k.leaf);
if(li<0)throw Error("This deposit has not settled into the pool yet.");
if(c.spent.includes(k.nu))throw Error("This note is already spent.");
if(c.leaves.length<20)pvNote.textContent="The pool holds only "+c.leaves.length+" notes, so an exit today is easy to link. Treat it as a test, not as privacy.";
const impl=ch===1?ZERO:await cpPins(ch);
const fee=self?0n:await cpFee(k.value);
if(!self&&fee>=k.value)throw Error("This note is too small to relay — submit it yourself instead.");
if(!self&&fee*10000n/k.value>300n)throw Error("The relay fee would be "+(Number(fee*10000n/k.value)/100).toFixed(1)+"% of this note — wait for cheaper gas, or submit it yourself.");
const net=k.value-fee,wei=net*CP_SCALE;
const x={ch,to,dl:String((Math.floor(Date.now()/86400000)+3)*86400),nonce:big(sha2(cpU8("zswap-exit-nonce-v1:"+cpFp+":"+n.i))).toString(),
fee:String(fee),wei:String(wei),self,fin:to};
let esc=to.toLowerCase(),warn="";
if(ch!==1){const code=await cfgRead("eth_getCode",[to,L]);
if(strip0x(code||"")!==""){x.fin=cpRescue(n.i).addr;x.rk=1;
if(ch===4663)pvNote.textContent="That recipient has contract code on Ethereum, so Robinhood will refund unused gas to an aliased address rather than to it."}
if(ch===4663){const g=await cpRhGas(to);x.gl=String(g.gl);x.sc=String(g.sc);x.mf=String(g.mf);
const over=g.sc+g.gl*g.mf;if(over>=wei)throw Error("The note would be eaten by Robinhood's gas budget.");x.l2v=String(wei-over)}
const r=cpRecipe(x);esc=cpEscrow(impl,r);
const onchain=retAddr(await cfgRead(C,[{to:CP_ROUTER,data:"0x"+SEL_CPESCROW+pad32("20")+cpEncRecipe(r)},L]));
if(onchain!==esc)throw Error("Escrow address mismatch — refusing to build a proof.");
x.esc=esc;
warn=await cpDry(x,r,esc)}
const{root,path}=cpTree(c.leaves,li);
const dlu=String(Math.ceil((Math.floor(Date.now()/1000)+3600)/600)*600),cb=cpBinding();
const ctx=cpCtx("tacit-unwrap-intent-v1",cb,CP_ETH,pad32(strip0x(esc)),[[k.cx,k.cy,k.owner]],[k.value,fee,BigInt(dlu)]);
const sig=cpSigma(k.value,k.blinding,ctx,cpNonce(k.blinding,ctx,"unwrap"));
const op={chainBinding:cb,spendRoot:root,asset:CP_ETH,cx:k.cx,cy:k.cy,owner:k.owner,leafIndex:li,path,nk:k.secret,secret:k.secret,
value:String(k.value),recipient:esc,fee:String(fee),deadline:dlu,sigR:sig.R,sigZ:sig.z};
n.ex=x;cpSaveNotes();cpPaint();
const r2=await relayFetch("/confidential/submit",{type:"unwrap",op,memos:[],mode:self?"prove":"settle"});
if(!r2.jobId)throw Error("The relay did not take the exit.");
x.job=r2.jobId;x.js=r2.status||"pending";x.rb=cpRelayBase();cpSaveNotes();
stat.textContent=(self?"Proving. The wallet will ask you to send it when the proof is ready.":ch===1?"The relay is settling the withdrawal.":"The relay is settling the exit. Activate it once it lands.")+(warn?" "+warn:"");
if(self)await cpSelfSend(n)};
const cpSelfSend=async n=>{const x=n.ex;
for(let t=0;t<180&&!(x.pv&&x.pr);t++){await new Promise(r=>setTimeout(r,5000));await cpRelayPoll(n);
if(x.js==="failed")throw Error("The prover failed: "+(x.jerr||"unknown"));
if(x.js==="settled")return cpPaint()}
if(!(x.pv&&x.pr))throw Error("The proof is taking a while — it keeps proving; come back and press Send.");
await checkWallet();
const req=x.ch===1?{from:account,to:CP_POOL,value:"0x0",data:cpSettleData(x.pv,x.pr),gas:toHex(700000)}
:{from:account,to:CP_ROUTER,value:"0x0",data:cpExitData(x.pv,x.pr,cpRecipe(x)),gas:toHex(2000000)};
await rpc(C,[req,L]);
x.ptx=await sendTx([req]);cpSaveNotes();cpPaint();
await settle(x.ptx);stat.textContent=x.ch===1?"Withdrawn to "+x.to+".":"Bridged. It lands on "+CHAINS[x.ch].name+" in a few minutes."};
const cpActivate=async n=>{const x=n.ex,r=cpRecipe(x),now=Math.floor(Date.now()/1000);
if(now>Number(x.dl))throw Error("The exit expired — reclaim it instead.");
if(!await cpEnsureBalance(x.esc,BigInt(x.wei)))throw Error("The escrow is not funded yet — the relay has not settled.");
const req={from:account,to:CP_ROUTER,value:"0x0",data:cpActData(r),gas:toHex(CP_L2[x.ch].gas)};
await rpc(C,[req,L]);
x.atx=await sendTx([req]);cpSaveNotes();cpPaint();
await settle(x.atx);stat.textContent="Bridged. It lands on "+CHAINS[x.ch].name+" in a few minutes."};
const cpReclaim=async n=>{const x=n.ex,r=cpRecipe(x);
if(Math.floor(Date.now()/1000)<=Number(x.dl))throw Error("Reclaim opens after "+new Date(Number(x.dl)*1000).toLocaleString()+".");
const req={from:account,to:CP_ROUTER,value:"0x0",data:cpReclData(r),gas:toHex(400000)};
await rpc(C,[req,L]);
x.rtx=await sendTx([req]);cpSaveNotes();cpPaint();
await settle(x.rtx);stat.textContent="Returned to "+x.fin+" on Ethereum."};
const cpRecover=async()=>{const c=await cpSync(true);let found=0;
for(const[id,amt]of c.wraps){const wei=BigInt(amt);if(wei%CP_SCALE)continue;const v=wei/CP_SCALE;
if(cpNotes.some(n=>!n.p&&BigInt(n.v)===v&&cpNoteOf(n).dep===id))continue;
for(let i=0;i<64;i++){const k=cpNoteOf({i,v:String(v)});
if(k.dep===id){if(!cpNotes.some(n=>!n.p&&n.i===i))cpNotes.push({i,v:String(v),at:0,rec:1});found++;break}}}
cpNotes.sort((a,b)=>a.i-b.i);cpSaveNotes();return found};
const cpFmt=v=>trimAmt(BigInt(v)*CP_SCALE,18);
const cpPaint=()=>{if(!pvMode)return;
const on=!!cpSeed;
pvKey.innerHTML=!account?"Connect a wallet to begin.":!on?"Sign once to unlock the key that owns your deposits.":
`Key unlocked <button type="button" class="fclink" data-a="request">request</button><button type="button" class="fclink" data-a="pay">pay</button><button type="button" class="fclink" data-a="backup">backup</button><button type="button" class="fclink" data-a="import">import</button><button type="button" class="fclink" data-a="recover">recover</button><button type="button" class="fclink" data-a="export">export</button><button type="button" class="fclink" data-a="relay">relay</button>`;
pvGo.textContent=!account?"Connect Wallet":!on?"Unlock key":"Deposit";
pvGo.disabled=!!cpBusy;
if(!on){pvList.innerHTML="";return}
const rows=cpNotes.map((n,j)=>{const{s,k}=cpStatus(n),x=n.ex,ch=x?CHAINS[x.ch].name:"",B=(a,l)=>`<button type="button" class="fclink" data-a="${a}" data-j="${j}">${l}</button>`;
const paid=n.inv&&!!cpPool&&cpPool.wraps.some(w=>w[0]===k.dep);
const act=s==="ready"?B("exit","exit")
:s==="pending"?(n.job?(n.js==="failed"?"relay failed"+B("settle","retry"):"settling…"):n.inv&&!paid?"awaiting payment":B("settle","settle"))
:s==="exiting"?(!x.job||x.js==="failed"?"relay failed"+B("exit","retry")
:x.self?(x.ptx?"sent…":x.pv?B("send","send"):"proving…"):"relaying…")
:s==="funded"?B("activate","activate")
:s==="expired"?B("reclaim","reclaim")
:s==="bridged"||s==="withdrawn"?`<a href="${CHAINS[x.ch].explorer}/address/${x.to}" target="_blank" rel="noreferrer">on ${ch}</a>`
:s==="paying"?(n.job?"settling…":B("settle","settle")):s==="payfailed"?"relay failed"+B("settle","retry")
:s==="paid"?`<a href="${CHAINS[1].explorer}/tx/${n.tx}" target="_blank" rel="noreferrer">paid</a>`:s;
return`<div class="cpr"><span><b>${cpFmt(n.v)} ETH</b> ${n.p?"paid":n.inv?"request #"+n.i:"#"+n.i}${x&&s!=="bridged"&&s!=="withdrawn"?" → "+ch:""}</span><span>${act}</span></div>`}).join("");
pvList.innerHTML=rows||`<div class="cpr"><span>No deposits yet.</span></div>`};
const pvPaint=()=>{const on=pvMode;
pv.setAttribute("aria-pressed",on?"true":"false");
pv.classList.toggle("on",on);
for(const el of [rate,flip,swap])if(el)el.classList.toggle("hide",on);
for(const el of [document.querySelector(".panel"),rcvPanel])if(el)el.classList.toggle("hide",on);
for(const el of [pvPanel,pvGo])el.classList.toggle("hide",!on);
for(const el of [rc,rcvEl])if(el)el.classList.toggle("hide",on);document.querySelector(".card").classList.toggle("pvon",on)};
function pvSet(on){
if(on)cbDisarm();
++seq;quoting=false;
pvMode=on;
if(on&&lqMode)lqSet(false);
if(on&&lnMode)lnSet(false);
if(on&&wnMode)wnSet(false);
pvPaint();
stat.textContent="";pvNote.textContent="";
clearInterval(pvTimer);
if(on){pvTimer=setInterval(()=>{if(document.hidden||!pvMode)return;pvRefresh()},8000);pvWake()}
fcSync();}
const pvWake=()=>{if(!account){cpSeed="";cpNotes=[]}else if(!cpSeed){const s=cpKeyFor(account);if(/^0x[0-9a-f]{64}$/i.test(s||""))cpUse(s)}
if(account)pvTo.placeholder=account;cpPaint();pvRefresh()};
const pvRefresh=async()=>{if(!cpSeed||cpBusy)return;const my=++pvTick;
try{await cpSync(cpNotes.some(n=>/pending|exiting/.test(cpStatus(n).s)));
for(const n of cpNotes){const t=n.ex||n;if(t.job&&!["settled","failed","proven"].includes(t.js||""))await cpRelayPoll(n);
if(n.inv&&!n.job&&!n.p&&cpPool&&cpPool.wraps.some(w=>w[0]===cpNoteOf(n).dep))try{await cpSettleWrap(n)}catch{}}
if(my===pvTick&&pvMode)cpPaint()}catch(e){try{if(my===pvTick&&pvMode)pvNote.textContent=explain(e)}catch{}}};
const pvRun=async(fn,label)=>{if(cpBusy)return;cpBusy=1;pvGo.disabled=true;stat.textContent=label||"";
try{await checkWallet();await fn();cpSyncAt=0}catch(e){err(e)}
cpBusy=0;cpSaveNotes();cpPaint();pvRefresh()};
pvGo.onclick=async()=>{
if(pvGo.disabled)return;
if(!account)return void connect();
if(!cpSeed)return pvRun(cpUnlock,"Check your wallet for the message to sign.");
pvRun(cpDeposit,"Building the deposit…")};
pvList.onclick=pvKey.onclick=e=>{const b=e.target.closest("button[data-a]");if(!b)return;sBlip();
const a=b.dataset.a,n=b.dataset.j!==undefined?cpNotes[+b.dataset.j]:null;
if(a==="backup"){prompt("Your private-bridge key. Anyone holding it can spend your deposits.",cpSeed);return}
if(a==="import"){const s=(prompt("Paste a private-bridge key (0x + 64 hex), or an exported note list.")||"").trim();
if(/^\[/.test(s)){let arr;try{arr=JSON.parse(s)}catch{}
if(!Array.isArray(arr))return void(stat.textContent="That is not a note list.");
let add=0;for(const r of arr)if(r&&Number.isInteger(r.i)&&/^\d+$/.test(r.v||"")&&!cpNotes.some(n=>n.i===r.i)){cpNotes.push(r);add++}
cpNotes.sort((a,b)=>a.i-b.i);cpSaveNotes();cpPaint();pvRefresh();stat.textContent=add?"Imported "+add+" note"+(add>1?"s":"")+".":"Nothing new in that list.";return}
if(!/^0x[0-9a-fA-F]{64}$/.test(s))return void(stat.textContent="That is not a key.");
try{LS[CP_KEY+account.toLowerCase()]=s.toLowerCase()}catch{}cpUse(s.toLowerCase());cpPaint();pvRefresh();return}
if(a==="export"){prompt("Your note records — keep them with the key. Exit recipes cannot be rebuilt without them.",JSON.stringify(cpNotes));return}
if(a==="relay"){const u=(prompt("Relay to prove and settle through. Leave empty for the default ("+CP_RELAY+").",cpRelayBase())||"").trim();
try{if(!u||u===CP_RELAY)delete LS["zswap:cprelay"];else if(/^https:\/\/[\w.-]+(:\d+)?(\/[\w./-]*)?$/.test(u))LS["zswap:cprelay"]=u.replace(/\/$/,"");else return void(stat.textContent="A relay is an https URL.")}catch{}
stat.textContent="Relay: "+cpRelayBase();return}
if(a==="request")return pvRun(cpRequest,"Building the request…");
if(a==="pay")return pvRun(cpPay,"Checking the request…");
if(a==="recover")return pvRun(async()=>{const f=await cpRecover();stat.textContent=f?"Recovered "+f+" deposit"+(f>1?"s":"")+".":"Nothing new on chain for this key."},"Scanning the pool…");
if(!n)return;
if(a==="settle")return pvRun(()=>cpSettleWrap(n),"Asking the relay…");
if(a==="exit")return pvRun(()=>cpExit(n),"Building the exit…");
if(a==="send")return pvRun(()=>cpSelfSend(n),"Sending the exit…");
if(a==="activate")return pvRun(()=>cpActivate(n),"Activating…");
if(a==="reclaim")return pvRun(()=>cpReclaim(n),"Reclaiming…")};
pv.onclick=()=>{sBlip();pvSet(!pvMode)};
let invOff=null;
const invIcons=()=>{const o=[],seen=new Set();
for(const t of TOKENS){const k=(t.sym||"").toLowerCase();
if(!k||seen.has(k)||t.addr===ZERO)continue;seen.add(k);o.push({s:t.sym,h:t.icon,l:!!t.launched})}
return o};
const invWei=n=>`<svg width="${n}" height="${n}" viewBox="0 0 16 16" fill="currentColor" fill-rule="evenodd" shape-rendering="crispEdges"><path d="M3 1h1v1H3zM5 2h1v1H5zM10 2h1v1h-1zM12 1h1v1h-1zM4 4h8v1H4zM3 5h10v1H3zM2 6h12v3H2zM3 9h10v1H3zM4 10h8v1H4zM5 11h2v1H5zM9 11h2v1H9zM2 12h3v1H2zM7 12h2v1H7zM11 12h3v1h-3zM1 13h2v1H1zM13 13h2v1h-2zM3 6h3v2H3zM10 6h3v2h-3z"/></svg>`;
function invPlay(){
if(invOff)return;
const host=document.querySelector(".card");if(!host)return;
const W=host.clientWidth,H=host.clientHeight,COLS=6,ROWS=4,IW=22,IH=20,SW=22,SH=20;
const mk=(c,h)=>{const d=document.createElement("div");d.className=c;if(h)d.innerHTML=h;return d};
const w=mk("inv");w.tabIndex=0;w.setAttribute("role","application");
w.setAttribute("aria-label","Token invaders. Arrow keys move, space fires, escape leaves.");
host.appendChild(w);
const hud=mk("invh");w.appendChild(hud);
hud.addEventListener("pointerdown",e=>{const k=e.target.className;
if(k==="invq"){e.stopPropagation();e.preventDefault();sBlip();quit()}
else if(k==="invm"){e.stopPropagation();e.preventDefault();mint()}
else if(k==="invsh"){e.stopPropagation();e.preventDefault();sBlip();shareMsg="";shareScore()}});
const ship=mk("invs",ETH_ICON);w.appendChild(ship);
const pool=invIcons();
let al=[],bu=[],bo=[],sx=(W-SW)/2,lf=0,rt=0,sc=0,lv=3,wv=1,ov=0,dir=1,sa=0,fa=-1e9,rq=0,lt=performance.now();
let tEnd=0,minted="",minting=0,mintMsg="",shareMsg="",runId=0;
const pos=(n,x,y)=>{n.style.transform=`translate(${x.toFixed(1)}px,${y.toFixed(1)}px)`};
const cur=pool.filter(t=>!t.l),lau=pool.filter(t=>t.l);
const face=(r,c,i)=>{const back=r<ROWS-1;
const src=back?(cur.length?cur:pool):(lau.length?lau:(cur.length?cur:pool));
const t=src.length?src[(back?i:c)%src.length]:null;
return(!t||back&&i%3===0&&src.length<COLS*(ROWS-1))?{s:"WEI",h:invWei(20)}:t};
const WAVE_MS=45000;
const left=()=>Math.max(0,Math.ceil((tEnd-performance.now())/1000));
const born=()=>{for(const a of al)a.node.remove();al=[];
tEnd=performance.now()+WAVE_MS;
const px=(W-COLS*IW)/2;
for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++){const t=face(r,c,r*COLS+c),n=mk("invx",t.h);
n.title=t.s;w.appendChild(n);al.push({node:n,s:t.s,x:px+c*IW,y:18+r*IH,c})}
dir=1};
let hudWas="";
const hud2=()=>{const t=left();
const html=(ov?`<span>game over \u00b7 ${sc}</span><span class="${minted||minting?"invmsg":"invm"}">`
+(minted?`${minted}`:minting?"minting\u2026":mintMsg?`${mintMsg}`:sc>0?"mint this score":"tap to play again")+`</span>`+(sc>0&&!minting?`<span class="invsh">${shareMsg||"share"}</span>`:"")
:`<span>${"\u25b2".repeat(Math.max(0,lv))}</span>`
+`<span${t<=10?' style="color:var(--w)"':""}>${t}s</span>`
+`<span>wave ${wv} \u00b7 ${sc}</span>`)
+`<span class="invq" title="Back to swapping (esc)">\u2715</span>`;
if(html===hudWas)return;
hudWas=html;hud.innerHTML=html};
const fire=()=>{const t=performance.now();
if(ov||bu.length||t-fa<120)return;fa=t;sPew();const n=mk("invb");w.appendChild(n);
bu.push({node:n,x:sx+SW/2-1,y:H-SH-12})};
const bomb=()=>{if(!al.length)return;const m=new Map();
for(const a of al){const p=m.get(a.c);if(!p||a.y>p.y)m.set(a.c,a)}
const f=[...m.values()],a=f[(Math.random()*f.length)|0],n=mk("invd");
w.appendChild(n);bo.push({node:n,x:a.x+IW/2,y:a.y+IH})};
const ht=(ax,ay,aw,ah,bx,by,bw,bh)=>ax<bx+bw&&ax+aw>bx&&ay<by+bh&&ay+ah>by;
const end=()=>{ov=1;sFail();hud2()};
const step=(dt,t)=>{
if(lf)sx-=190*dt;if(rt)sx+=190*dt;
sx=Math.max(0,Math.min(W-SW,sx));pos(ship,sx,H-SH-6);
const per=Math.max(90,620-(COLS*ROWS-al.length)*22-(wv-1)*60);
if(t-sa>per){sa=t;let e=0;
for(const a of al){const nx=a.x+dir*6;if(nx<0||nx+IW>W)e=1}
for(const a of al){if(e)a.y+=8;else a.x+=dir*6;pos(a.node,a.x,a.y)}
if(e)dir*=-1;
if(Math.random()<.5+wv*.08)bomb();
if(al.some(a=>a.y+IH>=H-SH-6))return end()}
if(tEnd&&performance.now()>tEnd){tEnd=performance.now()+WAVE_MS;lv--;sFail();
ship.classList.add("pop");setTimeout(()=>ship.classList.remove("pop"),200);
if(lv<=0)return end()}
for(const b of bu){b.y-=260*dt;pos(b.node,b.x,b.y)}
for(const b of bo){b.y+=110*dt;pos(b.node,b.x,b.y)}
for(let i=bu.length-1;i>=0;i--){const b=bu[i];
if(b.y+7<0){b.node.remove();bu.splice(i,1);continue}
const j=al.findIndex(a=>ht(a.x,a.y,IW-2,IH-2,b.x,b.y,2,7));
if(j>=0){const a=al[j],g=a.node;g.classList.add("pop");setTimeout(()=>g.remove(),120);
al.splice(j,1);sc+=a.s==="WEI"?25:10;b.node.remove();bu.splice(i,1);
if(al.length)sHit();else{sc+=100+left()*10;wv++;born();sGot()}}}
for(let i=bo.length-1;i>=0;i--){const b=bo[i];
if(b.y>H){b.node.remove();bo.splice(i,1);continue}
if(ht(sx,H-SH-6,SW,SH,b.x,b.y,2,6)){b.node.remove();bo.splice(i,1);lv--;
ship.classList.add("pop");setTimeout(()=>ship.classList.remove("pop"),200);
if(lv<=0)return end();sFail()}}
hud2()};
const loop=()=>{const t=performance.now(),dt=Math.min(.05,(t-lt)/1e3);lt=t;
if(!ov)step(dt,t);rq=requestAnimationFrame(loop)};
function quit(){if(rq)cancelAnimationFrame(rq);rq=0;w.remove();
document.removeEventListener("keydown",kd,true);document.removeEventListener("keyup",ku,true);
invOff=null}
const again=()=>{runId++;ov=0;sc=0;lv=3;wv=1;minted="";mintMsg="";shareMsg="";born();hud2()};
const shareScore=async()=>{
const what=(minted?`I scored ${sc} in zSwap invaders and claimed ${minted}`
:`I scored ${sc} in zSwap invaders`)+" \u{1F47E}";
const u="https://"+SHARE_HOST+(minted?"/#score="+minted:"");
const href="https://x.com/intent/post?text="+encodeURIComponent(what)+"&url="+encodeURIComponent(u);
let win=null;try{win=window.open(href,"_blank")}catch{}
if(win){try{win.opener=null}catch{}return}
shareMsg="copied";
try{await navigator.clipboard.writeText(what+" "+u)}catch{shareMsg="could not open x.com"}
hud2()};
const mint=async()=>{
if(!ov||minting||minted||!sc)return;
mintMsg="";
if(!account){mintMsg="connect to mint\u2026";hud2();
try{await connect()}catch{}
if(!account){mintMsg="wallet not connected";hud2();return}
mintMsg=""}
minting=1;hud2();
const r=runId;
try{await checkWallet();
const lbl=sc+"-w"+wv+"-"+Math.random().toString(36).slice(2).padEnd(4,"0").slice(0,4);
const lt=strTail(lbl),st=strTail(String(sc));
const req={from:account,to:SCOREM,value:"0x0",
data:"0x"+SEL_SCLAIM+pad32("60")+encUint(96+lt.length/2)+encAddr(account)+lt+st};
const ret=await rpc(C,[req,L]);
if(strip0x(ret||"").length!==64)throw Error("The minter did not answer \u2014 check the wallet is on Ethereum mainnet.");
await settle(await sendTx([req]));
if(r===runId){minted=lbl+".arcade.wei";sGot()}}catch(e){if(r===runId){try{console.error("zSwap mint failed:",e)}catch{}
mintMsg=String(isRejection(e)?"mint cancelled":explain(e)).replace(/[<>&"'`]/g,"").slice(0,60)||"the mint failed";sFail()}}
minting=0;hud2()};
function kd(e){if(e.key==="Escape"){e.preventDefault();quit();return}
if(e.key==="ArrowLeft"){lf=1;e.preventDefault()}
else if(e.key==="ArrowRight"){rt=1;e.preventDefault()}
else if(e.key===" "||e.key==="Enter"){e.preventDefault();ov?again():fire()}}
function ku(e){if(e.key==="ArrowLeft")lf=0;if(e.key==="ArrowRight")rt=0}
document.addEventListener("keydown",kd,true);document.addEventListener("keyup",ku,true);
let tp=0;
w.addEventListener("pointerdown",e=>{tp=1;sx=Math.max(0,Math.min(W-SW,e.offsetX-SW/2));ov?again():fire()});
w.addEventListener("pointermove",e=>{if(tp)sx=Math.max(0,Math.min(W-SW,e.offsetX-SW/2))});
w.addEventListener("pointerup",()=>{tp=0});
invOff=quit;born();hud2();w.focus();sOpen();rq=requestAnimationFrame(loop);
}
(()=>{let tm=0,hd=0;
const cn=()=>{if(tm)clearTimeout(tm);tm=0};
wn.addEventListener("pointerdown",()=>{hd=0;cn();tm=setTimeout(()=>{tm=0;hd=1;invPlay()},550)});
for(const ev of ["pointerup","pointerleave","pointercancel"])wn.addEventListener(ev,cn);
wn.addEventListener("click",e=>{if(hd){e.preventDefault();e.stopPropagation();hd=0}},true);
wn.addEventListener("contextmenu",e=>e.preventDefault());
})();
wnName.oninput=()=>{wnName.value=wnName.value.toLowerCase();wnQuote()};
wnTld.onchange=wnQuote;
wnEnter.onclick=async()=>{
if(!account)return void connect();
if(!wnEnter.dataset.id){wnRollEl.textContent="Type a paid .wei name you own in the box above, and it can enter the roll.";return}
wnEnter.disabled=true;
try{
await checkWallet();
const req={from:account,to:WROLL,value:"0x0",data:"0x"+SEL_RENTER+pad32(wnEnter.dataset.id)+encUint(0)};
await rpc(C,[req,L]);
await settle(await sendTx([req]));
await wnRoll();
}catch(e){err(e)}
wnEnter.disabled=false};
wnGo.onclick=async()=>{
if(wnGo.disabled)return;
if(!account)return void connect();
wnGo.disabled=true;
try{
await checkWallet();
const p=wnPend();
if(p){
if(!p.at){
if(Math.floor(Date.now()/1000)-(p.since||0)<=600)throw Error("The commitment has not confirmed yet.");
wnSave(null);stat.textContent="That commitment never reached the chain. Commit again.";
await wnQuote();return}
const age=Math.floor(Date.now()/1000)-p.at;
if(age<WN_MIN)throw Error(`Wait ${WN_MIN-age} more seconds.`);
if(age>=WN_MAX){wnSave(null);stat.textContent="That commitment lapsed. Commit again.";wnQuote();return}
const cost=await wnCost(p.label);
const req={from:account,to:WNS,value:toHex(cost),
data:"0x"+SEL_WREVEAL+pad32("40")+pad32(p.secret)+strTail(p.label)};
await rpc(C,[req,L]);
await settle(await sendTx([req]));
wnSave(null);
stat.textContent=`${p.label}.wei is yours.`;
wnName.value="";
}else if(wnQ&&wnQ.free){
const req={from:account,to:WREG,value:"0x0",
data:"0x"+SEL_WSUB+pad32(IDWEI)+pad32("40")+strTail(wnQ.label)};
await rpc(C,[req,L]);
await settle(await sendTx([req]));
stat.textContent=`${wnQ.label}.id.wei is yours.`;
wnName.value="";
}else if(wnQ){
const sec=wnSecret();
const req={from:account,to:WNS,value:"0x0",
data:"0x"+SEL_WCOMMIT+pad32(wnHash(wnQ.label,account,sec))};
await rpc(C,[req,L]);
wnSave({label:wnQ.label,secret:sec,at:0,since:Math.floor(Date.now()/1000)});
let h;
try{h=await sendTx([req])}
catch(e){
if(e&&(e.code===4001||/user (rejected|denied)|rejected the request|denied transaction/i.test(e.message||"")))wnSave(null);
throw e}
wnSave({label:wnQ.label,secret:sec,at:0,since:Math.floor(Date.now()/1000),tx:h});
try{await settle(h)}catch(e){if(/tx reverted/.test(e&&e.message||""))wnSave(null);throw e}
await wnStamp();
stat.textContent="Committed. Register the name in a minute.";
}
await wnQuote();await wnRoll();refreshBalance();
}catch(e){err(e)}
wnSync()};
lnGo.onclick=async()=>{
stat.textContent="";
if(lnGo.disabled)return;
if(!account)return void connect();
lnGo.disabled=true;
try{
await checkWallet();
if(lnKind.value==="cause")return await cauGo();
const name=lnName.value.trim(),sym=lnSym.value.trim().toUpperCase();
if(!name||!sym)throw Error("A name and a symbol, please.");
const utf8=v=>new TextEncoder().encode(v).length;
if(utf8(name)>64)throw Error(`That name is ${utf8(name)} bytes and the limit is 64.`);
if(utf8(sym)>16)throw Error(`That symbol is ${utf8(sym)} bytes and the limit is 16.`);
const supRaw=lnSupply.value.replace(/[, ]/g,"");
if(!/^\d+(\.\d+)?$/.test(supRaw)||!(Number(supRaw)>0))throw Error("Supply has to be a positive number.");
const supply=parseUnits(supRaw,18);
const typed=Number(lnAlloc.value||0);
if(!(typed>=0)||typed*100>LN_MAX_ALLOC)throw Error("Allocation runs from 0 to 20%.");
const alloc=Math.round(typed*100);
if(!(alloc>=0)||alloc>LN_MAX_ALLOC)throw Error("Allocation runs from 0 to 20%.");
if(typed>0&&alloc===0)throw Error("Allocation is priced in whole basis points — the smallest non-zero value is 0.01%.");
const mcap=parseUnits(String(lnMcap.value||"").trim(),18);
if(mcap<LN_MIN_MCAP)throw Error("That opening valuation is too small to price.");
const pooled=supply-supply*BigInt(alloc)/10000n;
if(pooled<LN_MIN_POOLED)throw Error("Too little supply reaches the pool to price a market.");
if(pooled>mcap*10n**36n)throw Error("That supply is too large for that opening valuation \u2014 raise the valuation.");
if(pooled*10n**24n<mcap)throw Error("That opening valuation is too large for that supply \u2014 lower it, or mint more.");
const owner=await lnOwner();
stat.textContent="Launching\u2026";
const req={from:account,to:PLAUNCH,value:"0x0",
data:encLaunch(name,sym,"",supply,BigInt(alloc),mcap,own
async function cbAct(){
if(!cbDao||cbAmt<=0n)return;
const b=document.getElementById("cbGo");if(!b||b.disabled)return;
const label=b.textContent;
try{
if(!account)return connect();
b.disabled=true;b.textContent=cbBuy?"Backing…":"Burning…";
const req=cbBuy
?{from:account,to:COFF,value:"0x"+cbCost.toString(16),
data:"0x"+SEL_BUY+encAddr(cbDao)+encUint(cbAmt)}
:{from:account,to:cbDao,value:"0x0",
data:"0x"+SEL_RAGEQUIT+encUint(96)+encUint(0)+encUint(cbAmt)+encUint(1)+encAddr(ZERO)};
if(!cbBuy&&cbShares&&cbTok){
let now2=null;
try{
const [b2,r2]=await Promise.all([
rpc("eth_getBalance",[cbDao,L]),
mc3Deep([{to:cbShares,data:"0x"+SEL_TSUP},{to:cbTok,data:"0x"+SEL_TSUP}])]);
const e2=BigInt(b2||"0x0"),s2=BigInt(r2[0]||"0x0")+BigInt(r2[1]||"0x0");
if(s2>0n)now2=e2*cbAmt/s2;
}catch{}
if(now2!==null&&now2<cbDue){
const lost=cbDue-now2;
if(now2<=0n)throw Error("The treasury has been drawn down since this was quoted \u2014 burning now would pay nothing.");
if(lost*100n>cbDue)throw Error(`This burn now pays ${trimAmt(now2,18)} ETH, not ${trimAmt(cbDue,18)} `
+`\u2014 the treasury was drawn down since it was quoted. Check the line again before burning.`);
}
}
await rpc(C,[req,L]);
await waitTx(await sendTx([req]));sGot();
if(cbBuy&&cbTok){
const kt=TOKENS.find(x=>x.addr&&x.addr.toLowerCase()===cbTok.toLowerCase());
if(kt){kt.cause=1;if(kt.cust)keepToken(kt)}
}
stat.textContent=cbBuy?"Backed · burn it back any time for what is left."
:"Burned · the ether is in your wallet.";
refreshBalance();cbSync();
}catch(e){err(e);b.disabled=false;b.textContent=label}
}
const LN_MIME={"image/png":0,"image/webp":1,"image/svg+xml":2,"image/gif":3,"image/jpeg":4};
const MIME_NAME=["image/png","image/webp","image/svg+xml","image/gif","image/jpeg","image/avif"];
const LN_MAX_ART=24575;
const LN_BUDGET=8192;
const lnCause=()=>lnKind.value==="cause";
const lnCap=()=>lnCause()?CAU_MAX_ART:LN_MAX_ART;
const lnBud=()=>lnCause()?CAU_MAX_ART:LN_BUDGET;
const LN_MAX_ALLOC=2000;
const LN_MIN_POOLED=2000000000000n;
const LN_MIN_MCAP=1000000000000n;
const lnHex=b=>{let h="";for(const x of b)h+=x.toString(16).padStart(2,"0");return h};
const lnTail=b=>{const h=lnHex(b);return encUint(b.length)+h.padEnd(Math.ceil(h.length/64)*64,"0")};
const encLaunch=(n,y,u,sup,alloc,mcap,own,art,mime)=>{
const T=new TextEncoder();
const dyn=[T.encode(n),T.encode(y),T.encode(u)];
const statics=[encUint(sup),encUint(alloc),encUint(mcap),encAddr(own)];
if(art){dyn.push(art);statics.push(encUint(mime))}
const words=statics.length+(art?4:3);
let off=words*32,head="",tail="";
const heads=[];
for(const b of dyn){heads.push(encUint(off));off+=lnTail(b).length/2;tail+=lnTail(b)}
head=heads.slice(0,3).join("")+statics.slice(0,4).join("")
+(art?heads[3]+statics[4]:"");
return "0x"+(art?SEL_LAUNCHART:SEL_LAUNCH)+head+tail;
};
async function lnOwner(){
const rv=rc.value.trim();
if(!rv)return account;
if(/^0x[0-9a-fA-F]{40}$/.test(rv)){
if(/^0x0{40}$/i.test(rv))throw Error("That cannot be the zero address.");
return rv;
}
if(!/\.(g?wei|eth)$/i.test(rv))throw Error("That must be an address, or a .eth / .wei name.");
rcvEl.textContent="resolving\u2026";
let r="";try{r=await nameFwd(rv.toLowerCase())}catch{r=""}
if(!r||/^0x0{40}$/i.test(r)){rcvEl.textContent="";throw Error("That name is not registered.")}
if(!isAddr(r))throw Error("That name resolved to something that is not an address.");
rcvEl.textContent=r;
return r;
}
const CSUM="0x00000000004473e1f31C8266612e7FD5504e6f2a";
const CMSUM="0x0000000000330B8df9E3bc5E553074DA58eE9138";
const CIMPL="643a45b599d81be3f3a68f37eb3de55ff10673c1";
const CRND="0x000000000011C799980827F52d3137b4abD6E654";
const COFF="0x000000A4Ad929C9E108aD2B1D2fBeDe0C2Ae57e1";
const CTAP="0x0000000060cdD33cbE020fAE696E70E7507bF56D";
const CLOOT="0x00000000000000000000000000000000000003ef";
const SEL_DAICO="4e1e3b11",SEL_SETALLOW="da46098c",SEL_CONFIGURE="fca53be5";
const CUNITS=10000000n,CONE=10n**18n,CMAX=(1n<<256n)-1n;
const CAU_MAX_ART=4096;
const CAU_MAX_DAYS=3650;
const cPad=h=>h.padEnd(Math.ceil(h.length/64)*64,"0");
const cTail=h=>{h=strip0x(h);return encUint(h.length/2)+cPad(h)};
const cCall=(to,data)=>encAddr(to)+encUint(0)+encUint(96)+cTail(data);
const cCalls=cs=>{let off=cs.length*32,head="",tail="";
for(const c of cs){head+=encUint(off);off+=c.length/2;tail+=c}
return encUint(cs.length)+head+tail};
const CLIMPL="6f1f2af76a3add953277e9f369242697c87bc6a5";
const cProxy=impl=>strip0x(keccak(hexToBytes(
"602d5f8160095f39f35f5f365f5f37365f73"+impl+"5af43d5f5f3e6029573d5ffd5b3d5ff3")));
const cLoot=dao=>{const d=strip0x(dao).toLowerCase();
return "0x"+keccak(hexToBytes("ff"+d+d+"0".repeat(24)+cProxy(CLIMPL))).slice(26)};
const cPredict=(owner,salt)=>{
const inner=encUint(96)+encUint(160)+strip0x(salt)
+encUint(1)+encAddr(owner)+encUint(1)+encUint(CONE);
const ss=strip0x(keccak(hexToBytes(inner)));
const ih=cProxy(CIMPL);
return "0x"+keccak(hexToBytes("ff"+strip0x(CMSUM)+ss+ih)).slice(26);
};
const encCause=(name,sym,uri,salt,owner,ben,dao,goal,rate,price,deadline,cap)=>{
const T=new TextEncoder(),Z=encUint(0);
const dyn=[lnTail(T.encode(name)),lnTail(T.encode(sym)),lnTail(T.encode(uri)),
encUint(1)+encAddr(owner),encUint(1)+encUint(CONE),encUint(0),
cCalls([
cCall(dao,SEL_SETALLOW+encAddr(COFF)+encAddr(CLOOT)+encUint(CMAX)),
cCall(COFF,SEL_CONFIGURE+encAddr(CLOOT)+encAddr(ZERO)+encUint(price)
+encUint(deadline)+encUint(cap))])];
const cfg=encUint(CONE)+encUint(604800)+encUint(259200)+encUint(CONE)+Z.repeat(17);
const tap=encAddr(CTAP)+Z+encUint(goal)+encAddr(ben)+encUint(rate);
let off=52*32,heads=[],tail="";
for(const d of dyn){heads.push(encUint(off));off+=d.length/2;tail+=d}
return "0x"+SEL_DAICO
+heads[0]+heads[1]+heads[2]
+encUint(1000)+encUint(1)+encAddr(CRND)+strip0x(salt)
+heads[3]+heads[4]+heads[5]
+cfg+Z.repeat(7)+tap+Z.repeat(8)+heads[6]+tail;
};
const cauURI=(name,sym)=>{
let img="";
if(lnArtBytes){let s="";for(const x of lnArtBytes)s+=String.fromCharCode(x);
img="data:"+MIME_NAME[lnArtMime]+";base64,"+btoa(s)}
return "data:application/json,"+encodeURIComponent(
JSON.stringify({name,symbol:sym,image:img,launchType:"cause"}));
};
function cauTerms(){
const g=String(lnGoal.value||"").replace(/[, ]/g,"").trim();
const d=String(lnDays.value||"").replace(/[, ]/g,"").trim();
if(!/^\d+(\.\d+)?$/.test(g)||!/^\d+(\.\d+)?$/.test(d))return null;
let goal;try{goal=parseUnits(g,18)}catch{return null}
const secs=BigInt(Math.round(Number(d)*86400));
if(goal<=0n||secs<3600n||Number(d)>CAU_MAX_DAYS)return null;
const price=goal/CUNITS;
if(price<=0n)return null;
const vest=BigInt(lnVest.value);
if(vest<=secs)return{err:`Backing runs for ${trimNum(Number(secs)/86400)} days but the ether is `
+`fully released in ${trimNum(Number(vest)/86400)} days — the treasury would empty before backing closes. `
+`Shorten the raise, or release over longer.`};
const rate=goal/vest||1n;
return{goal,secs,price,rate,vest,cap:(CUNITS-1n)*CONE};
}
const lnKindQuote=()=>lnKind.value==="cause"?cauQuote():lnQuote();
function cauQuote(){
lnGo.textContent=account?"Launch":"Connect Wallet";
const t=cauTerms();
if(!t){lnNote.textContent="";return}
if(t.err){lnNote.textContent=t.err;lnNote.style.color="var(--w)";return}
const per=(+formatUnits(t.price,18));
const tenthDays=t.rate>0n
?Number((t.goal/10n)/t.rate)/86400
:0;
lnNote.textContent=`1 ETH backs ${trimNum(1/per)} units`
+` · ${trimNum(+formatUnits(t.rate*86400n,18))} ETH a day at full funding, over `
+lnVest.options[lnVest.selectedIndex].textContent
+(tenthDays>0?` · at a tenth of the goal it empties in ${trimNum(Math.round(tenthDays*10)/10)} days`:"")
+` · backers burn back for the rest`
+(lnArtBytes?` · logo ${(lnArtBytes.length/1024).toFixed(1)} KB`
+(lnArtBytes.length>CAU_MAX_ART?` — over the 4 KB a cause allows, pick something simpler`:""):"");
lnNote.style.color=lnArtBytes&&lnArtBytes.length>CAU_MAX_ART?"var(--w)":"";
}
function cauKind(){
const on=lnKind.value==="cause";
for(const el of [lnMktL,lnSupplyL,lnMcapL,lnAllocL])el.classList.toggle("hide",on);
for(const el of [lnGoalL,lnDaysL,lnVestL])el.classList.toggle("hide",!on);
lnTitle.textContent=on?"Launch a cause":"Launch a coin";
lnSub.textContent=on?"backers burn back for whatever you have not drawn"
:"0.4% of each trade goes to the creator";
lnName.placeholder=on?"Feed Ducks":"Zero Cat";
lnSym.placeholder=on?"DUCK":"ZCAT";
rc.placeholder=on?"Beneficiary — defaults to you":"Creator — defaults to you";
rc.title=on?"Who the ether streams to. Leave blank and it is you. Worth setting to a multisig if the cause has one."
:"Who the token belongs to: receives the creator allocation, collects the creator share of trading fees for the life of the token, and may edit its name, symbol and logo. Leave blank and it is you. Worth setting to a multisig if the project has one.";
stat.textContent="";
on?cauQuote():lnQuote();
}
async function cauGo(){
const name=lnName.value.trim(),sym=lnSym.value.trim().toUpperCase();
if(!name||!sym)throw Error("A name and a symbol, please.");
const utf8=v=>new TextEncoder().encode(v).length;
if(utf8(name)>64)throw Error(`That name is ${utf8(name)} bytes and the limit is 64.`);
if(utf8(sym)>16)throw Error(`That symbol is ${utf8(sym)} bytes and the limit is 16.`);
if(lnArtBytes&&lnArtBytes.length>CAU_MAX_ART)
throw Error(`A cause's logo is stored in the DAO itself, so it is capped at 4 KB. `
+`That one is ${(lnArtBytes.length/1024).toFixed(1)} KB — pick something simpler.`);
const t=cauTerms();
if(!t)throw Error("A goal in ether, and how long it runs for — at least an hour, at most ten years.");
if(t.err)throw Error(t.err);
const ben=await lnOwner();
const owner=account;
const salt=wnSecret();
const dao=cPredict(owner,salt);
const deadline=BigInt(Math.floor(Date.now()/1000))+t.secs;
stat.textContent="Launching…";
const req={from:account,to:CSUM,
value:"0x"+t.price.toString(16),
data:encCause(name,sym,cauURI(name,sym),salt,owner,ben,dao,t.goal,t.rate,t.price,deadline,t.cap)};
await rpc(C,[req,L]);
await waitTx(await sendTx([req]));sGot();
lnArtBytes=null;lnArtNote.textContent="none";lnArtNote.classList.remove("set");
lnPrev.classList.remove("set");lnArt.value="";
let loot=cLoot(dao);
try{
const said=cbWord(await rpc(C,[{to:dao,data:"0x"+SEL_LOOTOF},L]));
if(isAddr(said||"")&&said.toLowerCase()!==loot.toLowerCase())loot=said;
}catch{}
try{const ix=await addCustomToken(loot);TOKENS[ix].cause=1;rebuild()}catch{}
stat.innerHTML=`${safeSym(sym)} is raising at `
+`<a href="${chainCfg().explorer}/token/${loot}" target="_blank" rel="noreferrer">${short(loot)}</a>`
+` · ${trimNum(+formatUnits(t.goal,18))} ETH over ${trimNum(Number(t.secs)/86400)} days`
+` · backers can burn back for whatever you have not drawn`;
}
let lnMode=false,lnArtBytes=null,lnArtMime=0;
function lnSet(on){
if(on&&PLAUNCH===ZERO)return;
if(on)cbDisarm();
++seq;quoting=false;
lnMode=on;
if(on&&lqMode)lqSet(false);
if(on&&wnMode)wnSet(false);
if(on&&pvMode)pvSet(false);
ln.setAttribute("aria-pressed",on?"true":"false");
ln.classList.toggle("on",on);
for(const el of [rate,flip,swap])if(el)el.classList.toggle("hide",on);
for(const el of [document.querySelector(".panel"),rcvPanel])if(el)el.classList.toggle("hide",on);
document.querySelector(".card").classList.toggle("lnon",on);
for(const el of [lnPanel,lnNote,lnGo])el.classList.toggle("hide",!on);
rc.value="";rc.classList.remove("bad");rcvEl.textContent="";
rc.title=on?"Who the token belongs to: receives the creator allocation, collects the creator share of trading fees for the life of the token, and may edit its name, symbol and logo. Leave blank and it is you. Worth setting to a multisig if the project has one.":"";
rc.placeholder=on?"Creator — defaults to you"
:"Recipient (optional) — 0x or .eth/.(g)wei";
stat.textContent="";lnNote.textContent="";
if(on)cauKind();
fcSync();
}
function lnQuote(){
lnGo.textContent=account?"Launch":"Connect Wallet";
const ns=v=>v.replace(/[, ]/g,"");
const sup=ns(lnSupply.value),mc=+ns(lnMcap.value),al=+ns(lnAlloc.value);
lnNote.textContent="";
if(!(Number(sup)>0)||!(mc>0))return;
const alBps=al>0?Math.round(al*100):0;
const pooled=Number(sup)*(1-alBps/10000);
if(!(pooled>0))return;
const sym=lnSym.value.trim().toUpperCase();
const poolFrac=99/(mc+.99);
const firstEth=poolFrac*pooled/Number(sup);
const firstTokens=poolFrac*pooled/100;
lnNote.textContent=`1 ETH buys ${firstEth>=10?Math.round(firstEth):firstEth.toFixed(firstEth<1?2:1)}% of all ${sym||"tokens"}`
+` \u00b7 opens at 1 ETH \u2248 ${trimNum(firstTokens)} ${sym||"tokens"}`
+(al>0&&alBps===0?` \u00b7 that keep is below one basis point \u2014 the smallest is 0.01%`:"")
+(alBps>0?` \u00b7 creator keeps ${trimNum(Number(sup)*alBps/10000)} (${alBps/100}%)`
+` \u00b7 fully diluted ${trimNum(mc*Number(sup)/pooled)} ETH`:"")
+(alBps>=1000?` \u00b7 that much can sell for most of the ether early buyers put in`:"")
+(lnArtBytes?` \u00b7 logo ${(lnArtBytes.length/1024).toFixed(1)} KB`:"");
lnNote.style.color=poolFrac>=10?"var(--w)":"";
}
const trimNum=n=>n>=1000?Math.round(n).toLocaleString("en-US")
:n>=1?n.toLocaleString("en-US",{maximumFractionDigits:4}):String(Number(n.toPrecision(4)));
const blobText=b=>b.text?b.text():new Promise((res,rej)=>{
const r=new FileReader();r.onload=()=>res(String(r.result));r.onerror=()=>rej(r.error);r.readAsText(b)});
const blobBytes=async b=>b.arrayBuffer?new Uint8Array(await b.arrayBuffer()):await new Promise((res,rej)=>{
const r=new FileReader();r.onload=()=>res(new Uint8Array(r.result));r.onerror=()=>rej(r.error);r.readAsArrayBuffer(b)});
async function lnPrepare(file){
if(/svg/.test(file.type)){
const b=new TextEncoder().encode((await blobText(file))
.replace(/<\?xml[^>]*\?>/g,"").replace(/<!--[\s\S]*?-->/g,"").replace(/<!DOCTYPE[^>]*>/gi,"")
.replace(/<script[\s\S]*?<\/script\s*>/gi,"").replace(/<script[\s\S]*$/gi,"")
.replace(/<foreignObject[\s\S]*?<\/foreignObject\s*>/gi,"").replace(/<foreignObject[\s\S]*$/gi,"")
.replace(/\son[a-z-]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]*)/gi,"")
.replace(/\s(?:[a-z]+:)?href\s*=\s*("\s*javascript:[^"]*"|'\s*javascript:[^']*'|javascript:[^\s>]*)/gi,"")
.replace(/>\s+</g,"><").trim());
if(b.length>lnCap())throw Error(`That SVG is ${(b.length/1024).toFixed(1)} KB and the on-chain limit is ${Math.round(lnCap()/1024)} KB.`);
return{bytes:b,mime:LN_MIME["image/svg+xml"]};
}
const bmp=await createImageBitmap(file);
let smallest=null;
for(const side of [512,384,256,192,128,96,64]){
for(const [type,q] of [["image/webp",1],["image/png",undefined],["image/webp",0.85],["image/webp",0.6]]){
const sc=Math.min(1,side/Math.max(bmp.width,bmp.height));
const cv=document.createElement("canvas");
cv.width=Math.max(1,Math.round(bmp.width*sc));cv.height=Math.max(1,Math.round(bmp.height*sc));
const cx=cv.getContext("2d");cx.imageSmoothingQuality="high";
cx.drawImage(bmp,0,0,cv.width,cv.height);
const blob=await new Promise(r=>cv.toBlob(r,type,q));
if(!blob||blob.type!==type)continue;
const bytes=await blobBytes(blob);
if(!smallest||bytes.length<smallest.bytes.length)smallest={bytes,mime:LN_MIME[type]};
if(bytes.length<=lnBud())return{bytes,mime:LN_MIME[type]};
}
}
if(smallest&&smallest.bytes.length<=lnCap())return smallest;
throw Error("That image will not compress small enough. Flat colours and simple shapes do far better than photographs.");
}
lnPick.onclick=()=>lnArt.click();
lnArt.onchange=async()=>{
const f=lnArt.files&&lnArt.files[0];
if(!f)return;
lnArtNote.textContent="reading…";
try{
const r=await lnPrepare(f);
lnArtBytes=r.bytes;lnArtMime=r.mime;
lnArtNote.textContent=`${(r.bytes.length/1024).toFixed(1)} KB`;
lnArtNote.classList.add("set");
let b64="";for(const x of r.bytes)b64+=String.fromCharCode(x);
lnPrev.src="data:"+MIME_NAME[r.mime]+";base64,"+btoa(b64);
lnPrev.classList.add("set");
}catch(e){
lnArtBytes=null;lnArtNote.classList.remove("set");lnPrev.classList.remove("set");
lnArtNote.textContent="none";stat.textContent=e.message||String(e);
}
lnKindQuote();
};
const lnGroup=el=>{
if(!/^[\d, ]*(\.[\d ]*)?$/.test(el.value))return;
const dot=el.value.indexOf(".");
const intRaw=(dot<0?el.value:el.value.slice(0,dot)).replace(/\D/g,"");
const frac=dot<0?"":"."+el.value.slice(dot+1).replace(/\D/g,"");
const digitsBefore=el.value.slice(0,el.selectionStart??el.value.length).replace(/\D/g,"").length;
const out=intRaw.replace(/\B(?=(\d{3})+(?!\d))/g,",")+frac;
if(out===el.value)return;
el.value=out;
let seen=0,pos=digitsBefore?out.length:0;
if(digitsBefore)for(let i=0;i<out.length;i++){
if(/\d/.test(out[i])&&++seen===digitsBefore){pos=i+1;break}
}
try{el.setSelectionRange(pos,pos)}catch{}
};
lnSupply.oninput=()=>{lnGroup(lnSupply);lnQuote()};
for(const el of [lnName,lnSym,lnMcap,lnAlloc])el.oninput=lnKindQuote;
lnKind.onchange=cauKind;
for(const el of [lnGoal,lnDays])el.oninput=cauQuote;
lnVest.onchange=cauQuote;
ln.onclick=()=>{sBlip();lnSet(!lnMode)};
let wnMode=false,wnQ=null,wnSeq=0,wnRollSeq=0,wnTimer=0,wnRound=0n,wnStampTry=0,wnRollAt=0;
const wnOk=l=>/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(l)&&new TextEncoder().encode(l).length<256;
const wnFree=()=>wnTld.value==="id";
const wnOffer=(t,id)=>{wnEnter.classList.remove("hide");wnEnter.textContent=t;
wnEnter.dataset.id=id||"";wnEnter.classList.toggle("mut",!id&&!!account)};
const wnFull=l=>l+(wnFree()?".id.wei":".wei");
const wnSecret=()=>{const a=new Uint8Array(32);crypto.getRandomValues(a);let h="";for(const x of a)h+=x.toString(16).padStart(2,"0");return"0x"+h};
const wnHash=(l,o,sec)=>keccak(hexToBytes(pad32("60")+encAddr(o)+pad32(sec)+strTail(l)));
const wnKey=()=>WN_KEY+":"+(account||"").toLowerCase();
const wnStamp=async()=>{const p=wnPend();
if(!p||p.at||!account)return;
try{const r=await rpc(C,[{to:WNS,data:"0x"+SEL_WCOMMITS+pad32(wnHash(p.label,account,p.secret))},L]);
const t=Number(dec1(r||"0x0",0));
if(t)wnSave({...p,at:t})}catch{}};
const wnPend=()=>{try{const p=JSON.parse(LS[wnKey()]||"null");return p&&p.label&&p.secret&&Number.isFinite(p.at)?p:null}catch{return null}};
const wnSave=p=>{try{if(p)LS[wnKey()]=JSON.stringify(p);else delete LS[wnKey()]}catch{}};
const wnId=n=>namehash(/\.wei$/.test(n)?n:n+".wei");
const wnCostCall=l=>[{to:WNS,data:"0x"+SEL_WFEE+encUint(new TextEncoder().encode(l).length)},
{to:WNS,data:"0x"+SEL_WPREM+pad32(wnId(l))}];
const wnCost=async l=>{const [f,pr]=await mc3(wnCostCall(l));return dec1(f,0)+dec1(pr,0)};
const wnSync=()=>{
if(!wnMode)return;
const p=wnPend();
if(p&&!p.at){
if(!p.since){wnSave({...p,since:Math.floor(Date.now()/1000)});wnGo.disabled=true;wnGo.textContent="Confirming\u2026";return}
if(Math.floor(Date.now()/1000)-p.since>600){wnGo.disabled=false;wnGo.textContent="Commit again";return}
wnGo.disabled=true;wnGo.textContent="Confirming\u2026";return}
if(p){
const left=WN_MIN-(Math.floor(Date.now()/1000)-p.at);
wnGo.disabled=left>0;
wnGo.textContent=left>0?`Ready in ${left}s`:`Register ${p.label}.wei`;
if(Math.floor(Date.now()/1000)-p.at>=WN_MAX){wnGo.disabled=false;wnGo.textContent="Commit again"}
return}
wnGo.disabled=!wnQ;
wnGo.textContent=wnQ?(wnQ.free?`Claim ${wnQ.label}.id.wei`:"Commit"):"Register"};
const wnTicketOffer=async(l,seq,round)=>{
try{const[t]=await mc3([{to:WROLL,data:"0x"+SEL_RTICKET+encUint(round)+pad32(wnId(l))}]);
if(seq!==wnSeq)return;
const inAlready=!!dec1(t,0);
wnOffer(inAlready?`${l}.wei is already in`:`Enter ${l}.wei in the roll`,inAlready?"":pad32(wnId(l)));
}catch{if(seq===wnSeq)wnOffer(`Could not read the roll for ${l}.wei`)}};
const wnQuote=async()=>{
const my=++wnSeq;
wnQ=null;
wnOffer(account?"Enter a name you own":"Connect to enter");
wnSub.textContent=wnFree()?"free":"from 0.0005 ETH a year";
const l=wnName.value.trim().toLowerCase();
const p=wnPend();
if(p){wnNote.textContent=p.at?`${p.label}.wei is held for you — register it within 24 hours or the commitment lapses.`
:`Waiting for the commitment on ${p.label}.wei to confirm.`;wnSync();return}
if(!l){wnNote.textContent="";wnSync();return}
if(!wnOk(l)){wnNote.textContent="Letters, digits and hyphens only, and not starting or ending with a hyphen.";wnSync();return}
wnNote.textContent="checking…";
wnSync();
try{
const free=wnFree();
const rc=!free&&account?[{to:WNS,data:"0x"+SEL_WOWNER+pad32(wnId(l))},
{to:WROLL,data:"0x"+SEL_RWEIGHT+pad32(wnId(l))},
{to:WROLL,data:"0x"+SEL_RSTATE}]:[];
const r=await mc3([{to:WNS,data:"0x"+SEL_WAVAIL+pad32("40")+pad32(free?IDWEI:"0")+strTail(l)},
...(free?[]:wnCostCall(l)),...rc]);
if(my!==wnSeq)return;
const av=r[0],f=r[1],pr=r[2];
if(!account)wnOffer("Connect to enter");
else if(free)wnOffer("A free name cannot enter the roll");
else if(dec1(av,0)===1n)wnOffer(`${l}.wei is not registered yet`);
else{const ow=retAddr(r[3]),mine=!!ow&&ow.toLowerCase()===account.toLowerCase();
const wt=r[4]&&dec1(r[4],0);
if(r[5])wnRound=dec1(r[5],1);
if(!mine||!wt)wnOffer(!mine?`${l}.wei is not yours`:`${l}.wei does not weigh`);
else wnTicketOffer(l,my,wnRound)}
if(dec1(av,0)!==1n){wnNote.textContent=`${wnFull(l)} is taken.`;wnSync();return}
if(free){wnQ={label:l,free:true};
wnNote.textContent=`${l}.id.wei is free, in one transaction. A free name does not weigh in the roll.`;wnSync();return}
const cost=dec1(f,0)+dec1(pr,0);
wnQ={label:l,free:false,cost};
wnNote.textContent=`${l}.wei costs ${trimAmt(cost,18)} ETH for a year, in two transactions a minute apart.`;
}catch(e){if(my===wnSeq)wnNote.textContent="Could not price that name."}
wnSync()};
const wnRoll=async()=>{
if(!wnMode)return;
const my=++wnRollSeq;
wnRollAt=Date.now();
try{
const [st,rv]=await mc3([{to:WROLL,data:"0x"+SEL_RSTATE},
...(account?[{to:WNS,data:"0x"+SEL_REV+encAddr(account)}]:[])]);
const round=dec1(st,1),end=Number(dec1(st,2)),pot=dec1(st,3),tix=dec1(st,5),tw=dec1(st,6);
if(my!==wnRollSeq)return;
const days=Math.max(0,Math.ceil((end-Date.now()/1000)/86400));
let line=`A monthly draw among paid .wei names. ${(+formatUnits(pot,18)).toFixed(3)} ETH staked, ${tix} entered, ${days} day${days===1?"":"s"} left.`;
wnRound=round;
const typed=wnName.value.trim();
if(!typed)wnOffer(account?"Enter a name you own":"Connect to enter");
if(account&&!typed){
const nm=decodeString(rv||"0x").trim();
if(!nm)line+=" Any paid .wei name you own can enter, free — type one above.";
else{
const id=wnId(nm);
const [w,t]=await mc3([{to:WROLL,data:"0x"+SEL_RWEIGHT+pad32(id)},
{to:WROLL,data:"0x"+SEL_RTICKET+encUint(round)+pad32(id)}]);
if(my!==wnRollSeq||wnName.value.trim()){wnRollEl.textContent=line;return}
const weight=dec1(w,0);
if(!weight){line+=` ${nm} does not weigh — only a paid .wei name does.`;
wnOffer("Enter a name you own")}
else if(dec1(t,0)){const odds=tw?Number(weight*10000n/tw)/100:0;
line+=` ${nm} is in, at about ${odds<0.01?"<0.01":odds.toFixed(2)}%.`}
else{line+=` ${nm} can enter, free.`;wnOffer(`Enter ${nm} in the roll`,pad32(id))}}}
wnRollEl.textContent=line;
}catch{wnRollEl.textContent="Could not read the roll."}};
const wnPaint=()=>{const on=wnMode;
wn.setAttribute("aria-pressed",on?"true":"false");
wn.classList.toggle("on",on);
for(const el of [rate,flip,swap])if(el)el.classList.toggle("hide",on);
for(const el of [document.querySelector(".panel"),rcvPanel])if(el)el.classList.toggle("hide",on);
for(const el of [wnPanel,wnGo,wnRollPanel])el.classList.toggle("hide",!on);
for(const el of [rc,rcvEl])if(el)el.classList.toggle("hide",on);document.querySelector(".card").classList.toggle("wnon",on)};
function wnSet(on){
if(on&&CHAIN_ID!==1)return;
if(on)cbDisarm();
++seq;quoting=false;
wnMode=on;
if(on&&lqMode)lqSet(false);
if(on&&lnMode)lnSet(false);
if(on&&pvMode)pvSet(false);
wnPaint();
stat.textContent="";
clearInterval(wnTimer);
if(on){wnTimer=setInterval(()=>{
if(document.hidden)return;
wnSync();
if(Date.now()-wnRollAt>30000)wnRoll();
const q=wnPend();
if(q&&!q.at&&Date.now()-wnStampTry>5000){wnStampTry=Date.now();
wnStamp().then(()=>{const r=wnPend();if(r&&r.at)wnQuote()})}
},1000);
wnStampTry=0;
const pp=wnPend();if(pp&&!pp.at)wnStamp().then(wnQuote);
wnQuote();wnRoll()}
else{wnNote.textContent="";wnRollEl.textContent=""}
fcSync();}
const syncSnd=()=>{sdw.style.display=snd?"":"none";sdx.style.display=snd?"none":"";
sd.style.opacity=snd?"":".5";sd.setAttribute("aria-pressed",snd?"true":"false");
sd.title=snd?"Sound on — click to mute":"Sound off
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"}
}
is minted, your allocation is paid to you immediately, and <b>everything else is seeded into a fresh ETH pool</b> — one-sided, so the pool opens holding only your token.</p>
<p><b>Starting market cap</b> is the pool's virtual ether reserve. It sets the opening price and how hard that price is to move, which are the same dial: roughly that much buying moves the market halfway. Small numbers mean the first buyer takes a large share of the supply, which is why the form says what one ether would buy.</p>
<p><b>Creator keeps</b> is a share of the token SUPPLY, minted straight to you when the coin launches — tokens, not ether, and not a fee on trading. Keep 10% of a billion and 100,000,000 land in your wallet at once, unlocked, with nothing vesting.</p>
<p>Whatever you keep does not go into the pool, and selling it later pulls ether back out of that pool — so a modest-looking percentage can be a claim on most of what early buyers put in. Anyone deciding whether to buy your coin can work that out too.</p>
<p>A <b>logo</b> is stored as contract code on Ethereum — no IPFS, no pinning service, nothing to go dark. Upload anything; the page compresses it and shows the size before you sign. You can replace it later.</p></details>
<details><summary>Launching a cause <em><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/></svg></em></summary>
<p>The other half of the launcher, and a different thing entirely: no pool, no market, no trading. Ten million units are offered at a fixed price in ether — your <b>goal</b> divided by ten million — and what backers pay accumulates in a treasury that belongs to the cause, not to you.</p>
<p>Two clocks, and they are deliberately different lengths. <b>Runs for</b> is how long backing stays open. <b>Released over</b> sets a fixed <b>rate</b>, not a fixed fraction: the tap pays out your <em>goal</em> divided by that window, every second, whatever was actually raised. Ten ether released over a year is under three hundredths of an ether a day.</p>
<p><b>So a cause that raises less than its goal empties faster than the window suggests.</b> Raise a tenth of the goal and the treasury is drained in a tenth of the time — a tenth of a year, not a year. Since what has not been released is exactly what a backer can still burn back for, an under-subscribed cause can leave its backers with nothing well before the release window is over. Set the window long enough that a partial raise still leaves something behind, and read the daily figure below as the speed at full funding.</p>
<p>The release clock starts the moment you launch and runs whether or not anyone has backed you yet, which is why it wants to be much longer than the raise. Release over the same thirty days you raise for and the whole treasury is yours the day backing closes — and somebody who backed you on the last day gets nothing back, because there is nothing left unreleased.</p>
<p>That is what makes the token worth something on its own: <b>whatever has not been released yet can be burned back for</b>. A backer who changes their mind, or who watches a cause go quiet, redeems their share of the untouched treasury and takes their ether home. Nobody has to agree and nothing has to be voted on.</p>
<p><b>The creator can close that exit, but not quietly and not quickly.</b> Burning calls <code>ragequit</code>, which the DAO can disable — and since the creator holds the only share, they alone can propose it and carry the vote. What they cannot do is act on it at once: the change has to go through the DAO, which queues it on chain and then waits out a <b>three-day timelock</b> before it can be executed. That queue is public from the moment it is entered, and burning back stays open the whole time. So the protection is not that nobody can take the exit away; it is that taking it away is visible, and slower than leaving.</p>
<p>Backers hold <b>loot</b>, which carries that claim and no vote. You keep a single share and the vote that comes with it, and you buy that share at the same price everyone else pays — so your claim on the treasury is the same shape as theirs.</p>
<p>Paste a cause’s token into the swap and the page prices the burn for you, against the treasury rather than against a market. It is the only price such a token has, and it is the honest one.</p></details>
<details><summary>Fees</summary>
<p>A launched market charges <b>1%</b> per swap. Half of that stays in the pool as reserves, which raises the floor for every holder. The other half is collectable, and splits <b>80% creator, 10% protocol, 10% tithe</b>.</p>
<p>The <b>tithe</b> is burned to Ethereum itself. The ether goes to the BETH burner, which destroys it and mints a BETH receipt recording that it happened. Nobody can spend it and it cannot be redeemed — the ether no longer exists. What it buys is a smaller ether supply, which accrues to everyone holding ether rather than to us.</p>
<p>The token side of collected fees is burned outright too — a launched coin's supply only ever falls.</p></details>
<details><summary>What this page is</summary>
<p>There is no server. This page is served from a contract, executes only its own bytes, and needs nothing but an Ethereum node to quote and swap. Those bytes cannot be changed by anyone, us included — a new version is a new address, not an edit. The token list is a contract too, artwork included; so are the node list, the solver roster, and the adapter an outside route settles through. It talks to your wallet, to Ethereum, and — when you ask for a quote — to the aggregators listed under Swapping. Which ones those are is itself recorded on chain and curated there, not decided by this page or by whoever answers a request; any of them can be switched off in a single transaction. They are asked for a price on a pair and a size, and <b>they are not told who is asking</b>: the address they are given is the shared adapter, the same one in every other person’s request. They never see a key, and they cannot move anything, because what they return is bounded on chain before it settles. Pin <b>On-chain</b> in the venue list to skip them entirely. WalletConnect is the other outside party: reaching your phone needs its relay. Reads go through a public node you can point anywhere you like.</p>
<p>Your keys never leave your wallet. Nothing here custodies your funds; every trade settles from your address to a contract you can read.</p>
<p>The token list is <b>zList</b>, ranked on chain by conviction staking. Anything not on it can still be traded by pasting its address.</p></details>
<div class="docsmore">Longer notes at <a href="https://docs.zswap.wei.limo" target="_blank" rel="noopener">docs.zswap.wei.limo</a></div>
</div>
<footer id="foot"><span id="footV"></span><span id="footAddr"></span><span id="footBurn"></span><span>· <a id="footDoc" href="#" role="button">how it works</a></span><span>· <a id="footList" target="_blank" rel="noopener" title="Token order here comes from zList, ranked by onchain conviction staking. Stake to move a token up the list - or to get one listed at all.">token list</a></span></footer>
</div>
<script>const IC0='<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">',
ICMOON=IC0+'<path d="M20.5 14.6A8.6 8.6 0 0 1 9.4 3.5a8.6 8.6 0 1 0 11.1 11.1z"/></svg>',
ICSUN=IC0+'<circle cx="12" cy="12" r="4.2"/><path d="M12 2v2m0 16v2M4.2 4.2l1.5 1.5m12.6 12.6 1.5 1.5M2 12h2m16 0h2M4.2 19.8l1.5-1.5M18.3 5.7l1.5-1.5"/></svg>';
const ic=()=>{const d=H.className=='d';th.innerHTML=d?ICSUN:ICMOON;tc.content=d?'#0a0a0a':'#fff'};ic();
const ZSWAP_VERSION="0.3";
const ZSWAP_SELF="";
const ZSWAP_PREVIOUS="0xe686952842627A2cf81DF42CCaD54ef98046DB8D";
const ZLIST_URL="https://0x0000006d936ba3653b8854490e16e782cd32a9a8.w4eth.io/";
const selfFromUrl=()=>{
const h=(location.hostname||"").split(".");
if(!/^0x[0-9a-fA-F]{40}$/.test(h[0]||""))return "";
if(/^\d+$/.test(h[1]||"")&&h[1]!=="1")return "";
return h[0];
};
const short=a=>a.slice(0,6)+"\u2026"+a.slice(-4);
(()=>{
const self=selfFromUrl()||ZSWAP_SELF;
footV.textContent=`zSwap v${ZSWAP_VERSION}`;
footList.href=ZLIST_URL;
if(self){
footAddr.innerHTML=`\u00b7 <a href="https://etherscan.io/address/${self}#code" target="_blank" rel="noopener" `
+`title="This exact build, on chain. Its bytes cannot change; a newer version is a different address.">${short(self)}</a>`;
if(ZSWAP_PREVIOUS)footAddr.innerHTML+=` \u00b7 <a href="https://etherscan.io/address/${ZSWAP_PREVIOUS}#code" `
+`target="_blank" rel="noopener" title="The version this one succeeded. It still serves its own page, unchanged.">prev</a>`;
}
})();
th.onclick=()=>{H.classList.toggle('d');LS.t=H.className=='d'?'d':'l';ic()};
const ZQUOTER="0x000000bd2db80567c23e353ca95a251c573cbf9b";
const WNS="0x0000000000696760E15f265e828DB644A0c242EB";
const GNS="0x9D51D507BC7264d4fE8Ad1cf7Fe191933A0a81d6";
const ENSREG="0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e";
const WREG="0x53745292f0d30d68204a63002C17bDa16C772bf7";
const WROLL="0x0000C82AA4D72871568eF3859D2b0E7CF37e45f2";
const IDWEI="cea9efa56b7c8a673303d04b917a7119a2a68f8c4803d8e6fd1c3a1f0d2e4ebe";
const ZROUTER="0x000000000000FB114709235f1ccBFfb925F600e4";
const PFACTORY="0x000000Eb27B557aB426d9E99cFd54EC455799e81";
const PLQLENS="0x000000956bf20A41C54BaE4a4b6F5C8A166DAB4E";
const PPLENS="0x000000Bad3a2fa57ed74fa06000573ccddF6B7fB";
const PI_WORDS=19,PI_POOL=0,PI_LIQ=9,PI_HOOK=10,PI_SL=3,PI_SH=4,
PI_FEE=5,PI_R0=6,PI_R1=7,PI_PX=8,PI_CBPS=12;
let CHAIN_ID=1;
const CHAIN_ICON_BASE=`<svg viewBox="0 0 32 32" width="20" height="20" aria-hidden="true"><rect width="32" height="32" fill="#0052FF"/><path d="M16 5.4A10.6 10.6 0 0 0 5.5 14.7h13.9v2.6H5.5A10.6 10.6 0 1 0 16 5.4Z" fill="#fff" transform="rotate(180 16 16)"/></svg>`;
const CHAIN_ICON_RH=`<svg viewBox="0 0 32 32" width="20" height="20" aria-hidden="true"><rect width="32" height="32" fill="#CCFF00"/><g transform="translate(4 4)" fill="#110E08"><path d="M2.84 24h.53c.096 0 .192-.048.224-.128C7.591 13.696 11.94 8.656 14.67 5.638c.112-.128.064-.225-.096-.225h-4.88a.55.55 0 0 0-.45.225L5.746 9.972c-.514.642-.642 1.236-.642 2.086v4.43c-1.14 3.194-1.862 5.361-2.392 7.32-.032.125.016.192.129.192M20.447.646c-.754-.802-4.157-.834-5.73-.224a3 3 0 0 0-.786.465 41 41 0 0 0-3.323 3.178c-.112.113-.064.225.097.225h5.409c.497 0 .786.289.786.786v6.1c0 .16.128.208.225.064l3.258-4.254c.53-.69.69-.898.835-1.861.192-1.413.08-3.58-.77-4.479m-6.982 16.18 2.231-3.676a.7.7 0 0 0 .064-.29V6.73c0-.16-.112-.225-.224-.097-3.355 3.74-5.971 7.672-8.395 12.407-.06.12.016.225.16.177l5.009-1.54c.565-.174.882-.402 1.155-.852"/></g></svg>`;
const CHAINS={
1:{name:"Ethereum",short:"ETH",slow:true,sweep4:true,curve:5,fill:"0x7A2f21e476cA2ADde027BC868c5a083338EEfE54",exec:"0x4f6633342045C80Efa6c8CAc41b9bdc6145168C4",slvId:"1",slvSlug:"ethereum",sources:["UniV2","Sushi","zAMM","UniV3","UniV4","Curve","Lido","Wrap","Precision"],v4port:"0x000000dfb53Fa7f1c486470034741d5BCBE14BE9",v4lens:"0x000000c3aE1692983941495162A4AAB40660E65F",tokenlist:"0x0000006013dF75A31678B786061C2B54bf531524",
wrapped:"0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",explorer:"https://etherscan.io",
rpcs:["https://ethereum-rpc.publicnode.com","https://eth-mainnet.public.blastapi.io"]},
8453:{name:"Base",short:"BASE",sweep4:false,curve:-1,fill:"0x03d95ecAF7B2Ab6027b8541b19Cb0Ce5CEd9bC74",exec:"0x1434A2579DE1009aA12b7064A161E53a5be2C030",slvId:"8453",slvSlug:"base",sources:["UniV2","Aero","zAMM","UniV3","UniV4","AeroCL","Wrap","","Precision"],v4port:"0x508ad1b0ae31FaF295c5af8C5c2bE9e33E0D19C4",v4lens:"0x0000000000000000000000000000000000000000",tokenlist:"0x0000006013dF75A31678B786061C2B54bf531524",
wrapped:"0x4200000000000000000000000000000000000006",explorer:"https://basescan.org",
icon:CHAIN_ICON_BASE,rpcs:["https://mainnet.base.org","https://base-rpc.publicnode.com"]},
4663:{name:"Robinhood",short:"RH",sweep4:false,curve:5,fill:"0x03d95ecAF7B2Ab6027b8541b19Cb0Ce5CEd9bC74",exec:"0x1434A2579DE1009aA12b7064A161E53a5be2C030",slvId:"4663",slvSlug:"robinhood",sources:["UniV2","Sushi","zAMM","UniV3","UniV4","Curve","Lido","Wrap","Precision"],v4port:"0x508ad1b0ae31FaF295c5af8C5c2bE9e33E0D19C4",v4lens:"0x0000000000000000000000000000000000000000",tokenlist:"0x0000006013dF75A31678B786061C2B54bf531524",
wrapped:"0x0bd7d308f8e1639fab988df18a8011f41eacad73",explorer:"https://robinscan.io",
rpcs:["https://rpc.mainnet.chain.robinhood.com"]},
};
CHAINS[4663].icon=CHAIN_ICON_RH;
const MB={sb:"0x000000dA7bb4B2A9E3e80e9A4D4157E26CA6189b",s1:"0x000000fF3D7A2d373615141d7489Ca66683DbecF",sw:"0x00000087A6dc5071779Ed1F8274A39230768B976",du:"0x000000a213b430D14Bae6062c176289B05e04489",ob:"0x000000e6c7a12C80525ee74e7434aAb919447D95",fl:"0x00000080198137F790DA4C52bb902cf87c276748",pr:"0x0000007Be74558A1F8c9045301c6F44C8eD0c9eB",la:"0x0000002fC8E77585A008Aa45d78A71ad36293aEe"};
const L2B={sb:"0x0000001330435808A906432449D233d482Bc9b60",sw:"0x00000032100E634903378DAEED6bA448f35F541a",du:"0x000000fa42d555173395323b2956e9c42EFaEFf2",ob:"0x0000007a1c93d42dD739E8d6cA9Dabf330934970",fl:"0x000000AC7d32e802B003a31F790eb28Ed3294Bac",pr:"0x000000D14E24e5FC8965bcDE58f21f704C944999"};
const chainCfg=()=>CHAINS[CHAIN_ID]||CHAINS[1];
const L1_RPCS=["https://ethereum-rpc.publicnode.com","https://eth-mainnet.public.blastapi.io"];
let l1i=0;
const l1Read=async(method,params)=>{
for(let k=0;k<L1_RPCS.length*2;k++){
const u=L1_RPCS[l1i%L1_RPCS.length];
try{return await httpRead(u,method,params)}catch(e){l1i++}
}
throw Error("no mainnet read node");
};
const cfgRead=(method,params)=>CHAIN_ID===1?rpc(method,params):l1Read(method,params);
const SHARE_HOST="new.zswap.wei.limo";
const ZERO="0x0000000000000000000000000000000000000000";
let WETH="0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2";
let TOKENLIST="0x0000006013dF75A31678B786061C2B54bf531524";
const ZLISTLENS="0x000000cEa3AB048d59473F3fb116A8D7F1abd247";
const SEL_RANKEDIDS="df7ca268";
const SEL_JSON="74e18e96";
const SEL_CID="fb021939",SEL_RES="4f896d4f",SEL_REV="9af8b7aa";
const SCOREM="0x57Fae63f7c63732fc4B77247F0255b11b9458AE1",SEL_SCLAIM="5cc2f461";
const SEL_WAVAIL="8f8dc386",SEL_WFEE="fcee45f4",SEL_WPREM="1bf1fffb",SEL_WCOMMIT="f14fcbc8",SEL_WCOMMITS="839df945",SEL_WREVEAL="ea9384fa",SEL_WSUB="a00fd3c8";
const SEL_RSTATE="c19d93fb",SEL_RWEIGHT="0767d178",SEL_RTICKET="673b4784",SEL_RENTER="23972aef",SEL_WOWNER="6352211e";
const WN_KEY="zswap:wns",WN_MIN=60,WN_MAX=86400;
const SEL_RSLV="0178b8bf",SEL_EADDR="3b3b57de",SEL_ENAME="691f3431";
const SEL_ERESOLVE="9061b923";
const NAME_OFFCHAIN="That name keeps its records off chain, and this page reads only on-chain state.";
const SEL_ALLOWANCE="dd62ed3e";
const SEL_LATEST="52bfe789",SEL_PREV="247dfaa8",SEL_SUCCAT="451aae60";
const MATURITY=259200;
const SEL_DS="3644e515",SEL_NONCES="7ecebe00",SEL_NAME="06fdde03",SEL_PERMIT_TH="30adf81f";
const TH_2612="6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9";
const SEL_RPERMIT="7ac2ff7b",SEL_MULTICALL="ac9650d8",SEL_SWEEP="cb019b84";
const PERMIT2="0x000000000022D473030F116dDEE9F6B43aC78BA3";
const SEL_P2TF="09d31579";
const EIP712DOM="EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)";
const SEL_APPROVE="095ea7b3";
const SEL_BALANCEOF="70a08231";
let V4PORT="0x000000dfb53Fa7f1c486470034741d5BCBE14BE9";
let V4LENS="0x000000c3aE1692983941495162A4AAB40660E65F";
const SEL_V4QUOTE="d500463c",SEL_V4SWAP="48e6f730";
const V4POOL_KEY="0x95a932c205571d4d1ca72715c642a2eca21dde79ffc28ff11509681f9383385f";
const C="eth_call",S="eth_sendTransaction",L="latest",I="eth_chainId";
let SOURCES=["UniV2","Sushi","zAMM","UniV3","UniV4","Curve","Lido","Wrap","Precision"];
const SRC_PRECISION=8;
let SRC_CURVE=5;
const ETH_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#627EEA"/><g fill="#FFF" fill-rule="nonzero"><path fill-opacity=".602" d="M16.498 4v8.87l7.497 3.35z"/><path d="M16.498 4L9 16.22l7.498-3.35z"/><path fill-opacity=".602" d="M16.498 21.968v6.027L24 17.616z"/><path d="M16.498 27.995v-6.028L9 17.616z"/><path fill-opacity=".2" d="M16.498 20.573l7.497-4.353-7.497-3.348z"/><path fill-opacity=".602" d="M9 16.22l7.498 4.353v-7.701z"/></g></g></svg>`;
const USDC_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none"><circle fill="#2775CA" cx="16" cy="16" r="16"/><g fill="#FFF"><path d="M20.022 18.124c0-2.124-1.28-2.852-3.84-3.156-1.828-.243-2.193-.728-2.193-1.578 0-.85.61-1.396 1.828-1.396 1.097 0 1.707.364 2.011 1.275a.458.458 0 00.427.303h.975a.416.416 0 00.427-.425v-.06a3.04 3.04 0 00-2.743-2.489V9.142c0-.243-.183-.425-.487-.486h-.915c-.243 0-.426.182-.487.486v1.396c-1.829.242-2.986 1.456-2.986 2.974 0 2.002 1.218 2.791 3.778 3.095 1.707.303 2.255.668 2.255 1.639 0 .97-.853 1.638-2.011 1.638-1.585 0-2.133-.667-2.316-1.578-.06-.242-.244-.364-.427-.364h-1.036a.416.416 0 00-.426.425v.06c.243 1.518 1.219 2.61 3.23 2.914v1.457c0 .242.183.425.487.485h.915c.243 0 .426-.182.487-.485V21.34c1.829-.303 3.047-1.578 3.047-3.217z"/><path d="M12.892 24.497c-4.754-1.7-7.192-6.98-5.424-11.653.914-2.55 2.925-4.491 5.424-5.402.244-.121.365-.303.365-.607v-.85c0-.242-.121-.424-.365-.485-.061 0-.183 0-.244.06a10.895 10.895 0 00-7.13 13.717c1.096 3.4 3.717 6.01 7.13 7.102.244.121.488 0 .548-.243.061-.06.061-.122.061-.243v-.85c0-.182-.182-.424-.365-.546zm6.46-18.936c-.244-.122-.488 0-.548.242-.061.061-.061.122-.061.243v.85c0 .243.182.485.365.607 4.754 1.7 7.192 6.98 5.424 11.653-.914 2.55-2.925 4.491-5.424 5.402-.244.121-.365.303-.365.607v.85c0 .242.121.424.365.485.061 0 .183 0 .244-.06a10.895 10.895 0 007.13-13.717c-1.096-3.46-3.778-6.07-7.13-7.162z"/></g></g></svg>`;
const USDT_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#26A17B"/><path fill="#FFF" d="M17.922 17.383v-.002c-.11.008-.677.042-1.942.042-1.01 0-1.721-.03-1.971-.042v.003c-3.888-.171-6.79-.848-6.79-1.658 0-.809 2.902-1.486 6.79-1.66v2.644c.254.018.982.061 1.988.061 1.207 0 1.812-.05 1.925-.06v-2.643c3.88.173 6.775.85 6.775 1.658 0 .81-2.895 1.485-6.775 1.657m0-3.59v-2.366h5.414V7.819H8.595v3.608h5.414v2.365c-4.4.202-7.709 1.074-7.709 2.118 0 1.044 3.309 1.915 7.709 2.118v7.582h3.913v-7.584c4.393-.202 7.694-1.073 7.694-2.116 0-1.043-3.301-1.914-7.694-2.117"/></g></svg>`;
const WBTC_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none" fill-rule="evenodd"><circle cx="16" cy="16" r="16" fill="#F7931A"/><path fill="#FFF" fill-rule="nonzero" d="M23.189 14.02c.314-2.096-1.283-3.223-3.465-3.975l.708-2.84-1.728-.43-.69 2.765c-.454-.114-.92-.22-1.385-.326l.695-2.783L15.596 6l-.708 2.839c-.376-.086-.746-.17-1.104-.26l.002-.009-2.384-.595-.46 1.846s1.283.294 1.256.312c.7.175.826.638.805 1.006l-.806 3.235c.048.012.11.03.18.057l-.183-.045-1.13 4.532c-.086.212-.303.531-.793.41.018.025-1.256-.313-1.256-.313l-.858 1.978 2.25.561c.418.105.828.215 1.231.318l-.715 2.872 1.727.43.708-2.84c.472.127.93.245 1.378.357l-.706 2.828 1.728.43.715-2.866c2.948.558 5.164.333 6.097-2.333.752-2.146-.037-3.385-1.588-4.192 1.13-.26 1.98-1.003 2.207-2.538zm-3.95 5.538c-.533 2.147-4.148.986-5.32.695l.95-3.805c1.172.293 4.929.872 4.37 3.11zm.535-5.569c-.487 1.953-3.495.96-4.47.717l.86-3.45c.975.243 4.118.696 3.61 2.733z"/></g></svg>`;
const WSTETH_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><g fill="none"><circle fill="#00A3FF" cx="16" cy="16" r="16"/><path d="M9.437 14.864l-.181.275c-2.048 3.097-1.603 7.253 1.034 9.824 1.561 1.521 3.622 2.353 5.683 2.353 0 0 0 0-6.536-12.452z" fill="#FFF"/><path opacity=".6" d="M15.997 18.611l-6.56-3.747c6.56 12.452 6.56 12.452 6.56 12.452 0-2.683 0-5.623 0-8.705z" fill="#FFF"/><path opacity=".6" d="M22.563 14.864l.181.275c2.048 3.097 1.603 7.253-1.034 9.824-1.561 1.521-3.622 2.353-5.683 2.353 0 0 0 0 6.536-12.452z" fill="#FFF"/><path opacity=".2" d="M16.003 18.611l6.56-3.747c-6.56 12.452-6.56 12.452-6.56 12.452 0-2.683 0-5.623 0-8.705z" fill="#FFF"/><path opacity=".2" d="M16.004 10.239v6.459l5.654-3.23-5.654-3.229z" fill="#FFF"/><path opacity=".6" d="M16.005 10.239l-5.655 3.229 5.655 3.23v-6.46z" fill="#FFF"/><path d="M16.005 4.805l-5.655 8.668 5.655-3.233V4.805z" fill="#FFF"/><path opacity=".6" d="M16.004 10.238l5.658 3.23-5.658-8.674v5.444z" fill="#FFF"/></g></svg>`;
const RETH_ICON=`<svg width="20" height="20" viewBox="0 0 32 32"><circle cx="16" cy="16" r="16" fill="#ED5A37"/><path d="M16 5.75L9.89 15.85 16 19.45l6.08-3.6z" fill="#FFF"/><path d="M16 20.6l-6.11-3.59L16 25.58l6.08-8.57z" fill="#FFF"/></svg>`;
const BOLD_ICON=`<svg width="20" height="20" viewBox="0 0 32 32" fill="none"><circle cx="16" cy="16" r="16" fill="#63D77D"/><path fill-rule="evenodd" clip-rule="evenodd" d="M12.1719 4.56641H8.58203V26.1016H15.7617V25.2422C16.8398 25.793 18.0586 26.1055 19.3555 26.1055C23.7148 26.1055 27.25 22.5703 27.25 18.207C27.25 13.8438 23.7148 10.3086 19.3555 10.3086C18.0586 10.3086 16.8398 10.6211 15.7617 11.1719V4.56641H12.1719ZM15.7617 11.1719C13.207 12.4805 11.457 15.1406 11.457 18.207C11.457 21.2734 13.207 23.9336 15.7617 25.2422V11.1719Z" fill="#1C1D4F"/></svg>`;
let TOKENS=[
{sym:"ETH", addr:ZERO, dec:18, icon:ETH_ICON},
{sym:"wstETH",addr:"0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0",dec:18,icon:WSTETH_ICON},
{sym:"WETH", addr:WETH, dec:18, icon:ETH_ICON},
{sym:"rETH", addr:"0xae78736cd615f374d3085123a210448e74fc6393",dec:18,icon:RETH_ICON},
{sym:"WBTC", addr:"0x2260fac5e5542a773aa44fbcfedf7c193bc2c599",dec:8, icon:WBTC_ICON},
{sym:"USDC", addr:"0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48",dec:6, icon:USDC_ICON},
{sym:"USDT", addr:"0xdac17f958d2ee523a2206206994597c13d831ec7",dec:6, icon:USDT_ICON},
{sym:"BOLD", addr:"0x6440f144b7e50d6a8439336510312d2f54beb01d",dec:18,icon:BOLD_ICON},
];
let BUILTIN=TOKENS.slice();
CHAINS[1].tokens=TOKENS.slice();
CHAINS[8453].tokens=[
{sym:"ETH",addr:ZERO,dec:18,icon:ETH_ICON},
{sym:"WETH",addr:"0x4200000000000000000000000000000000000006",dec:18,icon:ETH_ICON},
{sym:"USDC",addr:"0x833589fcd6edb6e08f4c7c32d4f71b54bda02913",dec:6,icon:USDC_ICON},
{sym:"cbBTC",addr:"0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf",dec:8},
{sym:"DAI",addr:"0x50c5725949a6f0c72e6c4a641f24049a917db0cb",dec:18},
{sym:"AERO",addr:"0x940181a94a35a4569e4529a3cdfb74e38fd98631",dec:18},
];
CHAINS[4663].tokens=[
{sym:"ETH",addr:ZERO,dec:18,icon:ETH_ICON},
{sym:"WETH",addr:"0x0bd7d308f8e1639fab988df18a8011f41eacad73",dec:18,icon:ETH_ICON},
{sym:"USDG",addr:"0x5fc5360d0400a0fd4f2af552add042d716f1d168",dec:6},
{sym:"NVDA",addr:"0xd0601ce157db5bdc3162bbac2a2c8af5320d9eec",dec:18},
{sym:"DEEP",addr:"0x1da24f6bb623b9d1afeae3f3146659a2662d6d27",dec:18},
];
const CHAIN_STORE="zswap:chain";
function setChain(id){
id=+id;
const c=CHAINS[id];
if(!c)return false;
if(id===CHAIN_ID)return true;
CHAIN_ID=id;
WETH=c.wrapped;
TOKENLIST=c.tokenlist;
V4LENS=c.v4lens||ZERO;
V4PORT=c.v4port||ZERO;
const b=id===1?MB:L2B;
SB2=b.sb||ZERO;SB1=b.s1||ZERO;SWAPBOL=b.sw||ZERO;DUTCH=b.du||ZERO;ORDERBOL=b.ob||ZERO;FLOOR=b.fl||ZERO;PROUTE=b.pr||ZERO;PLAUNCH=b.la||ZERO;
floorReady=null;listLive=false;listJob=null;
try{SOLVER_FILL_PIN=c.fill||ZERO;SOLVER_EXEC_PIN=c.exec||ZERO}catch{}
try{if(c.sources)SOURCES=c.sources.slice();SRC_CURVE=(c.curve===undefined?5:c.curve)}catch{}
if(c.tokens){TOKENS=c.tokens.slice();BUILTIN=TOKENS.slice();restoreCustom()}
try{if(c.rpcs&&c.rpcs.length){rpcPool=[...new Set(c.rpcs)];rpcPi=0;curated=0;curatedTry=0}}catch{}
try{localStorage[CHAIN_STORE]=String(id)}catch{}
try{chainUi()}catch{}
return true;
}
function parseV4Pools(extras,tokenAddr){
if(!Array.isArray(extras)||!/^0x[0-9a-f]{40}$/i.test(tokenAddr||""))return[];
const row=extras.find(x=>x&&typeof x.v==="string"&&String(x.k).toLowerCase()===V4POOL_KEY);
if(!row)return[];
const out=[];
for(const spec of row.v.split(";")){
const p=spec.trim().spli
,calc(100vw - 2em));background:var(--c);border:1px solid var(--hd);
overflow:hidden;
max-height:min(32em,calc(100vh - 2em));display:flex;flex-direction:column}
#wkHdr{flex:none}
#wkList{overflow-y:auto;overscroll-behavior:contain}
#wkHdr{padding:.7em .8em;border-bottom:1px solid var(--e);font-size:.72em;
letter-spacing:.14em;text-transform:uppercase;color:var(--m)}
#wkList img,#wkList .wki{width:20px;height:20px;border-radius:50%;flex:none}
.wcq{padding:.7em .8em 0}
.wcn{padding:.55em .8em .85em;font-size:.72em;color:var(--m)}
.wcnode{padding-top:0;opacity:.85}
#wkList .wki{display:block}
#wkList .wki svg{width:100%;height:100%;display:block}
.tkg{border-top:1px solid var(--e);padding:.7em .8em .3em;font-size:.62em;letter-spacing:.14em;text-transform:uppercase;color:var(--m)}
.tkr{display:flex;align-items:flex-start;gap:.55em;width:100%;box-sizing:border-box;
padding:.55em .8em;border:0;border-top:1px solid var(--e);background:transparent;color:var(--f);font:inherit;
text-align:left;cursor:pointer}
#tkList>.tkr:first-child,#tkList>.tkg:first-child,.tkg+.tkr{border-top:0}
.tkr:hover,.tkr.cur{background:var(--fh)}
.tkr[aria-disabled="true"]{opacity:.5;cursor:default}
.tkr[aria-disabled="true"]:hover{background:transparent}
.tkr img,.tkr svg{flex:none;border-radius:50%;margin-top:.1em}
.tki{flex:none;width:20px;height:20px;display:flex;align-items:center;justify-content:center}
.tkt{min-width:0;flex:1}
.tks{display:flex;align-items:baseline;gap:.45em}
.tks b{font-size:.85em;font-weight:600}
.tkn{font-size:.7em;color:var(--m);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.tkd{font-size:.66em;color:var(--m);line-height:1.45;margin-top:.15em;
display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.tkmark{flex:none;font-size:.75em;color:var(--m)}
.tkempty{padding:1.4em .8em;text-align:center;font-size:.72em;color:var(--m)}
.sc{margin-top:.7em;padding:.7em .8em;border:1px solid var(--e);font-size:.72em;color:var(--m);line-height:1.55}
.sc b{display:block;color:var(--f);font-size:1.15em;word-break:break-all}
.sc .scw{color:var(--w)}
.sc button{margin-top:.5em;padding:.4em .8em;font-size:1em}
body:has(.tkp:not(.hide))::before{content:"";position:fixed;inset:0;z-index:39;pointer-events:none;background:#0006}
@media(max-width:30em){#slipL span{display:none}}
@media(pointer:coarse){
#th,#lk,#lq,#ln,#wn,#pv{width:36px;height:36px}
#sd{width:44px;height:44px;top:.35em;right:.35em}
#addr{padding:.55em 0;margin:-.55em 0}
.tkb{padding:.7em 0 .7em .5em;margin:-.7em 0}
.flip{width:2.4em;height:2.4em;margin:-1.2em auto}
#slipL{padding:.5em 0;margin:-.5em .15em -.5em 0}
.chtog{padding:1em .1em}
.fclink{padding:.55em 0;margin:-.55em 0 -.55em .35em}
.docs summary{padding:1em .2em}
.tkp{position:fixed;left:0;right:0;bottom:0;top:auto;width:100%;max-width:none;
box-sizing:border-box;border-left:0;border-right:0;
max-height:82vh;border-bottom:0;
padding-bottom:env(safe-area-inset-bottom,0)}
.tkp::before{content:"";flex:none;width:2.2em;height:.25em;
background:var(--e);margin:.5em auto .1em}
.tkd{-webkit-line-clamp:1}
.tkr{padding:.75em .9em}
#tkFind{padding:.85em .9em;font-size:16px}.rcpt,.meta input,.dly input,.lqin{font-size:16px}.lnbtn{padding:.6em .8em}.p button,.o button,.lqbtn{padding:.6em .8em}.chtf button{padding:.5em}
.tabs button{padding:.95em .5em}
.dly{padding:.9em .8em}
.wnbtn{padding:.95em .8em}
#amt,#outAmt{padding:.3em 0}
.invh{font-size:11px;top:6px;left:10px;right:10px;gap:.3em;flex-wrap:wrap;row-gap:3px}
.invq{padding:10px 11px;margin:-10px -6px;opacity:1}
.invm,.invsh{padding:10px 3px;margin:-10px -1px}}
@media(pointer:coarse) and (max-width:24em){
.meta{gap:.12em}#th,#lk,#lq,#ln,#wn,#pv{width:32px;height:32px}#slip{width:2em}}</style>
<script>const H=document.documentElement;let LS;try{LS=localStorage}catch{LS={}}if(LS.t?LS.t=='d':matchMedia('(prefers-color-scheme:dark)').matches)H.className='d'</script>
<button id="sd" title="Sound" aria-label="Toggle sound"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M4 9.5v5h3.5L12 18V6L7.5 9.5H4z"/><path id="sdw" d="M15.5 9a4 4 0 0 1 0 6"/><path id="sdx" d="M16 9.5l4 4m0-4l-4 4"/></svg></button>
<a id="logoLink" href="https://zfi.wei.is/dao/#/dao/1/0x5E58BA0e06ED0F5558f83bE732a4b899a674053E" target="_blank" rel="noopener" aria-label="The DAO that governs this page" title="zFi DAO — the governance that owns this page's lineage"><svg id="logo" viewBox="0 0 400 400" aria-hidden="true"><clipPath id="lgf"><rect width="400" height="400"/></clipPath><g clip-path="url(#lgf)"><rect width="400" height="400"/><path d="M-60-20L460-20L460 90L80 310L460 310L460 420L-60 420L-60 310L320 90L-60 90Z"/></g></svg></a>
<svg id="fish" viewBox="0 0 24 14" fill="none" stroke="currentColor" stroke-width="1.1" stroke-linecap="round" aria-hidden="true"><path d="M2 12.2C7 .8 18 1.8 22 7"/><path d="M2 1.8C7 13.2 18 12.2 22 7"/></svg>
<div class="card">
<div class="meta"><span id="net" role="button" tabindex="0" title="Network"></span><span id="addr" role="button" tabindex="0" title="Connect a wallet">Connect</span><label id="slipL" title="Slippage"><span>Slippage </span><input id="slip" type="number" value="0.5" min="0.01" max="10" step="0.1">%</label><button id="lq" title="Liquidity for this pair" aria-label="Toggle liquidity mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3.2c3.6 4 6 7.2 6 10.1a6 6 0 0 1-12 0c0-2.9 2.4-6.1 6-10.1z"/></svg></button><button id="ln" title="Launch a coin" aria-label="Toggle launch mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/></svg></button><button id="wn" title="Claim a .wei name" aria-label="Toggle names mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 16 16" fill="currentColor" fill-rule="evenodd" shape-rendering="crispEdges" aria-hidden="true"><path d="M3 1h1v1H3zM5 2h1v1H5zM10 2h1v1h-1zM12 1h1v1h-1zM4 4h8v1H4zM3 5h10v1H3zM2 6h12v3H2zM3 9h10v1H3zM4 10h8v1H4zM5 11h2v1H5zM9 11h2v1H9zM2 12h3v1H2zM7 12h2v1H7zM11 12h3v1h-3zM1 13h2v1H1zM13 13h2v1h-2zM3 6h3v2H3zM10 6h3v2h-3z"/></svg></button><button id="pv" title="Bridge privately to Base or Robinhood" aria-label="Toggle private bridge mode" aria-pressed="false"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3l7 3v5c0 4.5-3 8.2-7 10-4-1.8-7-5.5-7-10V6l7-3z"/><path d="M9 12l2 2 4-4"/></svg></button><button id="lk" title="Copy shareable link" aria-label="Copy shareable link"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="9" y="9" width="12" height="12" rx="2.5"/><path d="M5.5 15H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h8a2 2 0 0 1 2 2v.5"/></svg></button><button id="th" title="Toggle theme" aria-label="Toggle theme"></button></div>
<div class="tabs" role="tablist"><button id="tabSwap" class="on" role="tab" aria-selected="true">Swap</button><button id="tabSend" role="tab" aria-selected="false">Send</button><button id="tabBook" role="tab" aria-selected="false">Orders</button></div>
<div class="panel"><div class="hdr"><small id="payL">You pay</small><span class="pill"><span id="fromIcon"></span><select id="fromSel" aria-labelledby="payL"></select><button type="button" class="tkb" id="fromPick" aria-haspopup="listbox" aria-expanded="false"><span class="tkbs"></span><span class="tkbc">▾</span></button></span></div><div class="row" id="amtRow"><input id="amt" type="text" inputmode="decimal" placeholder="0.0" aria-label="Amount to pay" autocomplete="off" spellcheck="false"></div><small id="bal"></small></div>
<button id="flip" class="flip" title="Flip tokens" aria-label="Flip tokens"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 5v14m-5-5 5 5 5-5"/></svg></button>
<div class="panel" id="rcvPanel"><div class="hdr"><small id="rcvHdr">You receive</small><span class="pill"><span id="toIcon"></span><select id="toSel" aria-labelledby="rcvHdr"></select><button type="button" class="tkb" id="toPick" aria-haspopup="listbox" aria-expanded="false"><span class="tkbs"></span><span class="tkbc">▾</span></button></span></div><div class="row" id="outRow"><input id="outAmt" type="text" inputmode="decimal" placeholder="0.0" aria-label="Amount to receive" autocomplete="off" spellcheck="false"></div><small id="bal1" class="hide"></small><small id="rate" role="status" aria-live="polite"></small><small id="rankBox" class="rank hide"></small></div>
<div id="lqPanel" class="hide"><div class="hdr"><small>Liquidity</small><small id="lqSub"></small></div><div id="lqList"></div></div>
<div id="lnPanel" class="hide">
<div class="hdr lnhdr"><small id="lnTitle">Launch a coin</small><small id="lnSub">0.4% of each trade goes to the creator</small></div>
<label class="dly" id="lnKindL" title="A coin opens a market: supply is minted and seeded into a pool that trades from the first block. A cause opens a fund: backers pay ether for a token that can always be burned back for its share of whatever the cause has not spent.">Launch<select id="lnKind"><option value="coin">Coin</option><option value="cause">Cause</option></select></label>
<label class="dly">Name<input id="lnName" maxlength="64" placeholder="Zero Cat" autocomplete="off" spellcheck="false"></label>
<label class="dly">Symbol<input id="lnSym" maxlength="16" placeholder="ZCAT" autocomplete="off" spellcheck="false"></label>
<label class="dly" id="lnSupplyL">Supply<input id="lnSupply" inputmode="decimal" value="1,000,000,000" autocomplete="off" spellcheck="false"></label>
<div class="lngap" id="lnMktL">Market</div>
<label class="dly" id="lnMcapL" title="The pool holds NO ether at launch - this is its virtual ether reserve, and it is the only thing deciding where on the curve your market opens. It sets the opening price and how hard that price is to move, which are the same knob: roughly this much buying moves the market halfway. Small numbers mean the first buyer takes a huge share of the supply.">Starting market cap<span class="lnu"><input id="lnMcap" inputmode="decimal" value="30" autocomplete="off" spellcheck="false"><i>ETH</i></span></label>
<label class="dly" id="lnAllocL" title="The percentage of total supply paid to the creator at launch. Everything else is seeded into the pool. Capped at 20%.">Creator keeps<span class="lnu"><input id="lnAlloc" inputmode="decimal" value="0" autocomplete="off" spellcheck="false"><i>%</i></span></label>
<label class="dly hide" id="lnGoalL" title="What the whole raise comes to. Ten million units are offered and the price is this divided by ten million, so a backer always knows what one unit costs and what it is a claim on.">Goal<span class="lnu"><input id="lnGoal" inputmode="decimal" value="10" autocomplete="off" spellcheck="false"><i>ETH</i></span></label>
<label class="dly hide" id="lnDaysL" title="How long backing stays open. After this, the sale closes and nobody else can buy in.">Runs for<span class="lnu"><input id="lnDays" inputmode="decimal" value="30" autocomplete="off" spellcheck="false"><i>days</i></span></label>
<label class="dly hide" id="lnVestL" title="How slowly the ether reaches you. The clock starts the moment you launch and runs whether or not anyone has backed you yet, so this wants to be well longer than the raise - what has not been released is exactly what a backer can still burn their token back for, and a release that finishes when backing closes leaves them nothing. The rate is your GOAL divided by this window, so a raise that falls short of the goal is emptied proportionally sooner - a tenth of the goal, a tenth of the window.">Released over<select id="lnVest"><option value="7889238">3 months</option><option value="15778476">6 months</option><option value="31556952" selected>12 months</option><option value="63113904">2 years</option></select></label>
<div class="lngap">Optional</div>
<label class="dly" id="lnArtRow" title="Held on-chain as contract code, not as a link. Nothing to pin and no gateway to go dark. Editable later, so a rough mark now costs nothing.">Logo<span><img id="lnPrev" alt=""><small id="lnArtNote">none</small><button type="button" id="lnPick" class="lnbtn">Choose</button></span></label>
<input id="lnArt" type="file" accept="image/png,image/jpeg,image/webp,image/gif,image/svg+xml" class="hide">
</div>
<div id="wnPanel" class="hide">
<div class="hdr lnhdr"><small>Claim a name</small><small id="wnSub">from 0.0005 ETH a year</small></div>
<label class="dly">Name<input id="wnName" maxlength="63" placeholder="human" autocomplete="off" spellcheck="false"></label>
<label class="dly" id="wnKindL">Ending<select id="wnTld"><option value="wei">.wei</option><option value="id">.id.wei — free</option></select></label>
<small id="wnNote" role="status" aria-live="polite"></small>
</div>
<div id="pvPanel" class="hide">
<div class="hdr lnhdr"><small>Private bridge</small><small id="pvSub">Shielded ether on Ethereum: exit to an L2, withdraw, or pay a request</small></div>
<label class="dly" title="Deposited into the confidential pool on Ethereum as a shielded note. Exit it later to an L2 address that nothing on-chain ties to this wallet. The amount, the L2 recipient and the timing of the exit are public; what is hidden is which deposit it came from, so the privacy is only as good as the number of other deposits in the pool.">Amount<span class="lnu"><input id="pvAmt" inputmode="decimal" placeholder="0.01" autocomplete="off" spellcheck="false"><i>ETH</i></span></label>
<label class="dly" title="Split the deposit into equal notes that can be spent one at a time. A withdrawal or exit always spends a whole note, so several small notes are what makes a partial spend possible later.">Notes<select id="pvSplit"><option value="1">1</option><option value="2">2</option><option value="4">4</option></select></label>
<label class="dly">Arrives on<select id="pvChain"><option value="8453">Base</option><option value="4663">Robinhood</option><option value="1">Ethereum (withdraw)</option></select></label>
<label class="dly" title="The address credited on the chain chosen above. Leave blank for this wallet. On Robinhood the unused gas budget refunds here too, so a contract wallet gets its refund at an aliased address.">Recipient<input id="pvTo" placeholder="0x… on that chain, defaults to you" autocomplete="off" spellcheck="false"></label>
<label class="dly" title="Relayed: the relay proves and settles the exit, paying its own gas out of a flat fee, so this wallet never signs the exit. Yourself: the relay only proves; you send one atomic transaction that exits and bridges, fee-free, but from this wallet.">Exit<select id="pvPath"><option value="relay">Relayed, for a fee</option><option value="self">Yourself, one tx</option></select></label>
<small id="pvKey" role="status"></small>
<div id="pvList"></div>
<small id="pvNote" role="status" aria-live="polite"></small>
</div>
<input id="rc" class="rcpt" placeholder="Recipient (optional) — 0x or .eth/.(g)wei" spellcheck="false" autocomplete="off">
<small id="rcvEl"></small>
<small id="fcEl" class="hide" role="status" aria-live="polite"></small>
<small id="cbEl" class="hide" role="status" aria-live="polite"></small>
<small id="lnNote" class="hide" role="status" aria-live="polite"></small>
<button id="lnGo" class="primary hide">Launch</button>
<button id="wnGo" class="primary hide">Register</button>
<button id="pvGo" class="primary hide">Deposit</button>
<div id="wnRollPanel" class="hide"><div class="lngap">roll.wei</div><small id="wnRollEl" role="status" aria-live="polite"></small><button id="wnEnter" class="wnbtn hide">Enter the roll</button></div>
<label class="dly hide" id="kindL">Order type<select id="kind"><option value="fixed">Fixed limit</option><option value="dutch">Dutch decay</option><option value="floor">Climbing bid</option></select></label>
<label class="dly hide" id="dlyL">SLOW<select id="dly"><option value="0">Instant</option><option value="600">10 minutes</option><option value="3600">1 hour</option><option value="86400">1 day</option><option value="259200">3 days</option><option value="604800">7 days</option></select></label>
<label class="dly hide" id="tipL" title="A time-locked transfer does not move on its own: once the lock expires somebody has to send the claim transaction. Tick this and the page posts a small ETH tip alongside the deposit, which pays a keeper to claim it for the recipient the moment it matures. The tip is refundable — if you reverse the transfer, the ether comes back to you.">Auto-claim<span class="tipr"><small id="tipNote"></small><input id="tipCk" type="checkbox" aria-describedby="tipNote"></span></label>
<label class="dly hide" id="fillL">Fill<select id="fill"><option value="1">Any amount</option><option value="0">All or nothing</option></select></label>
<label class="dly hide" id="floorL">Floor total<input id="floorAmt" inputmode="decimal" placeholder="0.0"></label>
<label class="dly hide" id="dRestL" title="A Dutch lot has TWO clocks. It decays to your floor over the window above, and then - unless it is given an end - it sits there fillable forever, until you spend gas cancelling. This is how long it rests at the floor before the escrow simply comes back to you.">Rest at floor<select id="dRest"><option value="0">Forever</option><option value="86400">1 day</option><option value="604800">1 week</option><option value="2592000">30 days</option></select></label>
<label class="dly hide" id="nftIdL">Token ID<input id="nftId" inputmode="numeric" placeholder="Any from collection" spellcheck="false" autocomplete="off"></label>
<label class="dly hide" id="ethModeL" title="Applies when YOU fill an order from the book, or cancel one of yours and the escrow comes back wrapped. It does NOT change what an order you place pays you: boards are denominated in WETH, and a maker's proceeds are transferred as WETH with no unwrap available - so a limit order asking for ETH is an order asking for WETH, and fills as such. Paying is not a setting either: the page spends whichever of your ETH and WETH covers it, and wraps the difference if only the two together do.">Fills & cancels pay<select id="ethMode"><option value="auto">ETH</option><option value="weth">WETH</option></select></label>
<small id="floorNote" class="hide" role="status" aria-live="polite"></small>
<button id="swap" class="primary">Connect Wallet</button>
<div id="listNote" class="hide" role="status" aria-live="polite"></div>
<div id="scEl" class="sc hide" role="status" aria-live="polite"></div>
<div id="wkWrap" class="hide"><div id="wkPanel" role="dialog" aria-modal="true" aria-labelledby="wkHdr"><div id="wkHdr">Choose a wallet</div><div id="wkList"></div></div></div>
<div id="tkPanel" class="tkp hide"><input id="tkFind" type="text" placeholder="Search name, symbol or address" autocomplete="off" spellcheck="false" aria-label="Filter tokens" role="combobox" aria-expanded="true" aria-controls="tkList" aria-autocomplete="list"><div id="tkList" role="listbox"></div></div>
<div id="stat" role="status" aria-live="polite"></div>
<button id="chTog" class="chtog hide" aria-expanded="false">Chart <span>▾</span></button>
<div id="chBox" class="hide"><div class="chtf" id="chTf"></div><div id="chArt"></div><small id="chNote"></small></div>
<div id="pos"></div>
<button id="bkTog" class="chtog hide" aria-expanded="true">Orderbook <span>▾</span></button>
<div id="book"></div>
<div id="docPanel" class="docs hide">
<details><summary>Swapping</summary>
<p>Every quote is compared across Uniswap v2, v3 and v4, Curve, Sushi, zAMM, Lido, our own Precision pools and the on-chain orderbook, and the best one wins. One route, one signature.</p>
<p>A handful of outside aggregators are asked too — 0x, 1inch, ParaSwap, KyberSwap, Enso, Bebop, OKX. They are compared on what they can be <b>held to</b>, not on what they advertise: a quote less its slippage, against the same number for the chain. One only wins if it beats the chain by a margin wide enough to cover the extra gas, and if none of them answer, nothing changes. Tap the <b>▾</b> beside the rate to see every venue that replied and what each was worth.</p>
<p>An outside route is executed through a small adapter that <b>measures what actually arrives</b> and reverts if it is less than your minimum. The adapter is named in this page's own bytes, so a bad answer from a node cannot swap it for another one.</p>
<p><b>Slippage</b> is the worst price you will accept — move past it and the trade reverts rather than filling badly. <b>Price impact</b> is how far your own size moves the price; the page warns as it grows and will not let a very bad one through quietly.</p>
<p>ETH and WETH are interchangeable here. The page wraps or unwraps whichever way the pool needs, inside the same transaction.</p></details>
<details><summary>Sending</summary>
<p>A plain transfer, or a <b>SLOW</b> one: time-locked, and reversible by you at any point until it matures. Useful for large amounts and for addresses you have not paid before.</p>
<p>A locked transfer does not deliver itself — someone must send the claim once it matures. Tick <b>auto-claim</b> and the page posts a small tip so a keeper does that for the recipient. The tip is refundable: reverse the transfer and it comes back.</p></details>
<details><summary>Orders</summary>
<p><b>Fixed limit</b> — a price you name. It rests until someone fills it or you cancel.</p>
<p><b>Dutch decay</b> — starts above your floor and falls to it over a window you choose. It has two clocks: the decay, then how long it rests at the floor before the escrow returns to you. Set that to <em>Forever</em> and it waits until you cancel.</p>
<p><b>Climbing bid</b> — the mirror: a bid that rises toward a ceiling you name.</p>
<p>Orders are escrowed on chain when placed, so a fill cannot fail for want of funds, and cancelling returns the escrow. Boards are denominated in WETH — an order asking for ETH is asking for WETH, and pays out as such.</p>
<p>The <b>book</b> under the form is live and takeable. Yours sit at the top; everyone else’s come cheapest first for the pair above, each row carrying its own unit price. <b>Fill</b> takes a single order, in part where it allows it. That same book is priced into every Swap quote — a swap that beats the pools on resting orders says <em>Orderbook</em> beside the rate, with the share it took.</p></details>
<details><summary>Liquidity <em><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 3.2c3.6 4 6 7.2 6 10.1a6 6 0 0 1-12 0c0-2.9 2.4-6.1 6-10.1z"/></svg></em></summary>
<p>Precision pools are <b>bands</b>, not endless curves. A position earns fees only while the price is inside its band, and holds one asset or the other at the edges. A wider band is safer and earns less; a narrow one is the opposite.</p>
<p>The bar on each row shows where the price sits in that band. <em>Out of range</em> means it is earning nothing right now.</p></details>
<details><summary>Launching a coin <em><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><circle cx="12" cy="12" r="3.5"/></svg></em></summary>
<p>One transaction, and no ether needed aside from gas. A fixed supply
t cpSettleWrap(n);pvAmt.value="";refreshBalance();
stat.textContent="Deposited. The relay is settling it into the pool.";sGot()};
const cpExit=async n=>{
const ch=+pvChain.value,to=pvTo.value.trim()||account,self=pvPath.value==="self",k=cpNoteOf(n);
if(n.ex&&n.ex.job&&n.ex.js!=="failed")throw Error("This note is already exiting.");
if(!isAddr(to))throw Error("The recipient must be a 0x address on "+CHAINS[ch].name+".");
if(!CP_L2[ch])throw Error("Unknown chain.");
const c=await cpSync(true),li=c.leaves.indexOf(k.leaf);
if(li<0)throw Error("This deposit has not settled into the pool yet.");
if(c.spent.includes(k.nu))throw Error("This note is already spent.");
if(c.leaves.length<20)pvNote.textContent="The pool holds only "+c.leaves.length+" notes, so an exit today is easy to link. Treat it as a test, not as privacy.";
const impl=await cpPins(ch);
const fee=self?0n:await cpFee(k.value);
if(!self&&fee>=k.value)throw Error("This note is too small to relay — submit it yourself instead.");
if(!self&&fee*10000n/k.value>300n)throw Error("The relay fee would be "+(Number(fee*10000n/k.value)/100).toFixed(1)+"% of this note — wait for cheaper gas, or submit it yourself.");
const net=k.value-fee,wei=net*CP_SCALE;
const code=await cfgRead("eth_getCode",[to,L]);
const x={ch,to,dl:String((Math.floor(Date.now()/86400000)+3)*86400),nonce:big(sha2(cpU8("zswap-exit-nonce-v1:"+cpFp+":"+n.i))).toString(),
fee:String(fee),wei:String(wei),self,fin:to};
if(strip0x(code||"")!==""){x.fin=cpRescue(n.i).addr;x.rk=1;
if(ch===4663)pvNote.textContent="That recipient has contract code on Ethereum, so Robinhood will refund unused gas to an aliased address rather than to it."}
if(ch===4663){const g=await cpRhGas(to);x.gl=String(g.gl);x.sc=String(g.sc);x.mf=String(g.mf);
const over=g.sc+g.gl*g.mf;if(over>=wei)throw Error("The note would be eaten by Robinhood's gas budget.");x.l2v=String(wei-over)}
const r=cpRecipe(x),esc=cpEscrow(impl,r);
const onchain=retAddr(await cfgRead(C,[{to:CP_ROUTER,data:"0x"+SEL_CPESCROW+pad32("20")+cpEncRecipe(r)},L]));
if(onchain!==esc)throw Error("Escrow address mismatch — refusing to build a proof.");
x.esc=esc;
const warn=await cpDry(x,r,esc);
const{root,path}=cpTree(c.leaves,li);
const dlu=String(Math.ceil((Math.floor(Date.now()/1000)+3600)/600)*600),cb=cpBinding();
const ctx=cpCtx("tacit-unwrap-intent-v1",cb,CP_ETH,pad32(strip0x(esc)),[[k.cx,k.cy,k.owner]],[k.value,fee,BigInt(dlu)]);
const sig=cpSigma(k.value,k.blinding,ctx,cpNonce(k.blinding,ctx,"unwrap"));
const op={chainBinding:cb,spendRoot:root,asset:CP_ETH,cx:k.cx,cy:k.cy,owner:k.owner,leafIndex:li,path,nk:k.secret,secret:k.secret,
value:String(k.value),recipient:esc,fee:String(fee),deadline:dlu,sigR:sig.R,sigZ:sig.z};
n.ex=x;cpSaveNotes();cpPaint();
const r2=await relayFetch("/confidential/submit",{type:"unwrap",op,memos:[],mode:self?"prove":"settle"});
if(!r2.jobId)throw Error("The relay did not take the exit.");
x.job=r2.jobId;x.js=r2.status||"pending";cpSaveNotes();
stat.textContent=(self?"Proving. The wallet will ask you to send the exit when the proof is ready.":"The relay is settling the exit. Activate it once it lands.")+(warn?" "+warn:"");
if(self)await cpSelfSend(n)};
const cpSelfSend=async n=>{const x=n.ex;
for(let t=0;t<180&&!(x.pv&&x.pr);t++){await new Promise(r=>setTimeout(r,5000));await cpRelayPoll(n);
if(x.js==="failed")throw Error("The prover failed: "+(x.jerr||"unknown"));
if(x.js==="settled")return cpPaint()}
if(!(x.pv&&x.pr))throw Error("The proof is taking a while — it keeps proving; come back and press Send.");
await checkWallet();
const req={from:account,to:CP_ROUTER,value:"0x0",data:cpExitData(x.pv,x.pr,cpRecipe(x)),gas:toHex(2000000)};
await rpc(C,[req,L]);
x.ptx=await sendTx([req]);cpSaveNotes();cpPaint();
await settle(x.ptx);stat.textContent="Bridged. It lands on "+CHAINS[x.ch].name+" in a few minutes."};
const cpActivate=async n=>{const x=n.ex,r=cpRecipe(x),now=Math.floor(Date.now()/1000);
if(now>Number(x.dl))throw Error("The exit expired — reclaim it instead.");
if(!await cpEnsureBalance(x.esc,BigInt(x.wei)))throw Error("The escrow is not funded yet — the relay has not settled.");
const req={from:account,to:CP_ROUTER,value:"0x0",data:cpActData(r),gas:toHex(CP_L2[x.ch].gas)};
await rpc(C,[req,L]);
x.atx=await sendTx([req]);cpSaveNotes();cpPaint();
await settle(x.atx);stat.textContent="Bridged. It lands on "+CHAINS[x.ch].name+" in a few minutes."};
const cpReclaim=async n=>{const x=n.ex,r=cpRecipe(x);
if(Math.floor(Date.now()/1000)<=Number(x.dl))throw Error("Reclaim opens after "+new Date(Number(x.dl)*1000).toLocaleString()+".");
const req={from:account,to:CP_ROUTER,value:"0x0",data:cpReclData(r),gas:toHex(400000)};
await rpc(C,[req,L]);
x.rtx=await sendTx([req]);cpSaveNotes();cpPaint();
await settle(x.rtx);stat.textContent="Returned to "+x.fin+" on Ethereum."};
const cpRecover=async()=>{const c=await cpSync(true);let found=0;
for(const[id,amt]of c.wraps){const wei=BigInt(amt);if(wei%CP_SCALE)continue;const v=wei/CP_SCALE;
if(cpNotes.some(n=>BigInt(n.v)===v&&cpDepId(v,cpDepCommit(...(k=>[k.cx,k.cy,k.owner])(cpNoteOf(n))))===id))continue;
for(let i=0;i<64;i++){const k=cpNoteOf({i,v:String(v)});
if(cpDepId(v,cpDepCommit(k.cx,k.cy,k.owner))===id){if(!cpNotes.some(n=>n.i===i))cpNotes.push({i,v:String(v),at:0,rec:1});found++;break}}}
cpNotes.sort((a,b)=>a.i-b.i);cpSaveNotes();return found};
const cpFmt=v=>trimAmt(BigInt(v)*CP_SCALE,18);
const cpPaint=()=>{if(!pvMode)return;
const on=!!cpSeed;
pvKey.innerHTML=!account?"Connect a wallet to begin.":!on?"Sign once to unlock the key that owns your deposits.":
`Key unlocked <button type="button" class="fclink" data-a="backup">backup</button><button type="button" class="fclink" data-a="import">import</button><button type="button" class="fclink" data-a="recover">recover</button><button type="button" class="fclink" data-a="export">export</button>`;
pvGo.textContent=!account?"Connect Wallet":!on?"Unlock key":"Deposit";
pvGo.disabled=!!cpBusy;
if(!on){pvList.innerHTML="";return}
const rows=cpNotes.map(n=>{const{s}=cpStatus(n),x=n.ex,ch=x?CHAINS[x.ch].name:"";
const act=s==="ready"?`<button type="button" class="fclink" data-a="exit" data-i="${n.i}">exit</button>`
:s==="pending"?(n.job?(n.js==="failed"?`relay failed<button type="button" class="fclink" data-a="settle" data-i="${n.i}">retry</button>`:"settling…")
:`<button type="button" class="fclink" data-a="settle" data-i="${n.i}">settle</button>`)
:s==="exiting"?(!x.job||x.js==="failed"?`relay failed<button type="button" class="fclink" data-a="exit" data-i="${n.i}">retry</button>`
:x.self?(x.pv?`<button type="button" class="fclink" data-a="send" data-i="${n.i}">send</button>`:"proving…"):"relaying…")
:s==="funded"?`<button type="button" class="fclink" data-a="activate" data-i="${n.i}">activate</button>`
:s==="expired"?`<button type="button" class="fclink" data-a="reclaim" data-i="${n.i}">reclaim</button>`
:s==="bridged"?`<a href="${CHAINS[x.ch].explorer}/address/${x.to}" target="_blank" rel="noreferrer">on ${ch}</a>`:s;
return`<div class="cpr"><span><b>${cpFmt(n.v)} ETH</b> #${n.i}${x&&s!=="bridged"?" → "+ch:""}</span><span>${act}</span></div>`}).join("");
pvList.innerHTML=rows||`<div class="cpr"><span>No deposits yet.</span></div>`};
const pvPaint=()=>{const on=pvMode;
pv.setAttribute("aria-pressed",on?"true":"false");
pv.classList.toggle("on",on);
for(const el of [rate,flip,swap])if(el)el.classList.toggle("hide",on);
for(const el of [document.querySelector(".panel"),rcvPanel])if(el)el.classList.toggle("hide",on);
for(const el of [pvPanel,pvGo])el.classList.toggle("hide",!on);
for(const el of [rc,rcvEl])if(el)el.classList.toggle("hide",on);document.querySelector(".card").classList.toggle("pvon",on)};
function pvSet(on){
if(on)cbDisarm();
++seq;quoting=false;
pvMode=on;
if(on&&lqMode)lqSet(false);
if(on&&lnMode)lnSet(false);
if(on&&wnMode)wnSet(false);
pvPaint();
stat.textContent="";pvNote.textContent="";
clearInterval(pvTimer);
if(on){pvTimer=setInterval(()=>{if(document.hidden||!pvMode)return;pvRefresh()},8000);pvWake()}
fcSync();}
const pvWake=()=>{if(!account){cpSeed="";cpNotes=[]}else if(!cpSeed){const s=cpKeyFor(account);if(/^0x[0-9a-f]{64}$/i.test(s||""))cpUse(s)}
if(account)pvTo.placeholder=account;cpPaint();pvRefresh()};
const pvRefresh=async()=>{if(!cpSeed||cpBusy)return;const my=++pvTick;
try{await cpSync(cpNotes.some(n=>/pending|exiting/.test(cpStatus(n).s)));
for(const n of cpNotes){const t=n.ex||n;if(t.job&&!["settled","failed","proven"].includes(t.js||""))await cpRelayPoll(n)}
if(my===pvTick&&pvMode)cpPaint()}catch(e){try{if(my===pvTick&&pvMode)pvNote.textContent=explain(e)}catch{}}};
const pvRun=async(fn,label)=>{if(cpBusy)return;cpBusy=1;pvGo.disabled=true;stat.textContent=label||"";
try{await checkWallet();await fn();cpSyncAt=0}catch(e){err(e)}
cpBusy=0;cpSaveNotes();cpPaint();pvRefresh()};
pvGo.onclick=async()=>{
if(pvGo.disabled)return;
if(!account)return void connect();
if(!cpSeed)return pvRun(cpUnlock,"Check your wallet for the message to sign.");
pvRun(cpDeposit,"Building the deposit…")};
pvList.onclick=pvKey.onclick=e=>{const b=e.target.closest("button[data-a]");if(!b)return;sBlip();
const a=b.dataset.a,n=cpNotes.find(x=>x.i===+b.dataset.i);
if(a==="backup"){prompt("Your private-bridge key. Anyone holding it can spend your deposits.",cpSeed);return}
if(a==="import"){const s=(prompt("Paste a private-bridge key (0x + 64 hex), or an exported note list.")||"").trim();
if(/^\[/.test(s)){let arr;try{arr=JSON.parse(s)}catch{}
if(!Array.isArray(arr))return void(stat.textContent="That is not a note list.");
let add=0;for(const r of arr)if(r&&Number.isInteger(r.i)&&/^\d+$/.test(r.v||"")&&!cpNotes.some(n=>n.i===r.i)){cpNotes.push(r);add++}
cpNotes.sort((a,b)=>a.i-b.i);cpSaveNotes();cpPaint();pvRefresh();stat.textContent=add?"Imported "+add+" note"+(add>1?"s":"")+".":"Nothing new in that list.";return}
if(!/^0x[0-9a-fA-F]{64}$/.test(s))return void(stat.textContent="That is not a key.");
try{LS[CP_KEY+account.toLowerCase()]=s.toLowerCase()}catch{}cpUse(s.toLowerCase());cpPaint();pvRefresh();return}
if(a==="export"){prompt("Your note records — keep them with the key. Exit recipes cannot be rebuilt without them.",JSON.stringify(cpNotes));return}
if(a==="recover")return pvRun(async()=>{const f=await cpRecover();stat.textContent=f?"Recovered "+f+" deposit"+(f>1?"s":"")+".":"Nothing new on chain for this key."},"Scanning the pool…");
if(!n)return;
if(a==="settle")return pvRun(()=>cpSettleWrap(n),"Asking the relay…");
if(a==="exit")return pvRun(()=>cpExit(n),"Building the exit…");
if(a==="send")return pvRun(()=>cpSelfSend(n),"Sending the exit…");
if(a==="activate")return pvRun(()=>cpActivate(n),"Activating…");
if(a==="reclaim")return pvRun(()=>cpReclaim(n),"Reclaiming…")};
pv.onclick=()=>{sBlip();pvSet(!pvMode)};
let invOff=null;
const invIcons=()=>{const o=[],seen=new Set();
for(const t of TOKENS){const k=(t.sym||"").toLowerCase();
if(!k||seen.has(k)||t.addr===ZERO)continue;seen.add(k);o.push({s:t.sym,h:t.icon,l:!!t.launched})}
return o};
const invWei=n=>`<svg width="${n}" height="${n}" viewBox="0 0 16 16" fill="currentColor" fill-rule="evenodd" shape-rendering="crispEdges"><path d="M3 1h1v1H3zM5 2h1v1H5zM10 2h1v1h-1zM12 1h1v1h-1zM4 4h8v1H4zM3 5h10v1H3zM2 6h12v3H2zM3 9h10v1H3zM4 10h8v1H4zM5 11h2v1H5zM9 11h2v1H9zM2 12h3v1H2zM7 12h2v1H7zM11 12h3v1h-3zM1 13h2v1H1zM13 13h2v1h-2zM3 6h3v2H3zM10 6h3v2h-3z"/></svg>`;
function invPlay(){
if(invOff)return;
const host=document.querySelector(".card");if(!host)return;
const W=host.clientWidth,H=host.clientHeight,COLS=6,ROWS=4,IW=22,IH=20,SW=22,SH=20;
const mk=(c,h)=>{const d=document.createElement("div");d.className=c;if(h)d.innerHTML=h;return d};
const w=mk("inv");w.tabIndex=0;w.setAttribute("role","application");
w.setAttribute("aria-label","Token invaders. Arrow keys move, space fires, escape leaves.");
host.appendChild(w);
const hud=mk("invh");w.appendChild(hud);
hud.addEventListener("pointerdown",e=>{const k=e.target.className;
if(k==="invq"){e.stopPropagation();e.preventDefault();sBlip();quit()}
else if(k==="invm"){e.stopPropagation();e.preventDefault();mint()}
else if(k==="invsh"){e.stopPropagation();e.preventDefault();sBlip();shareMsg="";shareScore()}});
const ship=mk("invs",ETH_ICON);w.appendChild(ship);
const pool=invIcons();
let al=[],bu=[],bo=[],sx=(W-SW)/2,lf=0,rt=0,sc=0,lv=3,wv=1,ov=0,dir=1,sa=0,fa=-1e9,rq=0,lt=performance.now();
let tEnd=0,minted="",minting=0,mintMsg="",shareMsg="",runId=0;
const pos=(n,x,y)=>{n.style.transform=`translate(${x.toFixed(1)}px,${y.toFixed(1)}px)`};
const cur=pool.filter(t=>!t.l),lau=pool.filter(t=>t.l);
const face=(r,c,i)=>{const back=r<ROWS-1;
const src=back?(cur.length?cur:pool):(lau.length?lau:(cur.length?cur:pool));
const t=src.length?src[(back?i:c)%src.length]:null;
return(!t||back&&i%3===0&&src.length<COLS*(ROWS-1))?{s:"WEI",h:invWei(20)}:t};
const WAVE_MS=45000;
const left=()=>Math.max(0,Math.ceil((tEnd-performance.now())/1000));
const born=()=>{for(const a of al)a.node.remove();al=[];
tEnd=performance.now()+WAVE_MS;
const px=(W-COLS*IW)/2;
for(let r=0;r<ROWS;r++)for(let c=0;c<COLS;c++){const t=face(r,c,r*COLS+c),n=mk("invx",t.h);
n.title=t.s;w.appendChild(n);al.push({node:n,s:t.s,x:px+c*IW,y:18+r*IH,c})}
dir=1};
let hudWas="";
const hud2=()=>{const t=left();
const html=(ov?`<span>game over \u00b7 ${sc}</span><span class="${minted||minting?"invmsg":"invm"}">`
+(minted?`${minted}`:minting?"minting\u2026":mintMsg?`${mintMsg}`:sc>0?"mint this score":"tap to play again")+`</span>`+(sc>0&&!minting?`<span class="invsh">${shareMsg||"share"}</span>`:"")
:`<span>${"\u25b2".repeat(Math.max(0,lv))}</span>`
+`<span${t<=10?' style="color:var(--w)"':""}>${t}s</span>`
+`<span>wave ${wv} \u00b7 ${sc}</span>`)
+`<span class="invq" title="Back to swapping (esc)">\u2715</span>`;
if(html===hudWas)return;
hudWas=html;hud.innerHTML=html};
const fire=()=>{const t=performance.now();
if(ov||bu.length||t-fa<120)return;fa=t;sPew();const n=mk("invb");w.appendChild(n);
bu.push({node:n,x:sx+SW/2-1,y:H-SH-12})};
const bomb=()=>{if(!al.length)return;const m=new Map();
for(const a of al){const p=m.get(a.c);if(!p||a.y>p.y)m.set(a.c,a)}
const f=[...m.values()],a=f[(Math.random()*f.length)|0],n=mk("invd");
w.appendChild(n);bo.push({node:n,x:a.x+IW/2,y:a.y+IH})};
const ht=(ax,ay,aw,ah,bx,by,bw,bh)=>ax<bx+bw&&ax+aw>bx&&ay<by+bh&&ay+ah>by;
const end=()=>{ov=1;sFail();hud2()};
const step=(dt,t)=>{
if(lf)sx-=190*dt;if(rt)sx+=190*dt;
sx=Math.max(0,Math.min(W-SW,sx));pos(ship,sx,H-SH-6);
const per=Math.max(90,620-(COLS*ROWS-al.length)*22-(wv-1)*60);
if(t-sa>per){sa=t;let e=0;
for(const a of al){const nx=a.x+dir*6;if(nx<0||nx+IW>W)e=1}
for(const a of al){if(e)a.y+=8;else a.x+=dir*6;pos(a.node,a.x,a.y)}
if(e)dir*=-1;
if(Math.random()<.5+wv*.08)bomb();
if(al.some(a=>a.y+IH>=H-SH-6))return end()}
if(tEnd&&performance.now()>tEnd){tEnd=performance.now()+WAVE_MS;lv--;sFail();
ship.classList.add("pop");setTimeout(()=>ship.classList.remove("pop"),200);
if(lv<=0)return end()}
for(const b of bu){b.y-=260*dt;pos(b.node,b.x,b.y)}
for(const b of bo){b.y+=110*dt;pos(b.node,b.x,b.y)}
for(let i=bu.length-1;i>=0;i--){const b=bu[i];
if(b.y+7<0){b.node.remove();bu.splice(i,1);continue}
const j=al.findIndex(a=>ht(a.x,a.y,IW-2,IH-2,b.x,b.y,2,7));
if(j>=0){const a=al[j],g=a.node;g.classList.add("pop");setTimeout(()=>g.remove(),120);
al.splice(j,1);sc+=a.s==="WEI"?25:10;b.node.remove();bu.splice(i,1);
if(al.length)sHit();else{sc+=100+left()*10;wv++;born();sGot()}}}
for(let i=bo.length-1;i>=0;i--){const b=bo[i];
if(b.y>H){b.node.remove();bo.splice(i,1);continue}
if(ht(sx,H-SH-6,SW,SH,b.x,b.y,2,6)){b.node.remove();bo.splice(i,1);lv--;
ship.classList.add("pop");setTimeout(()=>ship.classList.remove("pop"),200);
if(lv<=0)return end();sFail()}}
hud2()};
const loop=()=>{const t=performance.now(),dt=Math.min(.05,(t-lt)/1e3);lt=t;
if(!ov)step(dt,t);rq=requestAnimationFrame(loop)};
function quit(){if(rq)cancelAnimationFrame(rq);rq=0;w.remove();
document.removeEventListener("keydown",kd,true);document.removeEventListener("keyup",ku,true);
invOff=null}
const again=()=>{runId++;ov=0;sc=0;lv=3;wv=1;minted="";mintMsg="";shareMsg="";born();hud2()};
const shareScore=async()=>{
const what=(minted?`I scored ${sc} in zSwap invaders and claimed ${minted}`
:`I scored ${sc} in zSwap invaders`)+" \u{1F47E}";
const u="https://"+SHARE_HOST+(minted?"/#score="+minted:"");
const href="https://x.com/intent/post?text="+encodeURIComponent(what)+"&url="+encodeURIComponent(u);
let win=null;try{win=window.open(href,"_blank")}catch{}
if(win){try{win.opener=null}catch{}return}
shareMsg="copied";
try{await navigator.clipboard.writeText(what+" "+u)}catch{shareMsg="could not open x.com"}
hud2()};
const mint=async()=>{
if(!ov||minting||minted||!sc)return;
mintMsg="";
if(!account){mintMsg="connect to mint\u2026";hud2();
try{await connect()}catch{}
if(!account){mintMsg="wallet not connected";hud2();return}
mintMsg=""}
minting=1;hud2();
const r=runId;
try{await checkWallet();
const lbl=sc+"-w"+wv+"-"+Math.random().toString(36).slice(2).padEnd(4,"0").slice(0,4);
const lt=strTail(lbl),st=strTail(String(sc));
const req={from:account,to:SCOREM,value:"0x0",
data:"0x"+SEL_SCLAIM+pad32("60")+encUint(96+lt.length/2)+encAddr(account)+lt+st};
const ret=await rpc(C,[req,L]);
if(strip0x(ret||"").length!==64)throw Error("The minter did not answer \u2014 check the wallet is on Ethereum mainnet.");
await settle(await sendTx([req]));
if(r===runId){minted=lbl+".arcade.wei";sGot()}}catch(e){if(r===runId){try{console.error("zSwap mint failed:",e)}catch{}
mintMsg=String(isRejection(e)?"mint cancelled":explain(e)).replace(/[<>&"'`]/g,"").slice(0,60)||"the mint failed";sFail()}}
minting=0;hud2()};
function kd(e){if(e.key==="Escape"){e.preventDefault();quit();return}
if(e.key==="ArrowLeft"){lf=1;e.preventDefault()}
else if(e.key==="ArrowRight"){rt=1;e.preventDefault()}
else if(e.key===" "||e.key==="Enter"){e.preventDefault();ov?again():fire()}}
function ku(e){if(e.key==="ArrowLeft")lf=0;if(e.key==="ArrowRight")rt=0}
document.addEventListener("keydown",kd,true);document.addEventListener("keyup",ku,true);
let tp=0;
w.addEventListener("pointerdown",e=>{tp=1;sx=Math.max(0,Math.min(W-SW,e.offsetX-SW/2));ov?again():fire()});
w.addEventListener("pointermove",e=>{if(tp)sx=Math.max(0,Math.min(W-SW,e.offsetX-SW/2))});
w.addEventListener("pointerup",()=>{tp=0});
invOff=quit;born();hud2();w.focus();sOpen();rq=requestAnimationFrame(loop);
}
(()=>{let tm=0,hd=0;
const cn=()=>{if(tm)clearTimeout(tm);tm=0};
wn.addEventListener("pointerdown",()=>{hd=0;cn();tm=setTimeout(()=>{tm=0;hd=1;invPlay()},550)});
for(const ev of ["pointerup","pointerleave","pointercancel"])wn.addEventListener(ev,cn);
wn.addEventListener("click",e=>{if(hd){e.preventDefault();e.stopPropagation();hd=0}},true);
wn.addEventListener("contextmenu",e=>e.preventDefault());
})();
wnName.oninput=()=>{wnName.value=wnName.value.toLowerCase();wnQuote()};
wnTld.onchange=wnQuote;
wnEnter.onclick=async()=>{
if(!account)return void connect();
if(!wnEnter.dataset.id){wnRollEl.textContent="Type a paid .wei name you own in the box above, and it can enter the roll.";return}
wnEnter.disabled=true;
try{
await checkWallet();
const req={from:account,to:WROLL,value:"0x0",data:"0x"+SEL_RENTER+pad32(wnEnter.dataset.id)+encUint(0)};
await rpc(C,[req,L]);
await settle(await sendTx([req]));
await wnRoll();
}catch(e){err(e)}
wnEnter.disabled=false};
wnGo.onclick=async()=>{
if(wnGo.disabled)return;
if(!account)return void connect();
wnGo.disabled=true;
try{
await checkWallet();
const p=wnPend();
if(p){
if(!p.at){
if(Math.floor(Date.now()/1000)-(p.since||0)<=600)throw Error("The commitment has not confirmed yet.");
wnSave(null);stat.textContent="That commitment never reached the chain. Commit again.";
await wnQuote();return}
const age=Math.floor(Date.now()/1000)-p.at;
if(age<WN_MIN)throw Error(`Wait ${WN_MIN-age} more seconds.`);
if(age>=WN_MAX){wnSave(null);stat.textContent="That commitment lapsed. Commit again.";wnQuote();return}
const cost=await wnCost(p.label);
const req={from:account,to:WNS,value:toHex(cost),
data:"0x"+SEL_WREVEAL+pad32("40")+pad32(p.secret)+strTail(p.label)};
await rpc(C,[req,L]);
await settle(await sendTx([req]));
wnSave(null);
stat.textContent=`${p.label}.wei is yours.`;
wnName.value="";
}else if(wnQ&&wnQ.free){
const req={from:account,to:WREG,value:"0x0",
data:"0x"+SEL_WSUB+pad32(IDWEI)+pad32("40")+strTail(wnQ.label)};
await rpc(C,[req,L]);
await settle(await sendTx([req]));
stat.textContent=`${wnQ.label}.id.wei is yours.`;
wnName.value="";
}else if(wnQ){
const sec=wnSecret();
const req={from:account,to:WNS,value:"0x0",
data:"0x"+SEL_WCOMMIT+pad32(wnHash(wnQ.label,account,sec))};
await rpc(C,[req,L]);
wnSave({label:wnQ.label,secret:sec,at:0,since:Math.floor(Date.now()/1000)});
let h;
try{h=await sendTx([req])}
catch(e){
if(e&&(e.code===4001||/user (rejected|denied)|rejected the request|denied transaction/i.test(e.message||"")))wnSave(null);
throw e}
wnSave({label:wnQ.label,secret:sec,at:0,since:Math.floor(Date.now()/1000),tx:h});
try{await settle(h)}catch(e){if(/tx reverted/.test(e&&e.message||""))wnSave(null);throw e}
await wnStamp();
stat.textContent="Committed. Register the name in a minute.";
}
await wnQuote();await wnRoll();refreshBalance();
}catch(e){err(e)}
wnSync()};
lnGo.onclick=async()=>{
stat.textContent="";
if(lnGo.disabled)return;
if(!account)return void connect();
lnGo.disabled=true;
try{
await checkWallet();
if(lnKind.value==="cause")return await cauGo();
const name=lnName.value.trim(),sym=lnSym.value.trim().toUpperCase();
if(!name||!sym)throw Error("A name and a symbol, please.");
const utf8=v=>new TextEncoder().encode(v).length;
if(utf8(name)>64)throw Error(`That name is ${utf8(name)} bytes and the limit is 64.`);
if(utf8(sym)>16)throw Error(`That symbol is ${utf8(sym)} bytes and the limit is 16.`);
const supRaw=lnSupply.value.replace(/[, ]/g,"");
if(!/^\d+(\.\d+)?$/.test(supRaw)||!(Number(supRaw)>0))throw Error("Supply has to be a positive number.");
const supply=parseUnits(supRaw,18);
const typed=Number(lnAlloc.value||0);
if(!(typed>=0)||typed*100>LN_MAX_ALLOC)throw Error("Allocation runs from 0 to 20%.");
const alloc=Math.round(typed*100);
if(!(alloc>=0)||alloc>LN_MAX_ALLOC)throw Error("Allocation runs from 0 to 20%.");
if(typed>0&&alloc===0)throw Error("Allocation is priced in whole basis points — the smallest non-zero value is 0.01%.");
const mcap=parseUnits(String(lnMcap.value||"").trim(),18);
if(mcap<LN_MIN_MCAP)throw Error("That opening valuation is too small to price.");
const pooled=supply-supply*BigInt(alloc)/10000n;
if(pooled<LN_MIN_POOLED)throw Error("Too little supply reaches the pool to price a market.");
if(pooled>mcap*10n**36n)throw Error("That supply is too large for that opening valuation \u2014 raise the valuation.");
if(pooled*10n**24n<mcap)throw Error("That opening valuation is too large for that supply \u2014 lower it, or mint more.");
const owner=await lnOwner();
stat.textContent="Launching\u2026";
const req={from:account,to:PLAUNCH,value:"0x0",
data:encLaunch(name,sym,"",supply,BigInt(alloc),mcap,owner,lnArtBytes,lnArtMime)};
const sim=await rpc(C,[req,L]);
const candidate=/^0x[0-9a-fA-F]{128}$/.test(sim||"")?"0x"+strip0x(sim).slice(24,64):null;
const receipt=await waitTx(await sendTx([req]));sGot();
lnSet(false);
lnName.value="";lnSym.value="";lnSupply.value="";lnAlloc.value="";
const topic=kecStr("Launched(address,address,address,uint256,uint256,uint256)");
const lg=(receipt.logs||[]).find(l=>(l.address||"").toLowerCase()===PLAUNCH.toLowerCase()
&&(l.topics||[])[0]&&l.topics[0].toLowerCase()===topic.toLowerCase());
let token=lg?"0x"+strip0x(lg.topics[1]).slice(24):null;if(token&&!isAddr(token))token=null;
if(!token&&candidate){
try{
const c=await rpc(C,[{to:PLAUNCH,
"";
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"}
}
async function cbAct(){
if(!cbDao||cbAmt<=0n)return;
const b=document.getElementById("cbGo");if(!b||b.disabled)return;
const label=b.textContent;
try{
if(!account)return connect();
b.disabled=true;b.textContent=cbBuy?"Backing…":"Burning…";
const req=cbBuy
?{from:account,to:COFF,value:"0x"+cbCost.toString(16),
data:"0x"+SEL_BUY+encAddr(cbDao)+encUint(cbAmt)}
:{from:account,to:cbDao,value:"0x0",
data:"0x"+SEL_RAGEQUIT+encUint(96)+encUint(0)+encUint(cbAmt)+encUint(1)+encAddr(ZERO)};
if(!cbBuy&&cbShares&&cbTok){
let now2=null;
try{
const [b2,r2]=await Promise.all([
rpc("eth_getBalance",[cbDao,L]),
mc3Deep([{to:cbShares,data:"0x"+SEL_TSUP},{to:cbTok,data:"0x"+SEL_TSUP}])]);
const e2=BigInt(b2||"0x0"),s2=BigInt(r2[0]||"0x0")+BigInt(r2[1]||"0x0");
if(s2>0n)now2=e2*cbAmt/s2;
}catch{}
if(now2!==null&&now2<cbDue){
const lost=cbDue-now2;
if(now2<=0n)throw Error("The treasury has been drawn down since this was quoted \u2014 burning now would pay nothing.");
if(lost*100n>cbDue)throw Error(`This burn now pays ${trimAmt(now2,18)} ETH, not ${trimAmt(cbDue,18)} `
+`\u2014 the treasury was drawn down since it was quoted. Check the line again before burning.`);
}
}
await rpc(C,[req,L]);
await waitTx(await sendTx([req]));sGot();
if(cbBuy&&cbTok){
const kt=TOKENS.find(x=>x.addr&&x.addr.toLowerCase()===cbTok.toLowerCase());
if(kt){kt.cause=1;if(kt.cust)keepToken(kt)}
}
stat.textContent=cbBuy?"Backed · burn it back any time for what is left."
:"Burned · the ether is in your wallet.";
refreshBalance();cbSync();
}catch(e){err(e);b.disabled=false;b.textContent=label}
}
const LN_MIME={"image/png":0,"image/webp":1,"image/svg+xml":2,"image/gif":3,"image/jpeg":4};
const MIME_NAME=["image/png","image/webp","image/svg+xml","image/gif","image/jpeg","image/avif"];
const LN_MAX_ART=24575;
const LN_BUDGET=8192;
const lnCause=()=>lnKind.value==="cause";
const lnCap=()=>lnCause()?CAU_MAX_ART:LN_MAX_ART;
const lnBud=()=>lnCause()?CAU_MAX_ART:LN_BUDGET;
const LN_MAX_ALLOC=2000;
const LN_MIN_POOLED=2000000000000n;
const LN_MIN_MCAP=1000000000000n;
const lnHex=b=>{let h="";for(const x of b)h+=x.toString(16).padStart(2,"0");return h};
const lnTail=b=>{const h=lnHex(b);return encUint(b.length)+h.padEnd(Math.ceil(h.length/64)*64,"0")};
const encLaunch=(n,y,u,sup,alloc,mcap,own,art,mime)=>{
const T=new TextEncoder();
const dyn=[T.encode(n),T.encode(y),T.encode(u)];
const statics=[encUint(sup),encUint(alloc),encUint(mcap),encAddr(own)];
if(art){dyn.push(art);statics.push(encUint(mime))}
const words=statics.length+(art?4:3);
let off=words*32,head="",tail="";
const heads=[];
for(const b of dyn){heads.push(encUint(off));off+=lnTail(b).length/2;tail+=lnTail(b)}
head=heads.slice(0,3).join("")+statics.slice(0,4).join("")
+(art?heads[3]+statics[4]:"");
return "0x"+(art?SEL_LAUNCHART:SEL_LAUNCH)+head+tail;
};
async function lnOwner(){
const rv=rc.value.trim();
if(!rv)return account;
if(/^0x[0-9a-fA-F]{40}$/.test(rv)){
if(/^0x0{40}$/i.test(rv))throw Error("That cannot be the zero address.");
return rv;
}
if(!/\.(g?wei|eth)$/i.test(rv))throw Error("That must be an address, or a .eth / .wei name.");
rcvEl.textContent="resolving\u2026";
let r="";try{r=await nameFwd(rv.toLowerCase())}catch{r=""}
if(!r||/^0x0{40}$/i.test(r)){rcvEl.textContent="";throw Error("That name is not registered.")}
if(!isAddr(r))throw Error("That name resolved to something that is not an address.");
rcvEl.textContent=r;
return r;
}
const CSUM="0x00000000004473e1f31C8266612e7FD5504e6f2a";
const CMSUM="0x0000000000330B8df9E3bc5E553074DA58eE9138";
const CIMPL="643a45b599d81be3f3a68f37eb3de55ff10673c1";
const CRND="0x000000000011C799980827F52d3137b4abD6E654";
const COFF="0x000000A4Ad929C9E108aD2B1D2fBeDe0C2Ae57e1";
const CTAP="0x0000000060cdD33cbE020fAE696E70E7507bF56D";
const CLOOT="0x00000000000000000000000000000000000003ef";
const SEL_DAICO="4e1e3b11",SEL_SETALLOW="da46098c",SEL_CONFIGURE="fca53be5";
const CUNITS=10000000n,CONE=10n**18n,CMAX=(1n<<256n)-1n;
const CAU_MAX_ART=4096;
const CAU_MAX_DAYS=3650;
const cPad=h=>h.padEnd(Math.ceil(h.length/64)*64,"0");
const cTail=h=>{h=strip0x(h);return encUint(h.length/2)+cPad(h)};
const cCall=(to,data)=>encAddr(to)+encUint(0)+encUint(96)+cTail(data);
const cCalls=cs=>{let off=cs.length*32,head="",tail="";
for(const c of cs){head+=encUint(off);off+=c.length/2;tail+=c}
return encUint(cs.length)+head+tail};
const CLIMPL="6f1f2af76a3add953277e9f369242697c87bc6a5";
const cProxy=impl=>strip0x(keccak(hexToBytes(
"602d5f8160095f39f35f5f365f5f37365f73"+impl+"5af43d5f5f3e6029573d5ffd5b3d5ff3")));
const cLoot=dao=>{const d=strip0x(dao).toLowerCase();
return "0x"+keccak(hexToBytes("ff"+d+d+"0".repeat(24)+cProxy(CLIMPL))).slice(26)};
const cPredict=(owner,salt)=>{
const inner=encUint(96)+encUint(160)+strip0x(salt)
+encUint(1)+encAddr(owner)+encUint(1)+encUint(CONE);
const ss=strip0x(keccak(hexToBytes(inner)));
const ih=cProxy(CIMPL);
return "0x"+keccak(hexToBytes("ff"+strip0x(CMSUM)+ss+ih)).slice(26);
};
const encCause=(name,sym,uri,salt,owner,ben,dao,goal,rate,price,deadline,cap)=>{
const T=new TextEncoder(),Z=encUint(0);
const dyn=[lnTail(T.encode(name)),lnTail(T.encode(sym)),lnTail(T.encode(uri)),
encUint(1)+encAddr(owner),encUint(1)+encUint(CONE),encUint(0),
cCalls([
cCall(dao,SEL_SETALLOW+encAddr(COFF)+encAddr(CLOOT)+encUint(CMAX)),
cCall(COFF,SEL_CONFIGURE+encAddr(CLOOT)+encAddr(ZERO)+encUint(price)
+encUint(deadline)+encUint(cap))])];
const cfg=encUint(CONE)+encUint(604800)+encUint(259200)+encUint(CONE)+Z.repeat(17);
const tap=encAddr(CTAP)+Z+encUint(goal)+encAddr(ben)+encUint(rate);
let off=52*32,heads=[],tail="";
for(const d of dyn){heads.push(encUint(off));off+=d.length/2;tail+=d}
return "0x"+SEL_DAICO
+heads[0]+heads[1]+heads[2]
+encUint(1000)+encUint(1)+encAddr(CRND)+strip0x(salt)
+heads[3]+heads[4]+heads[5]
+cfg+Z.repeat(7)+tap+Z.repeat(8)+heads[6]+tail;
};
const cauURI=(name,sym)=>{
let img="";
if(lnArtBytes){let s="";for(const x of lnArtBytes)s+=String.fromCharCode(x);
img="data:"+MIME_NAME[lnArtMime]+";base64,"+btoa(s)}
return "data:application/json,"+encodeURIComponent(
JSON.stringify({name,symbol:sym,image:img,launchType:"cause"}));
};
function cauTerms(){
const g=String(lnGoal.value||"").replace(/[, ]/g,"").trim();
const d=String(lnDays.value||"").replace(/[, ]/g,"").trim();
if(!/^\d+(\.\d+)?$/.test(g)||!/^\d+(\.\d+)?$/.test(d))return null;
let goal;try{goal=parseUnits(g,18)}catch{return null}
const secs=BigInt(Math.round(Number(d)*86400));
if(goal<=0n||secs<3600n||Number(d)>CAU_MAX_DAYS)return null;
const price=goal/CUNITS;
if(price<=0n)return null;
const vest=BigInt(lnVest.value);
if(vest<=secs)return{err:`Backing runs for ${trimNum(Number(secs)/86400)} days but the ether is `
+`fully released in ${trimNum(Number(vest)/86400)} days — the treasury would empty before backing closes. `
+`Shorten the raise, or release over longer.`};
const rate=goal/vest||1n;
return{goal,secs,price,rate,vest,cap:(CUNITS-1n)*CONE};
}
const lnKindQuote=()=>lnKind.value==="cause"?cauQuote():lnQuote();
function cauQuote(){
lnGo.textContent=account?"Launch":"Connect Wallet";
const t=cauTerms();
if(!t){lnNote.textContent="";return}
if(t.err){lnNote.textContent=t.err;lnNote.style.color="var(--w)";return}
const per=(+formatUnits(t.price,18));
const tenthDays=t.rate>0n
?Number((t.goal/10n)/t.rate)/86400
:0;
lnNote.textContent=`1 ETH backs ${trimNum(1/per)} units`
+` · ${trimNum(+formatUnits(t.rate*86400n,18))} ETH a day at full funding, over `
+lnVest.options[lnVest.selectedIndex].textContent
+(tenthDays>0?` · at a tenth of the goal it empties in ${trimNum(Math.round(tenthDays*10)/10)} days`:"")
+` · backers burn back for the rest`
+(lnArtBytes?` · logo ${(lnArtBytes.length/1024).toFixed(1)} KB`
+(lnArtBytes.length>CAU_MAX_ART?` — over the 4 KB a cause allows, pick something simpler`:""):"");
lnNote.style.color=lnArtBytes&&lnArtBytes.length>CAU_MAX_ART?"var(--w)":"";
}
function cauKind(){
const on=lnKind.value==="cause";
for(const el of [lnMktL,lnSupplyL,lnMcapL,lnAllocL])el.classList.toggle("hide",on);
for(const el of [lnGoalL,lnDaysL,lnVestL])el.classList.toggle("hide",!on);
lnTitle.textContent=on?"Launch a cause":"Launch a coin";
lnSub.textContent=on?"backers burn back for whatever you have not drawn"
:"0.4% of each trade goes to the creator";
lnName.placeholder=on?"Feed Ducks":"Zero Cat";
lnSym.placeholder=on?"DUCK":"ZCAT";
rc.placeholder=on?"Beneficiary — defaults to you":"Creator — defaults to you";
rc.title=on?"Who the ether streams to. Leave blank and it is you. Worth setting to a multisig if the cause has one."
:"Who the token belongs to: receives the creator allocation, collects the creator share of trading fees for the life of the token, and may edit its name, symbol and logo. Leave blank and it is you. Worth setting to a multisig if the project has one.";
stat.textContent="";
on?cauQuote():lnQuote();
}
async function cauGo(){
const name=lnName.value.trim(),sym=lnSym.value.trim().toUpperCase();
if(!name||!sym)throw Error("A name and a symbol, please.");
const utf8=v=>new TextEncoder().encode(v).length;
if(utf8(name)>64)throw Error(`That name is ${utf8(name)} bytes and the limit is 64.`);
if(utf8(sym)>16)throw Error(`That symbol is ${utf8(sym)} bytes and the limit is 16.`);
if(lnArtBytes&&lnArtBytes.length>CAU_MAX_ART)
throw Error(`A cause's logo is stored in the DAO itself, so it is capped at 4 KB. `
+`That one is ${(lnArtBytes.length/1024).toFixed(1)} KB — pick something simpler.`);
const t=cauTerms();
if(!t)throw Error("A goal in ether, and how long it runs for — at least an hour, at most ten years.");
if(t.err)throw Error(t.err);
const ben=await lnOwner();
const owner=account;
const salt=wnSecret();
const dao=cPredict(owner,salt);
const deadline=BigInt(Math.floor(Date.now()/1000))+t.secs;
stat.textContent="Launching…";
const req={from:account,to:CSUM,
value:"0x"+t.price.toString(16),
data:encCause(name,sym,cauURI(name,sym),salt,owner,ben,dao,t.goal,t.rate,t.price,deadline,t.cap)};
await rpc(C,[req,L]);
await waitTx(await sendTx([req]));sGot();
lnArtBytes=null;lnArtNote.textContent="none";lnArtNote.classList.remove("set");
lnPrev.classList.remove("set");lnArt.value="";
let loot=cLoot(dao);
try{
const said=cbWord(await rpc(C,[{to:dao,data:"0x"+SEL_LOOTOF},L]));
if(isAddr(said||"")&&said.toLowerCase()!==loot.toLowerCase())loot=said;
}catch{}
try{const ix=await addCustomToken(loot);TOKENS[ix].cause=1;rebuild()}catch{}
stat.innerHTML=`${safeSym(sym)} is raising at `
+`<a href="${chainCfg().explorer}/token/${loot}" target="_blank" rel="noreferrer">${short(loot)}</a>`
+` · ${trimNum(+formatUnits(t.goal,18))} ETH over ${trimNum(Number(t.secs)/86400)} days`
+` · backers can burn back for whatever you have not drawn`;
}
let lnMode=false,lnArtBytes=null,lnArtMime=0;
function lnSet(on){
if(on&&PLAUNCH===ZERO)return;
if(on)cbDisarm();
++seq;quoting=false;
lnMode=on;
if(on&&lqMode)lqSet(false);
if(on&&wnMode)wnSet(false);
if(on&&pvMode)pvSet(false);
ln.setAttribute("aria-pressed",on?"true":"false");
ln.classList.toggle("on",on);
for(const el of [rate,flip,swap])if(el)el.classList.toggle("hide",on);
for(const el of [document.querySelector(".panel"),rcvPanel])if(el)el.classList.toggle("hide",on);
document.querySelector(".card").classList.toggle("lnon",on);
for(const el of [lnPanel,lnNote,lnGo])el.classList.toggle("hide",!on);
rc.value="";rc.classList.remove("bad");rcvEl.textContent="";
rc.title=on?"Who the token belongs to: receives the creator allocation, collects the creator share of trading fees for the life of the token, and may edit its name, symbol and logo. Leave blank and it is you. Worth setting to a multisig if the project has one.":"";
rc.placeholder=on?"Creator — defaults to you"
:"Recipient (optional) — 0x or .eth/.(g)wei";
stat.textContent="";lnNote.textContent="";
if(on)cauKind();
fcSync();
}
function lnQuote(){
lnGo.textContent=account?"Launch":"Connect Wallet";
const ns=v=>v.replace(/[, ]/g,"");
const sup=ns(lnSupply.value),mc=+ns(lnMcap.value),al=+ns(lnAlloc.value);
lnNote.textContent="";
if(!(Number(sup)>0)||!(mc>0))return;
const alBps=al>0?Math.round(al*100):0;
const pooled=Number(sup)*(1-alBps/10000);
if(!(pooled>0))return;
const sym=lnSym.value.trim().toUpperCase();
const poolFrac=99/(mc+.99);
const firstEth=poolFrac*pooled/Number(sup);
const firstTokens=poolFrac*pooled/100;
lnNote.textContent=`1 ETH buys ${firstEth>=10?Math.round(firstEth):firstEth.toFixed(firstEth<1?2:1)}% of all ${sym||"tokens"}`
+` \u00b7 opens at 1 ETH \u2248 ${trimNum(firstTokens)} ${sym||"tokens"}`
+(al>0&&alBps===0?` \u00b7 that keep is below one basis point \u2014 the smallest is 0.01%`:"")
+(alBps>0?` \u00b7 creator keeps ${trimNum(Number(sup)*alBps/10000)} (${alBps/100}%)`
+` \u00b7 fully diluted ${trimNum(mc*Number(sup)/pooled)} ETH`:"")
+(alBps>=1000?` \u00b7 that much can sell for most of the ether early buyers put in`:"")
+(lnArtBytes?` \u00b7 logo ${(lnArtBytes.length/1024).toFixed(1)} KB`:"");
lnNote.style.color=poolFrac>=10?"var(--w)":"";
}
const trimNum=n=>n>=1000?Math.round(n).toLocaleString("en-US")
:n>=1?n.toLocaleString("en-US",{maximumFractionDigits:4}):String(Number(n.toPrecision(4)));
const blobText=b=>b.text?b.text():new Promise((res,rej)=>{
const r=new FileReader();r.onload=()=>res(String(r.result));r.onerror=()=>rej(r.error);r.readAsText(b)});
const blobBytes=async b=>b.arrayBuffer?new Uint8Array(await b.arrayBuffer()):await new Promise((res,rej)=>{
const r=new FileReader();r.onload=()=>res(new Uint8Array(r.result));r.onerror=()=>rej(r.error);r.readAsArrayBuffer(b)});
async function lnPrepare(file){
if(/svg/.test(file.type)){
const b=new TextEncoder().encode((await blobText(file))
.replace(/<\?xml[^>]*\?>/g,"").replace(/<!--[\s\S]*?-->/g,"").replace(/<!DOCTYPE[^>]*>/gi,"")
.replace(/<script[\s\S]*?<\/script\s*>/gi,"").replace(/<script[\s\S]*$/gi,"")
.replace(/<foreignObject[\s\S]*?<\/foreignObject\s*>/gi,"").replace(/<foreignObject[\s\S]*$/gi,"")
.replace(/\son[a-z-]+\s*=\s*("[^"]*"|'[^']*'|[^\s>]*)/gi,"")
.replace(/\s(?:[a-z]+:)?href\s*=\s*("\s*javascript:[^"]*"|'\s*javascript:[^']*'|javascript:[^\s>]*)/gi,"")
.replace(/>\s+</g,"><").trim());
if(b.length>lnCap())throw Error(`That SVG is ${(b.length/1024).toFixed(1)} KB and the on-chain limit is ${Math.round(lnCap()/1024)} KB.`);
return{bytes:b,mime:LN_MIME["image/svg+xml"]};
}
const bmp=await createImageBitmap(file);
let smallest=null;
for(const side of [512,384,256,192,128,96,64]){
for(const [type,q] of [["image/webp",1],["image/png",undefined],["image/webp",0.85],["image/webp",0.6]]){
const sc=Math.min(1,side/Math.max(bmp.width,bmp.height));
const cv=document.createElement("canvas");
cv.width=Math.max(1,Math.round(bmp.width*sc));cv.height=Math.max(1,Math.round(bmp.height*sc));
const cx=cv.getContext("2d");cx.imageSmoothingQuality="high";
cx.drawImage(bmp,0,0,cv.width,cv.height);
const blob=await new Promise(r=>cv.toBlob(r,type,q));
if(!blob||blob.type!==type)continue;
const bytes=await blobBytes(blob);
if(!smallest||bytes.length<smallest.bytes.length)smallest={bytes,mime:LN_MIME[type]};
if(bytes.length<=lnBud())return{bytes,mime:LN_MIME[type]};
}
}
if(smallest&&smallest.bytes.length<=lnCap())return smallest;
throw Error("That image will not compress small enough. Flat colours and simple shapes do far better than photographs.");
}
lnPick.onclick=()=>lnArt.click();
lnArt.onchange=async()=>{
const f=lnArt.files&&lnArt.files[0];
if(!f)return;
lnArtNote.textContent="reading…";
try{
const r=await lnPrepare(f);
lnArtBytes=r.bytes;lnArtMime=r.mime;
lnArtNote.textContent=`${(r.bytes.length/1024).toFixed(1)} KB`;
lnArtNote.classList.add("set");
let b64="";for(const x of r.bytes)b64+=String.fromCharCode(x);
lnPrev.src="data:"+MIME_NAME[r.mime]+";base64,"+btoa(b64);
lnPrev.classList.add("set");
}catch(e){
lnArtBytes=null;lnArtNote.classList.remove("set");lnPrev.classList.remove("set");
lnArtNote.textContent="none";stat.textContent=e.message||String(e);
}
lnKindQuote();
};
const lnGroup=el=>{
if(!/^[\d, ]*(\.[\d ]*)?$/.test(el.value))return;
const dot=el.value.indexOf(".");
const intRaw=(dot<0?el.value:el.value.slice(0,dot)).replace(/\D/g,"");
const frac=dot<0?"":"."+el.value.slice(dot+1).replace(/\D/g,"");
const digitsBefore=el.value.slice(0,el.selectionStart??el.value.length).replace(/\D/g,"").length;
const out=intRaw.replace(/\B(?=(\d{3})+(?!\d))/g,",")+frac;
if(out===el.value)return;
el.value=out;
let seen=0,pos=digitsBefore?out.length:0;
if(digitsBefore)for(let i=0;i<out.length;i++){
if(/\d/.test(out[i])&&++seen===digitsBefore){pos=i+1;break}
}
try{el.setSelectionRange(pos,pos)}catch{}
};
lnSupply.oninput=()=>{lnGroup(lnSupply);lnQuote()};
for(const el of [lnName,lnSym,lnMcap,lnAlloc])el.oninput=lnKindQuote;
lnKind.onchange=cauKind;
for(const el of [lnGoal,lnDays])el.oninput=cauQuote;
lnVest.onchange=cauQuote;
ln.onclick=()=>{sBlip();lnSet(!lnMode)};
let wnMode=false,wnQ=null,wnSeq=0,wnRollSeq=0,wnTimer=0,wnRound=0n,wnStampTry=0,wnRollAt=0;
const wnOk=l=>/^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/.test(l)&&new TextEncoder().encode(l).length<256;
const wnFree=()=>wnTld.value==="id";
const wnOffer=(t,id)=>{wnEnter.classList.remove("hide");wnEnter.textContent=t;
wnEnter.dataset.id=id||"";wnEnter.classList.toggle("mut",!id&&!!account)};
const wnFull=l=>l+(wnFree()?".id.wei":".wei");
const wnSecret=()=>{const a=new Uint8Array(32);crypto.getRandomValues(a);let h="";for(const x of a)h+=x.toString(16).padStart(2,"0");return"0x"+h};
const wnHash=(l,o,sec)=>keccak(hexToBytes(pad32("60")+encAddr(o)+pad32(sec)+strTail(l)));
const wnKey=()=>WN_KEY+":"+(account||"").toLowerCase();
const wnStamp=async()=>{const p=wnPend();
if(!p||p.at||!account)return;
try{const r=await rpc(C,[{to:WNS,data:"0x"+SEL_WCOMMITS+pad32(wnHash(p.label,account,p.secret))},L]);
const t=Number(dec1(r||"0x0",0));
if(t)wnSave({...p,at:t})}catch{}};
const wnPend=()=>{try{const p=JSON.parse(LS[wnKey()]||"null");return p&&p.label&&p.secret&&Number.isFinite(p.at)?p:null}catch{return null}};
const wnSave=p=>{try{if(p)LS[wnKey()]=JSON.stringify(p);else delete LS[wnKey()]}catch{}};
const wnId=n=>namehash(/\.wei$/.test(n)?n:n+".wei");
const wnCostCall=l=>[{to:WNS,data:"0x"+SEL_WFEE+encUint(new TextEncoder().encode(l).length)},
{to:WNS,data:"0x"+SEL_WPREM+pad32(wnId(l))}];
const wnCost=async l=>{const [f,pr]=await mc3(wnCostCall(l));return dec1(f,0)+dec1(pr,0)};
const wnSync=()=>{
if(!wnMode)return;
const p=wnPend();
if(p&&!p.at){
if(!p.since){wnSave({...p,since:Math.floor(Date.now()/1000)});wnGo.disabled=true;wnGo.textContent="Confirming\u2026";return}
if(Math.floor(Date.now()/1000)-p.since>600){wnGo.disabled=false;wnGo.textContent="Commit again";return}
wnGo.disabled=true;wnGo.textContent="Confirming\u2026";return}
if(p){
const left=WN_MIN-(Math.floor(Date.now()/1000)-p.at);
wnGo.disabled=left>0;
wnGo.textContent=left>0?`Ready in ${left}s`:`Register ${p.label}.wei`;
if(Math.floor(Date.now()/1000)-p.at>=WN_MAX){wnGo.disabled=false;wnGo.textContent="Commit again"}
return}
wnGo.disabled=!wnQ;
wnGo.textContent=wnQ?(wnQ.free?`Claim ${wnQ.label}.id.wei`:"Commit"):"Register"};
const wnTicketOffer=async(l,seq,round)=>{
try{const[t]=await mc3([{to:WROLL,data:"0x"+SEL_RTICKET+encUint(round)+pad32(wnId(l))}]);
if(seq!==wnSeq)return;
const inAlready=!!dec1(t,0);
wnOffer(inAlready?`${l}.wei is already in`:`Enter ${l}.wei in the roll`,inAlready?"":pad32(wnId(l)));
}catch{if(seq===wnSeq)wnOffer(`Could not read the roll for ${l}.wei`)}};
const wnQuote=async()=>{
const my=++wnSeq;
wnQ=null;
wnOffer(account?"Enter a name you own":"Connect to enter");
wnSub.textContent=wnFree()?"free":"from 0.0005 ETH a year";
const l=wnName.value.trim().toLowerCase();
const p=wnPend();
if(p){wnNote.textContent=p.at?`${p.label}.wei is held for you — register it within 24 hours or the commitment lapses.`
:`Waiting for the commitment on ${p.label}.wei to confirm.`;wnSync();return}
if(!