@oasisprotocol/privana-sdk 0.2.0 → 0.3.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
@@ -1,14 +1,14 @@
1
1
  "use client";
2
- import { createContext, useMemo, useState, useEffect, useRef, useCallback, useContext, useSyncExternalStore, useId } from 'react';
2
+ import { createContext, useContext, useRef, useCallback, useSyncExternalStore, useState, useEffect, useMemo, useId } from 'react';
3
3
  import { zeroAddress, erc20Abi, hexToString } from 'viem';
4
+ import { createSiweMessage } from 'viem/siwe';
5
+ import { WagmiContext, useAccount, useWalletClient, useConfig, useWriteContract, useSendTransaction, useChainId, useSwitchChain, useBalance as useBalance$1, useReadContract } from 'wagmi';
6
+ import { watchAccount, getAccount, getWalletClient } from 'wagmi/actions';
4
7
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
5
8
  import { QueryClientContext, useQuery, useQueryClient, useMutation, keepPreviousData } from '@tanstack/react-query';
6
9
  import { clsx } from 'clsx';
7
10
  import { twMerge } from 'tailwind-merge';
8
- import { createSiweMessage } from 'viem/siwe';
9
- import { WagmiContext, useAccount, useWalletClient, useConfig, useWriteContract, useSendTransaction, useChainId, useSwitchChain, useBalance as useBalance$1, useReadContract } from 'wagmi';
10
- import { watchAccount, getAccount, getWalletClient } from 'wagmi/actions';
11
- import { waitForTransactionReceipt as waitForTransactionReceipt$1, getTransaction, call } from 'viem/actions';
11
+ import { getTransactionReceipt as getTransactionReceipt$1, waitForTransactionReceipt as waitForTransactionReceipt$1, getTransaction, call } from 'viem/actions';
12
12
  import { Slot } from '@radix-ui/react-slot';
13
13
  import { cva } from 'class-variance-authority';
14
14
  import * as DialogPrimitive from '@radix-ui/react-dialog';
@@ -29,7 +29,7 @@ var config_default = {
29
29
  chainId: 23295,
30
30
  name: "Sapphire Testnet",
31
31
  accountingContract: "0xaF8e5de153A584528B57DD4B9B0195956BBDF571",
32
- apiUrl: "https://flexvaults-staging.rofl.build"
32
+ apiUrl: "https://testnet.privana.finance"
33
33
  },
34
34
  mainnet: {
35
35
  chainId: 23294,
@@ -769,6 +769,235 @@ async function signWithdrawFromLockMessage({
769
769
  });
770
770
  return signature;
771
771
  }
