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.

591 lines (590 loc) 24.8 kB
/** * Enhanced RPC Client for Gorbchain SDK v2 * Provides network-aware token operations and custom 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()); }); }; import { detectNetworkFromEndpoint } from '../config/networks.js'; /** * Enhanced RPC Client with network-aware capabilities */ export class EnhancedRpcClient { constructor(rpcEndpoint, baseRpcClient) { this.rpcEndpoint = rpcEndpoint; this.baseRpcClient = baseRpcClient; this.networkConfig = detectNetworkFromEndpoint(rpcEndpoint); } /** * Get program accounts with filters */ getProgramAccounts(programId, filters) { return __awaiter(this, void 0, void 0, function* () { const params = [programId]; if (filters && filters.length > 0) { const config = { encoding: 'base64', filters: filters }; params.push(config); } else { // If no filters, just use basic encoding params.push({ encoding: 'base64' }); } try { const response = yield this.makeRpcCall('getProgramAccounts', params); return response.result || []; } catch (error) { // If complex filters fail, try without filters (less efficient but more compatible) if (filters && filters.length > 0) { console.warn(`Complex filters failed for program ${programId}, trying without filters:`, error.message); try { const simpleResponse = yield this.makeRpcCall('getProgramAccounts', [programId, { encoding: 'base64' }]); const allAccounts = simpleResponse.result || []; // Apply filters manually if we got all accounts return this.applyFiltersManually(allAccounts, filters); } catch (fallbackError) { console.warn(`Fallback without filters also failed for program ${programId}:`, fallbackError.message); return []; } } // If no filters and still failed, return empty array console.warn(`getProgramAccounts failed for program ${programId}:`, error.message); return []; } }); } /** * Apply filters manually to account results */ applyFiltersManually(accounts, filters) { return accounts.filter(account => { for (const filter of filters) { // Check dataSize filter if (filter.dataSize !== undefined) { const dataLength = account.account.data[0] ? Buffer.from(account.account.data[0], 'base64').length : 0; if (dataLength !== filter.dataSize) { return false; } } // Check memcmp filter if (filter.memcmp) { try { const data = Buffer.from(account.account.data[0], 'base64'); const { offset, bytes } = filter.memcmp; // Convert wallet address to bytes for comparison const targetBytes = this.addressToBytes(bytes); if (!targetBytes) continue; // Check if we have enough data at the offset if (data.length < offset + targetBytes.length) { return false; } // Compare bytes at offset const dataSlice = data.slice(offset, offset + targetBytes.length); if (!dataSlice.equals(targetBytes)) { return false; } } catch (error) { console.warn('Failed to apply memcmp filter:', error.message); return false; } } } return true; }); } /** * Convert address string to bytes for comparison */ addressToBytes(address) { try { // For Gorbchain, addresses might be in different formats // Try base58 decode first const { base58ToBytes } = require('../utils/base58'); return Buffer.from(base58ToBytes(address)); } catch (error) { try { // Try as hex if base58 fails if (address.startsWith('0x')) { return Buffer.from(address.slice(2), 'hex'); } return Buffer.from(address, 'hex'); } catch (hexError) { console.warn('Failed to convert address to bytes:', address, error.message); return null; } } } /** * Get token accounts by program for a specific wallet */ getTokenAccountsByProgram(walletAddress, programId) { return __awaiter(this, void 0, void 0, function* () { // Validate wallet address first if (!this.isValidAddress(walletAddress)) { console.warn(`Invalid wallet address: ${walletAddress}`); return []; } // Try with memcmp filter first try { const filters = [ { memcmp: { offset: 32, // Owner field offset in token accounts bytes: walletAddress } } ]; const accounts = yield this.getProgramAccounts(programId, filters); return accounts; } catch (error) { console.warn(`memcmp filter failed for wallet ${walletAddress} and program ${programId}, trying alternative approach:`, error.message); // Fallback: get all accounts and filter manually try { const allAccounts = yield this.getProgramAccounts(programId, []); return this.filterAccountsByOwner(allAccounts, walletAddress); } catch (fallbackError) { console.warn(`Alternative approach failed:`, fallbackError.message); return []; } } }); } /** * Validate if an address is in correct format */ isValidAddress(address) { if (!address || typeof address !== 'string') return false; // Basic length and character checks if (address.length < 32 || address.length > 44) return false; // Check for valid base58 characters (or hex if starts with 0x) if (address.startsWith('0x')) { return /^0x[0-9a-fA-F]+$/.test(address); } // Base58 check (simplified) return /^[1-9A-HJ-NP-Za-km-z]+$/.test(address); } /** * Filter accounts by owner manually */ filterAccountsByOwner(accounts, ownerAddress) { const filtered = []; for (const account of accounts) { try { // Parse token account to check owner if (account.account.data[0]) { const data = Buffer.from(account.account.data[0], 'base64'); if (data.length >= 64) { // Minimum size for token account const parsed = this.parseTokenAccount(data); if (parsed.owner === ownerAddress) { filtered.push(account); } } } } catch (parseError) { // Skip accounts that can't be parsed continue; } } return filtered; } /** * Get custom token holdings for a wallet */ getCustomTokenHoldings(walletAddress, config) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; const holdings = []; const processedMints = new Set(); // Determine which programs to scan const programsToScan = this.getProgramsToScan(config); // Scan each program for (const programId of programsToScan) { try { const accounts = yield this.getTokenAccountsByProgram(walletAddress, programId); for (const account of accounts) { try { const parsed = this.parseTokenAccount(Buffer.from(account.account.data[0], 'base64')); // Skip if we already processed this mint or if balance is zero if (processedMints.has(parsed.mint) || parseFloat(parsed.amount) === 0) { continue; } processedMints.add(parsed.mint); const holding = { mint: parsed.mint, tokenAccount: account.pubkey, balance: { raw: parsed.amount, decimal: parsed.uiAmount, formatted: parsed.uiAmount.toString() }, decimals: parsed.decimals, isNFT: parsed.uiAmount === 1 && parsed.decimals === 0, metadata: undefined, // Will be populated if metadata is available mintInfo: undefined // Will be populated if mint info is available }; // Try to get additional mint information try { const mintInfo = yield this.getMintAccountInfo(parsed.mint); if (mintInfo) { holding.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 }; } } catch (error) { // Mint info not available, continue without it } holdings.push(holding); } catch (parseError) { console.warn(`Failed to parse token account ${account.pubkey}:`, parseError); } } } catch (error) { console.warn(`Failed to scan program ${programId}:`, error); } } return holdings; }); } /** * Parse token account data from buffer */ parseTokenAccount(data) { if (data.length < 72) { throw new Error('Invalid token account data length'); } // Token account structure: // 0-32: mint (32 bytes) // 32-64: owner (32 bytes) // 64-72: amount (8 bytes, little endian) // 72-73: delegate option (1 byte) // 73-74: state (1 byte) // 74-75: is_native option (1 byte) // 75-83: delegated_amount (8 bytes) // 83-115: close_authority option (32 bytes) const mint = data.slice(0, 32); const owner = data.slice(32, 64); const amount = data.readBigUInt64LE(64); // Try to read decimals from offset 109 if available (some token programs store it there) let decimals = 0; if (data.length > 109) { try { decimals = data.readUInt8(109); } catch (_a) { // If reading decimals fails, default to 0 } } const mintAddress = this.bufferToBase58(mint); const ownerAddress = this.bufferToBase58(owner); const amountString = amount.toString(); const uiAmount = parseFloat(amountString) / Math.pow(10, decimals); return { mint: mintAddress, owner: ownerAddress, amount: amountString, decimals, uiAmount, isInitialized: true }; } /** * Parse mint account data from buffer */ parseMintAccount(data) { if (data.length < 82) { throw new Error('Invalid mint account data length'); } // Mint account structure: // 0-4: mint_authority option (4 bytes) + mint_authority (32 bytes if present) // 36-44: supply (8 bytes, little endian) // 44: decimals (1 byte) // 45: is_initialized (1 byte) // 46-50: freeze_authority option (4 bytes) + freeze_authority (32 bytes if present) const mintAuthorityOption = data.readUInt32LE(0); const mintAuthority = mintAuthorityOption === 1 ? this.bufferToBase58(data.slice(4, 36)) : null; const supply = data.readBigUInt64LE(36); const decimals = data.readUInt8(44); const isInitialized = data.readUInt8(45) === 1; const freezeAuthorityOption = data.readUInt32LE(46); const freezeAuthority = freezeAuthorityOption === 1 ? this.bufferToBase58(data.slice(50, 82)) : null; return { mintAuthority, supply: supply.toString(), decimals, isInitialized, freezeAuthority }; } /** * Get mint account information */ getMintAccountInfo(mintAddress) { return __awaiter(this, void 0, void 0, function* () { var _a, _b; try { const response = yield this.makeRpcCall('getAccountInfo', [ mintAddress, { encoding: 'base64' } ]); if (!((_b = (_a = response.result) === null || _a === void 0 ? void 0 : _a.value) === null || _b === void 0 ? void 0 : _b.data)) { return null; } const data = Buffer.from(response.result.value.data[0], 'base64'); return this.parseMintAccount(data); } catch (error) { return null; } }); } /** * Check if a specific RPC method is supported */ isMethodSupported(method) { return __awaiter(this, void 0, void 0, function* () { var _a; if (this.networkConfig) { return this.networkConfig.supportedMethods.includes(method); } // If no network config, try to detect by making a test call try { yield this.makeRpcCall(method, []); return true; } catch (error) { const errorMessage = ((_a = error.message) === null || _a === void 0 ? void 0 : _a.toLowerCase()) || ''; // Return false for network errors, timeouts, and unsupported methods if (errorMessage.includes('method not found') || errorMessage.includes('not supported') || errorMessage.includes('network') || errorMessage.includes('timeout') || errorMessage.includes('connection') || errorMessage.includes('fetch') || errorMessage.includes('http')) { return false; } return false; // Default to false for any error } }); } /** * Get list of supported RPC methods */ getSupportedMethods() { return __awaiter(this, void 0, void 0, function* () { if (this.networkConfig) { return this.networkConfig.supportedMethods; } // Fallback: test common methods const commonMethods = [ 'getBalance', 'getSlot', 'getAccountInfo', 'getProgramAccounts', 'getTokenAccountsByOwner', 'getTokenAccountInfo', 'getTokenInfo', 'getSignaturesForAddress', 'getTransaction' ]; const supportedMethods = []; for (const method of commonMethods) { if (yield this.isMethodSupported(method)) { supportedMethods.push(method); } } return supportedMethods; }); } /** * Get network configuration */ getNetworkConfig() { return this.networkConfig; } /** * Set network configuration manually */ setNetworkConfig(config) { this.networkConfig = config; } /** * Determine which token programs to scan based on config and network */ getProgramsToScan(config) { var _a, _b, _c; const programs = []; // Add custom programs from config if (config === null || config === void 0 ? void 0 : config.customPrograms) { programs.push(...config.customPrograms); } // Add network-specific custom programs if ((_a = this.networkConfig) === null || _a === void 0 ? void 0 : _a.tokenPrograms.custom) { programs.push(...this.networkConfig.tokenPrograms.custom); } // Add standard programs if requested and supported if ((config === null || config === void 0 ? void 0 : config.includeStandardTokens) !== false && ((_b = this.networkConfig) === null || _b === void 0 ? void 0 : _b.features.standardTokens)) { if (this.networkConfig.tokenPrograms.spl) { programs.push(this.networkConfig.tokenPrograms.spl); } } // Add Token-2022 if requested and supported if ((config === null || config === void 0 ? void 0 : config.includeToken2022) && ((_c = this.networkConfig) === null || _c === void 0 ? void 0 : _c.tokenPrograms.token2022)) { programs.push(this.networkConfig.tokenPrograms.token2022); } // Remove duplicates return [...new Set(programs)]; } /** * Make RPC call to the endpoint */ makeRpcCall(method, params) { return __awaiter(this, void 0, void 0, function* () { const response = yield fetch(this.rpcEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ jsonrpc: '2.0', id: Math.random(), method, params }) }); if (!response.ok) { throw new Error(`HTTP ${response.status}: ${response.statusText}`); } const data = yield response.json(); if (data.error) { throw new Error(`RPC Error: ${data.error.message}`); } return data; }); } /** * Convert buffer to base58 string */ bufferToBase58(buffer) { // This would use the actual base58 implementation from the SDK // For now, we'll assume it's available from the utils const { bytesToBase58 } = require('../utils/base58'); return bytesToBase58(new Uint8Array(buffer)); } // Proxy methods to the base RPC client for backward compatibility getBalance(publicKey) { return __awaiter(this, void 0, void 0, function* () { const accountInfo = yield this.baseRpcClient.getAccountInfo(publicKey); return (accountInfo === null || accountInfo === void 0 ? void 0 : accountInfo.lamports) || 0; }); } getSlot() { return __awaiter(this, void 0, void 0, function* () { return this.baseRpcClient.getSlot(); }); } getAccountInfo(publicKey, encoding) { return __awaiter(this, void 0, void 0, function* () { return this.baseRpcClient.getAccountInfo(publicKey, encoding); }); } getSignaturesForAddress(address, options) { return __awaiter(this, void 0, void 0, function* () { return this.baseRpcClient.request('getSignaturesForAddress', [address, options]); }); } getTransaction(signature, options) { return __awaiter(this, void 0, void 0, function* () { return this.baseRpcClient.request('getTransaction', [signature, options]); }); } // Network-aware token methods with fallbacks getTokenAccountsByOwner(walletAddress, filter, commitment) { return __awaiter(this, void 0, void 0, function* () { // Check if the method is supported if (yield this.isMethodSupported('getTokenAccountsByOwner')) { return this.baseRpcClient.getTokenAccountsByOwner(walletAddress, filter, commitment); } // Fallback: use custom implementation for unsupported networks if (filter.programId) { const accounts = yield this.getTokenAccountsByProgram(walletAddress, filter.programId); return accounts.map(account => ({ pubkey: account.pubkey, account: account.account })); } throw new Error('getTokenAccountsByOwner not supported and no fallback available'); }); } getTokenAccountInfo(tokenAccount, commitment) { return __awaiter(this, void 0, void 0, function* () { // Check if the method is supported if (yield this.isMethodSupported('getTokenAccountInfo')) { return this.baseRpcClient.getTokenAccountInfo(tokenAccount, commitment); } // Fallback: manually parse account data const accountInfo = yield this.getAccountInfo(tokenAccount, 'base64'); if (!(accountInfo === null || accountInfo === void 0 ? void 0 : accountInfo.data)) { return null; } const data = Buffer.from(accountInfo.data[0], 'base64'); const parsed = this.parseTokenAccount(data); return { mint: parsed.mint, owner: parsed.owner, tokenAmount: { amount: parsed.amount, decimals: parsed.decimals, uiAmount: parsed.uiAmount, uiAmountString: parsed.uiAmount.toString() } }; }); } getTokenInfo(mintAddress) { return __awaiter(this, void 0, void 0, function* () { // Check if the method is supported if (yield this.isMethodSupported('getTokenInfo')) { return this.baseRpcClient.getTokenInfo(mintAddress); } // Fallback: manually get mint info const mintInfo = yield this.getMintAccountInfo(mintAddress); if (!mintInfo) { return null; } return { supply: mintInfo.supply, decimals: mintInfo.decimals, mintAuthority: mintInfo.mintAuthority, freezeAuthority: mintInfo.freezeAuthority, isInitialized: mintInfo.isInitialized, isNFT: mintInfo.decimals === 0 && mintInfo.supply === '1' }; }); } }