jito-distributor-sdk
Version:
TypeScript SDK for JITO Merkle Distributor with production-ready versioning and double-hashing support
383 lines (281 loc) • 10.4 kB
Markdown
# JITO Merkle Distributor SDK
A production-ready TypeScript SDK for interacting with the JITO Merkle Distributor program on Solana, featuring advanced versioning and proper double-hashing implementation that fixes InvalidProof errors.
## Overview
This SDK provides a clean, type-safe interface to interact with the JITO Merkle Distributor program deployed on Solana. It supports creating token airdrops with merkle tree verification, vesting periods, and administrative controls.
**Program ID**: `mERkCfxWCS5nJn4dB4RNAkrgpVArgatvr7xZfTnPnbW`
## Features
- ✅ **Production-ready double-hashing** that matches Solana program exactly
- ✅ **Advanced deterministic versioning system** with race-condition protection
- ✅ **InvalidProof error resolution** - no more merkle proof failures
- ✅ Zero-FFI approach using Anchor TypeScript client
- ✅ Type-safe interfaces and error handling
- ✅ PDA derivation utilities
- ✅ Merkle proof validation
- ✅ Timestamp validation for vesting periods
- ✅ Support for locked/unlocked token distributions
- ✅ Administrative functions (clawback, admin transfer)
- ✅ **Real testing and deployment scripts for mainnet**
## Installation
```bash
npm install jito-distributor-sdk
# or
pnpm add jito-distributor-sdk
```
## 🚀 Quick Testing (Real Transactions)
Want to test with real transactions on devnet? We've got you covered!
### 1. Setup Environment
```bash
# Copy environment template
cp env.example .env
# Edit .env with your details:
# PRIVATE_KEY=your_base58_private_key
# CUSTOM_TOKEN_MINT=9xgdh8wQNqmz1EFiyQg9ctTE2AtjGiGFrd7VfcoNfAcr
# WALLET_ADDRESS=CoJebSiqLWbXmSCmSSis8NSjva4ag93isJV7dxcz8x5q
```
### 2. Create a Real Distributor
```bash
npm run create-distributor
```
This creates a distributor with proper versioning and double-hashing
### 3. Fund the Distributor
```bash
npm run fund
```
This funds the distributor with USDC tokens
### 4. Claim Your Tokens
```bash
npm run claim
```
This claims all tokens immediately using real merkle proofs and blockchain transactions.
**📖 Full Testing Guide**: See [TESTING.md](./TESTING.md) for complete instructions.
## Quick Start
### Prerequisites
1. **Node.js** (v16 or higher)
2. **Solana CLI** installed and configured
3. **Anchor CLI** (for program deployment)
4. A **Solana wallet** with SOL for transactions
### Installation
```bash
npm install jito-distributor-sdk
```
### Program Deployment
**First, deploy the Merkle Distributor program to devnet:**
```bash
# Deploy the program to devnet (one-time setup)
npm run deploy
```
This script will:
- Build the Anchor program
- Deploy it to Solana devnet
- Automatically update the SDK with the deployed program ID
- Verify the deployment
**Note:** You need SOL in your wallet to deploy the program. Get devnet SOL from the [Solana Faucet](https://faucet.solana.com/).
### Basic Usage
```typescript
import { MerkleDistributor } from 'jito-distributor-sdk';
import { AnchorProvider } from '@coral-xyz/anchor';
import { PublicKey } from '@solana/web3.js';
// Initialize with your Anchor provider
const distributor = new MerkleDistributor(provider);
// Create a new distributor
const signature = await distributor.createDistributor({
mint: new PublicKey('your-token-mint'),
version: 1n,
root: merkleRoot,
maxTotalClaim: 1000000n,
maxNumNodes: 100n,
startVestingTs: BigInt(Math.floor(Date.now() / 1000)),
endVestingTs: BigInt(Math.floor(Date.now() / 1000)),
clawbackStartTs: BigInt(Math.floor(Date.now() / 1000) + 86400 * 30),
clawbackReceiver: adminPublicKey,
admin: adminPublicKey
});
```
## Core Components
### MerkleDistributor Class
The main SDK class that provides methods to interact with the program.
#### Constructor
```typescript
constructor(provider: AnchorProvider)
```
#### Methods
##### `createDistributor(args: CreateDistributorArgs): Promise<string>`
Creates a new merkle distributor.
```typescript
interface CreateDistributorArgs {
mint: PublicKey;
version: bigint;
root: Uint8Array;
maxTotalClaim: bigint;
maxNumNodes: bigint;
startVestingTs: bigint;
endVestingTs: bigint;
clawbackStartTs: bigint;
clawbackReceiver: PublicKey;
admin: PublicKey;
}
```
##### `claim(args: ClaimArgs): Promise<string>`
Claims tokens from the distributor.
```typescript
interface ClaimArgs {
claimant: PublicKey;
distributor: PublicKey;
claimantTokenAccount: PublicKey;
amountUnlocked: bigint;
amountLocked: bigint;
proof: Uint8Array[];
}
```
##### `claimLocked(args: ClaimLockedArgs): Promise<string>`
Claims locked tokens after the vesting period.
```typescript
interface ClaimLockedArgs {
claimant: PublicKey;
distributor: PublicKey;
claimantTokenAccount: PublicKey;
}
```
##### `clawback(distributor: PublicKey, claimant: PublicKey): Promise<string>`
Claws back remaining tokens to the clawback receiver.
##### `setAdmin(distributor: PublicKey, currentAdmin: PublicKey, newAdmin: PublicKey): Promise<string>`
Sets a new admin for the distributor.
##### `setClawbackReceiver(distributor: PublicKey, newClawbackReceiver: PublicKey, admin: PublicKey): Promise<string>`
Sets a new clawback receiver.
##### Account Fetching Methods
- `getDistributor(distributor: PublicKey): Promise<MerkleDistributorAccount>`
- `getClaimStatus(claimStatus: PublicKey): Promise<ClaimStatus>`
- `getClaimStatusForClaimant(claimant: PublicKey, distributor: PublicKey): Promise<ClaimStatus | null>`
- `hasClaimed(claimant: PublicKey, distributor: PublicKey): Promise<boolean>`
### Utility Functions
#### PDA Derivation
```typescript
import { getDistributorPDA, getClaimStatusPDA } from 'jito-distributor-sdk';
const [distributorPDA, bump] = getDistributorPDA(mint, version);
const [claimStatusPDA, bump] = getClaimStatusPDA(claimant, distributor);
```
#### Data Conversion
```typescript
import { hexToUint8Array, uint8ArrayToHex, bigintToBN } from 'jito-distributor-sdk';
const bytes = hexToUint8Array('deadbeef');
const hex = uint8ArrayToHex(new Uint8Array([222, 173, 190, 239]));
const bn = bigintToBN(12345n);
```
#### Validation
```typescript
import { validateMerkleProof, validateTimestamps } from 'jito-distributor-sdk';
const isValidProof = validateMerkleProof(proof);
const { valid, error } = validateTimestamps(startTs, endTs, clawbackTs);
```
## Complete Example
```typescript
import {
MerkleDistributor,
getDistributorPDA,
hexToUint8Array,
validateTimestamps
} from 'jito-distributor-sdk';
import { AnchorProvider } from '@coral-xyz/anchor';
import { PublicKey } from '@solana/web3.js';
import { getAssociatedTokenAddress } from '@solana/spl-token';
async function createAndClaimAirdrop() {
// Setup provider
const provider = new AnchorProvider(connection, wallet, { commitment: 'confirmed' });
const sdk = new MerkleDistributor(provider);
// Configuration
const mint = new PublicKey('your-token-mint');
const version = 1n;
const merkleRoot = hexToUint8Array('your-merkle-root');
// Timestamps (in seconds)
const now = BigInt(Math.floor(Date.now() / 1000));
const startVesting = now; // Immediate distribution
const endVesting = now; // No vesting period
const clawbackStart = now + 86400n * 30n; // 30 days for clawback
// Validate timestamps
const validation = validateTimestamps(startVesting, endVesting, clawbackStart);
if (!validation.valid) {
throw new Error(validation.error);
}
// Get distributor PDA
const [distributorPDA] = getDistributorPDA(mint, version);
// Create distributor
const createSignature = await sdk.createDistributor({
mint,
version,
root: merkleRoot,
maxTotalClaim: 1000000n,
maxNumNodes: 100n,
startVestingTs: now, // Immediate distribution
endVestingTs: now, // No vesting period
clawbackStartTs: now + 86400n * 30n, // 30 days for clawback
clawbackReceiver: wallet.publicKey,
admin: wallet.publicKey,
});
console.log('Distributor created:', createSignature);
// Later: Claim tokens
const claimantTokenAccount = await getAssociatedTokenAddress(mint, claimantPublicKey);
const claimSignature = await sdk.claim({
claimant: claimantPublicKey,
distributor: distributorPDA,
claimantTokenAccount,
amountUnlocked: 300n, // All tokens unlocked immediately
amountLocked: 0n, // No locked tokens
proof: merkleProof, // Your merkle proof as Uint8Array[]
});
console.log('Tokens claimed:', claimSignature);
}
```
## Error Handling
The SDK includes comprehensive error types:
```typescript
import { DistributorError } from 'jito-distributor-sdk';
try {
await sdk.claim(args);
} catch (error) {
if (error.code === DistributorError.InvalidProof) {
console.error('Invalid merkle proof provided');
}
// Handle other errors...
}
```
## NPM Scripts
```bash
# Build the SDK
npm run build
# Run development mode (watch for changes)
npm run dev
# Run tests
npm run test
# Deploy the Anchor program to devnet (one-time setup)
npm run deploy
# Run examples
npm run demo # Demo without blockchain calls
npm run example # Mock wallet example
# Real mainnet testing (production scripts)
npm run create-distributor # Create a real distributor on mainnet
npm run fund # Fund the distributor with tokens
npm run claim # Claim tokens from distributor
npm run query # Query distributor status
```
See [DEPLOYMENT.md](./docs/DEPLOYMENT.md) for detailed deployment instructions.
## Testing
See [TESTING.md](./docs/TESTING.md) for complete testing instructions with real devnet transactions.
## Development
```bash
# Install dependencies
pnpm install
# Build the SDK
pnpm build
# Run tests
pnpm test
# Development mode (watch)
pnpm dev
```
## 📚 Documentation
- **[TESTING.md](./docs/TESTING.md)** - Complete guide for real devnet testing
## Merkle Tree Implementation
The SDK uses a simplified merkle tree implementation similar to the approach shown in the [Metaplex Token Claimer guide](https://developers.metaplex.com/token-metadata/guides/anchor/token-claimer-smart-contract). The merkle tree enables efficient and secure verification of airdrop eligibility using cryptographic proofs.
## License
MIT License - see LICENSE file for details.
## Contributing
Contributions are welcome! Please ensure all tests pass and follow the existing code style.