Skip to main content

Token Distribution

Token Distribution, the airdrop of the API and contracts, commits an allocation list under one Merkle root and pays it out by push, claim or vesting.
One shared base, three distinct claim contracts.Every token distribution verifies eligibility against the same fixed Merkle root and shares one timelocked withdrawal, which never takes tokens that claims can still reach. Below that shared base, three separate contract families diverge. A push distribution has the sender distribute tokens directly, with no claim step, and calling claim always reverts. A time-bound distribution lets each recipient claim their full allocation once, self-service, inside a fixed start and end window. A vesting distribution has the recipient initialise their allocation before a deadline, then claim repeatedly as the strategy pinned at that initialisation unlocks more.One shared base, three distinct claim contracts.The same figure as the desktop variant, drawn portrait for phone widths. The shared Merkle-root eligibility check and the timelocked withdrawal, which takes nothing claims can still reach, sit on top. The push, time-bound, and vesting families stack below them, each linked to the shared base by its own arrow rather than to each other.

Every token distribution shares one Merkle-root eligibility check and one timelocked withdrawal path, which never takes tokens that claims can still reach.

  • Token Distribution commits an allocation list of any size under one Merkle root, so publishing the list costs the same for ten recipients or ten thousand. Delivery costs gas: the sender pays for each push batch of up to 100 recipients, and each recipient pays for their own claim unless a relayer submits it. Every claim checks against that root instead of a stored per-address list.

  • In all three families, an owner withdrawal never takes tokens that claims can still reach. It runs seven days after scheduling at the earliest, and never before a time-bound distribution's claim window has ended or a vesting distribution's initialisation deadline has passed. A vesting distribution keeps the unclaimed part of every initialised allocation for its recipient. A push withdrawal takes the whole balance, including the tokens of indexes the owner has not distributed.

  • A push distribution moves tokens the moment the sender distributes them, with no claim step for the recipient. A time-bound distribution flips that: each recipient claims their own full allocation once, self-service, only inside a fixed start and end window. A vesting distribution initialises first, then pays claim after claim as the strategy pinned at that initialisation unlocks more.

Why put an allocation in many hands without running a sale

After a token is issued, an issuer often needs to put units into many wallets at once. That covers rewarding the holders who already stayed, seeding a launch's early community, or paying out the allocations a subscription round already promised. Doing that as a sequence of individual transfers means paying gas for every one, keeping a private list of who has been paid, and reconciling that list against what the chain moved.

Token distribution replaces that running record of who has been paid with one published Merkle root: a single committed value that fixes the whole recipient set and every allocation when the contract deploys. Publishing it costs the same whether the list names ten recipients or ten thousand, because the contract never stores the list itself, only the root a caller's proof is checked against. Each distribution transfer runs as an ordinary movement through that same instrument's transfer path, the one issuance and every other servicing operation already write to. The operator still keeps the recipient list, because the tree and every recipient's proof are built from it by the operator's own integration. That list is the input to the root. A private, off-platform record of who has been paid is the second ledger that can drift from the one the chain settled.

Why a distribution uses the token's own compliance path

The transfer a distribution pays out runs through the underlying token's own transfer-compliance modules, the same modules that check every other transfer on that token. Controls and evidence describes them. A payout system built beside the platform would need its own view of who may hold the token, a second copy of the eligibility rules the token's compliance modules already enforce. If the two views differ, a distribution could pay a wallet the token's own rules would refuse.

Nothing here needs a separate custodian or a second wallet holding funds in transit. In every family the distribution contract holds the tokens, funded in advance, and pays out from its own balance. That balance remains subject to the token's custodian role, which can force-transfer it out of the distribution contract.

What Token Distribution is, and how the three families differ

Markets call a token distribution an airdrop, and the platform's contract families and API identifiers keep that word. A distribution is created, funded, paid out and claimed with direct contract calls. The platform installs the distribution factories and shows the distributions in the Console and the Platform API. What differs from one family to the next is who pays gas, and when the reward reaches a recipient's wallet.

