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