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.

498 lines (497 loc) 21.5 kB
/** * Rich Transaction Operations - Enhanced transaction functions with decoded context * * These functions provide comprehensive transaction analysis including: * - Decoded instruction details * - Token metadata for token-related transactions * - Human-readable summaries * - Balance changes and effects */ 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()); }); }; /** * Get rich transaction with complete decoded context and metadata * * This function enhances the basic getTransaction with: * - Complete instruction decoding with human-readable descriptions * - Token metadata for all token-related operations * - Balance change analysis * - Transaction categorization and summary * - Human-readable transaction description * * @param sdk - GorbchainSDK instance * @param signature - Transaction signature to analyze * @param options - Configuration options * @returns Promise resolving to rich transaction with full context * * @example * ```typescript * const sdk = new GorbchainSDK({ rpcEndpoint: 'https://rpc.gorbchain.xyz' }); * * const richTx = await getRichTransaction(sdk, 'transaction_signature', { * includeTokenMetadata: true, * includeBalanceChanges: true, * resolveAddressLabels: true * }); * * console.log(richTx.summary.description); * // "Alice sent 100 USDC to Bob" * * console.log(`Category: ${richTx.summary.category}`); * console.log(`Total SOL involved: ${richTx.summary.totalSOL}`); * * // Analyze each instruction * richTx.instructions.forEach((instruction, i) => { * console.log(`${i + 1}. ${instruction.description}`); * * if (instruction.tokens?.transfers) { * instruction.tokens.transfers.forEach(transfer => { * console.log(` → ${transfer.amountFormatted} ${transfer.token.symbol} from ${transfer.from} to ${transfer.to}`); * }); * } * }); * ``` */ export function getRichTransaction(sdk_1, signature_1) { return __awaiter(this, arguments, void 0, function* (sdk, signature, options = {}) { var _a, _b, _c; const startTime = Date.now(); const { includeTokenMetadata = true, includeBalanceChanges = true, resolveAddressLabels = false, maxRetries = 3, commitment = 'finalized' } = options; try { // Get the basic transaction with decoding const decodedTx = yield sdk.getAndDecodeTransaction(signature, { richDecoding: true, includeTokenMetadata, maxRetries }); if (!decodedTx || !decodedTx.decoded) { throw new Error('Transaction not found or could not be decoded'); } // Get raw transaction for additional metadata const rawTx = yield sdk.rpc.request('getTransaction', [signature, { commitment, maxSupportedTransactionVersion: 0 }]); if (!rawTx) { throw new Error('Raw transaction data not found'); } // Build rich instructions const richInstructions = []; for (let i = 0; i < decodedTx.decoded.length; i++) { const instruction = decodedTx.decoded[i]; const richInstruction = yield enrichInstruction(sdk, instruction, i, rawTx, { includeTokenMetadata, resolveAddressLabels }); richInstructions.push(richInstruction); } // Analyze balance changes if requested let balanceChanges = { sol: [], tokens: [] }; if (includeBalanceChanges && rawTx && rawTx.meta) { balanceChanges = yield analyzeBalanceChanges(sdk, rawTx, { includeTokenMetadata }); } // Generate transaction summary const summary = generateTransactionSummary(richInstructions, balanceChanges); // Extract metadata safely const txMeta = rawTx.meta; const txData = rawTx.transaction; const meta = { computeUnitsConsumed: txMeta === null || txMeta === void 0 ? void 0 : txMeta.computeUnitsConsumed, innerInstructionsCount: ((_a = txMeta === null || txMeta === void 0 ? void 0 : txMeta.innerInstructions) === null || _a === void 0 ? void 0 : _a.length) || 0, totalAccounts: ((_c = (_b = txData === null || txData === void 0 ? void 0 : txData.message) === null || _b === void 0 ? void 0 : _b.accountKeys) === null || _c === void 0 ? void 0 : _c.length) || 0, programsInvolved: [...new Set(richInstructions.map(ix => ix.programId))], analysisDuration: Date.now() - startTime, metadataResolved: includeTokenMetadata }; return { signature, slot: rawTx.slot, blockTime: rawTx.blockTime ? rawTx.blockTime * 1000 : undefined, confirmationStatus: commitment, fee: (txMeta === null || txMeta === void 0 ? void 0 : txMeta.fee) || 0, success: !(txMeta === null || txMeta === void 0 ? void 0 : txMeta.err), error: (txMeta === null || txMeta === void 0 ? void 0 : txMeta.err) ? JSON.stringify(txMeta.err) : undefined, instructions: richInstructions, summary, balanceChanges, meta }; } catch (error) { throw new Error(`Failed to get rich transaction: ${error instanceof Error ? error.message : 'Unknown error'}`); } }); } /** * Enrich a single instruction with context and metadata */ function enrichInstruction(sdk, instruction, index, rawTx, options) { return __awaiter(this, void 0, void 0, function* () { var _a; const { includeTokenMetadata, resolveAddressLabels } = options; // Basic instruction info const richInstruction = { index, programId: instruction.programId, programName: getProgramName(instruction.programId), type: instruction.type || 'unknown', description: yield generateInstructionDescription(instruction), data: instruction.data || new Uint8Array(), accounts: instruction.accounts || [], decoded: instruction.decoded || instruction.data || {}, result: { success: true, // Will be updated based on transaction success error: undefined, logs: [] } }; // Add program logs if available if ((_a = rawTx.meta) === null || _a === void 0 ? void 0 : _a.logMessages) { richInstruction.result.logs = rawTx.meta.logMessages.filter((log) => log.includes(instruction.programId)); } // Analyze token-related operations if (isTokenRelatedInstruction(instruction)) { richInstruction.tokens = yield analyzeTokenOperations(sdk, instruction, { includeTokenMetadata }); } // Analyze SOL balance changes for this instruction if (isSOLRelatedInstruction(instruction)) { richInstruction.solChanges = yield analyzeSOLChanges(instruction, rawTx); } // Estimate fees for this instruction richInstruction.fees = estimateInstructionFees(instruction, rawTx); return richInstruction; }); } /** * Generate human-readable description for an instruction */ function generateInstructionDescription(instruction) { return __awaiter(this, void 0, void 0, function* () { const programName = getProgramName(instruction.programId); const type = instruction.type || 'unknown'; // Generate descriptions based on program and instruction type switch (instruction.programId) { case '11111111111111111111111111111111': // System Program switch (type) { case 'transfer': return `Transfer SOL between accounts`; case 'createAccount': return `Create new account`; case 'allocate': return `Allocate account space`; default: return `${programName} ${type} operation`; } case 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA': // SPL Token case 'FGyzDo6bhE7gFmSYymmFnJ3SZZu3xWGBA7sNHXR7QQsn': // Token-2022 switch (type) { case 'transfer': return `Transfer tokens between accounts`; case 'mint': return `Mint new tokens`; case 'burn': return `Burn tokens`; case 'createAccount': return `Create token account`; default: return `${programName} ${type} operation`; } default: return `${programName} ${type} operation`; } }); } /** * Get human-readable program name */ function getProgramName(programId) { const programNames = { '11111111111111111111111111111111': 'System Program', 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA': 'SPL Token', 'FGyzDo6bhE7gFmSYymmFnJ3SZZu3xWGBA7sNHXR7QQsn': 'Token-2022', '4YpYoLVTQ8bxcne9GneN85RUXeN7pqGTwgPcY71ZL5gX': 'Associated Token Account', 'BvoSmPBF6mBRxBMY9FPguw1zUoUg3xrc5CaWf7y5ACkc': 'MPL Core NFT', 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s': 'Metaplex NFT' }; return programNames[programId] || 'Unknown Program'; } /** * Check if instruction is token-related */ function isTokenRelatedInstruction(instruction) { const tokenPrograms = [ 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', 'FGyzDo6bhE7gFmSYymmFnJ3SZZu3xWGBA7sNHXR7QQsn', 'BvoSmPBF6mBRxBMY9FPguw1zUoUg3xrc5CaWf7y5ACkc', 'metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s' ]; return tokenPrograms.includes(instruction.programId); } /** * Check if instruction is SOL-related */ function isSOLRelatedInstruction(instruction) { return instruction.programId === '11111111111111111111111111111111'; } /** * Analyze token operations in an instruction with optimized metadata fetching */ function analyzeTokenOperations(sdk, instruction, options) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; const transfers = []; const accountChanges = []; // Extract token transfer information based on instruction type if (instruction.type === 'transfer' && instruction.decoded) { const transfer = { from: instruction.accounts[0] || 'unknown', to: instruction.accounts[1] || 'unknown', mint: instruction.decoded.mint || 'unknown', amount: ((_a = instruction.decoded.amount) === null || _a === void 0 ? void 0 : _a.toString()) || '0', amountFormatted: formatTokenAmount(((_b = instruction.decoded.amount) === null || _b === void 0 ? void 0 : _b.toString()) || '0', instruction.decoded.decimals || 0), token: { name: instruction.decoded.tokenName, symbol: instruction.decoded.tokenSymbol, decimals: instruction.decoded.decimals || 0, isNFT: instruction.decoded.isNFT || false, image: instruction.decoded.tokenImage } }; // Only fetch additional metadata if not already present and requested if (options.includeTokenMetadata && transfer.mint !== 'unknown' && (!transfer.token.name || !transfer.token.symbol)) { try { const metadata = yield fetchTokenMetadataForTransfer(sdk, transfer.mint); if (metadata) { // Only override if current values are empty transfer.token = Object.assign(Object.assign({}, transfer.token), { name: transfer.token.name || metadata.name, symbol: transfer.token.symbol || metadata.symbol, decimals: transfer.token.decimals || metadata.decimals, isNFT: transfer.token.isNFT || metadata.isNFT }); } } catch (error) { // Silently fail for better performance - metadata is not critical } } transfers.push(transfer); } return { transfers, accountChanges }; }); } /** * Analyze SOL balance changes in an instruction */ function analyzeSOLChanges(instruction, rawTx) { return __awaiter(this, void 0, void 0, function* () { var _a; const changes = []; if (instruction.type === 'transfer' && ((_a = instruction.decoded) === null || _a === void 0 ? void 0 : _a.lamports)) { changes.push({ account: instruction.accounts[0] || 'unknown', change: -instruction.decoded.lamports, changeSOL: -instruction.decoded.lamports / 1e9, type: 'debit' }); changes.push({ account: instruction.accounts[1] || 'unknown', change: instruction.decoded.lamports, changeSOL: instruction.decoded.lamports / 1e9, type: 'credit' }); } return changes; }); } /** * Estimate fees consumed by an instruction */ function estimateInstructionFees(instruction, rawTx) { var _a, _b, _c, _d; // Basic fee estimation - in reality this would be more sophisticated const baseFee = Math.floor((((_a = rawTx.meta) === null || _a === void 0 ? void 0 : _a.fee) || 5000) / (((_d = (_c = (_b = rawTx.transaction) === null || _b === void 0 ? void 0 : _b.message) === null || _c === void 0 ? void 0 : _c.instructions) === null || _d === void 0 ? void 0 : _d.length) || 1)); return { base: baseFee, computeUnits: instruction.computeUnits, priority: 0 // Would need to analyze priority fees }; } /** * Analyze balance changes across the entire transaction */ function analyzeBalanceChanges(sdk, rawTx, options) { return __awaiter(this, void 0, void 0, function* () { var _a, _b, _c, _d, _e, _f; const solChanges = []; const tokenChanges = []; // Analyze SOL balance changes if (((_a = rawTx.meta) === null || _a === void 0 ? void 0 : _a.preBalances) && ((_b = rawTx.meta) === null || _b === void 0 ? void 0 : _b.postBalances)) { const accountKeys = ((_d = (_c = rawTx.transaction) === null || _c === void 0 ? void 0 : _c.message) === null || _d === void 0 ? void 0 : _d.accountKeys) || []; for (let i = 0; i < accountKeys.length; i++) { const preBalance = rawTx.meta.preBalances[i] || 0; const postBalance = rawTx.meta.postBalances[i] || 0; const change = postBalance - preBalance; if (change !== 0) { solChanges.push({ account: accountKeys[i], before: preBalance, after: postBalance, change }); } } } // Analyze token balance changes if (((_e = rawTx.meta) === null || _e === void 0 ? void 0 : _e.preTokenBalances) && ((_f = rawTx.meta) === null || _f === void 0 ? void 0 : _f.postTokenBalances)) { // This would require more sophisticated analysis of token balance changes // For now, return empty array } return { sol: solChanges, tokens: tokenChanges }; }); } /** * Generate transaction summary */ function generateTransactionSummary(instructions, balanceChanges) { // Analyze primary action let primaryAction = 'Unknown transaction'; let category = 'unknown'; // Simple heuristics for categorization const hasTokenTransfer = instructions.some(ix => { var _a; return ((_a = ix.tokens) === null || _a === void 0 ? void 0 : _a.transfers) && ix.tokens.transfers.length > 0; }); const hasSOLTransfer = instructions.some(ix => ix.solChanges && ix.solChanges.length > 0); const hasNFTOperation = instructions.some(ix => { var _a, _b; return (_b = (_a = ix.tokens) === null || _a === void 0 ? void 0 : _a.transfers) === null || _b === void 0 ? void 0 : _b.some(transfer => transfer.token.isNFT); }); if (hasNFTOperation) { category = 'nft'; primaryAction = 'NFT transaction'; } else if (hasTokenTransfer) { category = 'transfer'; primaryAction = 'Token transfer'; } else if (hasSOLTransfer) { category = 'transfer'; primaryAction = 'SOL transfer'; } else { category = 'system'; primaryAction = 'System transaction'; } // Extract participants const participants = []; const addressSet = new Set(); instructions.forEach(ix => { ix.accounts.forEach(account => { if (!addressSet.has(account)) { addressSet.add(account); participants.push({ address: account, role: 'participant' // Would need more sophisticated role detection }); } }); }); // Calculate totals const totalSOL = balanceChanges.sol.reduce((sum, change) => sum + Math.abs(change.change), 0) / 1e9; const totalTokens = instructions.reduce((sum, ix) => { var _a, _b; return sum + (((_b = (_a = ix.tokens) === null || _a === void 0 ? void 0 : _a.transfers) === null || _b === void 0 ? void 0 : _b.filter(t => !t.token.isNFT).length) || 0); }, 0); const totalNFTs = instructions.reduce((sum, ix) => { var _a, _b; return sum + (((_b = (_a = ix.tokens) === null || _a === void 0 ? void 0 : _a.transfers) === null || _b === void 0 ? void 0 : _b.filter(t => t.token.isNFT).length) || 0); }, 0); return { primaryAction, description: generateHumanReadableDescription(instructions), category, participants: participants.slice(0, 10), // Limit to first 10 totalSOL, totalTokens, totalNFTs }; } /** * Generate human-readable transaction description */ function generateHumanReadableDescription(instructions) { if (instructions.length === 0) return 'Empty transaction'; if (instructions.length === 1) { return instructions[0].description; } return `Complex transaction with ${instructions.length} instructions`; } /** * Format token amount with proper decimals */ function formatTokenAmount(amount, decimals) { const num = parseFloat(amount) / Math.pow(10, decimals); return num.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: decimals }); } // Simple in-memory cache for token metadata to improve performance const metadataCache = new Map(); const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes const CACHE_CLEANUP_INTERVAL = 10 * 60 * 1000; // 10 minutes // Periodic cache cleanup let lastCleanup = Date.now(); /** * Fetch token metadata for transfer analysis with caching */ function fetchTokenMetadataForTransfer(sdk, mint) { return __awaiter(this, void 0, void 0, function* () { // Cleanup cache periodically const now = Date.now(); if (now - lastCleanup > CACHE_CLEANUP_INTERVAL) { cleanupMetadataCache(); lastCleanup = now; } // Check cache first const cached = metadataCache.get(mint); if (cached && (now - cached.timestamp < CACHE_DURATION)) { return cached.data; } try { const accountInfo = yield sdk.rpc.getAccountInfo(mint); if (!accountInfo) return null; // Basic metadata - would be enhanced with actual metadata fetching const metadata = { name: `Token ${mint.substring(0, 8)}...`, symbol: 'TOKEN', decimals: 9, isNFT: false }; // Cache the result metadataCache.set(mint, { data: metadata, timestamp: now }); return metadata; } catch (error) { return null; } }); } /** * Clean up expired cache entries */ function cleanupMetadataCache() { const now = Date.now(); for (const [key, value] of metadataCache.entries()) { if (now - value.timestamp > CACHE_DURATION) { metadataCache.delete(key); } } }