A push distribution puts every transfer on the sender. The issuer calls distribute or batchDistribute, and tokens land in each wallet unasked. Reach for it when a reward must land on a fixed date, and the issuer is willing to cover every transfer's gas.

A time-bound distribution makes each recipient pay and act. Each recipient calls claim themselves, once, inside a start and end window the issuer sets. The recipient pays the gas, the network fee for the transaction, unless a relayer submits the call through the trusted forwarder that the distribution implementation names for every distribution of its type. A recipient list of any size costs the issuer the same amount to publish, because eligibility rests on a Merkle proof instead of a stored per-address list. Reach for it when recipients can cover their own gas and should self-serve inside a window you set. The issuer then does not push every transfer out one by one.

A vesting distribution spreads the reward's economic effect out over time, discouraging an immediate sell-off. A recipient first initialises their allocation, which starts a vesting clock but moves no tokens, then claims repeatedly as more of the allocation unlocks under a pluggable strategy, such as a linear schedule with a cliff. No separate lock-up contract is needed for any single recipient. The distribution itself carries the schedule.

Every family shares one recovery path: the owner schedules a withdrawal, then waits at least seven days. On a time-bound or vesting distribution, the wait also lasts until the claim window has ended or the initialisation deadline has passed. The owner cannot shorten that wait. The withdrawal then sends only the tokens no claim can still reach, and the token's transfer rules still apply to it. The withdrawal rule binds the owner only. The token's custodian role can force-transfer the distribution's balance at once, and the system manager role can replace the code of every distribution of a factory, the withdrawal rule included.

Running a distribution day to day

Token distribution's three families divide the day-to-day operating work differently. Push distribution is owner-driven: only the contract owner can call distribute or batchDistribute, and that owner can be an automation's wallet. The owner is a single address the deployer names when the distribution is created. It needs no role on the token, and it can transfer or renounce ownership at any time. The owner alone distributes, sets the cap and schedules withdrawals. On a vesting distribution, the owner also changes the vesting strategy for later initialisations. Tokens reach each recipient without the recipient acting. Time-bound and vesting distributions are self-service, so the operator's job narrows to choices at deployment, then monitoring. A time-bound distribution needs a claim window. A vesting distribution needs an initialisation deadline and a vesting strategy.

Each recipient on a self-service distribution calls claim or batchClaim themselves. The operator never moves a token on their behalf.

A push distribution enforces two limits on chain. It cannot double-pay the same Merkle index, and it cannot exceed a distribution cap when one is set. A cap of zero means uncapped on chain, and the Platform API reports no cap as distributionCap: null. Both checks run inside distribute itself, so a batch that would violate either one fails before any tokens move. The owner can raise or lower the cap later. The change takes effect at once, with no timelock, and each distribution is checked against the running total at that moment.

A vesting distribution has an operator-visible step that push and time-bound skip: initialisation. A recipient's index moves through three states worth tracking separately: uninitialised, initialised and vesting, and fully claimed. An uninitialised index cannot be claimed at all, no matter how much time has passed, and initialisation itself stops working once the distribution's own initialisation deadline passes. Track which recipients have initialised separately from which have claimed, since initialising moves no tokens and is not itself a transfer. Only the recipient can initialise, because the leaf is verified against the caller's own address, so a recipient who misses the deadline cannot be initialised by the operator.

A vesting claim's three-state lifecycle.Before initialisation, an index is outside the vesting clock and a claim reverts. Initialising proves the Merkle allocation, pins the vesting strategy active at that moment, and starts the clock, without moving any tokens. Once initialised, claiming is repeatable: each call moves only the newly unlocked amount. When the cumulative claimed amount reaches the total allocation, the index is fully claimed and nothing more unlocks for it.A vesting claim's three-state lifecycle.The same figure as the desktop variant, drawn portrait for phone widths: awaiting initialisation, then initialised and vesting, then fully claimed, stacked as a vertical state sequence.

