UNPKG

jito-distributor-sdk

Version:

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

145 lines (144 loc) 4.92 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getDistributorPDA = getDistributorPDA; exports.getClaimStatusPDA = getClaimStatusPDA; exports.hexToUint8Array = hexToUint8Array; exports.uint8ArrayToHex = uint8ArrayToHex; exports.bigintToBN = bigintToBN; exports.validateMerkleProof = validateMerkleProof; exports.getCurrentTimestamp = getCurrentTimestamp; exports.validateTimestamps = validateTimestamps; const web3_js_1 = require("@solana/web3.js"); const types_1 = require("./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 */ function getDistributorPDA(mint, version) { const versionBuffer = Buffer.alloc(8); versionBuffer.writeBigUInt64LE(version); return web3_js_1.PublicKey.findProgramAddressSync([ Buffer.from(MERKLE_DISTRIBUTOR_SEED), mint.toBuffer(), versionBuffer, ], types_1.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 */ function getClaimStatusPDA(claimant, distributor) { return web3_js_1.PublicKey.findProgramAddressSync([ Buffer.from(CLAIM_STATUS_SEED), claimant.toBuffer(), distributor.toBuffer(), ], types_1.PROGRAM_ID); } /** * Converts a hex string to Uint8Array * @param hex Hex string (with or without 0x prefix) * @returns Uint8Array */ function hexToUint8Array(hex) { 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 */ function uint8ArrayToHex(bytes) { 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 */ function bigintToBN(value) { 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 */ function validateMerkleProof(proof) { 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 */ function getCurrentTimestamp() { 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 */ function validateTimestamps(startVestingTs, endVestingTs, clawbackStartTs) { 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 }; }