@openfort/react 1.6.0 → 1.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/build/components/Common/Modal/index.js +14 -0
  2. package/build/components/Common/Modal/index.js.map +1 -1
  3. package/build/components/Openfort/types.d.ts +1 -1
  4. package/build/components/Pages/AssetInventory/SolanaAssetInventory.js +7 -7
  5. package/build/components/Pages/DepositWallet/index.js +15 -10
  6. package/build/components/Pages/DepositWallet/index.js.map +1 -1
  7. package/build/components/Pages/ExportKey/index.js +44 -4
  8. package/build/components/Pages/ExportKey/index.js.map +1 -1
  9. package/build/components/Pages/ExportKey/styles.d.ts +5 -0
  10. package/build/components/Pages/ExportKey/styles.js +47 -0
  11. package/build/components/Pages/ExportKey/styles.js.map +1 -0
  12. package/build/components/Pages/Receive/index.js +2 -2
  13. package/build/components/Pages/SelectToken/SolanaSelectToken.js +7 -7
  14. package/build/components/Pages/SelectToken/index.js +7 -3
  15. package/build/components/Pages/SelectToken/index.js.map +1 -1
  16. package/build/components/Pages/Send/EthereumSend.js +25 -5
  17. package/build/components/Pages/Send/EthereumSend.js.map +1 -1
  18. package/build/components/Pages/Send/SolanaSend.js +23 -4
  19. package/build/components/Pages/Send/SolanaSend.js.map +1 -1
  20. package/build/components/Pages/Send/styles.d.ts +17 -11
  21. package/build/components/Pages/Send/styles.js +104 -49
  22. package/build/components/Pages/Send/styles.js.map +1 -1
  23. package/build/components/Pages/SendConfirmation/ConfirmationSummary.d.ts +3 -1
  24. package/build/components/Pages/SendConfirmation/ConfirmationSummary.js +2 -2
  25. package/build/components/Pages/SendConfirmation/EstimatedFees.d.ts +3 -1
  26. package/build/components/Pages/SendConfirmation/EstimatedFees.js +22 -15
  27. package/build/components/Pages/SendConfirmation/EstimatedFees.js.map +1 -1
  28. package/build/components/Pages/SendConfirmation/SolanaSendConfirmation.js +17 -5
  29. package/build/components/Pages/SendConfirmation/SolanaSendConfirmation.js.map +1 -1
  30. package/build/components/Pages/SendConfirmation/index.js +11 -5
  31. package/build/components/Pages/SendConfirmation/index.js.map +1 -1
  32. package/build/components/Pages/SendConfirmation/styles.d.ts +3 -1
  33. package/build/components/Pages/SendConfirmation/styles.js +20 -6
  34. package/build/components/Pages/SendConfirmation/styles.js.map +1 -1
  35. package/build/components/Pages/SignMessage/index.js +44 -19
  36. package/build/components/Pages/SignMessage/index.js.map +1 -1
  37. package/build/components/Pages/SignMessage/styles.d.ts +2 -3
  38. package/build/components/Pages/SignMessage/styles.js +19 -2
  39. package/build/components/Pages/SignMessage/styles.js.map +1 -1
  40. package/build/hooks/openfort/useSignMessage.d.ts +2 -2
  41. package/build/hooks/openfort/useUI.js +4 -2
  42. package/build/hooks/openfort/useUI.js.map +1 -1
  43. package/build/solana/transfer.d.ts +12 -0
  44. package/build/solana/transfer.js +29 -1
  45. package/build/solana/transfer.js.map +1 -1
  46. package/build/version.d.ts +1 -1
  47. package/build/version.js +1 -1
  48. package/package.json +1 -1
@@ -2,6 +2,7 @@ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
2
2
  import { AnimatePresence, motion } from 'framer-motion';
3
3
  import { useEffect, useState, useCallback, useRef } from 'react';
4
4
  import { useTransition } from 'react-transition-state';
5
+ import ResizeObserver from 'resize-observer-polyfill';
5
6
  import { useConnectionStrategy } from '../../../core/ConnectionStrategyContext.js';
6
7
  import { useEthereumBridge } from '../../../ethereum/OpenfortEthereumBridgeContext.js';
7
8
  import FocusTrap from '../../../hooks/useFocusTrap.js';
@@ -107,6 +108,7 @@ onInfo, }) => {
107
108
  };
108
109
  let blockTimeout;
109
110
  const contentRef = useCallback((node) => {
111
+ var _a;
110
112
  if (!node)
111
113
  return;
112
114
  ref.current = node;
@@ -116,6 +118,16 @@ onInfo, }) => {
116
118
  blockTimeout = setTimeout(() => setInTransition(false), 360);
117
119
  // Calculate new content bounds
118
120
  updateBounds(node);
121
+ // Auto-fit: re-measure whenever the active page's content size changes, so
122
+ // every page — and any new one — sizes the modal correctly without having
123
+ // to call triggerResize() itself.
124
+ (_a = resizeObserverRef.current) === null || _a === void 0 ? void 0 : _a.disconnect();
125
+ const observer = new ResizeObserver(() => {
126
+ if (ref.current === node)
127
+ updateBounds(node);
128
+ });
129
+ observer.observe(node);
130
+ resizeObserverRef.current = observer;
119
131
  }, [open, inTransition]);
120
132
  // Update layout on chain/network switch to avoid clipping
121
133
  const strategy = useConnectionStrategy();
