分发类型

钱包分发

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

createDistributionpermissionedDistributor 默认为 System Program 公钥。当 allowedDistributorPermissioned 时请传入真实 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 资产签名者领取

distributeToAssetAndClaimWallet 分配领取到 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 随后将代币转移到当前所有者。