@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.cjs CHANGED
@@ -3,13 +3,13 @@
3
3
 
4
4
  var react = require('react');
5
5
  var viem = require('viem');
6
+ var siwe = require('viem/siwe');
7
+ var wagmi = require('wagmi');
8
+ var actions = require('wagmi/actions');
6
9
  var jsxRuntime = require('react/jsx-runtime');
7
10
  var reactQuery = require('@tanstack/react-query');
8
11
  var clsx = require('clsx');
9
12
  var tailwindMerge = require('tailwind-merge');
10
- var siwe = require('viem/siwe');
11
- var wagmi = require('wagmi');
12
- var actions = require('wagmi/actions');
13
13
  var actions$1 = require('viem/actions');
14
14
  var reactSlot = require('@radix-ui/react-slot');
15
15
  var classVarianceAuthority = require('class-variance-authority');
@@ -51,7 +51,7 @@ var config_default = {
51
51
  chainId: 23295,
52
52
  name: "Sapphire Testnet",
53
53
  accountingContract: "0xaF8e5de153A584528B57DD4B9B0195956BBDF571",
54
- apiUrl: "https://flexvaults-staging.rofl.build"
54
+ apiUrl: "https://testnet.privana.finance"
55
55
  },
56
56
  mainnet: {
57
57
  chainId: 23294,
@@ -791,6 +791,235 @@ async function signWithdrawFromLockMessage({
791
791
  });
792
792
  return signature;
793
793
  }
