Fees and rewards
Collect LP fees
Section titled “Collect LP fees”Anyone can collect a vault’s fees. Each collection pays Blast its share (or holds it on a partnered vault) and pays every fee recipient. Of the holder allocation, Quote stays in the vault for reward rounds; Meme is paid to the governed FeePolicy.destination, recorded by HolderMemePaid.
- Cetus:
cetus::collect_fees<Meme, Quote, CoinA, CoinB>(vault, fee_policy, cetus_config, burn_manager, pool).poolis the vault’sPool<CoinA, CoinB>, in the same coin order as migration. - Bluefin:
bluefin::collect_fees<Meme, Quote>(vault, fee_policy, bluefin_config, pool, clock).poolis the vault’sPool<Meme, Quote>.
const tx = new Transaction();tx.moveCall({ target: `${PKG}::bluefin::collect_fees`, typeArguments: [MEME, QUOTE], arguments: [tx.object(VAULT), tx.object(FEE_POLICY), tx.object(BLUEFIN_CONFIG), tx.object(POOL), tx.object.clock()],});Another pool aborts with EPositionPoolMismatch. A collection of zero is valid and emits nothing. Neither adapter collects venue incentive rewards.
On a partnered vault, follow each collection with presale_vault::pay_protocol_lp_fees_with_partner(vault, fee_policy, partner, clock). Holding nothing is valid, so it can always follow a collection.
Lock a position without a presale
Section titled “Lock a position without a presale”A team that adds liquidity to Cetus or Bluefin itself can lock that position for good in a Vault. The vault then splits LP fees exactly as a migrated sale’s vault does: Blast’s LP-fee share first, then the team’s fee split between its fee recipients and holder-reward rounds. Anyone may lock their own position.
- Create the pool and open and fund the position with the venue’s own calls.
- Build a
fee_split(fee_recipients, holder_rewards_bps), as for a presale. - Lock it:
- Cetus:
cetus::new_vault<Meme, Quote, CoinA, CoinB>(position, fee_split, partner_id, creation_policy, fee_policy, pool, burn_manager, clock).poolis thePool<CoinA, CoinB>holding the position, with Meme and Quote in the pool’s order. The position is burned through Cetus LP-burn, and the vault keeps its fee-claiming proof. - Bluefin:
bluefin::new_vault<Meme, Quote>(position, fee_split, partner_id, creation_policy, fee_policy, pool, clock).poolis thePool<Meme, Quote>holding the position; a pool whose first coin is not Meme is not supported. The vault holds the position.
- Cetus:
- The
CreationPolicymust allow the quote and the venue, the same check a presale passes. - The vault snapshots the
FeePolicy’s current LP-fee rates, which Blast’sFeeAdminmay change later, as on any vault. - Its
presale_idandpresale_cap_idare the zero ID, so it has no residuals and noPresaleCapclaims from it.VaultCreatedcarries the zeropresale_id. partner_idis anOption<ID>. A named partner must be one theCreationPolicyaccepts now; Blast’s LP fees are then held forpay_protocol_lp_fees_with_partner, as on a partnered sale’s vault.- A position from another pool aborts with
EPositionPoolMismatch; one with no liquidity aborts withpresale_vault::EZeroLiquidity. - Fees are collected with the same
collect_feescalls. Publishing holder-reward rounds still needs Blast’sRewardsAdminwitness.
Migration residuals
Section titled “Migration residuals”When a sale’s pool is seeded, a little of either coin may not fit the position. The PresaleCap holder claims it once with presale_vault::claim_migration_residuals(vault, presale_cap), which returns (Coin<Meme>, Coin<Quote>). Transfer both in the same transaction. It aborts with ENoMigrationResiduals when both are empty.
Holder-reward rounds
Section titled “Holder-reward rounds”The holder Quote share of LP fees accrues in the vault. Blast’s RewardsAdmin publishes rounds funded from the available holder balances. A round keeps its holder budget separate from an optional Quote reserve for gas reimbursement.
-
Publish:
holder_rewards::publish_round(vault, witness, fee_policy, expected_treasury, number, start_checkpoint, end_checkpoint, root, manifest_digest, leaf_count, meme_amount, quote_amount, gas_reserve_amount, claim_deadline_ms, clock).witnessis anAdminWitness<RewardsAdmin>. The round number must equalvault.next_round, starting at zero. Its checkpoint interval must be nonempty and begin at or after the previous round ended; skipped intervals need no empty round. Root and manifest digest are each 32 bytes, the leaf count is 1 to 65,536, and the holder budget must be positive. Holder funding plus reserve must fit the vault’s balances. -
Treasury check:
expected_treasurymust still equalFeePolicy.destinationwhen published, or the call aborts withETreasuryChanged. The checked address is saved on the round. A zero gas reserve disables reimbursement and needs no settlement. -
Claim:
holder_rewards::claim(round, index, beneficiary, meme_amount, quote_amount, proof). Anyone can relay; payment goes tobeneficiary. Each index pays once. -
Settle gas reserve:
holder_rewards::settle_gas_reserve(round, witness, vault, reimbursement_amount), byRewardsAdmin, once. It pays up to the reserved amount to the saved treasury and returns unused Quote to the vault. Holder claims never touch this reserve. Actual gas costs are determined off chain; the contract enforces the reserve bound. -
Close:
holder_rewards::close_round(round, vault, clock). Anyone, oncenow ≥ claim_deadline_ms. Returns unclaimed holder balances and any unsettled gas reserve to the correct vault and deletes the round. Claims work until the round is closed; passing the deadline alone does not close it.
Leaf digest, with the same node hash and proof rules as allowlists:
blake2b256( 0x00 || "blast-presale-holder-rewards-v1" || bcs(vault_id: ID) // 32 bytes || bcs(round_number: u64) || bcs(index: u64) || bcs(beneficiary: address) || bcs(meme_amount: u64) || bcs(quote_amount: u64) )Economics
Section titled “Economics”All amounts are atomic units. All rates are basis points out of 10,000.
Largest-remainder apportionment
Section titled “Largest-remainder apportionment”Supply, raise, and every LP-fee split use one method. Each share gets floor(X × share ÷ 10,000). Leftover units go one each to the shares with the largest remainder, ties to the earlier share. Every unit lands in exactly one share.
- Supply, in order: buyers, pool, Blast’s token rate, then token recipients.
- Raise, in order: raise recipients, Blast’s raise rate, then the pool with what remains.
Fill and claims
Section titled “Fill and claims”With T total commitments, R the raise target, A the buyers’ tokens, and c one wallet’s commitment:
- Tokens:
floor(A × c ÷ T) - Overflow refund:
floor((T − R) × c ÷ T) - The last wallet to claim takes everything left of both coins, so no rounding dust is stranded.
- Presale price:
R ÷ Aquote per atomic token. Pool opening price: pool quote ÷ pool tokens. - Under a hard cap,
TequalsRand every refund is zero. If the sale already overflowed before its hard cap started, the hard cap accepts nothing more and the overflow fills pro rata as above.
Show earlier claim previews as exact and the last one as “at least”.
Blast’s fees
Section titled “Blast’s fees”Each sale snapshots FeePolicy’s rates at creation. Later set_fees calls never touch existing sales.
- Raise fee and token fee: apportioned at creation, paid at migration. Overflow refunds are never charged.
- LP fees: at each collection Blast takes
ceil(gross × rate ÷ 10,000)of each coin, at the vault’s current rates. Blast’sFeeAdmincan change a vault’s rates after migration. - Every payout goes to
FeePolicy.destinationas it is when paid. - Before
start_ms, Blast can replace a sale’s rates. A changed raise fee moves Quote to or from the pool; a changed token fee moves tokens to or from the buyers’ allocation. Recipients keep their amounts.
Partners
Section titled “Partners”A partner’s cut is floor(fee × share_bps ÷ 10,000) of Blast’s raise fee and LP fees, never of the token fee, using the partner’s share at payment time (at most 8,000 basis points).
LP-fee split
Section titled “LP-fee split”After Blast’s part, each coin is apportioned over the fee recipients in order, then the holder share. Ties favor fee recipients over holders and earlier recipients over later ones. Recipients are paid as coins; a zero amount sends nothing. Holder Quote stays in the vault for rounds, while holder Meme is paid to the governed fee-policy destination. The split itself never changes.