@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.js CHANGED
@@ -8,7 +8,7 @@ import { twMerge } from 'tailwind-merge';
8
8
  import { createSiweMessage } from 'viem/siwe';
9
9
  import { WagmiContext, useAccount, useWalletClient, useConfig, useWriteContract, useSendTransaction, useChainId, useSwitchChain, useBalance as useBalance$1, useReadContract } from 'wagmi';
10
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,
@@ -1608,6 +1608,12 @@ function getAction(client, actionFn, name) {
1608
1608
  function getChainId2(config) {
1609
1609
  return config.state.chainId;
1610
1610
  }
1611
+ async function getTransactionReceipt(config, parameters) {
1612
+ const { chainId, ...rest } = parameters;
1613
+ const client = config.getClient({ chainId });
1614
+ const action = getAction(client, getTransactionReceipt$1, "getTransactionReceipt");
1615
+ return action(rest);
1616
+ }
1611
1617
  async function waitForTransactionReceipt(config, parameters) {
1612
1618
  const { chainId, timeout = 0, ...rest } = parameters;
1613
1619
  const client = config.getClient({ chainId });
@@ -1688,6 +1694,36 @@ function useEnsureCorrectChain() {
1688
1694
  }
1689
1695
 
1690
1696
  // src/sdk/hooks/use-deposit.ts
1697
+ var STALE_MS = 30 * 60 * 1e3;
1698
+ function storageKey(address) {
1699
+ return `privana:pending-deposit:${address.toLowerCase()}`;
1700
+ }
1701
+ function savePendingDeposit(address, data) {
1702
+ try {
1703
+ sessionStorage.setItem(storageKey(address), JSON.stringify(data));
1704
+ } catch {
1705
+ }
1706
+ }
1707
+ function loadPendingDeposit(address) {
1708
+ try {
1709
+ const raw = sessionStorage.getItem(storageKey(address));
1710
+ if (!raw) return null;
1711
+ const data = JSON.parse(raw);
1712
+ if (Date.now() - data.savedAt > STALE_MS) {
1713
+ clearPendingDeposit(address);
1714
+ return null;
1715
+ }
1716
+ return data;
1717
+ } catch {
1718
+ return null;
1719
+ }
1720
+ }
1721
+ function clearPendingDeposit(address) {
1722
+ try {
1723
+ sessionStorage.removeItem(storageKey(address));
1724
+ } catch {
1725
+ }
1726
+ }
1691
1727
  function useDeposit(options = {}) {
1692
1728
  const { address } = useAccount();
1693
1729
  const { client, enabledTokens, getChainById: getChainById2 } = usePrivanaContext();
@@ -1755,13 +1791,19 @@ function useDeposit(options = {}) {
1755
1791
  const isSendingTx = isWritingContract || isSendingNative;
1756
1792
  const sendError = writeError ?? sendNativeError;
1757
1793
  const { ensureCorrectChain } = useEnsureCorrectChain();
1794
+ const invalidateGeneration = useCallback(() => {
1795
+ generationRef.current++;
1796
+ }, []);
1758
1797
  useEffect(() => {
1759
1798
  return () => {
1799
+ invalidateGeneration();
1760
1800
  if (pollIntervalRef.current) {
1761
1801
  clearTimeout(pollIntervalRef.current);
1802
+ pollIntervalRef.current = null;
1762
1803
  }
1763
1804
  };
1764
- }, []);
1805
+ }, [invalidateGeneration]);
1806
+ const resumedAddressRef = useRef(void 0);
1765
1807
  const stopPolling = useCallback(() => {
1766
1808
  if (pollIntervalRef.current) {
1767
1809
  clearTimeout(pollIntervalRef.current);
@@ -1771,6 +1813,7 @@ function useDeposit(options = {}) {
1771
1813
  const reset = useCallback(() => {
1772
1814
  generationRef.current++;
1773
1815
  stopPolling();
1816
+ if (address) clearPendingDeposit(address);
1774
1817
  verificationContextRef.current = null;
1775
1818
  setDepositAddress(null);
1776
1819
  setTxHash(void 0);
@@ -1783,7 +1826,7 @@ function useDeposit(options = {}) {
1783
1826
  addressMutation.reset();
1784
1827
  resetWriteContract();
1785
1828
  resetSendTransaction();
1786
- }, [addressMutation, resetWriteContract, resetSendTransaction, stopPolling]);
1829
+ }, [address, addressMutation, resetWriteContract, resetSendTransaction, stopPolling]);
1787
1830
  const runVerification = useCallback(
1788
1831
  async (ctx, generation) => {
1789
1832
  const isStale = () => generation !== generationRef.current;
@@ -1811,6 +1854,7 @@ function useDeposit(options = {}) {
1811
1854
  if (triggerResult.status === "credited") {
1812
1855
  setIsWaitingForProcessing(false);
1813
1856
  verificationContextRef.current = null;
1857
+ if (address) clearPendingDeposit(address);
1814
1858
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
1815
1859
  queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
1816
1860
  onCreditedRef.current?.(hash, triggerResult);
@@ -1844,6 +1888,7 @@ function useDeposit(options = {}) {
1844
1888
  stopPolling();
1845
1889
  setIsWaitingForProcessing(false);
1846
1890
  verificationContextRef.current = null;
1891
+ if (address) clearPendingDeposit(address);
1847
1892
  queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
1848
1893
  queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
1849
1894
  onCreditedRef.current?.(hash, result);
@@ -1883,7 +1928,7 @@ function useDeposit(options = {}) {
1883
1928
  );
1884
1929
  }
1885
1930
  },
1886
- [client, executePrivateRead, pollInterval, pollTimeout, queryClient, stopPolling]
1931
+ [address, client, executePrivateRead, pollInterval, pollTimeout, queryClient, stopPolling]
1887
1932
  );
1888
1933
  const retryVerification = useCallback(async () => {
1889
1934
  const ctx = verificationContextRef.current;
@@ -1895,6 +1940,55 @@ function useDeposit(options = {}) {
1895
1940
  const generation = generationRef.current;
1896
1941
  await runVerification(ctx, generation);
1897
1942
  }, [runVerification, stopPolling]);
1943
+ useEffect(() => {
1944
+ if (!address || resumedAddressRef.current === address) return;
1945
+ const persisted = loadPendingDeposit(address);
1946
+ if (!persisted) return;
1947
+ resumedAddressRef.current = address;
1948
+ const hash = persisted.txHash;
1949
+ setTxHash(hash);
1950
+ setDepositAddress(persisted.depositAddress);
1951
+ setIsWaitingForConfirmation(true);
1952
+ const ctx = {
1953
+ hash,
1954
+ chainId: persisted.chainId,
1955
+ amount: BigInt(persisted.amount)
1956
+ };
1957
+ verificationContextRef.current = ctx;
1958
+ const generation = ++generationRef.current;
1959
+ const isStale = () => generation !== generationRef.current;
1960
+ (async () => {
1961
+ try {
1962
+ let confirmed = false;
1963
+ try {
1964
+ await getTransactionReceipt(config, { hash, chainId: persisted.chainId });
1965
+ confirmed = true;
1966
+ } catch {
1967
+ }
1968
+ if (!confirmed) {
1969
+ await waitForTransactionReceipt(config, {
1970
+ hash,
1971
+ chainId: persisted.chainId,
1972
+ confirmations
1973
+ });
1974
+ }
1975
+ if (isStale()) return;
1976
+ setIsWaitingForConfirmation(false);
1977
+ onDepositSuccessRef.current?.(hash);
1978
+ queryClient.invalidateQueries({ queryKey: ["readContract"] });
1979
+ await runVerification(ctx, generation);
1980
+ } catch (err) {
1981
+ if (isStale()) return;
1982
+ setIsWaitingForConfirmation(false);
1983
+ stopPolling();
1984
+ const error2 = err instanceof Error ? err : new Error("Deposit verification failed");
1985
+ setIsWaitingForProcessing(false);
1986
+ setDepositError(error2);
1987
+ setVerificationFailed(true);
1988
+ onErrorRef.current?.(error2);
1989
+ }
1990
+ })();
1991
+ }, [address, config, confirmations, queryClient, runVerification, stopPolling]);
1898
1992
  const deposit = useCallback(
1899
1993
  async (params) => {
1900
1994
  if (verificationContextRef.current) {
@@ -1951,10 +2045,21 @@ function useDeposit(options = {}) {
1951
2045
  amount: params.amount
1952
2046
  };
1953
2047
  verificationContextRef.current = ctx;
2048
+ savePendingDeposit(address, {
2049
+ txHash: hash,
2050
+ chainId: sourceChain.id,
2051
+ amount: params.amount.toString(),
2052
+ depositAddress: addrResponse,
2053
+ savedAt: Date.now()
2054
+ });
1954
2055
  try {
1955
2056
  setIsWaitingForConfirmation(true);
1956
2057
  try {
1957
- await waitForTransactionReceipt(config, { hash, confirmations });
2058
+ await waitForTransactionReceipt(config, {
2059
+ hash,
2060
+ chainId: sourceChain.id,
2061
+ confirmations
2062
+ });
1958
2063
  } finally {
1959
2064
  if (!isStale()) setIsWaitingForConfirmation(false);
1960
2065
  }
@@ -3152,6 +3257,7 @@ function DepositForm({
3152
3257
  selectedToken,
3153
3258
  onTokenSelect,
3154
3259
  onPendingChange,
3260
+ onUnsafeToCloseChange,
3155
3261
  onSuccess
3156
3262
  }) {
3157
3263
  const { isConnected, address } = useAccount();
@@ -3180,7 +3286,7 @@ function DepositForm({
3180
3286
  }
3181
3287
  });
3182
3288
  const walletBalance = isNative ? nativeBalanceData?.value : erc20Balance;
3183
- const formattedWalletBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0";
3289
+ const formattedWalletBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0.00";
3184
3290
  const handleMaxClick = () => {
3185
3291
  if (formattedWalletBalance && parseFloat(formattedWalletBalance) > 0) {
3186
3292
  setAmount(formattedWalletBalance.replace(/[\s\u2009]/g, ""));
@@ -3247,6 +3353,9 @@ function DepositForm({
3247
3353
  useEffect(() => {
3248
3354
  onPendingChange?.(isPending && !cancelled);
3249
3355
  }, [isPending, cancelled, onPendingChange]);
3356
+ useEffect(() => {
3357
+ onUnsafeToCloseChange?.((isGettingAddress || isSendingTransaction) && !cancelled);
3358
+ }, [isGettingAddress, isSendingTransaction, cancelled, onUnsafeToCloseChange]);
3250
3359
  const handleCancel = () => {
3251
3360
  setCancelled(true);
3252
3361
  reset();
@@ -3410,7 +3519,12 @@ function DepositForm({
3410
3519
  )
3411
3520
  ] });
3412
3521
  }
3413
- function WithdrawForm({ selectedToken, onTokenSelect, onPendingChange }) {
3522
+ function WithdrawForm({
3523
+ selectedToken,
3524
+ onTokenSelect,
3525
+ onPendingChange,
3526
+ onUnsafeToCloseChange
3527
+ }) {
3414
3528
  const { isConnected, address } = useAccount();
3415
3529
  const { chains, getChainById: getChainById2 } = usePrivanaContext();
3416
3530
  const [amount, setAmount] = useState("");
@@ -3463,8 +3577,10 @@ function WithdrawForm({ selectedToken, onTokenSelect, onPendingChange }) {
3463
3577
  }
3464
3578
  }, [error]);
3465
3579
  useEffect(() => {
3466
- onPendingChange?.(isPending && !cancelled);
3467
- }, [isPending, cancelled, onPendingChange]);
3580
+ const pending = isPending && !cancelled;
3581
+ onPendingChange?.(pending);
3582
+ onUnsafeToCloseChange?.(pending);
3583
+ }, [isPending, cancelled, onPendingChange, onUnsafeToCloseChange]);
3468
3584
  const handleCancel = () => {
3469
3585
  setCancelled(true);
3470
3586
  reset();
@@ -3623,9 +3739,6 @@ function CloseIcon() {
3623
3739
  }
3624
3740
  ) });
3625
3741
  }
3626
- function SearchIcon() {
3627
- return /* @__PURE__ */ jsx("div", { className: "h-3 w-3 rounded-full border-[1.5px] border-current" });
3628
- }
3629
3742
  function ChevronRight() {
3630
3743
  return /* @__PURE__ */ jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "-rotate-90", children: /* @__PURE__ */ jsx(
3631
3744
  "path",
@@ -3686,7 +3799,7 @@ function BalanceCards({
3686
3799
  });
3687
3800
  const { totalLocked, isLoading: lockedLoading } = useLockedFunds({ enabled: showLockedFunds });
3688
3801
  const formattedBalance = formatTokenAmount(balanceWei, selectedToken.decimals);
3689
- const formattedLocked = showLockedFunds ? formatTokenAmount(String(totalLocked), selectedToken.decimals) : "0";
3802
+ const formattedLocked = showLockedFunds ? formatTokenAmount(String(totalLocked), selectedToken.decimals) : "0.00";
3690
3803
  return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-2", disabled && "opacity-50"), children: [
3691
3804
  /* @__PURE__ */ jsxs(
3692
3805
  "button",
@@ -3749,7 +3862,7 @@ function Tabs({
3749
3862
  "div",
3750
3863
  {
3751
3864
  className: cn(
3752
- "bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-4px)] rounded-md transition-transform duration-200",
3865
+ "bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-8px)] rounded-md transition-transform duration-200",
3753
3866
  activeTab === "withdraw" && "translate-x-[calc(100%+8px)]"
3754
3867
  )
3755
3868
  }
@@ -3784,7 +3897,7 @@ function Tabs({
3784
3897
  }
3785
3898
  );
3786
3899
  }
3787
- function LockedFundsView({ onBack, onClose }) {
3900
+ function LockedFundsView({ onBack }) {
3788
3901
  const { getTokenById } = usePrivanaContext();
3789
3902
  const { locks, isLoading } = useLockedFunds();
3790
3903
  const { unlockFunds, unlockAllExpired, isPending } = useUnlockFunds();
@@ -3817,27 +3930,17 @@ function LockedFundsView({ onBack, onClose }) {
3817
3930
  };
3818
3931
  const expiredCount = locks.filter((l) => l.is_expired).length;
3819
3932
  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(
3933
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
3934
+ /* @__PURE__ */ jsx(
3833
3935
  "button",
3834
3936
  {
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, {})
3937
+ onClick: onBack,
3938
+ className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
3939
+ children: /* @__PURE__ */ jsx(ChevronLeft, {})
3838
3940
  }
3839
- )
3840
- ] }),
3941
+ ),
3942
+ /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Locked Funds" })
3943
+ ] }) }),
3841
3944
  /* @__PURE__ */ jsxs("div", { className: "bg-muted flex min-h-0 flex-1 flex-col rounded-[10px] p-2", children: [
3842
3945
  /* @__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
3946
  /* @__PURE__ */ jsx("div", { className: "bg-secondary h-10 w-10 rounded-full" }),
@@ -3922,34 +4025,24 @@ function BalanceTokenRow({ token }) {
3922
4025
  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
4026
  ] });
3924
4027
  }
3925
- function BalanceDetailsView({ onBack, onClose }) {
4028
+ function BalanceDetailsView({ onBack }) {
3926
4029
  const { enabledTokens, chains } = usePrivanaContext();
3927
4030
  const [selectedChainId, setSelectedChainId] = useState(chains[0]?.id ?? 84532);
3928
4031
  const chainTokens = useMemo(() => {
3929
4032
  return enabledTokens.filter((t) => t.chainId === selectedChainId);
3930
4033
  }, [enabledTokens, selectedChainId]);
3931
4034
  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(
4035
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4036
+ /* @__PURE__ */ jsx(
3945
4037
  "button",
3946
4038
  {
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, {})
4039
+ onClick: onBack,
4040
+ className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4041
+ children: /* @__PURE__ */ jsx(ChevronLeft, {})
3950
4042
  }
3951
- )
3952
- ] }),
4043
+ ),
4044
+ /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Token Balances" })
4045
+ ] }) }),
3953
4046
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
3954
4047
  /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
