#Changelog
All notable changes to this fork of 0xlocker/D17,
newest first. V15.1 is live on Ethereum mainnet and on Sepolia (testnet).
#2026-11-13 - Welcome page
- The site opens on a simple welcome page: Godspel, then D17 as its first project
with its key guarantees, and a button to the launches. The launch terminal moved to
/launches; old links still work, they are forwarded there.
- A dark mode toggle on the welcome and documentation pages, and a link back home from
the terminal.
- The user guide now says D17 runs on Ethereum mainnet, with Sepolia for testing.
- Built and checked on a preview link first, then approved by the project lead.
#2026-11-12 - V15.1 live on Ethereum mainnet
- D17 V15.1 is deployed on Ethereum mainnet (block 26397656, factory
0xB46eb48f982b63B879E00592880D12d1741fDB02). Protocol fee 2%, paid to a fee splitter with no owner
(0xAefA3a5A469d89Aa100b80628aE604BBA25eA22B) that splits it 75 / 25 between D17 buybacks and the
development fund; the fee settings and every factory owner key are renounced, so
nobody can change the rules. verify:factory: 48 checks out of 48. Addresses and
transactions in release/deployments/mainnet.json.
- Reviewed beforehand by the project lead's Solidity reviewer (no blocking issue); no
formal professional audit yet.
- Source code verified on Etherscan for the 8 mainnet contracts, so anyone can read
the code behind each address, for example the
factory.
verify:etherscan has a new --direct mode (the Etherscan API without Hardhat: every
contract sent at once, each answer printed, the rate limit respected) and a
--print-args option for a manual verification on etherscan.io.
- The site runs on mainnet: hosted on Cloudflare Pages (static build, security
headers in
apps/web/public/_headers), with its own domain and a dedicated paid RPC
restricted to that domain. It opens on Ethereum mainnet by default; Sepolia stays
available. Every mainnet launch opens at least 24 hours after its creation.
- Next: a first test launch on mainnet, then the announcement.
#2026-11-12 - Mainnet protocol fee: 2%
- The mainnet protocol fee is 2%, the contract's maximum, decided by the project lead:
more of every successful raise goes to the ecosystem (75% to D17 buybacks, 25% to the
development fund), for one point less in the pool (93 instead of 94 ETH per 100 raised
with a 5% creator share). It is frozen at deployment, like before. The Sepolia test
suite keeps 1%; nothing else changes.
- The project lead's review of V15.1 found no blocking issue. The liquidity left in pools,
pools opened with too few tokens, and two smaller points are kept for V16.
#2026-11-11 - Static export for an IPFS mirror
npm run build:ipfs -w @d17/web exports the whole site as static files
(apps/web/out), for a mirror on IPFS reached through an ENS name and eth.limo. Every
page works there as on Vercel: the launch list, launch pages, the expert view, the
create form, the docs and the blog (checked by serving the export locally).
- docs/IPFS_MIRROR.md: how to build it, pin it, point the ENS
name to it and allow it on the RPC.
- The Vercel build is unchanged.
#2026-11-11 - V15.1 redeployed on Sepolia with its own identity
- The V15.1 suite with the
V15_1 identity constants is live on Sepolia (block
12112991, factory 0x260cbEA5Af1669446353d2B80D75a145016C1228), with the mainnet settings: 1% fee
split 75 / 25, fee settings renounced, 45 checks out of 45, source code verified on
Etherscan (8 of 8). It is the exact source of the audit package. The site now reads it.
#2026-11-11 - V15.1 identity constants
- The contracts now identify themselves as V15.1: every identity constant ends in
V15_1 (D17_LAUNCH_V15_1, D17_FACTORY_V15_1, ...) instead of V15's V15_HARDENED,
so V15 and V15.1 contracts can never be mistaken for each other, and the reviewed
bytecode is the one that goes to mainnet. Deployment manifests say
"protocolVersion": "V15_1".
- The site checks the identity announced by the published manifest, so it keeps working
with the current Sepolia suite until the V15.1 one is redeployed.
- Requires one more Sepolia deployment. Every later contract version (V16 included) gets
its own review before mainnet.
#2026-11-11 - Audit package
docs/AUDIT.md, also on the site at /docs/audit: what a reviewer needs to audit
V15.1. Scope (12 contracts, about 2,360 lines of Solidity), compiler settings, how to
build and run every test suite, how the system works and who can do what, the
behaviour that is deliberate, the known limitations, every issue already found and
fixed, the areas we most want reviewed, and the Sepolia deployment to test against.
#2026-11-11 - V15.1 tested end to end on Sepolia
On the suite with the batch gas fix (factory
0xF93a425c0a2DDd4b57b57C6304AEB09ED2Dcb7cB):
- Successful launch with an absent buyer (ABS4). Two buyers in round 1 with odd wei
amounts (0.123456789012345678 and 0.050000000000000007 ETH). One collected, the other
stayed away. Trading stayed closed until "Collect for 1 buyer" settled the absent
buyer, then the pool opened. The absent buyer sent all of their tokens to their
wallet. The 1% fee, 0.001734567890123456 WETH, was split 75 / 25 to the wei, nothing
left in the splitter.
- Cancelled launch (CNL1, previous suite). Round 1 missed its minimum; one buyer had
taken their ETH back for the 1% fee. At cancellation both got everything back, the
take-back fee included.
- Two more launches were cancelled by the contract as designed, because of settings
left at the form's defaults (a 1 ETH minimum, then the advanced form's price floor);
the second case led to the fix below.
#2026-11-11 - Round 1 threshold shown right
Found by a Sepolia test launch created with the advanced form: round 1 raised 0.1735 ETH
against a 0.1 ETH minimum and the launch was still cancelled, while the page said "It
raised 0.1735 of the 0.1 ETH it needed".
- Round 1 has two floors: the minimum raise and a minimum price per token. The advanced
form kept a fixed default price floor (0.00000005 ETH per token), which on that launch
meant about 0.9 ETH. The contract did its job; the site misled.
- The advanced form's price floor now follows the round 1 minimum until it is edited,
and the form says how much ETH round 1 really needs, in red when the price floor asks
for more than the minimum.
- The launch page shows the real round 1 threshold: the larger of the minimum raise
and what the price floor requires.
#2026-11-11 - V15.1 redeployed on Sepolia with the batch gas fix
- The V15.1 suite with the batch gas fix is live on Sepolia (block 12108022, factory
0xF93a425c0a2DDd4b57b57C6304AEB09ED2Dcb7cB), with the mainnet settings: 1% fee split 75 / 25, fee settings renounced,
45 checks out of 45, source code verified on Etherscan (8 of 8).
- Test results on the previous suite: the cancelled launch returned everything to
both buyers, including the 1% take-back fee one of them had paid; the successful
launch kept trading closed while one buyer had not collected (the all-settled rule
works), but its batch settlement skipped that buyer for lack of gas, which led to the
fix above.
#2026-11-10 - Batch settlement gas fix
Found by the first Sepolia test launch of V15.1: "Collect for 1 buyer" went through but
skipped the absent buyer.
- The batch gave each buyer too little gas.
settleAfterGraceBatch forwarded at most
1,000,000 gas per locker; a settlement costs 0.5 to 0.7 million on a local chain but
about 1 million on Sepolia, so the call ran out of gas and the batch skipped it (by
design, a failing entry never blocks the others). Each locker now gets 3,000,000 gas,
and a batch holds at most 4 lockers so that a full batch (about 12.2 million gas) stays
under the 16,777,216 gas cap per transaction (EIP-7825). Settling one buyer directly
(settleAfterGrace) was never affected.
- New checks in
v15-1-regressions-e2e: each settlement must cost less than half of the
gas the batch gives it, a full batch must fit the per-transaction cap, a batch above
the maximum is refused.
- The create form stops at a 4 KB logo (the contract accepts 8 KB, but an 8 KB logo
needs about 17 million gas, above the per-transaction cap), and never asks for more gas
than the cap.
- The Sepolia suite must be redeployed with this fix.
#2026-11-10 - V15.1, complete: every buyer settled before trading
The project lead's own V15.1 review went further than ours. Its contract changes are now
in this repository, with the decided 1% fee instead of its fixed 2%. Details in
docs/V15_HARDENING.md.
- The pool's ETH is now exact. The contract keeps the exact sum of every buyer's
share, updated at each commitment and refund, instead of one percentage of the total.
The pool gets exactly the right amount, and the rounding gap that could block it is
gone at the root.
- Trading opens only once every buyer has collected. Nobody's liquidity reaches the
pool after the price moved, so nothing is burned anymore. Absent buyers cannot block
it: after the claim window anyone can collect for them, up to 4 at a time, and their
tokens still go to their own vault. The site shows "Collect for N buyers" until
everyone is settled, then "Open trading".
- A cancelled launch returns everything, including the take-back fees paid in round
1's window, which V15 burned.
- Mainnet launches require a frozen fee: the factory refuses to create one while the
fee settings still have an owner.
deploy:factory and check:env require
RENOUNCE_FEE_CONFIG_OWNER=1 on mainnet.
- Safer ordering of accounting before transfers in the personal vault and the fee
splitter.
- Tests: our suites adapted, plus the project lead's suites for cancelled-launch refunds,
hostile callbacks, minimum liquidity and the rounding model, against the real Uniswap
V2 contracts. Fuzz campaigns pass.
- Redeployed on Sepolia (block 12105659, factory
0x21ae6ae63526B9874A98e1B7C1eeC6AE8A90Fd79) with the mainnet settings:
1% fee split 75 / 25, fee settings renounced, 45 checks out of 45, source code
verified on Etherscan (8 of 8). It replaces the
first V15.1 draft deployed earlier the same day. Mainnet comes only after an
independent audit.
#2026-11-10 - V15.1 deployed on Sepolia
- The V15.1 suite is live on Sepolia (block 12104784, factory
0x8E9aC0baf0a6A59F35dEcDd3B25335979bad0624), deployed with the exact mainnet settings as a rehearsal: 1% protocol fee paid
to a fee splitter with no owner, split 75 / 25 between two test addresses, and the fee
settings renounced at the end of the deployment, so nobody can change them.
verify:factory: 45 checks out of 45. Addresses and transactions in
release/deployments/sepolia.json.
- The web app now reads this suite on Sepolia. Launches of the first V15 suite are no
longer listed.
- Source code verified on Etherscan for the 8 contracts of the V15.1 suite
(
verify:etherscan: 8 of 8).
#2026-11-09 - V15.1: two contract fixes
Two bugs reported by the project lead after the Sepolia test launches. Both were
reproduced on V15 by a new test suite before being fixed; details in
docs/V15_HARDENING.md.
- A pool could never open. Fees are taken per buyer and rounded down, while the
pool's expected ETH was computed once on the total, so the two could differ by a few
wei. When every buyer collected before the pool opened, the pool asked for more
tokens than were reserved and its creation reverted forever, leaving the tokens stuck
(they cannot move before the pool exists). Odd amounts were enough, and anyone could
cause it on purpose. The pool now never takes more than the reserved tokens.
- Supplies too large for Uniswap V2 are refused. Uniswap V2 cannot hold more than
2^112 - 1 raw units per pool (about 5 million billion tokens). A larger launch could
collect ETH and then fail to open its pool; launch creation now rejects it, and the
create form says so before anything is sent.
- New test suite
v15-1-regressions-e2e (10 assertions, part of npm test): odd amounts
with a take-back fee and every buyer collecting first, then the pool opens and tokens
move; supplies above the limit rejected; the largest allowed supply opens its pool.
- Known limit, for V16: liquidity of buyers who collect after the pool opened is added at
the market price and the unmatched part is burned (pool depth lost, not the buyer's
tokens).
- The launch rules, ABIs and contract identities are unchanged. V15.1 must be redeployed
on Sepolia and run through a full launch before mainnet.
#2026-11-09 - Predatory launch warning, fee settings renounced at deployment
- Predatory launches are flagged. The contract accepts any number of tokens in the
trading pool, from 1 up: a creator who pairs the ETH raised with very few tokens opens
trading far above what buyers paid, and the first sellers can then drain the pool.
When the opening price is at least 2x the average price paid, the launch page shows a
red "Predatory launch" warning (simple view) or a "Predatory" badge (expert view), and
"How this launch works" always says how the opening price compares with what buyers
paid. A contract rule is planned for V16.
- "Are you sure this token deserves to exist?" The create form now asks it before
every launch, and a predatory split needs its own explicit consent; the button stays
disabled until then.
RENOUNCE_FEE_CONFIG_OWNER=1 for deploy:factory: the fee config is renounced as
the last transaction of the deployment, so nobody (no key, no multisig) can change the
fee or its recipient afterwards. check:env, verify:factory (new check "fee config
ownership renounced") and the mainnet checklist handle it. Rehearsed end to end on a
local chain with mainnet's chain id: deployment, 48 checks out of 48, publication and
the Etherscan dry run.
- Mainnet fee split: 75% D17 buybacks, 25% development fund.
#2026-11-02 - Mainnet deployment checklist
docs/MAINNET_CHECKLIST.md: every step of the mainnet deployment in order, from
the Safe that owns the fee settings and two fresh deployer keys to the checks after
the deployment and switching the site to mainnet. Includes the gas measured on a full
run (about 15.5M for the factory key, 2.3M for the locker factory key) and the timing
rule: mainnet launches open at least 24 hours after creation.
check:env checks more before a deployment: the gas price now and whether each
key holds enough ETH for its part (30% margin), and on mainnet the official WETH and
Uniswap V2 router addresses, that the fee owner is a contract (the Safe), the
confirmation flag and the Etherscan key.
#2026-10-26 - Etherscan source verification command
Done on Sepolia: the 8 contracts of the suite are verified on Etherscan (fee
splitter, fee config, factory, token factory, liquidity vault factory, launch deployer,
launch factory, locker factory). Their source code is readable on each address's
"Contract" tab.
npm run verify:etherscan -w @d17/contracts publishes the source code of the
deployed suite on Etherscan (Sepolia by default, -- --network mainnet later), so
anyone can read the code behind each address. Addresses come from
release/deployments/<network>.json; each contract's constructor arguments are read
back from its own deployment transaction, after checking that the local build
produces exactly the deployed bytecode. -- --dry-run runs every check and sends
nothing. Needs a free ETHERSCAN_API_KEY in contracts/.env, no private key.
#2026-10-26 - Docs, blog and progress on the site
- The documentation is now part of the web app, on the same domain as the launches:
/docs (user guide, contracts explained, technical reference, V15 changes,
self-hosting), /blog (the nine essays) and /progress (where the project
stands, what comes next, and this changelog). A "Docs" link sits in the app's header.
- The pages are built from the markdown in
docs/ and CHANGELOG.md: links between
documents open the matching page, links to other repository files open them on GitHub.
npm run content:sync refreshes the copy the web app reads, and check:release fails
if it is out of date.
- New
docs/PROGRESS.md: done, next, status.
- The guide no longer points to the upstream testnet site, and the blog's footer says V15
is deployed on Sepolia.
#2026-10-23 - Wording: defection cost, no "Launchpad"
- The fee for taking ETH back after round 3 or 4 is now called the defection cost
everywhere (expert view, guide, contract docs, blog), instead of "deflection".
The round table's column reads "Defection cost". The simple view keeps its plain
"take-back fee".
- The word "Launchpad" is gone from the header of the simple view and the
create-a-launch page: D17 is a protocol. The expert view keeps "Launch terminal".
#2026-10-22 - Simple view opens on the list of launches
- The simple view now opens on the list of launches, newest first, instead of
picking a launch for the visitor. Click a launch to see its rounds and take part;
"← All launches" at the top of a launch goes back to the list. A link to a
launch (
?launch=0x...) still opens that launch directly. The expert view keeps
opening on the newest launch.
- The list rows are highlighted on hover and show "Opening…" right after a click.
- While the list is read from the chain the page says "Looking for launches…"; with
no launch yet it links to "Create a launch".
#2026-10-21 - First full launch on Sepolia
- A complete launch ran on Sepolia, from creation to trading: round 1 reached its
minimum (0.25 ETH), the sale was closed, buyers collected their tokens (one of them
after trading opened), the official pool was created and tokens were sent to wallets.
- The protocol fee reached the fee splitter and was split exactly 50 / 12.5 / 12.5 /
25 between four test addresses, twice, with
distribute:fees.
#Fixed on the way
- Free RPC plans cap
eth_getLogs ranges and request bursts. The web app now shrinks
log ranges when refused, retries rate-limited reads, queues its RPC calls (4 at a time)
and reads a launch's history from its own creation block.
- A wallet with several vaults saw "You did not take part": the app now picks the vault
holding a position in the launch, and forgets the previous account's vault when the
wallet switches accounts.
#Added
npm run inspect:launch -w @d17/contracts -- <launch or token> [wallet] prints a
launch's phase, raise and protocol fee, and optionally a wallet's vaults and positions.
apps/web/.env.example recommends an RPC that accepts wide log ranges (PublicNode).
#2026-10-21 - Fee distribution command
npm run distribute:fees -w @d17/contracts splits the fees held by the published
D17FeeSplitter between its recipients and prints what each one received. Anyone can
run it, it only pays gas.
#2026-10-20 - Deployed on Sepolia
- The V15 suite is live on Sepolia (block 11958083), deployed with
deploy:factory
and checked by verify:factory: 45 checks out of 45 passed, every factory owner
key renounced.
- Factory
0xF02Ab7326c4212693a7a53Aa7BBa635213749C07, protocol fee 1%, paid to a
D17FeeSplitter (0x6ad3FAF084f554aAd54A47134bf168c4FAe9Ab23) that splits it
50 / 12.5 / 12.5 / 25 between four test addresses, the same layout planned for
mainnet. Every address and transaction is listed in release/deployments/sepolia.json.
- Fixed before deploying:
hardhat compile could fail with HHE905 on a machine without
a cached compiler (the undici override was too old for Hardhat 3.18).
#2026-10-20 - Fee splitter
#Added
D17FeeSplitter, an optional recipient for the protocol fee. It has no owner and
no setter: its recipients (1 to 5) and their shares (adding up to 100%) are fixed when
it is deployed and readable on-chain. Anyone can trigger the split; the fee arrives in
WETH, plain ETH is wrapped first, and the last recipient gets the rounding remainder so
nothing stays behind. The launch contracts are unchanged: the splitter is simply set as
the fee recipient.
deploy:factory deploys it when FEE_SPLIT_RECIPIENTS and FEE_SPLIT_BPS are set,
verify:factory checks it (identity, recipients, shares, fee config pointing to it).
- New test suite
fee-splitter-e2e (25 assertions): constructor rules, then a real
launch whose 1% fee lands in the splitter and is split 50/25/25, odd amounts, plain ETH
and other tokens. A full local deployment dry run passes all 45 verification checks.
npm run check:env -w @d17/contracts checks contracts/.env before a deployment
without printing any key or the RPC URL: network, address and balance behind each key,
fee settings and fee split, with a plain-English message for each problem found.
#2026-10-14 - Round changes shown on time, floor target in the chart
#Fixed
- Round changes could show late in the local demo and with a direct RPC. The page
re-read the chain every 15 seconds only, so with short rounds (the demo's 60-second
rounds) the "Put in" button could appear late, and a hidden tab (slowed down by the
browser) could miss a whole round. The page now re-reads the chain right after each
round and take-back window ends, and as soon as the tab is visible again. Checked with
one page left open through all 5 rounds: every round, round 4 included, accepts ETH.
#Added
- Why a round's bar stays at the floor: under the chart, the open round shows how
much ETH it needs before its price rises above the round-1 price, for example "Round
5 stays at the round-1 price until 0.6 ETH comes in (0.1 ETH so far)". Round 5 needs
more than the others: it also sells every token rounds 2 to 4 did not sell.
#2026-10-14 - Collecting after trading opens
#Fixed
- Tokens not collected before trading opened could no longer be collected from the
app. The contract allows it (late settlement) but the app hid the button once
trading was open and said "You have no tokens waiting in your vault". Trading open
now shows "You have about X to collect" with "Collect my tokens", then "Send my
tokens to my wallet". Same fix in the expert view.
- "Open trading" could fail with
NO_SETTLED_LIQUIDITY when nobody had collected
yet: the contract only opens trading once at least one buyer has collected. The page
now says so and shows the button only when it can work.
- After the sale, a round with no buyers reads "no buyers" in the chart instead of "no
price yet".
- The chart's "Round 1 price (the floor)" label sat on top of the last bar; it is now
on the left, above round 1 (which is always at the floor), and reads "Floor" on
phones.
#Added
- "Show X in my wallet" once the tokens are in the wallet: wallets only list the
tokens they were told about, this asks the wallet to add it.
- On test networks, the trading card explains that the token now trades in its
official pool and points to the expert view (on mainnet it links to Uniswap).
#2026-10-14 - Price per round chart and UX pass
#Added
- Price per round chart in the simple view, shown from round 1 onward. One bar per
round with the price everyone paid in it, a dashed line for the round-1 price (the
floor no round can go under), the current round highlighted, rounds not priced yet
drawn as outlines. Hover or tap a bar for its price and the ETH raised. Readable on
phones (labels R1 to R5) and backed by a table for screen readers. It is a price per
round, not a trading chart: the token only trades once the sale is over.
#Fixed
- Round 1 minimum was always read as 0 by the web app, on both the RPC and the API
paths:
roundAnchorTargetWeth(0) is always 0 because round 1 sets the anchor. The
minimum now comes from minPhase1Weth. Before this fix none of the "round 1 needs at
least X ETH" messages could appear.
- Take-back window after a round 1 that missed its minimum: the page now says the
launch will be cancelled and that everyone gets all of their ETH back with no fee,
warns not to take ETH back (it would cost the 1% fee for nothing), and step 3 reads
"Wait for the cancellation" instead of "Take back my ETH".
- Round 1 token estimate: no longer shows "about 0" while round 1 is open; it
estimates against the minimum and says "if round 1 reaches its minimum".
- Cancelled launches: "You put in X ETH: all of it comes back to you", then after
"Get my ETH back" a clear last step to send the ETH to the wallet, then "Your ETH is
back in your wallet".
- After a take-back: confirms it and points to the last step (send the ETH to the
wallet).
#Changed
- Transaction notifications in plain words in the simple view ("Put in ETH", "Collect
your tokens", "Done", "...: it did not go through").
- "Wallet connected" message no longer says "You can now take part" on launches that
are over or cancelled.
- No wallet in the browser: the page says to install MetaMask or open it in the
wallet app's browser.
- Create-a-launch form: problems are listed in the order of the form's steps.
#2026-10-07 to 2026-10-11 - Simple interface and local demo
#Added
- Simple view, now the default participant view: one column in plain language, the
current step in one sentence, one action at a time (connect wallet, create personal
vault, put in ETH, take it back, collect tokens, send them to the wallet) and the
launch's rules in everyday words. The full terminal stays one click away (Expert
view, or
?view=expert); the choice is remembered.
- Guided "Create a launch" form, now the default deploy page: duration presets,
safe defaults, the round-1 floor price computed automatically, plain-language error
messages. The full form stays available.
- Automatic network switch: the wallet is asked to switch to the page's network
(and, for the local demo only, to add it) instead of failing.
- Free local demo (
npm run demo:local): a local chain, the V15 contracts, demo
launches with bots, and the web app, without spending any ETH.
npm run demo:fund -- 0xAddress [amount] credits test ETH to any address.
- Send ETH back to the wallet after a take-back or a cancellation (out of the
vault, then WETH to ETH).
- Mainnet minimums test: a chain-id-1 simulation checks the 24-hour notice and the
1-hour minimum windows.
#2026-10-06 - V15 contracts
Hardened launch rules and a published protocol fee. Every rule change, with the V14
value next to the V15 one, is in docs/V15_HARDENING.md. In
short:
- Protocol fee 0-2%, fixed per launch at creation, charged only on successful
settlement, capped by the creator (
maxProtocolFeeBps).
- Creator treasury share capped at 10% (was 20%); at least 88% of every successful
commitment is locked in the official pool (was 80%).
- Take-back fees go into the official pool, never to the creator: 1% after rounds 1-2,
1-25% after rounds 3-4. Fees on a cancelled launch are burned.
- No round, the final one included, sells below the round-1 price; unsold tokens are
always burned.
- LP allocation scaled to the share actually sold; late liquidity added at the pair's
live ratio.
- Creator tokens vested over 180 days from pool creation.
- Launch creation reverts until every factory owner key is renounced.
- Mainnet launches start at least 24 hours after creation, every window at least 1
hour.
- Bugs fixed: factory at the contract size limit (creation moved to
D17LaunchDeployer), a last late settler that could revert forever
(LP_RESERVE_EXCEEDED), dust positions reverting in the pair's mint.
- Stateful fuzz and invariant suite added.
Based on upstream commit 57a63ab (D17 V14.1, 2026-07-20). Original MIT license
kept in LICENSE.