794
+ var defaultResult = {
795
+ address: void 0,
796
+ isConnected: false,
797
+ status: "disconnected"
798
+ };
799
+ function useSafeAccount() {
800
+ const context = react.useContext(wagmi.WagmiContext);
801
+ const cacheRef = react.useRef(defaultResult);
802
+ const subscribe = react.useCallback(
803
+ (onChange) => {
804
+ if (!context) return () => {
805
+ };
806
+ return actions.watchAccount(context, { onChange });
807
+ },
808
+ [context]
809
+ );
810
+ const getSnapshot = react.useCallback(() => {
811
+ if (!context) return defaultResult;
812
+ const account = actions.getAccount(context);
813
+ if (cacheRef.current.address !== account.address || cacheRef.current.isConnected !== account.isConnected || cacheRef.current.status !== account.status) {
814
+ cacheRef.current = {
815
+ address: account.address,
816
+ isConnected: account.isConnected,
817
+ status: account.status
818
+ };
819
+ }
820
+ return cacheRef.current;
821
+ }, [context]);
822
+ const getServerSnapshot = react.useCallback(() => defaultResult, []);
823
+ return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
824
+ }
825
+
826
+ // src/sdk/hooks/private-read-token-store.ts
827
+ var AUTH_CLOCK_SKEW_MS = 3e4;
828
+ var cache = /* @__PURE__ */ new Map();
829
+ function createScopeKey(apiUrl, chainId, address) {
830
+ return `${apiUrl.replace(/\/$/, "")}:${chainId}:${address.toLowerCase()}`;
831
+ }
832
+ function getCachedPrivateReadToken(scopeKey) {
833
+ const cached = cache.get(scopeKey);
834
+ if (!cached) return null;
835
+ if (cached.expiresAt <= Date.now() + AUTH_CLOCK_SKEW_MS) {
836
+ cache.delete(scopeKey);
837
+ return null;
838
+ }
839
+ return cached.token;
840
+ }
841
+ function setCachedPrivateReadToken(scopeKey, token, expiresAt) {
842
+ cache.set(scopeKey, { token, expiresAt });
843
+ }
844
+ function deleteCachedPrivateReadToken(scopeKey) {
845
+ cache.delete(scopeKey);
846
+ }
847
+ var DEFAULT_SIWE_VALIDITY_MS = 24 * 60 * 60 * 1e3;
848
+ var DEFAULT_STATEMENT = "Sign in to access your private account data.";
849
+ var AUTH_REFRESH_SKEW_MS = 3e4;
850
+ var SiweAuthContext = react.createContext(null);
851
+ function SiweAuthProvider({
852
+ children,
853
+ client,
854
+ networkConfig,
855
+ autoLogin = true,
856
+ statement
857
+ }) {
858
+ const wagmiContext = react.useContext(wagmi.WagmiContext);
859
+ const { address, isConnected, status } = useSafeAccount();
860
+ const [session, setSession] = react.useState(null);
861
+ const [tokens, setTokens] = react.useState(null);
862
+ const [isLoading, setIsLoading] = react.useState(false);
863
+ const [error, setError] = react.useState(null);
864
+ const [accessTokenExpiresAt, setAccessTokenExpiresAt] = react.useState(null);
865
+ const loginInFlight = react.useRef(false);
866
+ const autoAttemptedAddress = react.useRef(null);
867
+ const refreshInFlight = react.useRef(false);
868
+ const refreshDataRef = react.useRef(null);
869
+ const clearSession = react.useCallback(() => {
870
+ refreshDataRef.current = null;
871
+ setAccessTokenExpiresAt(null);
872
+ client.clearPrivateReadToken();
873
+ client.clearBearerToken();
874
+ setSession(null);
875
+ setTokens(null);
876
+ setError(null);
877
+ autoAttemptedAddress.current = null;
878
+ }, [client]);
879
+ const logout = react.useCallback(async () => {
880
+ setError(null);
881
+ const refreshToken = refreshDataRef.current?.refreshToken;
882
+ try {
883
+ if (refreshToken) {
884
+ await client.logoutJwtSession({ refresh_token: refreshToken });
885
+ }
886
+ } finally {
887
+ clearSession();
888
+ autoAttemptedAddress.current = address ?? null;
889
+ }
890
+ }, [clearSession, client, address]);
891
+ const login = react.useCallback(async () => {
892
+ if (!wagmiContext) throw new Error("WagmiProvider is required for SIWE auth");
893
+ if (!address) throw new Error("No wallet connected");
894
+ if (loginInFlight.current) return;
895
+ loginInFlight.current = true;
896
+ setIsLoading(true);
897
+ setError(null);
898
+ try {
899
+ const walletClient = await actions.getWalletClient(wagmiContext);
900
+ if (!walletClient) throw new Error("No wallet client available");
901
+ const [{ domain }, nonceRes] = await Promise.all([
902
+ client.getSiweDomain(),
903
+ client.getSiweNonce(address)
904
+ ]);
905
+ const issuedAt = /* @__PURE__ */ new Date();
906
+ const expirationTime = new Date(issuedAt.getTime() + DEFAULT_SIWE_VALIDITY_MS);
907
+ const uri = typeof window !== "undefined" && window.location.origin ? window.location.origin : networkConfig.apiUrl;
908
+ const message = siwe.createSiweMessage({
909
+ address,
910
+ chainId: networkConfig.chainId,
911
+ domain,
912
+ uri,
913
+ version: "1",
914
+ nonce: nonceRes.nonce,
915
+ statement: statement ?? DEFAULT_STATEMENT,
916
+ issuedAt,
917
+ expirationTime
918
+ });
919
+ const signature = await walletClient.signMessage({
920
+ account: walletClient.account ?? address,
921
+ message
922
+ });
923
+ const res = await client.loginWithSiwe({ siwe_message: message, signature });
924
+ const loggedInAt = Date.now();
925
+ client.setPrivateReadToken(res.siwe_token);
926
+ client.setBearerToken(res.jwt_access_token);
927
+ refreshDataRef.current = {
928
+ refreshToken: res.jwt_refresh_token,
929
+ refreshExpiresAt: loggedInAt + res.jwt_refresh_expires_in * 1e3
930
+ };
931
+ setCachedPrivateReadToken(
932
+ createScopeKey(networkConfig.apiUrl, networkConfig.chainId, address),
933
+ res.siwe_token,
934
+ expirationTime.getTime()
935
+ );
936
+ setSession({ address: res.address });
937
+ setTokens({
938
+ siwe_token: res.siwe_token,
939
+ jwt_access_token: res.jwt_access_token,
940
+ jwt_refresh_token: res.jwt_refresh_token,
941
+ address: res.address
942
+ });
943
+ setAccessTokenExpiresAt(loggedInAt + res.jwt_expires_in * 1e3);
944
+ } catch (err) {
945
+ setError(err instanceof Error ? err : new Error("Sign-in failed"));
946
+ throw err;
947
+ } finally {
948
+ setIsLoading(false);
949
+ loginInFlight.current = false;
950
+ }
951
+ }, [wagmiContext, address, client, networkConfig.chainId, networkConfig.apiUrl, statement]);
952
+ const refreshAccessToken = react.useCallback(async () => {
953
+ const data = refreshDataRef.current;
954
+ if (!data || refreshInFlight.current) return;
955
+ if (Date.now() >= data.refreshExpiresAt - AUTH_REFRESH_SKEW_MS) {
956
+ clearSession();
957
+ return;
958
+ }
959
+ refreshInFlight.current = true;
960
+ try {
961
+ const res = await client.refreshJwtSession({ refresh_token: data.refreshToken });
962
+ const refreshedAt = Date.now();
963
+ client.setBearerToken(res.token);
964
+ refreshDataRef.current = {
965
+ refreshToken: res.refresh_token,
966
+ refreshExpiresAt: refreshedAt + res.refresh_expires_in * 1e3
967
+ };
968
+ setTokens(
969
+ (prev) => prev ? { ...prev, jwt_access_token: res.token, jwt_refresh_token: res.refresh_token } : prev
970
+ );
971
+ setAccessTokenExpiresAt(refreshedAt + res.expires_in * 1e3);
972
+ } catch {
973
+ clearSession();
974
+ } finally {
975
+ refreshInFlight.current = false;
976
+ }
977
+ }, [client, clearSession]);
978
+ react.useEffect(() => {
979
+ if (accessTokenExpiresAt == null) return;
980
+ const delay = Math.max(accessTokenExpiresAt - AUTH_REFRESH_SKEW_MS - Date.now(), 0);
981
+ const timer = setTimeout(() => {
982
+ void refreshAccessToken();
983
+ }, delay);
984
+ return () => clearTimeout(timer);
985
+ }, [accessTokenExpiresAt, refreshAccessToken]);
986
+ react.useEffect(() => {
987
+ if (!autoLogin) return;
988
+ if (status === "connecting" || status === "reconnecting") return;
989
+ if (!isConnected && session) {
990
+ clearSession();
991
+ return;
992
+ }
993
+ if (isConnected && address && session && address.toLowerCase() !== session.address.toLowerCase()) {
994
+ clearSession();
995
+ return;
996
+ }
997
+ if (isConnected && address && !session && !isLoading && autoAttemptedAddress.current !== address) {
998
+ autoAttemptedAddress.current = address;
999
+ void login().catch(() => {
1000
+ });
1001
+ }
1002
+ }, [autoLogin, status, isConnected, address, session, isLoading, login, clearSession]);
1003
+ const value = react.useMemo(
1004
+ () => ({
1005
+ isAuthenticated: !!session,
1006
+ isLoading,
1007
+ error,
1008
+ session,
1009
+ accessToken: tokens?.jwt_access_token,
1010
+ tokens,
1011
+ login,
1012
+ logout
1013
+ }),
1014
+ [session, isLoading, error, tokens, login, logout]
1015
+ );
1016
+ return /* @__PURE__ */ jsxRuntime.jsx(SiweAuthContext.Provider, { value, children });
1017
+ }
1018
+ function useSiweAuth() {
1019
+ const ctx = react.useContext(SiweAuthContext);
1020
+ if (!ctx) throw new Error("useSiweAuth must be used within SiweAuthProvider");
1021
+ return ctx;
1022
+ }
794
1023
  var PrivanaContext = react.createContext(null);
