Airdrops

Airdrops

A PirateCrew airdrop is a keccak-256 merkle distribution anchored to an SPL mint. The creator computes a merkle tree off-chain, writes the root into an on-chain config PDA, and claimers redeem their slice by submitting a proof. The hash function is keccak-256 (Ethereum-compatible) — not SHA-256 — so the leaves match the on-chain verify_merkle_proof instruction in the Pirates program exactly.

A single mint can host many independent airdrops; the airdrop_count field (a u16, 0–65535) disambiguates them. The first airdrop for a mint is typically 0, the next 1, and so on. Each airdrop_count has its own root, its own vault, and its own claim state.

Lifecycle

Airdrop lifecycle — build → init → claim

Trees are first-class resources: POST /v2/merkle-trees computes and persists the tree, returning a tree_id that claimers later use to look up their own proof by wallet. The persisted leaves never leave the server, so distributors don't have to host them.

The optional swap_to_sol: true flag on claim appends a Jupiter swap instruction so the claimer immediately receives SOL instead of the airdropped token — useful for incentive flows where the user wants gas, not a bag.

Endpoints

MethodPathModeScopeNotes
POST/v2/merkle-treesn/aairdrop:writeCompute root; persist 1–50,000 leaves
POST/v2/airdropsunsigned | signedairdrop:admin (for signed)Write root to on-chain config PDA
GET/v2/merkle-trees/{id}/proofs/{wallet}n/areadFetch a wallet's proof from a persisted tree
POST/v2/airdrops/{id}/claimsunsigned onlyn/a (signed rejected)Claimer wallet must sign
POST/v2/airdrops/{id}/vault-withdrawalsunsigned | signedairdrop:admin (for signed)Admin withdraws unclaimed tokens

mode=signed is rejected on POST /v2/airdrops/{id}/claims because only the claimer's wallet has authority over the destination token account. See Transaction Modes for the broader split.

1. Build the tree

curl -X POST https://api.piratecrew.fun/v2/merkle-trees \
  -H "Authorization: Bearer $PIRATE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "mint": "GoLDDqDRHcGZBiGPeXAYi5ougndqBNQSNXdNeT3re6gr",
    "airdrop_count": 0,
    "decimals": 6,
    "entries": [
      { "wallet": "8mP3...AaB1", "amount": "1000000" },
      { "wallet": "9qZ2...CcD3", "amount": "2500000" },
      { "wallet": "4xR7...EeF5", "amount": "500000" }
    ]
  }'
{
  "data": {
    "root": "5ka...base58...",
    "root_hex": "0x9e3f7c8b2a1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f7a8b9c0d1",
    "mint": "GoLDDqDRHcGZBiGPeXAYi5ougndqBNQSNXdNeT3re6gr",
    "leaf_count": 3,
    "tree_id": "tree_01HXYZ..."
  },
  "meta": { "request_id": "req_…" }
}

amount is a string of raw base units (multiply by 10^decimals if you're working in display units). Save root_hex — you'll feed it to POST /v2/airdrops next.

2. Initialize the airdrop on-chain

curl -X POST https://api.piratecrew.fun/v2/airdrops \
  -H "Authorization: Bearer $PIRATE_API_KEY" \
  -H "Idempotency-Key: 5f3e2c8a-..." \
  -H "Content-Type: application/json" \
  -d '{
    "payer": "YOUR_WALLET_PUBKEY",
    "platform_id": "pirate-crew",
    "mint": "GoLDDqDRHcGZBiGPeXAYi5ougndqBNQSNXdNeT3re6gr",
    "airdrop_count": 0,
    "merkle_root": "0x9e3f7c8b2a1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8091a2b3c4d5e6f7a8b9c0d1",
    "mode": "unsigned"
  }'

The merkle_root accepts a hex string with or without the 0x prefix. If you have the airdrop:admin scope on your API key, use mode: "signed" to have the platform wallet sign and broadcast in one round-trip.

After this transaction confirms, the airdrop resource flips to status: "initialized" and the vault is ready to be funded.

3. Claim

A claimer first looks up their proof against the persisted tree:

curl "https://api.piratecrew.fun/v2/merkle-trees/tree_01HXYZ.../proofs/8mP3...AaB1" \
  -H "Authorization: Bearer $PIRATE_API_KEY"
{
  "data": {
    "wallet": "8mP3...AaB1",
    "mint": "GoLDDqDRHcGZBiGPeXAYi5ougndqBNQSNXdNeT3re6gr",
    "amount": "1000000",
    "root": "5ka...",
    "proof": ["0x1a2b...", "0x3c4d...", "0x5e6f..."]
  },
  "meta": { "request_id": "req_…" }
}

Then build the claim transaction. You can omit proof — the server will fetch it from the persisted tree:

curl -X POST https://api.piratecrew.fun/v2/airdrops/$AIRDROP_ID/claims \
  -H "Authorization: Bearer $PIRATE_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "claimer": "8mP3...AaB1",
    "amount": "1000000",
    "platform_fee": 0,
    "swap_to_sol": false,
    "mode": "unsigned"
  }'

The response is a base64 VersionedTransaction. Have the claimer sign it locally, then submit via POST /v2/transactions. mode: "signed" is rejected — there's no server-signing path for end-user claims.

Set swap_to_sol: true to receive the claim as SOL via a Jupiter swap appended to the same transaction. platform_fee (lamports) is an optional flat fee routed to the platform wallet inside the same tx.

Reference