@openfort/react 1.2.0 → 1.3.0

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.
Files changed (86) hide show
  1. package/build/assets/logos.d.ts +3 -0
  2. package/build/assets/logos.js +2 -0
  3. package/build/assets/logos.js.map +1 -1
  4. package/build/components/Common/SolanaChain/index.d.ts +8 -0
  5. package/build/components/Common/SolanaChain/index.js +40 -0
  6. package/build/components/Common/SolanaChain/index.js.map +1 -0
  7. package/build/components/ConnectModal/index.js +2 -0
  8. package/build/components/ConnectModal/index.js.map +1 -1
  9. package/build/components/Openfort/types.d.ts +17 -11
  10. package/build/components/Openfort/types.js +1 -0
  11. package/build/components/Openfort/types.js.map +1 -1
  12. package/build/components/Pages/Buy/coinbaseApi.d.ts +1 -1
  13. package/build/components/Pages/Buy/coinbaseApi.js +2 -13
  14. package/build/components/Pages/Buy/coinbaseApi.js.map +1 -1
  15. package/build/components/Pages/Buy/evmCurrencies.d.ts +11 -0
  16. package/build/components/Pages/Buy/evmCurrencies.js +27 -0
  17. package/build/components/Pages/Buy/evmCurrencies.js.map +1 -0
  18. package/build/components/Pages/Buy/index.js +8 -1
  19. package/build/components/Pages/Buy/index.js.map +1 -1
  20. package/build/components/Pages/Buy/onrampApi.d.ts +8 -1
  21. package/build/components/Pages/Buy/onrampApi.js +24 -14
  22. package/build/components/Pages/Buy/onrampApi.js.map +1 -1
  23. package/build/components/Pages/Buy/solanaCurrencies.d.ts +9 -0
  24. package/build/components/Pages/Buy/solanaCurrencies.js +25 -0
  25. package/build/components/Pages/Buy/solanaCurrencies.js.map +1 -0
  26. package/build/components/Pages/Buy/stripeApi.d.ts +1 -1
  27. package/build/components/Pages/Buy/stripeApi.js +2 -13
  28. package/build/components/Pages/Buy/stripeApi.js.map +1 -1
  29. package/build/components/Pages/BuyComplete/index.js +7 -1
  30. package/build/components/Pages/BuyComplete/index.js.map +1 -1
  31. package/build/components/Pages/BuyProcessing/index.js +9 -5
  32. package/build/components/Pages/BuyProcessing/index.js.map +1 -1
  33. package/build/components/Pages/BuySelectProvider/index.js +10 -5
  34. package/build/components/Pages/BuySelectProvider/index.js.map +1 -1
  35. package/build/components/Pages/Connected/SolanaConnected.js +3 -2
  36. package/build/components/Pages/Connected/SolanaConnected.js.map +1 -1
  37. package/build/components/Pages/Deposit/DepositProgress.js +3 -2
  38. package/build/components/Pages/Deposit/DepositProgress.js.map +1 -1
  39. package/build/components/Pages/Deposit/DepositSuccess.js +2 -1
  40. package/build/components/Pages/Deposit/DepositSuccess.js.map +1 -1
  41. package/build/components/Pages/Deposit/index.js +16 -2
  42. package/build/components/Pages/Deposit/index.js.map +1 -1
  43. package/build/components/Pages/Deposit/paymentOptions.js +3 -3
  44. package/build/components/Pages/Deposit/useDepositRoute.d.ts +0 -1
  45. package/build/components/Pages/Deposit/useDepositRoute.js +10 -11
  46. package/build/components/Pages/Deposit/useDepositRoute.js.map +1 -1
  47. package/build/components/Pages/Deposit/useFundingTarget.d.ts +2 -4
  48. package/build/components/Pages/Deposit/useFundingTarget.js +3 -5
  49. package/build/components/Pages/Deposit/useFundingTarget.js.map +1 -1
  50. package/build/components/Pages/DepositCex/index.js +7 -8
  51. package/build/components/Pages/DepositCex/index.js.map +1 -1
  52. package/build/components/Pages/DepositWallet/DepositWalletDesktop.d.ts +4 -1
  53. package/build/components/Pages/DepositWallet/DepositWalletDesktop.js +11 -20
  54. package/build/components/Pages/DepositWallet/DepositWalletDesktop.js.map +1 -1
  55. package/build/components/Pages/DepositWallet/index.d.ts +5 -5
  56. package/build/components/Pages/DepositWallet/index.js +36 -31
  57. package/build/components/Pages/DepositWallet/index.js.map +1 -1
  58. package/build/components/Pages/DepositWallet/walletDeeplinks.d.ts +6 -4
  59. package/build/components/Pages/DepositWallet/walletDeeplinks.js +3 -1
  60. package/build/components/Pages/DepositWallet/walletDeeplinks.js.map +1 -1
  61. package/build/components/Pages/SelectToken/SolanaSelectToken.d.ts +1 -0
  62. package/build/components/Pages/SelectToken/SolanaSelectToken.js +50 -0
  63. package/build/components/Pages/SelectToken/SolanaSelectToken.js.map +1 -0
  64. package/build/components/Pages/SelectToken/index.js +13 -2
  65. package/build/components/Pages/SelectToken/index.js.map +1 -1
  66. package/build/components/Pages/Send/SolanaSend.js +32 -31
  67. package/build/components/Pages/Send/SolanaSend.js.map +1 -1
  68. package/build/components/Pages/Send/utils.js +4 -1
  69. package/build/components/Pages/Send/utils.js.map +1 -1
  70. package/build/components/Pages/SendConfirmation/SolanaSendConfirmation.js +57 -13
  71. package/build/components/Pages/SendConfirmation/SolanaSendConfirmation.js.map +1 -1
  72. package/build/components/Pages/SendConfirmation/styles.d.ts +0 -5
  73. package/build/components/Pages/SendConfirmation/styles.js +1 -39
  74. package/build/components/Pages/SendConfirmation/styles.js.map +1 -1
  75. package/build/hooks/openfort/useFunding.js +7 -7
  76. package/build/hooks/openfort/useFundingChains.js +4 -6
  77. package/build/hooks/openfort/useFundingChains.js.map +1 -1
  78. package/build/shared/hooks/useAsyncData.js +15 -2
  79. package/build/shared/hooks/useAsyncData.js.map +1 -1
  80. package/build/solana/transfer.d.ts +35 -6
  81. package/build/solana/transfer.js +112 -18
  82. package/build/solana/transfer.js.map +1 -1
  83. package/build/solana/types.d.ts +8 -0
  84. package/build/version.d.ts +1 -1
  85. package/build/version.js +1 -1
  86. package/package.json +5 -1
