@5ive-tech/sdk 1.1.13 → 1.1.15
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 +22 -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/config/ProgramIdResolver.js +6 -2
- 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 +167 -98
- package/dist/modules/execute.d.ts +5 -0
- package/dist/modules/execute.js +109 -50
- 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/AccountTestFixture.js +3 -3
- 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 +17 -6
- package/dist/types.js +2 -1
- 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
|
@@ -17,6 +17,7 @@ const DEFAULT_FEE_VAULT_SHARD_COUNT = (() => {
|
|
|
17
17
|
return 2;
|
|
18
18
|
}
|
|
19
19
|
})();
|
|
20
|
+
const MAX_FEE_VAULT_SHARD_COUNT = 8;
|
|
20
21
|
const FEE_VAULT_NAMESPACE_SEED = Buffer.from([
|
|
21
22
|
0xff, 0x66, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x6d, 0x5f, 0x66, 0x65, 0x65,
|
|
22
23
|
0x5f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x31,
|
|
@@ -25,7 +26,7 @@ const EXECUTE_FEE_HEADER_A = 0xff;
|
|
|
25
26
|
const EXECUTE_FEE_HEADER_B = 0x53;
|
|
26
27
|
function clampShardCount(rawCount) {
|
|
27
28
|
const normalized = rawCount > 0 ? rawCount : DEFAULT_FEE_VAULT_SHARD_COUNT;
|
|
28
|
-
return Math.max(1, Math.min(
|
|
29
|
+
return Math.max(1, Math.min(MAX_FEE_VAULT_SHARD_COUNT, normalized));
|
|
29
30
|
}
|
|
30
31
|
async function deriveProgramFeeVault(programId, shardIndex) {
|
|
31
32
|
const { PublicKey } = await import("@solana/web3.js");
|
|
@@ -277,56 +278,100 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
|
|
|
277
278
|
: selectFeeShard(shardCount);
|
|
278
279
|
const feeVault = await deriveProgramFeeVault(programId, feeShardIndex);
|
|
279
280
|
const instructionAccounts = [
|
|
281
|
+
// Execute runtime reads script/vm_state; keep them readonly.
|
|
280
282
|
{ pubkey: scriptAccount, isSigner: false, isWritable: false },
|
|
281
283
|
{ pubkey: vmState, isSigner: false, isWritable: false },
|
|
282
284
|
];
|
|
283
285
|
const abiAccountMetadata = new Map();
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
286
|
+
const normalizeAccountPubkey = (value) => {
|
|
287
|
+
if (!value)
|
|
288
|
+
return undefined;
|
|
289
|
+
if (typeof value === "string")
|
|
290
|
+
return value;
|
|
291
|
+
if (typeof value.toBase58 === "function")
|
|
292
|
+
return value.toBase58();
|
|
293
|
+
if (typeof value.toString === "function") {
|
|
294
|
+
const str = value.toString();
|
|
295
|
+
if (str && str !== "[object Object]")
|
|
296
|
+
return str;
|
|
297
|
+
}
|
|
298
|
+
return undefined;
|
|
299
|
+
};
|
|
300
|
+
const isAccountParam = (param) => {
|
|
301
|
+
if (!param)
|
|
302
|
+
return false;
|
|
303
|
+
if (param.is_account || param.isAccount)
|
|
304
|
+
return true;
|
|
305
|
+
const type = (param.type || param.param_type || "").toString().trim().toLowerCase();
|
|
306
|
+
return type === "account" || type === "mint" || type === "tokenaccount";
|
|
307
|
+
};
|
|
308
|
+
const allParams = funcDef && Array.isArray(funcDef.parameters) ? funcDef.parameters : [];
|
|
309
|
+
const accountParams = allParams.filter((param) => isAccountParam(param));
|
|
310
|
+
const hasFullParameterList = allParams.length > 0 && parameters.length === allParams.length;
|
|
311
|
+
if (accountParams.length > 0) {
|
|
312
|
+
// First pass: detect if there's an @init constraint and identify payer param.
|
|
289
313
|
let hasInit = false;
|
|
290
|
-
let
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
306
|
-
}
|
|
314
|
+
let initParamName;
|
|
315
|
+
let payerParamName;
|
|
316
|
+
for (const param of accountParams) {
|
|
317
|
+
const attributes = param.attributes || [];
|
|
318
|
+
if (attributes.includes("init")) {
|
|
319
|
+
hasInit = true;
|
|
320
|
+
initParamName = param.name;
|
|
321
|
+
break;
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
if (hasInit) {
|
|
325
|
+
for (const param of accountParams) {
|
|
326
|
+
if (param.name !== initParamName &&
|
|
327
|
+
(param.attributes || []).includes("signer")) {
|
|
328
|
+
payerParamName = param.name;
|
|
307
329
|
break;
|
|
308
330
|
}
|
|
309
331
|
}
|
|
310
332
|
}
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
333
|
+
// Resolve pubkeys for account params in either full-param mode or args-only mode.
|
|
334
|
+
const accountPubkeysByParamName = new Map();
|
|
335
|
+
if (hasFullParameterList) {
|
|
336
|
+
allParams.forEach((param, paramIndex) => {
|
|
337
|
+
if (!isAccountParam(param))
|
|
338
|
+
return;
|
|
339
|
+
const pubkey = normalizeAccountPubkey(parameters[paramIndex]);
|
|
315
340
|
if (pubkey) {
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
341
|
+
accountPubkeysByParamName.set(param.name, pubkey);
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
const count = Math.min(accountParams.length, accounts.length);
|
|
347
|
+
for (let i = 0; i < count; i++) {
|
|
348
|
+
const paramName = accountParams[i]?.name;
|
|
349
|
+
const pubkey = accounts[i];
|
|
350
|
+
if (paramName && pubkey) {
|
|
351
|
+
accountPubkeysByParamName.set(paramName, pubkey);
|
|
326
352
|
}
|
|
327
353
|
}
|
|
328
|
-
}
|
|
354
|
+
}
|
|
355
|
+
for (const param of accountParams) {
|
|
356
|
+
const pubkey = accountPubkeysByParamName.get(param.name);
|
|
357
|
+
if (!pubkey)
|
|
358
|
+
continue;
|
|
359
|
+
const attributes = param.attributes || [];
|
|
360
|
+
const isSigner = attributes.includes("signer");
|
|
361
|
+
const isWritable = attributes.includes("mut") ||
|
|
362
|
+
attributes.includes("init") ||
|
|
363
|
+
(hasInit && payerParamName === param.name);
|
|
364
|
+
const existing = abiAccountMetadata.get(pubkey) || {
|
|
365
|
+
isSigner: false,
|
|
366
|
+
isWritable: false,
|
|
367
|
+
};
|
|
368
|
+
abiAccountMetadata.set(pubkey, {
|
|
369
|
+
isSigner: existing.isSigner || isSigner,
|
|
370
|
+
isWritable: existing.isWritable || isWritable,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
329
373
|
}
|
|
374
|
+
const unknownAccounts = [];
|
|
330
375
|
const userInstructionAccounts = accounts.map((acc) => {
|
|
331
376
|
// Check both derived ABI metadata and passed-in metadata (from FunctionBuilder)
|
|
332
377
|
const abiMetadata = abiAccountMetadata.get(acc);
|
|
@@ -335,25 +380,40 @@ export async function generateExecuteInstruction(scriptAccount, functionName, pa
|
|
|
335
380
|
const isSigner = metadata ? metadata.isSigner : false;
|
|
336
381
|
const isWritable = metadata
|
|
337
382
|
? (metadata.isSystemAccount ? false : metadata.isWritable)
|
|
338
|
-
:
|
|
383
|
+
: false;
|
|
384
|
+
if (!metadata) {
|
|
385
|
+
unknownAccounts.push(acc);
|
|
386
|
+
}
|
|
339
387
|
return {
|
|
340
388
|
pubkey: acc,
|
|
341
389
|
isSigner,
|
|
342
390
|
isWritable
|
|
343
391
|
};
|
|
344
392
|
});
|
|
393
|
+
if (unknownAccounts.length > 0) {
|
|
394
|
+
const deduped = Array.from(new Set(unknownAccounts));
|
|
395
|
+
console.warn(`[FiveSDK] Missing account metadata for ${deduped.join(", ")}; defaulting readonly. Pass accountMetadata or use FiveProgram.function(...).instruction().`);
|
|
396
|
+
}
|
|
345
397
|
instructionAccounts.push(...userInstructionAccounts);
|
|
346
398
|
const instructionData = encodeExecuteInstruction(functionIndex, encodedParams, actualParamCount, feeShardIndex, options.debug === true);
|
|
347
399
|
// Runtime requires strict tail: [payer, fee_vault, system_program].
|
|
400
|
+
// Prefer explicit payer, otherwise infer from signer+writable account metadata only.
|
|
348
401
|
const signerCandidates = instructionAccounts
|
|
349
402
|
.filter((acc) => acc.isSigner)
|
|
350
403
|
.map((acc) => acc.pubkey);
|
|
351
|
-
const
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
404
|
+
const metadataFor = (pubkey) => options.accountMetadata?.get(pubkey) || abiAccountMetadata.get(pubkey);
|
|
405
|
+
const writableSignerCandidates = signerCandidates.filter((pubkey) => {
|
|
406
|
+
const metadata = metadataFor(pubkey);
|
|
407
|
+
if (!metadata)
|
|
408
|
+
return false;
|
|
409
|
+
return !metadata.isSystemAccount && metadata.isWritable;
|
|
410
|
+
});
|
|
411
|
+
const inferredPayer = options.payerAccount || writableSignerCandidates[0];
|
|
355
412
|
if (!inferredPayer) {
|
|
356
|
-
|
|
413
|
+
if (signerCandidates.length > 0) {
|
|
414
|
+
throw new Error("Could not infer execute fee payer account from writable signer metadata. Pass options.payerAccount or use FiveProgram.function(...).payer(...).instruction().");
|
|
415
|
+
}
|
|
416
|
+
throw new Error("Could not infer execute fee payer account. Provide options.payerAccount or include a writable signer account.");
|
|
357
417
|
}
|
|
358
418
|
const feeTailAccounts = [
|
|
359
419
|
{ pubkey: inferredPayer, isSigner: true, isWritable: true },
|
|
@@ -404,6 +464,7 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
404
464
|
vmStateAccount: options.vmStateAccount,
|
|
405
465
|
fiveVMProgramId: options.fiveVMProgramId,
|
|
406
466
|
abi: options.abi,
|
|
467
|
+
accountMetadata: options.accountMetadata,
|
|
407
468
|
feeShardIndex: options.feeShardIndex,
|
|
408
469
|
payerAccount: options.payerAccount || signerKeypair.publicKey.toString(),
|
|
409
470
|
});
|
|
@@ -430,7 +491,6 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
430
491
|
for (const meta of accountKeys) {
|
|
431
492
|
if (meta.pubkey === signerPubkey) {
|
|
432
493
|
meta.isSigner = true;
|
|
433
|
-
meta.isWritable = true;
|
|
434
494
|
signerFound = true;
|
|
435
495
|
}
|
|
436
496
|
}
|
|
@@ -438,7 +498,7 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
438
498
|
accountKeys.push({
|
|
439
499
|
pubkey: signerPubkey,
|
|
440
500
|
isSigner: true,
|
|
441
|
-
isWritable:
|
|
501
|
+
isWritable: false,
|
|
442
502
|
});
|
|
443
503
|
}
|
|
444
504
|
const executeInstruction = new TransactionInstruction({
|
|
@@ -452,8 +512,8 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
452
512
|
});
|
|
453
513
|
transaction.add(executeInstruction);
|
|
454
514
|
transaction.feePayer = signerKeypair.publicKey;
|
|
455
|
-
const
|
|
456
|
-
transaction.recentBlockhash = blockhash;
|
|
515
|
+
const latestBlockhash = await connection.getLatestBlockhash("confirmed");
|
|
516
|
+
transaction.recentBlockhash = latestBlockhash.blockhash;
|
|
457
517
|
transaction.partialSign(signerKeypair);
|
|
458
518
|
const firstSig = transaction.signatures[0]?.signature;
|
|
459
519
|
if (firstSig) {
|
|
@@ -465,12 +525,11 @@ export async function executeOnSolana(scriptAccount, connection, signerKeypair,
|
|
|
465
525
|
maxRetries: options.maxRetries || 3,
|
|
466
526
|
});
|
|
467
527
|
lastSignature = signature;
|
|
468
|
-
const latestBlockhash = await connection.getLatestBlockhash("confirmed");
|
|
469
528
|
const confirmation = await confirmTransactionRobust(connection, signature, {
|
|
470
529
|
commitment: "confirmed",
|
|
471
530
|
timeoutMs: 120000,
|
|
472
531
|
debug: options.debug,
|
|
473
|
-
blockhash,
|
|
532
|
+
blockhash: latestBlockhash.blockhash,
|
|
474
533
|
lastValidBlockHeight: latestBlockhash.lastValidBlockHeight,
|
|
475
534
|
});
|
|
476
535
|
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
|
|
@@ -219,7 +219,7 @@ export class AccountTestFixture {
|
|
|
219
219
|
else if (spec.type === 'state' || spec.type === 'mutable') {
|
|
220
220
|
// Create state/mutable account with initial data
|
|
221
221
|
const space = 1024; // Default space
|
|
222
|
-
const owner = options.fiveVMProgramId || new PublicKey('
|
|
222
|
+
const owner = options.fiveVMProgramId || new PublicKey('5ive58PJUPaTyAe7tvU1bvBi25o7oieLLTRsJDoQNJst');
|
|
223
223
|
// Serialize state data if provided
|
|
224
224
|
let initialData;
|
|
225
225
|
if (spec.state && spec.type === 'state') {
|
|
@@ -242,7 +242,7 @@ export class AccountTestFixture {
|
|
|
242
242
|
else if (spec.type === 'init') {
|
|
243
243
|
// Create init account (will be initialized by script)
|
|
244
244
|
const space = 1024;
|
|
245
|
-
const owner = options.fiveVMProgramId || new PublicKey('
|
|
245
|
+
const owner = options.fiveVMProgramId || new PublicKey('5ive58PJUPaTyAe7tvU1bvBi25o7oieLLTRsJDoQNJst');
|
|
246
246
|
publicKey = await manager.createAccount(space, owner);
|
|
247
247
|
if (options.debug) {
|
|
248
248
|
console.log(` ${spec.name} (init): ${publicKey.toString()}`);
|
|
@@ -256,7 +256,7 @@ export class AccountTestFixture {
|
|
|
256
256
|
else {
|
|
257
257
|
// Create readonly account
|
|
258
258
|
const space = 0;
|
|
259
|
-
const owner = options.fiveVMProgramId || new PublicKey('
|
|
259
|
+
const owner = options.fiveVMProgramId || new PublicKey('5ive58PJUPaTyAe7tvU1bvBi25o7oieLLTRsJDoQNJst');
|
|
260
260
|
publicKey = await manager.createAccount(space, owner);
|
|
261
261
|
if (options.debug) {
|
|
262
262
|
console.log(` ${spec.name} (readonly): ${publicKey.toString()}`);
|
|
@@ -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
|
}
|