From d44221143496ef8e43203e64972368c28abc3068 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 27 Sep 2026 12:33:06 +0000 Subject: [PATCH] feat(swap): enable Swap on iOS Remove the iOS-only gates so Swap is available on every platform: the SwapModal trigger and SwapModalProvider no longer return null on iOS, the home Swap pill shows, and the Swaps fee row, Swap earning method and wallet tooltip copy are no longer hidden on iOS. Geo-restriction and disclaimer gates are unchanged. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01KtULm55FhtV8QpE98RAuqC --- components/Home/NewHome/WalletActions.tsx | 7 +++---- components/Rewards/EarnPointsSection.tsx | 7 ++----- components/Rewards/TierFeesTable.tsx | 5 +---- components/Swap/SwapModal.tsx | 5 ----- components/Swap/SwapModalProvider.tsx | 8 +------- components/Swap/__tests__/SwapModalProvider.ios.test.tsx | 5 +++-- components/Wallet/WalletCard.tsx | 4 ++-- 7 files changed, 12 insertions(+), 29 deletions(-) diff --git a/components/Home/NewHome/WalletActions.tsx b/components/Home/NewHome/WalletActions.tsx index 99bbe7790..48edb79dc 100644 --- a/components/Home/NewHome/WalletActions.tsx +++ b/components/Home/NewHome/WalletActions.tsx @@ -1,5 +1,5 @@ import { useState } from 'react'; -import { Platform, Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'; +import { Pressable, StyleSheet, useWindowDimensions, View } from 'react-native'; import HomeSend from '@/assets/images/home-send'; import HomeSwap from '@/assets/images/home-swap'; @@ -97,8 +97,7 @@ interface WalletActionsProps { /** * Home action row. No funds → full-width white "Add Funds". Funded → "Add Funds" - * plus "Swap" and "Send". Reuses the global Deposit/Swap/Send modals. Note - * SwapModal renders null on iOS, so Swap self-hides there (same as the legacy row). + * plus "Swap" and "Send". Reuses the global Deposit/Swap/Send modals. * * "Add Funds" has three destinations, and none of them is a destination picker: * @@ -117,7 +116,7 @@ const WalletActions = ({ hasFunds, hasCard }: WalletActionsProps) => { const fundsGoToCard = Boolean(hasCard) && canDepositToCard(provider); // Only the crowded three-pill row needs to shrink; alone, "Add Funds" always fits. const compact = hasFunds && width > 0 && width < COMPACT_WIDTH; - const showSwap = hasFunds && Platform.OS !== 'ios'; + const showSwap = hasFunds; const addFundsTrigger = ; const [isCardModalOpen, setIsCardModalOpen] = useState(false); diff --git a/components/Rewards/EarnPointsSection.tsx b/components/Rewards/EarnPointsSection.tsx index 8907e0ba4..e18489dc1 100644 --- a/components/Rewards/EarnPointsSection.tsx +++ b/components/Rewards/EarnPointsSection.tsx @@ -1,4 +1,4 @@ -import { ActivityIndicator, Platform, View } from 'react-native'; +import { ActivityIndicator, View } from 'react-native'; import { Text } from '@/components/ui/text'; import { useDimension } from '@/hooks/useDimension'; @@ -111,10 +111,7 @@ const EarnPointsSection = () => { ]; }; - // Swap is not available on iOS, so exclude it from the earning methods there. - const earningMethods = getEarningMethods().filter( - method => Platform.OS !== 'ios' || method.title !== 'Swap', - ); + const earningMethods = getEarningMethods(); return ( diff --git a/components/Rewards/TierFeesTable.tsx b/components/Rewards/TierFeesTable.tsx index 0dd378706..d4f162b26 100644 --- a/components/Rewards/TierFeesTable.tsx +++ b/components/Rewards/TierFeesTable.tsx @@ -1,5 +1,3 @@ -import { Platform } from 'react-native'; - import { resolveTierFees } from '@/components/Rewards/NewRewards/tierFees'; import { RewardsTier, TierBenefit, TierBenefits } from '@/lib/types'; @@ -13,8 +11,7 @@ interface TierFeesTableProps { const FEE_ROWS: { key: string; label: string; subtitle?: string }[] = [ { key: 'virtual_card', label: 'Virtual card', subtitle: 'Issued instantly' }, { key: 'bank_deposit', label: 'Bank deposit' }, - // Swap is not available on iOS, so the swap row is omitted there. - ...(Platform.OS === 'ios' ? [] : [{ key: 'swap', label: 'Swaps' }]), + { key: 'swap', label: 'Swaps' }, { key: 'stocks', label: 'Stocks' }, { key: 'fx', label: 'FX conversion' }, { key: 'offramp', label: 'Bank withdrawal' }, diff --git a/components/Swap/SwapModal.tsx b/components/Swap/SwapModal.tsx index 1694e48d3..ae238668c 100644 --- a/components/Swap/SwapModal.tsx +++ b/components/Swap/SwapModal.tsx @@ -1,5 +1,4 @@ import React, { ReactNode, useEffect } from 'react'; -import { Platform } from 'react-native'; import { SWAP_MODAL } from '@/constants/modals'; import { useSwapState } from '@/store/swapStore'; @@ -34,10 +33,6 @@ const SwapModal = ({ trigger = null, defaultOpen = false }: SwapModalProps) => { } }, [defaultOpen, setModal]); - if (Platform.OS === 'ios') { - return null; - } - // Headless usage - the global SwapModalProvider handles the modal if (trigger === null) { return null; diff --git a/components/Swap/SwapModalProvider.tsx b/components/Swap/SwapModalProvider.tsx index c2e6b95bc..d864d1018 100644 --- a/components/Swap/SwapModalProvider.tsx +++ b/components/Swap/SwapModalProvider.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useMemo } from 'react'; -import { Platform, View } from 'react-native'; +import { View } from 'react-native'; import { useRouter } from 'expo-router'; import { useShallow } from 'zustand/react/shallow'; @@ -149,12 +149,6 @@ const SwapModalProvider = () => { handleTransactionStatusPress, ]); - // Regular Swap remains unavailable on iOS. Buy FUSE can open through its - // dedicated entry points and still passes the geo and disclaimer gates. - if (Platform.OS === 'ios' && !isBuyFuse) { - return null; - } - return ( { act(() => root.unmount()); }); -it('keeps the regular Swap modal hidden on iOS', () => { +it('renders the regular Swap modal on iOS', () => { mockCurrentModal = SWAP_MODAL.OPEN_FORM; const root = render(); - expect(root.root.findAllByType('ResponsiveModal')).toHaveLength(0); + expect(root.root.findAllByType('ResponsiveModal')).toHaveLength(1); + expect(root.root.findAllByType('SwapPair')).toHaveLength(1); act(() => root.unmount()); }); diff --git a/components/Wallet/WalletCard.tsx b/components/Wallet/WalletCard.tsx index 9721980a8..f5c1c98e7 100644 --- a/components/Wallet/WalletCard.tsx +++ b/components/Wallet/WalletCard.tsx @@ -1,4 +1,4 @@ -import { Platform, Pressable, View } from 'react-native'; +import { Pressable, View } from 'react-native'; import WalletIcon from '@/assets/images/wallet'; import CountUp from '@/components/CountUp'; @@ -74,7 +74,7 @@ const WalletCard = ({ balance, className, tokens, isLoading, decimalPlaces }: Wa content={ Displaying top three tokens by balance. Wallet can contain any ERC-20 and native - token in Ethereum and Fuse for {Platform.OS === 'ios' ? 'Send' : 'Swap and Send'}. + token in Ethereum and Fuse for Swap and Send. } />