@oasisprotocol/privana-sdk 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -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,
@@ -1630,6 +1630,12 @@ function getAction(client, actionFn, name) {
1630
1630
  function getChainId2(config) {
1631
1631
  return config.state.chainId;
1632
1632
  }
1633
+ async function getTransactionReceipt(config, parameters) {
1634
+ const { chainId, ...rest } = parameters;
1635
+ const client = config.getClient({ chainId });
1636
+ const action = getAction(client, actions$1.getTransactionReceipt, "getTransactionReceipt");
1637
+ return action(rest);
1638
+ }
1633
1639
  async function waitForTransactionReceipt(config, parameters) {
1634
1640
  const { chainId, timeout = 0, ...rest } = parameters;
1635
1641
  const client = config.getClient({ chainId });
@@ -1710,6 +1716,36 @@ function useEnsureCorrectChain() {
1710
1716
  }
1711
1717
 
1712
1718
  // src/sdk/hooks/use-deposit.ts
1719
+ var STALE_MS = 30 * 60 * 1e3;
1720
+ function storageKey(address) {
1721
+ return `privana:pending-deposit:${address.toLowerCase()}`;
1722
+ }
1723
+ function savePendingDeposit(address, data) {
1724
+ try {
1725
+ sessionStorage.setItem(storageKey(address), JSON.stringify(data));
1726
+ } catch {
1727
+ }
1728
+ }
1729
+ function loadPendingDeposit(address) {
1730
+ try {
1731
+ const raw = sessionStorage.getItem(storageKey(address));
1732
+ if (!raw) return null;
1733
+ const data = JSON.parse(raw);
1734
+ if (Date.now() - data.savedAt > STALE_MS) {
1735
+ clearPendingDeposit(address);
1736
+ return null;
1737
+ }
1738
+ return data;
1739
+ } catch {
1740
+ return null;
1741
+ }
1742
+ }
1743
+ function clearPendingDeposit(address) {
1744
+ try {
1745
+ sessionStorage.removeItem(storageKey(address));
1746
+ } catch {
1747
+ }
1748
+ }
1713
1749
  function useDeposit(options = {}) {
1714
1750
  const { address } = wagmi.useAccount();
1715
1751
  const { client, enabledTokens, getChainById: getChainById2 } = usePrivanaContext();
@@ -1777,13 +1813,19 @@ function useDeposit(options = {}) {
1777
1813
  const isSendingTx = isWritingContract || isSendingNative;
1778
1814
  const sendError = writeError ?? sendNativeError;
1779
1815
  const { ensureCorrectChain } = useEnsureCorrectChain();
1816
+ const invalidateGeneration = react.useCallback(() => {
1817
+ generationRef.current++;
1818
+ }, []);
1780
1819
  react.useEffect(() => {
1781
1820
  return () => {
1821
+ invalidateGeneration();
1782
1822
  if (pollIntervalRef.current) {
1783
1823
  clearTimeout(pollIntervalRef.current);
1824
+ pollIntervalRef.current = null;
1784
1825
  }
1785
1826
  };
1786
- }, []);
1827
+ }, [invalidateGeneration]);
1828
+ const resumedAddressRef = react.useRef(void 0);
1787
1829
  const stopPolling = react.useCallback(() => {
1788
1830
  if (pollIntervalRef.current) {
1789
1831
  clearTimeout(pollIntervalRef.current);
@@ -1793,6 +1835,7 @@ function useDeposit(options = {}) {
1793
1835
  const reset = react.useCallback(() => {
1794
1836
  generationRef.current++;
1795
1837
  stopPolling();
1838
+ if (address) clearPendingDeposit(address);
1796
1839
  verificationContextRef.current = null;
1797
1840
  setDepositAddress(null);
1798
1841
  setTxHash(void 0);
@@ -1805,7 +1848,7 @@ function useDeposit(options = {}) {
1805
1848
  addressMutation.reset();
1806
1849
  resetWriteContract();
1807
1850
  resetSendTransaction();
1808
- }, [addressMutation, resetWriteContract, resetSendTransaction, stopPolling]);
1851
+ }, [address, addressMutation, resetWriteContract, resetSendTransaction, stopPolling]);
1809
1852
  const runVerification = react.useCallback(
1810
1853
  async (ctx, generation) => {
1811
1854
  const isStale = () => generation !== generationRef.current;
@@ -1833,6 +1876,7 @@ function useDeposit(options = {}) {
1833
1876
  if (triggerResult.status === "credited") {
1834
1877
  setIsWaitingForProcessing(false);
1835
1878
  verificationContextRef.current = null;
1879
+ if (address) clearPendingDeposit(address);
1836
1880
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
1837
1881
  queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
1838
1882
  onCreditedRef.current?.(hash, triggerResult);
@@ -1866,6 +1910,7 @@ function useDeposit(options = {}) {
1866
1910
  stopPolling();
1867
1911
  setIsWaitingForProcessing(false);
1868
1912
  verificationContextRef.current = null;
1913
+ if (address) clearPendingDeposit(address);
1869
1914
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
1870
1915
  queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
1871
1916
  onCreditedRef.current?.(hash, result);
@@ -1905,7 +1950,7 @@ function useDeposit(options = {}) {
1905
1950
  );
1906
1951
  }
1907
1952
  },
1908
- [client, executePrivateRead, pollInterval, pollTimeout, queryClient, stopPolling]
1953
+ [address, client, executePrivateRead, pollInterval, pollTimeout, queryClient, stopPolling]
1909
1954
  );
1910
1955
  const retryVerification = react.useCallback(async () => {
1911
1956
  const ctx = verificationContextRef.current;
@@ -1917,6 +1962,55 @@ function useDeposit(options = {}) {
1917
1962
  const generation = generationRef.current;
1918
1963
  await runVerification(ctx, generation);
1919
1964
  }, [runVerification, stopPolling]);
1965
+ react.useEffect(() => {
1966
+ if (!address || resumedAddressRef.current === address) return;
1967
+ const persisted = loadPendingDeposit(address);
1968
+ if (!persisted) return;
1969
+ resumedAddressRef.current = address;
1970
+ const hash = persisted.txHash;
1971
+ setTxHash(hash);
1972
+ setDepositAddress(persisted.depositAddress);
1973
+ setIsWaitingForConfirmation(true);
1974
+ const ctx = {
1975
+ hash,
1976
+ chainId: persisted.chainId,
1977
+ amount: BigInt(persisted.amount)
1978
+ };
1979
+ verificationContextRef.current = ctx;
1980
+ const generation = ++generationRef.current;
1981
+ const isStale = () => generation !== generationRef.current;
1982
+ (async () => {
1983
+ try {
1984
+ let confirmed = false;
1985
+ try {
1986
+ await getTransactionReceipt(config, { hash, chainId: persisted.chainId });
1987
+ confirmed = true;
1988
+ } catch {
1989
+ }
1990
+ if (!confirmed) {
1991
+ await waitForTransactionReceipt(config, {
1992
+ hash,
1993
+ chainId: persisted.chainId,
1994
+ confirmations
1995
+ });
1996
+ }
1997
+ if (isStale()) return;
1998
+ setIsWaitingForConfirmation(false);
1999
+ onDepositSuccessRef.current?.(hash);
2000
+ queryClient.invalidateQueries({ queryKey: ["readContract"] });
2001
+ await runVerification(ctx, generation);
2002
+ } catch (err) {
2003
+ if (isStale()) return;
2004
+ setIsWaitingForConfirmation(false);
2005
+ stopPolling();
2006
+ const error2 = err instanceof Error ? err : new Error("Deposit verification failed");
2007
+ setIsWaitingForProcessing(false);
2008
+ setDepositError(error2);
2009
+ setVerificationFailed(true);
2010
+ onErrorRef.current?.(error2);
2011
+ }
2012
+ })();
2013
+ }, [address, config, confirmations, queryClient, runVerification, stopPolling]);
1920
2014
  const deposit = react.useCallback(
1921
2015
  async (params) => {
1922
2016
  if (verificationContextRef.current) {
@@ -1973,10 +2067,21 @@ function useDeposit(options = {}) {
1973
2067
  amount: params.amount
1974
2068
  };
1975
2069
  verificationContextRef.current = ctx;
2070
+ savePendingDeposit(address, {
2071
+ txHash: hash,
2072
+ chainId: sourceChain.id,
2073
+ amount: params.amount.toString(),
2074
+ depositAddress: addrResponse,
2075
+ savedAt: Date.now()
2076
+ });
1976
2077
  try {
1977
2078
  setIsWaitingForConfirmation(true);
1978
2079
  try {
1979
- await waitForTransactionReceipt(config, { hash, confirmations });
2080
+ await waitForTransactionReceipt(config, {
2081
+ hash,
2082
+ chainId: sourceChain.id,
2083
+ confirmations
2084
+ });
1980
2085
  } finally {
1981
2086
  if (!isStale()) setIsWaitingForConfirmation(false);
1982
2087
  }
@@ -3174,6 +3279,7 @@ function DepositForm({
3174
3279
  selectedToken,
3175
3280
  onTokenSelect,
3176
3281
  onPendingChange,
3282
+ onUnsafeToCloseChange,
3177
3283
  onSuccess
3178
3284
  }) {
3179
3285
  const { isConnected, address } = wagmi.useAccount();
@@ -3202,7 +3308,7 @@ function DepositForm({
3202
3308
  }
3203
3309
  });
3204
3310
  const walletBalance = isNative ? nativeBalanceData?.value : erc20Balance;
3205
- const formattedWalletBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0";
3311
+ const formattedWalletBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0.00";
3206
3312
  const handleMaxClick = () => {
3207
3313
  if (formattedWalletBalance && parseFloat(formattedWalletBalance) > 0) {
3208
3314
  setAmount(formattedWalletBalance.replace(/[\s\u2009]/g, ""));
@@ -3269,6 +3375,9 @@ function DepositForm({
3269
3375
  react.useEffect(() => {
3270
3376
  onPendingChange?.(isPending && !cancelled);
3271
3377
  }, [isPending, cancelled, onPendingChange]);
3378
+ react.useEffect(() => {
3379
+ onUnsafeToCloseChange?.((isGettingAddress || isSendingTransaction) && !cancelled);
3380
+ }, [isGettingAddress, isSendingTransaction, cancelled, onUnsafeToCloseChange]);
3272
3381
  const handleCancel = () => {
3273
3382
  setCancelled(true);
3274
3383
  reset();
@@ -3432,7 +3541,12 @@ function DepositForm({
3432
3541
  )
3433
3542
  ] });
3434
3543
  }
3435
- function WithdrawForm({ selectedToken, onTokenSelect, onPendingChange }) {
3544
+ function WithdrawForm({
3545
+ selectedToken,
3546
+ onTokenSelect,
3547
+ onPendingChange,
3548
+ onUnsafeToCloseChange
3549
+ }) {
3436
3550
  const { isConnected, address } = wagmi.useAccount();
3437
3551
  const { chains, getChainById: getChainById2 } = usePrivanaContext();
3438
3552
  const [amount, setAmount] = react.useState("");
@@ -3485,8 +3599,10 @@ function WithdrawForm({ selectedToken, onTokenSelect, onPendingChange }) {
3485
3599
  }
3486
3600
  }, [error]);
3487
3601
  react.useEffect(() => {
3488
- onPendingChange?.(isPending && !cancelled);
3489
- }, [isPending, cancelled, onPendingChange]);
3602
+ const pending = isPending && !cancelled;
3603
+ onPendingChange?.(pending);
3604
+ onUnsafeToCloseChange?.(pending);
3605
+ }, [isPending, cancelled, onPendingChange, onUnsafeToCloseChange]);
3490
3606
  const handleCancel = () => {
3491
3607
  setCancelled(true);
3492
3608
  reset();
@@ -3645,9 +3761,6 @@ function CloseIcon() {
3645
3761
  }
3646
3762
  ) });
3647
3763
  }
3648
- function SearchIcon() {
3649
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-3 w-3 rounded-full border-[1.5px] border-current" });
3650
- }
3651
3764
  function ChevronRight() {
3652
3765
  return /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "-rotate-90", children: /* @__PURE__ */ jsxRuntime.jsx(
3653
3766
  "path",
@@ -3708,7 +3821,7 @@ function BalanceCards({
3708
3821
  });
3709
3822
  const { totalLocked, isLoading: lockedLoading } = useLockedFunds({ enabled: showLockedFunds });
3710
3823
  const formattedBalance = formatTokenAmount(balanceWei, selectedToken.decimals);
3711
- const formattedLocked = showLockedFunds ? formatTokenAmount(String(totalLocked), selectedToken.decimals) : "0";
3824
+ const formattedLocked = showLockedFunds ? formatTokenAmount(String(totalLocked), selectedToken.decimals) : "0.00";
3712
3825
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex gap-2", disabled && "opacity-50"), children: [
3713
3826
  /* @__PURE__ */ jsxRuntime.jsxs(
3714
3827
  "button",
@@ -3771,7 +3884,7 @@ function Tabs({
3771
3884
  "div",
3772
3885
  {
3773
3886
  className: cn(
3774
- "bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-4px)] rounded-md transition-transform duration-200",
3887
+ "bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-8px)] rounded-md transition-transform duration-200",
3775
3888
  activeTab === "withdraw" && "translate-x-[calc(100%+8px)]"
3776
3889
  )
3777
3890
  }
@@ -3806,7 +3919,7 @@ function Tabs({
3806
3919
  }
3807
3920
  );
3808
3921
  }
3809
- function LockedFundsView({ onBack, onClose }) {
3922
+ function LockedFundsView({ onBack }) {
3810
3923
  const { getTokenById } = usePrivanaContext();
3811
3924
  const { locks, isLoading } = useLockedFunds();
3812
3925
  const { unlockFunds, unlockAllExpired, isPending } = useUnlockFunds();
@@ -3839,27 +3952,17 @@ function LockedFundsView({ onBack, onClose }) {
3839
3952
  };
3840
3953
  const expiredCount = locks.filter((l) => l.is_expired).length;
3841
3954
  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(
3955
+ /* @__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: [
3956
+ /* @__PURE__ */ jsxRuntime.jsx(
3855
3957
  "button",
3856
3958
  {
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, {})
3959
+ onClick: onBack,
3960
+ className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
3961
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
3860
3962
  }
3861
- )
3862
- ] }),
3963
+ ),
3964
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Locked Funds" })
3965
+ ] }) }),
3863
3966
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex min-h-0 flex-1 flex-col rounded-[10px] p-2", children: [
3864
3967
  /* @__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
3968
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-10 w-10 rounded-full" }),
@@ -3944,34 +4047,24 @@ function BalanceTokenRow({ token }) {
3944
4047
  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
4048
  ] });
3946
4049
  }
3947
- function BalanceDetailsView({ onBack, onClose }) {
4050
+ function BalanceDetailsView({ onBack }) {
3948
4051
  const { enabledTokens, chains } = usePrivanaContext();
3949
4052
  const [selectedChainId, setSelectedChainId] = react.useState(chains[0]?.id ?? 84532);
3950
4053
  const chainTokens = react.useMemo(() => {
3951
4054
  return enabledTokens.filter((t) => t.chainId === selectedChainId);
3952
4055
  }, [enabledTokens, selectedChainId]);
3953
4056
  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(
4057
+ /* @__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: [
4058
+ /* @__PURE__ */ jsxRuntime.jsx(
3967
4059
  "button",
3968
4060
  {
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, {})
4061
+ onClick: onBack,
4062
+ className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4063
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
3972
4064
  }
3973
- )
3974
- ] }),
4065
+ ),
4066
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Token Balances" })
4067
+ ] }) }),
3975
4068
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
3976
4069
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
3977
4070
  /* @__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 +4134,30 @@ function TokenRow({
4041
4134
  }
4042
4135
  function TokenSelectorView({
4043
4136
  onBack,
4044
- onClose,
4045
4137
  onSelect,
4046
4138
  selectedTokenId
4047
4139
  }) {
4048
- const [tokenSearch, setTokenSearch] = react.useState("");
4049
4140
  const { enabledTokens, chains } = usePrivanaContext();
4050
4141
  const [selectedChainId, setSelectedChainId] = react.useState(chains[0]?.id ?? 84532);
4051
4142
  const chainTokens = react.useMemo(() => {
4052
4143
  return enabledTokens.filter((t) => t.chainId === selectedChainId);
4053
4144
  }, [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
4145
  const handleTokenSelect = (token) => {
4061
4146
  onSelect(token);
4062
4147
  onBack();
4063
4148
  };
4064
4149
  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(
4150
+ /* @__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: [
4151
+ /* @__PURE__ */ jsxRuntime.jsx(
4078
4152
  "button",
4079
4153
  {
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, {})
4154
+ onClick: onBack,
4155
+ className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4156
+ children: /* @__PURE__ */ jsxRuntime.jsx(ChevronLeft, {})
4083
4157
  }
