The Rewards program distributes tokens from a program-owned vault to a set of recipients, each on a vesting schedule. An authority funds the vault and defines who receives what. Recipients claim their vested tokens over time, and the authority can revoke recipients or reclaim leftover tokens when the distribution allows it. The program is built with Pinocchio, maintained by the Solana Foundation, and works with both SPL Token and Token-2022 mints, except Token-2022 mints with the transfer hook extension.
Program ID (mainnet and devnet):
REWArDioXgQJ2fZKkfu9LCLjQfRwYWVVfsvcsR5hoXi
The program was audited by OtterSec. See AUDIT_STATUS.md for the audited-through commit.
Choose a distribution type
The program supports two distribution types. Both use the same vesting schedules and revocation modes.
| Direct | Merkle | |
|---|---|---|
| How it works | Creates an onchain account per recipient | Stores a single merkle root onchain; recipients provide proofs to claim |
| Upfront cost | Payer funds rent for every recipient account | No per-recipient accounts until someone claims |
| Scale | Practical up to low thousands of recipients | Scales to millions of recipients; storage grows only as recipients claim |
| Mutability | Recipients can be added after creation | Recipient set is fixed at creation |
| Use it for | Small distributions that change over time | Large, fixed distributions such as airdrops |
Vesting schedules
Each allocation carries one of four schedules. Timestamps are Unix seconds.
| Schedule | Behavior |
|---|---|
Immediate | All tokens are claimable right away |
Linear | Tokens unlock proportionally between startTs and endTs |
Cliff | Nothing unlocks until cliffTs, then everything unlocks at once |
CliffLinear | Linear vesting from startTs to endTs, with nothing claimable until cliffTs; earlier accrual unlocks at the cliff |
Accounts
| Account | PDA seeds | Stores |
|---|---|---|
DirectDistribution | ["direct_distribution", mint, authority, seed] | Authority, mint, and allocation totals |
DirectRecipient | ["direct_recipient", distribution, recipient] | One recipient's allocation and vesting |
MerkleDistribution | ["merkle_distribution", mint, authority, seed] | Authority, mint, totals, and the merkle root |
MerkleClaim | ["merkle_claim", distribution, claimant] | Amount claimed so far by one claimant |
Revocation | ["revocation", distribution, user] | Marker that blocks a revoked merkle claimant |
The seed is the address of an extra signer passed at creation, so one
authority can run several distributions for the same mint. Each distribution's
vault is the associated token account of the distribution PDA for its mint.
Instructions
Direct distribution
- The authority calls
CreateDirectDistributionto create the distribution PDA and its vault. - The authority calls
AddDirectRecipientonce per recipient with an amount and a vesting schedule. Each call transfers that amount from the authority's token account into the vault. - Recipients call
ClaimDirectto withdraw vested tokens. Passamount: 0to claim everything currently vested. - Once a recipient has claimed everything, or the distribution is closed,
CloseDirectRecipientcloses the recipient account and refunds rent to the account that paid for it. - The authority calls
CloseDirectDistributionto recover the remaining vault balance and rent. IfclawbackTsis set, closing is allowed only after that time.
Merkle distribution
- The authority builds a merkle tree offchain. Each leaf commits to a claimant, that claimant's total amount, and a vesting schedule.
- The authority calls
CreateMerkleDistributionwith the root, the total claimable amount, and aclawbackTs, and funds the vault in the same instruction. - Claimants call
ClaimMerklewith their leaf data and proof. The first claim creates the claimant'sMerkleClaimaccount. Passamount: 0to claim everything currently vested. - After
clawbackTs, the authority callsCloseMerkleDistributionto recover the remaining vault balance and rent. - After the distribution is closed, claimants call
CloseMerkleClaimto recover the rent for their claim account.
The program hashes leaves with Keccak-256 as
keccak256(0x00 || keccak256(claimant || totalAmount || schedule)), where
totalAmount is a little-endian u64 and schedule uses the same encoding as
the instruction's VestingSchedule argument. Proof nodes are hashed as sorted
pairs, smaller hash first.
Revocation
Revocation is opt-in. The revocable bitmask set at creation chooses which
modes the authority may use: bit 0 enables NonVested, bit 1 enables Full,
and 0 disables revocation.
| Mode | Instruction | Result |
|---|---|---|
NonVested | RevokeDirectRecipient or RevokeMerkleClaim | Vested, unclaimed tokens go to the user; unvested tokens return to the authority |
Full | RevokeDirectRecipient or RevokeMerkleClaim | All unclaimed tokens, vested and unvested, return to the authority |
Revoking a merkle claimant creates a Revocation marker that blocks future
claims, even if they have never claimed. Revoking a direct recipient closes
their DirectRecipient account and refunds its rent.
Clients
The repository includes @solana/kit and
Rust clients generated from the program IDL with Codama, in
clients/.
The client isn't published to npm yet. Build it from source with
just generate-clients.
Resources
- Source code
- OtterSec audit report
- Report an issue
- Solana StackExchange, tag
rewards-program
Is this page helpful?