devs · v0.4.0 · mit

claimy

solana airdrop distribution toolkit. cli for operators, sdk for integrators, react widget that ships a claim page in 10 lines. no custom on-chain program — we target jupiter's merkle-distributor, so the riskiest layer is already battle-tested.

merkle-distributor compatible
token-2022 ready
client-side merkle
ship a claim page in 10 lines
typescript-first
solana mainnet
no custom program
mit licensed
snapshot → weight → tree → claim
audit-friendly surface
merkle-distributor compatible
token-2022 ready
client-side merkle
ship a claim page in 10 lines
typescript-first
solana mainnet
no custom program
mit licensed
snapshot → weight → tree → claim
audit-friendly surface
01

quickstart

two paths. operators build trees from a mint snapshot. integrators drop a widget into any react app.

preview

one terminal, one airdrop.

snapshot a mint, weight the holders, build a jupiter-compatible merkle tree. every step is deterministic and inspectable — you can verify any proof locally before you touch the chain.

claimy · zsh
✔ installed claimy@0.4.0
✔ fetched 12,847 holders via getProgramAccounts
✔ filtered 47 cex wallets, 312 dust (< min balance)
✔ allocated 1,000,000.00 across 12,488 claimants
✔ merkle root: 0x9f2a…c4e1
ℹ wrote 2 files:- proofs.json (2.4 mb)- root.txt
operator — build an airdrop
bash
# install cli
npm i -g claimy

# snapshot holders of a mint
export SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
airdrop snapshot --mint <MINT_ADDRESS> -o holders.csv

# weight into allocations
airdrop weight -i holders.csv --mode sqrt -o allocations.csv

# build a jupiter-compatible merkle tree
airdrop tree -i allocations.csv --mint <MINT_ADDRESS> -o proofs.json

outputs proofs.json (full proofs) + root.txt (on-chain merkle root).

integrator — ship a claim page
bash
npm i @claimy/widget @solana/wallet-adapter-react
tsx
import { AirdropClaim } from '@claimy/widget';
import '@claimy/widget/dist/styles.css';

export default function ClaimPage() {
  return (
    <AirdropClaim
      proofsUrl="/proofs.json"
      distributor="<DISTRIBUTOR_PUBKEY>"
    />
  );
}
env
SOLANA_RPC_URLrpc endpoint for cli. helius / triton / any standard. api keys in url are redacted in logs.
HELIUS_RPC_URLalias, same resolution
no secrets on the client. widget reads proofs.json statically.
02

snippets

build a merkle tree in the browser
ts
import { buildTree } from '@claimy/sdk';

const { root, proofs } = buildTree({
  mint: 'TOK3N...',
  decimals: 9,
  allocations: [
    { owner: '9x...abc', amount: '1000000000' },
    { owner: '7y...def', amount: '500000000' },
  ],
});

// upload proofs as /proofs.json,
// pass root to distributor deploy
headless claim hook
tsx
import { useAirdropClaim } from '@claimy/widget';

function CustomClaim({ proofsUrl, distributor }) {
  const { allocation, claim, isClaimed, status } =
    useAirdropClaim({ proofsUrl, distributor });

  if (!allocation) return <p>no allocation for this wallet.</p>;
  if (isClaimed)   return <p>already claimed.</p>;

  return (
    <button onClick={claim} disabled={status === 'pending'}>
      claim {allocation.amount}
    </button>
  );
}
03

cli reference

bin name is airdrop. claimy is the package name.

airdrop snapshot --mint <M>fetch every spl / token-2022 holder via one getProgramAccounts. filters: --min, --max, --exclude, --exclude-cex, --token2022.
airdrop weight -i holders.csv --mode <m>apply allocation strategy. modes: linear, sqrt, tiered, uniform.
airdrop tree -i allocations.csv --mint <M>build jupiter-compatible merkle tree. leaf = keccak256(index_le_u64 || owner_pubkey_32 || amount_le_u64).
airdrop verify -p proofs.json --owner <O>locally verify a single proof against the recorded root.
airdrop stats -p proofs.jsonprint summary: root, claimant count, total allocated.
airdrop format -p proofs.json --prettyreformat proofs.json (--pretty or --minify).
04

sdk surface

no http/grpc api — everything is client-side. three layers, one direction: widget → sdk ← cli. sdk has no react, cli has no dom.

fetch + lookup
  • fetchProofs(url)
  • lookupAllocation(proofs, owner)
  • isClaimed(connection, claimant, distributor)
build transactions
  • buildClaimInstruction({ claimant, distributor, allocation })
  • buildClaimTransaction({ connection, claimant, distributor, allocation })
  • buildFundVaultTransaction({ connection, payer, vault, mint, amountRaw, decimals })
merkle
  • buildTree({ mint, decimals, allocations })
  • computeLeaf(index, owner, amount)
  • verifyProof(root, leaf, proof)
pda helpers
  • deriveDistributorAddress({ base, mint, version })
  • deriveClaimStatusAddress({ claimant, distributor })
formatting
  • formatAmount
  • shortAddress
  • shortSignature
  • solscanTx
  • solscanAccount
05

architecture

text
operator (laptop)
  │
  ├─ claimy cli ── snapshot ─► getProgramAccounts ──► holders.csv
  │                weight   ─────────────────────────► allocations.csv
  │                tree     ── uses @claimy/sdk ─────► proofs.json + root.txt
  │
  └─ deploy distributor (jupiter merkle-distributor) with root
                    │
                    └─ fund vault (TransferChecked, via @claimy/sdk)

host app (browser)
  │
  ├─ @claimy/widget  <AirdropClaim />
  │       │
  │       └─ @claimy/sdk
  │           ├─ fetchProofs(/proofs.json)
  │           ├─ lookupAllocation(wallet)
  │           ├─ isClaimed()  ─► jupiter distributor program
  │           └─ buildClaimTransaction ─► wallet-adapter signs ─► rpc
  │
  └─ @solana/wallet-adapter-react  (user brings the wallet)