4084
- )
4085
- ] }),
4158
+ ),
4159
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Select Token" })
4160
+ ] }) }),
4086
4161
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
4087
4162
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
4088
4163
  /* @__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 +4181,8 @@ function TokenSelectorView({
4106
4181
  }) })
4107
4182
  ] }),
4108
4183
  /* @__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(
4184
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground text-sm", children: "Token" }) }),
4185
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-2 flex-1 overflow-y-auto", children: chainTokens.map((token) => /* @__PURE__ */ jsxRuntime.jsx(
4126
4186
  TokenRow,
4127
4187
  {
4128
4188
  token,
@@ -4136,9 +4196,8 @@ function TokenSelectorView({
4136
4196
  ] });
4137
4197
  }
4138
4198
  function ModalBody({
4139
- onClose,
4140
4199
  onViewChange,
4141
- onTransactionPendingChange,
4200
+ onCloseBlockedChange,
4142
4201
  showLockedFunds = true,
4143
4202
  defaultTab = "deposit",
4144
4203
  onDepositSuccess
@@ -4147,65 +4206,78 @@ function ModalBody({
4147
4206
  const [selectedToken, setSelectedToken] = react.useState(defaultToken);
4148
4207
  const [activeTab, setActiveTab] = react.useState(defaultTab);
4149
4208
  const [currentView, setCurrentView] = react.useState("main");
4150
- const [isTransactionPending, setIsTransactionPending] = react.useState(false);
4209
+ const [isInteractionPending, setIsInteractionPending] = react.useState(false);
4151
4210
  react.useEffect(() => {
4152
4211
  if (!selectedToken && defaultToken) {
4153
4212
  setSelectedToken(defaultToken);
4154
4213
  }
4155
4214
  }, [selectedToken, defaultToken]);
4156
- const handleTransactionPendingChange = (isPending) => {
4157
- setIsTransactionPending(isPending);
4158
- onTransactionPendingChange?.(isPending);
4215
+ const handlePendingChange = (isPending) => {
4216
+ setIsInteractionPending(isPending);
4217
+ };
4218
+ const handleCloseBlockedChange = (isBlocked) => {
4219
+ onCloseBlockedChange?.(isBlocked);
4159
4220
  };
4160
4221
  const handleViewChange = (view) => {
4161
- setCurrentView(view);
4162
- onViewChange?.(view);
4222
+ const update = () => {
4223
+ setCurrentView(view);
4224
+ onViewChange?.(view);
4225
+ };
4226
+ if (typeof document !== "undefined" && typeof document.startViewTransition === "function") {
4227
+ document.documentElement.dataset.privanaVtDir = view === "main" ? "back" : "forward";
4228
+ const transition = document.startViewTransition(update);
4229
+ transition.finished.then(() => {
4230
+ delete document.documentElement.dataset.privanaVtDir;
4231
+ });
4232
+ } else {
4233
+ update();
4234
+ }
4163
4235
  };
4164
4236
  const handleTokenSelect = (token) => {
4165
4237
  setSelectedToken(token);
4166
4238
  };
4167
4239
  if (tokensStatus === "loading" || !selectedToken) {
4168
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2 pb-4", children: [
4240
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-2", children: [
4169
4241
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-25 animate-pulse rounded-[10px]" }),
4170
4242
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-11 animate-pulse rounded-[10px]" }),
4171
4243
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-secondary h-50 animate-pulse rounded-[10px]" })
4172
4244
  ] });
4173
4245
  }
4174
4246
  if (currentView === "locked-funds") {
4175
- return /* @__PURE__ */ jsxRuntime.jsx(LockedFundsView, { onBack: () => handleViewChange("main"), onClose });
4247
+ return /* @__PURE__ */ jsxRuntime.jsx(LockedFundsView, { onBack: () => handleViewChange("main") });
4176
4248
  }
4177
4249
  if (currentView === "balance-details") {
4178
- return /* @__PURE__ */ jsxRuntime.jsx(BalanceDetailsView, { onBack: () => handleViewChange("main"), onClose });
4250
+ return /* @__PURE__ */ jsxRuntime.jsx(BalanceDetailsView, { onBack: () => handleViewChange("main") });
4179
4251
  }
4180
4252
  if (currentView === "select-token") {
4181
4253
  return /* @__PURE__ */ jsxRuntime.jsx(
4182
4254
  TokenSelectorView,
4183
4255
  {
4184
4256
  onBack: () => handleViewChange("main"),
4185
- onClose,
4186
4257
  onSelect: handleTokenSelect,
4187
4258
  selectedTokenId: selectedToken.id
4188
4259
  }
4189
4260
  );
4190
4261
  }
4191
4262
  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(
4263
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(isInteractionPending && "pointer-events-none"), children: /* @__PURE__ */ jsxRuntime.jsx(
4193
4264
  BalanceCards,
4194
4265
  {
4195
4266
  selectedToken,
4196
4267
  onLockedFundsClick: () => handleViewChange("locked-funds"),
4197
4268
  onBalanceClick: () => handleViewChange("balance-details"),
4198
4269
  showLockedFunds,
4199
- disabled: isTransactionPending
4270
+ disabled: isInteractionPending
4200
4271
  }
4201
4272
  ) }),
4202
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(isTransactionPending && "pointer-events-none"), children: /* @__PURE__ */ jsxRuntime.jsx(Tabs, { activeTab, onTabChange: setActiveTab, disabled: isTransactionPending }) }),
4273
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(isInteractionPending && "pointer-events-none"), children: /* @__PURE__ */ jsxRuntime.jsx(Tabs, { activeTab, onTabChange: setActiveTab, disabled: isInteractionPending }) }),
4203
4274
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-muted rounded-[10px] p-5", children: activeTab === "deposit" ? /* @__PURE__ */ jsxRuntime.jsx(
4204
4275
  DepositForm,