@@ -8,12 +8,15 @@ import { ModalHeading, ModalBody } from '../../Common/Modal/styles.js';
8
8
  import { routes } from '../../Openfort/types.js';
9
9
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
10
10
  import { PageContent } from '../../PageContent/index.js';
11
+ import { Section, SectionLabel, AmountCard, CurrencySymbol, AmountInput, PresetList, PresetButton } from '../Buy/styles.js';
11
12
  import { AddressPageLink } from '../Deposit/AddressPageLink.js';
12
13
  import { isDepositFlowActive, DepositProgress } from '../Deposit/DepositProgress.js';
13
14
  import { walletListBtn } from '../Deposit/formStyles.js';
14
15
  import { RouteSelectors } from '../Deposit/RouteSelectors.js';
16
+ import { isSolana } from '../Deposit/sources.js';
15
17
  import { StepDivider, Skeleton, ButtonLogo } from '../Deposit/styles.js';
16
18
  import { useDepositRoute } from '../Deposit/useDepositRoute.js';
19
+ import { sanitizeAmountInput } from '../Send/utils.js';
17
20
  import { DepositWalletDesktop } from './DepositWalletDesktop.js';
18
21
  import { OPENFORT_DEPOSIT_PAGE_URL, caipToChainId, buildDepositPageUrl, buildOpenDappLinks } from './walletDeeplinks.js';
19
22
 
@@ -26,58 +29,60 @@ const WALLET_LOGO = {
26
29
  rainbow: jsx(Logos.Rainbow, { round: true }),
27
30
  rabby: jsx(Logos.Rabby, {}),
28
31
  };
29
- const amountInputStyle = {
30
- width: '100%',
31
- padding: '12px 14px',
32
- borderRadius: 12,
33
- border: '1px solid var(--ck-body-divider, #e4e4e7)',
34
- background: 'var(--ck-body-background-secondary, #fafafa)',
35
- color: 'var(--ck-body-color, #111)',
36
- fontSize: 16,
37
- outline: 'none',
38
- marginTop: 14,
39
- };
40
- /** Keep only digits and a single decimal point. */
41
- function sanitizeAmount(v) {
42
- const cleaned = v.replace(/[^0-9.]/g, '');
43
- const [whole, ...rest] = cleaned.split('.');
44
- return rest.length ? `${whole}.${rest.join('')}` : cleaned;
45
- }
32
+ /** Preset deposit amounts, in the selected source token's units. */
33
+ const PRESETS = [10, 25, 50];
46
34
  /**
47
- * Transfer from wallet. On mobile, leads with open-dApp deeplinks that send the
48
- * user into their wallet app's in-app browser pointed at the hosted deposit page
49
- * (with the address/chain/token/amount prefilled). On desktop, sends straight
50
- * from the browser-extension wallet. Either way the manual deposit-address / QR
51
- * path stays available below.
35
+ * Transfer from wallet. Pick a source chain/token and an amount, then choose the
36
+ * wallet to send from. On mobile that's open-dApp deeplinks into the wallet app's
37
+ * in-app browser (address/chain/token/amount prefilled); on desktop it's a direct
38
+ * send from a browser-extension wallet. The manual deposit-address / QR path stays
39
+ * available below.
52
40
  */
53
41
  const DepositWallet = () => {
54
- var _a, _b, _c, _d, _e, _f, _g, _h;
42
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m;
55
43
  const { triggerResize, uiConfig } = useOpenfort();
56
44
  const isMobile = useIsMobile();
57
45
  const route = useDepositRoute('crypto');
58
46
  const [amount, setAmount] = useState('');
47
+ const [pressedPreset, setPressedPreset] = useState(null);
59
48
  const depositPageUrl = (_b = (_a = uiConfig.funding) === null || _a === void 0 ? void 0 : _a.depositPageUrl) !== null && _b !== void 0 ? _b : OPENFORT_DEPOSIT_PAGE_URL;
60
- const srcChainId = caipToChainId((_c = route.activeChain) === null || _c === void 0 ? void 0 : _c.id);
49
+ // Solana sources have no numeric chain id and no desktop EVM-extension send, so
50
+ // they route through the deeplink (Phantom) on every platform instead of the
51
+ // wagmi-bridge desktop path.
52
+ const isSolanaSrc = isSolana((_d = (_c = route.activeChain) === null || _c === void 0 ? void 0 : _c.id) !== null && _d !== void 0 ? _d : '');
53
+ const srcChainId = caipToChainId((_e = route.activeChain) === null || _e === void 0 ? void 0 : _e.id);
61
54
  const amountValid = Number.parseFloat(amount) > 0;
55
+ const handleAmountChange = (event) => {
56
+ const raw = sanitizeAmountInput(event.target.value);
57
+ if (raw === '' || /^[0-9]*\.?[0-9]*$/.test(raw)) {
58
+ setPressedPreset(null);
59
+ setAmount(raw);
60
+ }
61
+ };
62
+ const handlePreset = (value) => {
63
+ setPressedPreset(value);
64
+ setAmount(String(value));
65
+ };
62
66
  // Open-dApp deeplinks: prefer backend-provided ones; otherwise build them from
63
67
  // the hosted deposit page URL (if the integrator configured one) carrying the
64
68
  // resolved transfer params. The hosted page sends via the wallet's injected
65
69
  // provider, so no backend wiring is required for the link itself.
66
- const pageUrl = depositPageUrl && route.receiverAddress && route.activeCurrency && srcChainId
70
+ const pageUrl = depositPageUrl && route.receiverAddress && route.activeCurrency && (isSolanaSrc || srcChainId)
67
71
  ? buildDepositPageUrl(depositPageUrl, {
72
+ vm: isSolanaSrc ? 'svm' : 'evm',
68
73
  to: route.receiverAddress,
69
- chainId: srcChainId,
74
+ chainId: isSolanaSrc ? undefined : srcChainId,
70
75
  token: route.activeCurrency.native ? undefined : route.activeCurrency.address,
71
76
  decimals: route.activeCurrency.decimals,
72
77
  symbol: route.activeCurrency.symbol,
73
- chain: (_d = route.activeChain) === null || _d === void 0 ? void 0 : _d.name,
78
+ chain: (_f = route.activeChain) === null || _f === void 0 ? void 0 : _f.name,
74
79
  amount: amountValid ? parseUnits(amount, route.activeCurrency.decimals).toString() : undefined,
75
80
  })
76
81
  : null;
77
- const allDeeplinks = ((_f = (_e = route.pm) === null || _e === void 0 ? void 0 : _e.deeplinks) === null || _f === void 0 ? void 0 : _f.length)
82
+ const allDeeplinks = ((_h = (_g = route.pm) === null || _g === void 0 ? void 0 : _g.deeplinks) === null || _h === void 0 ? void 0 : _h.length)
78
83
  ? route.pm.deeplinks
79
84
  : pageUrl
80
- ? buildOpenDappLinks(pageUrl, (_h = (_g = route.activeChain) === null || _g === void 0 ? void 0 : _g.vmType) !== null && _h !== void 0 ? _h : 'evm')
85
+ ? buildOpenDappLinks(pageUrl, (_k = (_j = route.activeChain) === null || _j === void 0 ? void 0 : _j.vmType) !== null && _k !== void 0 ? _k : 'evm')
81
86
  : [];
82
87
  // Trust's in-app dApp browser was removed on iOS (Apple, 2021) — the link
83
88
  // dead-ends there, so hide it on iOS while keeping it on Android.
@@ -87,7 +92,7 @@ const DepositWallet = () => {
87
92
  }, [route.receiverAddress, route.loading, route.status, deeplinks.length, triggerResize]);
