发行类型

项目归属

Last updated August 24, 2026

Genesis 项目归属使用 ClaimScheduleBucketV2,按链上悬崖和周期性线性计划将一笔代币分配释放给一名接收方。

你将构建的内容

本指南创建一份为期一年的项目代币归属分配,包含 10% 悬崖、按月线性解锁,以及可选的权限方控制。

摘要

ClaimScheduleBucketV2 是用于项目、团队、顾问或国库代币归属的一等 Genesis 流出 Bucket。它在链上存储接收方、分配、领取历史、归属曲线、暂停状态、策略控制以及可选的取消行为。

  • 一个 Bucket 将一笔代币分配归属给一名当前接收方
  • ClaimSchedule 将独立悬崖与基于周期的线性解锁组合在一起
  • 领取是 Permissionless 的,但始终支付给 Bucket 的接收方
  • 可选策略支持权限方暂停、取消、接收方取消和接收方转移

跳转至: 快速开始 · 归属机制 · 运行时控制 · 取消 · 参考

ClaimScheduleBucketV2 与 ClaimSchedule

ClaimScheduleBucketV2 是拥有项目分配的账户,而 ClaimSchedule 是嵌入该账户的可复用归属曲线。

类型用途
ClaimScheduleBucketV2存储一名接收方、分配、已领取数量、计划、领取门控、暂停状态、策略和结束行为
ClaimSchedule定义悬崖数量、悬崖条件、线性开始条件、持续时间和解锁周期
ClaimScheduleV2Extensions存储运行时策略标志和可选的后端领取签名者

Genesis 没有 ClaimScheduleBucketV1。项目归属请使用 V2 账户和指令名称。ClaimSchedule 也被其他 Bucket 扩展使用,因此仅凭计划类型无法识别项目归属 Bucket。

快速开始

快速开始向已初始化但尚未 Finalize 的 Genesis V2 账户添加一个为期一年、带 10% 悬崖和按月线性解锁的归属 Bucket。

请先完成 Genesis 设置,并从基础代币供应量中预留归属分配。所有 Bucket 分配之和必须落在 Genesis 账户的总供应量之内。

创建项目归属 Bucket

addClaimScheduleBucketV2 在 Genesis 账户 Finalize 之前创建 Bucket。

addClaimScheduleBucketV2.ts
1import {
2 addClaimScheduleBucketV2,
3 createClaimSchedule,
4 createTimeAbsoluteCondition,
5 findClaimScheduleBucketV2Pda,
6 genesis,
7} from '@metaplex-foundation/genesis'
8import { keypairIdentity, publicKey } from '@metaplex-foundation/umi'
9import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
10
11const umi = createUmi('https://api.mainnet-beta.solana.com').use(genesis())
12
13// umi.use(keypairIdentity(yourKeypair))
14
15const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT')
16const baseMint = publicKey('YOUR_PROJECT_TOKEN_MINT')
17const recipient = publicKey('VESTING_RECIPIENT')
18const bucketIndex = 0
19
20const [vestingBucket] = findClaimScheduleBucketV2Pda(umi, {
21 genesisAccount,
22 bucketIndex,
23})
24
25const DAY = 86_400n
26const vestingStart = BigInt(Math.floor(Date.now() / 1000)) + 7n * DAY
27const vestingEnd = vestingStart + 365n * DAY
28
29await addClaimScheduleBucketV2(umi, {
30 genesisAccount,
31 baseMint,
32 authority: umi.identity,
33 recipient,
34 bucketIndex,
35 baseTokenAllocation: 100_000_000_000_000n, // 100,000 tokens at 9 decimals
36 claimStartCondition: createTimeAbsoluteCondition(vestingStart),
37 claimSchedule: createClaimSchedule({
38 startTime: vestingStart,
39 endTime: vestingEnd,
40 period: 30n * DAY,
41 cliffTime: vestingStart,
42 cliffAmountBps: 1_000, // 10%
43 }),
44 pausable: true,
45 cancelable: true,
46 cancelableByRecipient: false,
47 transferable: true,
48 transferableByRecipient: false,
49 backendSigner: null,
50 endBehaviors: [],
51}).sendAndConfirm(umi)
52
53console.log('ClaimScheduleBucketV2:', vestingBucket)

