@5ive-tech/sdk 1.1.13 → 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.
- package/README.md +16 -0
- package/dist/FiveSDK.d.ts +43 -1
- package/dist/FiveSDK.js +6 -0
- package/dist/accounts/index.d.ts +10 -28
- package/dist/accounts/index.js +33 -61
- package/dist/assets/vm/five_vm_wasm.d.ts +8 -0
- package/dist/assets/vm/five_vm_wasm.js +25 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm +0 -0
- package/dist/assets/vm/five_vm_wasm_bg.wasm.d.ts +3 -0
- package/dist/bin/gen-types.js +0 -0
- package/dist/compiler/BytecodeCompiler.js +10 -6
- package/dist/compiler/source-normalization.d.ts +1 -0
- package/dist/compiler/source-normalization.js +67 -0
- package/dist/constants/headers.d.ts +2 -0
- package/dist/constants/headers.js +2 -0
- package/dist/crypto/index.d.ts +8 -1
- package/dist/crypto/index.js +27 -14
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/modules/accounts.js +1 -1
- package/dist/modules/deploy.js +165 -97
- package/dist/modules/execute.d.ts +5 -0
- package/dist/modules/execute.js +107 -49
- package/dist/modules/fees.js +2 -2
- package/dist/modules/namespaces.d.ts +11 -0
- package/dist/modules/namespaces.js +64 -0
- package/dist/program/FiveProgram.js +4 -3
- package/dist/program/TypeGenerator.js +8 -1
- package/dist/project/config.js +113 -1
- package/dist/project/workspace.d.ts +5 -0
- package/dist/testing/TestDiscovery.d.ts +1 -0
- package/dist/testing/TestDiscovery.js +18 -2
- package/dist/testing/TestRunner.js +4 -1
- package/dist/types.d.ts +16 -5
- package/dist/types.js +1 -0
- package/dist/utils/abi.js +33 -10
- package/dist/utils/transaction.d.ts +16 -0
- package/dist/utils/transaction.js +81 -5
- package/dist/wasm/compiler/CompilationLogic.js +3 -3
- package/dist/wasm/vm.d.ts +2 -2
- package/dist/wasm/vm.js +10 -11
- package/package.json +1 -1
package/dist/modules/execute.js
CHANGED
|
@@ -277,56 +277,100 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
|
|
|
277
277
|
: selectFeeShard(shardCount);
|
|
278
278
|
const feeVault = await deriveProgramFeeVault(programId, feeShardIndex);
|
|
279
279
|
const instructionAccounts = [
|
|
280
|
+
// Execute runtime reads script/vm_state; keep them readonly.
|
|
280
281
|
{ pubkey: scriptAccount, isSigner: false, isWritable: false },
|
|
281
282
|
{ pubkey: vmState, isSigner: false, isWritable: false },
|
|
282
283
|
];
|
|
283
284
|
const abiAccountMetadata = new Map();
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
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.
|
|
289
312
|
let hasInit = false;
|
|
290
|
-
let
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
}
|
|
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;
|
|
307
328
|
break;
|
|
308
329
|
}
|
|
309
330
|
}
|
|
310
331
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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]);
|
|
315
339
|
if (pubkey) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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);
|
|
326
351
|
}
|
|
327
352
|
}
|
|
328
|
-
}
|
|
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
|
+
}
|
|
329
372
|
}
|
|
373
|
+
const unknownAccounts = [];
|
|
330
374
|
const userInstructionAccounts = accounts.map((acc) => {
|
|
331
375
|
// Check both derived ABI metadata and passed-in metadata (from FunctionBuilder)
|
|
332
376
|
const abiMetadata = abiAccountMetadata.get(acc);
|
|
@@ -335,25 +379,40 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
|
|
|
335
379
|
const isSigner = metadata ? metadata.isSigner : false;
|
|
336
380
|
const isWritable = metadata
|
|
337
381
|
? (metadata.isSystemAccount ? false : metadata.isWritable)
|
|
338
|
-
:
|
|
382
|
+
: false;
|
|
383
|
+
if (!metadata) {
|
|
384
|
+
unknownAccounts.push(acc);
|
|
385
|
+
}
|
|
339
386
|
return {
|
|
340
387
|
pubkey: acc,
|
|
341
388
|
isSigner,
|
|
342
389
|
isWritable
|
|
343
390
|
};
|
|
344
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
|
+
}
|
|
345
396
|
instructionAccounts.push(...userInstructionAccounts);
|
|
346
397
|
const instructionData = encodeExecuteInstruction(functionIndex, encodedParams, actualParamCount, feeShardIndex, options.debug === true);
|
|
347
398
|
// Runtime requires strict tail: [payer, fee_vault, system_program].
|
|
399
|
+
// Prefer explicit payer, otherwise infer from signer+writable account metadata only.
|
|
348
400
|
const signerCandidates = instructionAccounts
|
|
349
401
|
.filter((acc) => acc.isSigner)
|
|
350
402
|
.map((acc) => acc.pubkey);
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
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];
|
|
355
411
|
if (!inferredPayer) {
|
|
356
|
-
|
|
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.");
|
|
357
416
|
}
|
|
358
417
|
const feeTailAccounts = [
|
|
359
418
|
{ pubkey: inferredPayer, isSigner: true, isWritable: true },
|
|
@@ -404,6 +463,7 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
404
463
|
vmStateAccount: options.vmStateAccount,
|
|
405
464
|
fiveVMProgramId: options.fiveVMProgramId,
|
|
406
465
|
abi: options.abi,
|
|
466
|
+
accountMetadata: options.accountMetadata,
|
|
407
467
|
feeShardIndex: options.feeShardIndex,
|
|
408
468
|
payerAccount: options.payerAccount || signerKeypair.publicKey.toString(),
|
|
409
469
|
});
|
|
@@ -430,7 +490,6 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
430
490
|
for (const meta of accountKeys) {
|
|
431
491
|
if (meta.pubkey === signerPubkey) {
|
|
432
492
|
meta.isSigner = true;
|
|
433
|
-
meta.isWritable = true;
|
|
434
493
|
signerFound = true;
|
|
435
494
|
}
|
|
436
495
|
}
|
|
@@ -438,7 +497,7 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
438
497
|
accountKeys.push({
|
|
439
498
|
pubkey: signerPubkey,
|
|
440
499
|
isSigner: true,
|
|
441
|
-
isWritable:
|
|
500
|
+
isWritable: false,
|
|
442
501
|
});
|
|
443
502
|
}
|
|
444
503
|
const executeInstruction = new TransactionInstruction({
|
|
@@ -452,8 +511,8 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
452
511
|
});
|
|
453
512
|
transaction.add(executeInstruction);
|
|
454
513
|
transaction.feePayer = signerKeypair.publicKey;
|
|
455
|
-
const
|
|
456
|
-
transaction.recentBlockhash = blockhash;
|
|
514
|
+
const latestBlockhash = await connection.getLatestBlockhash("confirmed");
|
|
515
|
+
transaction.recentBlockhash = latestBlockhash.blockhash;
|
|
457
516
|
transaction.partialSign(signerKeypair);
|
|
458
517
|
const firstSig = transaction.signatures[0]?.signature;
|
|
459
518
|
if (firstSig) {
|
|
@@ -465,12 +524,11 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
465
524
|
maxRetries: options.maxRetries || 3,
|
|
466
525
|
});
|
|
467
526
|
lastSignature = signature;
|
|
468
|
-
const latestBlockhash = await connection.getLatestBlockhash("confirmed");
|
|
469
527
|
const confirmation = await confirmTransactionRobust(connection, signature, {
|
|
470
528
|
commitment: "confirmed",
|
|
471
529
|
timeoutMs: 120000,
|
|
472
530
|
debug: options.debug,
|
|
473
|
-
blockhash,
|
|
531
|
+
blockhash: latestBlockhash.blockhash,
|
|
474
532
|
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
|
475
533
|
});
|
|
476
534
|
if (!confirmation.success) {
|
package/dist/modules/fees.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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 =
|
|
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,
|
package/dist/project/config.js
CHANGED
|
@@ -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
|
|
@@ -12,6 +12,16 @@ import { FiveSDK } from '../FiveSDK.js';
|
|
|
12
12
|
* Discover tests from directory
|
|
13
13
|
*/
|
|
14
14
|
export class TestDiscovery {
|
|
15
|
+
static normalizeJsonTestCases(data) {
|
|
16
|
+
const testCases = data.tests || data.testCases || [];
|
|
17
|
+
if (Array.isArray(testCases)) {
|
|
18
|
+
return testCases;
|
|
19
|
+
}
|
|
20
|
+
if (testCases && typeof testCases === 'object') {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
15
25
|
/**
|
|
16
26
|
* Discover all tests in a directory
|
|
17
27
|
*/
|
|
@@ -66,7 +76,10 @@ export class TestDiscovery {
|
|
|
66
76
|
try {
|
|
67
77
|
const content = await readFile(file, 'utf8');
|
|
68
78
|
const data = JSON.parse(content);
|
|
69
|
-
const testCases =
|
|
79
|
+
const testCases = this.normalizeJsonTestCases(data);
|
|
80
|
+
if (testCases === null) {
|
|
81
|
+
continue;
|
|
82
|
+
}
|
|
70
83
|
for (const testCase of testCases) {
|
|
71
84
|
tests.push({
|
|
72
85
|
name: testCase.name,
|
|
@@ -98,7 +111,10 @@ export class TestDiscovery {
|
|
|
98
111
|
try {
|
|
99
112
|
const content = await readFile(file, 'utf8');
|
|
100
113
|
const data = JSON.parse(content);
|
|
101
|
-
const testCases =
|
|
114
|
+
const testCases = this.normalizeJsonTestCases(data);
|
|
115
|
+
if (testCases === null) {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
102
118
|
return testCases.map((testCase) => ({
|
|
103
119
|
name: testCase.name,
|
|
104
120
|
path: file,
|
|
@@ -211,10 +211,13 @@ export class FiveTestRunner {
|
|
|
211
211
|
try {
|
|
212
212
|
const content = await readFile(test.path, 'utf8');
|
|
213
213
|
const data = JSON.parse(content);
|
|
214
|
+
const testCases = Array.isArray(data.tests || data.testCases)
|
|
215
|
+
? (data.tests || data.testCases)
|
|
216
|
+
: [];
|
|
214
217
|
suites.push({
|
|
215
218
|
name: data.name || basename(test.path, '.test.json'),
|
|
216
219
|
description: data.description,
|
|
217
|
-
testCases
|
|
220
|
+
testCases
|
|
218
221
|
});
|
|
219
222
|
loadedJsonSuites.add(test.path);
|
|
220
223
|
}
|