Documentation
Holder rewards
A launch can send its creator tranche to the token's holders instead of the creator. On the create page this is Fees go to: Holders. The trading fee is unchanged, and the protocol keeps its 0.5%.
Launching with holder rewards#
Set creatorFeeRecipient to the factory's HOLDER_REWARDS() constant:
const HOLDER_REWARDS = await client.readContract({
address: FACTORY, abi: factoryAbi, functionName: "HOLDER_REWARDS",
});
const params = { ...tokenParams, creatorFeeRecipient: HOLDER_REWARDS, salt: freshSalt };The factory resolves that to the token's distributor address itself, so the address cannot be mispredicted. Use a fresh salt for each launch.
The distributor is a CREATE2 contract keyed by creator, salt and quote asset, and the launch names its
address before it has code. Fees can accrue to it from the first trade. If you want the address ahead
of time, call rewardsFactory.predict(creator, salt, quoteToken). POST /v1/launch/predict with
feesTo: "holders" returns it as distributor, and gives you the value to send as
paramsCreatorFeeRecipient.
How a distribution works#
- Deploy.
rewardsFactory.deploy(token, salt)puts the contract in place. It is permissionless, and every argument is checked against the launch record, so it does not matter who sends it. The Tartarus jobs service does this soon after launch.rewardsFactory.distributorOf(token)is zero until then. The salt is in theLaunchSaltevent. - Sweep. Fees reach the escrow under the distributor's address, like any recipient's.
- Harvest.
distributor.harvest()pulls that balance into itsunallocatedpot. Permissionless. The keeper runs it once about $1 is waiting. - Snapshot. About weekly, holder balances are read at one block. Each holder's share is
balance / eligible supply, rounded down. - Publish. The shares become a Merkle tree, and the distributor owner calls
publishEpoch(root, total). An epoch can never commit more thanunallocated. Epochs under about $1 are skipped and roll into the next one. - Claim. Holders have 90 days to claim each epoch.
- Roll over. After 90 days, anyone can call
rollOver(epochId). The unclaimed remainder returns tounallocatedand funds a later epoch.
Who is excluded#
Addresses that hold supply but could never claim get no share, and they do not count toward eligible supply: the zero address, the PoolManager, the locker, the hook, the escrow, the factory, the launch-and-buy router and the distributor itself. Paying the pool's locked position would destroy the money, so most of the balance held at launch is excluded.
This is enforced twice: off chain when the tree is built, and on chain by isExcluded inside claim.
A faulty root still cannot pay an excluded address.
Checking an epoch#
Everything needed to rebuild an epoch is public: Transfer events give the balances, isExcluded gives
the exclusions, and leafFor(index, account, amount) gives the exact leaf encoding:
keccak256(abi.encode(index, account, amount))Anyone can recompute the tree and compare it with getEpoch(epochId).merkleRoot. The distributor owner
can publish any root, so this check is what keeps that role honest. See
Trust model.
Claiming#
The portfolio page claims every open epoch in one transaction. In your own client, the API supplies the proofs:
const { claimable } = await fetch(`${API}/users/${account}`).then((r) => r.json());
for (const t of claimable.holders.byToken) {
const open = t.epochs.map((e) => e.calldata);
await wallet.writeContract({
address: t.distributor, abi: distributorAbi, functionName: "batchClaim",
args: [
open.map((c) => BigInt(c.epochId)),
account,
open.map((c) => BigInt(c.amountRaw)),
open.map((c) => BigInt(c.index)),
open.map((c) => c.proof),
],
});
}batchClaim is all-or-nothing, so one bad proof reverts the batch. claim(epochId, account, amount, index, proof) settles a single epoch.
Anyone may submit a claim, but the proof fixes the recipient. A claim always pays account, never
msg.sender. Each (epochId, account) pair claims once. Rewards arrive in the launch's quote asset,
straight from the distributor, not through the escrow.
| Error | Meaning |
|---|---|
InvalidProof | The proof does not match this (epoch, index, account, amount) |
AlreadyClaimed | Already settled |
ClaimWindowClosed | More than 90 days old |
ExcludedAccount | On the exclusion list |
NoEpoch | That epoch does not exist |
What the numbers mean#
- Claimable is the sum of your leaves in epochs still inside their 90-day window. It is exact.
- Pending is an estimate: your share of what has accrued since the last epoch, from your current balance. It moves with every trade and transfer until the next snapshot.
Selling before a snapshot forfeits that epoch's share, and buying just before one earns a full share of it. Only the balance at the snapshot block counts.