@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
JavaScript
/**
* 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
};
});
}
}