@coti-io/coti-wallet-plugin 0.1.0 → 0.2.1

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
@@ -596,6 +596,7 @@ var init_plugin = __esm({
596
596
  defaultNetworkId: void 0,
597
597
  debug: false,
598
598
  clearSessionKeyOnWagmiDisconnect: false,
599
+ waitForBalanceRefreshAfterTransfer: false,
599
600
  onboardingServices: { mode: "disabled" },
600
601
  onboardingGrantMinBalanceWei: 0,
601
602
  onboardingGrantPollIntervalMs: 2e3,
@@ -3827,6 +3828,17 @@ var usePrivateTokenBalance = () => {
3827
3828
  if (error instanceof CotiPluginError) {
3828
3829
  throw error;
3829
3830
  }
3831
+ const message = error instanceof Error ? error.message : String(error?.message ?? error ?? "");
3832
+ const code = error?.code;
3833
+ const isDecryptPayloadFailure = message.includes("Invalid encrypted payload") || code === -32603 && /encrypt|decrypt|ciphertext/i.test(message);
3834
+ if (isDecryptPayloadFailure || canUseSnapDecrypt) {
3835
+ logger.error(`\u274C Failed to decrypt private balance for ${contractAddress}`, error);
3836
+ throw new CotiPluginError(
3837
+ "AES_KEY_MISMATCH" /* AES_KEY_MISMATCH */,
3838
+ isDecryptPayloadFailure ? "Could not decrypt private balances. The Snap AES key may be missing or invalid \u2014 re-onboarding is required." : "Could not decrypt private balances via Snap. Re-onboarding may be required.",
3839
+ message
3840
+ );
3841
+ }
3830
3842
  logger.error(`\u274C Failed to fetch/decrypt for ${contractAddress}`, error);
3831
3843
  return "0.00";
3832
3844
  }
@@ -4227,7 +4239,7 @@ var useBalanceUpdater = ({
4227
4239
  return { symbol: token.symbol, value, isMismatch: false };
4228
4240
  } catch (e) {
4229
4241
  const msg = e?.message || "";
4230
- const isMismatch = msg.includes("AES key mismatch") || msg.includes("onboarding") || msg.includes("ACCOUNT_NOT_ONBOARDED") || msg.includes("implausible decrypted balance");
4242
+ const isMismatch = e instanceof CotiPluginError && (e.code === "AES_KEY_MISMATCH" /* AES_KEY_MISMATCH */ || e.code === "ACCOUNT_NOT_ONBOARDED" /* ACCOUNT_NOT_ONBOARDED */) || msg.includes("AES key mismatch") || msg.includes("Invalid encrypted payload") || msg.includes("onboarding") || msg.includes("ACCOUNT_NOT_ONBOARDED") || msg.includes("implausible decrypted balance");
4231
4243
  if (isMismatch) {
4232
4244
  logger.warn(`\u26A0\uFE0F Private token decrypt mismatch for ${tokenAddress}. Falling back to 0.`);
4233
4245
  return { symbol: token.symbol, value: "0", isMismatch: true };
@@ -7518,7 +7530,7 @@ var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
7518
7530
  function useAesKeyProvider(walletTypeInfo) {
7519
7531
  const [isOnboarding, setIsOnboarding] = (0, import_react16.useState)(false);
7520
7532
  const [onboardingError, setOnboardingError] = (0, import_react16.useState)(null);
7521
- const [onboardingWarning, setOnboardingWarning] = (0, import_react16.useState)(null);
7533
+ const [onboardingWarnings, setOnboardingWarnings] = (0, import_react16.useState)({});
7522
7534
  const [wasRestoreCancelled, setWasRestoreCancelled] = (0, import_react16.useState)(false);
7523
7535
  const [currentStep, setCurrentStep] = (0, import_react16.useState)("idle");
7524
7536
  const progressCallbackRef = (0, import_react16.useRef)();
@@ -7539,7 +7551,7 @@ function useAesKeyProvider(walletTypeInfo) {
7539
7551
  progressCallbackRef.current = onProgress ?? options.onProgress;
7540
7552
  setOnboardingError(null);
7541
7553
  debugTraceRef.current.clear();
7542
- setOnboardingWarning(null);
7554
+ setOnboardingWarnings({});
7543
7555
  setWasRestoreCancelled(false);
7544
7556
  const forceContractOnboarding = options?.forceContractOnboarding === true;
7545
7557
  if (!forceContractOnboarding) {
@@ -7648,7 +7660,9 @@ function useAesKeyProvider(walletTypeInfo) {
7648
7660
  }
7649
7661
  } catch (restoreError) {
7650
7662
  if (isUserRejection(restoreError)) {
7651
- setOnboardingWarning("Backup restore was cancelled. Approve the wallet signature to unlock from your encrypted backup.");
7663
+ setOnboardingWarnings({
7664
+ intro: "Backup restore was cancelled. Approve the wallet signature to unlock from your encrypted backup."
7665
+ });
7652
7666
  setWasRestoreCancelled(true);
7653
7667
  options.onRestoreCancelled?.();
7654
7668
  emitStep("idle");
@@ -7657,7 +7671,9 @@ function useAesKeyProvider(walletTypeInfo) {
7657
7671
  const message = restoreError instanceof Error ? restoreError.message : "Encrypted AES backup could not be restored.";
7658
7672
  logger.warn("\u26A0\uFE0F AES backup restore failed, falling back to contract onboarding:", restoreError);
7659
7673
  restoreBackupFailed = true;
7660
- setOnboardingWarning(`Encrypted backup could not be restored. Continuing with onboarding. ${message}`);
7674
+ setOnboardingWarnings({
7675
+ intro: `Encrypted backup could not be restored. Continuing with onboarding. ${message}`
7676
+ });
7661
7677
  }
7662
7678
  }
7663
7679
  if (options.restoreOnly) {
@@ -7818,9 +7834,9 @@ function useAesKeyProvider(walletTypeInfo) {
7818
7834
  savedToSnap = await saveAESKeyToSnap(aesKey, address);
7819
7835
  if (!savedToSnap) {
7820
7836
  logger.warn("\u26A0\uFE0F AES key retrieved but could not persist to Snap");
7821
- setOnboardingWarning(
7822
- "Onboarding succeeded, but the AES key could not be saved to MetaMask Snap. You can retry by unlocking again."
7823
- );
7837
+ setOnboardingWarnings({
7838
+ success: "Onboarding succeeded, but the AES key could not be saved to MetaMask Snap. You can retry by unlocking again."
7839
+ });
7824
7840
  }
7825
7841
  } else if (aesKey && walletTypeInfo.walletType === "metamask" && canPersistAesKeyToSnap() && !walletTypeInfo.isMetaMaskWithSnap) {
7826
7842
  logger.log(
@@ -7842,14 +7858,14 @@ function useAesKeyProvider(walletTypeInfo) {
7842
7858
  "\u26A0\uFE0F AES key retrieved but encrypted backup save failed:",
7843
7859
  backupResult.message
7844
7860
  );
7845
- setOnboardingWarning(
7846
- `Onboarding succeeded, but encrypted backup was not saved. ${backupResult.message}`
7847
- );
7861
+ setOnboardingWarnings({
7862
+ success: `Onboarding succeeded, but encrypted backup was not saved. ${backupResult.message}`
7863
+ });
7848
7864
  } else if (backupResult.status === "cancelled") {
7849
7865
  logger.warn("\u26A0\uFE0F AES key retrieved but encrypted backup save was cancelled");
7850
- setOnboardingWarning(
7851
- "Onboarding succeeded, but encrypted backup save was cancelled. You can save it later by re-entering your AES key."
7852
- );
7866
+ setOnboardingWarnings({
7867
+ success: "Onboarding succeeded, but encrypted backup save was cancelled. You can save it later by re-entering your AES key."
7868
+ });
7853
7869
  }
7854
7870
  }
7855
7871
  if (aesKey && isValidAesKey(aesKey)) {
@@ -7913,7 +7929,7 @@ function useAesKeyProvider(walletTypeInfo) {
7913
7929
  getAesKey,
7914
7930
  isOnboarding,
7915
7931
  onboardingError,
7916
- onboardingWarning,
7932
+ onboardingWarnings,
7917
7933
  wasRestoreCancelled,
7918
7934
  currentStep
7919
7935
  };
@@ -8021,7 +8037,7 @@ var usePrivacyBridgeSessionCore = ({
8021
8037
  getAesKey: getAesKeyFromProvider,
8022
8038
  isOnboarding,
8023
8039
  onboardingError,
8024
- onboardingWarning
8040
+ onboardingWarnings
8025
8041
  } = useAesKeyProvider(walletTypeInfo);
8026
8042
  const { fetchPrivateBalance } = usePrivateTokenBalance();
8027
8043
  (0, import_react18.useEffect)(() => {
@@ -8077,7 +8093,7 @@ var usePrivacyBridgeSessionCore = ({
8077
8093
  fetchPrivateBalance,
8078
8094
  getAesKeyFromProvider,
8079
8095
  onboardingError,
8080
- onboardingWarning
8096
+ onboardingWarnings
8081
8097
  };
8082
8098
  };
8083
8099
 
@@ -8711,6 +8727,7 @@ var usePrivacyBridgeWalletConnection = ({
8711
8727
  // src/context/privacyBridge/usePrivacyBridgeUnlockSession.ts
8712
8728
  var import_react23 = require("react");
8713
8729
  var import_wagmi10 = require("wagmi");
8730
+ init_plugin();
8714
8731
  init_logger();
8715
8732
 
8716
8733
  // src/hooks/privacyBridge/privateValueCrypto.ts
@@ -8980,6 +8997,13 @@ var usePrivacyBridgeUnlockSession = ({
8980
8997
  }, [resolveAesAccess, resolveSessionAesKey, walletAddress, aesKeyChainId, checkSnapStatus, hasAesKeyInSnap, walletTypeInfo.walletType, currentChainId]);
8981
8998
  const refreshPrivateBalances = (0, import_react23.useCallback)(async (aesKeyOptions) => {
8982
8999
  if (!walletAddress) return false;
9000
+ const lockSessionOnAesFailure = !aesKeyOptions?.preserveSessionOnError;
9001
+ const clearSessionAfterAesFailure = () => {
9002
+ setSessionAesKey(null);
9003
+ clearAesKeyValidatedForUnlock(walletAddress);
9004
+ clearSnapCache();
9005
+ setArePrivateBalancesHidden(true);
9006
+ };
8983
9007
  logger.log("Triggering private balance fetch...");
8984
9008
  try {
8985
9009
  const chainOverride = wagmiSyncRef.current ? wagmiChainId : void 0;
@@ -9058,10 +9082,7 @@ var usePrivacyBridgeUnlockSession = ({
9058
9082
  );
9059
9083
  }
9060
9084
  if (errorInfo.message?.includes("ACCOUNT_NOT_ONBOARDED")) {
9061
- setSessionAesKey(null);
9062
- clearAesKeyValidatedForUnlock(walletAddress);
9063
- clearSnapCache();
9064
- setArePrivateBalancesHidden(true);
9085
+ if (lockSessionOnAesFailure) clearSessionAfterAesFailure();
9065
9086
  throw new CotiPluginError(
9066
9087
  "ACCOUNT_NOT_ONBOARDED" /* ACCOUNT_NOT_ONBOARDED */,
9067
9088
  "Account has not been onboarded to the COTI network.",
@@ -9069,17 +9090,11 @@ var usePrivacyBridgeUnlockSession = ({
9069
9090
  );
9070
9091
  }
9071
9092
  if (err instanceof CotiPluginError && err.code === "AES_KEY_MISMATCH" /* AES_KEY_MISMATCH */) {
9072
- setSessionAesKey(null);
9073
- clearAesKeyValidatedForUnlock(walletAddress);
9074
- clearSnapCache();
9075
- setArePrivateBalancesHidden(true);
9093
+ if (lockSessionOnAesFailure) clearSessionAfterAesFailure();
9076
9094
  throw err;
9077
9095
  }
9078
9096
  if (errorInfo.message?.includes("AES key") || errorInfo.message?.includes("onboarding")) {
9079
- setSessionAesKey(null);
9080
- clearAesKeyValidatedForUnlock(walletAddress);
9081
- clearSnapCache();
9082
- setArePrivateBalancesHidden(true);
9097
+ if (lockSessionOnAesFailure) clearSessionAfterAesFailure();
9083
9098
  throw new CotiPluginError(
9084
9099
  "AES_KEY_MISMATCH" /* AES_KEY_MISMATCH */,
9085
9100
  "AES key mismatch or onboarding error.",
@@ -9124,7 +9139,13 @@ var usePrivacyBridgeUnlockSession = ({
9124
9139
  hasSnap: strategy.snapInstalled,
9125
9140
  getAESKeyFromSnap: strategy.snapInstalled ? getAESKeyFromSnap : void 0
9126
9141
  });
9127
- await refreshPrivateBalances();
9142
+ if (getPluginConfig().waitForBalanceRefreshAfterTransfer) {
9143
+ await refreshPrivateBalances();
9144
+ } else {
9145
+ void refreshPrivateBalances({ preserveSessionOnError: true }).catch(
9146
+ (err) => logger.error("Background balance refresh after transfer failed", err)
9147
+ );
9148
+ }
9128
9149
  return result;
9129
9150
  }, [
9130
9151
  walletAddress,
@@ -9274,7 +9295,7 @@ var usePrivacyBridgeSession = ({ modals }) => {
9274
9295
  refreshPublicBalances: unlock.refreshPublicBalances,
9275
9296
  refreshPrivateBalances: unlock.refreshPrivateBalances,
9276
9297
  onboardingError: core.onboardingError,
9277
- onboardingWarning: core.onboardingWarning,
9298
+ onboardingWarnings: core.onboardingWarnings,
9278
9299
  lockPrivateBalances: unlock.lockPrivateBalances,
9279
9300
  saveManualAesKey: unlock.saveManualAesKey,
9280
9301
  sendPrivateToken: unlock.sendPrivateToken,
@@ -9647,6 +9668,40 @@ function deriveOnboardScreen({
9647
9668
 
9648
9669
  // src/components/OnboardModal.tsx
9649
9670
  init_logger();
9671
+
9672
+ // src/lib/onboardModalWarnings.ts
9673
+ function mergeOnboardModalWarnings(...sources) {
9674
+ const merged = {};
9675
+ for (const source of sources) {
9676
+ if (!source) continue;
9677
+ for (const page of Object.keys(source)) {
9678
+ const message = source[page];
9679
+ if (message) {
9680
+ merged[page] = message;
9681
+ }
9682
+ }
9683
+ }
9684
+ return merged;
9685
+ }
9686
+ function getDefaultSuccessWarning(saveBackup) {
9687
+ return saveBackup ? "Important: An encrypted backup can help restore this key later, but you should still store it safely." : "Important: This key will be lost when you refresh the page. Store it in a secure location.";
9688
+ }
9689
+ function resolveOnboardPageWarning(page, options) {
9690
+ const runtime = options.runtimeWarnings?.[page]?.trim();
9691
+ if (runtime) return runtime;
9692
+ const app = options.warnings?.[page]?.trim();
9693
+ if (app) return app;
9694
+ if (page === "success") {
9695
+ return getDefaultSuccessWarning(options.saveBackup ?? true);
9696
+ }
9697
+ return null;
9698
+ }
9699
+ function hasOnboardModalWarnings(warnings) {
9700
+ if (!warnings) return false;
9701
+ return Object.values(warnings).some((message) => !!message?.trim());
9702
+ }
9703
+
9704
+ // src/components/OnboardModal.tsx
9650
9705
  var import_jsx_runtime = require("react/jsx-runtime");
9651
9706
  var defaultStyles = {
9652
9707
  backdrop: {
@@ -9818,28 +9873,23 @@ var defaultStyles = {
9818
9873
  },
9819
9874
  saveOptionBody: {
9820
9875
  flex: 1,
9821
- minWidth: 0
9876
+ minWidth: 0,
9877
+ display: "flex",
9878
+ alignItems: "center",
9879
+ minHeight: "32px"
9822
9880
  },
9823
9881
  saveOptionTitleRow: {
9824
9882
  display: "flex",
9825
9883
  alignItems: "center",
9826
- gap: "5px",
9827
- marginBottom: "2px"
9884
+ gap: "5px"
9828
9885
  },
9829
9886
  saveOptionTitle: {
9830
9887
  fontSize: "13px",
9831
9888
  fontWeight: 600,
9832
- lineHeight: 1.25,
9833
- color: "#ffffff"
9834
- },
9835
- saveOptionDescription: {
9836
- fontSize: "11px",
9837
- lineHeight: 1.3,
9838
- color: "rgba(255, 255, 255, 0.55)",
9839
- margin: 0,
9840
- whiteSpace: "nowrap",
9841
- overflow: "hidden",
9842
- textOverflow: "ellipsis"
9889
+ lineHeight: "14px",
9890
+ color: "#ffffff",
9891
+ display: "inline-flex",
9892
+ alignItems: "center"
9843
9893
  },
9844
9894
  saveOptionSwitchTrack: {
9845
9895
  position: "relative",
@@ -9881,12 +9931,13 @@ var defaultStyles = {
9881
9931
  tooltipButton: {
9882
9932
  width: "14px",
9883
9933
  height: "14px",
9934
+ boxSizing: "border-box",
9884
9935
  borderRadius: "50%",
9885
9936
  border: "1px solid rgba(255, 255, 255, 0.25)",
9886
9937
  backgroundColor: "rgba(255, 255, 255, 0.08)",
9887
9938
  color: "rgba(255, 255, 255, 0.8)",
9888
9939
  fontSize: "9px",
9889
- lineHeight: 1,
9940
+ lineHeight: "12px",
9890
9941
  padding: 0,
9891
9942
  cursor: "help",
9892
9943
  flexShrink: 0,
@@ -10190,8 +10241,7 @@ var MUTED_TEXT_KEYS = [
10190
10241
  "closeButton",
10191
10242
  "tooltipButton",
10192
10243
  "cancelButton",
10193
- "stepDescription",
10194
- "saveOptionDescription"
10244
+ "stepDescription"
10195
10245
  ];
10196
10246
  var ACCENT_TEXT_KEYS = [
10197
10247
  "keyInput",
@@ -10241,7 +10291,8 @@ var LIGHT_INTERACTIVE_SURFACE_KEYS = [
10241
10291
  "saveOptionCard",
10242
10292
  "saveOptionIconWrap",
10243
10293
  "saveOptionSwitchTrackOff",
10244
- "saveOptionSwitchTrackOn"
10294
+ "saveOptionSwitchTrackOn",
10295
+ "tooltipButton"
10245
10296
  ];
10246
10297
  function isDefaultDarkSurfaceBackground(backgroundColor) {
10247
10298
  if (!backgroundColor) return true;
@@ -10325,6 +10376,11 @@ function applyLightInteractiveSurfaceGaps(merged, theme) {
10325
10376
  saveOptionSwitchTrackOn: {
10326
10377
  backgroundColor: primary,
10327
10378
  borderColor: colorWithAlpha(primary, 0.45)
10379
+ },
10380
+ tooltipButton: {
10381
+ color: muted2,
10382
+ backgroundColor: colorWithAlpha(foreground, 0.08),
10383
+ border: `1px solid ${colorWithAlpha(foreground, 0.2)}`
10328
10384
  }
10329
10385
  };
10330
10386
  for (const key of LIGHT_INTERACTIVE_SURFACE_KEYS) {
@@ -10471,7 +10527,8 @@ var OnboardModal = ({
10471
10527
  showSaveBackupOption = true,
10472
10528
  onSaveBackupChange,
10473
10529
  onManualAesKeySubmit,
10474
- warning,
10530
+ warnings,
10531
+ runtimeWarnings,
10475
10532
  theme
10476
10533
  }) => {
10477
10534
  const [copied, setCopied] = (0, import_react27.useState)(false);
@@ -10517,6 +10574,15 @@ var OnboardModal = ({
10517
10574
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles2.iconContainer, children: icon }),
10518
10575
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { id: "onboard-modal-title", style: styles2.title, children: title })
10519
10576
  ] });
10577
+ const renderPageWarning = (page) => {
10578
+ const message = resolveOnboardPageWarning(page, {
10579
+ warnings,
10580
+ runtimeWarnings,
10581
+ saveBackup
10582
+ });
10583
+ if (!message) return null;
10584
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: warningStyles.box, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: warningStyles.text, children: message }) });
10585
+ };
10520
10586
  const renderSaveLocallyOption = () => {
10521
10587
  if (!showSaveBackupOption) return null;
10522
10588
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
@@ -10528,29 +10594,26 @@ var OnboardModal = ({
10528
10594
  },
10529
10595
  children: [
10530
10596
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles2.saveOptionIconWrap, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("path", { d: "M21 2l-2 2m-7.61 7.61a5.5 5.5 0 1 1-7.778 7.778 5.5 5.5 0 0 1 7.777-7.777zm0 0L15.5 7.5m0 0 3 3L22 7l-3-3m-3.5 3.5L19 4" }) }) }),
10531
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles2.saveOptionBody, children: [
10532
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles2.saveOptionTitleRow, children: [
10533
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles2.saveOptionTitle, children: "Save Locally" }),
10534
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles2.tooltipWrap, children: [
10535
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10536
- "button",
10537
- {
10538
- type: "button",
10539
- "aria-label": "How local save works",
10540
- "aria-describedby": showBackupTooltip ? "backup-details-tooltip" : void 0,
10541
- onMouseEnter: () => setShowBackupTooltip(true),
10542
- onMouseLeave: () => setShowBackupTooltip(false),
10543
- onFocus: () => setShowBackupTooltip(true),
10544
- onBlur: () => setShowBackupTooltip(false),
10545
- style: styles2.tooltipButton,
10546
- children: "?"
10547
- }
10548
- ),
10549
- showBackupTooltip && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { id: "backup-details-tooltip", role: "tooltip", style: styles2.tooltipBubble, children: "Only an encrypted blob is stored locally. Restoring it requires a wallet signature." })
10550
- ] })
10551
- ] }),
10552
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles2.saveOptionDescription, children: "Encrypted locally. Restore via wallet signature." })
10553
- ] }),
10597
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles2.saveOptionBody, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: styles2.saveOptionTitleRow, children: [
10598
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: styles2.saveOptionTitle, children: "Save Locally" }),
10599
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { style: styles2.tooltipWrap, children: [
10600
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10601
+ "button",
10602
+ {
10603
+ type: "button",
10604
+ "aria-label": "How local save works",
10605
+ "aria-describedby": showBackupTooltip ? "backup-details-tooltip" : void 0,
10606
+ onMouseEnter: () => setShowBackupTooltip(true),
10607
+ onMouseLeave: () => setShowBackupTooltip(false),
10608
+ onFocus: () => setShowBackupTooltip(true),
10609
+ onBlur: () => setShowBackupTooltip(false),
10610
+ style: styles2.tooltipButton,
10611
+ children: "?"
10612
+ }
10613
+ ),
10614
+ showBackupTooltip && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { id: "backup-details-tooltip", role: "tooltip", style: styles2.tooltipBubble, children: "Only an encrypted blob is stored locally. Restoring it requires a wallet signature." })
10615
+ ] })
10616
+ ] }) }),
10554
10617
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10555
10618
  "button",
10556
10619
  {
@@ -10746,7 +10809,7 @@ var OnboardModal = ({
10746
10809
  "Onboard User"
10747
10810
  ),
10748
10811
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { id: "onboard-modal-description", style: styles2.description, children: "This will execute a transaction on the COTI Network to retrieve your encryption key." }),
10749
- warning && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: warningStyles.box, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: warningStyles.text, children: warning }) }),
10812
+ renderPageWarning("intro"),
10750
10813
  renderSaveLocallyOption(),
10751
10814
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
10752
10815
  "div",
@@ -10763,7 +10826,7 @@ var OnboardModal = ({
10763
10826
  type: "text",
10764
10827
  value: manualAesKey,
10765
10828
  onChange: (event) => setManualAesKey(event.target.value),
10766
- placeholder: "Paste AES key",
10829
+ placeholder: "Paste key",
10767
10830
  "aria-label": "Manual AES key",
10768
10831
  disabled: isSubmittingManualKey,
10769
10832
  style: { ...styles2.manualKeyInput, ...styles2.actionPrimary },
@@ -10837,7 +10900,7 @@ var OnboardModal = ({
10837
10900
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles2.spinner }),
10838
10901
  getProgressTitle(currentStep)
10839
10902
  ),
10840
- warning && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: warningStyles.box, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: warningStyles.text, children: warning }) }),
10903
+ renderPageWarning("progress"),
10841
10904
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles2.stepperContainer, children: displaySteps.map((step) => {
10842
10905
  const status = getOnboardingStepStatus(step.id, currentStep, !!error, includePersistStep);
10843
10906
  const isActive = status === "active";
@@ -10935,12 +10998,7 @@ var OnboardModal = ({
10935
10998
  )
10936
10999
  ] })
