@swype-org/react-sdk 0.1.0 → 0.1.2

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,9 +1,11 @@
1
- import { createContext, useRef, useMemo, useContext, useState, useCallback, useEffect } from 'react';
1
+ import { createContext, useRef, useState, useCallback, useMemo, useContext, useEffect } from 'react';
2
2
  import { PrivyProvider, usePrivy } from '@privy-io/react-auth';
3
- import { createConfig, http, WagmiProvider, useAccount, useConnect, useSwitchChain, useSignTypedData, usePublicClient, useWalletClient } from 'wagmi';
3
+ import { createConfig, http, WagmiProvider, useAccount, useConnect, useSwitchChain, useSignTypedData, useWalletClient } from 'wagmi';
4
4
  import { mainnet, arbitrum, base } from 'wagmi/chains';
5
5
  import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
6
6
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
+ import { createPublicClient, http as http$1 } from 'viem';
8
+ import { arbitrum as arbitrum$1, base as base$1, mainnet as mainnet$1 } from 'viem/chains';
7
9
 
8
10
  var __defProp = Object.defineProperty;
9
11
  var __export = (target, all) => {
@@ -82,13 +84,19 @@ function SwypeProvider({
82
84
  if (!queryClientRef.current) {
83
85
  queryClientRef.current = new QueryClient();
84
86
  }
87
+ const [depositAmount, setDepositAmountRaw] = useState(null);
88
+ const setDepositAmount = useCallback((amount) => {
89
+ setDepositAmountRaw(amount);
90
+ }, []);
85
91
  const value = useMemo(
86
92
  () => ({
87
93
  apiBaseUrl,
88
94
  theme,
89
- tokens: getTheme(theme)
95
+ tokens: getTheme(theme),
96
+ depositAmount,
97
+ setDepositAmount
90
98
  }),
91
- [apiBaseUrl, theme]
99
+ [apiBaseUrl, theme, depositAmount, setDepositAmount]
92
100
  );
93
101
  return /* @__PURE__ */ jsx(QueryClientProvider, { client: queryClientRef.current, children: /* @__PURE__ */ jsx(WagmiProvider, { config: wagmiConfig, children: /* @__PURE__ */ jsx(
94
102
  PrivyProvider,
@@ -110,6 +118,20 @@ function useSwypeConfig() {
110
118
  }
111
119
  return ctx;
112
120
  }
121
+ function useSwypeDepositAmount() {
122
+ const ctx = useContext(SwypeContext);
123
+ if (!ctx) {
124
+ throw new Error(
125
+ "useSwypeDepositAmount must be used within a <SwypeProvider>"
126
+ );
127
+ }
128
+ return {
129
+ /** Current deposit amount, or null if not set */
130
+ amount: ctx.depositAmount,
131
+ /** Set the deposit amount (pass null to clear) */
132
+ setAmount: ctx.setDepositAmount
133
+ };
134
+ }
113
135
 
114
136
  // src/api.ts
115
137
  var api_exports = {};
@@ -121,7 +143,8 @@ __export(api_exports, {
121
143
  fetchProviders: () => fetchProviders,
122
144
  fetchTransfer: () => fetchTransfer,
123
145
  reportActionCompletion: () => reportActionCompletion,
124
- updateUserConfig: () => updateUserConfig
146
+ updateUserConfig: () => updateUserConfig,
147
+ updateUserConfigBySession: () => updateUserConfigBySession
125
148
  });
126
149
  async function throwApiError(res) {
127
150
  const body = await res.json().catch(() => null);
@@ -206,6 +229,17 @@ async function updateUserConfig(apiBaseUrl, token, config) {
206
229
  });
207
230
  if (!res.ok) await throwApiError(res);
208
231
  }
232
+ async function updateUserConfigBySession(apiBaseUrl, sessionId, config) {
233
+ const res = await fetch(
234
+ `${apiBaseUrl}/v1/authorization-sessions/${sessionId}/user-config`,
235
+ {
236
+ method: "PATCH",
237
+ headers: { "Content-Type": "application/json" },
238
+ body: JSON.stringify({ config })
239
+ }
240
+ );
241
+ if (!res.ok) await throwApiError(res);
242
+ }
209
243
  async function reportActionCompletion(apiBaseUrl, actionId, result) {
210
244
  const res = await fetch(
211
245
  `${apiBaseUrl}/v1/authorization-actions/${actionId}`,
@@ -218,6 +252,26 @@ async function reportActionCompletion(apiBaseUrl, actionId, result) {
218
252
  if (!res.ok) await throwApiError(res);
219
253
  return await res.json();
220
254
  }
255
+ var VIEM_CHAINS = {
256
+ 1: mainnet$1,
257
+ 8453: base$1,
258
+ 42161: arbitrum$1
259
+ };
260
+ var chainClientCache = /* @__PURE__ */ new Map();
261
+ function getPublicClientForChain(numericChainId) {
262
+ const existing = chainClientCache.get(numericChainId);
263
+ if (existing) return existing;
264
+ const viemChain = VIEM_CHAINS[numericChainId];
265
+ if (!viemChain) {
266
+ throw new Error(`Unsupported chain ID: ${numericChainId}`);
267
+ }
268
+ const client = createPublicClient({
269
+ chain: viemChain,
270
+ transport: http$1()
271
+ });
272
+ chainClientCache.set(numericChainId, client);
273
+ return client;
274
+ }
221
275
  function useTransferPolling(intervalMs = 3e3) {
222
276
  const { apiBaseUrl } = useSwypeConfig();
223
277
  const { getAccessToken } = usePrivy();
@@ -272,12 +326,30 @@ function useAuthorizationExecutor() {
272
326
  const { connectAsync, connectors } = useConnect();
273
327
  const { switchChainAsync } = useSwitchChain();
274
328
  const { signTypedDataAsync } = useSignTypedData();
275
- const publicClient = usePublicClient();
276
329
  const { data: walletClient } = useWalletClient();
277
330
  const [executing, setExecuting] = useState(false);
278
331
  const [results, setResults] = useState([]);
279
332
  const [error, setError] = useState(null);
280
333
  const executingRef = useRef(false);
334
+ const [pendingSelectSource, setPendingSelectSource] = useState(null);
335
+ const selectSourceResolverRef = useRef(null);
336
+ const resolveSelectSource = useCallback((selection) => {
337
+ if (selectSourceResolverRef.current) {
338
+ selectSourceResolverRef.current(selection);
339
+ selectSourceResolverRef.current = null;
340
+ setPendingSelectSource(null);
341
+ }
342
+ }, []);
343
+ const [pendingAllowanceSelection, setPendingAllowanceSelection] = useState(null);
344
+ const allowanceSelectionResolverRef = useRef(null);
345
+ const sessionIdRef = useRef(null);
346
+ const resolveAllowanceSelection = useCallback((selection) => {
347
+ if (allowanceSelectionResolverRef.current) {
348
+ allowanceSelectionResolverRef.current(selection);
349
+ allowanceSelectionResolverRef.current = null;
350
+ setPendingAllowanceSelection(null);
351
+ }
352
+ }, []);
281
353
  const executeOpenProvider = useCallback(
282
354
  async (action) => {
283
355
  try {
@@ -321,6 +393,49 @@ function useAuthorizationExecutor() {
321
393
  },
322
394
  [isConnected, address, currentChainId, connectors, connectAsync]
323
395
  );
396
+ const executeSelectSource = useCallback(
397
+ async (action) => {
398
+ try {
399
+ const options = action.metadata?.options;
400
+ const recommended = action.metadata?.recommended;
401
+ if (!options || options.length <= 1) {
402
+ const selection2 = recommended ?? { chainName: "Base", tokenSymbol: "USDC" };
403
+ return {
404
+ actionId: action.id,
405
+ type: action.type,
406
+ status: "success",
407
+ message: `Auto-selected ${selection2.tokenSymbol} on ${selection2.chainName}.`,
408
+ data: {
409
+ selectedChainName: selection2.chainName,
410
+ selectedTokenSymbol: selection2.tokenSymbol
411
+ }
412
+ };
413
+ }
414
+ const selection = await new Promise((resolve) => {
415
+ selectSourceResolverRef.current = resolve;
416
+ setPendingSelectSource(action);
417
+ });
418
+ return {
419
+ actionId: action.id,
420
+ type: action.type,
421
+ status: "success",
422
+ message: `Selected ${selection.tokenSymbol} on ${selection.chainName}.`,
423
+ data: {
424
+ selectedChainName: selection.chainName,
425
+ selectedTokenSymbol: selection.tokenSymbol
426
+ }
427
+ };
428
+ } catch (err) {
429
+ return {
430
+ actionId: action.id,
431
+ type: action.type,
432
+ status: "error",
433
+ message: err instanceof Error ? err.message : "Failed to select source"
434
+ };
435
+ }
436
+ },
437
+ []
438
+ );
324
439
  const executeSwitchChain = useCallback(
325
440
  async (action) => {
326
441
  try {
@@ -334,8 +449,17 @@ function useAuthorizationExecutor() {
334
449
  };
335
450
  }
336
451
  const targetChainIdNum = parseInt(targetChainIdHex, 16);
337
- await switchChainAsync({ chainId: targetChainIdNum });
338
452
  const hexChainId = `0x${targetChainIdNum.toString(16)}`;
453
+ if (currentChainId === targetChainIdNum) {
454
+ return {
455
+ actionId: action.id,
456
+ type: action.type,
457
+ status: "success",
458
+ message: `Already on chain ${hexChainId}. Skipped.`,
459
+ data: { chainId: hexChainId, switched: false }
460
+ };
461
+ }
462
+ await switchChainAsync({ chainId: targetChainIdNum });
339
463
  return {
340
464
  actionId: action.id,
341
465
  type: action.type,
@@ -352,13 +476,14 @@ function useAuthorizationExecutor() {
352
476
  };
353
477
  }
354
478
  },
355
- [switchChainAsync]
479
+ [currentChainId, switchChainAsync]
356
480
  );
357
481
  const executeApprovePermit2 = useCallback(
358
482
  async (action) => {
359
483
  try {
360
484
  const tokenAddress = action.metadata?.tokenAddress;
361
485
  const permit2Address = action.metadata?.permit2Address;
486
+ const metadataChainId = action.metadata?.chainId;
362
487
  if (!tokenAddress || !permit2Address) {
363
488
  return {
364
489
  actionId: action.id,
@@ -397,21 +522,26 @@ function useAuthorizationExecutor() {
397
522
  outputs: [{ name: "", type: "bool" }]
398
523
  }
399
524
  ];
400
- if (publicClient) {
401
- const currentAllowance = await publicClient.readContract({
402
- address: tokenAddress,
403
- abi: ERC20_ABI,
404
- functionName: "allowance",
405
- args: [address, permit2Address]
406
- });
407
- if (currentAllowance > 0n) {
408
- return {
409
- actionId: action.id,
410
- type: action.type,
411
- status: "success",
412
- message: `Permit2 already approved (allowance: ${currentAllowance.toString()}). Skipped.`,
413
- data: { skipped: true, existingAllowance: currentAllowance.toString() }
414
- };
525
+ const targetChainIdNum = metadataChainId ? parseInt(metadataChainId, 16) : currentChainId;
526
+ const chainClient = targetChainIdNum ? getPublicClientForChain(targetChainIdNum) : void 0;
527
+ if (chainClient) {
528
+ try {
529
+ const currentAllowance = await chainClient.readContract({
530
+ address: tokenAddress,
531
+ abi: ERC20_ABI,
532
+ functionName: "allowance",
533
+ args: [address, permit2Address]
534
+ });
535
+ if (currentAllowance > 0n) {
536
+ return {
537
+ actionId: action.id,
538
+ type: action.type,
539
+ status: "success",
540
+ message: `Permit2 already approved (allowance: ${currentAllowance.toString()}). Skipped.`,
541
+ data: { skipped: true, existingAllowance: currentAllowance.toString() }
542
+ };
543
+ }
544
+ } catch {
415
545
  }
416
546
  }
417
547
  if (!walletClient) {
@@ -429,8 +559,8 @@ function useAuthorizationExecutor() {
429
559
  functionName: "approve",
430
560
  args: [permit2Address, MAX_UINT256]
431
561
  });
432
- if (publicClient) {
433
- await publicClient.waitForTransactionReceipt({ hash: txHash });
562
+ if (chainClient) {
563
+ await chainClient.waitForTransactionReceipt({ hash: txHash });
434
564
  }
435
565
  return {
436
566
  actionId: action.id,
@@ -448,7 +578,7 @@ function useAuthorizationExecutor() {
448
578
  };
449
579
  }
450
580
  },
451
- [address, publicClient, walletClient]
581
+ [address, currentChainId, walletClient]
452
582
  );
453
583
  const executeSignPermit2 = useCallback(
454
584
  async (action) => {
@@ -483,34 +613,55 @@ function useAuthorizationExecutor() {
483
613
  message: "Missing spenderAddress or tokenAddress in action metadata."
484
614
  };
485
615
  }
486
- if (publicClient && address && metadataAmount) {
616
+ const permit2ChainId = metadataChainId ? parseInt(metadataChainId, 16) : currentChainId;
617
+ const chainClient = permit2ChainId ? getPublicClientForChain(permit2ChainId) : void 0;
618
+ let onChainNonce;
619
+ if (chainClient && address) {
487
620
  try {
488
- const [existingAmount, existingExpiration] = await publicClient.readContract({
621
+ const [existingAmount, existingExpiration, chainNonce] = await chainClient.readContract({
489
622
  address: PERMIT2_ADDRESS,
490
623
  abi: PERMIT2_ALLOWANCE_ABI,
491
624
  functionName: "allowance",
492
625
  args: [address, tokenAddress, spenderAddress]
493
626
  });
494
- const requiredSmallestUnit = BigInt(
495
- Math.ceil(metadataAmount * 1e6)
496
- );
497
- const now = Math.floor(Date.now() / 1e3);
498
- if (existingAmount >= requiredSmallestUnit && existingExpiration > now) {
499
- return {
500
- actionId: action.id,
501
- type: action.type,
502
- status: "success",
503
- message: `Permit2 allowance already sufficient (${existingAmount.toString()}). Skipped.`,
504
- data: { skipped: true, existingAllowance: existingAmount.toString() }
505
- };
627
+ onChainNonce = chainNonce;
628
+ if (metadataAmount) {
629
+ const requiredSmallestUnit = BigInt(metadataAmount);
630
+ const now = Math.floor(Date.now() / 1e3);
631
+ if (existingAmount >= requiredSmallestUnit && existingExpiration > now) {
632
+ return {
633
+ actionId: action.id,
634
+ type: action.type,
635
+ status: "success",
636
+ message: `Permit2 allowance already sufficient (${existingAmount.toString()}). Skipped.`,
637
+ data: { skipped: true, existingAllowance: existingAmount.toString() }
638
+ };
639
+ }
506
640
  }
507
641
  } catch {
508
642
  }
509
643
  }
510
- const metadataAllowance = action.metadata?.allowance;
511
- const permitAmount = metadataAllowance ? BigInt(Math.ceil(metadataAllowance * 1e6)) : metadataAmount ? BigInt(Math.ceil(metadataAmount * 1e6)) : BigInt(5e6);
512
- const permit2ChainId = metadataChainId ? parseInt(metadataChainId, 16) : currentChainId;
513
- const nonce = Number(action.metadata?.nonce ?? 0);
644
+ const allowanceSelection = await new Promise((resolve) => {
645
+ allowanceSelectionResolverRef.current = resolve;
646
+ setPendingAllowanceSelection(action);
647
+ });
648
+ if (allowanceSelection.topUpAmount > 0 && sessionIdRef.current) {
649
+ try {
650
+ await updateUserConfigBySession(
651
+ apiBaseUrl,
652
+ sessionIdRef.current,
653
+ { defaultAllowance: allowanceSelection.topUpAmount }
654
+ );
655
+ } catch {
656
+ }
657
+ }
658
+ const tokenDecimals = Number(action.metadata?.tokenDecimals ?? 6);
659
+ const topUpSmallestUnit = BigInt(
660
+ Math.round(allowanceSelection.topUpAmount * 10 ** tokenDecimals)
661
+ );
662
+ const baseAmount = metadataAmount ? BigInt(metadataAmount) : BigInt(0);
663
+ const permitAmount = baseAmount + topUpSmallestUnit;
664
+ const nonce = onChainNonce ?? Number(action.metadata?.nonce ?? 0);
514
665
  const sigDeadline = BigInt(Math.floor(Date.now() / 1e3) + 3600);
515
666
  const expiration = Math.floor(Date.now() / 1e3) + 30 * 24 * 60 * 60;
516
667
  const signature = await signTypedDataAsync({
@@ -567,13 +718,15 @@ function useAuthorizationExecutor() {
567
718
  };
568
719
  }
569
720
  },
570
- [address, currentChainId, publicClient, signTypedDataAsync]
721
+ [address, currentChainId, signTypedDataAsync, apiBaseUrl]
571
722
  );
572
723
  const executeAction = useCallback(
573
724
  async (action) => {
574
725
  switch (action.type) {
575
726
  case "OPEN_PROVIDER":
576
727
  return executeOpenProvider(action);
728
+ case "SELECT_SOURCE":
729
+ return executeSelectSource(action);
577
730
  case "SWITCH_CHAIN":
578
731
  return executeSwitchChain(action);
579
732
  case "APPROVE_PERMIT_2":
@@ -589,7 +742,7 @@ function useAuthorizationExecutor() {
589
742
  };
590
743
  }
591
744
  },
592
- [executeOpenProvider, executeSwitchChain, executeApprovePermit2, executeSignPermit2]
745
+ [executeOpenProvider, executeSelectSource, executeSwitchChain, executeApprovePermit2, executeSignPermit2]
593
746
  );
594
747
  const executeSession = useCallback(
595
748
  async (transfer) => {
@@ -601,6 +754,7 @@ function useAuthorizationExecutor() {
601
754
  return;
602
755
  }
603
756
  const sessionId = transfer.authorizationSessions[0].id;
757
+ sessionIdRef.current = sessionId;
604
758
  setExecuting(true);
605
759
  setError(null);
606
760
  setResults([]);
@@ -609,6 +763,19 @@ function useAuthorizationExecutor() {
609
763
  const allResults = [];
610
764
  const completedActionIds = /* @__PURE__ */ new Set();
611
765
  let pendingActions = currentSession.actions.filter((a) => a.status === "PENDING").sort((a, b) => a.orderIndex - b.orderIndex);
766
+ const ACTION_POLL_INTERVAL_MS = 500;
767
+ const ACTION_POLL_MAX_RETRIES = 20;
768
+ let actionPollRetries = 0;
769
+ while (pendingActions.length === 0 && currentSession.status !== "AUTHORIZED" && actionPollRetries < ACTION_POLL_MAX_RETRIES) {
770
+ await new Promise((r) => setTimeout(r, ACTION_POLL_INTERVAL_MS));
771
+ currentSession = await fetchAuthorizationSession(apiBaseUrl, sessionId);
772
+ pendingActions = currentSession.actions.filter((a) => a.status === "PENDING").sort((a, b) => a.orderIndex - b.orderIndex);
773
+ actionPollRetries++;
774
+ }
775
+ if (pendingActions.length === 0 && currentSession.status !== "AUTHORIZED") {
776
+ setError("Authorization actions were not created in time. Please try again.");
777
+ return;
778
+ }
612
779
  while (pendingActions.length > 0) {
613
780
  const action = pendingActions[0];
614
781
  if (completedActionIds.has(action.id)) break;
@@ -668,7 +835,16 @@ function useAuthorizationExecutor() {
668
835
  },
669
836
  [apiBaseUrl, executeAction]
670
837
  );
671
- return { executing, results, error, executeSession };
838
+ return {
839
+ executing,
840
+ results,
841
+ error,
842
+ pendingSelectSource,
843
+ resolveSelectSource,
844
+ pendingAllowanceSelection,
845
+ resolveAllowanceSelection,
846
+ executeSession
847
+ };
672
848
  }
673
849
  function Spinner({ size = 40, label }) {
674
850
  const { tokens } = useSwypeConfig();
@@ -786,163 +962,232 @@ function ProviderCard({ provider, selected, onClick }) {
786
962
  }
787
963
  );
788
964
  }
789
- function AccountWalletSelector({
965
+ function AccountDropdown({
790
966
  accounts,
791
- selection,
792
- onSelect
967
+ selectedAccountId,
968
+ onSelect,
969
+ selectedWalletId,
970
+ onWalletSelect
793
971
  }) {
794
972
  const { tokens } = useSwypeConfig();
795
- const [expandedAccountId, setExpandedAccountId] = useState(null);
796
- const activeAccounts = accounts.filter(
797
- (a) => a.wallets.some((w) => w.status === "ACTIVE")
973
+ const [open, setOpen] = useState(false);
974
+ const containerRef = useRef(null);
975
+ const selected = accounts.find((a) => a.id === selectedAccountId);
976
+ const selectedWallet = selected?.wallets.find(
977
+ (w) => w.id === selectedWalletId
798
978
  );
799
- if (activeAccounts.length === 0) return null;
800
- const handleAccountClick = (accountId) => {
801
- if (expandedAccountId === accountId) {
802
- setExpandedAccountId(null);
803
- } else {
804
- setExpandedAccountId(accountId);
805
- }
806
- };
807
- const handleWalletClick = (account, wallet) => {
808
- const isAlreadySelected = selection?.accountId === account.id && selection?.walletId === wallet.id;
809
- if (isAlreadySelected) {
810
- onSelect(null);
811
- } else {
812
- onSelect({
813
- accountId: account.id,
814
- walletId: wallet.id,
815
- accountName: account.name,
816
- walletName: wallet.name,
817
- chainName: wallet.chain.name
818
- });
819
- }
820
- };
821
- const formatBalance = (wallet) => {
822
- const parts = [];
823
- for (const src of wallet.sources) {
824
- if (src.token.status === "ACTIVE") {
825
- parts.push(
826
- `${src.balance.available.amount.toFixed(2)} ${src.balance.available.currency} (${src.token.symbol})`
827
- );
979
+ useEffect(() => {
980
+ if (!open) return;
981
+ const handleClick = (e) => {
982
+ if (containerRef.current && !containerRef.current.contains(e.target)) {
983
+ setOpen(false);
828
984
  }
985
+ };
986
+ document.addEventListener("mousedown", handleClick);
987
+ return () => document.removeEventListener("mousedown", handleClick);
988
+ }, [open]);
989
+ if (accounts.length === 0) return null;
990
+ const hasMultipleChoices = accounts.length > 1 || accounts.length === 1 && onWalletSelect && accounts[0].wallets.filter((w) => w.balance.available.amount > 0).length > 1;
991
+ if (!hasMultipleChoices) {
992
+ return /* @__PURE__ */ jsxs(
993
+ "div",
994
+ {
995
+ style: {
996
+ display: "flex",
997
+ alignItems: "center",
998
+ gap: "6px",
999
+ fontSize: "0.85rem",
1000
+ color: tokens.textSecondary
1001
+ },
1002
+ children: [
1003
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: accounts[0].name }),
1004
+ (selectedWallet ?? accounts[0].wallets[0]) && /* @__PURE__ */ jsx(
1005
+ "span",
1006
+ {
1007
+ style: {
1008
+ fontSize: "0.75rem",
1009
+ color: tokens.textMuted,
1010
+ fontFamily: '"SF Mono", "Fira Code", monospace'
1011
+ },
1012
+ children: selectedWallet ? `${selectedWallet.chain.name} \xB7 ${selectedWallet.name}` : accounts[0].wallets[0]?.name
1013
+ }
1014
+ )
1015
+ ]
1016
+ }
1017
+ );
1018
+ }
1019
+ const getAccountBalance = (account) => {
1020
+ let total = 0;
1021
+ for (const w of account.wallets) {
1022
+ total += w.balance.available.amount;
829
1023
  }
830
- if (parts.length === 0 && wallet.balance) {
831
- return `${wallet.balance.available.amount.toFixed(2)} ${wallet.balance.available.currency}`;
832
- }
833
- return parts.join(", ") || "No balance";
1024
+ return total > 0 ? `$${total.toFixed(2)}` : "";
834
1025
  };
835
- return /* @__PURE__ */ jsxs("div", { style: { width: "100%" }, children: [
836
- /* @__PURE__ */ jsx(
837
- "label",
1026
+ const hasActiveWallet = (account) => account.wallets.some((w) => w.status === "ACTIVE");
1027
+ return /* @__PURE__ */ jsxs("div", { ref: containerRef, style: { position: "relative" }, children: [
1028
+ /* @__PURE__ */ jsxs(
1029
+ "button",
838
1030
  {
1031
+ onClick: () => setOpen(!open),
839
1032
  style: {
840
- display: "block",
841
- fontSize: "0.8rem",
842
- color: tokens.textMuted,
843
- marginBottom: "8px",
844
- fontWeight: 500,
845
- textTransform: "uppercase",
846
- letterSpacing: "0.05em"
1033
+ display: "flex",
1034
+ alignItems: "center",
1035
+ gap: "6px",
1036
+ background: "transparent",
1037
+ border: "none",
1038
+ cursor: "pointer",
1039
+ padding: "4px 0",
1040
+ color: tokens.textSecondary,
1041
+ fontFamily: "inherit",
1042
+ fontSize: "0.85rem",
1043
+ outline: "none"
847
1044
  },
848
- children: "Or use a connected account"
1045
+ children: [
1046
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500, color: tokens.text }, children: selected?.name ?? "Select account" }),
1047
+ (selectedWallet ?? selected?.wallets?.[0]) && /* @__PURE__ */ jsx(
1048
+ "span",
1049
+ {
1050
+ style: {
1051
+ fontSize: "0.75rem",
1052
+ color: tokens.textMuted,
1053
+ fontFamily: '"SF Mono", "Fira Code", monospace'
1054
+ },
1055
+ children: selectedWallet ? `${selectedWallet.chain.name} \xB7 ${selectedWallet.name}` : selected.wallets[0].name
1056
+ }
1057
+ ),
1058
+ /* @__PURE__ */ jsx(
1059
+ "svg",
1060
+ {
1061
+ width: "12",
1062
+ height: "12",
1063
+ viewBox: "0 0 24 24",
1064
+ fill: "none",
1065
+ style: {
1066
+ transform: open ? "rotate(180deg)" : "rotate(0deg)",
1067
+ transition: "transform 0.15s ease",
1068
+ flexShrink: 0
1069
+ },
1070
+ children: /* @__PURE__ */ jsx(
1071
+ "path",
1072
+ {
1073
+ d: "M7 10l5 5 5-5",
1074
+ stroke: tokens.textMuted,
1075
+ strokeWidth: "2",
1076
+ strokeLinecap: "round",
1077
+ strokeLinejoin: "round"
1078
+ }
1079
+ )
1080
+ }
1081
+ )
1082
+ ]
849
1083
  }
850
1084
  ),
851
- /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: "6px" }, children: activeAccounts.map((account) => {
852
- const isExpanded = expandedAccountId === account.id;
853
- const activeWallets = account.wallets.filter((w) => w.status === "ACTIVE");
854
- const hasSelection = selection?.accountId === account.id;
855
- return /* @__PURE__ */ jsxs(
856
- "div",
857
- {
858
- style: {
859
- border: `1px solid ${hasSelection ? tokens.accent : tokens.border}`,
860
- borderRadius: tokens.radius,
861
- overflow: "hidden",
862
- transition: "border-color 0.15s ease"
863
- },
864
- children: [
1085
+ open && /* @__PURE__ */ jsx(
1086
+ "div",
1087
+ {
1088
+ style: {
1089
+ position: "absolute",
1090
+ top: "100%",
1091
+ left: 0,
1092
+ right: 0,
1093
+ marginTop: "4px",
1094
+ background: tokens.bgCard,
1095
+ border: `1px solid ${tokens.border}`,
1096
+ borderRadius: tokens.radius,
1097
+ boxShadow: tokens.shadowLg,
1098
+ zIndex: 50,
1099
+ overflow: "hidden",
1100
+ minWidth: "220px"
1101
+ },
1102
+ children: accounts.map((account) => {
1103
+ const isAcctSelected = account.id === selectedAccountId;
1104
+ const balance = getAccountBalance(account);
1105
+ const active = hasActiveWallet(account);
1106
+ const walletsWithBalance = account.wallets.filter(
1107
+ (w) => w.balance.available.amount > 0
1108
+ );
1109
+ const showWallets = onWalletSelect && walletsWithBalance.length > 0;
1110
+ return /* @__PURE__ */ jsxs("div", { children: [
865
1111
  /* @__PURE__ */ jsxs(
866
1112
  "button",
867
1113
  {
868
- onClick: () => handleAccountClick(account.id),
1114
+ onClick: () => {
1115
+ onSelect(account.id);
1116
+ if (!showWallets) setOpen(false);
1117
+ },
869
1118
  style: {
870
- width: "100%",
871
1119
  display: "flex",
872
1120
  alignItems: "center",
873
1121
  justifyContent: "space-between",
874
- padding: "12px 14px",
875
- background: hasSelection ? tokens.accent + "10" : tokens.bgInput,
1122
+ width: "100%",
1123
+ padding: "10px 14px",
1124
+ background: isAcctSelected && !selectedWalletId ? tokens.accent + "12" : "transparent",
876
1125
  border: "none",
1126
+ borderBottom: showWallets ? "none" : `1px solid ${tokens.border}`,
877
1127
  cursor: "pointer",
878
1128
  color: tokens.text,
879
1129
  fontFamily: "inherit",
880
- fontSize: "0.9rem",
881
- fontWeight: 500,
1130
+ fontSize: "0.85rem",
882
1131
  textAlign: "left",
883
1132
  outline: "none",
884
- transition: "background 0.15s ease"
1133
+ transition: "background 0.1s ease"
885
1134
  },
886
1135
  children: [
887
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "10px" }, children: [
888
- /* @__PURE__ */ jsx(
1136
+ /* @__PURE__ */ jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
1137
+ /* @__PURE__ */ jsxs(
889
1138
  "div",
890
1139
  {
891
1140
  style: {
892
- width: 28,
893
- height: 28,
894
- borderRadius: "6px",
895
- background: tokens.accent + "25",
896
1141
  display: "flex",
897
1142
  alignItems: "center",
898
- justifyContent: "center",
899
- fontSize: "0.75rem",
900
- fontWeight: 700,
901
- color: tokens.accent,
902
- flexShrink: 0
1143
+ gap: "6px"
903
1144
  },
904
- children: account.name.charAt(0).toUpperCase()
1145
+ children: [
1146
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: account.name }),
1147
+ active && /* @__PURE__ */ jsx(
1148
+ "span",
1149
+ {
1150
+ style: {
1151
+ fontSize: "0.6rem",
1152
+ fontWeight: 600,
1153
+ color: tokens.success,
1154
+ background: tokens.successBg,
1155
+ padding: "1px 5px",
1156
+ borderRadius: "3px",
1157
+ textTransform: "uppercase",
1158
+ letterSpacing: "0.03em"
1159
+ },
1160
+ children: "Active"
1161
+ }
1162
+ )
1163
+ ]
905
1164
  }
906
1165
  ),
907
- /* @__PURE__ */ jsxs("div", { children: [
908
- /* @__PURE__ */ jsx("div", { children: account.name }),
909
- /* @__PURE__ */ jsxs(
910
- "div",
911
- {
912
- style: {
913
- fontSize: "0.75rem",
914
- color: tokens.textMuted,
915
- marginTop: "2px"
916
- },
917
- children: [
918
- activeWallets.length,
919
- " wallet",
920
- activeWallets.length !== 1 ? "s" : ""
921
- ]
922
- }
923
- )
924
- ] })
1166
+ balance && /* @__PURE__ */ jsx(
1167
+ "div",
1168
+ {
1169
+ style: {
1170
+ fontSize: "0.75rem",
1171
+ color: tokens.textMuted,
1172
+ marginTop: "2px"
1173
+ },
1174
+ children: balance
1175
+ }
1176
+ )
925
1177
  ] }),
926
- /* @__PURE__ */ jsx(
1178
+ isAcctSelected && !selectedWalletId && /* @__PURE__ */ jsx(
927
1179
  "svg",
928
1180
  {
929
1181
  width: "14",
930
1182
  height: "14",
931
1183
  viewBox: "0 0 24 24",
932
1184
  fill: "none",
933
- style: {
934
- transform: isExpanded ? "rotate(180deg)" : "rotate(0deg)",
935
- transition: "transform 0.2s ease",
936
- flexShrink: 0
937
- },
1185
+ style: { flexShrink: 0, marginLeft: "8px" },
938
1186
  children: /* @__PURE__ */ jsx(
939
1187
  "path",
940
1188
  {
941
- d: "M7 10l5 5 5-5",
942
- stroke: tokens.textMuted,
943
- strokeWidth: "2",
944
- strokeLinecap: "round",
945
- strokeLinejoin: "round"
1189
+ d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1190
+ fill: tokens.accent
946
1191
  }
947
1192
  )
948
1193
  }
@@ -950,202 +1195,720 @@ function AccountWalletSelector({
950
1195
  ]
951
1196
  }
952
1197
  ),
953
- isExpanded && /* @__PURE__ */ jsx(
954
- "div",
955
- {
956
- style: {
957
- borderTop: `1px solid ${tokens.border}`,
958
- background: tokens.bgCard
959
- },
960
- children: activeWallets.map((wallet) => {
961
- const isSelected = selection?.walletId === wallet.id;
962
- return /* @__PURE__ */ jsxs(
963
- "button",
964
- {
965
- onClick: () => handleWalletClick(account, wallet),
966
- style: {
967
- width: "100%",
968
- display: "flex",
969
- alignItems: "center",
970
- justifyContent: "space-between",
971
- padding: "10px 14px 10px 24px",
972
- background: isSelected ? tokens.accent + "12" : "transparent",
973
- border: "none",
974
- borderBottom: `1px solid ${tokens.border}`,
975
- cursor: "pointer",
976
- color: tokens.text,
977
- fontFamily: "inherit",
978
- fontSize: "0.85rem",
979
- textAlign: "left",
980
- outline: "none",
981
- transition: "background 0.1s ease"
982
- },
983
- children: [
984
- /* @__PURE__ */ jsxs("div", { style: { minWidth: 0, flex: 1 }, children: [
985
- /* @__PURE__ */ jsxs(
986
- "div",
1198
+ showWallets && walletsWithBalance.map((wallet, wIdx) => {
1199
+ const isWalletSelected = isAcctSelected && wallet.id === selectedWalletId;
1200
+ const walletBal = wallet.balance.available.amount > 0 ? `$${wallet.balance.available.amount.toFixed(2)}` : "";
1201
+ const isLastWallet = wIdx === walletsWithBalance.length - 1;
1202
+ return /* @__PURE__ */ jsxs(
1203
+ "button",
1204
+ {
1205
+ onClick: () => {
1206
+ onWalletSelect(account.id, wallet.id);
1207
+ setOpen(false);
1208
+ },
1209
+ style: {
1210
+ display: "flex",
1211
+ alignItems: "center",
1212
+ justifyContent: "space-between",
1213
+ width: "100%",
1214
+ padding: "8px 14px 8px 28px",
1215
+ background: isWalletSelected ? tokens.accent + "12" : "transparent",
1216
+ border: "none",
1217
+ borderBottom: isLastWallet ? `1px solid ${tokens.border}` : "none",
1218
+ cursor: "pointer",
1219
+ color: tokens.text,
1220
+ fontFamily: "inherit",
1221
+ fontSize: "0.8rem",
1222
+ textAlign: "left",
1223
+ outline: "none",
1224
+ transition: "background 0.1s ease"
1225
+ },
1226
+ children: [
1227
+ /* @__PURE__ */ jsxs(
1228
+ "div",
1229
+ {
1230
+ style: {
1231
+ display: "flex",
1232
+ alignItems: "center",
1233
+ gap: "6px",
1234
+ minWidth: 0,
1235
+ flex: 1
1236
+ },
1237
+ children: [
1238
+ /* @__PURE__ */ jsx(
1239
+ "span",
987
1240
  {
988
1241
  style: {
989
- display: "flex",
990
- alignItems: "center",
991
- gap: "6px"
1242
+ fontWeight: 500,
1243
+ fontSize: "0.8rem"
992
1244
  },
993
- children: [
994
- /* @__PURE__ */ jsx("span", { style: { fontWeight: 500 }, children: wallet.name }),
995
- /* @__PURE__ */ jsx(
996
- "span",
997
- {
998
- style: {
999
- fontSize: "0.7rem",
1000
- color: tokens.textMuted,
1001
- background: tokens.bgHover,
1002
- padding: "1px 6px",
1003
- borderRadius: "4px"
1004
- },
1005
- children: wallet.chain.name
1006
- }
1007
- )
1008
- ]
1245
+ children: wallet.chain.name
1009
1246
  }
1010
1247
  ),
1011
1248
  /* @__PURE__ */ jsx(
1012
- "div",
1249
+ "span",
1013
1250
  {
1014
1251
  style: {
1015
- fontSize: "0.75rem",
1016
- color: tokens.textSecondary,
1017
- marginTop: "3px"
1252
+ fontSize: "0.7rem",
1253
+ color: tokens.textMuted,
1254
+ fontFamily: '"SF Mono", "Fira Code", monospace'
1018
1255
  },
1019
- children: formatBalance(wallet)
1256
+ children: wallet.name
1257
+ }
1258
+ ),
1259
+ walletBal && /* @__PURE__ */ jsx(
1260
+ "span",
1261
+ {
1262
+ style: {
1263
+ fontSize: "0.7rem",
1264
+ color: tokens.textMuted,
1265
+ marginLeft: "auto"
1266
+ },
1267
+ children: walletBal
1020
1268
  }
1021
1269
  )
1022
- ] }),
1023
- isSelected && /* @__PURE__ */ jsx(
1024
- "svg",
1270
+ ]
1271
+ }
1272
+ ),
1273
+ isWalletSelected && /* @__PURE__ */ jsx(
1274
+ "svg",
1275
+ {
1276
+ width: "12",
1277
+ height: "12",
1278
+ viewBox: "0 0 24 24",
1279
+ fill: "none",
1280
+ style: {
1281
+ flexShrink: 0,
1282
+ marginLeft: "8px"
1283
+ },
1284
+ children: /* @__PURE__ */ jsx(
1285
+ "path",
1025
1286
  {
1026
- width: "16",
1027
- height: "16",
1028
- viewBox: "0 0 24 24",
1029
- fill: "none",
1030
- style: { flexShrink: 0, marginLeft: "8px" },
1031
- children: /* @__PURE__ */ jsx(
1032
- "path",
1033
- {
1034
- d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1035
- fill: tokens.accent
1036
- }
1037
- )
1287
+ d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1288
+ fill: tokens.accent
1038
1289
  }
1039
1290
  )
1040
- ]
1041
- },
1042
- wallet.id
1043
- );
1044
- })
1045
- }
1046
- )
1047
- ]
1048
- },
1049
- account.id
1050
- );
1051
- }) }),
1052
- selection && /* @__PURE__ */ jsxs(
1053
- "div",
1291
+ }
1292
+ )
1293
+ ]
1294
+ },
1295
+ wallet.id
1296
+ );
1297
+ })
1298
+ ] }, account.id);
1299
+ })
1300
+ }
1301
+ )
1302
+ ] });
1303
+ }
1304
+ var ASSETS = ["USDC", "USDT"];
1305
+ function AdvancedSettings({
1306
+ settings,
1307
+ onChange,
1308
+ chains,
1309
+ providers,
1310
+ onConnectNewAccount,
1311
+ connectingNewAccount
1312
+ }) {
1313
+ const { tokens } = useSwypeConfig();
1314
+ const [open, setOpen] = useState(false);
1315
+ const [showProviders, setShowProviders] = useState(false);
1316
+ return /* @__PURE__ */ jsxs("div", { style: { marginTop: "12px" }, children: [
1317
+ /* @__PURE__ */ jsxs(
1318
+ "button",
1054
1319
  {
1320
+ onClick: () => setOpen(!open),
1055
1321
  style: {
1056
- marginTop: "8px",
1057
- fontSize: "0.8rem",
1058
- color: tokens.accent,
1059
1322
  display: "flex",
1060
1323
  alignItems: "center",
1061
- gap: "6px"
1324
+ gap: "6px",
1325
+ background: "transparent",
1326
+ border: "none",
1327
+ cursor: "pointer",
1328
+ padding: "4px 0",
1329
+ color: tokens.textMuted,
1330
+ fontFamily: "inherit",
1331
+ fontSize: "0.8rem",
1332
+ fontWeight: 500,
1333
+ outline: "none",
1334
+ letterSpacing: "0.02em"
1062
1335
  },
1063
1336
  children: [
1064
- /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", children: /* @__PURE__ */ jsx(
1065
- "path",
1337
+ /* @__PURE__ */ jsx(
1338
+ "svg",
1066
1339
  {
1067
- d: "M9 16.17L4.83 12l-1.42 1.41L9 19 21 7l-1.41-1.41L9 16.17z",
1068
- fill: tokens.accent
1340
+ width: "10",
1341
+ height: "10",
1342
+ viewBox: "0 0 24 24",
1343
+ fill: "none",
1344
+ style: {
1345
+ transform: open ? "rotate(180deg)" : "rotate(0deg)",
1346
+ transition: "transform 0.15s ease"
1347
+ },
1348
+ children: /* @__PURE__ */ jsx(
1349
+ "path",
1350
+ {
1351
+ d: "M7 10l5 5 5-5",
1352
+ stroke: tokens.textMuted,
1353
+ strokeWidth: "2.5",
1354
+ strokeLinecap: "round",
1355
+ strokeLinejoin: "round"
1356
+ }
1357
+ )
1069
1358
  }
1070
- ) }),
1071
- "Using ",
1072
- selection.walletName,
1073
- " on ",
1074
- selection.chainName
1359
+ ),
1360
+ "Advanced options"
1075
1361
  ]
1076
1362
  }
1077
- )
1078
- ] });
1079
- }
1080
- function isMobile() {
1081
- if (typeof navigator === "undefined") return false;
1082
- return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
1083
- navigator.userAgent
1084
- );
1085
- }
1086
- function SwypePayment({
1087
- destination,
1088
- onComplete,
1089
- onError
1090
- }) {
1091
- const { apiBaseUrl, tokens } = useSwypeConfig();
1092
- const { ready, authenticated, login, getAccessToken } = usePrivy();
1093
- const [step, setStep] = useState("login");
1094
- const [error, setError] = useState(null);
1095
- const [providers, setProviders] = useState([]);
1096
- const [accounts, setAccounts] = useState([]);
1097
- const [loadingData, setLoadingData] = useState(false);
1098
- const [selectedProviderId, setSelectedProviderId] = useState(
1099
- null
1100
- );
1101
- const [walletSelection, setWalletSelection] = useState(null);
1102
- const [amount, setAmount] = useState("0.10");
1103
- const [topUpBalance, setTopUpBalance] = useState("");
1104
- const [transfer, setTransfer] = useState(null);
1105
- const [creatingTransfer, setCreatingTransfer] = useState(false);
1106
- const [mobileFlow, setMobileFlow] = useState(false);
1107
- const pollingTransferIdRef = useRef(null);
1108
- const authExecutor = useAuthorizationExecutor();
1363
+ ),
1364
+ open && /* @__PURE__ */ jsxs(
1365
+ "div",
1366
+ {
1367
+ style: {
1368
+ marginTop: "10px",
1369
+ padding: "14px",
1370
+ background: tokens.bgInput,
1371
+ borderRadius: tokens.radius,
1372
+ border: `1px solid ${tokens.border}`
1373
+ },
1374
+ children: [
1375
+ /* @__PURE__ */ jsxs("div", { style: { marginBottom: "14px" }, children: [
1376
+ /* @__PURE__ */ jsx(
1377
+ "label",
1378
+ {
1379
+ style: {
1380
+ display: "block",
1381
+ fontSize: "0.7rem",
1382
+ fontWeight: 600,
1383
+ color: tokens.textMuted,
1384
+ textTransform: "uppercase",
1385
+ letterSpacing: "0.05em",
1386
+ marginBottom: "6px"
1387
+ },
1388
+ children: "Asset"
1389
+ }
1390
+ ),
1391
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", gap: "6px" }, children: ASSETS.map((asset) => {
1392
+ const isSelected = settings.asset === asset;
1393
+ return /* @__PURE__ */ jsx(
1394
+ "button",
1395
+ {
1396
+ onClick: () => onChange({
1397
+ ...settings,
1398
+ asset: isSelected ? null : asset
1399
+ }),
1400
+ style: {
1401
+ padding: "6px 14px",
1402
+ fontSize: "0.8rem",
1403
+ fontWeight: 600,
1404
+ fontFamily: "inherit",
1405
+ borderRadius: "6px",
1406
+ border: `1.5px solid ${isSelected ? tokens.accent : tokens.border}`,
1407
+ background: isSelected ? tokens.accent + "18" : "transparent",
1408
+ color: isSelected ? tokens.accent : tokens.text,
1409
+ cursor: "pointer",
1410
+ outline: "none",
1411
+ transition: "all 0.12s ease"
1412
+ },
1413
+ children: asset
1414
+ },
1415
+ asset
1416
+ );
1417
+ }) })
1418
+ ] }),
1419
+ chains.length > 0 && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "14px" }, children: [
1420
+ /* @__PURE__ */ jsx(
1421
+ "label",
1422
+ {
1423
+ style: {
1424
+ display: "block",
1425
+ fontSize: "0.7rem",
1426
+ fontWeight: 600,
1427
+ color: tokens.textMuted,
1428
+ textTransform: "uppercase",
1429
+ letterSpacing: "0.05em",
1430
+ marginBottom: "6px"
1431
+ },
1432
+ children: "Chain"
1433
+ }
1434
+ ),
1435
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: "6px" }, children: chains.map((chain) => {
1436
+ const isSelected = settings.chain === chain.name;
1437
+ return /* @__PURE__ */ jsx(
1438
+ "button",
1439
+ {
1440
+ onClick: () => onChange({
1441
+ ...settings,
1442
+ chain: isSelected ? null : chain.name
1443
+ }),
1444
+ style: {
1445
+ padding: "6px 14px",
1446
+ fontSize: "0.8rem",
1447
+ fontWeight: 600,
1448
+ fontFamily: "inherit",
1449
+ borderRadius: "6px",
1450
+ border: `1.5px solid ${isSelected ? tokens.accent : tokens.border}`,
1451
+ background: isSelected ? tokens.accent + "18" : "transparent",
1452
+ color: isSelected ? tokens.accent : tokens.text,
1453
+ cursor: "pointer",
1454
+ outline: "none",
1455
+ transition: "all 0.12s ease"
1456
+ },
1457
+ children: chain.name
1458
+ },
1459
+ chain.id
1460
+ );
1461
+ }) })
1462
+ ] }),
1463
+ /* @__PURE__ */ jsx("div", { children: !showProviders ? /* @__PURE__ */ jsxs(
1464
+ "button",
1465
+ {
1466
+ onClick: () => setShowProviders(true),
1467
+ disabled: connectingNewAccount,
1468
+ style: {
1469
+ display: "flex",
1470
+ alignItems: "center",
1471
+ gap: "6px",
1472
+ background: "transparent",
1473
+ border: `1px dashed ${tokens.border}`,
1474
+ borderRadius: tokens.radius,
1475
+ padding: "10px 14px",
1476
+ width: "100%",
1477
+ cursor: connectingNewAccount ? "not-allowed" : "pointer",
1478
+ color: tokens.textSecondary,
1479
+ fontFamily: "inherit",
1480
+ fontSize: "0.825rem",
1481
+ fontWeight: 500,
1482
+ outline: "none",
1483
+ opacity: connectingNewAccount ? 0.5 : 1,
1484
+ transition: "opacity 0.1s ease"
1485
+ },
1486
+ children: [
1487
+ /* @__PURE__ */ jsx(
1488
+ "svg",
1489
+ {
1490
+ width: "14",
1491
+ height: "14",
1492
+ viewBox: "0 0 24 24",
1493
+ fill: "none",
1494
+ children: /* @__PURE__ */ jsx(
1495
+ "path",
1496
+ {
1497
+ d: "M12 5v14M5 12h14",
1498
+ stroke: tokens.textMuted,
1499
+ strokeWidth: "2",
1500
+ strokeLinecap: "round"
1501
+ }
1502
+ )
1503
+ }
1504
+ ),
1505
+ "Connect new account"
1506
+ ]
1507
+ }
1508
+ ) : /* @__PURE__ */ jsxs("div", { children: [
1509
+ /* @__PURE__ */ jsxs(
1510
+ "div",
1511
+ {
1512
+ style: {
1513
+ display: "flex",
1514
+ alignItems: "center",
1515
+ justifyContent: "space-between",
1516
+ marginBottom: "8px"
1517
+ },
1518
+ children: [
1519
+ /* @__PURE__ */ jsx(
1520
+ "label",
1521
+ {
1522
+ style: {
1523
+ fontSize: "0.7rem",
1524
+ fontWeight: 600,
1525
+ color: tokens.textMuted,
1526
+ textTransform: "uppercase",
1527
+ letterSpacing: "0.05em"
1528
+ },
1529
+ children: "Select provider"
1530
+ }
1531
+ ),
1532
+ /* @__PURE__ */ jsx(
1533
+ "button",
1534
+ {
1535
+ onClick: () => setShowProviders(false),
1536
+ style: {
1537
+ background: "transparent",
1538
+ border: "none",
1539
+ cursor: "pointer",
1540
+ color: tokens.textMuted,
1541
+ fontSize: "0.75rem",
1542
+ fontFamily: "inherit",
1543
+ outline: "none",
1544
+ padding: "2px 4px"
1545
+ },
1546
+ children: "Cancel"
1547
+ }
1548
+ )
1549
+ ]
1550
+ }
1551
+ ),
1552
+ /* @__PURE__ */ jsx(
1553
+ "div",
1554
+ {
1555
+ style: {
1556
+ display: "flex",
1557
+ flexDirection: "column",
1558
+ gap: "6px"
1559
+ },
1560
+ children: providers.map((p) => /* @__PURE__ */ jsx(
1561
+ ProviderCard,
1562
+ {
1563
+ provider: p,
1564
+ selected: false,
1565
+ onClick: () => {
1566
+ onConnectNewAccount(p.id);
1567
+ setShowProviders(false);
1568
+ }
1569
+ },
1570
+ p.id
1571
+ ))
1572
+ }
1573
+ )
1574
+ ] }) })
1575
+ ]
1576
+ }
1577
+ )
1578
+ ] });
1579
+ }
1580
+ var TOP_UP_OPTIONS = [0, 25, 50, 100, 500];
1581
+ function AllowanceSelector({ action, onSelect }) {
1582
+ const { tokens } = useSwypeConfig();
1583
+ const metadataAmount = action.metadata?.amount;
1584
+ const tokenDecimals = Number(action.metadata?.tokenDecimals ?? 6);
1585
+ const currency = action.metadata?.currency ?? "USD";
1586
+ const transferAmountRaw = metadataAmount ? Number(BigInt(metadataAmount)) / 10 ** tokenDecimals : 0;
1587
+ const [selectedTopUp, setSelectedTopUp] = useState(0);
1588
+ const totalAllowance = transferAmountRaw + selectedTopUp;
1589
+ const formatAmount = (amount) => `$${amount.toLocaleString("en-US", { minimumFractionDigits: 2, maximumFractionDigits: 2 })}`;
1590
+ return /* @__PURE__ */ jsxs(
1591
+ "div",
1592
+ {
1593
+ style: {
1594
+ padding: "4px 0",
1595
+ textAlign: "left"
1596
+ },
1597
+ children: [
1598
+ /* @__PURE__ */ jsx(
1599
+ "h3",
1600
+ {
1601
+ style: {
1602
+ fontSize: "1rem",
1603
+ fontWeight: 600,
1604
+ color: tokens.text,
1605
+ margin: "0 0 6px 0",
1606
+ textAlign: "center"
1607
+ },
1608
+ children: "Set spending limit"
1609
+ }
1610
+ ),
1611
+ /* @__PURE__ */ jsx(
1612
+ "p",
1613
+ {
1614
+ style: {
1615
+ fontSize: "0.8rem",
1616
+ color: tokens.textMuted,
1617
+ margin: "0 0 16px 0",
1618
+ lineHeight: 1.5,
1619
+ textAlign: "center"
1620
+ },
1621
+ children: "Pre-authorize a higher amount so future payments go through instantly without wallet prompts."
1622
+ }
1623
+ ),
1624
+ /* @__PURE__ */ jsxs(
1625
+ "div",
1626
+ {
1627
+ style: {
1628
+ display: "flex",
1629
+ justifyContent: "space-between",
1630
+ alignItems: "center",
1631
+ padding: "10px 14px",
1632
+ background: tokens.bgInput,
1633
+ borderRadius: tokens.radius,
1634
+ marginBottom: "12px",
1635
+ fontSize: "0.825rem"
1636
+ },
1637
+ children: [
1638
+ /* @__PURE__ */ jsx("span", { style: { color: tokens.textSecondary }, children: "This payment" }),
1639
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 600, color: tokens.text }, children: [
1640
+ formatAmount(transferAmountRaw),
1641
+ " ",
1642
+ currency
1643
+ ] })
1644
+ ]
1645
+ }
1646
+ ),
1647
+ /* @__PURE__ */ jsx(
1648
+ "div",
1649
+ {
1650
+ style: {
1651
+ display: "flex",
1652
+ flexDirection: "column",
1653
+ gap: "6px",
1654
+ marginBottom: "16px"
1655
+ },
1656
+ children: TOP_UP_OPTIONS.map((topUp) => {
1657
+ const isSelected = selectedTopUp === topUp;
1658
+ const total = transferAmountRaw + topUp;
1659
+ const label = topUp === 0 ? "Just this payment" : `+${formatAmount(topUp)} for future payments`;
1660
+ return /* @__PURE__ */ jsxs(
1661
+ "button",
1662
+ {
1663
+ onClick: () => setSelectedTopUp(topUp),
1664
+ style: {
1665
+ display: "flex",
1666
+ alignItems: "center",
1667
+ justifyContent: "space-between",
1668
+ width: "100%",
1669
+ padding: "12px 14px",
1670
+ background: isSelected ? tokens.accent + "14" : "transparent",
1671
+ border: `1.5px solid ${isSelected ? tokens.accent : tokens.border}`,
1672
+ borderRadius: tokens.radius,
1673
+ cursor: "pointer",
1674
+ color: tokens.text,
1675
+ fontFamily: "inherit",
1676
+ fontSize: "0.825rem",
1677
+ textAlign: "left",
1678
+ outline: "none",
1679
+ transition: "all 0.12s ease"
1680
+ },
1681
+ children: [
1682
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: "8px" }, children: [
1683
+ /* @__PURE__ */ jsx(
1684
+ "div",
1685
+ {
1686
+ style: {
1687
+ width: 16,
1688
+ height: 16,
1689
+ borderRadius: "50%",
1690
+ border: `2px solid ${isSelected ? tokens.accent : tokens.border}`,
1691
+ display: "flex",
1692
+ alignItems: "center",
1693
+ justifyContent: "center",
1694
+ flexShrink: 0
1695
+ },
1696
+ children: isSelected && /* @__PURE__ */ jsx(
1697
+ "div",
1698
+ {
1699
+ style: {
1700
+ width: 8,
1701
+ height: 8,
1702
+ borderRadius: "50%",
1703
+ background: tokens.accent
1704
+ }
1705
+ }
1706
+ )
1707
+ }
1708
+ ),
1709
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: isSelected ? 600 : 400 }, children: label })
1710
+ ] }),
1711
+ /* @__PURE__ */ jsx(
1712
+ "span",
1713
+ {
1714
+ style: {
1715
+ fontWeight: 600,
1716
+ color: isSelected ? tokens.accent : tokens.textMuted,
1717
+ fontSize: "0.8rem",
1718
+ flexShrink: 0,
1719
+ marginLeft: "8px"
1720
+ },
1721
+ children: formatAmount(total)
1722
+ }
1723
+ )
1724
+ ]
1725
+ },
1726
+ topUp
1727
+ );
1728
+ })
1729
+ }
1730
+ ),
1731
+ /* @__PURE__ */ jsxs(
1732
+ "div",
1733
+ {
1734
+ style: {
1735
+ display: "flex",
1736
+ justifyContent: "space-between",
1737
+ alignItems: "center",
1738
+ padding: "10px 14px",
1739
+ background: tokens.bgInput,
1740
+ borderRadius: tokens.radius,
1741
+ marginBottom: "14px",
1742
+ fontSize: "0.825rem"
1743
+ },
1744
+ children: [
1745
+ /* @__PURE__ */ jsx("span", { style: { color: tokens.textSecondary }, children: "Total authorization" }),
1746
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 700, color: tokens.text, fontSize: "0.9rem" }, children: [
1747
+ formatAmount(totalAllowance),
1748
+ " ",
1749
+ currency
1750
+ ] })
1751
+ ]
1752
+ }
1753
+ ),
1754
+ /* @__PURE__ */ jsx(
1755
+ "button",
1756
+ {
1757
+ onClick: () => onSelect({ topUpAmount: selectedTopUp }),
1758
+ style: {
1759
+ width: "100%",
1760
+ padding: "14px",
1761
+ background: tokens.accent,
1762
+ color: tokens.accentText,
1763
+ border: "none",
1764
+ borderRadius: tokens.radius,
1765
+ fontSize: "1rem",
1766
+ fontWeight: 600,
1767
+ cursor: "pointer",
1768
+ transition: "background 0.15s ease",
1769
+ fontFamily: "inherit"
1770
+ },
1771
+ children: "Authorize & Sign"
1772
+ }
1773
+ )
1774
+ ]
1775
+ }
1776
+ );
1777
+ }
1778
+ function isMobile() {
1779
+ if (typeof navigator === "undefined") return false;
1780
+ return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
1781
+ navigator.userAgent
1782
+ );
1783
+ }
1784
+ function computeSmartDefaults(accts, transferAmount) {
1785
+ if (accts.length === 0) return null;
1786
+ for (const acct of accts) {
1787
+ for (const wallet of acct.wallets) {
1788
+ if (wallet.status === "ACTIVE" && wallet.lastAuthorizedToken) {
1789
+ const matchingSource = wallet.sources.find(
1790
+ (s) => s.token.symbol === wallet.lastAuthorizedToken.symbol
1791
+ );
1792
+ if (matchingSource && matchingSource.balance.available.amount >= transferAmount) {
1793
+ return { accountId: acct.id, walletId: wallet.id };
1794
+ }
1795
+ }
1796
+ }
1797
+ }
1798
+ let bestAccount = null;
1799
+ let bestWallet = null;
1800
+ let bestBalance = -1;
1801
+ for (const acct of accts) {
1802
+ for (const wallet of acct.wallets) {
1803
+ const walletBal = wallet.balance.available.amount;
1804
+ if (walletBal > bestBalance) {
1805
+ bestBalance = walletBal;
1806
+ bestAccount = acct;
1807
+ bestWallet = wallet;
1808
+ }
1809
+ }
1810
+ }
1811
+ if (bestAccount) {
1812
+ return {
1813
+ accountId: bestAccount.id,
1814
+ walletId: bestWallet?.id ?? null
1815
+ };
1816
+ }
1817
+ return { accountId: accts[0].id, walletId: null };
1818
+ }
1819
+ function SwypePayment({
1820
+ destination,
1821
+ onComplete,
1822
+ onError
1823
+ }) {
1824
+ const { apiBaseUrl, tokens, depositAmount } = useSwypeConfig();
1825
+ const { ready, authenticated, login, getAccessToken } = usePrivy();
1826
+ const [step, setStep] = useState("login");
1827
+ const [error, setError] = useState(null);
1828
+ const [providers, setProviders] = useState([]);
1829
+ const [accounts, setAccounts] = useState([]);
1830
+ const [chains, setChains] = useState([]);
1831
+ const [loadingData, setLoadingData] = useState(false);
1832
+ const [selectedAccountId, setSelectedAccountId] = useState(null);
1833
+ const [selectedWalletId, setSelectedWalletId] = useState(null);
1834
+ const [selectedProviderId, setSelectedProviderId] = useState(null);
1835
+ const [connectingNewAccount, setConnectingNewAccount] = useState(false);
1836
+ const [amount, setAmount] = useState(
1837
+ depositAmount != null ? depositAmount.toString() : ""
1838
+ );
1839
+ const [advancedSettings, setAdvancedSettings] = useState({
1840
+ asset: null,
1841
+ chain: null
1842
+ });
1843
+ const [transfer, setTransfer] = useState(null);
1844
+ const [creatingTransfer, setCreatingTransfer] = useState(false);
1845
+ const [mobileFlow, setMobileFlow] = useState(false);
1846
+ const pollingTransferIdRef = useRef(null);
1847
+ const authExecutor = useAuthorizationExecutor();
1109
1848
  const polling = useTransferPolling();
1110
- const sourceType = walletSelection ? "walletId" : "providerId";
1111
- const sourceId = walletSelection?.walletId ?? selectedProviderId ?? "";
1849
+ const sourceType = connectingNewAccount ? "providerId" : selectedWalletId ? "walletId" : selectedAccountId ? "accountId" : "providerId";
1850
+ const sourceId = connectingNewAccount ? selectedProviderId ?? "" : selectedWalletId ? selectedWalletId : selectedAccountId ? selectedAccountId : selectedProviderId ?? "";
1851
+ useEffect(() => {
1852
+ if (depositAmount != null) {
1853
+ setAmount(depositAmount.toString());
1854
+ }
1855
+ }, [depositAmount]);
1112
1856
  useEffect(() => {
1113
1857
  if (ready && authenticated && step === "login") {
1114
- setStep("select-source");
1858
+ if (depositAmount != null && depositAmount > 0) {
1859
+ setStep("ready");
1860
+ } else {
1861
+ setStep("enter-amount");
1862
+ }
1115
1863
  }
1116
- }, [ready, authenticated, step]);
1864
+ }, [ready, authenticated, step, depositAmount]);
1865
+ const loadingDataRef = useRef(false);
1117
1866
  useEffect(() => {
1118
- if (step !== "select-source") return;
1119
- if (providers.length > 0) return;
1867
+ if (!authenticated) return;
1868
+ if (accounts.length > 0 || loadingDataRef.current) return;
1120
1869
  let cancelled = false;
1870
+ loadingDataRef.current = true;
1121
1871
  const load = async () => {
1122
1872
  setLoadingData(true);
1123
1873
  setError(null);
1124
1874
  try {
1125
1875
  const token = await getAccessToken();
1126
1876
  if (!token) throw new Error("Not authenticated");
1127
- const [prov, accts] = await Promise.all([
1877
+ const [prov, accts, chn] = await Promise.all([
1128
1878
  fetchProviders(apiBaseUrl, token),
1129
- fetchAccounts(apiBaseUrl, token)
1879
+ fetchAccounts(apiBaseUrl, token),
1880
+ fetchChains(apiBaseUrl, token)
1130
1881
  ]);
1131
1882
  if (cancelled) return;
1132
1883
  setProviders(prov);
1133
1884
  setAccounts(accts);
1134
- if (prov.length > 0) setSelectedProviderId(prov[0].id);
1885
+ setChains(chn);
1886
+ const parsedAmt = depositAmount != null ? depositAmount : 0;
1887
+ const defaults = computeSmartDefaults(accts, parsedAmt);
1888
+ if (defaults) {
1889
+ setSelectedAccountId(defaults.accountId);
1890
+ setSelectedWalletId(defaults.walletId);
1891
+ } else if (prov.length > 0) {
1892
+ setSelectedProviderId(prov[0].id);
1893
+ }
1135
1894
  } catch (err) {
1136
1895
  if (!cancelled) {
1137
1896
  const msg = err instanceof Error ? err.message : "Failed to load data";
1138
1897
  setError(msg);
1139
1898
  }
1140
1899
  } finally {
1141
- if (!cancelled) setLoadingData(false);
1900
+ if (!cancelled) {
1901
+ setLoadingData(false);
1902
+ loadingDataRef.current = false;
1903
+ }
1142
1904
  }
1143
1905
  };
1144
1906
  load();
1145
1907
  return () => {
1146
1908
  cancelled = true;
1909
+ loadingDataRef.current = false;
1147
1910
  };
1148
- }, [step, providers.length, apiBaseUrl, getAccessToken]);
1911
+ }, [authenticated, accounts.length, apiBaseUrl, getAccessToken]);
1149
1912
  useEffect(() => {
1150
1913
  if (!polling.transfer) return;
1151
1914
  if (polling.transfer.status === "COMPLETED") {
@@ -1172,6 +1935,61 @@ function SwypePayment({
1172
1935
  document.removeEventListener("visibilitychange", handleVisibility);
1173
1936
  };
1174
1937
  }, [mobileFlow, polling]);
1938
+ useEffect(() => {
1939
+ if (!authExecutor.pendingSelectSource) return;
1940
+ const action = authExecutor.pendingSelectSource;
1941
+ const hasAdvancedOverride = advancedSettings.asset !== null || advancedSettings.chain !== null;
1942
+ if (hasAdvancedOverride) {
1943
+ const options = action.metadata?.options ?? [];
1944
+ const recommended = action.metadata?.recommended;
1945
+ const match = options.find(
1946
+ (opt) => (advancedSettings.chain === null || opt.chainName === advancedSettings.chain) && (advancedSettings.asset === null || opt.tokenSymbol === advancedSettings.asset)
1947
+ );
1948
+ if (match) {
1949
+ authExecutor.resolveSelectSource({
1950
+ chainName: match.chainName,
1951
+ tokenSymbol: match.tokenSymbol
1952
+ });
1953
+ } else if (recommended) {
1954
+ authExecutor.resolveSelectSource({
1955
+ chainName: recommended.chainName,
1956
+ tokenSymbol: recommended.tokenSymbol
1957
+ });
1958
+ }
1959
+ } else {
1960
+ const options = action.metadata?.options ?? [];
1961
+ const recommended = action.metadata?.recommended;
1962
+ const selWallet = selectedWalletId ? accounts.find((a) => a.id === selectedAccountId)?.wallets.find((w) => w.id === selectedWalletId) : null;
1963
+ if (selWallet) {
1964
+ const walletMatch = options.find(
1965
+ (opt) => opt.chainName === selWallet.chain.name
1966
+ );
1967
+ if (walletMatch) {
1968
+ authExecutor.resolveSelectSource({
1969
+ chainName: walletMatch.chainName,
1970
+ tokenSymbol: walletMatch.tokenSymbol
1971
+ });
1972
+ return;
1973
+ }
1974
+ }
1975
+ if (recommended) {
1976
+ authExecutor.resolveSelectSource({
1977
+ chainName: recommended.chainName,
1978
+ tokenSymbol: recommended.tokenSymbol
1979
+ });
1980
+ } else if (options.length > 0) {
1981
+ authExecutor.resolveSelectSource({
1982
+ chainName: options[0].chainName,
1983
+ tokenSymbol: options[0].tokenSymbol
1984
+ });
1985
+ } else {
1986
+ authExecutor.resolveSelectSource({
1987
+ chainName: "Base",
1988
+ tokenSymbol: "USDC"
1989
+ });
1990
+ }
1991
+ }
1992
+ }, [authExecutor, authExecutor.pendingSelectSource, advancedSettings, selectedWalletId, selectedAccountId, accounts]);
1175
1993
  const handlePay = useCallback(async () => {
1176
1994
  const parsedAmount = parseFloat(amount);
1177
1995
  if (isNaN(parsedAmount) || parsedAmount <= 0) {
@@ -1179,7 +1997,7 @@ function SwypePayment({
1179
1997
  return;
1180
1998
  }
1181
1999
  if (!sourceId) {
1182
- setError("Select a source.");
2000
+ setError("No account or provider selected.");
1183
2001
  return;
1184
2002
  }
1185
2003
  setStep("processing");
@@ -1189,12 +2007,6 @@ function SwypePayment({
1189
2007
  try {
1190
2008
  const token = await getAccessToken();
1191
2009
  if (!token) throw new Error("Not authenticated");
1192
- const parsedTopUp = parseFloat(topUpBalance);
1193
- if (!isNaN(parsedTopUp) && parsedTopUp > 0) {
1194
- await updateUserConfig(apiBaseUrl, token, {
1195
- defaultAllowance: parsedTopUp
1196
- });
1197
- }
1198
2010
  const t = await createTransfer(apiBaseUrl, token, {
1199
2011
  sourceType,
1200
2012
  sourceId,
@@ -1218,7 +2030,7 @@ function SwypePayment({
1218
2030
  const msg = err instanceof Error ? err.message : "Transfer creation failed";
1219
2031
  setError(msg);
1220
2032
  onError?.(msg);
1221
- setStep("enter-amount");
2033
+ setStep("ready");
1222
2034
  } finally {
1223
2035
  setCreatingTransfer(false);
1224
2036
  }
@@ -1231,19 +2043,24 @@ function SwypePayment({
1231
2043
  getAccessToken,
1232
2044
  authExecutor,
1233
2045
  polling,
1234
- onError,
1235
- topUpBalance
2046
+ onError
1236
2047
  ]);
1237
2048
  const handleNewPayment = () => {
1238
- setStep("select-source");
2049
+ setStep("ready");
1239
2050
  setTransfer(null);
1240
2051
  setError(null);
1241
- setAmount("0.10");
1242
- setTopUpBalance("");
2052
+ setAmount(depositAmount != null ? depositAmount.toString() : "");
1243
2053
  setMobileFlow(false);
1244
2054
  pollingTransferIdRef.current = null;
1245
- setWalletSelection(null);
1246
- if (providers.length > 0) setSelectedProviderId(providers[0].id);
2055
+ setConnectingNewAccount(false);
2056
+ setSelectedWalletId(null);
2057
+ setAdvancedSettings({ asset: null, chain: null });
2058
+ if (accounts.length > 0) setSelectedAccountId(accounts[0].id);
2059
+ };
2060
+ const handleConnectNewAccount = (providerId) => {
2061
+ setSelectedProviderId(providerId);
2062
+ setSelectedAccountId(null);
2063
+ setConnectingNewAccount(true);
1247
2064
  };
1248
2065
  const cardStyle = {
1249
2066
  background: tokens.bgCard,
@@ -1281,15 +2098,10 @@ function SwypePayment({
1281
2098
  opacity: 0.5,
1282
2099
  cursor: "not-allowed"
1283
2100
  };
1284
- const btnSecondary = {
2101
+ ({
1285
2102
  ...btnPrimary,
1286
- background: "transparent",
1287
2103
  color: tokens.textSecondary,
1288
- border: `1px solid ${tokens.border}`,
1289
- width: "auto",
1290
- flex: "0 0 auto",
1291
- padding: "14px 20px"
1292
- };
2104
+ border: `1px solid ${tokens.border}`});
1293
2105
  const errorStyle = {
1294
2106
  background: tokens.errorBg,
1295
2107
  border: `1px solid ${tokens.error}`,
@@ -1380,62 +2192,20 @@ function SwypePayment({
1380
2192
  /* @__PURE__ */ jsx("button", { style: btnPrimary, onClick: login, children: "Connect to Swype" })
1381
2193
  ] }) });
1382
2194
  }
1383
- if (step === "select-source") {
1384
- return /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
1385
- stepBadge("Select payment source"),
1386
- /* @__PURE__ */ jsx("h2", { style: headingStyle, children: "Choose a provider" }),
1387
- error && /* @__PURE__ */ jsx("div", { style: errorStyle, children: error }),
1388
- loadingData ? /* @__PURE__ */ jsx("div", { style: { padding: "24px 0", textAlign: "center" }, children: /* @__PURE__ */ jsx(Spinner, { label: "Loading providers..." }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1389
- accounts.length > 0 && /* @__PURE__ */ jsx("div", { style: { marginBottom: "16px" }, children: /* @__PURE__ */ jsx(
1390
- AccountWalletSelector,
1391
- {
1392
- accounts,
1393
- selection: walletSelection,
1394
- onSelect: (sel) => {
1395
- setWalletSelection(sel);
1396
- if (sel) setSelectedProviderId(null);
1397
- }
1398
- }
1399
- ) }),
1400
- !walletSelection && /* @__PURE__ */ jsx(
1401
- "div",
1402
- {
1403
- style: {
1404
- display: "flex",
1405
- flexDirection: "column",
1406
- gap: "8px",
1407
- marginBottom: "20px"
1408
- },
1409
- children: providers.map((p) => /* @__PURE__ */ jsx(
1410
- ProviderCard,
1411
- {
1412
- provider: p,
1413
- selected: selectedProviderId === p.id,
1414
- onClick: () => {
1415
- setSelectedProviderId(p.id);
1416
- setWalletSelection(null);
1417
- }
1418
- },
1419
- p.id
1420
- ))
1421
- }
1422
- ),
1423
- /* @__PURE__ */ jsx(
1424
- "button",
1425
- {
1426
- style: sourceId ? btnPrimary : btnDisabled,
1427
- disabled: !sourceId,
1428
- onClick: () => {
1429
- setError(null);
1430
- setStep("enter-amount");
1431
- },
1432
- children: "Continue"
1433
- }
1434
- )
1435
- ] })
1436
- ] });
1437
- }
1438
2195
  if (step === "enter-amount") {
2196
+ const parsedAmount = parseFloat(amount);
2197
+ const canContinue = !isNaN(parsedAmount) && parsedAmount > 0;
2198
+ let maxSourceBalance = null;
2199
+ for (const acct of accounts) {
2200
+ for (const wallet of acct.wallets) {
2201
+ for (const source of wallet.sources) {
2202
+ const bal = source.balance.available.amount;
2203
+ if (maxSourceBalance === null || bal > maxSourceBalance) {
2204
+ maxSourceBalance = bal;
2205
+ }
2206
+ }
2207
+ }
2208
+ }
1439
2209
  return /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
1440
2210
  stepBadge("Enter amount"),
1441
2211
  /* @__PURE__ */ jsx("h2", { style: headingStyle, children: "How much?" }),
@@ -1451,7 +2221,7 @@ function SwypePayment({
1451
2221
  border: `1px solid ${tokens.border}`,
1452
2222
  borderRadius: tokens.radius,
1453
2223
  padding: "4px 14px 4px 4px",
1454
- marginBottom: "20px"
2224
+ marginBottom: "8px"
1455
2225
  },
1456
2226
  children: [
1457
2227
  /* @__PURE__ */ jsx(
@@ -1475,6 +2245,7 @@ function SwypePayment({
1475
2245
  step: "0.01",
1476
2246
  value: amount,
1477
2247
  onChange: (e) => setAmount(e.target.value),
2248
+ placeholder: "0.00",
1478
2249
  style: {
1479
2250
  flex: 1,
1480
2251
  background: "transparent",
@@ -1506,154 +2277,248 @@ function SwypePayment({
1506
2277
  ]
1507
2278
  }
1508
2279
  ),
1509
- /* @__PURE__ */ jsxs(
2280
+ /* @__PURE__ */ jsx(
1510
2281
  "div",
1511
2282
  {
1512
2283
  style: {
1513
- display: "flex",
1514
- alignItems: "center",
1515
- gap: "8px",
1516
- background: tokens.bgInput,
1517
- border: `1px solid ${tokens.border}`,
1518
- borderRadius: tokens.radius,
1519
- padding: "4px 14px 4px 4px",
1520
- marginBottom: "20px"
2284
+ fontSize: "0.8rem",
2285
+ color: tokens.textMuted,
2286
+ marginBottom: "20px",
2287
+ paddingLeft: "2px"
1521
2288
  },
1522
- children: [
1523
- /* @__PURE__ */ jsx(
1524
- "span",
1525
- {
1526
- style: {
1527
- fontSize: "1rem",
1528
- fontWeight: 600,
1529
- color: tokens.textMuted,
1530
- paddingLeft: "10px",
1531
- userSelect: "none"
1532
- },
1533
- children: "$"
1534
- }
1535
- ),
1536
- /* @__PURE__ */ jsx(
1537
- "input",
1538
- {
1539
- type: "number",
1540
- min: "0",
1541
- step: "1",
1542
- placeholder: "Top up balance (optional)",
1543
- value: topUpBalance,
1544
- onChange: (e) => setTopUpBalance(e.target.value),
1545
- style: {
1546
- flex: 1,
1547
- background: "transparent",
1548
- border: "none",
1549
- outline: "none",
1550
- color: tokens.text,
1551
- fontSize: "1rem",
1552
- fontWeight: 600,
1553
- fontFamily: "inherit",
1554
- padding: "10px 0"
1555
- }
1556
- }
1557
- ),
1558
- /* @__PURE__ */ jsx(
1559
- "span",
1560
- {
1561
- style: {
1562
- fontSize: "0.75rem",
1563
- fontWeight: 600,
1564
- color: tokens.textMuted,
1565
- background: tokens.bgHover,
1566
- padding: "4px 10px",
1567
- borderRadius: "6px",
1568
- whiteSpace: "nowrap"
1569
- },
1570
- children: "USD"
1571
- }
1572
- )
1573
- ]
2289
+ children: loadingData ? /* @__PURE__ */ jsx("span", { children: "Loading balance..." }) : maxSourceBalance !== null && maxSourceBalance > 0 ? /* @__PURE__ */ jsxs("span", { children: [
2290
+ "Available: ",
2291
+ /* @__PURE__ */ jsxs("span", { style: { fontWeight: 600, color: tokens.textSecondary }, children: [
2292
+ "$",
2293
+ maxSourceBalance.toFixed(2)
2294
+ ] })
2295
+ ] }) : null
1574
2296
  }
1575
2297
  ),
1576
2298
  /* @__PURE__ */ jsx(
1577
- "p",
2299
+ "button",
1578
2300
  {
1579
- style: {
1580
- fontSize: "0.75rem",
1581
- color: tokens.textMuted,
1582
- margin: "-12px 0 16px 0",
1583
- lineHeight: 1.4
2301
+ style: canContinue ? btnPrimary : btnDisabled,
2302
+ disabled: !canContinue,
2303
+ onClick: () => {
2304
+ setError(null);
2305
+ setStep("ready");
1584
2306
  },
1585
- children: "Set a higher allowance to skip wallet prompts on future payments under this amount."
2307
+ children: "Continue"
1586
2308
  }
1587
- ),
1588
- /* @__PURE__ */ jsxs(
1589
- "div",
1590
- {
1591
- style: {
1592
- fontSize: "0.825rem",
1593
- color: tokens.textSecondary,
1594
- marginBottom: "20px",
1595
- padding: "12px 14px",
1596
- background: tokens.bgInput,
1597
- borderRadius: tokens.radius,
1598
- lineHeight: 1.7
1599
- },
1600
- children: [
1601
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
1602
- /* @__PURE__ */ jsx("span", { children: "To" }),
2309
+ )
2310
+ ] });
2311
+ }
2312
+ if (step === "ready") {
2313
+ const parsedAmount = parseFloat(amount);
2314
+ const canPay = !isNaN(parsedAmount) && parsedAmount > 0 && !!sourceId && !loadingData;
2315
+ const noAccounts = !loadingData && accounts.length === 0;
2316
+ return /* @__PURE__ */ jsxs("div", { style: cardStyle, children: [
2317
+ stepBadge("Review & pay"),
2318
+ error && /* @__PURE__ */ jsx("div", { style: errorStyle, children: error }),
2319
+ loadingData ? /* @__PURE__ */ jsx("div", { style: { padding: "24px 0", textAlign: "center" }, children: /* @__PURE__ */ jsx(Spinner, { label: "Loading..." }) }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2320
+ /* @__PURE__ */ jsxs(
2321
+ "div",
2322
+ {
2323
+ style: {
2324
+ textAlign: "center",
2325
+ marginBottom: "20px"
2326
+ },
2327
+ children: [
1603
2328
  /* @__PURE__ */ jsxs(
1604
- "span",
2329
+ "div",
1605
2330
  {
1606
2331
  style: {
1607
- fontFamily: '"SF Mono", "Fira Code", monospace',
1608
- fontSize: "0.8rem"
2332
+ fontSize: "2rem",
2333
+ fontWeight: 700,
2334
+ color: tokens.text,
2335
+ lineHeight: 1.2
1609
2336
  },
1610
2337
  children: [
1611
- destination.address.slice(0, 6),
1612
- "...",
1613
- destination.address.slice(-4)
2338
+ "$",
2339
+ parsedAmount > 0 ? parsedAmount.toFixed(2) : "0.00"
1614
2340
  ]
1615
2341
  }
2342
+ ),
2343
+ /* @__PURE__ */ jsx(
2344
+ "button",
2345
+ {
2346
+ onClick: () => setStep("enter-amount"),
2347
+ style: {
2348
+ background: "transparent",
2349
+ border: "none",
2350
+ cursor: "pointer",
2351
+ color: tokens.textMuted,
2352
+ fontSize: "0.75rem",
2353
+ fontFamily: "inherit",
2354
+ outline: "none",
2355
+ padding: "4px 8px",
2356
+ marginTop: "4px"
2357
+ },
2358
+ children: "Change amount"
2359
+ }
1616
2360
  )
1617
- ] }),
1618
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
1619
- /* @__PURE__ */ jsx("span", { children: "Token" }),
1620
- /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: destination.token.symbol })
1621
- ] }),
1622
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
1623
- /* @__PURE__ */ jsx("span", { children: "Source" }),
1624
- /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: walletSelection ? `${walletSelection.walletName} (${walletSelection.chainName})` : providers.find((p) => p.id === selectedProviderId)?.name ?? "Provider" })
1625
- ] })
1626
- ]
1627
- }
1628
- ),
1629
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: "10px" }, children: [
1630
- /* @__PURE__ */ jsx(
1631
- "button",
2361
+ ]
2362
+ }
2363
+ ),
2364
+ /* @__PURE__ */ jsxs(
2365
+ "div",
1632
2366
  {
1633
- style: btnSecondary,
1634
- onClick: () => {
1635
- setError(null);
1636
- setStep("select-source");
2367
+ style: {
2368
+ fontSize: "0.825rem",
2369
+ color: tokens.textSecondary,
2370
+ marginBottom: "16px",
2371
+ padding: "12px 14px",
2372
+ background: tokens.bgInput,
2373
+ borderRadius: tokens.radius,
2374
+ lineHeight: 1.7
1637
2375
  },
1638
- children: "Back"
2376
+ children: [
2377
+ /* @__PURE__ */ jsxs(
2378
+ "div",
2379
+ {
2380
+ style: { display: "flex", justifyContent: "space-between" },
2381
+ children: [
2382
+ /* @__PURE__ */ jsx("span", { children: "To" }),
2383
+ /* @__PURE__ */ jsxs(
2384
+ "span",
2385
+ {
2386
+ style: {
2387
+ fontFamily: '"SF Mono", "Fira Code", monospace',
2388
+ fontSize: "0.8rem"
2389
+ },
2390
+ children: [
2391
+ destination.address.slice(0, 6),
2392
+ "...",
2393
+ destination.address.slice(-4)
2394
+ ]
2395
+ }
2396
+ )
2397
+ ]
2398
+ }
2399
+ ),
2400
+ /* @__PURE__ */ jsxs(
2401
+ "div",
2402
+ {
2403
+ style: { display: "flex", justifyContent: "space-between" },
2404
+ children: [
2405
+ /* @__PURE__ */ jsx("span", { children: "Token" }),
2406
+ /* @__PURE__ */ jsx("span", { style: { fontWeight: 600 }, children: destination.token.symbol })
2407
+ ]
2408
+ }
2409
+ ),
2410
+ /* @__PURE__ */ jsxs(
2411
+ "div",
2412
+ {
2413
+ style: {
2414
+ display: "flex",
2415
+ justifyContent: "space-between",
2416
+ alignItems: "center"
2417
+ },
2418
+ children: [
2419
+ /* @__PURE__ */ jsx("span", { children: "From" }),
2420
+ noAccounts ? /* @__PURE__ */ jsx("span", { style: { fontWeight: 500, color: tokens.textMuted }, children: "New account" }) : /* @__PURE__ */ jsx(
2421
+ AccountDropdown,
2422
+ {
2423
+ accounts,
2424
+ selectedAccountId,
2425
+ selectedWalletId,
2426
+ onSelect: (id) => {
2427
+ setSelectedAccountId(id);
2428
+ setSelectedWalletId(null);
2429
+ setConnectingNewAccount(false);
2430
+ setSelectedProviderId(null);
2431
+ },
2432
+ onWalletSelect: (accountId, walletId) => {
2433
+ setSelectedAccountId(accountId);
2434
+ setSelectedWalletId(walletId);
2435
+ setConnectingNewAccount(false);
2436
+ setSelectedProviderId(null);
2437
+ }
2438
+ }
2439
+ )
2440
+ ]
2441
+ }
2442
+ )
2443
+ ]
1639
2444
  }
1640
2445
  ),
2446
+ noAccounts && /* @__PURE__ */ jsxs("div", { style: { marginBottom: "16px" }, children: [
2447
+ /* @__PURE__ */ jsx(
2448
+ "label",
2449
+ {
2450
+ style: {
2451
+ display: "block",
2452
+ fontSize: "0.8rem",
2453
+ color: tokens.textMuted,
2454
+ marginBottom: "8px",
2455
+ fontWeight: 500,
2456
+ textTransform: "uppercase",
2457
+ letterSpacing: "0.05em"
2458
+ },
2459
+ children: "Connect a wallet"
2460
+ }
2461
+ ),
2462
+ /* @__PURE__ */ jsx(
2463
+ "div",
2464
+ {
2465
+ style: {
2466
+ display: "flex",
2467
+ flexDirection: "column",
2468
+ gap: "8px"
2469
+ },
2470
+ children: providers.map((p) => /* @__PURE__ */ jsx(
2471
+ ProviderCard,
2472
+ {
2473
+ provider: p,
2474
+ selected: selectedProviderId === p.id,
2475
+ onClick: () => {
2476
+ setSelectedProviderId(p.id);
2477
+ setSelectedAccountId(null);
2478
+ setConnectingNewAccount(false);
2479
+ }
2480
+ },
2481
+ p.id
2482
+ ))
2483
+ }
2484
+ )
2485
+ ] }),
1641
2486
  /* @__PURE__ */ jsxs(
1642
2487
  "button",
1643
2488
  {
1644
- style: parseFloat(amount) > 0 ? btnPrimary : btnDisabled,
1645
- disabled: !(parseFloat(amount) > 0),
2489
+ style: canPay ? btnPrimary : btnDisabled,
2490
+ disabled: !canPay,
1646
2491
  onClick: handlePay,
1647
2492
  children: [
1648
2493
  "Pay $",
1649
- parseFloat(amount || "0").toFixed(2)
2494
+ parsedAmount > 0 ? parsedAmount.toFixed(2) : "0.00"
1650
2495
  ]
1651
2496
  }
2497
+ ),
2498
+ !noAccounts && /* @__PURE__ */ jsx(
2499
+ AdvancedSettings,
2500
+ {
2501
+ settings: advancedSettings,
2502
+ onChange: setAdvancedSettings,
2503
+ chains,
2504
+ providers,
2505
+ onConnectNewAccount: handleConnectNewAccount,
2506
+ connectingNewAccount
2507
+ }
1652
2508
  )
1653
2509
  ] })
1654
2510
  ] });