88
93
  if (isDepositFlowActive(route.status))
89
94
  return jsx(DepositProgress, { status: route.status });
90
- return (jsxs(PageContent, { onBack: routes.DEPOSIT, children: [jsx(ModalHeading, { children: "Transfer from wallet" }), jsx(RouteSelectors, { chains: route.chains, chain: route.chain, currency: route.currency, chainLabel: "Supported chain", onChainChange: route.setChain, onCurrencyChange: route.setCurrency }), !route.isAvailable && jsx(ModalBody, { children: "Funding isn't available right now." }), jsx(StepDivider, { children: isMobile ? 'Then open your wallet' : 'Then send from your wallet' }), isMobile ? (jsxs(Fragment, { children: [jsx("input", { value: amount, onChange: (e) => setAmount(sanitizeAmount(e.target.value)), placeholder: `Amount${route.activeCurrency ? ` in ${route.activeCurrency.symbol}` : ''}`, inputMode: "decimal", style: amountInputStyle }), !depositPageUrl && (jsx(ModalBody, { style: { marginTop: 12 }, children: "Use a deposit address below to fund from your wallet." })), route.loading && !route.pm && (jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8, marginTop: 14 }, children: [jsx(Skeleton, { "$h": "44px", "$r": "10px" }), jsx(Skeleton, { "$h": "44px", "$r": "10px" }), jsx(Skeleton, { "$h": "44px", "$r": "10px" })] })), deeplinks.length > 0 && (jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8, marginTop: 14 }, children: deeplinks.map((d) => (jsxs("a", { href: amountValid ? d.url : undefined, "aria-disabled": !amountValid, target: "_blank", rel: "noreferrer", style: {
95
+ return (jsxs(PageContent, { onBack: routes.DEPOSIT, children: [jsx(ModalHeading, { children: "Transfer from wallet" }), jsx(RouteSelectors, { chains: route.chains, chain: route.chain, currency: route.currency, chainLabel: "Supported chain", onChainChange: route.setChain, onCurrencyChange: route.setCurrency }), !route.isAvailable && jsx(ModalBody, { children: "Funding isn't available right now." }), jsxs(Section, { children: [jsx(SectionLabel, { children: "Amount" }), jsxs(AmountCard, { children: [jsx(CurrencySymbol, { children: (_m = (_l = route.activeCurrency) === null || _l === void 0 ? void 0 : _l.symbol) !== null && _m !== void 0 ? _m : '' }), jsx(AmountInput, { value: amount, onChange: handleAmountChange, placeholder: "0.00", inputMode: "decimal", autoComplete: "off" })] }), jsx(PresetList, { children: PRESETS.map((preset) => (jsx(PresetButton, { type: "button", "$active": pressedPreset === preset, onClick: () => handlePreset(preset), children: preset }, preset))) })] }), jsx(StepDivider, { children: "Then select the wallet you want to use" }), isMobile || isSolanaSrc ? (jsxs(Fragment, { children: [!depositPageUrl && (jsx(ModalBody, { style: { marginTop: 12 }, children: "Use a deposit address below to fund from your wallet." })), route.loading && !route.pm && (jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8, marginTop: 14 }, children: [jsx(Skeleton, { "$h": "44px", "$r": "10px" }), jsx(Skeleton, { "$h": "44px", "$r": "10px" }), jsx(Skeleton, { "$h": "44px", "$r": "10px" })] })), deeplinks.length > 0 && (jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8, marginTop: 14 }, children: deeplinks.map((d) => (jsxs("a", { href: amountValid ? d.url : undefined, "aria-disabled": !amountValid, target: "_blank", rel: "noreferrer", style: {
91
96
  ...walletListBtn,
92
97
  display: 'flex',
93
98
  alignItems: 'center',
@@ -95,7 +100,7 @@ const DepositWallet = () => {
95
100
  gap: 8,
96
101
  opacity: amountValid ? 1 : 0.55,
97
102
  pointerEvents: amountValid ? 'auto' : 'none',
98
- }, children: [WALLET_LOGO[d.app] && jsx(ButtonLogo, { children: WALLET_LOGO[d.app] }), d.label, " \u2197"] }, d.app))) }))] })) : (jsx(DepositWalletDesktop, { receiverAddress: route.receiverAddress, activeChain: route.activeChain, activeCurrency: route.activeCurrency, loading: route.loading })), jsx(AddressPageLink, { label: "Or send to a deposit address" }), route.error && jsx(ModalBody, { style: { color: '#dc2626', marginTop: 12 }, children: route.error.message })] }));
103
+ }, children: [WALLET_LOGO[d.app] && jsx(ButtonLogo, { children: WALLET_LOGO[d.app] }), d.label, " \u2197"] }, d.app))) }))] })) : (jsx(DepositWalletDesktop, { receiverAddress: route.receiverAddress, activeChain: route.activeChain, activeCurrency: route.activeCurrency, loading: route.loading, amount: amount })), jsx(AddressPageLink, { label: "Or send to a deposit address" }), route.error && jsx(ModalBody, { style: { color: '#dc2626', marginTop: 12 }, children: route.error.message })] }));
99
104
  };
100
105
 
101
106
  export { DepositWallet as default };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -23,11 +23,13 @@ export type VmType = 'evm' | 'svm';
23
23
  */
24
24
  export declare const OPENFORT_DEPOSIT_PAGE_URL = "https://deposit.openfort.io";
