@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/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, getChainId2, shortenAddress, formatTimeRemaining, parseTokenAmount, getExplorerAddressUrl } from './chunk-WV6CGHEF.js';
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-WV6CGHEF.js';
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, useChainId, useSwitchChain, useBalance as useBalance$1, useReadContract } from 'wagmi';
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
- // src/sdk/signatures/eip712-types.ts
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
- try {
528
- sessionStorage.setItem(storageKey(address), JSON.stringify(data));
529
- } catch {
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 = sessionStorage.getItem(storageKey(address));
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
- try {
548
- sessionStorage.removeItem(storageKey(address));
549
- } catch {
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
- if (address) clearPendingDeposit(address);
599
- onCreditedRef.current?.(hash, response);
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
- savePendingDeposit(address, {
764
- txHash: hash,
765
- chainId: sourceChain.id,
766
- amount: params.amount.toString(),
767
- depositAddress: addrResponse,
768
- savedAt: Date.now()
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: "View on BaseScan",
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(ChevronRight, {}) })
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(ChevronRight, {}) })
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(ChevronLeft, {})
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(ChevronDown, { collapsed: collapsedSections[section.title] })
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(ChevronLeft, {})
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(ChevronLeft, {})
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 { LOCK_TYPES, MODIFY_LOCK_TYPES, PrivanaButton, PrivanaInlineModal, PrivanaModal, TRANSFER_LOCKED_TYPES, TRANSFER_TYPES, WITHDRAW_FROM_LOCK_TYPES, WITHDRAW_TYPES, createDomain, createLockExpiry, getChainIcon, getTokenIcon, signLockMessage, signModifyLockMessage, signTransferLockedMessage, signTransferMessage, signWithdrawFromLockMessage, signWithdrawMessage, useBalance, useBatchBalances, useDeposit, useExpiredLocks, useHistory, useHostedRedirectAuth, useLockFunds, useLockedFunds, useModifyLock, usePendingWithdrawals, usePrivanaClient, useTokenInfo, useTokenList, useTotalLockedBalance, useTransfer, useUnlockFunds, useWithdraw };
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