UNPKG

@solana-tracker/data-api

Version:

Official Solana Tracker Data API client for accessing Solana Data

1,387 lines (1,381 loc) 66.3 kB
"use strict"; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/index.ts var index_exports = {}; __export(index_exports, { Client: () => Client, DataApiError: () => DataApiError, Datastream: () => Datastream, DatastreamRoom: () => DatastreamRoom, RateLimitError: () => RateLimitError, ValidationError: () => ValidationError, decodeBinaryEvents: () => decodeBinaryEvents, processEvents: () => processEvents, processEventsAsync: () => processEventsAsync }); module.exports = __toCommonJS(index_exports); // src/event-processor.ts var timeframes = { "1m": { time: 60 }, "5m": { time: 300 }, "15m": { time: 900 }, "30m": { time: 1800 }, "1h": { time: 3600 }, "2h": { time: 7200 }, "3h": { time: 10800 }, "4h": { time: 14400 }, "5h": { time: 18e3 }, "6h": { time: 21600 }, "12h": { time: 43200 }, "24h": { time: 86400 } }; var timeframeBoundaries = Object.entries(timeframes).map(([key, value]) => ({ key, seconds: value.time })).sort((a, b) => a.seconds - b.seconds); function decodeBinaryEvents(binaryData) { const data = binaryData instanceof ArrayBuffer ? new Uint8Array(binaryData) : binaryData; const view = new DataView(data.buffer, data.byteOffset, data.byteLength); let offset = 0; const walletCount = view.getUint32(offset, true); offset += 4; const wallets = []; for (let i = 0; i < walletCount; i++) { const length = view.getUint8(offset++); const walletBytes = data.slice(offset, offset + length); const wallet = new TextDecoder().decode(walletBytes); wallets.push(wallet); offset += length; } const tradeCount = view.getUint32(offset, true); offset += 4; const events = []; for (let i = 0; i < tradeCount; i++) { const walletIndex = view.getUint32(offset, true); const amount = view.getFloat32(offset + 4, true); const priceUsd = view.getFloat32(offset + 8, true); const volume = view.getFloat32(offset + 12, true); const typeCode = view.getUint8(offset + 16); const timeInSeconds = view.getUint32(offset + 17, true); events.push({ wallet: wallets[walletIndex], amount, priceUsd, volume, type: typeCode === 0 ? "buy" : "sell", time: timeInSeconds * 1e3 }); offset += 21; } return events; } function processEvents(binaryData) { let events; if (binaryData instanceof ArrayBuffer || binaryData instanceof Uint8Array) { events = decodeBinaryEvents(binaryData); } else if (Array.isArray(binaryData)) { events = binaryData; } else { throw new Error("Invalid input: expected binary data or events array"); } if (events.length === 0) return {}; const currentPrice = parseFloat(events[0].priceUsd.toString()); const currentTimestamp = Date.now() / 1e3; const stats = {}; timeframeBoundaries.forEach(({ key }) => { stats[key] = { buys: 0, sells: 0, buyVolume: 0, sellVolume: 0, buyers: /* @__PURE__ */ new Map(), sellers: /* @__PURE__ */ new Map(), totalTransactions: 0, totalVolume: 0, totalWallets: /* @__PURE__ */ new Map(), initialPrice: 0, lastPrice: 0, hasData: false }; }); for (let i = 0; i < events.length; i++) { const event = events[i]; const { time: timestamp, type, volume, wallet, priceUsd } = event; if (type !== "buy" && type !== "sell") continue; const eventTime = timestamp / 1e3; const timeDiff = currentTimestamp - eventTime; for (let j = 0; j < timeframeBoundaries.length; j++) { const { key, seconds } = timeframeBoundaries[j]; if (timeDiff > seconds) continue; const period = stats[key]; if (!period.hasData) { period.initialPrice = parseFloat(priceUsd.toString()); period.hasData = true; } period.totalTransactions++; period.totalVolume += parseFloat(volume.toString() || "0"); period.totalWallets.set(wallet, true); period.lastPrice = parseFloat(priceUsd.toString()); if (type === "buy") { period.buys++; period.buyVolume += parseFloat(volume.toString() || "0"); period.buyers.set(wallet, true); } else { period.sells++; period.sellVolume += parseFloat(volume.toString() || "0"); period.sellers.set(wallet, true); } } } const sortedStats = {}; Object.keys(timeframes).forEach((timeframe) => { const period = stats[timeframe]; if (!period.hasData) return; const priceChangePercent = period.initialPrice > 0 ? 100 * ((currentPrice - period.lastPrice) / period.lastPrice) : 0; const timeframeStats = { buyers: period.buyers.size, sellers: period.sellers.size, volume: { buys: period.buyVolume, sells: period.sellVolume, total: period.totalVolume }, transactions: period.totalTransactions, buys: period.buys, sells: period.sells, wallets: period.totalWallets.size, price: period.initialPrice, priceChangePercentage: priceChangePercent }; sortedStats[timeframe] = timeframeStats; }); return sortedStats; } async function processEventsAsync(binaryData, onProgress) { let events; if (binaryData instanceof ArrayBuffer || binaryData instanceof Uint8Array) { events = decodeBinaryEvents(binaryData); } else if (Array.isArray(binaryData)) { events = binaryData; } else { throw new Error("Invalid input: expected binary data or events array"); } if (events.length === 0) return {}; const CHUNK_SIZE = 1e5; const currentPrice = parseFloat(events[0].priceUsd.toString()); const currentTimestamp = Date.now() / 1e3; const stats = {}; timeframeBoundaries.forEach(({ key }) => { stats[key] = { buys: 0, sells: 0, buyVolume: 0, sellVolume: 0, buyers: /* @__PURE__ */ new Map(), sellers: /* @__PURE__ */ new Map(), totalTransactions: 0, totalVolume: 0, totalWallets: /* @__PURE__ */ new Map(), initialPrice: 0, lastPrice: 0, hasData: false }; }); for (let chunk = 0; chunk < events.length; chunk += CHUNK_SIZE) { await new Promise((resolve) => { setTimeout(() => { const end = Math.min(chunk + CHUNK_SIZE, events.length); for (let i = chunk; i < end; i++) { const event = events[i]; const { time: timestamp, type, volume, wallet, priceUsd } = event; if (type !== "buy" && type !== "sell") continue; const eventTime = timestamp / 1e3; const timeDiff = currentTimestamp - eventTime; for (let j = 0; j < timeframeBoundaries.length; j++) { const { key, seconds } = timeframeBoundaries[j]; if (timeDiff > seconds) continue; const period = stats[key]; if (!period.hasData) { period.initialPrice = parseFloat(priceUsd.toString()); period.hasData = true; } period.totalTransactions++; period.totalVolume += parseFloat(volume.toString() || "0"); period.totalWallets.set(wallet, true); period.lastPrice = parseFloat(priceUsd.toString()); if (type === "buy") { period.buys++; period.buyVolume += parseFloat(volume.toString() || "0"); period.buyers.set(wallet, true); } else { period.sells++; period.sellVolume += parseFloat(volume.toString() || "0"); period.sellers.set(wallet, true); } } } if (onProgress) { onProgress(end / events.length * 100); } resolve(); }, 0); }); } const sortedStats = {}; Object.keys(timeframes).forEach((timeframe) => { const period = stats[timeframe]; if (!period.hasData) return; const priceChangePercent = period.initialPrice > 0 ? 100 * ((currentPrice - period.lastPrice) / period.lastPrice) : 0; const timeframeStats = { buyers: period.buyers.size, sellers: period.sellers.size, volume: { buys: period.buyVolume, sells: period.sellVolume, total: period.totalVolume }, transactions: period.totalTransactions, buys: period.buys, sells: period.sells, wallets: period.totalWallets.size, price: period.initialPrice, priceChangePercentage: priceChangePercent }; sortedStats[timeframe] = timeframeStats; }); return sortedStats; } // src/data-api.ts var DataApiError = class extends Error { constructor(message, status, code, details) { super(message); this.status = status; this.code = code; this.name = "DataApiError"; this.details = details; } }; var RateLimitError = class extends DataApiError { constructor(message, retryAfter, details) { super(message, 429, "RATE_LIMIT_EXCEEDED", details); this.retryAfter = retryAfter; this.name = "RateLimitError"; } }; var ValidationError = class extends DataApiError { constructor(message, details) { super(message, 400, "VALIDATION_ERROR", details); this.name = "ValidationError"; } }; var Client = class { /** * Creates a new instance of the Solana Tracker Data API client * @param config Configuration options including API key */ constructor(config) { this.apiKey = config.apiKey; this.baseUrl = config.baseUrl || "https://data.solanatracker.io"; } /** * Makes a request to the API * @param endpoint The API endpoint * @param options Additional fetch options * @returns The API response */ async request(endpoint, options) { const headers = { "x-api-key": this.apiKey, "Content-Type": "application/json", ...options == null ? void 0 : options.headers }; try { const response = await fetch(`${this.baseUrl}${endpoint}`, { ...options, headers }); if (!response.ok) { let errorMessage = `API request failed: ${response.status} ${response.statusText}`; let errorDetails = null; try { const contentType = response.headers.get("content-type"); if (contentType && contentType.includes("application/json")) { errorDetails = await response.json(); if (typeof errorDetails === "string") { errorMessage = errorDetails; } else if (errorDetails && typeof errorDetails === "object") { if (typeof errorDetails.message === "string") { errorMessage = errorDetails.message; } else if (typeof errorDetails.error === "string") { errorMessage = errorDetails.error; } else if (typeof errorDetails.detail === "string") { errorMessage = errorDetails.detail; } else if (typeof errorDetails.msg === "string") { errorMessage = errorDetails.msg; } else if (errorDetails.error && typeof errorDetails.error === "object") { if (typeof errorDetails.error.message === "string") { errorMessage = errorDetails.error.message; } else if (typeof errorDetails.error.detail === "string") { errorMessage = errorDetails.error.detail; } else { errorMessage = `API error: ${JSON.stringify(errorDetails.error)}`; } } else { errorMessage = `API error: ${JSON.stringify(errorDetails)}`; } } } } catch (parseError) { console.error("Failed to parse error response:", parseError); } if (response.status === 429) { const retryAfter = response.headers.get("Retry-After"); if (!(options == null ? void 0 : options.disableLogs)) { console.warn(`Rate limit exceeded for ${endpoint}. Retry after: ${retryAfter || "1"} seconds`); } const error2 = new RateLimitError(errorMessage, retryAfter ? parseInt(retryAfter) : void 0); if (errorDetails) { error2.details = errorDetails; } throw error2; } const error = new DataApiError(errorMessage, response.status); if (errorDetails) { error.details = errorDetails; } throw error; } return response.json(); } catch (error) { if (error instanceof DataApiError) { throw error; } throw new DataApiError(`An unexpected error occurred: ${error instanceof Error ? error.message : "Unknown error"}`); } } /** * Validates a Solana public key * @param address The address to validate * @param paramName The parameter name for error messaging * @throws ValidationError if the address is invalid */ validatePublicKey(address, paramName) { if (!address || !/^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(address)) { throw new ValidationError(`Invalid ${paramName}: ${address}`); } } // ======== TOKEN ENDPOINTS ======== /** * Get comprehensive information about a specific token * @param tokenAddress The token's mint address * @returns Detailed token information */ async getTokenInfo(tokenAddress) { this.validatePublicKey(tokenAddress, "tokenAddress"); return this.request(`/tokens/${tokenAddress}`); } /** * Get token information by searching with a pool address * @param poolAddress The pool address * @returns Detailed token information */ async getTokenByPool(poolAddress) { this.validatePublicKey(poolAddress, "poolAddress"); return this.request(`/tokens/by-pool/${poolAddress}`); } /** * Get token holders information * @param tokenAddress The token's mint address * @returns Information about token holders */ async getTokenHolders(tokenAddress) { this.validatePublicKey(tokenAddress, "tokenAddress"); return this.request(`/tokens/${tokenAddress}/holders`); } /** * Get top 20 token holders * @param tokenAddress The token's mint address * @returns Top holders information */ async getTopHolders(tokenAddress) { this.validatePublicKey(tokenAddress, "tokenAddress"); return this.request(`/tokens/${tokenAddress}/holders/top`); } /** * Get the all-time high price and market cap for a token * @param tokenAddress The token's mint address * @returns All-time high price and market cap data */ async getAthPrice(tokenAddress) { this.validatePublicKey(tokenAddress, "tokenAddress"); return this.request(`/tokens/${tokenAddress}/ath`); } /** * Get tokens created by a specific wallet with pagination * @param wallet The deployer wallet address * @param page Page number (default: 1) * @param limit Number of items per page (default: 250, max: 500) * @returns List of tokens created by the deployer */ async getTokensByDeployer(wallet, page, limit) { this.validatePublicKey(wallet, "wallet"); const params = new URLSearchParams(); if (page) params.append("page", page.toString()); if (limit) params.append("limit", limit.toString()); const query = params.toString() ? `?${params.toString()}` : ""; return this.request(`/deployer/${wallet}${query}`); } /** * Search for tokens with flexible filtering options * @param params Search parameters and filters * @returns Search results */ async searchTokens(params) { const queryParams = new URLSearchParams(); for (const [key, value] of Object.entries(params)) { if (value !== void 0) { queryParams.append(key, value.toString()); } } return this.request(`/search?${queryParams}`); } /** * Get the latest tokens * @param page Page number (1-10) * @returns List of latest tokens */ async getLatestTokens(page = 1) { if (page < 1 || page > 10) { throw new ValidationError("Page must be between 1 and 10"); } return this.request(`/tokens/latest?page=${page}`); } /** * Get information about multiple tokens * @param tokenAddresses Array of token addresses * @returns Information about multiple tokens */ async getMultipleTokens(tokenAddresses) { if (tokenAddresses.length > 20) { throw new ValidationError("Maximum of 20 tokens per request"); } tokenAddresses.forEach((addr) => this.validatePublicKey(addr, "tokenAddress")); return this.request("/tokens/multi", { method: "POST", body: JSON.stringify({ tokens: tokenAddresses }) }); } /** * Get trending tokens * @param timeframe Optional timeframe for trending calculation * @returns List of trending tokens */ async getTrendingTokens(timeframe) { const validTimeframes = ["5m", "15m", "30m", "1h", "2h", "3h", "4h", "5h", "6h", "12h", "24h"]; if (timeframe && !validTimeframes.includes(timeframe)) { throw new ValidationError(`Invalid timeframe. Must be one of: ${validTimeframes.join(", ")}`); } const endpoint = timeframe ? `/tokens/trending/${timeframe}` : "/tokens/trending"; return this.request(endpoint); } /** * Get tokens sorted by volume * @param timeframe Optional timeframe for volume calculation * @returns List of tokens sorted by volume */ async getTokensByVolume(timeframe) { const validTimeframes = ["5m", "15m", "30m", "1h", "6h", "12h", "24h"]; if (timeframe && !validTimeframes.includes(timeframe)) { throw new ValidationError(`Invalid timeframe. Must be one of: ${validTimeframes.join(", ")}`); } const endpoint = timeframe ? `/tokens/volume/${timeframe}` : "/tokens/volume"; return this.request(endpoint); } /** * Get an overview of latest, graduating, and graduated tokens * @param limit Optional limit for the number of tokens per category * @returns Token overview (Memescope / Pumpvision style) */ async getTokenOverview(limit) { if (limit !== void 0 && (!Number.isInteger(limit) || limit <= 0)) { throw new ValidationError("Limit must be a positive integer"); } const endpoint = limit ? `/tokens/multi/all?limit=${limit}` : "/tokens/multi/all"; return this.request(endpoint); } /** * Get graduated tokens * @returns List of graduated tokens */ async getGraduatedTokens() { return this.request("/tokens/multi/graduated"); } /** * Get graduated tokens * @returns List of graduated tokens */ async getGraduatingTokens() { return this.request("/tokens/multi/graduating"); } // ======== PRICE ENDPOINTS ======== /** * Get price information for a token * @param tokenAddress The token's mint address * @param priceChanges Include price change percentages * @returns Price data */ async getPrice(tokenAddress, priceChanges) { this.validatePublicKey(tokenAddress, "tokenAddress"); const query = priceChanges ? "&priceChanges=true" : ""; return this.request(`/price?token=${tokenAddress}${query}`); } /** * Get historic price information for a token * @param tokenAddress The token's mint address * @returns Historic price data */ async getPriceHistory(tokenAddress) { this.validatePublicKey(tokenAddress, "tokenAddress"); return this.request(`/price/history?token=${tokenAddress}`); } /** * Get price at a specific timestamp * @param tokenAddress The token's mint address * @param timestamp Unix timestamp * @returns Price at the specified timestamp */ async getPriceAtTimestamp(tokenAddress, timestamp) { this.validatePublicKey(tokenAddress, "tokenAddress"); return this.request(`/price/history/timestamp?token=${tokenAddress}&timestamp=${timestamp}`); } /** * Get lowest and highest price in a time range * @param tokenAddress The token's mint address * @param timeFrom Start time (unix timestamp) * @param timeTo End time (unix timestamp) * @returns Price range data */ async getPriceRange(tokenAddress, timeFrom, timeTo) { this.validatePublicKey(tokenAddress, "tokenAddress"); return this.request(`/price/history/range?token=${tokenAddress}&time_from=${timeFrom}&time_to=${timeTo}`); } /** * Get price information for a token (POST method) * @param tokenAddress The token's mint address * @param priceChanges Include price change percentages * @returns Price data */ async postPrice(tokenAddress, priceChanges) { this.validatePublicKey(tokenAddress, "tokenAddress"); return this.request("/price", { method: "POST", body: JSON.stringify({ token: tokenAddress, priceChanges: priceChanges || false }) }); } /** * Get price information for multiple tokens * @param tokenAddresses Array of token addresses * @param priceChanges Include price change percentages * @returns Price data for multiple tokens */ async getMultiplePrices(tokenAddresses, priceChanges) { if (tokenAddresses.length > 100) { throw new ValidationError("Maximum of 100 tokens per request"); } tokenAddresses.forEach((addr) => this.validatePublicKey(addr, "tokenAddress")); const query = priceChanges ? "&priceChanges=true" : ""; return this.request(`/price/multi?tokens=${tokenAddresses.join(",")}${query}`); } /** * Get price information for multiple tokens (POST method) * @param tokenAddresses Array of token addresses * @param priceChanges Include price change percentages * @returns Price data for multiple tokens */ async postMultiplePrices(tokenAddresses, priceChanges) { if (tokenAddresses.length > 100) { throw new ValidationError("Maximum of 100 tokens per request"); } tokenAddresses.forEach((addr) => this.validatePublicKey(addr, "tokenAddress")); return this.request("/price/multi", { method: "POST", body: JSON.stringify({ tokens: tokenAddresses, priceChanges: priceChanges || false }) }); } // ======== WALLET ENDPOINTS ======== /** * Get basic wallet information * @param owner Wallet address * @returns Basic wallet data */ async getWalletBasic(owner) { this.validatePublicKey(owner, "owner"); return this.request(`/wallet/${owner}/basic`); } /** * Get all tokens in a wallet * @param owner Wallet address * @returns Detailed wallet data */ async getWallet(owner) { this.validatePublicKey(owner, "owner"); return this.request(`/wallet/${owner}`); } /** * Get wallet tokens with pagination * @param owner Wallet address * @param page Page number * @returns Paginated wallet data */ async getWalletPage(owner, page) { this.validatePublicKey(owner, "owner"); return this.request(`/wallet/${owner}/page/${page}`); } /** * Get wallet portfolio chart data with PnL information * @param wallet Wallet address * @returns Wallet chart data with historical values and PnL * @throws DataApiError if no data found for the wallet */ async getWalletChart(wallet) { this.validatePublicKey(wallet, "wallet"); return this.request(`/wallet/${wallet}/chart`); } /** * Get wallet trades * @param owner Wallet address * @param cursor Pagination cursor * @param showMeta Include token metadata * @param parseJupiter Parse Jupiter swaps * @param hideArb Hide arbitrage transactions * @returns Wallet trades data */ async getWalletTrades(owner, cursor, showMeta, parseJupiter, hideArb) { this.validatePublicKey(owner, "owner"); const params = new URLSearchParams(); if (cursor) params.append("cursor", cursor.toString()); if (showMeta) params.append("showMeta", "true"); if (parseJupiter) params.append("parseJupiter", "true"); if (hideArb) params.append("hideArb", "true"); const query = params.toString() ? `?${params.toString()}` : ""; return this.request(`/wallet/${owner}/trades${query}`); } // ======== TRADE ENDPOINTS ======== /** * Get trades for a token * @param tokenAddress Token address * @param cursor Pagination cursor * @param showMeta Include token metadata * @param parseJupiter Parse Jupiter swaps * @param hideArb Hide arbitrage transactions * @returns Token trades data */ async getTokenTrades(tokenAddress, cursor, showMeta, parseJupiter, hideArb) { this.validatePublicKey(tokenAddress, "tokenAddress"); const params = new URLSearchParams(); if (cursor) params.append("cursor", cursor.toString()); if (showMeta) params.append("showMeta", "true"); if (parseJupiter) params.append("parseJupiter", "true"); if (hideArb) params.append("hideArb", "true"); const query = params.toString() ? `?${params.toString()}` : ""; return this.request(`/trades/${tokenAddress}${query}`); } /** * Get trades for a specific token and pool * @param tokenAddress Token address * @param poolAddress Pool address * @param cursor Pagination cursor * @param showMeta Include token metadata * @param parseJupiter Parse Jupiter swaps * @param hideArb Hide arbitrage transactions * @returns Pool-specific token trades data */ async getPoolTrades(tokenAddress, poolAddress, cursor, showMeta, parseJupiter, hideArb) { this.validatePublicKey(tokenAddress, "tokenAddress"); this.validatePublicKey(poolAddress, "poolAddress"); const params = new URLSearchParams(); if (cursor) params.append("cursor", cursor.toString()); if (showMeta) params.append("showMeta", "true"); if (parseJupiter) params.append("parseJupiter", "true"); if (hideArb) params.append("hideArb", "true"); const query = params.toString() ? `?${params.toString()}` : ""; return this.request(`/trades/${tokenAddress}/${poolAddress}${query}`); } /** * Get trades for a specific token, pool, and wallet * @param tokenAddress Token address * @param poolAddress Pool address * @param owner Wallet address * @param cursor Pagination cursor * @param showMeta Include token metadata * @param parseJupiter Parse Jupiter swaps * @param hideArb Hide arbitrage transactions * @returns User-specific pool trades data */ async getUserPoolTrades(tokenAddress, poolAddress, owner, cursor, showMeta, parseJupiter, hideArb) { this.validatePublicKey(tokenAddress, "tokenAddress"); this.validatePublicKey(poolAddress, "poolAddress"); this.validatePublicKey(owner, "owner"); const params = new URLSearchParams(); if (cursor) params.append("cursor", cursor.toString()); if (showMeta) params.append("showMeta", "true"); if (parseJupiter) params.append("parseJupiter", "true"); if (hideArb) params.append("hideArb", "true"); const query = params.toString() ? `?${params.toString()}` : ""; return this.request(`/trades/${tokenAddress}/${poolAddress}/${owner}${query}`); } /** * Get trades for a specific token and wallet * @param tokenAddress Token address * @param owner Wallet address * @param cursor Pagination cursor * @param showMeta Include token metadata * @param parseJupiter Parse Jupiter swaps * @param hideArb Hide arbitrage transactions * @returns User-specific token trades data */ async getUserTokenTrades(tokenAddress, owner, cursor, showMeta, parseJupiter, hideArb) { this.validatePublicKey(tokenAddress, "tokenAddress"); this.validatePublicKey(owner, "owner"); const params = new URLSearchParams(); if (cursor) params.append("cursor", cursor.toString()); if (showMeta) params.append("showMeta", "true"); if (parseJupiter) params.append("parseJupiter", "true"); if (hideArb) params.append("hideArb", "true"); const query = params.toString() ? `?${params.toString()}` : ""; return this.request(`/trades/${tokenAddress}/by-wallet/${owner}${query}`); } async getChartData(tokenAddressOrParams, type, timeFrom, timeTo, marketCap, removeOutliers, dynamicPools, timezone, fastCache) { var _a, _b, _c, _d, _e, _f, _g, _h; let tokenAddress; if (typeof tokenAddressOrParams === "object") { const params2 = tokenAddressOrParams; tokenAddress = params2.tokenAddress; type = (_a = params2.type) != null ? _a : type; timeFrom = (_b = params2.timeFrom) != null ? _b : timeFrom; timeTo = (_c = params2.timeTo) != null ? _c : timeTo; marketCap = (_d = params2.marketCap) != null ? _d : marketCap; removeOutliers = (_e = params2.removeOutliers) != null ? _e : removeOutliers; dynamicPools = (_f = params2.dynamicPools) != null ? _f : dynamicPools; timezone = (_g = params2.timezone) != null ? _g : timezone; fastCache = (_h = params2.fastCache) != null ? _h : fastCache; } else { tokenAddress = tokenAddressOrParams; } this.validatePublicKey(tokenAddress, "tokenAddress"); const params = new URLSearchParams(); if (type) params.append("type", type); if (timeFrom) params.append("time_from", timeFrom.toString()); if (timeTo) params.append("time_to", timeTo.toString()); if (marketCap) params.append("marketCap", "true"); if (removeOutliers === false) params.append("removeOutliers", "false"); if (dynamicPools === false) params.append("dynamicPools", "false"); if (timezone) { if (timezone === "current") { const currentTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; params.append("timezone", currentTimezone); } else { params.append("timezone", timezone); } } if (fastCache) params.append("fastCache", "true"); const query = params.toString() ? `?${params.toString()}` : ""; return this.request(`/chart/${tokenAddress}${query}`); } async getPoolChartData(tokenAddressOrParams, poolAddress, type, timeFrom, timeTo, marketCap, removeOutliers, timezone, fastCache) { var _a, _b, _c, _d, _e, _f, _g; let tokenAddress; let actualPoolAddress; if (typeof tokenAddressOrParams === "object") { const params2 = tokenAddressOrParams; tokenAddress = params2.tokenAddress; actualPoolAddress = params2.poolAddress; type = (_a = params2.type) != null ? _a : type; timeFrom = (_b = params2.timeFrom) != null ? _b : timeFrom; timeTo = (_c = params2.timeTo) != null ? _c : timeTo; marketCap = (_d = params2.marketCap) != null ? _d : marketCap; removeOutliers = (_e = params2.removeOutliers) != null ? _e : removeOutliers; timezone = (_f = params2.timezone) != null ? _f : timezone; fastCache = (_g = params2.fastCache) != null ? _g : fastCache; } else { tokenAddress = tokenAddressOrParams; actualPoolAddress = poolAddress; } this.validatePublicKey(tokenAddress, "tokenAddress"); this.validatePublicKey(actualPoolAddress, "poolAddress"); const params = new URLSearchParams(); if (type) params.append("type", type); if (timeFrom) params.append("time_from", timeFrom.toString()); if (timeTo) params.append("time_to", timeTo.toString()); if (marketCap) params.append("marketCap", "true"); if (removeOutliers === false) params.append("removeOutliers", "false"); if (timezone) { if (timezone === "current") { const currentTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone; params.append("timezone", currentTimezone); } else { params.append("timezone", timezone); } } if (fastCache) params.append("fastCache", "true"); const query = params.toString() ? `?${params.toString()}` : ""; return this.request(`/chart/${tokenAddress}/${actualPoolAddress}${query}`); } /** * Get holder count chart data * @param tokenAddress Token address * @param type Time interval (e.g., "1s", "1m", "1h", "1d") * @param timeFrom Start time (Unix timestamp in seconds) * @param timeTo End time (Unix timestamp in seconds) * @returns Holder count chart data */ async getHoldersChart(tokenAddress, type, timeFrom, timeTo) { this.validatePublicKey(tokenAddress, "tokenAddress"); const params = new URLSearchParams(); if (type) params.append("type", type); if (timeFrom) params.append("time_from", timeFrom.toString()); if (timeTo) params.append("time_to", timeTo.toString()); const query = params.toString() ? `?${params.toString()}` : ""; return this.request(`/holders/chart/${tokenAddress}${query}`); } // ======== PNL DATA ENDPOINTS ======== /** * Get PnL data for all positions of a wallet * @param wallet Wallet address * @param showHistoricPnL Add PnL data for 1d, 7d and 30d intervals (BETA) * @param holdingCheck Additional check for current holding value * @param hideDetails Return only summary without data for each token * @returns Wallet PnL data */ async getWalletPnL(wallet, showHistoricPnL, holdingCheck, hideDetails) { this.validatePublicKey(wallet, "wallet"); const params = new URLSearchParams(); if (showHistoricPnL) params.append("showHistoricPnL", "true"); if (holdingCheck) params.append("holdingCheck", "true"); if (hideDetails) params.append("hideDetails", "true"); const query = params.toString() ? `?${params.toString()}` : ""; return this.request(`/pnl/${wallet}${query}`); } /** * Get the first 100 buyers of a token with PnL data * @param tokenAddress Token address * @returns First buyers data with PnL */ async getFirstBuyers(tokenAddress) { this.validatePublicKey(tokenAddress, "tokenAddress"); return this.request(`/first-buyers/${tokenAddress}`); } /** * Get PnL data for a specific token in a wallet * @param wallet Wallet address * @param tokenAddress Token address * @param holdingCheck Additional check for current holding value in wallet * @returns Token-specific PnL data */ async getTokenPnL(wallet, tokenAddress, holdingCheck) { this.validatePublicKey(wallet, "wallet"); this.validatePublicKey(tokenAddress, "tokenAddress"); const params = new URLSearchParams(); if (holdingCheck) params.append("holdingCheck", "true"); const query = params.toString() ? `?${params.toString()}` : ""; return this.request(`/pnl/${wallet}/${tokenAddress}${query}`); } // ======== TOP TRADERS ENDPOINTS ======== /** * Get the most profitable traders across all tokens * @param page Page number (optional) * @param expandPnL Include detailed PnL data for each token * @param sortBy Sort results by metric ("total" or "winPercentage") * @returns Top traders data */ async getTopTraders(page, expandPnL, sortBy) { const params = new URLSearchParams(); if (expandPnL) params.append("expandPnL", "true"); if (sortBy) params.append("sortBy", sortBy); const query = params.toString() ? `?${params.toString()}` : ""; const endpoint = page ? `/top-traders/all/${page}${query}` : `/top-traders/all${query}`; return this.request(endpoint); } /** * Get top 100 traders by PnL for a token * @param tokenAddress Token address * @returns Top traders for a specific token */ async getTokenTopTraders(tokenAddress) { this.validatePublicKey(tokenAddress, "tokenAddress"); return this.request(`/top-traders/${tokenAddress}`); } // ======== ADDITIONAL ENDPOINTS ======== /** * Get detailed stats for a token over various time intervals * @param tokenAddress Token address * @returns Detailed token stats */ async getTokenStats(tokenAddress) { this.validatePublicKey(tokenAddress, "tokenAddress"); return this.request(`/stats/${tokenAddress}`); } /** * Get detailed stats for a specific token and pool * @param tokenAddress Token address * @param poolAddress Pool address * @returns Detailed token-pool stats */ async getPoolStats(tokenAddress, poolAddress) { this.validatePublicKey(tokenAddress, "tokenAddress"); this.validatePublicKey(poolAddress, "poolAddress"); return this.request(`/stats/${tokenAddress}/${poolAddress}`); } /** * Get current subscription information including credits, plan, and billing details * @returns Subscription information */ async getSubscription() { return this.request("/subscription"); } /** * Get remaining API credits for the current API key * @returns Credits information */ async getCredits() { return this.request("/credits"); } /** * Get events data for a token (all pools) * NOTE: For non-live statistics, use getTokenStats() instead which is more efficient * @param tokenAddress The token's mint address * @returns Decoded events array */ async getEvents(tokenAddress) { this.validatePublicKey(tokenAddress, "tokenAddress"); const response = await fetch(`${this.baseUrl}/events/${tokenAddress}`, { headers: { "x-api-key": this.apiKey, "Accept": "application/octet-stream" } }); if (!response.ok) { if (response.status === 429) { const retryAfter = response.headers.get("Retry-After"); throw new RateLimitError( "Rate limit exceeded", retryAfter ? parseInt(retryAfter) : void 0 ); } throw new DataApiError( `API request failed: ${response.status} ${response.statusText}`, response.status ); } const binaryData = await response.arrayBuffer(); const events = decodeBinaryEvents(binaryData); return events; } /** * Get events data for a specific token and pool * NOTE: For non-live statistics, use getPoolStats() instead which is more efficient * @param tokenAddress The token's mint address * @param poolAddress The pool's address * @returns Decoded events array */ async getPoolEvents(tokenAddress, poolAddress) { this.validatePublicKey(tokenAddress, "tokenAddress"); this.validatePublicKey(poolAddress, "poolAddress"); const response = await fetch(`${this.baseUrl}/events/${tokenAddress}/${poolAddress}`, { headers: { "x-api-key": this.apiKey, "Accept": "application/octet-stream" } }); if (!response.ok) { if (response.status === 429) { const retryAfter = response.headers.get("Retry-After"); throw new RateLimitError( "Rate limit exceeded", retryAfter ? parseInt(retryAfter) : void 0 ); } throw new DataApiError( `API request failed: ${response.status} ${response.statusText}`, response.status ); } const binaryData = await response.arrayBuffer(); const events = decodeBinaryEvents(binaryData); return events; } }; // src/datastream.ts var import_events = require("events"); // src/websocket-polyfill.ts var isBrowser = typeof window !== "undefined" && typeof window.WebSocket !== "undefined"; if (!isBrowser) { try { const WebSocketImpl = require("ws"); if (typeof global !== "undefined" && !global.WebSocket) { global.WebSocket = WebSocketImpl; } } catch (e) { console.warn( 'WebSocket implementation not found. If you are using Node.js, please install the "ws" package: npm install ws' ); } } // src/datastream.ts var DatastreamRoom = /* @__PURE__ */ ((DatastreamRoom2) => { DatastreamRoom2["LATEST"] = "latest"; DatastreamRoom2["PRICE_BY_TOKEN"] = "price-by-token"; DatastreamRoom2["PRICE_BY_POOL"] = "price"; DatastreamRoom2["TOKEN_TRANSACTIONS"] = "transaction"; DatastreamRoom2["WALLET_TRANSACTIONS"] = "wallet"; DatastreamRoom2["GRADUATING"] = "graduating"; DatastreamRoom2["GRADUATED"] = "graduated"; DatastreamRoom2["CURVE_PERCENTAGE"] = "curve"; DatastreamRoom2["METADATA"] = "metadata"; DatastreamRoom2["HOLDERS"] = "holders"; DatastreamRoom2["TOKEN_CHANGES"] = "token"; DatastreamRoom2["POOL_CHANGES"] = "pool"; DatastreamRoom2["SNIPERS"] = "sniper"; DatastreamRoom2["INSIDERS"] = "insider"; return DatastreamRoom2; })(DatastreamRoom || {}); var SubscriptionMethods = class { constructor(datastream) { this.ds = datastream; this.price = new PriceSubscriptions(datastream); this.tx = new TransactionSubscriptions(datastream); this.stats = new StatsSubscriptions(datastream); } /** * Subscribe to latest tokens and pools */ latest() { return this.ds._subscribe("latest"); } /** * Subscribe to graduating tokens * @param marketCapThresholdSOL Optional market cap threshold in SOL */ graduating(marketCapThresholdSOL) { const room = marketCapThresholdSOL ? `graduating:sol:${marketCapThresholdSOL}` : "graduating"; return this.ds._subscribe(room); } /** * Subscribe to tokens reaching a specific curve percentage for a market * @param market The market type: 'launchpad', 'pumpfun', 'boop', or 'meteora-curve' * @param percentage The curve percentage threshold (e.g., 30, 50, 75) * @returns Subscription response with curve percentage updates */ curvePercentage(market, percentage) { if (percentage < 0 || percentage > 100) { throw new Error("Percentage must be between 0 and 100"); } const room = `${market}:curve:${percentage}`; return this.ds._subscribe(room); } /** * Subscribe to graduated tokens */ graduated() { return this.ds._subscribe("graduated"); } /** * Subscribe to token metadata updates * @param tokenAddress The token address */ metadata(tokenAddress) { return this.ds._subscribe(`metadata:${tokenAddress}`); } /** * Subscribe to holder count updates for a token * @param tokenAddress The token address */ holders(tokenAddress) { return this.ds._subscribe(`holders:${tokenAddress}`); } /** * Subscribe to token-related events (all pools, primary pool, dev events, or top holders) * * @example * // For all pool updates: * datastream.subscribe.token('address').all().on(callback) * // Or using shorthand: * datastream.subscribe.token('address').on(callback) * * // For primary pool updates only: * datastream.subscribe.token('address').primary().on(callback) * * // For dev holding updates: * datastream.subscribe.token('address').dev.holding().on(callback) * * // For top 10 holders updates: * datastream.subscribe.token('address').top10().on(callback) * * @param tokenAddress The token address */ token(tokenAddress) { const ds = this.ds; const devSubscriptions = { holding: () => { return ds._subscribe(`dev_holding:${tokenAddress}`); } }; const baseSubscription = ds._subscribe(`token:${tokenAddress}`); const enhancedSubscription = { ...baseSubscription, // Keep the original on method for convenience on: (callback) => { return baseSubscription.on(callback); }, // New methods all: () => { return ds._subscribe(`token:${tokenAddress}`); }, primary: () => { return ds._subscribe(`token:${tokenAddress}:primary`); }, dev: devSubscriptions, top10: () => { return ds._subscribe(`top10:${tokenAddress}`); }, fees: () => { return ds._subscribe(`fees:${tokenAddress}`); } }; return enhancedSubscription; } /** * Subscribe to pool changes * @param poolId The pool address */ pool(poolId) { return this.ds._subscribe(`pool:${poolId}`); } /** * Subscribe to sniper updates for a token * @param tokenAddress The token address */ snipers(tokenAddress) { return this.ds._subscribe(`sniper:${tokenAddress}`); } /** * Subscribe to insider updates for a token * @param tokenAddress The token address */ insiders(tokenAddress) { return this.ds._subscribe(`insider:${tokenAddress}`); } /** * Subscribe to wallet balance updates * * @example * // For all balance updates: * datastream.subscribe.wallet('address').balance().on(callback) * * // For specific token balance: * datastream.subscribe.wallet('address').tokenBalance('token').on(callback) * * @param walletAddress The wallet address */ wallet(walletAddress) { const ds = this.ds; return { balance: () => { return ds._subscribe( `wallet:${walletAddress}:balance` ); }, tokenBalance: (tokenAddress) => { return ds._subscribe( `wallet:${walletAddress}:${tokenAddress}:balance` ); } }; } }; var StatsSubscriptions = class { constructor(datastream) { this.ds = datastream; } /** * Subscribe to live stats updates for a token across all timeframes * @param tokenAddress The token address * @returns Subscription response with stats updates */ token(tokenAddress) { return this.ds._subscribe(`stats:token:${tokenAddress}`); } /** * Subscribe to live stats updates for a specific pool across all timeframes * @param poolId The pool address * @returns Subscription response with stats updates */ pool(poolId) { return this.ds._subscribe(`stats:pool:${poolId}`); } }; var PriceSubscriptions = class { constructor(datastream) { this.ds = datastream; } /** * Subscribe to price updates for a token's primary/largest pool * @param tokenAddress The token address */ token(tokenAddress) { return this.ds._subscribe(`price-by-token:${tokenAddress}`); } /** * Subscribe to all price updates for a token across all pools * @param tokenAddress The token address */ allPoolsForToken(tokenAddress) { return this.ds._subscribe(`price:${tokenAddress}`); } /** * Subscribe to price updates for a specific pool * @param poolId The pool address */ pool(poolId) { return this.ds._subscribe(`price:${poolId}`); } }; var TransactionSubscriptions = class { constructor(datastream) { this.ds = datastream; } /** * Subscribe to transactions for a token across all pools * @param tokenAddress The token address */ token(tokenAddress) { return this.ds._subscribe(`transaction:${tokenAddress}`); } /** * Subscribe to transactions for a specific token and pool * @param tokenAddress The token address * @param poolId The pool address */ pool(tokenAddress, poolId) { return this.ds._subscribe( `transaction:${tokenAddress}:${poolId}` ); } /** * Subscribe to wallet transactions * * @example * // Subscribe to wallet transactions (default): * datastream.subscribe.tx.wallet('address').on(callback) * * // Subscribe to wallet transactions (explicit): * datastream.subscribe.tx.wallet('address').transactions().on(callback) * * @param walletAddress The wallet address */ wallet(walletAddress) { const ds = this.ds; const baseSubscription = ds._subscribe(`wallet:${walletAddress}`); return { ...baseSubscription, // Default on method for transactions on: (callback) => { return baseSubscription.on(callback); }, // Explicit transactions method (not deprecated) transactions: () => { return ds._subscribe(`wallet:${walletAddress}`); }, // Deprecated balance methods balance: () => { console.warn( "datastream.subscribe.tx.wallet().balance() is deprecated. Please use datastream.subscribe.wallet().balance() instead. This method will be removed in a future version." ); return ds._subscribe( `wallet:${walletAddress}:balance` ); }, tokenBalance: (tokenAddress) => { console.warn( "datastream.subscribe.tx.wallet().tokenBalance() is deprecated. Please use datastream.subscribe.wallet().tokenBalance() instead. This method will be removed in a future version." ); return ds._subscribe( `wallet:${walletAddress}:${tokenAddress}:balance` ); } }; } }; var Datastream = class extends import_events.EventEmitter { /** * Creates a new Datastream client for real-time Solana Tracker data * @param config Configuration options */ constructor(config) { super(); this.socket = null; this.transactionSocket = null; this.reconnectAttempts = 0; this.subscribedRooms = /* @__PURE__ */ new Set(); this.transactions = /* @__PURE__ */ new Set(); this.isConnecting = false; this.worker = null; this.wsUrl = config.wsUrl || ""; this.autoReconnect = config.autoReconnect !== false; this.reconnectDelay = config.reconnectDelay || 2500; this.reconnectDelayMax = config.reconnectDelayMax || 4500; this.randomizationFactor = config.randomizationFactor || 0.5; this.useWorker = config.useWorker || false; this.workerUrl = config.workerUrl; this.subscribe = new SubscriptionMethods(this); if (typeof window !== "undefined") { window.addEventListener("beforeunload", this.disconnect.bind(this)); } } /** * Connects to the WebSocket server * @returns Promise that resolves when connected */ async connect() { if (this.useWorker) { return this.connectWithWorker(); } if (this.socket && this.transactionSocket) { return; } if (this.isConnecting) { return; } this.isConnecting = true; try { await Promise.all([ this.createSocket("main"), this.createSocket("transaction") ]); this.isConnecting = false; this.emit("connected"); } catch (e) { this.isConnecting = false; t