@chipi-stack/backend 11.21.0 → 12.0.0

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/dist/index.js CHANGED
@@ -19,6 +19,7 @@ var ChipiClient = class {
19
19
  this.customAlphaUrl = config.alphaUrl;
20
20
  this.baseUrl = this.getBaseUrl();
21
21
  this.nodeUrl = config.nodeUrl || shared.STARKNET_NETWORKS.MAINNET;
22
+ this.sdkVersion = "11.21.0";
22
23
  }
23
24
  /**
24
25
  * Get the API public key (for internal SDK use)
@@ -27,10 +28,19 @@ var ChipiClient = class {
27
28
  return this.apiPublicKey;
28
29
  }
29
30
  getBaseUrl() {
31
+ const version = `v${shared.API_VERSIONING.VERSION}`;
30
32
  if (this.customAlphaUrl) {
31
- return this.customAlphaUrl.endsWith("/v1") ? this.customAlphaUrl : `${this.customAlphaUrl.replace(/\/$/, "")}/v1`;
33
+ const cleanUrl = this.customAlphaUrl.replace(/\/v\d+$/, "").replace(/\/$/, "");
34
+ return `${cleanUrl}/${version}`;
32
35
  }
33
- return "https://celebrated-vision-production-66a5.up.railway.app/v1";
36
+ return `https://celebrated-vision-production-66a5.up.railway.app/${version}`;
37
+ }
38
+ /**
39
+ * Add API version query parameters to track the API version being used
40
+ * This helps the backend handle versioning and track which SDK versions are in use
41
+ */
42
+ addVersionParams(url) {
43
+ url.searchParams.append("__chipi_api_version", shared.API_VERSIONING.VERSION_DATE);
34
44
  }