795
1024
  function readStoredHostedAuthSession(storage, hostedAuthStorageKey, now = Date.now()) {
796
1025
  const raw = storage.getItem(hostedAuthStorageKey);
@@ -831,8 +1060,14 @@ function PrivanaProvider({
831
1060
  chains,
832
1061
  pollingInterval = 1e4,
833
1062
  serviceAddress,
834
- hostedAuth
1063
+ hostedAuth,
1064
+ siweAuth
835
1065
  }) {
1066
+ if (hostedAuth && siweAuth) {
1067
+ throw new Error(
1068
+ "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."
1069
+ );
1070
+ }
836
1071
  const networkConfig = react.useMemo(() => {
837
1072
  const config = {
838
1073
  ...DEFAULT_NETWORK_CONFIG,
@@ -1067,7 +1302,16 @@ function PrivanaProvider({
1067
1302
  refreshHostedAuthSession
1068
1303
  ]
1069
1304
  );
1070
- return /* @__PURE__ */ jsxRuntime.jsx(PrivanaContext.Provider, { value, children });
1305
+ return /* @__PURE__ */ jsxRuntime.jsx(PrivanaContext.Provider, { value, children: siweAuth ? /* @__PURE__ */ jsxRuntime.jsx(
1306
+ SiweAuthProvider,
1307
+ {
1308
+ client,
1309
+ networkConfig,
1310
+ autoLogin: siweAuth.autoLogin,
1311
+ statement: siweAuth.statement,
1312
+ children
1313
+ }
1314
+ ) : children });
1071
1315
  }
1072
1316
  function usePrivanaContext() {
1073
1317
  const context = react.useContext(PrivanaContext);
@@ -1327,40 +1571,10 @@ function formatTimeRemaining(expiryTimestamp) {
1327
1571
  }
1328
1572
  return `${minutes}m left`;
1329
1573
  }
1330
- var defaultResult = {
1331
- address: void 0,
1332
- isConnected: false
1333
- };
1334
- function useSafeAccount() {
1335
- const context = react.useContext(wagmi.WagmiContext);
1336
- const cacheRef = react.useRef(defaultResult);
1337
- const subscribe = react.useCallback(
1338
- (onChange) => {
1339
- if (!context) return () => {
1340
- };
1341
- return actions.watchAccount(context, { onChange });
1342
- },
1343
- [context]
1344
- );
1345
- const getSnapshot = react.useCallback(() => {
1346
- if (!context) return defaultResult;
1347
- const account = actions.getAccount(context);
1348
- if (cacheRef.current.address !== account.address || cacheRef.current.isConnected !== account.isConnected) {
1349
- cacheRef.current = { address: account.address, isConnected: account.isConnected };
1350
- }
1351
- return cacheRef.current;
1352
- }, [context]);
1353
- const getServerSnapshot = react.useCallback(() => defaultResult, []);
1354
- return react.useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot);
1355
- }
1356
-
1357
- // src/sdk/hooks/use-private-read-request.ts
1358
- var AUTH_CLOCK_SKEW_MS = 3e4;
1359
1574
  var INITIAL_AUTH_BACKOFF_MS = 5e3;
1360
1575
  var MAX_AUTH_BACKOFF_MS = 6e4;
1361
1576
  var DEFAULT_SIWE_AUTH_VALIDITY_MS = 24 * 60 * 60 * 1e3;
1362
1577
  var PRIVATE_READ_STATEMENT = "Sign in to Privana to access private account data.";
1363
- var privateReadTokenCache = /* @__PURE__ */ new Map();
1364
1578
  var privateReadFailureCache = /* @__PURE__ */ new Map();
1365
1579
  var privateReadInflight = /* @__PURE__ */ new Map();
1366
1580
  async function executeHostedAuthPrivateReadRequest({
@@ -1394,20 +1608,8 @@ async function executeHostedAuthPrivateReadRequest({
1394
1608
  return request();
1395
1609
  }
1396
1610
  }
1397
- function createScopeKey(apiUrl, deploymentChainId, address) {
1398
- return `${apiUrl.replace(/\/$/, "")}:${deploymentChainId}:${address.toLowerCase()}`;
1399
- }
1400
- function getCachedPrivateReadToken(scopeKey) {
1401
- const cached = privateReadTokenCache.get(scopeKey);
1402
- if (!cached) return null;
1403
- if (cached.expiresAt <= Date.now() + AUTH_CLOCK_SKEW_MS) {
1404
- privateReadTokenCache.delete(scopeKey);
1405
- return null;
1406
- }
1407
- return cached.token;
1408
- }
1409
1611
  function clearPrivateReadScope(scopeKey, client) {
1410
- privateReadTokenCache.delete(scopeKey);
1612
+ deleteCachedPrivateReadToken(scopeKey);
1411
1613
  privateReadFailureCache.delete(scopeKey);
1412
1614
  client.clearPrivateReadToken();
1413
1615
  }
@@ -1506,10 +1708,7 @@ function usePrivateReadRequest() {
1506
1708
  siwe_message: message,
1507
1709
  signature
1508
1710
  });
1509
- privateReadTokenCache.set(scopeKey, {
1510
- token: login.siwe_token,
1511
- expiresAt: expirationTime.getTime()
1512
- });
1711
+ setCachedPrivateReadToken(scopeKey, login.siwe_token, expirationTime.getTime());
1513
1712
  privateReadFailureCache.delete(scopeKey);
1514
1713
  client.setPrivateReadToken(login.siwe_token);
1515
1714
  return login.siwe_token;
@@ -1630,6 +1829,12 @@ function getAction(client, actionFn, name) {
1630
1829
  function getChainId2(config) {
1631
1830
  return config.state.chainId;
1632
1831
  }
1832
+ async function getTransactionReceipt(config, parameters) {
1833
+ const { chainId, ...rest } = parameters;
1834
+ const client = config.getClient({ chainId });
1835
+ const action = getAction(client, actions$1.getTransactionReceipt, "getTransactionReceipt");
1836
+ return action(rest);
1837
+ }
1633
1838
  async function waitForTransactionReceipt(config, parameters) {
1634
1839
  const { chainId, timeout = 0, ...rest } = parameters;
1635
1840
  const client = config.getClient({ chainId });
@@ -1710,6 +1915,36 @@ function useEnsureCorrectChain() {
1710
1915
  }
1711
1916
 
1712
1917
  // src/sdk/hooks/use-deposit.ts
1918
+ var STALE_MS = 30 * 60 * 1e3;
1919
+ function storageKey(address) {
1920
+ return `privana:pending-deposit:${address.toLowerCase()}`;
1921
+ }
1922
+ function savePendingDeposit(address, data) {
1923
+ try {
1924
+ sessionStorage.setItem(storageKey(address), JSON.stringify(data));
1925
+ } catch {
1926
+ }
1927
+ }
1928
+ function loadPendingDeposit(address) {
1929
+ try {
1930
+ const raw = sessionStorage.getItem(storageKey(address));
1931
+ if (!raw) return null;
1932
+ const data = JSON.parse(raw);
1933
+ if (Date.now() - data.savedAt > STALE_MS) {
1934
+ clearPendingDeposit(address);
1935
+ return null;
1936
+ }
1937
+ return data;
1938
+ } catch {
1939
+ return null;
1940
+ }
1941
+ }
1942
+ function clearPendingDeposit(address) {
1943
+ try {
1944
+ sessionStorage.removeItem(storageKey(address));
1945
+ } catch {
1946
+ }
1947
+ }
1713
1948
  function useDeposit(options = {}) {
1714
1949
  const { address } = wagmi.useAccount();
1715
1950
  const { client, enabledTokens, getChainById: getChainById2 } = usePrivanaContext();
@@ -1777,13 +2012,19 @@ function useDeposit(options = {}) {
1777
2012
  const isSendingTx = isWritingContract || isSendingNative;
1778
2013
  const sendError = writeError ?? sendNativeError;
1779
2014
  const { ensureCorrectChain } = useEnsureCorrectChain();
2015
+ const invalidateGeneration = react.useCallback(() => {
2016
+ generationRef.current++;
2017
+ }, []);
1780
2018
  react.useEffect(() => {
1781
2019
  return () => {
2020
+ invalidateGeneration();
1782
2021
  if (pollIntervalRef.current) {
1783
2022
  clearTimeout(pollIntervalRef.current);
2023
+ pollIntervalRef.current = null;
1784
2024
  }
1785
2025
  };
1786
- }, []);
2026
+ }, [invalidateGeneration]);
2027
+ const resumedAddressRef = react.useRef(void 0);
1787
2028
  const stopPolling = react.useCallback(() => {
1788
2029
  if (pollIntervalRef.current) {
1789
2030
  clearTimeout(pollIntervalRef.current);
@@ -1793,6 +2034,7 @@ function useDeposit(options = {}) {
1793
2034
  const reset = react.useCallback(() => {
1794
2035
  generationRef.current++;
1795
2036
  stopPolling();
2037
+ if (address) clearPendingDeposit(address);
1796
2038
  verificationContextRef.current = null;
1797
2039
  setDepositAddress(null);
1798
2040
  setTxHash(void 0);
@@ -1805,7 +2047,7 @@ function useDeposit(options = {}) {
1805
2047
  addressMutation.reset();
1806
2048
  resetWriteContract();
1807
2049
  resetSendTransaction();
1808
- }, [addressMutation, resetWriteContract, resetSendTransaction, stopPolling]);
2050
+ }, [address, addressMutation, resetWriteContract, resetSendTransaction, stopPolling]);
1809
2051
  const runVerification = react.useCallback(
1810
2052
  async (ctx, generation) => {
1811
2053
  const isStale = () => generation !== generationRef.current;
@@ -1833,6 +2075,7 @@ function useDeposit(options = {}) {
1833
2075
  if (triggerResult.status === "credited") {
1834
2076
  setIsWaitingForProcessing(false);
1835
2077
  verificationContextRef.current = null;
2078
+ if (address) clearPendingDeposit(address);
1836
2079
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
1837
2080
  queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
1838
2081
  onCreditedRef.current?.(hash, triggerResult);
@@ -1866,6 +2109,7 @@ function useDeposit(options = {}) {
1866
2109
  stopPolling();
1867
2110
  setIsWaitingForProcessing(false);
1868
2111
  verificationContextRef.current = null;
2112
+ if (address) clearPendingDeposit(address);
1869
2113
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
1870
2114
  queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
1871
2115
  onCreditedRef.current?.(hash, result);
@@ -1905,7 +2149,7 @@ function useDeposit(options = {}) {
1905
2149
  );
1906
2150
  }
1907
2151
  },
1908
- [client, executePrivateRead, pollInterval, pollTimeout, queryClient, stopPolling]
2152
+ [address, client, executePrivateRead, pollInterval, pollTimeout, queryClient, stopPolling]
1909
2153
  );
1910
2154
  const retryVerification = react.useCallback(async () => {
1911
2155
  const ctx = verificationContextRef.current;
@@ -1917,6 +2161,55 @@ function useDeposit(options = {}) {
1917
2161
  const generation = generationRef.current;
1918
2162
  await runVerification(ctx, generation);
1919
2163
  }, [runVerification, stopPolling]);
2164
+ react.useEffect(() => {
2165
+ if (!address || resumedAddressRef.current === address) return;
2166
+ const persisted = loadPendingDeposit(address);
2167
+ if (!persisted) return;
2168
+ resumedAddressRef.current = address;
2169
+ const hash = persisted.txHash;
2170
+ setTxHash(hash);
2171
+ setDepositAddress(persisted.depositAddress);
2172
+ setIsWaitingForConfirmation(true);
2173
+ const ctx = {
2174
+ hash,
2175
+ chainId: persisted.chainId,
2176
+ amount: BigInt(persisted.amount)
2177
+ };
2178
+ verificationContextRef.current = ctx;
2179
+ const generation = ++generationRef.current;
2180
+ const isStale = () => generation !== generationRef.current;
2181
+ (async () => {
2182
+ try {
2183
+ let confirmed = false;
2184
+ try {
2185
+ await getTransactionReceipt(config, { hash, chainId: persisted.chainId });
2186
+ confirmed = true;
2187
+ } catch {
2188
+ }
2189
+ if (!confirmed) {
2190
+ await waitForTransactionReceipt(config, {
2191
+ hash,
2192
+ chainId: persisted.chainId,
2193
+ confirmations
2194
+ });
2195
+ }
2196
+ if (isStale()) return;
2197
+ setIsWaitingForConfirmation(false);
2198
+ onDepositSuccessRef.current?.(hash);
2199
+ queryClient.invalidateQueries({ queryKey: ["readContract"] });
2200
+ await runVerification(ctx, generation);
2201
+ } catch (err) {
2202
+ if (isStale()) return;
2203
+ setIsWaitingForConfirmation(false);
2204
+ stopPolling();
2205
+ const error2 = err instanceof Error ? err : new Error("Deposit verification failed");
2206
+ setIsWaitingForProcessing(false);
2207
+ setDepositError(error2);
2208
+ setVerificationFailed(true);
2209
+ onErrorRef.current?.(error2);
2210
+ }
2211
+ })();
2212
+ }, [address, config, confirmations, queryClient, runVerification, stopPolling]);
1920
2213
  const deposit = react.useCallback(
1921
2214
  async (params) => {
1922
2215
  if (verificationContextRef.current) {
@@ -1973,10 +2266,21 @@ function useDeposit(options = {}) {
1973
2266
  amount: params.amount
1974
2267
  };
1975
2268
  verificationContextRef.current = ctx;
2269
+ savePendingDeposit(address, {
2270
+ txHash: hash,
2271
+ chainId: sourceChain.id,
2272
+ amount: params.amount.toString(),
2273
+ depositAddress: addrResponse,
2274
+ savedAt: Date.now()
2275
+ });
1976
2276
  try {
1977
2277
  setIsWaitingForConfirmation(true);
1978
2278
  try {
1979
- await waitForTransactionReceipt(config, { hash, confirmations });
2279
+ await waitForTransactionReceipt(config, {
2280
+ hash,
2281
+ chainId: sourceChain.id,
2282
+ confirmations
2283
+ });
1980
2284
  } finally {
1981
2285
  if (!isStale()) setIsWaitingForConfirmation(false);
1982
2286
  }
@@ -3174,6 +3478,7 @@ function DepositForm({
3174
3478
  selectedToken,
3175
3479
  onTokenSelect,
3176
3480
  onPendingChange,
3481
+ onUnsafeToCloseChange,
3177
3482
  onSuccess
3178
3483
  }) {
3179
3484
  const { isConnected, address } = wagmi.useAccount();
@@ -3202,7 +3507,7 @@ function DepositForm({
3202
3507
  }
3203
3508
  });
3204
3509
  const walletBalance = isNative ? nativeBalanceData?.value : erc20Balance;
3205
- const formattedWalletBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0";
3510
+ const formattedWalletBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0.00";
3206
3511
  const handleMaxClick = () => {
3207
3512
  if (formattedWalletBalance && parseFloat(formattedWalletBalance) > 0) {
3208
3513
  setAmount(formattedWalletBalance.replace(/[\s\u2009]/g, ""));
@@ -3269,6 +3574,9 @@ function DepositForm({
3269
3574
  react.useEffect(() => {
3270
3575
  onPendingChange?.(isPending && !cancelled);
3271
3576
  }, [isPending, cancelled, onPendingChange]);
3577
+ react.useEffect(() => {
3578
+ onUnsafeToCloseChange?.((isGettingAddress || isSendingTransaction) && !cancelled);
3579
+ }, [isGettingAddress, isSendingTransaction, cancelled, onUnsafeToCloseChange]);
3272
3580
  const handleCancel = () => {
3273
3581
  setCancelled(true);
3274
3582
  reset();
@@ -3432,7 +3740,12 @@ function DepositForm({
3432
3740
  )
3433
3741
  ] });
3434
3742
  }
3435
- function WithdrawForm({ selectedToken, onTokenSelect, onPendingChange }) {
3743
+ function WithdrawForm({
3744
+ selectedToken,
3745
+ onTokenSelect,
3746
+ onPendingChange,
3747
+ onUnsafeToCloseChange
3748
+ }) {
3436
3749
  const { isConnected, address } = wagmi.useAccount();
3437
3750
  const { chains, getChainById: getChainById2 } = usePrivanaContext();
3438
3751
  const [amount, setAmount] = react.useState("");
@@ -3485,8 +3798,10 @@ function WithdrawForm({ selectedToken, onTokenSelect, onPendingChange }) {
3485
3798
  }
3486
3799
  }, [error]);
3487
3800
  react.useEffect(() => {
3488
- onPendingChange?.(isPending && !cancelled);
3489
- }, [isPending, cancelled, onPendingChange]);
3801
+ const pending = isPending && !cancelled;
3802
+ onPendingChange?.(pending);
3803
+ onUnsafeToCloseChange?.(pending);
3804
+ }, [isPending, cancelled, onPendingChange, onUnsafeToCloseChange]);
3490
3805
  const handleCancel = () => {
3491
3806
  setCancelled(true);
3492
3807
  reset();
@@ -3645,9 +3960,6 @@ function CloseIcon() {
3645
3960
  }
3646
3961
  ) });
3647
3962
  }
3648
- function SearchIcon() {
3649
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-3 rounded-full border-[1.5px] border-current" });
3650
- }
3651
3963
  function ChevronRight() {
3652
3964
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "-rotate-90", children: /* @__PURE__ */ jsxRuntime.jsx(
3653
3965
  "path",
@@ -3708,7 +4020,7 @@ function BalanceCards({
3708
4020
  });
3709
4021
  const { totalLocked, isLoading: lockedLoading } = useLockedFunds({ enabled: showLockedFunds });
3710
4022
  const formattedBalance = formatTokenAmount(balanceWei, selectedToken.decimals);
3711
- const formattedLocked = showLockedFunds ? formatTokenAmount(String(totalLocked), selectedToken.decimals) : "0";
4023
+ const formattedLocked = showLockedFunds ? formatTokenAmount(String(totalLocked), selectedToken.decimals) : "0.00";
3712
4024
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex gap-2", disabled && "opacity-50"), children: [
3713
4025
  /* @__PURE__ */ jsxRuntime.jsxs(
3714
4026
  "button",
@@ -3771,7 +4083,7 @@ function Tabs({
3771
4083
  "div",
3772
4084
  {
3773
4085
  className: cn(
3774
- "bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-4px)] rounded-md transition-transform duration-200",
4086
+ "bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-8px)] rounded-md transition-transform duration-200",
3775
4087
  activeTab === "withdraw" && "translate-x-[calc(100%+8px)]"
3776
4088
  )
3777
4089
  }
@@ -3806,7 +4118,7 @@ function Tabs({
3806
4118
  }
3807
4119
  );
3808
4120
  }
3809
- function LockedFundsView({ onBack, onClose }) {
4121
+ function LockedFundsView({ onBack }) {
3810
4122
  const { getTokenById } = usePrivanaContext();
3811
4123
  const { locks, isLoading } = useLockedFunds();
3812
4124
  const { unlockFunds, unlockAllExpired, isPending } = useUnlockFunds();
@@ -3839,27 +4151,17 @@ function LockedFundsView({ onBack, onClose }) {
3839
4151
  };
3840
4152
  const expiredCount = locks.filter((l) => l.is_expired).length;
3841
4153
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3842
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-4", children: [
3843
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5", children: [
3844
- /* @__PURE__ */ jsxRuntime.jsx(
3845
- "button",
3846
- {
3847
- onClick: onBack,
3848
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
3849
- children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
3850
- }
3851
- ),
3852
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Locked Funds" })
3853
- ] }),
3854
- onClose && /* @__PURE__ */ jsxRuntime.jsx(
4154
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
4155
+ /* @__PURE__ */ jsxRuntime.jsx(
3855
4156
  "button",
3856
4157
  {
3857
- onClick: onClose,
3858
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
3859
- children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
4158
+ onClick: onBack,
4159
+ className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4160
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
3860
4161
  }
3861
- )
3862
- ] }),
4162
+ ),
4163
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Locked Funds" })
4164
+ ] }) }),
3863
4165
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex min-h-0 flex-1 flex-col rounded-[10px] p-2", children: [
3864
4166
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 overflow-y-auto", children: isLoading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex flex-col gap-2 p-3", children: [1, 2].map((i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex animate-pulse items-center gap-3 rounded-lg p-3", children: [
3865
4167
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-10 w-10 rounded-full" }),
@@ -3944,34 +4246,24 @@ function BalanceTokenRow({ token }) {
3944
4246
  isLoading ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "bg-secondary h-4 w-16 animate-pulse rounded" }) : /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: formattedBalance })
3945
4247
  ] });
3946
4248
  }
3947
- function BalanceDetailsView({ onBack, onClose }) {
4249
+ function BalanceDetailsView({ onBack }) {
3948
4250
  const { enabledTokens, chains } = usePrivanaContext();
3949
4251
  const [selectedChainId, setSelectedChainId] = react.useState(chains[0]?.id ?? 84532);
3950
4252
  const chainTokens = react.useMemo(() => {
3951
4253
  return enabledTokens.filter((t) => t.chainId === selectedChainId);
3952
4254
  }, [enabledTokens, selectedChainId]);
3953
4255
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3954
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-4", children: [
3955
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5", children: [
3956
- /* @__PURE__ */ jsxRuntime.jsx(
3957
- "button",
3958
- {
3959
- onClick: onBack,
3960
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
3961
- children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
3962
- }
3963
- ),
3964
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Token Balances" })
3965
- ] }),
3966
- onClose && /* @__PURE__ */ jsxRuntime.jsx(
4256
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
4257
+ /* @__PURE__ */ jsxRuntime.jsx(
3967
4258
  "button",
3968
4259
  {
3969
- onClick: onClose,
3970
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
3971
- children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
4260
+ onClick: onBack,
4261
+ className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4262
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
3972
4263
  }
3973
- )
3974
- ] }),
4264
+ ),
4265
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Token Balances" })
4266
+ ] }) }),
3975
4267
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
3976
4268
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
3977
4269
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Network" }) }),
@@ -4041,48 +4333,30 @@ function TokenRow({
4041
4333
  }
4042
4334
  function TokenSelectorView({
4043
4335
  onBack,
4044
- onClose,
4045
4336
  onSelect,
4046
4337
  selectedTokenId
4047
4338
  }) {
4048
- const [tokenSearch, setTokenSearch] = react.useState("");
4049
4339
  const { enabledTokens, chains } = usePrivanaContext();
4050
4340
  const [selectedChainId, setSelectedChainId] = react.useState(chains[0]?.id ?? 84532);
4051
4341
  const chainTokens = react.useMemo(() => {
4052
4342
  return enabledTokens.filter((t) => t.chainId === selectedChainId);
4053
4343
  }, [enabledTokens, selectedChainId]);
4054
- const filteredTokens = react.useMemo(() => {
4055
- if (!tokenSearch) return chainTokens;
4056
- return chainTokens.filter(
4057
- (t) => t.symbol.toLowerCase().includes(tokenSearch.toLowerCase()) || t.name.toLowerCase().includes(tokenSearch.toLowerCase())
4058
- );
4059
- }, [tokenSearch, chainTokens]);
4060
4344
  const handleTokenSelect = (token) => {
4061
4345
  onSelect(token);
4062
4346
  onBack();
4063
4347
  };
4064
4348
  return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
4065
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-4", children: [
4066
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2.5", children: [
4067
- /* @__PURE__ */ jsxRuntime.jsx(
4068
- "button",
4069
- {
4070
- onClick: onBack,
4071
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4072
- children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
4073
- }
4074
- ),
4075
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Select Token" })
4076
- ] }),
4077
- onClose && /* @__PURE__ */ jsxRuntime.jsx(
4349
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
4350
+ /* @__PURE__ */ jsxRuntime.jsx(
4078
4351
  "button",
4079
4352
  {
4080
- onClick: onClose,
4081
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4082
- children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
4353
+ onClick: onBack,
4354
+ className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4355
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
4083
4356
  }
4084
- )
4085
- ] }),
4357
+ ),
4358
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Select Token" })
4359
+ ] }) }),
4086
4360
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
4087
4361
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
4088
4362
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Network" }) }),
@@ -4106,23 +4380,8 @@ function TokenSelectorView({
4106
4380
  }) })
