@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.
- package/README.md +16 -0
- package/dist/FiveSDK.d.ts +45 -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/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 +172 -99
- package/dist/modules/execute.d.ts +5 -0
- package/dist/modules/execute.js +115 -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/FunctionBuilder.d.ts +8 -0
- package/dist/program/FunctionBuilder.js +18 -5
- 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/assets/vm/dummy.file +0 -0
- package/dist/assets/vm/five_vm_wasm_bg.js +0 -3307
package/dist/modules/execute.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
282
|
-
|
|
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
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
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
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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 =
|
|
378
|
+
const metadata = passedMetadata || abiMetadata;
|
|
329
379
|
const isSigner = metadata ? metadata.isSigner : false;
|
|
330
|
-
const isWritable = metadata
|
|
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
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
|
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) {
|
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
|
}
|
|
@@ -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
|
-
|
|
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 =
|
|
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
|