UNPKG

jito-distributor-sdk

Version:

TypeScript SDK for JITO Merkle Distributor with production-ready versioning and double-hashing support

156 lines (139 loc) 4.56 kB
import { PublicKey } from '@solana/web3.js'; import { PROGRAM_ID } from './types'; /** * Seeds for PDA derivation */ const MERKLE_DISTRIBUTOR_SEED = 'MerkleDistributor'; const CLAIM_STATUS_SEED = 'ClaimStatus'; /** * Derives the PDA for a MerkleDistributor account * @param mint The mint public key * @param version The version number * @returns [PDA, bump] tuple */ export function getDistributorPDA(mint: PublicKey, version: bigint): [PublicKey, number] { const versionBuffer = Buffer.alloc(8); versionBuffer.writeBigUInt64LE(version); return PublicKey.findProgramAddressSync( [ Buffer.from(MERKLE_DISTRIBUTOR_SEED), mint.toBuffer(), versionBuffer, ], PROGRAM_ID ); } /** * Derives the PDA for a ClaimStatus account * @param claimant The claimant's public key * @param distributor The distributor's public key * @returns [PDA, bump] tuple */ export function getClaimStatusPDA(claimant: PublicKey, distributor: PublicKey): [PublicKey, number] { return PublicKey.findProgramAddressSync( [ Buffer.from(CLAIM_STATUS_SEED), claimant.toBuffer(), distributor.toBuffer(), ], PROGRAM_ID ); } /** * Converts a hex string to Uint8Array * @param hex Hex string (with or without 0x prefix) * @returns Uint8Array */ export function hexToUint8Array(hex: string): Uint8Array { const cleanHex = hex.startsWith('0x') ? hex.slice(2) : hex; if (cleanHex.length % 2 !== 0) { throw new Error('Hex string must have even length'); } const bytes = new Uint8Array(cleanHex.length / 2); for (let i = 0; i < cleanHex.length; i += 2) { bytes[i / 2] = parseInt(cleanHex.substr(i, 2), 16); } return bytes; } /** * Converts Uint8Array to hex string * @param bytes Uint8Array * @returns Hex string without 0x prefix */ export function uint8ArrayToHex(bytes: Uint8Array): string { return Array.from(bytes) .map(b => b.toString(16).padStart(2, '0')) .join(''); } /** * Converts bigint to BN (Anchor's Big Number) * @param value bigint value * @returns BN */ export function bigintToBN(value: bigint): any { const { BN } = require('@coral-xyz/anchor'); return new BN(value.toString()); } /** * Validates that a merkle proof is properly formatted * @param proof Array of hex strings or Uint8Arrays * @returns boolean */ export function validateMerkleProof(proof: (string | Uint8Array)[]): boolean { for (const element of proof) { if (typeof element === 'string') { try { const bytes = hexToUint8Array(element); if (bytes.length !== 32) return false; } catch { return false; } } else if (element instanceof Uint8Array) { if (element.length !== 32) return false; } else { return false; } } return true; } /** * Gets the current Unix timestamp * @returns Current timestamp in seconds */ export function getCurrentTimestamp(): number { return Math.floor(Date.now() / 1000); } /** * Validates timestamp parameters for distributor creation * @param startVestingTs Start vesting timestamp * @param endVestingTs End vesting timestamp * @param clawbackStartTs Clawback start timestamp * @returns Object with validation result and error message if invalid */ export function validateTimestamps( startVestingTs: bigint, endVestingTs: bigint, clawbackStartTs: bigint ): { valid: boolean; error?: string } { const now = BigInt(getCurrentTimestamp()); const oneDay = BigInt(86400); // 24 hours in seconds // Allow immediate distribution (current time) or future timestamps if (startVestingTs < now - 60n) { // Allow 60 second tolerance for immediate distribution return { valid: false, error: 'Start vesting timestamp cannot be in the past (beyond 60 seconds)' }; } if (endVestingTs < now - 60n) { // Allow 60 second tolerance for immediate distribution return { valid: false, error: 'End vesting timestamp cannot be in the past (beyond 60 seconds)' }; } if (clawbackStartTs <= now) { return { valid: false, error: 'Clawback start timestamp must be in the future' }; } // Check if start is before end (or equal for immediate unlock) if (startVestingTs > endVestingTs) { return { valid: false, error: 'Start vesting must be before or equal to end vesting' }; } // Check if clawback is at least one day after vesting ends if (clawbackStartTs < endVestingTs + oneDay) { return { valid: false, error: 'Clawback start must be at least one day after vesting ends' }; } return { valid: true }; }