4107
4381
  ] }),
4108
4382
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex flex-[2] flex-col overflow-hidden rounded-[10px] p-2", children: [
4109
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1", children: [
4110
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Token" }) }),
4111
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-3", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-border bg-input flex items-center gap-2.5 rounded-lg border px-3 py-2.5", children: [
4112
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: /* @__PURE__ */ jsxRuntime.jsx(SearchIcon, {}) }),
4113
- /* @__PURE__ */ jsxRuntime.jsx(
4114
- "input",
4115
- {
4116
- type: "text",
4117
- placeholder: "Search",
4118
- value: tokenSearch,
4119
- onChange: (e) => setTokenSearch(e.target.value),
4120
- className: "text-foreground placeholder:text-muted-foreground flex-1 bg-transparent text-sm outline-none"
4121
- }
4122
- )
4123
- ] }) })
4124
- ] }),
4125
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex-1 overflow-y-auto", children: filteredTokens.map((token) => /* @__PURE__ */ jsxRuntime.jsx(
4383
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Token" }) }),
4384
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex-1 overflow-y-auto", children: chainTokens.map((token) => /* @__PURE__ */ jsxRuntime.jsx(
4126
4385
  TokenRow,
4127
4386
  {
4128
4387
  token,
@@ -4136,9 +4395,8 @@ function TokenSelectorView({
4136
4395
  ] });
