@openfort/react 1.5.0 → 1.6.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 (84) hide show
  1. package/build/components/Common/Chain/styles.d.ts +10 -0
  2. package/build/components/Common/Chain/styles.js +103 -1
  3. package/build/components/Common/Chain/styles.js.map +1 -1
  4. package/build/components/Common/Modal/index.js +2 -0
  5. package/build/components/Common/Modal/index.js.map +1 -1
  6. package/build/components/Common/SolanaChain/index.d.ts +5 -4
  7. package/build/components/Common/SolanaChain/index.js +9 -17
  8. package/build/components/Common/SolanaChain/index.js.map +1 -1
  9. package/build/components/ConnectModal/index.js +2 -0
  10. package/build/components/ConnectModal/index.js.map +1 -1
  11. package/build/components/Openfort/OpenfortProvider.js +4 -0
  12. package/build/components/Openfort/OpenfortProvider.js.map +1 -1
  13. package/build/components/Openfort/context.d.ts +4 -1
  14. package/build/components/Openfort/types.d.ts +22 -0
  15. package/build/components/Openfort/types.js +1 -0
  16. package/build/components/Openfort/types.js.map +1 -1
  17. package/build/components/Pages/Buy/styles.js +10 -10
  18. package/build/components/Pages/BuySelectProvider/index.js +9 -1
  19. package/build/components/Pages/BuySelectProvider/index.js.map +1 -1
  20. package/build/components/Pages/Connected/EthereumConnected.js +7 -1
  21. package/build/components/Pages/Connected/EthereumConnected.js.map +1 -1
  22. package/build/components/Pages/Deposit/TestnetNotice.js +15 -3
  23. package/build/components/Pages/Deposit/TestnetNotice.js.map +1 -1
  24. package/build/components/Pages/Deposit/UnsupportedNetworkNotice.d.ts +9 -0
  25. package/build/components/Pages/Deposit/UnsupportedNetworkNotice.js +11 -1
  26. package/build/components/Pages/Deposit/UnsupportedNetworkNotice.js.map +1 -1
  27. package/build/components/Pages/Deposit/index.js +2 -5
  28. package/build/components/Pages/Deposit/index.js.map +1 -1
  29. package/build/components/Pages/Deposit/paymentOptions.d.ts +0 -6
  30. package/build/components/Pages/Deposit/paymentOptions.js +3 -5
  31. package/build/components/Pages/Deposit/paymentOptions.js.map +1 -1
  32. package/build/components/Pages/Deposit/sources.d.ts +3 -0
  33. package/build/components/Pages/Deposit/sources.js +4 -1
  34. package/build/components/Pages/Deposit/sources.js.map +1 -1
  35. package/build/components/Pages/Deposit/useDepositRoute.d.ts +1 -0
  36. package/build/components/Pages/Deposit/useDepositRoute.js +37 -10
  37. package/build/components/Pages/Deposit/useDepositRoute.js.map +1 -1
  38. package/build/components/Pages/Deposit/useFundingTarget.d.ts +6 -4
  39. package/build/components/Pages/Deposit/useFundingTarget.js +18 -7
  40. package/build/components/Pages/Deposit/useFundingTarget.js.map +1 -1
  41. package/build/components/Pages/DepositCex/index.js +24 -5
  42. package/build/components/Pages/DepositCex/index.js.map +1 -1
  43. package/build/components/Pages/DepositCrypto/index.js +2 -2
  44. package/build/components/Pages/DepositWallet/index.js +2 -2
  45. package/build/components/Pages/Send/EthereumSend.js +10 -4
  46. package/build/components/Pages/Send/EthereumSend.js.map +1 -1
  47. package/build/components/Pages/Send/SolanaSend.js +9 -4
  48. package/build/components/Pages/Send/SolanaSend.js.map +1 -1
  49. package/build/components/Pages/Send/styles.d.ts +0 -1
  50. package/build/components/Pages/Send/styles.js +2 -13
  51. package/build/components/Pages/Send/styles.js.map +1 -1
  52. package/build/components/Pages/SendConfirmation/ConfirmationSummary.d.ts +27 -0
  53. package/build/components/Pages/SendConfirmation/ConfirmationSummary.js +15 -0
  54. package/build/components/Pages/SendConfirmation/ConfirmationSummary.js.map +1 -0
  55. package/build/components/Pages/SendConfirmation/SolanaSendConfirmation.js +3 -3
  56. package/build/components/Pages/SendConfirmation/index.js +14 -30
  57. package/build/components/Pages/SendConfirmation/index.js.map +1 -1
  58. package/build/components/Pages/SendConfirmation/styles.d.ts +7 -1
  59. package/build/components/Pages/SendConfirmation/styles.js +49 -17
  60. package/build/components/Pages/SendConfirmation/styles.js.map +1 -1
  61. package/build/components/Pages/SignMessage/index.d.ts +2 -0
  62. package/build/components/Pages/SignMessage/index.js +81 -0
  63. package/build/components/Pages/SignMessage/index.js.map +1 -0
  64. package/build/components/Pages/SignMessage/styles.d.ts +14 -0
  65. package/build/components/Pages/SignMessage/styles.js +82 -0
  66. package/build/components/Pages/SignMessage/styles.js.map +1 -0
  67. package/build/hooks/openfort/useSignMessage.d.ts +27 -0
  68. package/build/hooks/openfort/useSignMessage.js +52 -0
  69. package/build/hooks/openfort/useSignMessage.js.map +1 -0
  70. package/build/hooks/openfort/useUI.d.ts +6 -1
  71. package/build/hooks/openfort/useUI.js +12 -2
  72. package/build/hooks/openfort/useUI.js.map +1 -1
  73. package/build/index.d.ts +2 -1
  74. package/build/index.js +1 -0
  75. package/build/index.js.map +1 -1
  76. package/build/localizations/locales/en-US.js +1 -1
  77. package/build/utils/rpc.d.ts +6 -0
  78. package/build/utils/rpc.js +12 -1
  79. package/build/utils/rpc.js.map +1 -1
  80. package/build/version.d.ts +1 -1
  81. package/build/version.js +1 -1
  82. package/build/wagmi/components/ChainSelect/index.js +2 -94
  83. package/build/wagmi/components/ChainSelect/index.js.map +1 -1
  84. package/package.json +1 -1
