@5ive-tech/sdk 1.1.8 → 1.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -52,6 +52,8 @@ export declare function generateExecuteInstruction(scriptAccount: string, functi
52
52
  isWritable: boolean;
53
53
  isSystemAccount?: boolean;
54
54
  }>;
55
+ feeShardIndex?: number;
56
+ payerAccount?: string;
55
57
  }): Promise<SerializedExecution>;
56
58
  export declare function executeOnSolana(scriptAccount: string, connection: any, signerKeypair: any, functionName: string | number, parameters?: any[], accounts?: string[], options?: {
57
59
  debug?: boolean;
@@ -63,6 +65,8 @@ export declare function executeOnSolana(scriptAccount: string, connection: any,
63
65
  vmStateAccount?: string;
64
66
  fiveVMProgramId?: string;
65
67
  abi?: any;
68
+ feeShardIndex?: number;
69
+ payerAccount?: string;
66
70
  }): Promise<{
67
71
  success: boolean;
68
72
  result?: any;
@@ -7,6 +7,45 @@ import { validator, Validators } from "../validation/index.js";
7
7
  import { calculateExecuteFee } from "./fees.js";
8
8
  import { loadWasmVM } from "../wasm/instance.js";
9
9
  import { ProgramIdResolver } from "../config/ProgramIdResolver.js";
10
+ const DEFAULT_FEE_VAULT_SHARD_COUNT = 10;
11
+ const FEE_VAULT_NAMESPACE_SEED = Buffer.from([
12
+ 0xff, 0x66, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x6d, 0x5f, 0x66, 0x65, 0x65,
13
+ 0x5f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x31,
14
+ ]);
15
+ const EXECUTE_FEE_HEADER_A = 0xff;
16
+ const EXECUTE_FEE_HEADER_B = 0x53;
17
+ async function deriveProgramFeeVault(programId, shardIndex) {
18
+ const { PublicKey } = await import("@solana/web3.js");
19
+ const [pda, bump] = PublicKey.findProgramAddressSync([FEE_VAULT_NAMESPACE_SEED, Buffer.from([shardIndex])], new PublicKey(programId));
20
+ return { address: pda.toBase58(), bump };
21
+ }
22
+ async function readVMStateShardCount(connection, vmStateAddress) {
23
+ if (!connection)
24
+ return DEFAULT_FEE_VAULT_SHARD_COUNT;
25
+ try {
26
+ const { PublicKey } = await import("@solana/web3.js");
27
+ const info = await connection.getAccountInfo(new PublicKey(vmStateAddress), "confirmed");
28
+ if (!info)
29
+ return DEFAULT_FEE_VAULT_SHARD_COUNT;
30
+ const data = new Uint8Array(info.data);
31
+ if (data.length <= 50)
32
+ return DEFAULT_FEE_VAULT_SHARD_COUNT;
33
+ const shardCount = data[50];
34
+ return shardCount > 0 ? shardCount : DEFAULT_FEE_VAULT_SHARD_COUNT;
35
+ }
36
+ catch {
37
+ return DEFAULT_FEE_VAULT_SHARD_COUNT;
38
+ }
39
+ }
40
+ function selectFeeShard(shardCount) {
41
+ const totalShards = Math.max(1, shardCount | 0);
42
+ if (typeof crypto !== "undefined" && typeof crypto.getRandomValues === "function") {
43
+ const bytes = new Uint32Array(1);
44
+ crypto.getRandomValues(bytes);
45
+ return bytes[0] % totalShards;
46
+ }
47
+ return Math.floor(Math.random() * totalShards);
48
+ }
10
49
  // Helper function to initialize ParameterEncoder if needed (though BytecodeEncoder is preferred)
11
50
  // Assume BytecodeEncoder handles it or call it if needed.
12
51
  // BytecodeEncoder uses WASM module directly via loader.
@@ -188,9 +227,9 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
188
227
  funcDef = Array.isArray(scriptMetadata.functions)
189
228
  ? scriptMetadata.functions.find((f) => f.index === functionIndex)
190
229
  : scriptMetadata.functions[functionIndex];
191
- const paramDefs = (funcDef.parameters || []);
192
- actualParamCount = paramDefs.length;
193
- encodedParams = await encodeParametersWithABI(parameters, funcDef, functionIndex, accounts, options);
230
+ const encoded = await encodeParametersWithABI(parameters, funcDef, functionIndex, accounts, options);
231
+ actualParamCount = encoded.paramCount;
232
+ encodedParams = encoded.encoded;
194
233
  }
195
234
  catch (metadataError) {
196
235
  if (options.debug) {
@@ -212,34 +251,18 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
212
251
  // Resolve program ID with consistent precedence
213
252
  const programId = ProgramIdResolver.resolve(options.fiveVMProgramId);
214
253
  const vmStatePDA = await PDAUtils.deriveVMStatePDA(programId);
215
- const vmState = options.vmStateAccount || vmStatePDA.address;
216
- let adminAccount = options.adminAccount;
217
- if (!adminAccount && connection) {
218
- try {
219
- let vmStateAddress = options.vmStateAccount;
220
- if (!vmStateAddress) {
221
- const pda = await PDAUtils.deriveVMStatePDA(programId);
222
- vmStateAddress = pda.address;
223
- }
224
- const { PublicKey } = await import("@solana/web3.js");
225
- const info = await connection.getAccountInfo(new PublicKey(vmStateAddress));
226
- if (info) {
227
- const data = new Uint8Array(info.data);
228
- if (data.length >= 32) {
229
- const authorityPubkey = new PublicKey(data.slice(0, 32));
230
- adminAccount = authorityPubkey.toBase58();
231
- }
232
- }
233
- }
234
- catch (error) {
235
- if (options.debug) {
236
- console.warn(`[FiveSDK] Failed to resolve admin account from VM state:`, error);
237
- }
238
- }
254
+ if (options.vmStateAccount && options.vmStateAccount !== vmStatePDA.address) {
255
+ throw new Error(`vmStateAccount must be canonical PDA ${vmStatePDA.address}; got ${options.vmStateAccount}`);
239
256
  }
257
+ const vmState = vmStatePDA.address;
258
+ const shardCount = await readVMStateShardCount(connection, vmState);
259
+ const feeShardIndex = options.feeShardIndex !== undefined
260
+ ? ((options.feeShardIndex % shardCount) + shardCount) % shardCount
261
+ : selectFeeShard(shardCount);
262
+ const feeVault = await deriveProgramFeeVault(programId, feeShardIndex);
240
263
  const instructionAccounts = [
241
264
  { pubkey: scriptAccount, isSigner: false, isWritable: false },
242
- { pubkey: vmState, isSigner: false, isWritable: true },
265
+ { pubkey: vmState, isSigner: false, isWritable: false },
243
266
  ];
244
267
  const abiAccountMetadata = new Map();
245
268
  if (funcDef && funcDef.parameters) {
@@ -285,12 +308,12 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
285
308
  }
286
309
  });
287
310
  }
288
- const userInstructionAccounts = accounts.map((acc, index) => {
311
+ const userInstructionAccounts = accounts.map((acc) => {
289
312
  // Check both derived ABI metadata and passed-in metadata (from FunctionBuilder)
290
313
  const abiMetadata = abiAccountMetadata.get(acc);
291
314
  const passedMetadata = options.accountMetadata?.get(acc);
292
315
  const metadata = abiMetadata || passedMetadata;
293
- const isSigner = metadata ? metadata.isSigner : (index === 0 && adminAccount ? true : false);
316
+ const isSigner = metadata ? metadata.isSigner : false;
294
317
  const isWritable = metadata ? metadata.isWritable : true;
295
318
  return {
296
319
  pubkey: acc,
@@ -299,20 +322,24 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
299
322
  };
300
323
  });
301
324
  instructionAccounts.push(...userInstructionAccounts);
302
- if (adminAccount) {
303
- const existingAdminIdx = instructionAccounts.findIndex(a => a.pubkey === adminAccount);
304
- if (existingAdminIdx === -1) {
305
- instructionAccounts.push({
306
- pubkey: adminAccount,
307
- isSigner: false,
308
- isWritable: true,
309
- });
310
- }
311
- else {
312
- instructionAccounts[existingAdminIdx].isWritable = true;
313
- }
325
+ const instructionData = encodeExecuteInstruction(functionIndex, encodedParams, actualParamCount, feeShardIndex, feeVault.bump);
326
+ // Runtime requires strict tail: [payer, fee_vault, system_program].
327
+ const signerCandidates = instructionAccounts
328
+ .filter((acc) => acc.isSigner)
329
+ .map((acc) => acc.pubkey);
330
+ const inferredPayer = options.payerAccount ||
331
+ (signerCandidates.length > 0
332
+ ? signerCandidates[signerCandidates.length - 1]
333
+ : accounts[0]);
334
+ if (!inferredPayer) {
335
+ throw new Error("Could not infer execute fee payer account. Provide a signer account or set options.payerAccount.");
314
336
  }
315
- const instructionData = encodeExecuteInstruction(functionIndex, encodedParams, actualParamCount);
337
+ const feeTailAccounts = [
338
+ { pubkey: inferredPayer, isSigner: true, isWritable: true },
339
+ { pubkey: feeVault.address, isSigner: false, isWritable: true },
340
+ { pubkey: "11111111111111111111111111111111", isSigner: false, isWritable: false },
341
+ ];
342
+ instructionAccounts.push(...feeTailAccounts);
316
343
  const result = {
317
344
  instruction: {
318
345
  programId: programId,
@@ -328,7 +355,7 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
328
355
  requiredSigners: [],
329
356
  estimatedComputeUnits: options.computeUnitLimit ||
330
357
  estimateComputeUnits(functionIndex, parameters.length),
331
- adminAccount: adminAccount,
358
+ adminAccount: feeVault.address,
332
359
  };
333
360
  const shouldEstimateFees = options.estimateFees !== false && connection;
334
361
  if (shouldEstimateFees) {
@@ -356,6 +383,8 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
356
383
  vmStateAccount: options.vmStateAccount,
357
384
  fiveVMProgramId: options.fiveVMProgramId,
358
385
  abi: options.abi,
386
+ feeShardIndex: options.feeShardIndex,
387
+ payerAccount: options.payerAccount || signerKeypair.publicKey.toString(),
359
388
  });
360
389
  }
361
390
  catch (metadataError) {
@@ -375,19 +404,12 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
375
404
  transaction.add(computePriceIx);
376
405
  }
377
406
  const accountKeys = [...executionData.instruction.accounts];
378
- if (options.vmStateAccount && accountKeys.length >= 2) {
379
- for (let i = 0; i < accountKeys.length; i++) {
380
- if (i === 1) {
381
- accountKeys[i].pubkey = options.vmStateAccount;
382
- break;
383
- }
384
- }
385
- }
386
407
  const signerPubkey = signerKeypair.publicKey.toString();
387
408
  let signerFound = false;
388
409
  for (const meta of accountKeys) {
389
410
  if (meta.pubkey === signerPubkey) {
390
411
  meta.isSigner = true;
412
+ meta.isWritable = true;
391
413
  signerFound = true;
392
414
  }
393
415
  }
@@ -537,9 +559,15 @@ export async function executeScriptAccount(scriptAccount, functionIndex = 0, par
537
559
  });
538
560
  }
539
561
  // Helpers
540
- function encodeExecuteInstruction(functionIndex, encodedParams, paramCount) {
562
+ function encodeExecuteInstruction(functionIndex, encodedParams, paramCount, feeShardIndex, feeVaultBump) {
541
563
  const parts = [];
542
564
  parts.push(new Uint8Array([9]));
565
+ parts.push(new Uint8Array([
566
+ EXECUTE_FEE_HEADER_A,
567
+ EXECUTE_FEE_HEADER_B,
568
+ feeShardIndex & 0xff,
569
+ feeVaultBump & 0xff,
570
+ ]));
543
571
  // Function index as fixed u32
544
572
  parts.push(encodeU32(functionIndex));
545
573
  // Param count as fixed u32
@@ -582,7 +610,7 @@ function inferParameterType(value) {
582
610
  return "string";
583
611
  }
584
612
  }
585
- async function encodeParametersWithABI(parameters, functionDef, functionIndex, accounts = [], options = {}) {
613
+ async function encodeParametersWithABI(parameters, functionDef, functionIndex, _accounts = [], options = {}) {
586
614
  const isAccountParam = (param) => {
587
615
  if (!param)
588
616
  return false;
@@ -598,51 +626,33 @@ async function encodeParametersWithABI(parameters, functionDef, functionIndex, a
598
626
  return type === 'pubkey';
599
627
  };
600
628
  const paramDefs = (functionDef.parameters || []);
601
- if (parameters.length !== paramDefs.length) {
629
+ const nonAccountParamDefs = paramDefs.filter((param) => !isAccountParam(param));
630
+ const fullParameterListProvided = parameters.length >= paramDefs.length;
631
+ if (fullParameterListProvided && parameters.length !== paramDefs.length) {
602
632
  console.warn(`[FiveSDK] Parameter validation warning: Function '${functionDef.name}' expects ${paramDefs.length} parameters, but received ${parameters.length}.`);
603
633
  }
604
634
  const paramValues = {};
605
- paramDefs.forEach((param, index) => {
606
- if (index < parameters.length) {
607
- let value = parameters[index];
608
- if (isAccountParam(param)) {
609
- let accountPubkey = null;
610
- if (value && typeof value === 'object' && typeof value.toBase58 === 'function') {
611
- accountPubkey = value.toBase58();
612
- }
613
- else if (typeof value === 'string') {
614
- accountPubkey = value;
615
- }
616
- else if (typeof value === 'number') {
617
- if (value >= 0 && value < accounts.length) {
618
- accountPubkey = accounts[value];
619
- }
620
- else {
621
- throw new Error(`Account index ${value} out of bounds`);
622
- }
623
- }
624
- if (accountPubkey) {
625
- const accountIndex = accounts.indexOf(accountPubkey);
626
- if (accountIndex >= 0) {
627
- // MitoVM receives accounts excluding the script account.
628
- // Account index 0 is the VM state account.
629
- value = accountIndex + 1;
630
- }
631
- else {
632
- throw new Error(`Account ${accountPubkey} not found in accounts array`);
633
- }
634
- }
635
- }
636
- else if (isPubkeyParam(param)) {
637
- if (value && typeof value === 'object' && typeof value.toBase58 === 'function') {
638
- value = value.toBase58();
639
- }
635
+ let argCursor = 0;
636
+ for (let index = 0; index < paramDefs.length; index++) {
637
+ const param = paramDefs[index];
638
+ if (isAccountParam(param)) {
639
+ continue;
640
+ }
641
+ const sourceIndex = fullParameterListProvided ? index : argCursor;
642
+ if (sourceIndex >= parameters.length) {
643
+ throw new Error(`Missing value for parameter: ${param.name}`);
644
+ }
645
+ let value = parameters[sourceIndex];
646
+ if (isPubkeyParam(param)) {
647
+ if (value && typeof value === 'object' && typeof value.toBase58 === 'function') {
648
+ value = value.toBase58();
640
649
  }
641
- paramValues[param.name] = value;
642
650
  }
643
- });
644
- const encoded = await BytecodeEncoder.encodeExecute(functionIndex, paramDefs, paramValues, true, options);
645
- return encoded;
651
+ paramValues[param.name] = value;
652
+ argCursor += 1;
653
+ }
654
+ const encoded = await BytecodeEncoder.encodeExecute(functionIndex, nonAccountParamDefs, paramValues, true, options);
655
+ return { encoded, paramCount: nonAccountParamDefs.length };
646
656
  }
647
657
  function estimateComputeUnits(functionIndex, parameterCount) {
648
658
  return Math.max(5000, 1000 + parameterCount * 500 + functionIndex * 100);
@@ -1,5 +1,7 @@
1
1
  import { FeeInformation } from "../types.js";
2
2
  export declare function getFees(connection: any, fiveVMProgramId?: string): Promise<{
3
+ deployFeeLamports: number;
4
+ executeFeeLamports: number;
3
5
  deployFeeBps: number;
4
6
  executeFeeBps: number;
5
7
  adminAccount: string | null;
@@ -4,16 +4,20 @@ export async function getFees(connection, fiveVMProgramId) {
4
4
  try {
5
5
  const state = await getVMState(connection, fiveVMProgramId);
6
6
  return {
7
- deployFeeBps: state.deployFeeBps,
8
- executeFeeBps: state.executeFeeBps,
9
- adminAccount: state.authority
7
+ deployFeeLamports: state.deployFeeLamports,
8
+ executeFeeLamports: state.executeFeeLamports,
9
+ deployFeeBps: state.deployFeeLamports,
10
+ executeFeeBps: state.executeFeeLamports,
11
+ adminAccount: state.authority,
10
12
  };
11
13
  }
12
14
  catch (error) {
13
15
  return {
16
+ deployFeeLamports: 0,
17
+ executeFeeLamports: 0,
14
18
  deployFeeBps: 0,
15
19
  executeFeeBps: 0,
16
- adminAccount: null
20
+ adminAccount: null,
17
21
  };
18
22
  }
19
23
  }
@@ -22,17 +26,16 @@ export async function calculateDeployFee(bytecodeSize, connection, fiveVMProgram
22
26
  const accountSize = 64 + bytecodeSize;
23
27
  const rentLamports = await RentCalculator.calculateRentExemption(accountSize);
24
28
  const vmState = await getVMState(connection, fiveVMProgramId);
25
- const deployFeeBps = vmState.deployFeeBps;
26
- const feeLamports = Math.floor((rentLamports * deployFeeBps) / 10000);
29
+ const deployFeeLamports = vmState.deployFeeLamports;
27
30
  return {
28
- feeBps: deployFeeBps,
31
+ feeBps: 0,
29
32
  basisLamports: rentLamports,
30
- feeLamports,
31
- totalEstimatedCost: rentLamports + feeLamports,
33
+ feeLamports: deployFeeLamports,
34
+ totalEstimatedCost: rentLamports + deployFeeLamports,
32
35
  costBreakdown: {
33
36
  basis: RentCalculator.formatSOL(rentLamports),
34
- fee: RentCalculator.formatSOL(feeLamports),
35
- total: RentCalculator.formatSOL(rentLamports + feeLamports),
37
+ fee: RentCalculator.formatSOL(deployFeeLamports),
38
+ total: RentCalculator.formatSOL(rentLamports + deployFeeLamports),
36
39
  },
37
40
  };
38
41
  }
@@ -56,17 +59,16 @@ export async function calculateExecuteFee(connection, fiveVMProgramId) {
56
59
  const STANDARD_TX_FEE = 5000;
57
60
  try {
58
61
  const vmState = await getVMState(connection, fiveVMProgramId);
59
- const executeFeeBps = vmState.executeFeeBps;
60
- const feeLamports = Math.floor((STANDARD_TX_FEE * executeFeeBps) / 10000);
62
+ const executeFeeLamports = vmState.executeFeeLamports;
61
63
  return {
62
- feeBps: executeFeeBps,
64
+ feeBps: 0,
63
65
  basisLamports: STANDARD_TX_FEE,
64
- feeLamports,
65
- totalEstimatedCost: STANDARD_TX_FEE + feeLamports,
66
+ feeLamports: executeFeeLamports,
67
+ totalEstimatedCost: STANDARD_TX_FEE + executeFeeLamports,
66
68
  costBreakdown: {
67
69
  basis: RentCalculator.formatSOL(STANDARD_TX_FEE),
68
- fee: RentCalculator.formatSOL(feeLamports),
69
- total: RentCalculator.formatSOL(STANDARD_TX_FEE + feeLamports),
70
+ fee: RentCalculator.formatSOL(executeFeeLamports),
71
+ total: RentCalculator.formatSOL(STANDARD_TX_FEE + executeFeeLamports),
70
72
  },
71
73
  };
72
74
  }
@@ -91,7 +93,7 @@ export async function getFeeInformation(bytecodeSize, connection, fiveVMProgramI
91
93
  calculateExecuteFee(connection, fiveVMProgramId),
92
94
  getVMState(connection, fiveVMProgramId),
93
95
  ]);
94
- const feesEnabled = vmState.deployFeeBps > 0 || vmState.executeFeeBps > 0;
96
+ const feesEnabled = vmState.deployFeeLamports > 0 || vmState.executeFeeLamports > 0;
95
97
  return {
96
98
  deploy: deployFee,
97
99
  execute: executeFee,
@@ -1,7 +1,11 @@
1
1
  export declare function getVMState(connection: any, fiveVMProgramId?: string): Promise<{
2
2
  authority: string;
3
3
  scriptCount: number;
4
+ deployFeeLamports: number;
5
+ executeFeeLamports: number;
4
6
  deployFeeBps: number;
5
7
  executeFeeBps: number;
8
+ feeVaultShardCount: number;
9
+ vmStateBump: number;
6
10
  isInitialized: boolean;
7
11
  }>;
@@ -30,11 +30,18 @@ export async function getVMState(connection, fiveVMProgramId) {
30
30
  throw new Error(`VM State account data too small: expected 56, got ${accountData.length}`);
31
31
  const authority = Base58Utils.encode(accountData.slice(0, 32));
32
32
  const view = new DataView(accountData.buffer, accountData.byteOffset, accountData.byteLength);
33
+ const deployFeeLamports = view.getUint32(40, true);
34
+ const executeFeeLamports = view.getUint32(44, true);
33
35
  return {
34
36
  authority,
35
37
  scriptCount: Number(view.getBigUint64(32, true)),
36
- deployFeeBps: view.getUint32(40, true),
37
- executeFeeBps: view.getUint32(44, true),
38
+ deployFeeLamports,
39
+ executeFeeLamports,
40
+ // Backward-compatible aliases; deprecated.
41
+ deployFeeBps: deployFeeLamports,
42
+ executeFeeBps: executeFeeLamports,
43
+ feeVaultShardCount: accountData[50] || 10,
44
+ vmStateBump: accountData[51] || 0,
38
45
  isInitialized: accountData[48] === 1
39
46
  };
40
47
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@5ive-tech/sdk",
3
- "version": "1.1.8",
3
+ "version": "1.1.9",
4
4
  "description": "Client-agnostic TypeScript SDK for Five VM scripts on Solana",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,6 +36,7 @@
36
36
  "copy-assets": "mkdir -p dist/assets/vm && COPYFILE_DISABLE=1 cp src/assets/vm/* dist/assets/vm/",
37
37
  "test": "node examples/basic-usage.js",
38
38
  "test:jest": "NODE_OPTIONS=--experimental-vm-modules jest --runInBand",
39
+ "test:localnet": "USE_SOLANA_MOCKS=0 RUN_LOCALNET_VALIDATOR_TESTS=1 NODE_OPTIONS=--experimental-vm-modules jest --runInBand src/__tests__/integration/localnet-fee-vault.test.ts",
39
40
  "gen-types": "node ./node_modules/typescript/bin/tsc && node dist/bin/gen-types.js",
40
41
  "prepublishOnly": "npm run build"
41
42
  },
@@ -56,4 +57,4 @@
56
57
  "publishConfig": {
57
58
  "access": "public"
58
59
  }
59
- }
60
+ }
File without changes