@5ive-tech/sdk 1.1.10 → 1.1.13

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.
Files changed (36) hide show
  1. package/README.md +1 -1
  2. package/dist/FiveSDK.d.ts +3 -4
  3. package/dist/FiveSDK.js +47 -5
  4. package/dist/accounts/index.js +3 -2
  5. package/dist/assets/vm/five_vm_wasm_bg.wasm +0 -0
  6. package/dist/assets/vm/five_vm_wasm_bg.wasm.d.ts +5 -5
  7. package/dist/bin/gen-types.js +0 -0
  8. package/dist/compiler/BytecodeCompiler.d.ts +1 -7
  9. package/dist/compiler/BytecodeCompiler.js +105 -44
  10. package/dist/config/ProgramIdResolver.d.ts +1 -6
  11. package/dist/config/ProgramIdResolver.js +11 -16
  12. package/dist/config/VmClusterConfigResolver.d.ts +27 -0
  13. package/dist/config/VmClusterConfigResolver.js +111 -0
  14. package/dist/index.d.ts +1 -1
  15. package/dist/index.js +1 -1
  16. package/dist/modules/accounts.js +7 -1
  17. package/dist/modules/admin.js +25 -12
  18. package/dist/modules/deploy.js +62 -23
  19. package/dist/modules/execute.js +71 -37
  20. package/dist/modules/vm-state.js +6 -1
  21. package/dist/program/FunctionBuilder.d.ts +8 -0
  22. package/dist/program/FunctionBuilder.js +18 -5
  23. package/dist/project/config.js +0 -1
  24. package/dist/testing/TestRunner.js +6 -1
  25. package/dist/types.d.ts +19 -2
  26. package/dist/utils/abi.d.ts +4 -0
  27. package/dist/utils/abi.js +3 -0
  28. package/dist/utils/transaction.d.ts +22 -0
  29. package/dist/utils/transaction.js +94 -12
  30. package/dist/wasm/compiler/CompilationLogic.d.ts +0 -5
  31. package/dist/wasm/compiler/CompilationLogic.js +45 -163
  32. package/dist/wasm/compiler/FiveCompiler.d.ts +0 -5
  33. package/dist/wasm/compiler/FiveCompiler.js +0 -6
  34. package/dist/wasm/compiler/utils.d.ts +34 -1
  35. package/dist/wasm/compiler/utils.js +223 -5
  36. package/package.json +4 -3
@@ -1,5 +1,6 @@
1
1
  import { validator } from "../validation/index.js";
2
2
  import { ProgramIdResolver } from "../config/ProgramIdResolver.js";
3
+ import { confirmTransactionRobust } from "../utils/transaction.js";
3
4
  import bs58 from "bs58";
4
5
  const VM_STATE_SEED = Buffer.from("vm_state", "utf8");
5
6
  const FEE_VAULT_NAMESPACE_SEED = Buffer.from([
@@ -205,9 +206,12 @@ export async function initializeVmStateOnSolana(connection, authorityKeypair, op
205
206
  preflightCommitment: "confirmed",
206
207
  maxRetries: options.maxRetries || 3,
207
208
  });
208
- const confirmation = await connection.confirmTransaction(signature, "confirmed");
209
- if (confirmation?.value?.err) {
210
- return { success: false, transactionId: signature, vmStateAccount: generated.vmStateAccount, error: JSON.stringify(confirmation.value.err) };
209
+ const confirmation = await confirmTransactionRobust(connection, signature, {
210
+ commitment: "finalized",
211
+ timeoutMs: 120000,
212
+ });
213
+ if (!confirmation.success) {
214
+ return { success: false, transactionId: signature, vmStateAccount: generated.vmStateAccount, error: confirmation.error || "confirmation failed" };
211
215
  }
212
216
  return { success: true, transactionId: signature, vmStateAccount: generated.vmStateAccount };
213
217
  }
@@ -235,9 +239,12 @@ export async function setFeesOnSolana(connection, authorityKeypair, deployFeeLam
235
239
  preflightCommitment: "confirmed",
236
240
  maxRetries: options.maxRetries || 3,
237
241
  });
238
- const confirmation = await connection.confirmTransaction(signature, "confirmed");
239
- if (confirmation?.value?.err) {
240
- return { success: false, transactionId: signature, vmStateAccount: generated.vmStateAccount, error: JSON.stringify(confirmation.value.err) };
242
+ const confirmation = await confirmTransactionRobust(connection, signature, {
243
+ commitment: "finalized",
244
+ timeoutMs: 120000,
245
+ });
246
+ if (!confirmation.success) {
247
+ return { success: false, transactionId: signature, vmStateAccount: generated.vmStateAccount, error: confirmation.error || "confirmation failed" };
241
248
  }
242
249
  return { success: true, transactionId: signature, vmStateAccount: generated.vmStateAccount };
243
250
  }
