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