@@ -123,10 +135,12 @@ onInfo, }) => {
123
135
  const chainId = (_j = (_f = strategy === null || strategy === void 0 ? void 0 : strategy.getChainId()) !== null && _f !== void 0 ? _f : (_h = (_g = bridge === null || bridge === void 0 ? void 0 : bridge.account) === null || _g === void 0 ? void 0 : _g.chain) === null || _h === void 0 ? void 0 : _h.id) !== null && _j !== void 0 ? _j : bridge === null || bridge === void 0 ? void 0 : bridge.chainId;
124
136
  const switchChain = (_k = bridge === null || bridge === void 0 ? void 0 : bridge.switchChain) === null || _k === void 0 ? void 0 : _k.switchChain;
125
137
  const ref = useRef(null);
138
+ const resizeObserverRef = useRef(null);
126
139
  useEffect(() => {
127
140
  if (ref.current)
128
141
  updateBounds(ref.current);
129
142
  }, [chainId, switchChain, mobile, context.uiConfig, context.resize]);
143
+ useEffect(() => () => { var _a; return (_a = resizeObserverRef.current) === null || _a === void 0 ? void 0 : _a.disconnect(); }, []);
130
144
  useEffect(() => {
131
145
  if (!mounted) {
132
146
  setDimensions({
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -492,7 +492,7 @@ export type SignRequest = ({
492
492
  kind: 'typedData';
493
493
  typedData: SignTypedDataPayload;
494
494
  }) & {
495
- resolve: (signature: `0x${string}`) => void;
495
+ resolve: (signature: string) => void;
496
496
  reject: (reason?: unknown) => void;
497
497
  };
498
498
  export type BuyProviderId = 'moonpay' | 'coinbase' | 'stripe';
@@ -1,19 +1,19 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { useEffect, useState } from 'react';
2
+ import { useEffect } from 'react';
3
3
  import { formatUnits } from 'viem';
4
- import { TOKEN_LOGO, symbolToColor } from '../../../constants/logos.js';
4
+ import { currencyLogoUrl } from '../../../constants/logos.js';
5
5
  import { useSolanaWalletAssets } from '../../../solana/hooks/useSolanaWalletAssets.js';
6
6
  import { ModalHeading } from '../../Common/Modal/styles.js';
7
7
  import { routes } from '../../Openfort/types.js';
8
8
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
9
- import { SelectTokenContent, EmptyState, ContentWrapper, TokenList, TokenContainer, TokenLeftGroup, TokenInfo, TokenSymbol, TokenName, TokenLogoArea, TokenLogoImg, TokenLogoFallback } from '../SelectToken/styles.js';
9
+ import { AssetChainLogo } from '../Deposit/AssetChainLogo.js';
10
+ import { SelectTokenContent, EmptyState, ContentWrapper, TokenList, TokenContainer, TokenLeftGroup, TokenInfo, TokenSymbol, TokenName, TokenLogoArea } from '../SelectToken/styles.js';
10
11
 
11
12
  const ZERO = BigInt(0);
13
+ /** Token logo with the Solana chain badge, matching the EVM inventory. */
12
14
  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() })) }));
15
+ var _a, _b;
16
+ return (jsx(TokenLogoArea, { children: jsx(AssetChainLogo, { assetLogo: (_a = currencyLogoUrl(symbol)) !== null && _a !== void 0 ? _a : '', chainLogo: (_b = currencyLogoUrl('SOL')) !== null && _b !== void 0 ? _b : '', symbol: symbol }) }));
17
17
  }
