@solana-tracker/data-api
Version:
Official Solana Tracker Data API client for accessing Solana Data
633 lines (632 loc) • 25.1 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.Client = exports.ValidationError = exports.RateLimitError = exports.DataApiError = void 0;
class DataApiError extends Error {
constructor(message, status, code) {
super(message);
this.status = status;
this.code = code;
this.name = 'DataApiError';
}
}
exports.DataApiError = DataApiError;
class RateLimitError extends DataApiError {
constructor(message, retryAfter) {
super(message, 429, 'RATE_LIMIT_EXCEEDED');
this.retryAfter = retryAfter;
this.name = 'RateLimitError';
}
}
exports.RateLimitError = RateLimitError;
class ValidationError extends DataApiError {
constructor(message) {
super(message, 400, 'VALIDATION_ERROR');
this.name = 'ValidationError';
}
}
exports.ValidationError = ValidationError;
/**
* Solana Tracker Data API client
*/
class Client {
/**
* 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 || options === void 0 ? void 0 : options.headers,
};
try {
const response = await fetch(`${this.baseUrl}${endpoint}`, {
...options,
headers,
});
if (!response.ok) {
if (response.status === 429) {
const retryAfter = response.headers.get('Retry-After');
if (options === null || options === void 0 ? void 0 : options.disableLogs) {
console.warn(`Rate limit exceeded for ${endpoint}. Retry after: ${retryAfter || '1'} seconds`);
}
throw new RateLimitError('Rate limit exceeded', retryAfter ? parseInt(retryAfter) : undefined);
}
throw new DataApiError(`API request failed: ${response.status} ${response.statusText}`, response.status);
}
return response.json();
}
catch (error) {
if (error instanceof DataApiError) {
throw error;
}
throw new DataApiError('An unexpected error occurred');
}
}
/**
* 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) {
// Basic validation - a more robust implementation would use the PublicKey class from @solana/web3.js
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 for a token
* @param tokenAddress The token's mint address
* @returns All-time high price data
*/
async getAthPrice(tokenAddress) {
this.validatePublicKey(tokenAddress, 'tokenAddress');
return this.request(`/tokens/${tokenAddress}/ath`);
}
/**
* Get tokens created by a specific wallet
* @param wallet The deployer wallet address
* @returns List of tokens created by the deployer
*/
async getTokensByDeployer(wallet) {
this.validatePublicKey(wallet, 'wallet');
return this.request(`/deployer/${wallet}`);
}
/**
* 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 !== undefined) {
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
* @returns Token overview
*/
async getTokenOverview() {
return this.request('/tokens/multi/all');
}
/**
* Get graduated tokens
* @returns List of graduated tokens
*/
async getGraduatedTokens() {
return this.request('/tokens/multi/graduated');
}
// ======== 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}×tamp=${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 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}`);
}
// ======== CHART DATA ENDPOINTS ========
/**
* Get OHLCV data for a token
* @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)
* @param marketCap Return chart for market cap instead of pricing
* @param removeOutliers Disable outlier removal if set to false (default: true)
* @returns OHLCV chart data
*/
async getChartData(tokenAddress, type, timeFrom, timeTo, marketCap, removeOutliers) {
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');
const query = params.toString() ? `?${params.toString()}` : '';
return this.request(`/chart/${tokenAddress}${query}`);
}
/**
* Get OHLCV data for a specific token and pool
* @param tokenAddress Token address
* @param poolAddress Pool 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)
* @param marketCap Return chart for market cap instead of pricing
* @param removeOutliers Disable outlier removal if set to false (default: true)
* @returns OHLCV chart data for a specific pool
*/
async getPoolChartData(tokenAddress, poolAddress, type, timeFrom, timeTo, marketCap, removeOutliers) {
this.validatePublicKey(tokenAddress, 'tokenAddress');
this.validatePublicKey(poolAddress, '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');
const query = params.toString() ? `?${params.toString()}` : '';
return this.request(`/chart/${tokenAddress}/${poolAddress}${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
* @returns Token-specific PnL data
*/
async getTokenPnL(wallet, tokenAddress) {
this.validatePublicKey(wallet, 'wallet');
this.validatePublicKey(tokenAddress, 'tokenAddress');
return this.request(`/pnl/${wallet}/${tokenAddress}`);
}
// ======== 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}`);
}
}
exports.Client = Client;