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
JavaScript
;
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;