@5ive-tech/sdk 1.1.16 → 1.1.18

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Five VM Team
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
Binary file
@@ -3,6 +3,12 @@ export interface VmClusterProfile {
3
3
  configPath: string;
4
4
  programId: string;
5
5
  feeVaultShardCount: number;
6
+ sessionService?: {
7
+ scriptAccount?: string;
8
+ codeHash?: string;
9
+ status?: 'active' | 'disabled';
10
+ version?: number;
11
+ };
6
12
  }
7
13
  export interface VmClusterAddresses {
8
14
  cluster: VmClusterProfile['cluster'];
@@ -84,11 +84,27 @@ export class VmClusterConfigResolver {
84
84
  throw new Error(`Invalid fee_vault_shard_count for cluster ${cluster}`);
85
85
  }
86
86
  const programId = new PublicKey(entry.program_id).toBase58();
87
+ const sessionScript = entry.session_v1_script_account
88
+ ? new PublicKey(entry.session_v1_script_account).toBase58()
89
+ : undefined;
90
+ const sessionHash = entry.session_v1_code_hash
91
+ ? new PublicKey(entry.session_v1_code_hash).toBase58()
92
+ : undefined;
93
+ const sessionStatus = entry.session_v1_status === 0 ? 'disabled' : 'active';
94
+ const sessionVersion = Number.isInteger(entry.session_v1_version)
95
+ ? entry.session_v1_version
96
+ : 1;
87
97
  return {
88
98
  cluster: cluster,
89
99
  configPath,
90
100
  programId,
91
101
  feeVaultShardCount: entry.fee_vault_shard_count,
102
+ sessionService: {
103
+ scriptAccount: sessionScript,
104
+ codeHash: sessionHash,
105
+ status: sessionStatus,
106
+ version: sessionVersion,
107
+ },
92
108
  };
93
109
  }
94
110
  static deriveVmAddresses(profile) {
@@ -68,6 +68,7 @@ export class BytecodeEncoder {
68
68
  maxLen: typeSpec.maxLen,
69
69
  };
70
70
  });
71
+ let wasmLoadError = null;
71
72
  // Load WASM module using shared loader