18
18
  /**
19
19
  * SVM counterpart of {@link AssetInventory}: lists the connected Solana wallet's
@@ -6,6 +6,7 @@ import { useEthereumBridge } from '../../../ethereum/OpenfortEthereumBridgeConte
6
6
  import useIsMobile from '../../../hooks/useIsMobile.js';
7
7
  import styled from '../../../styles/styled/index.js';
8
8
  import { isIOS } from '../../../utils/index.js';
9
+ import { TextLinkButton } from '../../Common/Button/styles.js';
9
10
  import { ModalHeading, ModalBody } from '../../Common/Modal/styles.js';
10
11
  import { ScrollArea } from '../../Common/ScrollArea/index.js';
11
12
  import { routes } from '../../Openfort/types.js';
@@ -83,6 +84,8 @@ const DepositWallet = () => {
83
84
  // (the funding deposit-address mint uses a fixed nominal amount regardless).
84
85
  const [amount, setAmount] = useState('1');
85
86
  const [pressedPreset, setPressedPreset] = useState(null);
87
+ // Collapse the wallet list to keep the picker short on mobile; "Show more" reveals the rest.
88
+ const [showAllWallets, setShowAllWallets] = useState(false);
86
89
  const depositPageUrl = (_b = (_a = uiConfig.funding) === null || _a === void 0 ? void 0 : _a.depositPageUrl) !== null && _b !== void 0 ? _b : OPENFORT_DEPOSIT_PAGE_URL;
87
90
  // Solana sources have no numeric chain id and no desktop EVM-extension send, so
88
91
  // they route through the deeplink (Phantom) on every platform instead of the
@@ -125,20 +128,22 @@ const DepositWallet = () => {
125
128
  // Trust's in-app dApp browser was removed on iOS (Apple, 2021) — the link
126
129
  // dead-ends there, so hide it on iOS while keeping it on Android.
127
130
  const deeplinks = isIOS() ? allDeeplinks.filter((d) => d.app !== 'trust') : allDeeplinks;
131
+ const WALLET_LIMIT = 3;
132
+ const visibleDeeplinks = showAllWallets ? deeplinks : deeplinks.slice(0, WALLET_LIMIT);
128
133
  useEffect(() => {
129
134
  triggerResize();
130
- }, [route.receiverAddress, route.loading, route.status, deeplinks.length, triggerResize]);
135
+ }, [route.receiverAddress, route.loading, route.status, deeplinks.length, showAllWallets, triggerResize]);
131
136
  if (isDepositFlowActive(route.status))
132
137
  return jsx(DepositProgress, { status: route.status });
133
- return (jsxs(PageContent, { onBack: routes.DEPOSIT, children: [jsx(ModalHeading, { children: "Transfer from wallet" }), jsx(TestnetNotice, {}), route.targetUnsupported && (jsx(UnsupportedNetworkNotice, { targetChain: route.target.chain, railChains: route.railChains })), !route.targetUnsupported && route.accountUnusableOnTarget && (jsx(AccountChainNotice, { targetChain: route.target.chain })), !route.targetUnsupported && !route.accountUnusableOnTarget && (jsxs(Layout, { children: [jsxs(TopFixed, { children: [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" })] }), jsx(ProvidersRegion, { children: isMobile || isSolanaSrc || !bridge ? (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 }, children: [jsx(Skeleton, { "$h": "44px", "$r": "10px" }), jsx(Skeleton, { "$h": "44px", "$r": "10px" }), jsx(Skeleton, { "$h": "44px", "$r": "10px" })] })), deeplinks.length > 0 && (jsx(ScrollArea, { fill: true, children: jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: deeplinks.map((d) => (jsxs("a", { href: amountValid ? d.url : undefined, "aria-disabled": !amountValid, target: "_blank", rel: "noreferrer", style: {
134
- ...walletListBtn,
135
- display: 'flex',
136
- alignItems: 'center',
137
- justifyContent: 'center',
138
- gap: 8,
139
- opacity: amountValid ? 1 : 0.55,
140
- pointerEvents: amountValid ? 'auto' : 'none',
141
- }, 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 })) }), jsxs(FixedFooter, { children: [jsx(AddressPageLink, { label: "Or send to a deposit address" }), route.error && jsx(ModalBody, { style: { color: '#dc2626', marginTop: 12 }, children: route.error.message })] })] }))] }));
138
+ return (jsxs(PageContent, { onBack: routes.DEPOSIT, children: [jsx(ModalHeading, { children: "Transfer from wallet" }), jsx(TestnetNotice, {}), route.targetUnsupported && (jsx(UnsupportedNetworkNotice, { targetChain: route.target.chain, railChains: route.railChains })), !route.targetUnsupported && route.accountUnusableOnTarget && (jsx(AccountChainNotice, { targetChain: route.target.chain })), !route.targetUnsupported && !route.accountUnusableOnTarget && (jsxs(Layout, { children: [jsxs(TopFixed, { children: [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" })] }), jsx(ProvidersRegion, { children: isMobile || isSolanaSrc || !bridge ? (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 }, children: [jsx(Skeleton, { "$h": "44px", "$r": "10px" }), jsx(Skeleton, { "$h": "44px", "$r": "10px" }), jsx(Skeleton, { "$h": "44px", "$r": "10px" })] })), deeplinks.length > 0 && (jsx(ScrollArea, { fill: true, children: jsxs("div", { style: { display: 'flex', flexDirection: 'column', gap: 8 }, children: [visibleDeeplinks.map((d) => (jsxs("a", { href: amountValid ? d.url : undefined, "aria-disabled": !amountValid, target: "_blank", rel: "noreferrer", style: {
139
+ ...walletListBtn,
140
+ display: 'flex',
141
+ alignItems: 'center',
142
+ justifyContent: 'center',
143
+ gap: 8,
144
+ opacity: amountValid ? 1 : 0.55,
145
+ pointerEvents: amountValid ? 'auto' : 'none',
146
+ }, children: [WALLET_LOGO[d.app] && jsx(ButtonLogo, { children: WALLET_LOGO[d.app] }), d.label, " \u2197"] }, d.app))), deeplinks.length > WALLET_LIMIT && (jsx(TextLinkButton, { type: "button", onClick: () => setShowAllWallets((v) => !v), style: { alignSelf: 'center', marginTop: 2 }, children: showAllWallets ? 'Show less' : `Show ${deeplinks.length - WALLET_LIMIT} more` }))] }) }))] })) : (jsx(DepositWalletDesktop, { receiverAddress: route.receiverAddress, activeChain: route.activeChain, activeCurrency: route.activeCurrency, loading: route.loading, amount: amount })) }), jsxs(FixedFooter, { children: [jsx(AddressPageLink, { label: "Or send to a deposit address" }), route.error && jsx(ModalBody, { style: { color: '#dc2626', marginTop: 12 }, children: route.error.message })] })] }))] }));
142
147
  };
143
148
 
144
149
  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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,14 +1,16 @@
1
1
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
- import { useState, useEffect } from 'react';
2
+ import { useState, useRef, useCallback, useEffect } from 'react';
3
3
  import { KeyIcon } from '../../../assets/icons.js';
4
4
  import { useEthereumEmbeddedWallet } from '../../../ethereum/hooks/useEthereumEmbeddedWallet.js';
5
- import Button from '../../Common/Button/index.js';
6
- import { CopyText } from '../../Common/CopyToClipboard/CopyText.js';
5
+ import { CopyIconButton } from '../../Common/CopyToClipboard/CopyIconButton.js';
7
6
  import { ModalHeading, ModalContent, ModalBody } from '../../Common/Modal/styles.js';
8
7
  import { FloatingGraphic } from '../../FloatingGraphic/index.js';
9
8
  import { PageContent } from '../../PageContent/index.js';
9
+ import { Label, AddressRow, AddressField } from '../Receive/styles.js';
10
+ import { HoldButton, HoldFill, HoldLabel, KeyReveal } from './styles.js';
10
11
 
11
12
  // TODO: Localize
13
+ const HOLD_MS = 5000;
12
14
  const ExportKey = () => {
13
15
  var _a, _b;
14
16
  const wallet = useEthereumEmbeddedWallet();
@@ -22,6 +24,42 @@ const ExportKey = () => {
22
24
  const [exportedKey, setExportedKey] = useState(null);
23
25
  const [exportError, setExportError] = useState(null);
24
26
  const [showExportedKey, setShowExportedKey] = useState(false);
27
+ // Press-and-hold gate: the key only reveals after a deliberate 5s hold.
28
+ const [progress, setProgress] = useState(0);
29
+ const holdingRef = useRef(false);
30
+ const rafRef = useRef(null);
31
+ const startRef = useRef(0);
32
+ const stopHold = useCallback(() => {
33
+ holdingRef.current = false;
34
+ if (rafRef.current !== null) {
35
+ cancelAnimationFrame(rafRef.current);
36
+ rafRef.current = null;
37
+ }
38
+ setProgress((p) => (p >= 1 ? p : 0));
39
+ }, []);
40
+ const startHold = useCallback(() => {
41
+ if (showExportedKey || holdingRef.current)
42
+ return;
43
+ holdingRef.current = true;
44
+ startRef.current = performance.now();
45
+ const tick = (now) => {
46
+ if (!holdingRef.current)
47
+ return;
48
+ const p = Math.min(1, (now - startRef.current) / HOLD_MS);
49
+ setProgress(p);
50
+ if (p >= 1) {
51
+ holdingRef.current = false;
52
+ setShowExportedKey(true);
53
+ return;
54
+ }
55
+ rafRef.current = requestAnimationFrame(tick);
56
+ };
57
+ rafRef.current = requestAnimationFrame(tick);
58
+ }, [showExportedKey]);
59
+ useEffect(() => () => {
60
+ if (rafRef.current !== null)
61
+ cancelAnimationFrame(rafRef.current);
62
+ }, []);
25
63
  useEffect(() => {
26
64
  const asyncExportKey = async () => {
27
65
  try {
@@ -45,7 +83,9 @@ const ExportKey = () => {
45
83
  logo: jsx(KeyIcon, {}),
46
84
  }, logoBottomLeft: {
47
85
  logo: jsx(KeyIcon, {}),
48
- } }), jsxs(ModalContent, { children: [jsxs(ModalBody, { children: [isSmartAccount ? (jsxs("p", { style: { marginBottom: 6 }, children: ["This is your account's ", jsx("strong", { children: "owner (signer) key" }), " \u2014 not the account itself. Your smart account", accountAddress ? ` (${accountAddress.slice(0, 6)}…${accountAddress.slice(-4)})` : '', " is a contract with no private key, so importing this key into another wallet shows the owner address,", ' ', jsx("strong", { children: "not your funds" }), ". To move funds, use Send to withdraw to another wallet."] })) : (jsx("p", { style: { marginBottom: 6 }, children: "With your private key, you can access your account outside this application." })), jsx("p", { children: "Keep it safe and never share it with anyone you don't trust." })] }), !showExportedKey ? (jsx(Button, { onClick: () => setShowExportedKey(true), style: { marginTop: 12 }, children: "Export key" })) : exportError ? (jsx(ModalBody, { style: { marginTop: 12 }, "$error": true, children: exportError })) : exportedKey ? (jsx("div", { style: { marginTop: 12 }, children: jsxs(CopyText, { value: exportedKey, children: [exportedKey.slice(0, 10), "...", exportedKey.slice(-10)] }) })) : (jsx(Fragment, { children: "Loading..." }))] })] }));
86
+ } }), jsxs(ModalContent, { children: [jsxs(ModalBody, { children: [isSmartAccount ? (jsxs("p", { style: { marginBottom: 6 }, children: ["This is your account's ", jsx("strong", { children: "owner (signer) key" }), " \u2014 not the account itself. Your smart account", accountAddress ? ` (${accountAddress.slice(0, 6)}…${accountAddress.slice(-4)})` : '', " is a contract with no private key, so importing this key into another wallet shows the owner address,", ' ', jsx("strong", { children: "not your funds" }), ". To move funds, use Send to withdraw to another wallet."] })) : (jsx("p", { style: { marginBottom: 6 }, children: "With your private key, you can access your account outside this application." })), jsx("p", { children: "Keep it safe and never share it with anyone you don't trust." })] }), !showExportedKey ? (jsxs(HoldButton, { type: "button", onPointerDown: startHold, onPointerUp: stopHold, onPointerLeave: stopHold, onPointerCancel: stopHold, style: { marginTop: 12 }, children: [jsx(HoldFill, { style: { width: `${progress * 100}%` } }), jsx(HoldLabel, { children: progress > 0
87
+ ? `Hold to reveal… ${Math.ceil((1 - progress) * (HOLD_MS / 1000))}s`
88
+ : 'Hold 5s to reveal key' })] })) : exportError ? (jsx(ModalBody, { style: { marginTop: 12 }, "$error": true, children: exportError })) : exportedKey ? (jsxs(KeyReveal, { children: [jsx(Label, { children: "Your private key" }), jsxs(AddressRow, { children: [jsx(AddressField, { style: { fontFamily: 'ui-monospace, SFMono-Regular, Menlo, monospace' }, children: exportedKey }), jsx(CopyIconButton, { value: exportedKey })] })] })) : (jsx(Fragment, { children: "Loading..." }))] })] }));
49
89
  };
50
90
 
51
91
  export { ExportKey as default };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,5 @@
1
+ /** Press-and-hold reveal button: a fill grows left→right over the hold duration. */
2
+ export declare const HoldButton: import("styled-components").StyledComponent<"button", any, {}, never>;
3
+ export declare const HoldFill: import("styled-components").StyledComponent<"div", any, {}, never>;
4
+ export declare const HoldLabel: import("styled-components").StyledComponent<"span", any, {}, never>;
5
+ export declare const KeyReveal: import("styled-components").StyledComponent<"div", any, {}, never>;
@@ -0,0 +1,47 @@
1
+ import styled from '../../../styles/styled/index.js';
2
+
3
+ /** Press-and-hold reveal button: a fill grows left→right over the hold duration. */
4
+ const HoldButton = styled.button `
5
+ position: relative;
6
+ overflow: hidden;
7
+ width: 100%;
8
+ padding: 14px 16px;
9
+ border-radius: var(--ck-primary-button-border-radius, 16px);
10
+ border: 1px solid var(--ck-body-divider);
11
+ background: var(--ck-secondary-button-background);
12
+ color: var(--ck-body-color);
13
+ font-size: 16px;
14
+ font-weight: 600;
15
+ cursor: pointer;
16
+ user-select: none;
17
+ -webkit-user-select: none;
18
+ touch-action: none;
19
+ transition: border-color 150ms ease;
20
+
21
+ &:hover {
22
+ border-color: var(--ck-body-color-muted);
23
+ }
24
+ `;
25
+ const HoldFill = styled.div `
26
+ position: absolute;
27
+ top: 0;
28
+ left: 0;
29
+ bottom: 0;
30
+ background: var(--ck-focus-color, #1a88f8);
31
+ opacity: 0.22;
32
+ pointer-events: none;
33
+ `;
34
+ const HoldLabel = styled.span `
35
+ position: relative;
36
+ z-index: 1;
37
+ `;
38
+ const KeyReveal = styled.div `
39
+ display: flex;
40
+ flex-direction: column;
41
+ gap: 10px;
42
+ margin-top: 12px;
43
+ text-align: left;
44
+ `;
45
+
46
+ export { HoldButton, HoldFill, HoldLabel, KeyReveal };
47
+ //# sourceMappingURL=styles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"styles.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -8,7 +8,7 @@ import { useOpenfortCore } from '../../../openfort/useOpenfort.js';
8
8
  import { useSolanaEmbeddedWallet } from '../../../solana/hooks/useSolanaEmbeddedWallet.js';