4205
4276
  {
4206
4277
  selectedToken,
4207
4278
  onTokenSelect: () => handleViewChange("select-token"),
4208
- onPendingChange: handleTransactionPendingChange,
4279
+ onPendingChange: handlePendingChange,
4280
+ onUnsafeToCloseChange: handleCloseBlockedChange,
4209
4281
  onSuccess: onDepositSuccess
4210
4282
  }
4211
4283
  ) : /* @__PURE__ */ jsxRuntime.jsx(
@@ -4213,7 +4285,8 @@ function ModalBody({
4213
4285
  {
4214
4286
  selectedToken,
4215
4287
  onTokenSelect: () => handleViewChange("select-token"),
4216
- onPendingChange: handleTransactionPendingChange
4288
+ onPendingChange: handlePendingChange,
4289
+ onUnsafeToCloseChange: handleCloseBlockedChange
4217
4290
  }
4218
4291
  ) })
4219
4292
  ] }) });
@@ -4225,55 +4298,84 @@ function PrivanaModal({
4225
4298
  defaultTab,
4226
4299
  onDepositSuccess
4227
4300
  }) {
4228
- const [isTransactionPending, setIsTransactionPending] = react.useState(false);
4301
+ const [currentView, setCurrentView] = react.useState("main");
4229
4302
  const titleId = react.useId();
4230
4303
  const descId = react.useId();
4231
- const handleOpenChange = (isOpen) => {
4232
- if (!isOpen && isTransactionPending) {
4233
- return;
4234
- }
4235
- if (!isOpen) {
4236
- onClose();
4237
- }
4304
+ const [isCloseBlocked, setIsCloseBlocked] = react.useState(false);
4305
+ const handleClose = () => {
4306
+ if (!isCloseBlocked) onClose();
4238
4307
  };
4239
- return /* @__PURE__ */ jsxRuntime.jsx(Dialog, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxRuntime.jsxs(
4240
- DialogContent,
4308
+ return /* @__PURE__ */ jsxRuntime.jsx(
4309
+ Dialog,
4241
4310
  {
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(
4311
+ open,
4312
+ onOpenChange: (isOpen) => {
4313
+ if (!isOpen) handleClose();
4314
+ },
4315
+ children: /* @__PURE__ */ jsxRuntime.jsxs(
4316
+ DialogContent,
4317
+ {
4318
+ "data-privana": true,
4319
+ "data-view": currentView,
4320
+ showCloseButton: false,
4321
+ onInteractOutside: isCloseBlocked ? (e) => e.preventDefault() : void 0,
4322
+ onEscapeKeyDown: isCloseBlocked ? (e) => e.preventDefault() : void 0,
4323
+ 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]",
4324
+ "aria-labelledby": titleId,
4325
+ "aria-describedby": descId,
4326
+ children: [
4327
+ /* @__PURE__ */ jsxRuntime.jsx(
4255
4328
  "button",
4256
4329
  {
4257
- onClick: onClose,
4258
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4330
+ "data-privana-close": true,
4331
+ onClick: handleClose,
4332
+ disabled: isCloseBlocked,
4333
+ "aria-label": "Close",
4334
+ className: cn(
4335
+ "absolute top-6 right-5 z-20 flex h-6 w-6 items-center justify-center transition-colors",
4336
+ isCloseBlocked ? "text-muted-foreground/40 cursor-not-allowed" : "text-muted-foreground hover:text-foreground cursor-pointer"
4337
+ ),
4259
4338
  children: /* @__PURE__ */ jsxRuntime.jsx(CloseIcon, {})
4260
4339
  }
4340
+ ),
4341
+ /* @__PURE__ */ jsxRuntime.jsxs(
4342
+ "div",
4343
+ {
4344
+ "data-privana-content": true,
4345
+ "data-view": currentView,
4346
+ className: "flex min-h-0 flex-1 flex-col gap-2",
4347
+ children: [
4348
+ /* @__PURE__ */ jsxRuntime.jsx(DialogTitle, { id: titleId, className: "sr-only", children: "Privana" }),
4349
+ /* @__PURE__ */ jsxRuntime.jsx(DialogDescription, { id: descId, className: "sr-only", children: "Deposit or withdraw tokens from your Privana" }),
4350
+ 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" }) }) }),
4351
+ /* @__PURE__ */ jsxRuntime.jsx(
4352
+ ModalBody,
4353
+ {
4354
+ onCloseBlockedChange: setIsCloseBlocked,
4355
+ onViewChange: setCurrentView,
4356
+ showLockedFunds,
4357
+ defaultTab,
4358
+ onDepositSuccess
4359
+ }
4360
+ )
4361
+ ]
4362
+ }
4363
+ ),
4364
+ /* @__PURE__ */ jsxRuntime.jsx(
4365
+ "a",
4366
+ {
4367
+ "data-privana-footer": true,
4368
+ href: "https://privana.finance",
4369
+ target: "_blank",
4370
+ rel: "noopener noreferrer",
4371
+ "aria-label": "Powered by Privana"
4372
+ }
4261
4373
  )
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
- ]
4374
+ ]
4375
+ }
4376
+ )
4275
4377
  }
4276
- ) });
4378
+ );
4277
4379
  }
4278
4380
  function PrivanaInlineModal({
4279
4381
  className,