Some functionality will be degraded until the official mainnet launch on the 9th.
Documentation
Guides

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

Warning
This is a one-way door. Only the current recipient can move a fee stream, and a distributor has no function that could call transferCreatorFeeRecipient. Once a launch pays its holders, it pays them for the life of the token, barring the owner's timelocked override.

Launching with holder rewards#

Set creatorFeeRecipient to the factory's HOLDER_REWARDS() constant:

$ ts
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#

  1. 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 the LaunchSalt event.
  2. Sweep. Fees reach the escrow under the distributor's address, like any recipient's.
  3. Harvest. distributor.harvest() pulls that balance into its unallocated pot. Permissionless. The keeper runs it once about $1 is waiting.
  4. Snapshot. About weekly, holder balances are read at one block. Each holder's share is balance / eligible supply, rounded down.
  5. Publish. The shares become a Merkle tree, and the distributor owner calls publishEpoch(root, total). An epoch can never commit more than unallocated. Epochs under about $1 are skipped and roll into the next one.
  6. Claim. Holders have 90 days to claim each epoch.
  7. Roll over. After 90 days, anyone can call rollOver(epochId). The unclaimed remainder returns to unallocated and 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:

$ solidity
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:

$ ts
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.

ErrorMeaning
InvalidProofThe proof does not match this (epoch, index, account, amount)
AlreadyClaimedAlready settled
ClaimWindowClosedMore than 90 days old
ExcludedAccountOn the exclusion list
NoEpochThat 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.

POLLINGElysium · 99901 · Mainnet
--:--:-- UTC