Introduction

Production Delivery

Last updated August 27, 2026

MPL-Distro stores only the Merkle root on-chain. A production airdrop persists each allocation's proof off-chain and gives recipients a way to submit it.

Summary

Production delivery is the off-chain work around an MPL-Distro distribution: store claim records, serve them to the right claimant, and recover leftovers when the window ends.

No Hosted Claim Interface

MPL-Distro does not ship a claim website or email, SMS, or Discord identity. The Metaplex CLI can create, fund, inspect, and recover a distribution; it does not generate Merkle proofs or submit claims. Notify users through any channel you already use; the on-chain leaf is still a wallet or legacy NFT mint.

Jump to: Prerequisites · Submission Mode · Persist Records · Deliver Proofs · Recover Tokens

Quick Start

A production MPL-Distro airdrop has five delivery steps around the on-chain program.

  1. Build a complete allocation list and generate the root with prepareDistribution.
  2. Persist one claim record per allocation, then create and fund the distribution.
  3. Serve each record from a claim page or lookup API keyed by wallet or NFT mint.
  4. Submit distribute or distributeToLegacyNft with the stored amount, nonce, and proof.
  5. After endTime, withdraw unclaimed tokens and unused receipt-rent subsidy.

Prerequisites

Production delivery starts from an existing SPL token mint and a finished allocation list.

  • A Getting Started distribution (or the same create and deposit steps in your backend)
  • Durable storage for claim records (database, object store, or downloadable file)
  • A claim transaction payer funded for rent, network fees, and the 0.002 SOL protocol fee
  • A distribution type: wallet or legacy NFT

Allocation amounts are token base units. For a 6-decimal mint, 1.0 token is 1_000_000.

Choose a Claim Submission Mode

allowedDistributor decides who may submit a valid proof; it does not change where the tokens go.

ModeWho signs the claimTypical production shape
PermissionlessAny funded payerA claim page where the user or a relayer pays; tokens still go to the leaf
RecipientThe leaf wallet or current NFT ownerA claim page where the beneficiary must approve the transaction
PermissionedThe configured permissionedDistributorOne backend is the only signer allowed to submit proofs

Tokens always arrive at the leaf's canonical associated token account (or the current NFT owner's ATA for LegacyNft). Permissionless submission cannot redirect funds to the payer.

Keep the distribution authority and any permissioned-distributor key outside browser applications.

Persist Allocation Records

Each claim needs the same address, amount, nonce, and proof that prepareDistribution used for that leaf. The on-chain account cannot reconstruct those values from the root.

Start from a complete list, then store the proof at the same index:

allocations.json
[
{
"address": "8SoWVrwJ6vPa3rcdNBkhznR54yJ6iQqPSmgcXVGnwtEu",
"amount": "10000000",
"nonce": "0"
},
{
"address": "GjwcWFQYzemBtpUoN5fMAP2FZviTtMRWCmrppGuTthJS",
"amount": "5000000",
"nonce": "0"
}
]
persistClaimRecords.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 allocations = [
10 { address: publicKey(process.env.RECIPIENT_1!), amount: 100_000n, nonce: 0n },
11 { address: publicKey(process.env.RECIPIENT_2!), amount: 250_000n, nonce: 0n },
12]
13
14const { root, proofs, treeHeight } = prepareDistribution(allocations)
15
16const claimRecords = allocations.map((allocation, index) => ({
17 address: allocation.address,
18 amount: allocation.amount.toString(),
19 nonce: (allocation.nonce ?? 0n).toString(),
20 proof: proofs[index],
21}))
22
23// Persist claimRecords with the distribution PDA after createDistribution.
24console.log(root, treeHeight, claimRecords.length)
25
26// One durable record per allocation: address, amount, nonce, and proof.

After createDistribution, store the distribution PDA on every record. The claim transaction needs that address plus mint, amount, nonce, and proof.

FieldRequired forNotes
addressLeaf identityWallet public key or legacy NFT mint
amountLeaf dataToken base units as a string or bigint
nonceLeaf dataDefaults to 0; required when the same address and amount appear twice
proofdistributeOne 32-byte sibling hash per tree level, in SDK order
distributiondistributePDA from findDistributionPda after create

