@oasisprotocol/privana-sdk 0.5.1 → 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-4IW4V7YJ.cjs → chunk-7KEQHWZB.cjs} +909 -240
- package/dist/chunk-7KEQHWZB.cjs.map +1 -0
- package/dist/{chunk-SAJ7K5WT.js → chunk-DZURIIRE.js} +905 -243
- package/dist/chunk-DZURIIRE.js.map +1 -0
- package/dist/index.cjs +1465 -365
- package/dist/index.cjs.map +1 -1
- package/dist/index.css +2 -2
- package/dist/index.css.map +1 -1
- package/dist/index.d.cts +26 -13
- package/dist/index.d.ts +26 -13
- package/dist/index.js +1227 -135
- 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-4IW4V7YJ.cjs.map +0 -1
- package/dist/chunk-SAJ7K5WT.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, 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);
|
|
@@ -479,8 +483,12 @@ function useDeposit(options = {}) {
|
|
|
479
483
|
try {
|
|
480
484
|
let confirmed = false;
|
|
481
485
|
try {
|
|
482
|
-
await getTransactionReceipt(config, {
|
|
483
|
-
|
|
486
|
+
const receipt = await getTransactionReceipt(config, {
|
|
487
|
+
hash,
|
|
488
|
+
chainId: persisted.chainId
|
|
489
|
+
});
|
|
490
|
+
const blockNumber = await getBlockNumber(config, { chainId: persisted.chainId });
|
|
491
|
+
confirmed = blockNumber - receipt.blockNumber + 1n >= BigInt(confirmations);
|
|
484
492
|
} catch {
|
|
485
493
|
}
|
|
486
494
|
if (!confirmed) {
|
|
@@ -541,6 +549,7 @@ function useDeposit(options = {}) {
|
|
|
541
549
|
const lockAmount = clampLockAmount(params.amount, params.postDepositLock?.maxAmount);
|
|
542
550
|
let signedLock;
|
|
543
551
|
if (params.postDepositLock) {
|
|
552
|
+
const lockOwner = requireDepositLockOwner(address, privateReadAddress);
|
|
544
553
|
setIsSwitchingChain(true);
|
|
545
554
|
try {
|
|
546
555
|
await ensureCorrectChain(networkConfig.chainId);
|
|
@@ -554,7 +563,7 @@ function useDeposit(options = {}) {
|
|
|
554
563
|
signedLock = await createSignedLockRequest({
|
|
555
564
|
client,
|
|
556
565
|
walletClient: signingWalletClient,
|
|
557
|
-
userAddress:
|
|
566
|
+
userAddress: lockOwner,
|
|
558
567
|
networkConfig,
|
|
559
568
|
serviceAddress: requireServiceAddress(
|
|
560
569
|
params.postDepositLock.serviceAddress ?? serviceAddress
|
|
@@ -563,6 +572,9 @@ function useDeposit(options = {}) {
|
|
|
563
572
|
amount: lockAmount,
|
|
564
573
|
lockDuration: params.postDepositLock.lockDuration
|
|
565
574
|
});
|
|
575
|
+
if (privateReadAddressRef.current?.toLowerCase() !== lockOwner.toLowerCase()) {
|
|
576
|
+
throw new Error("Authenticated deposit account changed while signing");
|
|
577
|
+
}
|
|
566
578
|
}
|
|
567
579
|
if (isStale()) return;
|
|
568
580
|
setIsSwitchingChain(true);
|
|
@@ -645,6 +657,7 @@ function useDeposit(options = {}) {
|
|
|
645
657
|
enabledTokens,
|
|
646
658
|
ensureCorrectChain,
|
|
647
659
|
networkConfig,
|
|
660
|
+
privateReadAddress,
|
|
648
661
|
queryClient,
|
|
649
662
|
serviceAddress,
|
|
650
663
|
writeContractAsync,
|
|
@@ -681,12 +694,6 @@ function useDeposit(options = {}) {
|
|
|
681
694
|
reset
|
|
682
695
|
};
|
|
683
696
|
}
|
|
684
|
-
function requireServiceAddress(serviceAddress) {
|
|
685
|
-
if (!serviceAddress) {
|
|
686
|
-
throw new Error("Service address not configured");
|
|
687
|
-
}
|
|
688
|
-
return serviceAddress;
|
|
689
|
-
}
|
|
690
697
|
function useDepositAddress(options = {}) {
|
|
691
698
|
const queryClient = useContext(QueryClientContext);
|
|
692
699
|
const accountingContext = useSafePrivanaContext();
|
|
@@ -699,11 +706,12 @@ function useDepositAddress(options = {}) {
|
|
|
699
706
|
queryFn: async () => {
|
|
700
707
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
701
708
|
if (!client) throw new Error("No accounting client");
|
|
702
|
-
return executePrivateRead(() =>
|
|
709
|
+
return executePrivateRead((readClient) => readClient.getDepositAddress());
|
|
703
710
|
},
|
|
704
711
|
enabled: isReady && (options.enabled ?? true)
|
|
705
712
|
});
|
|
706
713
|
return {
|
|
714
|
+
response: query.data,
|
|
707
715
|
depositAddress: query.data?.deposit_address ?? null,
|
|
708
716
|
isReady,
|
|
709
717
|
isLoading: query.isLoading,
|
|
@@ -1154,7 +1162,7 @@ function useLockedFunds(options = {}) {
|
|
|
1154
1162
|
queryKey: ["accounting-locked-funds", ...privateReadQueryScope, serviceAddress ?? null],
|
|
1155
1163
|
queryFn: async () => {
|
|
1156
1164
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1157
|
-
return executePrivateRead(() =>
|
|
1165
|
+
return executePrivateRead((readClient) => readClient.getLockedFunds(serviceAddress));
|
|
1158
1166
|
},
|
|
1159
1167
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!client,
|
|
1160
1168
|
refetchInterval: pollingInterval,
|
|
@@ -1179,7 +1187,7 @@ function useTotalLockedBalance(options = {}) {
|
|
|
1179
1187
|
queryFn: async () => {
|
|
1180
1188
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1181
1189
|
if (!tokenId) throw new Error("No token ID provided");
|
|
1182
|
-
return executePrivateRead(() =>
|
|
1190
|
+
return executePrivateRead((readClient) => readClient.getTotalLockedBalance(tokenId));
|
|
1183
1191
|
},
|
|
1184
1192
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!tokenId && !!client,
|
|
1185
1193
|
refetchInterval: pollingInterval
|
|
@@ -1199,7 +1207,7 @@ function useExpiredLocks(options = {}) {
|
|
|
1199
1207
|
queryKey: ["accounting-expired-locks", ...privateReadQueryScope],
|
|
1200
1208
|
queryFn: async () => {
|
|
1201
1209
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1202
|
-
return executePrivateRead(() =>
|
|
1210
|
+
return executePrivateRead((readClient) => readClient.getExpiredLocks());
|
|
1203
1211
|
},
|
|
1204
1212
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!client,
|
|
1205
1213
|
refetchInterval: pollingInterval
|
|
@@ -1232,7 +1240,7 @@ function usePendingDeposits(options = {}) {
|
|
|
1232
1240
|
if (!client) throw new Error("No accounting client");
|
|
1233
1241
|
try {
|
|
1234
1242
|
return await executePrivateRead(
|
|
1235
|
-
() =>
|
|
1243
|
+
(readClient) => readClient.getPendingDeposits({
|
|
1236
1244
|
chain_id: chainId,
|
|
1237
1245
|
token_address: tokenAddress,
|
|
1238
1246
|
lookback_blocks: lookbackBlocks
|
|
@@ -1313,7 +1321,7 @@ function useHistory(options = {}) {
|
|
|
1313
1321
|
queryKey: ["accounting-history", ...privateReadQueryScope, offset, limit],
|
|
1314
1322
|
queryFn: async () => {
|
|
1315
1323
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1316
|
-
return executePrivateRead(() =>
|
|
1324
|
+
return executePrivateRead((readClient) => readClient.getHistory({ offset, limit }));
|
|
1317
1325
|
},
|
|
1318
1326
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!client,
|
|
1319
1327
|
refetchInterval: pollingInterval,
|
|
@@ -1900,7 +1908,16 @@ function WarningIcon() {
|
|
|
1900
1908
|
}
|
|
1901
1909
|
) });
|
|
1902
1910
|
}
|
|
1903
|
-
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
|
+
}) {
|
|
1904
1921
|
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-4", children: [
|
|
1905
1922
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1906
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, {}) }),
|
|
@@ -1909,14 +1926,26 @@ function TransactionWarningView({ title, message, onDone }) {
|
|
|
1909
1926
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: message })
|
|
1910
1927
|
] })
|
|
1911
1928
|
] }),
|
|
1912
|
-
/* @__PURE__ */
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
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
|
+
] })
|
|
1920
1949
|
] });
|
|
1921
1950
|
}
|
|
1922
1951
|
function TransactionErrorView({
|
|
@@ -1926,7 +1955,9 @@ function TransactionErrorView({
|
|
|
1926
1955
|
explorerLabel,
|
|
1927
1956
|
onRetry,
|
|
1928
1957
|
onDismiss,
|
|
1929
|
-
isRetrying
|
|
1958
|
+
isRetrying,
|
|
1959
|
+
retryLabel = "Retry Verification",
|
|
1960
|
+
dismissLabel = "Close"
|
|
1930
1961
|
}) {
|
|
1931
1962
|
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-4", children: [
|
|
1932
1963
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
@@ -1950,13 +1981,13 @@ function TransactionErrorView({
|
|
|
1950
1981
|
}
|
|
1951
1982
|
),
|
|
1952
1983
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1953
|
-
/* @__PURE__ */ jsx(
|
|
1984
|
+
onDismiss && /* @__PURE__ */ jsx(
|
|
1954
1985
|
"button",
|
|
1955
1986
|
{
|
|
1956
1987
|
onClick: onDismiss,
|
|
1957
1988
|
disabled: isRetrying,
|
|
1958
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",
|
|
1959
|
-
children:
|
|
1990
|
+
children: dismissLabel
|
|
1960
1991
|
}
|
|
1961
1992
|
),
|
|
1962
1993
|
/* @__PURE__ */ jsx(
|
|
@@ -1965,7 +1996,7 @@ function TransactionErrorView({
|
|
|
1965
1996
|
onClick: onRetry,
|
|
1966
1997
|
disabled: isRetrying,
|
|
1967
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",
|
|
1968
|
-
children: isRetrying ? "Retrying
|
|
1999
|
+
children: isRetrying ? "Retrying\u2026" : retryLabel
|
|
1969
2000
|
}
|
|
1970
2001
|
)
|
|
1971
2002
|
] })
|
|
@@ -2105,7 +2136,7 @@ function DepositForm({
|
|
|
2105
2136
|
};
|
|
2106
2137
|
const getButtonText = () => {
|
|
2107
2138
|
if (!isConnected) return "Connect Wallet";
|
|
2108
|
-
if (isSwitchingChain) return "Switching
|
|
2139
|
+
if (isSwitchingChain) return "Switching\u2026";
|
|
2109
2140
|
return "Deposit";
|
|
2110
2141
|
};
|
|
2111
2142
|
if (showSuccess) {
|
|
@@ -2129,7 +2160,7 @@ function DepositForm({
|
|
|
2129
2160
|
);
|
|
2130
2161
|
}
|
|
2131
2162
|
if (verificationFailed) {
|
|
2132
|
-
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.";
|
|
2133
2164
|
const detail = error?.message;
|
|
2134
2165
|
const message = detail ? `${baseMessage} (${detail})` : baseMessage;
|
|
2135
2166
|
return /* @__PURE__ */ jsx(
|
|
@@ -2149,7 +2180,7 @@ function DepositForm({
|
|
|
2149
2180
|
return /* @__PURE__ */ jsx(
|
|
2150
2181
|
TransactionProgressView,
|
|
2151
2182
|
{
|
|
2152
|
-
title: "Depositing
|
|
2183
|
+
title: "Depositing\u2026",
|
|
2153
2184
|
steps: depositSteps,
|
|
2154
2185
|
onCancel: canCancel ? handleCancel : void 0
|
|
2155
2186
|
}
|
|
@@ -3089,6 +3120,745 @@ function PrivanaButton({
|
|
|
3089
3120
|
)
|
|
3090
3121
|
] });
|
|
3091
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
|
+
}
|
|
3092
3862
|
var DEFAULT_MOONPAY_API_URL = "https://api.moonpay.com/v3";
|
|
3093
3863
|
function useMoonpayLimits({
|
|
3094
3864
|
currencyCode,
|
|
@@ -3323,6 +4093,14 @@ function MoonPayGate({ enabled, children }) {
|
|
|
3323
4093
|
if (!enabled || !apiKey) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
3324
4094
|
return /* @__PURE__ */ jsx(MoonPayProvider, { apiKey, children });
|
|
3325
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
|
+
}
|
|
3326
4104
|
function MethodTabs({
|
|
3327
4105
|
activeTab,
|
|
3328
4106
|
onTabChange
|
|
@@ -3389,19 +4167,21 @@ function DepositView({
|
|
|
3389
4167
|
selectedToken,
|
|
3390
4168
|
amount,
|
|
3391
4169
|
allowance,
|
|
4170
|
+
externalMinimum,
|
|
3392
4171
|
onAmountChange,
|
|
3393
4172
|
onSelectToken,
|
|
3394
4173
|
onConnectWallet,
|
|
3395
4174
|
onSubmit,
|
|
3396
4175
|
isSubmitting = false
|
|
3397
4176
|
}) {
|
|
3398
|
-
const { getChainById: getChainById2, chains, serviceName, serviceIcon, networkConfig } = usePrivanaContext();
|
|
4177
|
+
const { getChainById: getChainById2, chains, serviceName, serviceIcon, networkConfig, hostedAuthConfig } = usePrivanaContext();
|
|
3399
4178
|
const { address, isConnected } = useAccount();
|
|
3400
4179
|
const appName = serviceName ?? "Privana";
|
|
3401
4180
|
const chain = selectedToken ? getChainById2(selectedToken.chainId) : void 0;
|
|
3402
4181
|
const targetChain = chain ?? chains[0];
|
|
3403
4182
|
const sourceLabel = source === "connected" ? "Connected Wallet" : "External Wallet";
|
|
3404
4183
|
const isConnectedSource = source === "connected";
|
|
4184
|
+
const isExternal = source === "external";
|
|
3405
4185
|
const isCreditCard = source === "credit-card";
|
|
3406
4186
|
const isNative = selectedToken?.contract === zeroAddress;
|
|
3407
4187
|
const { data: nativeBalanceData } = useBalance$1({
|
|
@@ -3435,8 +4215,14 @@ function DepositView({
|
|
|
3435
4215
|
const belowMoonpayMin = isCreditCard && hasValidAmount && moonpayMinBuy != null && parseFloat(amount) < moonpayMinBuy;
|
|
3436
4216
|
const moonpayLimitsUnready = isCreditCard && !!selectedToken?.moonpayCurrencyCode && moonpayMinBuy == null;
|
|
3437
4217
|
const creditCardUnavailable = isCreditCard && !!selectedToken && !selectedToken.moonpayCurrencyCode;
|
|
3438
|
-
const
|
|
3439
|
-
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;
|
|
3440
4226
|
const handleMax = () => {
|
|
3441
4227
|
const max = formattedWalletBalance.replace(/\s/g, "");
|
|
3442
4228
|
if (parseFloat(max) > 0) onAmountChange(max);
|
|
@@ -3445,9 +4231,12 @@ function DepositView({
|
|
|
3445
4231
|
isCreditCard ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3446
4232
|
/* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Buy with credit card and deposit" }),
|
|
3447
4233
|
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Enter your deposit amount and proceed to sign a policy." })
|
|
3448
|
-
] }) : /* @__PURE__ */ jsxs("
|
|
3449
|
-
"
|
|
3450
|
-
|
|
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." })
|
|
3451
4240
|
] }),
|
|
3452
4241
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
3453
4242
|
/* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Token" }),
|
|
@@ -3456,7 +4245,8 @@ function DepositView({
|
|
|
3456
4245
|
{
|
|
3457
4246
|
type: "button",
|
|
3458
4247
|
onClick: onSelectToken,
|
|
3459
|
-
|
|
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",
|
|
3460
4250
|
children: [
|
|
3461
4251
|
selectedToken ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3462
4252
|
/* @__PURE__ */ jsx("div", { className: "h-8 w-8 shrink-0 overflow-hidden rounded-full", children: getTokenIcon(selectedToken.symbol, 32) }),
|
|
@@ -3497,6 +4287,7 @@ function DepositView({
|
|
|
3497
4287
|
type: "text",
|
|
3498
4288
|
inputMode: "decimal",
|
|
3499
4289
|
placeholder: "Enter Amount",
|
|
4290
|
+
disabled: isSubmitting,
|
|
3500
4291
|
value: amount,
|
|
3501
4292
|
onChange: (e) => {
|
|
3502
4293
|
const value = e.target.value.replace(/[^0-9.,]/g, "").replace(/,/g, ".");
|
|
@@ -3528,11 +4319,24 @@ function DepositView({
|
|
|
3528
4319
|
moonpayMinBuy,
|
|
3529
4320
|
" USD."
|
|
3530
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." }),
|
|
3531
4331
|
isCreditCard && moonpayLimitsError && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Couldn\u2019t load purchase limits. Please try again." }),
|
|
3532
4332
|
isCreditCard && moonpayLimitsLoading && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Checking purchase limits\u2026" }),
|
|
3533
4333
|
creditCardUnavailable && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
3534
4334
|
selectedToken?.symbol ?? "This token",
|
|
3535
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."
|
|
3536
4340
|
] })
|
|
3537
4341
|
] }),
|
|
3538
4342
|
allowance && /* @__PURE__ */ jsx(
|
|
@@ -3607,19 +4411,33 @@ function AwaitingDepositStatus({
|
|
|
3607
4411
|
function ExternalDepositView({
|
|
3608
4412
|
token,
|
|
3609
4413
|
amount,
|
|
3610
|
-
|
|
4414
|
+
depositAddressState,
|
|
4415
|
+
onCredited,
|
|
4416
|
+
onDiscardLock,
|
|
4417
|
+
lock
|
|
3611
4418
|
}) {
|
|
3612
|
-
const { getChainById: getChainById2 } = usePrivanaContext();
|
|
3613
|
-
const
|
|
4419
|
+
const { getChainById: getChainById2, getTokenById, serviceName } = usePrivanaContext();
|
|
4420
|
+
const appName = serviceName ?? "Privana";
|
|
4421
|
+
const { depositAddress, isReady, isLoading } = depositAddressState;
|
|
3614
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;
|
|
3615
4428
|
const [copied, setCopied] = useState(false);
|
|
3616
4429
|
const [deadline] = useState(() => Date.now() + LISTENING_WINDOW_MS);
|
|
3617
4430
|
const secondsLeft = useCountdown(deadline);
|
|
3618
4431
|
const listening = secondsLeft > 0;
|
|
3619
4432
|
const [nothingFound, setNothingFound] = useState(false);
|
|
3620
4433
|
const [credited, setCredited] = useState(null);
|
|
4434
|
+
const [showCancelWarning, setShowCancelWarning] = useState(false);
|
|
3621
4435
|
const verification = useDepositVerification({
|
|
3622
|
-
onCredited: (txHash) => {
|
|
4436
|
+
onCredited: (txHash, _response, creditedAmount) => {
|
|
4437
|
+
if (settleAfterCredit) {
|
|
4438
|
+
void settleAfterCredit(txHash, creditedAmount);
|
|
4439
|
+
return;
|
|
4440
|
+
}
|
|
3623
4441
|
if (onCredited) {
|
|
3624
4442
|
onCredited();
|
|
3625
4443
|
return;
|
|
@@ -3627,8 +4445,9 @@ function ExternalDepositView({
|
|
|
3627
4445
|
setCredited({ txHash });
|
|
3628
4446
|
}
|
|
3629
4447
|
});
|
|
3630
|
-
const { isVerifying, verificationFailed, didTimeout, verify } = verification;
|
|
3631
|
-
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;
|
|
3632
4451
|
const {
|
|
3633
4452
|
pending,
|
|
3634
4453
|
isFetching: isScanning,
|
|
@@ -3637,23 +4456,58 @@ function ExternalDepositView({
|
|
|
3637
4456
|
isUnavailable,
|
|
3638
4457
|
refetch: refetchPendingDeposits
|
|
3639
4458
|
} = usePendingDeposits({
|
|
3640
|
-
chainId:
|
|
3641
|
-
|
|
4459
|
+
chainId: discoveryToken?.chainId,
|
|
4460
|
+
tokenAddress: discoveryToken?.contract === zeroAddress ? void 0 : discoveryToken?.contract,
|
|
4461
|
+
enabled: isReady && !!depositAddress && !!discoveryToken && discoveryToken.contract !== zeroAddress,
|
|
3642
4462
|
refetchInterval: listening && !scanPaused ? 3e4 : false
|
|
3643
4463
|
});
|
|
3644
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]);
|
|
3645
4488
|
useEffect(() => {
|
|
3646
4489
|
if (scanPaused) return;
|
|
3647
|
-
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
|
+
);
|
|
3648
4493
|
if (!next) return;
|
|
3649
|
-
|
|
3650
|
-
void verify({
|
|
4494
|
+
const context = {
|
|
3651
4495
|
hash: next.tx_hash,
|
|
3652
4496
|
chainId: next.chain_id,
|
|
3653
4497
|
amount: BigInt(next.amount),
|
|
3654
4498
|
logIndex: next.log_index
|
|
3655
|
-
}
|
|
3656
|
-
|
|
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]);
|
|
3657
4511
|
const nothingFoundTimerRef = useRef(void 0);
|
|
3658
4512
|
useEffect(() => () => clearTimeout(nothingFoundTimerRef.current), []);
|
|
3659
4513
|
const handleRefresh = () => {
|
|
@@ -3666,11 +4520,27 @@ function ExternalDepositView({
|
|
|
3666
4520
|
}
|
|
3667
4521
|
});
|
|
3668
4522
|
};
|
|
3669
|
-
const handleCopy = () => {
|
|
4523
|
+
const handleCopy = async () => {
|
|
3670
4524
|
if (!depositAddress) return;
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
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
|
+
}
|
|
3674
4544
|
};
|
|
3675
4545
|
return /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
3676
4546
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
@@ -3684,27 +4554,64 @@ function ExternalDepositView({
|
|
|
3684
4554
|
/* @__PURE__ */ jsx("div", { className: "bg-border h-px w-full" }),
|
|
3685
4555
|
/* @__PURE__ */ jsx(SummaryRow, { value: amount || "\u2014", label: "Value" })
|
|
3686
4556
|
] }),
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Deposit
|
|
3690
|
-
/* @__PURE__ */ jsxs("div", { className: "flex
|
|
3691
|
-
/* @__PURE__ */ jsx("
|
|
3692
|
-
/* @__PURE__ */ jsxs(
|
|
3693
|
-
"
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
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
|
+
] })
|
|
3705
4578
|
] })
|
|
3706
4579
|
] }),
|
|
3707
|
-
|
|
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(
|
|
3708
4615
|
TransactionSuccessView,
|
|
3709
4616
|
{
|
|
3710
4617
|
title: "Deposit Credited",
|
|
@@ -3721,14 +4628,15 @@ function ExternalDepositView({
|
|
|
3721
4628
|
title: "Could not verify deposit",
|
|
3722
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.",
|
|
3723
4630
|
onRetry: () => void verification.retryVerification(),
|
|
3724
|
-
onDismiss: () => verification.reset(),
|
|
4631
|
+
onDismiss: lock ? canCheckAnotherTransfer ? handleCheckAnotherTransfer : void 0 : () => verification.reset(),
|
|
4632
|
+
dismissLabel: lock && canCheckAnotherTransfer ? "Check another transfer" : void 0,
|
|
3725
4633
|
isRetrying: isVerifying
|
|
3726
4634
|
}
|
|
3727
4635
|
) : isVerifying ? /* @__PURE__ */ jsx(
|
|
3728
4636
|
AwaitingDepositStatus,
|
|
3729
4637
|
{
|
|
3730
4638
|
title: "Deposit detected",
|
|
3731
|
-
subtitle: "Crediting the incoming transaction to your Privana balance
|
|
4639
|
+
subtitle: "Crediting the incoming transaction to your Privana balance\u2026"
|
|
3732
4640
|
}
|
|
3733
4641
|
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3734
4642
|
/* @__PURE__ */ jsx(
|
|
@@ -3750,7 +4658,16 @@ function ExternalDepositView({
|
|
|
3750
4658
|
children: "Refresh deposit status"
|
|
3751
4659
|
}
|
|
3752
4660
|
),
|
|
3753
|
-
|
|
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
|
|
3754
4671
|
] })
|
|
3755
4672
|
] })
|
|
3756
4673
|
] });
|
|
@@ -3769,7 +4686,7 @@ function DepositModalContent({
|
|
|
3769
4686
|
onCloseBlockedChange,
|
|
3770
4687
|
onExit
|
|
3771
4688
|
}) {
|
|
3772
|
-
const { serviceName, enabledTokens, defaultToken, hostedAuthConfig, getChainById: getChainById2 } = usePrivanaContext();
|
|
4689
|
+
const { serviceName, enabledTokens, defaultToken, hostedAuthConfig, getChainById: getChainById2, getTokenById } = usePrivanaContext();
|
|
3773
4690
|
const { address } = useAccount();
|
|
3774
4691
|
const appName = serviceName ?? "Privana";
|
|
3775
4692
|
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
@@ -3778,6 +4695,10 @@ function DepositModalContent({
|
|
|
3778
4695
|
const [selectedTokenId, setSelectedTokenId] = useState(defaultToken?.id ?? "");
|
|
3779
4696
|
const [amount, setAmount] = useState("");
|
|
3780
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;
|
|
3781
4702
|
const prevAddressRef = useRef(address);
|
|
3782
4703
|
useEffect(() => {
|
|
3783
4704
|
const prev = prevAddressRef.current;
|
|
@@ -3797,9 +4718,10 @@ function DepositModalContent({
|
|
|
3797
4718
|
const [showTimeout, setShowTimeout] = useState(false);
|
|
3798
4719
|
const [cancelled, setCancelled] = useState(false);
|
|
3799
4720
|
const [isSubmittingLock, setIsSubmittingLock] = useState(false);
|
|
3800
|
-
const [
|
|
4721
|
+
const [lockFailure, setLockFailure] = useState(null);
|
|
3801
4722
|
const finishDeposit = () => {
|
|
3802
4723
|
setAmount("");
|
|
4724
|
+
if (view === "external-deposit") setView("deposit");
|
|
3803
4725
|
if (onDepositSuccess) {
|
|
3804
4726
|
resetDeposit();
|
|
3805
4727
|
onDepositSuccess();
|
|
@@ -3834,6 +4756,7 @@ function DepositModalContent({
|
|
|
3834
4756
|
},
|
|
3835
4757
|
onLockSubmitted: () => {
|
|
3836
4758
|
setIsSubmittingLock(false);
|
|
4759
|
+
setLockFailure(null);
|
|
3837
4760
|
finishDeposit();
|
|
3838
4761
|
},
|
|
3839
4762
|
// The deposit credited; only the policy lock failed. Success must not
|
|
@@ -3841,7 +4764,7 @@ function DepositModalContent({
|
|
|
3841
4764
|
// error view and let the host re-prompt for a fresh lock.
|
|
3842
4765
|
onLockFailed: (err) => {
|
|
3843
4766
|
setIsSubmittingLock(false);
|
|
3844
|
-
|
|
4767
|
+
setLockFailure(err);
|
|
3845
4768
|
onLockFailed?.(err);
|
|
3846
4769
|
},
|
|
3847
4770
|
onCheckTimeout: () => {
|
|
@@ -3849,7 +4772,37 @@ function DepositModalContent({
|
|
|
3849
4772
|
setShowTimeout(true);
|
|
3850
4773
|
}
|
|
3851
4774
|
});
|
|
3852
|
-
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;
|
|
3853
4806
|
useEffect(() => {
|
|
3854
4807
|
onCloseBlockedChange?.(isUnsafeToClose);
|
|
3855
4808
|
}, [isUnsafeToClose, onCloseBlockedChange]);
|
|
@@ -3862,7 +4815,33 @@ function DepositModalContent({
|
|
|
3862
4815
|
}, [depositError, verificationFailed]);
|
|
3863
4816
|
const handleSubmit = (args) => {
|
|
3864
4817
|
if (args.source === "external") {
|
|
3865
|
-
|
|
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
|
+
});
|
|
3866
4845
|
return;
|
|
3867
4846
|
}
|
|
3868
4847
|
if (args.source === "credit-card") {
|
|
@@ -3914,7 +4893,14 @@ function DepositModalContent({
|
|
|
3914
4893
|
}
|
|
3915
4894
|
] : []
|
|
3916
4895
|
];
|
|
3917
|
-
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;
|
|
3918
4904
|
const activeView = flowView ?? view;
|
|
3919
4905
|
const handleDepositDone = () => {
|
|
3920
4906
|
setShowSuccess(false);
|
|
@@ -3927,10 +4913,28 @@ function DepositModalContent({
|
|
|
3927
4913
|
resetDeposit();
|
|
3928
4914
|
};
|
|
3929
4915
|
const handleLockFailedDone = () => {
|
|
3930
|
-
|
|
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);
|
|
3931
4921
|
setAmount("");
|
|
3932
4922
|
setCancelled(false);
|
|
3933
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");
|
|
3934
4938
|
};
|
|
3935
4939
|
const handleDismissVerificationError = () => {
|
|
3936
4940
|
setAmount("");
|
|
@@ -3943,6 +4947,10 @@ function DepositModalContent({
|
|
|
3943
4947
|
};
|
|
3944
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" };
|
|
3945
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
|
+
}
|
|
3946
4954
|
if (back.to === "method") {
|
|
3947
4955
|
setAmount("");
|
|
3948
4956
|
setSelectedTokenId("");
|
|
@@ -3988,9 +4996,14 @@ function DepositModalContent({
|
|
|
3988
4996
|
activeView === "lock-error" && /* @__PURE__ */ jsx(
|
|
3989
4997
|
TransactionWarningView,
|
|
3990
4998
|
{
|
|
3991
|
-
title: "Deposit credited, lock failed",
|
|
3992
|
-
message: `Your deposit was credited but locking the funds for ${appName} failed: ${
|
|
3993
|
-
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
|
|
3994
5007
|
}
|
|
3995
5008
|
),
|
|
3996
5009
|
activeView === "deposit-timeout" && /* @__PURE__ */ jsx(
|
|
@@ -4005,7 +5018,7 @@ function DepositModalContent({
|
|
|
4005
5018
|
TransactionErrorView,
|
|
4006
5019
|
{
|
|
4007
5020
|
title: "Verification failed",
|
|
4008
|
-
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.",
|
|
4009
5022
|
explorerUrl: explorerTxUrl,
|
|
4010
5023
|
explorerLabel: "View transaction",
|
|
4011
5024
|
onRetry: handleRetryVerification,
|
|
@@ -4015,7 +5028,7 @@ function DepositModalContent({
|
|
|
4015
5028
|
activeView === "depositing" && /* @__PURE__ */ jsx(
|
|
4016
5029
|
TransactionProgressView,
|
|
4017
5030
|
{
|
|
4018
|
-
title: "Depositing
|
|
5031
|
+
title: "Depositing\u2026",
|
|
4019
5032
|
steps: depositSteps,
|
|
4020
5033
|
onCancel: isGettingAddress || isSendingTransaction ? handleDepositCancel : void 0
|
|
4021
5034
|
}
|
|
@@ -4069,11 +5082,12 @@ function DepositModalContent({
|
|
|
4069
5082
|
selectedToken,
|
|
4070
5083
|
amount,
|
|
4071
5084
|
allowance,
|
|
5085
|
+
externalMinimum,
|
|
4072
5086
|
onAmountChange: setAmount,
|
|
4073
5087
|
onSelectToken: () => setView("select-token"),
|
|
4074
5088
|
onConnectWallet,
|
|
4075
5089
|
onSubmit: handleSubmit,
|
|
4076
|
-
isSubmitting: isPending
|
|
5090
|
+
isSubmitting: isPending || externalLock.isSigning || externalLock.isSubmittingLock
|
|
4077
5091
|
}
|
|
4078
5092
|
) }),
|
|
4079
5093
|
activeView === "select-token" && /* @__PURE__ */ jsx(
|
|
@@ -4086,12 +5100,15 @@ function DepositModalContent({
|
|
|
4086
5100
|
}
|
|
4087
5101
|
}
|
|
4088
5102
|
),
|
|
4089
|
-
activeView === "external-deposit" && /* @__PURE__ */ jsx(
|
|
5103
|
+
activeView === "external-deposit" && (!allowance || externalLock.session) && /* @__PURE__ */ jsx(
|
|
4090
5104
|
ExternalDepositView,
|
|
4091
5105
|
{
|
|
4092
5106
|
token: selectedToken,
|
|
4093
5107
|
amount,
|
|
4094
|
-
|
|
5108
|
+
depositAddressState: externalDepositAddress,
|
|
5109
|
+
onCredited: allowance ? void 0 : onDepositSuccess,
|
|
5110
|
+
onDiscardLock: handleExternalVerificationDiscard,
|
|
5111
|
+
lock: externalLock.session ? externalLock : void 0
|
|
4095
5112
|
}
|
|
4096
5113
|
),
|
|
4097
5114
|
activeView === "credit-card-widget" && /* @__PURE__ */ jsx(MoonPayGate, { enabled: true, children: /* @__PURE__ */ jsx(
|
|
@@ -4103,7 +5120,7 @@ function DepositModalContent({
|
|
|
4103
5120
|
onCredited: allowance ? void 0 : finishCardPurchase,
|
|
4104
5121
|
onLockSubmitted: finishCardPurchase,
|
|
4105
5122
|
onLockFailed: (err) => {
|
|
4106
|
-
|
|
5123
|
+
setLockFailure(err);
|
|
4107
5124
|
onLockFailed?.(err);
|
|
4108
5125
|
}
|
|
4109
5126
|
}
|
|
@@ -4550,7 +5567,7 @@ function WalletBalanceView({
|
|
|
4550
5567
|
allowance,
|
|
4551
5568
|
amount,
|
|
4552
5569
|
onAmountChange,
|
|
4553
|
-
|
|
5570
|
+
onFundSession,
|
|
4554
5571
|
onAddFunds,
|
|
4555
5572
|
onWithdraw
|
|
4556
5573
|
}) {
|
|
@@ -4575,18 +5592,17 @@ function WalletBalanceView({
|
|
|
4575
5592
|
const hasValidAmount = !!amount && parseFloat(amount) > 0;
|
|
4576
5593
|
const tooManyDecimals = hasValidAmount && !!token && amount.includes(".") && amount.split(".")[1].length > token.decimals;
|
|
4577
5594
|
const exceedsBalance = hasValidAmount && !tooManyDecimals && !!token && !isBalanceLoading && !isBalanceError && parseTokenAmount(amount, token.decimals) > availableWei;
|
|
4578
|
-
const
|
|
5595
|
+
const canFundSession = hasValidAmount && !!token && !tooManyDecimals && !exceedsBalance && !isBalanceLoading && !isBalanceError;
|
|
4579
5596
|
const handleMax = () => {
|
|
4580
5597
|
const max = availableFormatted.replace(/\s/g, "");
|
|
4581
5598
|
if (parseFloat(max) > 0) onAmountChange(max);
|
|
4582
5599
|
};
|
|
4583
|
-
const
|
|
4584
|
-
if (!token || !
|
|
4585
|
-
|
|
4586
|
-
onAmountChange("");
|
|
5600
|
+
const handleFundSession = () => {
|
|
5601
|
+
if (!token || !canFundSession || !onFundSession) return;
|
|
5602
|
+
onFundSession({ tokenId: token.id, amount });
|
|
4587
5603
|
};
|
|
4588
5604
|
const showInput = variant !== "fully-in-use";
|
|
4589
|
-
const
|
|
5605
|
+
const showInlineFund = variant === "session-zero" || variant === "mixed";
|
|
4590
5606
|
return /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
4591
5607
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
4592
5608
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
@@ -4638,14 +5654,14 @@ function WalletBalanceView({
|
|
|
4638
5654
|
}
|
|
4639
5655
|
)
|
|
4640
5656
|
] }),
|
|
4641
|
-
|
|
5657
|
+
showInlineFund && /* @__PURE__ */ jsx(
|
|
4642
5658
|
"button",
|
|
4643
5659
|
{
|
|
4644
5660
|
type: "button",
|
|
4645
|
-
disabled: !
|
|
4646
|
-
onClick:
|
|
5661
|
+
disabled: !onFundSession || !canFundSession,
|
|
5662
|
+
onClick: handleFundSession,
|
|
4647
5663
|
className: "border-border flex h-10 min-w-20 cursor-pointer items-center justify-center rounded-[10px] border px-3 text-sm font-medium text-[#4fc77f] transition-opacity hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50",
|
|
4648
|
-
children: "
|
|
5664
|
+
children: "Fund session"
|
|
4649
5665
|
}
|
|
4650
5666
|
)
|
|
4651
5667
|
] }),
|
|
@@ -4669,10 +5685,10 @@ function WalletBalanceView({
|
|
|
4669
5685
|
"button",
|
|
4670
5686
|
{
|
|
4671
5687
|
type: "button",
|
|
4672
|
-
disabled: !
|
|
4673
|
-
onClick:
|
|
5688
|
+
disabled: !onFundSession || !canFundSession,
|
|
5689
|
+
onClick: handleFundSession,
|
|
4674
5690
|
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",
|
|
4675
|
-
children: "
|
|
5691
|
+
children: "Fund session"
|
|
4676
5692
|
}
|
|
4677
5693
|
),
|
|
4678
5694
|
/* @__PURE__ */ jsx(
|
|
@@ -4709,10 +5725,34 @@ function WalletBalanceView({
|
|
|
4709
5725
|
] }) })
|
|
4710
5726
|
] });
|
|
4711
5727
|
}
|
|
5728
|
+
function invokeGuarded(runRef, invoke, handlers) {
|
|
5729
|
+
const run = ++runRef.current;
|
|
5730
|
+
const isCurrent = () => runRef.current === run;
|
|
5731
|
+
let result;
|
|
5732
|
+
try {
|
|
5733
|
+
result = invoke();
|
|
5734
|
+
} catch (err) {
|
|
5735
|
+
if (isCurrent()) handlers.onError(err);
|
|
5736
|
+
return;
|
|
5737
|
+
}
|
|
5738
|
+
if (result == null || typeof result.then !== "function") {
|
|
5739
|
+
handlers.onSyncResult();
|
|
5740
|
+
return;
|
|
5741
|
+
}
|
|
5742
|
+
handlers.onAsyncStart();
|
|
5743
|
+
result.then(
|
|
5744
|
+
() => {
|
|
5745
|
+
if (isCurrent()) handlers.onSuccess();
|
|
5746
|
+
},
|
|
5747
|
+
(err) => {
|
|
5748
|
+
if (isCurrent()) handlers.onError(err);
|
|
5749
|
+
}
|
|
5750
|
+
);
|
|
5751
|
+
}
|
|
4712
5752
|
function WalletModalContent({
|
|
4713
5753
|
session,
|
|
4714
5754
|
allowance,
|
|
4715
|
-
|
|
5755
|
+
onFundSession,
|
|
4716
5756
|
onEndSession,
|
|
4717
5757
|
onDepositSuccess,
|
|
4718
5758
|
onClose,
|
|
@@ -4727,7 +5767,10 @@ function WalletModalContent({
|
|
|
4727
5767
|
const [depositBlocked, setDepositBlocked] = useState(false);
|
|
4728
5768
|
const [withdrawPending, setWithdrawPending] = useState(false);
|
|
4729
5769
|
const [endSessionError, setEndSessionError] = useState(null);
|
|
5770
|
+
const [fundSessionError, setFundSessionError] = useState(null);
|
|
4730
5771
|
const endSessionRunRef = useRef(0);
|
|
5772
|
+
const fundSessionRunRef = useRef(0);
|
|
5773
|
+
const [pendingFundSession, setPendingFundSession] = useState(null);
|
|
4731
5774
|
const prevAddressRef = useRef(address);
|
|
4732
5775
|
useEffect(() => {
|
|
4733
5776
|
const prev = prevAddressRef.current;
|
|
@@ -4739,10 +5782,13 @@ function WalletModalContent({
|
|
|
4739
5782
|
setDepositBlocked(false);
|
|
4740
5783
|
setWithdrawPending(false);
|
|
4741
5784
|
setEndSessionError(null);
|
|
5785
|
+
setFundSessionError(null);
|
|
5786
|
+
setPendingFundSession(null);
|
|
4742
5787
|
endSessionRunRef.current++;
|
|
5788
|
+
fundSessionRunRef.current++;
|
|
4743
5789
|
}
|
|
4744
5790
|
}, [address, hostedAuthConfig]);
|
|
4745
|
-
const closeBlocked = depositBlocked || withdrawPending || view === "ending-session";
|
|
5791
|
+
const closeBlocked = depositBlocked || withdrawPending || view === "funding-session" || view === "ending-session";
|
|
4746
5792
|
useEffect(() => {
|
|
4747
5793
|
onCloseBlockedChange?.(closeBlocked);
|
|
4748
5794
|
}, [closeBlocked, onCloseBlockedChange]);
|
|
@@ -4756,20 +5802,16 @@ function WalletModalContent({
|
|
|
4756
5802
|
};
|
|
4757
5803
|
const startEndSession = () => {
|
|
4758
5804
|
if (!onEndSession) return;
|
|
4759
|
-
const run = ++endSessionRunRef.current;
|
|
4760
5805
|
setEndSessionError(null);
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
() =>
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
},
|
|
4767
|
-
(err) => {
|
|
4768
|
-
if (endSessionRunRef.current !== run) return;
|
|
5806
|
+
invokeGuarded(endSessionRunRef, () => onEndSession(), {
|
|
5807
|
+
onSyncResult: () => setView("withdraw"),
|
|
5808
|
+
onAsyncStart: () => setView("ending-session"),
|
|
5809
|
+
onSuccess: () => setView("withdraw"),
|
|
5810
|
+
onError: (err) => {
|
|
4769
5811
|
setEndSessionError(err instanceof Error ? err.message : null);
|
|
4770
5812
|
setView("end-session-error");
|
|
4771
5813
|
}
|
|
4772
|
-
);
|
|
5814
|
+
});
|
|
4773
5815
|
};
|
|
4774
5816
|
const handleWithdraw = () => {
|
|
4775
5817
|
if (session && onEndSession) {
|
|
@@ -4783,6 +5825,36 @@ function WalletModalContent({
|
|
|
4783
5825
|
setEndSessionError(null);
|
|
4784
5826
|
setView("balance");
|
|
4785
5827
|
};
|
|
5828
|
+
const fundSession = (args) => {
|
|
5829
|
+
if (!onFundSession) return;
|
|
5830
|
+
setFundSessionError(null);
|
|
5831
|
+
setPendingFundSession(args);
|
|
5832
|
+
setAmount("");
|
|
5833
|
+
invokeGuarded(fundSessionRunRef, () => onFundSession(args), {
|
|
5834
|
+
onSyncResult: () => {
|
|
5835
|
+
setPendingFundSession(null);
|
|
5836
|
+
setView("balance");
|
|
5837
|
+
},
|
|
5838
|
+
onAsyncStart: () => setView("funding-session"),
|
|
5839
|
+
onSuccess: () => {
|
|
5840
|
+
setPendingFundSession(null);
|
|
5841
|
+
setView("balance");
|
|
5842
|
+
},
|
|
5843
|
+
onError: (err) => {
|
|
5844
|
+
setFundSessionError(err instanceof Error ? err.message : null);
|
|
5845
|
+
setView("fund-session-error");
|
|
5846
|
+
}
|
|
5847
|
+
});
|
|
5848
|
+
};
|
|
5849
|
+
const dismissFundSessionError = () => {
|
|
5850
|
+
fundSessionRunRef.current++;
|
|
5851
|
+
setFundSessionError(null);
|
|
5852
|
+
if (pendingFundSession) {
|
|
5853
|
+
setAmount(pendingFundSession.amount);
|
|
5854
|
+
setPendingFundSession(null);
|
|
5855
|
+
}
|
|
5856
|
+
setView("balance");
|
|
5857
|
+
};
|
|
4786
5858
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4787
5859
|
onClose && /* @__PURE__ */ jsx(
|
|
4788
5860
|
"button",
|
|
@@ -4798,7 +5870,7 @@ function WalletModalContent({
|
|
|
4798
5870
|
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
4799
5871
|
}
|
|
4800
5872
|
),
|
|
4801
|
-
(view === "balance" || view === "ending-session" || view === "end-session-error") && /* @__PURE__ */ jsx("div", { className: "flex items-center px-5 py-4", children: /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-5 font-medium", children: appName }) }),
|
|
5873
|
+
(view === "balance" || view === "funding-session" || view === "fund-session-error" || view === "ending-session" || view === "end-session-error") && /* @__PURE__ */ jsx("div", { className: "flex items-center px-5 py-4", children: /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-5 font-medium", children: appName }) }),
|
|
4802
5874
|
view === "balance" && /* @__PURE__ */ jsx(
|
|
4803
5875
|
WalletBalanceView,
|
|
4804
5876
|
{
|
|
@@ -4806,11 +5878,30 @@ function WalletModalContent({
|
|
|
4806
5878
|
allowance,
|
|
4807
5879
|
amount,
|
|
4808
5880
|
onAmountChange: setAmount,
|
|
4809
|
-
|
|
5881
|
+
onFundSession: onFundSession ? fundSession : void 0,
|
|
4810
5882
|
onAddFunds: () => setView("deposit"),
|
|
4811
|
-
onWithdraw: handleWithdraw
|
|
5883
|
+
onWithdraw: session && !onEndSession ? void 0 : handleWithdraw
|
|
4812
5884
|
}
|
|
4813
5885
|
),
|
|
5886
|
+
view === "funding-session" && /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: /* @__PURE__ */ jsx(
|
|
5887
|
+
TransactionProgressView,
|
|
5888
|
+
{
|
|
5889
|
+
title: "Funding session...",
|
|
5890
|
+
steps: [{ label: "Waiting for the session to be funded", status: "active" }]
|
|
5891
|
+
}
|
|
5892
|
+
) }),
|
|
5893
|
+
view === "fund-session-error" && /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: /* @__PURE__ */ jsx(
|
|
5894
|
+
TransactionErrorView,
|
|
5895
|
+
{
|
|
5896
|
+
title: "Could not fund session",
|
|
5897
|
+
message: fundSessionError ? `Your session could not be funded. (${fundSessionError})` : "Your session could not be funded.",
|
|
5898
|
+
onRetry: () => {
|
|
5899
|
+
if (pendingFundSession) fundSession(pendingFundSession);
|
|
5900
|
+
},
|
|
5901
|
+
onDismiss: dismissFundSessionError,
|
|
5902
|
+
retryLabel: "Try again"
|
|
5903
|
+
}
|
|
5904
|
+
) }),
|
|
4814
5905
|
view === "ending-session" && /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: /* @__PURE__ */ jsx(
|
|
4815
5906
|
TransactionProgressView,
|
|
4816
5907
|
{
|
|
@@ -4824,7 +5915,8 @@ function WalletModalContent({
|
|
|
4824
5915
|
title: "Could not end session",
|
|
4825
5916
|
message: endSessionError ? `Your game session could not be ended, so the funds committed to it are not available to withdraw yet. (${endSessionError})` : "Your game session could not be ended, so the funds committed to it are not available to withdraw yet.",
|
|
4826
5917
|
onRetry: startEndSession,
|
|
4827
|
-
onDismiss: dismissEndSessionError
|
|
5918
|
+
onDismiss: dismissEndSessionError,
|
|
5919
|
+
retryLabel: "Try again"
|
|
4828
5920
|
}
|
|
4829
5921
|
) }),
|
|
4830
5922
|
view === "withdraw" && /* @__PURE__ */ jsx(WithdrawModalContent, { onBack: exitWithdraw, onPendingChange: setWithdrawPending }),
|