@5ive-tech/sdk 1.1.12 → 1.1.14

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 (45) hide show
  1. package/README.md +16 -0
  2. package/dist/FiveSDK.d.ts +45 -1
  3. package/dist/FiveSDK.js +6 -0
  4. package/dist/accounts/index.d.ts +10 -28
  5. package/dist/accounts/index.js +33 -61
  6. package/dist/assets/vm/five_vm_wasm.d.ts +8 -0
  7. package/dist/assets/vm/five_vm_wasm.js +25 -0
  8. package/dist/assets/vm/five_vm_wasm_bg.wasm +0 -0
  9. package/dist/assets/vm/five_vm_wasm_bg.wasm.d.ts +3 -0
  10. package/dist/compiler/BytecodeCompiler.js +10 -6
  11. package/dist/compiler/source-normalization.d.ts +1 -0
  12. package/dist/compiler/source-normalization.js +67 -0
  13. package/dist/constants/headers.d.ts +2 -0
  14. package/dist/constants/headers.js +2 -0
  15. package/dist/crypto/index.d.ts +8 -1
  16. package/dist/crypto/index.js +27 -14
  17. package/dist/index.d.ts +1 -0
  18. package/dist/index.js +1 -0
  19. package/dist/modules/accounts.js +1 -1
  20. package/dist/modules/deploy.js +172 -99
  21. package/dist/modules/execute.d.ts +5 -0
  22. package/dist/modules/execute.js +115 -49
  23. package/dist/modules/fees.js +2 -2
  24. package/dist/modules/namespaces.d.ts +11 -0
  25. package/dist/modules/namespaces.js +64 -0
  26. package/dist/program/FiveProgram.js +4 -3
  27. package/dist/program/FunctionBuilder.d.ts +8 -0
  28. package/dist/program/FunctionBuilder.js +18 -5
  29. package/dist/program/TypeGenerator.js +8 -1
  30. package/dist/project/config.js +113 -1
  31. package/dist/project/workspace.d.ts +5 -0
  32. package/dist/testing/TestDiscovery.d.ts +1 -0
  33. package/dist/testing/TestDiscovery.js +18 -2
  34. package/dist/testing/TestRunner.js +4 -1
  35. package/dist/types.d.ts +16 -5
  36. package/dist/types.js +1 -0
  37. package/dist/utils/abi.js +33 -10
  38. package/dist/utils/transaction.d.ts +16 -0
  39. package/dist/utils/transaction.js +81 -5
  40. package/dist/wasm/compiler/CompilationLogic.js +3 -3
  41. package/dist/wasm/vm.d.ts +2 -2
  42. package/dist/wasm/vm.js +10 -11
  43. package/package.json +1 -1
  44. package/dist/assets/vm/dummy.file +0 -0
  45. package/dist/assets/vm/five_vm_wasm_bg.js +0 -3307
@@ -23,6 +23,10 @@ const FEE_VAULT_NAMESPACE_SEED = Buffer.from([
23
23
  ]);
24
24
  const EXECUTE_FEE_HEADER_A = 0xff;