A vesting index moves through one initialisation, any number of claims, then a fully claimed state, while push and time-bound skip initialisation.

Withdrawing the tokens no claim can still reach is the one lifecycle step every family shares, and it is deliberately two steps. The owner calls scheduleWithdrawal, and the contract records the withdrawal's eligible time: seven days after scheduling, or the moment claims close when that comes later. A time-bound distribution's claims close when its claim window ends. A vesting distribution's claims close to new allocations when its initialisation deadline passes. Recipients of allocations initialised by then keep claiming, from tokens the withdrawal leaves in place. A push distribution has no claims, so only the seven days apply.

The owner cannot bring the eligible time forward, and executeWithdrawal refuses before it with DALP-4353. The owner can cancel the schedule at any point before execution, and scheduling again sets a new eligible time by the same rule, with the seven days counted from the new schedule. Only one withdrawal can be pending at a time, and no direct, one-call withdrawal exists: the older withdrawTokens call always refuses.

A withdrawal takes a schedule, a wait until claims close, then execution.The owner schedules a withdrawal, and the contract records an eligible time that anyone can read on chain. The wait lasts at least seven days and until claims close, and the owner cannot shorten it. Execution then moves the tokens no claim can still reach to the address the owner names. The owner can cancel at any time before execution, and a new schedule starts the wait over.A withdrawal takes a schedule, a wait until claims close, then execution.The same figure as the desktop variant, drawn portrait for phone widths: scheduled, the waiting period, and execution, stacked as a vertical sequence with the cancel note beneath.

Recovery waits at least seven days and until claims close, then takes only what no claim can still reach.

A cancelled schedule starts over instead of resuming.

Family
Eligible time
What the withdrawal sends
Push
Seven days after scheduling, because recipients cannot claim
The whole balance of the distributed token, including the tokens of every index the owner has not distributed
Time-bound
Seven days after scheduling, or one second after the end time when that is later, because a claim at the end time still succeeds
The whole balance, because no claim can follow the window
Vesting
Seven days after scheduling, or one second after the initialisation deadline when that is later, because an initialisation at the deadline still succeeds
On a distribution created with withdrawal reserves, the balance less the unclaimed part of every initialised allocation. On a fully funded distribution, that is the allocations nobody initialised in time, plus any tokens sent beyond the allocations

When a withdrawal becomes eligible, and what it sends, in each family.

A vesting withdrawal leaves the unclaimed part of every initialised allocation in the contract: its total less what its recipient has claimed so far. Those recipients keep claiming after the withdrawal as their allocations vest. An initialised allocation that its recipient never claims stays in the contract, because no withdrawal can take it. Only a forced transfer by the token's custodian role moves it out.

A vesting distribution created before withdrawal reserves were introduced keeps its whole balance reserved after its factory moves to the new implementation. Recipients can still claim, but owner recovery sends zero, even after all claims finish: the contract cannot reconstruct the older allocations from its records.

When nothing is spare, because every token in the contract still backs an open claim, executeWithdrawal sends nothing, still clears the schedule, and emits TokensWithdrawn with an amount of zero. The event's amount is always what the withdrawal sent.

withdrawalSchedule() returns whether a withdrawal is pending and its eligible time, and the WithdrawalScheduled event carries the scheduling time and the eligible time. The Platform API reports the eligible time as withdrawalEligibleAt, and the Console's airdrop detail screen shows it as “Recovery available”.

Watch a push distribution's cap and per-index state for failed batches as well as completed ones: one invalid item, including a recipient the token's compliance rules refuse, reverts the whole batch. A time-bound distribution needs its window watched, since a claim outside the start and end time fails outright and does not queue. The window is set when the distribution deploys and has no setter, so extending one means deploying a new distribution. A vesting distribution needs its initialisation deadline tracked separately from its claims, since a recipient who never initialises in time never enters the vesting clock at all.