72
73
  if (!wasmModule) {
73
74
  try {
@@ -88,11 +89,12 @@ export class BytecodeEncoder {
88
89
  }
89
90
  }
90
91
  catch (e) {
91
- // Silently ignore loader errors and try fallback
92
92
  wasmModule = null;
93
+ wasmLoadError = e;
93
94
  }
94
95
  // Fallback: import the wasm-pack generated module for Node.js.
95
- if (!wasmModule && typeof process !== 'undefined') {
96
+ const hasWindow = typeof globalThis.window !== 'undefined';
97
+ if (!wasmModule && !hasWindow && typeof process !== 'undefined') {
96
98
  console.log("[DEBUG] (SRC) Attempting wasm-pack module import...");
97
99
  try {
98
100
  const fs = await import('fs');
@@ -123,6 +125,14 @@ export class BytecodeEncoder {
123
125
  // Don't throw - let it fall through to error handling below.
124
126
  }
125
127
  }
128
+ if (!wasmModule || !wasmModule.ParameterEncoder) {
129
+ const detail = wasmLoadError instanceof Error
130
+ ? wasmLoadError.message
131
+ : wasmLoadError
132
+ ? String(wasmLoadError)
133
+ : "WASM module loaded without ParameterEncoder";
134
+ throw new Error(`[BytecodeEncoder] WASM ParameterEncoder unavailable: ${detail}`);
135
+ }
126
136
  }
127
137
  const filteredParams = normalizedParameters;
128
138
  const paramValues = filteredParams.map(param => {
@@ -92,6 +92,10 @@ export interface ParameterDefinition {
92
92
  param_type?: string;
93
93
  /** Whether this is an account parameter */
94
94
  is_account?: boolean;
95
+ /** Compiler-injected parameter not authored in source */
96
+ implicit?: boolean;
97
+ /** Parameter origin */
98
+ source?: 'authored' | 'compiler';
95
99
  /** Account attributes (e.g., "mut", "signer", "init") */
96
100
  attributes?: string[];
97
101
  /** Whether parameter is optional */
@@ -16,6 +16,7 @@ options?: {
16
16
  fiveVMProgramId?: string;
17
17
  computeBudget?: number;
18
18
  exportMetadata?: ExportMetadataInput;
19
+ service?: "session_v1";
19
20
  }): Promise<{
20
21
  transaction: any;
21
22
  scriptKeypair: any;
@@ -34,6 +35,7 @@ options?: {
34
35
  vmStateAccount?: string;
35
36
  adminAccount?: string;
36
37
  exportMetadata?: ExportMetadataInput;
38
+ service?: "session_v1";
37
39
  }): Promise<{
38
40
  success: boolean;
39
41
  programId?: string;
@@ -18,10 +18,20 @@ const FEE_VAULT_NAMESPACE_SEED = Buffer.from([
18
18
  0xff, 0x66, 0x69, 0x76, 0x65, 0x5f, 0x76, 0x6d, 0x5f, 0x66, 0x65, 0x65,
19
19
  0x5f, 0x76, 0x61, 0x75, 0x6c, 0x74, 0x5f, 0x76, 0x31,
20
20
  ]);
21
+ const SESSION_V1_SERVICE_SEED = Buffer.from("session_v1", "utf-8");
22
+ const SERVICE_KIND_NONE = 0;
23
+ const SERVICE_KIND_SESSION_V1 = 1;
21
24
  function clampShardCount(rawCount) {
22
25
  const normalized = rawCount > 0 ? rawCount : DEFAULT_FEE_VAULT_SHARD_COUNT;
23
26
  return Math.max(1, Math.min(MAX_FEE_VAULT_SHARD_COUNT, normalized));
24
27
  }
28
+ function resolveServiceKind(service) {
29
+ if (!service)
30
+ return SERVICE_KIND_NONE;
31
+ if (service === "session_v1")
32
+ return SERVICE_KIND_SESSION_V1;
33
+ throw new Error(`Unsupported deploy service: ${service}`);
34
+ }
25
35
  function normalizeRpcEndpoint(connection) {
26
36
  return String(connection?.rpcEndpoint || connection?._rpcEndpoint || "").toLowerCase();
27
37
  }
@@ -71,6 +81,14 @@ async function deriveProgramFeeVault(programId, shardIndex) {
71
81
  }
72
82
  async function resolveScriptAccountDerivation(bytecode, deployer, programId, options) {
73
83
  const { PublicKey } = await import("@solana/web3.js");
84
+ if (options.service === "session_v1") {
85
+ const [servicePda, serviceBump] = PublicKey.findProgramAddressSync([SESSION_V1_SERVICE_SEED], new PublicKey(programId));
86
+ return {
87
+ address: servicePda.toBase58(),
88
+ bump: serviceBump,
89
+ seed: "session_v1",
90
+ };
91
+ }
74
92
  if (options.scriptAccount) {
75
93
  validator.validateBase58Address(options.scriptAccount, "options.scriptAccount");
76
94
  if (!options.scriptSeed) {
@@ -181,7 +199,7 @@ export async function generateDeployInstruction(bytecode, deployer, options = {}
181
199
  isSigner: false,
182
200
  isWritable: false,
183
201
  });
184
- const instructionData = encodeDeployInstruction(bytecode, options.permissions || 0, exportMetadata, deployShardIndex, deployVault.bump);
202
+ const instructionData = encodeDeployInstruction(bytecode, options.permissions || 0, exportMetadata, deployShardIndex, deployVault.bump, resolveServiceKind(options.service));
185
203
  const result = {
186
204
  programId: programId,
187
205
  instruction: {
@@ -193,15 +211,23 @@ export async function generateDeployInstruction(bytecode, deployer, options = {}
193
211
  requiredSigners: [deployer],
194
212
  estimatedCost: rentLamports + (options.extraLamports || 0),
195
213
  bytecodeSize: bytecode.length,
196
- setupInstructions: {
197
- createScriptAccount: {
198
- pda: scriptAccount,
199
- seed: scriptSeed,
200
- space: totalAccountSize,
201
- rent: rentLamports,
202
- owner: programId,
214
+ setupInstructions: options.service === "session_v1"
215
+ ? {
216
+ serviceDeployment: {
217
+ service: "session_v1",
218
+ pda: scriptAccount,
219
+ seed: scriptSeed,
220
+ },
221
+ }
222
+ : {
223
+ createScriptAccount: {
224
+ pda: scriptAccount,
225
+ seed: scriptSeed,
226
+ space: totalAccountSize,
227
+ rent: rentLamports,
228
+ owner: programId,
229
+ },
203
230
  },
204
- },
205
231
  adminAccount: options.adminAccount,
206
232
  };
207
233
  if (options.debug) {
@@ -237,9 +263,18 @@ options = {}) {
237
263
  const { Keypair, PublicKey, Transaction, TransactionInstruction, SystemProgram, ComputeBudgetProgram, } = await import("@solana/web3.js");
238
264
  const programIdStr = ProgramIdResolver.resolve(options.fiveVMProgramId);
239
265
  const programId = new PublicKey(programIdStr);
240
- // Generate script keypair
241
- const scriptKeypair = Keypair.generate();
242
- const scriptAccount = scriptKeypair.publicKey.toString();
266
+ const serviceKind = resolveServiceKind(options.service);
267
+ let scriptKeypair = null;
268
+ let scriptPubkey;
269
+ if (serviceKind === SERVICE_KIND_SESSION_V1) {
270
+ const derived = PublicKey.findProgramAddressSync([SESSION_V1_SERVICE_SEED], new PublicKey(programIdStr));
271
+ scriptPubkey = derived[0];
272
+ }
273
+ else {
274
+ scriptKeypair = Keypair.generate();
275
+ scriptPubkey = scriptKeypair.publicKey;
276
+ }
277
+ const scriptAccount = scriptPubkey.toString();
243
278
  // Calculate account size and rent
244
279
  const exportMetadata = encodeExportMetadata(options.exportMetadata);
245
280
  const totalAccountSize = SCRIPT_ACCOUNT_HEADER_LEN + exportMetadata.length + bytecode.length;
@@ -275,18 +310,22 @@ options = {}) {
275
310
  }));
276
311
  }
277
312
  // 2. Create Script Account
278
- tx.add(SystemProgram.createAccount({
279
- fromPubkey: deployerPublicKey,
280
- newAccountPubkey: scriptKeypair.publicKey,
281
- lamports: rentLamports,
282
- space: totalAccountSize,
283
- programId: programId,
284
- }));
313
+ tx.add(...(scriptKeypair
314
+ ? [
315
+ SystemProgram.createAccount({
316
+ fromPubkey: deployerPublicKey,
317
+ newAccountPubkey: scriptKeypair.publicKey,
318
+ lamports: rentLamports,
319
+ space: totalAccountSize,
320
+ programId: programId,
321
+ }),
322
+ ]
323
+ : []));
285
324
  // 3. Deploy Instruction
286
- const deployData = encodeDeployInstruction(bytecode, 0, exportMetadata);
325
+ const deployData = encodeDeployInstruction(bytecode, 0, exportMetadata, 0, undefined, serviceKind);
287
326
  tx.add(new TransactionInstruction({
288
327
  keys: [
289
- { pubkey: scriptKeypair.publicKey, isSigner: false, isWritable: true },
328
+ { pubkey: scriptPubkey, isSigner: false, isWritable: true },
290
329
  { pubkey: vmStatePubkey, isSigner: false, isWritable: true },
291
330
  { pubkey: deployerPublicKey, isSigner: true, isWritable: true },
292
331
  ],
@@ -297,7 +336,9 @@ options = {}) {
297
336
  tx.recentBlockhash = blockhash;
298
337
  tx.feePayer = deployerPublicKey;
299
338
  // Partial sign with generated keys
300
- tx.partialSign(scriptKeypair);
339
+ if (scriptKeypair) {
340
+ tx.partialSign(scriptKeypair);
341
+ }
301
342
  return {
302
343
  transaction: tx,
303
344
  scriptKeypair,
@@ -319,10 +360,25 @@ options = {}) {
319
360
  }
320
361
  // Generate script keypair like frontend-five
321
362
  const { Keypair, PublicKey, Transaction, TransactionInstruction, SystemProgram, } = await import("@solana/web3.js");
322
- const scriptKeypair = Keypair.generate();
323
- const scriptAccount = scriptKeypair.publicKey.toString();
363
+ const serviceKind = resolveServiceKind(options.service);
364
+ let scriptKeypair = null;
365
+ let scriptPubkey;
366
+ if (serviceKind === SERVICE_KIND_SESSION_V1) {
367
+ const derived = PublicKey.findProgramAddressSync([SESSION_V1_SERVICE_SEED], new PublicKey(programId));
368
+ scriptPubkey = derived[0];
369
+ }
370
+ else {
371
+ scriptKeypair = Keypair.generate();
372
+ scriptPubkey = scriptKeypair.publicKey;
373
+ }
374
+ const scriptAccount = scriptPubkey.toString();
324
375
  if (options.debug) {
325
- console.log(`[FiveSDK] Generated script keypair: ${scriptAccount}`);
376
+ if (scriptKeypair) {
377
+ console.log(`[FiveSDK] Generated script keypair: ${scriptAccount}`);
378
+ }
379
+ else {
380
+ console.log(`[FiveSDK] Using canonical service PDA script account: ${scriptAccount}`);
381
+ }
326
382
  }
327
383
  // Calculate account size and rent
328
384
  const exportMetadata = encodeExportMetadata(options.exportMetadata);
@@ -372,21 +428,23 @@ options = {}) {
372
428
  }
373
429
  catch { }
374
430
  }
375
- // 1) Create script account
376
- const createAccountIx = SystemProgram.createAccount({
377
- fromPubkey: deployerKeypair.publicKey,
378
- newAccountPubkey: scriptKeypair.publicKey,
379
- lamports: rentLamports,
380
- space: totalAccountSize,
381
- programId: new PublicKey(programId),
382
- });
383
- tx.add(createAccountIx);
384
- const deployData = encodeDeployInstruction(bytecode, 0, exportMetadata, deployShardIndex, deployVault.bump);
431
+ // 1) Create script account for non-service deployments.
432
+ if (scriptKeypair) {
433
+ const createAccountIx = SystemProgram.createAccount({
434
+ fromPubkey: deployerKeypair.publicKey,
435
+ newAccountPubkey: scriptKeypair.publicKey,
436
+ lamports: rentLamports,
437
+ space: totalAccountSize,
438
+ programId: new PublicKey(programId),
439
+ });
440
+ tx.add(createAccountIx);
441
+ }
442
+ const deployData = encodeDeployInstruction(bytecode, 0, exportMetadata, deployShardIndex, deployVault.bump, serviceKind);
385
443
  const instructionDataBuffer = Buffer.from(deployData);
386
444
  const deployIx = new TransactionInstruction({
387
445
  keys: [
388
446
  {
389
- pubkey: scriptKeypair.publicKey,
447
+ pubkey: scriptPubkey,
390
448
  isSigner: false,
391
449
  isWritable: true,
392
450
  },
@@ -419,7 +477,9 @@ options = {}) {
419
477
  tx.recentBlockhash = blockhash;
420
478
  tx.feePayer = deployerKeypair.publicKey;
421
479
  tx.partialSign(deployerKeypair);
422
- tx.partialSign(scriptKeypair);
480
+ if (scriptKeypair) {
481
+ tx.partialSign(scriptKeypair);
482
+ }
423
483
  const txSerialized = tx.serialize();
424
484
  if (options.debug) {
425
485
  console.log(`[FiveSDK] Transaction serialized: ${txSerialized.length} bytes`);
@@ -1085,13 +1145,14 @@ options = {}) {
1085
1145
  };
1086
1146
  }
1087
1147
  }
1088
- function encodeDeployInstruction(bytecode, permissions = 0, metadata = new Uint8Array(), feeShardIndex = 0, feeVaultBump) {
1148
+ function encodeDeployInstruction(bytecode, permissions = 0, metadata = new Uint8Array(), feeShardIndex = 0, feeVaultBump, serviceKind = SERVICE_KIND_NONE) {
1089
1149
  const lengthBuffer = Buffer.allocUnsafe(4);
1090
1150
  lengthBuffer.writeUInt32LE(bytecode.length, 0);
1091
1151
  const metadataLenBuffer = Buffer.allocUnsafe(4);
1092
1152
  metadataLenBuffer.writeUInt32LE(metadata.length, 0);
1093
1153
  const hasFeeTrailer = typeof feeVaultBump === "number";
1094
- const result = new Uint8Array(1 + 4 + 1 + 4 + metadata.length + bytecode.length + (hasFeeTrailer ? 2 : 0));
1154
+ const hasServiceTrailer = serviceKind !== SERVICE_KIND_NONE;
1155
+ const result = new Uint8Array(1 + 4 + 1 + 4 + metadata.length + bytecode.length + (hasFeeTrailer ? 2 : 0) + (hasServiceTrailer ? 1 : 0));
1095
1156
  result[0] = 8; // Deploy discriminator (matches on-chain FIVE program)
1096
1157
  result.set(new Uint8Array(lengthBuffer), 1); // u32 LE length at bytes 1-4
1097
1158
  result[5] = permissions; // permissions byte at byte 5
@@ -1103,6 +1164,10 @@ function encodeDeployInstruction(bytecode, permissions = 0, metadata = new Uint8
1103
1164
  result[trailerOffset] = feeShardIndex & 0xff;
1104
1165
  result[trailerOffset + 1] = feeVaultBump & 0xff;
1105
1166
  }
1167
+ if (hasServiceTrailer) {
1168
+ const trailerOffset = 10 + metadata.length + bytecode.length + (hasFeeTrailer ? 2 : 0);
1169
+ result[trailerOffset] = serviceKind & 0xff;
1170
+ }
1106
1171
  console.log(`[FiveSDK] Deploy instruction encoded:`, {
1107
1172
  discriminator: result[0],
1108
1173
  lengthBytes: Array.from(new Uint8Array(lengthBuffer)),
@@ -1110,7 +1175,8 @@ function encodeDeployInstruction(bytecode, permissions = 0, metadata = new Uint8
1110
1175
  metadataLength: metadata.length,
1111
1176
  bytecodeLength: bytecode.length,
1112
1177
  totalInstructionLength: result.length,
1113
- expectedFormat: `[8, ${bytecode.length}_as_u32le, 0x${permissions.toString(16).padStart(2, '0')}, ${metadata.length}_as_u32le, metadata_bytes, bytecode_bytes, optional(shard,bump)]`,
1178
+ serviceKind,
1179
+ expectedFormat: `[8, ${bytecode.length}_as_u32le, 0x${permissions.toString(16).padStart(2, '0')}, ${metadata.length}_as_u32le, metadata_bytes, bytecode_bytes, optional(shard,bump), optional(service_kind)]`,
1114
1180
  instructionHex: Buffer.from(result).toString("hex").substring(0, 20) + "...",
1115
1181
  });
1116
1182
  return result;
@@ -21,6 +21,7 @@ import type { ScriptABI, FunctionDefinition, AccountFetcher } from '../metadata/
21
21
  import type { Provider } from '../types.js';
22
22
  import { FunctionBuilder } from './FunctionBuilder.js';
23
23
  import { ProgramAccount } from './ProgramAccount.js';
24
+ import type { SessionManager } from './SessionManager.js';
24
25
  export interface FiveProgramOptions {
25
26
  /** Enable debug logging */
26
27
  debug?: boolean;
@@ -34,6 +35,13 @@ export interface FiveProgramOptions {
34
35
  feeReceiverAccount?: string;
35
36
  /** Wallet/Network Provider for RPC calls */
36
37
  provider?: Provider;
38
+ /** Optional session helper for delegated signer flows */
39
+ session?: {
40
+ manager: SessionManager;
41
+ mode?: 'auto' | 'force-direct' | 'force-session';
42
+ sessionAccountByFunction?: Record<string, string>;
43
+ delegateAccountByFunction?: Record<string, string>;
44
+ };
37
45
  }
38
46
  /**
39
47
  * FiveProgram represents a deployed Five script with its ABI
@@ -109,6 +117,10 @@ export declare class FiveProgram {
109
117
  * Get Five VM Program ID with consistent resolver precedence
110
118
  */
111
119
  getFiveVMProgramId(): string;
120
+ /**
121
+ * Return a program instance that auto-applies session account/delegate mapping.
122
+ */
123
+ withSession(config: NonNullable<FiveProgramOptions['session']>): FiveProgram;
112
124
  /**
113
125
  * Derive a Program Derived Address (PDA)
114
126
  *
@@ -210,6 +210,18 @@ export class FiveProgram {
210
210
  getFiveVMProgramId() {
211
211
  return ProgramIdResolver.resolve(this.options.fiveVMProgramId);
212
212
  }
213
+ /**
214
+ * Return a program instance that auto-applies session account/delegate mapping.
215
+ */
216
+ withSession(config) {
217
+ return new FiveProgram(this.scriptAccount, this.abi, {
218
+ ...this.options,
219
+ session: {
220
+ mode: 'auto',
221
+ ...config,
222
+ },
223
+ });
224
+ }
213
225
  /**
214
226
  * Derive a Program Derived Address (PDA)
215
227
  *
@@ -77,6 +77,8 @@ export declare class FunctionBuilder {
77
77
  skipPreflight?: boolean;
78
78
  computeUnits?: number;
79
79
  }): Promise<string>;
80
+ private applySessionDefaults;
81
+ private resolveSessionSigner;
80
82
  /**
81
83
  * Validate that all required parameters are provided
82
84
  * @throws Error if any required parameter is missing
@@ -79,6 +79,8 @@ export class FunctionBuilder {
79
79
  */
80
80
  async instruction() {
81
81
  // Validate parameters later, after auto-injection
82
+ // Optional session auto-wiring.
83
+ this.applySessionDefaults();
82
84
  // Resolve system accounts (auto-inject when needed)
83
85
  const resolver = new AccountResolver(this.options);
84
86
  const resolvedSystemAccounts = resolver.resolveSystemAccounts(this.functionDef, this.accountsMap);
@@ -156,11 +158,52 @@ export class FunctionBuilder {
156
158
  }
157
159
  const tx = await this.transaction({ computeUnits: options.computeUnits });
158
160
  // Send
159
- const signers = options.signers || [];
161
+ const signers = [...(options.signers || [])];
162
+ const sessionSigner = this.resolveSessionSigner();
163
+ if (sessionSigner) {
164
+ signers.push(sessionSigner);
165
+ }
160
166
  return await provider.sendAndConfirm(tx, signers, {
161
167
  skipPreflight: options.skipPreflight
162
168
  });
163
169
  }
170
+ applySessionDefaults() {
171
+ const session = this.options.session;
172
+ if (session?.mode === 'force-direct') {
173
+ return;
174
+ }
175
+ const functionName = this.functionDef.name;
176
+ const sessionAddress = session?.sessionAccountByFunction?.[functionName];
177
+ const authorityAddress = this.accountsMap.get('owner') || this.accountsMap.get('authority');
178
+ for (const param of this.functionDef.parameters) {
179
+ if (!param.is_account)
180
+ continue;
181
+ const attrs = param.attributes || [];
182
+ const isImplicit = param.implicit === true || param.source === 'compiler';
183
+ const isLegacyInjectedSessionParam = param.name === '__session';
184
+ if ((attrs.includes('session') || isImplicit || param.name === '__session') &&
185
+ sessionAddress &&
186
+ !this.accountsMap.has(param.name)) {
187
+ this.accountsMap.set(param.name, sessionAddress);
188
+ }
189
+ // Direct-owner fallback for compiler-injected implicit session wiring:
190
+ // if no session manager config is present, alias hidden session/delegate
191
+ // accounts to owner/authority so @session can take the direct-owner path.
192
+ if ((isImplicit || isLegacyInjectedSessionParam) && !this.accountsMap.has(param.name) && authorityAddress) {
193
+ if (isLegacyInjectedSessionParam) {
194
+ this.accountsMap.set(param.name, authorityAddress);
195
+ }
196
+ }
197
+ }
198
+ }
199
+ resolveSessionSigner() {
200
+ const session = this.options.session;
201
+ if (!session || session.mode === 'force-direct') {
202
+ return undefined;
203
+ }
204
+ const maybeSigner = session.manager?.delegateSigner;
205
+ return maybeSigner;
206
+ }
164
207
  /**
165
208
  * Validate that all required parameters are provided
166
209
  * @throws Error if any required parameter is missing
@@ -169,6 +212,13 @@ export class FunctionBuilder {
169
212
  for (const param of this.functionDef.parameters) {
170
213
  if (param.is_account) {
171
214
  if (!this.accountsMap.has(param.name)) {
215
+ const isImplicit = param.implicit === true ||
216
+ param.source === 'compiler' ||
217
+ param.name === '__session';
218
+ if (isImplicit) {
219
+ throw new Error(`Missing implicit account '${param.name}' for function '${this.functionDef.name}'. ` +
220
+ `Provide it via .accounts({ ${param.name}: ... }) or configure program.withSession(...) for auto-wiring.`);
221
+ }
172
222
  throw new Error(`Missing required account '${param.name}' for function '${this.functionDef.name}'`);
173
223
  }
174
224
  }
@@ -0,0 +1,61 @@
1
+ import { FiveProgram } from './FiveProgram.js';
2
+ export interface SessionScope {
3
+ functions: string[];
4
+ ttlSlots?: number;
5
+ bindAccount?: string;
6
+ nonce?: string;
7
+ }
8
+ export interface SessionCreateParams {
9
+ authority: string;
10
+ delegate: string;
11
+ targetProgram: string;
12
+ expiresAtSlot: number;
13
+ scopeHash: string;
14
+ bindAccount?: string;
15
+ nonce?: string;
16
+ payer?: string;
17
+ }
18
+ export interface SessionRecord {
19
+ sessionAddress: string;
20
+ authority: string;
21
+ delegate: string;
22
+ targetProgram: string;
23
+ expiresAtSlot: number;
24
+ }
25
+ export interface CanonicalSessionService {
26
+ cluster: 'localnet' | 'devnet' | 'mainnet';
27
+ scriptAccount: string;
28
+ codeHash: string;
29
+ version: number;
30
+ status: 'active' | 'disabled';
31
+ }
32
+ export interface SessionManagerOptions {
33
+ identity?: CanonicalSessionService;
34
+ enforceCanonical?: boolean;
35
+ allowUnsafeOverride?: boolean;
36
+ }
37
+ /**
38
+ * Lightweight helper around a deployed session-manager script.
39
+ * Uses normal Five execute flow; no VM opcode/runtime changes required.
40
+ */
41
+ export declare class SessionManager {
42
+ readonly managerProgram: FiveProgram;
43
+ readonly defaultTtlSlots: number;
44
+ readonly identity: CanonicalSessionService;
45
+ private readonly enforceCanonical;
46
+ private readonly allowUnsafeOverride;
47
+ constructor(managerProgram: FiveProgram, defaultTtlSlots?: number, // ~20m on Solana-like slot timings
48
+ options?: SessionManagerOptions);
49
+ static resolveCanonicalIdentity(input?: {
50
+ cluster?: 'localnet' | 'devnet' | 'mainnet';
51
+ vmProgramId?: string;
52
+ scriptAccount?: string;
53
+ codeHash?: string;
54
+ status?: 'active' | 'disabled';
55
+ version?: number;
56
+ }): CanonicalSessionService;
57
+ static scopeHashForFunctions(functions: string[]): string;
58
+ deriveSessionAddress(authority: string, delegate: string, targetProgram: string): Promise<string>;
59
+ buildCreateSessionInstruction(params: SessionCreateParams): Promise<import("../types.js").SerializedInstruction>;
60
+ buildRevokeSessionInstruction(authority: string, delegate: string, targetProgram: string, payer?: string): Promise<import("../types.js").SerializedInstruction>;
61
+ }
@@ -0,0 +1,97 @@
1
+ import { PublicKey } from '@solana/web3.js';
2
+ /**
3
+ * Lightweight helper around a deployed session-manager script.
4
+ * Uses normal Five execute flow; no VM opcode/runtime changes required.
5
+ */
6
+ export class SessionManager {
7
+ constructor(managerProgram, defaultTtlSlots = 3000, // ~20m on Solana-like slot timings
8
+ options = {}) {
9
+ this.managerProgram = managerProgram;
10
+ this.defaultTtlSlots = defaultTtlSlots;
11
+ this.identity =
12
+ options.identity ||
13
+ SessionManager.resolveCanonicalIdentity({
14
+ vmProgramId: this.managerProgram.getFiveVMProgramId(),
15
+ });
16
+ this.allowUnsafeOverride = options.allowUnsafeOverride ?? false;
17
+ const strictByDefault = this.identity.cluster === 'mainnet';
18
+ this.enforceCanonical = options.enforceCanonical ?? strictByDefault;
19
+ if (this.identity.cluster === 'mainnet' &&
20
+ options.enforceCanonical === false &&
21
+ !this.allowUnsafeOverride) {
22
+ throw new Error('Disabling canonical session manager on mainnet requires allowUnsafeOverride');
23
+ }
24
+ if (this.enforceCanonical && this.identity.status !== 'active') {
25
+ throw new Error('Canonical session service is disabled for current cluster');
26
+ }
27
+ const hasPinnedScript = this.identity.scriptAccount !== '11111111111111111111111111111111';
28
+ if (this.enforceCanonical &&
29
+ hasPinnedScript &&
30
+ this.managerProgram.getScriptAccount() !== this.identity.scriptAccount) {
31
+ throw new Error('SessionManager program does not match canonical session_v1 service');
32
+ }
33
+ }
34
+ static resolveCanonicalIdentity(input = {}) {
35
+ const vmProgramId = input.vmProgramId || process.env.FIVE_PROGRAM_ID;
36
+ if (!vmProgramId) {
37
+ throw new Error('SessionManager canonical identity requires vmProgramId or FIVE_PROGRAM_ID');
38
+ }
39
+ const vmProgram = new PublicKey(vmProgramId);
40
+ const [scriptPda] = PublicKey.findProgramAddressSync([Buffer.from('session_v1', 'utf-8')], vmProgram);
41
+ const cluster = input.cluster || (process.env.FIVE_VM_CLUSTER || 'localnet');
42
+ return {
43
+ cluster,
44
+ scriptAccount: input.scriptAccount || scriptPda.toBase58(),
45
+ codeHash: input.codeHash || '11111111111111111111111111111111',
46
+ version: input.version ?? 1,
47
+ status: input.status || 'active',
48
+ };
49
+ }
50
+ static scopeHashForFunctions(functions) {
51
+ const sorted = [...functions].sort();
52
+ // Stable v1 hash seed; caller may replace with stronger domain-specific hashing if desired.
53
+ let acc = 0n;
54
+ for (const ch of sorted.join('|')) {
55
+ acc = (acc * 131n + BigInt(ch.charCodeAt(0))) & ((1n << 256n) - 1n);
56
+ }
57
+ return '0x' + acc.toString(16).padStart(64, '0');
58
+ }
59
+ async deriveSessionAddress(authority, delegate, targetProgram) {
60
+ const [pda] = await this.managerProgram.findAddress(['session', authority, delegate, targetProgram], this.managerProgram.getFiveVMProgramId());
61
+ return pda;
62
+ }
63
+ async buildCreateSessionInstruction(params) {
64
+ const sessionAddress = await this.deriveSessionAddress(params.authority, params.delegate, params.targetProgram);
65
+ const builder = this.managerProgram
66
+ .function('create_session')
67
+ .accounts({
68
+ session: sessionAddress,
69
+ authority: params.authority,
70
+ delegate: params.delegate,
71
+ })
72
+ .args({
73
+ target_program: params.targetProgram,
74
+ expires_at_slot: params.expiresAtSlot,
75
+ scope_hash: params.scopeHash,
76
+ bind_account: params.bindAccount || '11111111111111111111111111111111',
77
+ nonce: params.nonce || '0x00',
78
+ });
79
+ if (params.payer) {
80
+ builder.payer(params.payer);
81
+ }
82
+ return builder.instruction();
83
+ }
84
+ async buildRevokeSessionInstruction(authority, delegate, targetProgram, payer) {
85
+ const sessionAddress = await this.deriveSessionAddress(authority, delegate, targetProgram);
86
+ const builder = this.managerProgram
87
+ .function('revoke_session')
88
+ .accounts({
89
+ session: sessionAddress,
90
+ authority,
91
+ });
92
+ if (payer) {
93
+ builder.payer(payer);
94
+ }
95
+ return builder.instruction();
96
+ }
97
+ }
@@ -22,3 +22,9 @@ export { AccountResolver } from './AccountResolver.js';
22
22
  export type { ResolvedSystemAccounts } from './AccountResolver.js';
23
23
  export { TypeGenerator } from './TypeGenerator.js';
24
24
  export type { TypeGeneratorOptions } from './TypeGenerator.js';
25
+ export * from './FiveProgram.js';
26
+ export * from './FunctionBuilder.js';
27
+ export * from './ProgramAccount.js';
28
+ export * from './TypeGenerator.js';
29
+ export * from './AccountResolver.js';
30
+ export * from './SessionManager.js';
@@ -19,3 +19,9 @@ export { FiveProgram } from './FiveProgram.js';
19
19
  export { FunctionBuilder } from './FunctionBuilder.js';
20
20
  export { AccountResolver } from './AccountResolver.js';
21
21
  export { TypeGenerator } from './TypeGenerator.js';
22
+ export * from './FiveProgram.js';
23
+ export * from './FunctionBuilder.js';
24
+ export * from './ProgramAccount.js';
25
+ export * from './TypeGenerator.js';
26
+ export * from './AccountResolver.js';
27
+ export * from './SessionManager.js';
@@ -18,7 +18,10 @@ export class TestDiscovery {
18
18
  return testCases;
19
19
  }
20
20
  if (testCases && typeof testCases === 'object') {
21
- return null;
21
+ return Object.entries(testCases).map(([name, value]) => ({
22
+ name,
23
+ ...(typeof value === 'object' && value !== null ? value : {}),
24
+ }));
22
25
  }
23
26
  return [];
24
27
  }
@@ -82,7 +85,7 @@ export class TestDiscovery {
82
85
  }
83
86
  for (const testCase of testCases) {
84
87
  tests.push({
85
- name: testCase.name,
88
+ name: testCase.name || testCase.function || testCase.id || 'unnamed_test',
86
89
  path: file,
87
90
  type: 'json-suite',
88
91
  description: testCase.description,
@@ -116,7 +119,7 @@ export class TestDiscovery {
116
119
  return [];
117
120
  }
118
121
  return testCases.map((testCase) => ({
119
- name: testCase.name,
122
+ name: testCase.name || testCase.function || testCase.id || 'unnamed_test',
120
123
  path: file,
121
124
  type: 'json-suite',
122
125
  description: testCase.description,
@@ -5,7 +5,7 @@
5
5
  * Five SDK usage. Provides comprehensive testing capabilities for Five VM scripts.
6
6
  */
7
7
  import { readFile } from 'fs/promises';
8
- import { basename } from 'path';
8
+ import { basename, dirname, join } from 'path';
9
9
  import { FiveSDK } from '../FiveSDK.js';
10
10
  import { TestDiscovery } from './TestDiscovery.js';
11
11
  /**
@@ -203,6 +203,9 @@ export class FiveTestRunner {
203
203
  const suites = [];
204
204
  const byFile = new Map();
205
205
  const loadedJsonSuites = new Set();
206
+ const jsonSuitePaths = new Set(discovered
207
+ .filter((test) => test.type === 'json-suite')
208
+ .map((test) => test.path));
206
209
  for (const test of discovered) {
207
210
  if (test.type === 'json-suite') {
208
211
  if (loadedJsonSuites.has(test.path)) {
@@ -211,13 +214,30 @@ export class FiveTestRunner {
211
214
  try {
212
215
  const content = await readFile(test.path, 'utf8');
213
216
  const data = JSON.parse(content);
214
- const testCases = Array.isArray(data.tests || data.testCases)
215
- ? (data.tests || data.testCases)
216
- : [];
217
+ const rawCases = data.tests || data.testCases || [];
218
+ const testCases = Array.isArray(rawCases)
219
+ ? rawCases
220
+ : Object.entries(rawCases).map(([name, value]) => ({
221
+ name,
222
+ ...(typeof value === 'object' && value !== null ? value : {}),
223
+ }));
224
+ const inferredSource = data.source
225
+ ? join(dirname(test.path), data.source)
226
+ : test.path.replace(/\.test\.json$/i, '.test.v');
227
+ const defaultSource = inferredSource;
228
+ const normalizedCases = testCases.map((testCase, idx) => {
229
+ const name = testCase.name || testCase.function || testCase.id || `test_${idx}`;
230
+ return {
231
+ ...testCase,
232
+ name,
233
+ source: testCase.source || defaultSource,
234
+ function: testCase.function || name,
235
+ };
236
+ });
217
237
  suites.push({
218
238
  name: data.name || basename(test.path, '.test.json'),
219
239
  description: data.description,
220
- testCases
240
+ testCases: normalizedCases
221
241
  });
222
242
  loadedJsonSuites.add(test.path);
223
243
  }
@@ -227,6 +247,12 @@ export class FiveTestRunner {
227
247
  continue;
228
248
  }
229
249
  if (test.type === 'v-source' && test.source) {
250
+ const jsonPath = test.path.endsWith('.test.v')
251
+ ? test.path.replace(/\.test\.v$/i, '.test.json')
252
+ : test.path.replace(/\.v$/i, '.test.json');
253
+ if (jsonSuitePaths.has(jsonPath)) {
254
+ continue;
255
+ }
230
256
  const cases = byFile.get(test.path) || [];
231
257
  cases.push({
232
258
  name: test.name,
package/dist/types.d.ts CHANGED
@@ -269,6 +269,7 @@ export interface DeploymentOptions {
269
269
  }>;
270
270
  };
271
271
  namespace?: string;
272
+ service?: 'session_v1';
272
273
  }
273
274
  export interface FeeInformation {
274
275
  feeBps: number;
@@ -8,6 +8,8 @@ export interface NormalizedABIFunction {
8
8
  optional?: boolean;
9
9
  is_account?: boolean;
10
10
  isAccount?: boolean;
11
+ implicit?: boolean;
12
+ source?: 'authored' | 'compiler';
11
13
  attributes?: string[];
12
14
  }>;
13
15
  returnType?: string;
package/dist/utils/abi.js CHANGED
@@ -21,6 +21,8 @@ export function normalizeAbiFunctions(abiFunctions) {
21
21
  optional: param.optional ?? false,
22
22
  is_account: param.is_account ?? param.isAccount ?? false,
23
23
  isAccount: param.isAccount ?? param.is_account ?? false,
24
+ implicit: param.implicit ?? false,
25
+ source: param.source ?? 'authored',
24
26
  attributes: Array.isArray(param.attributes) ? [...param.attributes] : [],
25
27
  }));
26
28
  const existingParameterNames = new Set(normalizedParameters.map((param) => param.name));
@@ -39,6 +41,8 @@ export function normalizeAbiFunctions(abiFunctions) {
39
41
  optional: false,
40
42
  is_account: true,
41
43
  isAccount: true,
44
+ implicit: false,
45
+ source: 'authored',
42
46
  attributes,
43
47
  };
44
48
  })
@@ -89,17 +89,23 @@ export async function getWasmModule() {
89
89
  // Universal initialization (Browser/Node) if default export is init function
90
90
  if (mod && typeof mod.default === 'function') {
91
91
  try {
92
- await mod.default();
92
+ const initialized = await mod.default();
93
+ const normalizedInit = resolveEncoderModule(initialized);
94
+ if (normalizedInit) {
95
+ wasmModule = normalizedInit;
96
+ return wasmModule;
97
+ }
93
98
  }
94
99
  catch (initErr) {
95
100
  tried.push({ path: candidate, error: initErr });
96
101
  }
97
102
  }
98
- if (mod) {
99
- wasmModule = mod;
103
+ const normalized = resolveEncoderModule(mod);
104
+ if (normalized) {
105
+ wasmModule = normalized;
100
106
  return wasmModule;
101
107
  }
102
- tried.push({ path: candidate, error: 'Module import returned null/undefined' });
108
+ tried.push({ path: candidate, error: 'Module missing ParameterEncoder export' });
103
109
  }
104
110
  catch (e) {
105
111
  tried.push({ path: candidate, error: e });
@@ -110,3 +116,12 @@ export async function getWasmModule() {
110
116
  .join('\n');
111
117
  throw new Error(`Five VM WASM module not found or failed to load. Please ensure five-wasm is built.\nAttempts:\n${attempted}`);
112
118
  }
119
+ const resolveEncoderModule = (mod) => {
120
+ if (!mod)
121
+ return null;
122
+ if (mod.ParameterEncoder)
123
+ return mod;
124
+ if (mod.default && mod.default.ParameterEncoder)
125
+ return mod.default;
126
+ return null;
127
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@5ive-tech/sdk",
3
- "version": "1.1.16",
3
+ "version": "1.1.18",
4
4
  "description": "Client-agnostic TypeScript SDK for Five VM scripts on Solana",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",