9
9
  import { CopyIconButton } from '../../Common/CopyToClipboard/CopyIconButton.js';
10
10
  import CustomQRCode from '../../Common/CustomQRCode/index.js';
11
- import { ModalHeading, ModalBody } from '../../Common/Modal/styles.js';
11
+ import { ModalHeading } from '../../Common/Modal/styles.js';
12
12
  import { routes } from '../../Openfort/types.js';
13
13
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
14
14
  import { PageContent } from '../../PageContent/index.js';
@@ -63,7 +63,7 @@ const Receive = () => {
63
63
  const timer = setTimeout(() => context.triggerResize(), 100);
64
64
  return () => clearTimeout(timer);
65
65
  }, [address, context]);
66
- return (jsx(PageContent, { onBack: isSolanaRoute ? routes.SOL_CONNECTED : routes.CONNECTED, children: jsxs(ReceiveContent, { children: [jsx(ModalHeading, { children: "Receive funds" }), jsx(ModalBody, { children: "Scan the QR code or copy your wallet details." }), address && (jsx(QRWrapper, { children: jsx(CustomQRCode, { value: qrValue, image: jsx("div", { style: { padding: 10 }, children: renderLogo() }) }) })), jsxs(AddressSection, { children: [jsx(Label, { children: "Your wallet address" }), jsxs(AddressRow, { children: [jsx(AddressField, { children: address !== null && address !== void 0 ? address : '--' }), jsx(CopyIconButton, { value: address !== null && address !== void 0 ? address : '' })] })] }), networkLabel && jsxs(NetworkInfo, { children: ["Network: ", networkLabel] })] }) }));
66
+ return (jsx(PageContent, { onBack: isSolanaRoute ? routes.SOL_CONNECTED : routes.CONNECTED, children: jsxs(ReceiveContent, { children: [jsx(ModalHeading, { children: "Receive money" }), address && (jsx(QRWrapper, { children: jsx(CustomQRCode, { value: qrValue, image: jsx("div", { style: { padding: 10 }, children: renderLogo() }) }) })), jsxs(AddressSection, { children: [jsx(Label, { children: "Your wallet address" }), jsxs(AddressRow, { children: [jsx(AddressField, { children: address !== null && address !== void 0 ? address : '--' }), jsx(CopyIconButton, { value: address !== null && address !== void 0 ? address : '' })] })] }), networkLabel && jsxs(NetworkInfo, { children: ["Network: ", networkLabel] })] }) }));
67
67
  };
