操作
注资与回收
Last updated August 26, 2026
MPL-Distro 将分发金库中的代币资金与领取收据租金的可选 SOL 资金分开。
摘要
权限方将 SPL 代币存入分发的 associated token account,并在启用收据补贴时可以向分发 PDA 存入 SOL。
- 存入足够的代币基本单位以覆盖每笔 Merkle 分配。
- 为每次预期成功领取预算一次领取收据租金。
- 同时监控记录的
totalAmount和实际金库代币余额。 - 仅在分发非活动时提取未领取代币和未使用的补贴 SOL。
快速开始
MPL-Distro 注资与回收遵循四个运营步骤。
- 将所有分配量求和,并存入那么多代币基本单位。
- 启用收据补贴时,将预期收据租金预算转入分发 PDA。
- 监控实际金库余额、分发 SOL 和领取合计。
- 窗口结束后,提取未领取代币和未使用的补贴 SOL。
存入分发代币
deposit 指令将代币从存款人账户转入分发 PDA 的规范 associated token account。即使由不同钱包提供代币,当前分发权限方也必须签署每一笔存款。
1import {
2 deposit,
3 mplDistro,
4} from '@metaplex-foundation/mpl-distro'
5import { publicKey } from '@metaplex-foundation/umi'
6import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
7
8const umi = createUmi(
9 process.env.RPC_URL ?? 'https://api.devnet.solana.com'
10).use(mplDistro())
11
12// The Umi identity must be the current distribution authority.
13
14const distribution = publicKey(process.env.DISTRIBUTION_ADDRESS!)
15const mint = publicKey(process.env.TOKEN_MINT!)
16const totalAmount = 350_000n
17
18await deposit(umi, {
19 distribution,
20 mint,
21 amount: totalAmount,
22}).sendAndConfirm(umi)
23
24// The distribution ATA contains 350000 base units.
SDK 将 depositor、payer 和 authority 默认为 Umi 支付方,并推导两个 associated token account。当另一个钱包拥有源代币时,传入单独的存款人签名者,并仍传入当前分发权限方。
1import { deposit, mplDistro } from '@metaplex-foundation/mpl-distro'
2import { publicKey } from '@metaplex-foundation/umi'
3import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
4
5const umi = createUmi(
6 process.env.RPC_URL ?? 'https://api.devnet.solana.com'
7).use(mplDistro())
8
9const distribution = publicKey(process.env.DISTRIBUTION_ADDRESS!)
10const mint = publicKey(process.env.TOKEN_MINT!)
11const amount = 350_000n
12const treasurySigner = umi.identity
13const feePayer = umi.payer
14const distributionAuthority = umi.identity
15
16await deposit(umi, {
17 distribution,
18 mint,
19 depositor: treasurySigner,
20 payer: feePayer,
21 authority: distributionAuthority,
22 amount,
23}).sendAndConfirm(umi)
24
25// Tokens move from the treasury ATA to the distribution vault.
程序在每次存款后增加 totalAmount。它不会将该值与 Merkle 根提交的分配之和比较。
计算代币存款
所需代币存款是以 mint 基本单位表示的所有分配量之和。
1import { publicKey } from '@metaplex-foundation/umi'
2
3const allocations = [
4 { address: publicKey(process.env.RECIPIENT_1!), amount: 100_000n },
5 { address: publicKey(process.env.RECIPIENT_2!), amount: 250_000n },
6]
7
8const totalAmount = allocations.reduce(
9 (total, allocation) => total + BigInt(allocation.amount),
10 0n
11)
12console.log(totalAmount)
13
14// 350000
仅当权限方接受稍后必须回收超额部分时,才存入有意的缓冲。当记录余额低于其分配时,有效证明会以 InsufficientFunds 失败;如果实际金库余额更低,SPL 转账也可能失败。
为领取收据补贴注资
收据补贴让分发 PDA 能够向交易支付方报销创建每张领取收据所用的租金。
在 createDistribution 期间启用 subsidizeReceipts,通过 RPC 计算租金,并将 SOL 直接转入分发 PDA。
1import { getClaimReceiptSize, mplDistro } from '@metaplex-foundation/mpl-distro'
2import { transferSol } from '@metaplex-foundation/mpl-toolbox'
3import { multiplyAmount, publicKey } from '@metaplex-foundation/umi'
4import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
5
6const umi = createUmi(
7 process.env.RPC_URL ?? 'https://api.devnet.solana.com'
8).use(mplDistro())
9
10const distribution = publicKey(process.env.DISTRIBUTION_ADDRESS!)
11const expectedClaimCount = 2
12
13const receiptRent = await umi.rpc.getRent(getClaimReceiptSize())
14const budget = multiplyAmount(receiptRent, expectedClaimCount)
15
16await transferSol(umi, {
17 destination: distribution,
18 amount: budget,
19}).sendAndConfirm(umi)
20
21// The distribution PDA holds extra SOL for claim-receipt rent.
补贴预算边界
分发必须保留其自身的 rent-exempt 最低额。当剩余 SOL 无法同时覆盖分发租金和一次收据报销时,领取会以 InsufficientFundsToSubsidizeReceipts 失败。
MPL-Distro 注资快速参考
领取成本分为固定协议费、Solana 交易成本和账户租金。
| 成本 | 默认支付方 | 收据补贴是否覆盖 |
|---|---|---|
| 协议费(0.002 SOL) | 领取交易支付方 | 否 |
| 交易费 | 领取交易支付方 | 否 |
| 领取收据租金 | 领取交易支付方 | 是(启用且已注资时) |
| 接收方 ATA 租金 | 领取交易支付方 | 否 |
回收未领取代币
分发权限方在开始时间之前或结束时间之后用 withdraw 回收未领取或超额代币。
1import {
2 DISTRIBUTION_SIZE,
3 fetchDistribution,
4 mplDistro,
5 withdraw,
6 withdrawSubsidy,
7} from '@metaplex-foundation/mpl-distro'
8import { publicKey } from '@metaplex-foundation/umi'
9import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
10
11const umi = createUmi(
12 process.env.RPC_URL ?? 'https://api.devnet.solana.com'
13).use(mplDistro())
14
15// The Umi identity must be the current distribution authority.
16
17const distribution = publicKey(process.env.DISTRIBUTION_ADDRESS!)
18const mint = publicKey(process.env.TOKEN_MINT!)
19
20// Token and subsidy withdrawals succeed only outside the active window.
21await withdraw(umi, {
22 distribution,
23 mint,
24 amount: 50_000n,
25}).sendAndConfirm(umi)
26
27const distributionAccount = await fetchDistribution(umi, distribution)
28if (distributionAccount.subsidizeReceipts) {
29 const balance = await umi.rpc.getBalance(distribution)
30 const rent = await umi.rpc.getRent(DISTRIBUTION_SIZE)
31 const unusedSubsidy = balance.basisPoints - rent.basisPoints
32
33 if (unusedSubsidy > 0n) {
34 await withdrawSubsidy(umi, {
35 distribution,
36 recipient: umi.identity.publicKey,
37 amount: unusedSubsidy,
38 }).sendAndConfirm(umi)
39 }
40}
41
42// 50000 token base units and any unused receipt subsidy are returned.
活动区间是包含性的。当 startTime <= clusterTime <= endTime 时提取会被拒绝。
回收未使用的补贴 SOL
仅当补贴已启用且分发非活动时,权限方才用 withdrawSubsidy 回收未使用的收据补贴。
withdrawSubsidy 在转出请求的 lamport 金额的同时保留分发账户的 rent-exempt 最低额。请根据当前账户余额确定安全金额,而不是假设每次预期领取都已发生。
监控分发余额
生产系统应比较程序账本与实际 SPL 和 SOL 账户余额。
| 值 | 来源 | 含义 |
|---|---|---|
distribution.totalAmount | 分发账户 | 程序记录的存款减去提取;领取不会递减它 |
| Vault token amount | 分发 associated token account | 实际可转移的代币 |
| Distribution lamports | 分发 PDA 账户 | 租金储备加上可选的未使用收据补贴 |
claimCount | 分发账户 | 记录的成功领取次数 |
claimAmount | 分发账户 | 记录的已领取代币基本单位之和 |
代币提取账本使用 saturating subtraction,因此集成方不应假设 totalAmount 永远不会与 SPL 金库余额偏离。
注意事项
资金操作需要权限方控制和显式余额监控。
- 只有当前分发权限方可以授权存款。
- 存款允许在领取窗口之前、期间和之后进行。
- 代币和补贴提取在整个活动窗口期间被阻止。
- 任何人都可以直接向分发 PDA 转 SOL,但只有权限方可以通过程序提取补贴。
- 领取收据目前无法关闭,因此收据租金保持已分配。
常见问题
领取处于活动状态时,权限方可以提取代币吗?
不可以。代币提取从开始时间戳到结束时间戳(含两端)都会被拒绝。
subsidizeReceipts 报销哪些费用?
它只报销领取收据租金,不包括协议费、交易费或接收方代币账户租金。
领取开始后还可以再存入代币吗?
可以。存款不受时间限制,因此权限方可以补充资金不足的金库。
国库钱包能否在没有分发权限方的情况下存款?
不可以。即使由单独的存款人提供代币,当前权限方也必须签署 deposit。