4137
4396
  }
4138
4397
  function ModalBody({
4139
- onClose,
4140
4398
  onViewChange,
4141
- onTransactionPendingChange,
4399
+ onCloseBlockedChange,
4142
4400
  showLockedFunds = true,
4143
4401
  defaultTab = "deposit",
4144
4402
  onDepositSuccess
@@ -4147,65 +4405,78 @@ function ModalBody({
4147
4405
  const [selectedToken, setSelectedToken] = react.useState(defaultToken);
4148
4406
  const [activeTab, setActiveTab] = react.useState(defaultTab);
4149
4407
  const [currentView, setCurrentView] = react.useState("main");
4150
- const [isTransactionPending, setIsTransactionPending] = react.useState(false);
4408
+ const [isInteractionPending, setIsInteractionPending] = react.useState(false);
4151
4409
  react.useEffect(() => {
4152
4410
  if (!selectedToken && defaultToken) {
4153
4411
  setSelectedToken(defaultToken);
4154
4412
  }
4155
4413
  }, [selectedToken, defaultToken]);
4156
- const handleTransactionPendingChange = (isPending) => {
4157
- setIsTransactionPending(isPending);
4158
- onTransactionPendingChange?.(isPending);
4414
+ const handlePendingChange = (isPending) => {
4415
+ setIsInteractionPending(isPending);
4416
+ };
4417
+ const handleCloseBlockedChange = (isBlocked) => {
4418
+ onCloseBlockedChange?.(isBlocked);
4159
4419
  };
4160
4420
  const handleViewChange = (view) => {
4161
- setCurrentView(view);
4162
- onViewChange?.(view);
4421
+ const update = () => {
4422
+ setCurrentView(view);
4423
+ onViewChange?.(view);
4424
+ };
4425
+ if (typeof document !== "undefined" && typeof document.startViewTransition === "function") {
4426
+ document.documentElement.dataset.privanaVtDir = view === "main" ? "back" : "forward";
4427
+ const transition = document.startViewTransition(update);
4428
+ transition.finished.then(() => {
4429
+ delete document.documentElement.dataset.privanaVtDir;
4430
+ });
4431
+ } else {
4432
+ update();
4433
+ }
4163
4434
  };
4164
4435
  const handleTokenSelect = (token) => {
4165
4436
  setSelectedToken(token);
4166
4437
  };
4167
4438
  if (tokensStatus === "loading" || !selectedToken) {
4168
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 pb-4", children: [
4439
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
4169
4440
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-25 animate-pulse rounded-[10px]" }),
4170
4441
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-11 animate-pulse rounded-[10px]" }),
4171
4442
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-50 animate-pulse rounded-[10px]" })
4172
4443
  ] });
4173
4444
  }
4174
4445
  if (currentView === "locked-funds") {
4175
- return /* @__PURE__ */ jsxRuntime.jsx(LockedFundsView, { onBack: () => handleViewChange("main"), onClose });
4446
+ return /* @__PURE__ */ jsxRuntime.jsx(LockedFundsView, { onBack: () => handleViewChange("main") });
4176
4447
  }
4177
4448
  if (currentView === "balance-details") {
4178
- return /* @__PURE__ */ jsxRuntime.jsx(BalanceDetailsView, { onBack: () => handleViewChange("main"), onClose });
4449
+ return /* @__PURE__ */ jsxRuntime.jsx(BalanceDetailsView, { onBack: () => handleViewChange("main") });
4179
4450
  }
4180
4451
  if (currentView === "select-token") {
4181
4452
  return /* @__PURE__ */ jsxRuntime.jsx(
4182
4453
  TokenSelectorView,
4183
4454
  {
4184
4455
  onBack: () => handleViewChange("main"),
4185
- onClose,
4186
4456
  onSelect: handleTokenSelect,
4187
4457
  selectedTokenId: selectedToken.id
4188
4458
  }
4189
4459
  );
4190
4460
  }
4191
4461
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 pb-4", children: [
4192
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(isTransactionPending && "pointer-events-none"), children: /* @__PURE__ */ jsxRuntime.jsx(
4462
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(isInteractionPending && "pointer-events-none"), children: /* @__PURE__ */ jsxRuntime.jsx(
4193
4463
  BalanceCards,
4194
4464
  {
4195
4465
  selectedToken,
4196
4466
  onLockedFundsClick: () => handleViewChange("locked-funds"),
4197
4467
  onBalanceClick: () => handleViewChange("balance-details"),
4198
4468
  showLockedFunds,
4199
- disabled: isTransactionPending
4469
+ disabled: isInteractionPending
4200
4470
  }
4201
4471
  ) }),
4202
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(isTransactionPending && "pointer-events-none"), children: /* @__PURE__ */ jsxRuntime.jsx(Tabs, { activeTab, onTabChange: setActiveTab, disabled: isTransactionPending }) }),
4472
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(isInteractionPending && "pointer-events-none"), children: /* @__PURE__ */ jsxRuntime.jsx(Tabs, { activeTab, onTabChange: setActiveTab, disabled: isInteractionPending }) }),
4203
4473
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-muted rounded-[10px] p-5", children: activeTab === "deposit" ? /* @__PURE__ */ jsxRuntime.jsx(
4204
4474
  DepositForm,