One base contract, three claim implementations

One abstract base contract underlies all three token distribution families and owns exactly two things: Merkle-root eligibility and the timelocked withdrawal. Each family defines two values for that withdrawal: when its claims close, and how much its recipients can still claim. One withdrawal rule then serves all three. The root has no setter, so a changed eligibility list needs a new distribution contract. Each distribution runs the code its factory points to on every call, so a system manager who repoints the factory changes that code for every distribution of that type at once.

Everything about claiming diverges below that shared base. Each family plugs in its own claim-tracking behaviour, and the tracker type is what distinguishes a push claim from a vesting claim at the storage level. A push distribution tracks claims with a bitmap, one bit per index, claimed or not, with no concept of a partial amount. Time-bound and vesting distributions both track a cumulative claimed amount per index instead. Time-bound spends that capacity on one full claim. Vesting is the family that exercises it, claiming progressively.

Behaviour
Push
Time-bound
Vesting
Choose it when
A reward must land on a fixed date and the issuer covers every transfer's gas
Recipients can cover their own gas and should self-serve inside a window you set
The reward should unlock gradually instead of landing all at once
Who moves tokens
The owner, calling distribute or batchDistribute
Each recipient, calling claim or batchClaim
Each recipient, calling claim or batchClaim
Claim step
None. Claim and batchClaim always revert
One claim per index, full allocation
Initialise once per index, then claim repeatedly
Timing gate
None beyond the owner's own schedule
Only inside the start and end time
Initialise before the deadline, then claim once the cliff passes
Claim tracking
Bitmap: claimed or not, no partial amount
Amount-based, used for one full claim
Amount-based, used for progressive claims
Unlock pace
Set by the owner's own distribution schedule
Full amount unlocks at the single claim
Set by the strategy pinned at initialisation

The three distribution contract families, compared where they diverge.

A vesting distribution pins its strategy at initialisation. Calling initializeVesting for an index pins the strategy current at that moment into the index's own record, before any tokens move. Every later claim for that index recomputes against the pinned strategy, never the contract's current one. An owner can repoint future initialisations at a new strategy, but no index already initialised ever notices the change. A linear vesting strategy fixes its cliff and total duration as constructor arguments at deployment, both immutable afterward. The allocation vests linearly across the full duration, measured from the recipient's own initialisation, but nothing is claimable until the cliff elapses. The first claim after that pays out everything vested so far.

Every distribution, in any family, is deployed as its own contract with a predictable address, so its identity is known before deployment. Deploying one needs the TOKEN_FACTORY_MODULE_ROLE at the system level, or the GOVERNANCE_ROLE on that specific token, a role grant the access manager checks before the deployment call runs. It is not open to arbitrary callers. The deployment also creates an on-chain identity for the distribution and registers it in the system identity registry under the country the deployer names, so the distribution can receive and hold a token whose compliance rules accept only registered holders.

The integration contract

On chain, the three token distribution families expose different verb sets, and calling the wrong one for a distribution's family reverts. Push exposes distribute and batchDistribute, gated to the owner, and its claim and batchClaim always revert with a dedicated error (DALP-3018).

Time-bound and vesting both expose claim and batchClaim to any caller. Vesting requires initializeVesting or batchInitializeVesting too, as a mandatory first call per index, before that index's claim succeeds.

A claim, a distribution, and an initialisation all check against the same fixed Merkle root. Each leaf is the index, the account and the total amount, ABI-encoded and hashed with keccak256, and that hash is ABI-encoded and hashed again: keccak256(abi.encode(keccak256(abi.encode(index, account, totalAmount)))). The proof must match that encoding precisely, with no fallback path. Building the correct proof for a given entry is your integration's responsibility, including getting each recipient their proof.

