@oasisprotocol/privana-sdk 0.5.2 → 0.5.4
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 +35 -38
- package/dist/{chunk-UHXVYWTF.cjs → chunk-FGWMLLEC.cjs} +903 -241
- package/dist/chunk-FGWMLLEC.cjs.map +1 -0
- package/dist/{chunk-ZVUMV4NR.js → chunk-QQG4RPYZ.js} +899 -243
- package/dist/chunk-QQG4RPYZ.js.map +1 -0
- package/dist/index.cjs +1581 -1692
- 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 +33 -52
- package/dist/index.d.ts +33 -52
- package/dist/index.js +1389 -1505
- 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-DSEqUHOw.d.cts} +42 -8
- package/dist/{pending-lock-BdkuMjmY.d.ts → pending-lock-DSEqUHOw.d.ts} +42 -8
- package/package.json +1 -1
- package/dist/chunk-UHXVYWTF.cjs.map +0 -1
- package/dist/chunk-ZVUMV4NR.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { usePrivanaContext, createHostedAuthPendingStorageKey, clearHostedAuthPendingTransaction, HostedAuthRequiredError, HostedAuthError, createPkceVerifier, createPkceChallenge, createHostedAuthState, persistHostedAuthPendingTransaction, parseHostedAuthCallback, readHostedAuthPendingTransaction, HostedAuthStateMismatchError, buildHostedAuthSession, useSafePrivanaContext, usePrivateReadRequest, formatTokenAmount, submitPendingLock, PostDepositLockError, useDepositVerification, useEnsureCorrectChain, getTransactionReceipt, getBlockNumber, waitForTransactionReceipt, canUseBrowserStorage, clampLockAmount, getWalletClient, createSignedLockRequest, signWithdrawMessage, signLockMessage, signTransferMessage, signTransferLockedMessage, signModifyLockMessage, cn,
|
|
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, stripHostedAuthCallbackParams, AccountingApiError, getBrowserStorageItem, removeBrowserStorageItem, isSignedLockUsable, setBrowserStorageItem, canUseSharedBrowserStorage, DEFAULT_LOCK_DURATION_SECONDS, parseTokenAmount, Skeleton, shortenAddress, formatCountdown, FiatOnRampForm, getExplorerAddressUrl, getExplorerLabel, formatTimeRemaining, getSharedBrowserStorageItem, removeSharedBrowserStorageItem, setSharedBrowserStorageItem } from './chunk-QQG4RPYZ.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-QQG4RPYZ.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
|
+
import { MoonPayProvider, MoonPayContext } from '@moonpay/moonpay-react';
|
|
8
9
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
9
10
|
import { XIcon } from 'lucide-react';
|
|
10
11
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
11
12
|
import { toast } from 'sonner';
|
|
12
13
|
import { QRCodeSVG } from 'qrcode.react';
|
|
13
|
-
import { MoonPayProvider, MoonPayContext } from '@moonpay/moonpay-react';
|
|
14
14
|
|
|
15
15
|
// src/sdk/hooks/use-privana-client.ts
|
|
16
16
|
function usePrivanaClient() {
|
|
@@ -234,7 +234,7 @@ function useBalance(options = {}) {
|
|
|
234
234
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
235
235
|
if (!tokenId) throw new Error("No token ID provided");
|
|
236
236
|
if (!client) throw new Error("No accounting client");
|
|
237
|
-
return executePrivateRead(() =>
|
|
237
|
+
return executePrivateRead((readClient) => readClient.getBalance(tokenId));
|
|
238
238
|
},
|
|
239
239
|
enabled: hasProviders && (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!tokenId && !!client,
|
|
240
240
|
refetchInterval: pollingInterval
|
|
@@ -259,7 +259,9 @@ function useBatchBalances(options) {
|
|
|
259
259
|
queryKey: ["accounting-batch-balances", ...privateReadQueryScope, options.tokenIds],
|
|
260
260
|
queryFn: async () => {
|
|
261
261
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
262
|
-
return executePrivateRead(
|
|
262
|
+
return executePrivateRead(
|
|
263
|
+
(readClient) => readClient.getBatchBalances({ token_ids: options.tokenIds })
|
|
264
|
+
);
|
|
263
265
|
},
|
|
264
266
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && options.tokenIds.length > 0 && !!client,
|
|
265
267
|
refetchInterval: pollingInterval
|
|
@@ -313,7 +315,9 @@ function useDeposit(options = {}) {
|
|
|
313
315
|
const { data: walletClient } = useWalletClient();
|
|
314
316
|
const queryClient = useQueryClient();
|
|
315
317
|
const config = useConfig();
|
|
316
|
-
const { executePrivateRead } = usePrivateReadRequest();
|
|
318
|
+
const { executePrivateRead, privateReadAddress } = usePrivateReadRequest();
|
|
319
|
+
const privateReadAddressRef = useRef(privateReadAddress);
|
|
320
|
+
privateReadAddressRef.current = privateReadAddress;
|
|
317
321
|
const confirmations = options.confirmations ?? 15;
|
|
318
322
|
const [depositAddress, setDepositAddress] = useState(null);
|
|
319
323
|
const [txHash, setTxHash] = useState();
|
|
@@ -349,12 +353,12 @@ function useDeposit(options = {}) {
|
|
|
349
353
|
]);
|
|
350
354
|
const pendingLockRef = useRef(null);
|
|
351
355
|
const submitPendingLockAfterCredit = useCallback(
|
|
352
|
-
async (signedLock,
|
|
356
|
+
async (signedLock, creditedAmount) => {
|
|
353
357
|
try {
|
|
354
358
|
const result = await submitPendingLock({
|
|
355
359
|
client,
|
|
356
360
|
payload: signedLock,
|
|
357
|
-
creditedAmount
|
|
361
|
+
creditedAmount
|
|
358
362
|
});
|
|
359
363
|
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
360
364
|
queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
|
|
@@ -366,7 +370,7 @@ function useDeposit(options = {}) {
|
|
|
366
370
|
err instanceof Error ? err.message : "Lock submission failed",
|
|
367
371
|
"submission-failed",
|
|
368
372
|
BigInt(signedLock.amount),
|
|
369
|
-
|
|
373
|
+
creditedAmount,
|
|
370
374
|
{ cause: err }
|
|
371
375
|
);
|
|
372
376
|
(onLockFailedRef.current ?? onErrorRef.current)?.(error2);
|
|
@@ -384,12 +388,12 @@ function useDeposit(options = {}) {
|
|
|
384
388
|
} = useDepositVerification({
|
|
385
389
|
pollInterval: options.pollInterval,
|
|
386
390
|
pollTimeout: options.pollTimeout,
|
|
387
|
-
onCredited: (hash, response) => {
|
|
391
|
+
onCredited: (hash, response, creditedAmount) => {
|
|
388
392
|
verificationContextRef.current = null;
|
|
389
393
|
const signedLock = pendingLockRef.current;
|
|
390
394
|
pendingLockRef.current = null;
|
|
391
395
|
if (signedLock) {
|
|
392
|
-
void submitPendingLockAfterCredit(signedLock,
|
|
396
|
+
void submitPendingLockAfterCredit(signedLock, creditedAmount).finally(() => {
|
|
393
397
|
if (address) clearPendingDeposit(address, hash);
|
|
394
398
|
});
|
|
395
399
|
} else if (address) {
|
|
@@ -407,7 +411,7 @@ function useDeposit(options = {}) {
|
|
|
407
411
|
const addressMutation = useMutation({
|
|
408
412
|
mutationFn: async () => {
|
|
409
413
|
if (!address) throw new Error("No wallet connected");
|
|
410
|
-
return executePrivateRead(() =>
|
|
414
|
+
return executePrivateRead((readClient) => readClient.getDepositAddress());
|
|
411
415
|
},
|
|
412
416
|
onSuccess: (data) => {
|
|
413
417
|
setDepositAddress(data);
|
|
@@ -545,6 +549,7 @@ function useDeposit(options = {}) {
|
|
|
545
549
|
const lockAmount = clampLockAmount(params.amount, params.postDepositLock?.maxAmount);
|
|
546
550
|
let signedLock;
|
|
547
551
|
if (params.postDepositLock) {
|
|
552
|
+
const lockOwner = requireDepositLockOwner(address, privateReadAddress);
|
|
548
553
|
setIsSwitchingChain(true);
|
|
549
554
|
try {
|
|
550
555
|
await ensureCorrectChain(networkConfig.chainId);
|
|
@@ -558,7 +563,7 @@ function useDeposit(options = {}) {
|
|
|
558
563
|
signedLock = await createSignedLockRequest({
|
|
559
564
|
client,
|
|
560
565
|
walletClient: signingWalletClient,
|
|
561
|
-
userAddress:
|
|
566
|
+
userAddress: lockOwner,
|
|
562
567
|
networkConfig,
|
|
563
568
|
serviceAddress: requireServiceAddress(
|
|
564
569
|
params.postDepositLock.serviceAddress ?? serviceAddress
|
|
@@ -567,6 +572,9 @@ function useDeposit(options = {}) {
|
|
|
567
572
|
amount: lockAmount,
|
|
568
573
|
lockDuration: params.postDepositLock.lockDuration
|
|
569
574
|
});
|
|
575
|
+
if (privateReadAddressRef.current?.toLowerCase() !== lockOwner.toLowerCase()) {
|
|
576
|
+
throw new Error("Authenticated deposit account changed while signing");
|
|
577
|
+
}
|
|
570
578
|
}
|
|
571
579
|
if (isStale()) return;
|
|
572
580
|
setIsSwitchingChain(true);
|
|
@@ -649,6 +657,7 @@ function useDeposit(options = {}) {
|
|
|
649
657
|
enabledTokens,
|
|
650
658
|
ensureCorrectChain,
|
|
651
659
|
networkConfig,
|
|
660
|
+
privateReadAddress,
|
|
652
661
|
queryClient,
|
|
653
662
|
serviceAddress,
|
|
654
663
|
writeContractAsync,
|
|
@@ -685,12 +694,6 @@ function useDeposit(options = {}) {
|
|
|
685
694
|
reset
|
|
686
695
|
};
|
|
687
696
|
}
|
|
688
|
-
function requireServiceAddress(serviceAddress) {
|
|
689
|
-
if (!serviceAddress) {
|
|
690
|
-
throw new Error("Service address not configured");
|
|
691
|
-
}
|
|
692
|
-
return serviceAddress;
|
|
693
|
-
}
|
|
694
697
|
function useDepositAddress(options = {}) {
|
|
695
698
|
const queryClient = useContext(QueryClientContext);
|
|
696
699
|
const accountingContext = useSafePrivanaContext();
|
|
@@ -703,11 +706,12 @@ function useDepositAddress(options = {}) {
|
|
|
703
706
|
queryFn: async () => {
|
|
704
707
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
705
708
|
if (!client) throw new Error("No accounting client");
|
|
706
|
-
return executePrivateRead(() =>
|
|
709
|
+
return executePrivateRead((readClient) => readClient.getDepositAddress());
|
|
707
710
|
},
|
|
708
711
|
enabled: isReady && (options.enabled ?? true)
|
|
709
712
|
});
|
|
710
713
|
return {
|
|
714
|
+
response: query.data,
|
|
711
715
|
depositAddress: query.data?.deposit_address ?? null,
|
|
712
716
|
isReady,
|
|
713
717
|
isLoading: query.isLoading,
|
|
@@ -1158,7 +1162,7 @@ function useLockedFunds(options = {}) {
|
|
|
1158
1162
|
queryKey: ["accounting-locked-funds", ...privateReadQueryScope, serviceAddress ?? null],
|
|
1159
1163
|
queryFn: async () => {
|
|
1160
1164
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1161
|
-
return executePrivateRead(() =>
|
|
1165
|
+
return executePrivateRead((readClient) => readClient.getLockedFunds(serviceAddress));
|
|
1162
1166
|
},
|
|
1163
1167
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!client,
|
|
1164
1168
|
refetchInterval: pollingInterval,
|
|
@@ -1183,7 +1187,7 @@ function useTotalLockedBalance(options = {}) {
|
|
|
1183
1187
|
queryFn: async () => {
|
|
1184
1188
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1185
1189
|
if (!tokenId) throw new Error("No token ID provided");
|
|
1186
|
-
return executePrivateRead(() =>
|
|
1190
|
+
return executePrivateRead((readClient) => readClient.getTotalLockedBalance(tokenId));
|
|
1187
1191
|
},
|
|
1188
1192
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!tokenId && !!client,
|
|
1189
1193
|
refetchInterval: pollingInterval
|
|
@@ -1203,7 +1207,7 @@ function useExpiredLocks(options = {}) {
|
|
|
1203
1207
|
queryKey: ["accounting-expired-locks", ...privateReadQueryScope],
|
|
1204
1208
|
queryFn: async () => {
|
|
1205
1209
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1206
|
-
return executePrivateRead(() =>
|
|
1210
|
+
return executePrivateRead((readClient) => readClient.getExpiredLocks());
|
|
1207
1211
|
},
|
|
1208
1212
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!client,
|
|
1209
1213
|
refetchInterval: pollingInterval
|
|
@@ -1236,7 +1240,7 @@ function usePendingDeposits(options = {}) {
|
|
|
1236
1240
|
if (!client) throw new Error("No accounting client");
|
|
1237
1241
|
try {
|
|
1238
1242
|
return await executePrivateRead(
|
|
1239
|
-
() =>
|
|
1243
|
+
(readClient) => readClient.getPendingDeposits({
|
|
1240
1244
|
chain_id: chainId,
|
|
1241
1245
|
token_address: tokenAddress,
|
|
1242
1246
|
lookback_blocks: lookbackBlocks
|
|
@@ -1317,7 +1321,7 @@ function useHistory(options = {}) {
|
|
|
1317
1321
|
queryKey: ["accounting-history", ...privateReadQueryScope, offset, limit],
|
|
1318
1322
|
queryFn: async () => {
|
|
1319
1323
|
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1320
|
-
return executePrivateRead(() =>
|
|
1324
|
+
return executePrivateRead((readClient) => readClient.getHistory({ offset, limit }));
|
|
1321
1325
|
},
|
|
1322
1326
|
enabled: (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!client,
|
|
1323
1327
|
refetchInterval: pollingInterval,
|
|
@@ -1484,16 +1488,6 @@ function DialogContent({
|
|
|
1484
1488
|
)
|
|
1485
1489
|
] });
|
|
1486
1490
|
}
|
|
1487
|
-
function DialogHeader({ className, ...props }) {
|
|
1488
|
-
return /* @__PURE__ */ jsx(
|
|
1489
|
-
"div",
|
|
1490
|
-
{
|
|
1491
|
-
"data-slot": "dialog-header",
|
|
1492
|
-
className: cn("flex flex-col gap-2 text-center sm:text-left", className),
|
|
1493
|
-
...props
|
|
1494
|
-
}
|
|
1495
|
-
);
|
|
1496
|
-
}
|
|
1497
1491
|
function DialogTitle({ className, ...props }) {
|
|
1498
1492
|
return /* @__PURE__ */ jsx(
|
|
1499
1493
|
DialogPrimitive.Title,
|
|
@@ -1517,6 +1511,745 @@ function DialogDescription({
|
|
|
1517
1511
|
}
|
|
1518
1512
|
);
|
|
1519
1513
|
}
|
|
1514
|
+
|
|
1515
|
+
// src/sdk/hooks/external-deposit-lock.ts
|
|
1516
|
+
var ACTIVE_SESSION_PREFIX = "privana:external-deposit-lock:active:";
|
|
1517
|
+
var SESSION_PREFIX = "privana:external-deposit-lock:session:";
|
|
1518
|
+
var ExternalDepositLockSessionChangedError = class _ExternalDepositLockSessionChangedError extends Error {
|
|
1519
|
+
constructor() {
|
|
1520
|
+
super("External deposit lock session changed");
|
|
1521
|
+
this.name = "ExternalDepositLockSessionChangedError";
|
|
1522
|
+
Object.setPrototypeOf(this, _ExternalDepositLockSessionChangedError.prototype);
|
|
1523
|
+
}
|
|
1524
|
+
};
|
|
1525
|
+
function externalDepositSessionId(chainId, tokenId) {
|
|
1526
|
+
return `${chainId}:${tokenId.toLowerCase()}`;
|
|
1527
|
+
}
|
|
1528
|
+
function isExternalDepositBlockInSession(session, blockNumber) {
|
|
1529
|
+
return Number.isSafeInteger(blockNumber) && blockNumber > session.startBlock;
|
|
1530
|
+
}
|
|
1531
|
+
function isCurrentExternalDepositVerification(session, txHash) {
|
|
1532
|
+
return session.creditedAmount === void 0 && session.verification?.hash.toLowerCase() === txHash.toLowerCase();
|
|
1533
|
+
}
|
|
1534
|
+
function getExternalDepositMinimum(response, chainId) {
|
|
1535
|
+
const value = response?.min_deposit?.[String(chainId)]?.erc20;
|
|
1536
|
+
if (value === void 0) return void 0;
|
|
1537
|
+
try {
|
|
1538
|
+
const amount = BigInt(value);
|
|
1539
|
+
return amount >= 0n ? amount : void 0;
|
|
1540
|
+
} catch {
|
|
1541
|
+
return void 0;
|
|
1542
|
+
}
|
|
1543
|
+
}
|
|
1544
|
+
function activeSessionKey(owner) {
|
|
1545
|
+
return `${ACTIVE_SESSION_PREFIX}${owner.toLowerCase()}`;
|
|
1546
|
+
}
|
|
1547
|
+
function sessionKey(owner, sessionId) {
|
|
1548
|
+
return `${SESSION_PREFIX}${owner.toLowerCase()}:${sessionId}`;
|
|
1549
|
+
}
|
|
1550
|
+
function activeSessionId(owner) {
|
|
1551
|
+
const value = getSharedBrowserStorageItem(activeSessionKey(owner));
|
|
1552
|
+
return value || void 0;
|
|
1553
|
+
}
|
|
1554
|
+
function isPositiveIntegerString(value) {
|
|
1555
|
+
if (typeof value !== "string") return false;
|
|
1556
|
+
try {
|
|
1557
|
+
return BigInt(value) > 0n;
|
|
1558
|
+
} catch {
|
|
1559
|
+
return false;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
function isSessionRecord(value) {
|
|
1563
|
+
if (!value || typeof value !== "object") return false;
|
|
1564
|
+
const record = value;
|
|
1565
|
+
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)) {
|
|
1566
|
+
return false;
|
|
1567
|
+
}
|
|
1568
|
+
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))) {
|
|
1569
|
+
return false;
|
|
1570
|
+
}
|
|
1571
|
+
if (record.verification && record.creditedAmount !== void 0) return false;
|
|
1572
|
+
if (record.submissionAmbiguous && record.creditedAmount === void 0) return false;
|
|
1573
|
+
if (!record.payload) {
|
|
1574
|
+
return record.creditedAmount !== void 0 && !record.submissionAmbiguous;
|
|
1575
|
+
}
|
|
1576
|
+
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)) {
|
|
1577
|
+
return false;
|
|
1578
|
+
}
|
|
1579
|
+
return BigInt(record.payload.amount) <= BigInt(record.maxLockAmount);
|
|
1580
|
+
}
|
|
1581
|
+
function writeSessionRecord(session) {
|
|
1582
|
+
const id = externalDepositSessionId(session.chainId, session.tokenId);
|
|
1583
|
+
return setSharedBrowserStorageItem(sessionKey(session.owner, id), JSON.stringify(session));
|
|
1584
|
+
}
|
|
1585
|
+
function saveExternalDepositLockSession(session, expectedSession = null) {
|
|
1586
|
+
if (!isSessionRecord(session)) throw new Error("Invalid external deposit lock session");
|
|
1587
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
1588
|
+
if (expectedSession === null && current || expectedSession !== null && (!current || !isSameSessionSnapshot(current, expectedSession))) {
|
|
1589
|
+
throw new ExternalDepositLockSessionChangedError();
|
|
1590
|
+
}
|
|
1591
|
+
const nextId = externalDepositSessionId(session.chainId, session.tokenId);
|
|
1592
|
+
const previousId = activeSessionId(session.owner);
|
|
1593
|
+
if (!writeSessionRecord(session)) {
|
|
1594
|
+
throw new Error("Unable to persist external deposit lock session");
|
|
1595
|
+
}
|
|
1596
|
+
if (previousId !== nextId) {
|
|
1597
|
+
if (!setSharedBrowserStorageItem(activeSessionKey(session.owner), nextId)) {
|
|
1598
|
+
removeSharedBrowserStorageItem(sessionKey(session.owner, nextId));
|
|
1599
|
+
throw new Error("Unable to persist external deposit lock session");
|
|
1600
|
+
}
|
|
1601
|
+
if (previousId) {
|
|
1602
|
+
removeSharedBrowserStorageItem(sessionKey(session.owner, previousId));
|
|
1603
|
+
}
|
|
1604
|
+
}
|
|
1605
|
+
}
|
|
1606
|
+
function loadExternalDepositLockSession(owner) {
|
|
1607
|
+
const id = activeSessionId(owner);
|
|
1608
|
+
if (!id) return void 0;
|
|
1609
|
+
const key = sessionKey(owner, id);
|
|
1610
|
+
try {
|
|
1611
|
+
const raw = getSharedBrowserStorageItem(key);
|
|
1612
|
+
const parsed = raw ? JSON.parse(raw) : void 0;
|
|
1613
|
+
if (!isSessionRecord(parsed) || parsed.owner.toLowerCase() !== owner.toLowerCase() || externalDepositSessionId(parsed.chainId, parsed.tokenId) !== id) {
|
|
1614
|
+
removeSharedBrowserStorageItem(key);
|
|
1615
|
+
removeSharedBrowserStorageItem(activeSessionKey(owner));
|
|
1616
|
+
return void 0;
|
|
1617
|
+
}
|
|
1618
|
+
return parsed;
|
|
1619
|
+
} catch {
|
|
1620
|
+
removeSharedBrowserStorageItem(key);
|
|
1621
|
+
removeSharedBrowserStorageItem(activeSessionKey(owner));
|
|
1622
|
+
return void 0;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
function subscribeExternalDepositLockSession(owner, onChange) {
|
|
1626
|
+
if (typeof window === "undefined") return () => void 0;
|
|
1627
|
+
let localStorage;
|
|
1628
|
+
try {
|
|
1629
|
+
localStorage = window.localStorage;
|
|
1630
|
+
} catch {
|
|
1631
|
+
return () => void 0;
|
|
1632
|
+
}
|
|
1633
|
+
const activeKey = activeSessionKey(owner);
|
|
1634
|
+
const ownerSessionPrefix = `${SESSION_PREFIX}${owner.toLowerCase()}:`;
|
|
1635
|
+
const handleStorage = (event) => {
|
|
1636
|
+
if (event.storageArea !== localStorage) return;
|
|
1637
|
+
if (event.key !== null && event.key !== activeKey && !event.key.startsWith(ownerSessionPrefix)) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
onChange(loadExternalDepositLockSession(owner));
|
|
1641
|
+
};
|
|
1642
|
+
window.addEventListener("storage", handleStorage);
|
|
1643
|
+
return () => window.removeEventListener("storage", handleStorage);
|
|
1644
|
+
}
|
|
1645
|
+
function clearExternalDepositLockSession(owner, expectedSessionId, expectedGeneration) {
|
|
1646
|
+
const id = activeSessionId(owner);
|
|
1647
|
+
if (!id || expectedSessionId && id !== expectedSessionId) return;
|
|
1648
|
+
if (expectedGeneration) {
|
|
1649
|
+
const current = loadExternalDepositLockSession(owner);
|
|
1650
|
+
if (!current || current.generation !== expectedGeneration) return;
|
|
1651
|
+
}
|
|
1652
|
+
removeSharedBrowserStorageItem(sessionKey(owner, id));
|
|
1653
|
+
removeSharedBrowserStorageItem(activeSessionKey(owner));
|
|
1654
|
+
}
|
|
1655
|
+
function isSameVerification(left, right) {
|
|
1656
|
+
if (!left || !right) return left === right;
|
|
1657
|
+
return left.hash.toLowerCase() === right.hash.toLowerCase() && left.chainId === right.chainId && left.amount === right.amount && (left.logIndex ?? 0) === (right.logIndex ?? 0);
|
|
1658
|
+
}
|
|
1659
|
+
function isSamePayload(left, right) {
|
|
1660
|
+
if (!left || !right) return left === right;
|
|
1661
|
+
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;
|
|
1662
|
+
}
|
|
1663
|
+
function isSameSessionSnapshot(left, right) {
|
|
1664
|
+
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);
|
|
1665
|
+
}
|
|
1666
|
+
function isSameExternalDepositLockSession(left, right) {
|
|
1667
|
+
return isSameSessionSnapshot(left, right);
|
|
1668
|
+
}
|
|
1669
|
+
function clearExternalDepositVerification(session) {
|
|
1670
|
+
if (!session.verification || session.creditedAmount) return void 0;
|
|
1671
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
1672
|
+
if (!current || current.generation !== session.generation || current.creditedAmount || !isSameVerification(current.verification, session.verification)) {
|
|
1673
|
+
return void 0;
|
|
1674
|
+
}
|
|
1675
|
+
const next = { ...current, verification: void 0 };
|
|
1676
|
+
if (!writeSessionRecord(next)) {
|
|
1677
|
+
throw new Error("Unable to clear failed deposit verification");
|
|
1678
|
+
}
|
|
1679
|
+
return next;
|
|
1680
|
+
}
|
|
1681
|
+
function discardExternalDepositLockSession(session) {
|
|
1682
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
1683
|
+
if (!current) return true;
|
|
1684
|
+
if (!isSameSessionSnapshot(current, session)) return false;
|
|
1685
|
+
clearExternalDepositLockSession(
|
|
1686
|
+
current.owner,
|
|
1687
|
+
externalDepositSessionId(current.chainId, current.tokenId),
|
|
1688
|
+
current.generation
|
|
1689
|
+
);
|
|
1690
|
+
return loadExternalDepositLockSession(current.owner) === void 0;
|
|
1691
|
+
}
|
|
1692
|
+
function clearSubmittedPayload(session, signature, clearAmbiguous = false) {
|
|
1693
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
1694
|
+
if (!current?.payload || current.payload.signature !== signature || current.submissionAmbiguous && !clearAmbiguous) {
|
|
1695
|
+
return false;
|
|
1696
|
+
}
|
|
1697
|
+
return writeSessionRecord({ ...current, submissionAmbiguous: void 0, payload: void 0 });
|
|
1698
|
+
}
|
|
1699
|
+
function markSubmissionAmbiguous(session, signature) {
|
|
1700
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
1701
|
+
if (!current?.payload || current.payload.signature !== signature) return false;
|
|
1702
|
+
if (current.submissionAmbiguous) return true;
|
|
1703
|
+
return writeSessionRecord({ ...current, submissionAmbiguous: true });
|
|
1704
|
+
}
|
|
1705
|
+
function ambiguousSubmissionError(error) {
|
|
1706
|
+
return new PostDepositLockError(
|
|
1707
|
+
"Lock submission could not be confirmed. Retry only the saved signature, or check locked funds before stopping recovery.",
|
|
1708
|
+
error.reason,
|
|
1709
|
+
error.signedAmount,
|
|
1710
|
+
error.creditedAmount,
|
|
1711
|
+
{
|
|
1712
|
+
cause: error.cause,
|
|
1713
|
+
submissionMayHaveSucceeded: true
|
|
1714
|
+
}
|
|
1715
|
+
);
|
|
1716
|
+
}
|
|
1717
|
+
function isDefinitiveSubmissionFailure(error) {
|
|
1718
|
+
const cause = error.cause;
|
|
1719
|
+
return cause instanceof AccountingApiError && cause.statusCode !== 408 && cause.statusCode < 500;
|
|
1720
|
+
}
|
|
1721
|
+
function submissionLockName(session) {
|
|
1722
|
+
return `privana:external-deposit-lock:submit:${session.owner.toLowerCase()}:${externalDepositSessionId(session.chainId, session.tokenId)}`;
|
|
1723
|
+
}
|
|
1724
|
+
async function withSubmissionLock(session, task) {
|
|
1725
|
+
const locks = typeof navigator === "undefined" ? void 0 : navigator.locks;
|
|
1726
|
+
if (!locks) return task(false);
|
|
1727
|
+
return locks.request(submissionLockName(session), () => task(true));
|
|
1728
|
+
}
|
|
1729
|
+
function externalDepositRetryAmount(session) {
|
|
1730
|
+
if (!session.creditedAmount) throw new Error("No credited external deposit to recover");
|
|
1731
|
+
const creditedAmount = BigInt(session.creditedAmount);
|
|
1732
|
+
const maxLockAmount = BigInt(session.maxLockAmount);
|
|
1733
|
+
return creditedAmount < maxLockAmount ? creditedAmount : maxLockAmount;
|
|
1734
|
+
}
|
|
1735
|
+
async function submitExternalDepositLock(client, session) {
|
|
1736
|
+
return withSubmissionLock(session, async (crossTabExclusive) => {
|
|
1737
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
1738
|
+
if (!current || current.generation !== session.generation) {
|
|
1739
|
+
throw new PostDepositLockError(
|
|
1740
|
+
"External deposit lock session changed before submission",
|
|
1741
|
+
"not-found",
|
|
1742
|
+
BigInt(session.maxLockAmount),
|
|
1743
|
+
session.creditedAmount ? BigInt(session.creditedAmount) : void 0
|
|
1744
|
+
);
|
|
1745
|
+
}
|
|
1746
|
+
const result = await submitExternalDepositLockAttempt(client, current, crossTabExclusive);
|
|
1747
|
+
clearExternalDepositLockSession(
|
|
1748
|
+
current.owner,
|
|
1749
|
+
externalDepositSessionId(current.chainId, current.tokenId),
|
|
1750
|
+
current.generation
|
|
1751
|
+
);
|
|
1752
|
+
return result;
|
|
1753
|
+
});
|
|
1754
|
+
}
|
|
1755
|
+
async function submitExternalDepositLockAttempt(client, session, crossTabExclusive) {
|
|
1756
|
+
const creditedAmount = session.creditedAmount ? BigInt(session.creditedAmount) : void 0;
|
|
1757
|
+
if (!creditedAmount) {
|
|
1758
|
+
throw new PostDepositLockError(
|
|
1759
|
+
"External deposit has not been credited",
|
|
1760
|
+
"submission-failed",
|
|
1761
|
+
BigInt(session.maxLockAmount)
|
|
1762
|
+
);
|
|
1763
|
+
}
|
|
1764
|
+
const payload = session.payload;
|
|
1765
|
+
if (!payload) {
|
|
1766
|
+
throw new PostDepositLockError(
|
|
1767
|
+
"No persisted signed policy found for this deposit session",
|
|
1768
|
+
"not-found",
|
|
1769
|
+
BigInt(session.maxLockAmount),
|
|
1770
|
+
creditedAmount
|
|
1771
|
+
);
|
|
1772
|
+
}
|
|
1773
|
+
try {
|
|
1774
|
+
return await submitPendingLock({
|
|
1775
|
+
client,
|
|
1776
|
+
payload,
|
|
1777
|
+
creditedAmount,
|
|
1778
|
+
beforeSubmit: () => {
|
|
1779
|
+
if (!markSubmissionAmbiguous(session, payload.signature)) {
|
|
1780
|
+
throw new Error("Unable to persist lock submission recovery state");
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
});
|
|
1784
|
+
} catch (err) {
|
|
1785
|
+
if (err instanceof PostDepositLockError && err.reason === "submission-failed" && crossTabExclusive && !session.submissionAmbiguous && isDefinitiveSubmissionFailure(err)) {
|
|
1786
|
+
if (!clearSubmittedPayload(session, payload.signature, true)) {
|
|
1787
|
+
const latest = loadExternalDepositLockSession(session.owner);
|
|
1788
|
+
if (latest?.submissionAmbiguous) throw ambiguousSubmissionError(err);
|
|
1789
|
+
}
|
|
1790
|
+
} else if (err instanceof PostDepositLockError && err.reason === "submission-failed") {
|
|
1791
|
+
const ambiguous = markSubmissionAmbiguous(session, payload.signature) || loadExternalDepositLockSession(session.owner)?.submissionAmbiguous === true;
|
|
1792
|
+
if (ambiguous) throw ambiguousSubmissionError(err);
|
|
1793
|
+
} else {
|
|
1794
|
+
const current = loadExternalDepositLockSession(session.owner);
|
|
1795
|
+
if (session.submissionAmbiguous || current?.submissionAmbiguous) {
|
|
1796
|
+
if (err instanceof PostDepositLockError) throw ambiguousSubmissionError(err);
|
|
1797
|
+
throw err;
|
|
1798
|
+
}
|
|
1799
|
+
if (!clearSubmittedPayload(session, payload.signature)) {
|
|
1800
|
+
const latest = loadExternalDepositLockSession(session.owner);
|
|
1801
|
+
if (latest?.submissionAmbiguous && err instanceof PostDepositLockError) {
|
|
1802
|
+
throw ambiguousSubmissionError(err);
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
}
|
|
1806
|
+
throw err;
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
function useExternalDepositLock({
|
|
1810
|
+
allowance,
|
|
1811
|
+
onLockSubmitted,
|
|
1812
|
+
onLockFailed
|
|
1813
|
+
}) {
|
|
1814
|
+
const { client, networkConfig, serviceAddress, getTokenById, tokensStatus } = usePrivanaContext();
|
|
1815
|
+
const config = useConfig();
|
|
1816
|
+
const queryClient = useQueryClient();
|
|
1817
|
+
const { address } = useAccount();
|
|
1818
|
+
const { privateReadAddress } = usePrivateReadRequest();
|
|
1819
|
+
const { ensureCorrectChain } = useEnsureCorrectChain();
|
|
1820
|
+
const [isSigning, setIsSigning] = useState(false);
|
|
1821
|
+
const [isSubmittingLock, setIsSubmittingLock] = useState(false);
|
|
1822
|
+
const [session, setSession] = useState(null);
|
|
1823
|
+
const sessionRef = useRef(null);
|
|
1824
|
+
const signingRef = useRef(false);
|
|
1825
|
+
const settlingRef = useRef(false);
|
|
1826
|
+
const pendingCreditedTransitionRef = useRef(null);
|
|
1827
|
+
const hydratedScopeRef = useRef(null);
|
|
1828
|
+
const beneficiaryRef = useRef(privateReadAddress);
|
|
1829
|
+
beneficiaryRef.current = privateReadAddress;
|
|
1830
|
+
const onLockSubmittedRef = useRef(onLockSubmitted);
|
|
1831
|
+
const onLockFailedRef = useRef(onLockFailed);
|
|
1832
|
+
useEffect(() => {
|
|
1833
|
+
onLockSubmittedRef.current = onLockSubmitted;
|
|
1834
|
+
onLockFailedRef.current = onLockFailed;
|
|
1835
|
+
}, [onLockSubmitted, onLockFailed]);
|
|
1836
|
+
const installSession = useCallback((next) => {
|
|
1837
|
+
sessionRef.current = next;
|
|
1838
|
+
setSession(next);
|
|
1839
|
+
}, []);
|
|
1840
|
+
const preserveCreditedFallback = useCallback(
|
|
1841
|
+
(durable) => {
|
|
1842
|
+
const pending = pendingCreditedTransitionRef.current;
|
|
1843
|
+
if (pending && durable && isSameExternalDepositLockSession(durable, pending.predecessor)) {
|
|
1844
|
+
return pending.credited;
|
|
1845
|
+
}
|
|
1846
|
+
pendingCreditedTransitionRef.current = null;
|
|
1847
|
+
return durable;
|
|
1848
|
+
},
|
|
1849
|
+
[]
|
|
1850
|
+
);
|
|
1851
|
+
const notifyFailure = useCallback((error) => {
|
|
1852
|
+
onLockFailedRef.current?.(error);
|
|
1853
|
+
}, []);
|
|
1854
|
+
const submitSession = useCallback(
|
|
1855
|
+
async (active) => {
|
|
1856
|
+
if (settlingRef.current) return;
|
|
1857
|
+
settlingRef.current = true;
|
|
1858
|
+
setIsSubmittingLock(true);
|
|
1859
|
+
let result;
|
|
1860
|
+
try {
|
|
1861
|
+
let submission = active;
|
|
1862
|
+
const storedBeforeSubmit = loadExternalDepositLockSession(active.owner);
|
|
1863
|
+
if (storedBeforeSubmit?.generation === active.generation) {
|
|
1864
|
+
if (active.creditedAmount && !storedBeforeSubmit.creditedAmount) {
|
|
1865
|
+
const pending = pendingCreditedTransitionRef.current;
|
|
1866
|
+
if (!pending || !isSameExternalDepositLockSession(active, pending.credited) || !isSameExternalDepositLockSession(storedBeforeSubmit, pending.predecessor)) {
|
|
1867
|
+
throw new ExternalDepositLockSessionChangedError();
|
|
1868
|
+
}
|
|
1869
|
+
saveExternalDepositLockSession(pending.credited, pending.predecessor);
|
|
1870
|
+
pendingCreditedTransitionRef.current = null;
|
|
1871
|
+
submission = pending.credited;
|
|
1872
|
+
} else {
|
|
1873
|
+
submission = storedBeforeSubmit;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
result = await submitExternalDepositLock(client, submission);
|
|
1877
|
+
} catch (err) {
|
|
1878
|
+
const stored = loadExternalDepositLockSession(active.owner);
|
|
1879
|
+
const error = err instanceof PostDepositLockError ? err : new PostDepositLockError(
|
|
1880
|
+
err instanceof Error ? err.message : "Lock submission failed",
|
|
1881
|
+
"submission-failed",
|
|
1882
|
+
BigInt(active.maxLockAmount),
|
|
1883
|
+
active.creditedAmount ? BigInt(active.creditedAmount) : void 0,
|
|
1884
|
+
{ cause: err }
|
|
1885
|
+
);
|
|
1886
|
+
if (err instanceof ExternalDepositLockSessionChangedError) {
|
|
1887
|
+
pendingCreditedTransitionRef.current = null;
|
|
1888
|
+
installSession(stored ?? null);
|
|
1889
|
+
return;
|
|
1890
|
+
}
|
|
1891
|
+
if (!stored || stored.generation !== active.generation) {
|
|
1892
|
+
pendingCreditedTransitionRef.current = null;
|
|
1893
|
+
installSession(stored ?? null);
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
if (active.creditedAmount && !stored.creditedAmount) {
|
|
1897
|
+
const pending = pendingCreditedTransitionRef.current;
|
|
1898
|
+
if (pending && isSameExternalDepositLockSession(active, pending.credited) && isSameExternalDepositLockSession(stored, pending.predecessor)) {
|
|
1899
|
+
installSession(pending.credited);
|
|
1900
|
+
notifyFailure(error);
|
|
1901
|
+
return;
|
|
1902
|
+
}
|
|
1903
|
+
pendingCreditedTransitionRef.current = null;
|
|
1904
|
+
installSession(stored);
|
|
1905
|
+
return;
|
|
1906
|
+
}
|
|
1907
|
+
installSession(active.creditedAmount && !stored.creditedAmount ? active : stored);
|
|
1908
|
+
notifyFailure(error);
|
|
1909
|
+
return;
|
|
1910
|
+
} finally {
|
|
1911
|
+
settlingRef.current = false;
|
|
1912
|
+
setIsSubmittingLock(false);
|
|
1913
|
+
}
|
|
1914
|
+
installSession(loadExternalDepositLockSession(active.owner) ?? null);
|
|
1915
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
1916
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
|
|
1917
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
|
|
1918
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
1919
|
+
onLockSubmittedRef.current?.(result);
|
|
1920
|
+
},
|
|
1921
|
+
[client, installSession, notifyFailure, queryClient]
|
|
1922
|
+
);
|
|
1923
|
+
const validateRestoredSession = useCallback(
|
|
1924
|
+
(restored) => {
|
|
1925
|
+
if (!restored || !serviceAddress) return null;
|
|
1926
|
+
const token = getTokenById(restored.tokenId);
|
|
1927
|
+
const invalid = restored.serviceAddress.toLowerCase() !== serviceAddress.toLowerCase() || !token || token.chainId !== restored.chainId || token.contract === zeroAddress || !restored.creditedAmount && !restored.payload;
|
|
1928
|
+
if (!invalid) return restored;
|
|
1929
|
+
clearExternalDepositLockSession(
|
|
1930
|
+
restored.owner,
|
|
1931
|
+
externalDepositSessionId(restored.chainId, restored.tokenId),
|
|
1932
|
+
restored.generation
|
|
1933
|
+
);
|
|
1934
|
+
return null;
|
|
1935
|
+
},
|
|
1936
|
+
[getTokenById, serviceAddress]
|
|
1937
|
+
);
|
|
1938
|
+
useEffect(() => {
|
|
1939
|
+
if (!privateReadAddress || !serviceAddress || tokensStatus !== "ready") {
|
|
1940
|
+
setSession(null);
|
|
1941
|
+
return;
|
|
1942
|
+
}
|
|
1943
|
+
const hydrationKey = `${privateReadAddress.toLowerCase()}:${serviceAddress.toLowerCase()}`;
|
|
1944
|
+
const shouldResume = hydratedScopeRef.current !== hydrationKey;
|
|
1945
|
+
hydratedScopeRef.current = hydrationKey;
|
|
1946
|
+
const restored = preserveCreditedFallback(
|
|
1947
|
+
validateRestoredSession(loadExternalDepositLockSession(privateReadAddress))
|
|
1948
|
+
);
|
|
1949
|
+
installSession(restored);
|
|
1950
|
+
if (!restored) {
|
|
1951
|
+
return;
|
|
1952
|
+
}
|
|
1953
|
+
if (!shouldResume) return;
|
|
1954
|
+
if (restored.creditedAmount && restored.payload) {
|
|
1955
|
+
void submitSession(restored);
|
|
1956
|
+
} else if (restored.creditedAmount) {
|
|
1957
|
+
notifyFailure(
|
|
1958
|
+
new PostDepositLockError(
|
|
1959
|
+
"Deposit credited; sign a fresh policy to lock the funds",
|
|
1960
|
+
"not-found",
|
|
1961
|
+
BigInt(restored.maxLockAmount),
|
|
1962
|
+
BigInt(restored.creditedAmount)
|
|
1963
|
+
)
|
|
1964
|
+
);
|
|
1965
|
+
}
|
|
1966
|
+
}, [
|
|
1967
|
+
installSession,
|
|
1968
|
+
notifyFailure,
|
|
1969
|
+
privateReadAddress,
|
|
1970
|
+
preserveCreditedFallback,
|
|
1971
|
+
serviceAddress,
|
|
1972
|
+
submitSession,
|
|
1973
|
+
tokensStatus,
|
|
1974
|
+
validateRestoredSession
|
|
1975
|
+
]);
|
|
1976
|
+
useEffect(() => {
|
|
1977
|
+
if (!privateReadAddress || !serviceAddress || tokensStatus !== "ready") return;
|
|
1978
|
+
const reconcile = (restored) => {
|
|
1979
|
+
installSession(preserveCreditedFallback(validateRestoredSession(restored)));
|
|
1980
|
+
};
|
|
1981
|
+
const unsubscribe = subscribeExternalDepositLockSession(privateReadAddress, reconcile);
|
|
1982
|
+
reconcile(loadExternalDepositLockSession(privateReadAddress));
|
|
1983
|
+
return unsubscribe;
|
|
1984
|
+
}, [
|
|
1985
|
+
installSession,
|
|
1986
|
+
preserveCreditedFallback,
|
|
1987
|
+
privateReadAddress,
|
|
1988
|
+
serviceAddress,
|
|
1989
|
+
tokensStatus,
|
|
1990
|
+
validateRestoredSession
|
|
1991
|
+
]);
|
|
1992
|
+
const signAndPersist = useCallback(
|
|
1993
|
+
async ({ tokenId, amount }) => {
|
|
1994
|
+
if (!allowance) throw new Error("No allowance configured for this deposit");
|
|
1995
|
+
if (signingRef.current || settlingRef.current) {
|
|
1996
|
+
throw new Error("A policy operation is already in progress");
|
|
1997
|
+
}
|
|
1998
|
+
if (!canUseSharedBrowserStorage()) {
|
|
1999
|
+
throw new Error("Browser storage is required for locked deposit recovery");
|
|
2000
|
+
}
|
|
2001
|
+
const owner = requireDepositLockOwner(address, privateReadAddress);
|
|
2002
|
+
if (sessionRef.current || loadExternalDepositLockSession(owner)) {
|
|
2003
|
+
throw new Error("Finish or cancel the active external deposit before starting another");
|
|
2004
|
+
}
|
|
2005
|
+
const token = getTokenById(tokenId);
|
|
2006
|
+
if (!token) throw new Error(`Unknown token ID: ${tokenId}`);
|
|
2007
|
+
if (token.contract === zeroAddress) {
|
|
2008
|
+
throw new Error("External deposits support ERC20 tokens only");
|
|
2009
|
+
}
|
|
2010
|
+
const targetService = requireServiceAddress(serviceAddress);
|
|
2011
|
+
const lockDuration = allowance.lockDuration ?? DEFAULT_LOCK_DURATION_SECONDS;
|
|
2012
|
+
signingRef.current = true;
|
|
2013
|
+
setIsSigning(true);
|
|
2014
|
+
try {
|
|
2015
|
+
await ensureCorrectChain(networkConfig.chainId);
|
|
2016
|
+
const signingWalletClient = await getWalletClient(config, {
|
|
2017
|
+
chainId: networkConfig.chainId
|
|
2018
|
+
});
|
|
2019
|
+
const payload = await createSignedLockRequest({
|
|
2020
|
+
client,
|
|
2021
|
+
walletClient: signingWalletClient,
|
|
2022
|
+
userAddress: owner,
|
|
2023
|
+
networkConfig,
|
|
2024
|
+
serviceAddress: targetService,
|
|
2025
|
+
tokenId: token.id,
|
|
2026
|
+
amount: clampLockAmount(amount, BigInt(allowance.value)),
|
|
2027
|
+
lockDuration
|
|
2028
|
+
});
|
|
2029
|
+
const startBlock = await getBlockNumber(config, { chainId: token.chainId });
|
|
2030
|
+
if (startBlock > BigInt(Number.MAX_SAFE_INTEGER)) {
|
|
2031
|
+
throw new Error("Source-chain block number exceeds the supported range");
|
|
2032
|
+
}
|
|
2033
|
+
if (beneficiaryRef.current?.toLowerCase() !== owner.toLowerCase()) {
|
|
2034
|
+
throw new Error("Authenticated deposit account changed while signing");
|
|
2035
|
+
}
|
|
2036
|
+
const next = {
|
|
2037
|
+
version: 1,
|
|
2038
|
+
owner,
|
|
2039
|
+
serviceAddress: targetService,
|
|
2040
|
+
chainId: token.chainId,
|
|
2041
|
+
tokenId: token.id,
|
|
2042
|
+
startBlock: Number(startBlock),
|
|
2043
|
+
depositAmount: amount.toString(),
|
|
2044
|
+
maxLockAmount: String(payload.amount),
|
|
2045
|
+
lockDuration,
|
|
2046
|
+
generation: payload.signature,
|
|
2047
|
+
payload
|
|
2048
|
+
};
|
|
2049
|
+
saveExternalDepositLockSession(next, null);
|
|
2050
|
+
installSession(next);
|
|
2051
|
+
} finally {
|
|
2052
|
+
signingRef.current = false;
|
|
2053
|
+
setIsSigning(false);
|
|
2054
|
+
}
|
|
2055
|
+
},
|
|
2056
|
+
[
|
|
2057
|
+
address,
|
|
2058
|
+
allowance,
|
|
2059
|
+
client,
|
|
2060
|
+
config,
|
|
2061
|
+
ensureCorrectChain,
|
|
2062
|
+
getTokenById,
|
|
2063
|
+
installSession,
|
|
2064
|
+
networkConfig,
|
|
2065
|
+
privateReadAddress,
|
|
2066
|
+
serviceAddress
|
|
2067
|
+
]
|
|
2068
|
+
);
|
|
2069
|
+
const settleAfterCredit = useCallback(
|
|
2070
|
+
async (txHash, creditedAmount) => {
|
|
2071
|
+
const active = sessionRef.current;
|
|
2072
|
+
if (!active || settlingRef.current) return;
|
|
2073
|
+
if (!isCurrentExternalDepositVerification(active, txHash)) {
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
if (creditedAmount <= 0n) throw new Error("Credited amount must be positive");
|
|
2077
|
+
const credited = {
|
|
2078
|
+
...active,
|
|
2079
|
+
verification: void 0,
|
|
2080
|
+
creditedAmount: creditedAmount.toString()
|
|
2081
|
+
};
|
|
2082
|
+
try {
|
|
2083
|
+
saveExternalDepositLockSession(credited, active);
|
|
2084
|
+
pendingCreditedTransitionRef.current = null;
|
|
2085
|
+
} catch (err) {
|
|
2086
|
+
const stored = loadExternalDepositLockSession(active.owner);
|
|
2087
|
+
if (err instanceof ExternalDepositLockSessionChangedError || !stored || !isSameExternalDepositLockSession(stored, active)) {
|
|
2088
|
+
pendingCreditedTransitionRef.current = null;
|
|
2089
|
+
installSession(stored ?? null);
|
|
2090
|
+
return;
|
|
2091
|
+
}
|
|
2092
|
+
pendingCreditedTransitionRef.current = { predecessor: active, credited };
|
|
2093
|
+
installSession(credited);
|
|
2094
|
+
notifyFailure(
|
|
2095
|
+
new PostDepositLockError(
|
|
2096
|
+
err instanceof Error ? err.message : "Unable to persist credited deposit state",
|
|
2097
|
+
"submission-failed",
|
|
2098
|
+
BigInt(active.maxLockAmount),
|
|
2099
|
+
creditedAmount,
|
|
2100
|
+
{ cause: err }
|
|
2101
|
+
)
|
|
2102
|
+
);
|
|
2103
|
+
return;
|
|
2104
|
+
}
|
|
2105
|
+
installSession(credited);
|
|
2106
|
+
await submitSession(credited);
|
|
2107
|
+
},
|
|
2108
|
+
[installSession, notifyFailure, submitSession]
|
|
2109
|
+
);
|
|
2110
|
+
const recordVerification = useCallback(
|
|
2111
|
+
(context) => {
|
|
2112
|
+
const active = sessionRef.current;
|
|
2113
|
+
if (!active || active.creditedAmount) {
|
|
2114
|
+
throw new Error("No external deposit session is awaiting verification");
|
|
2115
|
+
}
|
|
2116
|
+
if (active.verification) {
|
|
2117
|
+
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);
|
|
2118
|
+
if (sameTransfer) return;
|
|
2119
|
+
throw new Error("Another transfer is already being verified for this deposit session");
|
|
2120
|
+
}
|
|
2121
|
+
if (context.chainId !== active.chainId || context.amount <= 0n) {
|
|
2122
|
+
throw new Error("Discovered transfer does not match the signed deposit session");
|
|
2123
|
+
}
|
|
2124
|
+
const current = loadExternalDepositLockSession(active.owner);
|
|
2125
|
+
if (!current || current.generation !== active.generation) {
|
|
2126
|
+
throw new Error("The signed deposit session changed before verification");
|
|
2127
|
+
}
|
|
2128
|
+
const next = {
|
|
2129
|
+
...active,
|
|
2130
|
+
verification: {
|
|
2131
|
+
hash: context.hash,
|
|
2132
|
+
chainId: context.chainId,
|
|
2133
|
+
amount: context.amount.toString(),
|
|
2134
|
+
logIndex: context.logIndex
|
|
2135
|
+
}
|
|
2136
|
+
};
|
|
2137
|
+
saveExternalDepositLockSession(next, active);
|
|
2138
|
+
installSession(next);
|
|
2139
|
+
},
|
|
2140
|
+
[installSession]
|
|
2141
|
+
);
|
|
2142
|
+
const retryAfterCredit = useCallback(async () => {
|
|
2143
|
+
const active = sessionRef.current;
|
|
2144
|
+
if (!active?.creditedAmount) throw new Error("No credited external deposit to recover");
|
|
2145
|
+
if (signingRef.current || settlingRef.current) {
|
|
2146
|
+
throw new Error("A policy operation is already in progress");
|
|
2147
|
+
}
|
|
2148
|
+
if (privateReadAddress?.toLowerCase() !== active.owner.toLowerCase()) {
|
|
2149
|
+
throw new Error("Authenticated deposit account does not match the credited deposit");
|
|
2150
|
+
}
|
|
2151
|
+
const token = getTokenById(active.tokenId);
|
|
2152
|
+
if (!token || token.chainId !== active.chainId || token.contract === zeroAddress) {
|
|
2153
|
+
throw new Error("The credited token is no longer available");
|
|
2154
|
+
}
|
|
2155
|
+
const targetService = requireServiceAddress(serviceAddress);
|
|
2156
|
+
if (targetService.toLowerCase() !== active.serviceAddress.toLowerCase()) {
|
|
2157
|
+
throw new Error("The service for this credited deposit has changed");
|
|
2158
|
+
}
|
|
2159
|
+
if (active.payload) {
|
|
2160
|
+
await submitSession(active);
|
|
2161
|
+
return;
|
|
2162
|
+
}
|
|
2163
|
+
if (active.submissionAmbiguous) {
|
|
2164
|
+
throw new Error(
|
|
2165
|
+
"The previous lock submission may have succeeded; a fresh signature is unsafe"
|
|
2166
|
+
);
|
|
2167
|
+
}
|
|
2168
|
+
const owner = requireDepositLockOwner(address, privateReadAddress);
|
|
2169
|
+
const amount = externalDepositRetryAmount(active);
|
|
2170
|
+
let retrySession;
|
|
2171
|
+
signingRef.current = true;
|
|
2172
|
+
setIsSigning(true);
|
|
2173
|
+
try {
|
|
2174
|
+
await ensureCorrectChain(networkConfig.chainId);
|
|
2175
|
+
const signingWalletClient = await getWalletClient(config, {
|
|
2176
|
+
chainId: networkConfig.chainId
|
|
2177
|
+
});
|
|
2178
|
+
const payload = await createSignedLockRequest({
|
|
2179
|
+
client,
|
|
2180
|
+
walletClient: signingWalletClient,
|
|
2181
|
+
userAddress: owner,
|
|
2182
|
+
networkConfig,
|
|
2183
|
+
serviceAddress: targetService,
|
|
2184
|
+
tokenId: active.tokenId,
|
|
2185
|
+
amount,
|
|
2186
|
+
lockDuration: active.lockDuration
|
|
2187
|
+
});
|
|
2188
|
+
if (beneficiaryRef.current?.toLowerCase() !== owner.toLowerCase()) {
|
|
2189
|
+
throw new Error("Authenticated deposit account changed while signing");
|
|
2190
|
+
}
|
|
2191
|
+
retrySession = {
|
|
2192
|
+
...active,
|
|
2193
|
+
depositAmount: amount.toString(),
|
|
2194
|
+
generation: payload.signature,
|
|
2195
|
+
verification: void 0,
|
|
2196
|
+
submissionAmbiguous: void 0,
|
|
2197
|
+
payload
|
|
2198
|
+
};
|
|
2199
|
+
saveExternalDepositLockSession(retrySession, active);
|
|
2200
|
+
installSession(retrySession);
|
|
2201
|
+
} finally {
|
|
2202
|
+
signingRef.current = false;
|
|
2203
|
+
setIsSigning(false);
|
|
2204
|
+
}
|
|
2205
|
+
await submitSession(retrySession);
|
|
2206
|
+
}, [
|
|
2207
|
+
address,
|
|
2208
|
+
client,
|
|
2209
|
+
config,
|
|
2210
|
+
ensureCorrectChain,
|
|
2211
|
+
getTokenById,
|
|
2212
|
+
installSession,
|
|
2213
|
+
networkConfig,
|
|
2214
|
+
privateReadAddress,
|
|
2215
|
+
serviceAddress,
|
|
2216
|
+
submitSession
|
|
2217
|
+
]);
|
|
2218
|
+
const clearVerification = useCallback(() => {
|
|
2219
|
+
const active = sessionRef.current;
|
|
2220
|
+
if (!active?.verification || active.creditedAmount) return false;
|
|
2221
|
+
const next = clearExternalDepositVerification(active);
|
|
2222
|
+
if (!next) {
|
|
2223
|
+
installSession(loadExternalDepositLockSession(active.owner) ?? null);
|
|
2224
|
+
return false;
|
|
2225
|
+
}
|
|
2226
|
+
installSession(next);
|
|
2227
|
+
return true;
|
|
2228
|
+
}, [installSession]);
|
|
2229
|
+
const discardSession = useCallback(() => {
|
|
2230
|
+
const active = sessionRef.current;
|
|
2231
|
+
if (!active) return true;
|
|
2232
|
+
if (signingRef.current || settlingRef.current) return false;
|
|
2233
|
+
const pending = pendingCreditedTransitionRef.current;
|
|
2234
|
+
const discardTarget = pending && isSameExternalDepositLockSession(active, pending.credited) ? pending.predecessor : active;
|
|
2235
|
+
const discarded = discardExternalDepositLockSession(discardTarget);
|
|
2236
|
+
const restored = loadExternalDepositLockSession(active.owner) ?? null;
|
|
2237
|
+
if (discarded) pendingCreditedTransitionRef.current = null;
|
|
2238
|
+
installSession(restored);
|
|
2239
|
+
return discarded && !restored;
|
|
2240
|
+
}, [installSession]);
|
|
2241
|
+
return {
|
|
2242
|
+
isSigning,
|
|
2243
|
+
isSubmittingLock,
|
|
2244
|
+
session,
|
|
2245
|
+
signAndPersist,
|
|
2246
|
+
recordVerification,
|
|
2247
|
+
settleAfterCredit,
|
|
2248
|
+
retryAfterCredit,
|
|
2249
|
+
clearVerification,
|
|
2250
|
+
discardSession
|
|
2251
|
+
};
|
|
2252
|
+
}
|
|
1520
2253
|
function USDCIcon({ className, size = 24 }) {
|
|
1521
2254
|
return /* @__PURE__ */ jsxs(
|
|
1522
2255
|
"svg",
|
|
@@ -1658,134 +2391,99 @@ function getChainIcon(chainId, size) {
|
|
|
1658
2391
|
);
|
|
1659
2392
|
}
|
|
1660
2393
|
}
|
|
1661
|
-
function
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
{
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
strokeLinecap: "round",
|
|
1669
|
-
strokeLinejoin: "round"
|
|
1670
|
-
}
|
|
1671
|
-
) });
|
|
1672
|
-
}
|
|
1673
|
-
function ChevronRightIcon() {
|
|
1674
|
-
return /* @__PURE__ */ jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "-rotate-90", children: /* @__PURE__ */ jsx(
|
|
1675
|
-
"path",
|
|
1676
|
-
{
|
|
1677
|
-
d: "M0 0l6 6 6-6",
|
|
1678
|
-
stroke: "currentColor",
|
|
1679
|
-
strokeWidth: "1.5",
|
|
1680
|
-
strokeLinecap: "round",
|
|
1681
|
-
strokeLinejoin: "round",
|
|
1682
|
-
fill: "none"
|
|
1683
|
-
}
|
|
1684
|
-
) });
|
|
1685
|
-
}
|
|
1686
|
-
function ChevronLeftIcon() {
|
|
1687
|
-
return /* @__PURE__ */ jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "rotate-90", children: /* @__PURE__ */ jsx(
|
|
1688
|
-
"path",
|
|
1689
|
-
{
|
|
1690
|
-
d: "M0 0l6 6 6-6",
|
|
1691
|
-
stroke: "currentColor",
|
|
1692
|
-
strokeWidth: "1.5",
|
|
1693
|
-
strokeLinecap: "round",
|
|
1694
|
-
strokeLinejoin: "round",
|
|
1695
|
-
fill: "none"
|
|
1696
|
-
}
|
|
1697
|
-
) });
|
|
1698
|
-
}
|
|
1699
|
-
function ChevronDownIcon({
|
|
1700
|
-
direction = "down",
|
|
1701
|
-
className
|
|
1702
|
-
}) {
|
|
1703
|
-
return /* @__PURE__ */ jsx(
|
|
1704
|
-
"svg",
|
|
1705
|
-
{
|
|
1706
|
-
width: "16",
|
|
1707
|
-
height: "16",
|
|
1708
|
-
viewBox: "0 0 16 16",
|
|
1709
|
-
fill: "none",
|
|
1710
|
-
className: cn(
|
|
1711
|
-
"transition-transform",
|
|
1712
|
-
direction === "right" && "-rotate-90",
|
|
1713
|
-
direction === "up" && "rotate-180",
|
|
1714
|
-
className
|
|
1715
|
-
),
|
|
1716
|
-
children: /* @__PURE__ */ jsx(
|
|
1717
|
-
"path",
|
|
2394
|
+
function TokenSelectorView({ selectedTokenId, onSelect }) {
|
|
2395
|
+
const { enabledTokens } = usePrivanaContext();
|
|
2396
|
+
return /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col rounded-[10px] p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex max-h-72 flex-col gap-1 overflow-y-auto", children: [
|
|
2397
|
+
enabledTokens.map((token) => {
|
|
2398
|
+
const isSelected = selectedTokenId === token.id;
|
|
2399
|
+
return /* @__PURE__ */ jsxs(
|
|
2400
|
+
"button",
|
|
1718
2401
|
{
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
}
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
}
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
strokeLinejoin: "round"
|
|
1759
|
-
}
|
|
1760
|
-
)
|
|
1761
|
-
] });
|
|
2402
|
+
type: "button",
|
|
2403
|
+
onClick: () => onSelect(token.id),
|
|
2404
|
+
className: cn(
|
|
2405
|
+
"hover:bg-secondary flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-left transition-colors",
|
|
2406
|
+
isSelected && "bg-secondary"
|
|
2407
|
+
),
|
|
2408
|
+
children: [
|
|
2409
|
+
/* @__PURE__ */ jsx("div", { className: "h-6 w-6 shrink-0 overflow-hidden rounded-full", children: getTokenIcon(token.symbol, 24) }),
|
|
2410
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
2411
|
+
/* @__PURE__ */ jsx("div", { className: "text-foreground text-sm font-medium", children: token.symbol }),
|
|
2412
|
+
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-[11px]", children: token.name })
|
|
2413
|
+
] }),
|
|
2414
|
+
isSelected && /* @__PURE__ */ jsx("div", { className: "bg-primary flex h-4 w-4 shrink-0 items-center justify-center rounded-full", children: /* @__PURE__ */ jsx(
|
|
2415
|
+
"svg",
|
|
2416
|
+
{
|
|
2417
|
+
width: "10",
|
|
2418
|
+
height: "10",
|
|
2419
|
+
viewBox: "0 0 10 10",
|
|
2420
|
+
fill: "none",
|
|
2421
|
+
className: "text-primary-foreground",
|
|
2422
|
+
children: /* @__PURE__ */ jsx(
|
|
2423
|
+
"path",
|
|
2424
|
+
{
|
|
2425
|
+
d: "M2 5L4 7L8 3",
|
|
2426
|
+
stroke: "currentColor",
|
|
2427
|
+
strokeWidth: "1.5",
|
|
2428
|
+
strokeLinecap: "round",
|
|
2429
|
+
strokeLinejoin: "round"
|
|
2430
|
+
}
|
|
2431
|
+
)
|
|
2432
|
+
}
|
|
2433
|
+
) })
|
|
2434
|
+
]
|
|
2435
|
+
},
|
|
2436
|
+
token.id
|
|
2437
|
+
);
|
|
2438
|
+
}),
|
|
2439
|
+
enabledTokens.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground px-3 py-6 text-center text-sm", children: "No tokens available" })
|
|
2440
|
+
] }) });
|
|
1762
2441
|
}
|
|
1763
|
-
function
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
2442
|
+
function CreditCardWidgetView({
|
|
2443
|
+
token,
|
|
2444
|
+
amount,
|
|
2445
|
+
allowance,
|
|
2446
|
+
onCredited,
|
|
2447
|
+
onLockSubmitted,
|
|
2448
|
+
onLockFailed
|
|
2449
|
+
}) {
|
|
2450
|
+
const moonpayCurrencyCode = token?.moonpayCurrencyCode;
|
|
2451
|
+
const containerRef = useRef(null);
|
|
2452
|
+
const [widgetTheme, setWidgetTheme] = useState();
|
|
2453
|
+
useEffect(() => {
|
|
2454
|
+
if (containerRef.current) setWidgetTheme(resolveSdkTheme(containerRef.current));
|
|
2455
|
+
}, []);
|
|
2456
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
2457
|
+
/* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Complete your purchase" }),
|
|
2458
|
+
token && moonpayCurrencyCode ? /* @__PURE__ */ jsx(
|
|
2459
|
+
FiatOnRampForm,
|
|
1768
2460
|
{
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
2461
|
+
tokenId: token.id,
|
|
2462
|
+
currencyCode: moonpayCurrencyCode,
|
|
2463
|
+
quoteCurrencyAmount: amount || void 0,
|
|
2464
|
+
lockAmount: true,
|
|
2465
|
+
variant: "embedded",
|
|
2466
|
+
autoStart: true,
|
|
2467
|
+
theme: widgetTheme,
|
|
2468
|
+
postDepositLock: allowance ? {
|
|
2469
|
+
maxAmount: BigInt(allowance.value),
|
|
2470
|
+
lockDuration: allowance.lockDuration
|
|
2471
|
+
} : void 0,
|
|
2472
|
+
onCredited,
|
|
2473
|
+
onLockSubmitted,
|
|
2474
|
+
onLockFailed
|
|
1774
2475
|
}
|
|
1775
|
-
)
|
|
2476
|
+
) : /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-sm", children: [
|
|
2477
|
+
token?.symbol ?? "This token",
|
|
2478
|
+
" isn\u2019t available for card purchases yet."
|
|
2479
|
+
] })
|
|
1776
2480
|
] });
|
|
1777
2481
|
}
|
|
1778
|
-
function
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
stroke: "currentColor",
|
|
1784
|
-
strokeWidth: "1.5",
|
|
1785
|
-
strokeLinecap: "round",
|
|
1786
|
-
strokeLinejoin: "round"
|
|
1787
|
-
}
|
|
1788
|
-
) });
|
|
2482
|
+
function resolveSdkTheme(el) {
|
|
2483
|
+
const scheme = getComputedStyle(el).colorScheme;
|
|
2484
|
+
if (scheme === "dark") return "dark";
|
|
2485
|
+
if (scheme === "light") return "light";
|
|
2486
|
+
return el.closest(".dark") ? "dark" : "light";
|
|
1789
2487
|
}
|
|
1790
2488
|
function CheckIcon() {
|
|
1791
2489
|
return /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx(
|
|
@@ -1904,7 +2602,16 @@ function WarningIcon() {
|
|
|
1904
2602
|
}
|
|
1905
2603
|
) });
|
|
1906
2604
|
}
|
|
1907
|
-
function TransactionWarningView({
|
|
2605
|
+
function TransactionWarningView({
|
|
2606
|
+
title,
|
|
2607
|
+
message,
|
|
2608
|
+
onDone,
|
|
2609
|
+
actionLabel = "Done",
|
|
2610
|
+
pendingLabel = actionLabel,
|
|
2611
|
+
isPending = false,
|
|
2612
|
+
onDismiss,
|
|
2613
|
+
dismissLabel = "Dismiss"
|
|
2614
|
+
}) {
|
|
1908
2615
|
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-4", children: [
|
|
1909
2616
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
1910
2617
|
/* @__PURE__ */ jsx("div", { className: "flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-amber-500/10 text-amber-400", children: /* @__PURE__ */ jsx(WarningIcon, {}) }),
|
|
@@ -1913,14 +2620,26 @@ function TransactionWarningView({ title, message, onDone }) {
|
|
|
1913
2620
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: message })
|
|
1914
2621
|
] })
|
|
1915
2622
|
] }),
|
|
1916
|
-
/* @__PURE__ */
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
2623
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
2624
|
+
onDismiss && /* @__PURE__ */ jsx(
|
|
2625
|
+
"button",
|
|
2626
|
+
{
|
|
2627
|
+
onClick: onDismiss,
|
|
2628
|
+
disabled: isPending,
|
|
2629
|
+
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",
|
|
2630
|
+
children: dismissLabel
|
|
2631
|
+
}
|
|
2632
|
+
),
|
|
2633
|
+
/* @__PURE__ */ jsx(
|
|
2634
|
+
"button",
|
|
2635
|
+
{
|
|
2636
|
+
onClick: onDone,
|
|
2637
|
+
disabled: isPending,
|
|
2638
|
+
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",
|
|
2639
|
+
children: isPending ? pendingLabel : actionLabel
|
|
2640
|
+
}
|
|
2641
|
+
)
|
|
2642
|
+
] })
|
|
1924
2643
|
] });
|
|
1925
2644
|
}
|
|
1926
2645
|
function TransactionErrorView({
|
|
@@ -1931,7 +2650,8 @@ function TransactionErrorView({
|
|
|
1931
2650
|
onRetry,
|
|
1932
2651
|
onDismiss,
|
|
1933
2652
|
isRetrying,
|
|
1934
|
-
retryLabel = "Retry Verification"
|
|
2653
|
+
retryLabel = "Retry Verification",
|
|
2654
|
+
dismissLabel = "Close"
|
|
1935
2655
|
}) {
|
|
1936
2656
|
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-4", children: [
|
|
1937
2657
|
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
@@ -1955,13 +2675,13 @@ function TransactionErrorView({
|
|
|
1955
2675
|
}
|
|
1956
2676
|
),
|
|
1957
2677
|
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1958
|
-
/* @__PURE__ */ jsx(
|
|
2678
|
+
onDismiss && /* @__PURE__ */ jsx(
|
|
1959
2679
|
"button",
|
|
1960
2680
|
{
|
|
1961
2681
|
onClick: onDismiss,
|
|
1962
2682
|
disabled: isRetrying,
|
|
1963
2683
|
className: "border-border text-foreground hover:bg-secondary flex h-10 flex-1 cursor-pointer items-center justify-center rounded-[10px] border px-3 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
|
|
1964
|
-
children:
|
|
2684
|
+
children: dismissLabel
|
|
1965
2685
|
}
|
|
1966
2686
|
),
|
|
1967
2687
|
/* @__PURE__ */ jsx(
|
|
@@ -1970,1130 +2690,141 @@ function TransactionErrorView({
|
|
|
1970
2690
|
onClick: onRetry,
|
|
1971
2691
|
disabled: isRetrying,
|
|
1972
2692
|
className: "bg-primary text-primary-foreground hover:bg-primary/90 flex h-10 flex-1 cursor-pointer items-center justify-center rounded-[10px] px-3 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
|
|
1973
|
-
children: isRetrying ? "Retrying
|
|
2693
|
+
children: isRetrying ? "Retrying\u2026" : retryLabel
|
|
1974
2694
|
}
|
|
1975
2695
|
)
|
|
1976
2696
|
] })
|
|
1977
2697
|
] });
|
|
1978
2698
|
}
|
|
1979
|
-
function
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
const [amount, setAmount] = useState("");
|
|
1989
|
-
const [showSuccess, setShowSuccess] = useState(false);
|
|
1990
|
-
const [showTimeout, setShowTimeout] = useState(false);
|
|
1991
|
-
const [cancelled, setCancelled] = useState(false);
|
|
1992
|
-
const targetChain = getChainById2(selectedToken.chainId) ?? chains[0];
|
|
1993
|
-
const isNative = selectedToken.contract === zeroAddress;
|
|
1994
|
-
const { data: nativeBalanceData } = useBalance$1({
|
|
1995
|
-
address,
|
|
1996
|
-
chainId: targetChain?.id,
|
|
1997
|
-
query: {
|
|
1998
|
-
enabled: !!address && isNative
|
|
2699
|
+
function CloseIcon() {
|
|
2700
|
+
return /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx(
|
|
2701
|
+
"path",
|
|
2702
|
+
{
|
|
2703
|
+
d: "M11.99997 11.99997l-5.99995-5.99995-6.00002-6.00002m6.00002 6.00002l6.00001-6.00002m-12.00003 12.00003l6.00002-6.00001",
|
|
2704
|
+
stroke: "currentColor",
|
|
2705
|
+
strokeWidth: "1.5",
|
|
2706
|
+
strokeLinecap: "round",
|
|
2707
|
+
strokeLinejoin: "round"
|
|
1999
2708
|
}
|
|
2000
|
-
});
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2709
|
+
) });
|
|
2710
|
+
}
|
|
2711
|
+
function ChevronRightIcon() {
|
|
2712
|
+
return /* @__PURE__ */ jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "-rotate-90", children: /* @__PURE__ */ jsx(
|
|
2713
|
+
"path",
|
|
2714
|
+
{
|
|
2715
|
+
d: "M0 0l6 6 6-6",
|
|
2716
|
+
stroke: "currentColor",
|
|
2717
|
+
strokeWidth: "1.5",
|
|
2718
|
+
strokeLinecap: "round",
|
|
2719
|
+
strokeLinejoin: "round",
|
|
2720
|
+
fill: "none"
|
|
2009
2721
|
}
|
|
2010
|
-
});
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2722
|
+
) });
|
|
2723
|
+
}
|
|
2724
|
+
function ChevronLeftIcon() {
|
|
2725
|
+
return /* @__PURE__ */ jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "rotate-90", children: /* @__PURE__ */ jsx(
|
|
2726
|
+
"path",
|
|
2727
|
+
{
|
|
2728
|
+
d: "M0 0l6 6 6-6",
|
|
2729
|
+
stroke: "currentColor",
|
|
2730
|
+
strokeWidth: "1.5",
|
|
2731
|
+
strokeLinecap: "round",
|
|
2732
|
+
strokeLinejoin: "round",
|
|
2733
|
+
fill: "none"
|
|
2016
2734
|
}
|
|
2017
|
-
};
|
|
2018
|
-
const hasValidAmount = amount && parseFloat(amount) > 0;
|
|
2019
|
-
const tooManyDecimals = hasValidAmount && amount.includes(".") && amount.split(".")[1].length > selectedToken.decimals;
|
|
2020
|
-
const exceedsBalance = hasValidAmount && !tooManyDecimals && walletBalance != null && parseTokenAmount(amount, selectedToken.decimals) > walletBalance;
|
|
2021
|
-
const {
|
|
2022
|
-
txHash,
|
|
2023
|
-
isGettingAddress,
|
|
2024
|
-
isSwitchingChain,
|
|
2025
|
-
isSendingTransaction,
|
|
2026
|
-
isWaitingForConfirmation,
|
|
2027
|
-
isWaitingForProcessing,
|
|
2028
|
-
verificationFailed,
|
|
2029
|
-
isPending,
|
|
2030
|
-
error,
|
|
2031
|
-
deposit,
|
|
2032
|
-
retryVerification,
|
|
2033
|
-
reset
|
|
2034
|
-
} = useDeposit({
|
|
2035
|
-
onCredited: () => {
|
|
2036
|
-
setAmount("");
|
|
2037
|
-
if (onSuccess) {
|
|
2038
|
-
reset();
|
|
2039
|
-
onSuccess();
|
|
2040
|
-
} else {
|
|
2041
|
-
setShowSuccess(true);
|
|
2042
|
-
}
|
|
2043
|
-
},
|
|
2044
|
-
onCheckTimeout: () => {
|
|
2045
|
-
setAmount("");
|
|
2046
|
-
setShowTimeout(true);
|
|
2047
|
-
}
|
|
2048
|
-
});
|
|
2049
|
-
const depositSteps = [
|
|
2050
|
-
{
|
|
2051
|
-
label: "Getting deposit address",
|
|
2052
|
-
status: isGettingAddress ? "active" : isSwitchingChain || isSendingTransaction || isWaitingForConfirmation || isWaitingForProcessing ? "completed" : "pending"
|
|
2053
|
-
},
|
|
2054
|
-
{
|
|
2055
|
-
label: `Switching to ${targetChain?.name ?? "deposit chain"}`,
|
|
2056
|
-
status: isSwitchingChain ? "active" : isSendingTransaction || isWaitingForConfirmation || isWaitingForProcessing ? "completed" : "pending"
|
|
2057
|
-
},
|
|
2058
|
-
{
|
|
2059
|
-
label: "Confirm in wallet",
|
|
2060
|
-
status: isSendingTransaction ? "active" : isWaitingForConfirmation || isWaitingForProcessing ? "completed" : "pending"
|
|
2061
|
-
},
|
|
2062
|
-
{
|
|
2063
|
-
label: "Confirming transaction",
|
|
2064
|
-
status: isWaitingForConfirmation ? "active" : isWaitingForProcessing ? "completed" : "pending"
|
|
2065
|
-
},
|
|
2066
|
-
{
|
|
2067
|
-
label: "Verifying deposit \u2014 may take up to a few minutes",
|
|
2068
|
-
status: isWaitingForProcessing ? "active" : "pending"
|
|
2069
|
-
}
|
|
2070
|
-
];
|
|
2071
|
-
useEffect(() => {
|
|
2072
|
-
if (error && !verificationFailed) {
|
|
2073
|
-
toast.error(error.message.length > 100 ? `${error.message.slice(0, 100)}...` : error.message);
|
|
2074
|
-
}
|
|
2075
|
-
}, [error, verificationFailed]);
|
|
2076
|
-
useEffect(() => {
|
|
2077
|
-
onPendingChange?.(isPending && !cancelled);
|
|
2078
|
-
}, [isPending, cancelled, onPendingChange]);
|
|
2079
|
-
useEffect(() => {
|
|
2080
|
-
onUnsafeToCloseChange?.((isGettingAddress || isSendingTransaction) && !cancelled);
|
|
2081
|
-
}, [isGettingAddress, isSendingTransaction, cancelled, onUnsafeToCloseChange]);
|
|
2082
|
-
const handleCancel = () => {
|
|
2083
|
-
setCancelled(true);
|
|
2084
|
-
reset();
|
|
2085
|
-
};
|
|
2086
|
-
const handleDone = () => {
|
|
2087
|
-
setShowSuccess(false);
|
|
2088
|
-
setShowTimeout(false);
|
|
2089
|
-
setCancelled(false);
|
|
2090
|
-
reset();
|
|
2091
|
-
};
|
|
2092
|
-
const handleDismissVerificationError = () => {
|
|
2093
|
-
setAmount("");
|
|
2094
|
-
setCancelled(false);
|
|
2095
|
-
reset();
|
|
2096
|
-
};
|
|
2097
|
-
const handleRetryVerification = () => {
|
|
2098
|
-
retryVerification().catch(() => {
|
|
2099
|
-
});
|
|
2100
|
-
};
|
|
2101
|
-
const explorerTxUrl = txHash && targetChain?.explorerUrl ? `${targetChain.explorerUrl}/tx/${txHash}` : void 0;
|
|
2102
|
-
const handleSubmit = async () => {
|
|
2103
|
-
if (!amount || !selectedToken || exceedsBalance) return;
|
|
2104
|
-
setCancelled(false);
|
|
2105
|
-
const amountInWei = parseTokenAmount(amount, selectedToken.decimals);
|
|
2106
|
-
await deposit({
|
|
2107
|
-
tokenId: selectedToken.id,
|
|
2108
|
-
amount: amountInWei
|
|
2109
|
-
});
|
|
2110
|
-
};
|
|
2111
|
-
const getButtonText = () => {
|
|
2112
|
-
if (!isConnected) return "Connect Wallet";
|
|
2113
|
-
if (isSwitchingChain) return "Switching...";
|
|
2114
|
-
return "Deposit";
|
|
2115
|
-
};
|
|
2116
|
-
if (showSuccess) {
|
|
2117
|
-
return /* @__PURE__ */ jsx(
|
|
2118
|
-
TransactionSuccessView,
|
|
2119
|
-
{
|
|
2120
|
-
title: "Deposit Successful",
|
|
2121
|
-
message: `Your ${selectedToken.symbol} deposit has been processed.`,
|
|
2122
|
-
onDone: handleDone
|
|
2123
|
-
}
|
|
2124
|
-
);
|
|
2125
|
-
}
|
|
2126
|
-
if (showTimeout) {
|
|
2127
|
-
return /* @__PURE__ */ jsx(
|
|
2128
|
-
TransactionWarningView,
|
|
2129
|
-
{
|
|
2130
|
-
title: "Deposit Processing",
|
|
2131
|
-
message: `Your transaction was confirmed but the deposit is still being processed. Please check your balance - it should update shortly.`,
|
|
2132
|
-
onDone: handleDone
|
|
2133
|
-
}
|
|
2134
|
-
);
|
|
2135
|
-
}
|
|
2136
|
-
if (verificationFailed) {
|
|
2137
|
-
const baseMessage = "Your transfer was sent on-chain but we could not verify the deposit. The funds are already at the deposit address \u2014 retry verification instead of starting a new deposit.";
|
|
2138
|
-
const detail = error?.message;
|
|
2139
|
-
const message = detail ? `${baseMessage} (${detail})` : baseMessage;
|
|
2140
|
-
return /* @__PURE__ */ jsx(
|
|
2141
|
-
TransactionErrorView,
|
|
2142
|
-
{
|
|
2143
|
-
title: "Verification failed",
|
|
2144
|
-
message,
|
|
2145
|
-
explorerUrl: explorerTxUrl,
|
|
2146
|
-
explorerLabel: "View transaction",
|
|
2147
|
-
onRetry: handleRetryVerification,
|
|
2148
|
-
onDismiss: handleDismissVerificationError
|
|
2149
|
-
}
|
|
2150
|
-
);
|
|
2151
|
-
}
|
|
2152
|
-
if (isPending && !cancelled) {
|
|
2153
|
-
const canCancel = isGettingAddress || isSendingTransaction;
|
|
2154
|
-
return /* @__PURE__ */ jsx(
|
|
2155
|
-
TransactionProgressView,
|
|
2156
|
-
{
|
|
2157
|
-
title: "Depositing...",
|
|
2158
|
-
steps: depositSteps,
|
|
2159
|
-
onCancel: canCancel ? handleCancel : void 0
|
|
2160
|
-
}
|
|
2161
|
-
);
|
|
2162
|
-
}
|
|
2163
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-6", children: [
|
|
2164
|
-
/* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-3", children: [
|
|
2165
|
-
/* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Token" }),
|
|
2166
|
-
/* @__PURE__ */ jsxs(
|
|
2167
|
-
"button",
|
|
2168
|
-
{
|
|
2169
|
-
onClick: onTokenSelect,
|
|
2170
|
-
className: "border-border bg-input flex w-full cursor-pointer items-center gap-3 rounded-lg border p-3",
|
|
2171
|
-
children: [
|
|
2172
|
-
/* @__PURE__ */ jsx("div", { className: "h-8 w-8 shrink-0 overflow-hidden rounded-full", children: getTokenIcon(selectedToken.symbol, 32) }),
|
|
2173
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-start gap-1", children: [
|
|
2174
|
-
/* @__PURE__ */ jsx("span", { className: "text-foreground text-sm font-medium", children: selectedToken.symbol }),
|
|
2175
|
-
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
2176
|
-
"on ",
|
|
2177
|
-
targetChain?.name ?? "Base Sepolia"
|
|
2178
|
-
] })
|
|
2179
|
-
] }),
|
|
2180
|
-
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-5 w-5 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
|
|
2181
|
-
]
|
|
2182
|
-
}
|
|
2183
|
-
)
|
|
2184
|
-
] }),
|
|
2185
|
-
/* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-3", children: [
|
|
2186
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2187
|
-
/* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Amount" }),
|
|
2188
|
-
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-sm", children: [
|
|
2189
|
-
formattedWalletBalance,
|
|
2190
|
-
" ",
|
|
2191
|
-
selectedToken.symbol
|
|
2192
|
-
] })
|
|
2193
|
-
] }),
|
|
2194
|
-
/* @__PURE__ */ jsxs("div", { className: "border-border bg-input flex items-center gap-2 rounded-[10px] border py-1 pr-1 pl-3", children: [
|
|
2195
|
-
/* @__PURE__ */ jsx(
|
|
2196
|
-
"input",
|
|
2197
|
-
{
|
|
2198
|
-
type: "text",
|
|
2199
|
-
inputMode: "decimal",
|
|
2200
|
-
placeholder: "Enter Amount",
|
|
2201
|
-
value: amount,
|
|
2202
|
-
onChange: (e) => {
|
|
2203
|
-
const value = e.target.value.replace(/[^0-9.,]/g, "").replace(/,/g, ".");
|
|
2204
|
-
if (value.split(".").length <= 2) {
|
|
2205
|
-
setAmount(value);
|
|
2206
|
-
}
|
|
2207
|
-
},
|
|
2208
|
-
className: cn(
|
|
2209
|
-
"text-foreground flex-1 bg-transparent text-sm outline-none",
|
|
2210
|
-
"placeholder:text-muted-foreground/50"
|
|
2211
|
-
)
|
|
2212
|
-
}
|
|
2213
|
-
),
|
|
2214
|
-
/* @__PURE__ */ jsx(
|
|
2215
|
-
"button",
|
|
2216
|
-
{
|
|
2217
|
-
onClick: handleMaxClick,
|
|
2218
|
-
className: "bg-secondary text-foreground hover:bg-secondary/80 cursor-pointer rounded px-3 py-2.5 text-xs font-semibold transition-colors",
|
|
2219
|
-
children: "MAX"
|
|
2220
|
-
}
|
|
2221
|
-
)
|
|
2222
|
-
] }),
|
|
2223
|
-
tooManyDecimals && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
2224
|
-
"Too many decimal places (max: ",
|
|
2225
|
-
selectedToken.decimals,
|
|
2226
|
-
")"
|
|
2227
|
-
] }),
|
|
2228
|
-
exceedsBalance && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Insufficient balance" })
|
|
2229
|
-
] }),
|
|
2230
|
-
/* @__PURE__ */ jsx(
|
|
2231
|
-
"button",
|
|
2232
|
-
{
|
|
2233
|
-
onClick: handleSubmit,
|
|
2234
|
-
disabled: !isConnected || !hasValidAmount || tooManyDecimals || !!exceedsBalance || isPending,
|
|
2235
|
-
className: cn(
|
|
2236
|
-
"flex h-10 w-full cursor-pointer items-center justify-center rounded-[10px] px-3 py-2 text-sm font-medium transition-colors",
|
|
2237
|
-
"bg-primary text-primary-foreground hover:bg-primary/90",
|
|
2238
|
-
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
2239
|
-
),
|
|
2240
|
-
children: getButtonText()
|
|
2241
|
-
}
|
|
2242
|
-
)
|
|
2243
|
-
] });
|
|
2244
|
-
}
|
|
2245
|
-
function WithdrawForm({
|
|
2246
|
-
selectedToken,
|
|
2247
|
-
onTokenSelect,
|
|
2248
|
-
onPendingChange,
|
|
2249
|
-
onUnsafeToCloseChange
|
|
2250
|
-
}) {
|
|
2251
|
-
const { isConnected, address } = useAccount();
|
|
2252
|
-
const { chains, getChainById: getChainById2 } = usePrivanaContext();
|
|
2253
|
-
const [amount, setAmount] = useState("");
|
|
2254
|
-
const [showSuccess, setShowSuccess] = useState(false);
|
|
2255
|
-
const [showTimeout, setShowTimeout] = useState(false);
|
|
2256
|
-
const [cancelled, setCancelled] = useState(false);
|
|
2257
|
-
const targetChain = getChainById2(selectedToken.chainId) ?? chains[0];
|
|
2258
|
-
const {
|
|
2259
|
-
balanceWei,
|
|
2260
|
-
isLoading: isBalanceLoading,
|
|
2261
|
-
isError: isBalanceError
|
|
2262
|
-
} = useBalance({
|
|
2263
|
-
tokenId: selectedToken.id
|
|
2264
|
-
});
|
|
2265
|
-
const formattedBalance = formatTokenAmount(balanceWei, selectedToken.decimals);
|
|
2266
|
-
const { withdraw, isPending, currentStep, error, reset } = useWithdraw({
|
|
2267
|
-
onProcessingSuccess: () => {
|
|
2268
|
-
setAmount("");
|
|
2269
|
-
setShowSuccess(true);
|
|
2270
|
-
},
|
|
2271
|
-
onProcessingTimeout: () => {
|
|
2272
|
-
setAmount("");
|
|
2273
|
-
setShowTimeout(true);
|
|
2274
|
-
}
|
|
2275
|
-
});
|
|
2276
|
-
const explorerUrl = address && targetChain ? getExplorerAddressUrl(targetChain.id, address) : void 0;
|
|
2277
|
-
const getStepStatus = (step, after) => {
|
|
2278
|
-
if (currentStep === step) return "active";
|
|
2279
|
-
if (after.includes(currentStep)) return "completed";
|
|
2280
|
-
return "pending";
|
|
2281
|
-
};
|
|
2282
|
-
const withdrawSteps = [
|
|
2283
|
-
{
|
|
2284
|
-
label: "Switching to signing chain",
|
|
2285
|
-
status: getStepStatus("switching-chain", ["signing", "submitting", "processing"])
|
|
2286
|
-
},
|
|
2287
|
-
{ label: "Sign in wallet", status: getStepStatus("signing", ["submitting", "processing"]) },
|
|
2288
|
-
{
|
|
2289
|
-
label: "Submitting withdrawal",
|
|
2290
|
-
status: getStepStatus("submitting", ["processing"])
|
|
2291
|
-
},
|
|
2292
|
-
{
|
|
2293
|
-
label: "Processing \u2014 may take a minute or two",
|
|
2294
|
-
status: getStepStatus("processing", [])
|
|
2295
|
-
}
|
|
2296
|
-
];
|
|
2297
|
-
useEffect(() => {
|
|
2298
|
-
if (error) {
|
|
2299
|
-
toast.error(error.message.length > 100 ? `${error.message.slice(0, 100)}...` : error.message);
|
|
2300
|
-
}
|
|
2301
|
-
}, [error]);
|
|
2302
|
-
useEffect(() => {
|
|
2303
|
-
const pending = isPending && !cancelled;
|
|
2304
|
-
onPendingChange?.(pending);
|
|
2305
|
-
onUnsafeToCloseChange?.(pending);
|
|
2306
|
-
}, [isPending, cancelled, onPendingChange, onUnsafeToCloseChange]);
|
|
2307
|
-
const handleCancel = () => {
|
|
2308
|
-
setCancelled(true);
|
|
2309
|
-
reset();
|
|
2310
|
-
};
|
|
2311
|
-
const handleDone = () => {
|
|
2312
|
-
setShowSuccess(false);
|
|
2313
|
-
setShowTimeout(false);
|
|
2314
|
-
setCancelled(false);
|
|
2315
|
-
reset();
|
|
2316
|
-
};
|
|
2317
|
-
const handleWithdraw = async () => {
|
|
2318
|
-
if (!amount || !selectedToken || exceedsBalance) return;
|
|
2319
|
-
setCancelled(false);
|
|
2320
|
-
const amountInWei = parseTokenAmount(amount, selectedToken.decimals);
|
|
2321
|
-
await withdraw({
|
|
2322
|
-
tokenId: selectedToken.id,
|
|
2323
|
-
amount: amountInWei
|
|
2324
|
-
});
|
|
2325
|
-
};
|
|
2326
|
-
const handleMaxClick = () => {
|
|
2327
|
-
if (formattedBalance && parseFloat(formattedBalance) > 0) {
|
|
2328
|
-
setAmount(formattedBalance.replace(/[\s\u2009]/g, ""));
|
|
2329
|
-
}
|
|
2330
|
-
};
|
|
2331
|
-
const hasValidAmount = amount && parseFloat(amount) > 0;
|
|
2332
|
-
const tooManyDecimals = hasValidAmount && amount.includes(".") && amount.split(".")[1].length > selectedToken.decimals;
|
|
2333
|
-
const exceedsBalance = hasValidAmount && !tooManyDecimals && !isBalanceLoading && !isBalanceError && parseTokenAmount(amount, selectedToken.decimals) > BigInt(balanceWei);
|
|
2334
|
-
const getButtonText = () => {
|
|
2335
|
-
if (!isConnected) return "Connect Wallet";
|
|
2336
|
-
return "Withdraw";
|
|
2337
|
-
};
|
|
2338
|
-
if (showSuccess) {
|
|
2339
|
-
return /* @__PURE__ */ jsx(
|
|
2340
|
-
TransactionSuccessView,
|
|
2341
|
-
{
|
|
2342
|
-
title: "Withdrawal Complete",
|
|
2343
|
-
message: `Your ${selectedToken.symbol} withdrawal has been processed. Funds should appear in your wallet shortly.`,
|
|
2344
|
-
explorerUrl,
|
|
2345
|
-
explorerLabel: targetChain ? getExplorerLabel(targetChain.id) : void 0,
|
|
2346
|
-
onDone: handleDone
|
|
2347
|
-
}
|
|
2348
|
-
);
|
|
2349
|
-
}
|
|
2350
|
-
if (showTimeout) {
|
|
2351
|
-
return /* @__PURE__ */ jsx(
|
|
2352
|
-
TransactionWarningView,
|
|
2353
|
-
{
|
|
2354
|
-
title: "Withdrawal Processing",
|
|
2355
|
-
message: `Your withdrawal is still being processed. Please check your balance \u2014 it should update shortly.`,
|
|
2356
|
-
onDone: handleDone
|
|
2357
|
-
}
|
|
2358
|
-
);
|
|
2359
|
-
}
|
|
2360
|
-
if (isPending && !cancelled) {
|
|
2361
|
-
const canCancel = currentStep === "idle" || currentStep === "switching-chain" || currentStep === "signing";
|
|
2362
|
-
return /* @__PURE__ */ jsx(
|
|
2363
|
-
TransactionProgressView,
|
|
2364
|
-
{
|
|
2365
|
-
title: "Withdrawing...",
|
|
2366
|
-
steps: withdrawSteps,
|
|
2367
|
-
onCancel: canCancel ? handleCancel : void 0
|
|
2368
|
-
}
|
|
2369
|
-
);
|
|
2370
|
-
}
|
|
2371
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-6", children: [
|
|
2372
|
-
/* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-3", children: [
|
|
2373
|
-
/* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Token" }),
|
|
2374
|
-
/* @__PURE__ */ jsxs(
|
|
2375
|
-
"button",
|
|
2376
|
-
{
|
|
2377
|
-
onClick: onTokenSelect,
|
|
2378
|
-
className: "border-border bg-input flex w-full cursor-pointer items-center gap-3 rounded-lg border p-3",
|
|
2379
|
-
children: [
|
|
2380
|
-
/* @__PURE__ */ jsx("div", { className: "h-8 w-8 shrink-0 overflow-hidden rounded-full", children: getTokenIcon(selectedToken.symbol, 32) }),
|
|
2381
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-start gap-1", children: [
|
|
2382
|
-
/* @__PURE__ */ jsx("span", { className: "text-foreground text-sm font-medium", children: selectedToken.symbol }),
|
|
2383
|
-
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
2384
|
-
"on ",
|
|
2385
|
-
targetChain?.name ?? "Base Sepolia"
|
|
2386
|
-
] })
|
|
2387
|
-
] }),
|
|
2388
|
-
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-5 w-5 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
|
|
2389
|
-
]
|
|
2390
|
-
}
|
|
2391
|
-
)
|
|
2392
|
-
] }),
|
|
2393
|
-
/* @__PURE__ */ jsxs("div", { className: "flex w-full flex-col gap-3", children: [
|
|
2394
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
2395
|
-
/* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Amount" }),
|
|
2396
|
-
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-sm", children: [
|
|
2397
|
-
formattedBalance,
|
|
2398
|
-
" ",
|
|
2399
|
-
selectedToken.symbol
|
|
2400
|
-
] })
|
|
2401
|
-
] }),
|
|
2402
|
-
/* @__PURE__ */ jsxs("div", { className: "border-border bg-input flex items-center gap-2 rounded-[10px] border py-1 pr-1 pl-3", children: [
|
|
2403
|
-
/* @__PURE__ */ jsx(
|
|
2404
|
-
"input",
|
|
2405
|
-
{
|
|
2406
|
-
type: "text",
|
|
2407
|
-
inputMode: "decimal",
|
|
2408
|
-
placeholder: "Enter Amount",
|
|
2409
|
-
value: amount,
|
|
2410
|
-
onChange: (e) => {
|
|
2411
|
-
const value = e.target.value.replace(/[^0-9.,]/g, "").replace(/,/g, ".");
|
|
2412
|
-
if (value.split(".").length <= 2) {
|
|
2413
|
-
setAmount(value);
|
|
2414
|
-
}
|
|
2415
|
-
},
|
|
2416
|
-
className: cn(
|
|
2417
|
-
"text-foreground flex-1 bg-transparent text-sm outline-none",
|
|
2418
|
-
"placeholder:text-muted-foreground/50"
|
|
2419
|
-
)
|
|
2420
|
-
}
|
|
2421
|
-
),
|
|
2422
|
-
/* @__PURE__ */ jsx(
|
|
2423
|
-
"button",
|
|
2424
|
-
{
|
|
2425
|
-
onClick: handleMaxClick,
|
|
2426
|
-
className: "bg-secondary text-foreground hover:bg-secondary/80 cursor-pointer rounded px-3 py-2.5 text-xs font-semibold transition-colors",
|
|
2427
|
-
children: "MAX"
|
|
2428
|
-
}
|
|
2429
|
-
)
|
|
2430
|
-
] }),
|
|
2431
|
-
tooManyDecimals && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
2432
|
-
"Too many decimal places (max: ",
|
|
2433
|
-
selectedToken.decimals,
|
|
2434
|
-
")"
|
|
2435
|
-
] }),
|
|
2436
|
-
exceedsBalance && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Insufficient balance" })
|
|
2437
|
-
] }),
|
|
2438
|
-
/* @__PURE__ */ jsx(
|
|
2439
|
-
"button",
|
|
2440
|
-
{
|
|
2441
|
-
onClick: handleWithdraw,
|
|
2442
|
-
disabled: !isConnected || !hasValidAmount || tooManyDecimals || !!exceedsBalance || isPending,
|
|
2443
|
-
className: cn(
|
|
2444
|
-
"flex h-10 w-full cursor-pointer items-center justify-center rounded-[10px] px-3 py-2 text-sm font-medium transition-colors",
|
|
2445
|
-
"bg-primary text-primary-foreground hover:bg-primary/90",
|
|
2446
|
-
"disabled:cursor-not-allowed disabled:opacity-50"
|
|
2447
|
-
),
|
|
2448
|
-
children: getButtonText()
|
|
2449
|
-
}
|
|
2450
|
-
)
|
|
2451
|
-
] });
|
|
2452
|
-
}
|
|
2453
|
-
function BalanceCards({
|
|
2454
|
-
selectedToken,
|
|
2455
|
-
onLockedFundsClick,
|
|
2456
|
-
onBalanceClick,
|
|
2457
|
-
showLockedFunds = true,
|
|
2458
|
-
disabled
|
|
2459
|
-
}) {
|
|
2460
|
-
const { balanceWei, isLoading: balanceLoading } = useBalance({
|
|
2461
|
-
tokenId: selectedToken.id
|
|
2462
|
-
});
|
|
2463
|
-
const { totalLocked, isLoading: lockedLoading } = useLockedFunds({ enabled: showLockedFunds });
|
|
2464
|
-
const formattedBalance = formatTokenAmount(balanceWei, selectedToken.decimals);
|
|
2465
|
-
const formattedLocked = showLockedFunds ? formatTokenAmount(String(totalLocked), selectedToken.decimals) : "0.00";
|
|
2466
|
-
return /* @__PURE__ */ jsxs("div", { className: cn("flex gap-2", disabled && "opacity-50"), children: [
|
|
2467
|
-
/* @__PURE__ */ jsxs(
|
|
2468
|
-
"button",
|
|
2469
|
-
{
|
|
2470
|
-
onClick: onBalanceClick,
|
|
2471
|
-
disabled,
|
|
2472
|
-
className: cn(
|
|
2473
|
-
"bg-muted flex flex-1 flex-col gap-2 rounded-[10px] p-5 text-left transition-colors",
|
|
2474
|
-
disabled ? "cursor-not-allowed" : "hover:bg-muted/80 cursor-pointer"
|
|
2475
|
-
),
|
|
2476
|
-
children: [
|
|
2477
|
-
/* @__PURE__ */ jsxs("div", { className: "flex w-full items-center justify-between", children: [
|
|
2478
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
2479
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Balance" }),
|
|
2480
|
-
/* @__PURE__ */ jsx("span", { className: "bg-secondary text-muted-foreground rounded-full px-2 py-[5px] text-[10px] font-bold", children: selectedToken.symbol })
|
|
2481
|
-
] }),
|
|
2482
|
-
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-6 w-6 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
|
|
2483
|
-
] }),
|
|
2484
|
-
/* @__PURE__ */ jsx("div", { className: "text-foreground text-xl font-medium", children: balanceLoading ? /* @__PURE__ */ jsx("span", { className: "bg-secondary inline-block h-6 w-24 animate-pulse rounded" }) : formattedBalance })
|
|
2485
|
-
]
|
|
2486
|
-
}
|
|
2487
|
-
),
|
|
2488
|
-
showLockedFunds && /* @__PURE__ */ jsxs(
|
|
2489
|
-
"button",
|
|
2490
|
-
{
|
|
2491
|
-
onClick: onLockedFundsClick,
|
|
2492
|
-
disabled,
|
|
2493
|
-
className: cn(
|
|
2494
|
-
"bg-muted flex flex-1 flex-col gap-2 rounded-[10px] p-5 text-left transition-colors",
|
|
2495
|
-
disabled ? "cursor-not-allowed" : "hover:bg-muted/80 cursor-pointer"
|
|
2496
|
-
),
|
|
2497
|
-
children: [
|
|
2498
|
-
/* @__PURE__ */ jsxs("div", { className: "flex w-full items-center justify-between", children: [
|
|
2499
|
-
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
2500
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Locked Funds" }),
|
|
2501
|
-
/* @__PURE__ */ jsx("span", { className: "bg-secondary text-muted-foreground rounded-full px-2 py-[5px] text-[10px] font-bold", children: selectedToken.symbol })
|
|
2502
|
-
] }),
|
|
2503
|
-
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-6 w-6 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
|
|
2504
|
-
] }),
|
|
2505
|
-
/* @__PURE__ */ jsx("div", { className: "text-foreground text-xl font-medium", children: lockedLoading ? /* @__PURE__ */ jsx("span", { className: "bg-secondary inline-block h-6 w-20 animate-pulse rounded" }) : formattedLocked })
|
|
2506
|
-
]
|
|
2507
|
-
}
|
|
2508
|
-
)
|
|
2509
|
-
] });
|
|
2510
|
-
}
|
|
2511
|
-
function Tabs({
|
|
2512
|
-
activeTab,
|
|
2513
|
-
onTabChange,
|
|
2514
|
-
disabled
|
|
2515
|
-
}) {
|
|
2516
|
-
return /* @__PURE__ */ jsxs(
|
|
2517
|
-
"div",
|
|
2518
|
-
{
|
|
2519
|
-
className: cn(
|
|
2520
|
-
"bg-muted relative flex gap-2 overflow-hidden rounded-[10px] p-1",
|
|
2521
|
-
disabled && "opacity-50"
|
|
2522
|
-
),
|
|
2523
|
-
children: [
|
|
2524
|
-
/* @__PURE__ */ jsx(
|
|
2525
|
-
"div",
|
|
2526
|
-
{
|
|
2527
|
-
className: cn(
|
|
2528
|
-
"bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-8px)] rounded-md transition-transform duration-200",
|
|
2529
|
-
activeTab === "withdraw" && "translate-x-[calc(100%+8px)]"
|
|
2530
|
-
)
|
|
2531
|
-
}
|
|
2532
|
-
),
|
|
2533
|
-
/* @__PURE__ */ jsx(
|
|
2534
|
-
"button",
|
|
2535
|
-
{
|
|
2536
|
-
onClick: () => !disabled && onTabChange("deposit"),
|
|
2537
|
-
disabled,
|
|
2538
|
-
className: cn(
|
|
2539
|
-
"relative z-10 flex-1 rounded-md px-3 py-[9px] text-sm transition-colors",
|
|
2540
|
-
activeTab === "deposit" ? "text-foreground" : "text-muted-foreground",
|
|
2541
|
-
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
|
2542
|
-
),
|
|
2543
|
-
children: "Deposit"
|
|
2544
|
-
}
|
|
2545
|
-
),
|
|
2546
|
-
/* @__PURE__ */ jsx(
|
|
2547
|
-
"button",
|
|
2548
|
-
{
|
|
2549
|
-
onClick: () => !disabled && onTabChange("withdraw"),
|
|
2550
|
-
disabled,
|
|
2551
|
-
className: cn(
|
|
2552
|
-
"relative z-10 flex-1 rounded-md px-3 py-[9px] text-sm transition-colors",
|
|
2553
|
-
activeTab === "withdraw" ? "text-foreground" : "text-muted-foreground",
|
|
2554
|
-
disabled ? "cursor-not-allowed" : "cursor-pointer"
|
|
2555
|
-
),
|
|
2556
|
-
children: "Withdraw"
|
|
2557
|
-
}
|
|
2558
|
-
)
|
|
2559
|
-
]
|
|
2560
|
-
}
|
|
2561
|
-
);
|
|
2562
|
-
}
|
|
2563
|
-
function LockedFundsView({ onBack }) {
|
|
2564
|
-
const { getTokenById } = usePrivanaContext();
|
|
2565
|
-
const { locks, isLoading } = useLockedFunds();
|
|
2566
|
-
const { unlockFunds, unlockAllExpired, isPending } = useUnlockFunds();
|
|
2567
|
-
const [collapsedSections, setCollapsedSections] = useState({});
|
|
2568
|
-
const sections = useMemo(() => {
|
|
2569
|
-
const sectionMap = {};
|
|
2570
|
-
locks.forEach((lock) => {
|
|
2571
|
-
const serviceName = shortenAddress(lock.service_address);
|
|
2572
|
-
if (!sectionMap[lock.service_address]) {
|
|
2573
|
-
sectionMap[lock.service_address] = {
|
|
2574
|
-
title: `Service ${serviceName}`,
|
|
2575
|
-
items: []
|
|
2576
|
-
};
|
|
2577
|
-
}
|
|
2578
|
-
sectionMap[lock.service_address].items.push({
|
|
2579
|
-
lockId: lock.lock_id,
|
|
2580
|
-
amount: formatTokenAmount(String(lock.amount), getTokenById(lock.token_id)?.decimals ?? 18),
|
|
2581
|
-
serviceAddress: lock.service_address,
|
|
2582
|
-
time: lock.is_expired ? "Click to unlock" : formatTimeRemaining(lock.expiry),
|
|
2583
|
-
isExpired: lock.is_expired
|
|
2584
|
-
});
|
|
2585
|
-
});
|
|
2586
|
-
return Object.values(sectionMap);
|
|
2587
|
-
}, [getTokenById, locks]);
|
|
2588
|
-
const toggleSection = (title) => {
|
|
2589
|
-
setCollapsedSections((prev) => ({
|
|
2590
|
-
...prev,
|
|
2591
|
-
[title]: !prev[title]
|
|
2592
|
-
}));
|
|
2593
|
-
};
|
|
2594
|
-
const expiredCount = locks.filter((l) => l.is_expired).length;
|
|
2595
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2596
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2597
|
-
/* @__PURE__ */ jsx(
|
|
2598
|
-
"button",
|
|
2599
|
-
{
|
|
2600
|
-
onClick: onBack,
|
|
2601
|
-
className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
2602
|
-
children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
|
|
2603
|
-
}
|
|
2604
|
-
),
|
|
2605
|
-
/* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Locked Funds" })
|
|
2606
|
-
] }) }),
|
|
2607
|
-
/* @__PURE__ */ jsxs("div", { className: "bg-muted flex min-h-0 flex-1 flex-col rounded-[10px] p-2", children: [
|
|
2608
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto", children: isLoading ? /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-2 p-3", children: [1, 2].map((i) => /* @__PURE__ */ jsxs("div", { className: "flex animate-pulse items-center gap-3 rounded-lg p-3", children: [
|
|
2609
|
-
/* @__PURE__ */ jsx("div", { className: "bg-secondary h-10 w-10 rounded-full" }),
|
|
2610
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
|
|
2611
|
-
/* @__PURE__ */ jsx("div", { className: "bg-secondary mb-2 h-3.5 w-24 rounded" }),
|
|
2612
|
-
/* @__PURE__ */ jsx("div", { className: "bg-secondary h-3 w-32 rounded" })
|
|
2613
|
-
] })
|
|
2614
|
-
] }, i)) }) : sections.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center p-8", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "No locked funds" }) }) : sections.map((section) => /* @__PURE__ */ jsxs("div", { children: [
|
|
2615
|
-
/* @__PURE__ */ jsxs(
|
|
2616
|
-
"button",
|
|
2617
|
-
{
|
|
2618
|
-
onClick: () => toggleSection(section.title),
|
|
2619
|
-
className: "text-muted-foreground hover:bg-secondary flex w-full cursor-pointer items-center justify-between rounded-lg px-4 py-4 transition-colors",
|
|
2620
|
-
children: [
|
|
2621
|
-
/* @__PURE__ */ jsx("span", { className: "text-sm", children: section.title }),
|
|
2622
|
-
/* @__PURE__ */ jsx(
|
|
2623
|
-
ChevronDownIcon,
|
|
2624
|
-
{
|
|
2625
|
-
direction: collapsedSections[section.title] ? "right" : "down"
|
|
2626
|
-
}
|
|
2627
|
-
)
|
|
2628
|
-
]
|
|
2629
|
-
}
|
|
2630
|
-
),
|
|
2631
|
-
!collapsedSections[section.title] && section.items.map((item) => /* @__PURE__ */ jsxs(
|
|
2632
|
-
"div",
|
|
2633
|
-
{
|
|
2634
|
-
className: cn(
|
|
2635
|
-
"flex items-center justify-between gap-3 rounded-lg p-3",
|
|
2636
|
-
item.isExpired && "bg-secondary"
|
|
2637
|
-
),
|
|
2638
|
-
children: [
|
|
2639
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-center gap-3", children: [
|
|
2640
|
-
/* @__PURE__ */ jsx("div", { className: "bg-secondary h-10 w-10 rounded-full" }),
|
|
2641
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
2642
|
-
/* @__PURE__ */ jsxs("span", { className: "text-foreground text-sm font-medium", children: [
|
|
2643
|
-
item.amount,
|
|
2644
|
-
" USDC"
|
|
2645
|
-
] }),
|
|
2646
|
-
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
2647
|
-
"Service: ",
|
|
2648
|
-
shortenAddress(item.serviceAddress)
|
|
2649
|
-
] })
|
|
2650
|
-
] })
|
|
2651
|
-
] }),
|
|
2652
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-end gap-2", children: [
|
|
2653
|
-
item.isExpired ? /* @__PURE__ */ jsx(
|
|
2654
|
-
"button",
|
|
2655
|
-
{
|
|
2656
|
-
onClick: () => unlockFunds({ lockId: Number(item.lockId) }),
|
|
2657
|
-
disabled: isPending,
|
|
2658
|
-
className: "text-foreground hover:text-foreground/80 cursor-pointer text-sm transition-colors disabled:opacity-50",
|
|
2659
|
-
children: "Click to unlock"
|
|
2660
|
-
}
|
|
2661
|
-
) : /* @__PURE__ */ jsx("span", { className: "text-sm text-amber-500", children: item.time }),
|
|
2662
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: "on Base Sepolia" })
|
|
2663
|
-
] })
|
|
2664
|
-
]
|
|
2665
|
-
},
|
|
2666
|
-
`${item.serviceAddress}-${item.lockId}`
|
|
2667
|
-
))
|
|
2668
|
-
] }, section.title)) }),
|
|
2669
|
-
expiredCount > 0 && /* @__PURE__ */ jsx("div", { className: "p-3", children: /* @__PURE__ */ jsxs(
|
|
2670
|
-
"button",
|
|
2671
|
-
{
|
|
2672
|
-
onClick: () => unlockAllExpired(),
|
|
2673
|
-
disabled: isPending,
|
|
2674
|
-
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 disabled:opacity-50",
|
|
2675
|
-
children: [
|
|
2676
|
-
"Unlock All (",
|
|
2677
|
-
expiredCount,
|
|
2678
|
-
")"
|
|
2679
|
-
]
|
|
2680
|
-
}
|
|
2681
|
-
) })
|
|
2682
|
-
] })
|
|
2683
|
-
] });
|
|
2684
|
-
}
|
|
2685
|
-
function BalanceTokenRow({ token }) {
|
|
2686
|
-
const { balanceWei, isLoading } = useBalance({
|
|
2687
|
-
tokenId: token.id
|
|
2688
|
-
});
|
|
2689
|
-
const formattedBalance = formatTokenAmount(balanceWei, token.decimals);
|
|
2690
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-2 rounded-lg px-3 py-2.5", children: [
|
|
2691
|
-
/* @__PURE__ */ jsx("div", { className: "h-[18px] w-[18px] overflow-hidden rounded-full", children: getTokenIcon(token.symbol, 18) }),
|
|
2692
|
-
/* @__PURE__ */ jsx("span", { className: "text-foreground flex-1 text-sm", children: token.symbol }),
|
|
2693
|
-
isLoading ? /* @__PURE__ */ jsx("span", { className: "bg-secondary h-4 w-16 animate-pulse rounded" }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: formattedBalance })
|
|
2694
|
-
] });
|
|
2695
|
-
}
|
|
2696
|
-
function BalanceDetailsView({ onBack }) {
|
|
2697
|
-
const { enabledTokens, chains } = usePrivanaContext();
|
|
2698
|
-
const [selectedChainId, setSelectedChainId] = useState(chains[0]?.id ?? 84532);
|
|
2699
|
-
const chainTokens = useMemo(() => {
|
|
2700
|
-
return enabledTokens.filter((t) => t.chainId === selectedChainId);
|
|
2701
|
-
}, [enabledTokens, selectedChainId]);
|
|
2702
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2703
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2704
|
-
/* @__PURE__ */ jsx(
|
|
2705
|
-
"button",
|
|
2706
|
-
{
|
|
2707
|
-
onClick: onBack,
|
|
2708
|
-
className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
2709
|
-
children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
|
|
2710
|
-
}
|
|
2711
|
-
),
|
|
2712
|
-
/* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Token Balances" })
|
|
2713
|
-
] }) }),
|
|
2714
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
|
|
2715
|
-
/* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
|
|
2716
|
-
/* @__PURE__ */ jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Network" }) }),
|
|
2717
|
-
/* @__PURE__ */ jsx("div", { className: "mt-1 flex-1 overflow-y-auto", children: chains.map((chain) => {
|
|
2718
|
-
const isSelected = selectedChainId === chain.id;
|
|
2719
|
-
return /* @__PURE__ */ jsxs(
|
|
2720
|
-
"button",
|
|
2721
|
-
{
|
|
2722
|
-
onClick: () => setSelectedChainId(chain.id),
|
|
2723
|
-
className: cn(
|
|
2724
|
-
"hover:bg-secondary flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2.5 text-left transition-colors",
|
|
2725
|
-
isSelected && "bg-secondary"
|
|
2726
|
-
),
|
|
2727
|
-
children: [
|
|
2728
|
-
/* @__PURE__ */ jsx("div", { className: "h-[18px] w-[18px] overflow-hidden rounded-full", children: getChainIcon(chain.id, 18) }),
|
|
2729
|
-
/* @__PURE__ */ jsx("span", { className: "text-foreground flex-1 text-sm", children: chain.name })
|
|
2730
|
-
]
|
|
2731
|
-
},
|
|
2732
|
-
chain.id
|
|
2733
|
-
);
|
|
2734
|
-
}) })
|
|
2735
|
-
] }),
|
|
2736
|
-
/* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-[2] flex-col overflow-hidden rounded-[10px] p-2", children: [
|
|
2737
|
-
/* @__PURE__ */ jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Token Balance" }) }),
|
|
2738
|
-
/* @__PURE__ */ jsx("div", { className: "mt-2 flex-1 overflow-y-auto", children: chainTokens.map((token) => /* @__PURE__ */ jsx(BalanceTokenRow, { token }, token.id)) })
|
|
2739
|
-
] })
|
|
2740
|
-
] })
|
|
2741
|
-
] });
|
|
2742
|
-
}
|
|
2743
|
-
function TokenRow({
|
|
2744
|
-
token,
|
|
2745
|
-
isSelected,
|
|
2746
|
-
onClick
|
|
2747
|
-
}) {
|
|
2748
|
-
const { address } = useAccount();
|
|
2749
|
-
const isNative = token.contract === zeroAddress;
|
|
2750
|
-
const { data: nativeBalanceData } = useBalance$1({
|
|
2751
|
-
address,
|
|
2752
|
-
chainId: token.chainId,
|
|
2753
|
-
query: { enabled: !!address && isNative }
|
|
2754
|
-
});
|
|
2755
|
-
const { data: erc20Balance } = useReadContract({
|
|
2756
|
-
address: token.contract,
|
|
2757
|
-
abi: erc20Abi,
|
|
2758
|
-
functionName: "balanceOf",
|
|
2759
|
-
args: address ? [address] : void 0,
|
|
2760
|
-
chainId: token.chainId,
|
|
2761
|
-
query: { enabled: !!address && !isNative }
|
|
2762
|
-
});
|
|
2763
|
-
const walletBalance = isNative ? nativeBalanceData?.value : erc20Balance;
|
|
2764
|
-
const formattedBalance = walletBalance ? formatTokenAmount(walletBalance.toString(), token.decimals) : "0.00";
|
|
2765
|
-
return /* @__PURE__ */ jsxs(
|
|
2766
|
-
"button",
|
|
2767
|
-
{
|
|
2768
|
-
onClick,
|
|
2769
|
-
className: cn(
|
|
2770
|
-
"hover:bg-secondary flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2.5 text-left transition-colors",
|
|
2771
|
-
isSelected && "bg-secondary"
|
|
2772
|
-
),
|
|
2773
|
-
children: [
|
|
2774
|
-
/* @__PURE__ */ jsx("div", { className: "h-[18px] w-[18px] overflow-hidden rounded-full", children: getTokenIcon(token.symbol, 18) }),
|
|
2775
|
-
/* @__PURE__ */ jsx("span", { className: "text-foreground flex-1 text-sm", children: token.symbol }),
|
|
2776
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: formattedBalance })
|
|
2777
|
-
]
|
|
2778
|
-
}
|
|
2779
|
-
);
|
|
2780
|
-
}
|
|
2781
|
-
function TokenSelectorView({
|
|
2782
|
-
onBack,
|
|
2783
|
-
onSelect,
|
|
2784
|
-
selectedTokenId
|
|
2785
|
-
}) {
|
|
2786
|
-
const { enabledTokens, chains } = usePrivanaContext();
|
|
2787
|
-
const [selectedChainId, setSelectedChainId] = useState(chains[0]?.id ?? 84532);
|
|
2788
|
-
const chainTokens = useMemo(() => {
|
|
2789
|
-
return enabledTokens.filter((t) => t.chainId === selectedChainId);
|
|
2790
|
-
}, [enabledTokens, selectedChainId]);
|
|
2791
|
-
const handleTokenSelect = (token) => {
|
|
2792
|
-
onSelect(token);
|
|
2793
|
-
onBack();
|
|
2794
|
-
};
|
|
2795
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2796
|
-
/* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-5 py-4", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
2797
|
-
/* @__PURE__ */ jsx(
|
|
2798
|
-
"button",
|
|
2799
|
-
{
|
|
2800
|
-
onClick: onBack,
|
|
2801
|
-
className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
2802
|
-
children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
|
|
2803
|
-
}
|
|
2804
|
-
),
|
|
2805
|
-
/* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Select Token" })
|
|
2806
|
-
] }) }),
|
|
2807
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 gap-2", children: [
|
|
2808
|
-
/* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-1 flex-col overflow-hidden rounded-[10px] p-2", children: [
|
|
2809
|
-
/* @__PURE__ */ jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Network" }) }),
|
|
2810
|
-
/* @__PURE__ */ jsx("div", { className: "mt-1 flex-1 overflow-y-auto", children: chains.map((chain) => {
|
|
2811
|
-
const isSelected = selectedChainId === chain.id;
|
|
2812
|
-
return /* @__PURE__ */ jsxs(
|
|
2813
|
-
"button",
|
|
2814
|
-
{
|
|
2815
|
-
onClick: () => setSelectedChainId(chain.id),
|
|
2816
|
-
className: cn(
|
|
2817
|
-
"hover:bg-secondary flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2.5 text-left transition-colors",
|
|
2818
|
-
isSelected && "bg-secondary"
|
|
2819
|
-
),
|
|
2820
|
-
children: [
|
|
2821
|
-
/* @__PURE__ */ jsx("div", { className: "h-[18px] w-[18px] overflow-hidden rounded-full", children: getChainIcon(chain.id, 18) }),
|
|
2822
|
-
/* @__PURE__ */ jsx("span", { className: "text-foreground flex-1 text-sm", children: chain.name })
|
|
2823
|
-
]
|
|
2824
|
-
},
|
|
2825
|
-
chain.id
|
|
2826
|
-
);
|
|
2827
|
-
}) })
|
|
2828
|
-
] }),
|
|
2829
|
-
/* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-[2] flex-col overflow-hidden rounded-[10px] p-2", children: [
|
|
2830
|
-
/* @__PURE__ */ jsx("div", { className: "px-4 pt-4 pb-2", children: /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Token" }) }),
|
|
2831
|
-
/* @__PURE__ */ jsx("div", { className: "mt-2 flex-1 overflow-y-auto", children: chainTokens.map((token) => /* @__PURE__ */ jsx(
|
|
2832
|
-
TokenRow,
|
|
2833
|
-
{
|
|
2834
|
-
token,
|
|
2835
|
-
isSelected: selectedTokenId === token.id,
|
|
2836
|
-
onClick: () => handleTokenSelect(token)
|
|
2837
|
-
},
|
|
2838
|
-
token.id
|
|
2839
|
-
)) })
|
|
2840
|
-
] })
|
|
2841
|
-
] })
|
|
2842
|
-
] });
|
|
2843
|
-
}
|
|
2844
|
-
function ModalBody({
|
|
2845
|
-
onViewChange,
|
|
2846
|
-
onCloseBlockedChange,
|
|
2847
|
-
showLockedFunds = true,
|
|
2848
|
-
defaultTab = "deposit",
|
|
2849
|
-
onDepositSuccess
|
|
2850
|
-
}) {
|
|
2851
|
-
const { defaultToken, tokensStatus } = usePrivanaContext();
|
|
2852
|
-
const [selectedToken, setSelectedToken] = useState(defaultToken);
|
|
2853
|
-
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
2854
|
-
const [currentView, setCurrentView] = useState("main");
|
|
2855
|
-
const [isInteractionPending, setIsInteractionPending] = useState(false);
|
|
2856
|
-
useEffect(() => {
|
|
2857
|
-
if (!selectedToken && defaultToken) {
|
|
2858
|
-
setSelectedToken(defaultToken);
|
|
2859
|
-
}
|
|
2860
|
-
}, [selectedToken, defaultToken]);
|
|
2861
|
-
const handlePendingChange = (isPending) => {
|
|
2862
|
-
setIsInteractionPending(isPending);
|
|
2863
|
-
};
|
|
2864
|
-
const handleCloseBlockedChange = (isBlocked) => {
|
|
2865
|
-
onCloseBlockedChange?.(isBlocked);
|
|
2866
|
-
};
|
|
2867
|
-
const handleViewChange = (view) => {
|
|
2868
|
-
const update = () => {
|
|
2869
|
-
setCurrentView(view);
|
|
2870
|
-
onViewChange?.(view);
|
|
2871
|
-
};
|
|
2872
|
-
if (typeof document !== "undefined" && typeof document.startViewTransition === "function") {
|
|
2873
|
-
document.documentElement.dataset.privanaVtDir = view === "main" ? "back" : "forward";
|
|
2874
|
-
const transition = document.startViewTransition(update);
|
|
2875
|
-
transition.finished.then(() => {
|
|
2876
|
-
delete document.documentElement.dataset.privanaVtDir;
|
|
2877
|
-
});
|
|
2878
|
-
} else {
|
|
2879
|
-
update();
|
|
2880
|
-
}
|
|
2881
|
-
};
|
|
2882
|
-
const handleTokenSelect = (token) => {
|
|
2883
|
-
setSelectedToken(token);
|
|
2884
|
-
};
|
|
2885
|
-
if (tokensStatus === "loading" || !selectedToken) {
|
|
2886
|
-
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
2887
|
-
/* @__PURE__ */ jsx("div", { className: "bg-secondary h-25 animate-pulse rounded-[10px]" }),
|
|
2888
|
-
/* @__PURE__ */ jsx("div", { className: "bg-secondary h-11 animate-pulse rounded-[10px]" }),
|
|
2889
|
-
/* @__PURE__ */ jsx("div", { className: "bg-secondary h-50 animate-pulse rounded-[10px]" })
|
|
2890
|
-
] });
|
|
2891
|
-
}
|
|
2892
|
-
if (currentView === "locked-funds") {
|
|
2893
|
-
return /* @__PURE__ */ jsx(LockedFundsView, { onBack: () => handleViewChange("main") });
|
|
2894
|
-
}
|
|
2895
|
-
if (currentView === "balance-details") {
|
|
2896
|
-
return /* @__PURE__ */ jsx(BalanceDetailsView, { onBack: () => handleViewChange("main") });
|
|
2897
|
-
}
|
|
2898
|
-
if (currentView === "select-token") {
|
|
2899
|
-
return /* @__PURE__ */ jsx(
|
|
2900
|
-
TokenSelectorView,
|
|
2901
|
-
{
|
|
2902
|
-
onBack: () => handleViewChange("main"),
|
|
2903
|
-
onSelect: handleTokenSelect,
|
|
2904
|
-
selectedTokenId: selectedToken.id
|
|
2905
|
-
}
|
|
2906
|
-
);
|
|
2907
|
-
}
|
|
2908
|
-
return /* @__PURE__ */ jsx(Fragment, { children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2 pb-4", children: [
|
|
2909
|
-
/* @__PURE__ */ jsx("div", { className: cn(isInteractionPending && "pointer-events-none"), children: /* @__PURE__ */ jsx(
|
|
2910
|
-
BalanceCards,
|
|
2911
|
-
{
|
|
2912
|
-
selectedToken,
|
|
2913
|
-
onLockedFundsClick: () => handleViewChange("locked-funds"),
|
|
2914
|
-
onBalanceClick: () => handleViewChange("balance-details"),
|
|
2915
|
-
showLockedFunds,
|
|
2916
|
-
disabled: isInteractionPending
|
|
2917
|
-
}
|
|
2918
|
-
) }),
|
|
2919
|
-
/* @__PURE__ */ jsx("div", { className: cn(isInteractionPending && "pointer-events-none"), children: /* @__PURE__ */ jsx(Tabs, { activeTab, onTabChange: setActiveTab, disabled: isInteractionPending }) }),
|
|
2920
|
-
/* @__PURE__ */ jsx("div", { className: "bg-muted rounded-[10px] p-5", children: activeTab === "deposit" ? /* @__PURE__ */ jsx(
|
|
2921
|
-
DepositForm,
|
|
2922
|
-
{
|
|
2923
|
-
selectedToken,
|
|
2924
|
-
onTokenSelect: () => handleViewChange("select-token"),
|
|
2925
|
-
onPendingChange: handlePendingChange,
|
|
2926
|
-
onUnsafeToCloseChange: handleCloseBlockedChange,
|
|
2927
|
-
onSuccess: onDepositSuccess
|
|
2928
|
-
}
|
|
2929
|
-
) : /* @__PURE__ */ jsx(
|
|
2930
|
-
WithdrawForm,
|
|
2931
|
-
{
|
|
2932
|
-
selectedToken,
|
|
2933
|
-
onTokenSelect: () => handleViewChange("select-token"),
|
|
2934
|
-
onPendingChange: handlePendingChange,
|
|
2935
|
-
onUnsafeToCloseChange: handleCloseBlockedChange
|
|
2936
|
-
}
|
|
2937
|
-
) })
|
|
2938
|
-
] }) });
|
|
2939
|
-
}
|
|
2940
|
-
function PrivanaModal({
|
|
2941
|
-
open,
|
|
2942
|
-
onClose,
|
|
2943
|
-
showLockedFunds,
|
|
2944
|
-
defaultTab,
|
|
2945
|
-
onDepositSuccess
|
|
2946
|
-
}) {
|
|
2947
|
-
const [currentView, setCurrentView] = useState("main");
|
|
2948
|
-
const titleId = useId();
|
|
2949
|
-
const descId = useId();
|
|
2950
|
-
const [isCloseBlocked, setIsCloseBlocked] = useState(false);
|
|
2951
|
-
const handleClose = () => {
|
|
2952
|
-
if (!isCloseBlocked) onClose();
|
|
2953
|
-
};
|
|
2954
|
-
return /* @__PURE__ */ jsx(
|
|
2955
|
-
Dialog,
|
|
2956
|
-
{
|
|
2957
|
-
open,
|
|
2958
|
-
onOpenChange: (isOpen) => {
|
|
2959
|
-
if (!isOpen) handleClose();
|
|
2960
|
-
},
|
|
2961
|
-
children: /* @__PURE__ */ jsxs(
|
|
2962
|
-
DialogContent,
|
|
2963
|
-
{
|
|
2964
|
-
"data-privana": true,
|
|
2965
|
-
"data-view": currentView,
|
|
2966
|
-
showCloseButton: false,
|
|
2967
|
-
onInteractOutside: isCloseBlocked ? (e) => e.preventDefault() : void 0,
|
|
2968
|
-
onEscapeKeyDown: isCloseBlocked ? (e) => e.preventDefault() : void 0,
|
|
2969
|
-
className: "bg-card flex h-[596px] max-h-[95dvh] w-[560px] max-w-[95vw] flex-col gap-2 overflow-hidden rounded-2xl border-0 p-2 pb-[2.75rem]",
|
|
2970
|
-
"aria-labelledby": titleId,
|
|
2971
|
-
"aria-describedby": descId,
|
|
2972
|
-
children: [
|
|
2973
|
-
/* @__PURE__ */ jsx(
|
|
2974
|
-
"button",
|
|
2975
|
-
{
|
|
2976
|
-
"data-privana-close": true,
|
|
2977
|
-
onClick: handleClose,
|
|
2978
|
-
disabled: isCloseBlocked,
|
|
2979
|
-
"aria-label": "Close",
|
|
2980
|
-
className: cn(
|
|
2981
|
-
"absolute top-6 right-5 z-20 flex h-6 w-6 items-center justify-center transition-colors",
|
|
2982
|
-
isCloseBlocked ? "text-muted-foreground/40 cursor-not-allowed" : "text-muted-foreground hover:text-foreground cursor-pointer"
|
|
2983
|
-
),
|
|
2984
|
-
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
2985
|
-
}
|
|
2986
|
-
),
|
|
2987
|
-
/* @__PURE__ */ jsxs(
|
|
2988
|
-
"div",
|
|
2989
|
-
{
|
|
2990
|
-
"data-privana-content": true,
|
|
2991
|
-
"data-view": currentView,
|
|
2992
|
-
className: "flex min-h-0 flex-1 flex-col gap-2",
|
|
2993
|
-
children: [
|
|
2994
|
-
/* @__PURE__ */ jsx(DialogTitle, { id: titleId, className: "sr-only", children: "Privana" }),
|
|
2995
|
-
/* @__PURE__ */ jsx(DialogDescription, { id: descId, className: "sr-only", children: "Deposit or withdraw tokens from your Privana" }),
|
|
2996
|
-
currentView === "main" && /* @__PURE__ */ jsx(DialogHeader, { children: /* @__PURE__ */ jsx("div", { className: "flex items-center px-5 py-4", children: /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Privana" }) }) }),
|
|
2997
|
-
/* @__PURE__ */ jsx(
|
|
2998
|
-
ModalBody,
|
|
2999
|
-
{
|
|
3000
|
-
onCloseBlockedChange: setIsCloseBlocked,
|
|
3001
|
-
onViewChange: setCurrentView,
|
|
3002
|
-
showLockedFunds,
|
|
3003
|
-
defaultTab,
|
|
3004
|
-
onDepositSuccess
|
|
3005
|
-
}
|
|
3006
|
-
)
|
|
3007
|
-
]
|
|
3008
|
-
}
|
|
3009
|
-
),
|
|
3010
|
-
/* @__PURE__ */ jsx(
|
|
3011
|
-
"a",
|
|
3012
|
-
{
|
|
3013
|
-
"data-privana-footer": true,
|
|
3014
|
-
href: "https://privana.finance",
|
|
3015
|
-
target: "_blank",
|
|
3016
|
-
rel: "noopener noreferrer",
|
|
3017
|
-
"aria-label": "Powered by Privana"
|
|
3018
|
-
}
|
|
3019
|
-
)
|
|
3020
|
-
]
|
|
3021
|
-
}
|
|
3022
|
-
)
|
|
3023
|
-
}
|
|
3024
|
-
);
|
|
2735
|
+
) });
|
|
3025
2736
|
}
|
|
3026
|
-
function
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
defaultTab,
|
|
3030
|
-
onDepositSuccess
|
|
2737
|
+
function ChevronDownIcon({
|
|
2738
|
+
direction = "down",
|
|
2739
|
+
className
|
|
3031
2740
|
}) {
|
|
3032
2741
|
return /* @__PURE__ */ jsx(
|
|
3033
|
-
"
|
|
2742
|
+
"svg",
|
|
3034
2743
|
{
|
|
3035
|
-
"
|
|
2744
|
+
width: "16",
|
|
2745
|
+
height: "16",
|
|
2746
|
+
viewBox: "0 0 16 16",
|
|
2747
|
+
fill: "none",
|
|
3036
2748
|
className: cn(
|
|
3037
|
-
"
|
|
2749
|
+
"transition-transform",
|
|
2750
|
+
direction === "right" && "-rotate-90",
|
|
2751
|
+
direction === "up" && "rotate-180",
|
|
3038
2752
|
className
|
|
3039
2753
|
),
|
|
3040
2754
|
children: /* @__PURE__ */ jsx(
|
|
3041
|
-
|
|
2755
|
+
"path",
|
|
3042
2756
|
{
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
2757
|
+
d: "M4 6l4 4 4-4",
|
|
2758
|
+
stroke: "currentColor",
|
|
2759
|
+
strokeWidth: "1.5",
|
|
2760
|
+
strokeLinecap: "round",
|
|
2761
|
+
strokeLinejoin: "round"
|
|
3046
2762
|
}
|
|
3047
2763
|
)
|
|
3048
2764
|
}
|
|
3049
2765
|
);
|
|
3050
2766
|
}
|
|
3051
|
-
function
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
variant = "outline",
|
|
3055
|
-
size = "default",
|
|
3056
|
-
asChild = false,
|
|
3057
|
-
renderButton,
|
|
3058
|
-
hideWhenDisconnected = true,
|
|
3059
|
-
showLockedFunds = true,
|
|
3060
|
-
defaultTab,
|
|
3061
|
-
onDepositSuccess,
|
|
3062
|
-
...buttonProps
|
|
3063
|
-
}) {
|
|
3064
|
-
const [modalOpen, setModalOpen] = useState(false);
|
|
3065
|
-
const { isConnected } = useAccount();
|
|
3066
|
-
if (hideWhenDisconnected && !isConnected) {
|
|
3067
|
-
return null;
|
|
3068
|
-
}
|
|
3069
|
-
const handleClick = () => setModalOpen(true);
|
|
3070
|
-
const buttonElement = renderButton ? renderButton({ onClick: handleClick, isOpen: modalOpen }) : /* @__PURE__ */ jsx(
|
|
3071
|
-
Button,
|
|
2767
|
+
function CopyIcon() {
|
|
2768
|
+
return /* @__PURE__ */ jsxs(
|
|
2769
|
+
"svg",
|
|
3072
2770
|
{
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
2771
|
+
width: "16",
|
|
2772
|
+
height: "16",
|
|
2773
|
+
viewBox: "0 0 24 24",
|
|
2774
|
+
fill: "none",
|
|
2775
|
+
stroke: "currentColor",
|
|
2776
|
+
strokeWidth: "2",
|
|
2777
|
+
strokeLinecap: "round",
|
|
2778
|
+
strokeLinejoin: "round",
|
|
2779
|
+
children: [
|
|
2780
|
+
/* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
|
|
2781
|
+
/* @__PURE__ */ jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
|
|
2782
|
+
]
|
|
3081
2783
|
}
|
|
3082
2784
|
);
|
|
3083
|
-
|
|
3084
|
-
|
|
2785
|
+
}
|
|
2786
|
+
function CircleCheckIcon() {
|
|
2787
|
+
return /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
|
|
2788
|
+
/* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "9", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
2789
|
+
/* @__PURE__ */ jsx(
|
|
2790
|
+
"path",
|
|
2791
|
+
{
|
|
2792
|
+
d: "M6 10l2.5 2.5L14 7.5",
|
|
2793
|
+
stroke: "currentColor",
|
|
2794
|
+
strokeWidth: "1.5",
|
|
2795
|
+
strokeLinecap: "round",
|
|
2796
|
+
strokeLinejoin: "round"
|
|
2797
|
+
}
|
|
2798
|
+
)
|
|
2799
|
+
] });
|
|
2800
|
+
}
|
|
2801
|
+
function CircleXIcon() {
|
|
2802
|
+
return /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
|
|
2803
|
+
/* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "9", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
3085
2804
|
/* @__PURE__ */ jsx(
|
|
3086
|
-
|
|
2805
|
+
"path",
|
|
3087
2806
|
{
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
2807
|
+
d: "M7 7l6 6M13 7l-6 6",
|
|
2808
|
+
stroke: "currentColor",
|
|
2809
|
+
strokeWidth: "1.5",
|
|
2810
|
+
strokeLinecap: "round",
|
|
2811
|
+
strokeLinejoin: "round"
|
|
3093
2812
|
}
|
|
3094
2813
|
)
|
|
3095
2814
|
] });
|
|
3096
2815
|
}
|
|
2816
|
+
function ArrowRightIcon({ className }) {
|
|
2817
|
+
return /* @__PURE__ */ jsx("svg", { width: "24", height: "8", viewBox: "0 0 24 8", fill: "none", className, children: /* @__PURE__ */ jsx(
|
|
2818
|
+
"path",
|
|
2819
|
+
{
|
|
2820
|
+
d: "M0 4h22m0 0l-4-4m4 4l-4 4",
|
|
2821
|
+
stroke: "currentColor",
|
|
2822
|
+
strokeWidth: "1.5",
|
|
2823
|
+
strokeLinecap: "round",
|
|
2824
|
+
strokeLinejoin: "round"
|
|
2825
|
+
}
|
|
2826
|
+
) });
|
|
2827
|
+
}
|
|
3097
2828
|
var DEFAULT_MOONPAY_API_URL = "https://api.moonpay.com/v3";
|
|
3098
2829
|
function useMoonpayLimits({
|
|
3099
2830
|
currencyCode,
|
|
@@ -3120,115 +2851,21 @@ function useMoonpayLimits({
|
|
|
3120
2851
|
);
|
|
3121
2852
|
if (!res.ok) {
|
|
3122
2853
|
throw new Error(`MoonPay limits request failed: ${res.status}`);
|
|
3123
|
-
}
|
|
3124
|
-
const data = await res.json();
|
|
3125
|
-
const asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3126
|
-
return {
|
|
3127
|
-
minBuyAmount: asNumber(data.baseCurrency?.minBuyAmount),
|
|
3128
|
-
maxBuyAmount: asNumber(data.baseCurrency?.maxBuyAmount)
|
|
3129
|
-
};
|
|
3130
|
-
}
|
|
3131
|
-
});
|
|
3132
|
-
return {
|
|
3133
|
-
minBuyAmount: query.data?.minBuyAmount,
|
|
3134
|
-
maxBuyAmount: query.data?.maxBuyAmount,
|
|
3135
|
-
isLoading: query.isLoading,
|
|
3136
|
-
error: query.error ?? null
|
|
3137
|
-
};
|
|
3138
|
-
}
|
|
3139
|
-
function TokenSelectorView2({ selectedTokenId, onSelect }) {
|
|
3140
|
-
const { enabledTokens } = usePrivanaContext();
|
|
3141
|
-
return /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col rounded-[10px] p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex max-h-72 flex-col gap-1 overflow-y-auto", children: [
|
|
3142
|
-
enabledTokens.map((token) => {
|
|
3143
|
-
const isSelected = selectedTokenId === token.id;
|
|
3144
|
-
return /* @__PURE__ */ jsxs(
|
|
3145
|
-
"button",
|
|
3146
|
-
{
|
|
3147
|
-
type: "button",
|
|
3148
|
-
onClick: () => onSelect(token.id),
|
|
3149
|
-
className: cn(
|
|
3150
|
-
"hover:bg-secondary flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-left transition-colors",
|
|
3151
|
-
isSelected && "bg-secondary"
|
|
3152
|
-
),
|
|
3153
|
-
children: [
|
|
3154
|
-
/* @__PURE__ */ jsx("div", { className: "h-6 w-6 shrink-0 overflow-hidden rounded-full", children: getTokenIcon(token.symbol, 24) }),
|
|
3155
|
-
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
3156
|
-
/* @__PURE__ */ jsx("div", { className: "text-foreground text-sm font-medium", children: token.symbol }),
|
|
3157
|
-
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-[11px]", children: token.name })
|
|
3158
|
-
] }),
|
|
3159
|
-
isSelected && /* @__PURE__ */ jsx("div", { className: "bg-primary flex h-4 w-4 shrink-0 items-center justify-center rounded-full", children: /* @__PURE__ */ jsx(
|
|
3160
|
-
"svg",
|
|
3161
|
-
{
|
|
3162
|
-
width: "10",
|
|
3163
|
-
height: "10",
|
|
3164
|
-
viewBox: "0 0 10 10",
|
|
3165
|
-
fill: "none",
|
|
3166
|
-
className: "text-primary-foreground",
|
|
3167
|
-
children: /* @__PURE__ */ jsx(
|
|
3168
|
-
"path",
|
|
3169
|
-
{
|
|
3170
|
-
d: "M2 5L4 7L8 3",
|
|
3171
|
-
stroke: "currentColor",
|
|
3172
|
-
strokeWidth: "1.5",
|
|
3173
|
-
strokeLinecap: "round",
|
|
3174
|
-
strokeLinejoin: "round"
|
|
3175
|
-
}
|
|
3176
|
-
)
|
|
3177
|
-
}
|
|
3178
|
-
) })
|
|
3179
|
-
]
|
|
3180
|
-
},
|
|
3181
|
-
token.id
|
|
3182
|
-
);
|
|
3183
|
-
}),
|
|
3184
|
-
enabledTokens.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground px-3 py-6 text-center text-sm", children: "No tokens available" })
|
|
3185
|
-
] }) });
|
|
3186
|
-
}
|
|
3187
|
-
function CreditCardWidgetView({
|
|
3188
|
-
token,
|
|
3189
|
-
amount,
|
|
3190
|
-
allowance,
|
|
3191
|
-
onCredited,
|
|
3192
|
-
onLockSubmitted,
|
|
3193
|
-
onLockFailed
|
|
3194
|
-
}) {
|
|
3195
|
-
const moonpayCurrencyCode = token?.moonpayCurrencyCode;
|
|
3196
|
-
const containerRef = useRef(null);
|
|
3197
|
-
const [widgetTheme, setWidgetTheme] = useState();
|
|
3198
|
-
useEffect(() => {
|
|
3199
|
-
if (containerRef.current) setWidgetTheme(resolveSdkTheme(containerRef.current));
|
|
3200
|
-
}, []);
|
|
3201
|
-
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
3202
|
-
/* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Complete your purchase" }),
|
|
3203
|
-
token && moonpayCurrencyCode ? /* @__PURE__ */ jsx(
|
|
3204
|
-
FiatOnRampForm,
|
|
3205
|
-
{
|
|
3206
|
-
tokenId: token.id,
|
|
3207
|
-
currencyCode: moonpayCurrencyCode,
|
|
3208
|
-
quoteCurrencyAmount: amount || void 0,
|
|
3209
|
-
lockAmount: true,
|
|
3210
|
-
variant: "embedded",
|
|
3211
|
-
autoStart: true,
|
|
3212
|
-
theme: widgetTheme,
|
|
3213
|
-
postDepositLock: allowance ? {
|
|
3214
|
-
maxAmount: BigInt(allowance.value),
|
|
3215
|
-
lockDuration: allowance.lockDuration
|
|
3216
|
-
} : void 0,
|
|
3217
|
-
onCredited,
|
|
3218
|
-
onLockSubmitted,
|
|
3219
|
-
onLockFailed
|
|
3220
|
-
}
|
|
3221
|
-
) : /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-sm", children: [
|
|
3222
|
-
token?.symbol ?? "This token",
|
|
3223
|
-
" isn\u2019t available for card purchases yet."
|
|
3224
|
-
] })
|
|
3225
|
-
] });
|
|
3226
|
-
}
|
|
3227
|
-
function resolveSdkTheme(el) {
|
|
3228
|
-
const scheme = getComputedStyle(el).colorScheme;
|
|
3229
|
-
if (scheme === "dark") return "dark";
|
|
3230
|
-
if (scheme === "light") return "light";
|
|
3231
|
-
return el.closest(".dark") ? "dark" : "light";
|
|
2854
|
+
}
|
|
2855
|
+
const data = await res.json();
|
|
2856
|
+
const asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
2857
|
+
return {
|
|
2858
|
+
minBuyAmount: asNumber(data.baseCurrency?.minBuyAmount),
|
|
2859
|
+
maxBuyAmount: asNumber(data.baseCurrency?.maxBuyAmount)
|
|
2860
|
+
};
|
|
2861
|
+
}
|
|
2862
|
+
});
|
|
2863
|
+
return {
|
|
2864
|
+
minBuyAmount: query.data?.minBuyAmount,
|
|
2865
|
+
maxBuyAmount: query.data?.maxBuyAmount,
|
|
2866
|
+
isLoading: query.isLoading,
|
|
2867
|
+
error: query.error ?? null
|
|
2868
|
+
};
|
|
3232
2869
|
}
|
|
3233
2870
|
function PrivanaIcon({ className, size = 32 }) {
|
|
3234
2871
|
return /* @__PURE__ */ jsxs(
|
|
@@ -3322,91 +2959,26 @@ function AllowancePolicySection({
|
|
|
3322
2959
|
] })
|
|
3323
2960
|
] });
|
|
3324
2961
|
}
|
|
3325
|
-
function MoonPayGate({ enabled, children }) {
|
|
3326
|
-
const { networkConfig } = usePrivanaContext();
|
|
3327
|
-
const apiKey = networkConfig.moonpayApiKey;
|
|
3328
|
-
if (!enabled || !apiKey) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
3329
|
-
return /* @__PURE__ */ jsx(MoonPayProvider, { apiKey, children });
|
|
3330
|
-
}
|
|
3331
|
-
function MethodTabs({
|
|
3332
|
-
activeTab,
|
|
3333
|
-
onTabChange
|
|
3334
|
-
}) {
|
|
3335
|
-
return /* @__PURE__ */ jsxs("div", { className: "bg-secondary relative flex gap-2 overflow-hidden rounded-[10px] p-1", children: [
|
|
3336
|
-
/* @__PURE__ */ jsx(
|
|
3337
|
-
"div",
|
|
3338
|
-
{
|
|
3339
|
-
className: cn(
|
|
3340
|
-
"bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-8px)] rounded-md transition-transform duration-200",
|
|
3341
|
-
activeTab === "credit-card" && "translate-x-[calc(100%+8px)]"
|
|
3342
|
-
)
|
|
3343
|
-
}
|
|
3344
|
-
),
|
|
3345
|
-
/* @__PURE__ */ jsx(
|
|
3346
|
-
"button",
|
|
3347
|
-
{
|
|
3348
|
-
type: "button",
|
|
3349
|
-
onClick: () => onTabChange("crypto"),
|
|
3350
|
-
className: cn(
|
|
3351
|
-
"relative z-10 flex-1 cursor-pointer rounded-md px-3 py-[9px] text-sm font-medium transition-colors",
|
|
3352
|
-
activeTab === "crypto" ? "text-foreground" : "text-muted-foreground"
|
|
3353
|
-
),
|
|
3354
|
-
children: "Crypto"
|
|
3355
|
-
}
|
|
3356
|
-
),
|
|
3357
|
-
/* @__PURE__ */ jsx(
|
|
3358
|
-
"button",
|
|
3359
|
-
{
|
|
3360
|
-
type: "button",
|
|
3361
|
-
onClick: () => onTabChange("credit-card"),
|
|
3362
|
-
className: cn(
|
|
3363
|
-
"relative z-10 flex-1 cursor-pointer rounded-md px-3 py-[9px] text-sm font-medium transition-colors",
|
|
3364
|
-
activeTab === "credit-card" ? "text-foreground" : "text-muted-foreground"
|
|
3365
|
-
),
|
|
3366
|
-
children: "Credit Card"
|
|
3367
|
-
}
|
|
3368
|
-
)
|
|
3369
|
-
] });
|
|
3370
|
-
}
|
|
3371
|
-
function MethodOption({
|
|
3372
|
-
title,
|
|
3373
|
-
description,
|
|
3374
|
-
onClick
|
|
3375
|
-
}) {
|
|
3376
|
-
return /* @__PURE__ */ jsxs(
|
|
3377
|
-
"button",
|
|
3378
|
-
{
|
|
3379
|
-
type: "button",
|
|
3380
|
-
onClick,
|
|
3381
|
-
className: "bg-input hover:bg-input/70 border-border flex w-full cursor-pointer items-center gap-3 rounded-lg border p-3 text-left transition-colors",
|
|
3382
|
-
children: [
|
|
3383
|
-
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
3384
|
-
/* @__PURE__ */ jsx("span", { className: "text-foreground text-sm leading-[14px] font-medium", children: title }),
|
|
3385
|
-
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs leading-3", children: description })
|
|
3386
|
-
] }),
|
|
3387
|
-
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-5 w-5 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
|
|
3388
|
-
]
|
|
3389
|
-
}
|
|
3390
|
-
);
|
|
3391
|
-
}
|
|
3392
2962
|
function DepositView({
|
|
3393
2963
|
source,
|
|
3394
2964
|
selectedToken,
|
|
3395
2965
|
amount,
|
|
3396
2966
|
allowance,
|
|
2967
|
+
externalMinimum,
|
|
3397
2968
|
onAmountChange,
|
|
3398
2969
|
onSelectToken,
|
|
3399
2970
|
onConnectWallet,
|
|
3400
2971
|
onSubmit,
|
|
3401
2972
|
isSubmitting = false
|
|
3402
2973
|
}) {
|
|
3403
|
-
const { getChainById: getChainById2, chains, serviceName, serviceIcon, networkConfig } = usePrivanaContext();
|
|
2974
|
+
const { getChainById: getChainById2, chains, serviceName, serviceIcon, networkConfig, hostedAuthConfig } = usePrivanaContext();
|
|
3404
2975
|
const { address, isConnected } = useAccount();
|
|
3405
2976
|
const appName = serviceName ?? "Privana";
|
|
3406
2977
|
const chain = selectedToken ? getChainById2(selectedToken.chainId) : void 0;
|
|
3407
2978
|
const targetChain = chain ?? chains[0];
|
|
3408
2979
|
const sourceLabel = source === "connected" ? "Connected Wallet" : "External Wallet";
|
|
3409
2980
|
const isConnectedSource = source === "connected";
|
|
2981
|
+
const isExternal = source === "external";
|
|
3410
2982
|
const isCreditCard = source === "credit-card";
|
|
3411
2983
|
const isNative = selectedToken?.contract === zeroAddress;
|
|
3412
2984
|
const { data: nativeBalanceData } = useBalance$1({
|
|
@@ -3440,8 +3012,14 @@ function DepositView({
|
|
|
3440
3012
|
const belowMoonpayMin = isCreditCard && hasValidAmount && moonpayMinBuy != null && parseFloat(amount) < moonpayMinBuy;
|
|
3441
3013
|
const moonpayLimitsUnready = isCreditCard && !!selectedToken?.moonpayCurrencyCode && moonpayMinBuy == null;
|
|
3442
3014
|
const creditCardUnavailable = isCreditCard && !!selectedToken && !selectedToken.moonpayCurrencyCode;
|
|
3443
|
-
const
|
|
3444
|
-
const
|
|
3015
|
+
const externalTokenUnavailable = isExternal && !!selectedToken && isNative;
|
|
3016
|
+
const externalMinimumRequired = isExternal && !!allowance && !!selectedToken;
|
|
3017
|
+
const externalMinimumLoading = externalMinimumRequired && externalMinimum === void 0;
|
|
3018
|
+
const externalMinimumUnavailable = externalMinimumRequired && externalMinimum === null;
|
|
3019
|
+
const belowExternalMinimum = externalMinimumRequired && hasValidAmount && !tooManyDecimals && !!selectedToken && typeof externalMinimum === "bigint" && parseTokenAmount(amount, selectedToken.decimals) < externalMinimum;
|
|
3020
|
+
const externalNeedsWallet = isExternal && (!!allowance || !hostedAuthConfig);
|
|
3021
|
+
const needsConnect = (isConnectedSource || externalNeedsWallet) && !isConnected;
|
|
3022
|
+
const canDeposit = hasValidAmount && !!selectedToken && !tooManyDecimals && !exceedsBalance && !belowMoonpayMin && !moonpayLimitsUnready && !creditCardUnavailable && !externalTokenUnavailable && !externalMinimumLoading && !externalMinimumUnavailable && !belowExternalMinimum && !needsConnect;
|
|
3445
3023
|
const handleMax = () => {
|
|
3446
3024
|
const max = formattedWalletBalance.replace(/\s/g, "");
|
|
3447
3025
|
if (parseFloat(max) > 0) onAmountChange(max);
|
|
@@ -3450,9 +3028,12 @@ function DepositView({
|
|
|
3450
3028
|
isCreditCard ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3451
3029
|
/* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Buy with credit card and deposit" }),
|
|
3452
3030
|
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Enter your deposit amount and proceed to sign a policy." })
|
|
3453
|
-
] }) : /* @__PURE__ */ jsxs("
|
|
3454
|
-
"
|
|
3455
|
-
|
|
3031
|
+
] }) : /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3032
|
+
/* @__PURE__ */ jsxs("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: [
|
|
3033
|
+
"Deposit from ",
|
|
3034
|
+
sourceLabel
|
|
3035
|
+
] }),
|
|
3036
|
+
allowance && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Enter your deposit amount and proceed to sign a policy." })
|
|
3456
3037
|
] }),
|
|
3457
3038
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
3458
3039
|
/* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Token" }),
|
|
@@ -3461,7 +3042,8 @@ function DepositView({
|
|
|
3461
3042
|
{
|
|
3462
3043
|
type: "button",
|
|
3463
3044
|
onClick: onSelectToken,
|
|
3464
|
-
|
|
3045
|
+
disabled: isSubmitting,
|
|
3046
|
+
className: "border-border bg-input flex w-full cursor-pointer items-center gap-3 rounded-lg border p-3 text-left disabled:cursor-not-allowed disabled:opacity-50",
|
|
3465
3047
|
children: [
|
|
3466
3048
|
selectedToken ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3467
3049
|
/* @__PURE__ */ jsx("div", { className: "h-8 w-8 shrink-0 overflow-hidden rounded-full", children: getTokenIcon(selectedToken.symbol, 32) }),
|
|
@@ -3502,6 +3084,7 @@ function DepositView({
|
|
|
3502
3084
|
type: "text",
|
|
3503
3085
|
inputMode: "decimal",
|
|
3504
3086
|
placeholder: "Enter Amount",
|
|
3087
|
+
disabled: isSubmitting,
|
|
3505
3088
|
value: amount,
|
|
3506
3089
|
onChange: (e) => {
|
|
3507
3090
|
const value = e.target.value.replace(/[^0-9.,]/g, "").replace(/,/g, ".");
|
|
@@ -3533,11 +3116,24 @@ function DepositView({
|
|
|
3533
3116
|
moonpayMinBuy,
|
|
3534
3117
|
" USD."
|
|
3535
3118
|
] }),
|
|
3119
|
+
belowExternalMinimum && selectedToken && typeof externalMinimum === "bigint" && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
3120
|
+
"Minimum deposit is ",
|
|
3121
|
+
formatUnits(externalMinimum, selectedToken.decimals),
|
|
3122
|
+
" ",
|
|
3123
|
+
selectedToken.symbol,
|
|
3124
|
+
"."
|
|
3125
|
+
] }),
|
|
3126
|
+
externalMinimumLoading && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Checking minimum deposit\u2026" }),
|
|
3127
|
+
externalMinimumUnavailable && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Couldn\u2019t load the minimum deposit. Please try again." }),
|
|
3536
3128
|
isCreditCard && moonpayLimitsError && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Couldn\u2019t load purchase limits. Please try again." }),
|
|
3537
3129
|
isCreditCard && moonpayLimitsLoading && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Checking purchase limits\u2026" }),
|
|
3538
3130
|
creditCardUnavailable && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
3539
3131
|
selectedToken?.symbol ?? "This token",
|
|
3540
3132
|
" isn\u2019t available for card purchases yet."
|
|
3133
|
+
] }),
|
|
3134
|
+
externalTokenUnavailable && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
3135
|
+
selectedToken?.symbol ?? "This token",
|
|
3136
|
+
" can\u2019t be used for external deposits. Choose an ERC20 token."
|
|
3541
3137
|
] })
|
|
3542
3138
|
] }),
|
|
3543
3139
|
allowance && /* @__PURE__ */ jsx(
|
|
@@ -3612,19 +3208,33 @@ function AwaitingDepositStatus({
|
|
|
3612
3208
|
function ExternalDepositView({
|
|
3613
3209
|
token,
|
|
3614
3210
|
amount,
|
|
3615
|
-
|
|
3211
|
+
depositAddressState,
|
|
3212
|
+
onCredited,
|
|
3213
|
+
onDiscardLock,
|
|
3214
|
+
lock
|
|
3616
3215
|
}) {
|
|
3617
|
-
const { getChainById: getChainById2 } = usePrivanaContext();
|
|
3618
|
-
const
|
|
3216
|
+
const { getChainById: getChainById2, getTokenById, serviceName } = usePrivanaContext();
|
|
3217
|
+
const appName = serviceName ?? "Privana";
|
|
3218
|
+
const { depositAddress, isReady, isLoading } = depositAddressState;
|
|
3619
3219
|
const chain = token ? getChainById2(token.chainId) : void 0;
|
|
3220
|
+
const lockSession = lock?.session;
|
|
3221
|
+
const recordVerification = lock?.recordVerification;
|
|
3222
|
+
const settleAfterCredit = lock?.settleAfterCredit;
|
|
3223
|
+
const retryAfterCredit = lock?.retryAfterCredit;
|
|
3224
|
+
const discoveryToken = lockSession ? getTokenById(lockSession.tokenId) : token;
|
|
3620
3225
|
const [copied, setCopied] = useState(false);
|
|
3621
3226
|
const [deadline] = useState(() => Date.now() + LISTENING_WINDOW_MS);
|
|
3622
3227
|
const secondsLeft = useCountdown(deadline);
|
|
3623
3228
|
const listening = secondsLeft > 0;
|
|
3624
3229
|
const [nothingFound, setNothingFound] = useState(false);
|
|
3625
3230
|
const [credited, setCredited] = useState(null);
|
|
3231
|
+
const [showCancelWarning, setShowCancelWarning] = useState(false);
|
|
3626
3232
|
const verification = useDepositVerification({
|
|
3627
|
-
onCredited: (txHash) => {
|
|
3233
|
+
onCredited: (txHash, _response, creditedAmount) => {
|
|
3234
|
+
if (settleAfterCredit) {
|
|
3235
|
+
void settleAfterCredit(txHash, creditedAmount);
|
|
3236
|
+
return;
|
|
3237
|
+
}
|
|
3628
3238
|
if (onCredited) {
|
|
3629
3239
|
onCredited();
|
|
3630
3240
|
return;
|
|
@@ -3632,8 +3242,9 @@ function ExternalDepositView({
|
|
|
3632
3242
|
setCredited({ txHash });
|
|
3633
3243
|
}
|
|
3634
3244
|
});
|
|
3635
|
-
const { isVerifying, verificationFailed, didTimeout, verify } = verification;
|
|
3636
|
-
const scanPaused = isVerifying || verificationFailed || didTimeout || !!credited;
|
|
3245
|
+
const { isVerifying, verificationFailed, canCheckAnotherTransfer, didTimeout, verify } = verification;
|
|
3246
|
+
const scanPaused = isVerifying || verificationFailed || didTimeout || !!credited || !!lock?.isSigning || !!lock?.isSubmittingLock || !!lockSession?.verification || !!lockSession?.creditedAmount;
|
|
3247
|
+
const showDepositInstructions = !credited && !lockSession?.creditedAmount;
|
|
3637
3248
|
const {
|
|
3638
3249
|
pending,
|
|
3639
3250
|
isFetching: isScanning,
|
|
@@ -3642,23 +3253,58 @@ function ExternalDepositView({
|
|
|
3642
3253
|
isUnavailable,
|
|
3643
3254
|
refetch: refetchPendingDeposits
|
|
3644
3255
|
} = usePendingDeposits({
|
|
3645
|
-
chainId:
|
|
3646
|
-
|
|
3256
|
+
chainId: discoveryToken?.chainId,
|
|
3257
|
+
tokenAddress: discoveryToken?.contract === zeroAddress ? void 0 : discoveryToken?.contract,
|
|
3258
|
+
enabled: isReady && !!depositAddress && !!discoveryToken && discoveryToken.contract !== zeroAddress,
|
|
3647
3259
|
refetchInterval: listening && !scanPaused ? 3e4 : false
|
|
3648
3260
|
});
|
|
3649
3261
|
const processedRef = useRef(/* @__PURE__ */ new Set());
|
|
3262
|
+
const verificationRunRef = useRef(null);
|
|
3263
|
+
useEffect(
|
|
3264
|
+
() => () => {
|
|
3265
|
+
verificationRunRef.current = null;
|
|
3266
|
+
},
|
|
3267
|
+
[]
|
|
3268
|
+
);
|
|
3269
|
+
useEffect(() => {
|
|
3270
|
+
const stored = lockSession?.verification;
|
|
3271
|
+
if (!stored || lockSession.creditedAmount || isVerifying || verificationFailed || didTimeout) {
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
const key = `${lockSession.generation}:${stored.chainId}:${stored.hash}:${stored.logIndex ?? 0}`;
|
|
3275
|
+
if (verificationRunRef.current === key) return;
|
|
3276
|
+
verificationRunRef.current = key;
|
|
3277
|
+
processedRef.current.add(`${stored.hash}:${stored.logIndex ?? 0}`);
|
|
3278
|
+
void verify({
|
|
3279
|
+
hash: stored.hash,
|
|
3280
|
+
chainId: stored.chainId,
|
|
3281
|
+
amount: BigInt(stored.amount),
|
|
3282
|
+
logIndex: stored.logIndex
|
|
3283
|
+
});
|
|
3284
|
+
}, [didTimeout, isVerifying, lockSession, verificationFailed, verify]);
|
|
3650
3285
|
useEffect(() => {
|
|
3651
3286
|
if (scanPaused) return;
|
|
3652
|
-
const next = [...pending].sort((a, b) => a.block_number - b.block_number).find(
|
|
3287
|
+
const next = [...pending].sort((a, b) => a.block_number - b.block_number).find(
|
|
3288
|
+
(deposit) => (!lockSession || isExternalDepositBlockInSession(lockSession, deposit.block_number)) && !processedRef.current.has(`${deposit.tx_hash}:${deposit.log_index}`)
|
|
3289
|
+
);
|
|
3653
3290
|
if (!next) return;
|
|
3654
|
-
|
|
3655
|
-
void verify({
|
|
3291
|
+
const context = {
|
|
3656
3292
|
hash: next.tx_hash,
|
|
3657
3293
|
chainId: next.chain_id,
|
|
3658
3294
|
amount: BigInt(next.amount),
|
|
3659
3295
|
logIndex: next.log_index
|
|
3660
|
-
}
|
|
3661
|
-
|
|
3296
|
+
};
|
|
3297
|
+
if (recordVerification) {
|
|
3298
|
+
try {
|
|
3299
|
+
recordVerification(context);
|
|
3300
|
+
} catch (err) {
|
|
3301
|
+
toast.error(err instanceof Error ? err.message : "Unable to save deposit recovery state");
|
|
3302
|
+
return;
|
|
3303
|
+
}
|
|
3304
|
+
}
|
|
3305
|
+
processedRef.current.add(`${next.tx_hash}:${next.log_index}`);
|
|
3306
|
+
if (!recordVerification) void verify(context);
|
|
3307
|
+
}, [lockSession, pending, recordVerification, scanPaused, verify]);
|
|
3662
3308
|
const nothingFoundTimerRef = useRef(void 0);
|
|
3663
3309
|
useEffect(() => () => clearTimeout(nothingFoundTimerRef.current), []);
|
|
3664
3310
|
const handleRefresh = () => {
|
|
@@ -3671,11 +3317,27 @@ function ExternalDepositView({
|
|
|
3671
3317
|
}
|
|
3672
3318
|
});
|
|
3673
3319
|
};
|
|
3674
|
-
const handleCopy = () => {
|
|
3320
|
+
const handleCopy = async () => {
|
|
3675
3321
|
if (!depositAddress) return;
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3322
|
+
try {
|
|
3323
|
+
await navigator.clipboard.writeText(depositAddress);
|
|
3324
|
+
setCopied(true);
|
|
3325
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
3326
|
+
} catch {
|
|
3327
|
+
toast.error("Unable to copy the deposit address");
|
|
3328
|
+
}
|
|
3329
|
+
};
|
|
3330
|
+
const handleCheckAnotherTransfer = () => {
|
|
3331
|
+
try {
|
|
3332
|
+
if (!lock?.clearVerification()) {
|
|
3333
|
+
toast.error("Deposit recovery changed in another tab. Please review the current state.");
|
|
3334
|
+
return;
|
|
3335
|
+
}
|
|
3336
|
+
verificationRunRef.current = null;
|
|
3337
|
+
verification.reset();
|
|
3338
|
+
} catch (err) {
|
|
3339
|
+
toast.error(err instanceof Error ? err.message : "Unable to continue deposit discovery");
|
|
3340
|
+
}
|
|
3679
3341
|
};
|
|
3680
3342
|
return /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
3681
3343
|
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
@@ -3689,27 +3351,64 @@ function ExternalDepositView({
|
|
|
3689
3351
|
/* @__PURE__ */ jsx("div", { className: "bg-border h-px w-full" }),
|
|
3690
3352
|
/* @__PURE__ */ jsx(SummaryRow, { value: amount || "\u2014", label: "Value" })
|
|
3691
3353
|
] }),
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Deposit
|
|
3695
|
-
/* @__PURE__ */ jsxs("div", { className: "flex
|
|
3696
|
-
/* @__PURE__ */ jsx("
|
|
3697
|
-
/* @__PURE__ */ jsxs(
|
|
3698
|
-
"
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3354
|
+
showDepositInstructions && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3355
|
+
!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." }),
|
|
3356
|
+
/* @__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" }) }),
|
|
3357
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
3358
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Deposit Address" }),
|
|
3359
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
3360
|
+
/* @__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" }) }),
|
|
3361
|
+
/* @__PURE__ */ jsxs(
|
|
3362
|
+
"button",
|
|
3363
|
+
{
|
|
3364
|
+
type: "button",
|
|
3365
|
+
onClick: handleCopy,
|
|
3366
|
+
disabled: !depositAddress,
|
|
3367
|
+
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",
|
|
3368
|
+
children: [
|
|
3369
|
+
/* @__PURE__ */ jsx(CopyIcon, {}),
|
|
3370
|
+
copied ? "Copied" : "Copy"
|
|
3371
|
+
]
|
|
3372
|
+
}
|
|
3373
|
+
)
|
|
3374
|
+
] })
|
|
3710
3375
|
] })
|
|
3711
3376
|
] }),
|
|
3712
|
-
|
|
3377
|
+
showCancelWarning && lockSession && !lockSession.verification && !lockSession.creditedAmount ? /* @__PURE__ */ jsx(
|
|
3378
|
+
TransactionWarningView,
|
|
3379
|
+
{
|
|
3380
|
+
title: "Cancel this deposit?",
|
|
3381
|
+
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.",
|
|
3382
|
+
onDone: () => onDiscardLock?.(),
|
|
3383
|
+
actionLabel: "I haven\u2019t sent funds",
|
|
3384
|
+
onDismiss: () => setShowCancelWarning(false),
|
|
3385
|
+
dismissLabel: "Keep waiting"
|
|
3386
|
+
}
|
|
3387
|
+
) : lock?.isSigning ? /* @__PURE__ */ jsx(
|
|
3388
|
+
AwaitingDepositStatus,
|
|
3389
|
+
{
|
|
3390
|
+
title: "Confirm policy in your wallet",
|
|
3391
|
+
subtitle: "Sign a fresh policy for the funds already credited."
|
|
3392
|
+
}
|
|
3393
|
+
) : lock?.isSubmittingLock ? /* @__PURE__ */ jsx(
|
|
3394
|
+
AwaitingDepositStatus,
|
|
3395
|
+
{
|
|
3396
|
+
title: `Locking funds for ${appName}`,
|
|
3397
|
+
subtitle: "Deposit credited. Applying your signed policy\u2026"
|
|
3398
|
+
}
|
|
3399
|
+
) : lock?.session?.creditedAmount ? /* @__PURE__ */ jsx(
|
|
3400
|
+
TransactionWarningView,
|
|
3401
|
+
{
|
|
3402
|
+
title: lock.session.submissionAmbiguous ? "Lock status unknown" : "Deposit credited",
|
|
3403
|
+
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.",
|
|
3404
|
+
onDone: () => {
|
|
3405
|
+
void retryAfterCredit?.().catch((err) => {
|
|
3406
|
+
toast.error(err instanceof Error ? err.message : "Policy recovery failed");
|
|
3407
|
+
});
|
|
3408
|
+
},
|
|
3409
|
+
actionLabel: lock.session.payload ? "Retry lock submission" : "Sign policy and lock funds"
|
|
3410
|
+
}
|
|
3411
|
+
) : credited ? /* @__PURE__ */ jsx(
|
|
3713
3412
|
TransactionSuccessView,
|
|
3714
3413
|
{
|
|
3715
3414
|
title: "Deposit Credited",
|
|
@@ -3726,14 +3425,15 @@ function ExternalDepositView({
|
|
|
3726
3425
|
title: "Could not verify deposit",
|
|
3727
3426
|
message: didTimeout ? "Verification timed out \u2014 your deposit may still be credited in the background." : verification.error?.message ?? "Your transfer was found but could not be verified.",
|
|
3728
3427
|
onRetry: () => void verification.retryVerification(),
|
|
3729
|
-
onDismiss: () => verification.reset(),
|
|
3428
|
+
onDismiss: lock ? canCheckAnotherTransfer ? handleCheckAnotherTransfer : void 0 : () => verification.reset(),
|
|
3429
|
+
dismissLabel: lock && canCheckAnotherTransfer ? "Check another transfer" : void 0,
|
|
3730
3430
|
isRetrying: isVerifying
|
|
3731
3431
|
}
|
|
3732
3432
|
) : isVerifying ? /* @__PURE__ */ jsx(
|
|
3733
3433
|
AwaitingDepositStatus,
|
|
3734
3434
|
{
|
|
3735
3435
|
title: "Deposit detected",
|
|
3736
|
-
subtitle: "Crediting the incoming transaction to your Privana balance
|
|
3436
|
+
subtitle: "Crediting the incoming transaction to your Privana balance\u2026"
|
|
3737
3437
|
}
|
|
3738
3438
|
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3739
3439
|
/* @__PURE__ */ jsx(
|
|
@@ -3755,11 +3455,95 @@ function ExternalDepositView({
|
|
|
3755
3455
|
children: "Refresh deposit status"
|
|
3756
3456
|
}
|
|
3757
3457
|
),
|
|
3758
|
-
|
|
3458
|
+
lockSession && !lockSession.verification && !lockSession.creditedAmount && /* @__PURE__ */ jsx(
|
|
3459
|
+
"button",
|
|
3460
|
+
{
|
|
3461
|
+
type: "button",
|
|
3462
|
+
onClick: () => setShowCancelWarning(true),
|
|
3463
|
+
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",
|
|
3464
|
+
children: "Cancel deposit"
|
|
3465
|
+
}
|
|
3466
|
+
),
|
|
3467
|
+
isRateLimited ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", children: "Checked too recently. Try again in a moment." }) : isUnavailable ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", children: "Deposit discovery is unavailable for this chain." }) : isScanError ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", children: "Chain temporarily unreachable. Retrying automatically." }) : nothingFound ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", children: "No incoming transaction found yet." }) : null
|
|
3759
3468
|
] })
|
|
3760
3469
|
] })
|
|
3761
3470
|
] });
|
|
3762
3471
|
}
|
|
3472
|
+
function MoonPayGate({ enabled, children }) {
|
|
3473
|
+
const { networkConfig } = usePrivanaContext();
|
|
3474
|
+
const apiKey = networkConfig.moonpayApiKey;
|
|
3475
|
+
if (!enabled || !apiKey) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
3476
|
+
return /* @__PURE__ */ jsx(MoonPayProvider, { apiKey, children });
|
|
3477
|
+
}
|
|
3478
|
+
function formatPostDepositLockFailure(error, token) {
|
|
3479
|
+
if (error?.reason === "credited-below-signed" && error.creditedAmount !== void 0 && error.signedAmount !== void 0 && token) {
|
|
3480
|
+
const creditedAmount = `${formatUnits(error.creditedAmount, token.decimals)} ${token.symbol}`;
|
|
3481
|
+
const signedAmount = `${formatUnits(error.signedAmount, token.decimals)} ${token.symbol}`;
|
|
3482
|
+
return `Credited amount (${creditedAmount}) is below the signed lock amount (${signedAmount})`;
|
|
3483
|
+
}
|
|
3484
|
+
return error?.message ?? "Lock submission failed";
|
|
3485
|
+
}
|
|
3486
|
+
function MethodTabs({
|
|
3487
|
+
activeTab,
|
|
3488
|
+
onTabChange
|
|
3489
|
+
}) {
|
|
3490
|
+
return /* @__PURE__ */ jsxs("div", { className: "bg-secondary relative flex gap-2 overflow-hidden rounded-[10px] p-1", children: [
|
|
3491
|
+
/* @__PURE__ */ jsx(
|
|
3492
|
+
"div",
|
|
3493
|
+
{
|
|
3494
|
+
className: cn(
|
|
3495
|
+
"bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-8px)] rounded-md transition-transform duration-200",
|
|
3496
|
+
activeTab === "credit-card" && "translate-x-[calc(100%+8px)]"
|
|
3497
|
+
)
|
|
3498
|
+
}
|
|
3499
|
+
),
|
|
3500
|
+
/* @__PURE__ */ jsx(
|
|
3501
|
+
"button",
|
|
3502
|
+
{
|
|
3503
|
+
type: "button",
|
|
3504
|
+
onClick: () => onTabChange("crypto"),
|
|
3505
|
+
className: cn(
|
|
3506
|
+
"relative z-10 flex-1 cursor-pointer rounded-md px-3 py-[9px] text-sm font-medium transition-colors",
|
|
3507
|
+
activeTab === "crypto" ? "text-foreground" : "text-muted-foreground"
|
|
3508
|
+
),
|
|
3509
|
+
children: "Crypto"
|
|
3510
|
+
}
|
|
3511
|
+
),
|
|
3512
|
+
/* @__PURE__ */ jsx(
|
|
3513
|
+
"button",
|
|
3514
|
+
{
|
|
3515
|
+
type: "button",
|
|
3516
|
+
onClick: () => onTabChange("credit-card"),
|
|
3517
|
+
className: cn(
|
|
3518
|
+
"relative z-10 flex-1 cursor-pointer rounded-md px-3 py-[9px] text-sm font-medium transition-colors",
|
|
3519
|
+
activeTab === "credit-card" ? "text-foreground" : "text-muted-foreground"
|
|
3520
|
+
),
|
|
3521
|
+
children: "Credit Card"
|
|
3522
|
+
}
|
|
3523
|
+
)
|
|
3524
|
+
] });
|
|
3525
|
+
}
|
|
3526
|
+
function MethodOption({
|
|
3527
|
+
title,
|
|
3528
|
+
description,
|
|
3529
|
+
onClick
|
|
3530
|
+
}) {
|
|
3531
|
+
return /* @__PURE__ */ jsxs(
|
|
3532
|
+
"button",
|
|
3533
|
+
{
|
|
3534
|
+
type: "button",
|
|
3535
|
+
onClick,
|
|
3536
|
+
className: "bg-input hover:bg-input/70 border-border flex w-full cursor-pointer items-center gap-3 rounded-lg border p-3 text-left transition-colors",
|
|
3537
|
+
children: [
|
|
3538
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
3539
|
+
/* @__PURE__ */ jsx("span", { className: "text-foreground text-sm leading-[14px] font-medium", children: title }),
|
|
3540
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs leading-3", children: description })
|
|
3541
|
+
] }),
|
|
3542
|
+
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-5 w-5 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
|
|
3543
|
+
]
|
|
3544
|
+
}
|
|
3545
|
+
);
|
|
3546
|
+
}
|
|
3763
3547
|
function DepositModalContent({
|
|
3764
3548
|
defaultTab = "crypto",
|
|
3765
3549
|
allowance,
|
|
@@ -3774,7 +3558,7 @@ function DepositModalContent({
|
|
|
3774
3558
|
onCloseBlockedChange,
|
|
3775
3559
|
onExit
|
|
3776
3560
|
}) {
|
|
3777
|
-
const { serviceName, enabledTokens, defaultToken, hostedAuthConfig, getChainById: getChainById2 } = usePrivanaContext();
|
|
3561
|
+
const { serviceName, enabledTokens, defaultToken, hostedAuthConfig, getChainById: getChainById2, getTokenById } = usePrivanaContext();
|
|
3778
3562
|
const { address } = useAccount();
|
|
3779
3563
|
const appName = serviceName ?? "Privana";
|
|
3780
3564
|
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
@@ -3783,6 +3567,10 @@ function DepositModalContent({
|
|
|
3783
3567
|
const [selectedTokenId, setSelectedTokenId] = useState(defaultToken?.id ?? "");
|
|
3784
3568
|
const [amount, setAmount] = useState("");
|
|
3785
3569
|
const selectedToken = enabledTokens.find((t) => t.id === selectedTokenId) ?? defaultToken;
|
|
3570
|
+
const externalDepositAddress = useDepositAddress({
|
|
3571
|
+
enabled: source === "external" && (view === "deposit" || view === "external-deposit")
|
|
3572
|
+
});
|
|
3573
|
+
const externalMinimum = source !== "external" || !selectedToken ? void 0 : externalDepositAddress.isError ? null : externalDepositAddress.response ? getExternalDepositMinimum(externalDepositAddress.response, selectedToken.chainId) ?? null : void 0;
|
|
3786
3574
|
const prevAddressRef = useRef(address);
|
|
3787
3575
|
useEffect(() => {
|
|
3788
3576
|
const prev = prevAddressRef.current;
|
|
@@ -3802,9 +3590,10 @@ function DepositModalContent({
|
|
|
3802
3590
|
const [showTimeout, setShowTimeout] = useState(false);
|
|
3803
3591
|
const [cancelled, setCancelled] = useState(false);
|
|
3804
3592
|
const [isSubmittingLock, setIsSubmittingLock] = useState(false);
|
|
3805
|
-
const [
|
|
3593
|
+
const [lockFailure, setLockFailure] = useState(null);
|
|
3806
3594
|
const finishDeposit = () => {
|
|
3807
3595
|
setAmount("");
|
|
3596
|
+
if (view === "external-deposit") setView("deposit");
|
|
3808
3597
|
if (onDepositSuccess) {
|
|
3809
3598
|
resetDeposit();
|
|
3810
3599
|
onDepositSuccess();
|
|
@@ -3839,6 +3628,7 @@ function DepositModalContent({
|
|
|
3839
3628
|
},
|
|
3840
3629
|
onLockSubmitted: () => {
|
|
3841
3630
|
setIsSubmittingLock(false);
|
|
3631
|
+
setLockFailure(null);
|
|
3842
3632
|
finishDeposit();
|
|
3843
3633
|
},
|
|
3844
3634
|
// The deposit credited; only the policy lock failed. Success must not
|
|
@@ -3846,7 +3636,7 @@ function DepositModalContent({
|
|
|
3846
3636
|
// error view and let the host re-prompt for a fresh lock.
|
|
3847
3637
|
onLockFailed: (err) => {
|
|
3848
3638
|
setIsSubmittingLock(false);
|
|
3849
|
-
|
|
3639
|
+
setLockFailure(err);
|
|
3850
3640
|
onLockFailed?.(err);
|
|
3851
3641
|
},
|
|
3852
3642
|
onCheckTimeout: () => {
|
|
@@ -3854,7 +3644,37 @@ function DepositModalContent({
|
|
|
3854
3644
|
setShowTimeout(true);
|
|
3855
3645
|
}
|
|
3856
3646
|
});
|
|
3857
|
-
const
|
|
3647
|
+
const externalLock = useExternalDepositLock({
|
|
3648
|
+
allowance,
|
|
3649
|
+
onLockSubmitted: () => {
|
|
3650
|
+
setLockFailure(null);
|
|
3651
|
+
finishDeposit();
|
|
3652
|
+
},
|
|
3653
|
+
// Same contract as the connected flow: the deposit credited, only the
|
|
3654
|
+
// lock failed — never report success, show the lock-error view instead.
|
|
3655
|
+
onLockFailed: (err) => {
|
|
3656
|
+
setLockFailure(err);
|
|
3657
|
+
onLockFailed?.(err);
|
|
3658
|
+
}
|
|
3659
|
+
});
|
|
3660
|
+
useEffect(() => {
|
|
3661
|
+
const restored = externalLock.session;
|
|
3662
|
+
if (!restored) return;
|
|
3663
|
+
const canRestore = view === "method" || source === "external" && (view === "deposit" || view === "external-deposit");
|
|
3664
|
+
if (!canRestore) return;
|
|
3665
|
+
const token = getTokenById(restored.tokenId);
|
|
3666
|
+
if (!token) return;
|
|
3667
|
+
setSource("external");
|
|
3668
|
+
setSelectedTokenId(token.id);
|
|
3669
|
+
setAmount(formatUnits(BigInt(restored.depositAmount), token.decimals));
|
|
3670
|
+
setView("external-deposit");
|
|
3671
|
+
}, [externalLock.session, getTokenById, source, view]);
|
|
3672
|
+
useEffect(() => {
|
|
3673
|
+
if (view === "external-deposit" && allowance && !externalLock.session) {
|
|
3674
|
+
setView("deposit");
|
|
3675
|
+
}
|
|
3676
|
+
}, [allowance, externalLock.session, view]);
|
|
3677
|
+
const isUnsafeToClose = (isGettingAddress || isSendingTransaction) && !cancelled || externalLock.isSigning || externalLock.isSubmittingLock;
|
|
3858
3678
|
useEffect(() => {
|
|
3859
3679
|
onCloseBlockedChange?.(isUnsafeToClose);
|
|
3860
3680
|
}, [isUnsafeToClose, onCloseBlockedChange]);
|
|
@@ -3867,7 +3687,33 @@ function DepositModalContent({
|
|
|
3867
3687
|
}, [depositError, verificationFailed]);
|
|
3868
3688
|
const handleSubmit = (args) => {
|
|
3869
3689
|
if (args.source === "external") {
|
|
3870
|
-
|
|
3690
|
+
const token2 = enabledTokens.find((candidate) => candidate.id === args.tokenId);
|
|
3691
|
+
if (!token2) return;
|
|
3692
|
+
if (token2.contract === zeroAddress) {
|
|
3693
|
+
toast.error("External deposits support ERC20 tokens only");
|
|
3694
|
+
return;
|
|
3695
|
+
}
|
|
3696
|
+
const depositAmount = parseTokenAmount(args.amount, token2.decimals);
|
|
3697
|
+
if (!allowance) {
|
|
3698
|
+
setView("external-deposit");
|
|
3699
|
+
return;
|
|
3700
|
+
}
|
|
3701
|
+
if (typeof externalMinimum !== "bigint") {
|
|
3702
|
+
toast.error("Minimum deposit is unavailable. Please try again.");
|
|
3703
|
+
return;
|
|
3704
|
+
}
|
|
3705
|
+
if (depositAmount < externalMinimum) {
|
|
3706
|
+
toast.error(
|
|
3707
|
+
`Minimum deposit is ${formatUnits(externalMinimum, token2.decimals)} ${token2.symbol}`
|
|
3708
|
+
);
|
|
3709
|
+
return;
|
|
3710
|
+
}
|
|
3711
|
+
externalLock.signAndPersist({
|
|
3712
|
+
tokenId: token2.id,
|
|
3713
|
+
amount: depositAmount
|
|
3714
|
+
}).then(() => setView("external-deposit")).catch((err) => {
|
|
3715
|
+
toast.error(err instanceof Error ? err.message : "Policy signing failed");
|
|
3716
|
+
});
|
|
3871
3717
|
return;
|
|
3872
3718
|
}
|
|
3873
3719
|
if (args.source === "credit-card") {
|
|
@@ -3919,7 +3765,14 @@ function DepositModalContent({
|
|
|
3919
3765
|
}
|
|
3920
3766
|
] : []
|
|
3921
3767
|
];
|
|
3922
|
-
const
|
|
3768
|
+
const hasExternalLockRecovery = view === "external-deposit" && !!externalLock.session?.creditedAmount;
|
|
3769
|
+
const externalSubmissionAmbiguous = hasExternalLockRecovery && !!lockFailure?.submissionMayHaveSucceeded;
|
|
3770
|
+
const savedExternalPayloadUsable = !!externalLock.session?.payload && isSignedLockUsable(externalLock.session.payload);
|
|
3771
|
+
const ambiguousRecoveryMustBeAbandoned = externalSubmissionAmbiguous && !savedExternalPayloadUsable;
|
|
3772
|
+
const externalRecoveryExitLabel = externalSubmissionAmbiguous ? "Stop recovery" : "Keep funds unlocked";
|
|
3773
|
+
const lockFailureToken = hasExternalLockRecovery && externalLock.session ? getTokenById(externalLock.session.tokenId) ?? selectedToken : selectedToken;
|
|
3774
|
+
const lockFailureMessage = formatPostDepositLockFailure(lockFailure, lockFailureToken);
|
|
3775
|
+
const flowView = showSuccess ? "deposit-success" : lockFailure ? "lock-error" : showTimeout ? "deposit-timeout" : verificationFailed ? "deposit-error" : isPending && !cancelled || isSubmittingLock ? "depositing" : null;
|
|
3923
3776
|
const activeView = flowView ?? view;
|
|
3924
3777
|
const handleDepositDone = () => {
|
|
3925
3778
|
setShowSuccess(false);
|
|
@@ -3932,10 +3785,28 @@ function DepositModalContent({
|
|
|
3932
3785
|
resetDeposit();
|
|
3933
3786
|
};
|
|
3934
3787
|
const handleLockFailedDone = () => {
|
|
3935
|
-
|
|
3788
|
+
if (hasExternalLockRecovery && !externalLock.discardSession()) {
|
|
3789
|
+
toast.error("Deposit recovery changed in another tab. Please review the current state.");
|
|
3790
|
+
return;
|
|
3791
|
+
}
|
|
3792
|
+
setLockFailure(null);
|
|
3936
3793
|
setAmount("");
|
|
3937
3794
|
setCancelled(false);
|
|
3938
3795
|
resetDeposit();
|
|
3796
|
+
if (view === "external-deposit") setView("deposit");
|
|
3797
|
+
};
|
|
3798
|
+
const handleExternalLockRetry = () => {
|
|
3799
|
+
externalLock.retryAfterCredit().catch((err) => {
|
|
3800
|
+
toast.error(err instanceof Error ? err.message : "Policy signing failed");
|
|
3801
|
+
});
|
|
3802
|
+
};
|
|
3803
|
+
const handleExternalVerificationDiscard = () => {
|
|
3804
|
+
if (!externalLock.discardSession()) {
|
|
3805
|
+
toast.error("Deposit recovery changed in another tab. Please review the current state.");
|
|
3806
|
+
return;
|
|
3807
|
+
}
|
|
3808
|
+
setAmount("");
|
|
3809
|
+
setView("deposit");
|
|
3939
3810
|
};
|
|
3940
3811
|
const handleDismissVerificationError = () => {
|
|
3941
3812
|
setAmount("");
|
|
@@ -3948,6 +3819,10 @@ function DepositModalContent({
|
|
|
3948
3819
|
};
|
|
3949
3820
|
const back = view === "external-deposit" ? { to: "deposit", label: "Deposit from External Wallet" } : view === "credit-card-widget" ? { to: "deposit", label: "Buy with credit card and deposit" } : view === "select-token" ? { to: "deposit", label: "Deposit" } : { to: "method", label: "Deposit Method" };
|
|
3950
3821
|
const goBack = () => {
|
|
3822
|
+
if (view === "external-deposit" && externalLock.session) {
|
|
3823
|
+
toast.error("Cancel the active deposit from the address screen before going back");
|
|
3824
|
+
return;
|
|
3825
|
+
}
|
|
3951
3826
|
if (back.to === "method") {
|
|
3952
3827
|
setAmount("");
|
|
3953
3828
|
setSelectedTokenId("");
|
|
@@ -3993,9 +3868,14 @@ function DepositModalContent({
|
|
|
3993
3868
|
activeView === "lock-error" && /* @__PURE__ */ jsx(
|
|
3994
3869
|
TransactionWarningView,
|
|
3995
3870
|
{
|
|
3996
|
-
title: "Deposit credited, lock failed",
|
|
3997
|
-
message: `Your deposit was credited but locking the funds for ${appName} failed: ${
|
|
3998
|
-
onDone: handleLockFailedDone
|
|
3871
|
+
title: externalSubmissionAmbiguous ? "Deposit credited, lock status unknown" : "Deposit credited, lock failed",
|
|
3872
|
+
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}`,
|
|
3873
|
+
onDone: hasExternalLockRecovery && !ambiguousRecoveryMustBeAbandoned ? handleExternalLockRetry : handleLockFailedDone,
|
|
3874
|
+
actionLabel: hasExternalLockRecovery ? ambiguousRecoveryMustBeAbandoned ? externalRecoveryExitLabel : externalLock.session?.payload ? "Retry lock submission" : "Sign policy and lock funds" : void 0,
|
|
3875
|
+
pendingLabel: externalLock.isSigning ? "Confirm policy in wallet\u2026" : "Locking funds\u2026",
|
|
3876
|
+
isPending: externalLock.isSigning || externalLock.isSubmittingLock,
|
|
3877
|
+
onDismiss: hasExternalLockRecovery && !ambiguousRecoveryMustBeAbandoned ? handleLockFailedDone : void 0,
|
|
3878
|
+
dismissLabel: externalRecoveryExitLabel
|
|
3999
3879
|
}
|
|
4000
3880
|
),
|
|
4001
3881
|
activeView === "deposit-timeout" && /* @__PURE__ */ jsx(
|
|
@@ -4010,7 +3890,7 @@ function DepositModalContent({
|
|
|
4010
3890
|
TransactionErrorView,
|
|
4011
3891
|
{
|
|
4012
3892
|
title: "Verification failed",
|
|
4013
|
-
message: depositError?.message ? `Your transfer was sent on-chain but we could not verify the deposit. The funds are already at the deposit address
|
|
3893
|
+
message: depositError?.message ? `Your transfer was sent on-chain but we could not verify the deposit. The funds are already at the deposit address. Retry verification instead of starting a new deposit. (${depositError.message})` : "Your transfer was sent on-chain but we could not verify the deposit. The funds are already at the deposit address. Retry verification instead of starting a new deposit.",
|
|
4014
3894
|
explorerUrl: explorerTxUrl,
|
|
4015
3895
|
explorerLabel: "View transaction",
|
|
4016
3896
|
onRetry: handleRetryVerification,
|
|
@@ -4020,7 +3900,7 @@ function DepositModalContent({
|
|
|
4020
3900
|
activeView === "depositing" && /* @__PURE__ */ jsx(
|
|
4021
3901
|
TransactionProgressView,
|
|
4022
3902
|
{
|
|
4023
|
-
title: "Depositing
|
|
3903
|
+
title: "Depositing\u2026",
|
|
4024
3904
|
steps: depositSteps,
|
|
4025
3905
|
onCancel: isGettingAddress || isSendingTransaction ? handleDepositCancel : void 0
|
|
4026
3906
|
}
|
|
@@ -4074,15 +3954,16 @@ function DepositModalContent({
|
|
|
4074
3954
|
selectedToken,
|
|
4075
3955
|
amount,
|
|
4076
3956
|
allowance,
|
|
3957
|
+
externalMinimum,
|
|
4077
3958
|
onAmountChange: setAmount,
|
|
4078
3959
|
onSelectToken: () => setView("select-token"),
|
|
4079
3960
|
onConnectWallet,
|
|
4080
3961
|
onSubmit: handleSubmit,
|
|
4081
|
-
isSubmitting: isPending
|
|
3962
|
+
isSubmitting: isPending || externalLock.isSigning || externalLock.isSubmittingLock
|
|
4082
3963
|
}
|
|
4083
3964
|
) }),
|
|
4084
3965
|
activeView === "select-token" && /* @__PURE__ */ jsx(
|
|
4085
|
-
|
|
3966
|
+
TokenSelectorView,
|
|
4086
3967
|
{
|
|
4087
3968
|
selectedTokenId: selectedToken?.id,
|
|
4088
3969
|
onSelect: (id) => {
|
|
@@ -4091,12 +3972,15 @@ function DepositModalContent({
|
|
|
4091
3972
|
}
|
|
4092
3973
|
}
|
|
4093
3974
|
),
|
|
4094
|
-
activeView === "external-deposit" && /* @__PURE__ */ jsx(
|
|
3975
|
+
activeView === "external-deposit" && (!allowance || externalLock.session) && /* @__PURE__ */ jsx(
|
|
4095
3976
|
ExternalDepositView,
|
|
4096
3977
|
{
|
|
4097
3978
|
token: selectedToken,
|
|
4098
3979
|
amount,
|
|
4099
|
-
|
|
3980
|
+
depositAddressState: externalDepositAddress,
|
|
3981
|
+
onCredited: allowance ? void 0 : onDepositSuccess,
|
|
3982
|
+
onDiscardLock: handleExternalVerificationDiscard,
|
|
3983
|
+
lock: externalLock.session ? externalLock : void 0
|
|
4100
3984
|
}
|
|
4101
3985
|
),
|
|
4102
3986
|
activeView === "credit-card-widget" && /* @__PURE__ */ jsx(MoonPayGate, { enabled: true, children: /* @__PURE__ */ jsx(
|
|
@@ -4108,7 +3992,7 @@ function DepositModalContent({
|
|
|
4108
3992
|
onCredited: allowance ? void 0 : finishCardPurchase,
|
|
4109
3993
|
onLockSubmitted: finishCardPurchase,
|
|
4110
3994
|
onLockFailed: (err) => {
|
|
4111
|
-
|
|
3995
|
+
setLockFailure(err);
|
|
4112
3996
|
onLockFailed?.(err);
|
|
4113
3997
|
}
|
|
4114
3998
|
}
|
|
@@ -4437,7 +4321,7 @@ function WithdrawModalContent({
|
|
|
4437
4321
|
}
|
|
4438
4322
|
),
|
|
4439
4323
|
view === "select-token" && /* @__PURE__ */ jsx(
|
|
4440
|
-
|
|
4324
|
+
TokenSelectorView,
|
|
4441
4325
|
{
|
|
4442
4326
|
selectedTokenId: selectedToken?.id,
|
|
4443
4327
|
onSelect: (id) => {
|
|
@@ -4868,7 +4752,7 @@ function WalletModalContent({
|
|
|
4868
4752
|
onAmountChange: setAmount,
|
|
4869
4753
|
onFundSession: onFundSession ? fundSession : void 0,
|
|
4870
4754
|
onAddFunds: () => setView("deposit"),
|
|
4871
|
-
onWithdraw: handleWithdraw
|
|
4755
|
+
onWithdraw: session && !onEndSession ? void 0 : handleWithdraw
|
|
4872
4756
|
}
|
|
4873
4757
|
),
|
|
4874
4758
|
view === "funding-session" && /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: /* @__PURE__ */ jsx(
|
|
@@ -4978,6 +4862,6 @@ function WalletInlineModal({ className, ...handlers }) {
|
|
|
4978
4862
|
);
|
|
4979
4863
|
}
|
|
4980
4864
|
|
|
4981
|
-
export { DepositInlineModal, DepositModal,
|
|
4865
|
+
export { DepositInlineModal, DepositModal, PrivanaIcon, WalletInlineModal, WalletModal, WithdrawInlineModal, WithdrawModal, getChainIcon, getTokenIcon, useBalance, useBatchBalances, useDeposit, useDepositAddress, useExpiredLocks, useHistory, useHostedRedirectAuth, useLockFunds, useLockedFunds, useModifyLock, usePendingDeposits, usePendingWithdrawals, usePrivanaClient, useTokenInfo, useTokenList, useTotalLockedBalance, useTransfer, useUnlockFunds, useWithdraw };
|
|
4982
4866
|
//# sourceMappingURL=index.js.map
|
|
4983
4867
|
//# sourceMappingURL=index.js.map
|