分发类型

钱包分发

Last updated August 27, 2026

钱包分发将固定代币量分配给公钥,并通过 distribute 验证每笔分配。

摘要

钱包分发以钱包或其他公钥作为 Merkle 叶子 identity,并始终将分配转入该 identity 的 associated token account。

  • 用 prepareDistribution 生成兼容的根和证明。
  • 当重复的接收方和 amount 分配必须保持不同时设置 nonce。
  • 选择与应用程序签名模型匹配的 distributor 模式。
  • 保存每份证明,因为无法仅从链上根重建证明。

钱包分配形态

每笔钱包分配包含地址、代币基本单位的 amount,以及可选的无符号 64 位 nonce。

walletAllocations.ts
1import { mplDistro, prepareDistribution } 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 contributorA = publicKey(process.env.RECIPIENT_1!)
10const contributorB = publicKey(process.env.RECIPIENT_2!)
11
12const allocations = [
13 { address: contributorA, amount: 1_000_000n, nonce: 0n },
14 { address: contributorB, amount: 2_500_000n, nonce: 0n },
15]
16
17const { root, proofs, treeHeight } = prepareDistribution(allocations)
18console.log(root, proofs.length, treeHeight)
19
20// Merkle root, two proofs, and treeHeight for the wallet allocations

amount 必须大于零。nonce 默认为零,仅当两片叶子否则会包含相同地址和 amount 时才应更改。

MPL-Distro Merkle 格式

MPL-Distro 使用 Keccak-256 和已排序的内部节点对来哈希分配数据。

