@chipi-stack/chipi-react 14.13.0 → 14.14.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
@@ -95,6 +95,18 @@ function useChipiContext() {
95
95
  }
96
96
  return context;
97
97
  }
98
+
99
+ // src/utils/with-error-code.ts
100
+ function withErrorCode(wrapped, original) {
101
+ const code = original.code;
102
+ if (typeof code === "string") {
103
+ Object.defineProperty(wrapped, "code", { value: code, enumerable: true });
104
+ }
105
+ Object.defineProperty(wrapped, "cause", { value: original, enumerable: false });
106
+ return wrapped;
107
+ }
108
+
109
+ // src/hooks/useCreateWallet.ts
98
110
  function useCreateWallet() {
99
111
  const { chipiSDK } = useChipiContext();
100
112
  const mutation = reactQuery.useMutation({
@@ -125,7 +137,7 @@ function useCreateWallet() {
125
137
  }
126
138
  } catch (error) {
127
139
  if (error instanceof Error) {
128
- throw new Error(`Passkey creation failed: ${error.message}`);
140
+ throw withErrorCode(new Error(`Passkey creation failed: ${error.message}`), error);
129
141
  }
130
142
  throw new Error("Failed to create passkey for wallet");
131
143
  }
@@ -205,6 +217,17 @@ function useGetWallet(input) {
205
217
  fetchWallet
206
218
  };
207
219
  }
220
+
221
+ // src/utils/wallet-capabilities.ts
222
+ var SHHH_SESSION_SIGNER_KINDS = /* @__PURE__ */ new Set(["STARK", "ED25519"]);
223
+ function walletSupportsSessions(wallet) {
224
+ if (!wallet) return false;
225
+ if (wallet.walletType === "CHIPI") return true;
226
+ if (wallet.walletType === "SHHH") {
227
+ return !wallet.signerKind || SHHH_SESSION_SIGNER_KINDS.has(wallet.signerKind);
228
+ }
229
+ return false;
230
+ }
208
231
  function useChipiWallet(config3) {
209
232
  const { chipiSDK } = useChipiContext();
210
233
  const queryClient = reactQuery.useQueryClient();
@@ -285,7 +308,7 @@ function useChipiWallet(config3) {
285
308
  const data = walletQuery.data;
286
309
  return {
287
310
  ...data,
288
- supportsSessionKeys: data.walletType === "CHIPI",
311
+ supportsSessionKeys: walletSupportsSessions(data),
289
312
  shortAddress: formatAddress(data.publicKey)
290
313
  };
291
314
  }, [walletQuery.data, walletQuery.isLoading]);
@@ -342,6 +365,7 @@ function useChipiSession(config3) {
342
365
  const {
343
366
  wallet,
344
367
  encryptKey,
368
+ getEncryptKey,
345
369
  getBearerToken,
346
370
  storedSession,
347
371
  onSessionCreated,
@@ -355,10 +379,13 @@ function useChipiSession(config3) {
355
379
  React.useEffect(() => {
356
380
  setLocalSession(storedSession ?? null);
357
381
  }, [storedSession]);
358
- const supportsSession = React.useMemo(() => {
359
- if (!wallet) return false;
360
- return wallet.walletType === "CHIPI";
361
- }, [wallet]);
382
+ const supportsSession = React.useMemo(() => walletSupportsSessions(wallet), [wallet]);
383
+ const resolveEncryptKey = React.useCallback(async () => {
384
+ const key = encryptKey || (getEncryptKey ? await getEncryptKey() : void 0);
385
+ if (!key) throw new Error("Encryption key (PIN) is required");
386
+ return key;
387
+ }, [encryptKey, getEncryptKey]);
388
+ const [awaitingRegistrationUntil, setAwaitingRegistrationUntil] = React.useState(null);
362
389
  const sessionStatusQuery = reactQuery.useQuery({
363
390
  queryKey: ["chipi-session-status", wallet?.publicKey, localSession?.publicKey],
364
391
  queryFn: async () => {
@@ -371,34 +398,41 @@ function useChipiSession(config3) {
371
398
  enabled: Boolean(
372
399
  autoCheckStatus && wallet?.publicKey && localSession?.publicKey && supportsSession
373
400
  ),
374
- staleTime: 3e4
401
+ staleTime: 3e4,
375
402
  // 30 seconds
403
+ // The registration hash comes back before the transaction is in a block, so
404
+ // right after registerSession the struct can still read as zero. Poll until
405
+ // it shows up (or for two minutes), then go back to on-demand reads.
406
+ refetchInterval: (query) => awaitingRegistrationUntil !== null && Date.now() < awaitingRegistrationUntil && (query.state.data?.validUntil ?? 0) === 0 ? 3e3 : false
376
407
  });
408
+ const [revokedSessionKey, setRevokedSessionKey] = React.useState(null);
377
409
  const sessionState = React.useMemo(() => {
378
410
  if (!localSession) return "none";
411
+ if (revokedSessionKey === localSession.publicKey) return "revoked";
412
+ if (localSession.validUntil * 1e3 < Date.now()) return "expired";
379
413
  const status = sessionStatusQuery.data;
380
414
  if (status) {
381
- if (!status.isActive) return "revoked";
415
+ if (status.validUntil === 0) return "created";
382
416
  if (status.validUntil * 1e3 < Date.now()) return "expired";
383
417
  if (status.remainingCalls <= 0) return "expired";
418
+ if (!status.isActive) return "revoked";
384
419
  return "active";
385
420
  }
386
- if (localSession.validUntil * 1e3 < Date.now()) return "expired";
387
421
  return "created";
388
- }, [localSession, sessionStatusQuery.data]);
422
+ }, [localSession, revokedSessionKey, sessionStatusQuery.data]);
389
423
  const hasActiveSession = sessionState === "active";
390
424
  const isSessionExpired = sessionState === "expired";
391
425
  const remainingCalls = sessionStatusQuery.data?.remainingCalls;
392
426
  const createSessionMutation = reactQuery.useMutation(
393
427
  {
394
428
  mutationFn: async (params = {}) => {
395
- if (!encryptKey) throw new Error("Encryption key (PIN) is required");
396
429
  if (!supportsSession)
397
430
  throw new Error(
398
- "Wallet does not support sessions. Only CHIPI wallets support session keys."
431
+ "Wallet does not support sessions. Session keys need a CHIPI wallet or a SHHH wallet with a STARK / ED25519 signer."
399
432
  );
433
+ const key = await resolveEncryptKey();
400
434
  const sessionData = chipiSDK.sessions.createSessionKey({
401
- encryptKey,
435
+ encryptKey: key,
402
436
  durationSeconds: params.durationSeconds ?? defaultDurationSeconds
403
437
  });
404
438
  return sessionData;
@@ -418,28 +452,38 @@ function useChipiSession(config3) {
418
452
  const registerSessionMutation = reactQuery.useMutation({
419
453
  mutationFn: async (sessionConfig = {}) => {
420
454
  if (!wallet) throw new Error("Wallet is required");
421
- if (!localSession) throw new Error("No session to register. Call createSession first.");
422
- if (!encryptKey) throw new Error("Encryption key (PIN) is required");
455
+ const sessionPublicKey = sessionConfig.sessionPublicKey ?? localSession?.publicKey;
456
+ const validUntil = sessionConfig.validUntil ?? localSession?.validUntil;
457
+ if (!sessionPublicKey || validUntil === void 0)
458
+ throw new Error(
459
+ "No session to register. Call createSession first, or pass sessionPublicKey and validUntil."
460
+ );
423
461
  if (!supportsSession) throw new Error("Wallet does not support sessions");
462
+ const key = await resolveEncryptKey();
424
463
  const bearerToken = await getBearerToken();
425
464
  if (!bearerToken) throw new Error("Bearer token is required");
426
- const txHash = await chipiSDK.sessions.addSessionKeyToContract(
427
- {
428
- encryptKey,
429
- wallet,
430
- sessionConfig: {
431
- sessionPublicKey: localSession.publicKey,
432
- validUntil: sessionConfig.validUntil ?? localSession.validUntil,
433
- maxCalls: sessionConfig.maxCalls ?? defaultMaxCalls,
434
- allowedEntrypoints: sessionConfig.allowedEntrypoints ?? []
435
- }
436
- },
437
- bearerToken
438
- );
439
- return txHash;
465
+ const params = {
466
+ encryptKey: key,
467
+ wallet,
468
+ sessionConfig: {
469
+ sessionPublicKey,
470
+ validUntil,
471
+ maxCalls: sessionConfig.maxCalls ?? defaultMaxCalls,
472
+ allowedEntrypoints: sessionConfig.allowedEntrypoints ?? []
473
+ }
474
+ };
475
+ if (sessionConfig.spendingPolicies?.length) {
476
+ return chipiSDK.sessions.setupSession(
477
+ { ...params, spendingPolicies: sessionConfig.spendingPolicies },
478
+ bearerToken
479
+ );
480
+ }
481
+ return chipiSDK.sessions.addSessionKeyToContract(params, bearerToken);
440
482
  },
441
483
  onSuccess: () => {
442
484
  setError(null);
485
+ if (localSession && revokedSessionKey === localSession.publicKey) setRevokedSessionKey(null);
486
+ setAwaitingRegistrationUntil(Date.now() + 12e4);
443
487
  queryClient.invalidateQueries({
444
488
  queryKey: ["chipi-session-status", wallet?.publicKey, localSession?.publicKey]
445
489
  });
@@ -449,25 +493,28 @@ function useChipiSession(config3) {
449
493
  }
450
494
  });
451
495
  const revokeSessionMutation = reactQuery.useMutation({
452
- mutationFn: async () => {
496
+ mutationFn: async (sessionPublicKey) => {
453
497
  if (!wallet) throw new Error("Wallet is required");
454
- if (!localSession) throw new Error("No session to revoke");
455
- if (!encryptKey) throw new Error("Encryption key (PIN) is required");
498
+ const target = sessionPublicKey ?? localSession?.publicKey;
499
+ if (!target) throw new Error("No session to revoke");
456
500
  if (!supportsSession) throw new Error("Wallet does not support sessions");
501
+ const key = await resolveEncryptKey();
457
502
  const bearerToken = await getBearerToken();
458
503
  if (!bearerToken) throw new Error("Bearer token is required");
459
504
  const txHash = await chipiSDK.sessions.revokeSessionKey(
460
505
  {
461
- encryptKey,
506
+ encryptKey: key,
462
507
  wallet,
463
- sessionPublicKey: localSession.publicKey
508
+ sessionPublicKey: target
464
509
  },
465
510
  bearerToken
466
511
  );
467
512
  return txHash;
468
513
  },
469
- onSuccess: () => {
514
+ onSuccess: (_txHash, sessionPublicKey) => {
470
515
  setError(null);
516
+ const target = sessionPublicKey ?? localSession?.publicKey;
517
+ if (target) setRevokedSessionKey(target);
471
518
  queryClient.invalidateQueries({
472
519
  queryKey: ["chipi-session-status", wallet?.publicKey, localSession?.publicKey]
473
520
  });
@@ -480,13 +527,13 @@ function useChipiSession(config3) {
480
527
  mutationFn: async (calls) => {
481
528
  if (!wallet) throw new Error("Wallet is required");
482
529
  if (!localSession) throw new Error("No active session");
483
- if (!encryptKey) throw new Error("Encryption key (PIN) is required");
484
530
  if (!hasActiveSession) throw new Error("Session is not active. Register the session first.");
531
+ const key = await resolveEncryptKey();
485
532
  const bearerToken = await getBearerToken();
486
533
  if (!bearerToken) throw new Error("Bearer token is required");
487
534
  const txHash = await chipiSDK.executeTransactionWithSession({
488
535
  params: {
489
- encryptKey,
536
+ encryptKey: key,
490
537
  wallet,
491
538
  session: localSession,
492
539
  calls
@@ -510,13 +557,13 @@ function useChipiSession(config3) {
510
557
  if (!wallet) throw new Error("Wallet is required");
511
558
  if (!localSession)
512
559
  throw new Error("No session to set policy on. Create and register a session first.");
513
- if (!encryptKey) throw new Error("Encryption key (PIN) is required");
514
560
  if (!supportsSession) throw new Error("Wallet does not support sessions");
561
+ const key = await resolveEncryptKey();
515
562
  const bearerToken = await getBearerToken();
516
563
  if (!bearerToken) throw new Error("Bearer token is required");
517
564
  const txHash = await chipiSDK.sessions.setSpendingPolicy(
518
565
  {
519
- encryptKey,
566
+ encryptKey: key,
520
567
  wallet,
521
568
  spendingPolicyConfig: {
522
569
  sessionPublicKey: localSession.publicKey,
@@ -538,13 +585,13 @@ function useChipiSession(config3) {
538
585
  mutationFn: async (token) => {
539
586
  if (!wallet) throw new Error("Wallet is required");
540
587
  if (!localSession) throw new Error("No session to remove policy from");
541
- if (!encryptKey) throw new Error("Encryption key (PIN) is required");
542
588
  if (!supportsSession) throw new Error("Wallet does not support sessions");
589
+ const key = await resolveEncryptKey();
543
590
  const bearerToken = await getBearerToken();
544
591
  if (!bearerToken) throw new Error("Bearer token is required");
545
592
  const txHash = await chipiSDK.sessions.removeSpendingPolicy(
546
593
  {
547
- encryptKey,
594
+ encryptKey: key,
548
595
  wallet,
549
596
  sessionPublicKey: localSession.publicKey,
550
597
  token
@@ -572,9 +619,14 @@ function useChipiSession(config3) {
572
619
  },
573
620
  [registerSessionMutation]
574
621
  );
575
- const revokeSession = React.useCallback(async () => {
576
- return revokeSessionMutation.mutateAsync();
577
- }, [revokeSessionMutation]);
622
+ const revokeSession = React.useCallback(
623
+ async (sessionPublicKey) => {
624
+ return revokeSessionMutation.mutateAsync(
625
+ typeof sessionPublicKey === "string" ? sessionPublicKey : void 0
626
+ );
627
+ },
628
+ [revokeSessionMutation]
629
+ );
578
630
  const executeWithSession = React.useCallback(
579
631
  async (calls) => {
580
632
  return executeWithSessionMutation.mutateAsync(calls);
@@ -708,7 +760,7 @@ function useMigrateWalletToPasskey() {
708
760
  };
709
761
  } catch (error) {
710
762
  if (error instanceof Error) {
711
- throw new Error(`Migration failed: ${error.message}`);
763
+ throw withErrorCode(new Error(`Migration failed: ${error.message}`), error);
712
764
  }
713
765
  throw new Error("Failed to migrate wallet to passkey");
714
766
  }
@@ -21261,6 +21313,21 @@ function ChipiInjector({ children, ...config3 }) {
21261
21313
  }, [walletAddr]);
21262
21314
  return children ?? null;
21263
21315
  }
21316
+ var DEFAULT_RECOVER_LABELS = {
21317
+ proposeAddDevice: "Propose new device",
21318
+ proposeRecovery: "Initiate recovery",
21319
+ executeAddDevice: "Execute add-owner",
21320
+ executeRecovery: "Finalize recovery",
21321
+ timelockAddDevice: "48-hour timelock before this device becomes active.",
21322
+ timelockRecovery: "7-day timelock before the recovered wallet becomes active.",
21323
+ submitting: "Submitting\u2026",
21324
+ secondsRemaining: (s) => `${s}s remaining`,
21325
+ cancel: "Cancel pending op",
21326
+ timelockElapsed: "Timelock elapsed.",
21327
+ cancelling: "Cancelling\u2026",
21328
+ doneAddDevice: "New device added.",
21329
+ doneRecovery: "Wallet recovered."
21330
+ };
21264
21331
  function deriveStaticPhase(pendingOp, executed, isReady, nowSecs) {
21265
21332
  if (executed) return "done";
21266
21333
  if (!pendingOp) return "form-idle";
@@ -21342,10 +21409,17 @@ function Recover(props) {
21342
21409
  setSubmittingAction(null);
21343
21410
  }
21344
21411
  }, [activePendingOp, onCancel]);
21412
+ const L = React.useMemo(
21413
+ () => ({
21414
+ ...DEFAULT_RECOVER_LABELS,
21415
+ ...Object.fromEntries(Object.entries(props.labels ?? {}).filter(([, v]) => v !== void 0))
21416
+ }),
21417
+ [props.labels]
21418
+ );
21345
21419
  const isAddDevice = mode === "add-device";
21346
- const proposeLabel = isAddDevice ? "Propose new device" : "Initiate recovery";
21347
- const executeLabel = isAddDevice ? "Execute add-owner" : "Finalize recovery";
21348
- const timelockDescription = isAddDevice ? "48-hour timelock before this device becomes active." : "7-day timelock before the recovered wallet becomes active.";
21420
+ const proposeLabel = isAddDevice ? L.proposeAddDevice : L.proposeRecovery;
21421
+ const executeLabel = isAddDevice ? L.executeAddDevice : L.executeRecovery;
21422
+ const timelockDescription = isAddDevice ? L.timelockAddDevice : L.timelockRecovery;
21349
21423
  return /* @__PURE__ */ jsxRuntime.jsxs(
21350
21424
  "div",
21351
21425
  {
@@ -21373,18 +21447,15 @@ function Recover(props) {
21373
21447
  }
21374
21448
  )
21375
21449
  ] }),
21376
- phase === "submitting-propose" && /* @__PURE__ */ jsxRuntime.jsx("section", { "data-chipi-recover-section": "propose", children: /* @__PURE__ */ jsxRuntime.jsx("p", { "data-chipi-recover-status": "submitting", children: "Submitting\u2026" }) }),
21450
+ phase === "submitting-propose" && /* @__PURE__ */ jsxRuntime.jsx("section", { "data-chipi-recover-section": "propose", children: /* @__PURE__ */ jsxRuntime.jsx("p", { "data-chipi-recover-status": "submitting", children: L.submitting }) }),
21377
21451
  phase === "awaiting-timelock" && activePendingOp && /* @__PURE__ */ jsxRuntime.jsxs("section", { "data-chipi-recover-section": "awaiting", children: [
21378
21452
  /* @__PURE__ */ jsxRuntime.jsx("p", { "data-chipi-recover-status": "awaiting", children: timelockDescription }),
21379
- /* @__PURE__ */ jsxRuntime.jsxs(
21453
+ /* @__PURE__ */ jsxRuntime.jsx(
21380
21454
  "p",
21381
21455
  {
21382
21456
  "data-chipi-recover-countdown": "",
21383
21457
  "data-chipi-recover-seconds-remaining": String(secondsRemaining),
21384
- children: [
21385
- secondsRemaining,
21386
- "s remaining"
21387
- ]
21458
+ children: L.secondsRemaining(secondsRemaining)
21388
21459
  }
21389
21460
  ),
21390
21461
  /* @__PURE__ */ jsxRuntime.jsx("p", { "data-chipi-recover-op-id": "", children: String(activePendingOp.opId) }),
@@ -21395,12 +21466,12 @@ function Recover(props) {
21395
21466
  "data-chipi-recover-action": "cancel",
21396
21467
  onClick: handleCancel,
21397
21468
  disabled: submittingAction !== null,
21398
- children: "Cancel pending op"
21469
+ children: L.cancel
21399
21470
  }
21400
21471
  )
21401
21472
  ] }),
21402
21473
  (phase === "ready-to-execute" || phase === "submitting-execute") && activePendingOp && /* @__PURE__ */ jsxRuntime.jsxs("section", { "data-chipi-recover-section": "execute", children: [
21403
- /* @__PURE__ */ jsxRuntime.jsx("p", { "data-chipi-recover-status": "ready", children: "Timelock elapsed." }),
21474
+ /* @__PURE__ */ jsxRuntime.jsx("p", { "data-chipi-recover-status": "ready", children: L.timelockElapsed }),
21404
21475
  /* @__PURE__ */ jsxRuntime.jsx(
21405
21476
  "button",
21406
21477
  {
@@ -21418,12 +21489,12 @@ function Recover(props) {
21418
21489
  "data-chipi-recover-action": "cancel",
21419
21490
  onClick: handleCancel,
21420
21491
  disabled: submittingAction !== null,
21421
- children: "Cancel pending op"
21492
+ children: L.cancel
21422
21493
  }
21423
21494
  )
21424
21495
  ] }),
21425
- phase === "submitting-cancel" && /* @__PURE__ */ jsxRuntime.jsx("section", { "data-chipi-recover-section": "cancel", children: /* @__PURE__ */ jsxRuntime.jsx("p", { "data-chipi-recover-status": "submitting", children: "Cancelling\u2026" }) }),
21426
- phase === "done" && /* @__PURE__ */ jsxRuntime.jsx("section", { "data-chipi-recover-section": "done", children: /* @__PURE__ */ jsxRuntime.jsx("p", { "data-chipi-recover-status": "done", children: isAddDevice ? "New device added." : "Wallet recovered." }) }),
21496
+ phase === "submitting-cancel" && /* @__PURE__ */ jsxRuntime.jsx("section", { "data-chipi-recover-section": "cancel", children: /* @__PURE__ */ jsxRuntime.jsx("p", { "data-chipi-recover-status": "submitting", children: L.cancelling }) }),
21497
+ phase === "done" && /* @__PURE__ */ jsxRuntime.jsx("section", { "data-chipi-recover-section": "done", children: /* @__PURE__ */ jsxRuntime.jsx("p", { "data-chipi-recover-status": "done", children: isAddDevice ? L.doneAddDevice : L.doneRecovery }) }),
21427
21498
  error && /* @__PURE__ */ jsxRuntime.jsx("p", { "data-chipi-recover-error": "", role: "alert", children: error.message })
21428
21499
  ]
21429
21500
  }
@@ -21463,6 +21534,7 @@ Object.defineProperty(exports, "ChainToken", {
21463
21534
  });
21464
21535
  exports.ChipiInjector = ChipiInjector;
21465
21536
  exports.ChipiProvider = ChipiProvider;
21537
+ exports.DEFAULT_RECOVER_LABELS = DEFAULT_RECOVER_LABELS;
21466
21538
  exports.Recover = Recover;
21467
21539
  exports.createChipiWindowObject = createChipiWindowObject;
21468
21540
  exports.derivePrivacyAccount = derivePrivacyAccount;
@@ -21503,5 +21575,6 @@ exports.useTransfer = useTransfer;
21503
21575
  exports.useUnshield = useUnshield;
21504
21576
  exports.useUpdateWalletEncryption = useUpdateWalletEncryption;
21505
21577
  exports.useX402Payment = useX402Payment;
21578
+ exports.walletSupportsSessions = walletSupportsSessions;
21506
21579
  //# sourceMappingURL=index.js.map
21507
21580
  //# sourceMappingURL=index.js.map