@oasisprotocol/privana-sdk 0.5.2 → 0.5.3
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/README.md +34 -0
- package/dist/{chunk-UHXVYWTF.cjs → chunk-7KEQHWZB.cjs} +902 -240
- package/dist/chunk-7KEQHWZB.cjs.map +1 -0
- package/dist/{chunk-ZVUMV4NR.js → chunk-DZURIIRE.js} +898 -242
- package/dist/chunk-DZURIIRE.js.map +1 -0
- package/dist/index.cjs +1356 -336
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +1 -1
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +24 -11
- package/dist/index.d.ts +24 -11
- package/dist/index.js +1116 -104
- package/dist/index.js.map +1 -1
- package/dist/on-ramp.cjs +3 -3
- package/dist/on-ramp.d.cts +5 -4
- package/dist/on-ramp.d.ts +5 -4
- package/dist/on-ramp.js +1 -1
- package/dist/{pending-lock-BdkuMjmY.d.cts → pending-lock-f4bWWTyN.d.cts} +41 -7
- package/dist/{pending-lock-BdkuMjmY.d.ts → pending-lock-f4bWWTyN.d.ts} +41 -7
- package/package.json +1 -1
- package/dist/chunk-UHXVYWTF.cjs.map +0 -1
- package/dist/chunk-ZVUMV4NR.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { usePrivanaContext, createHostedAuthPendingStorageKey, clearHostedAuthPendingTransaction, HostedAuthRequiredError, HostedAuthError, createPkceVerifier, createPkceChallenge, createHostedAuthState, persistHostedAuthPendingTransaction, parseHostedAuthCallback, readHostedAuthPendingTransaction, HostedAuthStateMismatchError, buildHostedAuthSession, useSafePrivanaContext, usePrivateReadRequest, formatTokenAmount, submitPendingLock, PostDepositLockError, useDepositVerification, useEnsureCorrectChain, getTransactionReceipt, getBlockNumber, waitForTransactionReceipt, canUseBrowserStorage, clampLockAmount, getWalletClient, createSignedLockRequest, signWithdrawMessage, signLockMessage, signTransferMessage, signTransferLockedMessage, signModifyLockMessage, cn, Button, stripHostedAuthCallbackParams, AccountingApiError, getBrowserStorageItem, removeBrowserStorageItem, isSignedLockUsable, setBrowserStorageItem, shortenAddress, formatTimeRemaining, parseTokenAmount, getExplorerAddressUrl, getExplorerLabel, Skeleton, formatCountdown, FiatOnRampForm } from './chunk-
|
|
3
|
-
export { AccountingApiError, Button, DEFAULT_LOCK_DURATION_SECONDS, DEFAULT_ONRAMP_LOCK_BUFFER, HOSTED_AUTH_CLOCK_SKEW_MS, HostedAuthError, HostedAuthRequiredError, HostedAuthStateMismatchError, HttpClient, LOCK_TYPES, MODIFY_LOCK_TYPES, NETWORK_CONFIG, NetworkError, PostDepositLockError, PrivanaClient, PrivanaProvider, SUPPORTED_CHAINS, SiweAuthProvider, Skeleton, TRANSFER_LOCKED_TYPES, TRANSFER_TYPES, ValidationError, WITHDRAW_FROM_LOCK_TYPES, WITHDRAW_TYPES, applyLockBuffer, applyRefreshResponse, buildHostedAuthSession, buildSiweStatement, buttonVariants, clampLockAmount, clearHostedAuthPendingTransaction, clearPendingLock, createDomain, createHostedAuthPendingStorageKey, createHostedAuthState, createHostedAuthStorageKey, createLockExpiry, createPkceChallenge, createPkceVerifier, createSignedLockRequest, getAccountingContract, getApiUrl, getChainById, getChainId, getExplorerAddressUrl, getExplorerLabel, isHostedAuthRefreshActive, isHostedAuthSessionActive, isSignedLockUsable, loadPendingLock, normalizeAddress, normalizeHex, parseHostedAuthCallback, persistHostedAuthPendingTransaction, readHostedAuthPendingTransaction, readStoredHostedAuthSession, savePendingLock, signLockMessage, signModifyLockMessage, signTransferLockedMessage, signTransferMessage, signWithdrawFromLockMessage, signWithdrawMessage, stripHostedAuthCallbackParams, submitPendingLock, syncHostedAuthSessionToClient, useDepositVerification, usePrivanaContext, useSafeAccount, useSafePrivanaContext, useSiweAuth } from './chunk-
|
|
2
|
+
import { usePrivanaContext, createHostedAuthPendingStorageKey, clearHostedAuthPendingTransaction, HostedAuthRequiredError, HostedAuthError, createPkceVerifier, createPkceChallenge, createHostedAuthState, persistHostedAuthPendingTransaction, parseHostedAuthCallback, readHostedAuthPendingTransaction, HostedAuthStateMismatchError, buildHostedAuthSession, useSafePrivanaContext, usePrivateReadRequest, formatTokenAmount, submitPendingLock, PostDepositLockError, useDepositVerification, useEnsureCorrectChain, getTransactionReceipt, getBlockNumber, waitForTransactionReceipt, canUseBrowserStorage, clampLockAmount, requireDepositLockOwner, getWalletClient, createSignedLockRequest, requireServiceAddress, signWithdrawMessage, signLockMessage, signTransferMessage, signTransferLockedMessage, signModifyLockMessage, cn, Button, stripHostedAuthCallbackParams, AccountingApiError, getBrowserStorageItem, removeBrowserStorageItem, isSignedLockUsable, setBrowserStorageItem, shortenAddress, formatTimeRemaining, parseTokenAmount, getExplorerAddressUrl, getExplorerLabel, canUseSharedBrowserStorage, DEFAULT_LOCK_DURATION_SECONDS, Skeleton, formatCountdown, FiatOnRampForm, getSharedBrowserStorageItem, removeSharedBrowserStorageItem, setSharedBrowserStorageItem } from './chunk-DZURIIRE.js';
|
|
3
|
+
export { AccountingApiError, Button, DEFAULT_LOCK_DURATION_SECONDS, DEFAULT_ONRAMP_LOCK_BUFFER, HOSTED_AUTH_CLOCK_SKEW_MS, HostedAuthError, HostedAuthRequiredError, HostedAuthStateMismatchError, HttpClient, LOCK_TYPES, MODIFY_LOCK_TYPES, NETWORK_CONFIG, NetworkError, PostDepositLockError, PrivanaClient, PrivanaProvider, SUPPORTED_CHAINS, SiweAuthProvider, Skeleton, TRANSFER_LOCKED_TYPES, TRANSFER_TYPES, ValidationError, WITHDRAW_FROM_LOCK_TYPES, WITHDRAW_TYPES, applyLockBuffer, applyRefreshResponse, buildHostedAuthSession, buildSiweStatement, buttonVariants, clampLockAmount, clearHostedAuthPendingTransaction, clearPendingLock, createDomain, createHostedAuthPendingStorageKey, createHostedAuthState, createHostedAuthStorageKey, createLockExpiry, createPkceChallenge, createPkceVerifier, createSignedLockRequest, getAccountingContract, getApiUrl, getChainById, getChainId, getExplorerAddressUrl, getExplorerLabel, isHostedAuthRefreshActive, isHostedAuthSessionActive, isSignedLockUsable, loadPendingLock, normalizeAddress, normalizeHex, parseHostedAuthCallback, persistHostedAuthPendingTransaction, readHostedAuthPendingTransaction, readStoredHostedAuthSession, requireDepositLockOwner, requireServiceAddress, savePendingLock, signLockMessage, signModifyLockMessage, signTransferLockedMessage, signTransferMessage, signWithdrawFromLockMessage, signWithdrawMessage, stripHostedAuthCallbackParams, submitPendingLock, syncHostedAuthSessionToClient, useDepositVerification, usePrivanaContext, useSafeAccount, useSafePrivanaContext, useSiweAuth } from './chunk-DZURIIRE.js';
|
|
4
4
|
import { useState, useRef, useMemo, useCallback, useContext, useEffect, useId } from 'react';
|
|
5
5
|
import { QueryClientContext, useQuery, useQueryClient, useMutation, keepPreviousData } from '@tanstack/react-query';
|
|
6
6
|
import { useAccount, useWalletClient, useConfig, useWriteContract, useSendTransaction, useBalance as useBalance$1, useReadContract } from 'wagmi';
|
|
7
|
-
import { zeroAddress, erc20Abi } from 'viem';
|
|
7
|
+
import { zeroAddress, erc20Abi, formatUnits } from 'viem';
|
|
8
8
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
9
9
|
import { XIcon } from 'lucide-react';
|
|
10
10
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
@@ -234,7 +234,7 @@ function useBalance(options = {}) {
|
|
|
234
234
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
235
235
|
if (!tokenId) throw new Error("No token ID provided");
|
|
236
236
|
if (!client) throw new Error("No accounting client");
|
|
237
|
-
return executePrivateRead(() =>
|
|
237
|
+
return executePrivateRead((readClient) => readClient.getBalance(tokenId));
|
|
238
238
|
},
|
|
239
239
|
enabled: hasProviders && (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!tokenId && !!client,
|
|
240
240
|
refetchInterval: pollingInterval
|
|
@@ -259,7 +259,9 @@ function useBatchBalances(options) {
|
|
|
259
259
|
queryKey: ["accounting-batch-balances", ...privateReadQueryScope, options.tokenIds],
|
|
260
260
|
queryFn: async () => {
|
|
261
261
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
262
|
-
return executePrivateRead(
|
|
262
|
+
return executePrivateRead(
|
|
263
|
+
(readClient) => readClient.getBatchBalances({ token_ids: options.tokenIds })
|
|
264
|
+
);
|
|
263
265
|
},
|
|
264
266
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && options.tokenIds.length > 0 && !!client,
|
|
265
267
|
refetchInterval: pollingInterval
|
|
@@ -313,7 +315,9 @@ function useDeposit(options = {}) {
|
|
|
313
315
|
const { data: walletClient } = useWalletClient();
|
|
314
316
|
const queryClient = useQueryClient();
|
|
315
317
|
const config = useConfig();
|
|
316
|
-
const { executePrivateRead } = usePrivateReadRequest();
|
|
318
|
+
const { executePrivateRead, privateReadAddress } = usePrivateReadRequest();
|
|
319
|
+
const privateReadAddressRef = useRef(privateReadAddress);
|
|
320
|
+
privateReadAddressRef.current = privateReadAddress;
|
|
317
321
|
const confirmations = options.confirmations ?? 15;
|
|
318
322
|
const [depositAddress, setDepositAddress] = useState(null);
|
|
319
323
|
const [txHash, setTxHash] = useState();
|
|
@@ -349,12 +353,12 @@ function useDeposit(options = {}) {
|
|
|
349
353
|
]);
|
|
350
354
|
const pendingLockRef = useRef(null);
|
|
351
355
|
const submitPendingLockAfterCredit = useCallback(
|
|
352
|
-
async (signedLock,
|
|
356
|
+
async (signedLock, creditedAmount) => {
|
|
353
357
|
try {
|
|
354
358
|
const result = await submitPendingLock({
|
|
355
359
|
client,
|
|
356
360
|
payload: signedLock,
|
|
357
|
-
creditedAmount
|
|
361
|
+
creditedAmount
|
|
358
362
|
});
|
|
359
363
|
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
360
364
|
queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
|
|
@@ -366,7 +370,7 @@ function useDeposit(options = {}) {
|
|
|
366
370
|
err instanceof Error ? err.message : "Lock submission failed",
|
|
367
371
|
"submission-failed",
|
|
368
372
|
BigInt(signedLock.amount),
|
|
369
|
-
|
|
373
|
+
creditedAmount,
|
|
370
374
|
{ cause: err }
|
|
371
375
|
);
|
|
372
376
|
(onLockFailedRef.current ?? onErrorRef.current)?.(error2);
|
|
@@ -384,12 +388,12 @@ function useDeposit(options = {}) {
|
|
|
384
388
|
} = useDepositVerification({
|
|
385
389
|
pollInterval: options.pollInterval,
|
|
386
390
|
pollTimeout: options.pollTimeout,
|
|
387
|
-
onCredited: (hash, response) => {
|
|
391
|
+
onCredited: (hash, response, creditedAmount) => {
|
|
388
392
|
verificationContextRef.current = null;
|
|
389
393
|
const signedLock = pendingLockRef.current;
|
|
390
394
|
pendingLockRef.current = null;
|
|
391
395
|
if (signedLock) {
|
|
392
|
-
void submitPendingLockAfterCredit(signedLock,
|
|
396
|
+
void submitPendingLockAfterCredit(signedLock, creditedAmount).finally(() => {
|
|
393
397
|
if (address) clearPendingDeposit(address, hash);
|
|
394
398
|
});
|
|
395
399
|
} else if (address) {
|
|
@@ -407,7 +411,7 @@ function useDeposit(options = {}) {
|
|
|
407
411
|
const addressMutation = useMutation({
|
|
408
412
|
mutationFn: async () => {
|
|
409
413
|
if (!address) throw new Error("No wallet connected");
|
|
410
|
-
return executePrivateRead(() =>
|
|
414
|
+
return executePrivateRead((readClient) => readClient.getDepositAddress());
|
|
411
415
|
},
|
|
412
416
|
onSuccess: (data) => {
|
|
413
417
|
setDepositAddress(data);
|
|
@@ -545,6 +549,7 @@ function useDeposit(options = {}) {
|
|
|
545
549
|
const lockAmount = clampLockAmount(params.amount, params.postDepositLock?.maxAmount);
|
|
546
550
|
let signedLock;
|
|
547
551
|
if (params.postDepositLock) {
|
|
552
|
+
const lockOwner = requireDepositLockOwner(address, privateReadAddress);
|
|
548
553
|
setIsSwitchingChain(true);
|
|
549
554
|
try {
|
|
550
555
|
await ensureCorrectChain(networkConfig.chainId);
|
|
@@ -558,7 +563,7 @@ function useDeposit(options = {}) {
|
|
|
558
563
|
signedLock = await createSignedLockRequest({
|
|
559
564
|
client,
|
|
560
565
|
walletClient: signingWalletClient,
|
|
561
|
-
userAddress:
|
|
566
|
+
userAddress: lockOwner,
|
|
562
567
|
networkConfig,
|
|
563
568
|
serviceAddress: requireServiceAddress(
|
|
564
569
|
params.postDepositLock.serviceAddress ?? serviceAddress
|
|
@@ -567,6 +572,9 @@ function useDeposit(options = {}) {
|
|
|
567
572
|
amount: lockAmount,
|
|
568
573
|
lockDuration: params.postDepositLock.lockDuration
|
|
569
574
|
});
|
|
575
|
+
if (privateReadAddressRef.current?.toLowerCase() !== lockOwner.toLowerCase()) {
|
|
576
|
+
throw new Error("Authenticated deposit account changed while signing");
|
|
577
|
+
}
|
|
570
578
|
}
|
|
571
579
|
if (isStale()) return;
|
|
572
580
|
setIsSwitchingChain(true);
|
|
@@ -649,6 +657,7 @@ function useDeposit(options = {}) {
|
|
|
649
657
|
enabledTokens,
|
|
650
658
|
ensureCorrectChain,
|
|
651
659
|
networkConfig,
|
|
660
|
+
privateReadAddress,
|
|
652
661
|
queryClient,
|
|
653
662
|
serviceAddress,
|
|
654
663
|
writeContractAsync,
|
|
@@ -685,12 +694,6 @@ function useDeposit(options = {}) {
|
|
|
685
694
|
reset
|
|
686
695
|
};
|
|
687
696
|
}
|
|
688
|
-
function requireServiceAddress(serviceAddress) {
|
|
689
|
-
if (!serviceAddress) {
|
|
690
|
-
throw new Error("Service address not configured");
|
|
691
|
-
}
|
|
692
|
-
return serviceAddress;
|
|
693
|
-
}
|
|
694
697
|
function useDepositAddress(options = {}) {
|
|
695
698
|
const queryClient = useContext(QueryClientContext);
|
|
696
699
|
const accountingContext = useSafePrivanaContext();
|
|
@@ -703,11 +706,12 @@ function useDepositAddress(options = {}) {
|
|
|
703
706
|
queryFn: async () => {
|
|
704
707
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
705
708
|
if (!client) throw new Error("No accounting client");
|
|
706
|
-
return executePrivateRead(() =>
|
|
709
|
+
return executePrivateRead((readClient) => readClient.getDepositAddress());
|
|
707
710
|
},
|
|
708
711
|
enabled: isReady && (options.enabled ?? true)
|
|
709
712
|
});
|
|
710
713
|
return {
|
|
714
|
+
response: query.data,
|
|
711
715
|
depositAddress: query.data?.deposit_address ?? null,
|
|
712
716
|
isReady,
|
|
713
717
|
isLoading: query.isLoading,
|
|
@@ -1158,7 +1162,7 @@ function useLockedFunds(options = {}) {
|
|
|
1158
1162
|
queryKey: ["accounting-locked-funds", ...privateReadQueryScope, serviceAddress ?? null],
|
|
1159
1163
|
queryFn: async () => {
|
|
1160
1164
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1161
|
-
return executePrivateRead(() =>
|
|
1165
|
+
return executePrivateRead((readClient) => readClient.getLockedFunds(serviceAddress));
|
|
1162
1166
|
},
|
|
1163
1167
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!client,
|
|
1164
1168
|
refetchInterval: pollingInterval,
|
|
@@ -1183,7 +1187,7 @@ function useTotalLockedBalance(options = {}) {
|
|
|
1183
1187
|
queryFn: async () => {
|
|
1184
1188
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1185
1189
|
if (!tokenId) throw new Error("No token ID provided");
|
|
1186
|
-
return executePrivateRead(() =>
|
|
1190
|
+
return executePrivateRead((readClient) => readClient.getTotalLockedBalance(tokenId));
|
|
1187
1191
|
},
|
|
1188
1192
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!tokenId && !!client,
|
|
1189
1193
|
refetchInterval: pollingInterval
|
|
@@ -1203,7 +1207,7 @@ function useExpiredLocks(options = {}) {
|
|
|
1203
1207
|
queryKey: ["accounting-expired-locks", ...privateReadQueryScope],
|
|
1204
1208
|
queryFn: async () => {
|
|
1205
1209
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1206
|
-
return executePrivateRead(() =>
|
|
1210
|
+
return executePrivateRead((readClient) => readClient.getExpiredLocks());
|
|
1207
1211
|
},
|
|
1208
1212
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!client,
|
|
1209
1213
|
refetchInterval: pollingInterval
|
|
@@ -1236,7 +1240,7 @@ function usePendingDeposits(options = {}) {
|
|
|
1236
1240
|
if (!client) throw new Error("No accounting client");
|
|
1237
1241
|
try {
|
|
1238
1242
|
return await executePrivateRead(
|
|
1239
|
-
() =>
|
|
1243
|
+
(readClient) => readClient.getPendingDeposits({
|
|
1240
1244
|
chain_id: chainId,
|
|
1241
1245
|
token_address: tokenAddress,
|
|
1242
1246
|
lookback_blocks: lookbackBlocks
|
|
@@ -1317,7 +1321,7 @@ function useHistory(options = {}) {
|
|
|
1317
1321
|
queryKey: ["accounting-history", ...privateReadQueryScope, offset, limit],
|
|
1318
1322
|
queryFn: async () => {
|
|
1319
1323
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1320
|
-
return executePrivateRead(() =>
|
|
1324
|
+
return executePrivateRead((readClient) => readClient.getHistory({ offset, limit }));
|
|
1321
1325
|
},
|
|
1322
1326
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!client,
|
|
1323
1327
|
refetchInterval: pollingInterval,
|
|
@@ -1904,7 +1908,16 @@ function WarningIcon() {
|
|
|
1904
1908
|
}
|
|
1905
1909
|
) });
|
|
1906
1910
|
}
|
|
1907
|
-
function TransactionWarningView({
|
|
1911
|
+
function TransactionWarningView({
|
|
1912
|
+
title,
|
|
1913
|
+
message,
|
|
1914
|
+
onDone,
|
|
1915
|
+
actionLabel = "Done",
|
|
1916
|
+
pendingLabel = actionLabel,
|
|
1917
|
+
isPending = false,
|
|
1918
|
+
onDismiss,
|
|
1919
|
+
dismissLabel = "Dismiss"
|
|
1920
|
+
}) {
|
|
1908
1921
|
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-4", children: [
|
|
1909
1922
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1910
1923
|
/* @__PURE__ */ jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-amber-500/10 text-amber-400", children: /* @__PURE__ */ jsx(WarningIcon, {}) }),
|
|
@@ -1913,14 +1926,26 @@ function TransactionWarningView({ title, message, onDone }) {
|
|
|
1913
1926
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: message })
|
|
1914
1927
|
] })
|
|
1915
1928
|
] }),
|
|
1916
|
-
/* @__PURE__ */
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1929
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1930
|
+
onDismiss && /* @__PURE__ */ jsx(
|
|
1931
|
+
"button",
|
|
1932
|
+
{
|
|
1933
|
+
onClick: onDismiss,
|
|
1934
|
+
disabled: isPending,
|
|
1935
|
+
className: "border-border text-foreground hover:bg-secondary flex h-10 flex-1 cursor-pointer items-center justify-center rounded-[10px] border px-3 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
|
|
1936
|
+
children: dismissLabel
|
|
1937
|
+
}
|
|
1938
|
+
),
|
|
1939
|
+
/* @__PURE__ */ jsx(
|
|
1940
|
+
"button",
|
|
1941
|
+
{
|
|
1942
|
+
onClick: onDone,
|
|
1943
|
+
disabled: isPending,
|
|
1944
|
+
className: "bg-primary text-primary-foreground hover:bg-primary/90 flex h-10 flex-1 cursor-pointer items-center justify-center rounded-[10px] px-3 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
|
|
1945
|
+
children: isPending ? pendingLabel : actionLabel
|
|
1946
|
+
}
|
|
1947
|
+
)
|
|
1948
|
+
] })
|
|
1924
1949
|
] });
|
|
1925
1950
|
}
|
|
1926
1951
|
function TransactionErrorView({
|
|
@@ -1931,7 +1956,8 @@ function TransactionErrorView({
|
|
|
1931
1956
|
onRetry,
|
|
1932
1957
|
onDismiss,
|
|
1933
1958
|
isRetrying,
|
|
1934
|
-
retryLabel = "Retry Verification"
|
|
1959
|
+
retryLabel = "Retry Verification",
|
|
1960
|
+
dismissLabel = "Close"
|
|
1935
1961
|
}) {
|
|
1936
1962
|
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-4", children: [
|
|
1937
1963
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
@@ -1955,13 +1981,13 @@ function TransactionErrorView({
|
|
|
1955
1981
|
}
|
|
1956
1982
|
),
|
|
1957
1983
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1958
|
-
/* @__PURE__ */ jsx(
|
|
1984
|
+
onDismiss && /* @__PURE__ */ jsx(
|
|
1959
1985
|
"button",
|
|
1960
1986
|
{
|
|
1961
1987
|
onClick: onDismiss,
|
|
1962
1988
|
disabled: isRetrying,
|
|
1963
1989
|
className: "border-border text-foreground hover:bg-secondary flex h-10 flex-1 cursor-pointer items-center justify-center rounded-[10px] border px-3 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
|
|
1964
|
-
children:
|
|
1990
|
+
children: dismissLabel
|
|
1965
1991
|
}
|
|
1966
1992
|
),
|
|
1967
1993
|
/* @__PURE__ */ jsx(
|
|
@@ -1970,7 +1996,7 @@ function TransactionErrorView({
|
|
|
1970
1996
|
onClick: onRetry,
|
|
1971
1997
|
disabled: isRetrying,
|
|
1972
1998
|
className: "bg-primary text-primary-foreground hover:bg-primary/90 flex h-10 flex-1 cursor-pointer items-center justify-center rounded-[10px] px-3 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
|
|
1973
|
-
children: isRetrying ? "Retrying
|
|
1999
|
+
children: isRetrying ? "Retrying\u2026" : retryLabel
|
|
1974
2000
|
}
|
|
1975
2001
|
)
|
|
1976
2002
|
] })
|
|
@@ -2110,7 +2136,7 @@ function DepositForm({
|
|
|
2110
2136
|
};
|
|
2111
2137
|
const getButtonText = () => {
|
|
2112
2138
|
if (!isConnected) return "Connect Wallet";
|
|
2113
|
-
if (isSwitchingChain) return "Switching
|
|
2139
|
+
if (isSwitchingChain) return "Switching\u2026";
|
|
2114
2140
|
return "Deposit";
|
|
2115
2141
|
};
|
|
2116
2142
|
if (showSuccess) {
|
|
@@ -2134,7 +2160,7 @@ function DepositForm({
|
|
|
2134
2160
|
);
|
|
2135
2161
|
}
|
|
2136
2162
|
if (verificationFailed) {
|
|
2137
|
-
const baseMessage = "Your transfer was sent on-chain but we could not verify the deposit. The funds are already at the deposit address
|
|
2163
|
+
const baseMessage = "Your transfer was sent on-chain but we could not verify the deposit. The funds are already at the deposit address. Retry verification instead of starting a new deposit.";
|
|
2138
2164
|
const detail = error?.message;
|
|
2139
2165
|
const message = detail ? `${baseMessage} (${detail})` : baseMessage;
|
|
2140
2166
|
return /* @__PURE__ */ jsx(
|
|
@@ -2154,7 +2180,7 @@ function DepositForm({
|
|
|
2154
2180
|
return /* @__PURE__ */ jsx(
|
|
2155
2181
|
TransactionProgressView,
|
|
2156
2182
|
{
|
|
2157
|
-
title: "Depositing
|
|
2183
|
+
title: "Depositing\u2026",
|
|
2158
2184
|
steps: depositSteps,
|
|
2159
2185
|
onCancel: canCancel ? handleCancel : void 0
|
|
2160
2186
|
}
|
|
@@ -3094,6 +3120,745 @@ function PrivanaButton({
|
|
|
3094
3120
|
)
|
|
3095
3121
|
] });
|
|
3096
3122
|
}
|
|
3123
|
+
|
|
3124
|
+
// src/sdk/hooks/external-deposit-lock.ts
|
|
3125
|
+
var ACTIVE_SESSION_PREFIX = "privana:external-deposit-lock:active:";
|
|
3126
|
+
var SESSION_PREFIX = "privana:external-deposit-lock:session:";
|
|
3127
|
+
var ExternalDepositLockSessionChangedError = class _ExternalDepositLockSessionChangedError extends Error {
|
|
3128
|
+
constructor() {
|
|
3129
|
+
super("External deposit lock session changed");
|
|
3130
|
+
this.name = "ExternalDepositLockSessionChangedError";
|
|
3131
|
+
Object.setPrototypeOf(this, _ExternalDepositLockSessionChangedError.prototype);
|
|
3132
|
+
}
|
|
3133
|
+
};
|
|
3134
|
+
function externalDepositSessionId(chainId, tokenId) {
|
|
3135
|
+
return `${chainId}:${tokenId.toLowerCase()}`;
|
|
3136
|
+
}
|
|
3137
|
+
function isExternalDepositBlockInSession(session, blockNumber) {
|
|
3138
|
+
return Number.isSafeInteger(blockNumber) && blockNumber > session.startBlock;
|
|
3139
|
+
}
|
|
3140
|
+
function isCurrentExternalDepositVerification(session, txHash) {
|
|
3141
|
+
return session.creditedAmount === void 0 && session.verification?.hash.toLowerCase() === txHash.toLowerCase();
|
|
3142
|
+
}
|
|
3143
|
+
function getExternalDepositMinimum(response, chainId) {
|
|
3144
|
+
const value = response?.min_deposit?.[String(chainId)]?.erc20;
|
|
3145
|
+
if (value === void 0) return void 0;
|
|
3146
|
+
try {
|
|
3147
|
+
const amount = BigInt(value);
|
|
3148
|
+
return amount >= 0n ? amount : void 0;
|
|
3149
|
+
} catch {
|
|
3150
|
+
return void 0;
|
|
3151
|
+
}
|
|
3152
|
+
}
|
|
3153
|
+
function activeSessionKey(owner) {
|
|
3154
|
+
return `${ACTIVE_SESSION_PREFIX}${owner.toLowerCase()}`;
|
|
3155
|
+
}
|
|
3156
|
+
function sessionKey(owner, sessionId) {
|
|
3157
|
+
return `${SESSION_PREFIX}${owner.toLowerCase()}:${sessionId}`;
|
|
3158
|
+
}
|
|
3159
|
+
function activeSessionId(owner) {
|
|
3160
|
+
const value = getSharedBrowserStorageItem(activeSessionKey(owner));
|
|
3161
|
+
return value || void 0;
|
|
3162
|
+
}
|
|
3163
|
+
function isPositiveIntegerString(value) {
|
|
3164
|
+
if (typeof value !== "string") return false;
|
|
3165
|
+
try {
|
|
3166
|
+
return BigInt(value) > 0n;
|
|
3167
|
+
} catch {
|
|
3168
|
+
return false;
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
function isSessionRecord(value) {
|
|
3172
|
+
if (!value || typeof value !== "object") return false;
|
|
3173
|
+
const record = value;
|
|
3174
|
+
if (record.version !== 1 || typeof record.owner !== "string" || typeof record.serviceAddress !== "string" || !Number.isInteger(record.chainId) || (record.chainId ?? 0) <= 0 || typeof record.tokenId !== "string" || !Number.isSafeInteger(record.startBlock) || (record.startBlock ?? -1) < 0 || !isPositiveIntegerString(record.depositAmount) || !isPositiveIntegerString(record.maxLockAmount) || !Number.isInteger(record.lockDuration) || (record.lockDuration ?? 0) <= 0 || typeof record.generation !== "string" || !record.generation || record.submissionAmbiguous !== void 0 && typeof record.submissionAmbiguous !== "boolean" || record.creditedAmount !== void 0 && !isPositiveIntegerString(record.creditedAmount)) {
|
|
3175
|
+
return false;
|
|
3176
|
+
}
|
|
3177
|
+
if (record.verification && (typeof record.verification.hash !== "string" || record.verification.chainId !== record.chainId || !isPositiveIntegerString(record.verification.amount) || record.verification.logIndex !== void 0 && (!Number.isInteger(record.verification.logIndex) || record.verification.logIndex < 0))) {
|
|
3178
|
+
return false;
|
|
3179
|
+
}
|
|
3180
|
+
if (record.verification && record.creditedAmount !== void 0) return false;
|
|
3181
|
+
if (record.submissionAmbiguous && record.creditedAmount === void 0) return false;
|
|
3182
|
+
if (!record.payload) {
|
|
3183
|
+
return record.creditedAmount !== void 0 && !record.submissionAmbiguous;
|
|
3184
|
+
}
|
|
3185
|
+
if (typeof record.payload.signature !== "string" || typeof record.payload.token_id !== "string" || typeof record.payload.service_address !== "string" || record.payload.signature !== record.generation || record.payload.token_id.toLowerCase() !== record.tokenId.toLowerCase() || record.payload.service_address.toLowerCase() !== record.serviceAddress.toLowerCase() || !isPositiveIntegerString(record.payload.amount)) {
|
|
3186
|
+
return false;
|
|
3187
|
+
}
|
|
3188
|
+
return BigInt(record.payload.amount) <= BigInt(record.maxLockAmount);
|
|
3189
|
+
}
|
|
3190
|
+
function writeSessionRecord(session) {
|
|
3191
|
+
const id = externalDepositSessionId(session.chainId, session.tokenId);
|
|
3192
|
+
return setSharedBrowserStorageItem(sessionKey(session.owner, id), JSON.stringify(session));
|
|
3193
|
+
}
|
|
3194
|
+
function saveExternalDepositLockSession(session, expectedSession = null) {
|
|
3195
|
+
if (!isSessionRecord(session)) throw new Error("Invalid external deposit lock session");
|
|
3196
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
3197
|
+
if (expectedSession === null && current || expectedSession !== null && (!current || !isSameSessionSnapshot(current, expectedSession))) {
|
|
3198
|
+
throw new ExternalDepositLockSessionChangedError();
|
|
3199
|
+
}
|
|
3200
|
+
const nextId = externalDepositSessionId(session.chainId, session.tokenId);
|
|
3201
|
+
const previousId = activeSessionId(session.owner);
|
|
3202
|
+
if (!writeSessionRecord(session)) {
|
|
3203
|
+
throw new Error("Unable to persist external deposit lock session");
|
|
3204
|
+
}
|
|
3205
|
+
if (previousId !== nextId) {
|
|
3206
|
+
if (!setSharedBrowserStorageItem(activeSessionKey(session.owner), nextId)) {
|
|
3207
|
+
removeSharedBrowserStorageItem(sessionKey(session.owner, nextId));
|
|
3208
|
+
throw new Error("Unable to persist external deposit lock session");
|
|
3209
|
+
}
|
|
3210
|
+
if (previousId) {
|
|
3211
|
+
removeSharedBrowserStorageItem(sessionKey(session.owner, previousId));
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
function loadExternalDepositLockSession(owner) {
|
|
3216
|
+
const id = activeSessionId(owner);
|
|
3217
|
+
if (!id) return void 0;
|
|
3218
|
+
const key = sessionKey(owner, id);
|
|
3219
|
+
try {
|
|
3220
|
+
const raw = getSharedBrowserStorageItem(key);
|
|
3221
|
+
const parsed = raw ? JSON.parse(raw) : void 0;
|
|
3222
|
+
if (!isSessionRecord(parsed) || parsed.owner.toLowerCase() !== owner.toLowerCase() || externalDepositSessionId(parsed.chainId, parsed.tokenId) !== id) {
|
|
3223
|
+
removeSharedBrowserStorageItem(key);
|
|
3224
|
+
removeSharedBrowserStorageItem(activeSessionKey(owner));
|
|
3225
|
+
return void 0;
|
|
3226
|
+
}
|
|
3227
|
+
return parsed;
|
|
3228
|
+
} catch {
|
|
3229
|
+
removeSharedBrowserStorageItem(key);
|
|
3230
|
+
removeSharedBrowserStorageItem(activeSessionKey(owner));
|
|
3231
|
+
return void 0;
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3234
|
+
function subscribeExternalDepositLockSession(owner, onChange) {
|
|
3235
|
+
if (typeof window === "undefined") return () => void 0;
|
|
3236
|
+
let localStorage;
|
|
3237
|
+
try {
|
|
3238
|
+
localStorage = window.localStorage;
|
|
3239
|
+
} catch {
|
|
3240
|
+
return () => void 0;
|
|
3241
|
+
}
|
|
3242
|
+
const activeKey = activeSessionKey(owner);
|
|
3243
|
+
const ownerSessionPrefix = `${SESSION_PREFIX}${owner.toLowerCase()}:`;
|
|
3244
|
+
const handleStorage = (event) => {
|
|
3245
|
+
if (event.storageArea !== localStorage) return;
|
|
3246
|
+
if (event.key !== null && event.key !== activeKey && !event.key.startsWith(ownerSessionPrefix)) {
|
|
3247
|
+
return;
|
|
3248
|
+
}
|
|
3249
|
+
onChange(loadExternalDepositLockSession(owner));
|
|
3250
|
+
};
|
|
3251
|
+
window.addEventListener("storage", handleStorage);
|
|
3252
|
+
return () => window.removeEventListener("storage", handleStorage);
|
|
3253
|
+
}
|
|
3254
|
+
function clearExternalDepositLockSession(owner, expectedSessionId, expectedGeneration) {
|
|
3255
|
+
const id = activeSessionId(owner);
|
|
3256
|
+
if (!id || expectedSessionId && id !== expectedSessionId) return;
|
|
3257
|
+
if (expectedGeneration) {
|
|
3258
|
+
const current = loadExternalDepositLockSession(owner);
|
|
3259
|
+
if (!current || current.generation !== expectedGeneration) return;
|
|
3260
|
+
}
|
|
3261
|
+
removeSharedBrowserStorageItem(sessionKey(owner, id));
|
|
3262
|
+
removeSharedBrowserStorageItem(activeSessionKey(owner));
|
|
3263
|
+
}
|
|
3264
|
+
function isSameVerification(left, right) {
|
|
3265
|
+
if (!left || !right) return left === right;
|
|
3266
|
+
return left.hash.toLowerCase() === right.hash.toLowerCase() && left.chainId === right.chainId && left.amount === right.amount && (left.logIndex ?? 0) === (right.logIndex ?? 0);
|
|
3267
|
+
}
|
|
3268
|
+
function isSamePayload(left, right) {
|
|
3269
|
+
if (!left || !right) return left === right;
|
|
3270
|
+
return left.service_address.toLowerCase() === right.service_address.toLowerCase() && left.token_id.toLowerCase() === right.token_id.toLowerCase() && left.amount === right.amount && left.expiry === right.expiry && left.nonce === right.nonce && left.signature === right.signature;
|
|
3271
|
+
}
|
|
3272
|
+
function isSameSessionSnapshot(left, right) {
|
|
3273
|
+
return left.version === right.version && left.owner.toLowerCase() === right.owner.toLowerCase() && left.serviceAddress.toLowerCase() === right.serviceAddress.toLowerCase() && left.chainId === right.chainId && left.tokenId.toLowerCase() === right.tokenId.toLowerCase() && left.startBlock === right.startBlock && left.depositAmount === right.depositAmount && left.maxLockAmount === right.maxLockAmount && left.lockDuration === right.lockDuration && left.generation === right.generation && left.creditedAmount === right.creditedAmount && left.submissionAmbiguous === right.submissionAmbiguous && isSameVerification(left.verification, right.verification) && isSamePayload(left.payload, right.payload);
|
|
3274
|
+
}
|
|
3275
|
+
function isSameExternalDepositLockSession(left, right) {
|
|
3276
|
+
return isSameSessionSnapshot(left, right);
|
|
3277
|
+
}
|
|
3278
|
+
function clearExternalDepositVerification(session) {
|
|
3279
|
+
if (!session.verification || session.creditedAmount) return void 0;
|
|
3280
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
3281
|
+
if (!current || current.generation !== session.generation || current.creditedAmount || !isSameVerification(current.verification, session.verification)) {
|
|
3282
|
+
return void 0;
|
|
3283
|
+
}
|
|
3284
|
+
const next = { ...current, verification: void 0 };
|
|
3285
|
+
if (!writeSessionRecord(next)) {
|
|
3286
|
+
throw new Error("Unable to clear failed deposit verification");
|
|
3287
|
+
}
|
|
3288
|
+
return next;
|
|
3289
|
+
}
|
|
3290
|
+
function discardExternalDepositLockSession(session) {
|
|
3291
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
3292
|
+
if (!current) return true;
|
|
3293
|
+
if (!isSameSessionSnapshot(current, session)) return false;
|
|
3294
|
+
clearExternalDepositLockSession(
|
|
3295
|
+
current.owner,
|
|
3296
|
+
externalDepositSessionId(current.chainId, current.tokenId),
|
|
3297
|
+
current.generation
|
|
3298
|
+
);
|
|
3299
|
+
return loadExternalDepositLockSession(current.owner) === void 0;
|
|
3300
|
+
}
|
|
3301
|
+
function clearSubmittedPayload(session, signature, clearAmbiguous = false) {
|
|
3302
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
3303
|
+
if (!current?.payload || current.payload.signature !== signature || current.submissionAmbiguous && !clearAmbiguous) {
|
|
3304
|
+
return false;
|
|
3305
|
+
}
|
|
3306
|
+
return writeSessionRecord({ ...current, submissionAmbiguous: void 0, payload: void 0 });
|
|
3307
|
+
}
|
|
3308
|
+
function markSubmissionAmbiguous(session, signature) {
|
|
3309
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
3310
|
+
if (!current?.payload || current.payload.signature !== signature) return false;
|
|
3311
|
+
if (current.submissionAmbiguous) return true;
|
|
3312
|
+
return writeSessionRecord({ ...current, submissionAmbiguous: true });
|
|
3313
|
+
}
|
|
3314
|
+
function ambiguousSubmissionError(error) {
|
|
3315
|
+
return new PostDepositLockError(
|
|
3316
|
+
"Lock submission could not be confirmed. Retry only the saved signature, or check locked funds before stopping recovery.",
|
|
3317
|
+
error.reason,
|
|
3318
|
+
error.signedAmount,
|
|
3319
|
+
error.creditedAmount,
|
|
3320
|
+
{
|
|
3321
|
+
cause: error.cause,
|
|
3322
|
+
submissionMayHaveSucceeded: true
|
|
3323
|
+
}
|
|
3324
|
+
);
|
|
3325
|
+
}
|
|
3326
|
+
function isDefinitiveSubmissionFailure(error) {
|
|
3327
|
+
const cause = error.cause;
|
|
3328
|
+
return cause instanceof AccountingApiError && cause.statusCode !== 408 && cause.statusCode < 500;
|
|
3329
|
+
}
|
|
3330
|
+
function submissionLockName(session) {
|
|
3331
|
+
return `privana:external-deposit-lock:submit:${session.owner.toLowerCase()}:${externalDepositSessionId(session.chainId, session.tokenId)}`;
|
|
3332
|
+
}
|
|
3333
|
+
async function withSubmissionLock(session, task) {
|
|
3334
|
+
const locks = typeof navigator === "undefined" ? void 0 : navigator.locks;
|
|
3335
|
+
if (!locks) return task(false);
|
|
3336
|
+
return locks.request(submissionLockName(session), () => task(true));
|
|
3337
|
+
}
|
|
3338
|
+
function externalDepositRetryAmount(session) {
|
|
3339
|
+
if (!session.creditedAmount) throw new Error("No credited external deposit to recover");
|
|
3340
|
+
const creditedAmount = BigInt(session.creditedAmount);
|
|
3341
|
+
const maxLockAmount = BigInt(session.maxLockAmount);
|
|
3342
|
+
return creditedAmount < maxLockAmount ? creditedAmount : maxLockAmount;
|
|
3343
|
+
}
|
|
3344
|
+
async function submitExternalDepositLock(client, session) {
|
|
3345
|
+
return withSubmissionLock(session, async (crossTabExclusive) => {
|
|
3346
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
3347
|
+
if (!current || current.generation !== session.generation) {
|
|
3348
|
+
throw new PostDepositLockError(
|
|
3349
|
+
"External deposit lock session changed before submission",
|
|
3350
|
+
"not-found",
|
|
3351
|
+
BigInt(session.maxLockAmount),
|
|
3352
|
+
session.creditedAmount ? BigInt(session.creditedAmount) : void 0
|
|
3353
|
+
);
|
|
3354
|
+
}
|
|
3355
|
+
const result = await submitExternalDepositLockAttempt(client, current, crossTabExclusive);
|
|
3356
|
+
clearExternalDepositLockSession(
|
|
3357
|
+
current.owner,
|
|
3358
|
+
externalDepositSessionId(current.chainId, current.tokenId),
|
|
3359
|
+
current.generation
|
|
3360
|
+
);
|
|
3361
|
+
return result;
|
|
3362
|
+
});
|
|
3363
|
+
}
|
|
3364
|
+
async function submitExternalDepositLockAttempt(client, session, crossTabExclusive) {
|
|
3365
|
+
const creditedAmount = session.creditedAmount ? BigInt(session.creditedAmount) : void 0;
|
|
3366
|
+
if (!creditedAmount) {
|
|
3367
|
+
throw new PostDepositLockError(
|
|
3368
|
+
"External deposit has not been credited",
|
|
3369
|
+
"submission-failed",
|
|
3370
|
+
BigInt(session.maxLockAmount)
|
|
3371
|
+
);
|
|
3372
|
+
}
|
|
3373
|
+
const payload = session.payload;
|
|
3374
|
+
if (!payload) {
|
|
3375
|
+
throw new PostDepositLockError(
|
|
3376
|
+
"No persisted signed policy found for this deposit session",
|
|
3377
|
+
"not-found",
|
|
3378
|
+
BigInt(session.maxLockAmount),
|
|
3379
|
+
creditedAmount
|
|
3380
|
+
);
|
|
3381
|
+
}
|
|
3382
|
+
try {
|
|
3383
|
+
return await submitPendingLock({
|
|
3384
|
+
client,
|
|
3385
|
+
payload,
|
|
3386
|
+
creditedAmount,
|
|
3387
|
+
beforeSubmit: () => {
|
|
3388
|
+
if (!markSubmissionAmbiguous(session, payload.signature)) {
|
|
3389
|
+
throw new Error("Unable to persist lock submission recovery state");
|
|
3390
|
+
}
|
|
3391
|
+
}
|
|
3392
|
+
});
|
|
3393
|
+
} catch (err) {
|
|
3394
|
+
if (err instanceof PostDepositLockError && err.reason === "submission-failed" && crossTabExclusive && !session.submissionAmbiguous && isDefinitiveSubmissionFailure(err)) {
|
|
3395
|
+
if (!clearSubmittedPayload(session, payload.signature, true)) {
|
|
3396
|
+
const latest = loadExternalDepositLockSession(session.owner);
|
|
3397
|
+
if (latest?.submissionAmbiguous) throw ambiguousSubmissionError(err);
|
|
3398
|
+
}
|
|
3399
|
+
} else if (err instanceof PostDepositLockError && err.reason === "submission-failed") {
|
|
3400
|
+
const ambiguous = markSubmissionAmbiguous(session, payload.signature) || loadExternalDepositLockSession(session.owner)?.submissionAmbiguous === true;
|
|
3401
|
+
if (ambiguous) throw ambiguousSubmissionError(err);
|
|
3402
|
+
} else {
|
|
3403
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
3404
|
+
if (session.submissionAmbiguous || current?.submissionAmbiguous) {
|
|
3405
|
+
if (err instanceof PostDepositLockError) throw ambiguousSubmissionError(err);
|
|
3406
|
+
throw err;
|
|
3407
|
+
}
|
|
3408
|
+
if (!clearSubmittedPayload(session, payload.signature)) {
|
|
3409
|
+
const latest = loadExternalDepositLockSession(session.owner);
|
|
3410
|
+
if (latest?.submissionAmbiguous && err instanceof PostDepositLockError) {
|
|
3411
|
+
throw ambiguousSubmissionError(err);
|
|
3412
|
+
}
|
|
3413
|
+
}
|
|
3414
|
+
}
|
|
3415
|
+
throw err;
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
function useExternalDepositLock({
|
|
3419
|
+
allowance,
|
|
3420
|
+
onLockSubmitted,
|
|
3421
|
+
onLockFailed
|
|
3422
|
+
}) {
|
|
3423
|
+
const { client, networkConfig, serviceAddress, getTokenById, tokensStatus } = usePrivanaContext();
|
|
3424
|
+
const config = useConfig();
|
|
3425
|
+
const queryClient = useQueryClient();
|
|
3426
|
+
const { address } = useAccount();
|
|
3427
|
+
const { privateReadAddress } = usePrivateReadRequest();
|
|
3428
|
+
const { ensureCorrectChain } = useEnsureCorrectChain();
|
|
3429
|
+
const [isSigning, setIsSigning] = useState(false);
|
|
3430
|
+
const [isSubmittingLock, setIsSubmittingLock] = useState(false);
|
|
3431
|
+
const [session, setSession] = useState(null);
|
|
3432
|
+
const sessionRef = useRef(null);
|
|
3433
|
+
const signingRef = useRef(false);
|
|
3434
|
+
const settlingRef = useRef(false);
|
|
3435
|
+
const pendingCreditedTransitionRef = useRef(null);
|
|
3436
|
+
const hydratedScopeRef = useRef(null);
|
|
3437
|
+
const beneficiaryRef = useRef(privateReadAddress);
|
|
3438
|
+
beneficiaryRef.current = privateReadAddress;
|
|
3439
|
+
const onLockSubmittedRef = useRef(onLockSubmitted);
|
|
3440
|
+
const onLockFailedRef = useRef(onLockFailed);
|
|
3441
|
+
useEffect(() => {
|
|
3442
|
+
onLockSubmittedRef.current = onLockSubmitted;
|
|
3443
|
+
onLockFailedRef.current = onLockFailed;
|
|
3444
|
+
}, [onLockSubmitted, onLockFailed]);
|
|
3445
|
+
const installSession = useCallback((next) => {
|
|
3446
|
+
sessionRef.current = next;
|
|
3447
|
+
setSession(next);
|
|
3448
|
+
}, []);
|
|
3449
|
+
const preserveCreditedFallback = useCallback(
|
|
3450
|
+
(durable) => {
|
|
3451
|
+
const pending = pendingCreditedTransitionRef.current;
|
|
3452
|
+
if (pending && durable && isSameExternalDepositLockSession(durable, pending.predecessor)) {
|
|
3453
|
+
return pending.credited;
|
|
3454
|
+
}
|
|
3455
|
+
pendingCreditedTransitionRef.current = null;
|
|
3456
|
+
return durable;
|
|
3457
|
+
},
|
|
3458
|
+
[]
|
|
3459
|
+
);
|
|
3460
|
+
const notifyFailure = useCallback((error) => {
|
|
3461
|
+
onLockFailedRef.current?.(error);
|
|
3462
|
+
}, []);
|
|
3463
|
+
const submitSession = useCallback(
|
|
3464
|
+
async (active) => {
|
|
3465
|
+
if (settlingRef.current) return;
|
|
3466
|
+
settlingRef.current = true;
|
|
3467
|
+
setIsSubmittingLock(true);
|
|
3468
|
+
let result;
|
|
3469
|
+
try {
|
|
3470
|
+
let submission = active;
|
|
3471
|
+
const storedBeforeSubmit = loadExternalDepositLockSession(active.owner);
|
|
3472
|
+
if (storedBeforeSubmit?.generation === active.generation) {
|
|
3473
|
+
if (active.creditedAmount && !storedBeforeSubmit.creditedAmount) {
|
|
3474
|
+
const pending = pendingCreditedTransitionRef.current;
|
|
3475
|
+
if (!pending || !isSameExternalDepositLockSession(active, pending.credited) || !isSameExternalDepositLockSession(storedBeforeSubmit, pending.predecessor)) {
|
|
3476
|
+
throw new ExternalDepositLockSessionChangedError();
|
|
3477
|
+
}
|
|
3478
|
+
saveExternalDepositLockSession(pending.credited, pending.predecessor);
|
|
3479
|
+
pendingCreditedTransitionRef.current = null;
|
|
3480
|
+
submission = pending.credited;
|
|
3481
|
+
} else {
|
|
3482
|
+
submission = storedBeforeSubmit;
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
result = await submitExternalDepositLock(client, submission);
|
|
3486
|
+
} catch (err) {
|
|
3487
|
+
const stored = loadExternalDepositLockSession(active.owner);
|
|
3488
|
+
const error = err instanceof PostDepositLockError ? err : new PostDepositLockError(
|
|
3489
|
+
err instanceof Error ? err.message : "Lock submission failed",
|
|
3490
|
+
"submission-failed",
|
|
3491
|
+
BigInt(active.maxLockAmount),
|
|
3492
|
+
active.creditedAmount ? BigInt(active.creditedAmount) : void 0,
|
|
3493
|
+
{ cause: err }
|
|
3494
|
+
);
|
|
3495
|
+
if (err instanceof ExternalDepositLockSessionChangedError) {
|
|
3496
|
+
pendingCreditedTransitionRef.current = null;
|
|
3497
|
+
installSession(stored ?? null);
|
|
3498
|
+
return;
|
|
3499
|
+
}
|
|
3500
|
+
if (!stored || stored.generation !== active.generation) {
|
|
3501
|
+
pendingCreditedTransitionRef.current = null;
|
|
3502
|
+
installSession(stored ?? null);
|
|
3503
|
+
return;
|
|
3504
|
+
}
|
|
3505
|
+
if (active.creditedAmount && !stored.creditedAmount) {
|
|
3506
|
+
const pending = pendingCreditedTransitionRef.current;
|
|
3507
|
+
if (pending && isSameExternalDepositLockSession(active, pending.credited) && isSameExternalDepositLockSession(stored, pending.predecessor)) {
|
|
3508
|
+
installSession(pending.credited);
|
|
3509
|
+
notifyFailure(error);
|
|
3510
|
+
return;
|
|
3511
|
+
}
|
|
3512
|
+
pendingCreditedTransitionRef.current = null;
|
|
3513
|
+
installSession(stored);
|
|
3514
|
+
return;
|
|
3515
|
+
}
|
|
3516
|
+
installSession(active.creditedAmount && !stored.creditedAmount ? active : stored);
|
|
3517
|
+
notifyFailure(error);
|
|
3518
|
+
return;
|
|
3519
|
+
} finally {
|
|
3520
|
+
settlingRef.current = false;
|
|
3521
|
+
setIsSubmittingLock(false);
|
|
3522
|
+
}
|
|
3523
|
+
installSession(loadExternalDepositLockSession(active.owner) ?? null);
|
|
3524
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
3525
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
|
|
3526
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
|
|
3527
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
3528
|
+
onLockSubmittedRef.current?.(result);
|
|
3529
|
+
},
|
|
3530
|
+
[client, installSession, notifyFailure, queryClient]
|
|
3531
|
+
);
|
|
3532
|
+
const validateRestoredSession = useCallback(
|
|
3533
|
+
(restored) => {
|
|
3534
|
+
if (!restored || !serviceAddress) return null;
|
|
3535
|
+
const token = getTokenById(restored.tokenId);
|
|
3536
|
+
const invalid = restored.serviceAddress.toLowerCase() !== serviceAddress.toLowerCase() || !token || token.chainId !== restored.chainId || token.contract === zeroAddress || !restored.creditedAmount && !restored.payload;
|
|
3537
|
+
if (!invalid) return restored;
|
|
3538
|
+
clearExternalDepositLockSession(
|
|
3539
|
+
restored.owner,
|
|
3540
|
+
externalDepositSessionId(restored.chainId, restored.tokenId),
|
|
3541
|
+
restored.generation
|
|
3542
|
+
);
|
|
3543
|
+
return null;
|
|
3544
|
+
},
|
|
3545
|
+
[getTokenById, serviceAddress]
|
|
3546
|
+
);
|
|
3547
|
+
useEffect(() => {
|
|
3548
|
+
if (!privateReadAddress || !serviceAddress || tokensStatus !== "ready") {
|
|
3549
|
+
setSession(null);
|
|
3550
|
+
return;
|
|
3551
|
+
}
|
|
3552
|
+
const hydrationKey = `${privateReadAddress.toLowerCase()}:${serviceAddress.toLowerCase()}`;
|
|
3553
|
+
const shouldResume = hydratedScopeRef.current !== hydrationKey;
|
|
3554
|
+
hydratedScopeRef.current = hydrationKey;
|
|
3555
|
+
const restored = preserveCreditedFallback(
|
|
3556
|
+
validateRestoredSession(loadExternalDepositLockSession(privateReadAddress))
|
|
3557
|
+
);
|
|
3558
|
+
installSession(restored);
|
|
3559
|
+
if (!restored) {
|
|
3560
|
+
return;
|
|
3561
|
+
}
|
|
3562
|
+
if (!shouldResume) return;
|
|
3563
|
+
if (restored.creditedAmount && restored.payload) {
|
|
3564
|
+
void submitSession(restored);
|
|
3565
|
+
} else if (restored.creditedAmount) {
|
|
3566
|
+
notifyFailure(
|
|
3567
|
+
new PostDepositLockError(
|
|
3568
|
+
"Deposit credited; sign a fresh policy to lock the funds",
|
|
3569
|
+
"not-found",
|
|
3570
|
+
BigInt(restored.maxLockAmount),
|
|
3571
|
+
BigInt(restored.creditedAmount)
|
|
3572
|
+
)
|
|
3573
|
+
);
|
|
3574
|
+
}
|
|
3575
|
+
}, [
|
|
3576
|
+
installSession,
|
|
3577
|
+
notifyFailure,
|
|
3578
|
+
privateReadAddress,
|
|
3579
|
+
preserveCreditedFallback,
|
|
3580
|
+
serviceAddress,
|
|
3581
|
+
submitSession,
|
|
3582
|
+
tokensStatus,
|
|
3583
|
+
validateRestoredSession
|
|
3584
|
+
]);
|
|
3585
|
+
useEffect(() => {
|
|
3586
|
+
if (!privateReadAddress || !serviceAddress || tokensStatus !== "ready") return;
|
|
3587
|
+
const reconcile = (restored) => {
|
|
3588
|
+
installSession(preserveCreditedFallback(validateRestoredSession(restored)));
|
|
3589
|
+
};
|
|
3590
|
+
const unsubscribe = subscribeExternalDepositLockSession(privateReadAddress, reconcile);
|
|
3591
|
+
reconcile(loadExternalDepositLockSession(privateReadAddress));
|
|
3592
|
+
return unsubscribe;
|
|
3593
|
+
}, [
|
|
3594
|
+
installSession,
|
|
3595
|
+
preserveCreditedFallback,
|
|
3596
|
+
privateReadAddress,
|
|
3597
|
+
serviceAddress,
|
|
3598
|
+
tokensStatus,
|
|
3599
|
+
validateRestoredSession
|
|
3600
|
+
]);
|
|
3601
|
+
const signAndPersist = useCallback(
|
|
3602
|
+
async ({ tokenId, amount }) => {
|
|
3603
|
+
if (!allowance) throw new Error("No allowance configured for this deposit");
|
|
3604
|
+
if (signingRef.current || settlingRef.current) {
|
|
3605
|
+
throw new Error("A policy operation is already in progress");
|
|
3606
|
+
}
|
|
3607
|
+
if (!canUseSharedBrowserStorage()) {
|
|
3608
|
+
throw new Error("Browser storage is required for locked deposit recovery");
|
|
3609
|
+
}
|
|
3610
|
+
const owner = requireDepositLockOwner(address, privateReadAddress);
|
|
3611
|
+
if (sessionRef.current || loadExternalDepositLockSession(owner)) {
|
|
3612
|
+
throw new Error("Finish or cancel the active external deposit before starting another");
|
|
3613
|
+
}
|
|
3614
|
+
const token = getTokenById(tokenId);
|
|
3615
|
+
if (!token) throw new Error(`Unknown token ID: ${tokenId}`);
|
|
3616
|
+
if (token.contract === zeroAddress) {
|
|
3617
|
+
throw new Error("External deposits support ERC20 tokens only");
|
|
3618
|
+
}
|
|
3619
|
+
const targetService = requireServiceAddress(serviceAddress);
|
|
3620
|
+
const lockDuration = allowance.lockDuration ?? DEFAULT_LOCK_DURATION_SECONDS;
|
|
3621
|
+
signingRef.current = true;
|
|
3622
|
+
setIsSigning(true);
|
|
3623
|
+
try {
|
|
3624
|
+
await ensureCorrectChain(networkConfig.chainId);
|
|
3625
|
+
const signingWalletClient = await getWalletClient(config, {
|
|
3626
|
+
chainId: networkConfig.chainId
|
|
3627
|
+
});
|
|
3628
|
+
const payload = await createSignedLockRequest({
|
|
3629
|
+
client,
|
|
3630
|
+
walletClient: signingWalletClient,
|
|
3631
|
+
userAddress: owner,
|
|
3632
|
+
networkConfig,
|
|
3633
|
+
serviceAddress: targetService,
|
|
3634
|
+
tokenId: token.id,
|
|
3635
|
+
amount: clampLockAmount(amount, BigInt(allowance.value)),
|
|
3636
|
+
lockDuration
|
|
3637
|
+
});
|
|
3638
|
+
const startBlock = await getBlockNumber(config, { chainId: token.chainId });
|
|
3639
|
+
if (startBlock > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
3640
|
+
throw new Error("Source-chain block number exceeds the supported range");
|
|
3641
|
+
}
|
|
3642
|
+
if (beneficiaryRef.current?.toLowerCase() !== owner.toLowerCase()) {
|
|
3643
|
+
throw new Error("Authenticated deposit account changed while signing");
|
|
3644
|
+
}
|
|
3645
|
+
const next = {
|
|
3646
|
+
version: 1,
|
|
3647
|
+
owner,
|
|
3648
|
+
serviceAddress: targetService,
|
|
3649
|
+
chainId: token.chainId,
|
|
3650
|
+
tokenId: token.id,
|
|
3651
|
+
startBlock: Number(startBlock),
|
|
3652
|
+
depositAmount: amount.toString(),
|
|
3653
|
+
maxLockAmount: String(payload.amount),
|
|
3654
|
+
lockDuration,
|
|
3655
|
+
generation: payload.signature,
|
|
3656
|
+
payload
|
|
3657
|
+
};
|
|
3658
|
+
saveExternalDepositLockSession(next, null);
|
|
3659
|
+
installSession(next);
|
|
3660
|
+
} finally {
|
|
3661
|
+
signingRef.current = false;
|
|
3662
|
+
setIsSigning(false);
|
|
3663
|
+
}
|
|
3664
|
+
},
|
|
3665
|
+
[
|
|
3666
|
+
address,
|
|
3667
|
+
allowance,
|
|
3668
|
+
client,
|
|
3669
|
+
config,
|
|
3670
|
+
ensureCorrectChain,
|
|
3671
|
+
getTokenById,
|
|
3672
|
+
installSession,
|
|
3673
|
+
networkConfig,
|
|
3674
|
+
privateReadAddress,
|
|
3675
|
+
serviceAddress
|
|
3676
|
+
]
|
|
3677
|
+
);
|
|
3678
|
+
const settleAfterCredit = useCallback(
|
|
3679
|
+
async (txHash, creditedAmount) => {
|
|
3680
|
+
const active = sessionRef.current;
|
|
3681
|
+
if (!active || settlingRef.current) return;
|
|
3682
|
+
if (!isCurrentExternalDepositVerification(active, txHash)) {
|
|
3683
|
+
return;
|
|
3684
|
+
}
|
|
3685
|
+
if (creditedAmount <= 0n) throw new Error("Credited amount must be positive");
|
|
3686
|
+
const credited = {
|
|
3687
|
+
...active,
|
|
3688
|
+
verification: void 0,
|
|
3689
|
+
creditedAmount: creditedAmount.toString()
|
|
3690
|
+
};
|
|
3691
|
+
try {
|
|
3692
|
+
saveExternalDepositLockSession(credited, active);
|
|
3693
|
+
pendingCreditedTransitionRef.current = null;
|
|
3694
|
+
} catch (err) {
|
|
3695
|
+
const stored = loadExternalDepositLockSession(active.owner);
|
|
3696
|
+
if (err instanceof ExternalDepositLockSessionChangedError || !stored || !isSameExternalDepositLockSession(stored, active)) {
|
|
3697
|
+
pendingCreditedTransitionRef.current = null;
|
|
3698
|
+
installSession(stored ?? null);
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
pendingCreditedTransitionRef.current = { predecessor: active, credited };
|
|
3702
|
+
installSession(credited);
|
|
3703
|
+
notifyFailure(
|
|
3704
|
+
new PostDepositLockError(
|
|
3705
|
+
err instanceof Error ? err.message : "Unable to persist credited deposit state",
|
|
3706
|
+
"submission-failed",
|
|
3707
|
+
BigInt(active.maxLockAmount),
|
|
3708
|
+
creditedAmount,
|
|
3709
|
+
{ cause: err }
|
|
3710
|
+
)
|
|
3711
|
+
);
|
|
3712
|
+
return;
|
|
3713
|
+
}
|
|
3714
|
+
installSession(credited);
|
|
3715
|
+
await submitSession(credited);
|
|
3716
|
+
},
|
|
3717
|
+
[installSession, notifyFailure, submitSession]
|
|
3718
|
+
);
|
|
3719
|
+
const recordVerification = useCallback(
|
|
3720
|
+
(context) => {
|
|
3721
|
+
const active = sessionRef.current;
|
|
3722
|
+
if (!active || active.creditedAmount) {
|
|
3723
|
+
throw new Error("No external deposit session is awaiting verification");
|
|
3724
|
+
}
|
|
3725
|
+
if (active.verification) {
|
|
3726
|
+
const sameTransfer = active.verification.hash.toLowerCase() === context.hash.toLowerCase() && active.verification.chainId === context.chainId && active.verification.amount === context.amount.toString() && (active.verification.logIndex ?? 0) === (context.logIndex ?? 0);
|
|
3727
|
+
if (sameTransfer) return;
|
|
3728
|
+
throw new Error("Another transfer is already being verified for this deposit session");
|
|
3729
|
+
}
|
|
3730
|
+
if (context.chainId !== active.chainId || context.amount <= 0n) {
|
|
3731
|
+
throw new Error("Discovered transfer does not match the signed deposit session");
|
|
3732
|
+
}
|
|
3733
|
+
const current = loadExternalDepositLockSession(active.owner);
|
|
3734
|
+
if (!current || current.generation !== active.generation) {
|
|
3735
|
+
throw new Error("The signed deposit session changed before verification");
|
|
3736
|
+
}
|
|
3737
|
+
const next = {
|
|
3738
|
+
...active,
|
|
3739
|
+
verification: {
|
|
3740
|
+
hash: context.hash,
|
|
3741
|
+
chainId: context.chainId,
|
|
3742
|
+
amount: context.amount.toString(),
|
|
3743
|
+
logIndex: context.logIndex
|
|
3744
|
+
}
|
|
3745
|
+
};
|
|
3746
|
+
saveExternalDepositLockSession(next, active);
|
|
3747
|
+
installSession(next);
|
|
3748
|
+
},
|
|
3749
|
+
[installSession]
|
|
3750
|
+
);
|
|
3751
|
+
const retryAfterCredit = useCallback(async () => {
|
|
3752
|
+
const active = sessionRef.current;
|
|
3753
|
+
if (!active?.creditedAmount) throw new Error("No credited external deposit to recover");
|
|
3754
|
+
if (signingRef.current || settlingRef.current) {
|
|
3755
|
+
throw new Error("A policy operation is already in progress");
|
|
3756
|
+
}
|
|
3757
|
+
if (privateReadAddress?.toLowerCase() !== active.owner.toLowerCase()) {
|
|
3758
|
+
throw new Error("Authenticated deposit account does not match the credited deposit");
|
|
3759
|
+
}
|
|
3760
|
+
const token = getTokenById(active.tokenId);
|
|
3761
|
+
if (!token || token.chainId !== active.chainId || token.contract === zeroAddress) {
|
|
3762
|
+
throw new Error("The credited token is no longer available");
|
|
3763
|
+
}
|
|
3764
|
+
const targetService = requireServiceAddress(serviceAddress);
|
|
3765
|
+
if (targetService.toLowerCase() !== active.serviceAddress.toLowerCase()) {
|
|
3766
|
+
throw new Error("The service for this credited deposit has changed");
|
|
3767
|
+
}
|
|
3768
|
+
if (active.payload) {
|
|
3769
|
+
await submitSession(active);
|
|
3770
|
+
return;
|
|
3771
|
+
}
|
|
3772
|
+
if (active.submissionAmbiguous) {
|
|
3773
|
+
throw new Error(
|
|
3774
|
+
"The previous lock submission may have succeeded; a fresh signature is unsafe"
|
|
3775
|
+
);
|
|
3776
|
+
}
|
|
3777
|
+
const owner = requireDepositLockOwner(address, privateReadAddress);
|
|
3778
|
+
const amount = externalDepositRetryAmount(active);
|
|
3779
|
+
let retrySession;
|
|
3780
|
+
signingRef.current = true;
|
|
3781
|
+
setIsSigning(true);
|
|
3782
|
+
try {
|
|
3783
|
+
await ensureCorrectChain(networkConfig.chainId);
|
|
3784
|
+
const signingWalletClient = await getWalletClient(config, {
|
|
3785
|
+
chainId: networkConfig.chainId
|
|
3786
|
+
});
|
|
3787
|
+
const payload = await createSignedLockRequest({
|
|
3788
|
+
client,
|
|
3789
|
+
walletClient: signingWalletClient,
|
|
3790
|
+
userAddress: owner,
|
|
3791
|
+
networkConfig,
|
|
3792
|
+
serviceAddress: targetService,
|
|
3793
|
+
tokenId: active.tokenId,
|
|
3794
|
+
amount,
|
|
3795
|
+
lockDuration: active.lockDuration
|
|
3796
|
+
});
|
|
3797
|
+
if (beneficiaryRef.current?.toLowerCase() !== owner.toLowerCase()) {
|
|
3798
|
+
throw new Error("Authenticated deposit account changed while signing");
|
|
3799
|
+
}
|
|
3800
|
+
retrySession = {
|
|
3801
|
+
...active,
|
|
3802
|
+
depositAmount: amount.toString(),
|
|
3803
|
+
generation: payload.signature,
|
|
3804
|
+
verification: void 0,
|
|
3805
|
+
submissionAmbiguous: void 0,
|
|
3806
|
+
payload
|
|
3807
|
+
};
|
|
3808
|
+
saveExternalDepositLockSession(retrySession, active);
|
|
3809
|
+
installSession(retrySession);
|
|
3810
|
+
} finally {
|
|
3811
|
+
signingRef.current = false;
|
|
3812
|
+
setIsSigning(false);
|
|
3813
|
+
}
|
|
3814
|
+
await submitSession(retrySession);
|
|
3815
|
+
}, [
|
|
3816
|
+
address,
|
|
3817
|
+
client,
|
|
3818
|
+
config,
|
|
3819
|
+
ensureCorrectChain,
|
|
3820
|
+
getTokenById,
|
|
3821
|
+
installSession,
|
|
3822
|
+
networkConfig,
|
|
3823
|
+
privateReadAddress,
|
|
3824
|
+
serviceAddress,
|
|
3825
|
+
submitSession
|
|
3826
|
+
]);
|
|
3827
|
+
const clearVerification = useCallback(() => {
|
|
3828
|
+
const active = sessionRef.current;
|
|
3829
|
+
if (!active?.verification || active.creditedAmount) return false;
|
|
3830
|
+
const next = clearExternalDepositVerification(active);
|
|
3831
|
+
if (!next) {
|
|
3832
|
+
installSession(loadExternalDepositLockSession(active.owner) ?? null);
|
|
3833
|
+
return false;
|
|
3834
|
+
}
|
|
3835
|
+
installSession(next);
|
|
3836
|
+
return true;
|
|
3837
|
+
}, [installSession]);
|
|
3838
|
+
const discardSession = useCallback(() => {
|
|
3839
|
+
const active = sessionRef.current;
|
|
3840
|
+
if (!active) return true;
|
|
3841
|
+
if (signingRef.current || settlingRef.current) return false;
|
|
3842
|
+
const pending = pendingCreditedTransitionRef.current;
|
|
3843
|
+
const discardTarget = pending && isSameExternalDepositLockSession(active, pending.credited) ? pending.predecessor : active;
|
|
3844
|
+
const discarded = discardExternalDepositLockSession(discardTarget);
|
|
3845
|
+
const restored = loadExternalDepositLockSession(active.owner) ?? null;
|
|
3846
|
+
if (discarded) pendingCreditedTransitionRef.current = null;
|
|
3847
|
+
installSession(restored);
|
|
3848
|
+
return discarded && !restored;
|
|
3849
|
+
}, [installSession]);
|
|
3850
|
+
return {
|
|
3851
|
+
isSigning,
|
|
3852
|
+
isSubmittingLock,
|
|
3853
|
+
session,
|
|
3854
|
+
signAndPersist,
|
|
3855
|
+
recordVerification,
|
|
3856
|
+
settleAfterCredit,
|
|
3857
|
+
retryAfterCredit,
|
|
3858
|
+
clearVerification,
|
|
3859
|
+
discardSession
|
|
3860
|
+
};
|
|
3861
|
+
}
|
|
3097
3862
|
var DEFAULT_MOONPAY_API_URL = "https://api.moonpay.com/v3";
|
|
3098
3863
|
function useMoonpayLimits({
|
|
3099
3864
|
currencyCode,
|
|
@@ -3328,6 +4093,14 @@ function MoonPayGate({ enabled, children }) {
|
|
|
3328
4093
|
if (!enabled || !apiKey) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
3329
4094
|
return /* @__PURE__ */ jsx(MoonPayProvider, { apiKey, children });
|
|
3330
4095
|
}
|
|
4096
|
+
function formatPostDepositLockFailure(error, token) {
|
|
4097
|
+
if (error?.reason === "credited-below-signed" && error.creditedAmount !== void 0 && error.signedAmount !== void 0 && token) {
|
|
4098
|
+
const creditedAmount = `${formatUnits(error.creditedAmount, token.decimals)} ${token.symbol}`;
|
|
4099
|
+
const signedAmount = `${formatUnits(error.signedAmount, token.decimals)} ${token.symbol}`;
|
|
4100
|
+
return `Credited amount (${creditedAmount}) is below the signed lock amount (${signedAmount})`;
|
|
4101
|
+
}
|
|
4102
|
+
return error?.message ?? "Lock submission failed";
|
|
4103
|
+
}
|
|
3331
4104
|
function MethodTabs({
|
|
3332
4105
|
activeTab,
|
|
3333
4106
|
onTabChange
|
|
@@ -3394,19 +4167,21 @@ function DepositView({
|
|
|
3394
4167
|
selectedToken,
|
|
3395
4168
|
amount,
|
|
3396
4169
|
allowance,
|
|
4170
|
+
externalMinimum,
|
|
3397
4171
|
onAmountChange,
|
|
3398
4172
|
onSelectToken,
|
|
3399
4173
|
onConnectWallet,
|
|
3400
4174
|
onSubmit,
|
|
3401
4175
|
isSubmitting = false
|
|
3402
4176
|
}) {
|
|
3403
|
-
const { getChainById: getChainById2, chains, serviceName, serviceIcon, networkConfig } = usePrivanaContext();
|
|
4177
|
+
const { getChainById: getChainById2, chains, serviceName, serviceIcon, networkConfig, hostedAuthConfig } = usePrivanaContext();
|
|
3404
4178
|
const { address, isConnected } = useAccount();
|
|
3405
4179
|
const appName = serviceName ?? "Privana";
|
|
3406
4180
|
const chain = selectedToken ? getChainById2(selectedToken.chainId) : void 0;
|
|
3407
4181
|
const targetChain = chain ?? chains[0];
|
|
3408
4182
|
const sourceLabel = source === "connected" ? "Connected Wallet" : "External Wallet";
|
|
3409
4183
|
const isConnectedSource = source === "connected";
|
|
4184
|
+
const isExternal = source === "external";
|
|
3410
4185
|
const isCreditCard = source === "credit-card";
|
|
3411
4186
|
const isNative = selectedToken?.contract === zeroAddress;
|
|
3412
4187
|
const { data: nativeBalanceData } = useBalance$1({
|
|
@@ -3440,8 +4215,14 @@ function DepositView({
|
|
|
3440
4215
|
const belowMoonpayMin = isCreditCard && hasValidAmount && moonpayMinBuy != null && parseFloat(amount) < moonpayMinBuy;
|
|
3441
4216
|
const moonpayLimitsUnready = isCreditCard && !!selectedToken?.moonpayCurrencyCode && moonpayMinBuy == null;
|
|
3442
4217
|
const creditCardUnavailable = isCreditCard && !!selectedToken && !selectedToken.moonpayCurrencyCode;
|
|
3443
|
-
const
|
|
3444
|
-
const
|
|
4218
|
+
const externalTokenUnavailable = isExternal && !!selectedToken && isNative;
|
|
4219
|
+
const externalMinimumRequired = isExternal && !!allowance && !!selectedToken;
|
|
4220
|
+
const externalMinimumLoading = externalMinimumRequired && externalMinimum === void 0;
|
|
4221
|
+
const externalMinimumUnavailable = externalMinimumRequired && externalMinimum === null;
|
|
4222
|
+
const belowExternalMinimum = externalMinimumRequired && hasValidAmount && !tooManyDecimals && !!selectedToken && typeof externalMinimum === "bigint" && parseTokenAmount(amount, selectedToken.decimals) < externalMinimum;
|
|
4223
|
+
const externalNeedsWallet = isExternal && (!!allowance || !hostedAuthConfig);
|
|
4224
|
+
const needsConnect = (isConnectedSource || externalNeedsWallet) && !isConnected;
|
|
4225
|
+
const canDeposit = hasValidAmount && !!selectedToken && !tooManyDecimals && !exceedsBalance && !belowMoonpayMin && !moonpayLimitsUnready && !creditCardUnavailable && !externalTokenUnavailable && !externalMinimumLoading && !externalMinimumUnavailable && !belowExternalMinimum && !needsConnect;
|
|
3445
4226
|
const handleMax = () => {
|
|
3446
4227
|
const max = formattedWalletBalance.replace(/\s/g, "");
|
|
3447
4228
|
if (parseFloat(max) > 0) onAmountChange(max);
|
|
@@ -3450,9 +4231,12 @@ function DepositView({
|
|
|
3450
4231
|
isCreditCard ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3451
4232
|
/* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Buy with credit card and deposit" }),
|
|
3452
4233
|
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Enter your deposit amount and proceed to sign a policy." })
|
|
3453
|
-
] }) : /* @__PURE__ */ jsxs("
|
|
3454
|
-
"
|
|
3455
|
-
|
|
4234
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
4235
|
+
/* @__PURE__ */ jsxs("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: [
|
|
4236
|
+
"Deposit from ",
|
|
4237
|
+
sourceLabel
|
|
4238
|
+
] }),
|
|
4239
|
+
allowance && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Enter your deposit amount and proceed to sign a policy." })
|
|
3456
4240
|
] }),
|
|
3457
4241
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
3458
4242
|
/* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Token" }),
|
|
@@ -3461,7 +4245,8 @@ function DepositView({
|
|
|
3461
4245
|
{
|
|
3462
4246
|
type: "button",
|
|
3463
4247
|
onClick: onSelectToken,
|
|
3464
|
-
|
|
4248
|
+
disabled: isSubmitting,
|
|
4249
|
+
className: "border-border bg-input flex w-full cursor-pointer items-center gap-3 rounded-lg border p-3 text-left disabled:cursor-not-allowed disabled:opacity-50",
|
|
3465
4250
|
children: [
|
|
3466
4251
|
selectedToken ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3467
4252
|
/* @__PURE__ */ jsx("div", { className: "h-8 w-8 shrink-0 overflow-hidden rounded-full", children: getTokenIcon(selectedToken.symbol, 32) }),
|
|
@@ -3502,6 +4287,7 @@ function DepositView({
|
|
|
3502
4287
|
type: "text",
|
|
3503
4288
|
inputMode: "decimal",
|
|
3504
4289
|
placeholder: "Enter Amount",
|
|
4290
|
+
disabled: isSubmitting,
|
|
3505
4291
|
value: amount,
|
|
3506
4292
|
onChange: (e) => {
|
|
3507
4293
|
const value = e.target.value.replace(/[^0-9.,]/g, "").replace(/,/g, ".");
|
|
@@ -3533,11 +4319,24 @@ function DepositView({
|
|
|
3533
4319
|
moonpayMinBuy,
|
|
3534
4320
|
" USD."
|
|
3535
4321
|
] }),
|
|
4322
|
+
belowExternalMinimum && selectedToken && typeof externalMinimum === "bigint" && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
4323
|
+
"Minimum deposit is ",
|
|
4324
|
+
formatUnits(externalMinimum, selectedToken.decimals),
|
|
4325
|
+
" ",
|
|
4326
|
+
selectedToken.symbol,
|
|
4327
|
+
"."
|
|
4328
|
+
] }),
|
|
4329
|
+
externalMinimumLoading && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Checking minimum deposit\u2026" }),
|
|
4330
|
+
externalMinimumUnavailable && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Couldn\u2019t load the minimum deposit. Please try again." }),
|
|
3536
4331
|
isCreditCard && moonpayLimitsError && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Couldn\u2019t load purchase limits. Please try again." }),
|
|
3537
4332
|
isCreditCard && moonpayLimitsLoading && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Checking purchase limits\u2026" }),
|
|
3538
4333
|
creditCardUnavailable && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
3539
4334
|
selectedToken?.symbol ?? "This token",
|
|
3540
4335
|
" isn\u2019t available for card purchases yet."
|
|
4336
|
+
] }),
|
|
4337
|
+
externalTokenUnavailable && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
4338
|
+
selectedToken?.symbol ?? "This token",
|
|
4339
|
+
" can\u2019t be used for external deposits. Choose an ERC20 token."
|
|
3541
4340
|
] })
|
|
3542
4341
|
] }),
|
|
3543
4342
|
allowance && /* @__PURE__ */ jsx(
|
|
@@ -3612,19 +4411,33 @@ function AwaitingDepositStatus({
|
|
|
3612
4411
|
function ExternalDepositView({
|
|
3613
4412
|
token,
|
|
3614
4413
|
amount,
|
|
3615
|
-
|
|
4414
|
+
depositAddressState,
|
|
4415
|
+
onCredited,
|
|
4416
|
+
onDiscardLock,
|
|
4417
|
+
lock
|
|
3616
4418
|
}) {
|
|
3617
|
-
const { getChainById: getChainById2 } = usePrivanaContext();
|
|
3618
|
-
const
|
|
4419
|
+
const { getChainById: getChainById2, getTokenById, serviceName } = usePrivanaContext();
|
|
4420
|
+
const appName = serviceName ?? "Privana";
|
|
4421
|
+
const { depositAddress, isReady, isLoading } = depositAddressState;
|
|
3619
4422
|
const chain = token ? getChainById2(token.chainId) : void 0;
|
|
4423
|
+
const lockSession = lock?.session;
|
|
4424
|
+
const recordVerification = lock?.recordVerification;
|
|
4425
|
+
const settleAfterCredit = lock?.settleAfterCredit;
|
|
4426
|
+
const retryAfterCredit = lock?.retryAfterCredit;
|
|
4427
|
+
const discoveryToken = lockSession ? getTokenById(lockSession.tokenId) : token;
|
|
3620
4428
|
const [copied, setCopied] = useState(false);
|
|
3621
4429
|
const [deadline] = useState(() => Date.now() + LISTENING_WINDOW_MS);
|
|
3622
4430
|
const secondsLeft = useCountdown(deadline);
|
|
3623
4431
|
const listening = secondsLeft > 0;
|
|
3624
4432
|
const [nothingFound, setNothingFound] = useState(false);
|
|
3625
4433
|
const [credited, setCredited] = useState(null);
|
|
4434
|
+
const [showCancelWarning, setShowCancelWarning] = useState(false);
|
|
3626
4435
|
const verification = useDepositVerification({
|
|
3627
|
-
onCredited: (txHash) => {
|
|
4436
|
+
onCredited: (txHash, _response, creditedAmount) => {
|
|
4437
|
+
if (settleAfterCredit) {
|
|
4438
|
+
void settleAfterCredit(txHash, creditedAmount);
|
|
4439
|
+
return;
|
|
4440
|
+
}
|
|
3628
4441
|
if (onCredited) {
|
|
3629
4442
|
onCredited();
|
|
3630
4443
|
return;
|
|
@@ -3632,8 +4445,9 @@ function ExternalDepositView({
|
|
|
3632
4445
|
setCredited({ txHash });
|
|
3633
4446
|
}
|
|
3634
4447
|
});
|
|
3635
|
-
const { isVerifying, verificationFailed, didTimeout, verify } = verification;
|
|
3636
|
-
const scanPaused = isVerifying || verificationFailed || didTimeout || !!credited;
|
|
4448
|
+
const { isVerifying, verificationFailed, canCheckAnotherTransfer, didTimeout, verify } = verification;
|
|
4449
|
+
const scanPaused = isVerifying || verificationFailed || didTimeout || !!credited || !!lock?.isSigning || !!lock?.isSubmittingLock || !!lockSession?.verification || !!lockSession?.creditedAmount;
|
|
4450
|
+
const showDepositInstructions = !credited && !lockSession?.creditedAmount;
|
|
3637
4451
|
const {
|
|
3638
4452
|
pending,
|
|
3639
4453
|
isFetching: isScanning,
|
|
@@ -3642,23 +4456,58 @@ function ExternalDepositView({
|
|
|
3642
4456
|
isUnavailable,
|
|
3643
4457
|
refetch: refetchPendingDeposits
|
|
3644
4458
|
} = usePendingDeposits({
|
|
3645
|
-
chainId:
|
|
3646
|
-
|
|
4459
|
+
chainId: discoveryToken?.chainId,
|
|
4460
|
+
tokenAddress: discoveryToken?.contract === zeroAddress ? void 0 : discoveryToken?.contract,
|
|
4461
|
+
enabled: isReady && !!depositAddress && !!discoveryToken && discoveryToken.contract !== zeroAddress,
|
|
3647
4462
|
refetchInterval: listening && !scanPaused ? 3e4 : false
|
|
3648
4463
|
});
|
|
3649
4464
|
const processedRef = useRef(/* @__PURE__ */ new Set());
|
|
4465
|
+
const verificationRunRef = useRef(null);
|
|
4466
|
+
useEffect(
|
|
4467
|
+
() => () => {
|
|
4468
|
+
verificationRunRef.current = null;
|
|
4469
|
+
},
|
|
4470
|
+
[]
|
|
4471
|
+
);
|
|
4472
|
+
useEffect(() => {
|
|
4473
|
+
const stored = lockSession?.verification;
|
|
4474
|
+
if (!stored || lockSession.creditedAmount || isVerifying || verificationFailed || didTimeout) {
|
|
4475
|
+
return;
|
|
4476
|
+
}
|
|
4477
|
+
const key = `${lockSession.generation}:${stored.chainId}:${stored.hash}:${stored.logIndex ?? 0}`;
|
|
4478
|
+
if (verificationRunRef.current === key) return;
|
|
4479
|
+
verificationRunRef.current = key;
|
|
4480
|
+
processedRef.current.add(`${stored.hash}:${stored.logIndex ?? 0}`);
|
|
4481
|
+
void verify({
|
|
4482
|
+
hash: stored.hash,
|
|
4483
|
+
chainId: stored.chainId,
|
|
4484
|
+
amount: BigInt(stored.amount),
|
|
4485
|
+
logIndex: stored.logIndex
|
|
4486
|
+
});
|
|
4487
|
+
}, [didTimeout, isVerifying, lockSession, verificationFailed, verify]);
|
|
3650
4488
|
useEffect(() => {
|
|
3651
4489
|
if (scanPaused) return;
|
|
3652
|
-
const next = [...pending].sort((a, b) => a.block_number - b.block_number).find(
|
|
4490
|
+
const next = [...pending].sort((a, b) => a.block_number - b.block_number).find(
|
|
4491
|
+
(deposit) => (!lockSession || isExternalDepositBlockInSession(lockSession, deposit.block_number)) && !processedRef.current.has(`${deposit.tx_hash}:${deposit.log_index}`)
|
|
4492
|
+
);
|
|
3653
4493
|
if (!next) return;
|
|
3654
|
-
|
|
3655
|
-
void verify({
|
|
4494
|
+
const context = {
|
|
3656
4495
|
hash: next.tx_hash,
|
|
3657
4496
|
chainId: next.chain_id,
|
|
3658
4497
|
amount: BigInt(next.amount),
|
|
3659
4498
|
logIndex: next.log_index
|
|
3660
|
-
}
|
|
3661
|
-
|
|
4499
|
+
};
|
|
4500
|
+
if (recordVerification) {
|
|
4501
|
+
try {
|
|
4502
|
+
recordVerification(context);
|
|
4503
|
+
} catch (err) {
|
|
4504
|
+
toast.error(err instanceof Error ? err.message : "Unable to save deposit recovery state");
|
|
4505
|
+
return;
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
processedRef.current.add(`${next.tx_hash}:${next.log_index}`);
|
|
4509
|
+
if (!recordVerification) void verify(context);
|
|
4510
|
+
}, [lockSession, pending, recordVerification, scanPaused, verify]);
|
|
3662
4511
|
const nothingFoundTimerRef = useRef(void 0);
|
|
3663
4512
|
useEffect(() => () => clearTimeout(nothingFoundTimerRef.current), []);
|
|
3664
4513
|
const handleRefresh = () => {
|
|
@@ -3671,11 +4520,27 @@ function ExternalDepositView({
|
|
|
3671
4520
|
}
|
|
3672
4521
|
});
|
|
3673
4522
|
};
|
|
3674
|
-
const handleCopy = () => {
|
|
4523
|
+
const handleCopy = async () => {
|
|
3675
4524
|
if (!depositAddress) return;
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
4525
|
+
try {
|
|
4526
|
+
await navigator.clipboard.writeText(depositAddress);
|
|
4527
|
+
setCopied(true);
|
|
4528
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
4529
|
+
} catch {
|
|
4530
|
+
toast.error("Unable to copy the deposit address");
|
|
4531
|
+
}
|
|
4532
|
+
};
|
|
4533
|
+
const handleCheckAnotherTransfer = () => {
|
|
4534
|
+
try {
|
|
4535
|
+
if (!lock?.clearVerification()) {
|
|
4536
|
+
toast.error("Deposit recovery changed in another tab. Please review the current state.");
|
|
4537
|
+
return;
|
|
4538
|
+
}
|
|
4539
|
+
verificationRunRef.current = null;
|
|
4540
|
+
verification.reset();
|
|
4541
|
+
} catch (err) {
|
|
4542
|
+
toast.error(err instanceof Error ? err.message : "Unable to continue deposit discovery");
|
|
4543
|
+
}
|
|
3679
4544
|
};
|
|
3680
4545
|
return /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
3681
4546
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
@@ -3689,27 +4554,64 @@ function ExternalDepositView({
|
|
|
3689
4554
|
/* @__PURE__ */ jsx("div", { className: "bg-border h-px w-full" }),
|
|
3690
4555
|
/* @__PURE__ */ jsx(SummaryRow, { value: amount || "\u2014", label: "Value" })
|
|
3691
4556
|
] }),
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Deposit
|
|
3695
|
-
/* @__PURE__ */ jsxs("div", { className: "flex
|
|
3696
|
-
/* @__PURE__ */ jsx("
|
|
3697
|
-
/* @__PURE__ */ jsxs(
|
|
3698
|
-
"
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
4557
|
+
showDepositInstructions && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4558
|
+
!scanPaused && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Send the displayed amount in one transfer. Transfers below the minimum are not combined or automatically returned." }),
|
|
4559
|
+
/* @__PURE__ */ jsx("div", { className: "bg-secondary flex h-50 items-center justify-center rounded-[10px]", children: !isReady ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground px-6 text-center text-sm", children: "Connect your wallet to generate a deposit address." }) : depositAddress ? /* @__PURE__ */ jsx("div", { className: "rounded-[10px] bg-white p-3", children: /* @__PURE__ */ jsx(QRCodeSVG, { value: depositAddress, size: 160 }) }) : isLoading ? /* @__PURE__ */ jsx(Skeleton, { className: "h-full w-full rounded-[10px]" }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Deposit address unavailable" }) }),
|
|
4560
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
4561
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Deposit Address" }),
|
|
4562
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
4563
|
+
/* @__PURE__ */ jsx("div", { className: "border-border flex h-10 min-w-0 flex-1 items-center rounded-[10px] border px-3", children: depositAddress ? /* @__PURE__ */ jsx("p", { className: "text-foreground min-w-0 flex-1 truncate text-sm", children: depositAddress }) : isReady && isLoading ? /* @__PURE__ */ jsx(Skeleton, { className: "h-4 w-3/4" }) : /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "\u2014" }) }),
|
|
4564
|
+
/* @__PURE__ */ jsxs(
|
|
4565
|
+
"button",
|
|
4566
|
+
{
|
|
4567
|
+
type: "button",
|
|
4568
|
+
onClick: handleCopy,
|
|
4569
|
+
disabled: !depositAddress,
|
|
4570
|
+
className: "bg-primary text-primary-foreground hover:bg-primary/90 flex h-10 shrink-0 cursor-pointer items-center gap-2 rounded-[10px] px-3 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
|
|
4571
|
+
children: [
|
|
4572
|
+
/* @__PURE__ */ jsx(CopyIcon, {}),
|
|
4573
|
+
copied ? "Copied" : "Copy"
|
|
4574
|
+
]
|
|
4575
|
+
}
|
|
4576
|
+
)
|
|
4577
|
+
] })
|
|
3710
4578
|
] })
|
|
3711
4579
|
] }),
|
|
3712
|
-
|
|
4580
|
+
showCancelWarning && lockSession && !lockSession.verification && !lockSession.creditedAmount ? /* @__PURE__ */ jsx(
|
|
4581
|
+
TransactionWarningView,
|
|
4582
|
+
{
|
|
4583
|
+
title: "Cancel this deposit?",
|
|
4584
|
+
message: "Only cancel if you have not sent funds. Canceling after a transfer was sent stops recovery and can leave the transfer uncredited or unlocked.",
|
|
4585
|
+
onDone: () => onDiscardLock?.(),
|
|
4586
|
+
actionLabel: "I haven\u2019t sent funds",
|
|
4587
|
+
onDismiss: () => setShowCancelWarning(false),
|
|
4588
|
+
dismissLabel: "Keep waiting"
|
|
4589
|
+
}
|
|
4590
|
+
) : lock?.isSigning ? /* @__PURE__ */ jsx(
|
|
4591
|
+
AwaitingDepositStatus,
|
|
4592
|
+
{
|
|
4593
|
+
title: "Confirm policy in your wallet",
|
|
4594
|
+
subtitle: "Sign a fresh policy for the funds already credited."
|
|
4595
|
+
}
|
|
4596
|
+
) : lock?.isSubmittingLock ? /* @__PURE__ */ jsx(
|
|
4597
|
+
AwaitingDepositStatus,
|
|
4598
|
+
{
|
|
4599
|
+
title: `Locking funds for ${appName}`,
|
|
4600
|
+
subtitle: "Deposit credited. Applying your signed policy\u2026"
|
|
4601
|
+
}
|
|
4602
|
+
) : lock?.session?.creditedAmount ? /* @__PURE__ */ jsx(
|
|
4603
|
+
TransactionWarningView,
|
|
4604
|
+
{
|
|
4605
|
+
title: lock.session.submissionAmbiguous ? "Lock status unknown" : "Deposit credited",
|
|
4606
|
+
message: lock.session.submissionAmbiguous ? "The lock may already have succeeded. Retry only the saved signature, or check locked funds before stopping recovery." : "The deposit is credited, but its signed lock still needs to be submitted.",
|
|
4607
|
+
onDone: () => {
|
|
4608
|
+
void retryAfterCredit?.().catch((err) => {
|
|
4609
|
+
toast.error(err instanceof Error ? err.message : "Policy recovery failed");
|
|
4610
|
+
});
|
|
4611
|
+
},
|
|
4612
|
+
actionLabel: lock.session.payload ? "Retry lock submission" : "Sign policy and lock funds"
|
|
4613
|
+
}
|
|
4614
|
+
) : credited ? /* @__PURE__ */ jsx(
|
|
3713
4615
|
TransactionSuccessView,
|
|
3714
4616
|
{
|
|
3715
4617
|
title: "Deposit Credited",
|
|
@@ -3726,14 +4628,15 @@ function ExternalDepositView({
|
|
|
3726
4628
|
title: "Could not verify deposit",
|
|
3727
4629
|
message: didTimeout ? "Verification timed out \u2014 your deposit may still be credited in the background." : verification.error?.message ?? "Your transfer was found but could not be verified.",
|
|
3728
4630
|
onRetry: () => void verification.retryVerification(),
|
|
3729
|
-
onDismiss: () => verification.reset(),
|
|
4631
|
+
onDismiss: lock ? canCheckAnotherTransfer ? handleCheckAnotherTransfer : void 0 : () => verification.reset(),
|
|
4632
|
+
dismissLabel: lock && canCheckAnotherTransfer ? "Check another transfer" : void 0,
|
|
3730
4633
|
isRetrying: isVerifying
|
|
3731
4634
|
}
|
|
3732
4635
|
) : isVerifying ? /* @__PURE__ */ jsx(
|
|
3733
4636
|
AwaitingDepositStatus,
|
|
3734
4637
|
{
|
|
3735
4638
|
title: "Deposit detected",
|
|
3736
|
-
subtitle: "Crediting the incoming transaction to your Privana balance
|
|
4639
|
+
subtitle: "Crediting the incoming transaction to your Privana balance\u2026"
|
|
3737
4640
|
}
|
|
3738
4641
|
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3739
4642
|
/* @__PURE__ */ jsx(
|
|
@@ -3755,7 +4658,16 @@ function ExternalDepositView({
|
|
|
3755
4658
|
children: "Refresh deposit status"
|
|
3756
4659
|
}
|
|
3757
4660
|
),
|
|
3758
|
-
|
|
4661
|
+
lockSession && !lockSession.verification && !lockSession.creditedAmount && /* @__PURE__ */ jsx(
|
|
4662
|
+
"button",
|
|
4663
|
+
{
|
|
4664
|
+
type: "button",
|
|
4665
|
+
onClick: () => setShowCancelWarning(true),
|
|
4666
|
+
className: "border-border text-foreground hover:bg-secondary flex h-10 w-full cursor-pointer items-center justify-center rounded-[10px] border px-3 py-2 text-sm font-medium transition-colors",
|
|
4667
|
+
children: "Cancel deposit"
|
|
4668
|
+
}
|
|
4669
|
+
),
|
|
4670
|
+
isRateLimited ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", children: "Checked too recently. Try again in a moment." }) : isUnavailable ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", children: "Deposit discovery is unavailable for this chain." }) : isScanError ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", children: "Chain temporarily unreachable. Retrying automatically." }) : nothingFound ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", children: "No incoming transaction found yet." }) : null
|
|
3759
4671
|
] })
|
|
3760
4672
|
] })
|
|
3761
4673
|
] });
|
|
@@ -3774,7 +4686,7 @@ function DepositModalContent({
|
|
|
3774
4686
|
onCloseBlockedChange,
|
|
3775
4687
|
onExit
|
|
3776
4688
|
}) {
|
|
3777
|
-
const { serviceName, enabledTokens, defaultToken, hostedAuthConfig, getChainById: getChainById2 } = usePrivanaContext();
|
|
4689
|
+
const { serviceName, enabledTokens, defaultToken, hostedAuthConfig, getChainById: getChainById2, getTokenById } = usePrivanaContext();
|
|
3778
4690
|
const { address } = useAccount();
|
|
3779
4691
|
const appName = serviceName ?? "Privana";
|
|
3780
4692
|
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
@@ -3783,6 +4695,10 @@ function DepositModalContent({
|
|
|
3783
4695
|
const [selectedTokenId, setSelectedTokenId] = useState(defaultToken?.id ?? "");
|
|
3784
4696
|
const [amount, setAmount] = useState("");
|
|
3785
4697
|
const selectedToken = enabledTokens.find((t) => t.id === selectedTokenId) ?? defaultToken;
|
|
4698
|
+
const externalDepositAddress = useDepositAddress({
|
|
4699
|
+
enabled: source === "external" && (view === "deposit" || view === "external-deposit")
|
|
4700
|
+
});
|
|
4701
|
+
const externalMinimum = source !== "external" || !selectedToken ? void 0 : externalDepositAddress.isError ? null : externalDepositAddress.response ? getExternalDepositMinimum(externalDepositAddress.response, selectedToken.chainId) ?? null : void 0;
|
|
3786
4702
|
const prevAddressRef = useRef(address);
|
|
3787
4703
|
useEffect(() => {
|
|
3788
4704
|
const prev = prevAddressRef.current;
|
|
@@ -3802,9 +4718,10 @@ function DepositModalContent({
|
|
|
3802
4718
|
const [showTimeout, setShowTimeout] = useState(false);
|
|
3803
4719
|
const [cancelled, setCancelled] = useState(false);
|
|
3804
4720
|
const [isSubmittingLock, setIsSubmittingLock] = useState(false);
|
|
3805
|
-
const [
|
|
4721
|
+
const [lockFailure, setLockFailure] = useState(null);
|
|
3806
4722
|
const finishDeposit = () => {
|
|
3807
4723
|
setAmount("");
|
|
4724
|
+
if (view === "external-deposit") setView("deposit");
|
|
3808
4725
|
if (onDepositSuccess) {
|
|
3809
4726
|
resetDeposit();
|
|
3810
4727
|
onDepositSuccess();
|
|
@@ -3839,6 +4756,7 @@ function DepositModalContent({
|
|
|
3839
4756
|
},
|
|
3840
4757
|
onLockSubmitted: () => {
|
|
3841
4758
|
setIsSubmittingLock(false);
|
|
4759
|
+
setLockFailure(null);
|
|
3842
4760
|
finishDeposit();
|
|
3843
4761
|
},
|
|
3844
4762
|
// The deposit credited; only the policy lock failed. Success must not
|
|
@@ -3846,7 +4764,7 @@ function DepositModalContent({
|
|
|
3846
4764
|
// error view and let the host re-prompt for a fresh lock.
|
|
3847
4765
|
onLockFailed: (err) => {
|
|
3848
4766
|
setIsSubmittingLock(false);
|
|
3849
|
-
|
|
4767
|
+
setLockFailure(err);
|
|
3850
4768
|
onLockFailed?.(err);
|
|
3851
4769
|
},
|
|
3852
4770
|
onCheckTimeout: () => {
|
|
@@ -3854,7 +4772,37 @@ function DepositModalContent({
|
|
|
3854
4772
|
setShowTimeout(true);
|
|
3855
4773
|
}
|
|
3856
4774
|
});
|
|
3857
|
-
const
|
|
4775
|
+
const externalLock = useExternalDepositLock({
|
|
4776
|
+
allowance,
|
|
4777
|
+
onLockSubmitted: () => {
|
|
4778
|
+
setLockFailure(null);
|
|
4779
|
+
finishDeposit();
|
|
4780
|
+
},
|
|
4781
|
+
// Same contract as the connected flow: the deposit credited, only the
|
|
4782
|
+
// lock failed — never report success, show the lock-error view instead.
|
|
4783
|
+
onLockFailed: (err) => {
|
|
4784
|
+
setLockFailure(err);
|
|
4785
|
+
onLockFailed?.(err);
|
|
4786
|
+
}
|
|
4787
|
+
});
|
|
4788
|
+
useEffect(() => {
|
|
4789
|
+
const restored = externalLock.session;
|
|
4790
|
+
if (!restored) return;
|
|
4791
|
+
const canRestore = view === "method" || source === "external" && (view === "deposit" || view === "external-deposit");
|
|
4792
|
+
if (!canRestore) return;
|
|
4793
|
+
const token = getTokenById(restored.tokenId);
|
|
4794
|
+
if (!token) return;
|
|
4795
|
+
setSource("external");
|
|
4796
|
+
setSelectedTokenId(token.id);
|
|
4797
|
+
setAmount(formatUnits(BigInt(restored.depositAmount), token.decimals));
|
|
4798
|
+
setView("external-deposit");
|
|
4799
|
+
}, [externalLock.session, getTokenById, source, view]);
|
|
4800
|
+
useEffect(() => {
|
|
4801
|
+
if (view === "external-deposit" && allowance && !externalLock.session) {
|
|
4802
|
+
setView("deposit");
|
|
4803
|
+
}
|
|
4804
|
+
}, [allowance, externalLock.session, view]);
|
|
4805
|
+
const isUnsafeToClose = (isGettingAddress || isSendingTransaction) && !cancelled || externalLock.isSigning || externalLock.isSubmittingLock;
|
|
3858
4806
|
useEffect(() => {
|
|
3859
4807
|
onCloseBlockedChange?.(isUnsafeToClose);
|
|
3860
4808
|
}, [isUnsafeToClose, onCloseBlockedChange]);
|
|
@@ -3867,7 +4815,33 @@ function DepositModalContent({
|
|
|
3867
4815
|
}, [depositError, verificationFailed]);
|
|
3868
4816
|
const handleSubmit = (args) => {
|
|
3869
4817
|
if (args.source === "external") {
|
|
3870
|
-
|
|
4818
|
+
const token2 = enabledTokens.find((candidate) => candidate.id === args.tokenId);
|
|
4819
|
+
if (!token2) return;
|
|
4820
|
+
if (token2.contract === zeroAddress) {
|
|
4821
|
+
toast.error("External deposits support ERC20 tokens only");
|
|
4822
|
+
return;
|
|
4823
|
+
}
|
|
4824
|
+
const depositAmount = parseTokenAmount(args.amount, token2.decimals);
|
|
4825
|
+
if (!allowance) {
|
|
4826
|
+
setView("external-deposit");
|
|
4827
|
+
return;
|
|
4828
|
+
}
|
|
4829
|
+
if (typeof externalMinimum !== "bigint") {
|
|
4830
|
+
toast.error("Minimum deposit is unavailable. Please try again.");
|
|
4831
|
+
return;
|
|
4832
|
+
}
|
|
4833
|
+
if (depositAmount < externalMinimum) {
|
|
4834
|
+
toast.error(
|
|
4835
|
+
`Minimum deposit is ${formatUnits(externalMinimum, token2.decimals)} ${token2.symbol}`
|
|
4836
|
+
);
|
|
4837
|
+
return;
|
|
4838
|
+
}
|
|
4839
|
+
externalLock.signAndPersist({
|
|
4840
|
+
tokenId: token2.id,
|
|
4841
|
+
amount: depositAmount
|
|
4842
|
+
}).then(() => setView("external-deposit")).catch((err) => {
|
|
4843
|
+
toast.error(err instanceof Error ? err.message : "Policy signing failed");
|
|
4844
|
+
});
|
|
3871
4845
|
return;
|
|
3872
4846
|
}
|
|
3873
4847
|
if (args.source === "credit-card") {
|
|
@@ -3919,7 +4893,14 @@ function DepositModalContent({
|
|
|
3919
4893
|
}
|
|
3920
4894
|
] : []
|
|
3921
4895
|
];
|
|
3922
|
-
const
|
|
4896
|
+
const hasExternalLockRecovery = view === "external-deposit" && !!externalLock.session?.creditedAmount;
|
|
4897
|
+
const externalSubmissionAmbiguous = hasExternalLockRecovery && !!lockFailure?.submissionMayHaveSucceeded;
|
|
4898
|
+
const savedExternalPayloadUsable = !!externalLock.session?.payload && isSignedLockUsable(externalLock.session.payload);
|
|
4899
|
+
const ambiguousRecoveryMustBeAbandoned = externalSubmissionAmbiguous && !savedExternalPayloadUsable;
|
|
4900
|
+
const externalRecoveryExitLabel = externalSubmissionAmbiguous ? "Stop recovery" : "Keep funds unlocked";
|
|
4901
|
+
const lockFailureToken = hasExternalLockRecovery && externalLock.session ? getTokenById(externalLock.session.tokenId) ?? selectedToken : selectedToken;
|
|
4902
|
+
const lockFailureMessage = formatPostDepositLockFailure(lockFailure, lockFailureToken);
|
|
4903
|
+
const flowView = showSuccess ? "deposit-success" : lockFailure ? "lock-error" : showTimeout ? "deposit-timeout" : verificationFailed ? "deposit-error" : isPending && !cancelled || isSubmittingLock ? "depositing" : null;
|
|
3923
4904
|
const activeView = flowView ?? view;
|
|
3924
4905
|
const handleDepositDone = () => {
|
|
3925
4906
|
setShowSuccess(false);
|
|
@@ -3932,10 +4913,28 @@ function DepositModalContent({
|
|
|
3932
4913
|
resetDeposit();
|
|
3933
4914
|
};
|
|
3934
4915
|
const handleLockFailedDone = () => {
|
|
3935
|
-
|
|
4916
|
+
if (hasExternalLockRecovery && !externalLock.discardSession()) {
|
|
4917
|
+
toast.error("Deposit recovery changed in another tab. Please review the current state.");
|
|
4918
|
+
return;
|
|
4919
|
+
}
|
|
4920
|
+
setLockFailure(null);
|
|
3936
4921
|
setAmount("");
|
|
3937
4922
|
setCancelled(false);
|
|
3938
4923
|
resetDeposit();
|
|
4924
|
+
if (view === "external-deposit") setView("deposit");
|
|
4925
|
+
};
|
|
4926
|
+
const handleExternalLockRetry = () => {
|
|
4927
|
+
externalLock.retryAfterCredit().catch((err) => {
|
|
4928
|
+
toast.error(err instanceof Error ? err.message : "Policy signing failed");
|
|
4929
|
+
});
|
|
4930
|
+
};
|
|
4931
|
+
const handleExternalVerificationDiscard = () => {
|
|
4932
|
+
if (!externalLock.discardSession()) {
|
|
4933
|
+
toast.error("Deposit recovery changed in another tab. Please review the current state.");
|
|
4934
|
+
return;
|
|
4935
|
+
}
|
|
4936
|
+
setAmount("");
|
|
4937
|
+
setView("deposit");
|
|
3939
4938
|
};
|
|
3940
4939
|
const handleDismissVerificationError = () => {
|
|
3941
4940
|
setAmount("");
|
|
@@ -3948,6 +4947,10 @@ function DepositModalContent({
|
|
|
3948
4947
|
};
|
|
3949
4948
|
const back = view === "external-deposit" ? { to: "deposit", label: "Deposit from External Wallet" } : view === "credit-card-widget" ? { to: "deposit", label: "Buy with credit card and deposit" } : view === "select-token" ? { to: "deposit", label: "Deposit" } : { to: "method", label: "Deposit Method" };
|
|
3950
4949
|
const goBack = () => {
|
|
4950
|
+
if (view === "external-deposit" && externalLock.session) {
|
|
4951
|
+
toast.error("Cancel the active deposit from the address screen before going back");
|
|
4952
|
+
return;
|
|
4953
|
+
}
|
|
3951
4954
|
if (back.to === "method") {
|
|
3952
4955
|
setAmount("");
|
|
3953
4956
|
setSelectedTokenId("");
|
|
@@ -3993,9 +4996,14 @@ function DepositModalContent({
|
|
|
3993
4996
|
activeView === "lock-error" && /* @__PURE__ */ jsx(
|
|
3994
4997
|
TransactionWarningView,
|
|
3995
4998
|
{
|
|
3996
|
-
title: "Deposit credited, lock failed",
|
|
3997
|
-
message: `Your deposit was credited but locking the funds for ${appName} failed: ${
|
|
3998
|
-
onDone: handleLockFailedDone
|
|
4999
|
+
title: externalSubmissionAmbiguous ? "Deposit credited, lock status unknown" : "Deposit credited, lock failed",
|
|
5000
|
+
message: externalSubmissionAmbiguous ? `Your deposit was credited, but the lock for ${appName} may already have succeeded. Retry only the saved signature; if it has expired, check locked funds before stopping recovery.` : `Your deposit was credited but locking the funds for ${appName} failed: ${lockFailureMessage}`,
|
|
5001
|
+
onDone: hasExternalLockRecovery && !ambiguousRecoveryMustBeAbandoned ? handleExternalLockRetry : handleLockFailedDone,
|
|
5002
|
+
actionLabel: hasExternalLockRecovery ? ambiguousRecoveryMustBeAbandoned ? externalRecoveryExitLabel : externalLock.session?.payload ? "Retry lock submission" : "Sign policy and lock funds" : void 0,
|
|
5003
|
+
pendingLabel: externalLock.isSigning ? "Confirm policy in wallet\u2026" : "Locking funds\u2026",
|
|
5004
|
+
isPending: externalLock.isSigning || externalLock.isSubmittingLock,
|
|
5005
|
+
onDismiss: hasExternalLockRecovery && !ambiguousRecoveryMustBeAbandoned ? handleLockFailedDone : void 0,
|
|
5006
|
+
dismissLabel: externalRecoveryExitLabel
|
|
3999
5007
|
}
|
|
4000
5008
|
),
|
|
4001
5009
|
activeView === "deposit-timeout" && /* @__PURE__ */ jsx(
|
|
@@ -4010,7 +5018,7 @@ function DepositModalContent({
|
|
|
4010
5018
|
TransactionErrorView,
|
|
4011
5019
|
{
|
|
4012
5020
|
title: "Verification failed",
|
|
4013
|
-
message: depositError?.message ? `Your transfer was sent on-chain but we could not verify the deposit. The funds are already at the deposit address
|
|
5021
|
+
message: depositError?.message ? `Your transfer was sent on-chain but we could not verify the deposit. The funds are already at the deposit address. Retry verification instead of starting a new deposit. (${depositError.message})` : "Your transfer was sent on-chain but we could not verify the deposit. The funds are already at the deposit address. Retry verification instead of starting a new deposit.",
|
|
4014
5022
|
explorerUrl: explorerTxUrl,
|
|
4015
5023
|
explorerLabel: "View transaction",
|
|
4016
5024
|
onRetry: handleRetryVerification,
|
|
@@ -4020,7 +5028,7 @@ function DepositModalContent({
|
|
|
4020
5028
|
activeView === "depositing" && /* @__PURE__ */ jsx(
|
|
4021
5029
|
TransactionProgressView,
|
|
4022
5030
|
{
|
|
4023
|
-
title: "Depositing
|
|
5031
|
+
title: "Depositing\u2026",
|
|
4024
5032
|
steps: depositSteps,
|
|
4025
5033
|
onCancel: isGettingAddress || isSendingTransaction ? handleDepositCancel : void 0
|
|
4026
5034
|
}
|
|
@@ -4074,11 +5082,12 @@ function DepositModalContent({
|
|
|
4074
5082
|
selectedToken,
|
|
4075
5083
|
amount,
|
|
4076
5084
|
allowance,
|
|
5085
|
+
externalMinimum,
|
|
4077
5086
|
onAmountChange: setAmount,
|
|
4078
5087
|
onSelectToken: () => setView("select-token"),
|
|
4079
5088
|
onConnectWallet,
|
|
4080
5089
|
onSubmit: handleSubmit,
|
|
4081
|
-
isSubmitting: isPending
|
|
5090
|
+
isSubmitting: isPending || externalLock.isSigning || externalLock.isSubmittingLock
|
|
4082
5091
|
}
|
|
4083
5092
|
) }),
|
|
4084
5093
|
activeView === "select-token" && /* @__PURE__ */ jsx(
|
|
@@ -4091,12 +5100,15 @@ function DepositModalContent({
|
|
|
4091
5100
|
}
|
|
4092
5101
|
}
|
|
4093
5102
|
),
|
|
4094
|
-
activeView === "external-deposit" && /* @__PURE__ */ jsx(
|
|
5103
|
+
activeView === "external-deposit" && (!allowance || externalLock.session) && /* @__PURE__ */ jsx(
|
|
4095
5104
|
ExternalDepositView,
|
|
4096
5105
|
{
|
|
4097
5106
|
token: selectedToken,
|
|
4098
5107
|
amount,
|
|
4099
|
-
|
|
5108
|
+
depositAddressState: externalDepositAddress,
|
|
5109
|
+
onCredited: allowance ? void 0 : onDepositSuccess,
|
|
5110
|
+
onDiscardLock: handleExternalVerificationDiscard,
|
|
5111
|
+
lock: externalLock.session ? externalLock : void 0
|
|
4100
5112
|
}
|
|
4101
5113
|
),
|
|
4102
5114
|
activeView === "credit-card-widget" && /* @__PURE__ */ jsx(MoonPayGate, { enabled: true, children: /* @__PURE__ */ jsx(
|
|
@@ -4108,7 +5120,7 @@ function DepositModalContent({
|
|
|
4108
5120
|
onCredited: allowance ? void 0 : finishCardPurchase,
|
|
4109
5121
|
onLockSubmitted: finishCardPurchase,
|
|
4110
5122
|
onLockFailed: (err) => {
|
|
4111
|
-
|
|
5123
|
+
setLockFailure(err);
|
|
4112
5124
|
onLockFailed?.(err);
|
|
4113
5125
|
}
|
|
4114
5126
|
}
|
|
@@ -4868,7 +5880,7 @@ function WalletModalContent({
|
|
|
4868
5880
|
onAmountChange: setAmount,
|
|
4869
5881
|
onFundSession: onFundSession ? fundSession : void 0,
|
|
4870
5882
|
onAddFunds: () => setView("deposit"),
|
|
4871
|
-
onWithdraw: handleWithdraw
|
|
5883
|
+
onWithdraw: session && !onEndSession ? void 0 : handleWithdraw
|
|
4872
5884
|
}
|
|
4873
5885
|
),
|
|
4874
5886
|
view === "funding-session" && /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: /* @__PURE__ */ jsx(
|