772
+ var defaultResult = {
773
+ address: void 0,
774
+ isConnected: false,
775
+ status: "disconnected"
776
+ };
777
+ function useSafeAccount() {
778
+ const context = useContext(WagmiContext);
779
+ const cacheRef = useRef(defaultResult);
780
+ const subscribe = useCallback(
781
+ (onChange) => {
782
+ if (!context) return () => {
783
+ };
784
+ return watchAccount(context, { onChange });
785
+ },
786
+ [context]
787
+ );
788
+ const getSnapshot = useCallback(() => {
789
+ if (!context) return defaultResult;
790
+ const account = getAccount(context);
791
+ if (cacheRef.current.address !== account.address || cacheRef.current.isConnected !== account.isConnected || cacheRef.current.status !== account.status) {
792
+ cacheRef.current = {
793
+ address: account.address,
794
+ isConnected: account.isConnected,
795
+ status: account.status
796
+ };
797
+ }
798
+ return cacheRef.current;
799
+ }, [context]);
800
+ const getServerSnapshot = useCallback(() => defaultResult, []);
801
+ return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
802
+ }
803
+
804
+ // src/sdk/hooks/private-read-token-store.ts
805
+ var AUTH_CLOCK_SKEW_MS = 3e4;
806
+ var cache = /* @__PURE__ */ new Map();
807
+ function createScopeKey(apiUrl, chainId, address) {
808
+ return `${apiUrl.replace(/\/$/, "")}:${chainId}:${address.toLowerCase()}`;
809
+ }
810
+ function getCachedPrivateReadToken(scopeKey) {
811
+ const cached = cache.get(scopeKey);
812
+ if (!cached) return null;
813
+ if (cached.expiresAt <= Date.now() + AUTH_CLOCK_SKEW_MS) {
814
+ cache.delete(scopeKey);
815
+ return null;
816
+ }
817
+ return cached.token;
818
+ }
819
+ function setCachedPrivateReadToken(scopeKey, token, expiresAt) {
820
+ cache.set(scopeKey, { token, expiresAt });
821
+ }
822
+ function deleteCachedPrivateReadToken(scopeKey) {
823
+ cache.delete(scopeKey);
824
+ }
825
+ var DEFAULT_SIWE_VALIDITY_MS = 24 * 60 * 60 * 1e3;
826
+ var DEFAULT_STATEMENT = "Sign in to access your private account data.";
827
+ var AUTH_REFRESH_SKEW_MS = 3e4;
828
+ var SiweAuthContext = createContext(null);
829
+ function SiweAuthProvider({
830
+ children,
831
+ client,
832
+ networkConfig,
833
+ autoLogin = true,
834
+ statement
835
+ }) {
836
+ const wagmiContext = useContext(WagmiContext);
837
+ const { address, isConnected, status } = useSafeAccount();
838
+ const [session, setSession] = useState(null);
839
+ const [tokens, setTokens] = useState(null);
840
+ const [isLoading, setIsLoading] = useState(false);
841
+ const [error, setError] = useState(null);
842
+ const [accessTokenExpiresAt, setAccessTokenExpiresAt] = useState(null);
843
+ const loginInFlight = useRef(false);
844
+ const autoAttemptedAddress = useRef(null);
845
+ const refreshInFlight = useRef(false);
846
+ const refreshDataRef = useRef(null);
847
+ const clearSession = useCallback(() => {
848
+ refreshDataRef.current = null;
849
+ setAccessTokenExpiresAt(null);
850
+ client.clearPrivateReadToken();
851
+ client.clearBearerToken();
852
+ setSession(null);
853
+ setTokens(null);
854
+ setError(null);
855
+ autoAttemptedAddress.current = null;
856
+ }, [client]);
857
+ const logout = useCallback(async () => {
858
+ setError(null);
859
+ const refreshToken = refreshDataRef.current?.refreshToken;
860
+ try {
861
+ if (refreshToken) {
862
+ await client.logoutJwtSession({ refresh_token: refreshToken });
863
+ }
864
+ } finally {
865
+ clearSession();
866
+ autoAttemptedAddress.current = address ?? null;
867
+ }
868
+ }, [clearSession, client, address]);
869
+ const login = useCallback(async () => {
870
+ if (!wagmiContext) throw new Error("WagmiProvider is required for SIWE auth");
871
+ if (!address) throw new Error("No wallet connected");
872
+ if (loginInFlight.current) return;
873
+ loginInFlight.current = true;
874
+ setIsLoading(true);
875
+ setError(null);
876
+ try {
877
+ const walletClient = await getWalletClient(wagmiContext);
878
+ if (!walletClient) throw new Error("No wallet client available");
879
+ const [{ domain }, nonceRes] = await Promise.all([
880
+ client.getSiweDomain(),
881
+ client.getSiweNonce(address)
882
+ ]);
883
+ const issuedAt = /* @__PURE__ */ new Date();
884
+ const expirationTime = new Date(issuedAt.getTime() + DEFAULT_SIWE_VALIDITY_MS);
885
+ const uri = typeof window !== "undefined" && window.location.origin ? window.location.origin : networkConfig.apiUrl;
886
+ const message = createSiweMessage({
887
+ address,
888
+ chainId: networkConfig.chainId,
889
+ domain,
890
+ uri,
891
+ version: "1",
892
+ nonce: nonceRes.nonce,
893
+ statement: statement ?? DEFAULT_STATEMENT,
894
+ issuedAt,
895
+ expirationTime
896
+ });
897
+ const signature = await walletClient.signMessage({
898
+ account: walletClient.account ?? address,
899
+ message
900
+ });
901
+ const res = await client.loginWithSiwe({ siwe_message: message, signature });
902
+ const loggedInAt = Date.now();
903
+ client.setPrivateReadToken(res.siwe_token);
904
+ client.setBearerToken(res.jwt_access_token);
905
+ refreshDataRef.current = {
906
+ refreshToken: res.jwt_refresh_token,
907
+ refreshExpiresAt: loggedInAt + res.jwt_refresh_expires_in * 1e3
908
+ };
909
+ setCachedPrivateReadToken(
910
+ createScopeKey(networkConfig.apiUrl, networkConfig.chainId, address),
911
+ res.siwe_token,
912
+ expirationTime.getTime()
913
+ );
914
+ setSession({ address: res.address });
915
+ setTokens({
916
+ siwe_token: res.siwe_token,
917
+ jwt_access_token: res.jwt_access_token,
918
+ jwt_refresh_token: res.jwt_refresh_token,
919
+ address: res.address
920
+ });
921
+ setAccessTokenExpiresAt(loggedInAt + res.jwt_expires_in * 1e3);
922
+ } catch (err) {
923
+ setError(err instanceof Error ? err : new Error("Sign-in failed"));
924
+ throw err;
925
+ } finally {
926
+ setIsLoading(false);
927
+ loginInFlight.current = false;
928
+ }
929
+ }, [wagmiContext, address, client, networkConfig.chainId, networkConfig.apiUrl, statement]);
930
+ const refreshAccessToken = useCallback(async () => {
931
+ const data = refreshDataRef.current;
932
+ if (!data || refreshInFlight.current) return;
933
+ if (Date.now() >= data.refreshExpiresAt - AUTH_REFRESH_SKEW_MS) {
934
+ clearSession();
935
+ return;
936
+ }
937
+ refreshInFlight.current = true;
938
+ try {
939
+ const res = await client.refreshJwtSession({ refresh_token: data.refreshToken });
940
+ const refreshedAt = Date.now();
941
+ client.setBearerToken(res.token);
942
+ refreshDataRef.current = {
943
+ refreshToken: res.refresh_token,
944
+ refreshExpiresAt: refreshedAt + res.refresh_expires_in * 1e3
945
+ };
946
+ setTokens(
947
+ (prev) => prev ? { ...prev, jwt_access_token: res.token, jwt_refresh_token: res.refresh_token } : prev
948
+ );
949
+ setAccessTokenExpiresAt(refreshedAt + res.expires_in * 1e3);
950
+ } catch {
951
+ clearSession();
952
+ } finally {
953
+ refreshInFlight.current = false;
954
+ }
955
+ }, [client, clearSession]);
956
+ useEffect(() => {
957
+ if (accessTokenExpiresAt == null) return;
958
+ const delay = Math.max(accessTokenExpiresAt - AUTH_REFRESH_SKEW_MS - Date.now(), 0);
959
+ const timer = setTimeout(() => {
960
+ void refreshAccessToken();
961
+ }, delay);
962
+ return () => clearTimeout(timer);
963
+ }, [accessTokenExpiresAt, refreshAccessToken]);
964
+ useEffect(() => {
965
+ if (!autoLogin) return;
966
+ if (status === "connecting" || status === "reconnecting") return;
967
+ if (!isConnected && session) {
968
+ clearSession();
969
+ return;
970
+ }
971
+ if (isConnected && address && session && address.toLowerCase() !== session.address.toLowerCase()) {
972
+ clearSession();
973
+ return;
974
+ }
975
+ if (isConnected && address && !session && !isLoading && autoAttemptedAddress.current !== address) {
976
+ autoAttemptedAddress.current = address;
977
+ void login().catch(() => {
978
+ });
979
+ }
980
+ }, [autoLogin, status, isConnected, address, session, isLoading, login, clearSession]);
981
+ const value = useMemo(
982
+ () => ({
983
+ isAuthenticated: !!session,
984
+ isLoading,
985
+ error,
986
+ session,
987
+ accessToken: tokens?.jwt_access_token,
988
+ tokens,
989
+ login,
990
+ logout
991
+ }),
992
+ [session, isLoading, error, tokens, login, logout]
993
+ );
994
+ return /* @__PURE__ */ jsx(SiweAuthContext.Provider, { value, children });
995
+ }
996
+ function useSiweAuth() {
997
+ const ctx = useContext(SiweAuthContext);
998
+ if (!ctx) throw new Error("useSiweAuth must be used within SiweAuthProvider");
999
+ return ctx;
1000
+ }
772
1001
  var PrivanaContext = createContext(null);
