@oasisprotocol/privana-sdk 0.4.4 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-4IW4V7YJ.cjs +3805 -0
- package/dist/chunk-4IW4V7YJ.cjs.map +1 -0
- package/dist/chunk-SAJ7K5WT.js +3716 -0
- package/dist/chunk-SAJ7K5WT.js.map +1 -0
- package/dist/index.cjs +2528 -636
- 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 +177 -334
- package/dist/index.d.ts +177 -334
- package/dist/index.js +2152 -359
- package/dist/index.js.map +1 -1
- package/dist/on-ramp.cjs +10 -974
- package/dist/on-ramp.cjs.map +1 -1
- package/dist/on-ramp.d.cts +50 -4
- package/dist/on-ramp.d.ts +50 -4
- package/dist/on-ramp.js +1 -975
- package/dist/on-ramp.js.map +1 -1
- package/dist/pending-lock-BdkuMjmY.d.cts +600 -0
- package/dist/pending-lock-BdkuMjmY.d.ts +600 -0
- package/package.json +3 -2
- package/dist/chunk-NMRIKUDN.cjs +0 -1775
- package/dist/chunk-NMRIKUDN.cjs.map +0 -1
- package/dist/chunk-WV6CGHEF.js +0 -1720
- package/dist/chunk-WV6CGHEF.js.map +0 -1
- package/dist/on-ramp-CdXqJjmm.d.cts +0 -115
- package/dist/on-ramp-CdXqJjmm.d.ts +0 -115
package/dist/index.js
CHANGED
|
@@ -1,210 +1,16 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { usePrivanaContext, createHostedAuthPendingStorageKey, clearHostedAuthPendingTransaction, HostedAuthRequiredError, HostedAuthError, createPkceVerifier, createPkceChallenge, createHostedAuthState, persistHostedAuthPendingTransaction, parseHostedAuthCallback, readHostedAuthPendingTransaction, HostedAuthStateMismatchError, buildHostedAuthSession, useSafePrivanaContext, usePrivateReadRequest, formatTokenAmount, useDepositVerification, getTransactionReceipt, waitForTransactionReceipt, cn, Button, stripHostedAuthCallbackParams, AccountingApiError,
|
|
3
|
-
export { AccountingApiError, Button, HOSTED_AUTH_CLOCK_SKEW_MS, HostedAuthError, HostedAuthRequiredError, HostedAuthStateMismatchError, HttpClient, NETWORK_CONFIG, NetworkError, PrivanaClient, PrivanaProvider, SUPPORTED_CHAINS, SiweAuthProvider, Skeleton, ValidationError, applyRefreshResponse, buildHostedAuthSession, buildSiweStatement, buttonVariants, clearHostedAuthPendingTransaction, createHostedAuthPendingStorageKey, createHostedAuthState, createHostedAuthStorageKey, createPkceChallenge, createPkceVerifier, getAccountingContract, getApiUrl, getChainById, getChainId, getExplorerAddressUrl, isHostedAuthRefreshActive, isHostedAuthSessionActive, normalizeAddress, normalizeHex, parseHostedAuthCallback, persistHostedAuthPendingTransaction, readHostedAuthPendingTransaction, readStoredHostedAuthSession, stripHostedAuthCallbackParams, 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, waitForTransactionReceipt, canUseBrowserStorage, clampLockAmount, getWalletClient, createSignedLockRequest, signWithdrawMessage, signLockMessage, signTransferMessage, signTransferLockedMessage, signModifyLockMessage, cn, Button, stripHostedAuthCallbackParams, AccountingApiError, getBrowserStorageItem, removeBrowserStorageItem, isSignedLockUsable, setBrowserStorageItem, shortenAddress, formatTimeRemaining, parseTokenAmount, getExplorerAddressUrl, getExplorerLabel, Skeleton, formatCountdown, FiatOnRampForm } from './chunk-SAJ7K5WT.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, savePendingLock, signLockMessage, signModifyLockMessage, signTransferLockedMessage, signTransferMessage, signWithdrawFromLockMessage, signWithdrawMessage, stripHostedAuthCallbackParams, submitPendingLock, syncHostedAuthSessionToClient, useDepositVerification, usePrivanaContext, useSafeAccount, useSafePrivanaContext, useSiweAuth } from './chunk-SAJ7K5WT.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
|
-
import { useAccount, useWalletClient, useConfig, useWriteContract, useSendTransaction,
|
|
6
|
+
import { useAccount, useWalletClient, useConfig, useWriteContract, useSendTransaction, useBalance as useBalance$1, useReadContract } from 'wagmi';
|
|
7
7
|
import { zeroAddress, erc20Abi } from 'viem';
|
|
8
8
|
import * as DialogPrimitive from '@radix-ui/react-dialog';
|
|
9
9
|
import { XIcon } from 'lucide-react';
|
|
10
10
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
11
11
|
import { toast } from 'sonner';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
function createDomain(chainId, verifyingContract) {
|
|
15
|
-
return {
|
|
16
|
-
name: "AccountingModule",
|
|
17
|
-
version: "1",
|
|
18
|
-
chainId,
|
|
19
|
-
verifyingContract
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
var LOCK_TYPES = {
|
|
23
|
-
Lock: [
|
|
24
|
-
{ name: "serviceAddress", type: "address" },
|
|
25
|
-
{ name: "tokenId", type: "bytes32" },
|
|
26
|
-
{ name: "amount", type: "uint256" },
|
|
27
|
-
{ name: "expiry", type: "uint256" },
|
|
28
|
-
{ name: "nonce", type: "uint256" }
|
|
29
|
-
]
|
|
30
|
-
};
|
|
31
|
-
var TRANSFER_TYPES = {
|
|
32
|
-
Transfer: [
|
|
33
|
-
{ name: "toAddress", type: "address" },
|
|
34
|
-
{ name: "tokenId", type: "bytes32" },
|
|
35
|
-
{ name: "amount", type: "uint256" },
|
|
36
|
-
{ name: "nonce", type: "uint256" }
|
|
37
|
-
]
|
|
38
|
-
};
|
|
39
|
-
var TRANSFER_LOCKED_TYPES = {
|
|
40
|
-
TransferLocked: [
|
|
41
|
-
{ name: "userAddress", type: "address" },
|
|
42
|
-
{ name: "toAddress", type: "address" },
|
|
43
|
-
{ name: "lockId", type: "uint256" },
|
|
44
|
-
{ name: "amount", type: "uint256" },
|
|
45
|
-
{ name: "nonce", type: "uint256" },
|
|
46
|
-
{ name: "serviceAddress", type: "address" }
|
|
47
|
-
]
|
|
48
|
-
};
|
|
49
|
-
var WITHDRAW_TYPES = {
|
|
50
|
-
Withdraw: [
|
|
51
|
-
{ name: "tokenId", type: "bytes32" },
|
|
52
|
-
{ name: "amount", type: "uint256" },
|
|
53
|
-
{ name: "nonce", type: "uint256" }
|
|
54
|
-
]
|
|
55
|
-
};
|
|
56
|
-
var MODIFY_LOCK_TYPES = {
|
|
57
|
-
ModifyLock: [
|
|
58
|
-
{ name: "lockId", type: "uint256" },
|
|
59
|
-
{ name: "amount", type: "uint256" },
|
|
60
|
-
{ name: "newExpiry", type: "uint256" },
|
|
61
|
-
{ name: "nonce", type: "uint256" }
|
|
62
|
-
]
|
|
63
|
-
};
|
|
64
|
-
var WITHDRAW_FROM_LOCK_TYPES = {
|
|
65
|
-
WithdrawFromLock: [
|
|
66
|
-
{ name: "userAddress", type: "address" },
|
|
67
|
-
{ name: "toAddress", type: "address" },
|
|
68
|
-
{ name: "lockId", type: "uint256" },
|
|
69
|
-
{ name: "amount", type: "uint256" },
|
|
70
|
-
{ name: "nonce", type: "uint256" }
|
|
71
|
-
]
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
// src/sdk/signatures/sign-lock.ts
|
|
75
|
-
async function signLockMessage({
|
|
76
|
-
walletClient,
|
|
77
|
-
chainId,
|
|
78
|
-
verifyingContract,
|
|
79
|
-
message
|
|
80
|
-
}) {
|
|
81
|
-
const account = walletClient.account;
|
|
82
|
-
if (!account) {
|
|
83
|
-
throw new Error("No account connected to wallet client");
|
|
84
|
-
}
|
|
85
|
-
const domain = createDomain(chainId, verifyingContract);
|
|
86
|
-
const signature = await walletClient.signTypedData({
|
|
87
|
-
account,
|
|
88
|
-
domain,
|
|
89
|
-
types: LOCK_TYPES,
|
|
90
|
-
primaryType: "Lock",
|
|
91
|
-
message
|
|
92
|
-
});
|
|
93
|
-
return signature;
|
|
94
|
-
}
|
|
95
|
-
function createLockExpiry(minutesFromNow = 60) {
|
|
96
|
-
return BigInt(Math.floor(Date.now() / 1e3) + minutesFromNow * 60);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// src/sdk/signatures/sign-modify-lock.ts
|
|
100
|
-
async function signModifyLockMessage({
|
|
101
|
-
walletClient,
|
|
102
|
-
chainId,
|
|
103
|
-
verifyingContract,
|
|
104
|
-
message
|
|
105
|
-
}) {
|
|
106
|
-
const account = walletClient.account;
|
|
107
|
-
if (!account) {
|
|
108
|
-
throw new Error("No account connected to wallet client");
|
|
109
|
-
}
|
|
110
|
-
const domain = createDomain(chainId, verifyingContract);
|
|
111
|
-
const signature = await walletClient.signTypedData({
|
|
112
|
-
account,
|
|
113
|
-
domain,
|
|
114
|
-
types: MODIFY_LOCK_TYPES,
|
|
115
|
-
primaryType: "ModifyLock",
|
|
116
|
-
message
|
|
117
|
-
});
|
|
118
|
-
return signature;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/sdk/signatures/sign-transfer.ts
|
|
122
|
-
async function signTransferMessage({
|
|
123
|
-
walletClient,
|
|
124
|
-
chainId,
|
|
125
|
-
verifyingContract,
|
|
126
|
-
message
|
|
127
|
-
}) {
|
|
128
|
-
const account = walletClient.account;
|
|
129
|
-
if (!account) {
|
|
130
|
-
throw new Error("No account connected to wallet client");
|
|
131
|
-
}
|
|
132
|
-
const domain = createDomain(chainId, verifyingContract);
|
|
133
|
-
const signature = await walletClient.signTypedData({
|
|
134
|
-
account,
|
|
135
|
-
domain,
|
|
136
|
-
types: TRANSFER_TYPES,
|
|
137
|
-
primaryType: "Transfer",
|
|
138
|
-
message
|
|
139
|
-
});
|
|
140
|
-
return signature;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// src/sdk/signatures/sign-transfer-locked.ts
|
|
144
|
-
async function signTransferLockedMessage({
|
|
145
|
-
walletClient,
|
|
146
|
-
chainId,
|
|
147
|
-
verifyingContract,
|
|
148
|
-
message
|
|
149
|
-
}) {
|
|
150
|
-
const account = walletClient.account;
|
|
151
|
-
if (!account) {
|
|
152
|
-
throw new Error("No account connected to wallet client");
|
|
153
|
-
}
|
|
154
|
-
const domain = createDomain(chainId, verifyingContract);
|
|
155
|
-
const signature = await walletClient.signTypedData({
|
|
156
|
-
account,
|
|
157
|
-
domain,
|
|
158
|
-
types: TRANSFER_LOCKED_TYPES,
|
|
159
|
-
primaryType: "TransferLocked",
|
|
160
|
-
message
|
|
161
|
-
});
|
|
162
|
-
return signature;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// src/sdk/signatures/sign-withdraw.ts
|
|
166
|
-
async function signWithdrawMessage({
|
|
167
|
-
walletClient,
|
|
168
|
-
chainId,
|
|
169
|
-
verifyingContract,
|
|
170
|
-
message
|
|
171
|
-
}) {
|
|
172
|
-
const account = walletClient.account;
|
|
173
|
-
if (!account) {
|
|
174
|
-
throw new Error("No account connected to wallet client");
|
|
175
|
-
}
|
|
176
|
-
const domain = createDomain(chainId, verifyingContract);
|
|
177
|
-
const signature = await walletClient.signTypedData({
|
|
178
|
-
account,
|
|
179
|
-
domain,
|
|
180
|
-
types: WITHDRAW_TYPES,
|
|
181
|
-
primaryType: "Withdraw",
|
|
182
|
-
message
|
|
183
|
-
});
|
|
184
|
-
return signature;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// src/sdk/signatures/sign-withdraw-from-lock.ts
|
|
188
|
-
async function signWithdrawFromLockMessage({
|
|
189
|
-
walletClient,
|
|
190
|
-
chainId,
|
|
191
|
-
verifyingContract,
|
|
192
|
-
message
|
|
193
|
-
}) {
|
|
194
|
-
const account = walletClient.account;
|
|
195
|
-
if (!account) {
|
|
196
|
-
throw new Error("No account connected to wallet client");
|
|
197
|
-
}
|
|
198
|
-
const domain = createDomain(chainId, verifyingContract);
|
|
199
|
-
const signature = await walletClient.signTypedData({
|
|
200
|
-
account,
|
|
201
|
-
domain,
|
|
202
|
-
types: WITHDRAW_FROM_LOCK_TYPES,
|
|
203
|
-
primaryType: "WithdrawFromLock",
|
|
204
|
-
message
|
|
205
|
-
});
|
|
206
|
-
return signature;
|
|
207
|
-
}
|
|
12
|
+
import { QRCodeSVG } from 'qrcode.react';
|
|
13
|
+
import { MoonPayProvider, MoonPayContext } from '@moonpay/moonpay-react';
|
|
208
14
|
|
|
209
15
|
// src/sdk/hooks/use-privana-client.ts
|
|
210
16
|
function usePrivanaClient() {
|
|
@@ -466,75 +272,22 @@ function useBatchBalances(options) {
|
|
|
466
272
|
refetch: query.refetch
|
|
467
273
|
};
|
|
468
274
|
}
|
|
469
|
-
function useEnsureCorrectChain() {
|
|
470
|
-
const config = useConfig();
|
|
471
|
-
const chainId = useChainId();
|
|
472
|
-
const { switchChainAsync } = useSwitchChain();
|
|
473
|
-
const waitUntilOnChain = useCallback(
|
|
474
|
-
async (expectedChainId, timeoutMs, pollIntervalMs = 250) => {
|
|
475
|
-
const startedAt = Date.now();
|
|
476
|
-
while (Date.now() - startedAt < timeoutMs) {
|
|
477
|
-
const currentChainId = getChainId2(config);
|
|
478
|
-
if (currentChainId === expectedChainId) return true;
|
|
479
|
-
await new Promise((resolve) => setTimeout(resolve, pollIntervalMs));
|
|
480
|
-
}
|
|
481
|
-
return false;
|
|
482
|
-
},
|
|
483
|
-
[config]
|
|
484
|
-
);
|
|
485
|
-
const ensureCorrectChain = useCallback(
|
|
486
|
-
async (targetChainId) => {
|
|
487
|
-
const currentChainId = getChainId2(config);
|
|
488
|
-
if (currentChainId === targetChainId) return false;
|
|
489
|
-
let switchErrorMessage;
|
|
490
|
-
try {
|
|
491
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
492
|
-
setTimeout(() => reject(new Error("Chain switch timeout")), 3e3);
|
|
493
|
-
});
|
|
494
|
-
await Promise.race([switchChainAsync({ chainId: targetChainId }), timeoutPromise]);
|
|
495
|
-
} catch (error) {
|
|
496
|
-
if (error instanceof Error && error.message.includes("Unsupported Chain")) {
|
|
497
|
-
console.warn("Got 'Unsupported Chain' error, chain may have switched anyway.");
|
|
498
|
-
} else {
|
|
499
|
-
switchErrorMessage = error instanceof Error ? error.message : "Unknown chain switch error";
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
const settled = await waitUntilOnChain(targetChainId, 2e4);
|
|
503
|
-
if (settled) return true;
|
|
504
|
-
if (switchErrorMessage) {
|
|
505
|
-
throw new Error(
|
|
506
|
-
`Failed to switch to chain (Chain ID: ${targetChainId}): ${switchErrorMessage}`
|
|
507
|
-
);
|
|
508
|
-
}
|
|
509
|
-
throw new Error(`Chain switch did not settle in time (expected ${targetChainId}).`);
|
|
510
|
-
},
|
|
511
|
-
[config, switchChainAsync, waitUntilOnChain]
|
|
512
|
-
);
|
|
513
|
-
const isOnChain = useCallback((targetChainId) => chainId === targetChainId, [chainId]);
|
|
514
|
-
return {
|
|
515
|
-
chainId,
|
|
516
|
-
ensureCorrectChain,
|
|
517
|
-
isOnChain
|
|
518
|
-
};
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
// src/sdk/hooks/use-deposit.ts
|
|
522
275
|
var STALE_MS = 30 * 60 * 1e3;
|
|
523
276
|
function storageKey(address) {
|
|
524
277
|
return `privana:pending-deposit:${address.toLowerCase()}`;
|
|
525
278
|
}
|
|
526
279
|
function savePendingDeposit(address, data) {
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
280
|
+
const stored = setBrowserStorageItem(storageKey(address), JSON.stringify(data));
|
|
281
|
+
if (!stored && data.signedLock) {
|
|
282
|
+
throw new Error("Unable to persist pending locked deposit for recovery");
|
|
530
283
|
}
|
|
531
284
|
}
|
|
532
285
|
function loadPendingDeposit(address) {
|
|
533
286
|
try {
|
|
534
|
-
const raw =
|
|
287
|
+
const raw = getBrowserStorageItem(storageKey(address));
|
|
535
288
|
if (!raw) return null;
|
|
536
289
|
const data = JSON.parse(raw);
|
|
537
|
-
if (Date.now() - data.savedAt > STALE_MS) {
|
|
290
|
+
if (Date.now() - data.savedAt > STALE_MS && !(data.signedLock && isSignedLockUsable(data.signedLock))) {
|
|
538
291
|
clearPendingDeposit(address);
|
|
539
292
|
return null;
|
|
540
293
|
}
|
|
@@ -543,15 +296,20 @@ function loadPendingDeposit(address) {
|
|
|
543
296
|
return null;
|
|
544
297
|
}
|
|
545
298
|
}
|
|
546
|
-
function clearPendingDeposit(address) {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
299
|
+
function clearPendingDeposit(address, onlyForTxHash) {
|
|
300
|
+
if (onlyForTxHash) {
|
|
301
|
+
try {
|
|
302
|
+
const raw = getBrowserStorageItem(storageKey(address));
|
|
303
|
+
const recordTxHash = raw ? JSON.parse(raw).txHash : void 0;
|
|
304
|
+
if (recordTxHash && recordTxHash !== onlyForTxHash) return;
|
|
305
|
+
} catch {
|
|
306
|
+
}
|
|
550
307
|
}
|
|
308
|
+
removeBrowserStorageItem(storageKey(address));
|
|
551
309
|
}
|
|
552
310
|
function useDeposit(options = {}) {
|
|
553
311
|
const { address } = useAccount();
|
|
554
|
-
const { client, enabledTokens, getChainById: getChainById2 } = usePrivanaContext();
|
|
312
|
+
const { client, enabledTokens, getChainById: getChainById2, networkConfig, serviceAddress } = usePrivanaContext();
|
|
555
313
|
const { data: walletClient } = useWalletClient();
|
|
556
314
|
const queryClient = useQueryClient();
|
|
557
315
|
const config = useConfig();
|
|
@@ -568,21 +326,54 @@ function useDeposit(options = {}) {
|
|
|
568
326
|
const onDepositAddressReceivedRef = useRef(options.onDepositAddressReceived);
|
|
569
327
|
const onDepositSuccessRef = useRef(options.onDepositSuccess);
|
|
570
328
|
const onCreditedRef = useRef(options.onCredited);
|
|
329
|
+
const onLockSubmittedRef = useRef(options.onLockSubmitted);
|
|
330
|
+
const onLockFailedRef = useRef(options.onLockFailed);
|
|
571
331
|
const onCheckTimeoutRef = useRef(options.onCheckTimeout);
|
|
572
332
|
const onErrorRef = useRef(options.onError);
|
|
573
333
|
useEffect(() => {
|
|
574
334
|
onDepositAddressReceivedRef.current = options.onDepositAddressReceived;
|
|
575
335
|
onDepositSuccessRef.current = options.onDepositSuccess;
|
|
576
336
|
onCreditedRef.current = options.onCredited;
|
|
337
|
+
onLockSubmittedRef.current = options.onLockSubmitted;
|
|
338
|
+
onLockFailedRef.current = options.onLockFailed;
|
|
577
339
|
onCheckTimeoutRef.current = options.onCheckTimeout;
|
|
578
340
|
onErrorRef.current = options.onError;
|
|
579
341
|
}, [
|
|
580
342
|
options.onDepositAddressReceived,
|
|
581
343
|
options.onDepositSuccess,
|
|
582
344
|
options.onCredited,
|
|
345
|
+
options.onLockSubmitted,
|
|
346
|
+
options.onLockFailed,
|
|
583
347
|
options.onCheckTimeout,
|
|
584
348
|
options.onError
|
|
585
349
|
]);
|
|
350
|
+
const pendingLockRef = useRef(null);
|
|
351
|
+
const submitPendingLockAfterCredit = useCallback(
|
|
352
|
+
async (signedLock, response) => {
|
|
353
|
+
try {
|
|
354
|
+
const result = await submitPendingLock({
|
|
355
|
+
client,
|
|
356
|
+
payload: signedLock,
|
|
357
|
+
creditedAmount: response.amount != null ? BigInt(response.amount) : void 0
|
|
358
|
+
});
|
|
359
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-balance"] });
|
|
360
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-locked-funds"] });
|
|
361
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-total-locked-balance"] });
|
|
362
|
+
queryClient.invalidateQueries({ queryKey: ["accounting-history"] });
|
|
363
|
+
onLockSubmittedRef.current?.(result);
|
|
364
|
+
} catch (err) {
|
|
365
|
+
const error2 = err instanceof PostDepositLockError ? err : new PostDepositLockError(
|
|
366
|
+
err instanceof Error ? err.message : "Lock submission failed",
|
|
367
|
+
"submission-failed",
|
|
368
|
+
BigInt(signedLock.amount),
|
|
369
|
+
void 0,
|
|
370
|
+
{ cause: err }
|
|
371
|
+
);
|
|
372
|
+
(onLockFailedRef.current ?? onErrorRef.current)?.(error2);
|
|
373
|
+
}
|
|
374
|
+
},
|
|
375
|
+
[client, queryClient]
|
|
376
|
+
);
|
|
586
377
|
const {
|
|
587
378
|
isVerifying,
|
|
588
379
|
didTimeout,
|
|
@@ -595,8 +386,16 @@ function useDeposit(options = {}) {
|
|
|
595
386
|
pollTimeout: options.pollTimeout,
|
|
596
387
|
onCredited: (hash, response) => {
|
|
597
388
|
verificationContextRef.current = null;
|
|
598
|
-
|
|
599
|
-
|
|
389
|
+
const signedLock = pendingLockRef.current;
|
|
390
|
+
pendingLockRef.current = null;
|
|
391
|
+
if (signedLock) {
|
|
392
|
+
void submitPendingLockAfterCredit(signedLock, response).finally(() => {
|
|
393
|
+
if (address) clearPendingDeposit(address, hash);
|
|
394
|
+
});
|
|
395
|
+
} else if (address) {
|
|
396
|
+
clearPendingDeposit(address);
|
|
397
|
+
}
|
|
398
|
+
onCreditedRef.current?.(hash, response, signedLock !== null);
|
|
600
399
|
},
|
|
601
400
|
onCheckTimeout: (hash) => {
|
|
602
401
|
onCheckTimeoutRef.current?.(hash);
|
|
@@ -647,6 +446,7 @@ function useDeposit(options = {}) {
|
|
|
647
446
|
resetVerification();
|
|
648
447
|
if (address) clearPendingDeposit(address);
|
|
649
448
|
verificationContextRef.current = null;
|
|
449
|
+
pendingLockRef.current = null;
|
|
650
450
|
setDepositAddress(null);
|
|
651
451
|
setTxHash(void 0);
|
|
652
452
|
setIsSwitchingChain(false);
|
|
@@ -666,6 +466,7 @@ function useDeposit(options = {}) {
|
|
|
666
466
|
setTxHash(hash);
|
|
667
467
|
setDepositAddress(persisted.depositAddress);
|
|
668
468
|
setIsWaitingForConfirmation(true);
|
|
469
|
+
pendingLockRef.current = persisted.signedLock ?? null;
|
|
669
470
|
const ctx = {
|
|
670
471
|
hash,
|
|
671
472
|
chainId: persisted.chainId,
|
|
@@ -722,13 +523,6 @@ function useDeposit(options = {}) {
|
|
|
722
523
|
if (!sourceChain) throw new Error(`Chain ${token.chainId} not configured`);
|
|
723
524
|
const addrResponse = await addressMutation.mutateAsync();
|
|
724
525
|
if (isStale()) return;
|
|
725
|
-
setIsSwitchingChain(true);
|
|
726
|
-
try {
|
|
727
|
-
await ensureCorrectChain(sourceChain.id);
|
|
728
|
-
} finally {
|
|
729
|
-
if (!isStale()) setIsSwitchingChain(false);
|
|
730
|
-
}
|
|
731
|
-
if (isStale()) return;
|
|
732
526
|
const depositAddr = addrResponse.deposit_address;
|
|
733
527
|
if (!depositAddr || depositAddr === zeroAddress) {
|
|
734
528
|
throw new Error("Invalid deposit address received from API");
|
|
@@ -741,6 +535,43 @@ function useDeposit(options = {}) {
|
|
|
741
535
|
`Amount is below the minimum deposit (${minAmountStr}) for ${isNative ? "native" : "ERC-20"} on chain ${sourceChain.id}`
|
|
742
536
|
);
|
|
743
537
|
}
|
|
538
|
+
if (params.postDepositLock && !canUseBrowserStorage()) {
|
|
539
|
+
throw new Error("Browser storage is required for locked deposit recovery");
|
|
540
|
+
}
|
|
541
|
+
const lockAmount = clampLockAmount(params.amount, params.postDepositLock?.maxAmount);
|
|
542
|
+
let signedLock;
|
|
543
|
+
if (params.postDepositLock) {
|
|
544
|
+
setIsSwitchingChain(true);
|
|
545
|
+
try {
|
|
546
|
+
await ensureCorrectChain(networkConfig.chainId);
|
|
547
|
+
} finally {
|
|
548
|
+
if (!isStale()) setIsSwitchingChain(false);
|
|
549
|
+
}
|
|
550
|
+
if (isStale()) return;
|
|
551
|
+
const signingWalletClient = await getWalletClient(config, {
|
|
552
|
+
chainId: networkConfig.chainId
|
|
553
|
+
});
|
|
554
|
+
signedLock = await createSignedLockRequest({
|
|
555
|
+
client,
|
|
556
|
+
walletClient: signingWalletClient,
|
|
557
|
+
userAddress: address,
|
|
558
|
+
networkConfig,
|
|
559
|
+
serviceAddress: requireServiceAddress(
|
|
560
|
+
params.postDepositLock.serviceAddress ?? serviceAddress
|
|
561
|
+
),
|
|
562
|
+
tokenId: params.tokenId,
|
|
563
|
+
amount: lockAmount,
|
|
564
|
+
lockDuration: params.postDepositLock.lockDuration
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
if (isStale()) return;
|
|
568
|
+
setIsSwitchingChain(true);
|
|
569
|
+
try {
|
|
570
|
+
await ensureCorrectChain(sourceChain.id);
|
|
571
|
+
} finally {
|
|
572
|
+
if (!isStale()) setIsSwitchingChain(false);
|
|
573
|
+
}
|
|
574
|
+
if (isStale()) return;
|
|
744
575
|
const hash = token.contract === zeroAddress ? await sendTransactionAsync({
|
|
745
576
|
to: depositAddr,
|
|
746
577
|
value: params.amount,
|
|
@@ -760,13 +591,19 @@ function useDeposit(options = {}) {
|
|
|
760
591
|
amount: params.amount
|
|
761
592
|
};
|
|
762
593
|
verificationContextRef.current = ctx;
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
594
|
+
pendingLockRef.current = signedLock ?? null;
|
|
595
|
+
try {
|
|
596
|
+
savePendingDeposit(address, {
|
|
597
|
+
txHash: hash,
|
|
598
|
+
chainId: sourceChain.id,
|
|
599
|
+
amount: params.amount.toString(),
|
|
600
|
+
depositAddress: addrResponse,
|
|
601
|
+
signedLock,
|
|
602
|
+
savedAt: Date.now()
|
|
603
|
+
});
|
|
604
|
+
} catch (err) {
|
|
605
|
+
console.warn("Failed to persist pending deposit after transfer broadcast:", err);
|
|
606
|
+
}
|
|
770
607
|
try {
|
|
771
608
|
setIsWaitingForConfirmation(true);
|
|
772
609
|
try {
|
|
@@ -801,12 +638,15 @@ function useDeposit(options = {}) {
|
|
|
801
638
|
address,
|
|
802
639
|
walletClient,
|
|
803
640
|
addressMutation,
|
|
641
|
+
client,
|
|
804
642
|
getChainById2,
|
|
805
643
|
config,
|
|
806
644
|
confirmations,
|
|
807
645
|
enabledTokens,
|
|
808
646
|
ensureCorrectChain,
|
|
647
|
+
networkConfig,
|
|
809
648
|
queryClient,
|
|
649
|
+
serviceAddress,
|
|
810
650
|
writeContractAsync,
|
|
811
651
|
sendTransactionAsync,
|
|
812
652
|
reset,
|
|
@@ -841,6 +681,37 @@ function useDeposit(options = {}) {
|
|
|
841
681
|
reset
|
|
842
682
|
};
|
|
843
683
|
}
|
|
684
|
+
function requireServiceAddress(serviceAddress) {
|
|
685
|
+
if (!serviceAddress) {
|
|
686
|
+
throw new Error("Service address not configured");
|
|
687
|
+
}
|
|
688
|
+
return serviceAddress;
|
|
689
|
+
}
|
|
690
|
+
function useDepositAddress(options = {}) {
|
|
691
|
+
const queryClient = useContext(QueryClientContext);
|
|
692
|
+
const accountingContext = useSafePrivanaContext();
|
|
693
|
+
const hasProviders = !!queryClient && !!accountingContext;
|
|
694
|
+
const client = accountingContext?.client;
|
|
695
|
+
const { executePrivateRead, privateReadAddress, privateReadQueryScope, privateReadReady } = usePrivateReadRequest();
|
|
696
|
+
const isReady = hasProviders && privateReadReady && !!privateReadAddress && !!client;
|
|
697
|
+
const query = useQuery({
|
|
698
|
+
queryKey: ["accounting-deposit-address", ...privateReadQueryScope],
|
|
699
|
+
queryFn: async () => {
|
|
700
|
+
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
701
|
+
if (!client) throw new Error("No accounting client");
|
|
702
|
+
return executePrivateRead(() => client.getDepositAddress());
|
|
703
|
+
},
|
|
704
|
+
enabled: isReady && (options.enabled ?? true)
|
|
705
|
+
});
|
|
706
|
+
return {
|
|
707
|
+
depositAddress: query.data?.deposit_address ?? null,
|
|
708
|
+
isReady,
|
|
709
|
+
isLoading: query.isLoading,
|
|
710
|
+
isError: query.isError,
|
|
711
|
+
error: query.error,
|
|
712
|
+
refetch: query.refetch
|
|
713
|
+
};
|
|
714
|
+
}
|
|
844
715
|
function useWithdraw(options = {}) {
|
|
845
716
|
const { address } = useAccount();
|
|
846
717
|
const { data: walletClient } = useWalletClient();
|
|
@@ -1343,6 +1214,59 @@ function useExpiredLocks(options = {}) {
|
|
|
1343
1214
|
refetch: query.refetch
|
|
1344
1215
|
};
|
|
1345
1216
|
}
|
|
1217
|
+
function statusCodeOf(error) {
|
|
1218
|
+
return error instanceof AccountingApiError ? error.statusCode : void 0;
|
|
1219
|
+
}
|
|
1220
|
+
function usePendingDeposits(options = {}) {
|
|
1221
|
+
const queryClient = useContext(QueryClientContext);
|
|
1222
|
+
const accountingContext = useSafePrivanaContext();
|
|
1223
|
+
const hasProviders = !!queryClient && !!accountingContext;
|
|
1224
|
+
const client = accountingContext?.client;
|
|
1225
|
+
const { executePrivateRead, privateReadAddress, privateReadQueryScope, privateReadReady } = usePrivateReadRequest();
|
|
1226
|
+
const { chainId, tokenAddress, lookbackBlocks } = options;
|
|
1227
|
+
const query = useQuery({
|
|
1228
|
+
queryKey: ["accounting-pending-deposits", ...privateReadQueryScope, chainId, tokenAddress],
|
|
1229
|
+
queryFn: async () => {
|
|
1230
|
+
if (!privateReadAddress) throw new Error("No authenticated account available");
|
|
1231
|
+
if (!chainId) throw new Error("No chain ID provided");
|
|
1232
|
+
if (!client) throw new Error("No accounting client");
|
|
1233
|
+
try {
|
|
1234
|
+
return await executePrivateRead(
|
|
1235
|
+
() => client.getPendingDeposits({
|
|
1236
|
+
chain_id: chainId,
|
|
1237
|
+
token_address: tokenAddress,
|
|
1238
|
+
lookback_blocks: lookbackBlocks
|
|
1239
|
+
})
|
|
1240
|
+
);
|
|
1241
|
+
} catch (error) {
|
|
1242
|
+
if (statusCodeOf(error) === 404) {
|
|
1243
|
+
return { pending: [], scanned_from_block: 0, scanned_to_block: 0 };
|
|
1244
|
+
}
|
|
1245
|
+
throw error;
|
|
1246
|
+
}
|
|
1247
|
+
},
|
|
1248
|
+
enabled: hasProviders && (options.enabled ?? true) && privateReadReady && !!privateReadAddress && !!chainId && !!client,
|
|
1249
|
+
// A 503 means discovery is not configured for this chain server-side —
|
|
1250
|
+
// polling can't fix that, so stop until a manual refetch succeeds.
|
|
1251
|
+
refetchInterval: (query2) => statusCodeOf(query2.state.error) === 503 ? false : options.refetchInterval ?? 3e4,
|
|
1252
|
+
staleTime: 25e3,
|
|
1253
|
+
retry: (failureCount, error) => {
|
|
1254
|
+
const status = statusCodeOf(error);
|
|
1255
|
+
if (status === 429 || status === 404 || status === 503) return false;
|
|
1256
|
+
return failureCount < 2;
|
|
1257
|
+
}
|
|
1258
|
+
});
|
|
1259
|
+
return {
|
|
1260
|
+
pending: query.data?.pending ?? [],
|
|
1261
|
+
scannedToBlock: query.data?.scanned_to_block,
|
|
1262
|
+
isFetching: query.isFetching,
|
|
1263
|
+
isError: query.isError,
|
|
1264
|
+
error: query.error,
|
|
1265
|
+
isRateLimited: statusCodeOf(query.error) === 429,
|
|
1266
|
+
isUnavailable: statusCodeOf(query.error) === 503,
|
|
1267
|
+
refetch: async () => (await query.refetch()).data
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1346
1270
|
function usePendingWithdrawals(options = {}) {
|
|
1347
1271
|
const { address, isConnected } = useAccount();
|
|
1348
1272
|
const { client, pollingInterval } = usePrivanaContext();
|
|
@@ -1589,19 +1513,6 @@ function DialogDescription({
|
|
|
1589
1513
|
}
|
|
1590
1514
|
);
|
|
1591
1515
|
}
|
|
1592
|
-
function ChevronRightIcon() {
|
|
1593
|
-
return /* @__PURE__ */ jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "-rotate-90", children: /* @__PURE__ */ jsx(
|
|
1594
|
-
"path",
|
|
1595
|
-
{
|
|
1596
|
-
d: "M0 0l6 6 6-6",
|
|
1597
|
-
stroke: "currentColor",
|
|
1598
|
-
strokeWidth: "1.5",
|
|
1599
|
-
strokeLinecap: "round",
|
|
1600
|
-
strokeLinejoin: "round",
|
|
1601
|
-
fill: "none"
|
|
1602
|
-
}
|
|
1603
|
-
) });
|
|
1604
|
-
}
|
|
1605
1516
|
function USDCIcon({ className, size = 24 }) {
|
|
1606
1517
|
return /* @__PURE__ */ jsxs(
|
|
1607
1518
|
"svg",
|
|
@@ -1743,6 +1654,135 @@ function getChainIcon(chainId, size) {
|
|
|
1743
1654
|
);
|
|
1744
1655
|
}
|
|
1745
1656
|
}
|
|
1657
|
+
function CloseIcon() {
|
|
1658
|
+
return /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx(
|
|
1659
|
+
"path",
|
|
1660
|
+
{
|
|
1661
|
+
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",
|
|
1662
|
+
stroke: "currentColor",
|
|
1663
|
+
strokeWidth: "1.5",
|
|
1664
|
+
strokeLinecap: "round",
|
|
1665
|
+
strokeLinejoin: "round"
|
|
1666
|
+
}
|
|
1667
|
+
) });
|
|
1668
|
+
}
|
|
1669
|
+
function ChevronRightIcon() {
|
|
1670
|
+
return /* @__PURE__ */ jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "-rotate-90", children: /* @__PURE__ */ jsx(
|
|
1671
|
+
"path",
|
|
1672
|
+
{
|
|
1673
|
+
d: "M0 0l6 6 6-6",
|
|
1674
|
+
stroke: "currentColor",
|
|
1675
|
+
strokeWidth: "1.5",
|
|
1676
|
+
strokeLinecap: "round",
|
|
1677
|
+
strokeLinejoin: "round",
|
|
1678
|
+
fill: "none"
|
|
1679
|
+
}
|
|
1680
|
+
) });
|
|
1681
|
+
}
|
|
1682
|
+
function ChevronLeftIcon() {
|
|
1683
|
+
return /* @__PURE__ */ jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "rotate-90", children: /* @__PURE__ */ jsx(
|
|
1684
|
+
"path",
|
|
1685
|
+
{
|
|
1686
|
+
d: "M0 0l6 6 6-6",
|
|
1687
|
+
stroke: "currentColor",
|
|
1688
|
+
strokeWidth: "1.5",
|
|
1689
|
+
strokeLinecap: "round",
|
|
1690
|
+
strokeLinejoin: "round",
|
|
1691
|
+
fill: "none"
|
|
1692
|
+
}
|
|
1693
|
+
) });
|
|
1694
|
+
}
|
|
1695
|
+
function ChevronDownIcon({
|
|
1696
|
+
direction = "down",
|
|
1697
|
+
className
|
|
1698
|
+
}) {
|
|
1699
|
+
return /* @__PURE__ */ jsx(
|
|
1700
|
+
"svg",
|
|
1701
|
+
{
|
|
1702
|
+
width: "16",
|
|
1703
|
+
height: "16",
|
|
1704
|
+
viewBox: "0 0 16 16",
|
|
1705
|
+
fill: "none",
|
|
1706
|
+
className: cn(
|
|
1707
|
+
"transition-transform",
|
|
1708
|
+
direction === "right" && "-rotate-90",
|
|
1709
|
+
direction === "up" && "rotate-180",
|
|
1710
|
+
className
|
|
1711
|
+
),
|
|
1712
|
+
children: /* @__PURE__ */ jsx(
|
|
1713
|
+
"path",
|
|
1714
|
+
{
|
|
1715
|
+
d: "M4 6l4 4 4-4",
|
|
1716
|
+
stroke: "currentColor",
|
|
1717
|
+
strokeWidth: "1.5",
|
|
1718
|
+
strokeLinecap: "round",
|
|
1719
|
+
strokeLinejoin: "round"
|
|
1720
|
+
}
|
|
1721
|
+
)
|
|
1722
|
+
}
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
function CopyIcon() {
|
|
1726
|
+
return /* @__PURE__ */ jsxs(
|
|
1727
|
+
"svg",
|
|
1728
|
+
{
|
|
1729
|
+
width: "16",
|
|
1730
|
+
height: "16",
|
|
1731
|
+
viewBox: "0 0 24 24",
|
|
1732
|
+
fill: "none",
|
|
1733
|
+
stroke: "currentColor",
|
|
1734
|
+
strokeWidth: "2",
|
|
1735
|
+
strokeLinecap: "round",
|
|
1736
|
+
strokeLinejoin: "round",
|
|
1737
|
+
children: [
|
|
1738
|
+
/* @__PURE__ */ jsx("rect", { x: "9", y: "9", width: "13", height: "13", rx: "2", ry: "2" }),
|
|
1739
|
+
/* @__PURE__ */ jsx("path", { d: "M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" })
|
|
1740
|
+
]
|
|
1741
|
+
}
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
function CircleCheckIcon() {
|
|
1745
|
+
return /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
|
|
1746
|
+
/* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "9", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
1747
|
+
/* @__PURE__ */ jsx(
|
|
1748
|
+
"path",
|
|
1749
|
+
{
|
|
1750
|
+
d: "M6 10l2.5 2.5L14 7.5",
|
|
1751
|
+
stroke: "currentColor",
|
|
1752
|
+
strokeWidth: "1.5",
|
|
1753
|
+
strokeLinecap: "round",
|
|
1754
|
+
strokeLinejoin: "round"
|
|
1755
|
+
}
|
|
1756
|
+
)
|
|
1757
|
+
] });
|
|
1758
|
+
}
|
|
1759
|
+
function CircleXIcon() {
|
|
1760
|
+
return /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [
|
|
1761
|
+
/* @__PURE__ */ jsx("circle", { cx: "10", cy: "10", r: "9", stroke: "currentColor", strokeWidth: "1.5" }),
|
|
1762
|
+
/* @__PURE__ */ jsx(
|
|
1763
|
+
"path",
|
|
1764
|
+
{
|
|
1765
|
+
d: "M7 7l6 6M13 7l-6 6",
|
|
1766
|
+
stroke: "currentColor",
|
|
1767
|
+
strokeWidth: "1.5",
|
|
1768
|
+
strokeLinecap: "round",
|
|
1769
|
+
strokeLinejoin: "round"
|
|
1770
|
+
}
|
|
1771
|
+
)
|
|
1772
|
+
] });
|
|
1773
|
+
}
|
|
1774
|
+
function ArrowRightIcon({ className }) {
|
|
1775
|
+
return /* @__PURE__ */ jsx("svg", { width: "24", height: "8", viewBox: "0 0 24 8", fill: "none", className, children: /* @__PURE__ */ jsx(
|
|
1776
|
+
"path",
|
|
1777
|
+
{
|
|
1778
|
+
d: "M0 4h22m0 0l-4-4m4 4l-4 4",
|
|
1779
|
+
stroke: "currentColor",
|
|
1780
|
+
strokeWidth: "1.5",
|
|
1781
|
+
strokeLinecap: "round",
|
|
1782
|
+
strokeLinejoin: "round"
|
|
1783
|
+
}
|
|
1784
|
+
) });
|
|
1785
|
+
}
|
|
1746
1786
|
function CheckIcon() {
|
|
1747
1787
|
return /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx(
|
|
1748
1788
|
"path",
|
|
@@ -2297,7 +2337,7 @@ function WithdrawForm({
|
|
|
2297
2337
|
title: "Withdrawal Complete",
|
|
2298
2338
|
message: `Your ${selectedToken.symbol} withdrawal has been processed. Funds should appear in your wallet shortly.`,
|
|
2299
2339
|
explorerUrl,
|
|
2300
|
-
explorerLabel:
|
|
2340
|
+
explorerLabel: targetChain ? getExplorerLabel(targetChain.id) : void 0,
|
|
2301
2341
|
onDone: handleDone
|
|
2302
2342
|
}
|
|
2303
2343
|
);
|
|
@@ -2405,66 +2445,6 @@ function WithdrawForm({
|
|
|
2405
2445
|
)
|
|
2406
2446
|
] });
|
|
2407
2447
|
}
|
|
2408
|
-
function CloseIcon() {
|
|
2409
|
-
return /* @__PURE__ */ jsx("svg", { width: "10", height: "10", viewBox: "0 0 12 12", fill: "none", children: /* @__PURE__ */ jsx(
|
|
2410
|
-
"path",
|
|
2411
|
-
{
|
|
2412
|
-
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",
|
|
2413
|
-
stroke: "currentColor",
|
|
2414
|
-
strokeWidth: "1.5",
|
|
2415
|
-
strokeLinecap: "round",
|
|
2416
|
-
strokeLinejoin: "round"
|
|
2417
|
-
}
|
|
2418
|
-
) });
|
|
2419
|
-
}
|
|
2420
|
-
function ChevronRight() {
|
|
2421
|
-
return /* @__PURE__ */ jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "-rotate-90", children: /* @__PURE__ */ jsx(
|
|
2422
|
-
"path",
|
|
2423
|
-
{
|
|
2424
|
-
d: "M0 0l6 6 6-6",
|
|
2425
|
-
stroke: "currentColor",
|
|
2426
|
-
strokeWidth: "1.5",
|
|
2427
|
-
strokeLinecap: "round",
|
|
2428
|
-
strokeLinejoin: "round",
|
|
2429
|
-
fill: "none"
|
|
2430
|
-
}
|
|
2431
|
-
) });
|
|
2432
|
-
}
|
|
2433
|
-
function ChevronLeft() {
|
|
2434
|
-
return /* @__PURE__ */ jsx("svg", { width: "10", height: "5", viewBox: "0 0 12 6", className: "rotate-90", children: /* @__PURE__ */ jsx(
|
|
2435
|
-
"path",
|
|
2436
|
-
{
|
|
2437
|
-
d: "M0 0l6 6 6-6",
|
|
2438
|
-
stroke: "currentColor",
|
|
2439
|
-
strokeWidth: "1.5",
|
|
2440
|
-
strokeLinecap: "round",
|
|
2441
|
-
strokeLinejoin: "round",
|
|
2442
|
-
fill: "none"
|
|
2443
|
-
}
|
|
2444
|
-
) });
|
|
2445
|
-
}
|
|
2446
|
-
function ChevronDown({ collapsed }) {
|
|
2447
|
-
return /* @__PURE__ */ jsx(
|
|
2448
|
-
"svg",
|
|
2449
|
-
{
|
|
2450
|
-
width: "12",
|
|
2451
|
-
height: "6",
|
|
2452
|
-
viewBox: "0 0 12 6",
|
|
2453
|
-
className: cn("transition-transform", collapsed && "-rotate-90"),
|
|
2454
|
-
children: /* @__PURE__ */ jsx(
|
|
2455
|
-
"path",
|
|
2456
|
-
{
|
|
2457
|
-
d: "M0 0l6 6 6-6",
|
|
2458
|
-
stroke: "currentColor",
|
|
2459
|
-
strokeWidth: "1.5",
|
|
2460
|
-
strokeLinecap: "round",
|
|
2461
|
-
strokeLinejoin: "round",
|
|
2462
|
-
fill: "none"
|
|
2463
|
-
}
|
|
2464
|
-
)
|
|
2465
|
-
}
|
|
2466
|
-
);
|
|
2467
|
-
}
|
|
2468
2448
|
function BalanceCards({
|
|
2469
2449
|
selectedToken,
|
|
2470
2450
|
onLockedFundsClick,
|
|
@@ -2494,7 +2474,7 @@ function BalanceCards({
|
|
|
2494
2474
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Balance" }),
|
|
2495
2475
|
/* @__PURE__ */ jsx("span", { className: "bg-secondary text-muted-foreground rounded-full px-2 py-[5px] text-[10px] font-bold", children: selectedToken.symbol })
|
|
2496
2476
|
] }),
|
|
2497
|
-
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-6 w-6 items-center justify-center", children: /* @__PURE__ */ jsx(
|
|
2477
|
+
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-6 w-6 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
|
|
2498
2478
|
] }),
|
|
2499
2479
|
/* @__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 })
|
|
2500
2480
|
]
|
|
@@ -2515,7 +2495,7 @@ function BalanceCards({
|
|
|
2515
2495
|
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: "Locked Funds" }),
|
|
2516
2496
|
/* @__PURE__ */ jsx("span", { className: "bg-secondary text-muted-foreground rounded-full px-2 py-[5px] text-[10px] font-bold", children: selectedToken.symbol })
|
|
2517
2497
|
] }),
|
|
2518
|
-
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-6 w-6 items-center justify-center", children: /* @__PURE__ */ jsx(
|
|
2498
|
+
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-6 w-6 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
|
|
2519
2499
|
] }),
|
|
2520
2500
|
/* @__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 })
|
|
2521
2501
|
]
|
|
@@ -2614,7 +2594,7 @@ function LockedFundsView({ onBack }) {
|
|
|
2614
2594
|
{
|
|
2615
2595
|
onClick: onBack,
|
|
2616
2596
|
className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
2617
|
-
children: /* @__PURE__ */ jsx(
|
|
2597
|
+
children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
|
|
2618
2598
|
}
|
|
2619
2599
|
),
|
|
2620
2600
|
/* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Locked Funds" })
|
|
@@ -2634,7 +2614,12 @@ function LockedFundsView({ onBack }) {
|
|
|
2634
2614
|
className: "text-muted-foreground hover:bg-secondary flex w-full cursor-pointer items-center justify-between rounded-lg px-4 py-4 transition-colors",
|
|
2635
2615
|
children: [
|
|
2636
2616
|
/* @__PURE__ */ jsx("span", { className: "text-sm", children: section.title }),
|
|
2637
|
-
/* @__PURE__ */ jsx(
|
|
2617
|
+
/* @__PURE__ */ jsx(
|
|
2618
|
+
ChevronDownIcon,
|
|
2619
|
+
{
|
|
2620
|
+
direction: collapsedSections[section.title] ? "right" : "down"
|
|
2621
|
+
}
|
|
2622
|
+
)
|
|
2638
2623
|
]
|
|
2639
2624
|
}
|
|
2640
2625
|
),
|
|
@@ -2716,7 +2701,7 @@ function BalanceDetailsView({ onBack }) {
|
|
|
2716
2701
|
{
|
|
2717
2702
|
onClick: onBack,
|
|
2718
2703
|
className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
2719
|
-
children: /* @__PURE__ */ jsx(
|
|
2704
|
+
children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
|
|
2720
2705
|
}
|
|
2721
2706
|
),
|
|
2722
2707
|
/* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Token Balances" })
|
|
@@ -2809,7 +2794,7 @@ function TokenSelectorView({
|
|
|
2809
2794
|
{
|
|
2810
2795
|
onClick: onBack,
|
|
2811
2796
|
className: "text-muted-foreground hover:text-foreground -ml-2 flex h-6 w-6 cursor-pointer items-center justify-center transition-colors",
|
|
2812
|
-
children: /* @__PURE__ */ jsx(
|
|
2797
|
+
children: /* @__PURE__ */ jsx(ChevronLeftIcon, {})
|
|
2813
2798
|
}
|
|
2814
2799
|
),
|
|
2815
2800
|
/* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-6 font-medium", children: "Select Token" })
|
|
@@ -3104,7 +3089,1815 @@ function PrivanaButton({
|
|
|
3104
3089
|
)
|
|
3105
3090
|
] });
|
|
3106
3091
|
}
|
|
3092
|
+
var DEFAULT_MOONPAY_API_URL = "https://api.moonpay.com/v3";
|
|
3093
|
+
function useMoonpayLimits({
|
|
3094
|
+
currencyCode,
|
|
3095
|
+
baseCurrencyCode = "usd",
|
|
3096
|
+
paymentMethod = "credit_debit_card",
|
|
3097
|
+
apiBaseUrl = DEFAULT_MOONPAY_API_URL,
|
|
3098
|
+
enabled = true
|
|
3099
|
+
}) {
|
|
3100
|
+
const { apiKey } = useContext(MoonPayContext) ?? {};
|
|
3101
|
+
const canFetch = enabled && !!apiKey && !!currencyCode;
|
|
3102
|
+
const query = useQuery({
|
|
3103
|
+
queryKey: ["moonpay-limits", apiBaseUrl, currencyCode, baseCurrencyCode, paymentMethod, apiKey],
|
|
3104
|
+
enabled: canFetch,
|
|
3105
|
+
staleTime: 5 * 6e4,
|
|
3106
|
+
queryFn: async () => {
|
|
3107
|
+
const params = new URLSearchParams({
|
|
3108
|
+
apiKey,
|
|
3109
|
+
baseCurrencyCode,
|
|
3110
|
+
paymentMethod,
|
|
3111
|
+
areFeesIncluded: "true"
|
|
3112
|
+
});
|
|
3113
|
+
const res = await fetch(
|
|
3114
|
+
`${apiBaseUrl}/currencies/${currencyCode}/limits?${params.toString()}`
|
|
3115
|
+
);
|
|
3116
|
+
if (!res.ok) {
|
|
3117
|
+
throw new Error(`MoonPay limits request failed: ${res.status}`);
|
|
3118
|
+
}
|
|
3119
|
+
const data = await res.json();
|
|
3120
|
+
const asNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
3121
|
+
return {
|
|
3122
|
+
minBuyAmount: asNumber(data.baseCurrency?.minBuyAmount),
|
|
3123
|
+
maxBuyAmount: asNumber(data.baseCurrency?.maxBuyAmount)
|
|
3124
|
+
};
|
|
3125
|
+
}
|
|
3126
|
+
});
|
|
3127
|
+
return {
|
|
3128
|
+
minBuyAmount: query.data?.minBuyAmount,
|
|
3129
|
+
maxBuyAmount: query.data?.maxBuyAmount,
|
|
3130
|
+
isLoading: query.isLoading,
|
|
3131
|
+
error: query.error ?? null
|
|
3132
|
+
};
|
|
3133
|
+
}
|
|
3134
|
+
function TokenSelectorView2({ selectedTokenId, onSelect }) {
|
|
3135
|
+
const { enabledTokens } = usePrivanaContext();
|
|
3136
|
+
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: [
|
|
3137
|
+
enabledTokens.map((token) => {
|
|
3138
|
+
const isSelected = selectedTokenId === token.id;
|
|
3139
|
+
return /* @__PURE__ */ jsxs(
|
|
3140
|
+
"button",
|
|
3141
|
+
{
|
|
3142
|
+
type: "button",
|
|
3143
|
+
onClick: () => onSelect(token.id),
|
|
3144
|
+
className: cn(
|
|
3145
|
+
"hover:bg-secondary flex w-full cursor-pointer items-center gap-3 rounded-lg px-3 py-2 text-left transition-colors",
|
|
3146
|
+
isSelected && "bg-secondary"
|
|
3147
|
+
),
|
|
3148
|
+
children: [
|
|
3149
|
+
/* @__PURE__ */ jsx("div", { className: "h-6 w-6 shrink-0 overflow-hidden rounded-full", children: getTokenIcon(token.symbol, 24) }),
|
|
3150
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
3151
|
+
/* @__PURE__ */ jsx("div", { className: "text-foreground text-sm font-medium", children: token.symbol }),
|
|
3152
|
+
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground text-[11px]", children: token.name })
|
|
3153
|
+
] }),
|
|
3154
|
+
isSelected && /* @__PURE__ */ jsx("div", { className: "bg-primary flex h-4 w-4 shrink-0 items-center justify-center rounded-full", children: /* @__PURE__ */ jsx(
|
|
3155
|
+
"svg",
|
|
3156
|
+
{
|
|
3157
|
+
width: "10",
|
|
3158
|
+
height: "10",
|
|
3159
|
+
viewBox: "0 0 10 10",
|
|
3160
|
+
fill: "none",
|
|
3161
|
+
className: "text-primary-foreground",
|
|
3162
|
+
children: /* @__PURE__ */ jsx(
|
|
3163
|
+
"path",
|
|
3164
|
+
{
|
|
3165
|
+
d: "M2 5L4 7L8 3",
|
|
3166
|
+
stroke: "currentColor",
|
|
3167
|
+
strokeWidth: "1.5",
|
|
3168
|
+
strokeLinecap: "round",
|
|
3169
|
+
strokeLinejoin: "round"
|
|
3170
|
+
}
|
|
3171
|
+
)
|
|
3172
|
+
}
|
|
3173
|
+
) })
|
|
3174
|
+
]
|
|
3175
|
+
},
|
|
3176
|
+
token.id
|
|
3177
|
+
);
|
|
3178
|
+
}),
|
|
3179
|
+
enabledTokens.length === 0 && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground px-3 py-6 text-center text-sm", children: "No tokens available" })
|
|
3180
|
+
] }) });
|
|
3181
|
+
}
|
|
3182
|
+
function CreditCardWidgetView({
|
|
3183
|
+
token,
|
|
3184
|
+
amount,
|
|
3185
|
+
allowance,
|
|
3186
|
+
onCredited,
|
|
3187
|
+
onLockSubmitted,
|
|
3188
|
+
onLockFailed
|
|
3189
|
+
}) {
|
|
3190
|
+
const moonpayCurrencyCode = token?.moonpayCurrencyCode;
|
|
3191
|
+
const containerRef = useRef(null);
|
|
3192
|
+
const [widgetTheme, setWidgetTheme] = useState();
|
|
3193
|
+
useEffect(() => {
|
|
3194
|
+
if (containerRef.current) setWidgetTheme(resolveSdkTheme(containerRef.current));
|
|
3195
|
+
}, []);
|
|
3196
|
+
return /* @__PURE__ */ jsxs("div", { ref: containerRef, className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
3197
|
+
/* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Complete your purchase" }),
|
|
3198
|
+
token && moonpayCurrencyCode ? /* @__PURE__ */ jsx(
|
|
3199
|
+
FiatOnRampForm,
|
|
3200
|
+
{
|
|
3201
|
+
tokenId: token.id,
|
|
3202
|
+
currencyCode: moonpayCurrencyCode,
|
|
3203
|
+
quoteCurrencyAmount: amount || void 0,
|
|
3204
|
+
lockAmount: true,
|
|
3205
|
+
variant: "embedded",
|
|
3206
|
+
autoStart: true,
|
|
3207
|
+
theme: widgetTheme,
|
|
3208
|
+
postDepositLock: allowance ? {
|
|
3209
|
+
maxAmount: BigInt(allowance.value),
|
|
3210
|
+
lockDuration: allowance.lockDuration
|
|
3211
|
+
} : void 0,
|
|
3212
|
+
onCredited,
|
|
3213
|
+
onLockSubmitted,
|
|
3214
|
+
onLockFailed
|
|
3215
|
+
}
|
|
3216
|
+
) : /* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-sm", children: [
|
|
3217
|
+
token?.symbol ?? "This token",
|
|
3218
|
+
" isn\u2019t available for card purchases yet."
|
|
3219
|
+
] })
|
|
3220
|
+
] });
|
|
3221
|
+
}
|
|
3222
|
+
function resolveSdkTheme(el) {
|
|
3223
|
+
const scheme = getComputedStyle(el).colorScheme;
|
|
3224
|
+
if (scheme === "dark") return "dark";
|
|
3225
|
+
if (scheme === "light") return "light";
|
|
3226
|
+
return el.closest(".dark") ? "dark" : "light";
|
|
3227
|
+
}
|
|
3228
|
+
function PrivanaIcon({ className, size = 32 }) {
|
|
3229
|
+
return /* @__PURE__ */ jsxs(
|
|
3230
|
+
"svg",
|
|
3231
|
+
{
|
|
3232
|
+
width: size,
|
|
3233
|
+
height: size,
|
|
3234
|
+
viewBox: "0 0 32 32",
|
|
3235
|
+
fill: "none",
|
|
3236
|
+
xmlns: "http://www.w3.org/2000/svg",
|
|
3237
|
+
className,
|
|
3238
|
+
children: [
|
|
3239
|
+
/* @__PURE__ */ jsx("rect", { width: "32", height: "32", rx: "3.55556", fill: "black" }),
|
|
3240
|
+
/* @__PURE__ */ jsx(
|
|
3241
|
+
"path",
|
|
3242
|
+
{
|
|
3243
|
+
d: "M9.02789 9.05299L9.20884 7.82227H15.6221L16.1099 7.82683L16.5817 7.83825L17.0398 7.85652L17.4842 7.88163L17.9125 7.91588L18.3271 7.95698L18.7256 8.00722L19.1104 8.06202L19.4814 8.12595L19.8823 8.20815L20.2602 8.2972L20.6175 8.39538L20.9542 8.50042L21.2703 8.61687L21.5657 8.74017L21.8406 8.8726L22.0948 9.01645L22.1521 9.05299H9.02789ZM9.02789 12.0784L9.20884 10.8477H21.9505L22.1177 10.955L22.317 11.1057L22.4934 11.2633L22.6445 11.4322L22.7705 11.608L22.8713 11.793L22.9491 11.9871L22.9721 12.0784H9.02789ZM15.8167 11.0372H13.1369L13.0843 12.0784H18.8447L18.8264 12.0213L18.7645 11.9072L18.6844 11.7976L18.5836 11.6948L18.4645 11.6012L18.3271 11.5121L18.1736 11.4299L18.0041 11.3569L17.8209 11.2907L17.6193 11.2313L17.404 11.1811L17.1727 11.1354L16.9276 11.1011L16.6711 11.0737L16.3985 11.0532L16.1145 11.0418L15.8167 11.0372ZM9.02789 15.1038L9.20884 13.8731H22.7453L22.7728 13.9759L22.8003 14.1334L22.8232 14.3252L22.8163 14.5102L22.7819 14.6905L22.7178 14.8664L22.6239 15.0376L22.5735 15.1038H9.02789ZM13.1484 13.8731L13.0843 15.1038H17.5323L17.6674 15.0696L17.8644 15.0079L18.043 14.9372L18.2034 14.8618L18.3454 14.7796L18.4691 14.6883L18.5744 14.5946L18.6569 14.4942L18.721 14.3914L18.7622 14.2841L18.7828 14.1722V14.0558L18.7622 13.9348L18.7416 13.8731H13.1484ZM9.02789 18.1293L9.20884 16.8985H22.3605L22.2895 16.9899L22.1406 17.1406L21.9643 17.2867L21.7581 17.426L21.5268 17.5561L21.2703 17.6817L20.9862 17.7982L20.6747 17.9055L20.3381 18.006L19.9739 18.0996L19.8387 18.1293H9.02789ZM13.1484 16.8985L13.1392 17.0629H15.8144L16.1145 17.0607L16.3985 17.047L16.6665 17.0264L16.9207 16.999L17.1612 16.9625L17.388 16.9168L17.4658 16.8985H13.1484ZM9.02789 21.1547L9.20884 19.9217H19.7036L19.4562 19.9742L19.0989 20.0381L18.7256 20.0952L18.3316 20.1432L17.9239 20.1843L17.4956 20.2185L17.0536 20.2459L16.5932 20.2642L16.1168 20.2756L15.6243 20.2802H13.1301L13.0843 21.1547H9.02789ZM13.0843 24.1778H9.02789L9.20884 22.9471H13.1484L13.0843 24.1778Z",
|
|
3244
|
+
fill: "white"
|
|
3245
|
+
}
|
|
3246
|
+
)
|
|
3247
|
+
]
|
|
3248
|
+
}
|
|
3249
|
+
);
|
|
3250
|
+
}
|
|
3251
|
+
function PolicyTermRow({
|
|
3252
|
+
term,
|
|
3253
|
+
kind
|
|
3254
|
+
}) {
|
|
3255
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
3256
|
+
/* @__PURE__ */ jsx(
|
|
3257
|
+
"div",
|
|
3258
|
+
{
|
|
3259
|
+
className: cn(
|
|
3260
|
+
"mt-0.5 shrink-0",
|
|
3261
|
+
kind === "permission" ? "text-emerald-500" : "text-orange-500"
|
|
3262
|
+
),
|
|
3263
|
+
children: kind === "permission" ? /* @__PURE__ */ jsx(CircleCheckIcon, {}) : /* @__PURE__ */ jsx(CircleXIcon, {})
|
|
3264
|
+
}
|
|
3265
|
+
),
|
|
3266
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
3267
|
+
/* @__PURE__ */ jsx("span", { className: "text-foreground text-sm leading-5 font-medium", children: term.title }),
|
|
3268
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm leading-5", children: term.description })
|
|
3269
|
+
] })
|
|
3270
|
+
] });
|
|
3271
|
+
}
|
|
3272
|
+
function AllowancePolicySection({
|
|
3273
|
+
allowance,
|
|
3274
|
+
serviceName,
|
|
3275
|
+
serviceIcon
|
|
3276
|
+
}) {
|
|
3277
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
3278
|
+
const permissions = allowance.terms?.permissions ?? [];
|
|
3279
|
+
const restrictions = allowance.terms?.restrictions ?? [];
|
|
3280
|
+
const hasTerms = permissions.length > 0 || restrictions.length > 0;
|
|
3281
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
3282
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4", children: [
|
|
3283
|
+
/* @__PURE__ */ jsx("div", { className: "flex h-8 w-8 items-center justify-center overflow-hidden rounded-lg", children: serviceIcon }),
|
|
3284
|
+
/* @__PURE__ */ jsx(ArrowRightIcon, { className: "text-muted-foreground" }),
|
|
3285
|
+
/* @__PURE__ */ jsx(PrivanaIcon, { size: 32 })
|
|
3286
|
+
] }),
|
|
3287
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm leading-5", children: [
|
|
3288
|
+
/* @__PURE__ */ jsx("span", { className: "text-foreground font-medium", children: serviceName }),
|
|
3289
|
+
" ",
|
|
3290
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: "wants a policy on your Privana account." })
|
|
3291
|
+
] }),
|
|
3292
|
+
hasTerms && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3293
|
+
/* @__PURE__ */ jsxs(
|
|
3294
|
+
"button",
|
|
3295
|
+
{
|
|
3296
|
+
type: "button",
|
|
3297
|
+
onClick: () => setCollapsed((prev) => !prev),
|
|
3298
|
+
className: "flex w-full cursor-pointer items-center gap-2",
|
|
3299
|
+
children: [
|
|
3300
|
+
/* @__PURE__ */ jsx("span", { className: "bg-border h-px flex-1" }),
|
|
3301
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-[10px] leading-[14px] font-medium tracking-[0.2px] whitespace-nowrap uppercase", children: collapsed ? "Show details" : "Hide details" }),
|
|
3302
|
+
/* @__PURE__ */ jsx(
|
|
3303
|
+
ChevronDownIcon,
|
|
3304
|
+
{
|
|
3305
|
+
direction: collapsed ? "down" : "up",
|
|
3306
|
+
className: "text-foreground shrink-0"
|
|
3307
|
+
}
|
|
3308
|
+
),
|
|
3309
|
+
/* @__PURE__ */ jsx("span", { className: "bg-border h-px flex-1" })
|
|
3310
|
+
]
|
|
3311
|
+
}
|
|
3312
|
+
),
|
|
3313
|
+
!collapsed && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-4", children: [
|
|
3314
|
+
permissions.map((term, i) => /* @__PURE__ */ jsx(PolicyTermRow, { term, kind: "permission" }, `permission-${i}`)),
|
|
3315
|
+
restrictions.map((term, i) => /* @__PURE__ */ jsx(PolicyTermRow, { term, kind: "restriction" }, `restriction-${i}`))
|
|
3316
|
+
] })
|
|
3317
|
+
] })
|
|
3318
|
+
] });
|
|
3319
|
+
}
|
|
3320
|
+
function MoonPayGate({ enabled, children }) {
|
|
3321
|
+
const { networkConfig } = usePrivanaContext();
|
|
3322
|
+
const apiKey = networkConfig.moonpayApiKey;
|
|
3323
|
+
if (!enabled || !apiKey) return /* @__PURE__ */ jsx(Fragment, { children });
|
|
3324
|
+
return /* @__PURE__ */ jsx(MoonPayProvider, { apiKey, children });
|
|
3325
|
+
}
|
|
3326
|
+
function MethodTabs({
|
|
3327
|
+
activeTab,
|
|
3328
|
+
onTabChange
|
|
3329
|
+
}) {
|
|
3330
|
+
return /* @__PURE__ */ jsxs("div", { className: "bg-secondary relative flex gap-2 overflow-hidden rounded-[10px] p-1", children: [
|
|
3331
|
+
/* @__PURE__ */ jsx(
|
|
3332
|
+
"div",
|
|
3333
|
+
{
|
|
3334
|
+
className: cn(
|
|
3335
|
+
"bg-input absolute top-1 bottom-1 left-1 w-[calc(50%-8px)] rounded-md transition-transform duration-200",
|
|
3336
|
+
activeTab === "credit-card" && "translate-x-[calc(100%+8px)]"
|
|
3337
|
+
)
|
|
3338
|
+
}
|
|
3339
|
+
),
|
|
3340
|
+
/* @__PURE__ */ jsx(
|
|
3341
|
+
"button",
|
|
3342
|
+
{
|
|
3343
|
+
type: "button",
|
|
3344
|
+
onClick: () => onTabChange("crypto"),
|
|
3345
|
+
className: cn(
|
|
3346
|
+
"relative z-10 flex-1 cursor-pointer rounded-md px-3 py-[9px] text-sm font-medium transition-colors",
|
|
3347
|
+
activeTab === "crypto" ? "text-foreground" : "text-muted-foreground"
|
|
3348
|
+
),
|
|
3349
|
+
children: "Crypto"
|
|
3350
|
+
}
|
|
3351
|
+
),
|
|
3352
|
+
/* @__PURE__ */ jsx(
|
|
3353
|
+
"button",
|
|
3354
|
+
{
|
|
3355
|
+
type: "button",
|
|
3356
|
+
onClick: () => onTabChange("credit-card"),
|
|
3357
|
+
className: cn(
|
|
3358
|
+
"relative z-10 flex-1 cursor-pointer rounded-md px-3 py-[9px] text-sm font-medium transition-colors",
|
|
3359
|
+
activeTab === "credit-card" ? "text-foreground" : "text-muted-foreground"
|
|
3360
|
+
),
|
|
3361
|
+
children: "Credit Card"
|
|
3362
|
+
}
|
|
3363
|
+
)
|
|
3364
|
+
] });
|
|
3365
|
+
}
|
|
3366
|
+
function MethodOption({
|
|
3367
|
+
title,
|
|
3368
|
+
description,
|
|
3369
|
+
onClick
|
|
3370
|
+
}) {
|
|
3371
|
+
return /* @__PURE__ */ jsxs(
|
|
3372
|
+
"button",
|
|
3373
|
+
{
|
|
3374
|
+
type: "button",
|
|
3375
|
+
onClick,
|
|
3376
|
+
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",
|
|
3377
|
+
children: [
|
|
3378
|
+
/* @__PURE__ */ jsxs("div", { className: "flex min-w-0 flex-1 flex-col gap-1", children: [
|
|
3379
|
+
/* @__PURE__ */ jsx("span", { className: "text-foreground text-sm leading-[14px] font-medium", children: title }),
|
|
3380
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs leading-3", children: description })
|
|
3381
|
+
] }),
|
|
3382
|
+
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-5 w-5 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
|
|
3383
|
+
]
|
|
3384
|
+
}
|
|
3385
|
+
);
|
|
3386
|
+
}
|
|
3387
|
+
function DepositView({
|
|
3388
|
+
source,
|
|
3389
|
+
selectedToken,
|
|
3390
|
+
amount,
|
|
3391
|
+
allowance,
|
|
3392
|
+
onAmountChange,
|
|
3393
|
+
onSelectToken,
|
|
3394
|
+
onConnectWallet,
|
|
3395
|
+
onSubmit,
|
|
3396
|
+
isSubmitting = false
|
|
3397
|
+
}) {
|
|
3398
|
+
const { getChainById: getChainById2, chains, serviceName, serviceIcon, networkConfig } = usePrivanaContext();
|
|
3399
|
+
const { address, isConnected } = useAccount();
|
|
3400
|
+
const appName = serviceName ?? "Privana";
|
|
3401
|
+
const chain = selectedToken ? getChainById2(selectedToken.chainId) : void 0;
|
|
3402
|
+
const targetChain = chain ?? chains[0];
|
|
3403
|
+
const sourceLabel = source === "connected" ? "Connected Wallet" : "External Wallet";
|
|
3404
|
+
const isConnectedSource = source === "connected";
|
|
3405
|
+
const isCreditCard = source === "credit-card";
|
|
3406
|
+
const isNative = selectedToken?.contract === zeroAddress;
|
|
3407
|
+
const { data: nativeBalanceData } = useBalance$1({
|
|
3408
|
+
address,
|
|
3409
|
+
chainId: targetChain?.id,
|
|
3410
|
+
query: { enabled: isConnectedSource && !!address && !!selectedToken && isNative }
|
|
3411
|
+
});
|
|
3412
|
+
const { data: erc20Balance } = useReadContract({
|
|
3413
|
+
address: selectedToken?.contract,
|
|
3414
|
+
abi: erc20Abi,
|
|
3415
|
+
functionName: "balanceOf",
|
|
3416
|
+
args: address ? [address] : void 0,
|
|
3417
|
+
chainId: targetChain?.id,
|
|
3418
|
+
query: { enabled: isConnectedSource && !!address && !!selectedToken && !isNative }
|
|
3419
|
+
});
|
|
3420
|
+
const walletBalance = isNative ? nativeBalanceData?.value : erc20Balance;
|
|
3421
|
+
const formattedWalletBalance = walletBalance != null && selectedToken ? formatTokenAmount(walletBalance.toString(), selectedToken.decimals) : "0.00";
|
|
3422
|
+
const {
|
|
3423
|
+
minBuyAmount: moonpayMinBuy,
|
|
3424
|
+
isLoading: moonpayLimitsLoading,
|
|
3425
|
+
error: moonpayLimitsError
|
|
3426
|
+
} = useMoonpayLimits({
|
|
3427
|
+
currencyCode: selectedToken?.moonpayCurrencyCode,
|
|
3428
|
+
apiBaseUrl: networkConfig.moonpayApiUrl,
|
|
3429
|
+
enabled: isCreditCard
|
|
3430
|
+
});
|
|
3431
|
+
const hasValidAmount = !!amount && parseFloat(amount) > 0;
|
|
3432
|
+
const maxAmountDecimals = isCreditCard ? 2 : selectedToken?.decimals;
|
|
3433
|
+
const tooManyDecimals = hasValidAmount && maxAmountDecimals != null && amount.includes(".") && amount.split(".")[1].length > maxAmountDecimals;
|
|
3434
|
+
const exceedsBalance = isConnectedSource && hasValidAmount && !tooManyDecimals && !!selectedToken && walletBalance != null && parseTokenAmount(amount, selectedToken.decimals) > walletBalance;
|
|
3435
|
+
const belowMoonpayMin = isCreditCard && hasValidAmount && moonpayMinBuy != null && parseFloat(amount) < moonpayMinBuy;
|
|
3436
|
+
const moonpayLimitsUnready = isCreditCard && !!selectedToken?.moonpayCurrencyCode && moonpayMinBuy == null;
|
|
3437
|
+
const creditCardUnavailable = isCreditCard && !!selectedToken && !selectedToken.moonpayCurrencyCode;
|
|
3438
|
+
const needsConnect = isConnectedSource && !isConnected;
|
|
3439
|
+
const canDeposit = hasValidAmount && !!selectedToken && !tooManyDecimals && !exceedsBalance && !belowMoonpayMin && !moonpayLimitsUnready && !creditCardUnavailable && !needsConnect;
|
|
3440
|
+
const handleMax = () => {
|
|
3441
|
+
const max = formattedWalletBalance.replace(/\s/g, "");
|
|
3442
|
+
if (parseFloat(max) > 0) onAmountChange(max);
|
|
3443
|
+
};
|
|
3444
|
+
return /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
3445
|
+
isCreditCard ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3446
|
+
/* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Buy with credit card and deposit" }),
|
|
3447
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Enter your deposit amount and proceed to sign a policy." })
|
|
3448
|
+
] }) : /* @__PURE__ */ jsxs("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: [
|
|
3449
|
+
"Deposit from ",
|
|
3450
|
+
sourceLabel
|
|
3451
|
+
] }),
|
|
3452
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
3453
|
+
/* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Token" }),
|
|
3454
|
+
/* @__PURE__ */ jsxs(
|
|
3455
|
+
"button",
|
|
3456
|
+
{
|
|
3457
|
+
type: "button",
|
|
3458
|
+
onClick: onSelectToken,
|
|
3459
|
+
className: "border-border bg-input flex w-full cursor-pointer items-center gap-3 rounded-lg border p-3 text-left",
|
|
3460
|
+
children: [
|
|
3461
|
+
selectedToken ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3462
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 w-8 shrink-0 overflow-hidden rounded-full", children: getTokenIcon(selectedToken.symbol, 32) }),
|
|
3463
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-start gap-1", children: [
|
|
3464
|
+
/* @__PURE__ */ jsx("span", { className: "text-foreground text-sm font-medium", children: selectedToken.symbol }),
|
|
3465
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
3466
|
+
"on ",
|
|
3467
|
+
chain?.name ?? "\u2014"
|
|
3468
|
+
] })
|
|
3469
|
+
] })
|
|
3470
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground flex-1 text-sm", children: "Select token" }),
|
|
3471
|
+
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-5 w-5 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
|
|
3472
|
+
]
|
|
3473
|
+
}
|
|
3474
|
+
)
|
|
3475
|
+
] }),
|
|
3476
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
3477
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
3478
|
+
/* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Amount" }),
|
|
3479
|
+
isConnectedSource && /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-sm", children: [
|
|
3480
|
+
"Available ",
|
|
3481
|
+
formattedWalletBalance,
|
|
3482
|
+
" ",
|
|
3483
|
+
selectedToken?.symbol
|
|
3484
|
+
] })
|
|
3485
|
+
] }),
|
|
3486
|
+
/* @__PURE__ */ jsxs(
|
|
3487
|
+
"div",
|
|
3488
|
+
{
|
|
3489
|
+
className: cn(
|
|
3490
|
+
"border-border bg-input flex items-center gap-2 rounded-[10px] border",
|
|
3491
|
+
isConnectedSource ? "py-1 pr-1 pl-3" : "px-3 py-3"
|
|
3492
|
+
),
|
|
3493
|
+
children: [
|
|
3494
|
+
/* @__PURE__ */ jsx(
|
|
3495
|
+
"input",
|
|
3496
|
+
{
|
|
3497
|
+
type: "text",
|
|
3498
|
+
inputMode: "decimal",
|
|
3499
|
+
placeholder: "Enter Amount",
|
|
3500
|
+
value: amount,
|
|
3501
|
+
onChange: (e) => {
|
|
3502
|
+
const value = e.target.value.replace(/[^0-9.,]/g, "").replace(/,/g, ".");
|
|
3503
|
+
if (value.split(".").length <= 2) onAmountChange(value);
|
|
3504
|
+
},
|
|
3505
|
+
className: "text-foreground placeholder:text-muted-foreground/50 flex-1 bg-transparent text-sm outline-none"
|
|
3506
|
+
}
|
|
3507
|
+
),
|
|
3508
|
+
isConnectedSource ? /* @__PURE__ */ jsx(
|
|
3509
|
+
"button",
|
|
3510
|
+
{
|
|
3511
|
+
type: "button",
|
|
3512
|
+
onClick: handleMax,
|
|
3513
|
+
className: "bg-secondary text-foreground hover:bg-secondary/80 cursor-pointer rounded px-3 py-2.5 text-xs font-semibold transition-colors",
|
|
3514
|
+
children: "MAX"
|
|
3515
|
+
}
|
|
3516
|
+
) : isCreditCard ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: selectedToken?.symbol ?? "USD" }) : selectedToken && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm", children: selectedToken.symbol })
|
|
3517
|
+
]
|
|
3518
|
+
}
|
|
3519
|
+
),
|
|
3520
|
+
tooManyDecimals && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
3521
|
+
"Too many decimal places (max: ",
|
|
3522
|
+
maxAmountDecimals,
|
|
3523
|
+
")"
|
|
3524
|
+
] }),
|
|
3525
|
+
exceedsBalance && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Insufficient balance" }),
|
|
3526
|
+
belowMoonpayMin && moonpayMinBuy != null && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
3527
|
+
"Minimum purchase is $",
|
|
3528
|
+
moonpayMinBuy,
|
|
3529
|
+
" USD."
|
|
3530
|
+
] }),
|
|
3531
|
+
isCreditCard && moonpayLimitsError && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Couldn\u2019t load purchase limits. Please try again." }),
|
|
3532
|
+
isCreditCard && moonpayLimitsLoading && /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Checking purchase limits\u2026" }),
|
|
3533
|
+
creditCardUnavailable && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
3534
|
+
selectedToken?.symbol ?? "This token",
|
|
3535
|
+
" isn\u2019t available for card purchases yet."
|
|
3536
|
+
] })
|
|
3537
|
+
] }),
|
|
3538
|
+
allowance && /* @__PURE__ */ jsx(
|
|
3539
|
+
AllowancePolicySection,
|
|
3540
|
+
{
|
|
3541
|
+
allowance,
|
|
3542
|
+
serviceName: appName,
|
|
3543
|
+
serviceIcon
|
|
3544
|
+
}
|
|
3545
|
+
),
|
|
3546
|
+
/* @__PURE__ */ jsx(
|
|
3547
|
+
"button",
|
|
3548
|
+
{
|
|
3549
|
+
type: "button",
|
|
3550
|
+
disabled: needsConnect ? !onConnectWallet : !canDeposit || isSubmitting,
|
|
3551
|
+
onClick: () => {
|
|
3552
|
+
if (needsConnect) {
|
|
3553
|
+
onConnectWallet?.();
|
|
3554
|
+
return;
|
|
3555
|
+
}
|
|
3556
|
+
if (selectedToken) onSubmit({ source, tokenId: selectedToken.id, amount });
|
|
3557
|
+
},
|
|
3558
|
+
className: "bg-primary text-primary-foreground hover:bg-primary/90 flex h-10 w-full 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",
|
|
3559
|
+
children: needsConnect ? "Connect Wallet" : allowance ? "Sign Policy and Deposit" : "Deposit"
|
|
3560
|
+
}
|
|
3561
|
+
)
|
|
3562
|
+
] });
|
|
3563
|
+
}
|
|
3564
|
+
function SummaryRow({ value, label }) {
|
|
3565
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-sm leading-5", children: [
|
|
3566
|
+
/* @__PURE__ */ jsx("span", { className: "text-foreground font-medium", children: value }),
|
|
3567
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: label })
|
|
3568
|
+
] });
|
|
3569
|
+
}
|
|
3570
|
+
var LISTENING_WINDOW_MS = 36e5;
|
|
3571
|
+
function remainingSeconds(deadline) {
|
|
3572
|
+
return Math.max(0, Math.round((deadline - Date.now()) / 1e3));
|
|
3573
|
+
}
|
|
3574
|
+
function useCountdown(deadline) {
|
|
3575
|
+
const [secondsLeft, setSecondsLeft] = useState(() => remainingSeconds(deadline));
|
|
3576
|
+
useEffect(() => {
|
|
3577
|
+
if (remainingSeconds(deadline) <= 0) return;
|
|
3578
|
+
const id = setInterval(() => {
|
|
3579
|
+
const left = remainingSeconds(deadline);
|
|
3580
|
+
setSecondsLeft(left);
|
|
3581
|
+
if (left <= 0) clearInterval(id);
|
|
3582
|
+
}, 1e3);
|
|
3583
|
+
return () => clearInterval(id);
|
|
3584
|
+
}, [deadline]);
|
|
3585
|
+
return secondsLeft;
|
|
3586
|
+
}
|
|
3587
|
+
function AwaitingDepositStatus({
|
|
3588
|
+
title,
|
|
3589
|
+
subtitle,
|
|
3590
|
+
remaining
|
|
3591
|
+
}) {
|
|
3592
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center gap-3 text-center", children: [
|
|
3593
|
+
/* @__PURE__ */ jsx("div", { className: "text-foreground", children: /* @__PURE__ */ jsx(Spinner, { size: 32 }) }),
|
|
3594
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3595
|
+
/* @__PURE__ */ jsx("h3", { className: "text-foreground text-xl leading-6 font-medium", children: title }),
|
|
3596
|
+
/* @__PURE__ */ jsxs("p", { className: "text-muted-foreground text-sm leading-[18px]", children: [
|
|
3597
|
+
subtitle,
|
|
3598
|
+
remaining && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3599
|
+
/* @__PURE__ */ jsx("br", {}),
|
|
3600
|
+
"Remaining time: ",
|
|
3601
|
+
remaining
|
|
3602
|
+
] })
|
|
3603
|
+
] })
|
|
3604
|
+
] })
|
|
3605
|
+
] });
|
|
3606
|
+
}
|
|
3607
|
+
function ExternalDepositView({
|
|
3608
|
+
token,
|
|
3609
|
+
amount,
|
|
3610
|
+
onCredited
|
|
3611
|
+
}) {
|
|
3612
|
+
const { getChainById: getChainById2 } = usePrivanaContext();
|
|
3613
|
+
const { depositAddress, isReady, isLoading } = useDepositAddress();
|
|
3614
|
+
const chain = token ? getChainById2(token.chainId) : void 0;
|
|
3615
|
+
const [copied, setCopied] = useState(false);
|
|
3616
|
+
const [deadline] = useState(() => Date.now() + LISTENING_WINDOW_MS);
|
|
3617
|
+
const secondsLeft = useCountdown(deadline);
|
|
3618
|
+
const listening = secondsLeft > 0;
|
|
3619
|
+
const [nothingFound, setNothingFound] = useState(false);
|
|
3620
|
+
const [credited, setCredited] = useState(null);
|
|
3621
|
+
const verification = useDepositVerification({
|
|
3622
|
+
onCredited: (txHash) => {
|
|
3623
|
+
if (onCredited) {
|
|
3624
|
+
onCredited();
|
|
3625
|
+
return;
|
|
3626
|
+
}
|
|
3627
|
+
setCredited({ txHash });
|
|
3628
|
+
}
|
|
3629
|
+
});
|
|
3630
|
+
const { isVerifying, verificationFailed, didTimeout, verify } = verification;
|
|
3631
|
+
const scanPaused = isVerifying || verificationFailed || didTimeout || !!credited;
|
|
3632
|
+
const {
|
|
3633
|
+
pending,
|
|
3634
|
+
isFetching: isScanning,
|
|
3635
|
+
isError: isScanError,
|
|
3636
|
+
isRateLimited,
|
|
3637
|
+
isUnavailable,
|
|
3638
|
+
refetch: refetchPendingDeposits
|
|
3639
|
+
} = usePendingDeposits({
|
|
3640
|
+
chainId: token?.chainId,
|
|
3641
|
+
enabled: isReady && !!depositAddress && !!token,
|
|
3642
|
+
refetchInterval: listening && !scanPaused ? 3e4 : false
|
|
3643
|
+
});
|
|
3644
|
+
const processedRef = useRef(/* @__PURE__ */ new Set());
|
|
3645
|
+
useEffect(() => {
|
|
3646
|
+
if (scanPaused) return;
|
|
3647
|
+
const next = [...pending].sort((a, b) => a.block_number - b.block_number).find((d) => !processedRef.current.has(`${d.tx_hash}:${d.log_index}`));
|
|
3648
|
+
if (!next) return;
|
|
3649
|
+
processedRef.current.add(`${next.tx_hash}:${next.log_index}`);
|
|
3650
|
+
void verify({
|
|
3651
|
+
hash: next.tx_hash,
|
|
3652
|
+
chainId: next.chain_id,
|
|
3653
|
+
amount: BigInt(next.amount),
|
|
3654
|
+
logIndex: next.log_index
|
|
3655
|
+
});
|
|
3656
|
+
}, [pending, scanPaused, verify]);
|
|
3657
|
+
const nothingFoundTimerRef = useRef(void 0);
|
|
3658
|
+
useEffect(() => () => clearTimeout(nothingFoundTimerRef.current), []);
|
|
3659
|
+
const handleRefresh = () => {
|
|
3660
|
+
processedRef.current.clear();
|
|
3661
|
+
void refetchPendingDeposits().then((result) => {
|
|
3662
|
+
if (!result || result.pending.length === 0) {
|
|
3663
|
+
setNothingFound(true);
|
|
3664
|
+
clearTimeout(nothingFoundTimerRef.current);
|
|
3665
|
+
nothingFoundTimerRef.current = setTimeout(() => setNothingFound(false), 4e3);
|
|
3666
|
+
}
|
|
3667
|
+
});
|
|
3668
|
+
};
|
|
3669
|
+
const handleCopy = () => {
|
|
3670
|
+
if (!depositAddress) return;
|
|
3671
|
+
void navigator.clipboard.writeText(depositAddress);
|
|
3672
|
+
setCopied(true);
|
|
3673
|
+
setTimeout(() => setCopied(false), 2e3);
|
|
3674
|
+
};
|
|
3675
|
+
return /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
3676
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3677
|
+
/* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Deposit from an External Wallet" }),
|
|
3678
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Transfer crypto from external wallet or exchange." })
|
|
3679
|
+
] }),
|
|
3680
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
3681
|
+
/* @__PURE__ */ jsx(SummaryRow, { value: chain?.name ?? "\u2014", label: "Chain" }),
|
|
3682
|
+
/* @__PURE__ */ jsx("div", { className: "bg-border h-px w-full" }),
|
|
3683
|
+
/* @__PURE__ */ jsx(SummaryRow, { value: token?.symbol ?? "\u2014", label: "Currency" }),
|
|
3684
|
+
/* @__PURE__ */ jsx("div", { className: "bg-border h-px w-full" }),
|
|
3685
|
+
/* @__PURE__ */ jsx(SummaryRow, { value: amount || "\u2014", label: "Value" })
|
|
3686
|
+
] }),
|
|
3687
|
+
/* @__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" }) }),
|
|
3688
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
3689
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: "Deposit Address" }),
|
|
3690
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
|
|
3691
|
+
/* @__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" }) }),
|
|
3692
|
+
/* @__PURE__ */ jsxs(
|
|
3693
|
+
"button",
|
|
3694
|
+
{
|
|
3695
|
+
type: "button",
|
|
3696
|
+
onClick: handleCopy,
|
|
3697
|
+
disabled: !depositAddress,
|
|
3698
|
+
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",
|
|
3699
|
+
children: [
|
|
3700
|
+
/* @__PURE__ */ jsx(CopyIcon, {}),
|
|
3701
|
+
copied ? "Copied" : "Copy"
|
|
3702
|
+
]
|
|
3703
|
+
}
|
|
3704
|
+
)
|
|
3705
|
+
] })
|
|
3706
|
+
] }),
|
|
3707
|
+
credited ? /* @__PURE__ */ jsx(
|
|
3708
|
+
TransactionSuccessView,
|
|
3709
|
+
{
|
|
3710
|
+
title: "Deposit Credited",
|
|
3711
|
+
message: `Transfer ${shortenAddress(credited.txHash)} has been credited to your Privana balance.`,
|
|
3712
|
+
onDone: () => {
|
|
3713
|
+
setCredited(null);
|
|
3714
|
+
verification.reset();
|
|
3715
|
+
void refetchPendingDeposits();
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
) : verificationFailed || didTimeout ? /* @__PURE__ */ jsx(
|
|
3719
|
+
TransactionErrorView,
|
|
3720
|
+
{
|
|
3721
|
+
title: "Could not verify deposit",
|
|
3722
|
+
message: didTimeout ? "Verification timed out \u2014 your deposit may still be credited in the background." : verification.error?.message ?? "Your transfer was found but could not be verified.",
|
|
3723
|
+
onRetry: () => void verification.retryVerification(),
|
|
3724
|
+
onDismiss: () => verification.reset(),
|
|
3725
|
+
isRetrying: isVerifying
|
|
3726
|
+
}
|
|
3727
|
+
) : isVerifying ? /* @__PURE__ */ jsx(
|
|
3728
|
+
AwaitingDepositStatus,
|
|
3729
|
+
{
|
|
3730
|
+
title: "Deposit detected",
|
|
3731
|
+
subtitle: "Crediting the incoming transaction to your Privana balance..."
|
|
3732
|
+
}
|
|
3733
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3734
|
+
/* @__PURE__ */ jsx(
|
|
3735
|
+
AwaitingDepositStatus,
|
|
3736
|
+
{
|
|
3737
|
+
title: "Awaiting Deposit",
|
|
3738
|
+
subtitle: listening ? "We are listening for your incoming transaction for the next hour." : "We've stopped listening automatically. Use the button below to check for your deposit.",
|
|
3739
|
+
remaining: listening ? formatCountdown(secondsLeft) : void 0
|
|
3740
|
+
}
|
|
3741
|
+
),
|
|
3742
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
3743
|
+
/* @__PURE__ */ jsx(
|
|
3744
|
+
"button",
|
|
3745
|
+
{
|
|
3746
|
+
type: "button",
|
|
3747
|
+
onClick: handleRefresh,
|
|
3748
|
+
disabled: !depositAddress || isScanning,
|
|
3749
|
+
className: "bg-secondary text-foreground hover:bg-secondary/80 flex h-10 w-full 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",
|
|
3750
|
+
children: "Refresh deposit status"
|
|
3751
|
+
}
|
|
3752
|
+
),
|
|
3753
|
+
isRateLimited ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", children: "Checked too recently \u2014 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 \u2014 retrying automatically." }) : nothingFound ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-center text-sm", children: "No incoming transaction found yet." }) : null
|
|
3754
|
+
] })
|
|
3755
|
+
] })
|
|
3756
|
+
] });
|
|
3757
|
+
}
|
|
3758
|
+
function DepositModalContent({
|
|
3759
|
+
defaultTab = "crypto",
|
|
3760
|
+
allowance,
|
|
3761
|
+
onSelectConnectedWallet,
|
|
3762
|
+
onSelectExternalWallet,
|
|
3763
|
+
onSelectCreditCard,
|
|
3764
|
+
onConnectWallet,
|
|
3765
|
+
onDeposit,
|
|
3766
|
+
onDepositSuccess,
|
|
3767
|
+
onLockFailed,
|
|
3768
|
+
onClose,
|
|
3769
|
+
onCloseBlockedChange,
|
|
3770
|
+
onExit
|
|
3771
|
+
}) {
|
|
3772
|
+
const { serviceName, enabledTokens, defaultToken, hostedAuthConfig, getChainById: getChainById2 } = usePrivanaContext();
|
|
3773
|
+
const { address } = useAccount();
|
|
3774
|
+
const appName = serviceName ?? "Privana";
|
|
3775
|
+
const [activeTab, setActiveTab] = useState(defaultTab);
|
|
3776
|
+
const [view, setView] = useState("method");
|
|
3777
|
+
const [source, setSource] = useState("connected");
|
|
3778
|
+
const [selectedTokenId, setSelectedTokenId] = useState(defaultToken?.id ?? "");
|
|
3779
|
+
const [amount, setAmount] = useState("");
|
|
3780
|
+
const selectedToken = enabledTokens.find((t) => t.id === selectedTokenId) ?? defaultToken;
|
|
3781
|
+
const prevAddressRef = useRef(address);
|
|
3782
|
+
useEffect(() => {
|
|
3783
|
+
const prev = prevAddressRef.current;
|
|
3784
|
+
prevAddressRef.current = address;
|
|
3785
|
+
if (hostedAuthConfig) return;
|
|
3786
|
+
if (prev && address && prev !== address) {
|
|
3787
|
+
setView("method");
|
|
3788
|
+
setAmount("");
|
|
3789
|
+
setSelectedTokenId("");
|
|
3790
|
+
}
|
|
3791
|
+
}, [address, hostedAuthConfig]);
|
|
3792
|
+
const openDeposit = (next) => {
|
|
3793
|
+
setSource(next);
|
|
3794
|
+
setView("deposit");
|
|
3795
|
+
};
|
|
3796
|
+
const [showSuccess, setShowSuccess] = useState(false);
|
|
3797
|
+
const [showTimeout, setShowTimeout] = useState(false);
|
|
3798
|
+
const [cancelled, setCancelled] = useState(false);
|
|
3799
|
+
const [isSubmittingLock, setIsSubmittingLock] = useState(false);
|
|
3800
|
+
const [lockFailedMessage, setLockFailedMessage] = useState(null);
|
|
3801
|
+
const finishDeposit = () => {
|
|
3802
|
+
setAmount("");
|
|
3803
|
+
if (onDepositSuccess) {
|
|
3804
|
+
resetDeposit();
|
|
3805
|
+
onDepositSuccess();
|
|
3806
|
+
} else {
|
|
3807
|
+
setShowSuccess(true);
|
|
3808
|
+
}
|
|
3809
|
+
};
|
|
3810
|
+
const finishCardPurchase = () => {
|
|
3811
|
+
setAmount("");
|
|
3812
|
+
onDepositSuccess?.();
|
|
3813
|
+
};
|
|
3814
|
+
const {
|
|
3815
|
+
txHash,
|
|
3816
|
+
isGettingAddress,
|
|
3817
|
+
isSwitchingChain,
|
|
3818
|
+
isSendingTransaction,
|
|
3819
|
+
isWaitingForConfirmation,
|
|
3820
|
+
isWaitingForProcessing,
|
|
3821
|
+
verificationFailed,
|
|
3822
|
+
isPending,
|
|
3823
|
+
error: depositError,
|
|
3824
|
+
deposit,
|
|
3825
|
+
retryVerification,
|
|
3826
|
+
reset: resetDeposit
|
|
3827
|
+
} = useDeposit({
|
|
3828
|
+
onCredited: (_txHash, _response, lockPending) => {
|
|
3829
|
+
if (lockPending) {
|
|
3830
|
+
setIsSubmittingLock(true);
|
|
3831
|
+
return;
|
|
3832
|
+
}
|
|
3833
|
+
finishDeposit();
|
|
3834
|
+
},
|
|
3835
|
+
onLockSubmitted: () => {
|
|
3836
|
+
setIsSubmittingLock(false);
|
|
3837
|
+
finishDeposit();
|
|
3838
|
+
},
|
|
3839
|
+
// The deposit credited; only the policy lock failed. Success must not
|
|
3840
|
+
// fire (the host would act on unlocked funds) — show the dedicated
|
|
3841
|
+
// error view and let the host re-prompt for a fresh lock.
|
|
3842
|
+
onLockFailed: (err) => {
|
|
3843
|
+
setIsSubmittingLock(false);
|
|
3844
|
+
setLockFailedMessage(err.message);
|
|
3845
|
+
onLockFailed?.(err);
|
|
3846
|
+
},
|
|
3847
|
+
onCheckTimeout: () => {
|
|
3848
|
+
setAmount("");
|
|
3849
|
+
setShowTimeout(true);
|
|
3850
|
+
}
|
|
3851
|
+
});
|
|
3852
|
+
const isUnsafeToClose = (isGettingAddress || isSendingTransaction) && !cancelled;
|
|
3853
|
+
useEffect(() => {
|
|
3854
|
+
onCloseBlockedChange?.(isUnsafeToClose);
|
|
3855
|
+
}, [isUnsafeToClose, onCloseBlockedChange]);
|
|
3856
|
+
useEffect(() => {
|
|
3857
|
+
if (depositError && !verificationFailed) {
|
|
3858
|
+
toast.error(
|
|
3859
|
+
depositError.message.length > 100 ? `${depositError.message.slice(0, 100)}...` : depositError.message
|
|
3860
|
+
);
|
|
3861
|
+
}
|
|
3862
|
+
}, [depositError, verificationFailed]);
|
|
3863
|
+
const handleSubmit = (args) => {
|
|
3864
|
+
if (args.source === "external") {
|
|
3865
|
+
setView("external-deposit");
|
|
3866
|
+
return;
|
|
3867
|
+
}
|
|
3868
|
+
if (args.source === "credit-card") {
|
|
3869
|
+
setView("credit-card-widget");
|
|
3870
|
+
return;
|
|
3871
|
+
}
|
|
3872
|
+
const token = enabledTokens.find((t) => t.id === args.tokenId);
|
|
3873
|
+
if (!token) return;
|
|
3874
|
+
onDeposit?.(args);
|
|
3875
|
+
setCancelled(false);
|
|
3876
|
+
deposit({
|
|
3877
|
+
tokenId: token.id,
|
|
3878
|
+
amount: parseTokenAmount(args.amount, token.decimals),
|
|
3879
|
+
postDepositLock: allowance ? {
|
|
3880
|
+
maxAmount: BigInt(allowance.value),
|
|
3881
|
+
lockDuration: allowance.lockDuration
|
|
3882
|
+
} : void 0
|
|
3883
|
+
}).catch((err) => {
|
|
3884
|
+
toast.error(err instanceof Error ? err.message : "Deposit failed");
|
|
3885
|
+
});
|
|
3886
|
+
};
|
|
3887
|
+
const targetChain = selectedToken ? getChainById2(selectedToken.chainId) : void 0;
|
|
3888
|
+
const explorerTxUrl = txHash && targetChain?.explorerUrl ? `${targetChain.explorerUrl}/tx/${txHash}` : void 0;
|
|
3889
|
+
const depositSteps = [
|
|
3890
|
+
{
|
|
3891
|
+
label: "Getting deposit address",
|
|
3892
|
+
status: isGettingAddress ? "active" : isSwitchingChain || isSendingTransaction || isWaitingForConfirmation || isWaitingForProcessing || isSubmittingLock ? "completed" : "pending"
|
|
3893
|
+
},
|
|
3894
|
+
{
|
|
3895
|
+
label: `Switching to ${targetChain?.name ?? "deposit chain"}`,
|
|
3896
|
+
status: isSwitchingChain ? "active" : isSendingTransaction || isWaitingForConfirmation || isWaitingForProcessing || isSubmittingLock ? "completed" : "pending"
|
|
3897
|
+
},
|
|
3898
|
+
{
|
|
3899
|
+
label: "Confirm in wallet",
|
|
3900
|
+
status: isSendingTransaction ? "active" : isWaitingForConfirmation || isWaitingForProcessing || isSubmittingLock ? "completed" : "pending"
|
|
3901
|
+
},
|
|
3902
|
+
{
|
|
3903
|
+
label: "Confirming transaction",
|
|
3904
|
+
status: isWaitingForConfirmation ? "active" : isWaitingForProcessing || isSubmittingLock ? "completed" : "pending"
|
|
3905
|
+
},
|
|
3906
|
+
{
|
|
3907
|
+
label: "Verifying deposit \u2014 may take up to a few minutes",
|
|
3908
|
+
status: isWaitingForProcessing ? "active" : isSubmittingLock ? "completed" : "pending"
|
|
3909
|
+
},
|
|
3910
|
+
...allowance ? [
|
|
3911
|
+
{
|
|
3912
|
+
label: `Locking funds for ${appName}`,
|
|
3913
|
+
status: isSubmittingLock ? "active" : "pending"
|
|
3914
|
+
}
|
|
3915
|
+
] : []
|
|
3916
|
+
];
|
|
3917
|
+
const flowView = showSuccess ? "deposit-success" : lockFailedMessage ? "lock-error" : showTimeout ? "deposit-timeout" : verificationFailed ? "deposit-error" : isPending && !cancelled || isSubmittingLock ? "depositing" : null;
|
|
3918
|
+
const activeView = flowView ?? view;
|
|
3919
|
+
const handleDepositDone = () => {
|
|
3920
|
+
setShowSuccess(false);
|
|
3921
|
+
setShowTimeout(false);
|
|
3922
|
+
setCancelled(false);
|
|
3923
|
+
resetDeposit();
|
|
3924
|
+
};
|
|
3925
|
+
const handleDepositCancel = () => {
|
|
3926
|
+
setCancelled(true);
|
|
3927
|
+
resetDeposit();
|
|
3928
|
+
};
|
|
3929
|
+
const handleLockFailedDone = () => {
|
|
3930
|
+
setLockFailedMessage(null);
|
|
3931
|
+
setAmount("");
|
|
3932
|
+
setCancelled(false);
|
|
3933
|
+
resetDeposit();
|
|
3934
|
+
};
|
|
3935
|
+
const handleDismissVerificationError = () => {
|
|
3936
|
+
setAmount("");
|
|
3937
|
+
setCancelled(false);
|
|
3938
|
+
resetDeposit();
|
|
3939
|
+
};
|
|
3940
|
+
const handleRetryVerification = () => {
|
|
3941
|
+
retryVerification().catch(() => {
|
|
3942
|
+
});
|
|
3943
|
+
};
|
|
3944
|
+
const back = view === "external-deposit" ? { to: "deposit", label: "Deposit from External Wallet" } : view === "credit-card-widget" ? { to: "deposit", label: "Buy with credit card and deposit" } : view === "select-token" ? { to: "deposit", label: "Deposit" } : { to: "method", label: "Deposit Method" };
|
|
3945
|
+
const goBack = () => {
|
|
3946
|
+
if (back.to === "method") {
|
|
3947
|
+
setAmount("");
|
|
3948
|
+
setSelectedTokenId("");
|
|
3949
|
+
}
|
|
3950
|
+
setView(back.to);
|
|
3951
|
+
};
|
|
3952
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
3953
|
+
onClose && /* @__PURE__ */ jsx(
|
|
3954
|
+
"button",
|
|
3955
|
+
{
|
|
3956
|
+
"data-privana-close": true,
|
|
3957
|
+
onClick: onClose,
|
|
3958
|
+
disabled: isUnsafeToClose,
|
|
3959
|
+
"aria-label": "Close",
|
|
3960
|
+
className: cn(
|
|
3961
|
+
"absolute top-6 right-5 z-20 flex h-5 w-5 items-center justify-center transition-colors",
|
|
3962
|
+
isUnsafeToClose ? "text-muted-foreground/40 cursor-not-allowed" : "text-muted-foreground hover:text-foreground cursor-pointer"
|
|
3963
|
+
),
|
|
3964
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
3965
|
+
}
|
|
3966
|
+
),
|
|
3967
|
+
flowView || activeView === "method" && !onExit ? /* @__PURE__ */ jsx("div", { className: "flex items-center px-5 py-4", children: /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-5 font-medium", children: appName }) }) : /* @__PURE__ */ jsxs(
|
|
3968
|
+
"button",
|
|
3969
|
+
{
|
|
3970
|
+
type: "button",
|
|
3971
|
+
onClick: activeView === "method" ? onExit : goBack,
|
|
3972
|
+
className: "text-foreground flex w-fit cursor-pointer items-center gap-2 px-5 py-4 text-sm font-medium transition-opacity hover:opacity-70",
|
|
3973
|
+
children: [
|
|
3974
|
+
/* @__PURE__ */ jsx(ChevronLeftIcon, {}),
|
|
3975
|
+
activeView === "method" ? appName : back.label
|
|
3976
|
+
]
|
|
3977
|
+
}
|
|
3978
|
+
),
|
|
3979
|
+
flowView && /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
3980
|
+
activeView === "deposit-success" && /* @__PURE__ */ jsx(
|
|
3981
|
+
TransactionSuccessView,
|
|
3982
|
+
{
|
|
3983
|
+
title: "Deposit Successful",
|
|
3984
|
+
message: `Your ${selectedToken?.symbol ?? ""} deposit has been processed.`,
|
|
3985
|
+
onDone: handleDepositDone
|
|
3986
|
+
}
|
|
3987
|
+
),
|
|
3988
|
+
activeView === "lock-error" && /* @__PURE__ */ jsx(
|
|
3989
|
+
TransactionWarningView,
|
|
3990
|
+
{
|
|
3991
|
+
title: "Deposit credited, lock failed",
|
|
3992
|
+
message: `Your deposit was credited but locking the funds for ${appName} failed: ${lockFailedMessage}`,
|
|
3993
|
+
onDone: handleLockFailedDone
|
|
3994
|
+
}
|
|
3995
|
+
),
|
|
3996
|
+
activeView === "deposit-timeout" && /* @__PURE__ */ jsx(
|
|
3997
|
+
TransactionWarningView,
|
|
3998
|
+
{
|
|
3999
|
+
title: "Deposit Processing",
|
|
4000
|
+
message: "Your transaction was confirmed but the deposit is still being processed. Please check your balance - it should update shortly.",
|
|
4001
|
+
onDone: handleDepositDone
|
|
4002
|
+
}
|
|
4003
|
+
),
|
|
4004
|
+
activeView === "deposit-error" && /* @__PURE__ */ jsx(
|
|
4005
|
+
TransactionErrorView,
|
|
4006
|
+
{
|
|
4007
|
+
title: "Verification failed",
|
|
4008
|
+
message: depositError?.message ? `Your transfer was sent on-chain but we could not verify the deposit. The funds are already at the deposit address \u2014 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 \u2014 retry verification instead of starting a new deposit.",
|
|
4009
|
+
explorerUrl: explorerTxUrl,
|
|
4010
|
+
explorerLabel: "View transaction",
|
|
4011
|
+
onRetry: handleRetryVerification,
|
|
4012
|
+
onDismiss: handleDismissVerificationError
|
|
4013
|
+
}
|
|
4014
|
+
),
|
|
4015
|
+
activeView === "depositing" && /* @__PURE__ */ jsx(
|
|
4016
|
+
TransactionProgressView,
|
|
4017
|
+
{
|
|
4018
|
+
title: "Depositing...",
|
|
4019
|
+
steps: depositSteps,
|
|
4020
|
+
onCancel: isGettingAddress || isSendingTransaction ? handleDepositCancel : void 0
|
|
4021
|
+
}
|
|
4022
|
+
)
|
|
4023
|
+
] }),
|
|
4024
|
+
activeView === "method" && /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
4025
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
4026
|
+
/* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: activeTab === "crypto" ? "Choose the deposit method" : "Buy with credit card and deposit" }),
|
|
4027
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-sm", children: activeTab === "crypto" ? "Choose the deposit method." : "Use credit card to buy crypto and deposit" })
|
|
4028
|
+
] }),
|
|
4029
|
+
/* @__PURE__ */ jsx(MethodTabs, { activeTab, onTabChange: setActiveTab }),
|
|
4030
|
+
activeTab === "crypto" ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
4031
|
+
/* @__PURE__ */ jsx(
|
|
4032
|
+
MethodOption,
|
|
4033
|
+
{
|
|
4034
|
+
title: "Connected wallet",
|
|
4035
|
+
description: "Deposit from your connected wallet.",
|
|
4036
|
+
onClick: () => {
|
|
4037
|
+
onSelectConnectedWallet?.();
|
|
4038
|
+
openDeposit("connected");
|
|
4039
|
+
}
|
|
4040
|
+
}
|
|
4041
|
+
),
|
|
4042
|
+
/* @__PURE__ */ jsx(
|
|
4043
|
+
MethodOption,
|
|
4044
|
+
{
|
|
4045
|
+
title: "External Wallet",
|
|
4046
|
+
description: "Send funds from external wallet or exchange.",
|
|
4047
|
+
onClick: () => {
|
|
4048
|
+
onSelectExternalWallet?.();
|
|
4049
|
+
openDeposit("external");
|
|
4050
|
+
}
|
|
4051
|
+
}
|
|
4052
|
+
)
|
|
4053
|
+
] }) : /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-3", children: /* @__PURE__ */ jsx(
|
|
4054
|
+
MethodOption,
|
|
4055
|
+
{
|
|
4056
|
+
title: "Moonpay",
|
|
4057
|
+
description: "on selected address",
|
|
4058
|
+
onClick: () => {
|
|
4059
|
+
onSelectCreditCard?.();
|
|
4060
|
+
openDeposit("credit-card");
|
|
4061
|
+
}
|
|
4062
|
+
}
|
|
4063
|
+
) })
|
|
4064
|
+
] }),
|
|
4065
|
+
activeView === "deposit" && /* @__PURE__ */ jsx(MoonPayGate, { enabled: source === "credit-card", children: /* @__PURE__ */ jsx(
|
|
4066
|
+
DepositView,
|
|
4067
|
+
{
|
|
4068
|
+
source,
|
|
4069
|
+
selectedToken,
|
|
4070
|
+
amount,
|
|
4071
|
+
allowance,
|
|
4072
|
+
onAmountChange: setAmount,
|
|
4073
|
+
onSelectToken: () => setView("select-token"),
|
|
4074
|
+
onConnectWallet,
|
|
4075
|
+
onSubmit: handleSubmit,
|
|
4076
|
+
isSubmitting: isPending
|
|
4077
|
+
}
|
|
4078
|
+
) }),
|
|
4079
|
+
activeView === "select-token" && /* @__PURE__ */ jsx(
|
|
4080
|
+
TokenSelectorView2,
|
|
4081
|
+
{
|
|
4082
|
+
selectedTokenId: selectedToken?.id,
|
|
4083
|
+
onSelect: (id) => {
|
|
4084
|
+
setSelectedTokenId(id);
|
|
4085
|
+
setView("deposit");
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
),
|
|
4089
|
+
activeView === "external-deposit" && /* @__PURE__ */ jsx(
|
|
4090
|
+
ExternalDepositView,
|
|
4091
|
+
{
|
|
4092
|
+
token: selectedToken,
|
|
4093
|
+
amount,
|
|
4094
|
+
onCredited: allowance ? void 0 : onDepositSuccess
|
|
4095
|
+
}
|
|
4096
|
+
),
|
|
4097
|
+
activeView === "credit-card-widget" && /* @__PURE__ */ jsx(MoonPayGate, { enabled: true, children: /* @__PURE__ */ jsx(
|
|
4098
|
+
CreditCardWidgetView,
|
|
4099
|
+
{
|
|
4100
|
+
token: selectedToken,
|
|
4101
|
+
amount,
|
|
4102
|
+
allowance,
|
|
4103
|
+
onCredited: allowance ? void 0 : finishCardPurchase,
|
|
4104
|
+
onLockSubmitted: finishCardPurchase,
|
|
4105
|
+
onLockFailed: (err) => {
|
|
4106
|
+
setLockFailedMessage(err.message);
|
|
4107
|
+
onLockFailed?.(err);
|
|
4108
|
+
}
|
|
4109
|
+
}
|
|
4110
|
+
) })
|
|
4111
|
+
] });
|
|
4112
|
+
}
|
|
4113
|
+
function DepositModal({ open, onClose, ...handlers }) {
|
|
4114
|
+
const titleId = useId();
|
|
4115
|
+
const descId = useId();
|
|
4116
|
+
const [isCloseBlocked, setIsCloseBlocked] = useState(false);
|
|
4117
|
+
const handleClose = () => {
|
|
4118
|
+
if (!isCloseBlocked) onClose();
|
|
4119
|
+
};
|
|
4120
|
+
return /* @__PURE__ */ jsx(
|
|
4121
|
+
Dialog,
|
|
4122
|
+
{
|
|
4123
|
+
open,
|
|
4124
|
+
onOpenChange: (isOpen) => {
|
|
4125
|
+
if (!isOpen) handleClose();
|
|
4126
|
+
},
|
|
4127
|
+
children: /* @__PURE__ */ jsxs(
|
|
4128
|
+
DialogContent,
|
|
4129
|
+
{
|
|
4130
|
+
"data-privana": true,
|
|
4131
|
+
showCloseButton: false,
|
|
4132
|
+
onInteractOutside: isCloseBlocked ? (e) => e.preventDefault() : void 0,
|
|
4133
|
+
onEscapeKeyDown: isCloseBlocked ? (e) => e.preventDefault() : void 0,
|
|
4134
|
+
className: "bg-card flex w-[560px] max-w-[95vw] flex-col gap-2 rounded-2xl border-0 p-2",
|
|
4135
|
+
"aria-labelledby": titleId,
|
|
4136
|
+
"aria-describedby": descId,
|
|
4137
|
+
children: [
|
|
4138
|
+
/* @__PURE__ */ jsx(DialogTitle, { id: titleId, className: "sr-only", children: "Deposit" }),
|
|
4139
|
+
/* @__PURE__ */ jsx(DialogDescription, { id: descId, className: "sr-only", children: "Deposit funds into your account." }),
|
|
4140
|
+
/* @__PURE__ */ jsx(
|
|
4141
|
+
DepositModalContent,
|
|
4142
|
+
{
|
|
4143
|
+
onClose: handleClose,
|
|
4144
|
+
onCloseBlockedChange: setIsCloseBlocked,
|
|
4145
|
+
...handlers
|
|
4146
|
+
}
|
|
4147
|
+
)
|
|
4148
|
+
]
|
|
4149
|
+
}
|
|
4150
|
+
)
|
|
4151
|
+
}
|
|
4152
|
+
);
|
|
4153
|
+
}
|
|
4154
|
+
function DepositInlineModal({ className, ...handlers }) {
|
|
4155
|
+
return /* @__PURE__ */ jsx(
|
|
4156
|
+
"div",
|
|
4157
|
+
{
|
|
4158
|
+
"data-privana": true,
|
|
4159
|
+
className: cn(
|
|
4160
|
+
"bg-card relative flex w-[560px] max-w-full flex-col gap-2 overflow-hidden rounded-2xl p-2 shadow-lg",
|
|
4161
|
+
className
|
|
4162
|
+
),
|
|
4163
|
+
children: /* @__PURE__ */ jsx(DepositModalContent, { ...handlers })
|
|
4164
|
+
}
|
|
4165
|
+
);
|
|
4166
|
+
}
|
|
4167
|
+
function WithdrawView({
|
|
4168
|
+
selectedToken,
|
|
4169
|
+
amount,
|
|
4170
|
+
onAmountChange,
|
|
4171
|
+
onSelectToken,
|
|
4172
|
+
onPendingChange
|
|
4173
|
+
}) {
|
|
4174
|
+
const { chains, getChainById: getChainById2 } = usePrivanaContext();
|
|
4175
|
+
const { isConnected, address } = useAccount();
|
|
4176
|
+
const [showSuccess, setShowSuccess] = useState(false);
|
|
4177
|
+
const [showTimeout, setShowTimeout] = useState(false);
|
|
4178
|
+
const [cancelled, setCancelled] = useState(false);
|
|
4179
|
+
const targetChain = selectedToken ? getChainById2(selectedToken.chainId) ?? chains[0] : chains[0];
|
|
4180
|
+
const {
|
|
4181
|
+
balanceWei,
|
|
4182
|
+
isLoading: isBalanceLoading,
|
|
4183
|
+
isError: isBalanceError
|
|
4184
|
+
} = useBalance({
|
|
4185
|
+
tokenId: selectedToken?.id,
|
|
4186
|
+
enabled: !!selectedToken
|
|
4187
|
+
});
|
|
4188
|
+
const formattedBalance = selectedToken ? formatTokenAmount(balanceWei, selectedToken.decimals) : "0.00";
|
|
4189
|
+
const { withdraw, isPending, currentStep, error, reset } = useWithdraw({
|
|
4190
|
+
onProcessingSuccess: () => {
|
|
4191
|
+
onAmountChange("");
|
|
4192
|
+
setShowSuccess(true);
|
|
4193
|
+
},
|
|
4194
|
+
onProcessingTimeout: () => {
|
|
4195
|
+
onAmountChange("");
|
|
4196
|
+
setShowTimeout(true);
|
|
4197
|
+
}
|
|
4198
|
+
});
|
|
4199
|
+
const explorerUrl = address && targetChain ? getExplorerAddressUrl(targetChain.id, address) : void 0;
|
|
4200
|
+
const getStepStatus = (step, after) => {
|
|
4201
|
+
if (currentStep === step) return "active";
|
|
4202
|
+
if (after.includes(currentStep)) return "completed";
|
|
4203
|
+
return "pending";
|
|
4204
|
+
};
|
|
4205
|
+
const withdrawSteps = [
|
|
4206
|
+
{
|
|
4207
|
+
label: "Switching to signing chain",
|
|
4208
|
+
status: getStepStatus("switching-chain", ["signing", "submitting", "processing"])
|
|
4209
|
+
},
|
|
4210
|
+
{ label: "Sign in wallet", status: getStepStatus("signing", ["submitting", "processing"]) },
|
|
4211
|
+
{ label: "Submitting withdrawal", status: getStepStatus("submitting", ["processing"]) },
|
|
4212
|
+
{ label: "Processing \u2014 may take a minute or two", status: getStepStatus("processing", []) }
|
|
4213
|
+
];
|
|
4214
|
+
useEffect(() => {
|
|
4215
|
+
if (error) {
|
|
4216
|
+
toast.error(error.message.length > 100 ? `${error.message.slice(0, 100)}...` : error.message);
|
|
4217
|
+
}
|
|
4218
|
+
}, [error]);
|
|
4219
|
+
useEffect(() => {
|
|
4220
|
+
onPendingChange?.(isPending && !cancelled);
|
|
4221
|
+
}, [isPending, cancelled, onPendingChange]);
|
|
4222
|
+
const hasValidAmount = !!amount && parseFloat(amount) > 0;
|
|
4223
|
+
const tooManyDecimals = !!hasValidAmount && !!selectedToken && amount.includes(".") && amount.split(".")[1].length > selectedToken.decimals;
|
|
4224
|
+
const exceedsBalance = !!hasValidAmount && !tooManyDecimals && !!selectedToken && !isBalanceLoading && !isBalanceError && parseTokenAmount(amount, selectedToken.decimals) > BigInt(balanceWei);
|
|
4225
|
+
const canWithdraw = isConnected && hasValidAmount && !!selectedToken && !tooManyDecimals && !exceedsBalance;
|
|
4226
|
+
const handleMax = () => {
|
|
4227
|
+
if (!selectedToken) return;
|
|
4228
|
+
const max = formattedBalance.replace(/\s/g, "");
|
|
4229
|
+
if (parseFloat(max) > 0) onAmountChange(max);
|
|
4230
|
+
};
|
|
4231
|
+
const handleWithdraw = async () => {
|
|
4232
|
+
if (!selectedToken || !canWithdraw) return;
|
|
4233
|
+
setCancelled(false);
|
|
4234
|
+
await withdraw({
|
|
4235
|
+
tokenId: selectedToken.id,
|
|
4236
|
+
amount: parseTokenAmount(amount, selectedToken.decimals)
|
|
4237
|
+
});
|
|
4238
|
+
};
|
|
4239
|
+
const handleCancel = () => {
|
|
4240
|
+
setCancelled(true);
|
|
4241
|
+
reset();
|
|
4242
|
+
};
|
|
4243
|
+
const handleDone = () => {
|
|
4244
|
+
setShowSuccess(false);
|
|
4245
|
+
setShowTimeout(false);
|
|
4246
|
+
setCancelled(false);
|
|
4247
|
+
reset();
|
|
4248
|
+
};
|
|
4249
|
+
if (showSuccess && selectedToken) {
|
|
4250
|
+
return /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: /* @__PURE__ */ jsx(
|
|
4251
|
+
TransactionSuccessView,
|
|
4252
|
+
{
|
|
4253
|
+
title: "Withdrawal Complete",
|
|
4254
|
+
message: `Your ${selectedToken.symbol} withdrawal has been processed. Funds should appear in your wallet shortly.`,
|
|
4255
|
+
explorerUrl,
|
|
4256
|
+
explorerLabel: targetChain ? getExplorerLabel(targetChain.id) : void 0,
|
|
4257
|
+
onDone: handleDone
|
|
4258
|
+
}
|
|
4259
|
+
) });
|
|
4260
|
+
}
|
|
4261
|
+
if (showTimeout) {
|
|
4262
|
+
return /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: /* @__PURE__ */ jsx(
|
|
4263
|
+
TransactionWarningView,
|
|
4264
|
+
{
|
|
4265
|
+
title: "Withdrawal Processing",
|
|
4266
|
+
message: "Your withdrawal is still being processed. Please check your balance \u2014 it should update shortly.",
|
|
4267
|
+
onDone: handleDone
|
|
4268
|
+
}
|
|
4269
|
+
) });
|
|
4270
|
+
}
|
|
4271
|
+
if (isPending && !cancelled) {
|
|
4272
|
+
const canCancel = currentStep === "idle" || currentStep === "switching-chain" || currentStep === "signing";
|
|
4273
|
+
return /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: /* @__PURE__ */ jsx(
|
|
4274
|
+
TransactionProgressView,
|
|
4275
|
+
{
|
|
4276
|
+
title: "Withdrawing...",
|
|
4277
|
+
steps: withdrawSteps,
|
|
4278
|
+
onCancel: canCancel ? handleCancel : void 0
|
|
4279
|
+
}
|
|
4280
|
+
) });
|
|
4281
|
+
}
|
|
4282
|
+
return /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
4283
|
+
/* @__PURE__ */ jsx("h2", { className: "text-foreground text-[28px] leading-8 font-medium", children: "Withdraw" }),
|
|
4284
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
4285
|
+
/* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Token" }),
|
|
4286
|
+
/* @__PURE__ */ jsxs(
|
|
4287
|
+
"button",
|
|
4288
|
+
{
|
|
4289
|
+
type: "button",
|
|
4290
|
+
onClick: onSelectToken,
|
|
4291
|
+
className: "border-border bg-input flex w-full cursor-pointer items-center gap-3 rounded-lg border p-3 text-left",
|
|
4292
|
+
children: [
|
|
4293
|
+
selectedToken ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4294
|
+
/* @__PURE__ */ jsx("div", { className: "h-8 w-8 shrink-0 overflow-hidden rounded-full", children: getTokenIcon(selectedToken.symbol, 32) }),
|
|
4295
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-1 flex-col items-start gap-1", children: [
|
|
4296
|
+
/* @__PURE__ */ jsx("span", { className: "text-foreground text-sm font-medium", children: selectedToken.symbol }),
|
|
4297
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-xs", children: [
|
|
4298
|
+
"on ",
|
|
4299
|
+
targetChain?.name ?? "\u2014"
|
|
4300
|
+
] })
|
|
4301
|
+
] })
|
|
4302
|
+
] }) : /* @__PURE__ */ jsx("span", { className: "text-muted-foreground flex-1 text-sm", children: "Select token" }),
|
|
4303
|
+
/* @__PURE__ */ jsx("div", { className: "text-muted-foreground flex h-5 w-5 items-center justify-center", children: /* @__PURE__ */ jsx(ChevronRightIcon, {}) })
|
|
4304
|
+
]
|
|
4305
|
+
}
|
|
4306
|
+
)
|
|
4307
|
+
] }),
|
|
4308
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
4309
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
|
|
4310
|
+
/* @__PURE__ */ jsx("label", { className: "text-muted-foreground text-sm", children: "Amount" }),
|
|
4311
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground text-sm", children: [
|
|
4312
|
+
"Available ",
|
|
4313
|
+
formattedBalance,
|
|
4314
|
+
" ",
|
|
4315
|
+
selectedToken?.symbol
|
|
4316
|
+
] })
|
|
4317
|
+
] }),
|
|
4318
|
+
/* @__PURE__ */ jsxs("div", { className: "border-border bg-input flex items-center gap-2 rounded-[10px] border py-1 pr-1 pl-3", children: [
|
|
4319
|
+
/* @__PURE__ */ jsx(
|
|
4320
|
+
"input",
|
|
4321
|
+
{
|
|
4322
|
+
type: "text",
|
|
4323
|
+
inputMode: "decimal",
|
|
4324
|
+
placeholder: "Enter Amount",
|
|
4325
|
+
value: amount,
|
|
4326
|
+
onChange: (e) => {
|
|
4327
|
+
const value = e.target.value.replace(/[^0-9.,]/g, "").replace(/,/g, ".");
|
|
4328
|
+
if (value.split(".").length <= 2) onAmountChange(value);
|
|
4329
|
+
},
|
|
4330
|
+
className: "text-foreground placeholder:text-muted-foreground/50 flex-1 bg-transparent text-sm outline-none"
|
|
4331
|
+
}
|
|
4332
|
+
),
|
|
4333
|
+
/* @__PURE__ */ jsx(
|
|
4334
|
+
"button",
|
|
4335
|
+
{
|
|
4336
|
+
type: "button",
|
|
4337
|
+
onClick: handleMax,
|
|
4338
|
+
className: "bg-secondary text-foreground hover:bg-secondary/80 cursor-pointer rounded px-3 py-2.5 text-xs font-semibold transition-colors",
|
|
4339
|
+
children: "MAX"
|
|
4340
|
+
}
|
|
4341
|
+
)
|
|
4342
|
+
] }),
|
|
4343
|
+
tooManyDecimals && selectedToken && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
4344
|
+
"Too many decimal places (max: ",
|
|
4345
|
+
selectedToken.decimals,
|
|
4346
|
+
")"
|
|
4347
|
+
] }),
|
|
4348
|
+
exceedsBalance && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Insufficient balance" })
|
|
4349
|
+
] }),
|
|
4350
|
+
/* @__PURE__ */ jsx(
|
|
4351
|
+
"button",
|
|
4352
|
+
{
|
|
4353
|
+
type: "button",
|
|
4354
|
+
disabled: !canWithdraw,
|
|
4355
|
+
onClick: handleWithdraw,
|
|
4356
|
+
className: "bg-primary text-primary-foreground hover:bg-primary/90 flex h-10 w-full 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",
|
|
4357
|
+
children: !isConnected ? "Connect Wallet" : "Withdraw"
|
|
4358
|
+
}
|
|
4359
|
+
)
|
|
4360
|
+
] });
|
|
4361
|
+
}
|
|
4362
|
+
function WithdrawModalContent({
|
|
4363
|
+
onClose,
|
|
4364
|
+
onPendingChange,
|
|
4365
|
+
onBack
|
|
4366
|
+
}) {
|
|
4367
|
+
const { serviceName, enabledTokens, defaultToken, hostedAuthConfig } = usePrivanaContext();
|
|
4368
|
+
const { address } = useAccount();
|
|
4369
|
+
const appName = serviceName ?? "Privana";
|
|
4370
|
+
const [view, setView] = useState("form");
|
|
4371
|
+
const [selectedTokenId, setSelectedTokenId] = useState(defaultToken?.id ?? "");
|
|
4372
|
+
const [amount, setAmount] = useState("");
|
|
4373
|
+
const [isPending, setIsPending] = useState(false);
|
|
4374
|
+
const handlePendingChange = (pending) => {
|
|
4375
|
+
setIsPending(pending);
|
|
4376
|
+
onPendingChange?.(pending);
|
|
4377
|
+
};
|
|
4378
|
+
const selectedToken = enabledTokens.find((t) => t.id === selectedTokenId) ?? defaultToken;
|
|
4379
|
+
const prevAddressRef = useRef(address);
|
|
4380
|
+
useEffect(() => {
|
|
4381
|
+
const prev = prevAddressRef.current;
|
|
4382
|
+
prevAddressRef.current = address;
|
|
4383
|
+
if (hostedAuthConfig) return;
|
|
4384
|
+
if (prev && address && prev !== address) {
|
|
4385
|
+
setView("form");
|
|
4386
|
+
setAmount("");
|
|
4387
|
+
setSelectedTokenId("");
|
|
4388
|
+
}
|
|
4389
|
+
}, [address, hostedAuthConfig]);
|
|
4390
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4391
|
+
onClose && !isPending && /* @__PURE__ */ jsx(
|
|
4392
|
+
"button",
|
|
4393
|
+
{
|
|
4394
|
+
"data-privana-close": true,
|
|
4395
|
+
onClick: onClose,
|
|
4396
|
+
"aria-label": "Close",
|
|
4397
|
+
className: "text-muted-foreground hover:text-foreground absolute top-6 right-5 z-20 flex h-5 w-5 cursor-pointer items-center justify-center transition-colors",
|
|
4398
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
4399
|
+
}
|
|
4400
|
+
),
|
|
4401
|
+
view === "select-token" ? /* @__PURE__ */ jsxs(
|
|
4402
|
+
"button",
|
|
4403
|
+
{
|
|
4404
|
+
type: "button",
|
|
4405
|
+
onClick: () => setView("form"),
|
|
4406
|
+
className: "text-foreground flex w-fit cursor-pointer items-center gap-2 px-5 py-4 text-sm font-medium transition-opacity hover:opacity-70",
|
|
4407
|
+
children: [
|
|
4408
|
+
/* @__PURE__ */ jsx(ChevronLeftIcon, {}),
|
|
4409
|
+
"Withdraw"
|
|
4410
|
+
]
|
|
4411
|
+
}
|
|
4412
|
+
) : onBack && !isPending ? /* @__PURE__ */ jsxs(
|
|
4413
|
+
"button",
|
|
4414
|
+
{
|
|
4415
|
+
type: "button",
|
|
4416
|
+
onClick: onBack,
|
|
4417
|
+
className: "text-foreground flex w-fit cursor-pointer items-center gap-2 px-5 py-4 text-sm font-medium transition-opacity hover:opacity-70",
|
|
4418
|
+
children: [
|
|
4419
|
+
/* @__PURE__ */ jsx(ChevronLeftIcon, {}),
|
|
4420
|
+
appName
|
|
4421
|
+
]
|
|
4422
|
+
}
|
|
4423
|
+
) : /* @__PURE__ */ jsx("div", { className: "flex items-center px-5 py-4", children: /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-5 font-medium", children: appName }) }),
|
|
4424
|
+
view === "form" && /* @__PURE__ */ jsx(
|
|
4425
|
+
WithdrawView,
|
|
4426
|
+
{
|
|
4427
|
+
selectedToken,
|
|
4428
|
+
amount,
|
|
4429
|
+
onAmountChange: setAmount,
|
|
4430
|
+
onSelectToken: () => setView("select-token"),
|
|
4431
|
+
onPendingChange: handlePendingChange
|
|
4432
|
+
}
|
|
4433
|
+
),
|
|
4434
|
+
view === "select-token" && /* @__PURE__ */ jsx(
|
|
4435
|
+
TokenSelectorView2,
|
|
4436
|
+
{
|
|
4437
|
+
selectedTokenId: selectedToken?.id,
|
|
4438
|
+
onSelect: (id) => {
|
|
4439
|
+
setSelectedTokenId(id);
|
|
4440
|
+
setView("form");
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
)
|
|
4444
|
+
] });
|
|
4445
|
+
}
|
|
4446
|
+
function WithdrawModal({ open, onClose }) {
|
|
4447
|
+
const titleId = useId();
|
|
4448
|
+
const descId = useId();
|
|
4449
|
+
const [isCloseBlocked, setIsCloseBlocked] = useState(false);
|
|
4450
|
+
const handleClose = () => {
|
|
4451
|
+
if (!isCloseBlocked) onClose();
|
|
4452
|
+
};
|
|
4453
|
+
return /* @__PURE__ */ jsx(
|
|
4454
|
+
Dialog,
|
|
4455
|
+
{
|
|
4456
|
+
open,
|
|
4457
|
+
onOpenChange: (isOpen) => {
|
|
4458
|
+
if (!isOpen) handleClose();
|
|
4459
|
+
},
|
|
4460
|
+
children: /* @__PURE__ */ jsxs(
|
|
4461
|
+
DialogContent,
|
|
4462
|
+
{
|
|
4463
|
+
"data-privana": true,
|
|
4464
|
+
showCloseButton: false,
|
|
4465
|
+
onInteractOutside: isCloseBlocked ? (e) => e.preventDefault() : void 0,
|
|
4466
|
+
onEscapeKeyDown: isCloseBlocked ? (e) => e.preventDefault() : void 0,
|
|
4467
|
+
className: "bg-card flex w-[560px] max-w-[95vw] flex-col gap-2 rounded-2xl border-0 p-2",
|
|
4468
|
+
"aria-labelledby": titleId,
|
|
4469
|
+
"aria-describedby": descId,
|
|
4470
|
+
children: [
|
|
4471
|
+
/* @__PURE__ */ jsx(DialogTitle, { id: titleId, className: "sr-only", children: "Withdraw" }),
|
|
4472
|
+
/* @__PURE__ */ jsx(DialogDescription, { id: descId, className: "sr-only", children: "Withdraw funds from your account." }),
|
|
4473
|
+
/* @__PURE__ */ jsx(WithdrawModalContent, { onClose: handleClose, onPendingChange: setIsCloseBlocked })
|
|
4474
|
+
]
|
|
4475
|
+
}
|
|
4476
|
+
)
|
|
4477
|
+
}
|
|
4478
|
+
);
|
|
4479
|
+
}
|
|
4480
|
+
function WithdrawInlineModal({ className }) {
|
|
4481
|
+
return /* @__PURE__ */ jsx(
|
|
4482
|
+
"div",
|
|
4483
|
+
{
|
|
4484
|
+
"data-privana": true,
|
|
4485
|
+
className: cn(
|
|
4486
|
+
"bg-card relative flex w-[560px] max-w-full flex-col gap-2 overflow-hidden rounded-2xl p-2 shadow-lg",
|
|
4487
|
+
className
|
|
4488
|
+
),
|
|
4489
|
+
children: /* @__PURE__ */ jsx(WithdrawModalContent, {})
|
|
4490
|
+
}
|
|
4491
|
+
);
|
|
4492
|
+
}
|
|
4493
|
+
var AVAILABLE_COLOR = "bg-[#007bff]";
|
|
4494
|
+
var IN_USE_COLOR = "bg-[#4fc77f]";
|
|
4495
|
+
function toInUseWei(value) {
|
|
4496
|
+
let parsed;
|
|
4497
|
+
try {
|
|
4498
|
+
parsed = typeof value === "bigint" ? value : BigInt(value);
|
|
4499
|
+
} catch {
|
|
4500
|
+
console.warn(`[privana-sdk] Invalid session.inUse value: ${value}`);
|
|
4501
|
+
return 0n;
|
|
4502
|
+
}
|
|
4503
|
+
if (parsed < 0n) {
|
|
4504
|
+
console.warn(`[privana-sdk] Negative session.inUse value: ${value}`);
|
|
4505
|
+
return 0n;
|
|
4506
|
+
}
|
|
4507
|
+
return parsed;
|
|
4508
|
+
}
|
|
4509
|
+
function useNow(intervalMs, enabled) {
|
|
4510
|
+
const [, setTick] = useState(0);
|
|
4511
|
+
useEffect(() => {
|
|
4512
|
+
if (!enabled) return;
|
|
4513
|
+
const id = setInterval(() => setTick((t) => t + 1), intervalMs);
|
|
4514
|
+
return () => clearInterval(id);
|
|
4515
|
+
}, [intervalMs, enabled]);
|
|
4516
|
+
}
|
|
4517
|
+
function SegmentedBalanceBar({
|
|
4518
|
+
availableWei,
|
|
4519
|
+
inUseWei
|
|
4520
|
+
}) {
|
|
4521
|
+
const total = availableWei + inUseWei;
|
|
4522
|
+
const showAvailable = inUseWei === 0n || availableWei > 0n;
|
|
4523
|
+
const showInUse = inUseWei > 0n;
|
|
4524
|
+
const grow = (part) => total > 0n ? Number(part * 1000n / total) : 1;
|
|
4525
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-1.5 w-full", children: [
|
|
4526
|
+
showAvailable && /* @__PURE__ */ jsx(
|
|
4527
|
+
"div",
|
|
4528
|
+
{
|
|
4529
|
+
className: cn(AVAILABLE_COLOR, "rounded-l-full", !showInUse && "rounded-r-full"),
|
|
4530
|
+
style: { flexGrow: grow(availableWei) }
|
|
4531
|
+
}
|
|
4532
|
+
),
|
|
4533
|
+
showInUse && /* @__PURE__ */ jsx(
|
|
4534
|
+
"div",
|
|
4535
|
+
{
|
|
4536
|
+
className: cn(IN_USE_COLOR, "rounded-r-full", !showAvailable && "rounded-l-full"),
|
|
4537
|
+
style: { flexGrow: grow(inUseWei) }
|
|
4538
|
+
}
|
|
4539
|
+
)
|
|
4540
|
+
] });
|
|
4541
|
+
}
|
|
4542
|
+
function BalanceLegendItem({ color, label }) {
|
|
4543
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
|
|
4544
|
+
/* @__PURE__ */ jsx("span", { className: cn("h-2.5 w-2.5 rounded-full", color) }),
|
|
4545
|
+
/* @__PURE__ */ jsx("span", { className: "text-foreground text-sm leading-5", children: label })
|
|
4546
|
+
] });
|
|
4547
|
+
}
|
|
4548
|
+
function WalletBalanceView({
|
|
4549
|
+
session,
|
|
4550
|
+
allowance,
|
|
4551
|
+
amount,
|
|
4552
|
+
onAmountChange,
|
|
4553
|
+
onPlay,
|
|
4554
|
+
onAddFunds,
|
|
4555
|
+
onWithdraw
|
|
4556
|
+
}) {
|
|
4557
|
+
const { serviceName, serviceIcon, defaultToken } = usePrivanaContext();
|
|
4558
|
+
const appName = serviceName ?? "Privana";
|
|
4559
|
+
const token = defaultToken;
|
|
4560
|
+
const {
|
|
4561
|
+
balanceWei,
|
|
4562
|
+
isLoading: isBalanceLoading,
|
|
4563
|
+
isError: isBalanceError
|
|
4564
|
+
} = useBalance({ tokenId: token?.id, enabled: !!token });
|
|
4565
|
+
const inUseWei = session ? toInUseWei(session.inUse) : 0n;
|
|
4566
|
+
const availableWei = BigInt(balanceWei);
|
|
4567
|
+
const variant = !session ? "idle" : inUseWei === 0n ? "session-zero" : availableWei === 0n ? "fully-in-use" : "mixed";
|
|
4568
|
+
const expiry = session?.expiry;
|
|
4569
|
+
useNow(3e4, expiry != null);
|
|
4570
|
+
const countdown = expiry != null ? formatTimeRemaining(expiry) : null;
|
|
4571
|
+
const decimals = token?.decimals ?? 18;
|
|
4572
|
+
const totalFormatted = formatTokenAmount((availableWei + inUseWei).toString(), decimals);
|
|
4573
|
+
const availableFormatted = formatTokenAmount(availableWei.toString(), decimals);
|
|
4574
|
+
const inUseFormatted = formatTokenAmount(inUseWei.toString(), decimals);
|
|
4575
|
+
const hasValidAmount = !!amount && parseFloat(amount) > 0;
|
|
4576
|
+
const tooManyDecimals = hasValidAmount && !!token && amount.includes(".") && amount.split(".")[1].length > token.decimals;
|
|
4577
|
+
const exceedsBalance = hasValidAmount && !tooManyDecimals && !!token && !isBalanceLoading && !isBalanceError && parseTokenAmount(amount, token.decimals) > availableWei;
|
|
4578
|
+
const canPlay = hasValidAmount && !!token && !tooManyDecimals && !exceedsBalance && !isBalanceLoading && !isBalanceError;
|
|
4579
|
+
const handleMax = () => {
|
|
4580
|
+
const max = availableFormatted.replace(/\s/g, "");
|
|
4581
|
+
if (parseFloat(max) > 0) onAmountChange(max);
|
|
4582
|
+
};
|
|
4583
|
+
const handlePlay = () => {
|
|
4584
|
+
if (!token || !canPlay || !onPlay) return;
|
|
4585
|
+
onPlay({ tokenId: token.id, amount });
|
|
4586
|
+
onAmountChange("");
|
|
4587
|
+
};
|
|
4588
|
+
const showInput = variant !== "fully-in-use";
|
|
4589
|
+
const showInlinePlay = variant === "session-zero" || variant === "mixed";
|
|
4590
|
+
return /* @__PURE__ */ jsxs("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: [
|
|
4591
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
4592
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
4593
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm leading-[14px]", children: "Total" }),
|
|
4594
|
+
/* @__PURE__ */ jsx("span", { className: "bg-secondary text-muted-foreground rounded-full px-2 py-[5px] text-[10px] leading-[10px] font-bold", children: token?.symbol ?? "\u2014" })
|
|
4595
|
+
] }),
|
|
4596
|
+
isBalanceLoading ? /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-40" }) : isBalanceError ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-[32px] leading-9 font-medium", children: "\u2014" }) : /* @__PURE__ */ jsx("span", { className: "text-foreground text-[32px] leading-9 font-medium", children: totalFormatted })
|
|
4597
|
+
] }),
|
|
4598
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
4599
|
+
/* @__PURE__ */ jsx(SegmentedBalanceBar, { availableWei, inUseWei }),
|
|
4600
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-2", children: [
|
|
4601
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: variant === "fully-in-use" ? /* @__PURE__ */ jsx(BalanceLegendItem, { color: IN_USE_COLOR, label: "In use" }) : variant === "mixed" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4602
|
+
/* @__PURE__ */ jsx(
|
|
4603
|
+
BalanceLegendItem,
|
|
4604
|
+
{
|
|
4605
|
+
color: AVAILABLE_COLOR,
|
|
4606
|
+
label: `Available (${availableFormatted})`
|
|
4607
|
+
}
|
|
4608
|
+
),
|
|
4609
|
+
/* @__PURE__ */ jsx(BalanceLegendItem, { color: IN_USE_COLOR, label: `In use (${inUseFormatted})` })
|
|
4610
|
+
] }) : /* @__PURE__ */ jsx(BalanceLegendItem, { color: AVAILABLE_COLOR, label: "Available" }) }),
|
|
4611
|
+
countdown && /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-sm leading-5 whitespace-nowrap", children: countdown })
|
|
4612
|
+
] })
|
|
4613
|
+
] }),
|
|
4614
|
+
showInput && /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-3", children: [
|
|
4615
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
4616
|
+
/* @__PURE__ */ jsxs("div", { className: "border-border bg-input flex h-10 flex-1 items-center gap-2 rounded-[10px] border py-1 pr-1 pl-3", children: [
|
|
4617
|
+
/* @__PURE__ */ jsx(
|
|
4618
|
+
"input",
|
|
4619
|
+
{
|
|
4620
|
+
type: "text",
|
|
4621
|
+
inputMode: "decimal",
|
|
4622
|
+
placeholder: "Enter Amount",
|
|
4623
|
+
value: amount,
|
|
4624
|
+
onChange: (e) => {
|
|
4625
|
+
const value = e.target.value.replace(/[^0-9.,]/g, "").replace(/,/g, ".");
|
|
4626
|
+
if (value.split(".").length <= 2) onAmountChange(value);
|
|
4627
|
+
},
|
|
4628
|
+
className: "text-foreground placeholder:text-muted-foreground/50 min-w-0 flex-1 bg-transparent text-sm outline-none"
|
|
4629
|
+
}
|
|
4630
|
+
),
|
|
4631
|
+
/* @__PURE__ */ jsx(
|
|
4632
|
+
"button",
|
|
4633
|
+
{
|
|
4634
|
+
type: "button",
|
|
4635
|
+
onClick: handleMax,
|
|
4636
|
+
className: "text-foreground cursor-pointer rounded px-3 py-2.5 text-xs font-semibold",
|
|
4637
|
+
children: "MAX"
|
|
4638
|
+
}
|
|
4639
|
+
)
|
|
4640
|
+
] }),
|
|
4641
|
+
showInlinePlay && /* @__PURE__ */ jsx(
|
|
4642
|
+
"button",
|
|
4643
|
+
{
|
|
4644
|
+
type: "button",
|
|
4645
|
+
disabled: !onPlay || !canPlay,
|
|
4646
|
+
onClick: handlePlay,
|
|
4647
|
+
className: "border-border flex h-10 min-w-20 cursor-pointer items-center justify-center rounded-[10px] border px-3 text-sm font-medium text-[#4fc77f] transition-opacity hover:opacity-80 disabled:cursor-not-allowed disabled:opacity-50",
|
|
4648
|
+
children: "Play"
|
|
4649
|
+
}
|
|
4650
|
+
)
|
|
4651
|
+
] }),
|
|
4652
|
+
tooManyDecimals && token && /* @__PURE__ */ jsxs("p", { className: "text-destructive text-sm", children: [
|
|
4653
|
+
"Too many decimal places (max: ",
|
|
4654
|
+
token.decimals,
|
|
4655
|
+
")"
|
|
4656
|
+
] }),
|
|
4657
|
+
exceedsBalance && /* @__PURE__ */ jsx("p", { className: "text-destructive text-sm", children: "Insufficient balance" })
|
|
4658
|
+
] }),
|
|
4659
|
+
variant === "idle" && allowance && /* @__PURE__ */ jsx("div", { className: "bg-muted rounded-[10px] p-4", children: /* @__PURE__ */ jsx(
|
|
4660
|
+
AllowancePolicySection,
|
|
4661
|
+
{
|
|
4662
|
+
allowance,
|
|
4663
|
+
serviceName: appName,
|
|
4664
|
+
serviceIcon
|
|
4665
|
+
}
|
|
4666
|
+
) }),
|
|
4667
|
+
/* @__PURE__ */ jsx("div", { className: "flex items-start gap-4", children: variant === "idle" ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4668
|
+
/* @__PURE__ */ jsx(
|
|
4669
|
+
"button",
|
|
4670
|
+
{
|
|
4671
|
+
type: "button",
|
|
4672
|
+
disabled: !onPlay || !canPlay,
|
|
4673
|
+
onClick: handlePlay,
|
|
4674
|
+
className: "bg-primary text-primary-foreground hover:bg-primary/90 flex h-10 flex-1 cursor-pointer items-center justify-center rounded-[10px] px-3 py-2 text-sm font-medium transition-colors disabled:cursor-not-allowed disabled:opacity-50",
|
|
4675
|
+
children: "Play"
|
|
4676
|
+
}
|
|
4677
|
+
),
|
|
4678
|
+
/* @__PURE__ */ jsx(
|
|
4679
|
+
"button",
|
|
4680
|
+
{
|
|
4681
|
+
type: "button",
|
|
4682
|
+
disabled: !onAddFunds,
|
|
4683
|
+
onClick: onAddFunds,
|
|
4684
|
+
className: "bg-secondary text-foreground hover:bg-secondary/80 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",
|
|
4685
|
+
children: "Add funds"
|
|
4686
|
+
}
|
|
4687
|
+
)
|
|
4688
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4689
|
+
/* @__PURE__ */ jsx(
|
|
4690
|
+
"button",
|
|
4691
|
+
{
|
|
4692
|
+
type: "button",
|
|
4693
|
+
disabled: !onAddFunds,
|
|
4694
|
+
onClick: onAddFunds,
|
|
4695
|
+
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",
|
|
4696
|
+
children: "Add Funds"
|
|
4697
|
+
}
|
|
4698
|
+
),
|
|
4699
|
+
/* @__PURE__ */ jsx(
|
|
4700
|
+
"button",
|
|
4701
|
+
{
|
|
4702
|
+
type: "button",
|
|
4703
|
+
disabled: !onWithdraw,
|
|
4704
|
+
onClick: onWithdraw,
|
|
4705
|
+
className: "bg-secondary text-foreground hover:bg-secondary/80 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",
|
|
4706
|
+
children: "Withdraw"
|
|
4707
|
+
}
|
|
4708
|
+
)
|
|
4709
|
+
] }) })
|
|
4710
|
+
] });
|
|
4711
|
+
}
|
|
4712
|
+
function WalletModalContent({
|
|
4713
|
+
session,
|
|
4714
|
+
allowance,
|
|
4715
|
+
onPlay,
|
|
4716
|
+
onEndSession,
|
|
4717
|
+
onDepositSuccess,
|
|
4718
|
+
onClose,
|
|
4719
|
+
onCloseBlockedChange,
|
|
4720
|
+
...depositHandlers
|
|
4721
|
+
}) {
|
|
4722
|
+
const { serviceName, hostedAuthConfig } = usePrivanaContext();
|
|
4723
|
+
const { address } = useAccount();
|
|
4724
|
+
const appName = serviceName ?? "Privana";
|
|
4725
|
+
const [view, setView] = useState("balance");
|
|
4726
|
+
const [amount, setAmount] = useState("");
|
|
4727
|
+
const [depositBlocked, setDepositBlocked] = useState(false);
|
|
4728
|
+
const [withdrawPending, setWithdrawPending] = useState(false);
|
|
4729
|
+
const [endSessionError, setEndSessionError] = useState(null);
|
|
4730
|
+
const endSessionRunRef = useRef(0);
|
|
4731
|
+
const prevAddressRef = useRef(address);
|
|
4732
|
+
useEffect(() => {
|
|
4733
|
+
const prev = prevAddressRef.current;
|
|
4734
|
+
prevAddressRef.current = address;
|
|
4735
|
+
if (hostedAuthConfig) return;
|
|
4736
|
+
if (prev && address && prev !== address) {
|
|
4737
|
+
setView("balance");
|
|
4738
|
+
setAmount("");
|
|
4739
|
+
setDepositBlocked(false);
|
|
4740
|
+
setWithdrawPending(false);
|
|
4741
|
+
setEndSessionError(null);
|
|
4742
|
+
endSessionRunRef.current++;
|
|
4743
|
+
}
|
|
4744
|
+
}, [address, hostedAuthConfig]);
|
|
4745
|
+
const closeBlocked = depositBlocked || withdrawPending || view === "ending-session";
|
|
4746
|
+
useEffect(() => {
|
|
4747
|
+
onCloseBlockedChange?.(closeBlocked);
|
|
4748
|
+
}, [closeBlocked, onCloseBlockedChange]);
|
|
4749
|
+
const exitDeposit = () => {
|
|
4750
|
+
setDepositBlocked(false);
|
|
4751
|
+
setView("balance");
|
|
4752
|
+
};
|
|
4753
|
+
const exitWithdraw = () => {
|
|
4754
|
+
setWithdrawPending(false);
|
|
4755
|
+
setView("balance");
|
|
4756
|
+
};
|
|
4757
|
+
const startEndSession = () => {
|
|
4758
|
+
if (!onEndSession) return;
|
|
4759
|
+
const run = ++endSessionRunRef.current;
|
|
4760
|
+
setEndSessionError(null);
|
|
4761
|
+
setView("ending-session");
|
|
4762
|
+
Promise.resolve().then(() => onEndSession()).then(
|
|
4763
|
+
() => {
|
|
4764
|
+
if (endSessionRunRef.current !== run) return;
|
|
4765
|
+
setView("withdraw");
|
|
4766
|
+
},
|
|
4767
|
+
(err) => {
|
|
4768
|
+
if (endSessionRunRef.current !== run) return;
|
|
4769
|
+
setEndSessionError(err instanceof Error ? err.message : null);
|
|
4770
|
+
setView("end-session-error");
|
|
4771
|
+
}
|
|
4772
|
+
);
|
|
4773
|
+
};
|
|
4774
|
+
const handleWithdraw = () => {
|
|
4775
|
+
if (session && onEndSession) {
|
|
4776
|
+
startEndSession();
|
|
4777
|
+
} else {
|
|
4778
|
+
setView("withdraw");
|
|
4779
|
+
}
|
|
4780
|
+
};
|
|
4781
|
+
const dismissEndSessionError = () => {
|
|
4782
|
+
endSessionRunRef.current++;
|
|
4783
|
+
setEndSessionError(null);
|
|
4784
|
+
setView("balance");
|
|
4785
|
+
};
|
|
4786
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4787
|
+
onClose && /* @__PURE__ */ jsx(
|
|
4788
|
+
"button",
|
|
4789
|
+
{
|
|
4790
|
+
"data-privana-close": true,
|
|
4791
|
+
onClick: onClose,
|
|
4792
|
+
disabled: closeBlocked,
|
|
4793
|
+
"aria-label": "Close",
|
|
4794
|
+
className: cn(
|
|
4795
|
+
"absolute top-6 right-5 z-20 flex h-5 w-5 items-center justify-center transition-colors",
|
|
4796
|
+
closeBlocked ? "text-muted-foreground/40 cursor-not-allowed" : "text-muted-foreground hover:text-foreground cursor-pointer"
|
|
4797
|
+
),
|
|
4798
|
+
children: /* @__PURE__ */ jsx(CloseIcon, {})
|
|
4799
|
+
}
|
|
4800
|
+
),
|
|
4801
|
+
(view === "balance" || view === "ending-session" || view === "end-session-error") && /* @__PURE__ */ jsx("div", { className: "flex items-center px-5 py-4", children: /* @__PURE__ */ jsx("span", { className: "text-foreground text-xl leading-5 font-medium", children: appName }) }),
|
|
4802
|
+
view === "balance" && /* @__PURE__ */ jsx(
|
|
4803
|
+
WalletBalanceView,
|
|
4804
|
+
{
|
|
4805
|
+
session,
|
|
4806
|
+
allowance,
|
|
4807
|
+
amount,
|
|
4808
|
+
onAmountChange: setAmount,
|
|
4809
|
+
onPlay,
|
|
4810
|
+
onAddFunds: () => setView("deposit"),
|
|
4811
|
+
onWithdraw: handleWithdraw
|
|
4812
|
+
}
|
|
4813
|
+
),
|
|
4814
|
+
view === "ending-session" && /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: /* @__PURE__ */ jsx(
|
|
4815
|
+
TransactionProgressView,
|
|
4816
|
+
{
|
|
4817
|
+
title: "Ending game session...",
|
|
4818
|
+
steps: [{ label: "Waiting for the game session to settle", status: "active" }]
|
|
4819
|
+
}
|
|
4820
|
+
) }),
|
|
4821
|
+
view === "end-session-error" && /* @__PURE__ */ jsx("div", { className: "bg-muted flex flex-col gap-6 rounded-[10px] p-5", children: /* @__PURE__ */ jsx(
|
|
4822
|
+
TransactionErrorView,
|
|
4823
|
+
{
|
|
4824
|
+
title: "Could not end session",
|
|
4825
|
+
message: endSessionError ? `Your game session could not be ended, so the funds committed to it are not available to withdraw yet. (${endSessionError})` : "Your game session could not be ended, so the funds committed to it are not available to withdraw yet.",
|
|
4826
|
+
onRetry: startEndSession,
|
|
4827
|
+
onDismiss: dismissEndSessionError
|
|
4828
|
+
}
|
|
4829
|
+
) }),
|
|
4830
|
+
view === "withdraw" && /* @__PURE__ */ jsx(WithdrawModalContent, { onBack: exitWithdraw, onPendingChange: setWithdrawPending }),
|
|
4831
|
+
view === "deposit" && /* @__PURE__ */ jsx(
|
|
4832
|
+
DepositModalContent,
|
|
4833
|
+
{
|
|
4834
|
+
...depositHandlers,
|
|
4835
|
+
allowance,
|
|
4836
|
+
onExit: exitDeposit,
|
|
4837
|
+
onCloseBlockedChange: setDepositBlocked,
|
|
4838
|
+
onDepositSuccess: () => {
|
|
4839
|
+
exitDeposit();
|
|
4840
|
+
onDepositSuccess?.();
|
|
4841
|
+
}
|
|
4842
|
+
}
|
|
4843
|
+
)
|
|
4844
|
+
] });
|
|
4845
|
+
}
|
|
4846
|
+
function WalletModal({ open, onClose, ...handlers }) {
|
|
4847
|
+
const titleId = useId();
|
|
4848
|
+
const descId = useId();
|
|
4849
|
+
const [isCloseBlocked, setIsCloseBlocked] = useState(false);
|
|
4850
|
+
const handleClose = () => {
|
|
4851
|
+
if (!isCloseBlocked) onClose();
|
|
4852
|
+
};
|
|
4853
|
+
return /* @__PURE__ */ jsx(
|
|
4854
|
+
Dialog,
|
|
4855
|
+
{
|
|
4856
|
+
open,
|
|
4857
|
+
onOpenChange: (isOpen) => {
|
|
4858
|
+
if (!isOpen) handleClose();
|
|
4859
|
+
},
|
|
4860
|
+
children: /* @__PURE__ */ jsxs(
|
|
4861
|
+
DialogContent,
|
|
4862
|
+
{
|
|
4863
|
+
"data-privana": true,
|
|
4864
|
+
showCloseButton: false,
|
|
4865
|
+
onInteractOutside: isCloseBlocked ? (e) => e.preventDefault() : void 0,
|
|
4866
|
+
onEscapeKeyDown: isCloseBlocked ? (e) => e.preventDefault() : void 0,
|
|
4867
|
+
className: "bg-card flex w-[560px] max-w-[95vw] flex-col gap-2 rounded-2xl border-0 p-2",
|
|
4868
|
+
"aria-labelledby": titleId,
|
|
4869
|
+
"aria-describedby": descId,
|
|
4870
|
+
children: [
|
|
4871
|
+
/* @__PURE__ */ jsx(DialogTitle, { id: titleId, className: "sr-only", children: "Wallet" }),
|
|
4872
|
+
/* @__PURE__ */ jsx(DialogDescription, { id: descId, className: "sr-only", children: "Manage your account balance." }),
|
|
4873
|
+
/* @__PURE__ */ jsx(
|
|
4874
|
+
WalletModalContent,
|
|
4875
|
+
{
|
|
4876
|
+
onClose: handleClose,
|
|
4877
|
+
onCloseBlockedChange: setIsCloseBlocked,
|
|
4878
|
+
...handlers
|
|
4879
|
+
}
|
|
4880
|
+
)
|
|
4881
|
+
]
|
|
4882
|
+
}
|
|
4883
|
+
)
|
|
4884
|
+
}
|
|
4885
|
+
);
|
|
4886
|
+
}
|
|
4887
|
+
function WalletInlineModal({ className, ...handlers }) {
|
|
4888
|
+
return /* @__PURE__ */ jsx(
|
|
4889
|
+
"div",
|
|
4890
|
+
{
|
|
4891
|
+
"data-privana": true,
|
|
4892
|
+
className: cn(
|
|
4893
|
+
"bg-card relative flex w-[560px] max-w-full flex-col gap-2 overflow-hidden rounded-2xl p-2 shadow-lg",
|
|
4894
|
+
className
|
|
4895
|
+
),
|
|
4896
|
+
children: /* @__PURE__ */ jsx(WalletModalContent, { ...handlers })
|
|
4897
|
+
}
|
|
4898
|
+
);
|
|
4899
|
+
}
|
|
3107
4900
|
|
|
3108
|
-
export {
|
|
4901
|
+
export { DepositInlineModal, DepositModal, PrivanaButton, PrivanaIcon, PrivanaInlineModal, PrivanaModal, 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 };
|
|
3109
4902
|
//# sourceMappingURL=index.js.map
|
|
3110
4903
|
//# sourceMappingURL=index.js.map
|