Hartii developer docs

HartiiLabs

Token factory

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.

Launch flow, step by step

  1. Caller sends msg.value == creationFee (exact match required) and calls launch() with a name, symbol, metadata hash, and creator allocation in bps.
  2. The factory validates name length (1–64 bytes), symbol length (1–16 bytes), and creatorAllocBps <= MAX_CREATOR_ALLOC_BPS (2000 = 20%).
  3. It clones tokenImplementation and curveImplementation via inline EIP-1167 minimal-proxy bytecode, using plain CREATE (see the zone-safety note below).
  4. 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.
  5. 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.
  6. It records the token in tokens[], isHartiiLabsToken[token] = true, curveOf[token] = curve, adds creationFee to accumulatedFees, and emits TokenLaunched.

launch()

Signaturelaunch(string name_, string symbol_, bytes32 metadataHash, uint256 creatorAllocBps) external payable returns (address token, address curve)
Who may callAnyone
PayableYes — must equal creationFee exactly
Revert stringCondition
"Exact creation fee required"msg.value != creationFee
"Invalid name length"name_ is empty or over 64 bytes
"Invalid symbol length"symbol_ is empty or over 16 bytes
"Creator alloc too high"creatorAllocBps > MAX_CREATOR_ALLOC_BPS (2000)
"Clone failed"The internal CREATE for either clone returned the zero address

Emits TokenLaunched.

Creator allocation & vesting parameters

  • 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.

View functions

FunctionReturnsNotes
owner()addressAdmin address — can call every onlyOwner function below.
treasury()addressCreation-fee withdrawal destination.
tokenImplementation()addressimmutable. The LaunchToken clone target for every launch.
curveImplementation()addressimmutable. The BondingCurve clone target for every launch.
creationFee()uint256Flat fee (wei) required as msg.value on launch().
accumulatedFees()uint256Creation fees collected but not yet withdrawn.
virtualQuaiReserve()uint256Default applied to the next launch's curve.
virtualTokenReserve()uint256Default applied to the next launch's curve.
totalSupplyPerToken()uint256Fixed total supply minted for the next launch.
graduationRaiseWei()uint256Default belt-and-braces graduation threshold for the next launch.
creatorLockDuration()uint256Seconds from launch until the next launch's creator vesting window starts.
curveSellableBps()uint256Share (bps) of non-creator supply that is curve-sellable vs. migration reserve, for the next launch.
BPS_DENOMINATOR()uint256Constant, 10000.
MAX_CREATOR_ALLOC_BPS()uint256Constant, 2000 (20% hard cap).
tokens(uint256 i)addressThe i-th launched token, in launch order.
tokenCount()uint256Number of tokens launched so far.
isHartiiLabsToken(address)boolWhether an address is a token this factory launched.
curveOf(address token)addressThe BondingCurve clone for a given token.
pending(address)uint256Safe-pay fallback balance owed to an address whose direct payout previously failed.

Live values (read 2026-09-23, block 10249966)

FieldLive value
owner()equal to treasury() — read live, not printed here (see Contracts overview)
creationFee()5 QUAI (5000000000000000000 wei)
accumulatedFees()260 QUAI unwithdrawn at read time
virtualQuaiReserve()17,000 QUAI
virtualTokenReserve()1,073,000,000 tokens
totalSupplyPerToken()1,000,000,000 tokens
graduationRaiseWei()100,000 QUAI
creatorLockDuration()0 (vesting window starts immediately at launch)
curveSellableBps()8000 (80% of non-creator supply is curve-sellable, 20% is migration reserve)
tokenCount()33 tokens launched

Owner-only functions

All require msg.sender == owner(), else revert "Not owner".

FunctionEffectBounds / revertsEvent
setCreationFee(uint256 newFee)Sets creationFeeNo bound — can be set to any value, including 0CreationFeeUpdated(oldFee, newFee)
setCurveParams(uint256 _virtualQuaiReserve, uint256 _virtualTokenReserve, uint256 _totalSupplyPerToken, uint256 _graduationRaiseWei, uint256 _creatorLockDuration)Sets the five curve defaults applied to future launches only"Zero virtual reserve" if either reserve is 0; "Zero supply" if supply is 0; "Zero graduation raise" if the raise threshold is 0CurveParamsUpdated()
setCurveSellableBps(uint256 newBps)Sets the curve-sellable/migration-reserve split for future launches"Invalid bps" unless 0 < newBps <= 10000CurveParamsUpdated()
transferOwnership(address newOwner)Changes owner"Zero owner" if newOwner == address(0)
setTreasury(address newTreasury)Changes treasury (creation-fee destination)"Zero treasury" if zero address
withdrawFees()Pays accumulatedFees to treasury, zeroes the accumulator"Nothing to withdraw" if accumulatedFees == 0FeesWithdrawn(treasury, amount) (and PaymentDeferred if the direct send fails — see below)

Public functions

FunctionWho may callNotes
withdraw()Anyone with a nonzero pending[msg.sender] balancePull-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).

Events

EventIndexed fieldsNon-indexed fieldsEmitted by
TokenLaunchedtoken, curve, creatorname, symbol, metadataHashlaunch(), once per launch
CreationFeeUpdatedoldFee, newFeesetCreationFee
CurveParamsUpdatedsetCurveParams, setCurveSellableBps
FeesWithdrawntoamountwithdrawFees
PaymentDeferredtoamountAny internal _safePay whose direct QUAI send fails (currently only reachable from withdrawFees)
Withdrawntoamountwithdraw()

Safe-pay fallback

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.

Quai Network mainnet · chain 9 · Cyprus-1. Figures marked "read on" a date were read from the chain that day; re-read before relying on them.