添加所有其他分发 Bucket,然后按 Genesis 设置 调用 finalizeV2。Finalize 不可逆。

领取已归属的项目代币

claimClaimScheduleV2 将当前已归属且尚未领取的全部代币转到 Bucket 存储的接收方。

claimClaimScheduleV2.ts
1import {
2 claimClaimScheduleV2,
3 findClaimScheduleBucketV2Pda,
4 genesis,
5} from '@metaplex-foundation/genesis'
6import { keypairIdentity, publicKey } from '@metaplex-foundation/umi'
7import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
8
9const umi = createUmi('https://api.mainnet-beta.solana.com').use(genesis())
10
11// umi.use(keypairIdentity(yourKeypair))
12
13const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT')
14const baseMint = publicKey('YOUR_PROJECT_TOKEN_MINT')
15const recipient = publicKey('VESTING_RECIPIENT')
16const [vestingBucket] = findClaimScheduleBucketV2Pda(umi, {
17 genesisAccount,
18 bucketIndex: 0,
19})
20
21await claimClaimScheduleV2(umi, {
22 genesisAccount,
23 bucket: vestingBucket,
24 baseMint,
25 recipient,
26}).sendAndConfirm(umi)
27
28// All currently vested, unclaimed tokens are sent to the stored recipient.

支付方不必是接收方。该指令会在需要时创建接收方的 associated token account,并且永远不能将代币重定向到支付方。

若自上次领取以来尚未经过一个完整归属周期,领取可能返回 NothingToClaim。请等待下一个周期,或在发送交易前检查获取到的 Bucket 状态。

Claim Schedule 归属机制

领取计划将悬崖分配与剩余线性分配独立解锁。

字段约束效果
startConditionTimeAbsoluteTimeRelativeNever锚定线性归属时间线
duration大于零,不超过 10 年定义线性分配何时完全归属
period大于零且不超过 duration使线性归属按离散步骤推进
cliffConditionTimeAbsoluteTimeRelativeNever独立于线性计划解锁悬崖
cliffAmountBps010_000将分配的 0% 到 100% 分配给悬崖

对于分配 A 和悬崖基点 C,悬崖数量为 A × C / 10,000。剩余 A - cliffAmountduration 内按完整 period 步骤线性归属。

悬崖不会自动延迟线性计划。请将 startConditioncliffCondition 显式设置为预期时间戳;任一条件都可能在另一条件之前、期间或之后触发。

悬崖不得晚于 startCondition + duration。线性完成后的成功领取会触发 Bucket 的结束条件;更晚的悬崖将超出冻结的有效时间,可能永远无法变为可领取。

领取门控与归属曲线

claimStartCondition 门控代币提取,而 claimSchedule.startCondition 控制线性分配何时累积。

这种分离支持在领取开放之前就开始累积的计划。例如,归属可以从入职日期开始,而 claimStartCondition 在代币生成事件之前阻止提取。

TimeAbsolute 条件在领取检查它们时会自我更新。TimeRelative 条件是被动的,需要 triggerConditionsV2,并将每个被引用的 Bucket 作为可写 remaining account 传入。