4205
4475
  {
4206
4476
  selectedToken,
4207
4477
  onTokenSelect: () => handleViewChange("select-token"),
4208
- onPendingChange: handleTransactionPendingChange,
4478
+ onPendingChange: handlePendingChange,
4479
+ onUnsafeToCloseChange: handleCloseBlockedChange,
4209
4480
  onSuccess: onDepositSuccess
4210
4481
  }
4211
4482
  ) : /* @__PURE__ */ jsxRuntime.jsx(
@@ -4213,7 +4484,8 @@ function ModalBody({
4213
4484
  {
4214
4485
  selectedToken,
4215
4486
  onTokenSelect: () => handleViewChange("select-token"),
4216
- onPendingChange: handleTransactionPendingChange
4487
+ onPendingChange: handlePendingChange,
4488
+ onUnsafeToCloseChange: handleCloseBlockedChange
4217
4489
  }
4218
4490
  ) })
4219
4491
  ] }) });
@@ -4225,55 +4497,84 @@ function PrivanaModal({
4225
4497
  defaultTab,
4226
4498
  onDepositSuccess
4227
4499
  }) {
4228
- const [isTransactionPending, setIsTransactionPending] = react.useState(false);
4500
+ const [currentView, setCurrentView] = react.useState("main");
4229
4501
  const titleId = react.useId();
4230
4502
  const descId = react.useId();
4231
- const handleOpenChange = (isOpen) => {
4232
- if (!isOpen && isTransactionPending) {
4233
- return;
4234
- }
4235
- if (!isOpen) {
4236
- onClose();
4237
- }
4503
+ const [isCloseBlocked, setIsCloseBlocked] = react.useState(false);
4504
+ const handleClose = () => {
4505
+ if (!isCloseBlocked) onClose();
4238
4506
  };
4239
- return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(
4240
- DialogContent,
4507
+ return /* @__PURE__ */ jsxRuntime.jsx(
4508
+ Dialog,
4241
4509
  {
4242
- "data-privana": true,
4243
- showCloseButton: false,
4244
- className: "bg-card flex w-[560px] max-w-[95vw] flex-col gap-2 overflow-hidden rounded-2xl border-0 p-2",
4245
- overlayClassName: isTransactionPending ? "cursor-not-allowed" : void 0,
4246
- "aria-labelledby": titleId,
4247
- "aria-describedby": descId,
4248
- children: [
4249
- /* @__PURE__ */ jsxRuntime.jsxs(DialogHeader, { children: [
4250
- /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { id: titleId, className: "sr-only", children: "Privana" }),
4251
- /* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { id: descId, className: "sr-only", children: "Deposit or withdraw tokens from your Flexvault" }),
4252
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-5 py-4", children: [
4253
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Privana" }),
4254
- onClose && /* @__PURE__ */ jsxRuntime.jsx(
4510
+ open,
4511
+ onOpenChange: (isOpen) => {
4512
+ if (!isOpen) handleClose();
4513
+ },
4514
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4515
+ DialogContent,
4516
+ {
4517
+ "data-privana": true,
4518
+ "data-view": currentView,
4519
+ showCloseButton: false,
4520
+ onInteractOutside: isCloseBlocked ? (e) => e.preventDefault() : void 0,
4521
+ onEscapeKeyDown: isCloseBlocked ? (e) => e.preventDefault() : void 0,
4522
+ 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]",
4523
+ "aria-labelledby": titleId,
4524
+ "aria-describedby": descId,
4525
+ children: [
4526
+ /* @__PURE__ */ jsxRuntime.jsx(
4255
4527
  "button",
4256
4528
  {
4257
- onClick: onClose,
4258
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4529
+ "data-privana-close": true,
4530
+ onClick: handleClose,
4531
+ disabled: isCloseBlocked,
4532
+ "aria-label": "Close",
4533
+ className: cn(
4534
+ "absolute top-6 right-5 z-20 flex h-6 w-6 items-center justify-center transition-colors",
4535
+ isCloseBlocked ? "text-muted-foreground/40 cursor-not-allowed" : "text-muted-foreground hover:text-foreground cursor-pointer"
4536
+ ),
4259
4537
  children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
4260
4538
  }
4539
+ ),
4540
+ /* @__PURE__ */ jsxRuntime.jsxs(
4541
+ "div",
4542
+ {
4543
+ "data-privana-content": true,
4544
+ "data-view": currentView,
4545
+ className: "flex min-h-0 flex-1 flex-col gap-2",
4546
+ children: [
4547
+ /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { id: titleId, className: "sr-only", children: "Privana" }),
4548
+ /* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { id: descId, className: "sr-only", children: "Deposit or withdraw tokens from your Privana" }),
4549
+ currentView === "main" && /* @__PURE__ */ jsxRuntime.jsx(DialogHeader, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center px-5 py-4", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Privana" }) }) }),
4550
+ /* @__PURE__ */ jsxRuntime.jsx(
4551
+ ModalBody,
4552
+ {
4553
+ onCloseBlockedChange: setIsCloseBlocked,
4554
+ onViewChange: setCurrentView,
4555
+ showLockedFunds,
4556
+ defaultTab,
4557
+ onDepositSuccess
4558
+ }
4559
+ )
4560
+ ]
4561
+ }
4562
+ ),
4563
+ /* @__PURE__ */ jsxRuntime.jsx(
4564
+ "a",
4565
+ {
4566
+ "data-privana-footer": true,
4567
+ href: "https://privana.finance",
4568
+ target: "_blank",
4569
+ rel: "noopener noreferrer",
4570
+ "aria-label": "Powered by Privana"
4571
+ }
4261
4572
  )
4262
- ] })
4263
- ] }),
4264
- /* @__PURE__ */ jsxRuntime.jsx(
4265
- ModalBody,
4266
- {
4267
- onClose: isTransactionPending ? void 0 : onClose,
4268
- onTransactionPendingChange: setIsTransactionPending,
4269
- showLockedFunds,
4270
- defaultTab,
4271
- onDepositSuccess
4272
- }
4273
- )
4274
- ]
4573
+ ]
4574
+ }
4575
+ )
4275
4576
  }
