Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.raydium.io/llms.txt

Use this file to discover all available pages before exploring further.

Vesting is optional on a LaunchLab launch. Set vesting_param.total_locked_amount = 0 at Initialize and the section below does not apply. Once enabled, the schedule is fixed for the launch’s lifetime; the cliff and unlock periods cannot be changed retroactively.

Why vesting

The bonding curve sells base_supply_graduation tokens during fundraising and seeds the post-graduation pool with the remainder. Vesting carves an additional slice out of the supply, locks it for a configurable cliff, then releases it linearly to one or more beneficiaries — typically the creator’s team, advisors, or platform partners. Practical use cases:
  • Team allocation. A creator reserves, say, 5% of the supply for the founding team, locked for 6 months and unlocking linearly over the following 12 months.
  • Platform allocation. A launch platform receives a slice of every token it lists, on the same schedule, via CreatePlatformVestingAccount.
  • Advisor / contributor grants. Multiple beneficiaries with their own VestingRecord accounts, each tracking their own claimed amount independently.
Locked tokens never enter the curve and are not part of the graduation LP. They sit dormant in the pool’s base_vault until each beneficiary calls ClaimVestedToken.

Schedule shape

Vesting for a launch is described by three numbers, recorded once at Initialize time:
FieldTypeMeaning
total_locked_amountu64Sum of all base tokens locked across all beneficiaries (creator + platform). Must satisfy total_locked_amount <= supply * max_lock_rate / 1_000_000 from the binding GlobalConfig.
cliff_periodu64 (seconds)Wait time after fundraising ends before any tokens unlock.
unlock_periodu64 (seconds)Duration of the linear unlock window after the cliff. 0 means everything unlocks instantly at the end of the cliff.
These three values live on PoolState.vesting_schedule (a VestingSchedule struct) plus the on-chain start_time, which the program records as block_time + cliff_period at the moment fundraising successfully ends (when graduation conditions are first met).
// states/pool.rs
pub struct VestingSchedule {
    pub total_locked_amount:     u64,
    pub cliff_period:            u64,
    pub unlock_period:           u64,
    pub start_time:              u64,   // set by the program at fundraising end
    pub allocated_share_amount:  u64,   // running sum of allocations to vesting records
}
allocated_share_amount is the total amount already assigned to VestingRecord accounts via CreateVestingAccount / CreatePlatformVestingAccount. It must never exceed total_locked_amount. If a creator over-allocates, the next CreateVestingAccount call reverts with InvalidTotalLockedAmount.

Linear unlock formula

After fundraising ends, the program computes the cumulative unlocked amount for each VestingRecord as:
elapsed         = min(now, start_time + unlock_period) − start_time
unlocked_amount = token_share_amount × elapsed / unlock_period
If unlock_period == 0, the entire token_share_amount becomes claimable in one step at start_time. Otherwise the curve is a straight line from 0 at start_time to token_share_amount at start_time + unlock_period, capped at token_share_amount thereafter. The amount transferred on each ClaimVestedToken call is the delta between the freshly recomputed cumulative unlocked amount and the running claimed_amount field on the record.
delta_amount    = unlocked_amount − vesting_record.claimed_amount
vesting_record.claimed_amount = unlocked_amount
A claim before start_time reverts with VestingNotStarted. A claim after start_time + unlock_period settles the full remainder.

Account layouts

VestingSchedule

Lives inline on PoolState. See accounts.

VestingRecord

Per-beneficiary record. PDA derived as:
seeds = [
  b"pool_vesting",
  pool_state.key(),
  beneficiary.key(),
]
program = LaunchLab program
// states/vesting.rs
#[account]
pub struct VestingRecord {
    pub epoch:               u64,         // recent_epoch tracker
    pub pool:                Pubkey,      // back-pointer to PoolState
    pub beneficiary:         Pubkey,      // who can call ClaimVestedToken
    pub claimed_amount:      u64,         // cumulative claimed
    pub token_share_amount:  u64,         // total allocated to this beneficiary
    pub padding:             [u64; 8],
}
A beneficiary can only have one VestingRecord per launch. Allocating again to the same beneficiary on the same launch reverts because the PDA already exists.

Instructions

CreateVestingAccount

Creator-only. Allocates a slice of the pool’s total_locked_amount to a new beneficiary by initializing a fresh VestingRecord PDA. Arguments
share_amount: u64    // tokens to assign to this beneficiary
Accounts
#NameWSNotes
1creatorWSMust equal pool_state.creator; pays rent for the new account.
2beneficiaryWReceives the unlocked tokens later. The pubkey is locked in here — it cannot be changed.
3pool_stateWMutated to bump vesting_schedule.allocated_share_amount.
4vesting_recordWinit; PDA [b"pool_vesting", pool_state, beneficiary].
5system_programRequired for account creation.
Preconditions
  • share_amount > 0.
  • pool_state.vesting_schedule.allocated_share_amount + share_amount <= total_locked_amount.
  • The beneficiary pubkey has no existing VestingRecord for this pool.