Store Proofs Before Opening Claims

The authority cannot change the Merkle root, tree height, start time, or claimant count while startTime <= now <= endTime. Back up the full allocation file before the window starts.

Deliver Merkle Proofs

The application looks up one stored record and passes it to distribute or distributeToLegacyNft. MPL-Distro does not index recipients.

Common delivery shapes:

  1. Claim page. The user connects a wallet, pays network fees, and submits their stored proof.
  2. Lookup API. A service maps address{ amount, nonce, proof, distribution } for your frontend or relayer.
  3. Sponsored claim. The recipient (or an eligibility check) still triggers the claim. A relayer pays SOL so the user does not need a funded wallet. Tokens still go to the leaf ATA.

Sponsored claims are not a substitute for sending every allocation in one backend loop. Each Distro claim still pays the 0.002 SOL protocol fee. If every recipient will receive tokens immediately with no claim step, use direct SPL token transfers.

Use Distro when some allocations may go unclaimed, when you need a public Merkle commitment and time window, or when a relayer should pay only for people who actually claim.

For LegacyNft, key the lookup by NFT mint. Resolve the current owner at claim time; do not freeze a snapshot owner into the leaf unless you intended a wallet distribution instead.

Do not rebuild proofs from the on-chain root. A proof generated with a different hash, byte order, or leaf set fails with InvalidClaimProof.

Open the Claim Window

Claims succeed only when the cluster time is inside the inclusive startTimeendTime window and the vault holds enough tokens.

Create, deposit, and submit the first test claim with the Getting Started flow before opening the list to every recipient. Confirm:

  • A sample proof from the persisted file matches distribute.
  • The protocol fee payer has SOL for the 0.002 SOL fee plus receipt rent, or receipt subsidies are funded.
  • Authority keys are not exposed to the claim frontend.

Monitor Claims

A successful claim creates a permanent claim-receipt PDA. Fetch that account, or compare claimCount / claimAmount on the distribution, to know which allocations are done.

Treat AlreadyClaimed as success for that exact (distribution, recipient, amount, nonce) tuple. Ownership transfer of a LegacyNft mint does not reset the receipt.

Recover Unclaimed Tokens

The distribution authority withdraws leftover tokens and unused subsidy SOL only when the distribution is inactive: before startTime or after endTime.

See Funding and Recovery for withdraw and withdrawSubsidy. Leave operational margin around the end timestamp so the last claims are not racing a recovery transaction.

Production Delivery Checklist

Validate the off-chain file against the on-chain root before users rely on it.

  • Sum of amount values is covered by the vault deposit.
  • Every persisted proof is the prepareDistribution output for that same list, in the same order.
  • Recipient mode is used when a leaked proof must not be enough to submit.
  • Claim frontends never hold the distribution authority.
  • Unclaimed tokens have an owner who can call withdraw after endTime.

Notes

MPL-Distro does not replace your allocation database, notification channel, or claim UI.

  • totalClaimants is metadata and does not cap successful proofs.
  • Claim receipts are not closed, so receipt rent stays allocated.
  • Large lists should be built in a controlled Node.js process; prepareDistribution switches implementation at 1,000 leaves.

FAQ

Does MPL-Distro host a claim website?

No. The program stores only the Merkle root. The application must persist proofs and provide a claim page or API.

Can an email or Discord handle be the Merkle leaf?

No. Leaves are wallet public keys or legacy NFT mints. Off-chain channels can notify users, but they are not on-chain identities.

Is it safe to make Merkle proofs public?

In Permissionless mode, anyone with a valid proof can submit the claim; tokens still go to the leaf address. Use Recipient mode when proof access alone must not authorize submission.

Should a backend submit every Merkle proof itself?

No. Submitting every proof from a backend is usually more expensive than SPL token transfers because each Distro claim pays the protocol fee. Use a relayer so users without SOL can still claim, or use Distro when some allocations may go unclaimed and you need the Merkle window.

When can unclaimed tokens be recovered?

The authority can withdraw tokens before the start timestamp or after the end timestamp. Withdrawals are rejected while startTime <= clusterTime <= endTime.