Build the tree with one leaf per index. Every family records claims, and a vesting distribution its initialisations, per index, so two leaves under one index share one record: one recipient's claims count against the other's, and a vesting withdrawal can then take tokens that index can still claim, or revert every time it runs.

Every claim and initialisation verifies the leaf against the caller's own address and pays that address, so integrations submit claims from the recipient's wallet instead of a service account. And batch calls cap at 100 entries in every family. A larger batch reverts before any entry processes.

A batch call's effect on token movement also differs by family. A push batchDistribute sends a separate transfer to each named recipient. A batchClaim on time-bound or vesting collapses several of the caller's own indexes into one transfer.

The Platform API does not create, fund, pay or claim an EVM distribution: those are direct contract calls. When the platform's contracts are deployed on a network, the network's directory registers the push, vesting and time-bound factory implementations. A system manager therefore installs a factory through POST /api/v2/system/addon-factories without naming an implementation. An implementation that the caller supplies takes precedence. A factory's create call also takes the country under which the new distribution's on-chain identity is registered, so the distribution can hold a token whose compliance checks identities. Once a factory is installed, the Platform API serves the distributions the platform has recorded from the chain:

  • GET /api/v2/addons/airdrops lists the distributions of the active system's airdrop factories. Each item carries its contract address as id, its typeId (push-airdrop, time-bound-airdrop or vesting-airdrop), name, token, merkleRoot, distributionCap, amountTransferred, the withdrawal schedule (withdrawalScheduled, withdrawalEligibleAt), vestingStrategy, vestingInitializationDeadlineAt and createdAt. The list filters by search text, typeId and filter[token]. A system with no airdrop factory answers not found instead of an empty page.
  • GET /api/v2/addons/airdrops/{airdropAddress} reads one distribution with the same fields plus recipientCount.
  • GET /api/v2/addons/airdrops/{airdropAddress}/recipients lists every account the distribution has sent tokens to, with the amount and the last transfer time, most recent first. An account that was allocated tokens and has received none is not listed.

The vesting fields are empty for push and time-bound entries. When the platform has no indexed metadata for the token, token is null and the decimal amounts are null, while the exact amounts are still returned. An address that no airdrop factory of the active system deployed answers not found. The Platform API's distribution write routes do not serve EVM distributions and refuse one with OPERATION_FAMILY_UNSUPPORTED (HTTP 422).

In the Console, the “Placement” tab of an asset shows an “Airdrops” section when at least one distribution pays out that asset. It sums “Allocated”, “Distributed” and “Remaining” across the asset's campaigns, and shows “Allocated” and “Remaining” only when every campaign has a cap. The figures are left out when the platform has no metadata for the token or when the asset has more than 200 campaigns. The table lists each campaign with “Distributed”, “Recovery” and “Created”. “View details” opens a sheet with the campaign's figures, its recipient count and a paged list of recipients with “Amount received” and “Last transfer”.

Each installed distribution type also has its own entry under “Asset lifecycle” in the sidebar, “Push airdrop”, “Vesting airdrop” or “Time-bound airdrop”, which lists that factory's distributions and opens each one. When two factories of one type are installed, each entry shows its factory's name instead. A type that is not installed stays in the sidebar with the hint “… is not deployed. A system manager deploys it in Factories.”, or “… is not deployed. Open Factories to deploy it.” for a system manager. The Console has no control that creates or runs a distribution.

Map each refusal to its own message for the caller. An index that has not initialised fails vesting's claim with DALP-3025, and a claim outside the active window fails time-bound's claim with DALP-3001 or DALP-3002, depending on which side of the window it falls. Neither of those is the condition a bad Merkle proof produces (DALP-3032), nor the one an already-claimed index produces (DALP-3030). Treat all four as separate cases in your own error handling.

Controls and evidence

Who is on the recipient list, and for how much, is decided off chain by whoever builds the Merkle tree. The contract enforces only that a claim, a distribution, or an initialisation matches the committed root.