68
68
 
69
69
  export { Receive as default };
@@ -1,19 +1,19 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { useEffect, useState } from 'react';
2
+ import { useEffect } from 'react';
3
3
  import { formatUnits } from 'viem';
4
- import { TOKEN_LOGO, symbolToColor } from '../../../constants/logos.js';
4
+ import { currencyLogoUrl } from '../../../constants/logos.js';
5
5
  import { useSolanaWalletAssets } from '../../../solana/hooks/useSolanaWalletAssets.js';
6
6
  import { ModalHeading } from '../../Common/Modal/styles.js';
7
7
  import { routes } from '../../Openfort/types.js';
8
8
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
9
- import { SelectTokenContent, EmptyState, TokenList, TokenButton, TokenLeftGroup, TokenInfo, TokenSymbol, TokenName, TokenBalance, TokenLogoArea, TokenLogoImg, TokenLogoFallback } from './styles.js';
9
+ import { AssetChainLogo } from '../Deposit/AssetChainLogo.js';
10
+ import { SelectTokenContent, EmptyState, TokenList, TokenButton, TokenLeftGroup, TokenInfo, TokenSymbol, TokenName, TokenBalance, TokenLogoArea } from './styles.js';
10
11
 
11
12
  const ZERO = BigInt(0);
13
+ /** Token logo with the Solana chain badge, matching the EVM picker. */
12
14
  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() })) }));
15
+ var _a, _b;
16
+ return (jsx(TokenLogoArea, { children: jsx(AssetChainLogo, { assetLogo: (_a = currencyLogoUrl(symbol)) !== null && _a !== void 0 ? _a : '', chainLogo: (_b = currencyLogoUrl('SOL')) !== null && _b !== void 0 ? _b : '', symbol: symbol }) }));
17
17
  }