triggerConditionsV2.ts
1import { genesis, triggerConditionsV2 } from '@metaplex-foundation/genesis'
2import { keypairIdentity, publicKey } from '@metaplex-foundation/umi'
3import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
4
5const umi = createUmi('https://api.mainnet-beta.solana.com').use(genesis())
6
7// umi.use(keypairIdentity(yourKeypair))
8
9const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT')
10const baseMint = publicKey('YOUR_PROJECT_TOKEN_MINT')
11const vestingBucket = publicKey('CLAIM_SCHEDULE_BUCKET_PDA')
12const referenceBucket = publicKey('REFERENCE_BUCKET_PDA')
13
14await triggerConditionsV2(umi, {
15 genesisAccount,
16 bucket: vestingBucket,
17 baseMint,
18})
19 .addRemainingAccounts([
20 { pubkey: referenceBucket, isSigner: false, isWritable: true },
21 ])
22 .sendAndConfirm(umi)
23
24// Eligible TimeRelative conditions on the vesting bucket are now triggered.

在引用条件满足之后、领取或评估归属状态之前,运行此 Permissionless crank。

周期性线性解锁

period 字段使线性分配按步骤而不是连续解锁。

在 365 天 duration 和 30 天 period 下,线性分配在每个完整的 30 天周期后增加。任何舍入余数在完整 duration 结束时可领取。

暂停调整后的归属时间

暂停 Bucket 会停止归属时间,恢复时将有效时间线按总暂停时长平移。

Bucket 记录 pausedAttotalSecondsPaused。在暂停期间取消会将计划冻结在 pausedAt,因此暂停所耗时间不会增加已归属数量。

项目归属运行时控制

运行时控制默认关闭,必须在创建 Bucket 时用策略标志启用。

策略标志授权角色指令结果
pausableGenesis 权限方setClaimSchedulePausedStateV2暂停或恢复归属累积
cancelableGenesis 权限方cancelClaimScheduleBucketV2在取消时刻冻结归属
cancelableByRecipient接收方cancelClaimScheduleBucketV2允许接收方冻结归属
transferableGenesis 权限方transferRecipientClaimScheduleBucketV2更改归属接收方
transferableByRecipient接收方transferRecipientClaimScheduleBucketV2允许当前接收方转移分配

仅启用项目归属协议所需的控制。权限方取消或接收方转移权会实质改变向接收方提供的保证。

暂停和恢复项目归属

当创建时启用了 pausable 时,setClaimSchedulePausedStateV2 会暂停或恢复 Bucket。

pauseClaimScheduleBucketV2.ts
1import {
2 genesis,
3 setClaimSchedulePausedStateV2,
4} from '@metaplex-foundation/genesis'
5import { keypairIdentity, publicKey } from '@metaplex-foundation/umi'
6import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
7
8const umi = createUmi('https://api.mainnet-beta.solana.com').use(genesis())
9
10// umi.use(keypairIdentity(yourKeypair))
11
12const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT')
13const vestingBucket = publicKey('CLAIM_SCHEDULE_BUCKET_PDA')
14
15await setClaimSchedulePausedStateV2(umi, {
16 genesisAccount,
17 bucket: vestingBucket,
18 signer: umi.identity,
19 paused: true,
20 padding: Array(6).fill(0),
21}).sendAndConfirm(umi)
22
23// Set paused to false and send again to resume vesting.

设置 paused: false 以恢复。只有 Genesis 权限方可以使用此控制。

取消项目归属

cancelClaimScheduleBucketV2 冻结归属,但不会移除已经归属的代币。

cancelClaimScheduleBucketV2.ts
1import {
2 cancelClaimScheduleBucketV2,
3 genesis,
4} from '@metaplex-foundation/genesis'
5import { keypairIdentity, publicKey } from '@metaplex-foundation/umi'
6import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
7
8const umi = createUmi('https://api.mainnet-beta.solana.com').use(genesis())
9
10// umi.use(keypairIdentity(yourKeypair))
11
12const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT')
13const vestingBucket = publicKey('CLAIM_SCHEDULE_BUCKET_PDA')
14
15await cancelClaimScheduleBucketV2(umi, {
16 genesisAccount,
17 bucket: vestingBucket,
18 signer: umi.identity,
19 padding: Array(7).fill(0),
20}).sendAndConfirm(umi)
21
22// Vesting is frozen, but the recipient can still claim the vested remainder.