Postconditions
  • vesting_record initialized with token_share_amount = share_amount, claimed_amount = 0.
  • pool_state.vesting_schedule.allocated_share_amount += share_amount.
Common errorsInvalidTotalLockedAmount, InvalidInput.

CreatePlatformVestingAccount

Platform-admin variant of CreateVestingAccount. The platform’s vesting wallet (stored on PlatformConfig.platform_vesting_wallet) is the beneficiary, and the share is bounded by PlatformConfig.platform_vesting_scale. The signer must equal platform_config.platform_vesting_wallet. Other accounts mirror CreateVestingAccount. Use this when a platform contracts to receive a fixed vesting share on every launch it lists.

ClaimVestedToken

Beneficiary-only. Transfers any newly-unlocked tokens from the pool’s base_vault to the beneficiary’s ATA. Arguments None (the program computes the claim amount from the schedule). Accounts
#NameWSNotes
1beneficiaryWSMust equal vesting_record.beneficiary.
2authorityPDA [b"vault_auth_seed"]; signs the vault transfer.
3pool_stateWMutated only if the schedule needs to be re-validated.
4vesting_recordWclaimed_amount is updated.
5base_vaultWPool’s base-token vault; debited.
6beneficiary_ataWReceives the unlocked tokens; init_if_needed.
7base_mintPool’s base mint.
8token_programSPL Token or Token-2022 program.
9associated_token_programFor ATA creation if needed.
10system_programRequired for account creation.
Preconditions
  • block_time >= pool_state.vesting_schedule.start_time (otherwise VestingNotStarted).
  • pool_state.status == PoolStatus::Migrated — graduation must have already happened. Calling before graduation reverts.
  • The unlocked-amount delta is greater than zero. A no-op call (computed delta is 0) reverts.
Postconditions
  • vesting_record.claimed_amount advances to the new cumulative unlocked amount.
  • delta_amount of base token is transferred to beneficiary_ata.
Common errorsVestingNotStarted, NoAssetsToCollect, MathOverflow.

Worked example

A launch sets:
  • supply = 1_000_000_000
  • total_locked_amount = 100_000_000 (10% of supply)
  • cliff_period = 180 * 86400 (180 days)
  • unlock_period = 365 * 86400 (1 year linear after the cliff)
The creator allocates two VestingRecord accounts immediately after Initialize:
  • Beneficiary A (team): share_amount = 70_000_000
  • Beneficiary B (advisor): share_amount = 30_000_000
allocated_share_amount = 100_000_000, equal to total_locked_amount — no further allocations possible. Fundraising completes on 2027-01-01T00:00Z. The program sets start_time = 2027-01-01 + 180 days = 2027-06-30. On 2027-09-30 (90 days after start_time), Beneficiary A calls ClaimVestedToken:
elapsed         = min(now, start_time + 365·86400) − start_time
                = 90 · 86400
unlocked_amount = 70_000_000 × (90 / 365) ≈ 17_260_274
delta_amount    = 17_260_274 − 0 = 17_260_274
A’s wallet receives 17.26M base tokens. vesting_record.claimed_amount advances to 17_260_274. Six months later (2028-03-31, 270 days after start_time), A claims again:
unlocked_amount = 70_000_000 × (270 / 365) ≈ 51_780_822
delta_amount    = 51_780_822 − 17_260_274 = 34_520_548
A receives another 34.52M tokens. After 2028-06-30 (the end of unlock_period), the next claim transfers the remaining ~18.22M and leaves claimed_amount == token_share_amount.

Edge cases

  • Beneficiary loses their key. The pubkey on VestingRecord.beneficiary is the only signer that can call ClaimVestedToken. There is no recovery path. Set the beneficiary to a multisig if recovery matters.
  • Token-2022 transfer fees. If the base mint is a Token-2022 mint with a transfer-fee extension, the beneficiary receives delta_amount − transfer_fee, not the full delta. The pool’s vault still records the gross amount as transferred — the difference accrues to the mint’s withheld-fee account.
  • Pool not graduated. Calling ClaimVestedToken before graduation reverts. The vesting clock starts only when fundraising actually completes; an aborted launch (which never sets start_time) leaves locked tokens unreachable in the vault.
  • Over-allocation attempts. The program enforces allocated_share_amount <= total_locked_amount on every CreateVestingAccount. The remainder (if any) of total_locked_amount left unallocated is lost — those tokens stay in the vault forever once the launch graduates. Allocate the full amount unless that’s the intent.

Pointers

Sources:
  • raydium-launch/programs/launchpad/src/states/vesting.rsVestingRecord.
  • raydium-launch/programs/launchpad/src/states/pool.rsVestingSchedule, VestingParams, is_vesting_started, vesting_end_time.
  • raydium-launch/programs/launchpad/src/instructions/create_vesting_account.rs.
  • raydium-launch/programs/launchpad/src/instructions/claim_vested_token.rs.