元素编码
Leaf data`recipient_pubkey[32]
Leaf hash`keccak256("claim"
Internal node`keccak256(0x01
Odd node与自身配对
Proof item一个 32 字节兄弟哈希
Maximum configured height64

请使用 SDK 辅助函数,而不是独立实现此格式。用 SHA-256、大端整数、未排序对或不同域前缀生成的证明会以 InvalidClaimProof 失败。

树高度是证明上限

链上 treeHeight 限制证明长度;它不会独立验证 totalClaimants。请传递 prepareDistribution 返回的值。

钱包领取提交模式

allowedDistributor 设置决定谁可以提交 distribute。

Permissionless 钱包领取

Permissionless 领取允许任意有资金的支付方提交有效证明,而程序只将代币发送给已提交的接收方。

用于接收方付费的领取页面,或在有人实际领取时由中继者支付 SOL。不要用 Distro 从后端推送每一笔分配;那通常比直接 SPL 转账更贵。

Recipient 签名的钱包领取

Recipient 领取要求已提交的接收方签署交易。

当受益人必须明确接受分配,或仅凭证明访问不得授权提交时使用此模式。

Permissioned 钱包领取

Permissioned 领取需要配置的 permissionedDistributor 签名者。

当一个后端在更宽的链上领取窗口内控制释放时机时使用此模式。权限方稍后可以 更改 permissioned distributor。

创建时设置 Permissioned Distributor

createDistribution 将 permissionedDistributor 默认为 System Program 公钥。当 allowedDistributor 为 Permissioned 时请传入真实 distributor 地址,否则每笔领取都会以 InvalidDistributor 失败。

提交钱包领取

distribute 指令验证证明,必要时创建 associated token account,转移代币,并原子地记录收据。

claimDistribution.ts
1import {
2 distribute,
3 mplDistro,
4 prepareDistribution,
5} from '@metaplex-foundation/mpl-distro'
6import { publicKey } from '@metaplex-foundation/umi'
7import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
8
9const umi = createUmi(
10 process.env.RPC_URL ?? 'https://api.devnet.solana.com'
11).use(mplDistro())
12
13// The payer may be the recipient or a third-party distributor, depending on
14// the distribution's allowedDistributor setting.
15
16const distribution = publicKey(process.env.DISTRIBUTION_ADDRESS!)
17const mint = publicKey(process.env.TOKEN_MINT!)
18const recipients = [
19 { address: publicKey(process.env.RECIPIENT_1!), amount: 100_000n },
20 { address: publicKey(process.env.RECIPIENT_2!), amount: 250_000n },
21]
22const recipientIndex = 0
23const { proofs } = prepareDistribution(recipients)
24
25await distribute(umi, {
26 distribution,
27 mint,
28 recipient: recipients[recipientIndex].address,
29 amount: recipients[recipientIndex].amount,
30 proof: proofs[recipientIndex],
31 nonce: 0,
32}).sendAndConfirm(umi)
33
34// The recipient ATA receives 100000 base units and a claim receipt is created.

支付方支付交易费、0.002 SOL 协议费和账户租金。可选领取收据租金补贴见 注资与回收。

钱包领取收据

领取收据防止一笔精确分配被处理超过一次。

字段值
PDA seeds["claim_receipt", distribution, recipient, amount_le, nonce_le]
Stored distribution分发 PDA
Stored recipient叶子中的钱包或公钥
Stored amount已领取的代币基本单位
Stored nonce叶子 nonce
Account size88 字节

在当前程序中,领取收据是永久的,没有关闭指令。

向 Core 资产签名者领取

distributeToAssetAndClaim 将 Wallet 分配领取到 MPL Core 资产签名者 PDA,然后用 Core Execute 将代币转移到当前所有者。

从每个资产的签名者 PDA 构建 Merkle 叶子,而不是从所有者钱包。辅助函数随后从该 PDA 的 associated token account 转出已领取代币。

claimToCoreAsset.ts
1import { findAssetSignerPda } from '@metaplex-foundation/mpl-core'
2import {
3 distributeToAssetAndClaim,
4 mplDistro,
5 prepareDistribution,
6} from '@metaplex-foundation/mpl-distro'
7import { publicKey } from '@metaplex-foundation/umi'
8import { createUmi } from '@metaplex-foundation/umi-bundle-defaults'
9
10const umi = createUmi(
11 process.env.RPC_URL ?? 'https://api.devnet.solana.com'
12).use(mplDistro())
13
14const distribution = publicKey(process.env.DISTRIBUTION_ADDRESS!)
15const mint = publicKey(process.env.TOKEN_MINT!)
16const asset = publicKey(process.env.CORE_ASSET!)
17const currentOwner = publicKey(process.env.CORE_ASSET_OWNER!)
18
19const [assetSigner] = findAssetSignerPda(umi, { asset })
20const allocations = [{ address: assetSigner, amount: 100_000n }]
21const { proofs } = prepareDistribution(allocations)
22
23await distributeToAssetAndClaim(umi, {
24 distribution,
25 mint,
26 asset,
27 recipient: currentOwner,
28 amount: allocations[0].amount,
29 proof: proofs[0],
30 nonce: 0,
31}).sendAndConfirm(umi)
32
33// Tokens are claimed to the asset-signer PDA, then transferred to the current owner.

此辅助函数是 Wallet 分发流程。它不是 LegacyNft 领取,也不会在链上验证 Core 集合成员资格。

钱包分发安全检查清单

生产钱包分发应在发布根之前校验分配完整性。

  • 确认分配之和不超过计划存款。
  • 调用 SDK 之前拒绝零、负数或超出范围的 amount。
  • 分配确定性 nonce 并与证明一起存储。
  • 针对最终根测试随机证明和每个边缘分配。
  • 将权限方和 permissioned-distributor 密钥放在浏览器应用程序之外。
  • 确认集群时间戳,并在开始和结束边界周围留出运营时间。

注意事项

钱包分发可以使用任意公钥作为叶子 identity,但默认目的地是其 SPL 代币 associated token account。

  • Core 资产领取使用 distributeToAssetAndClaim,并要求 Merkle 叶子中有资产签名者 PDA。
  • totalClaimants 不是链上领取上限。
  • 即使证明有效,金库代币不足时仍会失败。
  • 两个精确边界时间戳都会接受领取:startTime <= now <= endTime。

常见问题

后端能否在接收方不签名的情况下提交领取?

可以。Permissionless 分发允许中继者支付 SOL 并提交证明。代币仍到达叶子地址。用于让没有 SOL 的接收方能够领取,而不是替代批量 SPL 转账。

什么阻止同一钱包分配被领取两次?

确定性领取收据 PDA 记录每个唯一的 distribution、recipient、amount 和 nonce 元组。

totalClaimants 会限制成功领取次数吗?

不会。totalClaimants 是元数据;Merkle 包含和可用金库资金决定分配能否领取。

Core 资产分配叶子应放入哪个地址?

使用 Core 资产签名者 PDA。distributeToAssetAndClaim 随后将代币转移到当前所有者。