接收方可以继续领取已归属剩余。除非配置并触发 ReallocateBaseTokensOnCancel 结束行为,未归属代币仍留在 Genesis 账本中。

转移项目归属接收方

transferRecipientClaimScheduleBucketV2 更改接收所有未来领取的钱包。

transferClaimScheduleRecipientV2.ts
1import {
2 genesis,
3 transferRecipientClaimScheduleBucketV2,
4} from '@metaplex-foundation/genesis'
5import { keypairIdentity, publicKey } from '@metaplex-foundation/umi'
6import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
7
8const umi = createUmi('https://api.mainnet-beta.solana.com').use(genesis())
9
10// umi.use(keypairIdentity(yourKeypair))
11
12const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT')
13const vestingBucket = publicKey('CLAIM_SCHEDULE_BUCKET_PDA')
14
15await transferRecipientClaimScheduleBucketV2(umi, {
16 genesisAccount,
17 bucket: vestingBucket,
18 signer: umi.identity,
19 newRecipient: publicKey('NEW_RECIPIENT'),
20 padding: Array(7).fill(0),
21}).sendAndConfirm(umi)
22
23// Future claims are sent to the new stored recipient.

授权签名者取决于启用的是 transferable 还是 transferableByRecipient

取消后重新分配未归属代币

ReallocateBaseTokensOnCancel 将已取消 Bucket 的未归属剩余的 100% 转到 UnlockedBucketV2

在调用 finalizeV2 之前配置该行为,可通过 addClaimScheduleBucketV2.endBehaviorssetClaimScheduleBucketV2Behaviors。Genesis 程序在 Finalize 之后拒绝行为配置。

reallocateClaimScheduleOnCancelV2.ts
1import {
2 genesis,
3 setClaimScheduleBucketV2Behaviors,
4 triggerBehaviorsV2,
5} from '@metaplex-foundation/genesis'
6import { findAssociatedTokenPda, mplToolbox } from '@metaplex-foundation/mpl-toolbox'
7import { keypairIdentity, publicKey } from '@metaplex-foundation/umi'
8import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
9
10const umi = createUmi('https://api.mainnet-beta.solana.com')
11 .use(mplToolbox())
12 .use(genesis())
13
14// umi.use(keypairIdentity(yourKeypair))
15
16const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT')
17const baseMint = publicKey('YOUR_PROJECT_TOKEN_MINT')
18const quoteMint = publicKey('So11111111111111111111111111111111111111112')
19const vestingBucket = publicKey('CLAIM_SCHEDULE_BUCKET_PDA')
20const destinationBucket = publicKey('UNLOCKED_BUCKET_PDA')
21const [destinationQuoteTokenAccount] = findAssociatedTokenPda(umi, {
22 owner: destinationBucket,
23 mint: quoteMint,
24})
25
26// Configure before finalizeV2.
27await setClaimScheduleBucketV2Behaviors(umi, {
28 genesisAccount,
29 bucket: vestingBucket,
30 authority: umi.identity,
31 padding: Array(3).fill(0),
32 endBehaviors: [
33 {
34 __kind: 'ReallocateBaseTokensOnCancel',
35 processed: false,
36 padding: Array(6).fill(0),
37 destinationBucket,
38 },
39 ],
40}).sendAndConfirm(umi)
41
42// Run after finalization and after cancelClaimScheduleBucketV2 ends the bucket.
43await triggerBehaviorsV2(umi, {
44 genesisAccount,
45 primaryBucket: vestingBucket,
46 baseMint,
47 quoteMint,
48})
49 .addRemainingAccounts([
50 { pubkey: destinationBucket, isSigner: false, isWritable: true },
51 {
52 pubkey: destinationQuoteTokenAccount,
53 isSigner: false,
54 isWritable: true,
55 },
56 ])
57 .sendAndConfirm(umi)
58
59// The unvested remainder is moved to the destination UnlockedBucketV2 balance.