773
1002
  function readStoredHostedAuthSession(storage, hostedAuthStorageKey, now = Date.now()) {
774
1003
  const raw = storage.getItem(hostedAuthStorageKey);
@@ -809,8 +1038,14 @@ function PrivanaProvider({
809
1038
  chains,
810
1039
  pollingInterval = 1e4,
811
1040
  serviceAddress,
812
- hostedAuth
1041
+ hostedAuth,
1042
+ siweAuth
813
1043
  }) {
1044
+ if (hostedAuth && siweAuth) {
1045
+ throw new Error(
1046
+ "PrivanaProvider: `hostedAuth` and `siweAuth` are mutually exclusive - provide only one. When both are set, private reads use hosted auth and the in-app SIWE login is ignored."
1047
+ );
1048
+ }
814
1049
  const networkConfig = useMemo(() => {
815
1050
  const config = {
816
1051
  ...DEFAULT_NETWORK_CONFIG,
@@ -1045,7 +1280,16 @@ function PrivanaProvider({
1045
1280
  refreshHostedAuthSession
1046
1281
  ]
1047
1282
  );
1048
- return /* @__PURE__ */ jsx(PrivanaContext.Provider, { value, children });
1283
+ return /* @__PURE__ */ jsx(PrivanaContext.Provider, { value, children: siweAuth ? /* @__PURE__ */ jsx(
1284
+ SiweAuthProvider,
1285
+ {
1286
+ client,
1287
+ networkConfig,
1288
+ autoLogin: siweAuth.autoLogin,
1289
+ statement: siweAuth.statement,
1290
+ children
1291
+ }
1292
+ ) : children });
1049
1293
  }
1050
1294
  function usePrivanaContext() {
1051
1295
  const context = useContext(PrivanaContext);
@@ -1305,40 +1549,10 @@ function formatTimeRemaining(expiryTimestamp) {
1305
1549
  }
1306
1550
  return `${minutes}m left`;
1307
1551
  }
1308
- var defaultResult = {
1309
- address: void 0,
1310
- isConnected: false
1311
- };
1312
- function useSafeAccount() {
1313
- const context = useContext(WagmiContext);
1314
- const cacheRef = useRef(defaultResult);
1315
- const subscribe = useCallback(
1316
- (onChange) => {
1317
- if (!context) return () => {
1318
- };
1319
- return watchAccount(context, { onChange });
1320
- },
1321
- [context]
1322
- );
1323
- const getSnapshot = useCallback(() => {
1324
- if (!context) return defaultResult;
1325
- const account = getAccount(context);
1326
- if (cacheRef.current.address !== account.address || cacheRef.current.isConnected !== account.isConnected) {
1327
- cacheRef.current = { address: account.address, isConnected: account.isConnected };
1328
- }
1329
- return cacheRef.current;
1330
- }, [context]);
1331
- const getServerSnapshot = useCallback(() => defaultResult, []);
1332
- return useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
1333
- }
1334
-
1335
- // src/sdk/hooks/use-private-read-request.ts
1336
- var AUTH_CLOCK_SKEW_MS = 3e4;
1337
1552
  var INITIAL_AUTH_BACKOFF_MS = 5e3;
1338
1553
  var MAX_AUTH_BACKOFF_MS = 6e4;
1339
1554
  var DEFAULT_SIWE_AUTH_VALIDITY_MS = 24 * 60 * 60 * 1e3;
1340
1555
  var PRIVATE_READ_STATEMENT = "Sign in to Privana to access private account data.";
1341
- var privateReadTokenCache = /* @__PURE__ */ new Map();
1342
1556
  var privateReadFailureCache = /* @__PURE__ */ new Map();
1343
1557
  var privateReadInflight = /* @__PURE__ */ new Map();
1344
1558
  async function executeHostedAuthPrivateReadRequest({
@@ -1372,20 +1586,8 @@ async function executeHostedAuthPrivateReadRequest({
1372
1586
  return request();
1373
1587
  }
1374
1588
  }
1375
- function createScopeKey(apiUrl, deploymentChainId, address) {
1376
- return `${apiUrl.replace(/\/$/, "")}:${deploymentChainId}:${address.toLowerCase()}`;
1377
- }
1378
- function getCachedPrivateReadToken(scopeKey) {
1379
- const cached = privateReadTokenCache.get(scopeKey);
1380
- if (!cached) return null;
1381
- if (cached.expiresAt <= Date.now() + AUTH_CLOCK_SKEW_MS) {
1382
- privateReadTokenCache.delete(scopeKey);
1383
- return null;
1384
- }
1385
- return cached.token;
1386
- }
1387
1589
  function clearPrivateReadScope(scopeKey, client) {
1388
- privateReadTokenCache.delete(scopeKey);
1590
+ deleteCachedPrivateReadToken(scopeKey);
1389
1591
  privateReadFailureCache.delete(scopeKey);
1390
1592
  client.clearPrivateReadToken();
1391
1593
  }
@@ -1484,10 +1686,7 @@ function usePrivateReadRequest() {
1484
1686
  siwe_message: message,
1485
1687
  signature
1486
1688
  });
1487
- privateReadTokenCache.set(scopeKey, {
1488
- token: login.siwe_token,
1489
- expiresAt: expirationTime.getTime()
1490
- });
1689
+ setCachedPrivateReadToken(scopeKey, login.siwe_token, expirationTime.getTime());
1491
1690
  privateReadFailureCache.delete(scopeKey);
1492
1691
  client.setPrivateReadToken(login.siwe_token);
1493
1692
  return login.siwe_token;
@@ -1608,6 +1807,12 @@ function getAction(client, actionFn, name) {
1608
1807
  function getChainId2(config) {
1609
1808
  return config.state.chainId;
1610
1809
  }
1810
+ async function getTransactionReceipt(config, parameters) {
1811
+ const { chainId, ...rest } = parameters;
1812
+ const client = config.getClient({ chainId });
1813
+ const action = getAction(client, getTransactionReceipt$1, "getTransactionReceipt");
1814
+ return action(rest);
1815
+ }
1611
1816
  async function waitForTransactionReceipt(config, parameters) {
1612
1817
  const { chainId, timeout = 0, ...rest } = parameters;
1613
1818
  const client = config.getClient({ chainId });
@@ -1688,6 +1893,36 @@ function useEnsureCorrectChain() {
1688
1893
  }
1689
1894
 
1690
1895
  // src/sdk/hooks/use-deposit.ts
1896
+ var STALE_MS = 30 * 60 * 1e3;
1897
+ function storageKey(address) {
1898
+ return `privana:pending-deposit:${address.toLowerCase()}`;
1899
+ }
1900
+ function savePendingDeposit(address, data) {
1901
+ try {
1902
+ sessionStorage.setItem(storageKey(address), JSON.stringify(data));
1903
+ } catch {
1904
+ }
1905
+ }
1906
+ function loadPendingDeposit(address) {
1907
+ try {
1908
+ const raw = sessionStorage.getItem(storageKey(address));
1909
+ if (!raw) return null;
1910
+ const data = JSON.parse(raw);
1911
+ if (Date.now() - data.savedAt > STALE_MS) {
1912
+ clearPendingDeposit(address);
1913
+ return null;
1914
+ }
1915
+ return data;
1916
+ } catch {
1917
+ return null;
1918
+ }
1919
+ }
1920
+ function clearPendingDeposit(address) {
1921
+ try {
1922
+ sessionStorage.removeItem(storageKey(address));
1923
+ } catch {
1924
+ }
1925
+ }
1691
1926
  function useDeposit(options = {}) {
1692
1927
  const { address } = useAccount();
1693
1928
  const { client, enabledTokens, getChainById: getChainById2 } = usePrivanaContext();
@@ -1755,13 +1990,19 @@ function useDeposit(options = {}) {
1755
1990
  const isSendingTx = isWritingContract || isSendingNative;
1756
1991
  const sendError = writeError ?? sendNativeError;
1757
1992
  const { ensureCorrectChain } = useEnsureCorrectChain();
1993
+ const invalidateGeneration = useCallback(() => {
1994
+ generationRef.current++;
1995
+ }, []);
1758
1996
  useEffect(() => {
1759
1997
  return () => {
1998
+ invalidateGeneration();
1760
1999
  if (pollIntervalRef.current) {
1761
2000
  clearTimeout(pollIntervalRef.current);
2001
+ pollIntervalRef.current = null;
1762
2002
  }
1763
2003
  };
1764
- }, []);
2004
+ }, [invalidateGeneration]);
2005
+ const resumedAddressRef = useRef(void 0);
1765
2006
  const stopPolling = useCallback(() => {
1766
2007
  if (pollIntervalRef.current) {
1767
2008
  clearTimeout(pollIntervalRef.current);
@@ -1771,6 +2012,7 @@ function useDeposit(options = {}) {
1771
2012
  const reset = useCallback(() => {
1772
2013
  generationRef.current++;
1773
2014
  stopPolling();
2015
+ if (address) clearPendingDeposit(address);
1774
2016
  verificationContextRef.current = null;
1775
2017
  setDepositAddress(null);
1776
2018
  setTxHash(void 0);
@@ -1783,7 +2025,7 @@ function useDeposit(options = {}) {
1783
2025
  addressMutation.reset();
1784
2026
  resetWriteContract();
1785
2027
  resetSendTransaction();
1786
- }, [addressMutation, resetWriteContract, resetSendTransaction, stopPolling]);
2028
+ }, [address, addressMutation, resetWriteContract, resetSendTransaction, stopPolling]);
1787
2029
  const runVerification = useCallback(
1788
2030
  async (ctx, generation) => {
1789
2031
  const isStale = () => generation !== generationRef.current;
@@ -1811,6 +2053,7 @@ function useDeposit(options = {}) {
1811
2053
  if (triggerResult.status === "credited") {
1812
2054
  setIsWaitingForProcessing(false);
1813
2055
  verificationContextRef.current = null;
2056
+ if (address) clearPendingDeposit(address);
1814
2057
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
1815
2058
  queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
1816
2059
  onCreditedRef.current?.(hash, triggerResult);
@@ -1844,6 +2087,7 @@ function useDeposit(options = {}) {
1844
2087
  stopPolling();
1845
2088
  setIsWaitingForProcessing(false);
1846
2089
  verificationContextRef.current = null;
2090
+ if (address) clearPendingDeposit(address);
1847
2091
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
1848
2092
  queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
1849
2093
  onCreditedRef.current?.(hash, result);
@@ -1883,7 +2127,7 @@ function useDeposit(options = {}) {
1883
2127
  );
1884
2128
  }
1885
2129
  },
1886
- [client, executePrivateRead, pollInterval, pollTimeout, queryClient, stopPolling]
2130
+ [address, client, executePrivateRead, pollInterval, pollTimeout, queryClient, stopPolling]
1887
2131
  );
1888
2132
  const retryVerification = useCallback(async () => {
1889
2133
  const ctx = verificationContextRef.current;
@@ -1895,6 +2139,55 @@ function useDeposit(options = {}) {
1895
2139
  const generation = generationRef.current;
1896
2140
  await runVerification(ctx, generation);
1897
2141
  }, [runVerification, stopPolling]);
2142
+ useEffect(() => {
2143
+ if (!address || resumedAddressRef.current === address) return;
2144
+ const persisted = loadPendingDeposit(address);
2145
+ if (!persisted) return;
2146
+ resumedAddressRef.current = address;
2147
+ const hash = persisted.txHash;
2148
+ setTxHash(hash);
2149
+ setDepositAddress(persisted.depositAddress);
2150
+ setIsWaitingForConfirmation(true);
2151
+ const ctx = {
2152
+ hash,
2153
+ chainId: persisted.chainId,
2154
+ amount: BigInt(persisted.amount)
2155
+ };
2156
+ verificationContextRef.current = ctx;
2157
+ const generation = ++generationRef.current;
2158
+ const isStale = () => generation !== generationRef.current;
2159
+ (async () => {
2160
+ try {
2161
+ let confirmed = false;
2162
+ try {
2163
+ await getTransactionReceipt(config, { hash, chainId: persisted.chainId });
2164
+ confirmed = true;
2165
+ } catch {
2166
+ }
2167
+ if (!confirmed) {
2168
+ await waitForTransactionReceipt(config, {
2169
+ hash,
2170
+ chainId: persisted.chainId,
2171
+ confirmations
2172
+ });
2173
+ }
2174
+ if (isStale()) return;
2175
+ setIsWaitingForConfirmation(false);
2176
+ onDepositSuccessRef.current?.(hash);
2177
+ queryClient.invalidateQueries({ queryKey: ["readContract"] });
2178
+ await runVerification(ctx, generation);
2179
+ } catch (err) {
2180
+ if (isStale()) return;
2181
+ setIsWaitingForConfirmation(false);
2182
+ stopPolling();
2183
+ const error2 = err instanceof Error ? err : new Error("Deposit verification failed");
2184
+ setIsWaitingForProcessing(false);
2185
+ setDepositError(error2);
2186
+ setVerificationFailed(true);
2187
+ onErrorRef.current?.(error2);
2188
+ }
2189
+ })();
2190
+ }, [address, config, confirmations, queryClient, runVerification, stopPolling]);
1898
2191
  const deposit = useCallback(
1899
2192
  async (params) => {
1900
2193
  if (verificationContextRef.current) {
@@ -1951,10 +2244,21 @@ function useDeposit(options = {}) {
1951
2244
  amount: params.amount
1952
2245
  };
1953
2246
  verificationContextRef.current = ctx;
2247
+ savePendingDeposit(address, {
2248
+ txHash: hash,
2249
+ chainId: sourceChain.id,
2250
+ amount: params.amount.toString(),
2251
+ depositAddress: addrResponse,
2252
+ savedAt: Date.now()
2253
+ });
1954
2254
  try {
1955
2255
  setIsWaitingForConfirmation(true);
1956
2256
  try {
1957
- await waitForTransactionReceipt(config, { hash, confirmations });
2257
+ await waitForTransactionReceipt(config, {
2258
+ hash,
2259
+ chainId: sourceChain.id,
2260
+ confirmations
2261
+ });
1958
2262
  } finally {
1959
2263
  if (!isStale()) setIsWaitingForConfirmation(false);
1960
2264
  }
@@ -3152,6 +3456,7 @@ function DepositForm({
3152
3456
  selectedToken,
3153
3457
  onTokenSelect,
3154
3458
  onPendingChange,
3459
+ onUnsafeToCloseChange,
3155
3460
  onSuccess
3156
3461
  }) {
3157
3462
  const { isConnected, address } = useAccount();
@@ -3180,7 +3485,7 @@ function DepositForm({
3180
3485
  }
3181
3486
  });
3182
3487
  const walletBalance = isNative ? nativeBalanceData?.value : erc20Balance;
3183
- const formattedWalletBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0";
3488
+ const formattedWalletBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0.00";
3184
3489
  const handleMaxClick = () => {
3185
3490
  if (formattedWalletBalance && parseFloat(formattedWalletBalance) > 0) {
3186
3491
  setAmount(formattedWalletBalance.replace(/[\s\u2009]/g, ""));
@@ -3247,6 +3552,9 @@ function DepositForm({
3247
3552
  useEffect(() => {
3248
3553
  onPendingChange?.(isPending && !cancelled);
3249
3554
  }, [isPending, cancelled, onPendingChange]);
3555
+ useEffect(() => {
3556
+ onUnsafeToCloseChange?.((isGettingAddress || isSendingTransaction) && !cancelled);
3557
+ }, [isGettingAddress, isSendingTransaction, cancelled, onUnsafeToCloseChange]);
3250
3558
  const handleCancel = () => {
3251
3559
  setCancelled(true);
3252
3560
  reset();
@@ -3410,7 +3718,12 @@ function DepositForm({
3410
3718
  )
3411
3719
  ] });
3412
3720
  }
3413
- function WithdrawForm({ selectedToken, onTokenSelect, onPendingChange }) {
3721
+ function WithdrawForm({
3722
+ selectedToken,
3723
+ onTokenSelect,
3724
+ onPendingChange,
3725
+ onUnsafeToCloseChange
3726
+ }) {
3414
3727
  const { isConnected, address } = useAccount();
3415
3728
  const { chains, getChainById: getChainById2 } = usePrivanaContext();
3416
3729
  const [amount, setAmount] = useState("");
@@ -3463,8 +3776,10 @@ function WithdrawForm({ selectedToken, onTokenSelect, onPendingChange }) {
3463
3776
  }
3464
3777
  }, [error]);
3465
3778
  useEffect(() => {
3466
- onPendingChange?.(isPending && !cancelled);
3467
- }, [isPending, cancelled, onPendingChange]);
3779
+ const pending = isPending && !cancelled;
3780
+ onPendingChange?.(pending);
3781
+ onUnsafeToCloseChange?.(pending);
3782
+ }, [isPending, cancelled, onPendingChange, onUnsafeToCloseChange]);
3468
3783
  const handleCancel = () => {
3469
3784
  setCancelled(true);
3470
3785
  reset();
@@ -3623,9 +3938,6 @@ function CloseIcon() {
3623
3938
  }
3624
3939
  ) });
3625
3940
  }
3626
- function SearchIcon() {
3627
- return /* @__PURE__ */ jsx("div", { className: "h-3 w-3 rounded-full border-[1.5px] border-current" });
3628
- }
3629
3941
  function ChevronRight() {
3630
3942
  return /* @__PURE__ */ jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "-rotate-90", children: /* @__PURE__ */ jsx(
3631
3943
  "path",
@@ -3686,7 +3998,7 @@ function BalanceCards({
3686
3998
  });
3687
3999
  const { totalLocked, isLoading: lockedLoading } = useLockedFunds({ enabled: showLockedFunds });
3688
4000
  const formattedBalance = formatTokenAmount(balanceWei, selectedToken.decimals);
3689
- const formattedLocked = showLockedFunds ? formatTokenAmount(String(totalLocked), selectedToken.decimals) : "0";
4001
+ const formattedLocked = showLockedFunds ? formatTokenAmount(String(totalLocked), selectedToken.decimals) : "0.00";
3690
4002
  return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-2", disabled && "opacity-50"), children: [
3691
4003
  /* @__PURE__ */ jsxs(
3692
4004
  "button",
@@ -3749,7 +4061,7 @@ function Tabs({
3749
4061
  "div",
3750
4062
  {
3751
4063
  className: cn(
3752
- "bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-4px)] rounded-md transition-transform duration-200",
4064
+ "bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-8px)] rounded-md transition-transform duration-200",
3753
4065
  activeTab === "withdraw" && "translate-x-[calc(100%+8px)]"
3754
4066
  )
3755
4067
  }
@@ -3784,7 +4096,7 @@ function Tabs({
3784
4096
  }
3785
4097
  );
3786
4098
  }
3787
- function LockedFundsView({ onBack, onClose }) {
4099
+ function LockedFundsView({ onBack }) {
3788
4100
  const { getTokenById } = usePrivanaContext();
3789
4101
  const { locks, isLoading } = useLockedFunds();
3790
4102
  const { unlockFunds, unlockAllExpired, isPending } = useUnlockFunds();
@@ -3817,27 +4129,17 @@ function LockedFundsView({ onBack, onClose }) {
3817
4129
  };
3818
4130
  const expiredCount = locks.filter((l) => l.is_expired).length;
3819
4131
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3820
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4", children: [
3821
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
3822
- /* @__PURE__ */ jsx(
3823
- "button",
3824
- {
3825
- onClick: onBack,
3826
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
3827
- children: /* @__PURE__ */ jsx(ChevronLeft, {})
3828
- }
3829
- ),
3830
- /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Locked Funds" })
3831
- ] }),
3832
- onClose && /* @__PURE__ */ jsx(
4132
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4133
+ /* @__PURE__ */ jsx(
3833
4134
  "button",
3834
4135
  {
3835
- onClick: onClose,
3836
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
3837
- children: /* @__PURE__ */ jsx(CloseIcon, {})
4136
+ onClick: onBack,
4137
+ className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4138
+ children: /* @__PURE__ */ jsx(ChevronLeft, {})
3838
4139
  }
3839
- )
3840
- ] }),
4140
+ ),
4141
+ /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Locked Funds" })
4142
+ ] }) }),
3841
4143
  /* @__PURE__ */ jsxs("div", { className: "bg-muted flex min-h-0 flex-1 flex-col rounded-[10px] p-2", children: [
3842
4144
  /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: isLoading ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 p-3", children: [1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex animate-pulse items-center gap-3 rounded-lg p-3", children: [
3843
4145
  /* @__PURE__ */ jsx("div", { className: "bg-secondary h-10 w-10 rounded-full" }),
@@ -3922,34 +4224,24 @@ function BalanceTokenRow({ token }) {
3922
4224
  isLoading ? /* @__PURE__ */ jsx("span", { className: "bg-secondary h-4 w-16 animate-pulse rounded" }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: formattedBalance })
3923
4225
  ] });
3924
4226
  }
3925
- function BalanceDetailsView({ onBack, onClose }) {
4227
+ function BalanceDetailsView({ onBack }) {
3926
4228
  const { enabledTokens, chains } = usePrivanaContext();
3927
4229
  const [selectedChainId, setSelectedChainId] = useState(chains[0]?.id ?? 84532);
3928
4230
  const chainTokens = useMemo(() => {
3929
4231
  return enabledTokens.filter((t) => t.chainId === selectedChainId);
3930
4232
  }, [enabledTokens, selectedChainId]);
3931
4233
  return /* @__PURE__ */ jsxs(Fragment, { children: [
3932
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4", children: [
3933
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
3934
- /* @__PURE__ */ jsx(
3935
- "button",
3936
- {
3937
- onClick: onBack,
3938
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
3939
- children: /* @__PURE__ */ jsx(ChevronLeft, {})
3940
- }
3941
- ),
3942
- /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Token Balances" })
3943
- ] }),
3944
- onClose && /* @__PURE__ */ jsx(
4234
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4235
+ /* @__PURE__ */ jsx(
3945
4236
  "button",
3946
4237
  {
3947
- onClick: onClose,
3948
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
3949
- children: /* @__PURE__ */ jsx(CloseIcon, {})
4238
+ onClick: onBack,
4239
+ className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4240
+ children: /* @__PURE__ */ jsx(ChevronLeft, {})
3950
4241
  }
3951
- )
3952
- ] }),
4242
+ ),
4243
+ /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Token Balances" })
4244
+ ] }) }),
3953
4245
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
3954
4246
  /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
3955
4247
  /* @__PURE__ */ jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Network" }) }),
@@ -4019,48 +4311,30 @@ function TokenRow({
4019
4311
  }
4020
4312
  function TokenSelectorView({
4021
4313
  onBack,
4022
- onClose,
4023
4314
  onSelect,
4024
4315
  selectedTokenId
4025
4316
  }) {
4026
- const [tokenSearch, setTokenSearch] = useState("");
4027
4317
  const { enabledTokens, chains } = usePrivanaContext();
4028
4318
  const [selectedChainId, setSelectedChainId] = useState(chains[0]?.id ?? 84532);
4029
4319
  const chainTokens = useMemo(() => {
4030
4320
  return enabledTokens.filter((t) => t.chainId === selectedChainId);
4031
4321
  }, [enabledTokens, selectedChainId]);
4032
- const filteredTokens = useMemo(() => {
4033
- if (!tokenSearch) return chainTokens;
4034
- return chainTokens.filter(
4035
- (t) => t.symbol.toLowerCase().includes(tokenSearch.toLowerCase()) || t.name.toLowerCase().includes(tokenSearch.toLowerCase())
4036
- );
4037
- }, [tokenSearch, chainTokens]);
4038
4322
  const handleTokenSelect = (token) => {
4039
4323
  onSelect(token);
4040
4324
  onBack();
4041
4325
  };
4042
4326
  return /* @__PURE__ */ jsxs(Fragment, { children: [
4043
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4", children: [
4044
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
4045
- /* @__PURE__ */ jsx(
4046
- "button",
4047
- {
4048
- onClick: onBack,
4049
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4050
- children: /* @__PURE__ */ jsx(ChevronLeft, {})
4051
- }
4052
- ),
4053
- /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Select Token" })
4054
- ] }),
4055
- onClose && /* @__PURE__ */ jsx(
4327
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4328
+ /* @__PURE__ */ jsx(
4056
4329
  "button",
4057
4330
  {
4058
- onClick: onClose,
4059
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4060
- children: /* @__PURE__ */ jsx(CloseIcon, {})
4331
+ onClick: onBack,
4332
+ className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4333
+ children: /* @__PURE__ */ jsx(ChevronLeft, {})
4061
4334
  }
4062
- )
4063
- ] }),
4335
+ ),
4336
+ /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Select Token" })
4337
+ ] }) }),
4064
4338
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
4065
4339
  /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
