Operations
Funding and Recovery
Last updated August 26, 2026
MPL-Distro separates token funding in the distribution vault from optional SOL funding for claim-receipt rent.
Summary
The authority deposits SPL tokens into the distribution's associated token account and may fund the distribution PDA with SOL when receipt subsidies are enabled.
- Deposit enough token base units to cover every Merkle allocation.
- Budget one claim-receipt rent payment per expected successful claim.
- Monitor both the recorded
totalAmountand the actual vault token balance. - Withdraw unclaimed tokens and unused subsidy SOL only when the distribution is inactive.
Quick Start
MPL-Distro funding and recovery follows four operational steps.
- Sum all allocation amounts and deposit that many token base units.
- When receipt subsidies are enabled, transfer the expected receipt-rent budget to the distribution PDA.
- Monitor the actual vault balance, distribution SOL, and claim totals.
- After the window ends, withdraw unclaimed tokens and unused subsidy SOL.
Deposit Distribution Tokens
The deposit instruction transfers tokens from the depositor's account to the distribution PDA's canonical associated token account. The current distribution authority must sign every deposit, even when a different wallet supplies the tokens.
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.
The SDK defaults depositor, payer, and authority to the Umi payer and derives both associated token accounts. Supply a separate depositor signer when another wallet owns the source tokens, and still pass the current distribution authority.
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.
The program increments totalAmount after each deposit. It does not compare that value with the sum of allocations committed by the Merkle root.
Calculate the Token Deposit
The required token deposit is the sum of all allocation amounts expressed in the mint's base units.
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
Deposit a deliberate buffer only when the authority accepts that it must recover the excess later. A valid proof fails with InsufficientFunds when the recorded balance is below its allocation, and the SPL transfer can also fail if the actual vault balance is lower.
Fund Claim Receipt Subsidies
Receipt subsidies let the distribution PDA reimburse the transaction payer for the rent used to create each claim receipt.
Enable subsidizeReceipts during createDistribution, calculate rent through the RPC, and transfer SOL directly to the distribution 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.
Subsidy Budget Boundary
The distribution must retain its own rent-exempt minimum. A claim fails with InsufficientFundsToSubsidizeReceipts when the remaining SOL cannot cover both the distribution rent and one receipt reimbursement.
MPL-Distro Funding Quick Reference
Claim costs are split among a fixed protocol fee, Solana transaction costs, and account rent.
| Cost | Default payer | Receipt subsidy covers it |
|---|---|---|
| Protocol fee (0.002 SOL) | Claim transaction payer | No |
| Transaction fee | Claim transaction payer | No |
| Claim receipt rent | Claim transaction payer | Yes, when enabled and funded |
| Recipient ATA rent | Claim transaction payer | No |
Recover Unclaimed Tokens
The distribution authority recovers unclaimed or excess tokens with withdraw before the start time or after the end time.
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.
The active interval is inclusive. A withdrawal is rejected when startTime <= clusterTime <= endTime.
Recover Unused Subsidy SOL
The authority recovers unused receipt subsidy with withdrawSubsidy only when subsidies are enabled and the distribution is inactive.
withdrawSubsidy transfers a requested lamport amount while preserving the distribution account's rent-exempt minimum. Determine the safe amount from the current account balance instead of assuming every expected claim occurred.
Monitor Distribution Balances
Production systems should compare program bookkeeping with the actual SPL and SOL account balances.
| Value | Source | Meaning |
|---|---|---|
distribution.totalAmount | Distribution account | Deposits minus withdrawals recorded by the program; claims do not decrement it |
| Vault token amount | Distribution associated token account | Tokens actually available for transfer |
| Distribution lamports | Distribution PDA account | Rent reserve plus optional unused receipt subsidy |
claimCount | Distribution account | Number of recorded successful claims |
claimAmount | Distribution account | Sum of recorded claimed token base units |
The token withdrawal bookkeeping uses saturating subtraction, so integrations should not assume totalAmount can never diverge from the SPL vault balance.
Notes
Funding operations require authority controls and explicit balance monitoring.
- Only the current distribution authority can authorize a deposit.
- Deposits are allowed before, during, and after the claim window.
- Token and subsidy withdrawals are blocked throughout the active window.
- Anyone can transfer SOL directly to the distribution PDA, but only the authority can withdraw subsidy through the program.
- Receipt rent remains allocated because claim receipts cannot currently be closed.
FAQ
Can the authority withdraw tokens while claims are active?
No. Token withdrawals are rejected from the start timestamp through the end timestamp, inclusive.
What costs does subsidizeReceipts reimburse?
It reimburses claim-receipt rent only, not the protocol fee, transaction fee, or recipient token-account rent.
Can more tokens be deposited after claims start?
Yes. Deposits are not time-gated, so the authority can replenish an underfunded vault.
Can a treasury wallet deposit without the distribution authority?
No. The current authority must sign deposit, even when a separate depositor supplies the tokens.