该行为更改 Bucket 余额,而不是原始分配值。它只能在领取计划 Bucket 结束后运行,并且每个领取计划 Bucket 最多只能有一个取消再分配行为。

使用 TimeRelative 开始或悬崖条件的计划必须先触发这些条件,取消再分配才能计算已归属数量。请先对所需引用账户运行 triggerConditionsV2

发行前更新项目归属

updateClaimScheduleBucketV2 仅能在 Finalize 之前且归属尚未开始时替换分配、计划或领取开始条件。

在任何代币已被领取、claimStartCondition 已满足,或线性开始或悬崖条件已满足之后,Bucket 会以 ClaimScheduleUpdateForbidden 拒绝更新。这三个槽位中的任一 TimeRelative 条件也会立即禁用更新,因为程序在更新期间无法验证其引用 Bucket。运行时暂停、取消和转移控制使用专用指令,而不是更新指令。

获取项目归属状态

fetchClaimScheduleBucketV2 返回分配、领取进度、有效暂停状态、策略和结束行为。

fetchClaimScheduleBucketV2.ts
1import {
2 fetchClaimScheduleBucketV2,
3 findClaimScheduleBucketV2Pda,
4 genesis,
5} from '@metaplex-foundation/genesis'
6import { publicKey } from '@metaplex-foundation/umi'
7import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
8
9const umi = createUmi('https://api.mainnet-beta.solana.com').use(genesis())
10
11const genesisAccount = publicKey('YOUR_GENESIS_ACCOUNT')
12const [vestingBucket] = findClaimScheduleBucketV2Pda(umi, {
13 genesisAccount,
14 bucketIndex: 0,
15})
16
17const account = await fetchClaimScheduleBucketV2(umi, vestingBucket)
18
19console.log('Recipient:', account.recipient)
20console.log('Allocation:', account.bucket.baseTokenAllocation)
21console.log('Remaining balance:', account.bucket.baseTokenBalance)
22console.log('Claimed:', account.amountClaimed)
23console.log('Paused:', account.paused)
24console.log('Total seconds paused:', account.totalSecondsPaused)

会计不变量是 baseTokenBalance = baseTokenAllocation - amountClaimed,直到结束行为重新分配未归属余额。

ClaimScheduleBucketV2 账户字段

ClaimScheduleBucketV2 账户在固定归属状态之后存储可变长度的结束行为列表。

字段说明
bucket包含分配、剩余余额、mint、index 和费用数据的共享 Bucket 头
recipient接收每笔已归属代币领取的钱包
amountClaimed已转到接收方的累计代币
claimSchedule悬崖与基于周期的线性归属曲线
claimStartCondition领取开始前必须打开的独立门控
claimEndCondition由取消或自然完成触发的程序拥有结束条件
paused归属时间当前是否停止
pausedAt当前暂停开始的时间戳
totalSecondsPaused从归属中排除的累计暂停时长
extensions运行时策略和可选后端签名者
endBehaviorsBucket 结束后可用的操作

常见项目归属错误

领取计划错误标识无效的计划配置、未授权控制,或在错误生命周期阶段尝试的操作。

错误原因解决方法
InvalidClaimSchedulePeriodperiod 为零使用正数 period
InvalidClaimScheduleDurationduration 为零或超过 10 年使用 1 秒到 315,360,000 秒的 duration
ClaimScheduleDurationTooShortperiod 超过 duration减小 period 或增加 duration
InvalidClaimScheduleCliffAmountcliffAmountBps 超过 10_000使用 0 到 10,000 基点
NothingToClaim没有新的悬崖或完整线性周期已归属等待下一次解锁或检查 Bucket 状态
ClaimScheduleUpdateForbidden领取门控或计划已开始、代币已被领取,或相关条件为 TimeRelative在触发前配置绝对时间字段;相对计划无法更新
ClaimScheduleUnauthorized签名者不允许使用该控制使用已启用策略所要求的 Genesis 权限方或接收方
ClaimSchedulePolicyDisabled请求的暂停、取消或转移策略已关闭创建 Bucket 时启用该策略
InvalidBackendSigner配置的后端签名者未授权领取包含配置的后端签名者
ClaimScheduleConditionNotTriggered取消再分配依赖于未解析的相对条件先触发相对计划条件

