Chain
Contract addresses
The current deployment. The authoritative, machine-readable copy isdeployments/4663.json in the public repository; read it rather than hardcoding addresses, since a future deployment would ship a new file at the same path.
How a launch appears on-chain
A new coin is created byFamilyFactory.registerCandidate(name, symbol, uri, maxBond), which mints the token, initializes its pool and emits one event in the same transaction:
candidateId is a global, monotonically increasing id across every round. The event does not carry the coin’s name, symbol, creator or art: read those separately.
Token metadata
Every launch is an ERC-20 clone (FamilyToken) with a fixed supply and no owner. Read its metadata directly off the token:
name()andsymbol()— set once at registration, never change.uri()— an off-chain metadata pointer, immutable after registration. On the current deployment this is the coin’s art image URL. It is not the same as the creator-editable links described below, and it cannot be updated if a URL goes stale.tokenURI()andmetadataURI()— the two getter names trading terminals actually look for. Both return the same value: a JSON metadata document URL built as<metadata base>+ the token’s own lowercase hex address (no checksum casing), where<metadata base>is a fixed prefix set once when the whole deployment was created. Fetching that URL returns:
name and symbol are read live off the token; image falls back to uri() when no creator link has been set (and is only ever an https: URL); description, website and twitter come from the creator-editable links below when set, otherwise blank or null. The document is served with a public CORS header and a short cache, so a terminal can poll it freely.
The coin’s creator is not in CandidateCreated; read RoundManager.creatorOf(token), or match the token address against the RoundManager.CandidateRegistered event in the same block, whose creator field carries it directly.
The pool
Every coin trades against exactly one other coin, its parent, in a single Uniswap v4 pool. The pool’s key is:tokenIsCurrency0 in CandidateCreated says which side the new coin landed on. The pool id is keccak256(abi.encode(poolKey)) — the same value already emitted as poolId, so it never needs to be recomputed from scratch.
The parent currency. While a coin is a candidate, its parent is the round’s head coin: the coin at the top of the chain when the round opened (RoundManager.RoundOpened.parentToken, see below). If the coin wins its round and becomes a link in the chain, that same coin stays its parent permanently — a link’s parent never changes after it is crowned.
Price. Read sqrtPriceX96 from the pool’s slot0 (via the PoolManager or the StateView helper above, keyed by poolId). The raw price is (sqrtPriceX96 / 2^96)^2, which is currency1 per unit of currency0. To get the coin’s price in its parent:
Rounds
A round is a timed contest that adds the next coin to the chain. Every candidate in a round trades against the same parent, the current head coin.1
Registration
Anyone can register a candidate by posting a bond.
RoundManager.RoundOpened fires once, when the first candidate of a new round registers.2
Trading
All candidate pools trade against the head coin for a fixed window.
3
End
The round’s real end is settled on-chain, either from a public randomness beacon (
RoundManager.EndFulfilled) or, if the beacon was never relayed in time, deterministically at the planned end (RoundManager.RandomEndUnavailable).4
Finalize
RoundManager.RoundFinalized(roundId, hasWinner, winnerCandidateId, ...) names the winner, if any. The winning candidate is then written into the chain permanently via RoundManager.HeadChanged(index, token, parent), which also carries the coin’s numbered position in the chain.- No
RoundFinalizedyet for its round → candidate, still in registration or trading. RoundFinalizedfired,hasWinnertrue andwinnerCandidateIdmatches → link, permanently in the chain (its index is in the matchingHeadChanged).RoundFinalizedfired and it did not win → lost, it keeps its pool and keeps trading against its parent, but it never gets a chain position.
Opening price
Every coin’s opening market cap is anchored in ETH terms and set once, at registration, onFamilyFactory.
sqrtP is the reference price read from the oracle at that moment, D is the $DOLL-denominated target it implies, A is the coin’s opening market cap after the parent-relative clamp, and B is the curve basis passed into the sale curve. flags records which parts of the pricing path fired; the same value is readable later per round with FamilyFactory.roundStartFlags(roundId).
The high byte of
flags carries the oracle’s own status at read time:
After ten clean minutes of samples (no seed and no step-limited sample in the last ten minutes) the
oracle also prices with status 8 alone (slow average short): it uses the last ten minutes’ average,
and the 24-hour average joins once it exists. Before those ten clean minutes, and after any
step-limited sample within the last ten minutes, a coin opens on the fallback instead.
FamilyFactory.curveBasisOf(token) returns the stored curve basis B for a launched coin; BidDeployer reads it to size bids so a chain-coin burn never drifts a pool’s curve. FamilyFactory.startFdvOf(token) returns the coin’s opening market cap.
FamilyFactory.bindVenueOracle(oracle) binds the price oracle once. It is permissionless, but only succeeds when the given address’s code hash, venue pool id and pool manager all match the values fixed at deployment, so a look-alike contract can never be bound. Before it is bound, and whenever a read fails, every coin opens on the constant fallback described above, flagged in StartPriced.
The oracle itself exposes two read-only views:
startPrice() returns the price used to anchor new coins and a status byte (0 means every average was available and fresh); latest() returns the most recently stored, step-limited price and how long it has been running in the same direction.
Fees
Every Dollhouse pool takes two fees on a trade, both denominated in the coin the pool is priced in:- 1%, charged once per route, only on pools that sit on the edge of the chain (priced directly in $DOLL): 40% to the coin’s creator, 40% locked into buy liquidity up and down the chain, 20% to the developer.
- 0.075%, charged on every pool regardless of position, kept by that pool as buy liquidity just under its own price.
Trading chain coins
A chain coin trades on its canonical pool, where its fee is collected; ordinary DEX pools (v2, v3 and hookless v4) refuse it. A router should trade the canonical pool throughFamilyHook, and settle after the swap: the hook credits the coin’s canonical allowance during the swap itself, separately for each direction, so a transfer into the PoolManager spends only the inbound allowance and a transfer out of it only the outbound one. A router that pays the coin in before swapping finds no allowance yet and is refused. A transfer its direction’s allowance does not fully cover is refused whole with NonCanonicalVenue(from, to, reason, available), where reason is 1 for a transfer into the PoolManager beyond the inbound allowance, 2 for a transfer out of it beyond the outbound allowance, and 3 for a transfer to or from another pool of the coin (a contract answering token0() or token1() with the coin’s address), and available is the allowance left in that direction (zero for reason 3). canonicalAllowance() returns both allowances for the current transaction. A refused take surfaces inside the PoolManager’s WrappedError. Nothing is charged on the coin. Wallet-to-wallet transfers, including smart wallets, are free and are not refused.
Metadata feed
Two GET endpoints, both public and unauthenticated. Creator-set links and image.https://dollhouse-meta-api.dollhousemarkets.workers.dev/meta/4663/<token> — the website, X account, description and image a coin’s creator has set, if any. Empty JSON if none has ever been set.
https://dollhouse-meta-api.dollhousemarkets.workers.dev/launches?chainId=4663&limit=50 — every launch, newest first, built from the on-chain events above.
image falls back to the token’s own uri() when no creator link has been set, and is only ever an https: URL. status is one of candidate, link or lost; linkIndex is the coin’s numbered chain position once it is a link, otherwise null.
Supply
Three plain-number endpoints on the same host, for chain-listing and market-data providers:GET /supply/max— the fixed total supply.GET /supply/total— total supply minus burned tokens.GET /supply/circulating— total less burned, the launch locker, and unvested team and artist tokens.