18
18
  const SolanaSelectToken = () => {
19
19
  const { setSendForm, setRoute, triggerResize } = useOpenfort();
@@ -2,6 +2,8 @@ import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { ChainTypeEnum } from '@openfort/openfort-js';
3
3
  import { useState, useEffect } from 'react';
4
4
  import { formatUnits } from 'viem';
5
+ import { currencyLogoUrl, chainLogoUrl } from '../../../constants/logos.js';
6
+ import { useEthereumEmbeddedWallet } from '../../../ethereum/hooks/useEthereumEmbeddedWallet.js';
5
7
  import { useEthereumWalletAssets } from '../../../ethereum/hooks/useEthereumWalletAssets.js';
6
8
  import { useOpenfortCore } from '../../../openfort/useOpenfort.js';
7
9
  import { Arrow, ArrowChevron, TextLinkButton } from '../../Common/Button/styles.js';
@@ -10,8 +12,9 @@ import { routes } from '../../Openfort/types.js';
10
12
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
11
13
  import { EVM_BUY_CURRENCIES } from '../Buy/evmCurrencies.js';
12
14
  import { SOLANA_BUY_CURRENCIES } from '../Buy/solanaCurrencies.js';
15
+ import { AssetChainLogo } from '../Deposit/AssetChainLogo.js';
13
16
  import { getAssetSymbol, getAssetDecimals, formatBalanceWithSymbol } from '../Send/utils.js';
14
- import { SelectTokenContent, EmptyState, TokenList, TokenButton, TokenInfo, TokenSymbol, TokenName, TokenBalance } from './styles.js';
17
+ import { SelectTokenContent, EmptyState, TokenList, TokenButton, TokenLeftGroup, TokenLogoArea, TokenInfo, TokenSymbol, TokenName, TokenBalance } from './styles.js';
15
18
 
16
19
  const ZERO = BigInt(0);
17
20
  const usdFormatter = new Intl.NumberFormat('en-US', {
@@ -27,6 +30,7 @@ const SelectToken = ({ isBuyFlow }) => {
27
30
  triggerResize();
28
31
  }, [viewAllAssets]);
29
32
  const { chainType } = useOpenfortCore();
33
+ const { chainId } = useEthereumEmbeddedWallet();
30
34
  const { data: walletAssets, isLoading: isBalancesLoading } = useEthereumWalletAssets();
31
35
  // Buys pick from a fixed buyable-currency list (USDC first, then native) per
32
36
  // chain family, so the picker always has options even for a fresh wallet with no
@@ -68,7 +72,7 @@ const SelectToken = ({ isBuyFlow }) => {
68
72
  return jsx(EmptyState, { children: "No supported tokens found for this network yet." });
69
73
  }
70
74
  return (jsxs(TokenList, { children: [selectableTokens.map((token) => {
71
- var _a, _b, _c, _d, _e;
75
+ var _a, _b, _c, _d, _e, _f, _g;
72
76
  const key = token.type === 'erc20' ? token.address : 'native';
73
77
  const displaySymbol = getAssetSymbol(token);
74
78
  const displayName = ((_a = token.metadata) === null || _a === void 0 ? void 0 : _a.name) || displaySymbol || 'Unknown Token';
@@ -101,7 +105,7 @@ const SelectToken = ({ isBuyFlow }) => {
101
105
  }
102
106
  }
103
107
  }
104
- return (jsxs(TokenButton, { type: "button", onClick: () => handleSelect(token), style: { opacity: isDisabled ? 0.4 : 1, cursor: isDisabled ? 'not-allowed' : 'pointer' }, children: [jsxs(TokenInfo, { children: [jsx(TokenSymbol, { children: displayName }), isBuyFlow && jsx(TokenName, { children: displaySymbol })] }), isBuyFlow ? (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" }) })) : (jsxs(TokenInfo, { children: [jsx(TokenBalance, { children: balanceDisplay }), usdValue ? jsx(TokenName, { style: { textAlign: 'end' }, children: usdValue }) : null] }))] }, key));
108
+ return (jsxs(TokenButton, { type: "button", onClick: () => handleSelect(token), style: { opacity: isDisabled ? 0.4 : 1, cursor: isDisabled ? 'not-allowed' : 'pointer' }, children: [jsxs(TokenLeftGroup, { children: [jsx(TokenLogoArea, { children: jsx(AssetChainLogo, { assetLogo: (_f = currencyLogoUrl(displaySymbol)) !== null && _f !== void 0 ? _f : '', chainLogo: (_g = chainLogoUrl(chainId)) !== null && _g !== void 0 ? _g : '', symbol: displaySymbol }) }), jsxs(TokenInfo, { children: [jsx(TokenSymbol, { children: displayName }), isBuyFlow && jsx(TokenName, { children: displaySymbol })] })] }), isBuyFlow ? (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" }) })) : (jsxs(TokenInfo, { children: [jsx(TokenBalance, { children: balanceDisplay }), usdValue ? jsx(TokenName, { style: { textAlign: 'end' }, children: usdValue }) : null] }))] }, key));
105
109
  }), !isBuyFlow && (jsx(TextLinkButton, { type: "button", onClick: () => {
106
110
  setViewAllAssets(!viewAllAssets);
107
111
  }, children: viewAllAssets ? 'View less assets' : 'View all assets' }))] }));
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,20 +1,21 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { useEffect, useMemo } from 'react';
3
3
  import { parseUnits, isAddress, formatUnits } from 'viem';
4
+ import { currencyLogoUrl, chainLogoUrl } from '../../../constants/logos.js';
5
+ import { useEthereumEmbeddedWallet } from '../../../ethereum/hooks/useEthereumEmbeddedWallet.js';
4
6
  import { useEthereumWalletAssets } from '../../../ethereum/hooks/useEthereumWalletAssets.js';
5
7
  import Button from '../../Common/Button/index.js';
6
8
  import { Arrow, ArrowChevron } from '../../Common/Button/styles.js';
7
- import Input from '../../Common/Input/index.js';
8
9
  import { ModalHeading } from '../../Common/Modal/styles.js';
9
10
  import { routes } from '../../Openfort/types.js';
10
11
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
11
12
  import { PageContent } from '../../PageContent/index.js';
12
- import { AmountCard, AmountInput } from '../Buy/styles.js';
13
- import { Form, Field, FieldLabel, TokenSelectorButton, TokenSelectorContent, TokenSelectorValue, TokenSelectorRight, MaxButton, HelperText, ErrorText } from './styles.js';
13
+ import { AssetChainLogo } from '../Deposit/AssetChainLogo.js';
14
+ import { Form, SendCard, ToRow, CardLabel, RecipientInput, PasteButton, ErrorText, AmountRow, AmountField, TokenPill, PillLogo, AmountMeta, MetaText, BalanceMeta, UseMaxButton } from './styles.js';
14
15
  import { isSameToken, sanitizeForParsing, formatBalance, sanitizeAmountInput } from './utils.js';
15
16
 
16
17
  const EthereumSend = () => {
17
- var _a, _b, _c, _d;
18
+ var _a, _b, _c, _d, _e, _f;
18
19
  const { sendForm, setSendForm, setRoute, triggerResize } = useOpenfort();
19
20
  // Size the modal to the form on mount. Without this the screen isn't anchored
20
21
  // and scrolls within the modal — every other Page triggers a resize on mount.
@@ -22,6 +23,7 @@ const EthereumSend = () => {
22
23
  triggerResize();
23
24
  }, [triggerResize]);
24
25
  const { data: assets } = useEthereumWalletAssets();
26
+ const { chainId } = useEthereumEmbeddedWallet();
25
27
  const matchedToken = useMemo(() => assets === null || assets === void 0 ? void 0 : assets.find((asset) => isSameToken(asset, sendForm.asset)), [assets, sendForm.asset]);
26
28
  const selectedTokenOption = matchedToken !== null && matchedToken !== void 0 ? matchedToken : assets === null || assets === void 0 ? void 0 : assets[0];
27
29
  const selectedToken = selectedTokenOption !== null && selectedTokenOption !== void 0 ? selectedTokenOption : sendForm.asset;
@@ -64,6 +66,16 @@ const EthereumSend = () => {
64
66
  recipient: event.target.value,
65
67
  }));
66
68
  };
69
+ const handlePaste = async () => {
70
+ try {
71
+ const text = await navigator.clipboard.readText();
72
+ if (text)
73
+ setSendForm((prev) => ({ ...prev, recipient: text.trim() }));
74
+ }
75
+ catch {
76
+ // Clipboard unavailable or permission denied — leave the field as-is.
77
+ }
78
+ };
67
79
  const handleAmountChange = (event) => {
68
80
  const raw = sanitizeAmountInput(event.target.value);
69
81
  if (raw === '' || /^[0-9]*\.?[0-9]*$/.test(raw)) {
@@ -87,7 +99,15 @@ const EthereumSend = () => {
87
99
  };
88
100
  const availableLabel = formatBalance(selectedBalanceValue, selectedDecimalsValue);
89
101
  const maxDisabled = !selectedBalanceValue;
90
- return (jsxs(PageContent, { onBack: routes.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: selectedSymbol || 'Select token' }) }), jsxs(TokenSelectorRight, { children: [jsx(TokenSelectorValue, { children: availableLabel === '--' ? '--' : `${availableLabel} ${selectedSymbol !== null && selectedSymbol !== void 0 ? selectedSymbol : ''}` }), 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(AmountCard, { style: { marginTop: 12 }, children: [jsx(AmountInput, { placeholder: "0.00", value: sendForm.amount, onChange: handleAmountChange, inputMode: "decimal", autoComplete: "off" }), jsx(MaxButton, { type: "button", onClick: handleMax, disabled: maxDisabled, children: "Max" })] }), jsxs(HelperText, { children: ["Available: ", availableLabel, " ", selectedSymbol] }), sendForm.amount && parsedAmount === null && jsx(ErrorText, { children: "Enter a valid amount." }), insufficientBalance && jsx(ErrorText, { children: "Insufficient balance for this transfer." })] }), jsxs(Field, { children: [jsx(FieldLabel, { children: "Recipient address" }), jsx(Input, { placeholder: "0x...", value: sendForm.recipient, onChange: handleRecipientChange, autoComplete: "off" }), sendForm.recipient && !recipientValid && jsx(ErrorText, { children: "Enter a valid wallet address." })] }), jsx(Button, { variant: "primary", disabled: !canProceed, children: "Review transfer" })] })] }));
102
+ const fiatValue = useMemo(() => {
103
+ var _a, _b;
104
+ const perToken = (_b = (_a = selectedToken.metadata) === null || _a === void 0 ? void 0 : _a.fiat) === null || _b === void 0 ? void 0 : _b.value;
105
+ const n = Number(sanitizeForParsing(sendForm.amount));
106
+ if (!perToken || !Number.isFinite(n) || n <= 0)
107
+ return null;
108
+ return `$${(n * perToken).toFixed(2)}`;
109
+ }, [selectedToken.metadata, sendForm.amount]);
110
+ return (jsxs(PageContent, { onBack: routes.CONNECTED, children: [jsx(ModalHeading, { children: "Send money" }), jsxs(Form, { onSubmit: handleSubmit, children: [jsxs(SendCard, { children: [jsxs(ToRow, { children: [jsx(CardLabel, { children: "To" }), jsx(RecipientInput, { placeholder: "0x\u2026 or address", value: sendForm.recipient, onChange: handleRecipientChange, autoComplete: "off", spellCheck: false }), jsx(PasteButton, { type: "button", onClick: handlePaste, children: "Paste" })] }), sendForm.recipient && !recipientValid && jsx(ErrorText, { children: "Enter a valid wallet address." })] }), jsxs(SendCard, { children: [jsx(CardLabel, { children: "Amount" }), jsxs(AmountRow, { children: [jsx(AmountField, { placeholder: "0", value: sendForm.amount, onChange: handleAmountChange, inputMode: "decimal", autoComplete: "off" }), jsxs(TokenPill, { type: "button", onClick: handleOpenTokenSelector, children: [selectedSymbol && (jsx(PillLogo, { children: jsx(AssetChainLogo, { assetLogo: (_e = currencyLogoUrl(selectedSymbol)) !== null && _e !== void 0 ? _e : '', chainLogo: (_f = chainLogoUrl(chainId)) !== null && _f !== void 0 ? _f : '', symbol: selectedSymbol }) })), selectedSymbol || 'Select', 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(AmountMeta, { children: [jsx(MetaText, { style: { flex: 1, minWidth: 0 }, children: fiatValue !== null && fiatValue !== void 0 ? fiatValue : '' }), jsxs(BalanceMeta, { children: [jsx(MetaText, { children: availableLabel === '--' ? '--' : `${availableLabel} ${selectedSymbol}` }), jsx(UseMaxButton, { type: "button", onClick: handleMax, disabled: maxDisabled, children: "Use max" })] })] }), sendForm.amount && parsedAmount === null && jsx(ErrorText, { children: "Enter a valid amount." }), insufficientBalance && jsx(ErrorText, { children: "Insufficient balance for this transfer." })] }), jsx(Button, { variant: "primary", disabled: !canProceed, children: "Review transfer" })] })] }));
91
111
  };
92
112
 
93
113
  export { EthereumSend };
@@ -1 +1 @@
1
- {"version":3,"file":"EthereumSend.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"EthereumSend.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,16 +1,16 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { useEffect, useMemo } from 'react';
3
3
  import { parseUnits, formatUnits } from 'viem';
4
+ import { currencyLogoUrl } from '../../../constants/logos.js';
4
5
  import { useSolanaWalletAssets } from '../../../solana/hooks/useSolanaWalletAssets.js';
5
6
  import Button from '../../Common/Button/index.js';
6
7
  import { Arrow, ArrowChevron } from '../../Common/Button/styles.js';
7
- import Input from '../../Common/Input/index.js';
8
8
  import { ModalHeading } from '../../Common/Modal/styles.js';
9
9
  import { routes } from '../../Openfort/types.js';
10
10
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
11
11
  import { PageContent } from '../../PageContent/index.js';
12
- import { AmountCard, AmountInput } from '../Buy/styles.js';
13
- import { Form, Field, FieldLabel, TokenSelectorButton, TokenSelectorContent, TokenSelectorValue, TokenSelectorRight, MaxButton, HelperText, ErrorText } from './styles.js';
12
+ import { AssetChainLogo } from '../Deposit/AssetChainLogo.js';
13
+ import { Form, SendCard, ToRow, CardLabel, RecipientInput, PasteButton, ErrorText, AmountRow, AmountField, TokenPill, PillLogo, AmountMeta, MetaText, BalanceMeta, UseMaxButton } from './styles.js';
14
14
  import { sanitizeForParsing, formatBalance, sanitizeAmountInput } from './utils.js';
15
15
 
16
16
  const SOL_DECIMALS = 9;
@@ -27,6 +27,7 @@ function solAsset(balance) {
27
27
  };
28
28
  }
29
29
  const SolanaSend = () => {
30
+ var _a, _b;
30
31
  const { sendForm, setSendForm, setRoute, triggerResize } = useOpenfort();
31
32
  const { data: assets } = useSolanaWalletAssets();
32
33
  // Size the modal to the form on mount so it's anchored and doesn't scroll.
@@ -87,7 +88,25 @@ const SolanaSend = () => {
87
88
  setSendForm((prev) => ({ ...prev, amount: formatUnits(balanceBase, selected.decimals) }));
88
89
  };
89
90
  const handleOpenTokenSelector = () => setRoute(routes.SOL_SEND_TOKEN_SELECT);
90
- 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(AmountCard, { style: { marginTop: 12 }, children: [jsx(AmountInput, { placeholder: "0.00", value: sendForm.amount, onChange: handleAmountChange, inputMode: "decimal", autoComplete: "off" }), 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" })] })] }));
91
+ const handlePaste = async () => {
92
+ try {
93
+ const text = await navigator.clipboard.readText();
94
+ if (text)
95
+ setSendForm((prev) => ({ ...prev, recipient: text.trim() }));
96
+ }
97
+ catch {
98
+ // Clipboard unavailable or permission denied — leave the field as-is.
99
+ }
100
+ };
101
+ const fiatValue = useMemo(() => {
102
+ var _a, _b;
103
+ const perToken = (_b = (_a = asset.metadata) === null || _a === void 0 ? void 0 : _a.fiat) === null || _b === void 0 ? void 0 : _b.value;
104
+ const n = Number(sanitizeForParsing(sendForm.amount));
105
+ if (!perToken || !Number.isFinite(n) || n <= 0)
106
+ return null;
107
+ return `$${(n * perToken).toFixed(2)}`;
108
+ }, [asset.metadata, sendForm.amount]);
109
+ return (jsxs(PageContent, { onBack: routes.SOL_CONNECTED, children: [jsx(ModalHeading, { children: "Send money" }), jsxs(Form, { onSubmit: handleSubmit, children: [jsxs(SendCard, { children: [jsxs(ToRow, { children: [jsx(CardLabel, { children: "To" }), jsx(RecipientInput, { placeholder: "Solana address", value: sendForm.recipient, onChange: (e) => setSendForm((prev) => ({ ...prev, recipient: e.target.value })), autoComplete: "off", spellCheck: false }), jsx(PasteButton, { type: "button", onClick: handlePaste, children: "Paste" })] }), sendForm.recipient && !recipientValid && jsx(ErrorText, { children: "Enter a valid Solana address." })] }), jsxs(SendCard, { children: [jsx(CardLabel, { children: "Amount" }), jsxs(AmountRow, { children: [jsx(AmountField, { placeholder: "0", value: sendForm.amount, onChange: handleAmountChange, inputMode: "decimal", autoComplete: "off" }), jsxs(TokenPill, { type: "button", onClick: handleOpenTokenSelector, children: [selected.symbol && (jsx(PillLogo, { children: jsx(AssetChainLogo, { assetLogo: (_a = currencyLogoUrl(selected.symbol)) !== null && _a !== void 0 ? _a : '', chainLogo: (_b = currencyLogoUrl('SOL')) !== null && _b !== void 0 ? _b : '', symbol: selected.symbol }) })), 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(AmountMeta, { children: [jsx(MetaText, { style: { flex: 1, minWidth: 0 }, children: fiatValue !== null && fiatValue !== void 0 ? fiatValue : '' }), jsxs(BalanceMeta, { children: [jsx(MetaText, { children: availableLabel === '--' ? '--' : `${availableLabel} ${selected.symbol}` }), jsx(UseMaxButton, { type: "button", onClick: handleMax, disabled: balanceBase === undefined, children: "Use max" })] })] }), sendForm.amount && parsedAmount === null && jsx(ErrorText, { children: "Enter a valid amount." }), insufficientBalance && jsxs(ErrorText, { children: ["Insufficient ", selected.symbol, " balance for this transfer."] })] }), jsx(Button, { variant: "primary", disabled: !canProceed, children: "Review transfer" })] })] }));
91
110
  };
92
111
 
93
112
  export { SolanaSend };
@@ -1 +1 @@
1
- {"version":3,"file":"SolanaSend.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"SolanaSend.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,13 +1,19 @@
1
1
  export declare const Form: import("styled-components").StyledComponent<"form", any, {}, never>;
2
- export declare const Field: import("styled-components").StyledComponent<"div", any, {}, never>;
3
- export declare const FieldLabel: import("styled-components").StyledComponent<"span", any, {}, never>;
4
- export declare const TokenSelectorButton: import("styled-components").StyledComponent<"button", any, {}, never>;
5
- export declare const TokenSelectorContent: import("styled-components").StyledComponent<"div", any, {}, never>;
6
- export declare const TokenSelectorValue: import("styled-components").StyledComponent<"span", any, {
7
- $primary?: boolean;
8
- $muted?: boolean;
9
- }, never>;
10
- export declare const TokenSelectorRight: import("styled-components").StyledComponent<"div", any, {}, never>;
11
- export declare const MaxButton: import("styled-components").StyledComponent<"button", any, {}, never>;
12
- export declare const HelperText: import("styled-components").StyledComponent<"span", any, {}, never>;
2
+ /** Rounded container shared by the "To" and "Amount" boxes. */
3
+ export declare const SendCard: import("styled-components").StyledComponent<"div", any, {}, never>;
4
+ export declare const CardLabel: import("styled-components").StyledComponent<"span", any, {}, never>;
5
+ /** "To" box: inline label, recipient input, and a Paste button. */
6
+ export declare const ToRow: import("styled-components").StyledComponent<"div", any, {}, never>;
7
+ export declare const RecipientInput: import("styled-components").StyledComponent<"input", any, {}, never>;
8
+ export declare const PasteButton: import("styled-components").StyledComponent<"button", any, {}, never>;
9
+ /** "Amount" box: large amount input with an inline token selector. */
10
+ export declare const AmountRow: import("styled-components").StyledComponent<"div", any, {}, never>;
11
+ export declare const AmountField: import("styled-components").StyledComponent<"input", any, {}, never>;
12
+ export declare const PillLogo: import("styled-components").StyledComponent<"span", any, {}, never>;
13
+ export declare const TokenPill: import("styled-components").StyledComponent<"button", any, {}, never>;
14
+ /** Bottom row of the amount box: fiat value (left), balance + Use max (right). */
15
+ export declare const AmountMeta: import("styled-components").StyledComponent<"div", any, {}, never>;
16
+ export declare const MetaText: import("styled-components").StyledComponent<"span", any, {}, never>;
17
+ export declare const BalanceMeta: import("styled-components").StyledComponent<"span", any, {}, never>;
18
+ export declare const UseMaxButton: import("styled-components").StyledComponent<"button", any, {}, never>;
13
19
  export declare const ErrorText: import("styled-components").StyledComponent<"span", any, {}, never>;