@@ -266,14 +273,17 @@ export async function initFeeVaultOnSolana(connection, payerKeypair, shardIndex,
266
273
  preflightCommitment: "confirmed",
267
274
  maxRetries: options.maxRetries || 3,
268
275
  });
269
- const confirmation = await connection.confirmTransaction(signature, "confirmed");
270
- if (confirmation?.value?.err) {
276
+ const confirmation = await confirmTransactionRobust(connection, signature, {
277
+ commitment: "finalized",
278
+ timeoutMs: 120000,
279
+ });
280
+ if (!confirmation.success) {
271
281
  return {
272
282
  success: false,
273
283
  transactionId: signature,
274
284
  vmStateAccount: generated.vmStateAccount,
275
285
  feeVaultAccount: generated.feeVaultAccount,
276
- error: JSON.stringify(confirmation.value.err),
286
+ error: confirmation.error || "confirmation failed",
277
287
  };
278
288
  }
279
289
  return {
@@ -308,14 +318,17 @@ export async function withdrawScriptFeesOnSolana(connection, authorityKeypair, r
308
318
  preflightCommitment: "confirmed",
309
319
  maxRetries: options.maxRetries || 3,
310
320
  });
311
- const confirmation = await connection.confirmTransaction(signature, "confirmed");
312
- if (confirmation?.value?.err) {
321
+ const confirmation = await confirmTransactionRobust(connection, signature, {
322
+ commitment: "finalized",
323
+ timeoutMs: 120000,
324
+ });
325
+ if (!confirmation.success) {
313
326
  return {
314
327
  success: false,
315
328
  transactionId: signature,
316
329
  vmStateAccount: generated.vmStateAccount,
317
330
  feeVaultAccount: generated.feeVaultAccount,
318
- error: JSON.stringify(confirmation.value.err),
331
+ error: confirmation.error || "confirmation failed",
319
332
  };
320
333
  }
321
334
  return {
@@ -1,20 +1,36 @@
1
1
  import { PDAUtils, RentCalculator } from "../crypto/index.js";
2
2
  import { validator, Validators } from "../validation/index.js";
3
3
  import { calculateDeployFee } from "./fees.js";
4
- import { pollForConfirmation } from "../utils/transaction.js";
4
+ import { confirmTransactionRobust, getAccountInfoWithRetry, pollForConfirmation, SDK_COMMITMENTS, } from "../utils/transaction.js";
5
5
  import { ProgramIdResolver } from "../config/ProgramIdResolver.js";
6
- const DEFAULT_FEE_VAULT_SHARD_COUNT = 10;
6
+ import { VmClusterConfigResolver } from "../config/VmClusterConfigResolver.js";
7
+ const DEFAULT_FEE_VAULT_SHARD_COUNT = (() => {
8
+ try {
9
+ return VmClusterConfigResolver.loadClusterConfig().feeVaultShardCount;
10
+ }
11
+ catch {
12
+ return 2;
13
+ }
14
+ })();
7
15
  const FEE_VAULT_NAMESPACE_SEED = Buffer.from([
8
16
  0xff, 0x66, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x6d, 0x5f, 0x66, 0x65, 0x65,
9
17
  0x5f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x31,
10
18
  ]);
19
+ function clampShardCount(rawCount) {
20
+ const normalized = rawCount > 0 ? rawCount : DEFAULT_FEE_VAULT_SHARD_COUNT;
21
+ return Math.max(1, Math.min(DEFAULT_FEE_VAULT_SHARD_COUNT, normalized));
22
+ }
11
23
  async function readVMStateFeeConfig(connection, vmStateAddress) {
12
24
  if (!connection) {
13
25
  return { shardCount: DEFAULT_FEE_VAULT_SHARD_COUNT };
14
26
  }
15
27
  try {
16
28
  const { PublicKey } = await import("@solana/web3.js");
17
- const info = await connection.getAccountInfo(new PublicKey(vmStateAddress), "confirmed");
29
+ const info = await getAccountInfoWithRetry(connection, new PublicKey(vmStateAddress), {
30
+ commitment: SDK_COMMITMENTS.READ,
31
+ retries: 2,
32
+ delayMs: 700,
33
+ });
18
34
  if (!info) {
19
35
  return { shardCount: DEFAULT_FEE_VAULT_SHARD_COUNT };
20
36
  }
@@ -25,7 +41,7 @@ async function readVMStateFeeConfig(connection, vmStateAddress) {
25
41
  const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
26
42
  const deployFeeLamports = view.getUint32(40, true);
27
43
  const shardCountRaw = data.length > 50 ? data[50] : 0;
28
- const shardCount = shardCountRaw > 0 ? shardCountRaw : DEFAULT_FEE_VAULT_SHARD_COUNT;
44
+ const shardCount = clampShardCount(shardCountRaw);
29
45
  return { deployFeeLamports, shardCount };
30
46
  }
31
47
  catch {
@@ -43,7 +59,8 @@ function createInitFeeVaultInstructionData(shardIndex, bump) {
43
59
  async function initProgramFeeVaultShards(connection, programId, vmStateAccount, shardCount, payer, options = {}) {
44
60
  const { PublicKey, Transaction, TransactionInstruction, SystemProgram } = await import("@solana/web3.js");
45
61
  const signatures = [];
46
- for (let shardIndex = 0; shardIndex < shardCount; shardIndex++) {
62
+ const effectiveShardCount = clampShardCount(shardCount);
63
+ for (let shardIndex = 0; shardIndex < effectiveShardCount; shardIndex++) {
47
64
  const vault = await deriveProgramFeeVault(programId, shardIndex);
48
65
  const tx = new Transaction().add(new TransactionInstruction({
49
66
  programId: new PublicKey(programId),
@@ -64,7 +81,14 @@ async function initProgramFeeVaultShards(connection, programId, vmStateAccount,
64
81
  preflightCommitment: "confirmed",
65
82
  maxRetries: options.maxRetries || 3,
66
83
  });
67
- await connection.confirmTransaction(sig, "confirmed");
84
+ const shardConfirm = await confirmTransactionRobust(connection, sig, {
85
+ commitment: "finalized",
86
+ timeoutMs: 120000,
87
+ debug: options.debug,
88
+ });
89
+ if (!shardConfirm.success) {
90
+ throw new Error(`Fee vault shard init failed: ${shardConfirm.error || "unconfirmed"}`);
91
+ }
68
92
  signatures.push(sig);
69
93
  if (options.debug) {
70
94
  console.log(`[FiveSDK] Initialized fee vault shard ${shardIndex}: ${vault.address}`);
@@ -192,7 +216,7 @@ options = {}) {
192
216
  }));
193
217
  }
194
218
  // 1. Initialize canonical VM State if missing
195
- const vmStateInfo = await connection.getAccountInfo(vmStatePubkey);
219
+ const vmStateInfo = await connection.getAccountInfo(vmStatePubkey, "finalized");
196
220
  if (!vmStateInfo) {
197
221
  tx.add(new TransactionInstruction({
198
222
  keys: [
@@ -530,7 +554,14 @@ options = {}) {
530
554
  preflightCommitment: "confirmed",
531
555
  maxRetries: options.maxRetries || 3,
532
556
  });
533
- await connection.confirmTransaction(initSignature, "confirmed");
557
+ const initConfirm = await confirmTransactionRobust(connection, initSignature, {
558
+ commitment: "finalized",
559
+ timeoutMs: 120000,
560
+ debug: options.debug,
561
+ });
562
+ if (!initConfirm.success) {
563
+ throw new Error(`Initialization confirmation failed: ${initConfirm.error || "unconfirmed"}`);
564
+ }
534
565
  transactionIds.push(initSignature);
535
566
  if (options.debug) {
536
567
  console.log(`[FiveSDK] ✅ Initialization completed: ${initSignature}`);
@@ -549,16 +580,9 @@ options = {}) {
549
580
  console.log(`[FiveSDK] Step ${i + 2}: Appending chunk ${i + 1}/${chunks.length} (${chunk.length} bytes)`);
550
581
  }
551
582
  // Calculate additional rent needed for this chunk
552
- let currentInfo = await connection.getAccountInfo(scriptKeypair.publicKey);
553
- // Retry logic for account info if null (eventual consistency)
554
- if (!currentInfo) {
555
- if (options.debug)
556
- console.log(`[FiveSDK] Account info null, retrying...`);
557
- await new Promise(resolve => setTimeout(resolve, 1000));
558
- currentInfo = await connection.getAccountInfo(scriptKeypair.publicKey);
559
- if (!currentInfo)
560
- throw new Error("Script account not found after initialization");
561
- }
583
+ const currentInfo = await getAccountInfoWithRetry(connection, scriptKeypair.publicKey, { commitment: "finalized", retries: 2, delayMs: 1000, debug: options.debug });
584
+ if (!currentInfo)
585
+ throw new Error("Script account not found after initialization");
562
586
  const newSize = currentInfo.data.length + chunk.length;
563
587
  const newRentRequired = await connection.getMinimumBalanceForRentExemption(newSize);
564
588
  const additionalRent = Math.max(0, newRentRequired - currentInfo.lamports);
@@ -627,14 +651,29 @@ options = {}) {
627
651
  preflightCommitment: "confirmed",
628
652
  maxRetries: options.maxRetries || 3,
629
653
  });
630
- await connection.confirmTransaction(appendSignature, "confirmed");
654
+ const appendConfirm = await confirmTransactionRobust(connection, appendSignature, {
655
+ commitment: "finalized",
656
+ timeoutMs: 120000,
657
+ debug: options.debug,
658
+ });
659
+ if (!appendConfirm.success) {
660
+ throw new Error(`Append confirmation failed: ${appendConfirm.error || "unconfirmed"}`);
661
+ }
631
662
  transactionIds.push(appendSignature);
632
663
  if (options.debug) {
633
664
  console.log(`[FiveSDK] ✅ Chunk ${i + 1} appended: ${appendSignature}`);
634
665
  }
635
666
  }
636
667
  // Final verification
637
- const finalInfo = await connection.getAccountInfo(scriptKeypair.publicKey);
668
+ let finalInfo = await getAccountInfoWithRetry(connection, scriptKeypair.publicKey, {
669
+ commitment: "finalized",
670
+ retries: 2,
671
+ delayMs: 1000,
672
+ debug: options.debug,
673
+ });
674
+ if (!finalInfo) {
675
+ throw new Error("Script account not found during final verification");
676
+ }
638
677
  const expectedSize = SCRIPT_HEADER_SIZE + bytecode.length;
639
678
  if (options.debug) {
640
679
  console.log(`[FiveSDK] 🔍 Final verification:`);
@@ -882,7 +921,7 @@ options = {}) {
882
921
  preflightCommitment: "confirmed",
883
922
  maxRetries: options.maxRetries || 3,
884
923
  });
885
- const appendConfirmation = await pollForConfirmation(connection, appendSignature, "confirmed", 120000, options.debug);
924
+ const appendConfirmation = await pollForConfirmation(connection, appendSignature, "finalized", 120000, options.debug);
886
925
  if (!appendConfirmation.success) {
887
926
  return {
888
927
  success: false,
@@ -929,7 +968,7 @@ options = {}) {
929
968
  maxRetries: options.maxRetries || 3,
930
969
  });
931
970
  // Use custom polling for finalize to handle validator latency
932
- const finalizeConfirmation = await pollForConfirmation(connection, finalizeSignature, "confirmed", 120000, // 120 second timeout
971
+ const finalizeConfirmation = await pollForConfirmation(connection, finalizeSignature, "finalized", 120000, // 120 second timeout
933
972
  options.debug);
934
973
  if (!finalizeConfirmation.success) {
935
974
  console.error(`[FiveSDK] FinalizeScript confirmation failed: ${finalizeConfirmation.error}`);
@@ -1056,7 +1095,7 @@ async function ensureCanonicalVmStateAccount(connection, deployerKeypair, progra
1056
1095
  throw new Error(`vmStateAccount must be canonical PDA ${canonical.address}; got ${options.vmStateAccount}`);
1057
1096
  }
1058
1097
  const vmStatePubkey = new PublicKey(canonical.address);
1059
- const existing = await connection.getAccountInfo(vmStatePubkey);
1098
+ const existing = await connection.getAccountInfo(vmStatePubkey, "finalized");
1060
1099
  if (existing) {
1061
1100
  if (existing.owner.toBase58() !== programId.toBase58()) {
1062
1101
  throw new Error(`canonical VM state ${canonical.address} exists but is owned by ${existing.owner.toBase58()}, expected ${programId.toBase58()}`);
@@ -7,13 +7,26 @@ 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;
10
+ import { VmClusterConfigResolver } from "../config/VmClusterConfigResolver.js";
11
+ import { confirmTransactionRobust, getAccountInfoWithRetry, } from "../utils/transaction.js";
12
+ const DEFAULT_FEE_VAULT_SHARD_COUNT = (() => {
13
+ try {
14
+ return VmClusterConfigResolver.loadClusterConfig().feeVaultShardCount;
15
+ }
16
+ catch {
17
+ return 2;
18
+ }
19
+ })();
11
20
  const FEE_VAULT_NAMESPACE_SEED = Buffer.from([
12
21
  0xff, 0x66, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x6d, 0x5f, 0x66, 0x65, 0x65,
13
22
  0x5f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x31,
14
23
  ]);
15
24
  const EXECUTE_FEE_HEADER_A = 0xff;
16
25
  const EXECUTE_FEE_HEADER_B = 0x53;
26
+ function clampShardCount(rawCount) {
27
+ const normalized = rawCount > 0 ? rawCount : DEFAULT_FEE_VAULT_SHARD_COUNT;
28
+ return Math.max(1, Math.min(DEFAULT_FEE_VAULT_SHARD_COUNT, normalized));
29
+ }
17
30
  async function deriveProgramFeeVault(programId, shardIndex) {
18
31
  const { PublicKey } = await import("@solana/web3.js");
19
32
  const [pda, bump] = PublicKey.findProgramAddressSync([FEE_VAULT_NAMESPACE_SEED, Buffer.from([shardIndex])], new PublicKey(programId));
@@ -24,14 +37,17 @@ async function readVMStateShardCount(connection, vmStateAddress) {
24
37
  return DEFAULT_FEE_VAULT_SHARD_COUNT;
25
38
  try {
26
39
  const { PublicKey } = await import("@solana/web3.js");
27
- const info = await connection.getAccountInfo(new PublicKey(vmStateAddress), "confirmed");
40
+ const info = await getAccountInfoWithRetry(connection, new PublicKey(vmStateAddress), {
41
+ commitment: "finalized",
42
+ retries: 2,
43
+ delayMs: 1000,
44
+ });
28
45
  if (!info)
29
46
  return DEFAULT_FEE_VAULT_SHARD_COUNT;
30
47
  const data = new Uint8Array(info.data);
31
48
  if (data.length <= 50)
32
49
  return DEFAULT_FEE_VAULT_SHARD_COUNT;
33
- const shardCount = data[50];
34
- return shardCount > 0 ? shardCount : DEFAULT_FEE_VAULT_SHARD_COUNT;
50
+ return clampShardCount(data[50]);
35
51
  }
36
52
  catch {
37
53
  return DEFAULT_FEE_VAULT_SHARD_COUNT;
@@ -265,7 +281,10 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
265
281
  { pubkey: vmState, isSigner: false, isWritable: false },
266
282
  ];
267
283
  const abiAccountMetadata = new Map();
268
- if (funcDef && funcDef.parameters) {
284
+ const hasFullParameterList = !!funcDef &&
285
+ Array.isArray(funcDef.parameters) &&
286
+ parameters.length === funcDef.parameters.length;
287
+ if (funcDef && funcDef.parameters && hasFullParameterList) {
269
288
  // First pass: detect if there's an @init constraint and find the payer
270
289
  let hasInit = false;
271
290
  let payerPubkey;
@@ -312,9 +331,11 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
312
331
  // Check both derived ABI metadata and passed-in metadata (from FunctionBuilder)
313
332
  const abiMetadata = abiAccountMetadata.get(acc);
314
333
  const passedMetadata = options.accountMetadata?.get(acc);
315
- const metadata = abiMetadata || passedMetadata;
334
+ const metadata = passedMetadata || abiMetadata;
316
335
  const isSigner = metadata ? metadata.isSigner : false;
317
- const isWritable = metadata ? metadata.isWritable : true;
336
+ const isWritable = metadata
337
+ ? (metadata.isSystemAccount ? false : metadata.isWritable)
338
+ : true;
318
339
  return {
319
340
  pubkey: acc,
320
341
  isSigner,
@@ -322,7 +343,7 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
322
343
  };
323
344
  });
324
345
  instructionAccounts.push(...userInstructionAccounts);
325
- const instructionData = encodeExecuteInstruction(functionIndex, encodedParams, actualParamCount, feeShardIndex, feeVault.bump);
346
+ const instructionData = encodeExecuteInstruction(functionIndex, encodedParams, actualParamCount, feeShardIndex, options.debug === true);
326
347
  // Runtime requires strict tail: [payer, fee_vault, system_program].
327
348
  const signerCandidates = instructionAccounts
328
349
  .filter((acc) => acc.isSigner)
@@ -444,15 +465,15 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
444
465
  maxRetries: options.maxRetries || 3,
445
466
  });
446
467
  lastSignature = signature;
447
- let confirmation;
448
- try {
449
- confirmation = await connection.confirmTransaction({
450
- signature,
451
- blockhash,
452
- lastValidBlockHeight: (await connection.getLatestBlockhash("confirmed")).lastValidBlockHeight,
453
- }, "confirmed");
454
- }
455
- catch (confirmError) {
468
+ const latestBlockhash = await connection.getLatestBlockhash("confirmed");
469
+ const confirmation = await confirmTransactionRobust(connection, signature, {
470
+ commitment: "confirmed",
471
+ timeoutMs: 120000,
472
+ debug: options.debug,
473
+ blockhash,
474
+ lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
475
+ });
476
+ if (!confirmation.success) {
456
477
  try {
457
478
  const txDetails = await connection.getTransaction(signature, {
458
479
  commitment: "confirmed",
@@ -479,9 +500,9 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
479
500
  }
480
501
  }
481
502
  catch (getTransactionError) { }
482
- throw confirmError;
503
+ throw new Error(confirmation.error || "Execution confirmation failed");
483
504
  }
484
- if (confirmation.value.err) {
505
+ if (confirmation.err) {
485
506
  let logs = [];
486
507
  let computeUnitsUsed;
487
508
  try {
@@ -495,7 +516,7 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
495
516
  }
496
517
  }
497
518
  catch { }
498
- const errorMessage = `Execution transaction failed: ${JSON.stringify(confirmation.value.err)}`;
519
+ const errorMessage = `Execution transaction failed: ${JSON.stringify(confirmation.err)}`;
499
520
  return {
500
521
  success: false,
501
522
  error: errorMessage,
@@ -559,14 +580,13 @@ export async function executeScriptAccount(scriptAccount, functionIndex = 0, par
559
580
  });
560
581
  }
561
582
  // Helpers
562
- function encodeExecuteInstruction(functionIndex, encodedParams, paramCount, feeShardIndex, feeVaultBump) {
583
+ function encodeExecuteInstruction(functionIndex, encodedParams, paramCount, feeShardIndex, debug = false) {
563
584
  const parts = [];
564
585
  parts.push(new Uint8Array([9]));
565
586
  parts.push(new Uint8Array([
566
587
  EXECUTE_FEE_HEADER_A,
567
588
  EXECUTE_FEE_HEADER_B,
568
589
  feeShardIndex & 0xff,
569
- feeVaultBump & 0xff,
570
590
  ]));
571
591
  // Function index as fixed u32
572
592
  parts.push(encodeU32(functionIndex));
@@ -580,6 +600,15 @@ function encodeExecuteInstruction(functionIndex, encodedParams, paramCount, feeS
580
600
  result.set(part, resultOffset);
581
601
  resultOffset += part.length;
582
602
  }
603
+ if (debug) {
604
+ const payloadLen = encodedParams.length;
605
+ const previewLen = Math.min(24, result.length);
606
+ const previewHex = Buffer.from(result.subarray(0, previewLen)).toString("hex");
607
+ const legacyVarintLikely = result.length >= 9 &&
608
+ // Legacy varint flow had no fee header and placed varint fields immediately after discriminator.
609
+ !(result[1] === EXECUTE_FEE_HEADER_A && result[2] === EXECUTE_FEE_HEADER_B);
610
+ console.log(`[FiveSDK] Execute wire envelope: discr=${result[0]} fee_header=[${result[1]},${result[2]},${result[3]}] function_index_u32=${functionIndex} param_count_u32=${paramCount} payload_len=${payloadLen} total_len=${result.length} preview_hex=${previewHex}${legacyVarintLikely ? " legacy_varint_suspected=true" : ""}`);
611
+ }
583
612
  return result;
584
613
  }
585
614
  function encodeU32(value) {
@@ -625,21 +654,27 @@ async function encodeParametersWithABI(parameters, functionDef, functionIndex, _
625
654
  const type = (param.type || param.param_type || '').toString().trim().toLowerCase();
626
655
  return type === 'pubkey';
627
656
  };
628
- const paramDefs = (functionDef.parameters || []);
657
+ const paramDefs = functionDef.parameters || [];
629
658
  const nonAccountParamDefs = paramDefs.filter((param) => !isAccountParam(param));
630
- const fullParameterListProvided = parameters.length >= paramDefs.length;
631
- if (fullParameterListProvided && parameters.length !== paramDefs.length) {
632
- console.warn(`[FiveSDK] Parameter validation warning: Function '${functionDef.name}' expects ${paramDefs.length} parameters, but received ${parameters.length}.`);
659
+ const isFullParamList = parameters.length === paramDefs.length;
660
+ const isArgOnlyList = parameters.length === nonAccountParamDefs.length;
661
+ if (!isFullParamList && !isArgOnlyList) {
662
+ console.warn(`[FiveSDK] Parameter validation warning: Function '${functionDef.name}' expects ${paramDefs.length} total params (${nonAccountParamDefs.length} non-account), but received ${parameters.length}.`);
663
+ }
664
+ // Current VM/compiler contract encodes non-account parameters only.
665
+ // If callers pass full ABI params (accounts + args), normalize to args-only order.
666
+ if (isFullParamList) {
667
+ console.warn(`[FiveSDK] Deprecation: full ABI parameter lists (including account params) are normalized to non-account execute args for '${functionDef.name}'. Pass args-only params to avoid this warning.`);
633
668
  }
669
+ const defsForEncoding = nonAccountParamDefs;
634
670
  const paramValues = {};
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) {
671
+ const paramSourceIndexByName = new Map(paramDefs.map((param, index) => [param.name, index]));
672
+ for (let index = 0; index < defsForEncoding.length; index++) {
673
+ const param = defsForEncoding[index];
674
+ const sourceIndex = isFullParamList
675
+ ? (paramSourceIndexByName.get(param.name) ?? -1)
676
+ : index;
677
+ if (sourceIndex < 0 || sourceIndex >= parameters.length) {
643
678
  throw new Error(`Missing value for parameter: ${param.name}`);
644
679
  }
645
680
  let value = parameters[sourceIndex];
@@ -649,10 +684,9 @@ async function encodeParametersWithABI(parameters, functionDef, functionIndex, _
649
684
  }
650
685
  }
651
686
  paramValues[param.name] = value;
652
- argCursor += 1;
653
687
  }
654
- const encoded = await BytecodeEncoder.encodeExecute(functionIndex, nonAccountParamDefs, paramValues, true, options);
655
- return { encoded, paramCount: nonAccountParamDefs.length };
688
+ const encoded = await BytecodeEncoder.encodeExecute(functionIndex, defsForEncoding, paramValues, true, options);
689
+ return { encoded, paramCount: defsForEncoding.length };
656
690
  }
657
691
  function estimateComputeUnits(functionIndex, parameterCount) {
658
692
  return Math.max(5000, 1000 + parameterCount * 500 + functionIndex * 100);
@@ -1,5 +1,6 @@
1
1
  import { PDAUtils, Base58Utils } from "../crypto/index.js";
2
2
  import { ProgramIdResolver } from "../config/ProgramIdResolver.js";
3
+ import { getAccountInfoWithRetry } from "../utils/transaction.js";
3
4
  export async function getVMState(connection, fiveVMProgramId) {
4
5
  const programId = ProgramIdResolver.resolve(fiveVMProgramId);
5
6
  const vmStatePDA = await PDAUtils.deriveVMStatePDA(programId);
@@ -12,7 +13,11 @@ export async function getVMState(connection, fiveVMProgramId) {
12
13
  pubkey = new PublicKey(vmStatePDA.address);
13
14
  }
14
15
  catch { }
15
- const info = await connection.getAccountInfo(pubkey);
16
+ const info = await getAccountInfoWithRetry(connection, pubkey, {
17
+ commitment: "finalized",
18
+ retries: 2,
19
+ delayMs: 1000,
20
+ });
16
21
  if (!info)
17
22
  throw new Error("VM State account not found");
18
23
  accountData = new Uint8Array(info.data);
@@ -29,6 +29,7 @@ export declare class FunctionBuilder {
29
29
  private accountsMap;
30
30
  private argsMap;
31
31
  private resolvedAccounts;
32
+ private vmPayerAccount?;
32
33
  constructor(functionDef: FunctionDefinition, scriptAccount: string, abi: ScriptABI, options: FiveProgramOptions);
33
34
  /**
34
35
  * Specify accounts for this function call
@@ -47,6 +48,13 @@ export declare class FunctionBuilder {
47
48
  * @returns this for method chaining
48
49
  */
49
50
  args(args: Record<string, any>): this;
51
+ /**
52
+ * Override the VM execute-fee payer account for this instruction.
53
+ * This is distinct from the outer transaction fee payer.
54
+ */
55
+ payer(payer: string | {
56
+ toBase58(): string;
57
+ }): this;
50
58
  /**
51
59
  * Build and return serialized instruction data
52
60
  * This is the main method that orchestrates parameter resolution and instruction generation
@@ -62,6 +62,15 @@ export class FunctionBuilder {
62
62
  }
63
63
  return this;
64
64
  }
65
+ /**
66
+ * Override the VM execute-fee payer account for this instruction.
67
+ * This is distinct from the outer transaction fee payer.
68
+ */
69
+ payer(payer) {
70
+ this.vmPayerAccount =
71
+ typeof payer === 'string' ? payer : payer.toBase58();
72
+ return this;
73
+ }
65
74
  /**
66
75
  * Build and return serialized instruction data
67
76
  * This is the main method that orchestrates parameter resolution and instruction generation
@@ -93,7 +102,7 @@ export class FunctionBuilder {
93
102
  // Now validate that all required parameters are provided (including auto-injected ones)
94
103
  this.validateParameters();
95
104
  // Merge parameters in ABI order (accounts first, then data)
96
- const { mergedParams, accountPubkeys } = this.mergeParameters();
105
+ const { mergedParams, argParams, accountPubkeys } = this.mergeParameters();
97
106
  // Append system accounts to the account list (they go at the end)
98
107
  const allAccountPubkeys = [...accountPubkeys, ...systemAccountsList];
99
108
  // Build account metadata from ABI attributes
@@ -105,7 +114,7 @@ export class FunctionBuilder {
105
114
  }
106
115
  // Call existing SDK method to generate instruction
107
116
  // This reuses the proven parameter encoding logic
108
- const instruction = await this.generateInstructionData(mergedParams, allAccountPubkeys, accountMetadata);
117
+ const instruction = await this.generateInstructionData(mergedParams, argParams, allAccountPubkeys, accountMetadata);
109
118
  if (this.options.debug) {
110
119
  console.log(`[FunctionBuilder] Generated instruction:`, instruction);
111
120
  }
@@ -178,6 +187,7 @@ export class FunctionBuilder {
178
187
  */
179
188
  mergeParameters() {
180
189
  const mergedParams = [];
190
+ const argParams = [];
181
191
  const accountPubkeys = [];
182
192
  for (const param of this.functionDef.parameters) {
183
193
  if (param.is_account) {
@@ -196,9 +206,10 @@ export class FunctionBuilder {
196
206
  throw new Error(`Missing argument '${param.name}'`);
197
207
  }
198
208
  mergedParams.push(value);
209
+ argParams.push(value);
199
210
  }
200
211
  }
201
- return { mergedParams, accountPubkeys };
212
+ return { mergedParams, argParams, accountPubkeys };
202
213
  }
203
214
  /**
204
215
  * Build account metadata (isSigner, isWritable) from ABI attributes
@@ -279,14 +290,14 @@ export class FunctionBuilder {
279
290
  * @param accountMetadata - Account metadata (isSigner, isWritable)
280
291
  * @returns SerializedInstruction
281
292
  */
282
- async generateInstructionData(mergedParams, accountList, accountMetadata) {
293
+ async generateInstructionData(mergedParams, argParams, accountList, accountMetadata) {
283
294
  // Account list is already passed in
284
295
  // Dynamically import FiveSDK to avoid circular dependencies
285
296
  const { FiveSDK } = await import('../FiveSDK.js');
286
297
  // Call the SDK's generateExecuteInstruction method.
287
298
  // This handles fixed-size typed execute encoding and parameter validation.
288
299
  const executionResult = await FiveSDK.generateExecuteInstruction(this.scriptAccount, this.functionDef.index, // Use function index directly
289
- mergedParams, // All parameters in merged order
300
+ argParams, // Current VM contract encodes non-account args only
290
301
  accountList, // Account pubkey list
291
302
  undefined, // No connection needed - we have ABI
292
303
  {
@@ -295,6 +306,8 @@ export class FunctionBuilder {
295
306
  fiveVMProgramId: this.options.fiveVMProgramId,
296
307
  vmStateAccount: this.options.vmStateAccount,
297
308
  adminAccount: this.options.feeReceiverAccount,
309
+ payerAccount: this.vmPayerAccount ||
310
+ this.options.provider?.publicKey?.toBase58?.(),
298
311
  accountMetadata: accountMetadata, // Pass account metadata for correct isWritable flags
299
312
  });
300
313
  // Map SDK's instruction format (with 'accounts') to SerializedInstruction format (with 'keys')
@@ -22,7 +22,6 @@ export function parseProjectConfig(parsedToml) {
22
22
  rpcUrl: deploy.rpc_url,
23
23
  programId: deploy.program_id,
24
24
  keypairPath: deploy.keypair_path,
25
- multiFileMode: build.multi_file_mode ?? false,
26
25
  optimizations: {
27
26
  enableCompression: optimizations.enable_compression ?? true,
28
27
  enableConstraintOptimization: optimizations.enable_constraint_optimization ?? true,
@@ -238,9 +238,14 @@ export class FiveTestRunner {
238
238
  byFile.set(test.path, cases);
239
239
  }
240
240
  }
241
+ const moduleNameForFile = (file) => file
242
+ .replace(/\.v$/i, '')
243
+ .split(/[\\/]/)
244
+ .filter(Boolean)
245
+ .join('::');
241
246
  for (const [file, testCases] of byFile.entries()) {
242
247
  suites.push({
243
- name: basename(file, '.v'),
248
+ name: moduleNameForFile(file),
244
249
  description: `Tests from ${file}`,
245
250
  testCases
246
251
  });