3955
4048
  /* @__PURE__ */ jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Network" }) }),
@@ -4019,48 +4112,30 @@ function TokenRow({
4019
4112
  }
4020
4113
  function TokenSelectorView({
4021
4114
  onBack,
4022
- onClose,
4023
4115
  onSelect,
4024
4116
  selectedTokenId
4025
4117
  }) {
4026
- const [tokenSearch, setTokenSearch] = useState("");
4027
4118
  const { enabledTokens, chains } = usePrivanaContext();
4028
4119
  const [selectedChainId, setSelectedChainId] = useState(chains[0]?.id ?? 84532);
4029
4120
  const chainTokens = useMemo(() => {
4030
4121
  return enabledTokens.filter((t) => t.chainId === selectedChainId);
4031
4122
  }, [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
4123
  const handleTokenSelect = (token) => {
4039
4124
  onSelect(token);
4040
4125
  onBack();
4041
4126
  };
4042
4127
  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(
4128
+ /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
4129
+ /* @__PURE__ */ jsx(
4056
4130
  "button",
4057
4131
  {
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, {})
4132
+ onClick: onBack,
4133
+ className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4134
+ children: /* @__PURE__ */ jsx(ChevronLeft, {})
4061
4135
  }
4062
- )
4063
- ] }),
4136
+ ),
4137
+ /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Select Token" })
4138
+ ] }) }),
4064
4139
  /* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
4065
4140
  /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
4066
4141
  /* @__PURE__ */ jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Network" }) }),
