Campaign Kit docs
v0.1.0
Get help
● Operate · TON

Airdrop runbook
testnet first, then mainnet.

From lifetime points to an on-chain Merkle distributor: fair multipliers, snapshot, publish, deploy the distributor from your own wallet through TON Connect, fund it, let players claim, withdraw what is left after the deadline.

01Testnet first

Do the whole flow on testnet before mainnet

Set TON_NETWORK=testnet, use a testnet wallet and a testnet jetton, and run every step on this page once, including a player claim and the withdraw after the deadline. Publishing is irreversible, and the contract has not been independently audited. Have it audited by a third party before it holds meaningful value, and test against the real jetton you plan to distribute (its wallet code may differ).

Order: score players → set fairness → draft snapshot → publish → deploy distributor → check its jetton wallet → fund → players claim → withdraw after the deadline. Set the token's decimals and jetton master on the Token page first.

Airdrop page steps
Airdrop → Snapshots & claim: the five stages

021. Snapshot draft

Recompute anti-bot scores and work through the review queue first (Fairness & anti-bot). Then Airdrop → New draft (manager or owner): total to distribute (whole tokens, converted with the token's decimals), minimum lifetime points, formula and the rules shown to players. A new draft replaces the previous one; nothing is on-chain yet.

Eligible = not banned · not excluded by anti-bot (after overrides) · TON wallet linked with a verified proof · lifetime points ≥ minimum and > 0.

FormulaAllocation weight
linearLifetime points.
sqrt√ lifetime points: flattens whales.
tieredA fixed amount by the highest tier reached; scaled down if the tiers add up to more than the total.

Each weight is multiplied by the player's fairness multiplier (clean 1, review, excluded 0, overrides, re-verification). The maths is deterministic and in integer units; amounts are rounded down so the sum never exceeds the total. The draft page shows the distribution, the allocations (addresses redacted) and a CSV export with full addresses (logged).

032. Publish (immutable)

Publish (owner only) builds the Merkle tree, verifies every proof against the root, stores root and proofs, freezes the latest fairness rules version into the snapshot and moves the campaign to snapshot. From then on the snapshot cannot be edited or deleted, no new draft can be created, players can no longer link or change wallets, and earning stops.

The rules, Merkle root and list are public so anyone can check them:

https://api.example.com/api/c/<slug>/airdrop/published
https://api.example.com/api/c/<slug>/airdrop/published.csv
https://api.example.com/api/c/<slug>/airdrop/proof/<address>

043. Deploy the distributor from your wallet

Open the published snapshot. The Distributor contract steps are signed by your wallet through TON Connect; the server only prepares unsigned messages. You need the owner role.

  1. Connect the operator walletUse the wallet that holds the jetton supply for this airdrop, on the same network as the snapshot (the admin warns on a mismatch). It becomes the contract owner, the only address that can withdraw after the deadline.
  2. Pick the claim deadlineDefault is 30 days from now. Claims are accepted until then; afterwards the owner can withdraw what is left. Owner, jetton master, Merkle root, tree depth and deadline are fixed in the contract forever; a new snapshot needs a new distributor.
  3. Prepare deploy and signPrepare deploy, then sign in your wallet (0.1 TON attached). The admin then records the address together with the network, the claim deadline and the connected wallet as owner. The API rebuilds the expected contract address from Merkle root, tree depth, deadline, jetton master, owner and network and refuses any other address with distributor_mismatch (for example a different wallet or deadline than the one you signed with). On success the campaign moves to claim. If recording did not complete after signing (for example the page was closed), use Record deployed address with the same wallet connected.

054. Check the distributor's jetton wallet

On deploy the contract asks the jetton master for its own jetton wallet (TEP-89). The admin compares the stored wallet with the master's answer. If they do not match, do not fund: check the jetton master address on the Token page. If the wallet is still unknown, Retry discovery · 0.05 TON. For masters without TEP-89, Set jetton wallet manually; the contract accepts that once, only while the wallet is unset. On-chain status is read from toncenter's public API directly from your browser, without a key.

065. Fund

The admin looks up your own jetton wallet and its balance (or paste it). Sign the jetton transfer of the allocated total to the distributor (0.1 TON attached for fees; the unused part returns). Check that the distributor's jetton balance is at least the sum of all allocations. The admin shows "Funded. Eligible players can claim from the Mini App until the deadline."

076. Players claim

The Mini App shows the claim button only when the claim phase is open, the deadline has not passed and the distributor's jetton balance covers the player's amount (checked on-chain, cached 60 seconds). Before funding, players see "Claim opens once the distributor contract holds enough … to pay you".

  • The claim must come from the linked wallet: the contract pays only the address in the allocation.
  • Each allocation can be claimed once. If the payout bounces (for example the distributor ran out of jettons), the claim is un-marked so the player can retry. The TON attached to the bounced attempt stays in the contract; you recover it with the withdraw.
  • The player attaches 0.1 TON; the measured net cost is about 0.03 TON (including deploying the player's jetton wallet).

087. After the deadline: withdraw

After the deadline: withdraw leftovers sends unclaimed jettons and the contract's TON back to the owner wallet. The contract refuses a withdraw before the deadline (exit code 1010), so players keep their full claim window.

09TON costs

ActionSigned byTON attachedNotes
Deploy distributoryou0.1Pays the TEP-89 discovery round trip; the rest stays as storage reserve.
Retry discoveryyou0.05Only if needed.
Set jetton wallet / Withdrawyou0.1Owner-only messages.
Fund (jetton transfer)you0.1Unused part returns as excess.
Claimplayer0.1 (contract minimum 0.08)Measured net cost about 0.03 TON; the rest is refunded.

Source: packages/ton-claim/src/constants.ts and the gas table in packages/ton-claim/README.md (measured in @ton/sandbox; real network fees can differ).

10Contract exit codes

CodeMeaning
1001Claim window closed (now ≥ claim deadline)
1002Index already claimed
1003Invalid Merkle proof, or the claim came from a different wallet than the allocation
1004Less than 0.08 TON attached
1005Distributor jetton wallet not known yet
1006Unauthorized (not owner / not jetton master)
1007Index out of range for this tree
1008Jetton wallet already set
1009Amount must be greater than zero
1010Withdraw before the claim deadline

11Contract security model

  • No key exists for the contract. No server signs anything.
  • Owner, jetton master, Merkle root, tree depth and deadline live in the contract's initial state, fix its address, and cannot be changed. No upgrade path, no self-destruct, no unsigned external messages.
  • The jetton wallet is learned from the jetton master (TEP-89), not from the first transfer, so a fake wallet cannot pin the distributor.
  • Only jettons that follow the TEP-74 reference wallet behaviour should be used.
  • Independent audit: still open. Also decide how to handle players whose claim bounced (their attached TON stays in the distributor until you withdraw).