10937
11000
  ] }),
10938
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: warningStyles.box, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { style: warningStyles.text, children: [
10939
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: "Important:" }),
10940
- " ",
10941
- saveBackup ? "An encrypted backup can help restore this key later, but you should still store it safely." : "This key will be lost when you refresh the page. Store it in a secure location."
10942
- ] }) }),
10943
- warning && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: warningStyles.box, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: warningStyles.text, children: warning }) }),
11001
+ renderPageWarning("success"),
10944
11002
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10945
11003
  "button",
10946
11004
  {
@@ -10974,6 +11032,7 @@ var OnboardModal = ({
10974
11032
  ),
10975
11033
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { id: "onboard-modal-description", style: styles2.description, children: "The onboarding process encountered an error. You can retry the signature request." }),
10976
11034
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: styles2.errorBox, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { style: styles2.errorText, children: error || "An unknown error occurred" }) }),
11035
+ renderPageWarning("error"),
10977
11036
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
10978
11037
  "button",
10979
11038
  {
@@ -11084,14 +11143,14 @@ function keepForwardContractProgress(previous, next) {
11084
11143
  return nextOrder >= previousOrder ? next : previous;
11085
11144
  }
11086
11145
  function usePrivateUnlockController(options = {}) {
11087
- const { theme, warning, onUnlocked, onRestoreCancelled, onOnboardingCancelled } = options;
11146
+ const { theme, warnings, onUnlocked, onRestoreCancelled, onOnboardingCancelled } = options;
11088
11147
  const unlock = usePrivacyBridgeUnlock();
11089
11148
  const wallet = usePrivacyBridgeWallet();
11090
11149
  const walletTypeInfo = useWalletType();
11091
11150
  const [showOnboardModal, setShowOnboardModal] = (0, import_react28.useState)(false);
11092
11151
  const [isUnlocking, setIsUnlocking] = (0, import_react28.useState)(false);
11093
11152
  const [modalError, setModalError] = (0, import_react28.useState)(null);
11094
- const [modalWarning, setModalWarning] = (0, import_react28.useState)(null);
11153
+ const [modalRuntimeWarnings, setModalRuntimeWarnings] = (0, import_react28.useState)({});
11095
11154
  const [snapConnectedInModal, setSnapConnectedInModal] = (0, import_react28.useState)(false);
11096
11155
  const [saveBackup, setSaveBackup] = (0, import_react28.useState)(true);
11097
11156
  const [currentStep, setCurrentStep] = (0, import_react28.useState)("idle");
@@ -11128,7 +11187,7 @@ function usePrivateUnlockController(options = {}) {
11128
11187
  pendingActionOwnerRef.current = null;
11129
11188
  setCurrentStep("idle");
11130
11189
  setModalError(null);
11131
- setModalWarning(null);
11190
+ setModalRuntimeWarnings({});
11132
11191
  backupSaveProgressStartedAtRef.current = null;
11133
11192
  pendingCompleteRequestIdRef.current = null;
11134
11193
  contractOnboardingCancelledRef.current = false;
@@ -11157,7 +11216,7 @@ function usePrivateUnlockController(options = {}) {
11157
11216
  setShowOnboardModal(false);
11158
11217
  setCurrentStep("idle");
11159
11218
  setModalError(null);
11160
- setModalWarning(null);
11219
+ setModalRuntimeWarnings({});
11161
11220
  setIsUnlockInProgress(false);
11162
11221
  setIsUnlocking(false);
11163
11222
  backupSaveProgressStartedAtRef.current = null;
@@ -11182,7 +11241,7 @@ function usePrivateUnlockController(options = {}) {
11182
11241
  setShowOnboardModal(false);
11183
11242
  setCurrentStep("idle");
11184
11243
  setModalError(null);
11185
- setModalWarning(null);
11244
+ setModalRuntimeWarnings({});
11186
11245
  unlockInProgressOwnerRef.current = null;
11187
11246
  setIsUnlockInProgress(false);
11188
11247
  setIsUnlocking(false);
@@ -11218,14 +11277,18 @@ function usePrivateUnlockController(options = {}) {
11218
11277
  try {
11219
11278
  const connected = await unlock.requestSnapConnection();
11220
11279
  if (!connected) {
11221
- setModalWarning("Snap connection was skipped or rejected. Continuing without Snap storage.");
11280
+ setModalRuntimeWarnings({
11281
+ intro: "Snap connection was skipped or rejected. Continuing without Snap storage."
11282
+ });
11222
11283
  return false;
11223
11284
  }
11224
11285
  snapConnectedInModalRef.current = true;
11225
11286
  setSnapConnectedInModal(true);
11226
11287
  return true;
11227
11288
  } catch {
11228
- setModalWarning("Snap connection failed. Continuing without Snap storage.");
11289
+ setModalRuntimeWarnings({
11290
+ intro: "Snap connection failed. Continuing without Snap storage."
11291
+ });
11229
11292
  return false;
11230
11293
  }
11231
11294
  }, [canAttemptSnapInstall, unlock]);
@@ -11259,15 +11322,13 @@ function usePrivateUnlockController(options = {}) {
11259
11322
  if (failureMessage) {
11260
11323
  setCurrentStep("error");
11261
11324
  setModalError(failureMessage);
11262
- setModalWarning(unlock.onboardingWarning ?? null);
11263
11325
  releaseUnlockInProgress(requestId);
11264
11326
  return;
11265
11327
  }
11266
11328
  setCurrentStep("error");
11267
11329
  setModalError("Onboarding did not complete. Please retry.");
11268
- setModalWarning(unlock.onboardingWarning ?? null);
11269
11330
  releaseUnlockInProgress(requestId);
11270
- }, [dismissOnboardModal, isActiveUnlockRequest, onOnboardingCancelled, releaseUnlockInProgress, unlock.onboardingWarning]);
11331
+ }, [dismissOnboardModal, isActiveUnlockRequest, onOnboardingCancelled, releaseUnlockInProgress]);
11271
11332
  const handleStaleRestoreCompletion = (0, import_react28.useCallback)((requestId, refreshSucceeded) => {
11272
11333
  clearPendingActionForRequest(requestId);
11273
11334
  const unlocked = refreshSucceeded || isPrivateUnlockedRef.current;
@@ -11283,7 +11344,7 @@ function usePrivateUnlockController(options = {}) {
11283
11344
  setPendingActionForRequest(requestId, pendingAction);
11284
11345
  }
11285
11346
  setModalError(null);
11286
- setModalWarning(null);
11347
+ setModalRuntimeWarnings({});
11287
11348
  inFlightRestoreRequestIdRef.current = requestId;
11288
11349
  setIsUnlocking(true);
11289
11350
  try {
@@ -11317,7 +11378,6 @@ function usePrivateUnlockController(options = {}) {
11317
11378
  return false;
11318
11379
  }
11319
11380
  setCurrentStep("idle");
11320
- setModalWarning(unlock.onboardingWarning ?? null);
11321
11381
  setShowOnboardModal(true);
11322
11382
  return false;
11323
11383
  } catch (error) {
@@ -11379,7 +11439,6 @@ function usePrivateUnlockController(options = {}) {
11379
11439
  contractOnboardingFailureRef.current = failureMessage;
11380
11440
  setCurrentStep("error");
11381
11441
  setModalError(failureMessage);
11382
- setModalWarning(unlock.onboardingWarning ?? null);
11383
11442
  releaseUnlockInProgress(unlockRequestIdRef.current);
11384
11443
  }
11385
11444
  return;
@@ -11391,7 +11450,7 @@ function usePrivateUnlockController(options = {}) {
11391
11450
  if (step === "idle") {
11392
11451
  setModalError(null);
11393
11452
  }
11394
- }, [currentStep, showOnboardModal, releaseUnlockInProgress, unlock.onboardingWarning]);
11453
+ }, [currentStep, showOnboardModal, releaseUnlockInProgress]);
11395
11454
  const showOnboardingComplete = (0, import_react28.useCallback)((requestId) => {
11396
11455
  if (!isActiveUnlockRequest(requestId)) return;
11397
11456
  pendingCompleteRequestIdRef.current = null;
@@ -11409,7 +11468,7 @@ function usePrivateUnlockController(options = {}) {
11409
11468
  if (!connectedAddress) return;
11410
11469
  const requestId = unlockRequestIdRef.current;
11411
11470
  setModalError(null);
11412
- setModalWarning(null);
11471
+ setModalRuntimeWarnings({});
11413
11472
  setIsUnlocking(true);
11414
11473
  setShowOnboardModal(true);
11415
11474
  setCurrentStep("preparing-onboard");
@@ -11444,7 +11503,7 @@ function usePrivateUnlockController(options = {}) {
11444
11503
  currentStep,
11445
11504
  hasSessionAesKey: !!unlock.sessionAesKey,
11446
11505
  hasOnboardingError: !!unlock.onboardingError,
11447
- hasOnboardingWarning: !!unlock.onboardingWarning
11506
+ hasOnboardingWarning: hasOnboardModalWarnings(unlock.onboardingWarnings)
11448
11507
  });
11449
11508
  const backupSaveProgressStartedAt = backupSaveProgressStartedAtRef.current;
11450
11509
  if (backupSaveProgressStartedAt !== null) {
@@ -11560,7 +11619,7 @@ function usePrivateUnlockController(options = {}) {
11560
11619
  onSaveBackupChange: setSaveBackup,
11561
11620
  onManualAesKeySubmit: async (aesKey, { saveBackup: shouldSaveBackup }) => {
11562
11621
  const requestId = unlockRequestIdRef.current;
11563
- setModalWarning(null);
11622
+ setModalRuntimeWarnings({});
11564
11623
  const manualSaveResult = await unlock.saveManualAesKey(aesKey, {
11565
11624
  saveBackup: shouldSaveBackup,
11566
11625
  onProgress: (step) => {
@@ -11581,7 +11640,7 @@ function usePrivateUnlockController(options = {}) {
11581
11640
  return;
11582
11641
  }
11583
11642
  if (manualSaveResult.backupWarning) {
11584
- setModalWarning(manualSaveResult.backupWarning);
11643
+ setModalRuntimeWarnings({ success: manualSaveResult.backupWarning });
11585
11644
  setShowOnboardModal(true);
11586
11645
  setCurrentStep("complete");
11587
11646
  releaseUnlockInProgress(requestId);
@@ -11590,7 +11649,8 @@ function usePrivateUnlockController(options = {}) {
11590
11649
  await completeUnlock(requestId);
11591
11650
  },
11592
11651
  theme,
11593
- warning: modalWarning ?? unlock.onboardingWarning ?? warning ?? null
11652
+ warnings,
11653
+ runtimeWarnings: mergeOnboardModalWarnings(unlock.onboardingWarnings, modalRuntimeWarnings)
11594
11654
  }
11595
11655
  );
11596
11656
  return {
@@ -11763,7 +11823,7 @@ var PrivacyBridgeProvider = ({
11763
11823
  decryptPrivateValue: session.decryptPrivateValue,
11764
11824
  refreshPrivateBalances: session.refreshPrivateBalances,
11765
11825
  onboardingError: session.onboardingError,
11766
- onboardingWarning: session.onboardingWarning,
11826
+ onboardingWarnings: session.onboardingWarnings,
11767
11827
  lockPrivateBalances: session.lockPrivateBalances,
11768
11828
  handleOnboard: session.handleOnboard,
11769
11829
  saveManualAesKey: session.saveManualAesKey,