UNPKG

hypesdk

Version:

A powerful SDK for interacting with the Hype blockchain, featuring advanced routing and token swap capabilities

942 lines (941 loc) 47.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.HyperSDK = void 0; const ethers_1 = require("ethers"); const constants_1 = require("./constants"); const utils_1 = require("./utils"); const routing_1 = require("./services/routing"); const wallet_1 = require("./services/wallet"); class HyperSDK { constructor() { /** * Creates a new wallet * @returns {object} Object containing the wallet's address and private key */ this.createWallet = wallet_1.createWallet; this.provider = new ethers_1.ethers.JsonRpcProvider(constants_1.RPC_URL); } /** * Transfers native tokens * @param privateKey The sender's private key * @param toAddress The recipient's address * @param amount Amount to send in ether * @param priorityFeeMultiplier Optional multiplier for the priority fee * @returns Transaction hash */ async transfer(privateKey, toAddress, amount, priorityFeeMultiplier = 1) { const wallet = new ethers_1.ethers.Wallet(privateKey, this.provider); const parsedAmount = ethers_1.ethers.parseEther(amount); // Get current network conditions for gas pricing const feeData = await this.provider.getFeeData(); if (!feeData.gasPrice) { throw new Error("Failed to get gas price"); } // Calculate gas settings const gasSettings = await (0, utils_1.calculateGasSettings)(feeData, priorityFeeMultiplier, 21000); // Log gas settings (0, utils_1.logGasSettings)(gasSettings, priorityFeeMultiplier); const tx = await wallet.sendTransaction({ to: toAddress, value: parsedAmount, ...gasSettings, }); return tx.hash; } /** * Wraps native tokens into wrapped tokens * @param privateKey The sender's private key * @param amount Amount to wrap in ether * @param priorityFeeMultiplier Optional multiplier for the priority fee * @returns Transaction hash */ async wrap(privateKey, amount, priorityFeeMultiplier = 1) { const wallet = new ethers_1.ethers.Wallet(privateKey, this.provider); const wrapperAbi = ["function deposit() external payable"]; const wrapperContract = new ethers_1.ethers.Contract(constants_1.WRAPPED_TOKEN_ADDRESS, wrapperAbi, wallet); // Get current network conditions for gas pricing const feeData = await this.provider.getFeeData(); if (!feeData.gasPrice) { throw new Error("Failed to get gas price"); } // Calculate gas settings const gasSettings = await (0, utils_1.calculateGasSettings)(feeData, priorityFeeMultiplier); // Log gas settings (0, utils_1.logGasSettings)(gasSettings, priorityFeeMultiplier); const tx = await wrapperContract.deposit({ value: ethers_1.ethers.parseEther(amount), ...gasSettings, }); return tx.hash; } /** * Unwraps tokens back to native tokens * @param privateKey The sender's private key * @param amount Amount to unwrap in ether * @param priorityFeeMultiplier Optional multiplier for the priority fee * @returns Transaction hash */ async unwrap(privateKey, amount, priorityFeeMultiplier = 1) { const wallet = new ethers_1.ethers.Wallet(privateKey, this.provider); const wrapperAbi = ["function withdraw(uint256 wad) external"]; const wrapperContract = new ethers_1.ethers.Contract(constants_1.WRAPPED_TOKEN_ADDRESS, wrapperAbi, wallet); // Get current network conditions for gas pricing const feeData = await this.provider.getFeeData(); if (!feeData.gasPrice) { throw new Error("Failed to get gas price"); } // Calculate gas settings const gasSettings = await (0, utils_1.calculateGasSettings)(feeData, priorityFeeMultiplier); // Log gas settings (0, utils_1.logGasSettings)(gasSettings, priorityFeeMultiplier); const tx = await wrapperContract.withdraw(ethers_1.ethers.parseEther(amount), { ...gasSettings, }); return tx.hash; } /** * Execute a token swap using the best available route * @param privateKey The sender's private key * @param tokenIn Address of the input token * @param tokenOut Address of the output token * @param amountIn Amount of input tokens in human readable format * @param slippageBps Slippage tolerance in basis points (1 bp = 0.01%) * @param priorityFeeMultiplier Optional multiplier for the priority fee * @returns Transaction hash */ async buyWithRoute(privateKey, tokenIn, tokenOut, amountIn, slippageBps = 100, priorityFeeMultiplier = 1) { const wallet = new ethers_1.ethers.Wallet(privateKey, this.provider); const normalizedAmount = (0, utils_1.normalizeAmount)(amountIn); // Get optimal route from API using the normalized amount console.log("\nFetching optimal route..."); const routeData = await (0, routing_1.getRoute)(tokenIn, tokenOut, normalizedAmount); if (!routeData.success) { throw new Error("Failed to get route"); } const bestPath = routeData.data.bestPath; const tokenInfo = bestPath.tokenInfo; // Convert the human readable amount to base units for the contract interaction const parsedAmountIn = ethers_1.ethers.parseUnits(normalizedAmount, tokenInfo.tokenIn.decimals); // Log route information console.log("\nRoute Configuration:"); console.log("-------------------"); console.log("Path:", bestPath.path.join(" -> ")); console.log("Estimated Output:", bestPath.estimatedAmountOut); console.log("Price Impact:", bestPath.priceImpact); console.log("Input Amount:", normalizedAmount, tokenInfo.tokenIn.symbol); console.log("Input Amount (base units):", parsedAmountIn.toString()); // Check if input token is native HYPE const isNativeToken = tokenIn.toLowerCase() === constants_1.NATIVE_TOKEN.toLowerCase(); // Create router contract instance const router = new ethers_1.ethers.Contract(constants_1.ROUTER_ADDRESS, constants_1.ROUTER_ABI, wallet); // Format the hop data for the contract const tokens = bestPath.path; const hopSwaps = bestPath.hop.map((hop) => hop.allocations.map((allocation) => ({ tokenIn: allocation.tokenIn, tokenOut: allocation.tokenOut, routerIndex: allocation.routerIndex, fee: allocation.fee, amountIn: parsedAmountIn.toString(), stable: allocation.stable, }))); // Calculate minimum output amount based on slippage const estimatedOut = ethers_1.ethers.parseUnits(bestPath.estimatedAmountOut.split(".")[0], tokenInfo.tokenOut.decimals); const minAmountOut = (estimatedOut * BigInt(10000 - slippageBps)) / BigInt(10000); console.log("\nTransaction Settings:"); console.log("--------------------"); console.log("Min Amount Out:", ethers_1.ethers.formatUnits(minAmountOut, tokenInfo.tokenOut.decimals)); try { // Get current network conditions for gas pricing const feeData = await this.provider.getFeeData(); // Calculate gas settings const gasSettings = await (0, utils_1.calculateGasSettings)(feeData, priorityFeeMultiplier); // Log gas settings (0, utils_1.logGasSettings)(gasSettings, priorityFeeMultiplier); let tx; // Handle transaction based on token type if (isNativeToken) { console.log("\nExecuting native token swap..."); // For native token (HYPE), send the amount as value tx = await router.executeMultiHopSwap(tokens, 0, // This value is ignored when sending native tokens minAmountOut, hopSwaps, { ...gasSettings, value: parsedAmountIn, }); } else { // For ERC-20 tokens console.log("\nApproving token spend..."); const tokenContract = new ethers_1.ethers.Contract(tokenInfo.tokenIn.address, constants_1.ERC20_ABI, wallet); // First approve with higher gas settings const approveTx = await tokenContract.approve(constants_1.ROUTER_ADDRESS, parsedAmountIn, gasSettings); console.log("Waiting for approval transaction..."); await approveTx.wait(); console.log("Approval successful!"); console.log("\nExecuting ERC20 token swap..."); // Execute the swap with ERC-20 token tx = await router.executeMultiHopSwap(tokens, parsedAmountIn, minAmountOut, hopSwaps, { ...gasSettings, value: 0, }); } console.log("\nTransaction Details:"); console.log("-------------------"); console.log("Transaction Hash:", tx.hash); console.log("View on explorer: https://purrsec.com/tx/" + tx.hash); return tx.hash; } catch (error) { console.error("\nTransaction Failed:"); console.error("------------------"); if (error instanceof Error) { if (error.message.includes("insufficient funds")) { console.error("Error: Insufficient funds for transaction"); throw new Error("Insufficient funds for transaction"); } console.error("Full error:", error); } throw error; } } /** * Execute a token sell using the best available route * @param privateKey The sender's private key * @param tokenToSell Address of the token to sell * @param amountIn Amount of input tokens in human readable format * @param slippageBps Slippage tolerance in basis points (1 bp = 0.01%) * @param priorityFeeMultiplier Optional multiplier for the priority fee * @returns Transaction hash */ async sellWithRoute(privateKey, tokenToSell, amountIn, slippageBps = 100, // Default 1% slippage priorityFeeMultiplier = 1) { // Simply call buyWithRoute with WRAPPED_TOKEN_ADDRESS as the output token return this.buyWithRoute(privateKey, tokenToSell, constants_1.WRAPPED_TOKEN_ADDRESS, amountIn, slippageBps, priorityFeeMultiplier); } /** * Get token balances for a specific wallet address * @param walletAddress The address to check balances for * @param limit Optional maximum number of tokens to return * @returns Wallet balances including native HYPE and tokens */ async getWalletBalances(walletAddress, limit) { console.log("\nFetching wallet balances..."); console.log("Wallet:", walletAddress); if (limit) console.log("Limit:", limit); let url = `https://api.liqd.ag/tokens/balances?wallet=${walletAddress}`; if (limit) url += `&limit=${limit}`; try { const response = await fetch(url); console.log("API Response Status:", response.status); if (!response.ok) { throw new Error(`API request failed with status ${response.status}: ${response.statusText}`); } const data = (await response.json()); if (!data.success) { throw new Error(`API returned error: ${JSON.stringify(data)}`); } // Format balances to be human readable data.data.tokens = data.data.tokens.map((token) => ({ ...token, balance: ethers_1.ethers.formatUnits(token.balance, token.decimals), })); console.log("\nBalances fetched successfully!"); console.log(`Found ${data.data.count} tokens`); return data; } catch (error) { if (error instanceof Error) { throw new Error(`Failed to fetch wallet balances: ${error.message}`); } throw error; } } /** * Get balance of a specific token for a wallet address * @param tokenAddress The token contract address (use NATIVE_TOKEN for native HYPE) * @param walletAddress The wallet address to check balance for * @returns Token balance information including formatted balance and token details */ async getTokenBalance(tokenAddress, walletAddress) { console.log("\nFetching token balance..."); console.log("Token:", tokenAddress); console.log("Wallet:", walletAddress); try { // Handle native token (HYPE) balance check if (tokenAddress.toLowerCase() === constants_1.NATIVE_TOKEN.toLowerCase()) { const balance = await this.provider.getBalance(walletAddress); return { balance: balance.toString(), formattedBalance: ethers_1.ethers.formatEther(balance), symbol: "HYPE", decimals: 18, name: "Native HYPE", }; } // For other tokens, create contract instance const tokenContract = new ethers_1.ethers.Contract(tokenAddress, [ "function balanceOf(address) view returns (uint256)", "function symbol() view returns (string)", "function decimals() view returns (uint8)", "function name() view returns (string)", ], this.provider); // Get all token information in parallel const [balance, symbol, decimals, name] = await Promise.all([ tokenContract.balanceOf(walletAddress), tokenContract.symbol(), tokenContract.decimals(), tokenContract.name(), ]); // Format the balance const formattedBalance = ethers_1.ethers.formatUnits(balance, decimals); console.log("\nBalance fetched successfully!"); console.log("Token Name:", name); console.log("Symbol:", symbol); console.log("Balance:", formattedBalance); return { balance: balance.toString(), formattedBalance, symbol, decimals, name, }; } catch (error) { if (error instanceof Error) { throw new Error(`Failed to fetch token balance: ${error.message}`); } throw error; } } /** * Deploy a new token * @param privateKey The deployer's private key * @param name Token name * @param symbol Token symbol * @param initialSupply Initial token supply (in human readable format) * @param priorityFeeMultiplier Optional multiplier for the priority fee * @returns Deployed token information */ async deployToken(privateKey, name, symbol, initialSupply, priorityFeeMultiplier = 1) { console.log("\nDeploying new token..."); console.log("Name:", name); console.log("Symbol:", symbol); console.log("Initial Supply:", initialSupply); const wallet = new ethers_1.ethers.Wallet(privateKey, this.provider); // Create deployer contract instance const deployer = new ethers_1.ethers.Contract(constants_1.TOKEN_DEPLOYER_ADDRESS, constants_1.TOKEN_DEPLOYER_ABI, wallet); try { // Get current network conditions for gas pricing const feeData = await this.provider.getFeeData(); if (!feeData.gasPrice) { throw new Error("Failed to get gas price"); } // Calculate gas settings const gasSettings = await (0, utils_1.calculateGasSettings)(feeData, priorityFeeMultiplier); // Log gas settings (0, utils_1.logGasSettings)(gasSettings, priorityFeeMultiplier); // Convert initial supply to proper format (18 decimals) const parsedSupply = ethers_1.ethers.parseUnits(initialSupply, 18); // Deploy the token console.log("\nSubmitting deployment transaction..."); const tx = await deployer.deployToken(name, symbol, parsedSupply, gasSettings); console.log("Waiting for deployment to complete..."); const receipt = await tx.wait(); // Find the TokenDeployed event const event = receipt.logs .map((log) => { try { return deployer.interface.parseLog(log); } catch { return null; } }) .find((event) => event && event.name === "TokenDeployed"); if (!event) { throw new Error("Could not find TokenDeployed event in transaction logs"); } // Get the deployed token address from the event const tokenAddress = event.args.tokenAddress; // Create token contract instance to get decimals const tokenContract = new ethers_1.ethers.Contract(tokenAddress, constants_1.CUSTOM_TOKEN_ABI, this.provider); // Get token decimals const decimals = await tokenContract.decimals(); console.log("\nToken deployed successfully!"); console.log("Token address:", tokenAddress); console.log("Transaction hash:", tx.hash); console.log("View on explorer: https://purrsec.com/tx/" + tx.hash); // Return deployed token information const deployedToken = { address: tokenAddress, name, symbol, initialSupply, decimals, owner: wallet.address, transactionHash: tx.hash, }; return deployedToken; } catch (error) { if (error instanceof Error) { throw new Error(`Token deployment failed: ${error.message}`); } throw error; } } /** * Swap any token for any other token * @param privateKey The sender's private key * @param tokenIn Address of the input token * @param tokenOut Address of the output token * @param amountIn Amount of input tokens in human readable format * @param slippageBps Slippage tolerance in basis points (1 bp = 0.01%) * @param priorityFeeMultiplier Optional multiplier for the priority fee * @returns Transaction hash */ async swap(privateKey, tokenIn, tokenOut, amountIn, slippageBps = 100, priorityFeeMultiplier = 1) { console.log("\nInitiating token swap..."); console.log("Token In:", tokenIn); console.log("Token Out:", tokenOut); console.log("Amount In:", amountIn); console.log("Slippage:", slippageBps / 100, "%"); const wallet = new ethers_1.ethers.Wallet(privateKey, this.provider); const normalizedAmount = (0, utils_1.normalizeAmount)(amountIn); // Get optimal route from API console.log("\nFetching optimal route..."); const routeData = await (0, routing_1.getRoute)(tokenIn, tokenOut, normalizedAmount); if (!routeData.success) { throw new Error("Failed to get route"); } const bestPath = routeData.data.bestPath; const tokenInfo = bestPath.tokenInfo; // Convert the human readable amount to base units for the contract interaction const parsedAmountIn = ethers_1.ethers.parseUnits(normalizedAmount, tokenInfo.tokenIn.decimals); // Log route information console.log("\nRoute Configuration:"); console.log("-------------------"); console.log("Path:", bestPath.path.join(" -> ")); console.log("Estimated Output:", bestPath.estimatedAmountOut); console.log("Price Impact:", bestPath.priceImpact); console.log("Input Amount:", normalizedAmount, tokenInfo.tokenIn.symbol); console.log("Input Amount (base units):", parsedAmountIn.toString()); // Check if input token is native HYPE const isNativeToken = tokenIn.toLowerCase() === constants_1.NATIVE_TOKEN.toLowerCase(); // Create router contract instance const router = new ethers_1.ethers.Contract(constants_1.ROUTER_ADDRESS, constants_1.ROUTER_ABI, wallet); // Format the hop data for the contract const tokens = bestPath.path; const hopSwaps = bestPath.hop.map((hop) => hop.allocations.map((allocation) => ({ tokenIn: allocation.tokenIn, tokenOut: allocation.tokenOut, routerIndex: allocation.routerIndex, fee: allocation.fee, amountIn: parsedAmountIn.toString(), stable: allocation.stable, }))); // Calculate minimum output amount based on slippage const estimatedOut = ethers_1.ethers.parseUnits(bestPath.estimatedAmountOut.split(".")[0], tokenInfo.tokenOut.decimals); const minAmountOut = (estimatedOut * BigInt(10000 - slippageBps)) / BigInt(10000); console.log("\nTransaction Settings:"); console.log("--------------------"); console.log("Min Amount Out:", ethers_1.ethers.formatUnits(minAmountOut, tokenInfo.tokenOut.decimals), tokenInfo.tokenOut.symbol); try { // Get current network conditions for gas pricing const feeData = await this.provider.getFeeData(); // Calculate gas settings const gasSettings = await (0, utils_1.calculateGasSettings)(feeData, priorityFeeMultiplier); // Log gas settings (0, utils_1.logGasSettings)(gasSettings, priorityFeeMultiplier); let tx; // Handle transaction based on token type if (isNativeToken) { console.log("\nExecuting native token swap..."); // For native token (HYPE), send the amount as value tx = await router.executeMultiHopSwap(tokens, 0, // This value is ignored when sending native tokens minAmountOut, hopSwaps, { ...gasSettings, value: parsedAmountIn, }); } else { // For ERC-20 tokens console.log("\nApproving token spend..."); const tokenContract = new ethers_1.ethers.Contract(tokenInfo.tokenIn.address, constants_1.ERC20_ABI, wallet); // First approve with higher gas settings const approveTx = await tokenContract.approve(constants_1.ROUTER_ADDRESS, parsedAmountIn, gasSettings); console.log("Waiting for approval transaction..."); await approveTx.wait(); console.log("Approval successful!"); console.log("\nExecuting token swap..."); // Execute the swap with ERC-20 token tx = await router.executeMultiHopSwap(tokens, parsedAmountIn, minAmountOut, hopSwaps, { ...gasSettings, value: 0, }); } console.log("\nTransaction Details:"); console.log("-------------------"); console.log("Transaction Hash:", tx.hash); console.log("View on explorer: https://purrsec.com/tx/" + tx.hash); return tx.hash; } catch (error) { console.error("\nTransaction Failed:"); console.error("------------------"); if (error instanceof Error) { if (error.message.includes("insufficient funds")) { console.error("Error: Insufficient funds for transaction"); throw new Error("Insufficient funds for transaction"); } console.error("Full error:", error); } throw error; } } /** * Get token holders for a specific token * @param tokenAddress The token contract address * @param limit Optional maximum number of holders to return (default: 100, max: 100) * @returns Token holders information with formatted balances */ async getTokenHolders(tokenAddress, limit = 100) { console.log("\nFetching token holders..."); console.log("Token:", tokenAddress); console.log("Limit:", limit); try { // First get the total holder count const countersUrl = `https://www.hyperscan.com/api/v2/tokens/${tokenAddress}/counters`; const countersResponse = await fetch(countersUrl); if (!countersResponse.ok) { throw new Error(`Counters API request failed with status ${countersResponse.status}: ${countersResponse.statusText}`); } const countersData = await countersResponse.json(); const totalHolders = countersData.token_holders_count || 0; console.log(`Total holders from counter: ${totalHolders}`); // Get token decimals for formatting const tokenContract = new ethers_1.ethers.Contract(tokenAddress, ["function decimals() view returns (uint8)"], this.provider); const decimals = await tokenContract.decimals(); // Fetch holders from API with pagination const pageSize = 50; // API page size limit let allHolders = []; let page = 1; let hasMore = true; while (hasMore && allHolders.length < limit) { console.log(`Fetching page ${page}...`); const url = `https://www.hyperscan.com/api/v2/tokens/${tokenAddress}/holders?page=${page}&offset=${pageSize}`; const response = await fetch(url); if (!response.ok) { throw new Error(`API request failed with status ${response.status}: ${response.statusText}`); } const data = await response.json(); if (!data.items || data.items.length === 0) { hasMore = false; } else { allHolders = allHolders.concat(data.items); page++; } } // Format the response const holders = allHolders.slice(0, limit).map((item) => ({ address: item.address.hash, value: item.value, formattedValue: ethers_1.ethers.formatUnits(item.value, decimals), })); console.log("\nHolders fetched successfully!"); console.log(`Total holders from counter: ${totalHolders}`); console.log(`Fetched ${allHolders.length} holders`); console.log(`Returning ${holders.length} holders`); return { holders, totalHolders: totalHolders, }; } catch (error) { if (error instanceof Error) { throw new Error(`Failed to fetch token holders: ${error.message}`); } throw error; } } /** * Get detailed information about a transaction * @param txHash The transaction hash to look up * @returns Formatted transaction data from Hyperscan API */ async getTransactionInfo(txHash) { console.log("\nFetching transaction info..."); console.log("Transaction Hash:", txHash); try { const url = `https://www.hyperscan.com/api/v2/transactions/${txHash}`; const response = await fetch(url); if (!response.ok) { throw new Error(`API request failed with status ${response.status}: ${response.statusText}`); } const rawData = await response.json(); // Format the transaction data const data = { hash: rawData.hash, result: rawData.result, priority_fee: rawData.priority_fee, max_fee_per_gas: rawData.max_fee_per_gas, max_priority_fee_per_gas: rawData.max_priority_fee_per_gas, transaction_burnt_fee: rawData.transaction_burnt_fee, confirmations: rawData.confirmations, confirmation_duration: rawData.confirmation_duration, revert_reason: rawData.revert_reason, from: { hash: rawData.from.hash, name: rawData.from.name, is_contract: rawData.from.is_contract, is_verified: rawData.from.is_verified, }, to: { hash: rawData.to.hash, name: rawData.to.name, is_contract: rawData.to.is_contract, is_verified: rawData.to.is_verified, }, }; console.log("\nTransaction data fetched successfully!"); return { data, success: true, }; } catch (error) { if (error instanceof Error) { return { data: null, success: false, error: `Failed to fetch transaction info: ${error.message}`, }; } return { data: null, success: false, error: "An unknown error occurred", }; } } /** * Airdrop tokens to multiple recipients in batches of 10 * @param privateKey The sender's private key * @param tokenAddress The token contract address to airdrop * @param recipients Array of recipient addresses * @param amounts Array of amounts to send (in human readable format) * @param priorityFeeMultiplier Optional multiplier for the priority fee * @returns Array of transaction hashes */ async airdropToken(privateKey, tokenAddress, recipients, amounts, priorityFeeMultiplier = 1) { if (recipients.length !== amounts.length) { throw new Error("Recipients and amounts arrays must have the same length"); } if (recipients.length === 0) { throw new Error("Must provide at least one recipient"); } console.log("\nPreparing token airdrop..."); console.log("Token:", tokenAddress); console.log("Total number of recipients:", recipients.length); const BATCH_SIZE = 25; const numBatches = Math.ceil(recipients.length / BATCH_SIZE); console.log("Number of batches:", numBatches); const wallet = new ethers_1.ethers.Wallet(privateKey, this.provider); const txHashes = []; try { // Get token decimals const tokenContract = new ethers_1.ethers.Contract(tokenAddress, ["function decimals() view returns (uint8)"], this.provider); const decimals = await tokenContract.decimals(); // Create airdrop contract instance const airdropContract = new ethers_1.ethers.Contract(constants_1.AIRDROP_CONTRACT_ADDRESS, constants_1.AIRDROP_ABI, wallet); // Get current network conditions for gas pricing const feeData = await this.provider.getFeeData(); const gasSettings = await (0, utils_1.calculateGasSettings)(feeData, priorityFeeMultiplier); // Create token contract with signer for approvals const tokenContractWithSigner = new ethers_1.ethers.Contract(tokenAddress, constants_1.ERC20_ABI, wallet); // Check current allowance const currentAllowance = await tokenContractWithSigner.allowance(wallet.address, constants_1.AIRDROP_CONTRACT_ADDRESS); // If allowance is not max, approve max amount if (currentAllowance < ethers_1.ethers.MaxUint256) { console.log("\nApproving maximum token spend..."); const approveTx = await tokenContractWithSigner.approve(constants_1.AIRDROP_CONTRACT_ADDRESS, ethers_1.ethers.MaxUint256, gasSettings); console.log("Waiting for approval transaction..."); await approveTx.wait(); console.log("Approval successful!"); } else { console.log("\nMaximum token approval already exists"); } // Process batches for (let i = 0; i < numBatches; i++) { const start = i * BATCH_SIZE; const end = Math.min(start + BATCH_SIZE, recipients.length); const batchRecipients = recipients.slice(start, end); const batchAmounts = amounts.slice(start, end); console.log(`\nProcessing batch ${i + 1}/${numBatches}`); console.log("Recipients in this batch:", batchRecipients.length); // Convert amounts to proper decimals for this batch const parsedAmounts = batchAmounts.map((amount) => ethers_1.ethers.parseUnits(amount, decimals)); // Execute the airdrop for this batch console.log("\nExecuting airdrop for batch..."); const tx = await airdropContract.airdropToken(tokenAddress, batchRecipients, parsedAmounts, gasSettings); console.log("\nBatch Transaction Details:"); console.log("-------------------------"); console.log("Transaction Hash:", tx.hash); console.log("View on explorer: https://purrsec.com/tx/" + tx.hash); // Wait for transaction to be mined before proceeding to next batch console.log("Waiting for transaction confirmation..."); await tx.wait(); console.log("Transaction confirmed!"); txHashes.push(tx.hash); } console.log("\nAirdrop Summary:"); console.log("--------------"); console.log("Total batches processed:", numBatches); console.log("Total recipients:", recipients.length); console.log("All transactions successful!"); return txHashes; } catch (error) { if (error instanceof Error) { throw new Error(`Airdrop failed: ${error.message}`); } throw error; } } /** * Distribute native HYPE tokens to multiple recipients in batches of 100 * @param privateKey The sender's private key * @param recipients Array of recipient addresses * @param amounts Array of amounts to send (in human readable format) * @param priorityFeeMultiplier Optional multiplier for the priority fee (scales with batch size) * @returns Array of transaction hashes */ async distributeHype(privateKey, recipients, amounts, priorityFeeMultiplier = 1) { if (recipients.length !== amounts.length) { throw new Error("Recipients and amounts arrays must have the same length"); } if (recipients.length === 0) { throw new Error("Must provide at least one recipient"); } console.log("\nPreparing HYPE distribution..."); console.log("Total number of recipients:", recipients.length); const BATCH_SIZE = 100; const numBatches = Math.ceil(recipients.length / BATCH_SIZE); console.log("Number of batches:", numBatches); const wallet = new ethers_1.ethers.Wallet(privateKey, this.provider); const txHashes = []; try { // Create distributor contract instance const distributorContract = new ethers_1.ethers.Contract(constants_1.HYPE_DISTRIBUTOR_ADDRESS, constants_1.HYPE_DISTRIBUTOR_ABI, wallet); // Process batches for (let i = 0; i < numBatches; i++) { const start = i * BATCH_SIZE; const end = Math.min(start + BATCH_SIZE, recipients.length); const batchRecipients = recipients.slice(start, end); const batchAmounts = amounts.slice(start, end); console.log(`\nProcessing batch ${i + 1}/${numBatches}`); console.log("Recipients in this batch:", batchRecipients.length); // Convert amounts to proper decimals for this batch const parsedAmounts = batchAmounts.map((amount) => ethers_1.ethers.parseEther(amount)); // Calculate total amount for this batch const batchTotalAmount = parsedAmounts.reduce((sum, amount) => sum + BigInt(amount.toString()), BigInt(0)); // Get current network conditions for gas pricing const feeData = await this.provider.getFeeData(); // Calculate gas settings with increased multiplier for larger batches const batchSizeMultiplier = Math.max(1, batchRecipients.length / 20); // Scale gas with batch size const effectiveMultiplier = priorityFeeMultiplier * batchSizeMultiplier; const gasSettings = await (0, utils_1.calculateGasSettings)(feeData, effectiveMultiplier); // Log gas settings console.log("\nGas Settings (adjusted for batch size):"); (0, utils_1.logGasSettings)(gasSettings, effectiveMultiplier); // Execute the distribution for this batch console.log("\nExecuting HYPE distribution..."); const tx = await distributorContract.distribute(batchRecipients, parsedAmounts, { ...gasSettings, value: batchTotalAmount, // Send the total HYPE amount for the batch }); console.log("\nBatch Transaction Details:"); console.log("-------------------------"); console.log("Transaction Hash:", tx.hash); console.log("View on explorer: https://purrsec.com/tx/" + tx.hash); // Wait for transaction to be mined before proceeding to next batch console.log("Waiting for transaction confirmation..."); await tx.wait(); console.log("Transaction confirmed!"); txHashes.push(tx.hash); } console.log("\nDistribution Summary:"); console.log("--------------"); console.log("Total batches processed:", numBatches); console.log("Total recipients:", recipients.length); console.log("All transactions successful!"); return txHashes; } catch (error) { if (error instanceof Error) { throw new Error(`HYPE distribution failed: ${error.message}`); } throw error; } } /** * Get the current price of tokenB in terms of tokenA * @param tokenA The base token address * @param tokenB The quote token address * @returns Price information including the price of 1 tokenA in tokenB, and additional details */ async getPrice(tokenA, tokenB) { console.log("\nFetching price information..."); console.log("Base Token:", tokenA); console.log("Quote Token:", tokenB); // Use getRoute with 1 unit of tokenA to get price const routeData = await (0, routing_1.getRoute)(tokenA, tokenB, "1"); if (!routeData.success) { throw new Error("Failed to get price information"); } const bestPath = routeData.data.bestPath; const tokenInfo = bestPath.tokenInfo; // Get route information const routerNames = bestPath.hop .map((hop) => hop.allocations.map((alloc) => alloc.routerName || "Unknown Router")) .flat(); // Format the response return { price: bestPath.estimatedAmountOut, baseTokenSymbol: tokenInfo.tokenIn.symbol, quoteTokenSymbol: tokenInfo.tokenOut.symbol, baseTokenDecimals: tokenInfo.tokenIn.decimals, quoteTokenDecimals: tokenInfo.tokenOut.decimals, routeInfo: { path: bestPath.path, priceImpact: bestPath.priceImpact || "0", routerNames, }, }; } /** * Create a buy limit order that executes when the token price falls to the target price * @param privateKey The private key of the wallet executing the trade * @param tokenAddress The address of the token to buy * @param amountIn Amount of HYPE to spend (in human readable format) * @param targetPrice Target price of token in terms of HYPE * @param options Additional options like slippage, check interval, timeout, etc. * @returns Object containing the order ID and a function to cancel the order */ async createLimitOrderBuy(privateKey, tokenAddress, amountIn, targetPrice, options = {}) { console.log("\nCreating BUY limit order..."); console.log("Token to buy:", tokenAddress); console.log("HYPE to spend:", amountIn); console.log("Target price:", targetPrice); // Use wrapped token as the input token (what we're spending) return this.createLimitOrder(privateKey, constants_1.WRAPPED_TOKEN_ADDRESS, tokenAddress, amountIn, targetPrice, "buy", options); } /** * Create a sell limit order that executes when the token price rises to the target price * @param privateKey The private key of the wallet executing the trade * @param tokenAddress The address of the token to sell * @param amountIn Amount of tokens to sell (in human readable format) * @param targetPrice Target price of token in terms of HYPE * @param options Additional options like slippage, check interval, timeout, etc. * @returns Object containing the order ID and a function to cancel the order */ async createLimitOrderSell(privateKey, tokenAddress, amountIn, targetPrice, options = {}) { console.log("\nCreating SELL limit order..."); console.log("Token to sell:", tokenAddress); console.log("Token amount:", amountIn); console.log("Target price:", targetPrice); // Use wrapped token as the output token (what we're getting) return this.createLimitOrder(privateKey, tokenAddress, constants_1.WRAPPED_TOKEN_ADDRESS, amountIn, targetPrice, "sell", options); } /** * Create a limit order that executes when the target price is reached * @param privateKey The private key of the wallet executing the trade * @param tokenIn The input token address * @param tokenOut The output token address * @param amountIn Amount of input tokens (in human readable format) * @param targetPrice Target price of tokenOut in terms of tokenIn * @param orderType 'buy' or 'sell' * @param options Additional options like slippage, check interval, timeout, etc. * @returns Object containing the order ID and a function to cancel the order * @private This method is used internally by createLimitOrderBuy and createLimitOrderSell */ async createLimitOrder(privateKey, tokenIn, tokenOut, amountIn, targetPrice, orderType, options = {}) { const { slippageBps = 100, checkIntervalMs = 10000, // Check every 10 seconds by default timeoutMs = 0, // 0 means no timeout priorityFeeMultiplier = 1, } = options; console.log("\nCreating limit order..."); console.log("Type:", orderType.toUpperCase()); console.log("Token In:", tokenIn); console.log("Token Out:", tokenOut); console.log("Amount In:", amountIn); console.log("Target Price:", targetPrice); console.log("Check Interval:", checkIntervalMs + "ms"); console.log("Timeout:", timeoutMs === 0 ? "None" : timeoutMs + "ms"); // Generate a unique order ID const orderId = ethers_1.ethers.id(Date.now().toString()); let isActive = true; // Start monitoring price and execute when condition is met const monitor = async () => { const startTime = Date.now(); while (isActive) { try { // Check if order has timed out if (timeoutMs > 0 && Date.now() - startTime >= timeoutMs) { console.log(`\nOrder ${orderId} timed out`); isActive = false; break; } // Get current price const priceInfo = await this.getPrice(tokenIn, tokenOut); const currentPrice = parseFloat(priceInfo.price); const targetPriceNum = parseFloat(targetPrice); console.log(`\nChecking price for order ${orderId}...`); console.log("Current Price:", currentPrice); console.log("Target Price:", targetPriceNum); // Check if target price is met const shouldExecute = orderType === "buy" ? currentPrice <= targetPriceNum // For buy orders, execute when price is below or at target : currentPrice >= targetPriceNum; // For sell orders, execute when price is above or at target if (shouldExecute) { console.log("\nTarget price reached! Executing order..."); // Execute the trade const txHash = await this.swap(privateKey, tokenIn, tokenOut, amountIn, slippageBps, priorityFeeMultiplier); console.log("\nOrder executed successfully!"); console.log("Transaction Hash:", txHash); isActive = false; break; } // Wait for next check interval await new Promise((resolve) => setTimeout(resolve, checkIntervalMs)); } catch (error) { console.error("Error monitoring price:", error); // Wait before retrying await new Promise((resolve) => setTimeout(resolve, checkIntervalMs)); } } }; // Start monitoring in the background monitor().catch((error) => { console.error("Limit order monitoring failed:", error); isActive = false; }); // Return order ID and cancel function return { orderId, cancel: () => { console.log(`\nCancelling order ${orderId}...`); isActive = false; }, }; } } exports.HyperSDK = HyperSDK; // Export constants for external use HyperSDK.RPC_URL = constants_1.RPC_URL; HyperSDK.NATIVE_TOKEN = constants_1.NATIVE_TOKEN; HyperSDK.WRAPPED_TOKEN_ADDRESS = constants_1.WRAPPED_TOKEN_ADDRESS; HyperSDK.ROUTER_ADDRESS = constants_1.ROUTER_ADDRESS;