An audit of a distribution asks who built the Merkle tree, from what source list, and under what approval. The contract allows anything the tree contains, subject to the token's compliance rules.

The transfer runs through the underlying token's own transfer-compliance modules, the same modules that check every other transfer on that token by evaluating each configured rule before balances change and reverting if any rule fails.

Evidence is on chain and per distribution, across all three families. Creation, every transfer, whether a push distribution or a self-service claim, every vesting initialisation, and every withdrawal-schedule step each emits its own event. An auditor can tie each payment to its distribution, recipient and path from the events: a push send, a time-bound claim, or a vesting initialisation followed later by one or more claims. A batch claim emits one transfer for several indexes, so read the claim events for the split per index. A withdrawal's TokensWithdrawn event carries the amount it sent, which is zero when nothing was spare. The chain records execution. Keep the recipient list and its approval evidence separately to explain how the Merkle root was chosen.

A push distribution and a self-service claim carry different accountability even though both move the same tokens. A push distribution is a step the operator, or an authorised automation, took on the recipient's behalf. A claim is a step the recipient took themselves.

Frequently asked questions

What happens when a push distribution misses a recipient?

The owner can still distribute to that index later, because a push distribution has no claim window and no deadline of its own. The recipient cannot self-serve in the meantime: a claim on a push contract always reverts. The index stays payable until the owner distributes it or a withdrawal empties the contract. A push withdrawal sends the whole balance seven days after scheduling and keeps nothing back for indexes the owner has not distributed. A distribution that would exceed the cap fails until the owner raises the cap.

What happens if I never initialise my vesting allocation?

Nothing unlocks, ever. The vesting clock starts only when initialisation runs for your index, and the contract refuses that call once the distribution's initialisation deadline passes, so a late attempt reverts. Your allocation then counts as spare balance, which the owner's withdrawal can send out of the contract once the deadline has passed. Initialise before the deadline even when you plan to claim much later: an initialised allocation stays in the contract for you, whenever the owner withdraws.

Does my vesting schedule run from deployment or from my own initialisation?

From your own initialisation. The contract records the timestamp of your initialisation, and the strategy measures the cliff and vesting duration from that moment, so two recipients who initialise a week apart also finish a week apart. Waiting to initialise does not bank vested time. It delays the entire schedule, and the initialisation deadline caps how long you can wait.

Can I claim while the cliff is still running?

No. The call reverts instead of paying zero. Under a linear strategy, nothing is claimable until the cliff duration has passed since your initialisation, and the contract rejects a claim whose computed claimable amount is zero. Once the cliff passes, the first claim pays everything vested since initialisation in one transfer, and later claims pay only what unlocked since the previous one.

Can someone else claim my allocation for me?

The recipient must still authorise the claim, but a relayer can submit it and pay the gas through the platform's meta-transaction support, and the recipient still receives the tokens. When recipients cannot transact at all, even through a relayer, a push distribution is the family built for that.

Can the owner withdraw tokens while claims are still open?

No, not the tokens an open claim can still reach. The owner can schedule a withdrawal at any time, but it runs seven days after scheduling at the earliest, and never before a time-bound distribution's claim window has ended or a vesting distribution's initialisation deadline has passed. executeWithdrawal refuses before then with DALP-4353. What the withdrawal then sends depends on the family.

A time-bound withdrawal runs only after the end time, when no claim can follow, so it sends the whole balance. A vesting withdrawal runs only after the initialisation deadline and leaves the unclaimed part of every initialised allocation in the contract, so a recipient who initialised in time keeps claiming as the allocation vests. A push distribution has no claims to protect: its withdrawal sends the whole balance seven days after scheduling, so distribute every index first.

The rule binds the owner only. The token's custodian role can still force-transfer the distribution's balance at once, and the system manager role can replace the code of every distribution of a factory.

Was this page helpful?