@@ -52,11 +52,9 @@ function getPaymentOptions(ctx) {
52
52
  : visible;
53
53
  return ordered.map((method) => {
54
54
  const kind = method.target.kind;
55
- // Fiat (card/Apple Pay) and exchange rails move real money and only settle on
56
- // mainnet, so a test-key project can't use them disable with a clear reason.
57
- if (ctx.testnet && (kind === 'buy' || kind === 'cex')) {
58
- return { ...method, disabled: true, disabledReason: 'Not available on testnet' };
59
- }
55
+ // Fiat (card/Apple Pay) and exchange rails stay visible on testnet so the demo
56
+ // shows the full feature set; the final pay action is blocked downstream
57
+ // (BuyProcessing / DepositCex) with a testnet notice, since they settle on mainnet.
60
58
  const fundingRail = kind === 'crypto' || kind === 'wallet' || kind === 'cex';
61
59
  if (fundingRail && !ctx.fundingAvailable) {
62
60
  return { ...method, disabled: true, disabledReason: 'Coming soon' };
@@ -1 +1 @@
1
- {"version":3,"file":"paymentOptions.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"paymentOptions.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -13,5 +13,8 @@ export declare const DEST_USDC = "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913";
13
13
  */
14
14
  export declare const DEST_CHAIN_SOL = "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp";
15
15
  export declare const DEST_USDC_SOL = "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v";
16
+ /** EVM native-asset sentinel (zero address) — the default destination currency on
17
+ * chains where we don't ship a stablecoin address (e.g. testnets). */
18
+ export declare const NATIVE_TOKEN_ADDRESS = "0x0000000000000000000000000000000000000000";
16
19
  /** True for a CAIP-2 Solana chain id. */
17
20
  export declare function isSolana(chain: string): boolean;
@@ -13,10 +13,13 @@ const DEST_USDC = '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913';
13
13
  */
14
14
  const DEST_CHAIN_SOL = 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp';
15
15
  const DEST_USDC_SOL = 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v';
16
+ /** EVM native-asset sentinel (zero address) — the default destination currency on
17
+ * chains where we don't ship a stablecoin address (e.g. testnets). */
18
+ const NATIVE_TOKEN_ADDRESS = '0x0000000000000000000000000000000000000000';
16
19
  /** True for a CAIP-2 Solana chain id. */
17
20
  function isSolana(chain) {
18
21
  return chain.startsWith('solana:');
19
22
  }
20
23
 
21
- export { DEST_CHAIN, DEST_CHAIN_SOL, DEST_USDC, DEST_USDC_SOL, isSolana };
24
+ export { DEST_CHAIN, DEST_CHAIN_SOL, DEST_USDC, DEST_USDC_SOL, NATIVE_TOKEN_ADDRESS, isSolana };
22
25
  //# sourceMappingURL=sources.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"sources.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"sources.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -26,6 +26,7 @@ export declare function useDepositRoute(kind: DepositRouteKind): {
26
26
  receiverAddress: string | null;
27
27
  sameChain: boolean;
28
28
  targetUnsupported: boolean;
29
+ accountUnusableOnTarget: boolean;
29
30
  railChains: FundingChain[];
30
31
  status: import("../../../hooks/openfort/useFunding").SessionStatus | "idle";
31
32
  loading: boolean;
@@ -1,3 +1,4 @@
1
+ import { AccountTypeEnum } from '@openfort/openfort-js';
1
2
  import { useState, useRef, useEffect } from 'react';
2
3
  import { useEthereumEmbeddedWallet } from '../../../ethereum/hooks/useEthereumEmbeddedWallet.js';
3
4
  import { useFunding } from '../../../hooks/openfort/useFunding.js';
@@ -14,6 +15,22 @@ function paymentMethodFor(chain, currency) {
14
15
  // funding API has no separate 'cex' type, so every source resolves to evm/solana.
15
16
  return { type: isSolana(chain) ? 'solana' : 'evm', source };
16
17
  }
18
+ /**
19
+ * Whether the embedded account can transact on `chainId`. EOAs share one address
20
+ * across EVM chains, so they're always usable; smart/delegated accounts are per-chain
21
+ * deployments, usable only on a chain they're deployed on. Returns true when the type
22
+ * or deployments are unknown — the deposit flow only blocks when it's certain.
23
+ */
24
+ function accountUsableOnChain(wallet, chainId) {
25
+ if (!wallet || wallet.accountType == null)
26
+ return true;
27
+ if (wallet.accountType === AccountTypeEnum.EOA)
28
+ return true;
29
+ const chainScoped = wallet.accounts.filter((a) => a.chainId != null);
30
+ if (chainScoped.length === 0)
31
+ return true;
32
+ return chainScoped.some((a) => a.chainId === chainId);
33
+ }
17
34
  /**
18
35
  * Shared state for a deposit route: the source chain/currency selection (sourced
19
36
  * live from Relay via {@link useFundingChains}) plus the resolved deposit
@@ -21,7 +38,7 @@ function paymentMethodFor(chain, currency) {
21
38
  * address" page build on this — they differ only in the lead buttons.
22
39
  */
23
40
  function useDepositRoute(kind) {
24
- var _a, _b, _c, _d, _e, _f, _g, _h;
41
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j;
25
42
  const ethWallet = useEthereumEmbeddedWallet();
26
43
  const solWallet = useSolanaEmbeddedWallet();
27
44
  const { session, status, error, loading, isAvailable, fund, payLink, reset } = useFunding();
@@ -38,30 +55,38 @@ function useDepositRoute(kind) {
38
55
  // Coinbase Onramp, which only delivers to a fixed set of EVM chains.
39
56
  const chains = kind === 'cex' ? allChains.filter((c) => isCexDeliverable(c.id)) : allChains;
40
57
  // Where funds land: the active embedded wallet (the Relay deposit recipient).
41
- const address = wallet.status === 'connected' ? wallet.address : undefined;
58
+ // Use the address as soon as the wallet exposes one even mid-(re)connect or
59
+ // pending recovery — since receiving a deposit needs only the address, not a live
60
+ // signer. Gating on 'connected' left the page blank during those states.
61
+ const address = (_a = wallet.address) !== null && _a !== void 0 ? _a : undefined;
42
62
  const [chainId, setChainId] = useState('');
43
63
  const [currencySymbol, setCurrencySymbol] = useState('');
44
64
  const lastKey = useRef('');
45
65
  // Derive the active selection, falling back to the first available chain/currency
46
66
  // so the picker is valid before the user touches it and as chains load in.
47
- const activeChain = (_a = chains.find((c) => c.id === chainId)) !== null && _a !== void 0 ? _a : chains[0];
48
- const currencies = (_b = activeChain === null || activeChain === void 0 ? void 0 : activeChain.currencies) !== null && _b !== void 0 ? _b : [];
49
- const activeCurrency = (_c = currencies.find((c) => c.symbol === currencySymbol)) !== null && _c !== void 0 ? _c : currencies[0];
50
- const chain = (_d = activeChain === null || activeChain === void 0 ? void 0 : activeChain.id) !== null && _d !== void 0 ? _d : target.chain;
67
+ const activeChain = (_b = chains.find((c) => c.id === chainId)) !== null && _b !== void 0 ? _b : chains[0];
68
+ const currencies = (_c = activeChain === null || activeChain === void 0 ? void 0 : activeChain.currencies) !== null && _c !== void 0 ? _c : [];
69
+ const activeCurrency = (_d = currencies.find((c) => c.symbol === currencySymbol)) !== null && _d !== void 0 ? _d : currencies[0];
70
+ const chain = (_e = activeChain === null || activeChain === void 0 ? void 0 : activeChain.id) !== null && _e !== void 0 ? _e : target.chain;
51
71
  const sameChain = chain === target.chain;
52
72
  // The rail only delivers to chains in its list; if the active funding TARGET (the
53
73
  // embedded wallet's chain — e.g. Polygon Amoy or a Solana testnet) isn't one of
54
74
  // them, there's no route. Don't call Relay (it would 400 with a cryptic "invalid
55
75
  // currency"); the page prompts a switch to a supported chain instead.
56
76
  const targetUnsupported = !chainsLoading && railChains.length > 0 && !railChains.some((c) => c.id === target.chain);
77
+ // Funds settle on the TARGET chain. A smart/delegated account not deployed there
78
+ // can't use them, so block and guide the user to set up a usable account. EVM only —
79
+ // the recipient family already matches the target, and EOAs are always usable.
80
+ const targetChainId = isSolana(target.chain) ? null : Number(target.chain.split(':')[1]);
81
+ const accountUnusableOnTarget = targetChainId != null && !accountUsableOnChain(ethWallet.activeWallet, targetChainId);
57
82
  const receiverAddress = sameChain
58
83
  ? (address !== null && address !== void 0 ? address : null)
59
- : ((_f = (_e = session === null || session === void 0 ? void 0 : session.paymentMethod) === null || _e === void 0 ? void 0 : _e.receiverAddress) !== null && _f !== void 0 ? _f : null);
60
- const pm = (_g = session === null || session === void 0 ? void 0 : session.paymentMethod) !== null && _g !== void 0 ? _g : null;
84
+ : ((_g = (_f = session === null || session === void 0 ? void 0 : session.paymentMethod) === null || _f === void 0 ? void 0 : _f.receiverAddress) !== null && _g !== void 0 ? _g : null);
85
+ const pm = (_h = session === null || session === void 0 ? void 0 : session.paymentMethod) !== null && _h !== void 0 ? _h : null;
61
86
  useEffect(() => {
62
87
  if (!address || !isAvailable || !activeChain || !activeCurrency)
63
88
  return;
64
- if (targetUnsupported) {
89
+ if (targetUnsupported || accountUnusableOnTarget) {
65
90
  lastKey.current = '';
66
91
  reset();
67
92
  return;
@@ -89,6 +114,7 @@ function useDepositRoute(kind) {
89
114
  isAvailable,
90
115
  sameChain,
91
116
  targetUnsupported,
117
+ accountUnusableOnTarget,
92
118
  fund,
93
119
  reset,
94
120
  target.chain,
@@ -106,7 +132,7 @@ function useDepositRoute(kind) {
106
132
  chainsLoading,
107
133
  chain,
108
134
  setChain: setChainId,
109
- currency: (_h = activeCurrency === null || activeCurrency === void 0 ? void 0 : activeCurrency.symbol) !== null && _h !== void 0 ? _h : '',
135
+ currency: (_j = activeCurrency === null || activeCurrency === void 0 ? void 0 : activeCurrency.symbol) !== null && _j !== void 0 ? _j : '',
110
136
  setCurrency: setCurrencySymbol,
111
137
  currencies,
112
138
  activeChain,
@@ -117,6 +143,7 @@ function useDepositRoute(kind) {
117
143
  receiverAddress,
118
144
  sameChain,
119
145
  targetUnsupported,
146
+ accountUnusableOnTarget,
120
147
  railChains,
121
148
  status,
122
149
  loading,
@@ -1 +1 @@
1
- {"version":3,"file":"useDepositRoute.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"useDepositRoute.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * The destination route Deposit-hub funding settles into. Integrators override the
3
- * chain and currency via `uiConfig.funding.{targetChain,targetCurrency}`; both
4
- * default to USDC on the active chain type (Base for EVM, Solana mainnet for SVM)
5
- * so the flow works with zero configuration. The deposit recipient is always the
6
- * active embedded wallet for the destination chain family callers resolve it.
3
+ * chain and currency via `uiConfig.funding.{targetChain,targetCurrency}`. With no
4
+ * override the destination follows the wallet's ACTIVE chain so a testnet wallet
5
+ * funds on its own network instead of being told "funding isn't available on Base".
6
+ * Currency defaults to USDC where we ship its address (Base, Solana mainnet) and to
7
+ * the native asset otherwise, so the destination currency is always valid on-chain.
8
+ * The deposit recipient is always the active embedded wallet — callers resolve it.
7
9
  */
8
10
  export declare function useFundingTarget(): {
9
11
  chain: string;
@@ -1,23 +1,34 @@
1
1
  import { ChainTypeEnum } from '@openfort/openfort-js';
2
+ import { useEthereumEmbeddedWallet } from '../../../ethereum/hooks/useEthereumEmbeddedWallet.js';
2
3
  import { useOpenfortCore } from '../../../openfort/useOpenfort.js';
3
4
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
4
- import { DEST_CHAIN_SOL, DEST_CHAIN, DEST_USDC_SOL, DEST_USDC } from './sources.js';
5
+ import { DEST_CHAIN_SOL, DEST_CHAIN, DEST_USDC, DEST_USDC_SOL, NATIVE_TOKEN_ADDRESS } from './sources.js';
5
6
 
6
7
  /**
7
8
  * The destination route Deposit-hub funding settles into. Integrators override the
8
- * chain and currency via `uiConfig.funding.{targetChain,targetCurrency}`; both
9
- * default to USDC on the active chain type (Base for EVM, Solana mainnet for SVM)
10
- * so the flow works with zero configuration. The deposit recipient is always the
11
- * active embedded wallet for the destination chain family callers resolve it.
9
+ * chain and currency via `uiConfig.funding.{targetChain,targetCurrency}`. With no
10
+ * override the destination follows the wallet's ACTIVE chain so a testnet wallet
11
+ * funds on its own network instead of being told "funding isn't available on Base".
12
+ * Currency defaults to USDC where we ship its address (Base, Solana mainnet) and to
13
+ * the native asset otherwise, so the destination currency is always valid on-chain.
14
+ * The deposit recipient is always the active embedded wallet — callers resolve it.
12
15
  */
13
16
  function useFundingTarget() {
14
17
  var _a, _b, _c, _d;
15
18
  const { uiConfig } = useOpenfort();
16
19
  const { chainType } = useOpenfortCore();
20
+ const ethereumWallet = useEthereumEmbeddedWallet();
17
21
  const isSolana = chainType === ChainTypeEnum.SVM;
22
+ const activeChain = isSolana
23
+ ? DEST_CHAIN_SOL
24
+ : ethereumWallet.status === 'connected'
25
+ ? `eip155:${ethereumWallet.chainId}`
26
+ : DEST_CHAIN;
27
+ const chain = (_b = (_a = uiConfig.funding) === null || _a === void 0 ? void 0 : _a.targetChain) !== null && _b !== void 0 ? _b : activeChain;
28
+ const defaultCurrency = chain === DEST_CHAIN ? DEST_USDC : chain === DEST_CHAIN_SOL ? DEST_USDC_SOL : NATIVE_TOKEN_ADDRESS;
18
29
  return {
19
- chain: (_b = (_a = uiConfig.funding) === null || _a === void 0 ? void 0 : _a.targetChain) !== null && _b !== void 0 ? _b : (isSolana ? DEST_CHAIN_SOL : DEST_CHAIN),
20
- currency: (_d = (_c = uiConfig.funding) === null || _c === void 0 ? void 0 : _c.targetCurrency) !== null && _d !== void 0 ? _d : (isSolana ? DEST_USDC_SOL : DEST_USDC),
30
+ chain,
31
+ currency: (_d = (_c = uiConfig.funding) === null || _c === void 0 ? void 0 : _c.targetCurrency) !== null && _d !== void 0 ? _d : defaultCurrency,
21
32
  };
22
33
  }
23
34
 
@@ -1 +1 @@
1
- {"version":3,"file":"useFundingTarget.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"useFundingTarget.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -8,7 +8,9 @@ import { useFundingChains } from '../../../hooks/openfort/useFundingChains.js';
8
8
  import { invalidateBalance } from '../../../hooks/useBalance.js';
9
9
  import { useOpenfortCore } from '../../../openfort/useOpenfort.js';
10
10
  import { logger } from '../../../utils/logger.js';
11
+ import { getPublishableKeyEnvironment } from '../../../utils/validation.js';
11
12
  import { ModalHeading, ModalBody } from '../../Common/Modal/styles.js';
13
+ import Tooltip from '../../Common/Tooltip/index.js';
12
14
  import { routes } from '../../Openfort/types.js';
13
15
  import { useOpenfort } from '../../Openfort/useOpenfort.js';
14
16
  import { PageContent } from '../../PageContent/index.js';
@@ -19,6 +21,7 @@ import { DepositStatus } from '../Deposit/DepositStatus.js';
19
21
  import { walletListBtn } from '../Deposit/formStyles.js';
20
22
  import { isSolana, DEST_USDC } from '../Deposit/sources.js';
21
23
  import { StepDivider, ButtonLogo } from '../Deposit/styles.js';
24
+ import { TestnetNotice } from '../Deposit/TestnetNotice.js';
22
25
  import { useFundingTarget } from '../Deposit/useFundingTarget.js';
23
26
  import { sanitizeForParsing, sanitizeAmountInput } from '../Send/utils.js';
24
27
 
@@ -56,7 +59,10 @@ const hideBrokenLogo = (e) => {
56
59
  */
57
60
  const DepositCex = () => {
58
61
  var _a, _b, _c, _d, _e, _f;
59
- const { triggerResize } = useOpenfort();
62
+ const { triggerResize, publishableKey } = useOpenfort();
63
+ // Coinbase onramp settles real funds on mainnet, so a test key can't deliver here.
64
+ // Keep the button live for the demo but block the hand-off with a testnet notice.
65
+ const testnet = getPublishableKeyEnvironment(publishableKey) === 'test';
60
66
  const target = useFundingTarget();
61
67
  // CEX (Coinbase pay-link + session) is served by the Openfort API, not the
62
68
  // standalone funding service — resolve this rail's base URL from the API backend.
@@ -100,7 +106,9 @@ const DepositCex = () => {
100
106
  const createSessionRef = useRef(createSession);
101
107
  createSessionRef.current = createSession;
102
108
  useEffect(() => {
103
- if (!isAvailable || !address || !chainSupported)
109
+ // No session on testnet — Coinbase can't settle to a testnet wallet and the
110
+ // button below is blocked anyway; skip the mint so we don't fire a doomed call.
111
+ if (!isAvailable || !address || !chainSupported || testnet)
104
112
  return;
105
113
  const key = `${target.chain}|${target.currency}|${address}`;
106
114
  if (sessionKey.current === key)
@@ -125,7 +133,7 @@ const DepositCex = () => {
125
133
  // without this the guard above would block the retry after this cancel.
126
134
  sessionKey.current = '';
127
135
  };
128
- }, [isAvailable, address, chainSupported, target.chain, target.currency]);
136
+ }, [isAvailable, address, chainSupported, testnet, target.chain, target.currency]);
129
137
  const fiatAmount = useMemo(() => {
130
138
  const normalized = sanitizeForParsing(sanitizeAmountInput(amount));
131
139
  if (!normalized)
@@ -206,14 +214,25 @@ const DepositCex = () => {
206
214
  // success / refunded / expired screen (shared with the crypto rail).
207
215
  if (isDepositFlowActive(status))
208
216
  return jsx(DepositProgress, { status: status });
209
- return (jsxs(PageContent, { onBack: routes.DEPOSIT, children: [jsx(ModalHeading, { children: "Transfer from Exchange" }), jsxs(Section, { children: [jsx(SectionLabel, { children: "Amount" }), jsxs(AmountCard, { children: [jsx(CurrencySymbol, { children: "$" }), jsx(AmountInput, { value: amount, onChange: handleAmountChange, onBlur: handleAmountBlur, placeholder: "0.00", inputMode: "decimal", autoComplete: "off" })] }), jsx(PresetList, { children: PRESETS.map((preset) => (jsxs(PresetButton, { type: "button", "$active": pressedPreset === preset, onClick: () => handlePreset(preset), children: ["$", preset] }, preset))) }), amountTooLow ? (jsxs("span", { style: errorHelper, children: ["Enter at least $", MIN_AMOUNT, ".00 \u2014 the Coinbase minimum."] })) : (jsxs("span", { style: helperText, children: ["Minimum $", MIN_AMOUNT, ".00"] })), chainSupported && (jsxs("span", { style: destinationRow, children: [destAssetLogo && jsx("img", { src: destAssetLogo, alt: "", style: destinationLogo, onError: hideBrokenLogo }), destChainLogo && jsx("img", { src: destChainLogo, alt: "", style: destinationLogo, onError: hideBrokenLogo })] }))] }), !isAvailable && jsx(ModalBody, { children: "Funding isn't available right now." }), isAvailable && !chainSupported && jsxs(ModalBody, { children: ["Coinbase can't deliver to ", destChainName, " yet."] }), error && jsx(ModalBody, { style: { color: '#dc2626' }, children: error.message }), jsx(StepDivider, { children: "Then open an exchange" }), jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8, marginTop: 12 }, children: EXCHANGES.map((ex) => ex.comingSoon ? (jsxs("button", { type: "button", disabled: true, style: {
217
+ return (jsxs(PageContent, { onBack: routes.DEPOSIT, children: [jsx(ModalHeading, { children: "Transfer from Exchange" }), jsx(TestnetNotice, {}), jsxs(Section, { children: [jsx(SectionLabel, { children: "Amount" }), jsxs(AmountCard, { children: [jsx(CurrencySymbol, { children: "$" }), jsx(AmountInput, { value: amount, onChange: handleAmountChange, onBlur: handleAmountBlur, placeholder: "0.00", inputMode: "decimal", autoComplete: "off" })] }), jsx(PresetList, { children: PRESETS.map((preset) => (jsxs(PresetButton, { type: "button", "$active": pressedPreset === preset, onClick: () => handlePreset(preset), children: ["$", preset] }, preset))) }), amountTooLow ? (jsxs("span", { style: errorHelper, children: ["Enter at least $", MIN_AMOUNT, ".00 \u2014 the Coinbase minimum."] })) : (jsxs("span", { style: helperText, children: ["Minimum $", MIN_AMOUNT, ".00"] })), chainSupported && (jsxs("span", { style: destinationRow, children: [destAssetLogo && jsx("img", { src: destAssetLogo, alt: "", style: destinationLogo, onError: hideBrokenLogo }), destChainLogo && jsx("img", { src: destChainLogo, alt: "", style: destinationLogo, onError: hideBrokenLogo })] }))] }), !isAvailable && jsx(ModalBody, { children: "Funding isn't available right now." }), !testnet && isAvailable && !chainSupported && (jsxs(ModalBody, { children: ["Coinbase can't deliver to ", destChainName, " yet."] })), !testnet && error && jsx(ModalBody, { style: { color: '#dc2626' }, children: error.message }), jsx(StepDivider, { children: "Then open an exchange" }), jsx("div", { style: { display: 'flex', flexDirection: 'column', gap: 8, marginTop: 12 }, children: EXCHANGES.map((ex) => ex.comingSoon ? (jsxs("button", { type: "button", disabled: true, style: {
210
218
  ...walletListBtn,
211
219
  display: 'flex',
212
220
  alignItems: 'center',
213
221
  gap: 8,
214
222
  opacity: 0.55,
215
223
  cursor: 'not-allowed',
216
- }, children: [jsx(ButtonLogo, { children: EXCHANGE_LOGO[ex.id] }), jsx("span", { children: titleCase(ex.id) }), jsx("span", { style: { marginLeft: 'auto', fontSize: 11, fontWeight: 600 }, children: "Coming soon" })] }, ex.id)) : (jsxs("button", { type: "button", disabled: !payReady, style: {
224
+ }, children: [jsx(ButtonLogo, { children: EXCHANGE_LOGO[ex.id] }), jsx("span", { children: titleCase(ex.id) }), jsx("span", { style: { marginLeft: 'auto', fontSize: 11, fontWeight: 600 }, children: "Coming soon" })] }, ex.id)) : testnet ? (
225
+ // Blocked on testnet (Coinbase settles on mainnet). Use aria-disabled, not
226
+ // `disabled`, so the hover still fires the tooltip that explains why.
227
+ jsx(Tooltip, { message: "Coinbase settles on mainnet \u2014 not available on testnet.", children: jsxs("button", { type: "button", "aria-disabled": "true", style: {
228
+ ...walletListBtn,
229
+ display: 'flex',
230
+ alignItems: 'center',
231
+ justifyContent: 'center',
232
+ gap: 8,
233
+ opacity: 0.55,
234
+ cursor: 'not-allowed',
235
+ }, children: [jsx(ButtonLogo, { children: EXCHANGE_LOGO[ex.id] }), `Open ${titleCase(ex.id)} ↗`] }) }, ex.id)) : (jsxs("button", { type: "button", disabled: !payReady, style: {
217
236
  ...walletListBtn,
218
237
  display: 'flex',
219
238
  alignItems: 'center',
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -12,7 +12,7 @@ import { SameChainDepositStatus } from '../Deposit/SameChainDepositStatus.js';
12
12
  import { SameChainDepositSuccess } from '../Deposit/SameChainDepositSuccess.js';
13
13
  import { isSolana } from '../Deposit/sources.js';
14
14
  import { TestnetNotice } from '../Deposit/TestnetNotice.js';
15
- import { UnsupportedNetworkNotice } from '../Deposit/UnsupportedNetworkNotice.js';
15
+ import { UnsupportedNetworkNotice, AccountChainNotice } from '../Deposit/UnsupportedNetworkNotice.js';
16
16
  import { useDepositRoute } from '../Deposit/useDepositRoute.js';
17
17
  import { useSameChainArrival } from '../Deposit/useSameChainArrival.js';
18
18
  import { caipToChainId } from '../DepositWallet/walletDeeplinks.js';
@@ -45,7 +45,7 @@ const DepositCrypto = () => {
45
45
  return jsx(DepositProgress, { status: route.status });
46
46
  if (sameChainEnabled && arrived)
47
47
  return jsx(SameChainDepositSuccess, { address: sameChainAddress, chainId: sameChainId });
48
- return (jsxs(PageContent, { onBack: routes.DEPOSIT, children: [jsx(ModalHeading, { children: "Transfer from address" }), jsx(TestnetNotice, {}), route.targetUnsupported ? (jsx(UnsupportedNetworkNotice, { targetChain: route.target.chain, railChains: route.railChains })) : (jsxs(Fragment, { 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." }), jsx(DepositAddressBlock, { assetLogo: currencyLogoUrl((_b = route.activeCurrency) === null || _b === void 0 ? void 0 : _b.symbol, (_c = route.activeCurrency) === null || _c === void 0 ? void 0 : _c.logo), chainLogo: chainLogoUrl(caipToChainId((_d = route.activeChain) === null || _d === void 0 ? void 0 : _d.id), (_e = route.activeChain) === null || _e === void 0 ? void 0 : _e.logo), receiverAddress: route.receiverAddress, pm: route.pm, sourceCurrency: route.activeCurrency
48
+ return (jsxs(PageContent, { onBack: routes.DEPOSIT, children: [jsx(ModalHeading, { children: "Transfer from address" }), jsx(TestnetNotice, {}), route.targetUnsupported ? (jsx(UnsupportedNetworkNotice, { targetChain: route.target.chain, railChains: route.railChains })) : route.accountUnusableOnTarget ? (jsx(AccountChainNotice, { targetChain: route.target.chain })) : (jsxs(Fragment, { 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." }), jsx(DepositAddressBlock, { assetLogo: currencyLogoUrl((_b = route.activeCurrency) === null || _b === void 0 ? void 0 : _b.symbol, (_c = route.activeCurrency) === null || _c === void 0 ? void 0 : _c.logo), chainLogo: chainLogoUrl(caipToChainId((_d = route.activeChain) === null || _d === void 0 ? void 0 : _d.id), (_e = route.activeChain) === null || _e === void 0 ? void 0 : _e.logo), receiverAddress: route.receiverAddress, pm: route.pm, sourceCurrency: route.activeCurrency
49
49
  ? { symbol: route.activeCurrency.symbol, decimals: route.activeCurrency.decimals }
50
50
  : null, sameChain: route.sameChain, loading: route.loading, status: route.status }), sameChainEnabled && jsx(SameChainDepositStatus, {}), route.error && jsx(ModalBody, { style: { color: '#dc2626', marginTop: 12 }, children: route.error.message })] }))] }));
51
51
  };
@@ -19,7 +19,7 @@ import { RouteSelectors } from '../Deposit/RouteSelectors.js';
19
19
  import { isSolana } from '../Deposit/sources.js';
20
20
  import { StepDivider, Skeleton, ButtonLogo } from '../Deposit/styles.js';
21
21
  import { TestnetNotice } from '../Deposit/TestnetNotice.js';
22
- import { UnsupportedNetworkNotice } from '../Deposit/UnsupportedNetworkNotice.js';
22
+ import { UnsupportedNetworkNotice, AccountChainNotice } from '../Deposit/UnsupportedNetworkNotice.js';
23
23
  import { useDepositRoute } from '../Deposit/useDepositRoute.js';
24
24
  import { sanitizeAmountInput } from '../Send/utils.js';
25
25
  import { DepositWalletDesktop } from './DepositWalletDesktop.js';
@@ -130,7 +130,7 @@ const DepositWallet = () => {
130
130
  }, [route.receiverAddress, route.loading, route.status, deeplinks.length, triggerResize]);
131
131
  if (isDepositFlowActive(route.status))
132
132
  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 && (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: {
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
134
  ...walletListBtn,
135
135
  display: 'flex',
136
136
  alignItems: 'center',
@@ -1,5 +1,5 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { useMemo } from 'react';
2
+ import { useEffect, useMemo } from 'react';
3
3
  import { parseUnits, isAddress, formatUnits } from 'viem';
4
4
  import { useEthereumWalletAssets } from '../../../ethereum/hooks/useEthereumWalletAssets.js';
5
5
  import Button from '../../Common/Button/index.js';
@@ -9,12 +9,18 @@ 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 { Form, Field, FieldLabel, TokenSelectorButton, TokenSelectorContent, TokenSelectorValue, TokenSelectorRight, AmountInputWrapper, MaxButton, HelperText, ErrorText } from './styles.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
14
  import { isSameToken, sanitizeForParsing, formatBalance, sanitizeAmountInput } from './utils.js';
14
15
 
15
16
  const EthereumSend = () => {
16
17
  var _a, _b, _c, _d;
17
- const { sendForm, setSendForm, setRoute } = useOpenfort();
18
+ const { sendForm, setSendForm, setRoute, triggerResize } = useOpenfort();
19
+ // Size the modal to the form on mount. Without this the screen isn't anchored
20
+ // and scrolls within the modal — every other Page triggers a resize on mount.
21
+ useEffect(() => {
22
+ triggerResize();
23
+ }, [triggerResize]);
18
24
  const { data: assets } = useEthereumWalletAssets();
19
25
  const matchedToken = useMemo(() => assets === null || assets === void 0 ? void 0 : assets.find((asset) => isSameToken(asset, sendForm.asset)), [assets, sendForm.asset]);
20
26
  const selectedTokenOption = matchedToken !== null && matchedToken !== void 0 ? matchedToken : assets === null || assets === void 0 ? void 0 : assets[0];
@@ -81,7 +87,7 @@ const EthereumSend = () => {
81
87
  };
82
88
  const availableLabel = formatBalance(selectedBalanceValue, selectedDecimalsValue);
83
89
  const maxDisabled = !selectedBalanceValue;
84
- 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(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: 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" })] })] }));
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" })] })] }));
85
91
  };
86
92
 
87
93
  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,5 +1,5 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
- import { useMemo } from 'react';
2
+ import { useEffect, useMemo } from 'react';
3
3
  import { parseUnits, formatUnits } from 'viem';
4
4
  import { useSolanaWalletAssets } from '../../../solana/hooks/useSolanaWalletAssets.js';
5
5
  import Button from '../../Common/Button/index.js';
@@ -9,7 +9,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 { Form, Field, FieldLabel, TokenSelectorButton, TokenSelectorContent, TokenSelectorValue, TokenSelectorRight, AmountInputWrapper, MaxButton, HelperText, ErrorText } from './styles.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
14
  import { sanitizeForParsing, formatBalance, sanitizeAmountInput } from './utils.js';
14
15
 
15
16
  const SOL_DECIMALS = 9;
@@ -26,8 +27,12 @@ function solAsset(balance) {
26
27
  };
27
28
  }
28
29
  const SolanaSend = () => {
29
- const { sendForm, setSendForm, setRoute } = useOpenfort();
30
+ const { sendForm, setSendForm, setRoute, triggerResize } = useOpenfort();
30
31
  const { data: assets } = useSolanaWalletAssets();
32
+ // Size the modal to the form on mount so it's anchored and doesn't scroll.
33
+ useEffect(() => {
34
+ triggerResize();
35
+ }, [triggerResize]);
31
36
  const asset = sendForm.asset;
32
37
  const selected = asset.type === 'spl'
33
38
  ? { isSpl: true, mint: asset.address, decimals: asset.metadata.decimals, symbol: asset.metadata.symbol }
@@ -82,7 +87,7 @@ const SolanaSend = () => {
82
87
  setSendForm((prev) => ({ ...prev, amount: formatUnits(balanceBase, selected.decimals) }));
83
88
  };
84
89
  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" })] })] }));
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" })] })] }));
86
91
  };
87
92
 
88
93
  export { SolanaSend };
@@ -1 +1 @@
1
- {"version":3,"file":"SolanaSend.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"SolanaSend.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -8,7 +8,6 @@ export declare const TokenSelectorValue: import("styled-components").StyledCompo
8
8
  $muted?: boolean;
9
9
  }, never>;
10
10
  export declare const TokenSelectorRight: import("styled-components").StyledComponent<"div", any, {}, never>;
11
- export declare const AmountInputWrapper: import("styled-components").StyledComponent<"div", any, {}, never>;
12
11
  export declare const MaxButton: import("styled-components").StyledComponent<"button", any, {}, never>;
13
12
  export declare const HelperText: import("styled-components").StyledComponent<"span", any, {}, never>;
14
13
  export declare const ErrorText: import("styled-components").StyledComponent<"span", any, {}, never>;
@@ -62,19 +62,8 @@ const TokenSelectorRight = styled.div `
62
62
  gap: 6px;
63
63
  color: var(--ck-body-color-muted);
64
64
  `;
65
- const AmountInputWrapper = styled.div `
66
- position: relative;
67
- margin-top: 12px;
68
-
69
- > div {
70
- margin: 0;
71
- }
72
- `;
73
65
  const MaxButton = styled.button `
74
- position: absolute;
75
- right: 12px;
76
- top: 50%;
77
- transform: translateY(-50%);
66
+ flex-shrink: 0;
78
67
  padding: 6px 14px;
79
68
  border-radius: 16px;
80
69
  border: 1px solid var(--ck-body-divider);
@@ -108,5 +97,5 @@ const ErrorText = styled.span `
108
97
  color: var(--ck-body-color-danger);
109
98
  `;
110
99
 
111
- export { AmountInputWrapper, ErrorText, Field, FieldLabel, Form, HelperText, MaxButton, TokenSelectorButton, TokenSelectorContent, TokenSelectorRight, TokenSelectorValue };
100
+ export { ErrorText, Field, FieldLabel, Form, HelperText, MaxButton, TokenSelectorButton, TokenSelectorContent, TokenSelectorRight, TokenSelectorValue };
112
101
  //# 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,27 @@
1
+ import type { ReactNode } from 'react';
2
+ type ConfirmationAddress = {
3
+ display: string;
4
+ value: string;
5
+ };
6
+ interface ConfirmationSummaryProps {
7
+ /** Total being sent, e.g. "0.5" + "ETH". */
8
+ amount: string;
9
+ symbol: string;
10
+ /** Optional fiat estimate shown next to the total, e.g. "$180.84". */
11
+ fiat?: string | null;
12
+ /** Recipient address (truncated for display, full for copy). */
13
+ to?: ConfirmationAddress;
14
+ networkName: string;
15
+ networkIcon?: ReactNode;
16
+ /** Fee cell — the live estimate, or a "Sponsored" indicator. */
17
+ fee: ReactNode;
18
+ /** The wallet the funds are paid from. */
19
+ payWith?: ConfirmationAddress;
20
+ }
21
+ /**
22
+ * Shared, chain-agnostic transaction preview used by the EVM and Solana send
23
+ * confirmation screens. Renders the approval-style rows (Total / To / Network /
24
+ * Estimated fee) plus a "Pay with" card.
25
+ */
26
+ export declare function ConfirmationSummary({ amount, symbol, fiat, to, networkName, networkIcon, fee, payWith, }: ConfirmationSummaryProps): import("react/jsx-runtime").JSX.Element;
27
+ export {};
@@ -0,0 +1,15 @@
1
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
2
+ import { CopyText } from '../../Common/CopyToClipboard/CopyText.js';
3
+ import { SummaryList, SummaryItem, SummaryLabel, AmountValue, FiatValue, AddressValue, NetworkValue, PayWithCard, PayWithMeta, PayWithAddress, PayWithBadge } from './styles.js';
4
+
5
+ /**
6
+ * Shared, chain-agnostic transaction preview used by the EVM and Solana send
7
+ * confirmation screens. Renders the approval-style rows (Total / To / Network /
8
+ * Estimated fee) plus a "Pay with" card.
9
+ */
10
+ function ConfirmationSummary({ amount, symbol, fiat, to, networkName, networkIcon, fee, payWith, }) {
11
+ return (jsxs(Fragment, { children: [jsxs(SummaryList, { children: [jsxs(SummaryItem, { children: [jsx(SummaryLabel, { children: "Total" }), jsxs(AmountValue, { children: [amount || '0', " ", symbol, fiat ? jsxs(FiatValue, { children: ["\u2248 ", fiat] }) : null] })] }), jsxs(SummaryItem, { children: [jsx(SummaryLabel, { children: "To" }), jsx(AddressValue, { children: to ? (jsx(CopyText, { size: "1rem", value: to.value, children: to.display })) : ('--') })] }), jsxs(SummaryItem, { children: [jsx(SummaryLabel, { children: "Network" }), jsxs(NetworkValue, { children: [networkIcon, networkName] })] }), jsxs(SummaryItem, { children: [jsx(SummaryLabel, { children: "Estimated fee" }), fee] })] }), jsxs(PayWithCard, { children: [jsxs(PayWithMeta, { children: [jsx(SummaryLabel, { children: "Pay with" }), jsx(PayWithAddress, { children: payWith ? (jsx(CopyText, { size: "0.875rem", value: payWith.value, children: payWith.display })) : ('--') })] }), jsxs(PayWithBadge, { children: [amount || '0', " ", symbol] })] })] }));
12
+ }
13
+
14
+ export { ConfirmationSummary };
15
+ //# sourceMappingURL=ConfirmationSummary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ConfirmationSummary.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;"}