1655
2511
  }
1656
2512
  if (step === "processing") {
2513
+ if (authExecutor.pendingAllowanceSelection) {
2514
+ return /* @__PURE__ */ jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsx(
2515
+ AllowanceSelector,
2516
+ {
2517
+ action: authExecutor.pendingAllowanceSelection,
2518
+ onSelect: authExecutor.resolveAllowanceSelection
2519
+ }
2520
+ ) });
2521
+ }
1657
2522
  const statusLabel = creatingTransfer ? "Creating transfer..." : mobileFlow ? "Waiting for authorization..." : authExecutor.executing ? "Authorizing..." : polling.isPolling ? "Processing payment..." : "Please wait...";
1658
2523
  const statusDescription = creatingTransfer ? "Setting up your transfer..." : mobileFlow ? "Complete the authorization in your wallet app, then return here." : authExecutor.executing ? "Complete the wallet prompts to authorize this payment." : polling.isPolling ? "Your payment is being processed. This usually takes a few moments." : "Hang tight...";
1659
2524
  return /* @__PURE__ */ jsx("div", { style: cardStyle, children: /* @__PURE__ */ jsxs("div", { style: { textAlign: "center", padding: "16px 0" }, children: [
@@ -1830,6 +2695,6 @@ function SwypePayment({
1830
2695
  return null;
1831
2696
  }
1832
2697
 
1833
- export { SwypePayment, SwypeProvider, darkTheme, getTheme, lightTheme, api_exports as swypeApi, useAuthorizationExecutor, useSwypeConfig, useTransferPolling };
2698
+ export { SwypePayment, SwypeProvider, darkTheme, getTheme, lightTheme, api_exports as swypeApi, useAuthorizationExecutor, useSwypeConfig, useSwypeDepositAmount, useTransferPolling };
1834
2699
  //# sourceMappingURL=index.js.map
1835
2700
  //# sourceMappingURL=index.js.map