配布タイプ

ウォレット配布

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 は 0 より大きくなければなりません。nonce のデフォルトは 0 で、2 つのリーフが同じアドレスと amount になるときだけ変えます。

MPL-Distro の Merkle 形式

MPL-Distro は Keccak-256 とソート済み内部ノード対で割り当てデータをハッシュします。

要素エンコード
Leaf data`recipient_pubkey[32]
Leaf hash`keccak256("claim"
Internal node`keccak256(0x01
Odd node自身とペアになる
Proof item1 つの 32 バイト兄弟ハッシュ
Maximum configured height64

この形式を独自実装せず、SDK ヘルパーを使ってください。SHA-256、ビッグエンディアン整数、未ソート対、別のドメインプレフィックスで生成した証明は InvalidClaimProof で失敗します。

ツリー高は証明の上限

オンチェーンの treeHeight は証明長を制限します。totalClaimants を独立に検証しません。prepareDistribution が返す値を渡してください。

ウォレットクレームの送信モード

allowedDistributor 設定は、誰が distribute を送信できるかを決めます。

Permissionless ウォレットクレーム

Permissionless クレームでは、資金のある任意の支払者が有効な証明を送信でき、プログラムはコミット済み受取人にだけトークンを送ります。

受取人が支払うクレームページ、または実際にクレームしたときにリレイヤーが SOL を払う場合に使います。バックエンドから全割り当てを Distro で押し出さないでください。通常は直接 SPL 送金より高くなります。

Recipient 署名のウォレットクレーム

Recipient クレームでは、コミット済み受取人がトランザクションに署名する必要があります。

受益者が割り当てを明示的に受け入れる必要がある場合、または証明へのアクセスだけでは送信を許可したくない場合に使います。

Permissioned ウォレットクレーム

Permissioned クレームでは、設定された permissionedDistributor 署名者が必要です。

1 つのバックエンドが、より広いオンチェーンクレーム期間の中で解放タイミングを制御する場合に使います。権限者は後から 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 プロトコル手数料、アカウント家賃を払います。任意のクレームレシート家賃補助金は 資金投入と回収 を参照してください。

ウォレットクレームレシート

クレームレシートは、1 つの exact な割り当てが 2 回以上処理されないようにします。

フィールド
PDA seeds["claim_receipt", distribution, recipient, amount_le, nonce_le]
Stored distribution配布 PDA
Stored recipientリーフのウォレットまたは公開鍵
Stored amountクレームされたトークン最小単位
Stored nonceリーフ nonce
Account size88 バイト

現行プログラムではクレームレシートは永続的で、クローズ命令はありません。

コアアセットサイナーへのクレーム

distributeToAssetAndClaimWallet 割り当てを MPL Core アセットサイナー PDA へクレームし、Core Execute でトークンを現在の所有者へ移します。

Merkle リーフは所有者ウォレットではなく、各アセットのサイナー PDA から構築します。ヘルパーはその 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 を呼ぶ前に 0、負、範囲外の amount を拒否する。
  • 決定的 nonce を割り当て、証明と一緒に保存する。
  • 最終ルートに対してランダム証明とすべての端の割り当てをテストする。
  • 権限者と permissioned distributor の鍵をブラウザアプリケーションの外に置く。
  • クラスタタイムスタンプを確認し、開始と終了境界の周りに運用時間を残す。

注意事項

ウォレット配布は任意の公開鍵をリーフ identity にできますが、デフォルトの宛先はその SPL トークン associated token account です。

  • Core アセットクレームは distributeToAssetAndClaim を使い、Merkle リーフにアセットサイナー PDA が必要です。
  • totalClaimants はオンチェーンのクレーム上限ではありません。
  • 有効な証明でも、ボールトにトークンが足りないと失敗します。
  • 境界の両方のタイムスタンプでクレームは受理されます: startTime <= now <= endTime

FAQ

バックエンドは受取人の署名なしでクレームを送信できますか?

はい。Permissionless 配布ではリレイヤーが SOL を払って証明を送信できます。トークンはリーフアドレスへ行きます。SOL のない受取人がクレームできるように使うものであり、一括 SPL 送金の代替ではありません。

同じウォレット割り当てが 2 回クレームされるのを何が防ぎますか?

決定的なクレームレシート PDA が、一意の distribution、recipient、amount、nonce タプルを記録します。

totalClaimants は成功クレーム数を制限しますか?

いいえ。totalClaimants はメタデータです。Merkle 包含とボールト資金が、割り当てをクレームできるかを決めます。

Core アセット割り当てのリーフにはどのアドレスを入れますか?

Core アセットサイナー PDA を使います。distributeToAssetAndClaim がそのトークンを現在の所有者へ移します。