UNPKG

@five-vm/cli

Version:

High-performance CLI for Five VM development with WebAssembly integration

1,166 lines 117 kB
/** * Five SDK - Unified client library for Five VM scripts * * Provides a standardized way to interact with Five scripts deployed on Solana. * Key concepts: * - Five scripts (.v) compile to bytecode (.bin) * - Bytecode is deployed to script accounts on Solana * - Five VM Program executes scripts from script accounts * - This SDK abstracts the complexity while maintaining performance */ // Client-agnostic SDK - no direct Solana client dependencies import { FIVE_VM_PROGRAM_ID, FiveSDKError, } from "./types.js"; import { BytecodeCompiler } from "./compiler/BytecodeCompiler.js"; import { ParameterEncoder } from "./encoding/ParameterEncoder.js"; import { VLEEncoder } from "../lib/vle-encoder.js"; import { PDAUtils, RentCalculator } from "./crypto/index.js"; import { ScriptMetadataParser, MetadataCache, } from "./metadata/index.js"; import { validator, Validators } from "./validation/index.js"; console.log("[FiveSDK] Module loaded - deployToSolana method available"); /** * Main Five SDK class - entry point for all Five VM interactions * Client-agnostic design: generates serialized transaction data for any Solana client library */ export class FiveSDK { static compiler = null; static parameterEncoder = null; static metadataCache = new MetadataCache(); fiveVMProgramId; debug; network; /** * Create a new Five SDK instance (for configuration) */ constructor(config = {}) { this.fiveVMProgramId = config.fiveVMProgramId || FIVE_VM_PROGRAM_ID; this.debug = config.debug || false; this.network = config.network; // Cast to handle network property if (this.debug) { console.log(`[FiveSDK] Initialized with Five VM Program: ${this.fiveVMProgramId}`); } } /** * Get SDK configuration */ getConfig() { return { fiveVMProgramId: this.fiveVMProgramId, debug: this.debug, network: this.network, }; } /** * Initialize static components (lazy initialization) */ static async initializeComponents(debug = false) { if (!this.compiler) { this.compiler = new BytecodeCompiler({ debug }); } if (!this.parameterEncoder) { this.parameterEncoder = new ParameterEncoder(debug); } } // ==================== Static Factory Methods ==================== /** * Create SDK instance with default configuration */ static create(options = {}) { return new FiveSDK({ debug: options.debug || false, fiveVMProgramId: options.fiveVMProgramId, }); } /** * Create SDK instance for devnet */ static devnet(options = {}) { return new FiveSDK({ debug: options.debug || false, fiveVMProgramId: options.fiveVMProgramId, network: "devnet", }); } /** * Create SDK instance for mainnet */ static mainnet(options = {}) { return new FiveSDK({ debug: options.debug || false, fiveVMProgramId: options.fiveVMProgramId, network: "mainnet", }); } /** * Create SDK instance for localnet */ static localnet(options = {}) { return new FiveSDK({ debug: options.debug || false, fiveVMProgramId: options.fiveVMProgramId, network: "localnet", }); } // ==================== Script Compilation ==================== /** * Compile Five script source code to bytecode (static method) */ static async compile(source, options = {}) { // Input validation Validators.sourceCode(source); Validators.options(options); await this.initializeComponents(options.debug); if (options.debug) { console.log(`[FiveSDK] Compiling script (${source.length} chars)...`); } try { const result = await this.compiler.compile(source, options); // Generate .five format if compilation successful if (result.success && result.bytecode) { if (options.debug) { console.log("[FiveSDK] Debug - result.metadata:", JSON.stringify(result.metadata, null, 2)); console.log("[FiveSDK] Debug - result.abi:", JSON.stringify(result.abi, null, 2)); } // Generate ABI using dedicated WASM function let abiData = { functions: [], fields: [] }; try { const generatedABI = await this.compiler.generateABI(source); if (generatedABI && generatedABI.functions) { abiData = generatedABI; if (options.debug) { console.log("[FiveSDK] Generated ABI:", JSON.stringify(abiData, null, 2)); } } } catch (abiError) { if (options.debug) { console.warn("[FiveSDK] ABI generation failed, using empty ABI:", abiError); } } // Use generated ABI functions, fallback to empty array const functions = abiData.functions || []; result.fiveFile = { bytecode: Buffer.from(result.bytecode).toString("base64"), abi: { functions: functions, fields: abiData.fields || [], version: "1.0", }, disassembly: result.disassembly || [], debug: options.debug ? { compilationInfo: { sourceSize: result.metadata?.sourceSize || 0, bytecodeSize: result.metadata?.bytecodeSize || 0, compilationTime: result.metadata?.compilationTime || 0, }, } : undefined, version: "1.0", }; } if (options.debug) { if (result.success) { console.log(`[FiveSDK] Compilation successful: ${result.bytecode?.length} bytes`); } else { console.log(`[FiveSDK] Compilation failed: ${result.errors?.length} errors`); } } return result; } catch (error) { throw new FiveSDKError(`Compilation failed: ${error instanceof Error ? error.message : "Unknown error"}`, "COMPILATION_ERROR", { source: source.substring(0, 100) + "...", options }); } } /** * Compile script from file path (static method) */ static async compileFile(filePath, options = {}) { // Input validation Validators.filePath(filePath); Validators.options(options); await this.initializeComponents(options.debug); if (options.debug) { console.log(`[FiveSDK] Compiling file: ${filePath}`); } return this.compiler.compileFile(filePath, options); } // ==================== Five File Format Utilities ==================== /** * Load .five file and extract components */ static async loadFiveFile(fileContent) { try { const fiveFile = JSON.parse(fileContent); if (!fiveFile.bytecode || !fiveFile.abi) { throw new Error("Invalid .five file format: missing bytecode or ABI"); } const bytecode = new Uint8Array(Buffer.from(fiveFile.bytecode, "base64")); return { bytecode, abi: fiveFile.abi, debug: fiveFile.debug, }; } catch (error) { throw new FiveSDKError(`Failed to load .five file: ${error instanceof Error ? error.message : "Unknown error"}`, "FILE_LOAD_ERROR"); } } /** * Extract bytecode from .five file for deployment */ static extractBytecode(fiveFile) { return new Uint8Array(Buffer.from(fiveFile.bytecode, "base64")); } /** * Resolve function name to index using ABI */ static resolveFunctionIndex(abi, functionName) { if (!abi || !abi.functions) { throw new Error("No ABI information available for function name resolution"); } // Handle both array format: [{ name: "add", index: 0 }] and object format: { "add": { index: 0 } } if (Array.isArray(abi.functions)) { // Array format (legacy) const func = abi.functions.find((f) => f.name === functionName); if (!func) { const availableFunctions = abi.functions .map((f) => f.name) .join(", "); throw new Error(`Function '${functionName}' not found in ABI. Available functions: ${availableFunctions}`); } return func.index; } else { // Object format (new WASM ABI) const func = abi.functions[functionName]; if (!func) { const availableFunctions = Object.keys(abi.functions).join(", "); throw new Error(`Function '${functionName}' not found in ABI. Available functions: ${availableFunctions}`); } return func.index; } } // ==================== WASM VM Direct Execution (Local Testing) ==================== /** * Execute bytecode directly using WASM VM for local testing and development * This bypasses Solana entirely - no network connection needed! */ static async executeLocally(bytecode, functionName, parameters = [], options = {}) { // Input validation Validators.bytecode(bytecode); Validators.functionRef(functionName); Validators.parameters(parameters); Validators.options(options); const startTime = Date.now(); if (options.debug) { console.log(`[FiveSDK] Executing locally: function=${functionName}, params=${parameters.length}`); console.log(`[FiveSDK] Parameters:`, parameters); } try { // Load WASM VM const wasmVM = await this.loadWasmVM(); // Resolve function name to index if needed let resolvedFunctionIndex; if (typeof functionName === "number") { resolvedFunctionIndex = functionName; } else if (options.abi) { // Use provided ABI for function name resolution try { resolvedFunctionIndex = this.resolveFunctionIndex(options.abi, functionName); } catch (resolutionError) { throw new FiveSDKError(`Function name resolution failed: ${resolutionError instanceof Error ? resolutionError.message : "Unknown error"}`, "FUNCTION_RESOLUTION_ERROR"); } } else { // No ABI provided and function name given - cannot resolve throw new FiveSDKError(`Cannot resolve function name '${functionName}' without ABI information. Please provide function index or use compileAndExecuteLocally() instead.`, "MISSING_ABI_ERROR"); } // Prepare execution context const execOptions = { bytecode, functionIndex: resolvedFunctionIndex, parameters, maxComputeUnits: options.computeUnitLimit || 200000, trace: options.trace || options.debug || false, }; if (options.debug) { console.log(`[FiveSDK] WASM VM execution starting...`); } // Execute using WASM VM with proper VLE parameter encoding const transformedParams = parameters.map((param, index) => ({ type: this.inferParameterType(param), value: param, })); if (options.debug) { console.log(`[FiveSDK] Resolved function index: ${resolvedFunctionIndex}`); console.log(`[FiveSDK] Transformed parameters:`, transformedParams); } const result = await wasmVM.executeFunction(bytecode, resolvedFunctionIndex, transformedParams); const executionTime = Date.now() - startTime; if (options.debug) { console.log(`[FiveSDK] Local execution ${result.success ? "completed" : "failed"} in ${executionTime}ms`); if (result.computeUnitsUsed) { console.log(`[FiveSDK] Compute units used: ${result.computeUnitsUsed}`); } } return { success: result.success, result: result.result, logs: result.logs, computeUnitsUsed: result.computeUnitsUsed, executionTime, error: result.error, trace: result.trace, }; } catch (error) { const executionTime = Date.now() - startTime; const errorMessage = error instanceof Error ? error.message : "Unknown execution error"; if (options.debug) { console.log(`[FiveSDK] Local execution failed after ${executionTime}ms: ${errorMessage}`); } return { success: false, executionTime, error: errorMessage, }; } } /** * Compile and execute a script locally in one step (perfect for rapid testing) */ static async compileAndExecuteLocally(source, functionName, parameters = [], options = {}) { // Input validation Validators.sourceCode(source); Validators.functionRef(functionName); Validators.parameters(parameters); Validators.options(options); if (options.debug) { console.log(`[FiveSDK] Compile and execute locally: ${functionName}`); } // Compile the script const compilation = await this.compile(source, { optimize: options.optimize, debug: options.debug, }); if (!compilation.success || !compilation.bytecode) { return { success: false, compilationErrors: compilation.errors, error: "Compilation failed", }; } if (options.debug) { console.log(`[FiveSDK] Compilation successful, executing bytecode...`); } // Execute the compiled bytecode const execution = await this.executeLocally(compilation.bytecode, functionName, parameters, { ...options, abi: compilation.abi, // Pass ABI from compilation for function name resolution }); return { ...execution, compilation, bytecodeSize: compilation.bytecode.length, functions: compilation.metadata?.functions, }; } /** * Validate bytecode format and structure using WASM VM */ static async validateBytecode(bytecode, options = {}) { // Input validation Validators.bytecode(bytecode); Validators.options(options); if (options.debug) { console.log(`[FiveSDK] Validating bytecode (${bytecode.length} bytes)`); } try { const wasmVM = await this.loadWasmVM(); const validation = await wasmVM.validateBytecode(bytecode); if (options.debug) { console.log(`[FiveSDK] Validation ${validation.valid ? "passed" : "failed"}`); } return validation; } catch (error) { if (options.debug) { console.log(`[FiveSDK] Validation error: ${error}`); } return { valid: false, errors: [ error instanceof Error ? error.message : "Unknown validation error", ], }; } } // ==================== Serialized Deployment ==================== /** * Generate deployment instruction data (static method) * * Creates a complete deployment transaction that includes: * 1. Creating the script account PDA owned by Five VM program * 2. Deploying bytecode to the created account */ static async generateDeployInstruction(bytecode, deployer, // base58 pubkey string options = {}) { // Input validation Validators.bytecode(bytecode); validator.validateBase58Address(deployer, "deployer"); Validators.options(options); if (options.scriptAccount) { validator.validateBase58Address(options.scriptAccount, "options.scriptAccount"); } await this.initializeComponents(options.debug); if (options.debug) { console.log(`[FiveSDK] Generating deployment transaction (${bytecode.length} bytes)...`); } // Derive script account with seed const scriptResult = await PDAUtils.deriveScriptAccount(bytecode, FIVE_VM_PROGRAM_ID); const scriptAccount = scriptResult.address; const scriptSeed = scriptResult.seed; // Derive VM state PDA const vmStatePDA = await this.deriveVMStatePDA(); if (options.debug) { console.log(`[FiveSDK] Script Account: ${scriptAccount} (seed: ${scriptSeed})`); console.log(`[FiveSDK] VM State PDA: ${vmStatePDA}`); } // Calculate account size and rent const SCRIPT_HEADER_SIZE = 48; // FIVEScriptHeader size from Rust program const totalAccountSize = SCRIPT_HEADER_SIZE + bytecode.length; const rentLamports = await this.calculateRentExemption(totalAccountSize); // Build account list for deploy instruction (after PDA creation): // 0: script_account, 1: vm_state_account, 2: owner (signer) const deployAccounts = [ { pubkey: scriptAccount, isSigner: false, isWritable: true }, // Script account PDA { pubkey: vmStatePDA, isSigner: false, isWritable: true }, // VM state PDA { pubkey: deployer, isSigner: true, isWritable: true }, // Owner/deployer (must be signer) // System program for account creation { pubkey: "11111111111111111111111111111112", isSigner: false, isWritable: false, }, ]; // Encode deployment instruction data const instructionData = this.encodeDeployInstruction(bytecode); // Create the deployment result with setup instructions const result = { instruction: { programId: FIVE_VM_PROGRAM_ID, accounts: deployAccounts, data: Buffer.from(instructionData).toString("base64"), }, scriptAccount, requiredSigners: [deployer], estimatedCost: rentLamports + (options.extraLamports || 0), bytecodeSize: bytecode.length, // Add setup information for account creation setupInstructions: { createScriptAccount: { pda: scriptAccount, seed: scriptSeed, space: totalAccountSize, rent: rentLamports, owner: FIVE_VM_PROGRAM_ID, }, }, }; if (options.debug) { console.log(`[FiveSDK] Generated deployment transaction:`, { scriptAccount, scriptSeed, accountSize: totalAccountSize, rentCost: rentLamports, deployDataSize: instructionData.length, }); } return result; } // ==================== Serialized Execution ==================== /** * Generate execution instruction data (static method) */ static async generateExecuteInstruction(scriptAccount, // base58 pubkey string functionName, parameters = [], accounts = [], // base58 pubkey strings options = {}) { // Input validation validator.validateBase58Address(scriptAccount, "scriptAccount"); Validators.functionRef(functionName); Validators.parameters(parameters); Validators.accounts(accounts); Validators.options(options); await this.initializeComponents(options.debug); if (options.debug) { console.log(`[FiveSDK] Generating execution instruction:`, { scriptAccount, function: functionName, parameterCount: parameters.length, accountCount: accounts.length, }); } // Handle missing metadata gracefully - generate parameters for VLE encoding let functionIndex; let encodedParams; try { // Try to load script metadata for ABI-driven parameter encoding const scriptMetadata = await this.getScriptMetadata(scriptAccount); // Resolve function index functionIndex = typeof functionName === "number" ? functionName : FiveSDK.resolveFunctionIndex(scriptMetadata, functionName); // Encode parameters with ABI guidance encodedParams = await this.encodeParametersWithABI(parameters, scriptMetadata.functions[functionIndex], functionIndex); } catch (metadataError) { if (options.debug) { console.log(`[FiveSDK] Metadata not available, using VLE encoding with assumed parameter types`); } // GRACEFUL HANDLING: Use VLE encoding without metadata functionIndex = typeof functionName === "number" ? functionName : 0; // Create parameter definitions for VLE encoding (assume all u64) const paramDefs = parameters.map((_, index) => ({ name: `param${index}`, type: "u64", })); const paramValues = {}; paramDefs.forEach((param, index) => { paramValues[param.name] = parameters[index]; }); if (options.debug) { console.log(`[FiveSDK] About to call VLEEncoder.encodeExecuteVLE with:`, { functionIndex, paramDefs, paramValues, }); } encodedParams = await VLEEncoder.encodeExecuteVLE(functionIndex, paramDefs, paramValues); if (options.debug) { console.log(`[FiveSDK] VLE encoder returned:`, { encodedLength: encodedParams.length, encodedBytes: Array.from(encodedParams), hex: Buffer.from(encodedParams).toString("hex"), }); } } // Derive VM state PDA - required for all Five VM executions const vmStatePDA = await this.deriveVMStatePDA(); // Build account list with required VM state PDA const instructionAccounts = [ { pubkey: scriptAccount, isSigner: false, isWritable: false }, { pubkey: vmStatePDA, isSigner: false, isWritable: true }, // VM state PDA (required!) ...accounts.map((acc) => ({ pubkey: acc, isSigner: false, // Consumer will determine signing requirements isWritable: true, // Conservative default - consumer can override })), ]; // Encode execution instruction data const instructionData = this.encodeExecuteInstruction(functionIndex, encodedParams); const result = { instruction: { programId: FIVE_VM_PROGRAM_ID, accounts: instructionAccounts, data: Buffer.from(instructionData).toString("base64"), }, scriptAccount, parameters: { function: functionName, data: encodedParams, count: parameters.length, }, requiredSigners: [], // Consumer determines signers based on their context estimatedComputeUnits: options.computeUnitLimit || this.estimateComputeUnits(functionIndex, parameters.length), }; if (options.debug) { console.log(`[FiveSDK] Generated execution instruction:`, { function: functionName, functionIndex, parameterBytes: encodedParams.length, dataSize: instructionData.length, estimatedCU: result.estimatedComputeUnits, }); } return result; } // ==================== Script Analysis ==================== /** * Get script metadata for ABI-driven parameter encoding (static method) * Now uses real Solana account data parsing instead of mocks */ static async getScriptMetadata(scriptAccount, connection) { // Input validation validator.validateBase58Address(scriptAccount, "scriptAccount"); try { if (connection) { // Use real blockchain data if connection provided const metadata = await ScriptMetadataParser.getScriptMetadata(connection, scriptAccount); return { functions: metadata.abi.functions.map((func) => ({ name: func.name, index: func.index, parameters: func.parameters, returnType: func.returnType, visibility: func.visibility, })), }; } else { // Client-agnostic mode: metadata should be provided by client // This maintains the SDK's client-agnostic design throw new Error("No connection provided for metadata retrieval. " + "In client-agnostic mode, provide script metadata directly or use getScriptMetadataWithConnection()."); } } catch (error) { throw new Error(`Failed to get script metadata: ${error instanceof Error ? error.message : "Unknown error"}`); } } /** * Get script metadata with explicit connection (for use with any Solana client) */ static async getScriptMetadataWithConnection(scriptAccount, connection) { // Input validation validator.validateBase58Address(scriptAccount, "scriptAccount"); return ScriptMetadataParser.getScriptMetadata(connection, scriptAccount); } /** * Parse script metadata from raw account data (client-agnostic) */ static parseScriptMetadata(accountData, address) { // Input validation Validators.bytecode(accountData); // Reuse bytecode validation for account data validator.validateBase58Address(address, "address"); return ScriptMetadataParser.parseMetadata(accountData, address); } /** * Get script metadata with caching (for performance) */ static async getCachedScriptMetadata(scriptAccount, connection, cacheTTL = 5 * 60 * 1000) { // Input validation validator.validateBase58Address(scriptAccount, "scriptAccount"); validator.validateNumber(cacheTTL, "cacheTTL"); return this.metadataCache.getMetadata(scriptAccount, (address) => ScriptMetadataParser.getScriptMetadata(connection, address), cacheTTL); } /** * Invalidate metadata cache for a script */ static invalidateMetadataCache(scriptAccount) { // Input validation validator.validateBase58Address(scriptAccount, "scriptAccount"); this.metadataCache.invalidate(scriptAccount); } /** * Get metadata cache statistics */ static getMetadataCacheStats() { return this.metadataCache.getStats(); } // ==================== Private Utility Methods ==================== /** * Derive script account PDA from bytecode using real Solana PDA derivation */ static async deriveScriptAccount(bytecode) { const result = await PDAUtils.deriveScriptAccount(bytecode); return result.address; } /** * Derive VM state PDA using hardcoded seed (matches Five VM program) */ static async deriveVMStatePDA() { const result = await PDAUtils.deriveVMStatePDA(FIVE_VM_PROGRAM_ID); return result.address; } /** * Load WASM VM for direct execution */ static wasmVMInstance = null; static async loadWasmVM() { if (this.wasmVMInstance) { return this.wasmVMInstance; } try { // Import existing WASM VM from five-cli infrastructure const { FiveVM } = await import("../wasm/vm.js"); // Create a simple logger for WASM VM const logger = { debug: (msg) => console.debug("[WASM VM]", msg), info: (msg) => console.info("[WASM VM]", msg), warn: (msg) => console.warn("[WASM VM]", msg), error: (msg) => console.error("[WASM VM]", msg), }; this.wasmVMInstance = new FiveVM(logger); // Initialize WASM VM if (this.wasmVMInstance.initialize) { await this.wasmVMInstance.initialize(); } return this.wasmVMInstance; } catch (error) { throw new FiveSDKError(`Failed to load WASM VM: ${error instanceof Error ? error.message : "Unknown error"}`, "WASM_LOAD_ERROR"); } } /** * Calculate rent exemption for account size using real Solana rent calculations */ static async calculateRentExemption(dataSize) { return RentCalculator.calculateRentExemption(dataSize); } /** * Encode deployment instruction data */ static encodeDeployInstruction(bytecode) { // Deploy instruction: [discriminator(1), bytecode_length(u32_le), bytecode] // Format expected by Five VM Program (data[1..5] for length): // - Discriminator: 1 (u8) // - Length: bytecode.length (u32 little-endian, 4 bytes) // - Bytecode: actual bytecode bytes const lengthBytes = new Uint8Array(4); const lengthView = new DataView(lengthBytes.buffer); lengthView.setUint32(0, bytecode.length, true); // little-endian const result = new Uint8Array(1 + 4 + bytecode.length); result[0] = 1; // Deploy discriminator result.set(lengthBytes, 1); // u32 LE length at bytes 1-4 result.set(bytecode, 5); // bytecode starts at byte 5 console.log(`[FiveSDK] Deploy instruction encoded:`, { discriminator: result[0], lengthBytes: Array.from(lengthBytes), bytecodeLength: bytecode.length, totalInstructionLength: result.length, expectedFormat: `[1, ${bytecode.length}_as_u32le, bytecode_bytes]`, instructionHex: Buffer.from(result).toString("hex").substring(0, 20) + "...", }); return result; } /** * Encode execution instruction data */ static encodeExecuteInstruction(functionIndex, encodedParams) { // Execute instruction: [discriminator(2), function_index(VLE), params] // encodedParams contains: [VLE(paramCount), VLE(param1), ...] const parts = []; parts.push(new Uint8Array([2])); // Execute discriminator const functionIndexVLE = FiveSDK.encodeVLENumber(functionIndex); parts.push(functionIndexVLE); parts.push(encodedParams); // Contains: [VLE(paramCount), VLE(param1), ...] const totalLength = parts.reduce((sum, part) => sum + part.length, 0); const result = new Uint8Array(totalLength); let offset = 0; for (const part of parts) { result.set(part, offset); offset += part.length; } return result; } /** * Encode parameters with ABI guidance */ static async encodeParametersWithABI(parameters, functionDef, functionIndex) { if (!this.parameterEncoder) { await this.initializeComponents(); } // Use VLE encoder to properly encode parameters const paramDefs = functionDef.parameters || []; const paramValues = {}; // Map parameters to names paramDefs.forEach((param, index) => { if (index < parameters.length) { paramValues[param.name] = parameters[index]; } }); // Use ONLY VLE encoding - no fallbacks to maintain architecture integrity const encoded = await VLEEncoder.encodeExecuteVLE(functionIndex, paramDefs, paramValues); return encoded; } // REMOVED: encodeParametersSimple - Five uses ONLY VLE encoding // REMOVED: Local VLE encoding - Five uses centralized VLE encoder /** * VLE encode a number for instruction data */ static encodeVLENumber(value) { const bytes = []; let num = value; while (num >= 0x80) { bytes.push((num & 0x7f) | 0x80); num >>>= 7; } bytes.push(num & 0x7f); return new Uint8Array(bytes); } /** * Estimate compute units for function execution */ static estimateComputeUnits(functionIndex, parameterCount) { // Basic compute unit estimation return Math.max(5000, 1000 + parameterCount * 500 + functionIndex * 100); } /** * Infer parameter type from JavaScript value for VLE encoding */ static inferParameterType(value) { if (typeof value === "boolean") { return "bool"; } else if (typeof value === "number") { if (Number.isInteger(value)) { return value >= 0 ? "u64" : "i64"; } else { return "f64"; } } else if (typeof value === "string") { return "string"; } else if (value instanceof Uint8Array) { return "bytes"; } else { // Fallback to string representation return "string"; } } // ==================== Account Fetching and VLE Deserialization ==================== /** * Fetch account data and deserialize VLE-encoded script data * This is the method requested for pulling down accounts and deserializing Five script data */ static async fetchAccountAndDeserializeVLE(accountAddress, connection, // Solana Connection object options = {}) { try { if (options.debug) { console.log(`[FiveSDK] Fetching account and deserializing VLE data: ${accountAddress}`); } // Import Solana web3.js for account fetching const { PublicKey } = await import("@solana/web3.js"); // Validate account address format let accountPubkey; try { accountPubkey = new PublicKey(accountAddress); } catch (addressError) { return { success: false, error: `Invalid account address format: ${accountAddress}`, logs: [], }; } // Fetch account info from Solana blockchain const accountInfo = await connection.getAccountInfo(accountPubkey, "confirmed"); if (!accountInfo) { return { success: false, error: `Account not found: ${accountAddress}`, logs: [], }; } if (!accountInfo.data || accountInfo.data.length === 0) { return { success: false, error: `Account has no data: ${accountAddress}`, logs: [], }; } const logs = []; if (options.debug) { console.log(`[FiveSDK] Account fetched successfully:`); console.log(` - Address: ${accountAddress}`); console.log(` - Owner: ${accountInfo.owner.toString()}`); console.log(` - Lamports: ${accountInfo.lamports}`); console.log(` - Data length: ${accountInfo.data.length} bytes`); logs.push(`Account fetched: ${accountInfo.data.length} bytes`); logs.push(`Owner: ${accountInfo.owner.toString()}`); logs.push(`Balance: ${accountInfo.lamports / 1e9} SOL`); } const result = { success: true, accountInfo: { address: accountAddress, owner: accountInfo.owner.toString(), lamports: accountInfo.lamports, dataLength: accountInfo.data.length, }, logs, }; // Parse script metadata if requested if (options.parseMetadata) { try { const scriptMetadata = ScriptMetadataParser.parseMetadata(accountInfo.data, accountAddress); result.scriptMetadata = scriptMetadata; result.rawBytecode = scriptMetadata.bytecode; // Create VLE data structure with parsed information result.vleData = { header: { version: scriptMetadata.version, deployedAt: scriptMetadata.deployedAt, authority: scriptMetadata.authority, }, bytecode: scriptMetadata.bytecode, abi: scriptMetadata.abi, functions: scriptMetadata.abi.functions.map((func) => ({ name: func.name, index: func.index, parameters: func.parameters || [], })), }; if (options.debug) { console.log(`[FiveSDK] Script metadata parsed successfully:`); console.log(` - Script name: ${scriptMetadata.abi.name}`); console.log(` - Functions: ${scriptMetadata.abi.functions.length}`); console.log(` - Bytecode size: ${scriptMetadata.bytecode.length} bytes`); console.log(` - Authority: ${scriptMetadata.authority}`); logs.push(`Script metadata parsed: ${scriptMetadata.abi.functions.length} functions`); logs.push(`Bytecode: ${scriptMetadata.bytecode.length} bytes`); } } catch (metadataError) { if (options.debug) { console.warn(`[FiveSDK] Failed to parse script metadata:`, metadataError); } // Fallback: treat as raw bytecode without metadata result.rawBytecode = accountInfo.data; logs.push("Warning: Failed to parse script metadata, treating as raw data"); } } else { // Just return raw account data result.rawBytecode = accountInfo.data; logs.push("Raw account data returned (metadata parsing disabled)"); } // Validate VLE encoding if requested and we have bytecode if (options.validateVLE && result.rawBytecode) { try { const validation = await this.validateVLEEncoding(result.rawBytecode, options.debug); if (validation.valid) { logs.push("VLE encoding validation: PASSED"); if (options.debug) { console.log(`[FiveSDK] VLE validation passed: ${validation.info}`); } } else { logs.push(`VLE encoding validation: FAILED - ${validation.error}`); if (options.debug) { console.warn(`[FiveSDK] VLE validation failed: ${validation.error}`); } } } catch (vleError) { logs.push(`VLE validation error: ${vleError instanceof Error ? vleError.message : "Unknown error"}`); } } return result; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown account fetch error"; if (options.debug) { console.error(`[FiveSDK] Account fetch and VLE deserialization failed: ${errorMessage}`); } return { success: false, error: errorMessage, logs: [], }; } } /** * Batch fetch multiple accounts and deserialize their VLE data */ static async fetchMultipleAccountsAndDeserializeVLE(accountAddresses, connection, options = {}) { const batchSize = options.batchSize || 100; // Solana RPC limit const results = new Map(); if (options.debug) { console.log(`[FiveSDK] Batch fetching ${accountAddresses.length} accounts (batch size: ${batchSize})`); } // Process in batches to avoid RPC limits for (let i = 0; i < accountAddresses.length; i += batchSize) { const batch = accountAddresses.slice(i, i + batchSize); if (options.debug) { console.log(`[FiveSDK] Processing batch ${Math.floor(i / batchSize) + 1}/${Math.ceil(accountAddresses.length / batchSize)}`); } // Fetch each account in the batch concurrently const batchPromises = batch.map((address) => this.fetchAccountAndDeserializeVLE(address, connection, { debug: false, // Disable individual debug to avoid spam parseMetadata: options.parseMetadata, validateVLE: options.validateVLE, })); const batchResults = await Promise.allSettled(batchPromises); // Store results batch.forEach((address, index) => { const batchResult = batchResults[index]; if (batchResult.status === "fulfilled") { results.set(address, batchResult.value); } else { results.set(address, { success: false, error: `Batch processing failed: ${batchResult.reason}`, logs: [], }); } }); } if (options.debug) { const successful = Array.from(results.values()).filter((r) => r.success).length; console.log(`[FiveSDK] Batch processing completed: ${successful}/${accountAddresses.length} successful`); } return results; } /** * Deserialize VLE-encoded parameters from instruction data using WASM decoder */ static async deserializeVLEParameters(instructionData, expectedTypes = [], options = {}) { try { if (options.debug) { console.log(`[FiveSDK] Deserializing VLE parameters from ${instructionData.length} bytes:`); console.log(`[FiveSDK] Instruction data (hex):`, Buffer.from(instructionData).toString("hex")); console.log(`[FiveSDK] Expected parameter types:`, expectedTypes); } // Load WASM VM for VLE decoding const wasmVM = await this.loadWasmVM(); // Use WASM ParameterEncoder to decode VLE data try { const wasmModule = await import("../../assets/vm/five_vm_wasm.js"); if (options.debug) { console.log(`[FiveSDK] Using WASM ParameterEncoder for VLE decoding`); } // Decode the instruction data const decodedResult = wasmModule.ParameterEncoder.decode_vle_instruction(instructionData); if (options.debug) { console.log(`[FiveSDK] VLE decoding result:`, decodedResult); } // Parse the decoded result structure const parameters = []; if (decodedResult && decodedResult.parameters) { decodedResult.parameters.forEach((param, index) => { parameters.push({ type: expectedTypes[index] || "unknown", value: param, }); }); } return { success: true, parameters, functionIndex: decodedResult.function_index, discriminator: decodedResult.discriminator, }; } catch (wasmError) { if (options.debug) { console.warn(`[FiveSDK] WASM VLE decoding failed, attempting manual parsing:`, wasmError); } // Fallback: manual VLE parsing return this.parseVLEInstructionManually(instructionData, expectedTypes, options.debug); } } catch (error) { const errorMessage = error instanceof Error ? error.message : "Unknown VLE deserialization error"; if (options.debug) { console.error(`[FiveSDK] VLE parameter deserialization failed: ${errorMessage}`); } return { success: false, error: errorMessage, }; } } /** * Validate VLE encoding format in bytecode */ static async validateVLEEncoding(bytecode, debug = false) { try { // Check for Five VM bytecode header if (bytecode.length < 6) { return { valid: false, error: "Bytecode too short for Five VM format" }; } // Check for OptimizedHeader format: "5IVE" + features + function_count const magicBytes = bytecode.slice(0, 4); const expectedMagic = new Uint8Array([0x35, 0x49, 0x56, 0x45]); // "5IVE" let isValidHeader = true; for (let i = 0; i < 4; i++) { if (magicBytes[i] !== expectedMagic[i]) { isValidHeader = false; break; } } if (!isValidHeader) { return { valid: false, error: 'Invalid Five VM magic bytes (expected "5IVE")', }; } const features = bytecode[4]; const functionCount = bytecode[5]; if (debug) { console.log(`[FiveSDK] VLE validation - Magic: "5IVE", Features: ${features}, Functions: ${functionCount}`); } return { valid: true, info: `Valid Five VM bytecode with ${functionCount} functions (features: ${features})`, }; } catch (error) { return { valid: false, error: error instanceof Error ? error.message : "VLE validation error", }; } } /** * Manual VLE instruction parsing (fallback when WASM fails) */ static parseVLEInstructionManually(instructionData, expectedTypes, debug = false) { try { if (instructionData.length < 2) { return { success: false, error: "Instruction data too short" }; } let offset = 0; // Read discriminator const discriminator = instructionData[offset]; offset += 1; if (debug) { console.log(`[FiveSDK] Manual VLE parsing - Discriminator: ${discriminator}`); } // Read function index (VLE encoded) const { value: functionIndex, bytesRead } = this.readVLENumber(instructionData, offset); offset += bytesRead; if (debug) { console.log(`[FiveSDK] Manual VLE parsing - Function index: ${functionIndex}`); } // Read parameter count (VLE encoded) const { value: paramCount, bytesRead: paramCountBytes } = this.readVLENumber(instructionData, offset); offset += paramCountBytes; if (debug) { console.log(`[FiveSDK] Manual VLE parsing - Parameter count: ${paramCount}`); } // Read parameters const parameters = []; for (let i = 0; i < paramCount; i++) { const { value: paramValue, bytesRead: paramBytes } = this.readVLENumber(instructionData, offset); offset += paramBytes; parameters.push({ type: expectedTypes[i] || "u64", // Default to u64 value: paramValue, }); if (debug) { console.log(`[FiveSDK] Manual VLE parsing - Parameter ${i}: ${paramValue}`); } } return { success: true, parameters, functionIndex, discriminator, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Manual VLE parsing fa