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