快速参考

项目归属可通过 Genesis V2 和 @metaplex-foundation/genesis 使用。

项目
ProgramGNS1S5J5AspKXgpjz6SvKL66kPaKWAhaGRhCqPRxii2B
Tested SDK@metaplex-foundation/[email protected]
Tested Umi compatibility@metaplex-foundation/umi@^1.4.1
Bucket PDA seeds"claim_schedule_v2"、Genesis 账户、作为 u8bucketIndex
Maximum vesting duration315,360,000 秒(10 年)
Cliff range0 到 10,000 基点
Recipients per bucket一名
Bucket creation fee0
Devnet validation添加、Finalize、暂停、转移、领取、取消和再分配的完整流程于 2026-08-24 通过(测试账户
Sourcemetaplex-foundation/genesis

注意事项

项目归属具有应纳入项目代币分发设计的生命周期和授权约束。

  • 在调用 finalizeV2 之前添加并配置 ClaimScheduleBucketV2 账户。
  • 为每位接收方或独立管理的分配创建一个 Bucket。
  • 除非配置了可选后端签名者扩展,否则领取是 Permissionless 的。
  • 后端签名者增加领取授权,但不能将领取从当前存储的接收方重定向走。
  • 取消保留已归属代币;收回未归属代币需要 ReallocateBaseTokensOnCancel
  • Never 计划会永久锁定代币,主要用于 锁定的 LP 代币

常见问题

ClaimScheduleBucketV2 与 ClaimSchedule 有什么区别?

ClaimScheduleBucketV2 是持有一名接收方分配和运行时状态的 Genesis 流出 Bucket。ClaimSchedule 是存储在该 Bucket 内的可复用悬崖与线性解锁曲线。

归属接收方必须提交每一次领取吗?

不必。claimClaimScheduleV2 是 Permissionless 的,但程序始终将代币转到 Bucket 上存储的接收方。若配置了后端签名者扩展,该签名者也必须授权每一次领取。

归属开始后项目还能更改归属计划吗?

不能。updateClaimScheduleBucketV2 仅在 Finalize 之前、领取门控或任一计划条件满足之前、以及尚未发生任何领取时有效。TimeRelative 领取门控、线性开始或悬崖也会立即禁用更新。

归属 Bucket 被取消时,未归属代币会怎样?

取消会冻结归属,并保留已归属数量给接收方。若 Bucket 有 ReallocateBaseTokensOnCancel 行为,任何人都可以触发该行为,将未归属剩余转到 UnlockedBucketV2

一个 ClaimScheduleBucketV2 可以向多名接收方归属代币吗?

不可以。每个 Bucket 只有一名接收方。为需要独立记账或策略控制的每位接收方或每笔分配创建一个 ClaimScheduleBucketV2

术语表

项目归属术语区分 Bucket 账户、其嵌入计划和生命周期控制。

术语定义
ClaimScheduleBucketV2将一笔基础代币分配归属给一名接收方的 Genesis 流出 Bucket
ClaimSchedule可复用的悬崖与基于周期的线性代币解锁曲线
Claim gate控制提取何时可以开始的 Bucket 级 claimStartCondition
Cliff独立条件触发时解锁的总分配百分比
Period用于按离散步骤推进线性归属的间隔
Effective time排除 Bucket 暂停时长后的挂钟时间
Cancellation reallocation将已取消 Bucket 的未归属剩余转到未锁定 Bucket 的结束行为