25
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
+ }
26
30
  async function deriveProgramFeeVault(programId, shardIndex) {
27
31
  const { PublicKey } = await import("@solana/web3.js");
28
32
  const [pda, bump] = PublicKey.findProgramAddressSync([FEE_VAULT_NAMESPACE_SEED, Buffer.from([shardIndex])], new PublicKey(programId));
@@ -43,8 +47,7 @@ async function readVMStateShardCount(connection, vmStateAddress) {
43
47
  const data = new Uint8Array(info.data);
44
48
  if (data.length <= 50)
45
49
  return DEFAULT_FEE_VAULT_SHARD_COUNT;
46
- const shardCount = data[50];
47
- return shardCount > 0 ? shardCount : DEFAULT_FEE_VAULT_SHARD_COUNT;
50
+ return clampShardCount(data[50]);
48
51
  }
49
52
  catch {
50
53
  return DEFAULT_FEE_VAULT_SHARD_COUNT;
@@ -274,78 +277,142 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
274
277
  : selectFeeShard(shardCount);
275
278
  const feeVault = await deriveProgramFeeVault(programId, feeShardIndex);
276
279
  const instructionAccounts = [
280
+ // Execute runtime reads script/vm_state; keep them readonly.
277
281
  { pubkey: scriptAccount, isSigner: false, isWritable: false },
278
282
  { pubkey: vmState, isSigner: false, isWritable: false },
279
283
  ];
280
284
  const abiAccountMetadata = new Map();
281
- if (funcDef && funcDef.parameters) {
282
- // First pass: detect if there's an @init constraint and find the payer
285
+ const normalizeAccountPubkey = (value) => {
286
+ if (!value)
287
+ return undefined;
288
+ if (typeof value === "string")
289
+ return value;
290
+ if (typeof value.toBase58 === "function")
291
+ return value.toBase58();
292
+ if (typeof value.toString === "function") {
293
+ const str = value.toString();
294
+ if (str && str !== "[object Object]")
295
+ return str;
296
+ }
297
+ return undefined;
298
+ };
299
+ const isAccountParam = (param) => {
300
+ if (!param)
301
+ return false;
302
+ if (param.is_account || param.isAccount)
303
+ return true;
304
+ const type = (param.type || param.param_type || "").toString().trim().toLowerCase();
305
+ return type === "account" || type === "mint" || type === "tokenaccount";
306
+ };
307
+ const allParams = funcDef && Array.isArray(funcDef.parameters) ? funcDef.parameters : [];
308
+ const accountParams = allParams.filter((param) => isAccountParam(param));
309
+ const hasFullParameterList = allParams.length > 0 && parameters.length === allParams.length;
310
+ if (accountParams.length > 0) {
311
+ // First pass: detect if there's an @init constraint and identify payer param.
283
312
  let hasInit = false;
284
- let payerPubkey;
285
- for (let i = 0; i < funcDef.parameters.length; i++) {
286
- const param = funcDef.parameters[i];
287
- if (param.is_account || param.isAccount) {
288
- const attributes = param.attributes || [];
289
- if (attributes.includes('init')) {
290
- hasInit = true;
291
- for (let j = 0; j < funcDef.parameters.length; j++) {
292
- const payerParam = funcDef.parameters[j];
293
- if (i !== j &&
294
- (payerParam.is_account || payerParam.isAccount) &&
295
- (payerParam.attributes || []).includes('signer')) {
296
- const payerValue = parameters[j];
297
- payerPubkey = payerValue?.toString();
298
- break;
299
- }
300
- }
313
+ let initParamName;
314
+ let payerParamName;
315
+ for (const param of accountParams) {
316
+ const attributes = param.attributes || [];
317
+ if (attributes.includes("init")) {
318
+ hasInit = true;
319
+ initParamName = param.name;
320
+ break;
321
+ }
322
+ }
323
+ if (hasInit) {
324
+ for (const param of accountParams) {
325
+ if (param.name !== initParamName &&
326
+ (param.attributes || []).includes("signer")) {
327
+ payerParamName = param.name;
301
328
  break;
302
329
  }
303
330
  }
304
331
  }
305
- funcDef.parameters.forEach((param, paramIndex) => {
306
- if (param.is_account || param.isAccount) {
307
- const value = parameters[paramIndex];
308
- const pubkey = value?.toString();
332
+ // Resolve pubkeys for account params in either full-param mode or args-only mode.
333
+ const accountPubkeysByParamName = new Map();
334
+ if (hasFullParameterList) {
335
+ allParams.forEach((param, paramIndex) => {
336
+ if (!isAccountParam(param))
337
+ return;
338
+ const pubkey = normalizeAccountPubkey(parameters[paramIndex]);
309
339
  if (pubkey) {
310
- const attributes = param.attributes || [];
311
- const isSigner = attributes.includes('signer');
312
- const isWritable = attributes.includes('mut') ||
313
- attributes.includes('init') ||
314
- (hasInit && pubkey === payerPubkey);
315
- const existing = abiAccountMetadata.get(pubkey) || { isSigner: false, isWritable: false };
316
- abiAccountMetadata.set(pubkey, {
317
- isSigner: existing.isSigner || isSigner,
318
- isWritable: existing.isWritable || isWritable
319
- });
340
+ accountPubkeysByParamName.set(param.name, pubkey);
341
+ }
342
+ });
343
+ }
344
+ else {
345
+ const count = Math.min(accountParams.length, accounts.length);
346
+ for (let i = 0; i < count; i++) {
347
+ const paramName = accountParams[i]?.name;
348
+ const pubkey = accounts[i];
349
+ if (paramName && pubkey) {
350
+ accountPubkeysByParamName.set(paramName, pubkey);
320
351
  }
321
352
  }
322
- });
353
+ }
354
+ for (const param of accountParams) {
355
+ const pubkey = accountPubkeysByParamName.get(param.name);
356
+ if (!pubkey)
357
+ continue;
358
+ const attributes = param.attributes || [];
359
+ const isSigner = attributes.includes("signer");
360
+ const isWritable = attributes.includes("mut") ||
361
+ attributes.includes("init") ||
362
+ (hasInit && payerParamName === param.name);
363
+ const existing = abiAccountMetadata.get(pubkey) || {
364
+ isSigner: false,
365
+ isWritable: false,
366
+ };
367
+ abiAccountMetadata.set(pubkey, {
368
+ isSigner: existing.isSigner || isSigner,
369
+ isWritable: existing.isWritable || isWritable,
370
+ });
371
+ }
323
372
  }
373
+ const unknownAccounts = [];
324
374
  const userInstructionAccounts = accounts.map((acc) => {
325
375
  // Check both derived ABI metadata and passed-in metadata (from FunctionBuilder)
326
376
  const abiMetadata = abiAccountMetadata.get(acc);
327
377
  const passedMetadata = options.accountMetadata?.get(acc);
328
- const metadata = abiMetadata || passedMetadata;
378
+ const metadata = passedMetadata || abiMetadata;
329
379
  const isSigner = metadata ? metadata.isSigner : false;
330
- const isWritable = metadata ? metadata.isWritable : true;
380
+ const isWritable = metadata
381
+ ? (metadata.isSystemAccount ? false : metadata.isWritable)
382
+ : false;
383
+ if (!metadata) {
384
+ unknownAccounts.push(acc);
385
+ }
331
386
  return {
332
387
  pubkey: acc,
333
388
  isSigner,
334
389
  isWritable
335
390
  };
336
391
  });
392
+ if (unknownAccounts.length > 0) {
393
+ const deduped = Array.from(new Set(unknownAccounts));
394
+ console.warn(`[FiveSDK] Missing account metadata for ${deduped.join(", ")}; defaulting readonly. Pass accountMetadata or use FiveProgram.function(...).instruction().`);
395
+ }
337
396
  instructionAccounts.push(...userInstructionAccounts);
338
397
  const instructionData = encodeExecuteInstruction(functionIndex, encodedParams, actualParamCount, feeShardIndex, options.debug === true);
339
398
  // Runtime requires strict tail: [payer, fee_vault, system_program].
399
+ // Prefer explicit payer, otherwise infer from signer+writable account metadata only.
340
400
  const signerCandidates = instructionAccounts
341
401
  .filter((acc) => acc.isSigner)
342
402
  .map((acc) => acc.pubkey);
343
- const inferredPayer = options.payerAccount ||
344
- (signerCandidates.length > 0
345
- ? signerCandidates[signerCandidates.length - 1]
346
- : accounts[0]);
403
+ const metadataFor = (pubkey) => options.accountMetadata?.get(pubkey) || abiAccountMetadata.get(pubkey);
404
+ const writableSignerCandidates = signerCandidates.filter((pubkey) => {
405
+ const metadata = metadataFor(pubkey);
406
+ if (!metadata)
407
+ return false;
408
+ return !metadata.isSystemAccount && metadata.isWritable;
409
+ });
410
+ const inferredPayer = options.payerAccount || writableSignerCandidates[0];
347
411
  if (!inferredPayer) {
348
- throw new Error("Could not infer execute fee payer account. Provide a signer account or set options.payerAccount.");
412
+ if (signerCandidates.length > 0) {
413
+ throw new Error("Could not infer execute fee payer account from writable signer metadata. Pass options.payerAccount or use FiveProgram.function(...).payer(...).instruction().");
414
+ }
415
+ throw new Error("Could not infer execute fee payer account. Provide options.payerAccount or include a writable signer account.");
349
416
  }
350
417
  const feeTailAccounts = [
351
418
  { pubkey: inferredPayer, isSigner: true, isWritable: true },
@@ -396,6 +463,7 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
396
463
  vmStateAccount: options.vmStateAccount,
397
464
  fiveVMProgramId: options.fiveVMProgramId,
398
465
  abi: options.abi,
466
+ accountMetadata: options.accountMetadata,
399
467
  feeShardIndex: options.feeShardIndex,
400
468
  payerAccount: options.payerAccount || signerKeypair.publicKey.toString(),
401
469
  });
@@ -422,7 +490,6 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
422
490
  for (const meta of accountKeys) {
423
491
  if (meta.pubkey === signerPubkey) {
424
492
  meta.isSigner = true;
425
- meta.isWritable = true;
426
493
  signerFound = true;
427
494
  }
428
495
  }
@@ -430,7 +497,7 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
430
497
  accountKeys.push({
431
498
  pubkey: signerPubkey,
432
499
  isSigner: true,
433
- isWritable: true,
500
+ isWritable: false,
434
501
  });
435
502
  }
436
503
  const executeInstruction = new TransactionInstruction({
@@ -444,8 +511,8 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
444
511
  });
445
512
  transaction.add(executeInstruction);
446
513
  transaction.feePayer = signerKeypair.publicKey;
447
- const { blockhash } = await connection.getLatestBlockhash("confirmed");
448
- transaction.recentBlockhash = blockhash;
514
+ const latestBlockhash = await connection.getLatestBlockhash("confirmed");
515
+ transaction.recentBlockhash = latestBlockhash.blockhash;
449
516
  transaction.partialSign(signerKeypair);
450
517
  const firstSig = transaction.signatures[0]?.signature;
451
518
  if (firstSig) {
@@ -457,12 +524,11 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
457
524
  maxRetries: options.maxRetries || 3,
458
525
  });
459
526
  lastSignature = signature;
460
- const latestBlockhash = await connection.getLatestBlockhash("confirmed");
461
527
  const confirmation = await confirmTransactionRobust(connection, signature, {
462
528
  commitment: "confirmed",
463
529
  timeoutMs: 120000,
464
530
  debug: options.debug,
465
- blockhash,
531
+ blockhash: latestBlockhash.blockhash,
466
532
  lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
467
533
  });
468
534
  if (!confirmation.success) {
@@ -24,7 +24,7 @@ export async function getFees(connection, fiveVMProgramId) {
24
24
  export async function calculateDeployFee(bytecodeSize, connection, fiveVMProgramId) {
25
25
  try {
26
26
  const accountSize = 64 + bytecodeSize;
27
- const rentLamports = await RentCalculator.calculateRentExemption(accountSize);
27
+ const rentLamports = await RentCalculator.calculateRentExemptionWithConnection(accountSize, connection);
28
28
  const vmState = await getVMState(connection, fiveVMProgramId);
29
29
  const deployFeeLamports = vmState.deployFeeLamports;
30
30
  return {
@@ -41,7 +41,7 @@ export async function calculateDeployFee(bytecodeSize, connection, fiveVMProgram
41
41
  }
42
42
  catch (error) {
43
43
  const accountSize = 64 + bytecodeSize;
44
- const rentLamports = await RentCalculator.calculateRentExemption(accountSize);
44
+ const rentLamports = await RentCalculator.calculateRentExemptionWithConnection(accountSize, connection);
45
45
  return {
46
46
  feeBps: 0,
47
47
  basisLamports: rentLamports,
@@ -36,4 +36,15 @@ export declare function resolveNamespaceOnChain(namespaceValue: string, options:
36
36
  resolvedScript?: string;
37
37
  bindingAddress: string;
38
38
  }>;
39
+ export declare function setNamespaceSymbolPriceOnChain(symbol: string, priceLamports: number, options: NamespaceOnChainOptions): Promise<{
40
+ transactionId?: string;
41
+ symbol: ScopedNamespace["symbol"];
42
+ priceLamports: number;
43
+ }>;
44
+ export declare function getNamespaceSymbolPriceOnChain(symbol: string, options: NamespaceOnChainOptions): Promise<{
45
+ transactionId?: string;
46
+ symbol: ScopedNamespace["symbol"];
47
+ priceLamports: number;
48
+ priceSol: number;
49
+ }>;
39
50
  export {};
@@ -2,6 +2,7 @@ const SYMBOLS = new Set(["!", "@", "#", "$", "%"]);
2
2
  const NAMESPACE_CONFIG_SEED = "5ns_config";
3
3
  const NAMESPACE_TLD_SEED = "5ns_tld";
4
4
  const NAMESPACE_BINDING_SEED = "5ns_binding";
5
+ const LAMPORTS_PER_SOL = 1000000000;
5
6
  export function canonicalizeScopedNamespace(input) {
6
7
  const value = input.trim();
7
8
  if (value.length < 2) {
@@ -52,6 +53,17 @@ export function resolveNamespaceFromLockfile(namespaceValue, lockfile) {
52
53
  function asBuffer(value) {
53
54
  return Buffer.from(value, "utf8");
54
55
  }
56
+ function validateSymbol(symbol) {
57
+ if (!SYMBOLS.has(symbol)) {
58
+ throw new Error("namespace symbol must be one of ! @ # $ %");
59
+ }
60
+ return symbol;
61
+ }
62
+ async function deriveNamespaceConfigAccount(fiveVMProgramId) {
63
+ const { PDAUtils } = await import("../crypto/index.js");
64
+ const cfg = await PDAUtils.findProgramAddress([asBuffer(NAMESPACE_CONFIG_SEED)], fiveVMProgramId);
65
+ return cfg.address;
66
+ }
55
67
  export async function deriveNamespaceAccounts(namespaceValue, fiveVMProgramId) {
56
68
  const { PDAUtils } = await import("../crypto/index.js");
57
69
  const parsed = canonicalizeScopedNamespace(namespaceValue);
@@ -188,3 +200,55 @@ export async function resolveNamespaceOnChain(namespaceValue, options) {
188
200
  bindingAddress: addresses.binding,
189
201
  };
190
202
  }
203
+ export async function setNamespaceSymbolPriceOnChain(symbol, priceLamports, options) {
204
+ const { ProgramIdResolver } = await import("../config/ProgramIdResolver.js");
205
+ const { executeOnSolana } = await import("./execute.js");
206
+ const validatedSymbol = validateSymbol(symbol);
207
+ if (!Number.isFinite(priceLamports) || priceLamports <= 0 || !Number.isInteger(priceLamports)) {
208
+ throw new Error("priceLamports must be a positive integer");
209
+ }
210
+ const vmProgramId = ProgramIdResolver.resolve(options.fiveVMProgramId);
211
+ const configAddress = await deriveNamespaceConfigAccount(vmProgramId);
212
+ const admin = options.signerKeypair.publicKey.toBase58();
213
+ const result = await executeOnSolana(options.managerScriptAccount, options.connection, options.signerKeypair, "set_symbol_price", [configAddress, admin, validatedSymbol, priceLamports], [configAddress, admin], {
214
+ debug: options.debug,
215
+ fiveVMProgramId: vmProgramId,
216
+ computeUnitLimit: 300000,
217
+ });
218
+ if (!result.success) {
219
+ throw new Error(result.error || "set_symbol_price failed");
220
+ }
221
+ return {
222
+ transactionId: result.transactionId,
223
+ symbol: validatedSymbol,
224
+ priceLamports,
225
+ };
226
+ }
227
+ export async function getNamespaceSymbolPriceOnChain(symbol, options) {
228
+ const { ProgramIdResolver } = await import("../config/ProgramIdResolver.js");
229
+ const { executeOnSolana } = await import("./execute.js");
230
+ const validatedSymbol = validateSymbol(symbol);
231
+ const vmProgramId = ProgramIdResolver.resolve(options.fiveVMProgramId);
232
+ const configAddress = await deriveNamespaceConfigAccount(vmProgramId);
233
+ const result = await executeOnSolana(options.managerScriptAccount, options.connection, options.signerKeypair, "get_symbol_price", [configAddress, validatedSymbol], [configAddress], {
234
+ debug: options.debug,
235
+ fiveVMProgramId: vmProgramId,
236
+ computeUnitLimit: 300000,
237
+ });
238
+ if (!result.success) {
239
+ throw new Error(result.error || "get_symbol_price failed");
240
+ }
241
+ const priceLamportsRaw = typeof result.result === "number"
242
+ ? result.result
243
+ : Number(result.result);
244
+ if (!Number.isFinite(priceLamportsRaw) || priceLamportsRaw < 0) {
245
+ throw new Error(`invalid get_symbol_price result: ${String(result.result)}`);
246
+ }
247
+ const priceLamports = Math.trunc(priceLamportsRaw);
248
+ return {
249
+ transactionId: result.transactionId,
250
+ symbol: validatedSymbol,
251
+ priceLamports,
252
+ priceSol: priceLamports / LAMPORTS_PER_SOL,
253
+ };
254
+ }
@@ -48,12 +48,13 @@ export class FiveProgram {
48
48
  if (typeof prop === 'string') {
49
49
  return (...args) => {
50
50
  const builder = target.function(prop);
51
- // TODO: Robust argument mapping based on ABI types
52
- // Simple map: if args[0] is object, assume named params
53
51
  if (args.length === 1 && typeof args[0] === 'object' && !Array.isArray(args[0])) {
54
52
  builder.args(args[0]);
55
53
  }
56
- // Logic for positional args would go here
54
+ else if (args.length > 0) {
55
+ throw new Error(`FiveProgram.methods.${prop} only supports a single named-arguments object. ` +
56
+ `Use program.function("${prop}").args({...}) for now.`);
57
+ }
57
58
  return builder;
58
59
  };
59
60
  }
@@ -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')
@@ -20,12 +20,19 @@
20
20
  * }
21
21
  * ```
22
22
  */
23
+ import { normalizeAbiFunctions } from '../utils/abi.js';
23
24
  /**
24
25
  * TypeGenerator creates TypeScript interfaces from ABI
25
26
  */
26
27
  export class TypeGenerator {
27
28
  constructor(abi, options) {
28
- this.abi = abi;
29
+ this.abi = {
30
+ ...abi,
31
+ functions: normalizeAbiFunctions(abi.functions ?? abi).map((func) => ({
32
+ ...func,
33
+ visibility: func.visibility ?? 'public',
34
+ })),
35
+ };
29
36
  this.options = {
30
37
  scriptName: abi.name || 'Program',
31
38
  debug: false,
@@ -1,14 +1,28 @@
1
+ const SUPPORTED_SCHEMA_VERSION = 1;
1
2
  /**
2
3
  * Parses a raw TOML object into a strict ProjectConfig.
3
4
  */
4
5
  export function parseProjectConfig(parsedToml) {
6
+ const schemaVersionRaw = parsedToml.schema_version;
7
+ if (schemaVersionRaw === undefined) {
8
+ throw new Error(`Missing required top-level 'schema_version'. Add:\nschema_version = ${SUPPORTED_SCHEMA_VERSION}`);
9
+ }
10
+ if (!Number.isInteger(schemaVersionRaw) || Number(schemaVersionRaw) <= 0) {
11
+ throw new Error('schema_version must be a positive integer');
12
+ }
13
+ const schemaVersion = Number(schemaVersionRaw);
14
+ if (schemaVersion !== SUPPORTED_SCHEMA_VERSION) {
15
+ throw new Error(`Unsupported schema_version=${schemaVersion}. Supported schema_version: ${SUPPORTED_SCHEMA_VERSION}`);
16
+ }
5
17
  const project = parsedToml.project ?? {};
6
18
  const build = parsedToml.build ?? {};
7
19
  const optimizations = parsedToml.optimizations ?? {};
20
+ const dependencies = parsedToml.dependencies ?? {};
8
21
  const deploy = parsedToml.deploy ?? {};
9
22
  const name = project.name ?? 'five-project';
10
23
  const target = (project.target ?? 'vm');
11
24
  return {
25
+ schemaVersion,
12
26
  name,
13
27
  version: project.version ?? '0.1.0',
14
28
  description: project.description,
@@ -25,8 +39,106 @@ export function parseProjectConfig(parsedToml) {
25
39
  optimizations: {
26
40
  enableCompression: optimizations.enable_compression ?? true,
27
41
  enableConstraintOptimization: optimizations.enable_constraint_optimization ?? true,
42
+ // Public SDK config is locked to the canonical production mode.
28
43
  optimizationLevel: 'production'
29
44
  },
30
- dependencies: []
45
+ dependencies: parseDependencies(dependencies)
31
46
  };
32
47
  }
48
+ function parseDependencies(rawDeps) {
49
+ const out = [];
50
+ const seenNamespaces = new Set();
51
+ const seenAddresses = new Set();
52
+ const seenMoatTargets = new Set();
53
+ for (const [alias, rawValue] of Object.entries(rawDeps ?? {})) {
54
+ const value = (rawValue ?? {});
55
+ const dependency = {
56
+ alias,
57
+ package: String(value.package ?? ''),
58
+ version: value.version !== undefined ? String(value.version) : undefined,
59
+ source: String(value.source ?? ''),
60
+ link: String(value.link ?? ''),
61
+ path: value.path !== undefined ? String(value.path) : undefined,
62
+ namespace: value.namespace !== undefined ? String(value.namespace) : undefined,
63
+ address: value.address !== undefined ? String(value.address) : undefined,
64
+ moatAccount: value.moat_account !== undefined ? String(value.moat_account) : undefined,
65
+ module: value.module !== undefined ? String(value.module) : undefined,
66
+ pin: value.pin !== undefined ? String(value.pin) : undefined,
67
+ cluster: value.cluster !== undefined ? String(value.cluster) : undefined,
68
+ };
69
+ if (!dependency.package) {
70
+ throw new Error(`Invalid dependency '${alias}': missing required field 'package'`);
71
+ }
72
+ if (dependency.source !== 'bundled' &&
73
+ dependency.source !== 'path' &&
74
+ dependency.source !== 'namespace' &&
75
+ dependency.source !== 'address' &&
76
+ dependency.source !== 'moat') {
77
+ throw new Error(`Invalid dependency '${alias}': source must be one of bundled|path|namespace|address|moat`);
78
+ }
79
+ if (dependency.link !== 'inline' && dependency.link !== 'external') {
80
+ throw new Error(`Invalid dependency '${alias}': link must be one of inline|external`);
81
+ }
82
+ const hasPath = Boolean(dependency.path);
83
+ const hasNamespace = Boolean(dependency.namespace);
84
+ const hasAddress = Boolean(dependency.address);
85
+ const hasMoatAccount = Boolean(dependency.moatAccount);
86
+ const hasModule = Boolean(dependency.module);
87
+ if (dependency.source === 'path' && !hasPath) {
88
+ throw new Error(`Invalid dependency '${alias}': source=path requires 'path'`);
89
+ }
90
+ if (dependency.source === 'namespace' && !hasNamespace) {
91
+ throw new Error(`Invalid dependency '${alias}': source=namespace requires 'namespace'`);
92
+ }
93
+ if (dependency.source === 'address' && !hasAddress) {
94
+ throw new Error(`Invalid dependency '${alias}': source=address requires 'address'`);
95
+ }
96
+ if (dependency.source === 'moat' && (!hasMoatAccount || !hasModule)) {
97
+ throw new Error(`Invalid dependency '${alias}': source=moat requires 'moat_account' and 'module'`);
98
+ }
99
+ if (dependency.source !== 'path' && hasPath) {
100
+ throw new Error(`Invalid dependency '${alias}': 'path' is only valid for source=path`);
101
+ }
102
+ if (dependency.source !== 'namespace' && hasNamespace) {
103
+ throw new Error(`Invalid dependency '${alias}': 'namespace' is only valid for source=namespace`);
104
+ }
105
+ if (dependency.source !== 'address' && hasAddress) {
106
+ throw new Error(`Invalid dependency '${alias}': 'address' is only valid for source=address`);
107
+ }
108
+ if (dependency.source !== 'moat' && hasMoatAccount) {
109
+ throw new Error(`Invalid dependency '${alias}': 'moat_account' is only valid for source=moat`);
110
+ }
111
+ if (dependency.source !== 'moat' && hasModule) {
112
+ throw new Error(`Invalid dependency '${alias}': 'module' is only valid for source=moat`);
113
+ }
114
+ if (dependency.source === 'bundled' || dependency.source === 'path') {
115
+ if (dependency.link !== 'inline') {
116
+ throw new Error(`Invalid dependency '${alias}': source=${dependency.source} currently requires link=inline`);
117
+ }
118
+ }
119
+ else if (dependency.link !== 'external') {
120
+ throw new Error(`Invalid dependency '${alias}': source=${dependency.source} currently requires link=external`);
121
+ }
122
+ if (dependency.namespace) {
123
+ if (seenNamespaces.has(dependency.namespace)) {
124
+ throw new Error(`Invalid dependencies: duplicate namespace '${dependency.namespace}'`);
125
+ }
126
+ seenNamespaces.add(dependency.namespace);
127
+ }
128
+ if (dependency.address) {
129
+ if (seenAddresses.has(dependency.address)) {
130
+ throw new Error(`Invalid dependencies: duplicate address '${dependency.address}'`);
131
+ }
132
+ seenAddresses.add(dependency.address);
133
+ }
134
+ if (dependency.moatAccount && dependency.module) {
135
+ const moatTarget = `${dependency.moatAccount}::${dependency.module}`;
136
+ if (seenMoatTargets.has(moatTarget)) {
137
+ throw new Error(`Invalid dependencies: duplicate moat target '${moatTarget}'`);
138
+ }
139
+ seenMoatTargets.add(moatTarget);
140
+ }
141
+ out.push(dependency);
142
+ }
143
+ return out;
144
+ }
@@ -106,6 +106,11 @@ export interface LockEntry {
106
106
  address: string;
107
107
  bytecode_hash: string;
108
108
  deployed_at?: string;
109
+ package?: string;
110
+ source?: 'bundled' | 'path' | 'namespace' | 'address';
111
+ link?: LinkType;
112
+ resolved_namespace?: string;
113
+ resolved_script_account?: string;
109
114
  }
110
115
  /**
111
116
  * Resolved workspace state for IDE