@five-vm/cli
Version:
High-performance CLI for Five VM development with WebAssembly integration
1,166 lines • 117 kB
JavaScript
/**
* 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