4066
4340
  /* @__PURE__ */ jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Network" }) }),
@@ -4084,23 +4358,8 @@ function TokenSelectorView({
4084
4358
  }) })
4085
4359
  ] }),
4086
4360
  /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-[2] flex-col overflow-hidden rounded-[10px] p-2", children: [
4087
- /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
4088
- /* @__PURE__ */ jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Token" }) }),
4089
- /* @__PURE__ */ jsx("div", { className: "px-3", children: /* @__PURE__ */ jsxs("div", { className: "border-border bg-input flex items-center gap-2.5 rounded-lg border px-3 py-2.5", children: [
4090
- /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
4091
- /* @__PURE__ */ jsx(
4092
- "input",
4093
- {
4094
- type: "text",
4095
- placeholder: "Search",
4096
- value: tokenSearch,
4097
- onChange: (e) => setTokenSearch(e.target.value),
4098
- className: "text-foreground placeholder:text-muted-foreground flex-1 bg-transparent text-sm outline-none"
4099
- }
4100
- )
4101
- ] }) })
4102
- ] }),
4103
- /* @__PURE__ */ jsx("div", { className: "mt-2 flex-1 overflow-y-auto", children: filteredTokens.map((token) => /* @__PURE__ */ jsx(
4361
+ /* @__PURE__ */ jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Token" }) }),
4362
+ /* @__PURE__ */ jsx("div", { className: "mt-2 flex-1 overflow-y-auto", children: chainTokens.map((token) => /* @__PURE__ */ jsx(
4104
4363
  TokenRow,
4105
4364
  {
4106
4365
  token,
@@ -4114,9 +4373,8 @@ function TokenSelectorView({
4114
4373
  ] });
4115
4374
  }
4116
4375
  function ModalBody({
4117
- onClose,
4118
4376
  onViewChange,
4119
- onTransactionPendingChange,
4377
+ onCloseBlockedChange,
4120
4378
  showLockedFunds = true,
4121
4379
  defaultTab = "deposit",
4122
4380
  onDepositSuccess
@@ -4125,65 +4383,78 @@ function ModalBody({
4125
4383
  const [selectedToken, setSelectedToken] = useState(defaultToken);
4126
4384
  const [activeTab, setActiveTab] = useState(defaultTab);
4127
4385
  const [currentView, setCurrentView] = useState("main");
4128
- const [isTransactionPending, setIsTransactionPending] = useState(false);
4386
+ const [isInteractionPending, setIsInteractionPending] = useState(false);
4129
4387
  useEffect(() => {
4130
4388
  if (!selectedToken && defaultToken) {
4131
4389
  setSelectedToken(defaultToken);
4132
4390
  }
4133
4391
  }, [selectedToken, defaultToken]);
4134
- const handleTransactionPendingChange = (isPending) => {
4135
- setIsTransactionPending(isPending);
4136
- onTransactionPendingChange?.(isPending);
4392
+ const handlePendingChange = (isPending) => {
4393
+ setIsInteractionPending(isPending);
4394
+ };
4395
+ const handleCloseBlockedChange = (isBlocked) => {
4396
+ onCloseBlockedChange?.(isBlocked);
4137
4397
  };
4138
4398
  const handleViewChange = (view) => {
4139
- setCurrentView(view);
4140
- onViewChange?.(view);
4399
+ const update = () => {
4400
+ setCurrentView(view);
4401
+ onViewChange?.(view);
4402
+ };
4403
+ if (typeof document !== "undefined" && typeof document.startViewTransition === "function") {
4404
+ document.documentElement.dataset.privanaVtDir = view === "main" ? "back" : "forward";
4405
+ const transition = document.startViewTransition(update);
4406
+ transition.finished.then(() => {
4407
+ delete document.documentElement.dataset.privanaVtDir;
4408
+ });
4409
+ } else {
4410
+ update();
4411
+ }
4141
4412
  };
4142
4413
  const handleTokenSelect = (token) => {
4143
4414
  setSelectedToken(token);
4144
4415
  };
4145
4416
  if (tokensStatus === "loading" || !selectedToken) {
4146
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 pb-4", children: [
4417
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
4147
4418
  /* @__PURE__ */ jsx("div", { className: "bg-secondary h-25 animate-pulse rounded-[10px]" }),
4148
4419
  /* @__PURE__ */ jsx("div", { className: "bg-secondary h-11 animate-pulse rounded-[10px]" }),
4149
4420
  /* @__PURE__ */ jsx("div", { className: "bg-secondary h-50 animate-pulse rounded-[10px]" })
4150
4421
  ] });
4151
4422
  }
4152
4423
  if (currentView === "locked-funds") {
4153
- return /* @__PURE__ */ jsx(LockedFundsView, { onBack: () => handleViewChange("main"), onClose });
4424
+ return /* @__PURE__ */ jsx(LockedFundsView, { onBack: () => handleViewChange("main") });
4154
4425
  }
4155
4426
  if (currentView === "balance-details") {
4156
- return /* @__PURE__ */ jsx(BalanceDetailsView, { onBack: () => handleViewChange("main"), onClose });
4427
+ return /* @__PURE__ */ jsx(BalanceDetailsView, { onBack: () => handleViewChange("main") });
4157
4428
  }
4158
4429
  if (currentView === "select-token") {
4159
4430
  return /* @__PURE__ */ jsx(
4160
4431
  TokenSelectorView,
4161
4432
  {
4162
4433
  onBack: () => handleViewChange("main"),
4163
- onClose,
4164
4434
  onSelect: handleTokenSelect,
4165
4435
  selectedTokenId: selectedToken.id
4166
4436
  }
4167
4437
  );
4168
4438
  }
4169
4439
  return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 pb-4", children: [
4170
- /* @__PURE__ */ jsx("div", { className: cn(isTransactionPending && "pointer-events-none"), children: /* @__PURE__ */ jsx(
4440
+ /* @__PURE__ */ jsx("div", { className: cn(isInteractionPending && "pointer-events-none"), children: /* @__PURE__ */ jsx(
4171
4441
  BalanceCards,
4172
4442
  {
4173
4443
  selectedToken,
4174
4444
  onLockedFundsClick: () => handleViewChange("locked-funds"),
4175
4445
  onBalanceClick: () => handleViewChange("balance-details"),
4176
4446
  showLockedFunds,
4177
- disabled: isTransactionPending
4447
+ disabled: isInteractionPending
4178
4448
  }
4179
4449
  ) }),
4180
- /* @__PURE__ */ jsx("div", { className: cn(isTransactionPending && "pointer-events-none"), children: /* @__PURE__ */ jsx(Tabs, { activeTab, onTabChange: setActiveTab, disabled: isTransactionPending }) }),
4450
+ /* @__PURE__ */ jsx("div", { className: cn(isInteractionPending && "pointer-events-none"), children: /* @__PURE__ */ jsx(Tabs, { activeTab, onTabChange: setActiveTab, disabled: isInteractionPending }) }),
4181
4451
  /* @__PURE__ */ jsx("div", { className: "bg-muted rounded-[10px] p-5", children: activeTab === "deposit" ? /* @__PURE__ */ jsx(
4182
4452
  DepositForm,
4183
4453
  {
4184
4454
  selectedToken,
4185
4455
  onTokenSelect: () => handleViewChange("select-token"),
4186
- onPendingChange: handleTransactionPendingChange,
4456
+ onPendingChange: handlePendingChange,
4457
+ onUnsafeToCloseChange: handleCloseBlockedChange,
4187
4458
  onSuccess: onDepositSuccess
4188
4459
  }
4189
4460
  ) : /* @__PURE__ */ jsx(
@@ -4191,7 +4462,8 @@ function ModalBody({
4191
4462
  {
4192
4463
  selectedToken,
4193
4464
  onTokenSelect: () => handleViewChange("select-token"),
4194
- onPendingChange: handleTransactionPendingChange
4465
+ onPendingChange: handlePendingChange,
4466
+ onUnsafeToCloseChange: handleCloseBlockedChange
4195
4467
  }
4196
4468
  ) })
4197
4469
  ] }) });
@@ -4203,55 +4475,84 @@ function PrivanaModal({
4203
4475
  defaultTab,
4204
4476
  onDepositSuccess
4205
4477
  }) {
4206
- const [isTransactionPending, setIsTransactionPending] = useState(false);
4478
+ const [currentView, setCurrentView] = useState("main");
4207
4479
  const titleId = useId();
4208
4480
  const descId = useId();
4209
- const handleOpenChange = (isOpen) => {
4210
- if (!isOpen && isTransactionPending) {
4211
- return;
4212
- }
4213
- if (!isOpen) {
4214
- onClose();
4215
- }
4481
+ const [isCloseBlocked, setIsCloseBlocked] = useState(false);
4482
+ const handleClose = () => {
4483
+ if (!isCloseBlocked) onClose();
4216
4484
  };
4217
- return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs(
4218
- DialogContent,
4485
+ return /* @__PURE__ */ jsx(
4486
+ Dialog,
4219
4487
  {
4220
- "data-privana": true,
4221
- showCloseButton: false,
4222
- className: "bg-card flex w-[560px] max-w-[95vw] flex-col gap-2 overflow-hidden rounded-2xl border-0 p-2",
4223
- overlayClassName: isTransactionPending ? "cursor-not-allowed" : void 0,
4224
- "aria-labelledby": titleId,
4225
- "aria-describedby": descId,
4226
- children: [
4227
- /* @__PURE__ */ jsxs(DialogHeader, { children: [
4228
- /* @__PURE__ */ jsx(DialogTitle, { id: titleId, className: "sr-only", children: "Privana" }),
4229
- /* @__PURE__ */ jsx(DialogDescription, { id: descId, className: "sr-only", children: "Deposit or withdraw tokens from your Flexvault" }),
4230
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-5 py-4", children: [
4231
- /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Privana" }),
4232
- onClose && /* @__PURE__ */ jsx(
4488
+ open,
4489
+ onOpenChange: (isOpen) => {
4490
+ if (!isOpen) handleClose();
4491
+ },
4492
+ children: /* @__PURE__ */ jsxs(
4493
+ DialogContent,
4494
+ {
4495
+ "data-privana": true,
4496
+ "data-view": currentView,
4497
+ showCloseButton: false,
4498
+ onInteractOutside: isCloseBlocked ? (e) => e.preventDefault() : void 0,
4499
+ onEscapeKeyDown: isCloseBlocked ? (e) => e.preventDefault() : void 0,
4500
+ className: "bg-card flex h-[596px] max-h-[95dvh] w-[560px] max-w-[95vw] flex-col gap-2 overflow-hidden rounded-2xl border-0 p-2 pb-[2.75rem]",
4501
+ "aria-labelledby": titleId,
4502
+ "aria-describedby": descId,
4503
+ children: [
4504
+ /* @__PURE__ */ jsx(
4233
4505
  "button",
4234
4506
  {
4235
- onClick: onClose,
4236
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4507
+ "data-privana-close": true,
4508
+ onClick: handleClose,
4509
+ disabled: isCloseBlocked,
4510
+ "aria-label": "Close",
4511
+ className: cn(
4512
+ "absolute top-6 right-5 z-20 flex h-6 w-6 items-center justify-center transition-colors",
4513
+ isCloseBlocked ? "text-muted-foreground/40 cursor-not-allowed" : "text-muted-foreground hover:text-foreground cursor-pointer"
4514
+ ),
4237
4515
  children: /* @__PURE__ */ jsx(CloseIcon, {})
4238
4516
  }
4517
+ ),
4518
+ /* @__PURE__ */ jsxs(
4519
+ "div",
4520
+ {
4521
+ "data-privana-content": true,
4522
+ "data-view": currentView,
4523
+ className: "flex min-h-0 flex-1 flex-col gap-2",
4524
+ children: [
4525
+ /* @__PURE__ */ jsx(DialogTitle, { id: titleId, className: "sr-only", children: "Privana" }),
4526
+ /* @__PURE__ */ jsx(DialogDescription, { id: descId, className: "sr-only", children: "Deposit or withdraw tokens from your Privana" }),
4527
+ currentView === "main" && /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx("div", { className: "flex items-center px-5 py-4", children: /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Privana" }) }) }),
4528
+ /* @__PURE__ */ jsx(
4529
+ ModalBody,
4530
+ {
4531
+ onCloseBlockedChange: setIsCloseBlocked,
4532
+ onViewChange: setCurrentView,
4533
+ showLockedFunds,
4534
+ defaultTab,
4535
+ onDepositSuccess
4536
+ }
4537
+ )
4538
+ ]
4539
+ }
4540
+ ),
4541
+ /* @__PURE__ */ jsx(
4542
+ "a",
4543
+ {
4544
+ "data-privana-footer": true,
4545
+ href: "https://privana.finance",
4546
+ target: "_blank",
4547
+ rel: "noopener noreferrer",
4548
+ "aria-label": "Powered by Privana"
4549
+ }
4239
4550
  )
4240
- ] })
4241
- ] }),
4242
- /* @__PURE__ */ jsx(
4243
- ModalBody,
4244
- {
4245
- onClose: isTransactionPending ? void 0 : onClose,
4246
- onTransactionPendingChange: setIsTransactionPending,
4247
- showLockedFunds,
4248
- defaultTab,
4249
- onDepositSuccess
4250
- }
4251
- )
4252
- ]
4551
+ ]
4552
+ }
4553
+ )
4253
4554
  }
4254
- ) });
4555
+ );
4255
4556
  }
4256
4557
  function PrivanaInlineModal({
4257
4558
  className,
@@ -4335,6 +4636,6 @@ function Skeleton({ className, ...props }) {
4335
4636
  );
4336
4637
  }
4337
4638
 
4338
- export { AccountingApiError, Button, HOSTED_AUTH_CLOCK_SKEW_MS, HostedAuthError, HostedAuthRequiredError, HostedAuthStateMismatchError, HttpClient, LOCK_TYPES, MODIFY_LOCK_TYPES, NETWORK_CONFIG, NetworkError, PrivanaButton, PrivanaClient, PrivanaInlineModal, PrivanaModal, PrivanaProvider, SUPPORTED_CHAINS, Skeleton, TRANSFER_LOCKED_TYPES, TRANSFER_TYPES, ValidationError, WITHDRAW_FROM_LOCK_TYPES, WITHDRAW_TYPES, applyRefreshResponse, buildHostedAuthSession, buttonVariants, clearHostedAuthPendingTransaction, createDomain, createHostedAuthPendingStorageKey, createHostedAuthState, createHostedAuthStorageKey, createLockExpiry, createPkceChallenge, createPkceVerifier, getAccountingContract, getApiUrl, getChainById, getChainIcon, getChainId, getExplorerAddressUrl, getTokenIcon, isHostedAuthRefreshActive, isHostedAuthSessionActive, normalizeAddress, normalizeHex, parseHostedAuthCallback, persistHostedAuthPendingTransaction, readHostedAuthPendingTransaction, readStoredHostedAuthSession, signLockMessage, signModifyLockMessage, signTransferLockedMessage, signTransferMessage, signWithdrawFromLockMessage, signWithdrawMessage, stripHostedAuthCallbackParams, syncHostedAuthSessionToClient, useBalance, useBatchBalances, useDeposit, useExpiredLocks, useHistory, useHostedRedirectAuth, useLockFunds, useLockedFunds, useModifyLock, usePendingWithdrawals, usePrivanaClient, usePrivanaContext, useSafeAccount, useSafePrivanaContext, useTokenInfo, useTokenList, useTotalLockedBalance, useTransfer, useUnlockFunds, useWithdraw };
4639
+ export { AccountingApiError, Button, HOSTED_AUTH_CLOCK_SKEW_MS, HostedAuthError, HostedAuthRequiredError, HostedAuthStateMismatchError, HttpClient, LOCK_TYPES, MODIFY_LOCK_TYPES, NETWORK_CONFIG, NetworkError, PrivanaButton, PrivanaClient, PrivanaInlineModal, PrivanaModal, PrivanaProvider, SUPPORTED_CHAINS, SiweAuthProvider, Skeleton, TRANSFER_LOCKED_TYPES, TRANSFER_TYPES, ValidationError, WITHDRAW_FROM_LOCK_TYPES, WITHDRAW_TYPES, applyRefreshResponse, buildHostedAuthSession, buttonVariants, clearHostedAuthPendingTransaction, createDomain, createHostedAuthPendingStorageKey, createHostedAuthState, createHostedAuthStorageKey, createLockExpiry, createPkceChallenge, createPkceVerifier, getAccountingContract, getApiUrl, getChainById, getChainIcon, getChainId, getExplorerAddressUrl, getTokenIcon, isHostedAuthRefreshActive, isHostedAuthSessionActive, normalizeAddress, normalizeHex, parseHostedAuthCallback, persistHostedAuthPendingTransaction, readHostedAuthPendingTransaction, readStoredHostedAuthSession, signLockMessage, signModifyLockMessage, signTransferLockedMessage, signTransferMessage, signWithdrawFromLockMessage, signWithdrawMessage, stripHostedAuthCallbackParams, syncHostedAuthSessionToClient, useBalance, useBatchBalances, useDeposit, useExpiredLocks, useHistory, useHostedRedirectAuth, useLockFunds, useLockedFunds, useModifyLock, usePendingWithdrawals, usePrivanaClient, usePrivanaContext, useSafeAccount, useSafePrivanaContext, useSiweAuth, useTokenInfo, useTokenList, useTotalLockedBalance, useTransfer, useUnlockFunds, useWithdraw };
4339
4640
  //# sourceMappingURL=index.js.map
4340
4641
  //# sourceMappingURL=index.js.map