35
45
  getHeaders(bearerToken) {
36
46
  const headers = {
@@ -49,6 +59,7 @@ var ChipiClient = class {
49
59
  }) {
50
60
  try {
51
61
  const url = new URL(`${this.baseUrl}${endpoint}`);
62
+ this.addVersionParams(url);
52
63
  if (params) {
53
64
  Object.entries(params).forEach(([key, value]) => {
54
65
  if (value !== void 0 && value !== null) {
@@ -80,14 +91,16 @@ var ChipiClient = class {
80
91
  body
81
92
  }) {
82
93
  try {
83
- const response = await fetch(`${this.baseUrl}${endpoint}`, {
94
+ const url = new URL(`${this.baseUrl}${endpoint}`);
95
+ this.addVersionParams(url);
96
+ const response = await fetch(url.toString(), {
84
97
  method: "POST",
85
98
  headers: this.getHeaders(bearerToken),
86
99
  body: body ? JSON.stringify(body) : void 0
87
100
  });
88
101
  const data = await response.json();
89
102
  if (!response.ok) {
90
- console.log("there was an error", data);
103
+ console.error("there was an error", data);
91
104
  const errorData = shared.validateErrorResponse(data);
92
105
  throw new shared.ChipiApiError(
93
106
  errorData.message,
@@ -106,7 +119,9 @@ var ChipiClient = class {
106
119
  body
107
120
  }) {
108
121
  try {
109
- const response = await fetch(`${this.baseUrl}${endpoint}`, {
122
+ const url = new URL(`${this.baseUrl}${endpoint}`);
123
+ this.addVersionParams(url);
124
+ const response = await fetch(url.toString(), {
110
125
  method: "PUT",
111
126
  headers: this.getHeaders(bearerToken),
112
127
  body: body ? JSON.stringify(body) : void 0
@@ -130,7 +145,9 @@ var ChipiClient = class {
130
145
  bearerToken
131
146
  }) {
132
147
  try {
133
- const response = await fetch(`${this.baseUrl}${endpoint}`, {
148
+ const url = new URL(`${this.baseUrl}${endpoint}`);
149
+ this.addVersionParams(url);
150
+ const response = await fetch(url.toString(), {
134
151
  method: "DELETE",
135
152
  headers: this.getHeaders(bearerToken)
136
153
  });
@@ -181,23 +198,27 @@ var ChipiWallets = class {
181
198
  }
182
199
  async createWallet(params) {
183
200
  try {
184
- const { encryptKey, externalUserId, bearerToken, userId } = params;
185
- const provider = new starknet.RpcProvider({ nodeUrl: this.client.nodeUrl });
201
+ const {
202
+ encryptKey,
203
+ externalUserId,
204
+ bearerToken,
205
+ userId,
206
+ walletType = "CHIPI"
207
+ // Default to CHIPI wallet
208
+ } = params;
209
+ const rpcUrl = walletType === "READY" ? shared.WALLET_RPC_ENDPOINTS.READY : shared.WALLET_RPC_ENDPOINTS.CHIPI;
210
+ const provider = new starknet.RpcProvider({ nodeUrl: rpcUrl });
186
211
  const privateKeyAX = this.getPrivateKeyAX();
187
212
  const starkKeyPubAX = starknet.ec.starkCurve.getStarkKey(privateKeyAX);
188
- const accountClassHash = "0x036078334509b514626504edc9fb252328d1a240e4e948bef8d0c08dff45927f";
189
- const axSigner = new starknet.CairoCustomEnum({
190
- Starknet: { pubkey: starkKeyPubAX }
191
- });
192
- const axGuardian = new starknet.CairoOption(starknet.CairoOptionVariant.None);
193
- const AXConstructorCallData = starknet.CallData.compile({
194
- owner: axSigner,
195
- guardian: axGuardian
196
- });
213
+ const accountClassHash = walletType === "READY" ? shared.WALLET_CLASS_HASHES.READY : shared.WALLET_CLASS_HASHES.CHIPI;
214
+ const constructorCallData = this.buildConstructorCallData(
215
+ walletType,
216
+ starkKeyPubAX
217
+ );
197
218
  const publicKey = starknet.hash.calculateContractAddressFromHash(
198
219
  starkKeyPubAX,
199
220
  accountClassHash,
200
- AXConstructorCallData,
221
+ constructorCallData,
201
222
  0
202
223
  );
203
224
  const account = new starknet.Account(provider, publicKey, privateKeyAX);
@@ -205,7 +226,10 @@ var ChipiWallets = class {
205
226
  endpoint: `${shared.API_ENDPOINTS.CHIPI_WALLETS}/prepare-creation`,
206
227
  bearerToken,
207
228
  body: {
208
- publicKey
229
+ publicKey,
230
+ walletType,
231
+ starkKeyPubAX
232
+ // Needed for backend to build deployment data
209
233
  }
210
234
  });
211
235
  const { typeData, accountClassHash: accountClassHashResponse } = typeDataResponse;
@@ -214,7 +238,7 @@ var ChipiWallets = class {
214
238
  class_hash: accountClassHashResponse,
215
239
  salt: starkKeyPubAX,
216
240
  unique: `${starknet.num.toHex(0)}`,
217
- calldata: AXConstructorCallData.map((value) => starknet.num.toHex(value))
241
+ calldata: constructorCallData.map((value) => starknet.num.toHex(value))
218
242
  };
219
243
  const encryptedPrivateKey = encryptPrivateKey(privateKeyAX, encryptKey);
220
244
  const executeTransactionResponse = await this.client.post({
@@ -224,6 +248,7 @@ var ChipiWallets = class {
224
248
  externalUserId,
225
249
  userId,
226
250
  publicKey,
251
+ walletType,
227
252
  userSignature: {
228
253
  r: userSignature.r.toString(),
229
254
  s: userSignature.s.toString(),
@@ -244,7 +269,7 @@ var ChipiWallets = class {
244
269
  wallet: executeTransactionResponse.wallet
245
270
  };
246
271
  } catch (error) {
247
- console.error("Error detallado:", error);
272
+ console.error("Detailed error:", error);
248
273
  if (error instanceof Error && error.message.includes("SSL")) {
249
274
  throw new Error(
250
275
  "SSL connection error. Try using NODE_TLS_REJECT_UNAUTHORIZED=0 or verify the RPC URL"
@@ -256,6 +281,26 @@ var ChipiWallets = class {
256
281
  );
257
282
  }
258
283
  }
284
+ /**
285
+ * Build constructor calldata based on wallet type
286
+ * - CHIPI: Simple OpenZeppelin account with just public_key
287
+ * - ARGENT: Argent X Account with owner/guardian structure
288
+ */
289
+ buildConstructorCallData(walletType, starkKeyPubAX) {
290
+ if (walletType === "READY") {
291
+ const axSigner = new starknet.CairoCustomEnum({
292
+ Starknet: { pubkey: starkKeyPubAX }
293
+ });
294
+ const axGuardian = new starknet.CairoOption(starknet.CairoOptionVariant.None);
295
+ return starknet.CallData.compile({
296
+ owner: axSigner,
297
+ guardian: axGuardian
298
+ });
299
+ }
300
+ return starknet.CallData.compile({
301
+ public_key: starkKeyPubAX
302
+ });
303
+ }
259
304
  /**
260
305
  * Create a custodial merchant wallet
261
306
  */
@@ -301,11 +346,18 @@ var ChipiWallets = class {
301
346
  var executePaymasterTransaction = async ({
302
347
  params,
303
348
  bearerToken,
304
- apiPublicKey,
305
- backendUrl
349
+ client
306
350
  }) => {
307
351
  try {
308
352
  const { encryptKey, wallet, calls, saveToDatabase } = params;
353
+ if (!wallet.walletType) {
354
+ console.warn(
355
+ `[ChipiSDK] Wallet ${wallet.publicKey.slice(0, 10)}... has no walletType - defaulting to ARGENT for backward compatibility`
356
+ );
357
+ }
358
+ const walletType = wallet.walletType ?? "READY";
359
+ const rpcUrl = walletType === "READY" ? shared.WALLET_RPC_ENDPOINTS.READY : shared.WALLET_RPC_ENDPOINTS.CHIPI;
360
+ const accountClassHash = walletType === "READY" ? shared.WALLET_CLASS_HASHES.READY : shared.WALLET_CLASS_HASHES.CHIPI;
309
361
  const privateKeyDecrypted = decryptPrivateKey(
310
362
  wallet.encryptedPrivateKey,
311
363
  encryptKey
@@ -313,68 +365,164 @@ var executePaymasterTransaction = async ({
313
365
  if (!privateKeyDecrypted) {
314
366
  throw new Error("Failed to decrypt private key");
315
367
  }
316
- const provider = new starknet.RpcProvider({
317
- nodeUrl: "https://cloud.argent-api.com/v1/starknet/mainnet/rpc/v0.7"
318
- });
368
+ const provider = new starknet.RpcProvider({ nodeUrl: rpcUrl });
319
369
  const account = new starknet.Account(
320
370
  provider,
321
371
  wallet.publicKey,
322
372
  privateKeyDecrypted
323
373
  );
324
- const typeDataResponse = await fetch(
325
- `${backendUrl}/transactions/prepare-typed-data`,
326
- {
327
- method: "POST",
328
- headers: {
329
- "Content-Type": "application/json",
330
- Authorization: `Bearer ${bearerToken}`,
331
- "X-API-Key": apiPublicKey
374
+ const typeData = await client.post({
375
+ endpoint: "/transactions/prepare-typed-data",
376
+ bearerToken,
377
+ body: {
378
+ publicKey: wallet.publicKey,
379
+ walletType,
380
+ calls,
381
+ accountClassHash
382
+ }
383
+ });
384
+ const userSignature = await account.signMessage(typeData);
385
+ const result = await client.post({
386
+ endpoint: "/transactions/execute-sponsored-transaction",
387
+ bearerToken,
388
+ body: {
389
+ publicKey: wallet.publicKey,
390
+ typeData,
391
+ userSignature: {
392
+ r: userSignature.r.toString(),
393
+ s: userSignature.s.toString(),
394
+ recovery: userSignature.recovery
332
395
  },
333
- body: JSON.stringify({
334
- publicKey: wallet.publicKey,
335
- calls,
336
- accountClassHash: "0x036078334509b514626504edc9fb252328d1a240e4e948bef8d0c08dff45927f"
337
- })
396
+ saveToDatabase,
397
+ walletType
338
398
  }
339
- );
340
- if (!typeDataResponse.ok) {
341
- const errorText = await typeDataResponse.text();
342
- throw new Error(`Error in API: ${errorText}`);
399
+ });
400
+ if (!result.transactionHash) {
401
+ throw new Error("The response does not contain the transaction hash");
343
402
  }
344
- const typeData = await typeDataResponse.json();
345
- const userSignature = await account.signMessage(typeData);
346
- const executeTransaction = await fetch(
347
- `${backendUrl}/transactions/execute-sponsored-transaction`,
403
+ return result.transactionHash;
404
+ } catch (error) {
405
+ console.error("Error sending transaction with paymaster", error);
406
+ throw error;
407
+ }
408
+ };
409
+ var executePaymasterTransactionWithSession = async ({
410
+ params,
411
+ bearerToken,
412
+ client
413
+ }) => {
414
+ const { encryptKey, wallet, session, calls, saveToDatabase } = params;
415
+ if (wallet.walletType !== "CHIPI") {
416
+ console.error(
417
+ "[ChipiSDK:Session:Execute] Invalid wallet type for session execution",
348
418
  {
349
- method: "POST",
350
- headers: {
351
- "Content-Type": "application/json",
352
- Authorization: `Bearer ${bearerToken}`,
353
- "X-API-Key": apiPublicKey
354
- },
355
- body: JSON.stringify({
356
- publicKey: wallet.publicKey,
357
- typeData,
358
- userSignature: {
359
- r: userSignature.r.toString(),
360
- s: userSignature.s.toString(),
361
- recovery: userSignature.recovery
362
- },
363
- saveToDatabase
364
- })
419
+ provided: wallet.walletType,
420
+ required: "CHIPI",
421
+ expectedClassHash: shared.WALLET_CLASS_HASHES.CHIPI,
422
+ walletAddress: wallet.publicKey?.slice(0, 15) + "...",
423
+ hint: "Session keys only work with CHIPI wallets (SNIP-9 compatible)"
365
424
  }
366
425
  );
367
- if (!executeTransaction.ok) {
368
- const errorText = await executeTransaction.text();
369
- throw new Error(`Error executing sponsored transaction: ${errorText}`);
426
+ throw new shared.ChipiSessionError(
427
+ `Session execution requires CHIPI wallet type. Got: "${wallet.walletType || "undefined"}"`,
428
+ shared.SESSION_ERRORS.INVALID_WALLET_TYPE_FOR_SESSION
429
+ );
430
+ }
431
+ const nowSeconds = Math.floor(Date.now() / 1e3);
432
+ if (session.validUntil < nowSeconds) {
433
+ console.error("[ChipiSDK:Session:Execute] Session has expired", {
434
+ sessionExpiry: new Date(session.validUntil * 1e3).toISOString(),
435
+ currentTime: new Date(nowSeconds * 1e3).toISOString(),
436
+ expiredAgo: `${nowSeconds - session.validUntil} seconds`
437
+ });
438
+ throw new shared.ChipiSessionError(
439
+ `Session expired at ${new Date(session.validUntil * 1e3).toISOString()}. Create a new session key.`,
440
+ shared.SESSION_ERRORS.SESSION_EXPIRED
441
+ );
442
+ }
443
+ try {
444
+ const sessionPrivateKey = decryptPrivateKey(
445
+ session.encryptedPrivateKey,
446
+ encryptKey
447
+ );
448
+ if (!sessionPrivateKey) {
449
+ console.error(
450
+ "[ChipiSDK:Session:Execute] Failed to decrypt session private key",
451
+ {
452
+ sessionPubKey: session.publicKey?.slice(0, 15) + "...",
453
+ hint: "Ensure the encryptKey matches the one used when creating the session"
454
+ }
455
+ );
456
+ throw new shared.ChipiSessionError(
457
+ "Failed to decrypt session private key. Verify the encryptKey is correct.",
458
+ shared.SESSION_ERRORS.SESSION_DECRYPTION_FAILED
459
+ );
460
+ }
461
+ const derivedPubKey = starknet.ec.starkCurve.getStarkKey(sessionPrivateKey);
462
+ if (derivedPubKey.toLowerCase() !== session.publicKey.toLowerCase()) {
463
+ console.error("[ChipiSDK:Session:Execute] Session key mismatch", {
464
+ expected: session.publicKey.slice(0, 15) + "...",
465
+ derived: derivedPubKey.slice(0, 15) + "...",
466
+ hint: "The encrypted private key does not match the stored public key"
467
+ });
468
+ throw new shared.ChipiSessionError(
469
+ "Session key mismatch: decrypted private key does not match the public key",
470
+ shared.SESSION_ERRORS.SESSION_DECRYPTION_FAILED
471
+ );
370
472
  }
371
- const result = await executeTransaction.json();
473
+ const accountClassHash = shared.WALLET_CLASS_HASHES.CHIPI;
474
+ const typeDataResult = await client.post({
475
+ endpoint: "/transactions/prepare-typed-data",
476
+ bearerToken,
477
+ body: {
478
+ publicKey: wallet.publicKey,
479
+ calls,
480
+ accountClassHash,
481
+ walletType: "CHIPI"
482
+ }
483
+ });
484
+ const msgHash = starknet.typedData.getMessageHash(typeDataResult, wallet.publicKey);
485
+ const { r, s } = starknet.ec.starkCurve.sign(msgHash, sessionPrivateKey);
486
+ const sessionSignature = [
487
+ session.publicKey,
488
+ starknet.num.toHex(r),
489
+ starknet.num.toHex(s),
490
+ starknet.num.toHex(session.validUntil)
491
+ ];
492
+ const result = await client.post({
493
+ endpoint: "/transactions/execute-sponsored-transaction",
494
+ bearerToken,
495
+ body: {
496
+ publicKey: wallet.publicKey,
497
+ typeData: typeDataResult,
498
+ // Session signature format - array of 4 hex strings
499
+ userSignature: sessionSignature,
500
+ saveToDatabase,
501
+ walletType: "CHIPI",
502
+ isSessionSignature: true
503
+ // Flag to indicate session signature format
504
+ }
505
+ });
372
506
  if (!result.transactionHash) {
373
- throw new Error("The response does not contain the transaction hash");
507
+ console.error(
508
+ "[ChipiSDK:Session:Execute] No transaction hash in response",
509
+ {
510
+ result
511
+ }
512
+ );
513
+ throw new Error("Response does not contain transaction hash");
374
514
  }
375
515
  return result.transactionHash;
376
516
  } catch (error) {
377
- console.error("Error sending transaction with paymaster", error);
517
+ const err = error instanceof Error ? error : new Error(String(error));
518
+ if (error instanceof shared.ChipiSessionError) {
519
+ throw error;
520
+ }
521
+ console.error("[ChipiSDK:Session:Execute] Unexpected error", {
522
+ error: err.message,
523
+ walletAddress: wallet.publicKey?.slice(0, 15) + "...",
524
+ sessionPubKey: session.publicKey?.slice(0, 15) + "..."
525
+ });
378
526
  throw error;
379
527
  }
380
528
  };
@@ -400,8 +548,7 @@ var ChipiTransactions = class {
400
548
  saveToDatabase
401
549
  },
402
550
  bearerToken,
403
- backendUrl: this.client.baseUrl,
404
- apiPublicKey: this.client.getApiPublicKey()
551
+ client: this.client
405
552
  });
406
553
  }
407
554
  /**
@@ -534,16 +681,16 @@ var ChipiSkuTransactions = class {
534
681
  },
535
682
  {
536
683
  contractAddress: shared.SKU_CONTRACTS.RECHARGER_WITH_STRK_MAINNET,
537
- entrypoint: "newRecharge",
684
+ entrypoint: "process_recharge",
538
685
  calldata: starknet.CallData.compile({
539
- token_address: tokenAddress,
686
+ token: tokenAddress,
540
687
  amount: starknet.cairo.uint256(parsedAmount),
541
- rechargeId: CryptoES__default.default.SHA256(
688
+ recharge_id: CryptoES__default.default.SHA256(
542
689
  CryptoES__default.default.lib.WordArray.random(32)
543
690
  // 32 bytes = 256 bits entropy
544
691
  ).toString().slice(0, 20),
545
- productId: skuId,
546
- carrierId: shared.CARRIER_IDS.CHIPI_PAY
692
+ product_code: skuId,
693
+ merchant_slug: shared.CARRIER_IDS.CHIPI_PAY
547
694
  })
548
695
  },
549
696
  {
@@ -670,6 +817,382 @@ var Users = class {
670
817
  });
671
818
  }
672
819
  };
820
+ var ChipiSessions = class {
821
+ constructor(client) {
822
+ this.client = client;
823
+ }
824
+ /**
825
+ * Validate that the wallet is a CHIPI wallet (required for session operations).
826
+ * Throws ChipiSessionError if wallet type is not CHIPI.
827
+ */
828
+ validateChipiWallet(walletType, operation, walletPublicKey) {
829
+ if (walletType !== "CHIPI") {
830
+ console.error(`[ChipiSDK:Session:${operation}] Invalid wallet type`, {
831
+ provided: walletType,
832
+ required: "CHIPI",
833
+ expectedClassHash: shared.WALLET_CLASS_HASHES["CHIPI"],
834
+ walletAddress: walletPublicKey ? walletPublicKey.slice(0, 15) + "..." : "(not provided)",
835
+ hint: "Session keys only work with CHIPI wallets (SNIP-9 compatible)"
836
+ });
837
+ throw new shared.ChipiSessionError(
838
+ `Session keys require CHIPI wallet type. Got: "${walletType || "undefined"}". Session keys are only supported for CHIPI wallets with class hash ${shared.WALLET_CLASS_HASHES["CHIPI"]}`,
839
+ shared.SESSION_ERRORS.INVALID_WALLET_TYPE_FOR_SESSION
840
+ );
841
+ }
842
+ }
843
+ /**
844
+ * Convert a Uint8Array to hex string with 0x prefix.
845
+ */
846
+ toHex(uint8) {
847
+ return "0x" + Array.from(uint8).map((b) => b.toString(16).padStart(2, "0")).join("");
848
+ }
849
+ /**
850
+ * Generate a new session keypair locally.
851
+ *
852
+ * This method generates the session keys but does NOT register them on-chain.
853
+ * The returned SessionKeyData should be stored by the developer externally
854
+ * (e.g., in Clerk metadata, database, etc.) - the SDK does NOT persist this.
855
+ *
856
+ * After generating, call `addSessionKeyToContract()` to register the session on-chain.
857
+ *
858
+ * @param params - Session creation parameters
859
+ * @returns SessionKeyData for external storage
860
+ *
861
+ * @example
862
+ * ```typescript
863
+ * const session = sdk.sessions.createSessionKey({
864
+ * encryptKey: userEncryptKey,
865
+ * durationSeconds: 21600 // 6 hours
866
+ * });
867
+ * // Store session externally (e.g., in Clerk metadata)
868
+ * await saveToClerk(session);
869
+ * ```
870
+ */
871
+ createSessionKey(params) {
872
+ const { encryptKey, durationSeconds = shared.SESSION_DEFAULTS.DURATION_SECONDS } = params;
873
+ try {
874
+ const rawPrivateKey = starknet.ec.starkCurve.utils.randomPrivateKey();
875
+ const privateKeyHex = this.toHex(rawPrivateKey);
876
+ const publicKey = starknet.ec.starkCurve.getStarkKey(privateKeyHex);
877
+ const validUntil = Math.floor(Date.now() / 1e3) + durationSeconds;
878
+ const encryptedPrivateKey = encryptPrivateKey(privateKeyHex, encryptKey);
879
+ return {
880
+ publicKey,
881
+ encryptedPrivateKey,
882
+ validUntil
883
+ };
884
+ } catch (error) {
885
+ const err = error instanceof Error ? error : new Error(String(error));
886
+ console.error(
887
+ "[ChipiSDK:Session:Create] Failed to generate session key",
888
+ {
889
+ error: err.message,
890
+ durationRequested: durationSeconds
891
+ }
892
+ );
893
+ throw new shared.ChipiSessionError(
894
+ `Failed to create session key: ${err.message}`,
895
+ shared.SESSION_ERRORS.SESSION_CREATION_FAILED
896
+ );
897
+ }
898
+ }
899
+ /**
900
+ * Register a session key on the smart contract.
901
+ *
902
+ * This executes a sponsored transaction (using owner signature) to call
903
+ * `add_or_update_session_key` on the CHIPI wallet contract.
904
+ *
905
+ * The session must be registered before it can be used for transactions.
906
+ *
907
+ * @param params - Session registration parameters
908
+ * @param bearerToken - Authentication token
909
+ * @returns Transaction hash
910
+ *
911
+ * @example
912
+ * ```typescript
913
+ * const txHash = await sdk.sessions.addSessionKeyToContract({
914
+ * encryptKey: userEncryptKey,
915
+ * wallet: userWallet,
916
+ * sessionConfig: {
917
+ * sessionPublicKey: session.publicKey,
918
+ * validUntil: session.validUntil,
919
+ * maxCalls: 1000,
920
+ * allowedEntrypoints: [] // empty = all allowed
921
+ * }
922
+ * }, bearerToken);
923
+ * ```
924
+ */
925
+ async addSessionKeyToContract(params, bearerToken) {
926
+ const { encryptKey, wallet, sessionConfig } = params;
927
+ this.validateChipiWallet(
928
+ wallet.walletType,
929
+ "AddToContract",
930
+ wallet.publicKey
931
+ );
932
+ try {
933
+ console.log(
934
+ "[ChipiSDK:Session:AddToContract] Registering session on-chain",
935
+ {
936
+ walletAddress: wallet.publicKey.slice(0, 15) + "...",
937
+ sessionPubKey: sessionConfig.sessionPublicKey.slice(0, 15) + "...",
938
+ validUntil: new Date(sessionConfig.validUntil * 1e3).toISOString(),
939
+ maxCalls: sessionConfig.maxCalls,
940
+ allowedEntrypoints: sessionConfig.allowedEntrypoints.length
941
+ }
942
+ );
943
+ const calldata = [
944
+ sessionConfig.sessionPublicKey,
945
+ starknet.num.toHex(sessionConfig.validUntil),
946
+ starknet.num.toHex(sessionConfig.maxCalls),
947
+ // Array: first element is length, then elements
948
+ starknet.num.toHex(sessionConfig.allowedEntrypoints.length),
949
+ ...sessionConfig.allowedEntrypoints
950
+ ];
951
+ const txHash = await executePaymasterTransaction({
952
+ params: {
953
+ encryptKey,
954
+ wallet: { ...wallet, walletType: "CHIPI" },
955
+ calls: [
956
+ {
957
+ contractAddress: wallet.publicKey,
958
+ entrypoint: shared.SESSION_ENTRYPOINTS.ADD_OR_UPDATE,
959
+ calldata
960
+ }
961
+ ],
962
+ saveToDatabase: false
963
+ // Don't record session management txs
964
+ },
965
+ bearerToken,
966
+ client: this.client
967
+ });
968
+ console.log(
969
+ "[ChipiSDK:Session:AddToContract] Session registered successfully",
970
+ {
971
+ txHash,
972
+ sessionPubKey: sessionConfig.sessionPublicKey.slice(0, 15) + "..."
973
+ }
974
+ );
975
+ return txHash;
976
+ } catch (error) {
977
+ const err = error instanceof Error ? error : new Error(String(error));
978
+ console.error("[ChipiSDK:Session:AddToContract] Registration failed", {
979
+ error: err.message,
980
+ walletAddress: wallet.publicKey?.slice(0, 15) + "...",
981
+ sessionPubKey: sessionConfig.sessionPublicKey?.slice(0, 15) + "...",
982
+ validUntil: new Date(sessionConfig.validUntil * 1e3).toISOString(),
983
+ maxCalls: sessionConfig.maxCalls
984
+ });
985
+ throw error;
986
+ }
987
+ }
988
+ /**
989
+ * Revoke a session key from the smart contract.
990
+ *
991
+ * This executes a sponsored transaction (using owner signature) to call
992
+ * `revoke_session_key` on the CHIPI wallet contract.
993
+ *
994
+ * After revocation, the session key can no longer be used for transactions.
995
+ *
996
+ * @param params - Session revocation parameters
997
+ * @param bearerToken - Authentication token
998
+ * @returns Transaction hash
999
+ *
1000
+ * @example
1001
+ * ```typescript
1002
+ * const txHash = await sdk.sessions.revokeSessionKey({
1003
+ * encryptKey: userEncryptKey,
1004
+ * wallet: userWallet,
1005
+ * sessionPublicKey: session.publicKey
1006
+ * }, bearerToken);
1007
+ * ```
1008
+ */
1009
+ async revokeSessionKey(params, bearerToken) {
1010
+ const { encryptKey, wallet, sessionPublicKey } = params;
1011
+ this.validateChipiWallet(wallet.walletType, "Revoke", wallet.publicKey);
1012
+ try {
1013
+ console.log("[ChipiSDK:Session:Revoke] Revoking session from contract", {
1014
+ walletAddress: wallet.publicKey.slice(0, 15) + "...",
1015
+ sessionToRevoke: sessionPublicKey.slice(0, 15) + "..."
1016
+ });
1017
+ const txHash = await executePaymasterTransaction({
1018
+ params: {
1019
+ encryptKey,
1020
+ wallet: { ...wallet, walletType: "CHIPI" },
1021
+ calls: [
1022
+ {
1023
+ contractAddress: wallet.publicKey,
1024
+ entrypoint: shared.SESSION_ENTRYPOINTS.REVOKE,
1025
+ calldata: [sessionPublicKey]
1026
+ }
1027
+ ],
1028
+ saveToDatabase: false
1029
+ // Don't record session management txs
1030
+ },
1031
+ bearerToken,
1032
+ client: this.client
1033
+ });
1034
+ console.log("[ChipiSDK:Session:Revoke] Session revoked successfully", {
1035
+ txHash,
1036
+ sessionRevoked: sessionPublicKey.slice(0, 15) + "..."
1037
+ });
1038
+ return txHash;
1039
+ } catch (error) {
1040
+ const err = error instanceof Error ? error : new Error(String(error));
1041
+ console.error("[ChipiSDK:Session:Revoke] Revocation failed", {
1042
+ error: err.message,
1043
+ walletAddress: wallet.publicKey?.slice(0, 15) + "...",
1044
+ sessionToRevoke: sessionPublicKey?.slice(0, 15) + "..."
1045
+ });
1046
+ throw error;
1047
+ }
1048
+ }
1049
+ /**
1050
+ * Query session data from the smart contract.
1051
+ *
1052
+ * This is a read-only call that does not require signing or gas.
1053
+ *
1054
+ * @param params - Query parameters
1055
+ * @returns Session data including status, remaining calls, and allowed entrypoints
1056
+ *
1057
+ * @example
1058
+ * ```typescript
1059
+ * const sessionData = await sdk.sessions.getSessionData({
1060
+ * walletAddress: userWallet.publicKey,
1061
+ * sessionPublicKey: session.publicKey
1062
+ * });
1063
+ * if (sessionData.isActive) {
1064
+ * console.log(`Session has ${sessionData.remainingCalls} calls remaining`);
1065
+ * }
1066
+ * ```
1067
+ *
1068
+ * NOTE: This method intentionally does NOT validate walletType because:
1069
+ * 1. It's a read-only query - no state change or risk
1070
+ * 2. Non-CHIPI wallets will simply return isActive=false (graceful failure)
1071
+ * 3. It doesn't require walletType parameter at all - only walletAddress
1072
+ * Adding validation would require changing the interface for no functional benefit.
1073
+ */
1074
+ async getSessionData(params) {
1075
+ const { walletAddress, sessionPublicKey } = params;
1076
+ try {
1077
+ const provider = new starknet.RpcProvider({
1078
+ nodeUrl: shared.WALLET_RPC_ENDPOINTS["CHIPI"]
1079
+ });
1080
+ console.log("[ChipiSDK:Session:GetData] Querying session data", {
1081
+ walletAddress: walletAddress.slice(0, 15) + "...",
1082
+ sessionPubKey: sessionPublicKey.slice(0, 15) + "..."
1083
+ });
1084
+ const result = await provider.callContract({
1085
+ contractAddress: walletAddress,
1086
+ entrypoint: shared.SESSION_ENTRYPOINTS.GET_DATA,
1087
+ calldata: [sessionPublicKey]
1088
+ });
1089
+ const resultArray = Array.isArray(result) ? result : result.result || [];
1090
+ if (resultArray.length < 4) {
1091
+ console.warn("[ChipiSDK:Session:GetData] Unexpected response format", {
1092
+ resultLength: resultArray.length,
1093
+ result: resultArray
1094
+ });
1095
+ return {
1096
+ isActive: false,
1097
+ validUntil: 0,
1098
+ remainingCalls: 0,
1099
+ allowedEntrypoints: []
1100
+ };
1101
+ }
1102
+ const isActive = resultArray[0] === "0x1";
1103
+ const validUntil = Number(BigInt(resultArray[1]));
1104
+ const remainingCalls = Number(BigInt(resultArray[2]));
1105
+ const entrypointsLen = Number(BigInt(resultArray[3]));
1106
+ const allowedEntrypoints = resultArray.slice(4, 4 + entrypointsLen);
1107
+ const sessionData = {
1108
+ isActive,
1109
+ validUntil,
1110
+ remainingCalls,
1111
+ allowedEntrypoints
1112
+ };
1113
+ console.log("[ChipiSDK:Session:GetData] Session data retrieved", {
1114
+ isActive,
1115
+ validUntil: new Date(validUntil * 1e3).toISOString(),
1116
+ remainingCalls,
1117
+ allowedEntrypointsCount: allowedEntrypoints.length
1118
+ });
1119
+ return sessionData;
1120
+ } catch (error) {
1121
+ const err = error instanceof Error ? error : new Error(String(error));
1122
+ console.error("[ChipiSDK:Session:GetData] Query failed", {
1123
+ error: err.message,
1124
+ walletAddress: walletAddress?.slice(0, 15) + "...",
1125
+ sessionPubKey: sessionPublicKey?.slice(0, 15) + "..."
1126
+ });
1127
+ return {
1128
+ isActive: false,
1129
+ validUntil: 0,
1130
+ remainingCalls: 0,
1131
+ allowedEntrypoints: []
1132
+ };
1133
+ }
1134
+ }
1135
+ /**
1136
+ * Execute a gasless transaction using a session key.
1137
+ *
1138
+ * This uses the 4-element session signature format instead of owner signature.
1139
+ * The session must be:
1140
+ * 1. Generated via `createSessionKey()`
1141
+ * 2. Registered on-chain via `addSessionKeyToContract()`
1142
+ *
1143
+ * CHIPI wallets only - will throw if wallet type is not CHIPI.
1144
+ *
1145
+ * @param params - Session execution parameters
1146
+ * @param bearerToken - Authentication token
1147
+ * @returns Transaction hash
1148
+ *
1149
+ * @example
1150
+ * ```typescript
1151
+ * // 1. Create session key (store externally)
1152
+ * const session = sdk.sessions.createSessionKey({ encryptKey });
1153
+ *
1154
+ * // 2. Register on contract (one-time)
1155
+ * await sdk.sessions.addSessionKeyToContract({
1156
+ * encryptKey,
1157
+ * wallet,
1158
+ * sessionConfig: {
1159
+ * sessionPublicKey: session.publicKey,
1160
+ * validUntil: session.validUntil,
1161
+ * maxCalls: 1000,
1162
+ * allowedEntrypoints: []
1163
+ * }
1164
+ * }, bearerToken);
1165
+ *
1166
+ * // 3. Execute transactions with session (no owner key needed)
1167
+ * const txHash = await sdk.sessions.executeTransactionWithSession({
1168
+ * encryptKey,
1169
+ * wallet,
1170
+ * session,
1171
+ * calls
1172
+ * }, bearerToken);
1173
+ * ```
1174
+ */
1175
+ async executeTransactionWithSession(params, bearerToken) {
1176
+ const { encryptKey, wallet, session, calls } = params;
1177
+ if (wallet.walletType && wallet.walletType !== "CHIPI") {
1178
+ throw new shared.ChipiSessionError(
1179
+ `Session execution only supports CHIPI wallets. Got: ${wallet.walletType}`,
1180
+ shared.SESSION_ERRORS.INVALID_WALLET_TYPE_FOR_SESSION
1181
+ );
1182
+ }
1183
+ return executePaymasterTransactionWithSession({
1184
+ params: {
1185
+ encryptKey,
1186
+ wallet: { ...wallet, walletType: "CHIPI" },
1187
+ session,
1188
+ calls,
1189
+ saveToDatabase: true
1190
+ },
1191
+ bearerToken,
1192
+ client: this.client
1193
+ });
1194
+ }
1195
+ };
673
1196
 
674
1197
  // src/chipi-sdk.ts
675
1198
  var ChipiSDK = class {
@@ -682,7 +1205,9 @@ var ChipiSDK = class {
682
1205
  this.skuTransactions = new ChipiSkuTransactions(this.client);
683
1206
  this.skus = new ChipiSkus(this.client);
684
1207
  this.users = new Users(this.client);
1208
+ this.sessions = new ChipiSessions(this.client);
685
1209
  this.executeTransaction = this.executeTransaction.bind(this);
1210
+ this.executeTransactionWithSession = this.executeTransactionWithSession.bind(this);
686
1211
  this.transfer = this.transfer.bind(this);
687
1212
  this.approve = this.approve.bind(this);
688
1213
  this.stakeVesuUsdc = this.stakeVesuUsdc.bind(this);
@@ -716,6 +1241,49 @@ var ChipiSDK = class {
716
1241
  bearerToken: this.resolveBearerToken(bearerToken)
717
1242
  });
718
1243
  }
1244
+ /**
1245
+ * Execute a gasless transaction using a session key.
1246
+ *
1247
+ * This uses the 4-element session signature format instead of owner signature.
1248
+ * The session must be:
1249
+ * 1. Generated via `sessions.createSessionKey()`
1250
+ * 2. Registered on-chain via `sessions.addSessionKeyToContract()`
1251
+ *
1252
+ * CHIPI wallets only - will throw if wallet type is not CHIPI.
1253
+ *
1254
+ * @example
1255
+ * ```typescript
1256
+ * // 1. Create session key (store externally)
1257
+ * const session = sdk.sessions.createSessionKey({ encryptKey });
1258
+ *
1259
+ * // 2. Register on contract (one-time)
1260
+ * await sdk.sessions.addSessionKeyToContract({
1261
+ * encryptKey,
1262
+ * wallet,
1263
+ * sessionConfig: {
1264
+ * sessionPublicKey: session.publicKey,
1265
+ * validUntil: session.validUntil,
1266
+ * maxCalls: 1000,
1267
+ * allowedEntrypoints: []
1268
+ * }
1269
+ * }, bearerToken);
1270
+ *
1271
+ * // 3. Execute transactions with session (no owner key needed)
1272
+ * const txHash = await sdk.executeTransactionWithSession({
1273
+ * params: { encryptKey, wallet, session, calls },
1274
+ * bearerToken
1275
+ * });
1276
+ * ```
1277
+ */
1278
+ async executeTransactionWithSession({
1279
+ params,
1280
+ bearerToken
1281
+ }) {
1282
+ return this.sessions.executeTransactionWithSession(
1283
+ params,
1284
+ this.resolveBearerToken(bearerToken)
1285
+ );
1286
+ }
719
1287
  /**
720
1288
  * Transfer tokens
721
1289
  */
@@ -916,6 +1484,7 @@ exports.ChipiBrowserSDK = ChipiBrowserSDK;
916
1484
  exports.ChipiClient = ChipiClient;
917
1485
  exports.ChipiSDK = ChipiSDK;
918
1486
  exports.ChipiServerSDK = ChipiServerSDK;
1487
+ exports.ChipiSessions = ChipiSessions;
919
1488
  exports.ChipiSkuTransactions = ChipiSkuTransactions;
920
1489
  exports.ChipiSkus = ChipiSkus;
921
1490
  exports.ChipiTransactions = ChipiTransactions;