Launch token
LaunchToken is the original-generation HartiiLabs clone token.
Source: contracts/contracts/LaunchToken.sol in the hartii-labs repo. Original implementation
address:
0x0059af4b7441e15E06e4686bd2f4CC5dfF5AAA0A —
each token of that generation 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 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() | The bytes32 commitment supplied at launch; see launch images below for the composer metadata format. |
Launch images
The launch composer accepts an optional PNG, JPEG, or WebP image. Drop, paste, or choose it in Image, then drag to reposition the square crop. While Preparing your image… is visible, launch is blocked. Changing the file or crop replaces the pending preparation; only the latest result is accepted. Remove clears the image and invalidates pending work, so an earlier result cannot put it back.
Larger source images can be cropped and shrunk in the browser. The final launch-image payload must be at most 1 MiB (1,048,576 bytes). If preparation cannot produce an acceptable file, choose a smaller image or remove it before launching. An image error blocks launch.
Before submitting a paid launch, the composer requests a free wallet message signature and saves the prepared image with its token information. A selected-image save must succeed first. If it fails, the composer stops before requesting the paid launch and keeps the form and image on the open page for retry. No launch fee is charged by that stopped attempt. Keep the page open; this is not a promise that a reload or navigation restores an unsaved browser form. Without an image, an optional description/link save failure can still allow launch to continue without those fields. Rejecting the message signature cancels the attempt.
Save acknowledgement and image delivery
For integrations, POST /api/token-meta accepts the signed draft and optional logo file as
multipart form data. HTTP success alone is insufficient: require ok: true and stored: true,
and require logoStored: true whenever a file was submitted. For example:
{"ok":true,"draftId":"example-draft","stored":true,"logoStored":true,"logoCid":null}The platform saves the validated image bytes and creator-associated metadata before starting a
bounded, optional IPFS pin attempt. A null logoCid does not mean the acknowledged image was lost.
Token images are served through /api/token-logo/<token-address> from platform storage, with IPFS
as a fallback source. An IPFS outage does not need to delay an already-saved image or the launch.
Unlaunched drafts expire after seven days; a recognized, creator-matching launch promotes its
stored image and metadata for continued use.
The image endpoint can find saved bytes through the token's metadata hash even if its first indexed record lacks logo flags. Recovery checks its creator against the indexed creator, checks the stored image digest when present, and retains verified metadata for continued use. If a known CID cannot currently be fetched, it answers 503 temporarily unavailable without caching that failure as missing artwork. An unresolved image with a metadata commitment has a 30-second edge miss cache, rather than the five-minute cache used for an ordinary missing image. While a token avatar stays mounted, a failed platform-image request gets at most two automatic retries: after 30 seconds, then after another 60 seconds if it fails again. The avatar keeps its reserved frame during loading and retries. These bounds allow delayed images to appear without promising that every unavailable image will recover.
New composer commitments hash canonical metadata containing the creator address and a digest of the prepared image bytes, alongside the name, symbol, description and social links. Changing the creator or image changes the commitment. Existing on-chain commitments and their stored records remain readable without conversion.
An older token has no image
Connect the token's creator wallet, open its HartiiLabs token page and select Edit info. Under Logo, choose the image and press Sign & set logo. The server checks the signature against the indexed token creator and binds it to the uploaded bytes and file type. This updates the token's off-chain image; it does not require launching the token again.
If the original bytes are absent from both platform storage and reachable IPFS content, they cannot be reconstructed from the metadata hash. Supply the original file or another image through the creator editor. HartiiLabs image delivery does not guarantee that a third-party wallet will display the same artwork; its metadata and cache behavior require separate verification.