UNPKG

@gorbchain-xyz/chaindecode

Version:

GorbchainSDK V1.3+ - Complete Solana development toolkit with advanced cryptography, messaging, and collaboration features. Build secure applications with blockchain, DeFi, and end-to-end encryption.

318 lines (317 loc) 14.5 kB
/** * Advanced Token Holdings Analyzer for Gorbchain SDK v2 * Provides comprehensive token portfolio analysis with multi-program support */ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; /** * Advanced Token Holdings Analyzer */ export class AdvancedTokenHoldings { constructor(rpcClient) { this.rpcClient = rpcClient; this.networkConfig = rpcClient.getNetworkConfig(); } /** * Get all tokens for a wallet across all supported programs */ getAllTokens(walletAddress, config) { return __awaiter(this, void 0, void 0, function* () { const startTime = Date.now(); // Get SOL balance const solBalance = yield this.rpcClient.getBalance(walletAddress); // Get token holdings from all programs const tokenHoldings = yield this.rpcClient.getCustomTokenHoldings(walletAddress, config); // Enhance holdings with additional metadata and analysis const enhancedHoldings = yield this.enhanceHoldings(tokenHoldings); // Generate portfolio summary const summary = this.generatePortfolioSummary(enhancedHoldings); const endTime = Date.now(); console.log(`Portfolio analysis completed in ${endTime - startTime}ms`); return { walletAddress, holdings: enhancedHoldings, summary, timestamp: new Date().toISOString() }; }); } /** * Get tokens from a specific custom program */ getCustomProgramTokens(walletAddress, programId) { return __awaiter(this, void 0, void 0, function* () { const config = { customPrograms: [programId], includeStandardTokens: false, includeToken2022: false }; return this.rpcClient.getCustomTokenHoldings(walletAddress, config); }); } /** * Detect token decimals from mint account */ detectTokenDecimals(mintAddress) { return __awaiter(this, void 0, void 0, function* () { var _a; try { const mintInfo = yield this.rpcClient.getMintAccountInfo(mintAddress); return (_a = mintInfo === null || mintInfo === void 0 ? void 0 : mintInfo.decimals) !== null && _a !== void 0 ? _a : 0; } catch (error) { console.warn(`Failed to detect decimals for ${mintAddress}:`, error); return 0; } }); } /** * Resolve token metadata (when available) */ resolveTokenMetadata(mintAddress) { return __awaiter(this, void 0, void 0, function* () { // For now, return null as custom networks may not have standard metadata // In a full implementation, this would check various metadata sources return null; }); } /** * Analyze portfolio for insights */ analyzePortfolio(holdings) { return __awaiter(this, void 0, void 0, function* () { const totalHoldings = holdings.length; // Calculate diversification metrics const balances = holdings.map(h => h.balance.decimal); const totalValue = balances.reduce((sum, balance) => sum + balance, 0); const largestBalance = Math.max(...balances); const largestHoldingPercentage = totalValue > 0 ? (largestBalance / totalValue) * 100 : 0; let concentrationRisk = 'low'; if (largestHoldingPercentage > 50) { concentrationRisk = 'high'; } else if (largestHoldingPercentage > 25) { concentrationRisk = 'medium'; } // Analyze token types const fungibleTokens = holdings.filter(h => !h.isNFT).length; const nfts = holdings.filter(h => h.isNFT).length; const unknownTokens = holdings.filter(h => !h.metadata && !h.isNFT).length; // Analyze balance distribution const zeroBalance = holdings.filter(h => h.balance.decimal === 0).length; const smallBalance = holdings.filter(h => h.balance.decimal > 0 && h.balance.decimal < 1).length; const mediumBalance = holdings.filter(h => h.balance.decimal >= 1 && h.balance.decimal < 1000).length; const largeBalance = holdings.filter(h => h.balance.decimal >= 1000).length; return { diversification: { mintCount: totalHoldings, largestHoldingPercentage, concentrationRisk }, tokenTypes: { fungibleTokens, nfts, unknownTokens }, balanceDistribution: { zeroBalance, smallBalance, mediumBalance, largeBalance } }; }); } /** * Get tokens by category (NFTs vs Fungible) */ getTokensByCategory(walletAddress) { return __awaiter(this, void 0, void 0, function* () { const portfolio = yield this.getAllTokens(walletAddress); const nfts = portfolio.holdings.filter(h => h.isNFT); const fungibleTokens = portfolio.holdings.filter(h => !h.isNFT); return { nfts, fungibleTokens }; }); } /** * Get top holdings by balance */ getTopHoldings(walletAddress_1) { return __awaiter(this, arguments, void 0, function* (walletAddress, limit = 10) { const portfolio = yield this.getAllTokens(walletAddress); return portfolio.holdings .sort((a, b) => b.balance.decimal - a.balance.decimal) .slice(0, limit); }); } /** * Enhance holdings with additional metadata and analysis */ enhanceHoldings(holdings) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; const enhanced = []; for (const holding of holdings) { const enhancedHolding = Object.assign({}, holding); // Try to resolve metadata if not already present if (!enhancedHolding.metadata) { try { const metadata = yield this.resolveTokenMetadata(holding.mint); if (metadata) { enhancedHolding.metadata = metadata; } } catch (error) { // Metadata resolution failed, continue without it } } // Get mint information for accurate NFT detection and decimals try { const mintInfo = yield this.rpcClient.getMintAccountInfo(holding.mint); if (mintInfo) { // Update mint info enhancedHolding.mintInfo = { supply: mintInfo.supply, mintAuthority: (_a = mintInfo.mintAuthority) !== null && _a !== void 0 ? _a : undefined, freezeAuthority: (_b = mintInfo.freezeAuthority) !== null && _b !== void 0 ? _b : undefined, isInitialized: mintInfo.isInitialized }; // Check if this is an NFT based on supply=1 and decimals=0 const isNFT = mintInfo.supply === '1' && mintInfo.decimals === 0; enhancedHolding.isNFT = isNFT; // Update decimals from mint info if more accurate if (mintInfo.decimals !== enhancedHolding.decimals) { enhancedHolding.decimals = mintInfo.decimals; // Recalculate balance with correct decimals const rawAmount = parseFloat(enhancedHolding.balance.raw); const correctDecimalBalance = rawAmount / Math.pow(10, mintInfo.decimals); enhancedHolding.balance.decimal = correctDecimalBalance; enhancedHolding.balance.formatted = correctDecimalBalance.toString(); } } } catch (error) { // Mint info not available, fall back to token account analysis // For tokens with decimals=0 but not marked as NFT, try to detect decimals if (enhancedHolding.decimals === 0 && !enhancedHolding.isNFT) { try { const detectedDecimals = yield this.detectTokenDecimals(holding.mint); if (detectedDecimals > 0) { enhancedHolding.decimals = detectedDecimals; // Recalculate balance with correct decimals const rawAmount = parseFloat(enhancedHolding.balance.raw); const correctDecimalBalance = rawAmount / Math.pow(10, detectedDecimals); enhancedHolding.balance.decimal = correctDecimalBalance; enhancedHolding.balance.formatted = correctDecimalBalance.toString(); } else { // If decimals are still 0 and balance is 1, likely an NFT if (enhancedHolding.balance.decimal === 1) { enhancedHolding.isNFT = true; } } } catch (error) { // Decimals detection failed, use heuristic // If decimals are 0 and balance is exactly 1, likely an NFT if (enhancedHolding.decimals === 0 && enhancedHolding.balance.decimal === 1) { enhancedHolding.isNFT = true; } } } } enhanced.push(enhancedHolding); } return enhanced; }); } /** * Generate portfolio summary */ generatePortfolioSummary(holdings) { const totalTokens = holdings.length; const totalNFTs = holdings.filter(h => h.isNFT).length; const totalFungibleTokens = holdings.filter(h => !h.isNFT).length; const uniqueMints = new Set(holdings.map(h => h.mint)).size; const hasMetadata = holdings.filter(h => h.metadata).length; // Get top 5 holdings by balance const topHoldings = holdings .sort((a, b) => b.balance.decimal - a.balance.decimal) .slice(0, 5); return { totalTokens, totalNFTs, totalFungibleTokens, uniqueMints, hasMetadata, topHoldings }; } /** * Batch process multiple wallets */ batchAnalyzeWallets(walletAddresses, config) { return __awaiter(this, void 0, void 0, function* () { var _a; const results = []; // Process wallets in parallel with rate limiting const maxConcurrent = (_a = config === null || config === void 0 ? void 0 : config.maxConcurrentRequests) !== null && _a !== void 0 ? _a : 5; const batches = this.chunkArray(walletAddresses, maxConcurrent); for (const batch of batches) { const batchPromises = batch.map(address => this.getAllTokens(address, config)); const batchResults = yield Promise.allSettled(batchPromises); for (const result of batchResults) { if (result.status === 'fulfilled') { results.push(result.value); } else { console.warn('Failed to analyze wallet:', result.reason); } } } return results; }); } /** * Utility function to chunk array into smaller arrays */ chunkArray(array, size) { const chunks = []; for (let i = 0; i < array.length; i += size) { chunks.push(array.slice(i, i + size)); } return chunks; } /** * Compare portfolios to find similarities */ comparePortfolios(walletAddress1, walletAddress2) { return __awaiter(this, void 0, void 0, function* () { const [portfolio1, portfolio2] = yield Promise.all([ this.getAllTokens(walletAddress1), this.getAllTokens(walletAddress2) ]); const mints1 = new Set(portfolio1.holdings.map(h => h.mint)); const mints2 = new Set(portfolio2.holdings.map(h => h.mint)); const commonMints = new Set([...mints1].filter(mint => mints2.has(mint))); const commonTokens = portfolio1.holdings.filter(h => commonMints.has(h.mint)); const uniqueToWallet1 = portfolio1.holdings.filter(h => !mints2.has(h.mint)); const uniqueToWallet2 = portfolio2.holdings.filter(h => !mints1.has(h.mint)); const totalUniqueMints = new Set([...mints1, ...mints2]).size; const similarity = totalUniqueMints > 0 ? (commonMints.size / totalUniqueMints) * 100 : 0; return { commonTokens, uniqueToWallet1, uniqueToWallet2, similarity }; }); } }