A standalone claim program and supporting toolset for distributing DFX IOU tokens through Merkle proofs.
This repo is forked from Merkle distributor tooling and keeps the existing distributor/claim model intact: each claim tree stores a Merkle root, each claimant proves their allocation, and tokens are paid from a program-owned vault.
Current planned DFX distributor program ID:
distAitdwx9mDm3SaPMtGZRjpXMPUenLhmPwoySV3Hp
The matching program keypair is intentionally not committed. Keep it outside git and copy it to target/deploy/merkle_distributor-keypair.json only when preparing a deploy. If the deployer chooses a different keypair later, run anchor keys sync and update downstream dfx-claim configuration.
The DFX IOU mint itself is created with the helpers under scripts/:
scripts/upload-metadata— TypeScript uploader (Metaplex Umi + Irys) that pushes the logo image and off-chain JSON to Arweave and returns the metadata URI.scripts/create-vanity-token.sh— grinds a vanity mint address, creates the classic SPL Token, attaches Metaplex Token Metadata viametaboss(immutable), mints the initial supply, verifies the wallet balance, and disables the mint authority.
See scripts/README.md for prerequisites, the full flag reference, the end-to-end chained example, and mainnet caveats (immutable metadata, freeze authority handling, vanity prefix cost).
The resulting mint address is what gets passed as --mint [TOKEN_MINT] to every CLI command below.
- Create one distributor per asset or market-specific asset bucket.
- Build leaves at the Drift authority level.
- For immediate DFX IOU claims, set each leaf with
amount_unlocked = full entitlementandamount_locked = 0. - Fund each distributor vault with the intended token amount by SPL transfer.
- Users call
new_claimonce per eligible distributor to withdraw their unlocked amount.
The vesting fields remain available for compatibility with the original program. For DFX IOU claims, use unlocked-only trees and a far-future clawback timestamp.
Some claimants are programs rather than wallets — the program itself owned the bad debt, so a program PDA is the claimant in the merkle tree. Since new_claim requires the claimant to sign, such programs claim via CPI with the PDA signing through seeds.
See cpi-example/ for a complete worked example: an Anchor program with a claim endpoint and admin-gated withdraw, admin scripts that fetch proofs from the API, and a litesvm test covering the full flow. The example is not audited and is intended as a reference — adapt the pattern into your own program rather than deploying it as-is.
Build sharded Merkle trees, create distributors, fund vaults, and verify setup:
cargo build
# CSV amounts are raw on-chain base units (the prepare-*-csv.py scripts already scale to base units),
# so use --decimals 0 with the default --csv-amount-unit tokens. Passing --decimals 6 here would
# multiply every claim by 10^6. See MERKLE_TREES.md.
target/debug/cli create-merkle-tree --csv-path merkle-tree/devnet/csv/output.csv --merkle-tree-path merkle-tree/devnet/trees --max-nodes-per-tree 12000 --amount 0 --decimals 0 --start-airdrop-version 0
target/debug/cli --mint dfxKL8VLUjLMCnFiJ57ZjrjGDiDMLRX8tHmg8biUV39 --keypair-path ~/.config/solana/id.json --rpc-url https://api.devnet.solana.com new-distributor --start-vesting-ts 1779785439 --end-vesting-ts 1779786439 --merkle-tree-path merkle-tree/devnet/trees --clawback-start-ts 1811321499 --enable-slot 465006999
target/debug/cli --mint dfxKL8VLUjLMCnFiJ57ZjrjGDiDMLRX8tHmg8biUV39 --keypair-path ~/.config/solana/id.json --rpc-url https://api.devnet.solana.com fund-all --merkle-tree-path merkle-tree/devnet/trees
target/debug/cli --mint dfxKL8VLUjLMCnFiJ57ZjrjGDiDMLRX8tHmg8biUV39 --keypair-path ~/.config/solana/id.json --rpc-url https://api.devnet.solana.com verify --merkle-tree-path merkle-tree/devnet/trees --clawback-start-ts [CLAWBACK_START_TS] --enable-slot [ENABLE_SLOT] --admin [ADMIN]See MERKLE_TREES.md for CSV format, amount units, and distributor-version guidance.
The raw CLI commands above work for a one-off mint. For batch deploys — especially the Insurance Fund (IF), which spans ~63 spot markets each with its own mint — use the config-driven wrappers under scripts/deploy-merkle-trees/. They drive the same cli binary but read shared settings (RPC, program id, keypair, vesting/clawback timestamps, sharding size) from one JSON config so the CLI-call sequence never drifts between markets.
deploy-if.sh— IF: iteratemarkets[]and, per market, generate trees (create-merkle-tree) then create the on-chain distributor (new-distributor).deploy-dfx.sh— the single DFX IOU mint, same two steps.fund-if.sh/fund-dfx.sh— runfund-allagainst the generated trees to top each distributor vault up to its remaining (unclaimed) entitlement. Funding is idempotent, including after claiming has started, and skips clawed-back distributors.deploy-common.sh— sourced by all four; holds preflight checks, theclipath resolver, jq config readers, and the shareddeploy_market()/fund_market()functions.
Copy an example config, fill it in (the real files are gitignored), and run:
cp scripts/deploy-merkle-trees/if-markets.example.json scripts/if-markets.json
cp scripts/deploy-merkle-trees/dfx-config.example.json scripts/dfx-config.json
# generate trees + create distributors
./scripts/deploy-merkle-trees/deploy-if.sh --config scripts/if-markets.json --csv-dir ./if-csv --trees-dir ./if-trees
./scripts/deploy-merkle-trees/deploy-dfx.sh --config scripts/dfx-config.json --csv-dir ./dfx-csv --trees-dir ./dfx-trees
# then fund the vaults
./scripts/deploy-merkle-trees/fund-if.sh --config scripts/if-markets.json --trees-dir ./if-trees
./scripts/deploy-merkle-trees/fund-dfx.sh --config scripts/dfx-config.json --trees-dir ./dfx-treesPass --dry-run to print the exact, copy-pasteable CLI commands without touching the chain. Trees are always generated in base-unit mode (--csv-amount-unit tokens --decimals 0), so each CSV integer is the exact on-chain claim amount. The verify step and CSV generation stay manual — see scripts/deploy-merkle-trees/deploy-merkle-trees.md for the full flag reference, config keys, and caveats, and DEPLOY.md for the end-to-end runbook.
This repo includes a devcontainer with pinned Rust, Solana, and Anchor versions for program development.
Build the container:
cd .devcontainer
docker build -t dfx-distributor-dev .Start the devcontainer service:
docker compose up -dOpen a shell inside it:
docker compose exec dfx-distributor /bin/bashAlternatively, use your IDE's devcontainer integration. In VS Code or Cursor:
- Press
Cmd+Shift+Pon macOS, orCtrl+Shift+Pelsewhere. - Run
Dev Containers: Reopen in Container. - Wait for the container to build.
- Use the IDE terminal inside the devcontainer.
Common commands:
# build program without requiring the deploy keypair
anchor build
# build program with keypair check
mkdir -p target/deploy
cp /path/to/merkle_distributor-keypair.json target/deploy/merkle_distributor-keypair.json
anchor build
# update checked-in IDL after build
cp target/idl/merkle_distributor.json programs/merkle-distributor/idl/merkle_distributor.json
# run Rust tests
cargo test -p merkle-distributor --lib
cargo test -p jito-merkle-treeThe Axum server under api serves Merkle proof and claim-status data for users.
cargo build
target/debug/drift-dfx-distributor-api --merkle-tree-path merkle-tree/trees \
--program-id distAitdwx9mDm3SaPMtGZRjpXMPUenLhmPwoySV3Hp \
--mint dfxKL8VLUjLMCnFiJ57ZjrjGDiDMLRX8tHmg8biUV39 \
--rpc-url https://api.devnet.solana.com \
--ws-url wss://api.devnet.solana.com