Launch token
LaunchToken is cloned once per launch and is the ERC-20 every HartiiLabs token actually is.
Source: contracts/contracts/LaunchToken.sol in the hartii-labs repo. Implementation contract
live at
0x0059af4b7441e15E06e4686bd2f4CC5dfF5AAA0A —
every launch's actual token is a clone at its own address.
Non-standard behavior an integrator must know
- There is no
mint()function anywhere in this contract's ABI. The full fixed supply is minted exactly once, atomically, insideinitialize(). Supply can never be inflated after launch;totalSupplycan only ever go down, viaburn(). transfer/transferFromare the plain textbook ERC-20 implementation — no fee-on-transfer, no blacklist, no pause, no owner-only transfer hook of any kind. A creator cannot quietly disable sells because there is no hook point in the contract to add one.- The only non-standard transfer behavior is the creator vesting gate below, which applies
only to the
creatoraddress.
ERC-20 surface
| Function | Signature | Notes |
|---|---|---|
name() | string public name | Set once at initialize(). |
symbol() | string public symbol | Set once at initialize(). |
decimals() | uint8 public constant decimals | Always 18. |
totalSupply() | uint256 public totalSupply | Fixed at mint; only decreases via burn(). |
balanceOf(address account) | external view returns (uint256) | Standard. |
allowance(address owner_, address spender) | external view returns (uint256) | Standard. |
approve(address spender, uint256 amount) | external returns (bool) | Standard; no increaseAllowance/decreaseAllowance helpers exist. |
transfer(address to, uint256 amount) | external returns (bool) | Reverts "Zero to" (no burn-via-zero-address — use burn() instead), "Insufficient balance", and the creator vesting gate below if msg.sender == creator. |
transferFrom(address from, address to, uint256 amount) | external returns (bool) | Same reverts as transfer, plus "Insufficient allowance". An allowance of type(uint256).max is treated as infinite and never decremented (standard gas-saving pattern). |
burn()
function burn(uint256 amount) externalAny holder may destroy their own tokens: reduces their balance and totalSupply, emits
Transfer(msg.sender, address(0), amount). Reverts "Insufficient balance" if the caller doesn't
hold amount. If the caller is the creator, it is subject to the exact same vesting gate as a
transfer ("Creator allocation still locked").
Creator vesting
Set once at initialize(), driven by the factory's creatorLockDuration setting at the time of
that launch:
| Field | Meaning |
|---|---|
creator() | The launch's creator address. |
creatorAllocation() | Total tokens allocated to the creator (the locked pool), minted directly to their balance at launch. |
creatorClaimed() | Unused bookkeeping field — vesting is enforced purely by the transfer gate below, not by a separate claim step. |
lockedUntil() | block.timestamp + creatorLockDuration at launch time. Before this timestamp, zero of the creator's allocation is transferable. |
VESTING_DURATION | Constant, 7 days. Linear release window starting at lockedUntil. |
unlockedCreatorAllocation() | view — amount of the original allocation unlocked so far. 0 before lockedUntil; linear from 0 to creatorAllocation over the 7 days after; creatorAllocation (fully unlocked) once elapsed >= VESTING_DURATION. |
function unlockedCreatorAllocation() public view returns (uint256) {
if (block.timestamp < lockedUntil) return 0;
uint256 elapsed = block.timestamp - lockedUntil;
if (elapsed >= VESTING_DURATION) return creatorAllocation;
return (creatorAllocation * elapsed) / VESTING_DURATION;
}The transfer gate — applied inside _transfer (and inside burn()), on the creator address
specifically, gated on the original allocation, not the creator's current balance (so it can't
be bypassed by routing through another contract or by having already sold part of the position):
if (from == creator) {
uint256 locked = unlockedCreatorAllocation() < creatorAllocation
? creatorAllocation - unlockedCreatorAllocation()
: 0;
require(fromBalance - amount >= locked, "Creator allocation still locked");
}In plain terms: the creator can always move tokens down to their currently-locked remainder,
never below it. With the live factory default creatorLockDuration = 0, the 7-day linear release
starts immediately at launch.
Events
| Event | Indexed fields | Non-indexed fields | Emitted by |
|---|---|---|---|
Transfer | from, to | value | initialize() (the two initial mints, from address(0)), transfer, transferFrom, burn (to address(0)) |
Approval | owner, spender | value | approve |
CreatorAllocationClaimed | creator | amount | Declared in the ABI but never emitted anywhere in this contract's code — see Unverified below. |
Provenance fields
| Field | Meaning |
|---|---|
factory() | The TokenFactory that deployed and initialized this clone (msg.sender inside initialize()). |
metadataHash() | keccak256/CID-derived hash of the off-chain metadata JSON supplied at launch. |