25
25
  type DepositPageParams = {
26
- /** Relay deposit address the source funds go to. */
26
+ /** Source VM family. `evm` drives a window.ethereum send; `svm` a Solana Pay request. */
27
+ vm: VmType;
28
+ /** Relay deposit address the source funds go to (0x for EVM, base58 for Solana). */
27
29
  to: string;
28
- /** Numeric source chain id (e.g. 42161). */
29
- chainId: number;
30
- /** ERC-20 contract, or empty/undefined for the chain's native token. */
30
+ /** Numeric source chain id (e.g. 42161). EVM only. */
31
+ chainId?: number;
32
+ /** ERC-20 contract / SPL mint, or empty/undefined for the chain's native token. */
31
33
  token?: string;
32
34
  decimals: number;
33
35
  symbol: string;
@@ -19,8 +19,10 @@ const OPENFORT_DEPOSIT_PAGE_URL = 'https://deposit.openfort.io';
19
19
  /** Build the deposit "send" page URL with the transfer params encoded. */
20
20
  function buildDepositPageUrl(baseUrl, p) {
21
21
  const url = new URL(baseUrl);
22
+ url.searchParams.set('vm', p.vm);
22
23
  url.searchParams.set('to', p.to);
23
- url.searchParams.set('chainId', String(p.chainId));
24
+ if (p.vm === 'evm' && p.chainId !== undefined)
25
+ url.searchParams.set('chainId', String(p.chainId));
24
26
  if (p.token)
25
27
  url.searchParams.set('token', p.token);
26
28
  url.searchParams.set('decimals', String(p.decimals));
@@ -1 +1 @@
1
- {"version":3,"file":"walletDeeplinks.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"walletDeeplinks.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1 @@
1
+ export declare const SolanaSelectToken: () => import("react/jsx-runtime").JSX.Element;
@@ -0,0 +1,50 @@
1
+ import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { useEffect, useState } from 'react';
3
+ import { formatUnits } from 'viem';
4
+ import { TOKEN_LOGO, symbolToColor } from '../../../constants/logos.js';
5
+ import { useSolanaWalletAssets } from '../../../solana/hooks/useSolanaWalletAssets.js';
6
+ import { ModalHeading } from '../../Common/Modal/styles.js';
7
+ import { routes } from '../../Openfort/types.js';
8
+ import { useOpenfort } from '../../Openfort/useOpenfort.js';
9
+ import { SelectTokenContent, EmptyState, TokenList, TokenButton, TokenLeftGroup, TokenInfo, TokenSymbol, TokenName, TokenBalance, TokenLogoArea, TokenLogoImg, TokenLogoFallback } from './styles.js';
10
+
11
+ const ZERO = BigInt(0);
12
+ function SolanaTokenLogo({ symbol }) {
13
+ var _a;
14
+ const [imgError, setImgError] = useState(false);
15
+ const url = (_a = TOKEN_LOGO[symbol.toUpperCase()]) !== null && _a !== void 0 ? _a : null;
16
+ return (jsx(TokenLogoArea, { children: url && !imgError ? (jsx(TokenLogoImg, { src: url, alt: symbol, onError: () => setImgError(true) })) : (jsx(TokenLogoFallback, { "$bg": symbolToColor(symbol), children: symbol.charAt(0).toUpperCase() })) }));
17
+ }
18
+ const SolanaSelectToken = () => {
19
+ const { setSendForm, setRoute, triggerResize } = useOpenfort();
20
+ const { data, isLoading } = useSolanaWalletAssets();
21
+ const tokens = (data !== null && data !== void 0 ? data : []).filter((t) => t.amount > ZERO);
22
+ useEffect(() => {
23
+ if (!isLoading)
24
+ triggerResize();
25
+ }, [isLoading, triggerResize]);
26
+ const handleSelect = (token) => {
27
+ const asset = token.isNative
28
+ ? {
29
+ type: 'native',
30
+ balance: token.amount,
31
+ metadata: { symbol: 'SOL', decimals: token.decimals, fiat: { value: 0, currency: 'USD' } },
32
+ }
33
+ : {
34
+ type: 'spl',
35
+ address: token.mint,
36
+ balance: token.amount,
37
+ metadata: { symbol: token.symbol, name: token.name, decimals: token.decimals },
38
+ };
39
+ setSendForm((prev) => ({ ...prev, asset, amount: '' }));
40
+ setRoute(routes.SOL_SEND);
41
+ };
42
+ return (jsxs(SelectTokenContent, { onBack: routes.SOL_SEND, children: [jsx(ModalHeading, { children: "Select asset" }), tokens.length === 0 ? (jsx(EmptyState, { children: isLoading ? 'Loading balances…' : 'No assets found' })) : (jsx(TokenList, { children: tokens.map((token) => {
43
+ const amount = Number(formatUnits(token.amount, token.decimals));
44
+ const balanceStr = `${amount.toLocaleString('en-US', { maximumFractionDigits: 4 })} ${token.symbol}`;
45
+ return (jsxs(TokenButton, { type: "button", onClick: () => handleSelect(token), children: [jsxs(TokenLeftGroup, { children: [jsx(SolanaTokenLogo, { symbol: token.symbol }), jsxs(TokenInfo, { style: { textAlign: 'left' }, children: [jsx(TokenSymbol, { children: token.name }), jsx(TokenName, { children: token.symbol })] })] }), jsx(TokenBalance, { children: balanceStr })] }, token.mint));
46
+ }) }))] }));
47
+ };
48
+
49
+ export { SolanaSelectToken };
50
+ //# sourceMappingURL=SolanaSelectToken.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SolanaSelectToken.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,11 +1,15 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
+ import { ChainTypeEnum } from '@openfort/openfort-js';
2
3
  import { useState, useEffect } from 'react';
3
4
  import { formatUnits } from 'viem';
4
5
  import { useEthereumWalletAssets } from '../../../ethereum/hooks/useEthereumWalletAssets.js';
6
+ import { useOpenfortCore } from '../../../openfort/useOpenfort.js';
5
7
  import { Arrow, ArrowChevron, TextLinkButton } from '../../Common/Button/styles.js';
6
8
  import { ModalHeading } from '../../Common/Modal/styles.js';
7
9
  import { routes } from '../../Openfort/types.js';
8
10
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
11
+ import { EVM_BUY_CURRENCIES } from '../Buy/evmCurrencies.js';
12
+ import { SOLANA_BUY_CURRENCIES } from '../Buy/solanaCurrencies.js';
9
13
  import { getAssetSymbol, getAssetDecimals, formatBalanceWithSymbol } from '../Send/utils.js';
10
14
  import { SelectTokenContent, EmptyState, TokenList, TokenButton, TokenInfo, TokenSymbol, TokenName, TokenBalance } from './styles.js';
11
15
 
@@ -22,9 +26,16 @@ const SelectToken = ({ isBuyFlow }) => {
22
26
  useEffect(() => {
23
27
  triggerResize();
24
28
  }, [viewAllAssets]);
29
+ const { chainType } = useOpenfortCore();
25
30
  const { data: walletAssets, isLoading: isBalancesLoading } = useEthereumWalletAssets();
26
- // Show all tokens for both buy and send flows
27
- const selectableTokens = walletAssets || [];
31
+ // Buys pick from a fixed buyable-currency list (USDC first, then native) per
32
+ // chain family, so the picker always has options even for a fresh wallet with no
33
+ // indexed balances. The send flow reads the EVM wallet's actual assets.
34
+ const selectableTokens = isBuyFlow
35
+ ? chainType === ChainTypeEnum.SVM
36
+ ? SOLANA_BUY_CURRENCIES
37
+ : EVM_BUY_CURRENCIES
38
+ : walletAssets || [];
28
39
  const handleSelect = (asset) => {
29
40
  var _a;
30
41
  // In send flow, don't allow selecting tokens with 0 balance
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,16 +1,15 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { useMemo } from 'react';
3
3
  import { parseUnits, formatUnits } from 'viem';
4
- import { fetchSolanaBalance } from '../../../hooks/useBalance.js';
5
- import { useAsyncData } from '../../../shared/hooks/useAsyncData.js';
6
- import { useSolanaEmbeddedWallet } from '../../../solana/hooks/useSolanaEmbeddedWallet.js';
4
+ import { useSolanaWalletAssets } from '../../../solana/hooks/useSolanaWalletAssets.js';
7
5
  import Button from '../../Common/Button/index.js';
6
+ import { Arrow, ArrowChevron } from '../../Common/Button/styles.js';
8
7
  import Input from '../../Common/Input/index.js';
9
8
  import { ModalHeading } from '../../Common/Modal/styles.js';
10
9
  import { routes } from '../../Openfort/types.js';
11
10
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
12
11
  import { PageContent } from '../../PageContent/index.js';
13
- import { Form, Field, FieldLabel, AmountInputWrapper, MaxButton, HelperText, ErrorText } from './styles.js';
12
+ import { Form, Field, FieldLabel, TokenSelectorButton, TokenSelectorContent, TokenSelectorValue, TokenSelectorRight, AmountInputWrapper, MaxButton, HelperText, ErrorText } from './styles.js';
14
13
  import { sanitizeForParsing, formatBalance, sanitizeAmountInput } from './utils.js';
15
14
 
16
15
  const SOL_DECIMALS = 9;
@@ -18,39 +17,40 @@ const SOL_DECIMALS = 9;
18
17
  function isLikelySolanaAddress(value) {
19
18
  return /^[1-9A-HJ-NP-Za-km-z]{32,44}$/.test(value);
20
19
  }
20
+ /** A native SOL asset with the canonical metadata (the default form asset has none). */
21
+ function solAsset(balance) {
22
+ return {
23
+ type: 'native',
24
+ balance,
25
+ metadata: { symbol: 'SOL', decimals: SOL_DECIMALS, fiat: { value: 0, currency: 'USD' } },
26
+ };
27
+ }
21
28
  const SolanaSend = () => {
22
- var _a;
23
29
  const { sendForm, setSendForm, setRoute } = useOpenfort();
24
- const wallet = useSolanaEmbeddedWallet();
25
- const address = wallet.status === 'connected' ? wallet.address : undefined;
26
- const rpcUrl = wallet.rpcUrl;
27
- const balanceResult = useAsyncData({
28
- queryKey: ['solana-balance', address, rpcUrl],
29
- queryFn: async () => {
30
- if (!address || !rpcUrl)
31
- return null;
32
- const { value } = await fetchSolanaBalance(address, rpcUrl, 'confirmed');
33
- return value;
34
- },
35
- enabled: Boolean(address && rpcUrl),
36
- });
37
- const balanceLamports = (_a = balanceResult.data) !== null && _a !== void 0 ? _a : undefined;
30
+ const { data: assets } = useSolanaWalletAssets();
31
+ const asset = sendForm.asset;
32
+ const selected = asset.type === 'spl'
33
+ ? { isSpl: true, mint: asset.address, decimals: asset.metadata.decimals, symbol: asset.metadata.symbol }
34
+ : { isSpl: false, mint: 'native', decimals: SOL_DECIMALS, symbol: 'SOL' };
35
+ // Live balance (base units) for the selected token, matched by mint.
36
+ const liveToken = assets === null || assets === void 0 ? void 0 : assets.find((t) => (selected.isSpl ? t.mint === selected.mint : t.isNative));
37
+ const balanceBase = liveToken === null || liveToken === void 0 ? void 0 : liveToken.amount;
38
38
  const parsedAmount = useMemo(() => {
39
39
  const raw = sanitizeForParsing(sendForm.amount);
40
40
  if (!raw)
41
41
  return null;
42
42
  try {
43
- return parseUnits(raw, SOL_DECIMALS);
43
+ return parseUnits(raw, selected.decimals);
44
44
  }
45
45
  catch {
46
46
  return null;
47
47
  }
48
- }, [sendForm.amount]);
48
+ }, [sendForm.amount, selected.decimals]);
49
49
  const recipientValid = isLikelySolanaAddress(sendForm.recipient.trim());
50
- const insufficientBalance = parsedAmount !== null && balanceLamports !== undefined ? parsedAmount > balanceLamports : false;
50
+ const insufficientBalance = parsedAmount !== null && balanceBase !== undefined ? parsedAmount > balanceBase : false;
51
51
  const amountValid = parsedAmount !== null && parsedAmount > BigInt(0) && !insufficientBalance;
52
52
  const canProceed = recipientValid && amountValid;
53
- const availableLabel = formatBalance(balanceLamports, SOL_DECIMALS);
53
+ const availableLabel = formatBalance(balanceBase, selected.decimals);
54
54
  const handleSubmit = (event) => {
55
55
  event.preventDefault();
56
56
  if (!canProceed)
@@ -58,15 +58,15 @@ const SolanaSend = () => {
58
58
  const normalized = sanitizeForParsing(sendForm.amount);
59
59
  if (!normalized)
60
60
  return;
61
+ // Persist the selected token with its live balance so the confirmation reads it.
62
+ const nextAsset = asset.type === 'spl'
63
+ ? { type: 'spl', address: asset.address, balance: balanceBase !== null && balanceBase !== void 0 ? balanceBase : asset.balance, metadata: asset.metadata }
64
+ : solAsset(balanceBase !== null && balanceBase !== void 0 ? balanceBase : BigInt(0));
61
65
  setSendForm((prev) => ({
62
66
  ...prev,
63
67
  recipient: prev.recipient.trim(),
64
68
  amount: normalized,
65
- asset: {
66
- type: 'native',
67
- balance: balanceLamports !== null && balanceLamports !== void 0 ? balanceLamports : BigInt(0),
68
- metadata: { symbol: 'SOL', decimals: SOL_DECIMALS, fiat: { value: 0, currency: 'USD' } },
69
- },
69
+ asset: nextAsset,
70
70
  }));
71
71
  setRoute(routes.SOL_SEND_CONFIRMATION);
72
72
  };
@@ -77,11 +77,12 @@ const SolanaSend = () => {
77
77
  }
78
78
  };
79
79
  const handleMax = () => {
80
- if (balanceLamports === undefined)
80
+ if (balanceBase === undefined)
81
81
  return;
82
- setSendForm((prev) => ({ ...prev, amount: formatUnits(balanceLamports, SOL_DECIMALS) }));
82
+ setSendForm((prev) => ({ ...prev, amount: formatUnits(balanceBase, selected.decimals) }));
83
83
  };
84
- return (jsxs(PageContent, { onBack: routes.SOL_CONNECTED, children: [jsx(ModalHeading, { children: "Send SOL" }), jsxs(Form, { onSubmit: handleSubmit, children: [jsxs(Field, { children: [jsx(FieldLabel, { children: "Amount" }), jsxs(AmountInputWrapper, { children: [jsx(Input, { placeholder: "0.00", value: sendForm.amount, onChange: handleAmountChange, inputMode: "decimal", autoComplete: "off", style: { paddingRight: '86px' } }), jsx(MaxButton, { type: "button", onClick: handleMax, disabled: balanceLamports === undefined, children: "Max" })] }), jsxs(HelperText, { children: ["Available: ", availableLabel, " SOL"] }), sendForm.amount && parsedAmount === null && jsx(ErrorText, { children: "Enter a valid amount." }), insufficientBalance && jsx(ErrorText, { children: "Insufficient SOL balance for this transfer." })] }), jsxs(Field, { children: [jsx(FieldLabel, { children: "Recipient address" }), jsx(Input, { placeholder: "Solana address", value: sendForm.recipient, onChange: (e) => setSendForm((prev) => ({ ...prev, recipient: e.target.value })), autoComplete: "off" }), sendForm.recipient && !recipientValid && jsx(ErrorText, { children: "Enter a valid Solana address." })] }), jsx(Button, { variant: "primary", disabled: !canProceed, children: "Review transfer" })] })] }));
84
+ const handleOpenTokenSelector = () => setRoute(routes.SOL_SEND_TOKEN_SELECT);
85
+ return (jsxs(PageContent, { onBack: routes.SOL_CONNECTED, children: [jsx(ModalHeading, { children: "Send assets" }), jsxs(Form, { onSubmit: handleSubmit, children: [jsxs(Field, { children: [jsx(FieldLabel, { children: "Asset" }), jsxs(TokenSelectorButton, { type: "button", onClick: handleOpenTokenSelector, children: [jsx(TokenSelectorContent, { children: jsx(TokenSelectorValue, { "$primary": true, children: selected.symbol }) }), jsxs(TokenSelectorRight, { children: [jsx(TokenSelectorValue, { children: availableLabel === '--' ? '--' : `${availableLabel} ${selected.symbol}` }), jsx(Arrow, { width: "13", height: "12", viewBox: "0 0 13 12", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: jsx(ArrowChevron, { stroke: "currentColor", d: "M7.51431 1.5L11.757 5.74264M7.5 10.4858L11.7426 6.24314", strokeWidth: "2", strokeLinecap: "round" }) })] })] })] }), jsxs(Field, { children: [jsx(FieldLabel, { children: "Amount" }), jsxs(AmountInputWrapper, { children: [jsx(Input, { placeholder: "0.00", value: sendForm.amount, onChange: handleAmountChange, inputMode: "decimal", autoComplete: "off", style: { paddingRight: '86px' } }), jsx(MaxButton, { type: "button", onClick: handleMax, disabled: balanceBase === undefined, children: "Max" })] }), jsxs(HelperText, { children: ["Available: ", availableLabel, " ", selected.symbol] }), sendForm.amount && parsedAmount === null && jsx(ErrorText, { children: "Enter a valid amount." }), insufficientBalance && jsxs(ErrorText, { children: ["Insufficient ", selected.symbol, " balance for this transfer."] })] }), jsxs(Field, { children: [jsx(FieldLabel, { children: "Recipient address" }), jsx(Input, { placeholder: "Solana address", value: sendForm.recipient, onChange: (e) => setSendForm((prev) => ({ ...prev, recipient: e.target.value })), autoComplete: "off" }), sendForm.recipient && !recipientValid && jsx(ErrorText, { children: "Enter a valid Solana address." })] }), jsx(Button, { variant: "primary", disabled: !canProceed, children: "Review transfer" })] })] }));
85
86
  };
86
87
 
87
88
  export { SolanaSend };
@@ -1 +1 @@
1
- {"version":3,"file":"SolanaSend.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"SolanaSend.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -44,10 +44,13 @@ const isSameToken = (a, b) => {
44
44
  return false;
45
45
  if (a.type === 'native')
46
46
  return true;
47
- // At this point, a.type === 'erc20' and b.type === 'erc20'
48
47
  if (a.type === 'erc20' && b.type === 'erc20') {
49
48
  return a.address.toLowerCase() === b.address.toLowerCase();
50
49
  }
50
+ // SPL mints are base58 and case-sensitive — compare verbatim.
51
+ if (a.type === 'spl' && b.type === 'spl') {
52
+ return a.address === b.address;
53
+ }
51
54
  return false;
52
55
  };
53
56
  const getAssetSymbol = (asset) => {
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"utils.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,9 +1,10 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { ChainTypeEnum } from '@openfort/openfort-js';
3
3
  import { useState, useEffect } from 'react';
4
+ import { parseUnits } from 'viem';
4
5
  import { getExplorerUrl } from '../../../shared/utils/explorer.js';
5
6
  import { useSolanaEmbeddedWallet } from '../../../solana/hooks/useSolanaEmbeddedWallet.js';
6
- import { sendSolGasless, sendSol } from '../../../solana/transfer.js';
7
+ import { sendSplTokenGasless, sendSplToken, sendSolGasless, sendSol } from '../../../solana/transfer.js';
7
8
  import 'detect-browser';
8
9
  import { truncateSolanaAddress } from '../../../utils/format.js';
9
10
  import Button from '../../Common/Button/index.js';
@@ -13,11 +14,12 @@ import { ModalHeading, ModalBody } from '../../Common/Modal/styles.js';
13
14
  import { routes } from '../../Openfort/types.js';
14
15
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
15
16
  import { PageContent } from '../../PageContent/index.js';
16
- import { ButtonRow, SummaryList, SummaryItem, SummaryLabel, AmountValue, AddressValue, GaslessRow, GaslessToggle, ErrorContainer, ErrorTitle, ErrorMessage } from './styles.js';
17
+ import { ButtonRow, SummaryList, SummaryItem, SummaryLabel, AmountValue, AddressValue, FeesValue, ErrorContainer, ErrorTitle, ErrorMessage } from './styles.js';
17
18
 
19
+ const SOL_DECIMALS = 9;
18
20
  const SolanaSendConfirmation = () => {
19
- var _a;
20
- const { sendForm, setRoute, publishableKey, triggerResize } = useOpenfort();
21
+ var _a, _b;
22
+ const { sendForm, setRoute, publishableKey, triggerResize, walletConfig } = useOpenfort();
21
23
  const wallet = useSolanaEmbeddedWallet();
22
24
  const address = wallet.status === 'connected' ? wallet.address : undefined;
23
25
  const provider = wallet.status === 'connected' ? wallet.provider : undefined;
@@ -25,7 +27,11 @@ const SolanaSendConfirmation = () => {
25
27
  const rpcUrl = wallet.rpcUrl;
26
28
  const recipient = sendForm.recipient;
27
29
  const amount = sendForm.amount;
28
- const [gasless, setGasless] = useState(false);
30
+ const asset = sendForm.asset;
31
+ const symbol = asset.type === 'spl' ? asset.metadata.symbol : 'SOL';
32
+ const decimals = asset.type === 'spl' ? asset.metadata.decimals : SOL_DECIMALS;
33
+ // Fees are sponsored from config (the SVM counterpart of ethereumFeeSponsorshipId).
34
+ const isSponsored = Boolean((_b = walletConfig === null || walletConfig === void 0 ? void 0 : walletConfig.solana) === null || _b === void 0 ? void 0 : _b.sponsorFees);
29
35
  const [isLoading, setIsLoading] = useState(false);
30
36
  const [signature, setSignature] = useState(null);
31
37
  const [error, setError] = useState(null);
@@ -37,21 +43,59 @@ const SolanaSendConfirmation = () => {
37
43
  const handleConfirm = async () => {
38
44
  if (!address || !provider || isLoading)
39
45
  return;
40
- const amountSol = Number(amount);
41
- if (!Number.isFinite(amountSol) || amountSol <= 0) {
46
+ // The Send screen validates these before navigating here; re-guard in case
47
+ // this page is reached directly with an unvalidated form.
48
+ if (!recipient.trim()) {
49
+ setError('Enter a recipient address.');
50
+ return;
51
+ }
52
+ const amountNum = Number(amount);
53
+ if (!Number.isFinite(amountNum) || amountNum <= 0) {
42
54
  setError('Enter a valid amount.');
43
55
  return;
44
56
  }
45
- if (!gasless && !rpcUrl) {
57
+ if (!isSponsored && !rpcUrl) {
46
58
  setError('No Solana RPC is configured for this network.');
47
59
  return;
48
60
  }
49
61
  setIsLoading(true);
50
62
  setError(null);
51
63
  try {
52
- const sig = gasless
53
- ? await sendSolGasless({ from: address, to: recipient, amountSol, provider, cluster, publishableKey })
54
- : await sendSol({ from: address, to: recipient, amountSol, provider, rpcUrl: rpcUrl !== null && rpcUrl !== void 0 ? rpcUrl : '' });
64
+ let sig;
65
+ if (asset.type === 'spl') {
66
+ const baseUnits = parseUnits(amount, decimals);
67
+ sig = isSponsored
68
+ ? await sendSplTokenGasless({
69
+ from: address,
70
+ to: recipient,
71
+ mint: asset.address,
72
+ amount: baseUnits,
73
+ provider,
74
+ cluster,
75
+ publishableKey,
76
+ })
77
+ : await sendSplToken({
78
+ from: address,
79
+ to: recipient,
80
+ mint: asset.address,
81
+ amount: baseUnits,
82
+ decimals,
83
+ provider,
84
+ rpcUrl: rpcUrl !== null && rpcUrl !== void 0 ? rpcUrl : '',
85
+ });
86
+ }
87
+ else {
88
+ sig = isSponsored
89
+ ? await sendSolGasless({
90
+ from: address,
91
+ to: recipient,
92
+ amountSol: amountNum,
93
+ provider,
94
+ cluster,
95
+ publishableKey,
96
+ })
97
+ : await sendSol({ from: address, to: recipient, amountSol: amountNum, provider, rpcUrl: rpcUrl !== null && rpcUrl !== void 0 ? rpcUrl : '' });
98
+ }
55
99
  setSignature(sig);
56
100
  }
57
101
  catch (err) {
@@ -68,9 +112,9 @@ const SolanaSendConfirmation = () => {
68
112
  window.open(getExplorerUrl(ChainTypeEnum.SVM, { txHash: signature, cluster }), '_blank', 'noopener,noreferrer');
69
113
  };
70
114
  if (signature) {
71
- return (jsxs(PageContent, { children: [jsx(Loader, { isSuccess: true, header: "Transfer sent", description: `${amount} SOL sent successfully` }), jsxs(ButtonRow, { children: [jsx(Button, { variant: "primary", onClick: handleViewExplorer, children: "View on Explorer" }), jsx(Button, { variant: "secondary", onClick: handleFinish, children: "Back to profile" })] })] }));
115
+ return (jsxs(PageContent, { children: [jsx(Loader, { isSuccess: true, header: "Transfer sent", description: `${amount} ${symbol} sent successfully` }), jsxs(ButtonRow, { children: [jsx(Button, { variant: "primary", onClick: handleViewExplorer, children: "View on Explorer" }), jsx(Button, { variant: "secondary", onClick: handleFinish, children: "Back to profile" })] })] }));
72
116
  }
73
- return (jsxs(PageContent, { onBack: routes.SOL_SEND, children: [jsx(ModalHeading, { children: "Confirm transfer" }), jsx(ModalBody, { children: "Review the transaction details before sending." }), jsxs(SummaryList, { children: [jsxs(SummaryItem, { children: [jsx(SummaryLabel, { children: "Sending" }), jsxs(AmountValue, { children: [amount || '0', " SOL"] })] }), jsxs(SummaryItem, { children: [jsx(SummaryLabel, { children: "From" }), jsx(AddressValue, { children: address ? (jsx(CopyText, { size: "1rem", value: address, children: truncateSolanaAddress(address) })) : ('--') })] }), jsxs(SummaryItem, { children: [jsx(SummaryLabel, { children: "To" }), jsx(AddressValue, { children: recipient ? (jsx(CopyText, { size: "1rem", value: recipient, children: truncateSolanaAddress(recipient) })) : ('--') })] })] }), jsxs(GaslessRow, { children: [jsxs("div", { children: [jsx(SummaryLabel, { children: "Sponsor network fee" }), jsx("div", { style: { fontSize: 12, color: 'var(--ck-body-color-muted)', marginTop: 2 }, children: "Pay no SOL fee (requires a sponsorship policy)" })] }), jsx(GaslessToggle, { type: "button", role: "switch", "aria-checked": gasless, "aria-label": "Sponsor network fee", "$on": gasless, onClick: () => setGasless((v) => !v), disabled: isLoading, children: jsx("span", {}) })] }), error && (jsxs(ErrorContainer, { children: [jsx(ErrorTitle, { children: "Transaction failed" }), jsx(ErrorMessage, { children: error })] })), jsxs(ButtonRow, { children: [jsx(Button, { variant: "primary", onClick: handleConfirm, disabled: !address || isLoading, waiting: isLoading, children: isLoading ? 'Confirming...' : 'Confirm' }), jsx(Button, { variant: "secondary", onClick: () => setRoute(routes.SOL_SEND), disabled: isLoading, children: "Cancel" })] })] }));
117
+ return (jsxs(PageContent, { onBack: routes.SOL_SEND, children: [jsx(ModalHeading, { children: "Confirm transfer" }), jsx(ModalBody, { children: "Review the transaction details before sending." }), jsxs(SummaryList, { children: [jsxs(SummaryItem, { children: [jsx(SummaryLabel, { children: "Sending" }), jsxs(AmountValue, { children: [amount || '0', " ", symbol] })] }), jsxs(SummaryItem, { children: [jsx(SummaryLabel, { children: "From" }), jsx(AddressValue, { children: address ? (jsx(CopyText, { size: "1rem", value: address, children: truncateSolanaAddress(address) })) : ('--') })] }), jsxs(SummaryItem, { children: [jsx(SummaryLabel, { children: "To" }), jsx(AddressValue, { children: recipient ? (jsx(CopyText, { size: "1rem", value: recipient, children: truncateSolanaAddress(recipient) })) : ('--') })] }), isSponsored && (jsxs(SummaryItem, { children: [jsx(SummaryLabel, { children: "Network fee" }), jsx(FeesValue, { "$completed": true, children: "Sponsored" })] }))] }), error && (jsxs(ErrorContainer, { children: [jsx(ErrorTitle, { children: "Transaction failed" }), jsx(ErrorMessage, { children: error })] })), jsxs(ButtonRow, { children: [jsx(Button, { variant: "primary", onClick: handleConfirm, disabled: !address || isLoading, waiting: isLoading, children: isLoading ? 'Confirming...' : 'Confirm' }), jsx(Button, { variant: "secondary", onClick: () => setRoute(routes.SOL_SEND), disabled: isLoading, children: "Cancel" })] })] }));
74
118
  };
75
119
 
76
120
  export { SolanaSendConfirmation };
@@ -1 +1 @@
1
- {"version":3,"file":"SolanaSendConfirmation.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"SolanaSendConfirmation.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -16,8 +16,3 @@ export declare const ErrorContainer: import("styled-components").StyledComponent
16
16
  export declare const ErrorTitle: import("styled-components").StyledComponent<"div", any, {}, never>;
17
17
  export declare const ErrorMessage: import("styled-components").StyledComponent<"div", any, {}, never>;
18
18
  export declare const ErrorAction: import("styled-components").StyledComponent<"div", any, {}, never>;
19
- export declare const GaslessRow: import("styled-components").StyledComponent<"div", any, {}, never>;
20
- /** A minimal theme-aware switch for the Solana "sponsor fee" toggle. */
21
- export declare const GaslessToggle: import("styled-components").StyledComponent<"button", any, {
22
- $on?: boolean;
23
- }, never>;
@@ -124,44 +124,6 @@ const ErrorAction = styled.div `
124
124
  color: var(--ck-body-color-muted);
125
125
  line-height: 1.4;
126
126
  `;
127
- const GaslessRow = styled.div `
128
- display: flex;
129
- align-items: center;
130
- justify-content: space-between;
131
- gap: 12px;
132
- margin: 4px 0 8px;
133
- text-align: left;
134
- `;
135
- /** A minimal theme-aware switch for the Solana "sponsor fee" toggle. */
136
- const GaslessToggle = styled.button `
137
- position: relative;
138
- flex-shrink: 0;
139
- width: 40px;
140
- height: 24px;
141
- border-radius: 999px;
142
- border: 1px solid var(--ck-body-divider);
143
- background: ${(props) => (props.$on ? 'var(--ck-body-color-valid, #22c55e)' : 'var(--ck-body-background-secondary)')};
144
- cursor: pointer;
145
- transition: background 150ms ease;
146
- padding: 0;
147
-
148
- &:disabled {
149
- opacity: 0.5;
150
- cursor: not-allowed;
151
- }
152
-
153
- > span {
154
- position: absolute;
155
- top: 50%;
156
- left: ${(props) => (props.$on ? '18px' : '2px')};
157
- transform: translateY(-50%);
158
- width: 18px;
159
- height: 18px;
160
- border-radius: 50%;
161
- background: #fff;
162
- transition: left 150ms ease;
163
- }
164
- `;
165
127
 
166
- export { AddressValue, AmountValue, ButtonRow, CheckIconWrapper, ErrorAction, ErrorContainer, ErrorMessage, ErrorTitle, FeesValue, GaslessRow, GaslessToggle, InfoIconWrapper, StatusMessage, SummaryItem, SummaryLabel, SummaryList };
128
+ export { AddressValue, AmountValue, ButtonRow, CheckIconWrapper, ErrorAction, ErrorContainer, ErrorMessage, ErrorTitle, FeesValue, InfoIconWrapper, StatusMessage, SummaryItem, SummaryLabel, SummaryList };
167
129
  //# sourceMappingURL=styles.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"styles.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"styles.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}