TokenFactory is a singleton, non-upgradable contract. Source: contracts/contracts/TokenFactory.sol
in the hartii-labs repo. Live at
0x001AF1BbB40807fcb99C9Eeaa49dF5E91e7Efd42
on chain 9 (Cyprus-1). See Contracts overview for the architecture and trust model.
Caller sends msg.value == creationFee (exact match required) and calls launch() with a
name, symbol, metadata hash, and creator allocation in bps.
The factory validates name length (1–64 bytes), symbol length (1–16 bytes), and
creatorAllocBps <= MAX_CREATOR_ALLOC_BPS (2000 = 20%).
It clones tokenImplementation and curveImplementation via inline EIP-1167 minimal-proxy
bytecode, using plain CREATE (see the zone-safety note below).
It computes the supply split: creatorAllocation = supply * creatorAllocBps / 10000,
nonCreatorSupply = supply - creatorAllocation, then
curveSupply = nonCreatorSupply * curveSellableBps / 10000 and
migrationTokenReserve = nonCreatorSupply - curveSupply. These are derived, never
independently configured, so the token's minted balances and the curve's own bookkeeping can
never drift apart.
It calls LaunchToken.initialize() and BondingCurve.initialize() — both in this same
transaction, so there is no window where an uninitialized clone could be front-run.
It records the token in tokens[], isHartiiLabsToken[token] = true, curveOf[token] = curve,
adds creationFee to accumulatedFees, and emits TokenLaunched.
creatorAllocBps: 0–2000 (0%–20% of totalSupplyPerToken), chosen per launch by the caller.
The allocation is minted straight to the creator's balance inside LaunchToken.initialize(),
but is gated by that token's own vesting logic — see Launch token. The
vesting start delay (creatorLockDuration, seconds from launch until the linear-release
window begins) is a factory-wide setting applied to every launch at the time it happens, not
a per-launch parameter of launch() itself.
Pull-payment fallback: pays out and zeroes pending[msg.sender]. Reverts "Nothing to withdraw" if zero, "Withdraw failed" if the send itself fails (should not happen for an EOA/plain wallet). Emits Withdrawn(to, amount).
withdrawFees() pays QUAI via _safePay, which attempts a direct .call{value: amount}("") to
treasury and, if that call fails, credits pending[treasury] += amount and emits
PaymentDeferred instead of reverting the whole withdrawal. The deferred amount can later be
pulled by treasury calling withdraw(). This is the same pattern used throughout
BondingCurve.