@@ -4084,23 +4159,8 @@ function TokenSelectorView({
4084
4159
  }) })
4085
4160
  ] }),
4086
4161
  /* @__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(
4162
+ /* @__PURE__ */ jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Token" }) }),
4163
+ /* @__PURE__ */ jsx("div", { className: "mt-2 flex-1 overflow-y-auto", children: chainTokens.map((token) => /* @__PURE__ */ jsx(
4104
4164
  TokenRow,
4105
4165
  {
4106
4166
  token,
@@ -4114,9 +4174,8 @@ function TokenSelectorView({
4114
4174
  ] });
4115
4175
  }
4116
4176
  function ModalBody({
4117
- onClose,
4118
4177
  onViewChange,
4119
- onTransactionPendingChange,
4178
+ onCloseBlockedChange,
4120
4179
  showLockedFunds = true,
4121
4180
  defaultTab = "deposit",
4122
4181
  onDepositSuccess
@@ -4125,65 +4184,78 @@ function ModalBody({
4125
4184
  const [selectedToken, setSelectedToken] = useState(defaultToken);
4126
4185
  const [activeTab, setActiveTab] = useState(defaultTab);
4127
4186
  const [currentView, setCurrentView] = useState("main");
4128
- const [isTransactionPending, setIsTransactionPending] = useState(false);
4187
+ const [isInteractionPending, setIsInteractionPending] = useState(false);
4129
4188
  useEffect(() => {
4130
4189
  if (!selectedToken && defaultToken) {
4131
4190
  setSelectedToken(defaultToken);
4132
4191
  }
4133
4192
  }, [selectedToken, defaultToken]);
4134
- const handleTransactionPendingChange = (isPending) => {
4135
- setIsTransactionPending(isPending);
4136
- onTransactionPendingChange?.(isPending);
4193
+ const handlePendingChange = (isPending) => {
4194
+ setIsInteractionPending(isPending);
4195
+ };
4196
+ const handleCloseBlockedChange = (isBlocked) => {
4197
+ onCloseBlockedChange?.(isBlocked);
4137
4198
  };
4138
4199
  const handleViewChange = (view) => {
4139
- setCurrentView(view);
4140
- onViewChange?.(view);
4200
+ const update = () => {
4201
+ setCurrentView(view);
4202
+ onViewChange?.(view);
4203
+ };
4204
+ if (typeof document !== "undefined" && typeof document.startViewTransition === "function") {
4205
+ document.documentElement.dataset.privanaVtDir = view === "main" ? "back" : "forward";
4206
+ const transition = document.startViewTransition(update);
4207
+ transition.finished.then(() => {
4208
+ delete document.documentElement.dataset.privanaVtDir;
4209
+ });
4210
+ } else {
4211
+ update();
4212
+ }
4141
4213
  };
4142
4214
  const handleTokenSelect = (token) => {
4143
4215
  setSelectedToken(token);
4144
4216
  };
4145
4217
  if (tokensStatus === "loading" || !selectedToken) {
4146
- return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 pb-4", children: [
4218
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
4147
4219
  /* @__PURE__ */ jsx("div", { className: "bg-secondary h-25 animate-pulse rounded-[10px]" }),
4148
4220
  /* @__PURE__ */ jsx("div", { className: "bg-secondary h-11 animate-pulse rounded-[10px]" }),
4149
4221
  /* @__PURE__ */ jsx("div", { className: "bg-secondary h-50 animate-pulse rounded-[10px]" })
4150
4222
  ] });
4151
4223
  }
4152
4224
  if (currentView === "locked-funds") {
4153
- return /* @__PURE__ */ jsx(LockedFundsView, { onBack: () => handleViewChange("main"), onClose });
4225
+ return /* @__PURE__ */ jsx(LockedFundsView, { onBack: () => handleViewChange("main") });
4154
4226
  }
4155
4227
  if (currentView === "balance-details") {
4156
- return /* @__PURE__ */ jsx(BalanceDetailsView, { onBack: () => handleViewChange("main"), onClose });
4228
+ return /* @__PURE__ */ jsx(BalanceDetailsView, { onBack: () => handleViewChange("main") });
4157
4229
  }
4158
4230
  if (currentView === "select-token") {
4159
4231
  return /* @__PURE__ */ jsx(
4160
4232
  TokenSelectorView,
4161
4233
  {
4162
4234
  onBack: () => handleViewChange("main"),
4163
- onClose,
4164
4235
  onSelect: handleTokenSelect,
4165
4236
  selectedTokenId: selectedToken.id
4166
4237
  }
4167
4238
  );
4168
4239
  }
4169
4240
  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(
4241
+ /* @__PURE__ */ jsx("div", { className: cn(isInteractionPending && "pointer-events-none"), children: /* @__PURE__ */ jsx(
4171
4242
  BalanceCards,
4172
4243
  {
4173
4244
  selectedToken,
4174
4245
  onLockedFundsClick: () => handleViewChange("locked-funds"),
4175
4246
  onBalanceClick: () => handleViewChange("balance-details"),
4176
4247
  showLockedFunds,
4177
- disabled: isTransactionPending
4248
+ disabled: isInteractionPending
4178
4249
  }
4179
4250
  ) }),
4180
- /* @__PURE__ */ jsx("div", { className: cn(isTransactionPending && "pointer-events-none"), children: /* @__PURE__ */ jsx(Tabs, { activeTab, onTabChange: setActiveTab, disabled: isTransactionPending }) }),
4251
+ /* @__PURE__ */ jsx("div", { className: cn(isInteractionPending && "pointer-events-none"), children: /* @__PURE__ */ jsx(Tabs, { activeTab, onTabChange: setActiveTab, disabled: isInteractionPending }) }),
4181
4252
  /* @__PURE__ */ jsx("div", { className: "bg-muted rounded-[10px] p-5", children: activeTab === "deposit" ? /* @__PURE__ */ jsx(
4182
4253
  DepositForm,
4183
4254
  {
4184
4255
  selectedToken,
4185
4256
  onTokenSelect: () => handleViewChange("select-token"),
4186
- onPendingChange: handleTransactionPendingChange,
4257
+ onPendingChange: handlePendingChange,
4258
+ onUnsafeToCloseChange: handleCloseBlockedChange,
4187
4259
  onSuccess: onDepositSuccess
4188
4260
  }
4189
4261
  ) : /* @__PURE__ */ jsx(
@@ -4191,7 +4263,8 @@ function ModalBody({
4191
4263
  {
4192
4264
  selectedToken,
4193
4265
  onTokenSelect: () => handleViewChange("select-token"),
4194
- onPendingChange: handleTransactionPendingChange
4266
+ onPendingChange: handlePendingChange,
4267
+ onUnsafeToCloseChange: handleCloseBlockedChange
4195
4268
  }
4196
4269
  ) })
4197
4270
  ] }) });
@@ -4203,55 +4276,84 @@ function PrivanaModal({
4203
4276
  defaultTab,
4204
4277
  onDepositSuccess
4205
4278
  }) {
4206
- const [isTransactionPending, setIsTransactionPending] = useState(false);
4279
+ const [currentView, setCurrentView] = useState("main");
4207
4280
  const titleId = useId();
4208
4281
  const descId = useId();
4209
- const handleOpenChange = (isOpen) => {
4210
- if (!isOpen && isTransactionPending) {
4211
- return;
4212
- }
4213
- if (!isOpen) {
4214
- onClose();
4215
- }
4282
+ const [isCloseBlocked, setIsCloseBlocked] = useState(false);
4283
+ const handleClose = () => {
4284
+ if (!isCloseBlocked) onClose();
4216
4285
  };
4217
- return /* @__PURE__ */ jsx(Dialog, { open, onOpenChange: handleOpenChange, children: /* @__PURE__ */ jsxs(
4218
- DialogContent,
4286
+ return /* @__PURE__ */ jsx(
4287
+ Dialog,
4219
4288
  {
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(
4289
+ open,
4290
+ onOpenChange: (isOpen) => {
4291
+ if (!isOpen) handleClose();
4292
+ },
4293
+ children: /* @__PURE__ */ jsxs(
4294
+ DialogContent,
4295
+ {
4296
+ "data-privana": true,
4297
+ "data-view": currentView,
4298
+ showCloseButton: false,
4299
+ onInteractOutside: isCloseBlocked ? (e) => e.preventDefault() : void 0,
4300
+ onEscapeKeyDown: isCloseBlocked ? (e) => e.preventDefault() : void 0,
4301
+ 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]",
4302
+ "aria-labelledby": titleId,
4303
+ "aria-describedby": descId,
4304
+ children: [
4305
+ /* @__PURE__ */ jsx(
4233
4306
  "button",
4234
4307
  {
4235
- onClick: onClose,
4236
- className: "text-muted-foreground hover:text-foreground flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
4308
+ "data-privana-close": true,
4309
+ onClick: handleClose,
4310
+ disabled: isCloseBlocked,
4311
+ "aria-label": "Close",
4312
+ className: cn(
4313
+ "absolute top-6 right-5 z-20 flex h-6 w-6 items-center justify-center transition-colors",
4314
+ isCloseBlocked ? "text-muted-foreground/40 cursor-not-allowed" : "text-muted-foreground hover:text-foreground cursor-pointer"
4315
+ ),
4237
4316
  children: /* @__PURE__ */ jsx(CloseIcon, {})
4238
4317
  }
4318
+ ),
4319
+ /* @__PURE__ */ jsxs(
4320
+ "div",
4321
+ {
4322
+ "data-privana-content": true,
4323
+ "data-view": currentView,
4324
+ className: "flex min-h-0 flex-1 flex-col gap-2",
4325
+ children: [
4326
+ /* @__PURE__ */ jsx(DialogTitle, { id: titleId, className: "sr-only", children: "Privana" }),
4327
+ /* @__PURE__ */ jsx(DialogDescription, { id: descId, className: "sr-only", children: "Deposit or withdraw tokens from your Privana" }),
4328
+ 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" }) }) }),
4329
+ /* @__PURE__ */ jsx(
4330
+ ModalBody,
4331
+ {
4332
+ onCloseBlockedChange: setIsCloseBlocked,
4333
+ onViewChange: setCurrentView,
4334
+ showLockedFunds,
4335
+ defaultTab,
4336
+ onDepositSuccess
4337
+ }
4338
+ )
4339
+ ]
4340
+ }
4341
+ ),
4342
+ /* @__PURE__ */ jsx(
4343
+ "a",
4344
+ {
4345
+ "data-privana-footer": true,
4346
+ href: "https://privana.finance",
4347
+ target: "_blank",
4348
+ rel: "noopener noreferrer",
4349
+ "aria-label": "Powered by Privana"
4350
+ }
4239
4351
  )
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
- ]
4352
+ ]
4353
+ }
4354
+ )
4253
4355
  }
4254
- ) });
4356
+ );
4255
4357
  }
4256
4358
  function PrivanaInlineModal({
4257
4359
  className,