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