4276
- ) });
4577
+ );
4277
4578
  }
4278
4579
  function PrivanaInlineModal({
4279
4580
  className,
@@ -4374,6 +4675,7 @@ exports.PrivanaInlineModal = PrivanaInlineModal;
4374
4675
  exports.PrivanaModal = PrivanaModal;
4375
4676
  exports.PrivanaProvider = PrivanaProvider;
4376
4677
  exports.SUPPORTED_CHAINS = SUPPORTED_CHAINS;
4678
+ exports.SiweAuthProvider = SiweAuthProvider;
4377
4679
  exports.Skeleton = Skeleton;
4378
4680
  exports.TRANSFER_LOCKED_TYPES = TRANSFER_LOCKED_TYPES;
4379
4681
  exports.TRANSFER_TYPES = TRANSFER_TYPES;
@@ -4428,6 +4730,7 @@ exports.usePrivanaClient = usePrivanaClient;
4428
4730
  exports.usePrivanaContext = usePrivanaContext;
4429
4731
  exports.useSafeAccount = useSafeAccount;
4430
4732
  exports.useSafePrivanaContext = useSafePrivanaContext;
4733
+ exports.useSiweAuth = useSiweAuth;
4431
4734
  exports.useTokenInfo = useTokenInfo;
4432
4735
  exports.useTokenList = useTokenList;
4433
4736
  exports.useTotalLockedBalance = useTotalLockedBalance;