From f423bfa3c66084fa2efefe96628e20494da0bd90 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 6 Aug 2024 14:26:19 -0300 Subject: [PATCH 001/114] chore: implementing new account view --- .../core/src/controllers/RouterController.ts | 1 + packages/scaffold/src/index.ts | 2 +- .../scaffold/src/modal/w3m-router/index.tsx | 5 +- .../partials/w3m-account-activity/index.tsx | 11 + .../src/partials/w3m-account-nfts/index.tsx | 11 + .../w3m-account-placeholder/index.tsx | 32 +++ .../src/partials/w3m-account-tokens/index.tsx | 11 + .../w3m-account-wallet-features/index.tsx | 61 ++++++ .../w3m-account-wallet-features/styles.ts | 21 ++ .../src/partials/w3m-header/index.tsx | 1 + .../components/upgrade-wallet-button.tsx | 0 .../views/w3m-account-settings-view/index.tsx | 200 ++++++++++++++++++ .../views/w3m-account-settings-view/styles.ts | 21 ++ .../src/views/w3m-account-view/index.tsx | 194 ++--------------- .../src/views/w3m-account-view/styles.ts | 16 +- .../ui/src/assets/svg/ArrowBottomCircle.tsx | 12 ++ packages/ui/src/assets/svg/Paperplane.tsx | 12 ++ packages/ui/src/components/wui-icon/index.tsx | 4 + .../composites/wui-network-button/index.tsx | 33 +-- .../composites/wui-network-button/styles.ts | 2 +- packages/ui/src/composites/wui-tabs/index.tsx | 42 +++- packages/ui/src/composites/wui-tabs/styles.ts | 3 +- packages/ui/src/utils/TypesUtil.ts | 5 +- 23 files changed, 485 insertions(+), 215 deletions(-) create mode 100644 packages/scaffold/src/partials/w3m-account-activity/index.tsx create mode 100644 packages/scaffold/src/partials/w3m-account-nfts/index.tsx create mode 100644 packages/scaffold/src/partials/w3m-account-placeholder/index.tsx create mode 100644 packages/scaffold/src/partials/w3m-account-tokens/index.tsx create mode 100644 packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx create mode 100644 packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts rename packages/scaffold/src/views/{w3m-account-view => w3m-account-settings-view}/components/upgrade-wallet-button.tsx (100%) create mode 100644 packages/scaffold/src/views/w3m-account-settings-view/index.tsx create mode 100644 packages/scaffold/src/views/w3m-account-settings-view/styles.ts create mode 100644 packages/ui/src/assets/svg/ArrowBottomCircle.tsx create mode 100644 packages/ui/src/assets/svg/Paperplane.tsx diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index 9eecdd34d..88962e0a5 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -5,6 +5,7 @@ import type { WcWallet, CaipNetwork, Connector } from '../utils/TypeUtil'; export interface RouterControllerState { view: | 'Account' + | 'AccountSettings' | 'Connect' | 'ConnectingWalletConnect' | 'ConnectingExternal' diff --git a/packages/scaffold/src/index.ts b/packages/scaffold/src/index.ts index 766ef5854..907b9dc96 100644 --- a/packages/scaffold/src/index.ts +++ b/packages/scaffold/src/index.ts @@ -5,7 +5,7 @@ export * from './modal/w3m-network-button'; export * from './modal/w3m-modal'; export * from './modal/w3m-router'; -export * from './views/w3m-account-view'; +export * from './views/w3m-account-settings-view'; export * from './views/w3m-all-wallets-view'; export * from './views/w3m-connect-view'; export * from './views/w3m-connecting-view'; diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/scaffold/src/modal/w3m-router/index.tsx index fabe66c7c..37f58f0c8 100644 --- a/packages/scaffold/src/modal/w3m-router/index.tsx +++ b/packages/scaffold/src/modal/w3m-router/index.tsx @@ -8,7 +8,7 @@ import { AllWalletsView } from '../../views/w3m-all-wallets-view'; import { ConnectingView } from '../../views/w3m-connecting-view'; import { WhatIsAWalletView } from '../../views/w3m-what-is-a-wallet-view'; import { GetWalletView } from '../../views/w3m-get-wallet-view'; -import { AccountView } from '../../views/w3m-account-view'; +import { AccountSettingsView } from '../../views/w3m-account-settings-view'; import { NetworksView } from '../../views/w3m-networks-view'; import { WhatIsNetworkView } from '../../views/w3m-what-is-a-network-view'; import { NetworkSwitchView } from '../../views/w3m-network-switch-view'; @@ -20,6 +20,7 @@ import { UpdateEmailWalletView } from '../../views/w3m-update-email-wallet-view' import { UpdateEmailPrimaryOtpView } from '../../views/w3m-update-email-primary-otp-view'; import { UpdateEmailSecondaryOtpView } from '../../views/w3m-update-email-secondary-otp-view'; import { UpgradeEmailWalletView } from '../../views/w3m-upgrade-email-wallet-view'; +import { AccountView } from '../../views/w3m-account-view'; export function Web3Router() { const { view } = useSnapshot(RouterController.state); @@ -50,6 +51,8 @@ export function Web3Router() { return NetworkSwitchView; case 'Account': return AccountView; + case 'AccountSettings': + return AccountSettingsView; case 'EmailVerifyDevice': return EmailVerifyDeviceView; case 'EmailVerifyOtp': diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/scaffold/src/partials/w3m-account-activity/index.tsx new file mode 100644 index 000000000..9b91cf4b7 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-activity/index.tsx @@ -0,0 +1,11 @@ +import { AccountPlaceholder } from '../w3m-account-placeholder'; + +export function AccountActivity() { + return ( + + ); +} diff --git a/packages/scaffold/src/partials/w3m-account-nfts/index.tsx b/packages/scaffold/src/partials/w3m-account-nfts/index.tsx new file mode 100644 index 000000000..59643cabc --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-nfts/index.tsx @@ -0,0 +1,11 @@ +import { AccountPlaceholder } from '../w3m-account-placeholder'; + +export function AccountNfts() { + return ( + + ); +} diff --git a/packages/scaffold/src/partials/w3m-account-placeholder/index.tsx b/packages/scaffold/src/partials/w3m-account-placeholder/index.tsx new file mode 100644 index 000000000..8c0974c9c --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-placeholder/index.tsx @@ -0,0 +1,32 @@ +import { StyleSheet } from 'react-native'; +import { IconBox, Text, FlexView, Spacing, type IconType } from '@web3modal/ui-react-native'; + +interface Props { + icon: IconType; + title: string; + description: string; +} + +export function AccountPlaceholder({ icon, title, description }: Props) { + return ( + + + + {title} + + + {description} + + + ); +} + +const styles = StyleSheet.create({ + title: { + marginTop: Spacing.xl, + marginBottom: Spacing.xs + }, + description: { + maxWidth: '50%' + } +}); diff --git a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx new file mode 100644 index 000000000..9ed0ec60c --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx @@ -0,0 +1,11 @@ +import { AccountPlaceholder } from '../w3m-account-placeholder'; + +export function AccountTokens() { + return ( + + ); +} diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx new file mode 100644 index 000000000..1cb73c0e9 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx @@ -0,0 +1,61 @@ +import { View } from 'react-native'; +import { FlexView, IconLink, Tabs, Text } from '@web3modal/ui-react-native'; +import styles from './styles'; +import { useState } from 'react'; +import { AccountNfts } from '../w3m-account-nfts'; +import { AccountActivity } from '../w3m-account-activity'; +import { AccountTokens } from '../w3m-account-tokens'; + +export interface AccountWalletFeaturesProps { + value: string; +} + +export function AccountWalletFeatures() { + const [activeTab, setActiveTab] = useState(0); + + const onTabChange = (index: number) => { + setActiveTab(index); + }; + + return ( + + + $4798 + + .75 + + + + + + + + + {activeTab === 0 && } + {activeTab === 1 && } + {activeTab === 2 && } + + + ); +} diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts b/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts new file mode 100644 index 000000000..80388b3c2 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts @@ -0,0 +1,21 @@ +import { Spacing } from '@web3modal/ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + alignItems: 'center' + }, + balanceText: { + fontSize: 40, + fontWeight: '500' + }, + actionsContainer: { + width: '100%', + marginTop: Spacing.s, + marginBottom: Spacing.l + }, + action: { + flex: 1, + height: 52 + } +}); diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index ec120b185..c4cc1a9a5 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -18,6 +18,7 @@ export function Header() { return { Connect: 'Connect wallet', Account: undefined, + AccountSettings: undefined, ConnectingWalletConnect: walletName ?? 'WalletConnect', ConnectingExternal: connectorName ?? 'Connect wallet', ConnectingSiwe: 'Sign In', diff --git a/packages/scaffold/src/views/w3m-account-view/components/upgrade-wallet-button.tsx b/packages/scaffold/src/views/w3m-account-settings-view/components/upgrade-wallet-button.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-account-view/components/upgrade-wallet-button.tsx rename to packages/scaffold/src/views/w3m-account-settings-view/components/upgrade-wallet-button.tsx diff --git a/packages/scaffold/src/views/w3m-account-settings-view/index.tsx b/packages/scaffold/src/views/w3m-account-settings-view/index.tsx new file mode 100644 index 000000000..a486e61ea --- /dev/null +++ b/packages/scaffold/src/views/w3m-account-settings-view/index.tsx @@ -0,0 +1,200 @@ +import { useSnapshot } from 'valtio'; +import { useState } from 'react'; +import { Linking, ScrollView } from 'react-native'; +import { + AccountController, + ApiController, + AssetUtil, + ConnectionController, + ConnectorController, + CoreHelperUtil, + EventsController, + ModalController, + NetworkController, + OptionsController, + RouterController, + SnackController, + type W3mFrameProvider +} from '@web3modal/core-react-native'; +import { + Avatar, + Button, + FlexView, + IconLink, + Text, + UiUtil, + Spacing, + ListItem +} from '@web3modal/ui-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { UpgradeWalletButton } from './components/upgrade-wallet-button'; +import styles from './styles'; + +export function AccountSettingsView() { + const { address, profileName, profileImage, balance, balanceSymbol, addressExplorerUrl } = + useSnapshot(AccountController.state); + const [disconnecting, setDisconnecting] = useState(false); + const { caipNetwork } = useSnapshot(NetworkController.state); + const { connectedConnector } = useSnapshot(ConnectorController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const showCopy = OptionsController.isClipboardAvailable(); + const isEmail = connectedConnector === 'EMAIL'; + const { padding } = useCustomDimensions(); + + async function onDisconnect() { + try { + setDisconnecting(true); + await ConnectionController.disconnect(); + AccountController.setIsConnected(false); + ModalController.close(); + setDisconnecting(false); + EventsController.sendEvent({ + type: 'track', + event: 'DISCONNECT_SUCCESS' + }); + } catch (error) { + EventsController.sendEvent({ + type: 'track', + event: 'DISCONNECT_ERROR' + }); + } + } + + const onExplorerPress = () => { + if (addressExplorerUrl) { + Linking.openURL(addressExplorerUrl); + } + }; + + const onCopyAddress = () => { + if (address) { + OptionsController.copyToClipboard(profileName ?? address); + SnackController.showSuccess('Address copied'); + } + }; + + const onNetworkPress = () => { + RouterController.push('Networks'); + + EventsController.sendEvent({ + type: 'track', + event: 'CLICK_NETWORKS' + }); + }; + + const onUpgradePress = () => { + EventsController.sendEvent({ type: 'track', event: 'EMAIL_UPGRADE_FROM_MODAL' }); + RouterController.push('UpgradeEmailWallet'); + }; + + const getUserEmail = () => { + const provider = ConnectorController.getEmailConnector()?.provider as W3mFrameProvider; + if (!provider) return ''; + + return provider.getEmail(); + }; + + const addressExplorerTemplate = () => { + if (!addressExplorerUrl) return null; + + return ( + + ); + }; + + return ( + <> + + + + + + + {profileName + ? UiUtil.getTruncateString({ + string: profileName, + charsStart: 20, + charsEnd: 0, + truncate: 'end' + }) + : UiUtil.getTruncateString({ + string: address ?? '', + charsStart: 4, + charsEnd: 6, + truncate: 'middle' + })} + + {showCopy && ( + + )} + + {balance && ( + + {CoreHelperUtil.formatBalance(balance, balanceSymbol)} + + )} + {addressExplorerTemplate()} + + {isEmail && ( + <> + + + RouterController.push('UpdateEmailWallet', { email: getUserEmail() }) + } + chevron + testID="button-email" + > + {getUserEmail()} + + + )} + + + {caipNetwork?.name} + + + + Disconnect + + + + + + ); +} diff --git a/packages/scaffold/src/views/w3m-account-settings-view/styles.ts b/packages/scaffold/src/views/w3m-account-settings-view/styles.ts new file mode 100644 index 000000000..9aadd0d31 --- /dev/null +++ b/packages/scaffold/src/views/w3m-account-settings-view/styles.ts @@ -0,0 +1,21 @@ +import { Spacing } from '@web3modal/ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + closeIcon: { + alignSelf: 'flex-end', + position: 'absolute', + zIndex: 1, + top: Spacing.l, + right: Spacing.xl + }, + copyButton: { + marginLeft: Spacing['4xs'] + }, + networkButton: { + marginVertical: Spacing.xs + }, + upgradeButton: { + marginBottom: Spacing.s + } +}); diff --git a/packages/scaffold/src/views/w3m-account-view/index.tsx b/packages/scaffold/src/views/w3m-account-view/index.tsx index 0a6487910..498c5bfd4 100644 --- a/packages/scaffold/src/views/w3m-account-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-view/index.tsx @@ -1,200 +1,38 @@ import { useSnapshot } from 'valtio'; -import { useState } from 'react'; -import { Linking, ScrollView } from 'react-native'; +import { FlexView, Icon, IconLink, NetworkButton, useTheme } from '@web3modal/ui-react-native'; import { - AccountController, ApiController, AssetUtil, - ConnectionController, - ConnectorController, - CoreHelperUtil, - EventsController, ModalController, NetworkController, - OptionsController, - RouterController, - SnackController, - type W3mFrameProvider + RouterController } from '@web3modal/core-react-native'; -import { - Avatar, - Button, - FlexView, - IconLink, - Text, - UiUtil, - Spacing, - ListItem -} from '@web3modal/ui-react-native'; -import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { UpgradeWalletButton } from './components/upgrade-wallet-button'; +import { AccountWalletFeatures } from '../../partials/w3m-account-wallet-features'; import styles from './styles'; export function AccountView() { - const { address, profileName, profileImage, balance, balanceSymbol, addressExplorerUrl } = - useSnapshot(AccountController.state); - const [disconnecting, setDisconnecting] = useState(false); + const Theme = useTheme(); const { caipNetwork } = useSnapshot(NetworkController.state); - const { connectedConnector } = useSnapshot(ConnectorController.state); - const networkImage = AssetUtil.getNetworkImage(caipNetwork); - const showCopy = OptionsController.isClipboardAvailable(); - const isEmail = connectedConnector === 'EMAIL'; - const { padding } = useCustomDimensions(); - - async function onDisconnect() { - try { - setDisconnecting(true); - await ConnectionController.disconnect(); - AccountController.setIsConnected(false); - ModalController.close(); - setDisconnecting(false); - EventsController.sendEvent({ - type: 'track', - event: 'DISCONNECT_SUCCESS' - }); - } catch (error) { - EventsController.sendEvent({ - type: 'track', - event: 'DISCONNECT_ERROR' - }); - } - } - - const onExplorerPress = () => { - if (addressExplorerUrl) { - Linking.openURL(addressExplorerUrl); - } - }; - - const onCopyAddress = () => { - if (address) { - OptionsController.copyToClipboard(profileName ?? address); - SnackController.showSuccess('Address copied'); - } - }; const onNetworkPress = () => { RouterController.push('Networks'); - - EventsController.sendEvent({ - type: 'track', - event: 'CLICK_NETWORKS' - }); - }; - - const onUpgradePress = () => { - EventsController.sendEvent({ type: 'track', event: 'EMAIL_UPGRADE_FROM_MODAL' }); - RouterController.push('UpgradeEmailWallet'); - }; - - const getUserEmail = () => { - const provider = ConnectorController.getEmailConnector()?.provider as W3mFrameProvider; - if (!provider) return ''; - - return provider.getEmail(); - }; - - const addressExplorerTemplate = () => { - if (!addressExplorerUrl) return null; - - return ( - - ); }; return ( <> + + + - - - - - - {profileName - ? UiUtil.getTruncateString({ - string: profileName, - charsStart: 20, - charsEnd: 0, - truncate: 'end' - }) - : UiUtil.getTruncateString({ - string: address ?? '', - charsStart: 4, - charsEnd: 6, - truncate: 'middle' - })} - - {showCopy && ( - - )} - - {balance && ( - - {CoreHelperUtil.formatBalance(balance, balanceSymbol)} - - )} - {addressExplorerTemplate()} - - {isEmail && ( - <> - - - RouterController.push('UpdateEmailWallet', { email: getUserEmail() }) - } - chevron - testID="button-email" - > - {getUserEmail()} - - - )} - - - {caipNetwork?.name} - - - - Disconnect - - - - + + + ); } diff --git a/packages/scaffold/src/views/w3m-account-view/styles.ts b/packages/scaffold/src/views/w3m-account-view/styles.ts index 9aadd0d31..f373bdaa8 100644 --- a/packages/scaffold/src/views/w3m-account-view/styles.ts +++ b/packages/scaffold/src/views/w3m-account-view/styles.ts @@ -2,20 +2,18 @@ import { Spacing } from '@web3modal/ui-react-native'; import { StyleSheet } from 'react-native'; export default StyleSheet.create({ + networkIcon: { + alignSelf: 'flex-start', + position: 'absolute', + zIndex: 1, + top: Spacing.l, + left: Spacing.l + }, closeIcon: { alignSelf: 'flex-end', position: 'absolute', zIndex: 1, top: Spacing.l, right: Spacing.xl - }, - copyButton: { - marginLeft: Spacing['4xs'] - }, - networkButton: { - marginVertical: Spacing.xs - }, - upgradeButton: { - marginBottom: Spacing.s } }); diff --git a/packages/ui/src/assets/svg/ArrowBottomCircle.tsx b/packages/ui/src/assets/svg/ArrowBottomCircle.tsx new file mode 100644 index 000000000..4f19838a7 --- /dev/null +++ b/packages/ui/src/assets/svg/ArrowBottomCircle.tsx @@ -0,0 +1,12 @@ +import Svg, { Path, type SvgProps } from 'react-native-svg'; +const SvgArrowBottomCircle = (props: SvgProps) => ( + + + +); +export default SvgArrowBottomCircle; diff --git a/packages/ui/src/assets/svg/Paperplane.tsx b/packages/ui/src/assets/svg/Paperplane.tsx new file mode 100644 index 000000000..131e45f28 --- /dev/null +++ b/packages/ui/src/assets/svg/Paperplane.tsx @@ -0,0 +1,12 @@ +import Svg, { Path, type SvgProps } from 'react-native-svg'; +const SvgPaperplane = (props: SvgProps) => ( + + + +); +export default SvgPaperplane; diff --git a/packages/ui/src/components/wui-icon/index.tsx b/packages/ui/src/components/wui-icon/index.tsx index d34d7c9e7..f587e8628 100644 --- a/packages/ui/src/components/wui-icon/index.tsx +++ b/packages/ui/src/components/wui-icon/index.tsx @@ -5,6 +5,7 @@ import type { ColorType, IconType, SizeType, ThemeKeys } from '../../utils/Types import AllWalletsSvg from '../../assets/svg/AllWallets'; import AppleSvg from '../../assets/svg/Apple'; import ArrowBottomSvg from '../../assets/svg/ArrowBottom'; +import ArrowBottomCircleSvg from '../../assets/svg/ArrowBottomCircle'; import ArrowLeftSvg from '../../assets/svg/ArrowLeft'; import ArrowRightSvg from '../../assets/svg/ArrowRight'; import ArrowTopSvg from '../../assets/svg/ArrowTop'; @@ -39,6 +40,7 @@ import MobileSvg from '../../assets/svg/Mobile'; import NetworkPlaceholderSvg from '../../assets/svg/NetworkPlaceholder'; import NftPlaceholderSvg from '../../assets/svg/NftPlaceholder'; import OffSvg from '../../assets/svg/Off'; +import PaperplaneSvg from '../../assets/svg/Paperplane'; import QrCodeSvg from '../../assets/svg/QrCode'; import RefreshSvg from '../../assets/svg/Refresh'; import SearchSvg from '../../assets/svg/Search'; @@ -61,6 +63,7 @@ const svgOptions: Record JSX.Element> = { allWallets: AllWalletsSvg, apple: AppleSvg, arrowBottom: ArrowBottomSvg, + arrowBottomCircle: ArrowBottomCircleSvg, arrowLeft: ArrowLeftSvg, arrowRight: ArrowRightSvg, arrowTop: ArrowTopSvg, @@ -95,6 +98,7 @@ const svgOptions: Record JSX.Element> = { networkPlaceholder: NetworkPlaceholderSvg, nftPlaceholder: NftPlaceholderSvg, off: OffSvg, + paperplane: PaperplaneSvg, qrCode: QrCodeSvg, refresh: RefreshSvg, search: SearchSvg, diff --git a/packages/ui/src/composites/wui-network-button/index.tsx b/packages/ui/src/composites/wui-network-button/index.tsx index 11fe94841..b85600782 100644 --- a/packages/ui/src/composites/wui-network-button/index.tsx +++ b/packages/ui/src/composites/wui-network-button/index.tsx @@ -1,4 +1,4 @@ -import { Animated, Pressable, type StyleProp, type ViewStyle } from 'react-native'; +import { Animated, Pressable, View, type StyleProp, type ViewStyle } from 'react-native'; import { Image } from '../../components/wui-image'; import { Text } from '../../components/wui-text'; import { useTheme } from '../../hooks/useTheme'; @@ -11,37 +11,40 @@ import { LoadingSpinner } from '../../components/wui-loading-spinner'; const AnimatedPressable = Animated.createAnimatedComponent(Pressable); export interface NetworkButtonProps { - children: string; + children: string | React.ReactNode; onPress: () => void; + background?: boolean; + disabled?: boolean; imageSrc?: string; imageHeaders?: Record; - disabled?: boolean; - style?: StyleProp; loading?: boolean; + style?: StyleProp; } export function NetworkButton({ + children, + onPress, + background = true, + disabled, imageSrc, imageHeaders, - disabled, - onPress, - style, loading, - children + style }: NetworkButtonProps) { const Theme = useTheme(); const textColor = disabled ? 'fg-300' : 'fg-100'; const { animatedValue, setStartValue, setEndValue } = useAnimatedValue( - Theme['gray-glass-005'], + background ? Theme['gray-glass-005'] : 'transparent', Theme['gray-glass-010'] ); const backgroundColor = disabled ? Theme['gray-glass-015'] : animatedValue; + const borderColor = background ? Theme['gray-glass-005'] : 'transparent'; return ( - - {children} - + {typeof children === 'string' ? ( + + {children} + + ) : ( + {children} + )} ); } diff --git a/packages/ui/src/composites/wui-network-button/styles.ts b/packages/ui/src/composites/wui-network-button/styles.ts index eb36d0188..cdc38c9b9 100644 --- a/packages/ui/src/composites/wui-network-button/styles.ts +++ b/packages/ui/src/composites/wui-network-button/styles.ts @@ -11,7 +11,7 @@ export default StyleSheet.create({ borderRadius: 100, paddingHorizontal: Spacing['2xs'] }, - text: { + children: { paddingHorizontal: Spacing['2xs'] }, loader: { diff --git a/packages/ui/src/composites/wui-tabs/index.tsx b/packages/ui/src/composites/wui-tabs/index.tsx index f71114cb3..7514e823b 100644 --- a/packages/ui/src/composites/wui-tabs/index.tsx +++ b/packages/ui/src/composites/wui-tabs/index.tsx @@ -1,5 +1,12 @@ import { useRef, useState } from 'react'; -import { Animated, Pressable, View } from 'react-native'; +import { + Animated, + Pressable, + View, + type LayoutChangeEvent, + type StyleProp, + type ViewStyle +} from 'react-native'; import { Icon } from '../../components/wui-icon'; import { Text } from '../../components/wui-text'; import { useTheme } from '../../hooks/useTheme'; @@ -8,13 +15,16 @@ import styles from './styles'; export interface TabsProps { onTabChange: (index: number) => void; - tabs: TabOptionType[]; + tabs: TabOptionType[] | string[]; + style?: StyleProp; } -export function Tabs({ tabs, onTabChange }: TabsProps) { +export function Tabs({ tabs, onTabChange, style }: TabsProps) { const Theme = useTheme(); const [activeTab, setActiveTab] = useState(0); const animatedPosition = useRef(new Animated.Value(0)); + const [viewWidth, setViewWidth] = useState(1); + const tabWidth = Math.trunc(viewWidth / tabs.length) - 2; const onTabPress = (index: number) => { setActiveTab(index); @@ -28,27 +38,41 @@ export function Tabs({ tabs, onTabChange }: TabsProps) { const markPosition = animatedPosition.current.interpolate({ inputRange: [0, tabs.length - 1], - outputRange: [0, 100 * (tabs.length - 1)] + outputRange: [0, tabWidth * (tabs.length - 1)] }); + const onLayout = (event: LayoutChangeEvent) => { + const { width } = event.nativeEvent.layout; + setViewWidth(width); + }; + return ( - + {tabs.map((option, index) => { const isActive = index === activeTab; + const isString = typeof option === 'string'; return ( - onTabPress(index)} key={option.label} style={styles.tabItem}> - {option.icon && ( + onTabPress(index)} + key={isString ? option : option.label} + style={[styles.tabItem, { width: tabWidth }]} + > + {!isString && option.icon && ( )} - {option.label} + {isString ? option : option.label} ); diff --git a/packages/ui/src/composites/wui-tabs/styles.ts b/packages/ui/src/composites/wui-tabs/styles.ts index 7d1dca5de..6a81b4fb3 100644 --- a/packages/ui/src/composites/wui-tabs/styles.ts +++ b/packages/ui/src/composites/wui-tabs/styles.ts @@ -4,6 +4,7 @@ import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; export default StyleSheet.create({ container: { height: 34, + width: '100%', flexDirection: 'row', alignItems: 'center', paddingHorizontal: Spacing['3xs'], @@ -13,7 +14,6 @@ export default StyleSheet.create({ flexDirection: 'row', justifyContent: 'center', alignItems: 'center', - width: 100, paddingVertical: Spacing['2xs'] }, tabIcon: { @@ -22,7 +22,6 @@ export default StyleSheet.create({ activeMark: { position: 'absolute', height: 28, - width: 100, borderWidth: 1, borderRadius: BorderRadius['3xl'], margin: Spacing['3xs'] diff --git a/packages/ui/src/utils/TypesUtil.ts b/packages/ui/src/utils/TypesUtil.ts index 10a14ff0c..5bfe456ee 100644 --- a/packages/ui/src/utils/TypesUtil.ts +++ b/packages/ui/src/utils/TypesUtil.ts @@ -91,6 +91,7 @@ export type ColorType = | 'error-100' | 'fg-100' | 'fg-150' + | 'fg-175' | 'fg-200' | 'fg-250' | 'fg-275' @@ -112,6 +113,7 @@ export type IconType = | 'allWallets' | 'apple' | 'arrowBottom' + | 'arrowBottomCircle' | 'arrowLeft' | 'arrowRight' | 'arrowTop' @@ -146,6 +148,7 @@ export type IconType = | 'networkPlaceholder' | 'nftPlaceholder' | 'off' + | 'paperplane' | 'qrCode' | 'refresh' | 'search' @@ -193,7 +196,7 @@ export type CardSelectType = 'wallet' | 'network'; export type TabOptionType = { icon: IconType; - label: string; + label?: string; }; export type SpacingType = From 74d0a6d225fb4a0671560d170bc46a4121bda0bb Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 6 Aug 2024 15:20:12 -0300 Subject: [PATCH 002/114] chore: new account screen ui --- .../w3m-account-wallet-features/index.tsx | 8 +-- .../w3m-account-wallet-features/styles.ts | 9 +++ .../views/w3m-account-settings-view/index.tsx | 5 ++ .../views/w3m-account-settings-view/styles.ts | 7 ++ .../src/views/w3m-account-view/index.tsx | 34 ++++++++- .../src/views/w3m-account-view/styles.ts | 4 ++ .../partials/w3m-connecting-siwe/styles.ts | 7 +- .../composites/wui-account-button/styles.ts | 8 +-- .../src/composites/wui-account-pill/index.tsx | 72 +++++++++++++++++++ .../src/composites/wui-account-pill/styles.ts | 18 +++++ .../ui/src/composites/wui-avatar/styles.ts | 3 +- packages/ui/src/composites/wui-chip/styles.ts | 2 +- .../ui/src/composites/wui-list-item/styles.ts | 4 +- .../composites/wui-network-button/index.tsx | 4 +- .../composites/wui-network-button/styles.ts | 6 +- .../ui/src/composites/wui-snackbar/styles.ts | 3 +- packages/ui/src/index.ts | 1 + packages/ui/src/utils/ThemeUtil.ts | 3 +- 18 files changed, 174 insertions(+), 24 deletions(-) create mode 100644 packages/ui/src/composites/wui-account-pill/index.tsx create mode 100644 packages/ui/src/composites/wui-account-pill/styles.ts diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx index 1cb73c0e9..d4679e1af 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx @@ -33,7 +33,7 @@ export function AccountWalletFeatures() { background backgroundColor="accent-glass-010" pressedColor="accent-glass-020" - style={[styles.action, { marginRight: 8 }]} + style={[styles.action, styles.actionLeft]} /> {activeTab === 0 && } {activeTab === 1 && } diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts b/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts index 80388b3c2..75c000fb4 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts @@ -17,5 +17,14 @@ export default StyleSheet.create({ action: { flex: 1, height: 52 + }, + actionLeft: { + marginRight: 8 + }, + actionRight: { + marginLeft: 8 + }, + tabContainer: { + minHeight: 300 } }); diff --git a/packages/scaffold/src/views/w3m-account-settings-view/index.tsx b/packages/scaffold/src/views/w3m-account-settings-view/index.tsx index a486e61ea..30c24a0f7 100644 --- a/packages/scaffold/src/views/w3m-account-settings-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-settings-view/index.tsx @@ -36,10 +36,12 @@ export function AccountSettingsView() { const [disconnecting, setDisconnecting] = useState(false); const { caipNetwork } = useSnapshot(NetworkController.state); const { connectedConnector } = useSnapshot(ConnectorController.state); + const { history } = useSnapshot(RouterController.state); const networkImage = AssetUtil.getNetworkImage(caipNetwork); const showCopy = OptionsController.isClipboardAvailable(); const isEmail = connectedConnector === 'EMAIL'; const { padding } = useCustomDimensions(); + const showBack = history.length > 1; async function onDisconnect() { try { @@ -113,6 +115,9 @@ export function AccountSettingsView() { return ( <> + {showBack && ( + + )} diff --git a/packages/scaffold/src/views/w3m-account-settings-view/styles.ts b/packages/scaffold/src/views/w3m-account-settings-view/styles.ts index 9aadd0d31..44e6bdea2 100644 --- a/packages/scaffold/src/views/w3m-account-settings-view/styles.ts +++ b/packages/scaffold/src/views/w3m-account-settings-view/styles.ts @@ -2,6 +2,13 @@ import { Spacing } from '@web3modal/ui-react-native'; import { StyleSheet } from 'react-native'; export default StyleSheet.create({ + backIcon: { + alignSelf: 'flex-end', + position: 'absolute', + zIndex: 1, + top: Spacing.l, + left: Spacing.xl + }, closeIcon: { alignSelf: 'flex-end', position: 'absolute', diff --git a/packages/scaffold/src/views/w3m-account-view/index.tsx b/packages/scaffold/src/views/w3m-account-view/index.tsx index 498c5bfd4..9f6ba644a 100644 --- a/packages/scaffold/src/views/w3m-account-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-view/index.tsx @@ -1,11 +1,21 @@ import { useSnapshot } from 'valtio'; -import { FlexView, Icon, IconLink, NetworkButton, useTheme } from '@web3modal/ui-react-native'; import { + FlexView, + Icon, + IconLink, + NetworkButton, + AccountPill, + useTheme +} from '@web3modal/ui-react-native'; +import { + AccountController, ApiController, AssetUtil, ModalController, NetworkController, - RouterController + OptionsController, + RouterController, + SnackController } from '@web3modal/core-react-native'; import { AccountWalletFeatures } from '../../partials/w3m-account-wallet-features'; import styles from './styles'; @@ -13,6 +23,18 @@ import styles from './styles'; export function AccountView() { const Theme = useTheme(); const { caipNetwork } = useSnapshot(NetworkController.state); + const { address, profileName, profileImage } = useSnapshot(AccountController.state); + + const onCopyAddress = (value: string) => { + if (value) { + OptionsController.copyToClipboard(value); + SnackController.showSuccess('Address copied'); + } + }; + + const onProfilePress = () => { + RouterController.push('AccountSettings'); + }; const onNetworkPress = () => { RouterController.push('Networks'); @@ -31,6 +53,14 @@ export function AccountView() { + diff --git a/packages/scaffold/src/views/w3m-account-view/styles.ts b/packages/scaffold/src/views/w3m-account-view/styles.ts index f373bdaa8..24d8dc03b 100644 --- a/packages/scaffold/src/views/w3m-account-view/styles.ts +++ b/packages/scaffold/src/views/w3m-account-view/styles.ts @@ -15,5 +15,9 @@ export default StyleSheet.create({ zIndex: 1, top: Spacing.l, right: Spacing.xl + }, + accountPill: { + alignSelf: 'center', + marginBottom: Spacing.s } }); diff --git a/packages/siwe/src/scaffold/partials/w3m-connecting-siwe/styles.ts b/packages/siwe/src/scaffold/partials/w3m-connecting-siwe/styles.ts index d7391e1d6..863407547 100644 --- a/packages/siwe/src/scaffold/partials/w3m-connecting-siwe/styles.ts +++ b/packages/siwe/src/scaffold/partials/w3m-connecting-siwe/styles.ts @@ -1,10 +1,11 @@ +import { BorderRadius } from '@web3modal/ui-react-native'; import { StyleSheet } from 'react-native'; export default StyleSheet.create({ dappIcon: { height: 64, width: 64, - borderRadius: 100 + borderRadius: BorderRadius.full }, iconBorder: { width: 74, @@ -13,7 +14,7 @@ export default StyleSheet.create({ justifyContent: 'center' }, dappBorder: { - borderRadius: 100, + borderRadius: BorderRadius.full, zIndex: 2 }, walletBorder: { @@ -22,6 +23,6 @@ export default StyleSheet.create({ height: 72 }, walletAvatar: { - borderRadius: 100 + borderRadius: BorderRadius.full } }); diff --git a/packages/ui/src/composites/wui-account-button/styles.ts b/packages/ui/src/composites/wui-account-button/styles.ts index 61f2f0cf1..1eda1c32b 100644 --- a/packages/ui/src/composites/wui-account-button/styles.ts +++ b/packages/ui/src/composites/wui-account-button/styles.ts @@ -1,11 +1,11 @@ import { StyleSheet } from 'react-native'; -import { Spacing } from '../../utils/ThemeUtil'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; export default StyleSheet.create({ container: { flexDirection: 'row', height: 40, - borderRadius: 100, + borderRadius: BorderRadius.full, borderWidth: 1, justifyContent: 'center', alignItems: 'center', @@ -14,7 +14,7 @@ export default StyleSheet.create({ image: { height: 24, width: 24, - borderRadius: 100, + borderRadius: BorderRadius.full, borderWidth: 2 }, avatarPlaceholder: { @@ -35,7 +35,7 @@ export default StyleSheet.create({ alignItems: 'center', paddingLeft: Spacing['3xs'], paddingRight: Spacing.xs, - borderRadius: 100, + borderRadius: BorderRadius.full, borderWidth: 1 }, address: { diff --git a/packages/ui/src/composites/wui-account-pill/index.tsx b/packages/ui/src/composites/wui-account-pill/index.tsx new file mode 100644 index 000000000..de157c6f1 --- /dev/null +++ b/packages/ui/src/composites/wui-account-pill/index.tsx @@ -0,0 +1,72 @@ +import { Animated, Pressable, type StyleProp, type ViewStyle } from 'react-native'; +import { Avatar } from '../wui-avatar'; +import { UiUtil } from '../../utils/UiUtil'; +import { IconLink } from '../wui-icon-link'; +import { Text } from '../../components/wui-text'; +import useAnimatedValue from '../../hooks/useAnimatedValue'; +import { useTheme } from '../../hooks/useTheme'; +import styles from './styles'; + +export interface AccountPillProps { + onPress: () => void; + onCopy: (address: string) => void; + address?: string; + profileName?: string; + profileImage?: string; + style?: StyleProp; +} + +const AnimatedPressable = Animated.createAnimatedComponent(Pressable); + +export function AccountPill({ + onPress, + onCopy, + address, + profileName, + profileImage, + style +}: AccountPillProps) { + const Theme = useTheme(); + + const { animatedValue, setStartValue, setEndValue } = useAnimatedValue( + Theme['gray-glass-005'], + Theme['gray-glass-010'] + ); + + const backgroundColor = animatedValue; + const borderColor = Theme['gray-glass-005']; + + const handleCopyAddress = () => { + if (address) { + onCopy(address); + } + }; + + return ( + + + + {profileName + ? UiUtil.getTruncateString({ + string: profileName, + charsStart: 20, + charsEnd: 0, + truncate: 'end' + }) + : UiUtil.getTruncateString({ + string: address ?? '', + charsStart: 3, + charsEnd: 3, + truncate: 'middle' + })} + + + + ); +} diff --git a/packages/ui/src/composites/wui-account-pill/styles.ts b/packages/ui/src/composites/wui-account-pill/styles.ts new file mode 100644 index 000000000..e1a3df8d5 --- /dev/null +++ b/packages/ui/src/composites/wui-account-pill/styles.ts @@ -0,0 +1,18 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + container: { + height: 44, + width: 160, + paddingLeft: Spacing.xs, + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'center', + borderRadius: BorderRadius.full, + borderWidth: 1 + }, + text: { + marginLeft: Spacing['2xs'] + } +}); diff --git a/packages/ui/src/composites/wui-avatar/styles.ts b/packages/ui/src/composites/wui-avatar/styles.ts index b16828ba5..d3da925f9 100644 --- a/packages/ui/src/composites/wui-avatar/styles.ts +++ b/packages/ui/src/composites/wui-avatar/styles.ts @@ -1,7 +1,8 @@ import { StyleSheet } from 'react-native'; +import { BorderRadius } from '../../utils/ThemeUtil'; export default StyleSheet.create({ image: { - borderRadius: 100 + borderRadius: BorderRadius.full } }); diff --git a/packages/ui/src/composites/wui-chip/styles.ts b/packages/ui/src/composites/wui-chip/styles.ts index 5aad88507..1d7552b2e 100644 --- a/packages/ui/src/composites/wui-chip/styles.ts +++ b/packages/ui/src/composites/wui-chip/styles.ts @@ -68,7 +68,7 @@ export default StyleSheet.create({ borderWidth: 1 }, image: { - borderRadius: 100, + borderRadius: BorderRadius.full, borderWidth: 1 }, smImage: { diff --git a/packages/ui/src/composites/wui-list-item/styles.ts b/packages/ui/src/composites/wui-list-item/styles.ts index 4e12fe622..5c9e20768 100644 --- a/packages/ui/src/composites/wui-list-item/styles.ts +++ b/packages/ui/src/composites/wui-list-item/styles.ts @@ -18,7 +18,7 @@ export default StyleSheet.create({ imageContainer: { width: 36, height: 36, - borderRadius: 100, + borderRadius: BorderRadius.full, borderWidth: 2, alignItems: 'center', justifyContent: 'center' @@ -26,7 +26,7 @@ export default StyleSheet.create({ image: { width: 32, height: 32, - borderRadius: 100 + borderRadius: BorderRadius.full }, disabledImage: { opacity: 0.4 diff --git a/packages/ui/src/composites/wui-network-button/index.tsx b/packages/ui/src/composites/wui-network-button/index.tsx index b85600782..b5c37a01a 100644 --- a/packages/ui/src/composites/wui-network-button/index.tsx +++ b/packages/ui/src/composites/wui-network-button/index.tsx @@ -3,10 +3,10 @@ import { Image } from '../../components/wui-image'; import { Text } from '../../components/wui-text'; import { useTheme } from '../../hooks/useTheme'; import { IconBox } from '../wui-icon-box'; +import { LoadingSpinner } from '../../components/wui-loading-spinner'; +import useAnimatedValue from '../../hooks/useAnimatedValue'; import styles from './styles'; -import useAnimatedValue from '../../hooks/useAnimatedValue'; -import { LoadingSpinner } from '../../components/wui-loading-spinner'; const AnimatedPressable = Animated.createAnimatedComponent(Pressable); diff --git a/packages/ui/src/composites/wui-network-button/styles.ts b/packages/ui/src/composites/wui-network-button/styles.ts index cdc38c9b9..199f2d29b 100644 --- a/packages/ui/src/composites/wui-network-button/styles.ts +++ b/packages/ui/src/composites/wui-network-button/styles.ts @@ -1,5 +1,5 @@ import { StyleSheet } from 'react-native'; -import { Spacing } from '../../utils/ThemeUtil'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; export default StyleSheet.create({ container: { @@ -8,7 +8,7 @@ export default StyleSheet.create({ alignItems: 'center', justifyContent: 'center', borderWidth: 1, - borderRadius: 100, + borderRadius: BorderRadius.full, paddingHorizontal: Spacing['2xs'] }, children: { @@ -20,7 +20,7 @@ export default StyleSheet.create({ image: { height: 24, width: 24, - borderRadius: 100, + borderRadius: BorderRadius.full, borderWidth: 2, paddingLeft: Spacing['4xs'] }, diff --git a/packages/ui/src/composites/wui-snackbar/styles.ts b/packages/ui/src/composites/wui-snackbar/styles.ts index 1ef657b09..23c1b9bae 100644 --- a/packages/ui/src/composites/wui-snackbar/styles.ts +++ b/packages/ui/src/composites/wui-snackbar/styles.ts @@ -1,11 +1,12 @@ import { StyleSheet } from 'react-native'; +import { BorderRadius } from '../../utils/ThemeUtil'; export default StyleSheet.create({ container: { height: 40, flexDirection: 'row', paddingHorizontal: 8, - borderRadius: 100, + borderRadius: BorderRadius.full, borderWidth: 1, alignItems: 'center' }, diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 00c9a3ccf..e36caeebb 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -9,6 +9,7 @@ export { Visual, type VisualProps } from './components/wui-visual'; export { Shimmer, type ShimmerProps } from './components/wui-shimmer'; export { AccountButton, type AccountButtonProps } from './composites/wui-account-button'; +export { AccountPill, type AccountPillProps } from './composites/wui-account-pill'; export { ActionEntry, type ActionEntryProps } from './composites/wui-action-entry'; export { Avatar, type AvatarProps } from './composites/wui-avatar'; export { Button, type ButtonProps } from './composites/wui-button'; diff --git a/packages/ui/src/utils/ThemeUtil.ts b/packages/ui/src/utils/ThemeUtil.ts index 5fa3e4fb4..9c3fbcd7b 100644 --- a/packages/ui/src/utils/ThemeUtil.ts +++ b/packages/ui/src/utils/ThemeUtil.ts @@ -155,7 +155,8 @@ export const BorderRadius = { 's': 20, 'm': 28, 'l': 36, - '3xl': 80 + '3xl': 80, + 'full': 100 }; export const IconSize = { From 072c32698a4621368a651e1244fe31564c3cf4dc Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 6 Aug 2024 16:32:57 -0300 Subject: [PATCH 003/114] chore: show balance in account screen --- packages/common/src/index.ts | 1 + packages/common/src/utils/TypeUtil.ts | 15 ++++++++ .../controllers/AccountController.test.ts | 9 +++-- .../core/src/controllers/AccountController.ts | 36 ++++++++++++++++++- .../controllers/BlockchainApiController.ts | 19 ++++++++++ packages/core/src/utils/CoreHelperUtil.ts | 18 +++++++++- packages/core/src/utils/TypeUtil.ts | 6 ++++ .../w3m-account-wallet-features/index.tsx | 18 +++++----- .../src/views/w3m-account-view/index.tsx | 5 +++ .../src/composites/wui-account-pill/index.tsx | 6 ++-- .../src/composites/wui-account-pill/styles.ts | 3 +- .../ui/src/composites/wui-balance/index.tsx | 25 +++++++++++++ packages/ui/src/index.ts | 1 + 13 files changed, 145 insertions(+), 17 deletions(-) create mode 100644 packages/common/src/utils/TypeUtil.ts create mode 100644 packages/ui/src/composites/wui-balance/index.tsx diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index f27beb61e..96562da67 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,2 +1,3 @@ export { ConstantsUtil } from './utils/ConstantsUtil'; export { NetworkUtil } from './utils/NetworkUtil'; +export * from './utils/TypeUtil'; diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts new file mode 100644 index 000000000..ce2a40aa0 --- /dev/null +++ b/packages/common/src/utils/TypeUtil.ts @@ -0,0 +1,15 @@ +export interface Balance { + name: string; + symbol: string; + chainId: string; + address?: string; + value?: number; + price: number; + quantity: BalanceQuantity; + iconUrl: string; +} + +type BalanceQuantity = { + decimals: string; + numeric: string; +}; diff --git a/packages/core/src/__tests__/controllers/AccountController.test.ts b/packages/core/src/__tests__/controllers/AccountController.test.ts index c309f4755..2398d562a 100644 --- a/packages/core/src/__tests__/controllers/AccountController.test.ts +++ b/packages/core/src/__tests__/controllers/AccountController.test.ts @@ -7,10 +7,15 @@ const balanceSymbol = 'ETH'; const profileName = 'john.eth'; const profileImage = 'https://ipfs.com/0x123.png'; +const initialState = { + isConnected: false, + tokenBalance: [] +}; + // -- Tests -------------------------------------------------------------------- describe('AccountController', () => { it('should have valid default state', () => { - expect(AccountController.state).toEqual({ isConnected: false }); + expect(AccountController.state).toEqual(initialState); }); it('should update state correctly on setIsConnected()', () => { @@ -42,6 +47,6 @@ describe('AccountController', () => { it('should update state correctly on resetAccount()', () => { AccountController.resetAccount(); - expect(AccountController.state).toEqual({ isConnected: false }); + expect(AccountController.state).toEqual(initialState); }); }); diff --git a/packages/core/src/controllers/AccountController.ts b/packages/core/src/controllers/AccountController.ts index 2284a2810..bdbcc67c5 100644 --- a/packages/core/src/controllers/AccountController.ts +++ b/packages/core/src/controllers/AccountController.ts @@ -3,6 +3,10 @@ import { subscribeKey as subKey } from 'valtio/utils'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; import type { CaipAddress, ConnectedWalletInfo } from '../utils/TypeUtil'; +import type { Balance } from '@web3modal/common-react-native'; +import { NetworkController } from './NetworkController'; +import { BlockchainApiController } from './BlockchainApiController'; +import { SnackController } from './SnackController'; // -- Types --------------------------------------------- // export interface AccountControllerState { @@ -11,6 +15,7 @@ export interface AccountControllerState { address?: string; balance?: string; balanceSymbol?: string; + tokenBalance?: Balance[]; profileName?: string; profileImage?: string; addressExplorerUrl?: string; @@ -21,7 +26,8 @@ type StateKey = keyof AccountControllerState; // -- State --------------------------------------------- // const state = proxy({ - isConnected: false + isConnected: false, + tokenBalance: [] }); // -- Controller ---------------------------------------- // @@ -50,6 +56,10 @@ export const AccountController = { state.balanceSymbol = balanceSymbol; }, + setTokenBalance(tokenBalance: AccountControllerState['tokenBalance']) { + state.tokenBalance = tokenBalance; + }, + setProfileName(profileName: AccountControllerState['profileName']) { state.profileName = profileName; }, @@ -66,6 +76,29 @@ export const AccountController = { state.addressExplorerUrl = explorerUrl; }, + async fetchTokenBalance() { + const chainId = NetworkController.state.caipNetwork?.id; + const address = AccountController.state.address; + + try { + if (address && chainId) { + const response = await BlockchainApiController.getBalance(address, chainId); + + if (!response) { + throw new Error('Failed to fetch token balance'); + } + + const filteredBalances = response.balances.filter( + balance => balance.quantity.decimals !== '0' + ); + + this.setTokenBalance(filteredBalances); + } + } catch (error) { + SnackController.showError('Failed to fetch token balance'); + } + }, + resetAccount() { state.isConnected = false; state.caipAddress = undefined; @@ -75,5 +108,6 @@ export const AccountController = { state.profileName = undefined; state.profileImage = undefined; state.addressExplorerUrl = undefined; + state.tokenBalance = []; } }; diff --git a/packages/core/src/controllers/BlockchainApiController.ts b/packages/core/src/controllers/BlockchainApiController.ts index 3a1165062..6c480489e 100644 --- a/packages/core/src/controllers/BlockchainApiController.ts +++ b/packages/core/src/controllers/BlockchainApiController.ts @@ -3,6 +3,7 @@ import { proxy } from 'valtio'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; import { FetchUtil } from '../utils/FetchUtil'; import type { + BlockchainApiBalanceResponse, BlockchainApiIdentityRequest, BlockchainApiIdentityResponse } from '../utils/TypeUtil'; @@ -36,6 +37,24 @@ export const BlockchainApiController = { }); }, + async getBalance(address: string, chainId?: string, forceUpdate?: string) { + const { sdkType, sdkVersion } = OptionsController.state; + + return state.api.get({ + path: `/v1/account/${address}/balance`, + headers: { + 'x-sdk-type': sdkType, + 'x-sdk-version': sdkVersion + }, + params: { + currency: 'usd', + projectId: OptionsController.state.projectId, + chainId, + forceUpdate + } + }); + }, + setClientId(clientId: string | null) { state.clientId = clientId; state.api = new FetchUtil({ baseUrl, clientId }); diff --git a/packages/core/src/utils/CoreHelperUtil.ts b/packages/core/src/utils/CoreHelperUtil.ts index 781e93942..d1f8feae9 100644 --- a/packages/core/src/utils/CoreHelperUtil.ts +++ b/packages/core/src/utils/CoreHelperUtil.ts @@ -1,7 +1,7 @@ /* eslint-disable no-bitwise */ import { Linking, Platform } from 'react-native'; -import { ConstantsUtil as CommonConstants } from '@web3modal/common-react-native'; +import { ConstantsUtil as CommonConstants, type Balance } from '@web3modal/common-react-native'; import { ConstantsUtil } from './ConstantsUtil'; import type { CaipAddress, DataWallet, LinkingRecord } from './TypeUtil'; @@ -223,5 +223,21 @@ export const CoreHelperUtil = { .catch(reason => ({ status: 'rejected', reason })) ) ); + }, + + calculateAndFormatBalance(array?: Balance[]) { + if (!array || !array.length) { + return { dollars: '0', pennies: '00' }; + } + + let sum = 0; + for (const item of array) { + sum += item.value ?? 0; + } + + const roundedNumber = sum.toFixed(2); + const [dollars, pennies] = roundedNumber.split('.'); + + return { dollars, pennies }; } }; diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 0cd000e4d..c751e8769 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -1,3 +1,5 @@ +import type { Balance } from '@web3modal/common-react-native'; + export type CaipAddress = `${string}:${string}:${string}`; export type CaipNetworkId = `${string}:${string}`; @@ -113,6 +115,10 @@ export interface BlockchainApiIdentityResponse { name: string; } +export interface BlockchainApiBalanceResponse { + balances: Balance[]; +} + // -- OptionsController Types --------------------------------------------------- export interface Token { address: string; diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx index d4679e1af..1e8a63b3a 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx @@ -1,10 +1,13 @@ -import { View } from 'react-native'; -import { FlexView, IconLink, Tabs, Text } from '@web3modal/ui-react-native'; -import styles from './styles'; import { useState } from 'react'; +import { useSnapshot } from 'valtio'; +import { View } from 'react-native'; +import { Balance, FlexView, IconLink, Tabs } from '@web3modal/ui-react-native'; +import { AccountController, CoreHelperUtil } from '@web3modal/core-react-native'; +import type { Balance as BalanceType } from '@web3modal/common-react-native'; import { AccountNfts } from '../w3m-account-nfts'; import { AccountActivity } from '../w3m-account-activity'; import { AccountTokens } from '../w3m-account-tokens'; +import styles from './styles'; export interface AccountWalletFeaturesProps { value: string; @@ -12,6 +15,8 @@ export interface AccountWalletFeaturesProps { export function AccountWalletFeatures() { const [activeTab, setActiveTab] = useState(0); + const { tokenBalance } = useSnapshot(AccountController.state); + const balance = CoreHelperUtil.calculateAndFormatBalance(tokenBalance as BalanceType[]); const onTabChange = (index: number) => { setActiveTab(index); @@ -19,12 +24,7 @@ export function AccountWalletFeatures() { return ( - - $4798 - - .75 - - + { + AccountController.fetchTokenBalance(); + }, []); + return ( <> diff --git a/packages/ui/src/composites/wui-account-pill/styles.ts b/packages/ui/src/composites/wui-account-pill/styles.ts index e1a3df8d5..75d118b60 100644 --- a/packages/ui/src/composites/wui-account-pill/styles.ts +++ b/packages/ui/src/composites/wui-account-pill/styles.ts @@ -4,7 +4,8 @@ import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; export default StyleSheet.create({ container: { height: 44, - width: 160, + minWidth: 160, + maxWidth: 260, paddingLeft: Spacing.xs, flexDirection: 'row', alignItems: 'center', diff --git a/packages/ui/src/composites/wui-balance/index.tsx b/packages/ui/src/composites/wui-balance/index.tsx new file mode 100644 index 000000000..1f4a9a5ef --- /dev/null +++ b/packages/ui/src/composites/wui-balance/index.tsx @@ -0,0 +1,25 @@ +import { StyleSheet } from 'react-native'; +import { Text } from '../../components/wui-text'; + +export interface BalanceProps { + integer?: string; + decimal?: string; +} + +export function Balance({ integer = '0', decimal = '00' }: BalanceProps) { + return ( + + {`$${integer}`} + + {`.${decimal}`} + + + ); +} + +const styles = StyleSheet.create({ + text: { + fontSize: 40, + fontWeight: '500' + } +}); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index e36caeebb..36589043c 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -12,6 +12,7 @@ export { AccountButton, type AccountButtonProps } from './composites/wui-account export { AccountPill, type AccountPillProps } from './composites/wui-account-pill'; export { ActionEntry, type ActionEntryProps } from './composites/wui-action-entry'; export { Avatar, type AvatarProps } from './composites/wui-avatar'; +export { Balance, type BalanceProps } from './composites/wui-balance'; export { Button, type ButtonProps } from './composites/wui-button'; export { CardSelectLoader, From b012080c8622f06b2357372fb781ad3f55da4a74 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 6 Aug 2024 17:24:39 -0300 Subject: [PATCH 004/114] chore: added receive funds button in token tab --- .../stories/composites/ListItem.stories.tsx | 12 ------- .../composites/NetworkButton.stories.tsx | 12 +------ .../w3m-account-placeholder/index.tsx | 5 ++- .../src/partials/w3m-account-tokens/index.tsx | 19 ++++++++---- .../w3m-account-wallet-features/index.tsx | 7 +---- .../views/w3m-account-settings-view/index.tsx | 31 +++++++------------ .../ui/src/composites/wui-list-item/index.tsx | 23 +++++++------- packages/ui/src/utils/TypesUtil.ts | 10 +++++- 8 files changed, 51 insertions(+), 68 deletions(-) diff --git a/apps/gallery/stories/composites/ListItem.stories.tsx b/apps/gallery/stories/composites/ListItem.stories.tsx index 9177d919c..8b53f2e77 100644 --- a/apps/gallery/stories/composites/ListItem.stories.tsx +++ b/apps/gallery/stories/composites/ListItem.stories.tsx @@ -10,18 +10,10 @@ const meta: Meta = { imageSrc: { control: { type: 'text' } }, - variant: { - options: ['image', 'icon'], - control: { type: 'select' } - }, icon: { options: iconOptions, control: { type: 'select' } }, - iconVariant: { - options: ['blue', 'overlay'], - control: { type: 'select' } - }, disabled: { control: { type: 'boolean' } }, @@ -33,10 +25,8 @@ const meta: Meta = { } }, args: { - variant: 'image', imageSrc: networkImageSrc, icon: 'swapHorizontal', - iconVariant: 'blue', disabled: false, chevron: true, loading: false @@ -50,10 +40,8 @@ export const Default: Story = { render: (args: any) => ( = { component: NetworkButton, argTypes: { - variant: { - options: ['fill', 'shade'], - control: { type: 'select' } - }, disabled: { control: { type: 'boolean' } }, @@ -21,7 +17,6 @@ const meta: Meta = { } }, args: { - variant: 'fill', disabled: false, children: 'Ethereum', imageSrc: networkImageSrc @@ -33,12 +28,7 @@ type Story = StoryObj; export const Default: Story = { render: args => ( - {}} - > + {}}> {args.children} ) diff --git a/packages/scaffold/src/partials/w3m-account-placeholder/index.tsx b/packages/scaffold/src/partials/w3m-account-placeholder/index.tsx index 8c0974c9c..0c6eb5b17 100644 --- a/packages/scaffold/src/partials/w3m-account-placeholder/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-placeholder/index.tsx @@ -9,7 +9,7 @@ interface Props { export function AccountPlaceholder({ icon, title, description }: Props) { return ( - + {title} @@ -22,6 +22,9 @@ export function AccountPlaceholder({ icon, title, description }: Props) { } const styles = StyleSheet.create({ + container: { + flex: 1 + }, title: { marginTop: Spacing.xl, marginBottom: Spacing.xs diff --git a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx index 9ed0ec60c..940d4c757 100644 --- a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx @@ -1,11 +1,18 @@ -import { AccountPlaceholder } from '../w3m-account-placeholder'; +import { useSnapshot } from 'valtio'; +import { AccountController } from '@web3modal/core-react-native'; +import { FlexView, ListItem, Text } from '@web3modal/ui-react-native'; export function AccountTokens() { return ( - + + + + Receive funds + + + Transfer tokens on your wallet + + + ); } diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx index 1e8a63b3a..7419c01a4 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx @@ -46,12 +46,7 @@ export function AccountWalletFeatures() { /> - + {activeTab === 0 && } {activeTab === 1 && } {activeTab === 2 && } diff --git a/packages/scaffold/src/views/w3m-account-settings-view/index.tsx b/packages/scaffold/src/views/w3m-account-settings-view/index.tsx index 30c24a0f7..560aed996 100644 --- a/packages/scaffold/src/views/w3m-account-settings-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-settings-view/index.tsx @@ -62,6 +62,13 @@ export function AccountSettingsView() { } } + const getUserEmail = () => { + const provider = ConnectorController.getEmailConnector()?.provider as W3mFrameProvider; + if (!provider) return ''; + + return provider.getEmail(); + }; + const onExplorerPress = () => { if (addressExplorerUrl) { Linking.openURL(addressExplorerUrl); @@ -89,11 +96,8 @@ export function AccountSettingsView() { RouterController.push('UpgradeEmailWallet'); }; - const getUserEmail = () => { - const provider = ConnectorController.getEmailConnector()?.provider as W3mFrameProvider; - if (!provider) return ''; - - return provider.getEmail(); + const onEmailPress = () => { + RouterController.push('UpdateEmailWallet', { email: getUserEmail() }); }; const addressExplorerTemplate = () => { @@ -158,25 +162,15 @@ export function AccountSettingsView() { {isEmail && ( <> - - RouterController.push('UpdateEmailWallet', { email: getUserEmail() }) - } - chevron - testID="button-email" - > + {getUserEmail()} )} Disconnect diff --git a/packages/ui/src/composites/wui-list-item/index.tsx b/packages/ui/src/composites/wui-list-item/index.tsx index 98791b650..ddc177ac5 100644 --- a/packages/ui/src/composites/wui-list-item/index.tsx +++ b/packages/ui/src/composites/wui-list-item/index.tsx @@ -5,7 +5,7 @@ import { Image } from '../../components/wui-image'; import { LoadingSpinner } from '../../components/wui-loading-spinner'; import useAnimatedValue from '../../hooks/useAnimatedValue'; import { useTheme } from '../../hooks/useTheme'; -import type { IconType } from '../../utils/TypesUtil'; +import type { ColorType, IconType } from '../../utils/TypesUtil'; import { IconBox } from '../wui-icon-box'; import styles from './styles'; @@ -13,8 +13,9 @@ const AnimatedPressable = Animated.createAnimatedComponent(Pressable); export interface ListItemProps { icon?: IconType; - iconVariant?: 'blue' | 'overlay'; - variant?: 'image' | 'icon'; + iconColor?: ColorType; + iconBackgroundColor?: ColorType; + iconBorderColor?: ColorType; imageSrc?: string; imageHeaders?: Record; chevron?: boolean; @@ -29,10 +30,11 @@ export interface ListItemProps { export function ListItem({ children, icon, - variant, imageSrc, imageHeaders, - iconVariant = 'blue', + iconColor = 'fg-200', + iconBackgroundColor, + iconBorderColor = 'gray-glass-005', chevron, loading, disabled, @@ -47,7 +49,7 @@ export function ListItem({ ); function visualTemplate() { - if (variant === 'image' && imageSrc) { + if (imageSrc) { return ( ); - } else if (variant === 'icon' && icon) { - const iconColor = iconVariant === 'blue' ? 'accent-100' : 'fg-200'; - const borderColor = iconVariant === 'blue' ? 'accent-glass-005' : 'gray-glass-005'; - + } else if (icon) { return ( - + ); diff --git a/packages/ui/src/utils/TypesUtil.ts b/packages/ui/src/utils/TypesUtil.ts index 5bfe456ee..3399506f4 100644 --- a/packages/ui/src/utils/TypesUtil.ts +++ b/packages/ui/src/utils/TypesUtil.ts @@ -97,9 +97,17 @@ export type ColorType = | 'fg-275' | 'fg-300' | 'gray-glass-020' + | 'gray-glass-010' + | 'gray-glass-005' | 'inverse-000' | 'inverse-100' - | 'success-100'; + | 'success-100' + | 'teal-100' + | 'magenta-100' + | 'indigo-100' + | 'orange-100' + | 'purple-100' + | 'yellow-100'; export type SizeType = 'xl' | 'lg' | 'md' | 'sm' | 'xs' | 'xxs'; From 71b6d6f52333ee04927eea99984a66f81434ca93 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 7 Aug 2024 10:51:27 -0300 Subject: [PATCH 005/114] chore: show wallet screen if its email login --- packages/core/src/controllers/ModalController.ts | 4 +++- packages/scaffold/src/partials/w3m-account-tokens/index.tsx | 2 -- packages/ui/src/composites/wui-snackbar/index.tsx | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core/src/controllers/ModalController.ts b/packages/core/src/controllers/ModalController.ts index 3da4daf4c..763079f0d 100644 --- a/packages/core/src/controllers/ModalController.ts +++ b/packages/core/src/controllers/ModalController.ts @@ -5,6 +5,7 @@ import { RouterController } from './RouterController'; import { PublicStateController } from './PublicStateController'; import { EventsController } from './EventsController'; import { ApiController } from './ApiController'; +import { ConnectorController } from './ConnectorController'; // -- Types --------------------------------------------- // export interface ModalControllerState { @@ -34,7 +35,8 @@ export const ModalController = { if (options?.view) { RouterController.reset(options.view); } else if (AccountController.state.isConnected) { - RouterController.reset('Account'); + const isWallet = ConnectorController.state.connectedConnector === 'EMAIL'; + RouterController.reset(isWallet ? 'Account' : 'AccountSettings'); } else { RouterController.reset('Connect'); } diff --git a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx index 940d4c757..57254abeb 100644 --- a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx @@ -1,5 +1,3 @@ -import { useSnapshot } from 'valtio'; -import { AccountController } from '@web3modal/core-react-native'; import { FlexView, ListItem, Text } from '@web3modal/ui-react-native'; export function AccountTokens() { diff --git a/packages/ui/src/composites/wui-snackbar/index.tsx b/packages/ui/src/composites/wui-snackbar/index.tsx index de00e6546..299e79e5f 100644 --- a/packages/ui/src/composites/wui-snackbar/index.tsx +++ b/packages/ui/src/composites/wui-snackbar/index.tsx @@ -17,6 +17,7 @@ export function Snackbar({ message, iconColor, icon, style }: SnackbarProps) { return ( Date: Wed, 7 Aug 2024 13:05:40 -0300 Subject: [PATCH 006/114] chore: updated account default view, show wallet view for email login --- .../core/src/controllers/ModalController.ts | 2 +- .../core/src/controllers/RouterController.ts | 2 +- packages/scaffold/src/index.ts | 2 +- .../scaffold/src/modal/w3m-router/index.tsx | 6 +-- .../src/partials/w3m-account-tokens/index.tsx | 8 +++- .../w3m-account-wallet-features/index.tsx | 9 ++++- .../src/partials/w3m-header/index.tsx | 2 +- .../components/upgrade-wallet-button.tsx | 0 .../index.tsx | 40 +++++++++---------- .../styles.ts | 0 .../src/views/w3m-account-view/index.tsx | 2 +- 11 files changed, 41 insertions(+), 32 deletions(-) rename packages/scaffold/src/views/{w3m-account-settings-view => w3m-account-default-view}/components/upgrade-wallet-button.tsx (100%) rename packages/scaffold/src/views/{w3m-account-settings-view => w3m-account-default-view}/index.tsx (91%) rename packages/scaffold/src/views/{w3m-account-settings-view => w3m-account-default-view}/styles.ts (100%) diff --git a/packages/core/src/controllers/ModalController.ts b/packages/core/src/controllers/ModalController.ts index 763079f0d..5bbc51021 100644 --- a/packages/core/src/controllers/ModalController.ts +++ b/packages/core/src/controllers/ModalController.ts @@ -36,7 +36,7 @@ export const ModalController = { RouterController.reset(options.view); } else if (AccountController.state.isConnected) { const isWallet = ConnectorController.state.connectedConnector === 'EMAIL'; - RouterController.reset(isWallet ? 'Account' : 'AccountSettings'); + RouterController.reset(isWallet ? 'Account' : 'AccountDefault'); } else { RouterController.reset('Connect'); } diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index 88962e0a5..271bc8cc6 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -5,7 +5,7 @@ import type { WcWallet, CaipNetwork, Connector } from '../utils/TypeUtil'; export interface RouterControllerState { view: | 'Account' - | 'AccountSettings' + | 'AccountDefault' | 'Connect' | 'ConnectingWalletConnect' | 'ConnectingExternal' diff --git a/packages/scaffold/src/index.ts b/packages/scaffold/src/index.ts index 907b9dc96..e26590664 100644 --- a/packages/scaffold/src/index.ts +++ b/packages/scaffold/src/index.ts @@ -5,7 +5,7 @@ export * from './modal/w3m-network-button'; export * from './modal/w3m-modal'; export * from './modal/w3m-router'; -export * from './views/w3m-account-settings-view'; +export * from './views/w3m-account-default-view'; export * from './views/w3m-all-wallets-view'; export * from './views/w3m-connect-view'; export * from './views/w3m-connecting-view'; diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/scaffold/src/modal/w3m-router/index.tsx index 37f58f0c8..d0dee6a05 100644 --- a/packages/scaffold/src/modal/w3m-router/index.tsx +++ b/packages/scaffold/src/modal/w3m-router/index.tsx @@ -8,7 +8,7 @@ import { AllWalletsView } from '../../views/w3m-all-wallets-view'; import { ConnectingView } from '../../views/w3m-connecting-view'; import { WhatIsAWalletView } from '../../views/w3m-what-is-a-wallet-view'; import { GetWalletView } from '../../views/w3m-get-wallet-view'; -import { AccountSettingsView } from '../../views/w3m-account-settings-view'; +import { AccountDefaultView } from '../../views/w3m-account-default-view'; import { NetworksView } from '../../views/w3m-networks-view'; import { WhatIsNetworkView } from '../../views/w3m-what-is-a-network-view'; import { NetworkSwitchView } from '../../views/w3m-network-switch-view'; @@ -51,8 +51,8 @@ export function Web3Router() { return NetworkSwitchView; case 'Account': return AccountView; - case 'AccountSettings': - return AccountSettingsView; + case 'AccountDefault': + return AccountDefaultView; case 'EmailVerifyDevice': return EmailVerifyDeviceView; case 'EmailVerifyOtp': diff --git a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx index 57254abeb..046a1ec63 100644 --- a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx @@ -1,8 +1,14 @@ +import { SnackController } from '@web3modal/core-react-native'; import { FlexView, ListItem, Text } from '@web3modal/ui-react-native'; export function AccountTokens() { + // TODO: Implement this feature + const onMissingPress = () => { + SnackController.showError('Feature not implemented'); + }; + return ( - + Receive funds diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx index 7419c01a4..b7b6f147f 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx @@ -2,7 +2,7 @@ import { useState } from 'react'; import { useSnapshot } from 'valtio'; import { View } from 'react-native'; import { Balance, FlexView, IconLink, Tabs } from '@web3modal/ui-react-native'; -import { AccountController, CoreHelperUtil } from '@web3modal/core-react-native'; +import { AccountController, CoreHelperUtil, SnackController } from '@web3modal/core-react-native'; import type { Balance as BalanceType } from '@web3modal/common-react-native'; import { AccountNfts } from '../w3m-account-nfts'; import { AccountActivity } from '../w3m-account-activity'; @@ -22,6 +22,11 @@ export function AccountWalletFeatures() { setActiveTab(index); }; + // TODO: Implement this features + const onMissingPress = () => { + SnackController.showError('Feature not implemented'); + }; + return ( @@ -34,6 +39,7 @@ export function AccountWalletFeatures() { backgroundColor="accent-glass-010" pressedColor="accent-glass-020" style={[styles.action, styles.actionLeft]} + onPress={onMissingPress} /> diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index c4cc1a9a5..82277e295 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -18,7 +18,7 @@ export function Header() { return { Connect: 'Connect wallet', Account: undefined, - AccountSettings: undefined, + AccountDefault: undefined, ConnectingWalletConnect: walletName ?? 'WalletConnect', ConnectingExternal: connectorName ?? 'Connect wallet', ConnectingSiwe: 'Sign In', diff --git a/packages/scaffold/src/views/w3m-account-settings-view/components/upgrade-wallet-button.tsx b/packages/scaffold/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx similarity index 100% rename from packages/scaffold/src/views/w3m-account-settings-view/components/upgrade-wallet-button.tsx rename to packages/scaffold/src/views/w3m-account-default-view/components/upgrade-wallet-button.tsx diff --git a/packages/scaffold/src/views/w3m-account-settings-view/index.tsx b/packages/scaffold/src/views/w3m-account-default-view/index.tsx similarity index 91% rename from packages/scaffold/src/views/w3m-account-settings-view/index.tsx rename to packages/scaffold/src/views/w3m-account-default-view/index.tsx index 560aed996..5402251ed 100644 --- a/packages/scaffold/src/views/w3m-account-settings-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-default-view/index.tsx @@ -30,7 +30,7 @@ import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import { UpgradeWalletButton } from './components/upgrade-wallet-button'; import styles from './styles'; -export function AccountSettingsView() { +export function AccountDefaultView() { const { address, profileName, profileImage, balance, balanceSymbol, addressExplorerUrl } = useSnapshot(AccountController.state); const [disconnecting, setDisconnecting] = useState(false); @@ -40,8 +40,10 @@ export function AccountSettingsView() { const networkImage = AssetUtil.getNetworkImage(caipNetwork); const showCopy = OptionsController.isClipboardAvailable(); const isEmail = connectedConnector === 'EMAIL'; - const { padding } = useCustomDimensions(); + const showBalance = balance && !isEmail; + const showExplorer = addressExplorerUrl && !isEmail; const showBack = history.length > 1; + const { padding } = useCustomDimensions(); async function onDisconnect() { try { @@ -100,23 +102,6 @@ export function AccountSettingsView() { RouterController.push('UpdateEmailWallet', { email: getUserEmail() }); }; - const addressExplorerTemplate = () => { - if (!addressExplorerUrl) return null; - - return ( - - ); - }; - return ( <> {showBack && ( @@ -146,18 +131,29 @@ export function AccountSettingsView() { )} - {balance && ( + {showBalance && ( {CoreHelperUtil.formatBalance(balance, balanceSymbol)} )} - {addressExplorerTemplate()} + {showExplorer && ( + + )} {isEmail && ( <> diff --git a/packages/scaffold/src/views/w3m-account-settings-view/styles.ts b/packages/scaffold/src/views/w3m-account-default-view/styles.ts similarity index 100% rename from packages/scaffold/src/views/w3m-account-settings-view/styles.ts rename to packages/scaffold/src/views/w3m-account-default-view/styles.ts diff --git a/packages/scaffold/src/views/w3m-account-view/index.tsx b/packages/scaffold/src/views/w3m-account-view/index.tsx index 86d0934a4..a787b7659 100644 --- a/packages/scaffold/src/views/w3m-account-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-view/index.tsx @@ -34,7 +34,7 @@ export function AccountView() { }; const onProfilePress = () => { - RouterController.push('AccountSettings'); + RouterController.push('AccountDefault'); }; const onNetworkPress = () => { From a6fad43e6ae2a268acf9500232988c40ae9b9307 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 7 Aug 2024 14:27:42 -0300 Subject: [PATCH 007/114] chore: added changeset --- .changeset/stupid-guests-turn.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .changeset/stupid-guests-turn.md diff --git a/.changeset/stupid-guests-turn.md b/.changeset/stupid-guests-turn.md new file mode 100644 index 000000000..302af6e20 --- /dev/null +++ b/.changeset/stupid-guests-turn.md @@ -0,0 +1,18 @@ +--- +'@web3modal/scaffold-react-native': minor +'@web3modal/common-react-native': minor +'@web3modal/email-react-native': minor +'@web3modal/core-react-native': minor +'@web3modal/siwe-react-native': minor +'@web3modal/ui-react-native': minor +'@web3modal/coinbase-ethers-react-native': minor +'@web3modal/coinbase-wagmi-react-native': minor +'@web3modal/email-ethers-react-native': minor +'@web3modal/email-wagmi-react-native': minor +'@web3modal/ethers-react-native': minor +'@web3modal/ethers5-react-native': minor +'@web3modal/scaffold-utils-react-native': minor +'@web3modal/wagmi-react-native': minor +--- + +feat: implemented wallet features for universal wallets From 08853cf74a38360569d18b52cb99621ab8b20181 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 7 Aug 2024 17:58:07 -0300 Subject: [PATCH 008/114] chore: limit width of platform tab in connecting view --- .../partials/w3m-connecting-header/index.tsx | 37 +++++++++++++------ 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/scaffold/src/partials/w3m-connecting-header/index.tsx b/packages/scaffold/src/partials/w3m-connecting-header/index.tsx index 02fc1c59f..2d010293d 100644 --- a/packages/scaffold/src/partials/w3m-connecting-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-connecting-header/index.tsx @@ -1,22 +1,31 @@ import type { Platform } from '@web3modal/core-react-native'; -import { FlexView, Tabs } from '@web3modal/ui-react-native'; +import { FlexView, Tabs, type IconType } from '@web3modal/ui-react-native'; +import { StyleSheet } from 'react-native'; export interface ConnectingHeaderProps { platforms: Platform[]; onSelectPlatform: (platform: Platform) => void; } +interface Tab { + label: string; + icon: IconType; + platform: Platform; +} + export function ConnectingHeader({ platforms, onSelectPlatform }: ConnectingHeaderProps) { const generateTabs = () => { - const tabs = platforms.map(platform => { - if (platform === 'mobile') { - return { label: 'Mobile', icon: 'mobile', platform: 'mobile' } as const; - } else if (platform === 'web') { - return { label: 'Web', icon: 'browser', platform: 'web' } as const; - } else { - return { label: 'QR Code', icon: 'qrCode', platform: 'qrcode' } as const; - } - }); + const tabs = platforms + .map(platform => { + if (platform === 'mobile') { + return { label: 'Mobile', icon: 'mobile', platform: 'mobile' } as const; + } else if (platform === 'web') { + return { label: 'Web', icon: 'browser', platform: 'web' } as const; + } else { + return undefined; + } + }) + .filter(Boolean) as Tab[]; return tabs; }; @@ -32,7 +41,13 @@ export function ConnectingHeader({ platforms, onSelectPlatform }: ConnectingHead return ( - + ); } + +const styles = StyleSheet.create({ + tab: { + maxWidth: '50%' + } +}); From 0b3d8c056c1e8a566a9c5fc5c59c501edd7ef476 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 8 Aug 2024 16:25:29 -0300 Subject: [PATCH 009/114] feat: wip receive screen --- .../stories/composites/Chip.stories.tsx | 7 +-- apps/gallery/utils/PresetUtils.ts | 2 - .../core/src/controllers/OptionsController.ts | 2 +- .../core/src/controllers/RouterController.ts | 3 +- .../scaffold/src/modal/w3m-router/index.tsx | 3 + .../src/partials/w3m-account-tokens/index.tsx | 9 ++- .../w3m-account-wallet-features/index.tsx | 13 ++++- .../src/partials/w3m-header/index.tsx | 3 +- .../src/views/w3m-account-view/index.tsx | 2 +- .../w3m-upgrade-email-wallet-view/index.tsx | 11 +++- .../views/w3m-wallet-receive-view/index.tsx | 55 +++++++++++++++++++ .../views/w3m-wallet-receive-view/styles.ts | 8 +++ .../src/composites/wui-account-pill/index.tsx | 2 +- packages/ui/src/composites/wui-chip/index.tsx | 16 +++--- .../ui/src/composites/wui-qr-code/index.tsx | 23 ++++---- packages/ui/src/utils/UiUtil.ts | 18 ++++++ 16 files changed, 136 insertions(+), 41 deletions(-) create mode 100644 packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx create mode 100644 packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts diff --git a/apps/gallery/stories/composites/Chip.stories.tsx b/apps/gallery/stories/composites/Chip.stories.tsx index ae1768fd7..6cdcfe045 100644 --- a/apps/gallery/stories/composites/Chip.stories.tsx +++ b/apps/gallery/stories/composites/Chip.stories.tsx @@ -5,7 +5,6 @@ import { Chip } from '@web3modal/ui-react-native'; import { chipOptions, externalLabel, - externalLink, iconOptions, walletImagesOptions } from '../../utils/PresetUtils'; @@ -24,9 +23,6 @@ const meta: Meta = { disabled: { control: { type: 'boolean' } }, - link: { - control: { type: 'text' } - }, label: { control: { type: 'text' } }, @@ -43,7 +39,6 @@ const meta: Meta = { size: 'md', disabled: false, icon: 'disconnect', - link: externalLink, label: externalLabel, imageSrc: walletImagesOptions[3] } @@ -58,7 +53,7 @@ export const Default: Story = { variant={args.variant} size={args.size} disabled={args.disabled} - link={args.link} + onPress={() => {}} label={args.label} imageSrc={args.imageSrc} icon={args.icon} diff --git a/apps/gallery/utils/PresetUtils.ts b/apps/gallery/utils/PresetUtils.ts index 22328477b..8cddbeea7 100644 --- a/apps/gallery/utils/PresetUtils.ts +++ b/apps/gallery/utils/PresetUtils.ts @@ -32,8 +32,6 @@ export const avatarImageSrc = export const wcUri = 'wc:139520827546986d057472f8bbd7ef0484409458034b61cca59d908563773c7a@2?relay-protocol=irn&symKey=43b5fad11bf07bc8a0aa12231435a4ad3e72e2d1fa257cf191a90ec5b62cb0a'; -export const externalLink = 'https://www.fireblocks.com'; - export const externalLabel = 'www.fireblocks.com'; export const colorOptions: ColorType[] = [ diff --git a/packages/core/src/controllers/OptionsController.ts b/packages/core/src/controllers/OptionsController.ts index 018867c9d..e590dee3a 100644 --- a/packages/core/src/controllers/OptionsController.ts +++ b/packages/core/src/controllers/OptionsController.ts @@ -83,7 +83,7 @@ export const OptionsController = { copyToClipboard(value: string) { const client = state._clipboardClient; if (client) { - client.setString(value); + client?.setString(value); } } }; diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index 271bc8cc6..8bbd2531a 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -21,7 +21,8 @@ export interface RouterControllerState { | 'UpdateEmailPrimaryOtp' | 'UpdateEmailSecondaryOtp' | 'UpgradeEmailWallet' - | 'ConnectingSiwe'; + | 'ConnectingSiwe' + | 'WalletReceive'; history: RouterControllerState['view'][]; data?: { connector?: Connector; diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/scaffold/src/modal/w3m-router/index.tsx index d0dee6a05..8c8759f84 100644 --- a/packages/scaffold/src/modal/w3m-router/index.tsx +++ b/packages/scaffold/src/modal/w3m-router/index.tsx @@ -21,6 +21,7 @@ import { UpdateEmailPrimaryOtpView } from '../../views/w3m-update-email-primary- import { UpdateEmailSecondaryOtpView } from '../../views/w3m-update-email-secondary-otp-view'; import { UpgradeEmailWalletView } from '../../views/w3m-upgrade-email-wallet-view'; import { AccountView } from '../../views/w3m-account-view'; +import { WalletReceiveView } from '../../views/w3m-wallet-receive-view'; export function Web3Router() { const { view } = useSnapshot(RouterController.state); @@ -67,6 +68,8 @@ export function Web3Router() { return UpgradeEmailWalletView; case 'ConnectingSiwe': return ConnectingSiweView; + case 'WalletReceive': + return WalletReceiveView; default: return ConnectView; } diff --git a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx index 046a1ec63..6ed3f7af8 100644 --- a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx @@ -1,14 +1,13 @@ -import { SnackController } from '@web3modal/core-react-native'; +import { RouterController } from '@web3modal/core-react-native'; import { FlexView, ListItem, Text } from '@web3modal/ui-react-native'; export function AccountTokens() { - // TODO: Implement this feature - const onMissingPress = () => { - SnackController.showError('Feature not implemented'); + const onReceivePress = () => { + RouterController.push('WalletReceive'); }; return ( - + Receive funds diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx index b7b6f147f..991beb004 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx @@ -2,7 +2,12 @@ import { useState } from 'react'; import { useSnapshot } from 'valtio'; import { View } from 'react-native'; import { Balance, FlexView, IconLink, Tabs } from '@web3modal/ui-react-native'; -import { AccountController, CoreHelperUtil, SnackController } from '@web3modal/core-react-native'; +import { + AccountController, + CoreHelperUtil, + RouterController, + SnackController +} from '@web3modal/core-react-native'; import type { Balance as BalanceType } from '@web3modal/common-react-native'; import { AccountNfts } from '../w3m-account-nfts'; import { AccountActivity } from '../w3m-account-activity'; @@ -27,6 +32,10 @@ export function AccountWalletFeatures() { SnackController.showError('Feature not implemented'); }; + const onReceivePress = () => { + RouterController.push('WalletReceive'); + }; + return ( @@ -39,7 +48,7 @@ export function AccountWalletFeatures() { backgroundColor="accent-glass-010" pressedColor="accent-glass-020" style={[styles.action, styles.actionLeft]} - onPress={onMissingPress} + onPress={onReceivePress} /> { - if (value) { + if (OptionsController.isClipboardAvailable() && value) { OptionsController.copyToClipboard(value); SnackController.showSuccess('Address copied'); } diff --git a/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx index cf425e014..e79f32e05 100644 --- a/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx +++ b/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { StyleSheet } from 'react-native'; +import { Linking, StyleSheet } from 'react-native'; import { Chip, FlexView, Spacing, Text } from '@web3modal/ui-react-native'; import { ConnectorController, type W3mFrameProvider } from '@web3modal/core-react-native'; @@ -7,6 +7,13 @@ export function UpgradeEmailWalletView() { const { connectors } = useSnapshot(ConnectorController.state); const emailProvider = connectors.find(c => c.type === 'EMAIL')?.provider as W3mFrameProvider; + const onLinkPress = () => { + const link = emailProvider.getSecureSiteDashboardURL(); + Linking.canOpenURL(link).then(supported => { + if (supported) Linking.openURL(link); + }); + }; + return ( Follow the instructions on @@ -14,7 +21,7 @@ export function UpgradeEmailWalletView() { label="secure.walletconnect.com" icon="externalLink" imageSrc={emailProvider.getSecureSiteIconURL()} - link={emailProvider.getSecureSiteDashboardURL()} + onPress={onLinkPress} style={styles.chip} /> diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx new file mode 100644 index 000000000..e2560122f --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx @@ -0,0 +1,55 @@ +import { useSnapshot } from 'valtio'; +import { StyleSheet } from 'react-native'; +import { Chip, FlexView, QrCode, Spacing, Text, UiUtil } from '@web3modal/ui-react-native'; +import { + AccountController, + AssetUtil, + NetworkController, + OptionsController, + SnackController +} from '@web3modal/core-react-native'; + +export function WalletReceiveView() { + const { address, profileName } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const canCopy = OptionsController.isClipboardAvailable(); + + const label = UiUtil.getTruncateString({ + string: profileName ?? address ?? '', + charsStart: profileName ? 30 : 4, + charsEnd: profileName ? 0 : 4, + truncate: profileName ? 'end' : 'middle' + }); + + const onCopyAddress = () => { + if (canCopy && address) { + OptionsController.copyToClipboard(profileName ?? address); + SnackController.showSuccess('Address copied'); + } + }; + + if (!address) return; + + return ( + + + + + {canCopy ? 'Copy your address or scan this QR code' : 'Scan this QR code'} + + + ); +} + +const styles = StyleSheet.create({ + qrContainer: { + marginVertical: Spacing.xl + } +}); diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts new file mode 100644 index 000000000..c866ab3d8 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-receive-view/styles.ts @@ -0,0 +1,8 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center' + } +}); diff --git a/packages/ui/src/composites/wui-account-pill/index.tsx b/packages/ui/src/composites/wui-account-pill/index.tsx index bd66cd9cf..07f76d56e 100644 --- a/packages/ui/src/composites/wui-account-pill/index.tsx +++ b/packages/ui/src/composites/wui-account-pill/index.tsx @@ -55,7 +55,7 @@ export function AccountPill({ {profileName ? UiUtil.getTruncateString({ string: profileName, - charsStart: 18, + charsStart: 17, charsEnd: 0, truncate: 'end' }) diff --git a/packages/ui/src/composites/wui-chip/index.tsx b/packages/ui/src/composites/wui-chip/index.tsx index 76b381c0b..ed3764545 100644 --- a/packages/ui/src/composites/wui-chip/index.tsx +++ b/packages/ui/src/composites/wui-chip/index.tsx @@ -1,5 +1,5 @@ import { useRef, useState } from 'react'; -import { Animated, Linking, Pressable, type StyleProp, type ViewStyle } from 'react-native'; +import { Animated, Pressable, type StyleProp, type ViewStyle } from 'react-native'; import type { ChipType, ColorType, IconType, SizeType } from '../../utils/TypesUtil'; import { useTheme } from '../../hooks/useTheme'; import { Text } from '../../components/wui-text'; @@ -10,7 +10,6 @@ import styles, { getThemedChipStyle, getThemedTextColor } from './styles'; const AnimatedPressable = Animated.createAnimatedComponent(Pressable); export interface ChipProps { - link: string; label?: string; imageSrc?: string; icon?: IconType; @@ -18,10 +17,11 @@ export interface ChipProps { size?: Exclude; disabled?: boolean; style?: StyleProp; + onPress?: () => void; } export function Chip({ - link, + onPress, imageSrc, icon, variant = 'fill', @@ -38,10 +38,8 @@ export function Chip({ const themedTextColor = getThemedTextColor(variant, disabled, pressed); const iconSize = size === 'md' ? 'sm' : 'xs'; - const onPress = () => { - Linking.canOpenURL(link).then(supported => { - if (supported) Linking.openURL(link); - }); + const handlePress = () => { + onPress?.(); }; const onPressIn = () => { @@ -78,7 +76,7 @@ export function Chip({ style={[styles.container, styles[`${size}Chip`], { borderColor, backgroundColor }, style]} onPressIn={onPressIn} onPressOut={onPressOut} - onPress={onPress} + onPress={handlePress} > {imageSrc && ( - {label || link} + {label} {icon && ( ; } -export function QrCode({ size, uri, imageSrc, testID }: QrCodeProps) { +export function QrCode({ size, uri, imageSrc, testID, arenaClear, style }: QrCodeProps) { const Theme = LightTheme; const containerPadding = Spacing.l; const qrSize = size - containerPadding * 2; const dots = useMemo( - () => (uri ? QRCodeUtil.generate(uri, qrSize, qrSize / 4) : []), - [uri, qrSize] + () => (uri ? QRCodeUtil.generate(uri, qrSize, arenaClear ? 0 : qrSize / 4) : []), + [uri, qrSize, arenaClear] ); - const shimmerTemplate = () => { - return ; - }; - const logoTemplate = () => { + if (arenaClear) { + return null; + } + if (imageSrc) { return ( @@ -66,6 +69,6 @@ export function QrCode({ size, uri, imageSrc, testID }: QrCodeProps) { {logoTemplate()} ) : ( - shimmerTemplate() + ); } diff --git a/packages/ui/src/utils/UiUtil.ts b/packages/ui/src/utils/UiUtil.ts index e4678f6b2..3077dfbfb 100644 --- a/packages/ui/src/utils/UiUtil.ts +++ b/packages/ui/src/utils/UiUtil.ts @@ -66,6 +66,24 @@ export const UiUtil = { )}`; }, + getTruncateAddress(address: string, profileName?: string) { + if (profileName) { + return this.getTruncateString({ + string: profileName, + charsStart: 20, + charsEnd: 0, + truncate: 'end' + }); + } + + return this.getTruncateString({ + string: address ?? '', + charsStart: 4, + charsEnd: 4, + truncate: 'middle' + }); + }, + getWalletName(name: string, short = true) { return short ? name.split(' ')[0] : name; } From 632466814072d96d30e6f4fc3f44329968e40553 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 8 Aug 2024 18:36:17 -0300 Subject: [PATCH 010/114] chore: receive screen --- .../core/src/controllers/NetworkController.ts | 6 +++ .../views/w3m-wallet-receive-view/index.tsx | 22 ++++++++- .../wui-compatible-network/index.tsx | 46 +++++++++++++++++++ .../ui/src/composites/wui-list-item/index.tsx | 4 +- .../ui/src/composites/wui-list-item/styles.ts | 3 +- .../composites/wui-network-image/index.tsx | 38 +++++++++++---- .../composites/wui-network-image/styles.ts | 3 ++ packages/ui/src/index.ts | 4 ++ packages/ui/src/utils/UiUtil.ts | 18 -------- 9 files changed, 113 insertions(+), 31 deletions(-) create mode 100644 packages/ui/src/composites/wui-compatible-network/index.tsx diff --git a/packages/core/src/controllers/NetworkController.ts b/packages/core/src/controllers/NetworkController.ts index f917efa7b..1468cba9f 100644 --- a/packages/core/src/controllers/NetworkController.ts +++ b/packages/core/src/controllers/NetworkController.ts @@ -63,6 +63,12 @@ export const NetworkController = { state.approvedCaipNetworkIds = data.approvedCaipNetworkIds; }, + getApprovedCaipNetworks() { + return state.approvedCaipNetworkIds + ?.map(id => state.requestedCaipNetworks?.find(network => network.id === id)) + .filter(Boolean) as CaipNetwork[]; + }, + async switchActiveNetwork(network: NetworkControllerState['caipNetwork']) { await this._getClient().switchCaipNetwork(network); state.caipNetwork = network; diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx index e2560122f..86651810d 100644 --- a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx @@ -1,8 +1,17 @@ import { useSnapshot } from 'valtio'; import { StyleSheet } from 'react-native'; -import { Chip, FlexView, QrCode, Spacing, Text, UiUtil } from '@web3modal/ui-react-native'; +import { + Chip, + CompatibleNetwork, + FlexView, + QrCode, + Spacing, + Text, + UiUtil +} from '@web3modal/ui-react-native'; import { AccountController, + ApiController, AssetUtil, NetworkController, OptionsController, @@ -14,6 +23,11 @@ export function WalletReceiveView() { const { caipNetwork } = useSnapshot(NetworkController.state); const networkImage = AssetUtil.getNetworkImage(caipNetwork); const canCopy = OptionsController.isClipboardAvailable(); + const slicedNetworks = + NetworkController.getApprovedCaipNetworks() + .filter(network => network?.imageId) + ?.slice(0, 5) || []; + const imagesArray = slicedNetworks.map(AssetUtil.getNetworkImage).filter(Boolean) as string[]; const label = UiUtil.getTruncateString({ string: profileName ?? address ?? '', @@ -44,6 +58,12 @@ export function WalletReceiveView() { {canCopy ? 'Copy your address or scan this QR code' : 'Scan this QR code'} + {}} + networkImages={imagesArray} + imageHeaders={ApiController._getApiHeaders()} + /> ); } diff --git a/packages/ui/src/composites/wui-compatible-network/index.tsx b/packages/ui/src/composites/wui-compatible-network/index.tsx new file mode 100644 index 000000000..80de42dc5 --- /dev/null +++ b/packages/ui/src/composites/wui-compatible-network/index.tsx @@ -0,0 +1,46 @@ +import { Text } from '../../components/wui-text'; +import { NetworkImage } from '../wui-network-image'; +import { FlexView } from '../../layout/wui-flex'; +import { ListItem } from '../wui-list-item'; +import { StyleSheet } from 'react-native'; + +export interface CompatibleNetworkProps { + text: string; + onPress: () => void; + networkImages: string[]; + imageHeaders: Record; +} + +export function CompatibleNetwork({ + text, + onPress, + networkImages, + imageHeaders +}: CompatibleNetworkProps) { + return ( + + + {text} + + + {networkImages?.map((image, index) => ( + + ))} + + + ); +} + +const styles = StyleSheet.create({ + container: { + height: 48 + }, + contentContainer: { + flexDirection: 'row' + } +}); diff --git a/packages/ui/src/composites/wui-list-item/index.tsx b/packages/ui/src/composites/wui-list-item/index.tsx index ddc177ac5..5f9c66818 100644 --- a/packages/ui/src/composites/wui-list-item/index.tsx +++ b/packages/ui/src/composites/wui-list-item/index.tsx @@ -24,6 +24,7 @@ export interface ListItemProps { onPress?: () => void; children?: ReactNode; style?: StyleProp; + contentStyle?: StyleProp; testID?: string; } @@ -40,6 +41,7 @@ export function ListItem({ disabled, onPress, style, + contentStyle, testID }: ListItemProps) { const Theme = useTheme(); @@ -100,7 +102,7 @@ export function ListItem({ testID={testID} > {visualTemplate()} - {children} + {children} {rightTemplate()} ); diff --git a/packages/ui/src/composites/wui-list-item/styles.ts b/packages/ui/src/composites/wui-list-item/styles.ts index 5c9e20768..f7c9d79a7 100644 --- a/packages/ui/src/composites/wui-list-item/styles.ts +++ b/packages/ui/src/composites/wui-list-item/styles.ts @@ -13,7 +13,8 @@ export default StyleSheet.create({ content: { flexDirection: 'row', flexGrow: 1, - paddingHorizontal: Spacing.s + paddingHorizontal: Spacing.s, + alignItems: 'center' }, imageContainer: { width: 36, diff --git a/packages/ui/src/composites/wui-network-image/index.tsx b/packages/ui/src/composites/wui-network-image/index.tsx index 4dd561ecf..68af27ec4 100644 --- a/packages/ui/src/composites/wui-network-image/index.tsx +++ b/packages/ui/src/composites/wui-network-image/index.tsx @@ -1,37 +1,55 @@ import { Path, Svg, Image, Defs, Pattern } from 'react-native-svg'; import { useTheme } from '../../hooks/useTheme'; import type { SizeType } from '../../utils/TypesUtil'; -import { PathLg, PathNormal } from './styles'; +import { PathLg, PathNormal, PathSmall } from './styles'; +import type { StyleProp, ViewStyle } from 'react-native'; export interface NetworkImageProps { imageSrc?: string; imageHeaders?: Record; selected?: boolean; - size?: Exclude; + size?: Exclude; disabled?: boolean; + style?: StyleProp; } +const sizeToPath = { + lg: PathLg, + md: PathNormal, + sm: PathSmall +}; + +const sizeToHeight = { + lg: 96, + md: 56, + sm: 20 +}; + export function NetworkImage({ imageSrc, imageHeaders, disabled, selected, - size = 'md' + size = 'md', + style }: NetworkImageProps) { const Theme = useTheme(); - const isLg = size === 'lg'; - const svgWidth = isLg ? 96 : 56; - const svgHeight = isLg ? 96 : 56; const svgStroke = selected ? Theme['accent-100'] : Theme['gray-glass-010']; const opacity = disabled ? 0.5 : 1; return ( - + diff --git a/packages/ui/src/composites/wui-network-image/styles.ts b/packages/ui/src/composites/wui-network-image/styles.ts index 7041b19ce..837967d06 100644 --- a/packages/ui/src/composites/wui-network-image/styles.ts +++ b/packages/ui/src/composites/wui-network-image/styles.ts @@ -3,3 +3,6 @@ export const PathLg = export const PathNormal = 'M24.0002 2.34328C26.4754 0.914219 29.525 0.914219 32.0002 2.34328L48.2489 11.7245C50.7241 13.1535 52.2489 15.7946 52.2489 18.6527V37.4151C52.2489 40.2732 50.7241 42.9142 48.2489 44.3433L32.0002 53.7245C29.525 55.1535 26.4754 55.1535 24.0002 53.7245L7.75146 44.3433C5.27625 42.9142 3.75146 40.2732 3.75146 37.4151V18.6527C3.75146 15.7946 5.27626 13.1535 7.75146 11.7245L24.0002 2.34328Z'; + +export const PathSmall = + 'M8.57153 0.836886C9.45553 0.326507 10.5447 0.326507 11.4287 0.836886L17.2318 4.18731C18.1158 4.69769 18.6604 5.64091 18.6604 6.66167V13.3625C18.6604 14.3833 18.1158 15.3265 17.2318 15.8369L11.4287 19.1873C10.5447 19.6977 9.45553 19.6977 8.57153 19.1873L2.76841 15.8369C1.88441 15.3265 1.33984 14.3833 1.33984 13.3625V6.66167C1.33984 5.64091 1.88441 4.69769 2.76841 4.18731L8.57153 0.836886Z'; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 36589043c..f78370045 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -26,6 +26,10 @@ export { type CardSelectProps } from './composites/wui-card-select'; export { Chip, type ChipProps } from './composites/wui-chip'; +export { + CompatibleNetwork, + type CompatibleNetworkProps +} from './composites/wui-compatible-network'; export { ConnectButton, type ConnectButtonProps } from './composites/wui-connect-button'; export { EmailInput, type EmailInputProps } from './composites/wui-email-input'; export { IconBox, type IconBoxProps } from './composites/wui-icon-box'; diff --git a/packages/ui/src/utils/UiUtil.ts b/packages/ui/src/utils/UiUtil.ts index 3077dfbfb..e4678f6b2 100644 --- a/packages/ui/src/utils/UiUtil.ts +++ b/packages/ui/src/utils/UiUtil.ts @@ -66,24 +66,6 @@ export const UiUtil = { )}`; }, - getTruncateAddress(address: string, profileName?: string) { - if (profileName) { - return this.getTruncateString({ - string: profileName, - charsStart: 20, - charsEnd: 0, - truncate: 'end' - }); - } - - return this.getTruncateString({ - string: address ?? '', - charsStart: 4, - charsEnd: 4, - truncate: 'middle' - }); - }, - getWalletName(name: string, short = true) { return short ? name.split(' ')[0] : name; } From 5b38658eb94cae9b7e0cf26d3673eed3cfee8c85 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 9 Aug 2024 13:27:07 -0300 Subject: [PATCH 011/114] chore: network button ui improvements --- .../views/w3m-wallet-receive-view/index.tsx | 4 +++ .../wui-compatible-network/index.tsx | 28 +++++++++++++++---- .../composites/wui-network-image/index.tsx | 10 +++++-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx index 86651810d..ceb3096ff 100644 --- a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx @@ -63,6 +63,7 @@ export function WalletReceiveView() { onPress={() => {}} networkImages={imagesArray} imageHeaders={ApiController._getApiHeaders()} + style={styles.networksButton} /> ); @@ -71,5 +72,8 @@ export function WalletReceiveView() { const styles = StyleSheet.create({ qrContainer: { marginVertical: Spacing.xl + }, + networksButton: { + marginTop: Spacing.l } }); diff --git a/packages/ui/src/composites/wui-compatible-network/index.tsx b/packages/ui/src/composites/wui-compatible-network/index.tsx index 80de42dc5..6ff7436d4 100644 --- a/packages/ui/src/composites/wui-compatible-network/index.tsx +++ b/packages/ui/src/composites/wui-compatible-network/index.tsx @@ -2,25 +2,33 @@ import { Text } from '../../components/wui-text'; import { NetworkImage } from '../wui-network-image'; import { FlexView } from '../../layout/wui-flex'; import { ListItem } from '../wui-list-item'; -import { StyleSheet } from 'react-native'; +import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; +import { useTheme } from '../../hooks/useTheme'; export interface CompatibleNetworkProps { text: string; onPress: () => void; networkImages: string[]; imageHeaders: Record; + style?: StyleProp; } +const offset = [20, 15, 10, 5, 0]; +const zIndex = [5, 4, 3, 2, 1]; + export function CompatibleNetwork({ text, onPress, networkImages, - imageHeaders + imageHeaders, + style }: CompatibleNetworkProps) { + const Theme = useTheme(); + return ( @@ -29,7 +37,15 @@ export function CompatibleNetwork({ {networkImages?.map((image, index) => ( - + ))} @@ -41,6 +57,8 @@ const styles = StyleSheet.create({ height: 48 }, contentContainer: { - flexDirection: 'row' + flexDirection: 'row', + justifyContent: 'space-between', + paddingRight: 0 } }); diff --git a/packages/ui/src/composites/wui-network-image/index.tsx b/packages/ui/src/composites/wui-network-image/index.tsx index 68af27ec4..9324caff9 100644 --- a/packages/ui/src/composites/wui-network-image/index.tsx +++ b/packages/ui/src/composites/wui-network-image/index.tsx @@ -11,6 +11,8 @@ export interface NetworkImageProps { size?: Exclude; disabled?: boolean; style?: StyleProp; + borderColor?: string; + borderWidth?: number; } const sizeToPath = { @@ -31,7 +33,9 @@ export function NetworkImage({ disabled, selected, size = 'md', - style + style, + borderColor, + borderWidth = 1 }: NetworkImageProps) { const Theme = useTheme(); const svgStroke = selected ? Theme['accent-100'] : Theme['gray-glass-010']; @@ -41,8 +45,8 @@ export function NetworkImage({ From 422d4978c9ab732a5ce8deaee93bd7ae1d6a112e Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 9 Aug 2024 14:33:59 -0300 Subject: [PATCH 012/114] chore: wrapped new views in scrollview + landscape --- .../src/views/w3m-account-view/index.tsx | 8 +++- .../views/w3m-wallet-receive-view/index.tsx | 46 ++++++++++--------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/packages/scaffold/src/views/w3m-account-view/index.tsx b/packages/scaffold/src/views/w3m-account-view/index.tsx index c43cbc48e..faedff7c6 100644 --- a/packages/scaffold/src/views/w3m-account-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-view/index.tsx @@ -20,9 +20,12 @@ import { } from '@web3modal/core-react-native'; import { AccountWalletFeatures } from '../../partials/w3m-account-wallet-features'; import styles from './styles'; +import { ScrollView } from 'react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; export function AccountView() { const Theme = useTheme(); + const { padding } = useCustomDimensions(); const { caipNetwork } = useSnapshot(NetworkController.state); const { address, profileName, profileImage } = useSnapshot(AccountController.state); @@ -46,7 +49,7 @@ export function AccountView() { }, []); return ( - <> + + - + ); } diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx index ceb3096ff..222f9f3e7 100644 --- a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { StyleSheet } from 'react-native'; +import { ScrollView, StyleSheet } from 'react-native'; import { Chip, CompatibleNetwork, @@ -17,11 +17,13 @@ import { OptionsController, SnackController } from '@web3modal/core-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; export function WalletReceiveView() { const { address, profileName } = useSnapshot(AccountController.state); const { caipNetwork } = useSnapshot(NetworkController.state); const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const { padding } = useCustomDimensions(); const canCopy = OptionsController.isClipboardAvailable(); const slicedNetworks = NetworkController.getApprovedCaipNetworks() @@ -46,26 +48,28 @@ export function WalletReceiveView() { if (!address) return; return ( - - - - - {canCopy ? 'Copy your address or scan this QR code' : 'Scan this QR code'} - - {}} - networkImages={imagesArray} - imageHeaders={ApiController._getApiHeaders()} - style={styles.networksButton} - /> - + + + + + + {canCopy ? 'Copy your address or scan this QR code' : 'Scan this QR code'} + + {}} + networkImages={imagesArray} + imageHeaders={ApiController._getApiHeaders()} + style={styles.networksButton} + /> + + ); } From 5322428db4fa1b93352c91d825a3059356b29571 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 9 Aug 2024 15:40:11 -0300 Subject: [PATCH 013/114] chore: added network view + network image placeholder --- .../core/src/controllers/RouterController.ts | 3 +- .../scaffold/src/modal/w3m-router/index.tsx | 3 ++ .../src/partials/w3m-header/index.tsx | 3 +- .../index.tsx | 40 ++++++++++++++ .../styles.ts | 8 +++ .../views/w3m-wallet-receive-view/index.tsx | 7 ++- .../ui/src/composites/wui-banner/index.tsx | 28 ++++++++++ .../ui/src/composites/wui-banner/styles.ts | 15 ++++++ .../wui-compatible-network/index.tsx | 2 +- .../composites/wui-network-image/index.tsx | 52 ++++++++++++------- .../composites/wui-network-image/styles.ts | 3 ++ packages/ui/src/index.ts | 1 + 12 files changed, 142 insertions(+), 23 deletions(-) create mode 100644 packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx create mode 100644 packages/scaffold/src/views/w3m-wallet-compatible-networks-view/styles.ts create mode 100644 packages/ui/src/composites/wui-banner/index.tsx create mode 100644 packages/ui/src/composites/wui-banner/styles.ts diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index 8bbd2531a..b48c85f3b 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -22,7 +22,8 @@ export interface RouterControllerState { | 'UpdateEmailSecondaryOtp' | 'UpgradeEmailWallet' | 'ConnectingSiwe' - | 'WalletReceive'; + | 'WalletReceive' + | 'WalletCompatibleNetworks'; history: RouterControllerState['view'][]; data?: { connector?: Connector; diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/scaffold/src/modal/w3m-router/index.tsx index 8c8759f84..e0b800691 100644 --- a/packages/scaffold/src/modal/w3m-router/index.tsx +++ b/packages/scaffold/src/modal/w3m-router/index.tsx @@ -22,6 +22,7 @@ import { UpdateEmailSecondaryOtpView } from '../../views/w3m-update-email-second import { UpgradeEmailWalletView } from '../../views/w3m-upgrade-email-wallet-view'; import { AccountView } from '../../views/w3m-account-view'; import { WalletReceiveView } from '../../views/w3m-wallet-receive-view'; +import { WalletCompatibleNetworks } from '../../views/w3m-wallet-compatible-networks-view'; export function Web3Router() { const { view } = useSnapshot(RouterController.state); @@ -70,6 +71,8 @@ export function Web3Router() { return ConnectingSiweView; case 'WalletReceive': return WalletReceiveView; + case 'WalletCompatibleNetworks': + return WalletCompatibleNetworks; default: return ConnectView; } diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index ea7c5d211..bab98729e 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -34,7 +34,8 @@ export function Header() { UpdateEmailPrimaryOtp: 'Confirm current email', UpdateEmailSecondaryOtp: 'Confirm new email', UpgradeEmailWallet: 'Upgrade wallet', - WalletReceive: 'Receive' + WalletReceive: 'Receive', + WalletCompatibleNetworks: 'Compatible networks' }; }; diff --git a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx new file mode 100644 index 000000000..1748bad00 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx @@ -0,0 +1,40 @@ +import { ScrollView } from 'react-native'; +import { FlexView, Text, Banner, NetworkImage } from '@web3modal/ui-react-native'; +import { ApiController, AssetUtil, NetworkController } from '@web3modal/core-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function WalletCompatibleNetworks() { + const { padding } = useCustomDimensions(); + const approvedNetworks = NetworkController.getApprovedCaipNetworks(); + const imageHeaders = ApiController._getApiHeaders(); + + return ( + + + + {approvedNetworks.map(network => ( + + + + {network.name} + + + ))} + + + ); +} diff --git a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/styles.ts new file mode 100644 index 000000000..e4423cecd --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/styles.ts @@ -0,0 +1,8 @@ +import { Spacing } from '@web3modal/ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + image: { + marginRight: Spacing.s + } +}); diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx index 222f9f3e7..874a727b4 100644 --- a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx @@ -15,6 +15,7 @@ import { AssetUtil, NetworkController, OptionsController, + RouterController, SnackController } from '@web3modal/core-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; @@ -38,6 +39,10 @@ export function WalletReceiveView() { truncate: profileName ? 'end' : 'middle' }); + const onNetworkPress = () => { + RouterController.push('WalletCompatibleNetworks'); + }; + const onCopyAddress = () => { if (canCopy && address) { OptionsController.copyToClipboard(profileName ?? address); @@ -63,7 +68,7 @@ export function WalletReceiveView() { {}} + onPress={onNetworkPress} networkImages={imagesArray} imageHeaders={ApiController._getApiHeaders()} style={styles.networksButton} diff --git a/packages/ui/src/composites/wui-banner/index.tsx b/packages/ui/src/composites/wui-banner/index.tsx new file mode 100644 index 000000000..dfeb02bd3 --- /dev/null +++ b/packages/ui/src/composites/wui-banner/index.tsx @@ -0,0 +1,28 @@ +import type { IconType } from '../../utils/TypesUtil'; +import { FlexView } from '../../layout/wui-flex'; +import { IconBox } from '../wui-icon-box'; +import { Text } from '../../components/wui-text'; +import { useTheme } from '../../hooks/useTheme'; +import styles from './styles'; + +export interface BannerProps { + icon: IconType; + text: string; +} + +export function Banner({ icon, text }: BannerProps) { + const Theme = useTheme(); + + return ( + + + + {text} + + + ); +} diff --git a/packages/ui/src/composites/wui-banner/styles.ts b/packages/ui/src/composites/wui-banner/styles.ts new file mode 100644 index 000000000..9504601ba --- /dev/null +++ b/packages/ui/src/composites/wui-banner/styles.ts @@ -0,0 +1,15 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + container: { + padding: Spacing.s, + borderRadius: BorderRadius.s + }, + icon: { + marginRight: Spacing.xs + }, + text: { + flex: 1 + } +}); diff --git a/packages/ui/src/composites/wui-compatible-network/index.tsx b/packages/ui/src/composites/wui-compatible-network/index.tsx index 6ff7436d4..943ee852b 100644 --- a/packages/ui/src/composites/wui-compatible-network/index.tsx +++ b/packages/ui/src/composites/wui-compatible-network/index.tsx @@ -38,7 +38,7 @@ export function CompatibleNetwork({ {networkImages?.map((image, index) => ( ; selected?: boolean; - size?: Exclude; + size?: Exclude; disabled?: boolean; style?: StyleProp; borderColor?: string; @@ -18,13 +20,15 @@ export interface NetworkImageProps { const sizeToPath = { lg: PathLg, md: PathNormal, - sm: PathSmall + sm: PathSmall, + xs: PathXS }; const sizeToHeight = { lg: 96, md: 56, - sm: 20 + sm: 40, + xs: 20 }; export function NetworkImage({ @@ -50,22 +54,32 @@ export function NetworkImage({ style={style} > - - + + {imageSrc ? ( + + ) : ( + + + + )} - + {!imageSrc && } + ); } diff --git a/packages/ui/src/composites/wui-network-image/styles.ts b/packages/ui/src/composites/wui-network-image/styles.ts index 837967d06..dbd445cae 100644 --- a/packages/ui/src/composites/wui-network-image/styles.ts +++ b/packages/ui/src/composites/wui-network-image/styles.ts @@ -5,4 +5,7 @@ export const PathNormal = 'M24.0002 2.34328C26.4754 0.914219 29.525 0.914219 32.0002 2.34328L48.2489 11.7245C50.7241 13.1535 52.2489 15.7946 52.2489 18.6527V37.4151C52.2489 40.2732 50.7241 42.9142 48.2489 44.3433L32.0002 53.7245C29.525 55.1535 26.4754 55.1535 24.0002 53.7245L7.75146 44.3433C5.27625 42.9142 3.75146 40.2732 3.75146 37.4151V18.6527C3.75146 15.7946 5.27626 13.1535 7.75146 11.7245L24.0002 2.34328Z'; export const PathSmall = + 'M17.1428 1.67377C18.9108 0.653013 21.0891 0.653014 22.8571 1.67377L34.4633 8.37463C36.2313 9.39539 37.3205 11.2818 37.3205 13.3233V26.7251C37.3205 28.7666 36.2313 30.653 34.4633 31.6738L22.8571 38.3746C21.0891 39.3954 18.9108 39.3954 17.1428 38.3746L5.53659 31.6738C3.76858 30.653 2.67944 28.7666 2.67944 26.7251V13.3233C2.67944 11.2818 3.76858 9.39539 5.53659 8.37463L17.1428 1.67377Z'; + +export const PathXS = 'M8.57153 0.836886C9.45553 0.326507 10.5447 0.326507 11.4287 0.836886L17.2318 4.18731C18.1158 4.69769 18.6604 5.64091 18.6604 6.66167V13.3625C18.6604 14.3833 18.1158 15.3265 17.2318 15.8369L11.4287 19.1873C10.5447 19.6977 9.45553 19.6977 8.57153 19.1873L2.76841 15.8369C1.88441 15.3265 1.33984 14.3833 1.33984 13.3625V6.66167C1.33984 5.64091 1.88441 4.69769 2.76841 4.18731L8.57153 0.836886Z'; diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index f78370045..1f601734b 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -13,6 +13,7 @@ export { AccountPill, type AccountPillProps } from './composites/wui-account-pil export { ActionEntry, type ActionEntryProps } from './composites/wui-action-entry'; export { Avatar, type AvatarProps } from './composites/wui-avatar'; export { Balance, type BalanceProps } from './composites/wui-balance'; +export { Banner, type BannerProps } from './composites/wui-banner'; export { Button, type ButtonProps } from './composites/wui-button'; export { CardSelectLoader, From 0d2f1110c4dc9dbc0b39ea3330d5280f53a3254a Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:22:52 -0300 Subject: [PATCH 014/114] chore: code improvements --- packages/ui/src/composites/wui-compatible-network/index.tsx | 2 +- packages/ui/src/composites/wui-qr-code/index.tsx | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/composites/wui-compatible-network/index.tsx b/packages/ui/src/composites/wui-compatible-network/index.tsx index 943ee852b..af94c9358 100644 --- a/packages/ui/src/composites/wui-compatible-network/index.tsx +++ b/packages/ui/src/composites/wui-compatible-network/index.tsx @@ -39,7 +39,7 @@ export function CompatibleNetwork({ {networkImages?.map((image, index) => ( (uri ? QRCodeUtil.generate(uri, qrSize, arenaClear ? 0 : qrSize / 4) : []), - [uri, qrSize, arenaClear] + () => (uri ? QRCodeUtil.generate(uri, qrSize, logoSize) : []), + [uri, qrSize, logoSize] ); const logoTemplate = () => { From 367a3b1cd65abd79cc8c689d6ebcfe6eadd8fe51 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 9 Aug 2024 16:25:52 -0300 Subject: [PATCH 015/114] chore: code improvements --- packages/core/src/utils/CoreHelperUtil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/utils/CoreHelperUtil.ts b/packages/core/src/utils/CoreHelperUtil.ts index d1f8feae9..50fc280a0 100644 --- a/packages/core/src/utils/CoreHelperUtil.ts +++ b/packages/core/src/utils/CoreHelperUtil.ts @@ -226,7 +226,7 @@ export const CoreHelperUtil = { }, calculateAndFormatBalance(array?: Balance[]) { - if (!array || !array.length) { + if (!array?.length) { return { dollars: '0', pennies: '00' }; } From 0b60343231d7d6d84ae045c32a5c3eae76edd182 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:28:57 -0300 Subject: [PATCH 016/114] chore: added token list --- .../src/partials/w3m-account-tokens/index.tsx | 44 ++++++++++---- .../w3m-account-wallet-features/index.tsx | 2 +- .../w3m-account-wallet-features/styles.ts | 6 +- .../index.tsx | 5 +- .../src/composites/wui-list-token/index.tsx | 57 +++++++++++++++++++ .../src/composites/wui-list-token/styles.ts | 10 ++++ packages/ui/src/index.ts | 1 + packages/ui/src/utils/UiUtil.ts | 18 ++++++ 8 files changed, 124 insertions(+), 19 deletions(-) create mode 100644 packages/ui/src/composites/wui-list-token/index.tsx create mode 100644 packages/ui/src/composites/wui-list-token/styles.ts diff --git a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx index 6ed3f7af8..a7d80606e 100644 --- a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx @@ -1,21 +1,41 @@ -import { RouterController } from '@web3modal/core-react-native'; -import { FlexView, ListItem, Text } from '@web3modal/ui-react-native'; +import { useSnapshot } from 'valtio'; +import { AccountController, RouterController } from '@web3modal/core-react-native'; +import { FlexView, ListItem, Text, ListToken } from '@web3modal/ui-react-native'; +import { ScrollView } from 'react-native'; export function AccountTokens() { + const { tokenBalance } = useSnapshot(AccountController.state); const onReceivePress = () => { RouterController.push('WalletReceive'); }; + if (!tokenBalance?.length) { + return ( + + + + Receive funds + + + Transfer tokens on your wallet + + + + ); + } + return ( - - - - Receive funds - - - Transfer tokens on your wallet - - - + + {tokenBalance.map(token => ( + + ))} + ); } diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx index 991beb004..f44c262dd 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx @@ -62,7 +62,7 @@ export function AccountWalletFeatures() { /> - + {activeTab === 0 && } {activeTab === 1 && } {activeTab === 2 && } diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts b/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts index 75c000fb4..ef4af69cc 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts @@ -3,7 +3,8 @@ import { StyleSheet } from 'react-native'; export default StyleSheet.create({ container: { - alignItems: 'center' + alignItems: 'center', + height: 400 }, balanceText: { fontSize: 40, @@ -25,6 +26,7 @@ export default StyleSheet.create({ marginLeft: 8 }, tabContainer: { - minHeight: 300 + flex: 1, + width: '100%' } }); diff --git a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx index 1748bad00..9f5d03282 100644 --- a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx @@ -12,10 +12,7 @@ export function WalletCompatibleNetworks() { return ( - + {approvedNetworks.map(network => ( + + {imageSrc ? ( + + ) : ( + + + + )} + + + + {name} + + + {UiUtil.formatNumberToLocalString(amount, 4)} {currency} + + + + + ${value.toFixed(2)} + + + ); +} diff --git a/packages/ui/src/composites/wui-list-token/styles.ts b/packages/ui/src/composites/wui-list-token/styles.ts new file mode 100644 index 000000000..21d4971bb --- /dev/null +++ b/packages/ui/src/composites/wui-list-token/styles.ts @@ -0,0 +1,10 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, WalletImageSize } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + image: { + height: WalletImageSize.sm, + width: WalletImageSize.sm, + borderRadius: BorderRadius.full + } +}); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 1f601734b..3817986f2 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -40,6 +40,7 @@ export { InputNumeric, type InputNumericProps } from './composites/wui-input-num export { InputText, type InputTextProps } from './composites/wui-input-text'; export { Link, type LinkProps } from './composites/wui-link'; export { ListItem, type ListItemProps } from './composites/wui-list-item'; +export { ListToken, type ListTokenProps } from './composites/wui-list-token'; export { ListWallet, type ListWalletProps } from './composites/wui-list-wallet'; export { Logo, type LogoProps } from './composites/wui-logo'; export { LogoSelect, type LogoSelectProps } from './composites/wui-logo-select'; diff --git a/packages/ui/src/utils/UiUtil.ts b/packages/ui/src/utils/UiUtil.ts index e4678f6b2..bca68b003 100644 --- a/packages/ui/src/utils/UiUtil.ts +++ b/packages/ui/src/utils/UiUtil.ts @@ -68,5 +68,23 @@ export const UiUtil = { getWalletName(name: string, short = true) { return short ? name.split(' ')[0] : name; + }, + + formatNumberToLocalString(value: string | number | undefined, decimals = 2) { + if (value === undefined) { + return '0.00'; + } + + if (typeof value === 'number') { + return value.toLocaleString('en-US', { + maximumFractionDigits: decimals, + minimumFractionDigits: decimals + }); + } + + return parseFloat(value).toLocaleString('en-US', { + maximumFractionDigits: decimals, + minimumFractionDigits: decimals + }); } }; From 49516f6b813840155eeba89632ad454aaf349a5d Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:50:59 -0300 Subject: [PATCH 017/114] chore: changed list token types --- packages/ui/src/composites/wui-list-token/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/ui/src/composites/wui-list-token/index.tsx b/packages/ui/src/composites/wui-list-token/index.tsx index e0f6655a8..433b2fc42 100644 --- a/packages/ui/src/composites/wui-list-token/index.tsx +++ b/packages/ui/src/composites/wui-list-token/index.tsx @@ -9,8 +9,8 @@ import styles from './styles'; export interface ListTokenProps { imageSrc: string; name: string; - value: number; - amount: number; + value?: number; + amount?: string; currency: string; } @@ -50,7 +50,7 @@ export function ListToken({ imageSrc, name, value, amount, currency }: ListToken - ${value.toFixed(2)} + ${value?.toFixed(2) || '0.00'} ); From 494ce0c5816b5160729b80c013b5087a1e3ba907 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 12 Aug 2024 17:06:33 -0300 Subject: [PATCH 018/114] chore: code style --- packages/ui/src/composites/wui-list-token/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/composites/wui-list-token/index.tsx b/packages/ui/src/composites/wui-list-token/index.tsx index 433b2fc42..e13b71794 100644 --- a/packages/ui/src/composites/wui-list-token/index.tsx +++ b/packages/ui/src/composites/wui-list-token/index.tsx @@ -50,7 +50,7 @@ export function ListToken({ imageSrc, name, value, amount, currency }: ListToken - ${value?.toFixed(2) || '0.00'} + ${value?.toFixed(2) ?? '0.00'} ); From 851fce9bb9a8c4bdd78cc5cccf5414bff54b682b Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 13 Aug 2024 13:10:03 -0300 Subject: [PATCH 019/114] chore: added network icon in token item --- .../src/partials/w3m-account-tokens/index.tsx | 13 +++++++++++-- .../ui/src/composites/wui-list-token/index.tsx | 18 ++++++++++++++++-- .../ui/src/composites/wui-list-token/styles.ts | 14 ++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx index a7d80606e..9b90ba3c5 100644 --- a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx @@ -1,10 +1,18 @@ +import { ScrollView } from 'react-native'; import { useSnapshot } from 'valtio'; -import { AccountController, RouterController } from '@web3modal/core-react-native'; +import { + AccountController, + AssetUtil, + NetworkController, + RouterController +} from '@web3modal/core-react-native'; import { FlexView, ListItem, Text, ListToken } from '@web3modal/ui-react-native'; -import { ScrollView } from 'react-native'; export function AccountTokens() { const { tokenBalance } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const onReceivePress = () => { RouterController.push('WalletReceive'); }; @@ -31,6 +39,7 @@ export function AccountTokens() { key={token.name} name={token.name} imageSrc={token.iconUrl} + networkSrc={networkImage} value={token.value} amount={token.quantity.numeric} currency={token.symbol} diff --git a/packages/ui/src/composites/wui-list-token/index.tsx b/packages/ui/src/composites/wui-list-token/index.tsx index e13b71794..43acad2cf 100644 --- a/packages/ui/src/composites/wui-list-token/index.tsx +++ b/packages/ui/src/composites/wui-list-token/index.tsx @@ -8,13 +8,14 @@ import styles from './styles'; export interface ListTokenProps { imageSrc: string; + networkSrc?: string; name: string; value?: number; amount?: string; currency: string; } -export function ListToken({ imageSrc, name, value, amount, currency }: ListTokenProps) { +export function ListToken({ imageSrc, networkSrc, name, value, amount, currency }: ListTokenProps) { const Theme = useTheme(); return ( @@ -39,7 +40,20 @@ export function ListToken({ imageSrc, name, value, amount, currency }: ListToken )} - + + {networkSrc ? ( + + ) : ( + + )} + {name} diff --git a/packages/ui/src/composites/wui-list-token/styles.ts b/packages/ui/src/composites/wui-list-token/styles.ts index 21d4971bb..73afea33a 100644 --- a/packages/ui/src/composites/wui-list-token/styles.ts +++ b/packages/ui/src/composites/wui-list-token/styles.ts @@ -6,5 +6,19 @@ export default StyleSheet.create({ height: WalletImageSize.sm, width: WalletImageSize.sm, borderRadius: BorderRadius.full + }, + networkImageContainer: { + position: 'absolute', + bottom: -2, + left: 24, + borderWidth: 2, + borderRadius: BorderRadius.full, + width: 18, + height: 18 + }, + networkImage: { + width: 14, + height: 14, + borderRadius: BorderRadius.full } }); From a7fb6224edf13f60321c2fd9b62df05611a6c5c2 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 15 Aug 2024 17:11:50 -0300 Subject: [PATCH 020/114] chore: wip transaction list --- packages/common/package.json | 3 + packages/common/src/index.ts | 1 + packages/common/src/utils/DateUtil.ts | 47 +++++ packages/common/src/utils/TypeUtil.ts | 72 ++++++++ .../controllers/BlockchainApiController.ts | 24 ++- .../src/controllers/TransactionsController.ts | 133 ++++++++++++++ packages/core/src/index.ts | 64 ++++--- packages/core/src/utils/FetchUtil.ts | 4 + packages/core/src/utils/TypeUtil.ts | 53 +++++- .../scaffold/src/modal/w3m-modal/index.tsx | 6 +- .../partials/w3m-account-activity/index.tsx | 91 ++++++++- .../partials/w3m-account-activity/styles.ts | 14 ++ .../partials/w3m-account-activity/utils.ts | 25 +++ .../composites/wui-list-transaction/index.tsx | 65 +++++++ .../composites/wui-list-transaction/styles.ts | 10 + .../composites/wui-list-transaction/utils.ts | 68 +++++++ .../wui-transaction-visual/index.tsx | 78 ++++++++ .../wui-transaction-visual/styles.ts | 36 ++++ packages/ui/src/index.ts | 2 + packages/ui/src/utils/TransactionUtil.ts | 173 ++++++++++++++++++ packages/ui/src/utils/TypesUtil.ts | 19 ++ yarn.lock | 9 + 22 files changed, 959 insertions(+), 38 deletions(-) create mode 100644 packages/common/src/utils/DateUtil.ts create mode 100644 packages/core/src/controllers/TransactionsController.ts create mode 100644 packages/scaffold/src/partials/w3m-account-activity/styles.ts create mode 100644 packages/scaffold/src/partials/w3m-account-activity/utils.ts create mode 100644 packages/ui/src/composites/wui-list-transaction/index.tsx create mode 100644 packages/ui/src/composites/wui-list-transaction/styles.ts create mode 100644 packages/ui/src/composites/wui-list-transaction/utils.ts create mode 100644 packages/ui/src/composites/wui-transaction-visual/index.tsx create mode 100644 packages/ui/src/composites/wui-transaction-visual/styles.ts create mode 100644 packages/ui/src/utils/TransactionUtil.ts diff --git a/packages/common/package.json b/packages/common/package.json index 9bbfadde8..67e8fcddd 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -11,6 +11,9 @@ "test": "jest --passWithNoTests", "lint": "eslint . --ext .js,.jsx,.ts,.tsx" }, + "dependencies": { + "dayjs": "1.11.10" + }, "files": [ "src", "lib" diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 96562da67..b3cd9d033 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,3 +1,4 @@ export { ConstantsUtil } from './utils/ConstantsUtil'; export { NetworkUtil } from './utils/NetworkUtil'; +export { DateUtil } from './utils/DateUtil'; export * from './utils/TypeUtil'; diff --git a/packages/common/src/utils/DateUtil.ts b/packages/common/src/utils/DateUtil.ts new file mode 100644 index 000000000..e6c09dcd3 --- /dev/null +++ b/packages/common/src/utils/DateUtil.ts @@ -0,0 +1,47 @@ +import dayjs from 'dayjs'; +import englishLocale from 'dayjs/locale/en.js'; +import relativeTime from 'dayjs/plugin/relativeTime.js'; +import updateLocale from 'dayjs/plugin/updateLocale.js'; + +dayjs.extend(relativeTime); +dayjs.extend(updateLocale); + +const localeObject = { + ...englishLocale, + name: 'en-web3-modal', + relativeTime: { + future: 'in %s', + past: '%s ago', + s: '%d sec', + m: '1 min', + mm: '%d min', + h: '1 hr', + hh: '%d hrs', + d: '1 d', + dd: '%d d', + M: '1 mo', + MM: '%d mo', + y: '1 yr', + yy: '%d yr' + } +}; + +dayjs.locale('en-appkit', localeObject); + +export const DateUtil = { + getYear(date: string = new Date().toISOString()) { + return dayjs(date).year(); + }, + + getRelativeDateFromNow(date: string | number) { + return dayjs(date).locale('en-appkit').fromNow(true); + }, + + formatDate(date: string | number, format = 'DD MMM') { + return dayjs(date).format(format); + }, + + getMonth(month: number) { + return dayjs().month(month).format('MMMM'); + } +}; diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index ce2a40aa0..b5f83c35c 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -13,3 +13,75 @@ type BalanceQuantity = { decimals: string; numeric: string; }; + +export type TransactionStatus = 'confirmed' | 'failed' | 'pending'; +export type TransactionDirection = 'in' | 'out' | 'self'; +export type TransactionImage = { + type: 'FUNGIBLE' | 'NFT' | undefined; + url: string | undefined; +}; + +export interface Transaction { + id: string; + metadata: TransactionMetadata; + transfers: TransactionTransfer[]; +} + +export interface TransactionMetadata { + application: { + iconUrl: string | null; + name: string | null; + }; + operationType: string; + hash: string; + minedAt: string; + sentFrom: string; + sentTo: string; + status: TransactionStatus; + nonce: number; + chain?: string; +} + +export interface TransactionTransfer { + fungible_info?: { + name?: string; + symbol?: string; + icon?: { + url: string; + }; + }; + nft_info?: TransactionNftInfo; + direction: TransactionDirection; + quantity: TransactionQuantity; + value?: number; + price?: number; +} + +export interface TransactionNftInfo { + name?: string; + content?: TransactionContent; + flags: TransactionNftInfoFlags; +} + +export interface TransactionNftInfoFlags { + is_spam: boolean; +} + +export interface TransactionContent { + preview?: TransactionPreview; + detail?: TransactionDetail; +} + +export interface TransactionPreview { + url: string; + content_type?: null; +} + +export interface TransactionDetail { + url: string; + content_type?: null; +} + +export interface TransactionQuantity { + numeric: string; +} diff --git a/packages/core/src/controllers/BlockchainApiController.ts b/packages/core/src/controllers/BlockchainApiController.ts index 6c480489e..e64373df4 100644 --- a/packages/core/src/controllers/BlockchainApiController.ts +++ b/packages/core/src/controllers/BlockchainApiController.ts @@ -5,7 +5,9 @@ import { FetchUtil } from '../utils/FetchUtil'; import type { BlockchainApiBalanceResponse, BlockchainApiIdentityRequest, - BlockchainApiIdentityResponse + BlockchainApiIdentityResponse, + BlockchainApiTransactionsRequest, + BlockchainApiTransactionsResponse } from '../utils/TypeUtil'; import { OptionsController } from './OptionsController'; @@ -37,6 +39,26 @@ export const BlockchainApiController = { }); }, + fetchTransactions({ + account, + projectId, + cursor, + onramp, + signal, + cache + }: BlockchainApiTransactionsRequest) { + return state.api.get({ + path: `/v1/account/${account}/history`, + params: { + projectId, + cursor, + onramp + }, + signal, + cache + }); + }, + async getBalance(address: string, chainId?: string, forceUpdate?: string) { const { sdkType, sdkVersion } = OptionsController.state; diff --git a/packages/core/src/controllers/TransactionsController.ts b/packages/core/src/controllers/TransactionsController.ts new file mode 100644 index 000000000..bca626573 --- /dev/null +++ b/packages/core/src/controllers/TransactionsController.ts @@ -0,0 +1,133 @@ +import type { Transaction } from '@web3modal/common-react-native'; +import { proxy, subscribe as sub } from 'valtio/vanilla'; +import { OptionsController } from './OptionsController'; +import { EventsController } from './EventsController'; +import { SnackController } from './SnackController'; +import { NetworkController } from './NetworkController'; +import { BlockchainApiController } from './BlockchainApiController'; + +// -- Types --------------------------------------------- // +type TransactionByMonthMap = Record; +type TransactionByYearMap = Record; + +export interface TransactionsControllerState { + transactions: Transaction[]; + loading: boolean; + empty: boolean; + next: string | undefined; +} + +// -- State --------------------------------------------- // +const state = proxy({ + transactions: [], + loading: false, + empty: false, + next: undefined +}); + +// -- Controller ---------------------------------------- // +export const TransactionsController = { + state, + + subscribe(callback: (newState: TransactionsControllerState) => void) { + return sub(state, () => callback(state)); + }, + + async fetchTransactions(accountAddress?: string) { + const { projectId } = OptionsController.state; + + if (!projectId || !accountAddress) { + throw new Error("Transactions can't be fetched without a projectId and an accountAddress"); + } + + state.loading = true; + + try { + const response = await BlockchainApiController.fetchTransactions({ + account: accountAddress, + projectId, + cursor: state.next + }); + + const nonSpamTransactions = this.filterSpamTransactions(response?.data ?? []); + const filteredTransactions = [...state.transactions, ...nonSpamTransactions]; + + state.loading = false; + + state.transactions = filteredTransactions; + + state.empty = nonSpamTransactions.length === 0; + state.next = response?.next ? response.next : undefined; + } catch (error) { + EventsController.sendEvent({ + type: 'track', + event: 'ERROR_FETCH_TRANSACTIONS', + properties: { + address: accountAddress, + projectId, + cursor: state.next, + isSmartAccount: false + } + }); + SnackController.showError('Failed to fetch transactions'); + state.loading = false; + state.empty = true; + state.next = undefined; + } + }, + + getTransactionsByYearAndMonth(transactions: Transaction[]) { + const grouped: TransactionByYearMap = {}; + let filteredTransactions = this.filterByConnectedChain(transactions); + + filteredTransactions.forEach(transaction => { + const year = new Date(transaction.metadata.minedAt).getFullYear(); + const month = new Date(transaction.metadata.minedAt).getMonth(); + + const yearTransactions = grouped[year] ?? {}; + const monthTransactions = yearTransactions[month] ?? []; + + // If there's a transaction with the same id, remove the old one + const newMonthTransactions = monthTransactions.filter(tx => tx.id !== transaction.id); + + grouped[year] = { + ...yearTransactions, + [month]: [...newMonthTransactions, transaction].sort( + (a, b) => new Date(b.metadata.minedAt).getTime() - new Date(a.metadata.minedAt).getTime() + ) + }; + }); + + return grouped; + }, + + filterSpamTransactions(transactions: Transaction[]) { + return transactions.filter(transaction => { + const isAllSpam = transaction.transfers.every( + transfer => transfer.nft_info?.flags.is_spam === true + ); + + return !isAllSpam; + }); + }, + + filterByConnectedChain(transactions: Transaction[]) { + const chainId = NetworkController.state.caipNetwork?.id; + const filteredTransactions = transactions.filter( + transaction => transaction.metadata.chain === chainId + ); + + return filteredTransactions; + }, + + clearCursor() { + state.next = undefined; + }, + + resetTransactions() { + state.transactions = []; + state.loading = false; + state.empty = false; + state.next = undefined; + } +}; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 26ce21523..0d9b5eb55 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -1,50 +1,54 @@ // -- Controllers ------------------------------------------------------------- -export { ModalController } from './controllers/ModalController'; -export type { ModalControllerArguments, ModalControllerState } from './controllers/ModalController'; +export { + ModalController, + type ModalControllerArguments, + type ModalControllerState +} from './controllers/ModalController'; -export { RouterController } from './controllers/RouterController'; -export type { RouterControllerState } from './controllers/RouterController'; +export { RouterController, type RouterControllerState } from './controllers/RouterController'; -export { AccountController } from './controllers/AccountController'; -export type { AccountControllerState } from './controllers/AccountController'; +export { AccountController, type AccountControllerState } from './controllers/AccountController'; -export { NetworkController } from './controllers/NetworkController'; -export type { - NetworkControllerClient, - NetworkControllerState +export { + NetworkController, + type NetworkControllerClient, + type NetworkControllerState } from './controllers/NetworkController'; -export { ConnectionController } from './controllers/ConnectionController'; -export type { - ConnectionControllerClient, - ConnectionControllerState +export { + ConnectionController, + type ConnectionControllerClient, + type ConnectionControllerState } from './controllers/ConnectionController'; -export { ConnectorController } from './controllers/ConnectorController'; -export type { ConnectorControllerState } from './controllers/ConnectorController'; +export { + ConnectorController, + type ConnectorControllerState +} from './controllers/ConnectorController'; -export { SnackController } from './controllers/SnackController'; -export type { SnackControllerState } from './controllers/SnackController'; +export { SnackController, type SnackControllerState } from './controllers/SnackController'; -export { ApiController } from './controllers/ApiController'; -export type { ApiControllerState } from './controllers/ApiController'; +export { ApiController, type ApiControllerState } from './controllers/ApiController'; -export { AssetController } from './controllers/AssetController'; -export type { AssetControllerState } from './controllers/AssetController'; +export { AssetController, type AssetControllerState } from './controllers/AssetController'; -export { ThemeController } from './controllers/ThemeController'; -export type { ThemeControllerState } from './controllers/ThemeController'; +export { ThemeController, type ThemeControllerState } from './controllers/ThemeController'; -export { OptionsController } from './controllers/OptionsController'; -export type { OptionsControllerState } from './controllers/OptionsController'; +export { OptionsController, type OptionsControllerState } from './controllers/OptionsController'; -export { PublicStateController } from './controllers/PublicStateController'; -export type { PublicStateControllerState } from './controllers/PublicStateController'; +export { + PublicStateController, + type PublicStateControllerState +} from './controllers/PublicStateController'; export { BlockchainApiController } from './controllers/BlockchainApiController'; -export { EventsController } from './controllers/EventsController'; -export type { EventsControllerState } from './controllers/EventsController'; +export { EventsController, type EventsControllerState } from './controllers/EventsController'; + +export { + TransactionsController, + type TransactionsControllerState +} from './controllers/TransactionsController'; // -- Utils ------------------------------------------------------------------- export { AssetUtil } from './utils/AssetUtil'; diff --git a/packages/core/src/utils/FetchUtil.ts b/packages/core/src/utils/FetchUtil.ts index 99c0df370..b4d6d8057 100644 --- a/packages/core/src/utils/FetchUtil.ts +++ b/packages/core/src/utils/FetchUtil.ts @@ -1,3 +1,5 @@ +import type { RequestCache } from './TypeUtil'; + // -- Types ---------------------------------------------------------------------- interface Options { baseUrl: string; @@ -8,6 +10,8 @@ interface RequestArguments { path: string; headers?: HeadersInit_; params?: Record; + cache?: RequestCache; + signal?: AbortSignal; } interface PostArguments extends RequestArguments { diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index c751e8769..e8721aedb 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -1,4 +1,4 @@ -import type { Balance } from '@web3modal/common-react-native'; +import type { Balance, Transaction } from '@web3modal/common-react-native'; export type CaipAddress = `${string}:${string}:${string}`; @@ -99,6 +99,16 @@ export interface ApiGetAnalyticsConfigResponse { isAnalyticsEnabled: boolean; } +export type RequestCache = + | 'default' + | 'force-cache' + | 'no-cache' + | 'no-store' + | 'only-if-cached' + | 'reload'; + +// -- ThemeController Types --------------------------------------------------- + export type ThemeMode = 'dark' | 'light'; export interface ThemeVariables { @@ -119,6 +129,20 @@ export interface BlockchainApiBalanceResponse { balances: Balance[]; } +export interface BlockchainApiTransactionsRequest { + account: string; + projectId: string; + cursor?: string; + onramp?: 'coinbase'; + signal?: AbortSignal; + cache?: RequestCache; +} + +export interface BlockchainApiTransactionsResponse { + data: Transaction[]; + next: string | null; +} + // -- OptionsController Types --------------------------------------------------- export interface Token { address: string; @@ -283,6 +307,33 @@ export type Event = | { type: 'track'; event: 'SIWE_AUTH_ERROR'; + } + | { + type: 'track'; + event: 'CLICK_TRANSACTIONS'; + properties: { + isSmartAccount: boolean; + }; + } + | { + type: 'track'; + event: 'ERROR_FETCH_TRANSACTIONS'; + properties: { + address: string; + projectId: string; + cursor: string | undefined; + isSmartAccount: boolean; + }; + } + | { + type: 'track'; + event: 'LOAD_MORE_TRANSACTIONS'; + properties: { + address: string | undefined; + projectId: string; + cursor: string | undefined; + isSmartAccount: boolean; + }; }; // -- Email Types ------------------------------------------------ diff --git a/packages/scaffold/src/modal/w3m-modal/index.tsx b/packages/scaffold/src/modal/w3m-modal/index.tsx index 716bb25a3..32e4b0b99 100644 --- a/packages/scaffold/src/modal/w3m-modal/index.tsx +++ b/packages/scaffold/src/modal/w3m-modal/index.tsx @@ -13,6 +13,7 @@ import { ModalController, OptionsController, RouterController, + TransactionsController, type CaipAddress, type W3mFrameProvider } from '@web3modal/core-react-native'; @@ -67,8 +68,11 @@ export function Web3Modal() { return; } + const newAddress = CoreHelperUtil.getPlainAddress(address); + TransactionsController.resetTransactions(); + TransactionsController.fetchTransactions(newAddress); + if (isSiweEnabled) { - const newAddress = CoreHelperUtil.getPlainAddress(address); const newNetworkId = CoreHelperUtil.getNetworkId(address); const { SIWEController } = await import('@web3modal/siwe-react-native'); const { signOutOnAccountChange, signOutOnNetworkChange } = diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/scaffold/src/partials/w3m-account-activity/index.tsx index 9b91cf4b7..7c165fa6c 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-activity/index.tsx @@ -1,11 +1,92 @@ +import { useMemo } from 'react'; +import { useSnapshot } from 'valtio'; +import { ScrollView, View } from 'react-native'; +import { ListTransaction, LoadingSpinner, Text, TransactionUtil } from '@web3modal/ui-react-native'; +import { type Transaction, type TransactionImage } from '@web3modal/common-react-native'; +import { AssetUtil, NetworkController, TransactionsController } from '@web3modal/core-react-native'; import { AccountPlaceholder } from '../w3m-account-placeholder'; +import { getTransactionListItemProps } from './utils'; +import styles from './styles'; export function AccountActivity() { + const { loading, transactions } = useSnapshot(TransactionsController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + + const transactionsByYear = useMemo(() => { + return TransactionsController.getTransactionsByYearAndMonth(transactions as Transaction[]); + }, [transactions]); + + if (loading) { + return ; + } + + if (!Object.keys(transactionsByYear).length) { + return ( + + ); + } + return ( - + + {Object.keys(transactionsByYear) + .reverse() + .map(year => ( + + {Object.keys(transactionsByYear[year] || {}) + .reverse() + .map(month => ( + + + {TransactionUtil.getTransactionGroupTitle(year, month)} + + {transactionsByYear[year]?.[month]?.map((transaction: Transaction) => { + const { date, type, descriptions, status, images, isAllNFT, transfers } = + getTransactionListItemProps(transaction); + const hasMultipleTransfers = transfers?.length > 2; + + if (hasMultipleTransfers) { + return transfers.map((transfer, index) => { + const description = TransactionUtil.getTransferDescription(transfer); + + return ( + + ); + }); + } + + return ( + + ); + })} + + ))} + + ))} + ); } diff --git a/packages/scaffold/src/partials/w3m-account-activity/styles.ts b/packages/scaffold/src/partials/w3m-account-activity/styles.ts new file mode 100644 index 000000000..90c4759fe --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-activity/styles.ts @@ -0,0 +1,14 @@ +import { Spacing } from '@web3modal/ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + padding: Spacing.xs + }, + separatorText: { + marginVertical: Spacing.xs + }, + transactionItem: { + marginVertical: Spacing.xs + } +}); diff --git a/packages/scaffold/src/partials/w3m-account-activity/utils.ts b/packages/scaffold/src/partials/w3m-account-activity/utils.ts new file mode 100644 index 000000000..a0fc06396 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-account-activity/utils.ts @@ -0,0 +1,25 @@ +import { DateUtil, type Transaction } from '@web3modal/common-react-native'; +import { TransactionUtil } from '@web3modal/ui-react-native'; +import type { TransactionType } from '@web3modal/ui-react-native/lib/typescript/utils/TypesUtil'; + +export function getTransactionListItemProps(transaction: Transaction) { + const date = DateUtil.formatDate(transaction?.metadata?.minedAt); + const descriptions = TransactionUtil.getTransactionDescriptions(transaction); + + const transfers = transaction?.transfers; + const transfer = transaction?.transfers?.[0]; + const isAllNFT = + Boolean(transfer) && transaction?.transfers?.every(item => Boolean(item.nft_info)); + const images = TransactionUtil.getTransactionImages(transfers); + + return { + date, + direction: transfer?.direction, + descriptions, + isAllNFT, + images, + status: transaction.metadata?.status, + transfers, + type: transaction.metadata?.operationType as TransactionType + }; +} diff --git a/packages/ui/src/composites/wui-list-transaction/index.tsx b/packages/ui/src/composites/wui-list-transaction/index.tsx new file mode 100644 index 000000000..dbaa4197f --- /dev/null +++ b/packages/ui/src/composites/wui-list-transaction/index.tsx @@ -0,0 +1,65 @@ +import { type TransactionImage, type TransactionStatus } from '@web3modal/common-react-native'; + +import type { TransactionType } from '../../utils/TypesUtil'; +import { Text } from '../../components/wui-text'; +import { FlexView } from '../../layout/wui-flex'; +import { IconBox } from '../wui-icon-box'; +import { TransactionVisual } from '../wui-transaction-visual'; +import { getIcon, getTypeLabel } from './utils'; +import styles from './styles'; +import type { StyleProp, ViewStyle } from 'react-native'; + +export interface ListTransactionProps { + date: string; + status?: TransactionStatus; + type?: TransactionType; + nature?: 'nft' | 'token' | 'fiat'; + descriptions?: string[]; + images?: TransactionImage[]; + networkSrc?: string; + style?: StyleProp; + isAllNFT?: boolean; +} + +export function ListTransaction({ + date, + type, + descriptions, + images, + networkSrc, + style, + isAllNFT +}: ListTransactionProps) { + const joinSymbol = type === 'trade' ? ' → ' : ' - '; + + return ( + + + + + + {type && ( + + )} + + {getTypeLabel(type)} + + + + {descriptions?.join(joinSymbol)} + + + + + {date} + + + ); +} diff --git a/packages/ui/src/composites/wui-list-transaction/styles.ts b/packages/ui/src/composites/wui-list-transaction/styles.ts new file mode 100644 index 000000000..5554145c6 --- /dev/null +++ b/packages/ui/src/composites/wui-list-transaction/styles.ts @@ -0,0 +1,10 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + middleContainer: { + flex: 1 + }, + date: { + textTransform: 'uppercase' + } +}); diff --git a/packages/ui/src/composites/wui-list-transaction/utils.ts b/packages/ui/src/composites/wui-list-transaction/utils.ts new file mode 100644 index 000000000..d9fe336b0 --- /dev/null +++ b/packages/ui/src/composites/wui-list-transaction/utils.ts @@ -0,0 +1,68 @@ +import type { IconType, TransactionType } from '../../utils/TypesUtil'; + +export const getIcon = (type: TransactionType): IconType => { + switch (type) { + case 'approve': + case 'execute': + return 'checkmark'; + case 'repay': + case 'send': + case 'stake': + case 'withdraw': + return 'arrowTop'; + case 'burn': + case 'cancel': + return 'close'; + case 'trade': + return 'swapHorizontal'; + case 'deploy': + return 'arrowRight'; + default: + return 'arrowBottom'; + } +}; + +export const getTypeLabel = (type?: TransactionType) => { + if (!type) { + return 'Unknown'; + } + + switch (type) { + case 'approve': + return 'Approved'; + case 'bought': + return 'Bought'; + case 'borrow': + return 'Borrowed'; + case 'burn': + return 'Burnt'; + case 'cancel': + return 'Canceled'; + case 'claim': + return 'Claimed'; + case 'deploy': + return 'Deployed'; + case 'deposit': + return 'Deposited'; + case 'execute': + return 'Executed'; + case 'mint': + return 'Minted'; + case 'receive': + return 'Received'; + case 'repay': + return 'Repaid'; + case 'send': + return 'Sent'; + case 'stake': + return 'Staked'; + case 'trade': + return 'Swapped'; + case 'unstake': + return 'Unstaked'; + case 'withdraw': + return 'Withdrawn'; + default: + return 'Unknown'; + } +}; diff --git a/packages/ui/src/composites/wui-transaction-visual/index.tsx b/packages/ui/src/composites/wui-transaction-visual/index.tsx new file mode 100644 index 000000000..46a5761a0 --- /dev/null +++ b/packages/ui/src/composites/wui-transaction-visual/index.tsx @@ -0,0 +1,78 @@ +import type { TransactionImage } from '@web3modal/common-react-native'; + +import { FlexView } from '../../layout/wui-flex'; +import { Icon } from '../../components/wui-icon'; +import { Image } from '../../components/wui-image'; +import { useTheme } from '../../hooks/useTheme'; +import styles from './styles'; + +export interface TransactionVisualProps { + images?: TransactionImage[]; + networkSrc?: string; + isAllNFT?: boolean; +} + +export function TransactionVisual({ images, networkSrc, isAllNFT }: TransactionVisualProps) { + const Theme = useTheme(); + const backgroundColor = Theme['bg-200']; + const isFirstNFT = Boolean(images?.[0]?.type === 'NFT'); + const filteredImages = images?.filter(image => image.url); + const [firstImage, secondImage] = filteredImages ?? []; + const hasOneImage = filteredImages?.length === 1; + const hasTwoImages = filteredImages && filteredImages?.length > 1; + + return ( + + {!filteredImages?.length && ( + + + + )} + {hasOneImage && firstImage?.url && ( + + )} + {hasTwoImages && firstImage?.url && secondImage?.url && ( + + + + + + + + + )} + + {networkSrc ? ( + + ) : ( + + )} + + + ); +} diff --git a/packages/ui/src/composites/wui-transaction-visual/styles.ts b/packages/ui/src/composites/wui-transaction-visual/styles.ts new file mode 100644 index 000000000..4bc04db84 --- /dev/null +++ b/packages/ui/src/composites/wui-transaction-visual/styles.ts @@ -0,0 +1,36 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + image: { + height: 40, + width: 40, + borderRadius: BorderRadius.full, + marginRight: Spacing.s + }, + imageNft: { + borderRadius: BorderRadius.xxs + }, + halfContainer: { + overflow: 'hidden', + width: 20, + marginRight: 2 + }, + halfRight: { + left: -20 + }, + networkImageContainer: { + position: 'absolute', + bottom: -2, + left: 24, + borderWidth: 2, + borderRadius: BorderRadius.full, + width: 18, + height: 18 + }, + networkImage: { + width: 14, + height: 14, + borderRadius: BorderRadius.full + } +}); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 3817986f2..5b8df3e18 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -41,6 +41,7 @@ export { InputText, type InputTextProps } from './composites/wui-input-text'; export { Link, type LinkProps } from './composites/wui-link'; export { ListItem, type ListItemProps } from './composites/wui-list-item'; export { ListToken, type ListTokenProps } from './composites/wui-list-token'; +export { ListTransaction, type ListTransactionProps } from './composites/wui-list-transaction'; export { ListWallet, type ListWalletProps } from './composites/wui-list-wallet'; export { Logo, type LogoProps } from './composites/wui-logo'; export { LogoSelect, type LogoSelectProps } from './composites/wui-logo-select'; @@ -74,6 +75,7 @@ export type { VisualType } from './utils/TypesUtil'; export { UiUtil } from './utils/UiUtil'; +export { TransactionUtil } from './utils/TransactionUtil'; export { Spacing, BorderRadius } from './utils/ThemeUtil'; export { useTheme } from './hooks/useTheme'; diff --git a/packages/ui/src/utils/TransactionUtil.ts b/packages/ui/src/utils/TransactionUtil.ts new file mode 100644 index 000000000..4e30cfb32 --- /dev/null +++ b/packages/ui/src/utils/TransactionUtil.ts @@ -0,0 +1,173 @@ +import { DateUtil } from '@web3modal/common-react-native'; +import type { + TransactionTransfer, + Transaction, + TransactionImage +} from '@web3modal/common-react-native'; +import type { TransactionType } from './TypesUtil'; +import { UiUtil } from './UiUtil'; + +// -- Helpers --------------------------------------------- // +const FLOAT_FIXED_VALUE = 2; +const SMALL_FLOAT_FIXED_VALUE = 4; +const plusTypes: TransactionType[] = ['receive', 'deposit', 'borrow', 'claim']; +const minusTypes: TransactionType[] = ['withdraw', 'repay', 'burn']; + +export const TransactionUtil = { + getTransactionGroupTitle(year: string, month: string) { + const currentYear = DateUtil.getYear().toString(); + const monthName = DateUtil.getMonth(parseInt(month)); + const isCurrentYear = year === currentYear; + const groupTitle = isCurrentYear ? monthName : `${monthName} ${year}`; + + return groupTitle; + }, + + getTransactionImages(transfers: TransactionTransfer[]): TransactionImage[] { + const [transfer, secondTransfer] = transfers; + const isAllNFT = Boolean(transfer) && transfers?.every(item => Boolean(item.nft_info)); + const haveMultipleTransfers = transfers?.length > 1; + const haveTwoTransfers = transfers?.length === 2; + + if (haveTwoTransfers && !isAllNFT) { + return [this.getTransactionImage(transfer), this.getTransactionImage(secondTransfer)]; + } + + if (haveMultipleTransfers) { + return transfers.map(item => this.getTransactionImage(item)); + } + + return [this.getTransactionImage(transfer)]; + }, + + getTransactionImage(transfer?: TransactionTransfer): TransactionImage { + return { + type: TransactionUtil.getTransactionTransferTokenType(transfer), + url: TransactionUtil.getTransactionImageURL(transfer) + }; + }, + + getTransactionImageURL(transfer: TransactionTransfer | undefined) { + let imageURL; + const isNFT = Boolean(transfer?.nft_info); + const isFungible = Boolean(transfer?.fungible_info); + + if (transfer && isNFT) { + imageURL = transfer?.nft_info?.content?.preview?.url; + } else if (transfer && isFungible) { + imageURL = transfer?.fungible_info?.icon?.url; + } + + return imageURL; + }, + + getTransactionTransferTokenType(transfer?: TransactionTransfer): 'FUNGIBLE' | 'NFT' | undefined { + if (transfer?.fungible_info) { + return 'FUNGIBLE'; + } else if (transfer?.nft_info) { + return 'NFT'; + } + + return undefined; + }, + + getTransactionDescriptions(transaction: Transaction) { + const type = transaction?.metadata?.operationType as TransactionType; + + const transfers = transaction?.transfers; + const haveTransfer = transaction?.transfers?.length > 0; + const haveMultipleTransfers = transaction?.transfers?.length > 1; + const isSendOrReceive = type === 'send' || type === 'receive'; + const isFungible = + haveTransfer && transfers?.every(transfer => Boolean(transfer?.fungible_info)); + const [firstTransfer, secondTransfer] = transfers; + + let firstDescription = this.getTransferDescription(firstTransfer); + let secondDescription = this.getTransferDescription(secondTransfer); + + if (!haveTransfer) { + if (isSendOrReceive && isFungible) { + firstDescription = UiUtil.getTruncateString({ + string: transaction?.metadata.sentFrom, + charsStart: 4, + charsEnd: 6, + truncate: 'middle' + }); + secondDescription = UiUtil.getTruncateString({ + string: transaction?.metadata.sentTo, + charsStart: 4, + charsEnd: 6, + truncate: 'middle' + }); + + return [firstDescription, secondDescription]; + } + + return [transaction.metadata.status]; + } + + if (haveMultipleTransfers) { + return transfers.map(item => this.getTransferDescription(item)); + } + + let prefix = ''; + if (plusTypes.includes(type)) { + prefix = '+'; + } else if (minusTypes.includes(type)) { + prefix = '-'; + } + + firstDescription = prefix.concat(firstDescription); + + if (isSendOrReceive) { + const isSend = type === 'send'; + const address = UiUtil.getTruncateString({ + string: isSend ? transaction.metadata.sentTo : transaction.metadata.sentFrom, + charsStart: 4, + charsEnd: 4, + truncate: 'middle' + }); + const arrow = isSend ? '→' : '←'; + firstDescription = firstDescription.concat(` ${arrow} ${address}`); + } + + return [firstDescription]; + }, + + getTransferDescription(transfer?: TransactionTransfer) { + let description = ''; + + if (!transfer) { + return description; + } + + if (transfer?.nft_info) { + description = transfer?.nft_info?.name || '-'; + } else if (transfer?.fungible_info) { + description = this.getFungibleTransferDescription(transfer) || '-'; + } + + return description; + }, + + getFungibleTransferDescription(transfer?: TransactionTransfer) { + if (!transfer) { + return null; + } + + const quantity = this.getQuantityFixedValue(transfer?.quantity.numeric); + const description = [quantity, transfer?.fungible_info?.symbol].join(' ').trim(); + + return description; + }, + + getQuantityFixedValue(value: string | undefined) { + if (!value) { + return null; + } + + const parsedValue = parseFloat(value); + + return parsedValue.toFixed(parsedValue > 1 ? FLOAT_FIXED_VALUE : SMALL_FLOAT_FIXED_VALUE); + } +}; diff --git a/packages/ui/src/utils/TypesUtil.ts b/packages/ui/src/utils/TypesUtil.ts index 3399506f4..294a97883 100644 --- a/packages/ui/src/utils/TypesUtil.ts +++ b/packages/ui/src/utils/TypesUtil.ts @@ -249,3 +249,22 @@ export type TruncateOptions = { charsEnd: number; truncate: TruncateType; }; + +export type TransactionType = + | 'approve' + | 'bought' + | 'borrow' + | 'burn' + | 'cancel' + | 'claim' + | 'deploy' + | 'deposit' + | 'execute' + | 'mint' + | 'receive' + | 'repay' + | 'send' + | 'stake' + | 'trade' + | 'unstake' + | 'withdraw'; diff --git a/yarn.lock b/yarn.lock index 8a45a53c8..fc776e2d5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10699,6 +10699,8 @@ __metadata: "@web3modal/common-react-native@npm:2.0.1, @web3modal/common-react-native@workspace:packages/common": version: 0.0.0-use.local resolution: "@web3modal/common-react-native@workspace:packages/common" + dependencies: + dayjs: "npm:1.11.10" languageName: unknown linkType: soft @@ -13368,6 +13370,13 @@ __metadata: languageName: node linkType: hard +"dayjs@npm:1.11.10": + version: 1.11.10 + resolution: "dayjs@npm:1.11.10" + checksum: 4de9af50639d47df87f2e15fa36bb07e0f9ed1e9c52c6caa1482788ee9a384d668f1dbd00c54f82aaab163db07d61d2899384b8254da3a9184fc6deca080e2fe + languageName: node + linkType: hard + "dayjs@npm:^1.8.15": version: 1.11.9 resolution: "dayjs@npm:1.11.9" From ab3e662005e6eab4ce8062973cf729f9536d8228 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:00:06 -0300 Subject: [PATCH 021/114] chore: added load more button --- .../partials/w3m-account-activity/index.tsx | 41 ++++++++++++++++--- .../partials/w3m-account-activity/styles.ts | 14 +++++++ 2 files changed, 49 insertions(+), 6 deletions(-) diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/scaffold/src/partials/w3m-account-activity/index.tsx index 7c165fa6c..fbc2d4145 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-activity/index.tsx @@ -1,24 +1,41 @@ import { useMemo } from 'react'; import { useSnapshot } from 'valtio'; import { ScrollView, View } from 'react-native'; -import { ListTransaction, LoadingSpinner, Text, TransactionUtil } from '@web3modal/ui-react-native'; +import { + FlexView, + Link, + ListTransaction, + LoadingSpinner, + Text, + TransactionUtil +} from '@web3modal/ui-react-native'; import { type Transaction, type TransactionImage } from '@web3modal/common-react-native'; -import { AssetUtil, NetworkController, TransactionsController } from '@web3modal/core-react-native'; +import { + AccountController, + AssetUtil, + NetworkController, + TransactionsController +} from '@web3modal/core-react-native'; import { AccountPlaceholder } from '../w3m-account-placeholder'; import { getTransactionListItemProps } from './utils'; import styles from './styles'; export function AccountActivity() { - const { loading, transactions } = useSnapshot(TransactionsController.state); + const { loading, transactions, next } = useSnapshot(TransactionsController.state); + const { address } = useSnapshot(AccountController.state); const { caipNetwork } = useSnapshot(NetworkController.state); const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const handleFetchMore = () => { + TransactionsController.fetchTransactions(address); + }; + const transactionsByYear = useMemo(() => { return TransactionsController.getTransactionsByYearAndMonth(transactions as Transaction[]); }, [transactions]); - if (loading) { - return ; + if (loading && !transactions.length) { + return ; } if (!Object.keys(transactionsByYear).length) { @@ -32,7 +49,11 @@ export function AccountActivity() { } return ( - + {Object.keys(transactionsByYear) .reverse() .map(year => ( @@ -87,6 +108,14 @@ export function AccountActivity() { ))} ))} + + {next && !loading && ( + + Load more + + )} + {loading && } + ); } diff --git a/packages/scaffold/src/partials/w3m-account-activity/styles.ts b/packages/scaffold/src/partials/w3m-account-activity/styles.ts index 90c4759fe..e7fe55f58 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/styles.ts +++ b/packages/scaffold/src/partials/w3m-account-activity/styles.ts @@ -5,10 +5,24 @@ export default StyleSheet.create({ container: { padding: Spacing.xs }, + contentContainer: { + paddingBottom: Spacing.m + }, separatorText: { marginVertical: Spacing.xs }, transactionItem: { marginVertical: Spacing.xs + }, + footer: { + height: 40 + }, + loader: { + flex: 1 + }, + loadMoreButton: { + alignSelf: 'center', + width: 100, + marginVertical: Spacing.xs } }); From 3734e0445261af3d84e565e8cfb73aec431d1f02 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:16:46 -0300 Subject: [PATCH 022/114] chore: added activity evts --- .../src/partials/w3m-account-activity/index.tsx | 17 +++++++++++++++-- .../w3m-account-wallet-features/index.tsx | 14 ++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/scaffold/src/partials/w3m-account-activity/index.tsx index fbc2d4145..89195df2e 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-activity/index.tsx @@ -13,7 +13,9 @@ import { type Transaction, type TransactionImage } from '@web3modal/common-react import { AccountController, AssetUtil, + EventsController, NetworkController, + OptionsController, TransactionsController } from '@web3modal/core-react-native'; import { AccountPlaceholder } from '../w3m-account-placeholder'; @@ -24,10 +26,21 @@ export function AccountActivity() { const { loading, transactions, next } = useSnapshot(TransactionsController.state); const { address } = useSnapshot(AccountController.state); const { caipNetwork } = useSnapshot(NetworkController.state); + const { projectId } = useSnapshot(OptionsController.state); const networkImage = AssetUtil.getNetworkImage(caipNetwork); - const handleFetchMore = () => { + const handleLoadMore = () => { TransactionsController.fetchTransactions(address); + EventsController.sendEvent({ + type: 'track', + event: 'LOAD_MORE_TRANSACTIONS', + properties: { + address: address, + projectId, + cursor: next, + isSmartAccount: false + } + }); }; const transactionsByYear = useMemo(() => { @@ -110,7 +123,7 @@ export function AccountActivity() { ))} {next && !loading && ( - + Load more )} diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx index f44c262dd..3258e855f 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx @@ -5,6 +5,7 @@ import { Balance, FlexView, IconLink, Tabs } from '@web3modal/ui-react-native'; import { AccountController, CoreHelperUtil, + EventsController, RouterController, SnackController } from '@web3modal/core-react-native'; @@ -25,6 +26,19 @@ export function AccountWalletFeatures() { const onTabChange = (index: number) => { setActiveTab(index); + if (index === 2) { + onTransactionsPress(); + } + }; + + const onTransactionsPress = () => { + EventsController.sendEvent({ + type: 'track', + event: 'CLICK_TRANSACTIONS', + properties: { + isSmartAccount: false + } + }); }; // TODO: Implement this features From 34afaffc194616d6a8c94a06bf1a1619f53f3632 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:28:11 -0300 Subject: [PATCH 023/114] chore: change activity icon color using status --- .../src/composites/wui-list-transaction/index.tsx | 8 ++++---- .../src/composites/wui-list-transaction/utils.ts | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/ui/src/composites/wui-list-transaction/index.tsx b/packages/ui/src/composites/wui-list-transaction/index.tsx index dbaa4197f..2f188135d 100644 --- a/packages/ui/src/composites/wui-list-transaction/index.tsx +++ b/packages/ui/src/composites/wui-list-transaction/index.tsx @@ -5,7 +5,7 @@ import { Text } from '../../components/wui-text'; import { FlexView } from '../../layout/wui-flex'; import { IconBox } from '../wui-icon-box'; import { TransactionVisual } from '../wui-transaction-visual'; -import { getIcon, getTypeLabel } from './utils'; +import { getIcon, getTypeLabel, getIconColor } from './utils'; import styles from './styles'; import type { StyleProp, ViewStyle } from 'react-native'; @@ -13,7 +13,6 @@ export interface ListTransactionProps { date: string; status?: TransactionStatus; type?: TransactionType; - nature?: 'nft' | 'token' | 'fiat'; descriptions?: string[]; images?: TransactionImage[]; networkSrc?: string; @@ -28,7 +27,8 @@ export function ListTransaction({ images, networkSrc, style, - isAllNFT + isAllNFT, + status }: ListTransactionProps) { const joinSymbol = type === 'trade' ? ' → ' : ' - '; @@ -42,7 +42,7 @@ export function ListTransaction({ { @@ -22,6 +23,20 @@ export const getIcon = (type: TransactionType): IconType => { } }; +//Utils +export const getIconColor = (status?: TransactionStatus) => { + switch (status) { + case 'confirmed': + return 'success-100'; + case 'failed': + return 'error-100'; + case 'pending': + return 'fg-200'; + default: + return 'fg-200'; + } +}; + export const getTypeLabel = (type?: TransactionType) => { if (!type) { return 'Unknown'; From a3d252c41c46b17dfd3210f23d112bb640b0bb13 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 20 Aug 2024 17:32:49 -0300 Subject: [PATCH 024/114] chore: code style --- packages/ui/src/utils/TransactionUtil.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/ui/src/utils/TransactionUtil.ts b/packages/ui/src/utils/TransactionUtil.ts index 4e30cfb32..380492de3 100644 --- a/packages/ui/src/utils/TransactionUtil.ts +++ b/packages/ui/src/utils/TransactionUtil.ts @@ -144,7 +144,7 @@ export const TransactionUtil = { if (transfer?.nft_info) { description = transfer?.nft_info?.name || '-'; } else if (transfer?.fungible_info) { - description = this.getFungibleTransferDescription(transfer) || '-'; + description = this.getFungibleTransferDescription(transfer) ?? '-'; } return description; From df19896ea0f4cc2514872084fd5a76b7e9ada26b Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:47:16 -0300 Subject: [PATCH 025/114] feat: added activity view for eoa --- .../core/src/controllers/RouterController.ts | 21 ++++----- .../scaffold/src/modal/w3m-router/index.tsx | 43 ++++++++++--------- .../partials/w3m-account-activity/index.tsx | 42 ++++++++++-------- .../src/partials/w3m-header/index.tsx | 21 ++++----- .../views/w3m-account-default-view/index.tsx | 27 +++++++++++- .../views/w3m-account-default-view/styles.ts | 4 +- .../src/views/w3m-transactions-view/index.tsx | 16 +++++++ .../ui/src/composites/wui-list-item/index.tsx | 2 +- packages/ui/src/utils/TypesUtil.ts | 4 ++ 9 files changed, 118 insertions(+), 62 deletions(-) create mode 100644 packages/scaffold/src/views/w3m-transactions-view/index.tsx diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index b48c85f3b..d574633bc 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -6,24 +6,25 @@ export interface RouterControllerState { view: | 'Account' | 'AccountDefault' + | 'AllWallets' | 'Connect' - | 'ConnectingWalletConnect' | 'ConnectingExternal' - | 'Networks' - | 'SwitchNetwork' - | 'AllWallets' - | 'WhatIsAWallet' - | 'WhatIsANetwork' - | 'GetWallet' + | 'ConnectingSiwe' + | 'ConnectingWalletConnect' | 'EmailVerifyDevice' | 'EmailVerifyOtp' - | 'UpdateEmailWallet' + | 'GetWallet' + | 'Networks' + | 'SwitchNetwork' + | 'Transactions' | 'UpdateEmailPrimaryOtp' | 'UpdateEmailSecondaryOtp' + | 'UpdateEmailWallet' | 'UpgradeEmailWallet' - | 'ConnectingSiwe' | 'WalletReceive' - | 'WalletCompatibleNetworks'; + | 'WalletCompatibleNetworks' + | 'WhatIsANetwork' + | 'WhatIsAWallet'; history: RouterControllerState['view'][]; data?: { connector?: Connector; diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/scaffold/src/modal/w3m-router/index.tsx index e0b800691..68abd6932 100644 --- a/packages/scaffold/src/modal/w3m-router/index.tsx +++ b/packages/scaffold/src/modal/w3m-router/index.tsx @@ -23,6 +23,7 @@ import { UpgradeEmailWalletView } from '../../views/w3m-upgrade-email-wallet-vie import { AccountView } from '../../views/w3m-account-view'; import { WalletReceiveView } from '../../views/w3m-wallet-receive-view'; import { WalletCompatibleNetworks } from '../../views/w3m-wallet-compatible-networks-view'; +import { TransactionsView } from '../../views/w3m-transactions-view'; export function Web3Router() { const { view } = useSnapshot(RouterController.state); @@ -33,46 +34,48 @@ export function Web3Router() { const ViewComponent = useMemo(() => { switch (view) { - case 'Connect': - return ConnectView; + case 'Account': + return AccountView; + case 'AccountDefault': + return AccountDefaultView; case 'AllWallets': return AllWalletsView; - case 'ConnectingWalletConnect': - return ConnectingView; + case 'Connect': + return ConnectView; case 'ConnectingExternal': return ConnectingExternalView; - case 'WhatIsAWallet': - return WhatIsAWalletView; - case 'WhatIsANetwork': - return WhatIsNetworkView; + case 'ConnectingSiwe': + return ConnectingSiweView; + case 'ConnectingWalletConnect': + return ConnectingView; + case 'EmailVerifyDevice': + return EmailVerifyDeviceView; + case 'EmailVerifyOtp': + return EmailVerifyOtpView; case 'GetWallet': return GetWalletView; case 'Networks': return NetworksView; case 'SwitchNetwork': return NetworkSwitchView; - case 'Account': - return AccountView; - case 'AccountDefault': - return AccountDefaultView; - case 'EmailVerifyDevice': - return EmailVerifyDeviceView; - case 'EmailVerifyOtp': - return EmailVerifyOtpView; - case 'UpdateEmailWallet': - return UpdateEmailWalletView; + case 'Transactions': + return TransactionsView; case 'UpdateEmailPrimaryOtp': return UpdateEmailPrimaryOtpView; case 'UpdateEmailSecondaryOtp': return UpdateEmailSecondaryOtpView; + case 'UpdateEmailWallet': + return UpdateEmailWalletView; case 'UpgradeEmailWallet': return UpgradeEmailWalletView; - case 'ConnectingSiwe': - return ConnectingSiweView; case 'WalletReceive': return WalletReceiveView; case 'WalletCompatibleNetworks': return WalletCompatibleNetworks; + case 'WhatIsANetwork': + return WhatIsNetworkView; + case 'WhatIsAWallet': + return WhatIsAWalletView; default: return ConnectView; } diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/scaffold/src/partials/w3m-account-activity/index.tsx index 89195df2e..427617907 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-activity/index.tsx @@ -1,6 +1,6 @@ import { useMemo } from 'react'; import { useSnapshot } from 'valtio'; -import { ScrollView, View } from 'react-native'; +import { ScrollView, View, type StyleProp, type ViewStyle } from 'react-native'; import { FlexView, Link, @@ -22,7 +22,11 @@ import { AccountPlaceholder } from '../w3m-account-placeholder'; import { getTransactionListItemProps } from './utils'; import styles from './styles'; -export function AccountActivity() { +interface Props { + style?: StyleProp; +} + +export function AccountActivity({ style }: Props) { const { loading, transactions, next } = useSnapshot(TransactionsController.state); const { address } = useSnapshot(AccountController.state); const { caipNetwork } = useSnapshot(NetworkController.state); @@ -53,19 +57,21 @@ export function AccountActivity() { if (!Object.keys(transactionsByYear).length) { return ( - + + + ); } return ( {Object.keys(transactionsByYear) .reverse() @@ -121,14 +127,16 @@ export function AccountActivity() { ))} ))} - - {next && !loading && ( - - Load more - - )} - {loading && } - + {(next || loading) && ( + + {next && !loading && ( + + Load more + + )} + {loading && } + + )} ); } diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index bab98729e..cf94f9fd3 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -16,26 +16,27 @@ export function Header() { const networkName = RouterController.state.data?.network?.name; return { - Connect: 'Connect wallet', Account: undefined, AccountDefault: undefined, - ConnectingWalletConnect: walletName ?? 'WalletConnect', + AllWallets: 'All wallets', + Connect: 'Connect wallet', ConnectingExternal: connectorName ?? 'Connect wallet', ConnectingSiwe: 'Sign In', - Networks: 'Select network', - SwitchNetwork: networkName ?? 'Switch network', - AllWallets: 'All wallets', - WhatIsANetwork: 'What is a network?', - WhatIsAWallet: 'What is a wallet?', - GetWallet: 'Get a wallet', + ConnectingWalletConnect: walletName ?? 'WalletConnect', EmailVerifyDevice: ' ', EmailVerifyOtp: 'Confirm email', - UpdateEmailWallet: 'Edit email', + GetWallet: 'Get a wallet', + Networks: 'Select network', + SwitchNetwork: networkName ?? 'Switch network', + Transactions: 'Activity', UpdateEmailPrimaryOtp: 'Confirm current email', UpdateEmailSecondaryOtp: 'Confirm new email', + UpdateEmailWallet: 'Edit email', UpgradeEmailWallet: 'Upgrade wallet', + WalletCompatibleNetworks: 'Compatible networks', WalletReceive: 'Receive', - WalletCompatibleNetworks: 'Compatible networks' + WhatIsANetwork: 'What is a network?', + WhatIsAWallet: 'What is a wallet?' }; }; diff --git a/packages/scaffold/src/views/w3m-account-default-view/index.tsx b/packages/scaffold/src/views/w3m-account-default-view/index.tsx index 5402251ed..0852a562f 100644 --- a/packages/scaffold/src/views/w3m-account-default-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-default-view/index.tsx @@ -84,6 +84,10 @@ export function AccountDefaultView() { } }; + const onActivityPress = () => { + RouterController.push('Transactions'); + }; + const onNetworkPress = () => { RouterController.push('Networks'); @@ -158,7 +162,13 @@ export function AccountDefaultView() { {isEmail && ( <> - + {getUserEmail()} @@ -171,12 +181,25 @@ export function AccountDefaultView() { imageHeaders={ApiController._getApiHeaders()} onPress={onNetworkPress} testID="button-network" - style={styles.networkButton} + style={styles.actionButton} > {caipNetwork?.name} + {!isEmail && ( + + Activity + + )} ; +} + +const styles = StyleSheet.create({ + container: { + minHeight: 200, + paddingHorizontal: Spacing.l, + marginVertical: Spacing.s, + marginBottom: Spacing.l + } +}); diff --git a/packages/ui/src/composites/wui-list-item/index.tsx b/packages/ui/src/composites/wui-list-item/index.tsx index 5f9c66818..9cbacb172 100644 --- a/packages/ui/src/composites/wui-list-item/index.tsx +++ b/packages/ui/src/composites/wui-list-item/index.tsx @@ -82,7 +82,7 @@ export function ListItem({ if (loading) { return ; } else if (chevron) { - return ; + return ; } return null; diff --git a/packages/ui/src/utils/TypesUtil.ts b/packages/ui/src/utils/TypesUtil.ts index 294a97883..01a88675d 100644 --- a/packages/ui/src/utils/TypesUtil.ts +++ b/packages/ui/src/utils/TypesUtil.ts @@ -96,6 +96,10 @@ export type ColorType = | 'fg-250' | 'fg-275' | 'fg-300' + | 'accent-glass-020' + | 'accent-glass-015' + | 'accent-glass-010' + | 'accent-glass-005' | 'gray-glass-020' | 'gray-glass-010' | 'gray-glass-005' From 6c4dfd076c22862828c0242c0bca9ca3008479c2 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 22 Aug 2024 12:37:53 -0300 Subject: [PATCH 026/114] chore: style improvements --- .../partials/w3m-account-activity/index.tsx | 9 ++++++-- .../partials/w3m-account-activity/styles.ts | 6 ++--- .../src/partials/w3m-account-tokens/index.tsx | 10 ++++++--- .../w3m-account-wallet-features/index.tsx | 22 ++++++++++++------- .../w3m-account-wallet-features/styles.ts | 8 ++++++- .../src/views/w3m-account-view/index.tsx | 17 +++++++++----- .../src/views/w3m-account-view/styles.ts | 8 +++++-- .../src/views/w3m-transactions-view/index.tsx | 1 - 8 files changed, 56 insertions(+), 25 deletions(-) diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/scaffold/src/partials/w3m-account-activity/index.tsx index 427617907..041e5bd84 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-activity/index.tsx @@ -52,12 +52,16 @@ export function AccountActivity({ style }: Props) { }, [transactions]); if (loading && !transactions.length) { - return ; + return ( + + + + ); } if (!Object.keys(transactionsByYear).length) { return ( - + {Object.keys(transactionsByYear) diff --git a/packages/scaffold/src/partials/w3m-account-activity/styles.ts b/packages/scaffold/src/partials/w3m-account-activity/styles.ts index e7fe55f58..e80cb4e01 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/styles.ts +++ b/packages/scaffold/src/partials/w3m-account-activity/styles.ts @@ -3,7 +3,7 @@ import { StyleSheet } from 'react-native'; export default StyleSheet.create({ container: { - padding: Spacing.xs + paddingHorizontal: Spacing.xs }, contentContainer: { paddingBottom: Spacing.m @@ -17,8 +17,8 @@ export default StyleSheet.create({ footer: { height: 40 }, - loader: { - flex: 1 + placeholder: { + minHeight: 200 }, loadMoreButton: { alignSelf: 'center', diff --git a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx index 9b90ba3c5..b6d18c85c 100644 --- a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx @@ -1,4 +1,4 @@ -import { ScrollView } from 'react-native'; +import { ScrollView, type StyleProp, type ViewStyle } from 'react-native'; import { useSnapshot } from 'valtio'; import { AccountController, @@ -8,7 +8,11 @@ import { } from '@web3modal/core-react-native'; import { FlexView, ListItem, Text, ListToken } from '@web3modal/ui-react-native'; -export function AccountTokens() { +interface Props { + style?: StyleProp; +} + +export function AccountTokens({ style }: Props) { const { tokenBalance } = useSnapshot(AccountController.state); const { caipNetwork } = useSnapshot(NetworkController.state); const networkImage = AssetUtil.getNetworkImage(caipNetwork); @@ -33,7 +37,7 @@ export function AccountTokens() { } return ( - + {tokenBalance.map(token => ( + - + - - - {activeTab === 0 && } + + + + + {activeTab === 0 && } {activeTab === 1 && } - {activeTab === 2 && } + {activeTab === 2 && } - + ); } diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts b/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts index ef4af69cc..868ffd6a5 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/styles.ts @@ -3,7 +3,6 @@ import { StyleSheet } from 'react-native'; export default StyleSheet.create({ container: { - alignItems: 'center', height: 400 }, balanceText: { @@ -25,8 +24,15 @@ export default StyleSheet.create({ actionRight: { marginLeft: 8 }, + tab: { + width: '100%', + paddingHorizontal: Spacing.s + }, tabContainer: { flex: 1, width: '100%' + }, + tabContent: { + paddingHorizontal: Spacing.m } }); diff --git a/packages/scaffold/src/views/w3m-account-view/index.tsx b/packages/scaffold/src/views/w3m-account-view/index.tsx index faedff7c6..395f4bc87 100644 --- a/packages/scaffold/src/views/w3m-account-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-view/index.tsx @@ -1,5 +1,6 @@ import { useSnapshot } from 'valtio'; import { useEffect } from 'react'; +import { ScrollView } from 'react-native'; import { FlexView, Icon, @@ -19,9 +20,8 @@ import { SnackController } from '@web3modal/core-react-native'; import { AccountWalletFeatures } from '../../partials/w3m-account-wallet-features'; -import styles from './styles'; -import { ScrollView } from 'react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; export function AccountView() { const Theme = useTheme(); @@ -49,7 +49,15 @@ export function AccountView() { }, []); return ( - + - - + Date: Thu, 22 Aug 2024 12:40:36 -0300 Subject: [PATCH 027/114] chore: style changes --- packages/scaffold/src/views/w3m-transactions-view/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/scaffold/src/views/w3m-transactions-view/index.tsx b/packages/scaffold/src/views/w3m-transactions-view/index.tsx index b66f0217e..629e8d784 100644 --- a/packages/scaffold/src/views/w3m-transactions-view/index.tsx +++ b/packages/scaffold/src/views/w3m-transactions-view/index.tsx @@ -9,7 +9,6 @@ export function TransactionsView() { const styles = StyleSheet.create({ container: { paddingHorizontal: Spacing.l, - marginVertical: Spacing.s, - marginBottom: Spacing.l + marginTop: Spacing.s } }); From beccc6f78ba4112f0e06d762c74d7846f8563f1b Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 26 Aug 2024 16:16:45 -0300 Subject: [PATCH 028/114] chore: wip send view --- packages/common/src/contracts/erc20.ts | 222 +++++++++++++++++ packages/common/src/index.ts | 1 + .../src/controllers/ConnectionController.ts | 55 +++-- .../core/src/controllers/RouterController.ts | 5 +- .../core/src/controllers/SendController.ts | 230 ++++++++++++++++++ packages/core/src/utils/TypeUtil.ts | 58 +++++ packages/ethers/src/client.ts | 70 +++++- packages/ethers5/src/client.ts | 65 +++++ packages/scaffold/src/client.ts | 2 + .../scaffold/src/modal/w3m-router/index.tsx | 7 +- .../w3m-account-wallet-features/index.tsx | 20 +- .../src/partials/w3m-header/index.tsx | 3 + .../src/views/w3m-wallet-send-view/index.tsx | 10 + .../src/views/w3m-wallet-send-view/styles.ts | 8 + packages/wagmi/src/client.ts | 63 ++++- packages/wagmi/src/utils/helpers.ts | 14 +- 16 files changed, 802 insertions(+), 31 deletions(-) create mode 100644 packages/common/src/contracts/erc20.ts create mode 100644 packages/core/src/controllers/SendController.ts create mode 100644 packages/scaffold/src/views/w3m-wallet-send-view/index.tsx create mode 100644 packages/scaffold/src/views/w3m-wallet-send-view/styles.ts diff --git a/packages/common/src/contracts/erc20.ts b/packages/common/src/contracts/erc20.ts new file mode 100644 index 000000000..8f735d820 --- /dev/null +++ b/packages/common/src/contracts/erc20.ts @@ -0,0 +1,222 @@ +export const erc20ABI = [ + { + constant: true, + inputs: [], + name: 'name', + outputs: [ + { + name: '', + type: 'string' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: false, + inputs: [ + { + name: '_spender', + type: 'address' + }, + { + name: '_value', + type: 'uint256' + } + ], + name: 'approve', + outputs: [ + { + name: '', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'totalSupply', + outputs: [ + { + name: '', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: false, + inputs: [ + { + name: '_from', + type: 'address' + }, + { + name: '_to', + type: 'address' + }, + { + name: '_value', + type: 'uint256' + } + ], + name: 'transferFrom', + outputs: [ + { + name: '', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'decimals', + outputs: [ + { + name: '', + type: 'uint8' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address' + } + ], + name: 'balanceOf', + outputs: [ + { + name: 'balance', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'symbol', + outputs: [ + { + name: '', + type: 'string' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: false, + inputs: [ + { + name: '_to', + type: 'address' + }, + { + name: '_value', + type: 'uint256' + } + ], + name: 'transfer', + outputs: [ + { + name: '', + type: 'bool' + } + ], + payable: false, + stateMutability: 'nonpayable', + type: 'function' + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address' + }, + { + name: '_spender', + type: 'address' + } + ], + name: 'allowance', + outputs: [ + { + name: '', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + payable: true, + stateMutability: 'payable', + type: 'fallback' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address' + }, + { + indexed: true, + name: 'spender', + type: 'address' + }, + { + indexed: false, + name: 'value', + type: 'uint256' + } + ], + name: 'Approval', + type: 'event' + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + name: 'from', + type: 'address' + }, + { + indexed: true, + name: 'to', + type: 'address' + }, + { + indexed: false, + name: 'value', + type: 'uint256' + } + ], + name: 'Transfer', + type: 'event' + } +]; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index b3cd9d033..67fc41c28 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,4 +1,5 @@ export { ConstantsUtil } from './utils/ConstantsUtil'; export { NetworkUtil } from './utils/NetworkUtil'; export { DateUtil } from './utils/DateUtil'; +export { erc20ABI } from './contracts/erc20'; export * from './utils/TypeUtil'; diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index f4dca322b..3cccff639 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -2,9 +2,15 @@ import { subscribeKey as subKey } from 'valtio/utils'; import { proxy, ref } from 'valtio'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; import { StorageUtil } from '../utils/StorageUtil'; -import type { Connector, WcWallet } from '../utils/TypeUtil'; +import type { + Connector, + SendTransactionArgs, + WcWallet, + WriteContractArgs +} from '../utils/TypeUtil'; import { RouterController } from './RouterController'; import { ConnectorController } from './ConnectorController'; +import { TransactionsController } from './TransactionsController'; // -- Types --------------------------------------------- // export interface ConnectExternalOptions { @@ -18,6 +24,10 @@ export interface ConnectionControllerClient { connectWalletConnect: (onUri: (uri: string) => void) => Promise; connectExternal?: (options: ConnectExternalOptions) => Promise; signMessage: (message: string) => Promise; + sendTransaction: (args: SendTransactionArgs) => Promise<`0x${string}` | null>; + parseUnits: (value: string, decimals: number) => bigint; + formatUnits: (value: bigint, decimals: number) => string; + writeContract: (args: WriteContractArgs) => Promise<`0x${string}` | null>; disconnect: () => Promise; } @@ -85,19 +95,6 @@ export const ConnectionController = { return this._getClient().signMessage(message); }, - resetWcConnection() { - state.wcUri = undefined; - state.wcPairingExpiry = undefined; - state.wcPromise = undefined; - state.wcLinking = undefined; - state.pressedWallet = undefined; - state.connectedWalletImageUrl = undefined; - ConnectorController.setConnectedConnector(undefined); - StorageUtil.removeWalletConnectDeepLink(); - StorageUtil.removeConnectedWalletImageUrl(); - StorageUtil.removeConnectedConnector(); - }, - setWcLinking(wcLinking: ConnectionControllerState['wcLinking']) { state.wcLinking = wcLinking; }, @@ -132,6 +129,36 @@ export const ConnectionController = { } }, + parseUnits(value: string, decimals: number) { + return this._getClient().parseUnits(value, decimals); + }, + + formatUnits(value: bigint, decimals: number) { + return this._getClient().formatUnits(value, decimals); + }, + + async sendTransaction(args: SendTransactionArgs) { + return this._getClient().sendTransaction(args); + }, + + async writeContract(args: WriteContractArgs) { + return this._getClient().writeContract(args); + }, + + resetWcConnection() { + state.wcUri = undefined; + state.wcPairingExpiry = undefined; + state.wcPromise = undefined; + state.wcLinking = undefined; + state.pressedWallet = undefined; + state.connectedWalletImageUrl = undefined; + ConnectorController.setConnectedConnector(undefined); + TransactionsController.resetTransactions(); + StorageUtil.removeWalletConnectDeepLink(); + StorageUtil.removeConnectedWalletImageUrl(); + StorageUtil.removeConnectedConnector(); + }, + async disconnect() { await this._getClient().disconnect(); this.resetWcConnection(); diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index d574633bc..d579dd33d 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -21,8 +21,11 @@ export interface RouterControllerState { | 'UpdateEmailSecondaryOtp' | 'UpdateEmailWallet' | 'UpgradeEmailWallet' - | 'WalletReceive' | 'WalletCompatibleNetworks' + | 'WalletReceive' + | 'WalletSend' + | 'WalletSendPreview' + | 'WalletSendSelectToken' | 'WhatIsANetwork' | 'WhatIsAWallet'; history: RouterControllerState['view'][]; diff --git a/packages/core/src/controllers/SendController.ts b/packages/core/src/controllers/SendController.ts new file mode 100644 index 000000000..ca80e703e --- /dev/null +++ b/packages/core/src/controllers/SendController.ts @@ -0,0 +1,230 @@ +import { subscribeKey as subKey } from 'valtio/vanilla/utils'; +import { proxy, ref, subscribe as sub } from 'valtio/vanilla'; +import { type Balance } from '@web3modal/common-react-native'; +import { erc20ABI } from '@web3modal/common-react-native'; +// import { RouterController } from './RouterController'; +import { AccountController } from './AccountController'; +import { ConnectionController } from './ConnectionController'; +import { SnackController } from './SnackController'; +import { CoreHelperUtil } from '../utils/CoreHelperUtil'; +import { EventsController } from './EventsController'; +import { NetworkController } from './NetworkController'; + +// -- Types --------------------------------------------- // + +export interface TxParams { + receiverAddress: string; + sendTokenAmount: number; + gasPrice: bigint; + decimals: string; +} + +export interface ContractWriteParams { + receiverAddress: string; + tokenAddress: string; + sendTokenAmount: number; + decimals: string; +} +export interface SendControllerState { + token?: Balance; + sendTokenAmount?: number; + receiverAddress?: string; + receiverProfileName?: string; + receiverProfileImageUrl?: string; + gasPrice?: bigint; + gasPriceInUSD?: number; + loading: boolean; +} + +type StateKey = keyof SendControllerState; + +// -- State --------------------------------------------- // +const state = proxy({ + loading: false +}); + +// -- Controller ---------------------------------------- // +export const SendController = { + state, + + subscribe(callback: (newState: SendControllerState) => void) { + return sub(state, () => callback(state)); + }, + + subscribeKey(key: K, callback: (value: SendControllerState[K]) => void) { + return subKey(state, key, callback); + }, + + setToken(token: SendControllerState['token']) { + if (token) { + state.token = ref(token); + } + }, + + setTokenAmount(sendTokenAmount: SendControllerState['sendTokenAmount']) { + state.sendTokenAmount = sendTokenAmount; + }, + + setReceiverAddress(receiverAddress: SendControllerState['receiverAddress']) { + state.receiverAddress = receiverAddress; + }, + + setReceiverProfileImageUrl( + receiverProfileImageUrl: SendControllerState['receiverProfileImageUrl'] + ) { + state.receiverProfileImageUrl = receiverProfileImageUrl; + }, + + setReceiverProfileName(receiverProfileName: SendControllerState['receiverProfileName']) { + state.receiverProfileName = receiverProfileName; + }, + + setGasPrice(gasPrice: SendControllerState['gasPrice']) { + state.gasPrice = gasPrice; + }, + + setGasPriceInUsd(gasPriceInUSD: SendControllerState['gasPriceInUSD']) { + state.gasPriceInUSD = gasPriceInUSD; + }, + + setLoading(loading: SendControllerState['loading']) { + state.loading = loading; + }, + + sendToken() { + if (this.state.token?.address && this.state.sendTokenAmount && this.state.receiverAddress) { + EventsController.sendEvent({ + type: 'track', + event: 'SEND_INITIATED', + properties: { + isSmartAccount: false, + token: this.state.token.address, + amount: this.state.sendTokenAmount, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + this.sendERC20Token({ + receiverAddress: this.state.receiverAddress, + tokenAddress: this.state.token.address, + sendTokenAmount: this.state.sendTokenAmount, + decimals: this.state.token.quantity.decimals + }); + } else if ( + this.state.receiverAddress && + this.state.sendTokenAmount && + this.state.gasPrice && + this.state.token?.quantity.decimals + ) { + EventsController.sendEvent({ + type: 'track', + event: 'SEND_INITIATED', + properties: { + isSmartAccount: false, + token: this.state.token?.symbol, + amount: this.state.sendTokenAmount, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + this.sendNativeToken({ + receiverAddress: this.state.receiverAddress, + sendTokenAmount: this.state.sendTokenAmount, + gasPrice: this.state.gasPrice, + decimals: this.state.token.quantity.decimals + }); + } + }, + + async sendNativeToken(params: TxParams) { + // RouterController.pushTransactionStack({ + // view: 'Account', + // goBack: false + // }); + + const to = params.receiverAddress as `0x${string}`; + const address = AccountController.state.address as `0x${string}`; + const value = ConnectionController.parseUnits( + params.sendTokenAmount.toString(), + Number(params.decimals) + ); + const data = '0x'; + + try { + await ConnectionController.sendTransaction({ + to, + address, + data, + value, + gasPrice: params.gasPrice + }); + SnackController.showSuccess('Transaction started'); + EventsController.sendEvent({ + type: 'track', + event: 'SEND_SUCCESS', + properties: { + isSmartAccount: false, + token: this.state.token?.symbol || '', + amount: params.sendTokenAmount, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + this.resetSend(); + } catch (error) { + EventsController.sendEvent({ + type: 'track', + event: 'SEND_ERROR', + properties: { + isSmartAccount: false, + token: this.state.token?.symbol || '', + amount: params.sendTokenAmount, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + SnackController.showError('Something went wrong'); + } + }, + + async sendERC20Token(params: ContractWriteParams) { + // RouterController.pushTransactionStack({ + // view: 'Account', + // goBack: false + // }); + + const amount = ConnectionController.parseUnits( + params.sendTokenAmount.toString(), + Number(params.decimals) + ); + + try { + if ( + AccountController.state.address && + params.sendTokenAmount && + params.receiverAddress && + params.tokenAddress + ) { + await ConnectionController.writeContract({ + fromAddress: AccountController.state.address as `0x${string}`, + tokenAddress: CoreHelperUtil.getPlainAddress( + params.tokenAddress as `${string}:${string}:${string}` + ) as `0x${string}`, + receiverAddress: params.receiverAddress as `0x${string}`, + tokenAmount: amount, + method: 'transfer', + abi: erc20ABI + }); + SnackController.showSuccess('Transaction started'); + this.resetSend(); + } + } catch (error) { + SnackController.showError('Something went wrong'); + } + }, + + resetSend() { + state.token = undefined; + state.sendTokenAmount = undefined; + state.receiverAddress = undefined; + state.receiverProfileImageUrl = undefined; + state.receiverProfileName = undefined; + state.loading = false; + } +}; diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index e8721aedb..121da526e 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -334,8 +334,66 @@ export type Event = cursor: string | undefined; isSmartAccount: boolean; }; + } + | { + type: 'track'; + event: 'OPEN_SEND'; + properties: { + isSmartAccount: boolean; + network: string; + }; + } + | { + type: 'track'; + event: 'SEND_INITIATED'; + properties: { + isSmartAccount: boolean; + network: string; + token: string; + amount: number; + }; + } + | { + type: 'track'; + event: 'SEND_SUCCESS'; + properties: { + isSmartAccount: boolean; + network: string; + token: string; + amount: number; + }; + } + | { + type: 'track'; + event: 'SEND_ERROR'; + properties: { + isSmartAccount: boolean; + network: string; + token: string; + amount: number; + }; }; +// -- Send Controller Types ------------------------------------- + +export interface SendTransactionArgs { + to: `0x${string}`; + data: `0x${string}`; + value: bigint; + gas?: bigint; + gasPrice: bigint; + address: `0x${string}`; +} + +export interface WriteContractArgs { + receiverAddress: `0x${string}`; + tokenAmount: bigint; + tokenAddress: `0x${string}`; + fromAddress: `0x${string}`; + method: 'send' | 'transfer' | 'call'; + abi: any; +} + // -- Email Types ------------------------------------------------ /** * Matches type defined for packages/email/src/W3mFrameProvider.ts diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index 36ead8d20..1619bc6c4 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -1,10 +1,15 @@ import { + BrowserProvider, + Contract, InfuraProvider, JsonRpcProvider, + JsonRpcSigner, formatEther, + formatUnits, getAddress, hexlify, isHexString, + parseUnits, toUtf8Bytes } from 'ethers'; import { @@ -16,8 +21,10 @@ import { type LibraryOptions, type NetworkControllerClient, type PublicStateControllerState, + type SendTransactionArgs, type Token, - Web3ModalScaffold + Web3ModalScaffold, + type WriteContractArgs } from '@web3modal/scaffold-react-native'; import { NetworkUtil } from '@web3modal/common-react-native'; import { @@ -275,6 +282,67 @@ export class Web3Modal extends Web3ModalScaffold { }); return signature as `0x${string}`; + }, + + parseUnits: (value: string, decimals: number) => parseUnits(value, decimals), + + formatUnits: (value: bigint, decimals: number) => formatUnits(value, decimals), + + sendTransaction: async (data: SendTransactionArgs) => { + const { chainId, provider, address } = EthersStoreUtil.state; + + if (!provider) { + throw new Error('ethersClient:sendTransaction - provider is undefined'); + } + + if (!address) { + throw new Error('ethersClient:sendTransaction - address is undefined'); + } + + const txParams = { + to: data.to, + value: data.value, + gasLimit: data.gas, + gasPrice: data.gasPrice, + data: data.data, + type: 0 + }; + + const browserProvider = new BrowserProvider(provider, chainId); + const signer = new JsonRpcSigner(browserProvider, address); + const txResponse = await signer.sendTransaction(txParams); + const txReceipt = await txResponse.wait(); + + return (txReceipt?.hash as `0x${string}`) || null; + }, + + writeContract: async (data: WriteContractArgs) => { + const { chainId, provider, address } = EthersStoreUtil.state; + + if (!provider) { + throw new Error('ethersClient:writeContract - provider is undefined'); + } + + if (!address) { + throw new Error('ethersClient:writeContract - address is undefined'); + } + + const browserProvider = new BrowserProvider(provider, chainId); + const signer = new JsonRpcSigner(browserProvider, address); + const contract = new Contract(data.tokenAddress, data.abi, signer); + + if (!contract || !data.method) { + throw new Error('Contract method is undefined'); + } + + const method = contract[data.method]; + if (method) { + const tx = await method(data.receiverAddress, data.tokenAmount); + + return tx; + } + + throw new Error('Contract method is undefined'); } }; diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index c2ee1dbd7..670eddb4d 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -8,6 +8,7 @@ import { type LibraryOptions, type NetworkControllerClient, type PublicStateControllerState, + type SendTransactionArgs, type Token, Web3ModalScaffold } from '@web3modal/scaffold-react-native'; @@ -268,7 +269,71 @@ export class Web3Modal extends Web3ModalScaffold { }); return signature as `0x${string}`; + }, + + parseUnits: (value: string, decimals: number) => + ethers.utils.parseUnits(value, decimals).toBigInt(), + + formatUnits: (value: bigint, decimals: number) => ethers.utils.formatUnits(value, decimals), + + sendTransaction: async (data: SendTransactionArgs) => { + const provider = EthersStoreUtil.state.provider; + const address = EthersStoreUtil.state.address; + + if (!provider) { + throw new Error('connectionControllerClient:sendTransaction - provider is undefined'); + } + + if (!address) { + throw new Error('connectionControllerClient:sendTransaction - address is undefined'); + } + + const txParams = { + to: data.to, + value: data.value, + gasLimit: data.gas, + gasPrice: data.gasPrice, + data: data.data, + type: 0 + }; + + const browserProvider = new ethers.providers.Web3Provider(provider); + const signer = browserProvider.getSigner(); + + const txResponse = await signer.sendTransaction(txParams); + const txReceipt = await txResponse.wait(); + + return (txReceipt?.blockHash as `0x${string}`) || null; } + + // writeContract: async (data: WriteContractArgs) => { + // const { chainId, provider, address } = EthersStoreUtil.state; + + // if (!provider) { + // throw new Error('ethersClient:writeContract - provider is undefined'); + // } + + // if (!address) { + // throw new Error('ethersClient:writeContract - address is undefined'); + // } + + // const browserProvider = new BrowserProvider(provider, chainId); + // const signer = new JsonRpcSigner(browserProvider, address); + // const contract = new Contract(data.tokenAddress, data.abi, signer); + + // if (!contract || !data.method) { + // throw new Error('Contract method is undefined'); + // } + + // const method = contract[data.method]; + // if (method) { + // const tx = await method(data.receiverAddress, data.tokenAmount); + + // return tx; + // } + + // throw new Error('Contract method is undefined'); + // } }; super({ diff --git a/packages/scaffold/src/client.ts b/packages/scaffold/src/client.ts index 6abe5c721..0b1832af6 100644 --- a/packages/scaffold/src/client.ts +++ b/packages/scaffold/src/client.ts @@ -142,6 +142,8 @@ export class Web3ModalScaffold { AccountController.setCaipAddress(caipAddress); }; + protected getCaipAddress = () => AccountController.state.caipAddress; + protected setBalance: (typeof AccountController)['setBalance'] = (balance, balanceSymbol) => { AccountController.setBalance(balance, balanceSymbol); }; diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/scaffold/src/modal/w3m-router/index.tsx index 68abd6932..7d2a6be9d 100644 --- a/packages/scaffold/src/modal/w3m-router/index.tsx +++ b/packages/scaffold/src/modal/w3m-router/index.tsx @@ -24,6 +24,7 @@ import { AccountView } from '../../views/w3m-account-view'; import { WalletReceiveView } from '../../views/w3m-wallet-receive-view'; import { WalletCompatibleNetworks } from '../../views/w3m-wallet-compatible-networks-view'; import { TransactionsView } from '../../views/w3m-transactions-view'; +import { WalletSendView } from '../../views/w3m-wallet-send-view'; export function Web3Router() { const { view } = useSnapshot(RouterController.state); @@ -68,10 +69,12 @@ export function Web3Router() { return UpdateEmailWalletView; case 'UpgradeEmailWallet': return UpgradeEmailWalletView; - case 'WalletReceive': - return WalletReceiveView; case 'WalletCompatibleNetworks': return WalletCompatibleNetworks; + case 'WalletReceive': + return WalletReceiveView; + case 'WalletSend': + return WalletSendView; case 'WhatIsANetwork': return WhatIsNetworkView; case 'WhatIsAWallet': diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx index b75fa7b8e..5a3e680a8 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx @@ -5,8 +5,8 @@ import { AccountController, CoreHelperUtil, EventsController, - RouterController, - SnackController + NetworkController, + RouterController } from '@web3modal/core-react-native'; import type { Balance as BalanceType } from '@web3modal/common-react-native'; import { AccountNfts } from '../w3m-account-nfts'; @@ -21,6 +21,7 @@ export interface AccountWalletFeaturesProps { export function AccountWalletFeatures() { const [activeTab, setActiveTab] = useState(0); const { tokenBalance } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); const balance = CoreHelperUtil.calculateAndFormatBalance(tokenBalance as BalanceType[]); const onTabChange = (index: number) => { @@ -40,9 +41,16 @@ export function AccountWalletFeatures() { }); }; - // TODO: Implement this features - const onMissingPress = () => { - SnackController.showError('Feature not implemented'); + const onSendPress = () => { + EventsController.sendEvent({ + type: 'track', + event: 'OPEN_SEND', + properties: { + network: caipNetwork?.id || '', + isSmartAccount: false + } + }); + RouterController.push('WalletSend'); }; const onReceivePress = () => { @@ -76,7 +84,7 @@ export function AccountWalletFeatures() { backgroundColor="accent-glass-010" pressedColor="accent-glass-020" style={[styles.action, styles.actionRight]} - onPress={onMissingPress} + onPress={onSendPress} /> diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index cf94f9fd3..f38f37479 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -35,6 +35,9 @@ export function Header() { UpgradeEmailWallet: 'Upgrade wallet', WalletCompatibleNetworks: 'Compatible networks', WalletReceive: 'Receive', + WalletSend: 'Send', + WalletSendPreview: 'Review send', + WalletSendSelectToken: 'Select token', WhatIsANetwork: 'What is a network?', WhatIsAWallet: 'What is a wallet?' }; diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx new file mode 100644 index 000000000..d69077810 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx @@ -0,0 +1,10 @@ +import { View, Text } from 'react-native'; +import styles from './styles'; + +export function WalletSendView() { + return ( + + SEND + + ); +} diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts new file mode 100644 index 000000000..c866ab3d8 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts @@ -0,0 +1,8 @@ +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center' + } +}); diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index c7189ecf1..d5d5f9364 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -1,15 +1,20 @@ -import { formatUnits, type Hex } from 'viem'; +import { formatUnits, type Hex, parseUnits } from 'viem'; import { type GetAccountReturnType, connect, disconnect, signMessage, + getAccount, switchChain, watchAccount, watchConnectors, getEnsName, getEnsAvatar as wagmiGetEnsAvatar, - getBalance + getBalance, + prepareTransactionRequest, + sendTransaction as wagmiSendTransaction, + waitForTransactionReceipt, + writeContract as wagmiWriteContract } from '@wagmi/core'; import { mainnet, type Chain } from '@wagmi/core/chains'; import { EthereumProvider, OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; @@ -22,8 +27,10 @@ import { type LibraryOptions, type NetworkControllerClient, type PublicStateControllerState, + type SendTransactionArgs, type Token, - Web3ModalScaffold + Web3ModalScaffold, + type WriteContractArgs } from '@web3modal/scaffold-react-native'; import { ConstantsUtil, @@ -32,13 +39,14 @@ import { StorageUtil } from '@web3modal/scaffold-utils-react-native'; import { NetworkUtil } from '@web3modal/common-react-native'; +import { type Web3ModalSIWEClient } from '@web3modal/siwe-react-native'; import { getCaipDefaultChain, getEmailCaipNetworks, - getWalletConnectCaipNetworks + getWalletConnectCaipNetworks, + requireCaipAddress } from './utils/helpers'; import { defaultWagmiConfig } from './utils/defaultWagmiConfig'; -import { type Web3ModalSIWEClient } from '@web3modal/siwe-react-native'; // -- Types --------------------------------------------------------------------- type WagmiConfig = ReturnType; @@ -227,7 +235,50 @@ export class Web3Modal extends Web3ModalScaffold { const { SIWEController } = await import('@web3modal/siwe-react-native'); await SIWEController.signOut(); } - } + }, + + sendTransaction: async (data: SendTransactionArgs) => { + const { chainId } = getAccount(this.wagmiConfig); + + const txParams = { + account: data.address, + to: data.to, + value: data.value, + gas: data.gas, + gasPrice: data.gasPrice, + data: data.data, + chainId, + type: 'legacy' as const + }; + + await prepareTransactionRequest(this.wagmiConfig, txParams); + const tx = await wagmiSendTransaction(this.wagmiConfig, txParams); + + await waitForTransactionReceipt(this.wagmiConfig, { hash: tx, timeout: 25000 }); + + return tx; + }, + + writeContract: async (data: WriteContractArgs) => { + const caipAddress = this.getCaipAddress() || ''; + const account = requireCaipAddress(caipAddress); + const chainId = NetworkUtil.caipNetworkIdToNumber(this.getCaipNetwork()?.id); + + const tx = await wagmiWriteContract(wagmiConfig, { + chainId, + address: data.tokenAddress, + account, + abi: data.abi, + functionName: data.method, + args: [data.receiverAddress, data.tokenAmount] + }); + + return tx; + }, + + parseUnits, + + formatUnits }; super({ diff --git a/packages/wagmi/src/utils/helpers.ts b/packages/wagmi/src/utils/helpers.ts index bab0ff02c..63b2e6a3f 100644 --- a/packages/wagmi/src/utils/helpers.ts +++ b/packages/wagmi/src/utils/helpers.ts @@ -7,7 +7,7 @@ import { PresetsUtil, ConstantsUtil } from '@web3modal/scaffold-utils-react-nati import type { Connector } from '@wagmi/core'; import { EthereumProvider } from '@walletconnect/ethereum-provider'; import type { Web3ModalClientOptions } from '../client'; -import { http } from 'viem'; +import { http, type Hex } from 'viem'; export function getCaipDefaultChain(chain?: Web3ModalClientOptions['defaultChain']) { if (!chain) { @@ -56,3 +56,15 @@ export function getTransport({ chainId, projectId }: { chainId: number; projectI return http(`${RPC_URL}/v1/?chainId=${ConstantsUtil.EIP155}:${chainId}&projectId=${projectId}`); } + +export function requireCaipAddress(caipAddress: string) { + if (!caipAddress) { + throw new Error('No CAIP address provided'); + } + const account = caipAddress.split(':')[2] as Hex; + if (!account) { + throw new Error('Invalid CAIP address'); + } + + return account; +} From 087190f44076ba410a177ef375a37487f3ddb4aa Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 27 Aug 2024 18:01:40 -0300 Subject: [PATCH 029/114] chore: wip send screen --- packages/common/package.json | 1 + packages/common/src/index.ts | 3 +- packages/common/src/utils/NumberUtil.ts | 31 +++++++ packages/core/src/index.ts | 2 + .../src/partials/w3m-input-token/intex.tsx | 90 +++++++++++++++++++ .../src/partials/w3m-input-token/styles.ts | 20 +++++ .../src/partials/w3m-input-token/utils.ts | 21 +++++ .../src/views/w3m-account-view/index.tsx | 2 + .../src/views/w3m-wallet-send-view/index.tsx | 84 ++++++++++++++++- .../src/views/w3m-wallet-send-view/styles.ts | 21 ++++- yarn.lock | 8 ++ 11 files changed, 275 insertions(+), 8 deletions(-) create mode 100644 packages/common/src/utils/NumberUtil.ts create mode 100644 packages/scaffold/src/partials/w3m-input-token/intex.tsx create mode 100644 packages/scaffold/src/partials/w3m-input-token/styles.ts create mode 100644 packages/scaffold/src/partials/w3m-input-token/utils.ts diff --git a/packages/common/package.json b/packages/common/package.json index 67e8fcddd..cfc0e8128 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -12,6 +12,7 @@ "lint": "eslint . --ext .js,.jsx,.ts,.tsx" }, "dependencies": { + "bignumber.js": "9.1.2", "dayjs": "1.11.10" }, "files": [ diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 67fc41c28..7e84124c2 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,5 +1,6 @@ export { ConstantsUtil } from './utils/ConstantsUtil'; -export { NetworkUtil } from './utils/NetworkUtil'; export { DateUtil } from './utils/DateUtil'; +export { NetworkUtil } from './utils/NetworkUtil'; +export { NumberUtil } from './utils/NumberUtil'; export { erc20ABI } from './contracts/erc20'; export * from './utils/TypeUtil'; diff --git a/packages/common/src/utils/NumberUtil.ts b/packages/common/src/utils/NumberUtil.ts new file mode 100644 index 000000000..760071bad --- /dev/null +++ b/packages/common/src/utils/NumberUtil.ts @@ -0,0 +1,31 @@ +import * as BigNumber from 'bignumber.js'; + +export const NumberUtil = { + bigNumber(value: BigNumber.BigNumber.Value) { + return new BigNumber.BigNumber(value); + }, + + /** + * Multiply two numbers represented as strings with BigNumber to handle decimals correctly + * @param a string + * @param b string + * @returns + */ + multiply(a: BigNumber.BigNumber.Value | undefined, b: BigNumber.BigNumber.Value | undefined) { + if (a === undefined || b === undefined) { + return BigNumber.BigNumber(0); + } + + const aBigNumber = new BigNumber.BigNumber(a); + const bBigNumber = new BigNumber.BigNumber(b); + + return aBigNumber.multipliedBy(bBigNumber); + }, + + roundNumber(number: number, threshold: number, fixed: number) { + const roundedNumber = + number.toString().length >= threshold ? Number(number).toFixed(fixed) : number; + + return roundedNumber; + } +}; diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 0d9b5eb55..e1e8c96be 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -50,6 +50,8 @@ export { type TransactionsControllerState } from './controllers/TransactionsController'; +export { SendController, type SendControllerState } from './controllers/SendController'; + // -- Utils ------------------------------------------------------------------- export { AssetUtil } from './utils/AssetUtil'; export { ConstantsUtil } from './utils/ConstantsUtil'; diff --git a/packages/scaffold/src/partials/w3m-input-token/intex.tsx b/packages/scaffold/src/partials/w3m-input-token/intex.tsx new file mode 100644 index 000000000..517b9ac90 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-input-token/intex.tsx @@ -0,0 +1,90 @@ +import { TextInput, type StyleProp, type ViewStyle } from 'react-native'; +import { FlexView, Link, Text, useTheme } from '@web3modal/ui-react-native'; +import { NumberUtil, type Balance } from '@web3modal/common-react-native'; +import styles from './styles'; + +import { SendController } from '@web3modal/core-react-native'; +import { getMaxAmount, getSendValue } from './utils'; + +export interface InputTokenProps { + token?: Balance; + sendTokenAmount?: number; + gasPriceInUSD?: number; + style?: StyleProp; +} + +export function InputToken({ token, sendTokenAmount, gasPriceInUSD, style }: InputTokenProps) { + const Theme = useTheme(); + const sendValue = getSendValue(token, sendTokenAmount); + const maxAmount = getMaxAmount(token); + const maxError = token && sendTokenAmount && sendTokenAmount > Number(token.quantity.numeric); + + const onInputChange = (value: string) => { + const formattedValue = value.replace(/,/g, '.'); + Number(formattedValue) + ? SendController.setTokenAmount(Number(formattedValue)) + : SendController.setTokenAmount(undefined); + }; + + const onMaxPress = () => { + if (token && gasPriceInUSD) { + const amountOfTokenGasRequires = NumberUtil.bigNumber(gasPriceInUSD.toFixed(5)).dividedBy( + token.price + ); + + const isNetworkToken = token.address === undefined; + + const maxValue = isNetworkToken + ? NumberUtil.bigNumber(token.quantity.numeric).minus(amountOfTokenGasRequires) + : NumberUtil.bigNumber(token.quantity.numeric); + + SendController.setTokenAmount(Number(maxValue.toFixed(20))); + } + }; + + return ( + + + + ETH + + + + {sendValue ?? ''} + + + + {maxAmount ?? '1.204 tst'} + + Max + + + + ); +} diff --git a/packages/scaffold/src/partials/w3m-input-token/styles.ts b/packages/scaffold/src/partials/w3m-input-token/styles.ts new file mode 100644 index 000000000..fa338ac9f --- /dev/null +++ b/packages/scaffold/src/partials/w3m-input-token/styles.ts @@ -0,0 +1,20 @@ +import { BorderRadius, Spacing } from '@web3modal/ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + height: 100, + width: '100%', + borderRadius: BorderRadius.s, + borderWidth: 1 + }, + input: { + fontSize: 32, + flex: 1, + marginRight: Spacing.xs + }, + sendValue: { + flex: 1, + marginRight: Spacing.xs + } +}); diff --git a/packages/scaffold/src/partials/w3m-input-token/utils.ts b/packages/scaffold/src/partials/w3m-input-token/utils.ts new file mode 100644 index 000000000..6f384082b --- /dev/null +++ b/packages/scaffold/src/partials/w3m-input-token/utils.ts @@ -0,0 +1,21 @@ +import { type Balance, NumberUtil } from '@web3modal/common-react-native'; +import { UiUtil } from '@web3modal/ui-react-native'; + +export function getSendValue(token?: Balance, sendTokenAmount?: number) { + if (token && sendTokenAmount) { + const price = token.price; + const totalValue = price * sendTokenAmount; + + return totalValue ? `$${UiUtil.formatNumberToLocalString(totalValue, 2)}` : 'Incorrect value'; + } + + return null; +} + +export function getMaxAmount(token?: Balance) { + if (token) { + NumberUtil.roundNumber(Number(token.quantity.numeric), 6, 5); + } + + return null; +} diff --git a/packages/scaffold/src/views/w3m-account-view/index.tsx b/packages/scaffold/src/views/w3m-account-view/index.tsx index 395f4bc87..3e4152237 100644 --- a/packages/scaffold/src/views/w3m-account-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-view/index.tsx @@ -17,6 +17,7 @@ import { NetworkController, OptionsController, RouterController, + SendController, SnackController } from '@web3modal/core-react-native'; import { AccountWalletFeatures } from '../../partials/w3m-account-wallet-features'; @@ -46,6 +47,7 @@ export function AccountView() { useEffect(() => { AccountController.fetchTokenBalance(); + SendController.resetSend(); }, []); return ( diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx index d69077810..12bedcb62 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx @@ -1,10 +1,86 @@ -import { View, Text } from 'react-native'; +import { useEffect } from 'react'; +import { Platform, ScrollView } from 'react-native'; +import { useSnapshot } from 'valtio'; +import { AccountController, SendController } from '@web3modal/core-react-native'; +import { + Button, + FlexView, + IconBox, + LoadingSpinner, + Spacing, + Text +} from '@web3modal/ui-react-native'; +import { InputToken } from '../../partials/w3m-input-token/intex'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { useKeyboard } from '../../hooks/useKeyboard'; import styles from './styles'; export function WalletSendView() { + const { padding } = useCustomDimensions(); + const { keyboardShown, keyboardHeight } = useKeyboard(); + const { token, sendTokenAmount, receiverAddress, receiverProfileName, loading, gasPriceInUSD } = + useSnapshot(SendController.state); + const { tokenBalance } = useSnapshot(AccountController.state); + + const paddingBottom = Platform.select({ + android: keyboardShown ? keyboardHeight + Spacing['2xl'] : Spacing['2xl'], + default: Spacing['2xl'] + }); + + const onSendPress = () => { + if (loading) return; + }; + + const getActionText = () => { + if (token && sendTokenAmount && sendTokenAmount > Number(token.quantity.numeric)) { + return 'Insufficient balance'; + } + + return 'Send'; + }; + + useEffect(() => { + // TODO: remove this + SendController.setToken(tokenBalance[0]); + }, [tokenBalance]); + return ( - - SEND - + + + + + + + + + + ); } diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts index c866ab3d8..ae86e5648 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts +++ b/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts @@ -1,8 +1,23 @@ +import { Spacing } from '@web3modal/ui-react-native'; import { StyleSheet } from 'react-native'; export default StyleSheet.create({ - container: { - justifyContent: 'center', - alignItems: 'center' + sendButton: { + width: '100%', + marginTop: Spacing.xl + }, + tokenInput: { + marginBottom: Spacing.xs + }, + mockInput: { + width: '100%', + borderWidth: 1, + height: 120, + borderRadius: 20 + }, + arrowIcon: { + position: 'absolute', + top: -30, + borderRadius: 20 } }); diff --git a/yarn.lock b/yarn.lock index fc776e2d5..616a8382a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10700,6 +10700,7 @@ __metadata: version: 0.0.0-use.local resolution: "@web3modal/common-react-native@workspace:packages/common" dependencies: + bignumber.js: "npm:9.1.2" dayjs: "npm:1.11.10" languageName: unknown linkType: soft @@ -12054,6 +12055,13 @@ __metadata: languageName: node linkType: hard +"bignumber.js@npm:9.1.2": + version: 9.1.2 + resolution: "bignumber.js@npm:9.1.2" + checksum: e17786545433f3110b868725c449fa9625366a6e675cd70eb39b60938d6adbd0158cb4b3ad4f306ce817165d37e63f4aa3098ba4110db1d9a3b9f66abfbaf10d + languageName: node + linkType: hard + "binary-extensions@npm:^2.0.0": version: 2.2.0 resolution: "binary-extensions@npm:2.2.0" From fa3fa541c2266ac607d66081cabc930afd43551e Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 3 Sep 2024 17:05:39 -0300 Subject: [PATCH 030/114] chore: adding send view --- .../src/partials/w3m-input-address/index.tsx | 46 +++++++++++++++++++ .../src/partials/w3m-input-address/styles.ts | 15 ++++++ .../src/partials/w3m-input-token/intex.tsx | 27 +++++++---- .../src/partials/w3m-input-token/utils.ts | 2 +- .../src/views/w3m-wallet-send-view/index.tsx | 11 +++-- .../src/views/w3m-wallet-send-view/styles.ts | 3 ++ .../ui/src/composites/wui-button/index.tsx | 4 +- .../ui/src/composites/wui-button/styles.ts | 2 +- .../src/composites/wui-token-button/index.tsx | 18 ++++++++ .../src/composites/wui-token-button/styles.ts | 14 ++++++ packages/ui/src/index.ts | 1 + 11 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 packages/scaffold/src/partials/w3m-input-address/index.tsx create mode 100644 packages/scaffold/src/partials/w3m-input-address/styles.ts create mode 100644 packages/ui/src/composites/wui-token-button/index.tsx create mode 100644 packages/ui/src/composites/wui-token-button/styles.ts diff --git a/packages/scaffold/src/partials/w3m-input-address/index.tsx b/packages/scaffold/src/partials/w3m-input-address/index.tsx new file mode 100644 index 000000000..7c5acc15d --- /dev/null +++ b/packages/scaffold/src/partials/w3m-input-address/index.tsx @@ -0,0 +1,46 @@ +import { TextInput } from 'react-native'; +import { FlexView, useTheme } from '@web3modal/ui-react-native'; +import { SendController } from '@web3modal/core-react-native'; +import styles from './styles'; + +export interface InputAddressProps { + value?: string; +} + +export function InputAddress({ value }: InputAddressProps) { + const Theme = useTheme(); + + const onInputChange = (address: string) => { + SendController.setReceiverAddress(address); + }; + + return ( + + + + ); +} diff --git a/packages/scaffold/src/partials/w3m-input-address/styles.ts b/packages/scaffold/src/partials/w3m-input-address/styles.ts new file mode 100644 index 000000000..5a4b2e7ed --- /dev/null +++ b/packages/scaffold/src/partials/w3m-input-address/styles.ts @@ -0,0 +1,15 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius } from '@web3modal/ui-react-native'; + +export default StyleSheet.create({ + container: { + height: 100, + width: '100%', + borderRadius: BorderRadius.s, + borderWidth: 1 + }, + input: { + fontSize: 18, + flex: 1 + } +}); diff --git a/packages/scaffold/src/partials/w3m-input-token/intex.tsx b/packages/scaffold/src/partials/w3m-input-token/intex.tsx index 517b9ac90..ec65d3890 100644 --- a/packages/scaffold/src/partials/w3m-input-token/intex.tsx +++ b/packages/scaffold/src/partials/w3m-input-token/intex.tsx @@ -1,9 +1,10 @@ +import { useState } from 'react'; import { TextInput, type StyleProp, type ViewStyle } from 'react-native'; -import { FlexView, Link, Text, useTheme } from '@web3modal/ui-react-native'; +import { FlexView, Link, Text, useTheme, TokenButton } from '@web3modal/ui-react-native'; import { NumberUtil, type Balance } from '@web3modal/common-react-native'; -import styles from './styles'; - import { SendController } from '@web3modal/core-react-native'; + +import styles from './styles'; import { getMaxAmount, getSendValue } from './utils'; export interface InputTokenProps { @@ -15,12 +16,14 @@ export interface InputTokenProps { export function InputToken({ token, sendTokenAmount, gasPriceInUSD, style }: InputTokenProps) { const Theme = useTheme(); + const [inputValue, setInputValue] = useState(undefined); const sendValue = getSendValue(token, sendTokenAmount); const maxAmount = getMaxAmount(token); const maxError = token && sendTokenAmount && sendTokenAmount > Number(token.quantity.numeric); const onInputChange = (value: string) => { const formattedValue = value.replace(/,/g, '.'); + setInputValue(formattedValue); Number(formattedValue) ? SendController.setTokenAmount(Number(formattedValue)) : SendController.setTokenAmount(undefined); @@ -28,17 +31,18 @@ export function InputToken({ token, sendTokenAmount, gasPriceInUSD, style }: Inp const onMaxPress = () => { if (token && gasPriceInUSD) { - const amountOfTokenGasRequires = NumberUtil.bigNumber(gasPriceInUSD.toFixed(5)).dividedBy( + const amountOfTokenGasRequired = NumberUtil.bigNumber(gasPriceInUSD.toFixed(5)).dividedBy( token.price ); const isNetworkToken = token.address === undefined; const maxValue = isNetworkToken - ? NumberUtil.bigNumber(token.quantity.numeric).minus(amountOfTokenGasRequires) + ? NumberUtil.bigNumber(token.quantity.numeric).minus(amountOfTokenGasRequired) : NumberUtil.bigNumber(token.quantity.numeric); SendController.setTokenAmount(Number(maxValue.toFixed(20))); + setInputValue(maxValue.toFixed(20)); } }; @@ -60,7 +64,7 @@ export function InputToken({ token, sendTokenAmount, gasPriceInUSD, style }: Inp style={[styles.input, { color: Theme['fg-100'] }]} autoCapitalize="none" autoCorrect={false} - value={sendTokenAmount?.toLocaleString()} + value={inputValue} onChangeText={onInputChange} keyboardType="decimal-pad" inputMode="decimal" @@ -72,15 +76,20 @@ export function InputToken({ token, sendTokenAmount, gasPriceInUSD, style }: Inp numberOfLines={1} autoFocus={!!token} /> - ETH + - + {sendValue ?? ''} - {maxAmount ?? '1.204 tst'} + {maxAmount ?? ''} Max diff --git a/packages/scaffold/src/partials/w3m-input-token/utils.ts b/packages/scaffold/src/partials/w3m-input-token/utils.ts index 6f384082b..158ba144c 100644 --- a/packages/scaffold/src/partials/w3m-input-token/utils.ts +++ b/packages/scaffold/src/partials/w3m-input-token/utils.ts @@ -14,7 +14,7 @@ export function getSendValue(token?: Balance, sendTokenAmount?: number) { export function getMaxAmount(token?: Balance) { if (token) { - NumberUtil.roundNumber(Number(token.quantity.numeric), 6, 5); + return NumberUtil.roundNumber(Number(token.quantity.numeric), 6, 5); } return null; diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx index 12bedcb62..641d1799e 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx @@ -13,6 +13,7 @@ import { import { InputToken } from '../../partials/w3m-input-token/intex'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import { useKeyboard } from '../../hooks/useKeyboard'; +import { InputAddress } from '../../partials/w3m-input-address'; import styles from './styles'; export function WalletSendView() { @@ -36,12 +37,12 @@ export function WalletSendView() { return 'Insufficient balance'; } - return 'Send'; + return 'Preview Send'; }; useEffect(() => { - // TODO: remove this - SendController.setToken(tokenBalance[0]); + // TODO: check this + SendController.setToken(tokenBalance?.[0]); }, [tokenBalance]); return ( @@ -57,8 +58,8 @@ export function WalletSendView() { gasPriceInUSD={gasPriceInUSD} style={styles.tokenInput} /> - - + + - ) : ( + ) : typeof children === 'string' ? ( {children} + ) : ( + children )} {iconRight && ( + {imageSrc && } + {text} + + ); +} diff --git a/packages/ui/src/composites/wui-token-button/styles.ts b/packages/ui/src/composites/wui-token-button/styles.ts new file mode 100644 index 000000000..ad753204b --- /dev/null +++ b/packages/ui/src/composites/wui-token-button/styles.ts @@ -0,0 +1,14 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + container: { + height: 40 + }, + image: { + width: 24, + height: 24, + borderRadius: BorderRadius.full, + marginRight: Spacing['2xs'] + } +}); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 5b8df3e18..e3c05c2c0 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -53,6 +53,7 @@ export { SearchBar, type SearchBarProps } from './composites/wui-search-bar'; export { Snackbar, type SnackbarProps } from './composites/wui-snackbar'; export { Tabs, type TabsProps } from './composites/wui-tabs'; export { Tag, type TagProps } from './composites/wui-tag'; +export { TokenButton, type TokenButtonProps } from './composites/wui-token-button'; export { Tooltip, type TooltipProps } from './composites/wui-tooltip'; export { WalletImage, type WalletImageProps } from './composites/wui-wallet-image'; From c30cfe6bb0c5c87e53942501527e0f0b8c716c6d Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 5 Sep 2024 14:59:39 -0300 Subject: [PATCH 031/114] chore: removed account redirect for universal wallets --- packages/core/src/controllers/ModalController.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/core/src/controllers/ModalController.ts b/packages/core/src/controllers/ModalController.ts index 5bbc51021..5b768354c 100644 --- a/packages/core/src/controllers/ModalController.ts +++ b/packages/core/src/controllers/ModalController.ts @@ -5,7 +5,6 @@ import { RouterController } from './RouterController'; import { PublicStateController } from './PublicStateController'; import { EventsController } from './EventsController'; import { ApiController } from './ApiController'; -import { ConnectorController } from './ConnectorController'; // -- Types --------------------------------------------- // export interface ModalControllerState { @@ -35,8 +34,7 @@ export const ModalController = { if (options?.view) { RouterController.reset(options.view); } else if (AccountController.state.isConnected) { - const isWallet = ConnectorController.state.connectedConnector === 'EMAIL'; - RouterController.reset(isWallet ? 'Account' : 'AccountDefault'); + RouterController.reset('AccountDefault'); } else { RouterController.reset('Connect'); } From 411c192ec4c82d4d65a751788dd900cb2b721db6 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 5 Sep 2024 16:48:17 -0300 Subject: [PATCH 032/114] chore: removed changeset, solved ts error --- .changeset/stupid-guests-turn.md | 18 ------------------ .../src/composites/wui-token-button/index.tsx | 7 ++++++- 2 files changed, 6 insertions(+), 19 deletions(-) delete mode 100644 .changeset/stupid-guests-turn.md diff --git a/.changeset/stupid-guests-turn.md b/.changeset/stupid-guests-turn.md deleted file mode 100644 index 302af6e20..000000000 --- a/.changeset/stupid-guests-turn.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@web3modal/scaffold-react-native': minor -'@web3modal/common-react-native': minor -'@web3modal/email-react-native': minor -'@web3modal/core-react-native': minor -'@web3modal/siwe-react-native': minor -'@web3modal/ui-react-native': minor -'@web3modal/coinbase-ethers-react-native': minor -'@web3modal/coinbase-wagmi-react-native': minor -'@web3modal/email-ethers-react-native': minor -'@web3modal/email-wagmi-react-native': minor -'@web3modal/ethers-react-native': minor -'@web3modal/ethers5-react-native': minor -'@web3modal/scaffold-utils-react-native': minor -'@web3modal/wagmi-react-native': minor ---- - -feat: implemented wallet features for universal wallets diff --git a/packages/ui/src/composites/wui-token-button/index.tsx b/packages/ui/src/composites/wui-token-button/index.tsx index f50d3441a..551059544 100644 --- a/packages/ui/src/composites/wui-token-button/index.tsx +++ b/packages/ui/src/composites/wui-token-button/index.tsx @@ -4,11 +4,16 @@ import { Button } from '../wui-button'; import styles from './styles'; export interface TokenButtonProps { - text: string; + text?: string; imageSrc?: string; } export function TokenButton({ text, imageSrc }: TokenButtonProps) { + if (!text) { + // TODO: add empty state + return null; + } + return ( + + ); } diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-send-preview-view/styles.ts index babe3e1f4..432a72c36 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-preview-view/styles.ts +++ b/packages/scaffold/src/views/w3m-wallet-send-preview-view/styles.ts @@ -19,6 +19,17 @@ export default StyleSheet.create({ marginLeft: Spacing.xs }, details: { - marginTop: Spacing['2xl'] + marginTop: Spacing['2xl'], + marginBottom: Spacing.s + }, + reviewIcon: { + marginRight: Spacing['3xs'] + }, + cancelButton: { + flex: 1 + }, + sendButton: { + marginLeft: Spacing.s, + flex: 3 } }); diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx index ce74e5e8b..1c308ed18 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx @@ -45,6 +45,7 @@ export function WalletSendView() { const onSendPress = () => { if (SendController.state.loading) return; + // TODO: add validations RouterController.push('WalletSendPreview'); }; From e55ad32fd3e1630f3b28d3895f01a6c94bdeca25 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 20 Sep 2024 17:45:16 -0300 Subject: [PATCH 049/114] chore: working on send flow --- .../core/src/controllers/RouterController.ts | 37 ++++++++++++++++++- .../core/src/controllers/SendController.ts | 19 +++++----- .../scaffold/src/modal/w3m-modal/index.tsx | 6 ++- .../w3m-wallet-send-preview-view/index.tsx | 6 ++- packages/wallet/src/AppKitAuthWebview.tsx | 19 +++++++--- packages/wallet/src/AppKitFrameConstants.ts | 12 +++++- 6 files changed, 78 insertions(+), 21 deletions(-) diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index d579dd33d..6d29bf004 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -2,6 +2,15 @@ import { proxy } from 'valtio'; import type { WcWallet, CaipNetwork, Connector } from '../utils/TypeUtil'; // -- Types --------------------------------------------- // +type TransactionAction = { + goBack: boolean; + view: RouterControllerState['view'] | null; + close?: boolean; + replace?: boolean; + onSuccess?: () => void; + onCancel?: () => void; +}; + export interface RouterControllerState { view: | 'Account' @@ -36,12 +45,14 @@ export interface RouterControllerState { email?: string; newEmail?: string; }; + transactionStack: TransactionAction[]; } // -- State --------------------------------------------- // const state = proxy({ view: 'Connect', - history: ['Connect'] + history: ['Connect'], + transactionStack: [] }); // -- Controller ---------------------------------------- // @@ -56,6 +67,30 @@ export const RouterController = { } }, + pushTransactionStack(action: TransactionAction) { + state.transactionStack.push(action); + }, + + popTransactionStack(cancel?: boolean) { + const action = state.transactionStack.pop(); + + if (!action) { + return; + } + + if (cancel) { + this.goBack(); + action?.onCancel?.(); + } else { + if (action.goBack) { + this.goBack(); + } else if (action.view) { + this.reset(action.view); + } + action?.onSuccess?.(); + } + }, + reset(view: RouterControllerState['view']) { state.view = view; state.history = [view]; diff --git a/packages/core/src/controllers/SendController.ts b/packages/core/src/controllers/SendController.ts index 8e369052e..9766386ee 100644 --- a/packages/core/src/controllers/SendController.ts +++ b/packages/core/src/controllers/SendController.ts @@ -2,16 +2,15 @@ import { subscribeKey as subKey } from 'valtio/vanilla/utils'; import { proxy, ref, subscribe as sub } from 'valtio/vanilla'; import { type Balance } from '@reown/appkit-common-react-native'; import { erc20ABI } from '@reown/appkit-common-react-native'; -// import { RouterController } from './RouterController'; import { AccountController } from './AccountController'; import { ConnectionController } from './ConnectionController'; import { SnackController } from './SnackController'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; import { EventsController } from './EventsController'; import { NetworkController } from './NetworkController'; +import { RouterController } from './RouterController'; // -- Types --------------------------------------------- // - export interface TxParams { receiverAddress: string; sendTokenAmount: number; @@ -136,10 +135,10 @@ export const SendController = { }, async sendNativeToken(params: TxParams) { - // RouterController.pushTransactionStack({ - // view: 'Account', - // goBack: false - // }); + RouterController.pushTransactionStack({ + view: 'Account', + goBack: false + }); const to = params.receiverAddress as `0x${string}`; const address = AccountController.state.address as `0x${string}`; @@ -185,10 +184,10 @@ export const SendController = { }, async sendERC20Token(params: ContractWriteParams) { - // RouterController.pushTransactionStack({ - // view: 'Account', - // goBack: false - // }); + RouterController.pushTransactionStack({ + view: 'Account', + goBack: false + }); const amount = ConnectionController.parseUnits( params.sendTokenAmount.toString(), diff --git a/packages/scaffold/src/modal/w3m-modal/index.tsx b/packages/scaffold/src/modal/w3m-modal/index.tsx index eb377731d..c793a71e2 100644 --- a/packages/scaffold/src/modal/w3m-modal/index.tsx +++ b/packages/scaffold/src/modal/w3m-modal/index.tsx @@ -24,6 +24,8 @@ import { Snackbar } from '../../partials/w3m-snackbar'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; +const viewsAboveModal = ['ConnectingSiwe', 'WalletSendPreview']; + export function AppKit() { const { open, loading } = useSnapshot(ModalController.state); const { view: activeView } = useSnapshot(RouterController.state); @@ -35,7 +37,7 @@ export function AppKit() { const portraitHeight = height - 120; const landScapeHeight = height * 0.95 - (StatusBar.currentHeight ?? 0); const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; - const modalCoverScreen = activeView !== 'ConnectingSiwe'; + const enableCoverScreen = !viewsAboveModal.includes(activeView); const AuthView = authProvider?.AuthView; const onBackButtonPress = () => { @@ -119,7 +121,7 @@ export function AppKit() { <> { + SendController.sendToken(); + }; + return ( @@ -90,7 +94,7 @@ export function WalletSendPreviewView() { - diff --git a/packages/wallet/src/AppKitAuthWebview.tsx b/packages/wallet/src/AppKitAuthWebview.tsx index 72e18ba2e..0f3ef1e62 100644 --- a/packages/wallet/src/AppKitAuthWebview.tsx +++ b/packages/wallet/src/AppKitAuthWebview.tsx @@ -8,11 +8,13 @@ import { OptionsController, ModalController, type OptionsControllerState, - StorageUtil + StorageUtil, + RouterController } from '@reown/appkit-core-react-native'; import { useTheme, BorderRadius } from '@reown/appkit-ui-react-native'; import type { AppKitFrameProvider } from './AppKitFrameProvider'; -import { AppKitFrameConstants, AppKitFrameRpcConstants } from './AppKitFrameConstants'; +import { AppKitFrameConstants } from './AppKitFrameConstants'; +import { AppKitFrameHelpers } from './AppKitFrameHelpers'; const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView); @@ -50,13 +52,20 @@ export function AuthWebview() { provider.onMessage(event); provider.onRpcRequest(event, () => { - if (!AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(event.payload.method)) { - setIsVisible(true); + if (AppKitFrameHelpers.checkIfRequestExists(event)) { + if (!AppKitFrameHelpers.checkIfRequestIsAllowed(event)) { + setIsVisible(true); + } } }); provider.onRpcResponse(event, () => { - setIsVisible(false); + if (RouterController.state.transactionStack.length === 0) { + setIsVisible(false); + } else { + // TODO: send boolean in false in case of error + RouterController?.popTransactionStack(); + } }); provider.onIsConnected(event, () => { diff --git a/packages/wallet/src/AppKitFrameConstants.ts b/packages/wallet/src/AppKitFrameConstants.ts index 5ddd69ffe..a0a13d0f3 100644 --- a/packages/wallet/src/AppKitFrameConstants.ts +++ b/packages/wallet/src/AppKitFrameConstants.ts @@ -105,9 +105,17 @@ export const AppKitFrameRpcConstants = { 'eth_newPendingTransactionFilter', 'eth_sendRawTransaction', 'eth_syncing', - 'eth_uninstallFilter' + 'eth_uninstallFilter', + 'wallet_getCapabilities', + 'wallet_getCallsStatus' + ], + NOT_SAFE_RPC_METHODS: [ + 'personal_sign', + 'eth_signTypedData_v4', + 'eth_sendTransaction', + 'wallet_sendCalls', + 'wallet_grantPermissions' ], - NOT_SAFE_RPC_METHODS: ['personal_sign', 'eth_signTypedData_v4', 'eth_sendTransaction'], GET_CHAIN_ID: 'eth_chainId', RPC_METHOD_NOT_ALLOWED_MESSAGE: 'Requested RPC call is not allowed', RPC_METHOD_NOT_ALLOWED_UI_MESSAGE: 'Action not allowed' From 4c84e830c0a0c45c49b9c3ec6f07e4d084a209a7 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 23 Sep 2024 14:57:01 -0300 Subject: [PATCH 050/114] chore: auth provider refactor --- .../core/src/controllers/SendController.ts | 4 + .../w3m-wallet-send-preview-view/index.tsx | 4 +- packages/wallet/src/AppKitAuthWebview.tsx | 45 +- packages/wallet/src/AppKitFrameHelpers.ts | 18 +- packages/wallet/src/AppKitFrameProvider.ts | 598 +++++++----------- packages/wallet/src/AppKitFrameSchema.ts | 179 ++++-- packages/wallet/src/AppKitFrameTypes.ts | 27 +- packages/wallet/src/index.ts | 2 +- 8 files changed, 414 insertions(+), 463 deletions(-) diff --git a/packages/core/src/controllers/SendController.ts b/packages/core/src/controllers/SendController.ts index 9766386ee..5153f4cd8 100644 --- a/packages/core/src/controllers/SendController.ts +++ b/packages/core/src/controllers/SendController.ts @@ -93,6 +93,7 @@ export const SendController = { sendToken() { if (this.state.token?.address && this.state.sendTokenAmount && this.state.receiverAddress) { + state.loading = true; EventsController.sendEvent({ type: 'track', event: 'SEND_INITIATED', @@ -115,6 +116,7 @@ export const SendController = { this.state.gasPrice && this.state.token?.quantity.decimals ) { + state.loading = true; EventsController.sendEvent({ type: 'track', event: 'SEND_INITIATED', @@ -169,6 +171,7 @@ export const SendController = { }); this.resetSend(); } catch (error) { + state.loading = false; EventsController.sendEvent({ type: 'track', event: 'SEND_ERROR', @@ -215,6 +218,7 @@ export const SendController = { this.resetSend(); } } catch (error) { + state.loading = false; SnackController.showError('Something went wrong'); } }, diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx index 353f7e17c..5e2661ae1 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx @@ -12,7 +12,7 @@ import { PreviewSendDetails } from './components/preview-send-details'; export function WalletSendPreviewView() { const { caipNetwork } = useSnapshot(NetworkController.state); - const { token, receiverAddress, gasPriceInUSD } = useSnapshot(SendController.state); + const { token, receiverAddress, gasPriceInUSD, loading } = useSnapshot(SendController.state); const getSendValue = () => { if (SendController.state.token && SendController.state.sendTokenAmount) { @@ -94,7 +94,7 @@ export function WalletSendPreviewView() { - diff --git a/packages/wallet/src/AppKitAuthWebview.tsx b/packages/wallet/src/AppKitAuthWebview.tsx index 0f3ef1e62..438e511dc 100644 --- a/packages/wallet/src/AppKitAuthWebview.tsx +++ b/packages/wallet/src/AppKitAuthWebview.tsx @@ -15,6 +15,7 @@ import { useTheme, BorderRadius } from '@reown/appkit-ui-react-native'; import type { AppKitFrameProvider } from './AppKitFrameProvider'; import { AppKitFrameConstants } from './AppKitFrameConstants'; import { AppKitFrameHelpers } from './AppKitFrameHelpers'; +import type { AppKitFrameTypes } from './AppKitFrameTypes'; const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView); @@ -23,7 +24,7 @@ export function AuthWebview() { const Theme = useTheme(); const { connectors } = useSnapshot(ConnectorController.state); const { projectId, sdkVersion } = useSnapshot(OptionsController.state) as OptionsControllerState; - const [isVisible, setIsVisible] = useState(false); + const [isWebviewVisibile, setIsWebviewOpen] = useState(false); const [isBackdropVisible, setIsBackdropVisible] = useState(false); const animatedHeight = useRef(new Animated.Value(0)); const backdropOpacity = useRef(new Animated.Value(0)); @@ -51,21 +52,37 @@ export function AuthWebview() { provider.onMessage(event); - provider.onRpcRequest(event, () => { - if (AppKitFrameHelpers.checkIfRequestExists(event)) { - if (!AppKitFrameHelpers.checkIfRequestIsAllowed(event)) { - setIsVisible(true); + provider.onRpcRequest((request: AppKitFrameTypes.RPCRequest) => { + if (AppKitFrameHelpers.checkIfRequestExists(request)) { + if (!AppKitFrameHelpers.checkIfRequestIsAllowed(request)) { + setIsWebviewOpen(true); } } }); - provider.onRpcResponse(event, () => { + provider.onRpcSuccess((_, request) => { + const isSafeRequest = AppKitFrameHelpers.checkIfRequestIsSafe(request); + if (isSafeRequest) { + return; + } + if (RouterController.state.transactionStack.length === 0) { - setIsVisible(false); + ModalController.close(); } else { - // TODO: send boolean in false in case of error RouterController?.popTransactionStack(); } + setIsWebviewOpen(false); + }); + + provider.onRpcError(() => { + if (isWebviewVisibile) { + if (RouterController.state.transactionStack.length === 0) { + ModalController.close(); + } else { + RouterController?.popTransactionStack(true); + } + } + setIsWebviewOpen(false); }); provider.onIsConnected(event, () => { @@ -87,27 +104,27 @@ export function AuthWebview() { useEffect(() => { Animated.timing(animatedHeight.current, { - toValue: isVisible ? 1 : 0, + toValue: isWebviewVisibile ? 1 : 0, duration: 200, useNativeDriver: false }).start(); Animated.timing(webviewOpacity.current, { - toValue: isVisible ? 1 : 0, + toValue: isWebviewVisibile ? 1 : 0, duration: 300, useNativeDriver: false }).start(); - if (isVisible) { + if (isWebviewVisibile) { setIsBackdropVisible(true); } Animated.timing(backdropOpacity.current, { - toValue: isVisible ? 0.7 : 0, + toValue: isWebviewVisibile ? 0.7 : 0, duration: 300, useNativeDriver: false - }).start(() => setIsBackdropVisible(isVisible)); - }, [animatedHeight, backdropOpacity, isVisible, setIsBackdropVisible]); + }).start(() => setIsBackdropVisible(isWebviewVisibile)); + }, [animatedHeight, backdropOpacity, isWebviewVisibile, setIsBackdropVisible]); useEffect(() => { provider?.setWebviewRef(webviewRef); diff --git a/packages/wallet/src/AppKitFrameHelpers.ts b/packages/wallet/src/AppKitFrameHelpers.ts index 297a5052d..212fd6219 100644 --- a/packages/wallet/src/AppKitFrameHelpers.ts +++ b/packages/wallet/src/AppKitFrameHelpers.ts @@ -23,22 +23,18 @@ export const AppKitFrameHelpers = { } }, - checkIfRequestExists(request: unknown) { - const method = this.getRequestMethod(request); - + checkIfRequestExists(request: AppKitFrameTypes.RPCRequest) { return ( - AppKitFrameRpcConstants.NOT_SAFE_RPC_METHODS.includes(method) || - AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(method) + AppKitFrameRpcConstants.NOT_SAFE_RPC_METHODS.includes(request.method) || + AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(request.method) ); }, - getRequestMethod(request: unknown) { - return (request as { payload: AppKitFrameTypes.RPCRequest })?.payload?.method; + checkIfRequestIsAllowed(request: AppKitFrameTypes.RPCRequest) { + return AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(request.method); }, - checkIfRequestIsAllowed(request: unknown) { - const method = this.getRequestMethod(request); - - return AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(method); + checkIfRequestIsSafe(request: AppKitFrameTypes.RPCRequest) { + return AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(request.method); } }; diff --git a/packages/wallet/src/AppKitFrameProvider.ts b/packages/wallet/src/AppKitFrameProvider.ts index 05d6a60fa..d09b08655 100644 --- a/packages/wallet/src/AppKitFrameProvider.ts +++ b/packages/wallet/src/AppKitFrameProvider.ts @@ -1,3 +1,4 @@ +import { EventEmitter } from 'events'; import type { RefObject } from 'react'; import type WebView from 'react-native-webview'; import { CoreHelperUtil } from '@reown/appkit-core-react-native'; @@ -8,25 +9,6 @@ import { AppKitFrameHelpers } from './AppKitFrameHelpers'; import { AppKitFrameSchema } from './AppKitFrameSchema'; import { AuthWebview } from './AppKitAuthWebview'; -// -- Types ----------------------------------------------------------- -type Resolver = { resolve: (value: T) => void; reject: (reason?: unknown) => void } | undefined; -type ConnectEmailResolver = Resolver; -type ConnectDeviceResolver = Resolver; -type ConnectOtpResolver = Resolver; -type ConnectResolver = Resolver; -type DisconnectResolver = Resolver; -type IsConnectedResolver = Resolver; -type GetChainIdResolver = Resolver; -type SwitchChainResolver = Resolver; -type RpcRequestResolver = Resolver; -type UpdateEmailResolver = Resolver; -type UpdateEmailPrimaryOtpResolver = Resolver; -type UpdateEmailSecondaryOtpResolver = Resolver< - AppKitFrameTypes.Responses['FrameUpdateEmailSecondaryOtpResolver'] ->; -type SyncThemeResolver = Resolver; -type SyncDappDataResolver = Resolver; - // -- Provider -------------------------------------------------------- export class AppKitFrameProvider { private webviewRef: RefObject | undefined; @@ -37,6 +19,13 @@ export class AppKitFrameProvider { private email: string | undefined; + private rpcRequestHandler?: (request: AppKitFrameTypes.RPCRequest) => void; + private rpcSuccessHandler?: ( + response: AppKitFrameTypes.RPCResponse, + request: AppKitFrameTypes.RPCRequest + ) => void; + private rpcErrorHandler?: (error: Error, request: AppKitFrameTypes.RPCRequest) => void; + public webviewLoadPromise: Promise; public webviewLoadPromiseResolver: @@ -48,33 +37,11 @@ export class AppKitFrameProvider { public AuthView = AuthWebview; - private connectEmailResolver: ConnectEmailResolver = undefined; - - private connectDeviceResolver: ConnectDeviceResolver = undefined; - - private connectOtpResolver: ConnectOtpResolver | undefined = undefined; - - private connectResolver: ConnectResolver = undefined; - - private disconnectResolver: DisconnectResolver = undefined; - - private isConnectedResolver: IsConnectedResolver = undefined; - - private getChainIdResolver: GetChainIdResolver = undefined; - - private switchChainResolver: SwitchChainResolver = undefined; - - private rpcRequestResolver: RpcRequestResolver = undefined; - - private updateEmailResolver: UpdateEmailResolver = undefined; - - private updateEmailPrimaryOtpResolver: UpdateEmailPrimaryOtpResolver = undefined; + private openRpcRequests: Array< + AppKitFrameTypes.RPCRequest & { abortController: AbortController } + > = []; - private updateEmailSecondaryOtpResolver: UpdateEmailSecondaryOtpResolver = undefined; - - private syncThemeResolver: SyncThemeResolver = undefined; - - private syncDappDataResolver: SyncDappDataResolver = undefined; + public events: EventEmitter = new EventEmitter(); public constructor(projectId: string, metadata: AppKitFrameTypes.Metadata) { this.webviewLoadPromise = new Promise((resolve, reject) => { @@ -92,72 +59,9 @@ export class AppKitFrameProvider { this.webviewRef = webviewRef; } - public onMessage(e: AppKitFrameTypes.FrameEvent) { - this.onFrameEvent(e, event => { - // console.log('💻 received', e); // eslint-disable-line no-console - switch (event.type) { - case AppKitFrameConstants.FRAME_CONNECT_EMAIL_SUCCESS: - return this.onConnectEmailSuccess(event); - case AppKitFrameConstants.FRAME_CONNECT_EMAIL_ERROR: - return this.onConnectEmailError(event); - case AppKitFrameConstants.FRAME_CONNECT_DEVICE_SUCCESS: - return this.onConnectDeviceSuccess(); - case AppKitFrameConstants.FRAME_CONNECT_DEVICE_ERROR: - return this.onConnectDeviceError(event); - case AppKitFrameConstants.FRAME_CONNECT_OTP_SUCCESS: - return this.onConnectOtpSuccess(); - case AppKitFrameConstants.FRAME_CONNECT_OTP_ERROR: - return this.onConnectOtpError(event); - case AppKitFrameConstants.FRAME_GET_USER_SUCCESS: - return this.onConnectSuccess(event); - case AppKitFrameConstants.FRAME_GET_USER_ERROR: - return this.onConnectError(event); - case AppKitFrameConstants.FRAME_IS_CONNECTED_SUCCESS: - return this.onIsConnectedSuccess(event); - case AppKitFrameConstants.FRAME_IS_CONNECTED_ERROR: - return this.onIsConnectedError(event); - case AppKitFrameConstants.FRAME_GET_CHAIN_ID_SUCCESS: - return this.onGetChainIdSuccess(event); - case AppKitFrameConstants.FRAME_GET_CHAIN_ID_ERROR: - return this.onGetChainIdError(event); - case AppKitFrameConstants.FRAME_SIGN_OUT_SUCCESS: - return this.onSignOutSuccess(); - case AppKitFrameConstants.FRAME_SIGN_OUT_ERROR: - return this.onSignOutError(event); - case AppKitFrameConstants.FRAME_SWITCH_NETWORK_SUCCESS: - return this.onSwitchChainSuccess(event); - case AppKitFrameConstants.FRAME_SWITCH_NETWORK_ERROR: - return this.onSwitchChainError(event); - case AppKitFrameConstants.FRAME_RPC_REQUEST_SUCCESS: - return this.onRpcRequestSuccess(event); - case AppKitFrameConstants.FRAME_RPC_REQUEST_ERROR: - return this.onRpcRequestError(event); - case AppKitFrameConstants.FRAME_SESSION_UPDATE: - return this.onSessionUpdate(event); - case AppKitFrameConstants.FRAME_UPDATE_EMAIL_SUCCESS: - return this.onUpdateEmailSuccess(event); - case AppKitFrameConstants.FRAME_UPDATE_EMAIL_ERROR: - return this.onUpdateEmailError(event); - case AppKitFrameConstants.FRAME_UPDATE_EMAIL_PRIMARY_OTP_SUCCESS: - return this.onUpdateEmailPrimaryOtpSuccess(); - case AppKitFrameConstants.FRAME_UPDATE_EMAIL_PRIMARY_OTP_ERROR: - return this.onUpdateEmailPrimaryOtpError(event); - case AppKitFrameConstants.FRAME_UPDATE_EMAIL_SECONDARY_OTP_SUCCESS: - return this.onUpdateEmailSecondaryOtpSuccess(event); - case AppKitFrameConstants.FRAME_UPDATE_EMAIL_SECONDARY_OTP_ERROR: - return this.onUpdateEmailSecondaryOtpError(event); - case AppKitFrameConstants.FRAME_SYNC_THEME_SUCCESS: - return this.onSyncThemeSuccess(); - case AppKitFrameConstants.FRAME_SYNC_THEME_ERROR: - return this.onSyncThemeError(event); - case AppKitFrameConstants.FRAME_SYNC_DAPP_DATA_SUCCESS: - return this.onSyncDappDataSuccess(); - case AppKitFrameConstants.FRAME_SYNC_DAPP_DATA_ERROR: - return this.onSyncDappDataError(event); - default: - return null; - } - }); + public onMessage(event: AppKitFrameTypes.FrameEvent) { + // console.log('💻 received', e); // eslint-disable-line no-console + this.events.emit('message', event); } public onWebviewLoaded() { @@ -196,124 +100,141 @@ export class AppKitFrameProvider { } public rejectRpcRequest() { - this.rpcRequestResolver?.reject(); + try { + this.openRpcRequests.forEach(({ abortController, method }) => { + if (!AppKitFrameRpcConstants.SAFE_RPC_METHODS.includes(method)) { + abortController.abort(); + } + }); + this.openRpcRequests = []; + } catch (e) {} } public async connectEmail(payload: AppKitFrameTypes.Requests['AppConnectEmailRequest']) { await this.webviewLoadPromise; await AppKitFrameHelpers.checkIfAllowedToTriggerEmail(); - this.postAppEvent({ type: AppKitFrameConstants.APP_CONNECT_EMAIL, payload }); - return new Promise( - (resolve, reject) => { - this.connectEmailResolver = { resolve, reject }; - } - ); + const response = await this.appEvent<'ConnectEmail'>({ + type: AppKitFrameConstants.APP_CONNECT_EMAIL, + payload + } as AppKitFrameTypes.AppEvent); + + this.setNewLastEmailLoginTime(); + + return response; } public async connectDevice() { await this.webviewLoadPromise; - this.postAppEvent({ type: AppKitFrameConstants.APP_CONNECT_DEVICE }); - return new Promise((resolve, reject) => { - this.connectDeviceResolver = { resolve, reject }; - }); + const response = await this.appEvent<'ConnectDevice'>({ + type: AppKitFrameConstants.APP_CONNECT_DEVICE + } as AppKitFrameTypes.AppEvent); + + return response; } public async connectOtp(payload: AppKitFrameTypes.Requests['AppConnectOtpRequest']) { await this.webviewLoadPromise; - this.postAppEvent({ type: AppKitFrameConstants.APP_CONNECT_OTP, payload }); - return new Promise((resolve, reject) => { - this.connectOtpResolver = { resolve, reject }; - }); + const response = await this.appEvent<'ConnectOtp'>({ + type: AppKitFrameConstants.APP_CONNECT_OTP, + payload + } as AppKitFrameTypes.AppEvent); + + return response; } public async isConnected() { await this.webviewLoadPromise; - this.postAppEvent({ + + const response = await this.appEvent<'IsConnected'>({ type: AppKitFrameConstants.APP_IS_CONNECTED, payload: undefined - }); + } as AppKitFrameTypes.AppEvent); - return new Promise( - (resolve, reject) => { - this.isConnectedResolver = { resolve, reject }; - } - ); + if (!response.isConnected) { + this.deleteEmailLoginCache(); + } + + return response; } public async getChainId() { await this.webviewLoadPromise; - this.postAppEvent({ type: AppKitFrameConstants.APP_GET_CHAIN_ID }); - return new Promise((resolve, reject) => { - this.getChainIdResolver = { resolve, reject }; - }); + const response = await this.appEvent<'GetChainId'>({ + type: AppKitFrameConstants.APP_GET_CHAIN_ID + } as AppKitFrameTypes.AppEvent); + + this.setLastUsedChainId(response.chainId); + + return response; } public async updateEmail(payload: AppKitFrameTypes.Requests['AppUpdateEmailRequest']) { await this.webviewLoadPromise; await AppKitFrameHelpers.checkIfAllowedToTriggerEmail(); - this.postAppEvent({ type: AppKitFrameConstants.APP_UPDATE_EMAIL, payload }); - return new Promise( - (resolve, reject) => { - this.updateEmailResolver = { resolve, reject }; - } - ); + const response = await this.appEvent<'UpdateEmail'>({ + type: AppKitFrameConstants.APP_UPDATE_EMAIL, + payload + } as AppKitFrameTypes.AppEvent); + + this.setNewLastEmailLoginTime(); + + return response; } public async updateEmailPrimaryOtp( payload: AppKitFrameTypes.Requests['AppUpdateEmailPrimaryOtpRequest'] ) { await this.webviewLoadPromise; - this.postAppEvent({ + + const response = await this.appEvent<'UpdateEmailPrimaryOtp'>({ type: AppKitFrameConstants.APP_UPDATE_EMAIL_PRIMARY_OTP, payload - }); + } as AppKitFrameTypes.AppEvent); - return new Promise((resolve, reject) => { - this.updateEmailPrimaryOtpResolver = { resolve, reject }; - }); + return response; } public async updateEmailSecondaryOtp( payload: AppKitFrameTypes.Requests['AppUpdateEmailSecondaryOtpRequest'] ) { await this.webviewLoadPromise; - this.postAppEvent({ + + const response = await this.appEvent<'UpdateEmailSecondaryOtp'>({ type: AppKitFrameConstants.APP_UPDATE_EMAIL_SECONDARY_OTP, payload - }); + } as AppKitFrameTypes.AppEvent); - return new Promise( - (resolve, reject) => { - this.updateEmailSecondaryOtpResolver = { resolve, reject }; - } - ); + this.setEmailLoginSuccess(response.newEmail); + + return response; } public async syncTheme(payload: AppKitFrameTypes.Requests['AppSyncThemeRequest']) { await this.webviewLoadPromise; - this.postAppEvent({ type: AppKitFrameConstants.APP_SYNC_THEME, payload }); - return new Promise((resolve, reject) => { - this.syncThemeResolver = { resolve, reject }; - }); + const response = await this.appEvent<'SyncTheme'>({ + type: AppKitFrameConstants.APP_SYNC_THEME, + payload + } as AppKitFrameTypes.AppEvent); + + return response; } public async syncDappData(payload: AppKitFrameTypes.Requests['AppSyncDappDataRequest']) { await this.webviewLoadPromise; const metadata = payload.metadata ?? this.metadata; - this.postAppEvent({ + + const response = await this.appEvent<'SyncDappData'>({ type: AppKitFrameConstants.APP_SYNC_DAPP_DATA, payload: { ...payload, metadata } - }); + } as AppKitFrameTypes.AppEvent); - return new Promise((resolve, reject) => { - this.syncDappDataResolver = { resolve, reject }; - }); + return response; } // -- Provider Methods ------------------------------------------------ @@ -322,60 +243,74 @@ export class AppKitFrameProvider { const chainId = payload?.chainId ?? lastUsedChain ?? 1; await this.webviewLoadPromise; - this.postAppEvent({ + const response = await this.appEvent<'GetUser'>({ type: AppKitFrameConstants.APP_GET_USER, - payload: { chainId } - }); + payload: { ...payload, chainId } + } as AppKitFrameTypes.AppEvent); - return new Promise((resolve, reject) => { - this.connectResolver = { resolve, reject }; - }); + this.setEmailLoginSuccess(response.email); + this.setLastUsedChainId(response.chainId); + + return response; } public async switchNetwork(chainId: number) { await this.webviewLoadPromise; - this.postAppEvent({ + + const response = await this.appEvent<'SwitchNetwork'>({ type: AppKitFrameConstants.APP_SWITCH_NETWORK, payload: { chainId } - }); + } as AppKitFrameTypes.AppEvent); - return new Promise( - (resolve, reject) => { - this.switchChainResolver = { resolve, reject }; - } - ); + this.setLastUsedChainId(response.chainId); + + return response; } public async disconnect() { await this.webviewLoadPromise; - this.postAppEvent({ type: AppKitFrameConstants.APP_SIGN_OUT }); - return new Promise((resolve, reject) => { - this.disconnectResolver = { resolve, reject }; + const response = await this.appEvent<'SignOut'>({ + type: AppKitFrameConstants.APP_SIGN_OUT }); + + this.deleteEmailLoginCache(); + + return response; } - public async request(req: AppKitFrameTypes.RPCRequest) { - if (AppKitFrameRpcConstants.GET_CHAIN_ID === req.method) { - return await this.getLastUsedChainId(); + public async request(req: AppKitFrameTypes.RPCRequest): Promise { + try { + if (AppKitFrameRpcConstants.GET_CHAIN_ID === req.method) { + return this.getLastUsedChainId(); + } + + this.rpcRequestHandler?.(req); + const response = await this.appEvent<'Rpc'>({ + type: AppKitFrameConstants.APP_RPC_REQUEST, + payload: req + } as AppKitFrameTypes.AppEvent); + this.rpcSuccessHandler?.(response, req); + + return response; + } catch (error) { + this.rpcErrorHandler?.(error as Error, req); + throw error; } - await this.webviewLoadPromise; - this.postAppEvent({ - type: AppKitFrameConstants.APP_RPC_REQUEST, - payload: req - }); + } - return new Promise((resolve, reject) => { - this.rpcRequestResolver = { resolve, reject }; - }); + public onRpcRequest(callback: (request: AppKitFrameTypes.RPCRequest) => void) { + this.rpcRequestHandler = callback; } - public onRpcRequest(event: AppKitFrameTypes.AppEvent, callback: (request: unknown) => void) { - this.onAppEvent(event, appEvent => { - if (appEvent.type.includes(AppKitFrameConstants.RPC_METHOD_KEY)) { - callback(appEvent); - } - }); + public onRpcSuccess( + callback: (response: AppKitFrameTypes.FrameEvent, request: AppKitFrameTypes.RPCRequest) => void + ) { + this.rpcSuccessHandler = callback; + } + + public onRpcError(callback: (error: Error) => void) { + this.rpcErrorHandler = callback; } public onRpcResponse(event: AppKitFrameTypes.FrameEvent, callback: (request: unknown) => void) { @@ -408,193 +343,6 @@ export class AppKitFrameProvider { }); } - // -- Promise Handlers ------------------------------------------------ - private onConnectEmailSuccess( - event: Extract - ) { - this.connectEmailResolver?.resolve(event.payload); - this.setNewLastEmailLoginTime(); - } - - private onConnectEmailError( - event: Extract - ) { - this.connectEmailResolver?.reject(event.payload.message); - } - - private onConnectDeviceSuccess() { - this.connectDeviceResolver?.resolve(undefined); - } - - private onConnectDeviceError( - event: Extract - ) { - this.connectDeviceResolver?.reject(event.payload.message); - } - - private onConnectOtpSuccess() { - this.connectOtpResolver?.resolve(undefined); - } - - private onConnectOtpError( - event: Extract - ) { - this.connectOtpResolver?.reject(event.payload.message); - } - - private onConnectSuccess( - event: Extract - ) { - this.setEmailLoginSuccess(event.payload.email); - this.setLastUsedChainId(event.payload.chainId); - this.connectResolver?.resolve(event.payload); - } - - private onConnectError( - event: Extract - ) { - this.connectResolver?.reject(event.payload.message); - } - - private onIsConnectedSuccess( - event: Extract - ) { - if (!event.payload.isConnected) { - this.deleteEmailLoginCache(); - } - this.isConnectedResolver?.resolve(event.payload); - } - - private onIsConnectedError( - event: Extract - ) { - this.isConnectedResolver?.reject(event.payload.message); - } - - private onGetChainIdSuccess( - event: Extract - ) { - this.setLastUsedChainId(event.payload.chainId); - this.getChainIdResolver?.resolve(event.payload); - } - - private onGetChainIdError( - event: Extract - ) { - this.getChainIdResolver?.reject(event.payload.message); - } - - private onSignOutSuccess() { - this.disconnectResolver?.resolve(undefined); - this.deleteEmailLoginCache(); - } - - private onSignOutError( - event: Extract - ) { - this.disconnectResolver?.reject(event.payload.message); - } - - private onSwitchChainSuccess( - event: Extract - ) { - this.setLastUsedChainId(event.payload.chainId); - this.switchChainResolver?.resolve(event.payload); - } - - private onSwitchChainError( - event: Extract - ) { - this.switchChainResolver?.reject(event.payload.message); - } - - private onRpcRequestSuccess( - event: Extract - ) { - this.rpcRequestResolver?.resolve(event.payload); - } - - private onRpcRequestError( - event: Extract - ) { - this.rpcRequestResolver?.reject(event.payload.message); - } - - private onSessionUpdate( - event: Extract - ) { - const { payload } = event; - if (payload) { - // Ilja TODO: this.setSessionToken(payload.token) - } - } - - private onUpdateEmailSuccess( - event: Extract - ) { - this.updateEmailResolver?.resolve(event.payload); - this.setNewLastEmailLoginTime(); - } - - private onUpdateEmailError( - event: Extract - ) { - this.updateEmailResolver?.reject(event.payload.message); - } - - private onUpdateEmailPrimaryOtpSuccess() { - this.updateEmailPrimaryOtpResolver?.resolve(undefined); - } - - private onUpdateEmailPrimaryOtpError( - event: Extract< - AppKitFrameTypes.FrameEvent, - { type: '@w3m-frame/UPDATE_EMAIL_PRIMARY_OTP_ERROR' } - > - ) { - this.updateEmailPrimaryOtpResolver?.reject(event.payload.message); - } - - private onUpdateEmailSecondaryOtpSuccess( - event: Extract< - AppKitFrameTypes.FrameEvent, - { type: '@w3m-frame/UPDATE_EMAIL_SECONDARY_OTP_SUCCESS' } - > - ) { - const { newEmail } = event.payload; - this.setEmailLoginSuccess(newEmail); - this.updateEmailSecondaryOtpResolver?.resolve({ newEmail }); - } - - private onUpdateEmailSecondaryOtpError( - event: Extract< - AppKitFrameTypes.FrameEvent, - { type: '@w3m-frame/UPDATE_EMAIL_SECONDARY_OTP_ERROR' } - > - ) { - this.updateEmailSecondaryOtpResolver?.reject(event.payload.message); - } - - private onSyncThemeSuccess() { - this.syncThemeResolver?.resolve(undefined); - } - - private onSyncThemeError( - event: Extract - ) { - this.syncThemeResolver?.reject(event.payload.message); - } - - private onSyncDappDataSuccess() { - this.syncDappDataResolver?.resolve(undefined); - } - - private onSyncDappDataError( - event: Extract - ) { - this.syncDappDataResolver?.reject(event.payload.message); - } - // -- Private Methods ------------------------------------------------- private setNewLastEmailLoginTime() { AppKitFrameStorage.set(AppKitFrameConstants.LAST_EMAIL_LOGIN_TIME, Date.now().toString()); @@ -627,6 +375,70 @@ export class AppKitFrameProvider { return undefined; } + private async registerFrameEventHandler( + id: string, + callback: (event: AppKitFrameTypes.FrameEvent) => void, + signal: AbortSignal + ) { + const eventEmitter = this.events; + function eventHandler(data: AppKitFrameTypes.FrameEvent) { + if (!data.type?.includes(AppKitFrameConstants.FRAME_EVENT_KEY)) { + return; + } + const frameEvent = AppKitFrameSchema.frameEvent.parse(data); + if (frameEvent.id === id) { + callback(frameEvent); + eventEmitter.removeListener('message', eventHandler); + } + } + + eventEmitter.addListener('message', eventHandler); + signal.addEventListener('abort', () => { + eventEmitter.removeListener('message', eventHandler); + }); + } + + private async appEvent( + event: Omit + ): Promise { + await this.webviewLoadPromise; + const type = event.type.replace('@w3m-app/', ''); + + return new Promise((resolve, reject) => { + const id = Math.random().toString(36).substring(7); + + this.postAppEvent({ ...event, id } as AppKitFrameTypes.AppEvent); + const abortController = new AbortController(); + if (type === 'RPC_REQUEST') { + const rpcEvent = event as Extract< + AppKitFrameTypes.AppEvent, + { type: '@w3m-app/RPC_REQUEST' } + >; + this.openRpcRequests = [...this.openRpcRequests, { ...rpcEvent.payload, abortController }]; + } + abortController.signal.addEventListener('abort', () => { + if (type === 'RPC_REQUEST') { + reject(new Error('Request was aborted')); + } + }); + + function handler(frameEvent: AppKitFrameTypes.FrameEvent) { + if (frameEvent.type === `@w3m-frame/${type}_SUCCESS`) { + if ('payload' in frameEvent) { + resolve(frameEvent.payload); + } + resolve(undefined as unknown as AppKitFrameTypes.Responses[`Frame${T}Response`]); + } else if (frameEvent.type === `@w3m-frame/${type}_ERROR`) { + if ('payload' in frameEvent) { + reject(new Error(frameEvent.payload?.message || 'An error occurred')); + } + reject(new Error('An error occurred')); + } + } + this.registerFrameEventHandler(id, handler, abortController.signal); + }); + } + private onFrameEvent( event: AppKitFrameTypes.FrameEvent, callback: (event: AppKitFrameTypes.FrameEvent) => void @@ -679,3 +491,21 @@ export class AppKitFrameProvider { return email; } } + +export interface AppKitFrameProviderMethods { + // Email + connectEmail: AppKitFrameProvider['connectEmail']; + connectOtp: AppKitFrameProvider['connectOtp']; + updateEmail: AppKitFrameProvider['updateEmail']; + updateEmailPrimaryOtp: AppKitFrameProvider['updateEmailPrimaryOtp']; + updateEmailSecondaryOtp: AppKitFrameProvider['updateEmailSecondaryOtp']; + getEmail: AppKitFrameProvider['getEmail']; + + // Social + connectDevice: AppKitFrameProvider['connectDevice']; + + // Misc + syncTheme: AppKitFrameProvider['syncTheme']; + syncDappData: AppKitFrameProvider['syncDappData']; + switchNetwork: AppKitFrameProvider['switchNetwork']; +} diff --git a/packages/wallet/src/AppKitFrameSchema.ts b/packages/wallet/src/AppKitFrameSchema.ts index 6ee16be62..552338641 100644 --- a/packages/wallet/src/AppKitFrameSchema.ts +++ b/packages/wallet/src/AppKitFrameSchema.ts @@ -29,10 +29,10 @@ export const GetTransactionByHashResponse = z.object({ v: z.string(), value: z.string() }); -export const AppSwitchNetworkRequest = z.object({ chainId: z.number() }); +export const AppSwitchNetworkRequest = z.object({ chainId: z.string().or(z.number()) }); export const AppConnectEmailRequest = z.object({ email: z.string().email() }); export const AppConnectOtpRequest = z.object({ otp: z.string() }); -export const AppGetUserRequest = z.object({ chainId: z.optional(z.number()) }); +export const AppGetUserRequest = z.object({ chainId: z.optional(z.string().or(z.number())) }); export const AppUpdateEmailRequest = z.object({ email: z.string().email() }); export const AppUpdateEmailPrimaryOtpRequest = z.object({ otp: z.string() }); export const AppUpdateEmailSecondaryOtpRequest = z.object({ otp: z.string() }); @@ -73,7 +73,7 @@ export const FrameGetUserResponse = z.object({ export const FrameIsConnectedResponse = z.object({ isConnected: z.boolean() }); export const FrameGetChainIdResponse = z.object({ chainId: z.number() }); export const FrameSwitchNetworkResponse = z.object({ chainId: z.number() }); -export const FrameUpdateEmailSecondaryOtpResolver = z.object({ newEmail: z.string().email() }); +export const FrameUpdateEmailSecondaryOtpResponse = z.object({ newEmail: z.string().email() }); export const RpcResponse = z.any(); @@ -258,28 +258,35 @@ export const FrameSession = z.object({ token: z.string() }); +export const EventSchema = z.object({ + // Remove optional once all packages are updated + id: z.string().optional() +}); + export const AppKitFrameSchema = { // -- App Events ----------------------------------------------------------- - appEvent: z - .object({ type: zType('APP_SWITCH_NETWORK'), payload: AppSwitchNetworkRequest }) + appEvent: EventSchema.extend({ + type: zType('APP_SWITCH_NETWORK'), + payload: AppSwitchNetworkRequest + }) - .or(z.object({ type: zType('APP_CONNECT_EMAIL'), payload: AppConnectEmailRequest })) + .or(EventSchema.extend({ type: zType('APP_CONNECT_EMAIL'), payload: AppConnectEmailRequest })) - .or(z.object({ type: zType('APP_CONNECT_DEVICE') })) + .or(EventSchema.extend({ type: zType('APP_CONNECT_DEVICE') })) - .or(z.object({ type: zType('APP_CONNECT_OTP'), payload: AppConnectOtpRequest })) + .or(EventSchema.extend({ type: zType('APP_CONNECT_OTP'), payload: AppConnectOtpRequest })) - .or(z.object({ type: zType('APP_GET_USER'), payload: z.optional(AppGetUserRequest) })) + .or(EventSchema.extend({ type: zType('APP_GET_USER'), payload: z.optional(AppGetUserRequest) })) - .or(z.object({ type: zType('APP_SIGN_OUT') })) + .or(EventSchema.extend({ type: zType('APP_SIGN_OUT') })) - .or(z.object({ type: zType('APP_IS_CONNECTED'), payload: z.optional(FrameSession) })) + .or(EventSchema.extend({ type: zType('APP_IS_CONNECTED'), payload: z.optional(FrameSession) })) - .or(z.object({ type: zType('APP_GET_CHAIN_ID') })) + .or(EventSchema.extend({ type: zType('APP_GET_CHAIN_ID') })) .or( - z.object({ + EventSchema.extend({ type: zType('APP_RPC_REQUEST'), payload: RpcPersonalSignRequest.or(RpcEthSendTransactionRequest) .or(RpcEthAccountsRequest) @@ -322,96 +329,145 @@ export const AppKitFrameSchema = { }) ) - .or(z.object({ type: zType('APP_UPDATE_EMAIL'), payload: AppUpdateEmailRequest })) + .or(EventSchema.extend({ type: zType('APP_UPDATE_EMAIL'), payload: AppUpdateEmailRequest })) .or( - z.object({ + EventSchema.extend({ type: zType('APP_UPDATE_EMAIL_PRIMARY_OTP'), payload: AppUpdateEmailPrimaryOtpRequest }) ) .or( - z.object({ + EventSchema.extend({ type: zType('APP_UPDATE_EMAIL_SECONDARY_OTP'), payload: AppUpdateEmailSecondaryOtpRequest }) ) - .or(z.object({ type: zType('APP_SYNC_THEME'), payload: AppSyncThemeRequest })) + .or(EventSchema.extend({ type: zType('APP_SYNC_THEME'), payload: AppSyncThemeRequest })) - .or(z.object({ type: zType('APP_SYNC_DAPP_DATA'), payload: AppSyncDappDataRequest })), + .or(EventSchema.extend({ type: zType('APP_SYNC_DAPP_DATA'), payload: AppSyncDappDataRequest })), // -- Frame Events --------------------------------------------------------- - frameEvent: z - .object({ type: zType('FRAME_SWITCH_NETWORK_ERROR'), payload: zError, origin: z.string() }) + frameEvent: EventSchema.extend({ + type: zType('FRAME_SWITCH_NETWORK_ERROR'), + payload: zError, + origin: z.string() + }) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_SWITCH_NETWORK_SUCCESS'), payload: FrameSwitchNetworkResponse, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_CONNECT_EMAIL_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_CONNECT_EMAIL_ERROR'), + payload: zError, + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_CONNECT_EMAIL_SUCCESS'), payload: FrameConnectEmailResponse, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_CONNECT_OTP_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_CONNECT_OTP_ERROR'), + payload: zError, + origin: z.string() + }) + ) - .or(z.object({ type: zType('FRAME_CONNECT_OTP_SUCCESS'), origin: z.string() })) + .or(EventSchema.extend({ type: zType('FRAME_CONNECT_OTP_SUCCESS'), origin: z.string() })) .or( - z.object({ type: zType('FRAME_CONNECT_DEVICE_ERROR'), payload: zError, origin: z.string() }) + EventSchema.extend({ + type: zType('FRAME_CONNECT_DEVICE_ERROR'), + payload: zError, + origin: z.string() + }) ) - .or(z.object({ type: zType('FRAME_CONNECT_DEVICE_SUCCESS'), origin: z.string() })) + .or(EventSchema.extend({ type: zType('FRAME_CONNECT_DEVICE_SUCCESS'), origin: z.string() })) - .or(z.object({ type: zType('FRAME_GET_USER_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_GET_USER_ERROR'), + payload: zError, + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_GET_USER_SUCCESS'), payload: FrameGetUserResponse, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_SIGN_OUT_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_SIGN_OUT_ERROR'), + payload: zError, + origin: z.string() + }) + ) - .or(z.object({ type: zType('FRAME_SIGN_OUT_SUCCESS'), origin: z.string() })) + .or(EventSchema.extend({ type: zType('FRAME_SIGN_OUT_SUCCESS'), origin: z.string() })) - .or(z.object({ type: zType('FRAME_IS_CONNECTED_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_IS_CONNECTED_ERROR'), + payload: zError, + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_IS_CONNECTED_SUCCESS'), payload: FrameIsConnectedResponse, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_GET_CHAIN_ID_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_GET_CHAIN_ID_ERROR'), + payload: zError, + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_GET_CHAIN_ID_SUCCESS'), payload: FrameGetChainIdResponse, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_RPC_REQUEST_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_RPC_REQUEST_ERROR'), + payload: zError, + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_RPC_REQUEST_SUCCESS'), payload: RpcResponse, origin: z.string() @@ -419,13 +475,23 @@ export const AppKitFrameSchema = { ) .or( - z.object({ type: zType('FRAME_SESSION_UPDATE'), payload: FrameSession, origin: z.string() }) + EventSchema.extend({ + type: zType('FRAME_SESSION_UPDATE'), + payload: FrameSession, + origin: z.string() + }) ) - .or(z.object({ type: zType('FRAME_UPDATE_EMAIL_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_UPDATE_EMAIL_ERROR'), + payload: zError, + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_UPDATE_EMAIL_SUCCESS'), payload: FrameUpdateEmailResponse, origin: z.string() @@ -433,17 +499,22 @@ export const AppKitFrameSchema = { ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_UPDATE_EMAIL_PRIMARY_OTP_ERROR'), payload: zError, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_UPDATE_EMAIL_PRIMARY_OTP_SUCCESS'), origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_UPDATE_EMAIL_PRIMARY_OTP_SUCCESS'), + origin: z.string() + }) + ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_UPDATE_EMAIL_SECONDARY_OTP_ERROR'), payload: zError, origin: z.string() @@ -451,20 +522,30 @@ export const AppKitFrameSchema = { ) .or( - z.object({ + EventSchema.extend({ type: zType('FRAME_UPDATE_EMAIL_SECONDARY_OTP_SUCCESS'), - payload: FrameUpdateEmailSecondaryOtpResolver, + payload: FrameUpdateEmailSecondaryOtpResponse, origin: z.string() }) ) - .or(z.object({ type: zType('FRAME_SYNC_THEME_ERROR'), payload: zError, origin: z.string() })) + .or( + EventSchema.extend({ + type: zType('FRAME_SYNC_THEME_ERROR'), + payload: zError, + origin: z.string() + }) + ) - .or(z.object({ type: zType('FRAME_SYNC_THEME_SUCCESS'), origin: z.string() })) + .or(EventSchema.extend({ type: zType('FRAME_SYNC_THEME_SUCCESS'), origin: z.string() })) .or( - z.object({ type: zType('FRAME_SYNC_DAPP_DATA_ERROR'), payload: zError, origin: z.string() }) + EventSchema.extend({ + type: zType('FRAME_SYNC_DAPP_DATA_ERROR'), + payload: zError, + origin: z.string() + }) ) - .or(z.object({ type: zType('FRAME_SYNC_DAPP_DATA_SUCCESS'), origin: z.string() })) + .or(EventSchema.extend({ type: zType('FRAME_SYNC_DAPP_DATA_SUCCESS'), origin: z.string() })) }; diff --git a/packages/wallet/src/AppKitFrameTypes.ts b/packages/wallet/src/AppKitFrameTypes.ts index bc29ab59e..256f9f137 100644 --- a/packages/wallet/src/AppKitFrameTypes.ts +++ b/packages/wallet/src/AppKitFrameTypes.ts @@ -48,7 +48,7 @@ import { FrameSession, AppGetUserRequest, AppUpdateEmailRequest, - FrameUpdateEmailSecondaryOtpResolver, + FrameUpdateEmailSecondaryOtpResponse, AppUpdateEmailPrimaryOtpRequest, AppUpdateEmailSecondaryOtpRequest, AppSyncThemeRequest, @@ -80,9 +80,16 @@ export namespace AppKitFrameTypes { FrameGetChainIdResponse: z.infer; FrameGetUserResponse: z.infer; FrameIsConnectedResponse: z.infer; - FrameUpdateEmailSecondaryOtpResolver: z.infer; FrameSwitchNetworkResponse: z.infer; FrameUpdateEmailResponse: z.infer; + FrameConnectOtpResponse: undefined; + FrameSyncThemeResponse: undefined; + FrameSyncDappDataResponse: undefined; + FrameUpdateEmailPrimaryOtpResponse: undefined; + FrameUpdateEmailSecondaryOtpResponse: z.infer; + FrameConnectDeviceResponse: undefined; + FrameSignOutResponse: undefined; + FrameRpcResponse: RPCResponse; } export interface Network { @@ -139,4 +146,20 @@ export namespace AppKitFrameTypes { export type RPCResponse = z.infer; export type FrameSessionType = z.infer; + + export type ProviderRequestType = + | 'GetUser' + | 'ConnectDevice' + | 'ConnectEmail' + | 'ConnectOtp' + | 'SwitchNetwork' + | 'UpdateEmail' + | 'SyncTheme' + | 'SyncDappData' + | 'UpdateEmailPrimaryOtp' + | 'UpdateEmailSecondaryOtp' + | 'GetChainId' + | 'IsConnected' + | 'SignOut' + | 'Rpc'; } diff --git a/packages/wallet/src/index.ts b/packages/wallet/src/index.ts index c2c9f6d54..f77b09495 100644 --- a/packages/wallet/src/index.ts +++ b/packages/wallet/src/index.ts @@ -1,5 +1,5 @@ export { AppKitFrameHelpers } from './AppKitFrameHelpers'; -export { AppKitFrameProvider } from './AppKitFrameProvider'; +export { AppKitFrameProvider, type AppKitFrameProviderMethods } from './AppKitFrameProvider'; export { AppKitFrameSchema } from './AppKitFrameSchema'; export { AppKitFrameConstants, AppKitFrameRpcConstants } from './AppKitFrameConstants'; export { AppKitFrameStorage } from './AppKitFrameStorage'; From f37bfba8a5a986217e2f6dade4cc43f42de57262 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 23 Sep 2024 15:50:39 -0300 Subject: [PATCH 051/114] chore: send changes --- .../src/partials/w3m-input-token/intex.tsx | 20 ++++++++++--------- .../views/w3m-account-default-view/index.tsx | 5 ++++- .../views/w3m-wallet-receive-view/index.tsx | 4 +--- .../src/views/w3m-wallet-send-view/index.tsx | 8 ++++++-- packages/wallet/src/AppKitAuthWebview.tsx | 2 +- 5 files changed, 23 insertions(+), 16 deletions(-) diff --git a/packages/scaffold/src/partials/w3m-input-token/intex.tsx b/packages/scaffold/src/partials/w3m-input-token/intex.tsx index ca27213bf..b327ff4de 100644 --- a/packages/scaffold/src/partials/w3m-input-token/intex.tsx +++ b/packages/scaffold/src/partials/w3m-input-token/intex.tsx @@ -2,7 +2,7 @@ import { useRef, useState } from 'react'; import { TextInput, type StyleProp, type ViewStyle } from 'react-native'; import { FlexView, Link, Text, useTheme, TokenButton } from '@reown/appkit-ui-react-native'; import { NumberUtil, type Balance } from '@reown/appkit-common-react-native'; -import { SendController } from '@reown/appkit-core-react-native'; +import { ConstantsUtil, SendController } from '@reown/appkit-core-react-native'; import styles from './styles'; import { getMaxAmount, getSendValue } from './utils'; @@ -10,11 +10,11 @@ import { getMaxAmount, getSendValue } from './utils'; export interface InputTokenProps { token?: Balance; sendTokenAmount?: number; - gasPriceInUSD?: number; + gasPrice?: number; style?: StyleProp; } -export function InputToken({ token, sendTokenAmount, gasPriceInUSD, style }: InputTokenProps) { +export function InputToken({ token, sendTokenAmount, gasPrice, style }: InputTokenProps) { const Theme = useTheme(); const valueInputRef = useRef(null); const [inputValue, setInputValue] = useState(sendTokenAmount?.toString()); @@ -31,15 +31,17 @@ export function InputToken({ token, sendTokenAmount, gasPriceInUSD, style }: Inp }; const onMaxPress = () => { - if (token && gasPriceInUSD) { - const amountOfTokenGasRequired = NumberUtil.bigNumber(gasPriceInUSD.toFixed(5)).dividedBy( - token.price - ); + if (token && gasPrice) { + const isNetworkToken = + token.address === undefined || + Object.values(ConstantsUtil.NATIVE_TOKEN_ADDRESS).some( + nativeAddress => token?.address === nativeAddress + ); - const isNetworkToken = token.address === undefined; + const numericGas = NumberUtil.bigNumber(gasPrice).shiftedBy(-token.quantity.decimals); const maxValue = isNetworkToken - ? NumberUtil.bigNumber(token.quantity.numeric).minus(amountOfTokenGasRequired) + ? NumberUtil.bigNumber(token.quantity.numeric).minus(numericGas) : NumberUtil.bigNumber(token.quantity.numeric); SendController.setTokenAmount(Number(maxValue.toFixed(20))); diff --git a/packages/scaffold/src/views/w3m-account-default-view/index.tsx b/packages/scaffold/src/views/w3m-account-default-view/index.tsx index 3295bad8f..b00070b4f 100644 --- a/packages/scaffold/src/views/w3m-account-default-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-default-view/index.tsx @@ -78,7 +78,10 @@ export function AccountDefaultView() { }; const onCopyAddress = () => { - if (AccountController.state.address) { + if (AccountController.state.profileName) { + OptionsController.copyToClipboard(AccountController.state.profileName); + SnackController.showSuccess('Name copied'); + } else if (AccountController.state.address) { OptionsController.copyToClipboard( AccountController.state.profileName ?? AccountController.state.address ); diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx index ce7c0c8c1..51693a34f 100644 --- a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx @@ -45,9 +45,7 @@ export function WalletReceiveView() { const onCopyAddress = () => { if (canCopy && AccountController.state.address) { - OptionsController.copyToClipboard( - AccountController.state.profileName ?? AccountController.state.address - ); + OptionsController.copyToClipboard(AccountController.state.address); SnackController.showSuccess('Address copied'); } }; diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx index 1c308ed18..0ad2686e0 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx @@ -24,7 +24,7 @@ import styles from './styles'; export function WalletSendView() { const { padding } = useCustomDimensions(); const { keyboardShown, keyboardHeight } = useKeyboard(); - const { token, sendTokenAmount, receiverAddress, loading, gasPriceInUSD } = useSnapshot( + const { token, sendTokenAmount, receiverAddress, loading, gasPrice } = useSnapshot( SendController.state ); const { tokenBalance } = useSnapshot(AccountController.state); @@ -58,6 +58,10 @@ export function WalletSendView() { return 'Insufficient balance'; } + if (!SendController.state.receiverAddress) { + return 'Add address'; + } + return 'Preview Send'; }; @@ -77,7 +81,7 @@ export function WalletSendView() { diff --git a/packages/wallet/src/AppKitAuthWebview.tsx b/packages/wallet/src/AppKitAuthWebview.tsx index 438e511dc..32a1bee7d 100644 --- a/packages/wallet/src/AppKitAuthWebview.tsx +++ b/packages/wallet/src/AppKitAuthWebview.tsx @@ -75,7 +75,7 @@ export function AuthWebview() { }); provider.onRpcError(() => { - if (isWebviewVisibile) { + if (ModalController.state.open) { if (RouterController.state.transactionStack.length === 0) { ModalController.close(); } else { From 66b3551bc5d6f37081a75cc09f39065371ad348d Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 23 Sep 2024 19:41:25 -0300 Subject: [PATCH 052/114] chore: select token view --- packages/core/src/utils/CoreHelperUtil.ts | 21 ++++ .../scaffold/src/modal/w3m-router/index.tsx | 3 + .../src/partials/w3m-input-token/intex.tsx | 27 ++++-- .../index.tsx | 47 +++++++++ .../styles.ts | 11 +++ .../src/views/w3m-wallet-send-view/index.tsx | 49 ++++++++-- .../src/views/w3m-wallet-send-view/styles.ts | 5 +- .../ui/src/composites/wui-button/styles.ts | 2 +- .../src/composites/wui-list-token/index.tsx | 96 +++++++++++-------- .../src/composites/wui-token-button/index.tsx | 24 +++-- .../src/composites/wui-token-button/styles.ts | 4 + packages/wallet/src/AppKitFrameProvider.ts | 11 --- 12 files changed, 216 insertions(+), 84 deletions(-) create mode 100644 packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx create mode 100644 packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts diff --git a/packages/core/src/utils/CoreHelperUtil.ts b/packages/core/src/utils/CoreHelperUtil.ts index 6fd2906ee..99eb1255b 100644 --- a/packages/core/src/utils/CoreHelperUtil.ts +++ b/packages/core/src/utils/CoreHelperUtil.ts @@ -157,6 +157,27 @@ export const CoreHelperUtil = { return formattedBalance ? `${formattedBalance} ${symbol}` : `0.000 ${symbol || ''}`; }, + isAddress(address: string, chain = 'eip155'): boolean { + switch (chain) { + case 'eip155': + if (!/^(?:0x)?[0-9a-f]{40}$/iu.test(address)) { + return false; + } else if ( + /^(?:0x)?[0-9a-f]{40}$/iu.test(address) || + /^(?:0x)?[0-9A-F]{40}$/iu.test(address) + ) { + return true; + } + + return false; + case 'solana': + return /[1-9A-HJ-NP-Za-km-z]{32,44}$/iu.test(address); + + default: + return false; + } + }, + getApiUrl() { return CommonConstants.API_URL; }, diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/scaffold/src/modal/w3m-router/index.tsx index d3c514d92..f7c313e57 100644 --- a/packages/scaffold/src/modal/w3m-router/index.tsx +++ b/packages/scaffold/src/modal/w3m-router/index.tsx @@ -26,6 +26,7 @@ import { WalletCompatibleNetworks } from '../../views/w3m-wallet-compatible-netw import { TransactionsView } from '../../views/w3m-transactions-view'; import { WalletSendView } from '../../views/w3m-wallet-send-view'; import { WalletSendPreviewView } from '../../views/w3m-wallet-send-preview-view'; +import { WalletSendSelectTokenView } from '../../views/w3m-wallet-send-select-token-view'; export function AppKitRouter() { const { view } = useSnapshot(RouterController.state); @@ -78,6 +79,8 @@ export function AppKitRouter() { return WalletSendView; case 'WalletSendPreview': return WalletSendPreviewView; + case 'WalletSendSelectToken': + return WalletSendSelectTokenView; case 'WhatIsANetwork': return WhatIsNetworkView; case 'WhatIsAWallet': diff --git a/packages/scaffold/src/partials/w3m-input-token/intex.tsx b/packages/scaffold/src/partials/w3m-input-token/intex.tsx index b327ff4de..1e89612b9 100644 --- a/packages/scaffold/src/partials/w3m-input-token/intex.tsx +++ b/packages/scaffold/src/partials/w3m-input-token/intex.tsx @@ -4,17 +4,24 @@ import { FlexView, Link, Text, useTheme, TokenButton } from '@reown/appkit-ui-re import { NumberUtil, type Balance } from '@reown/appkit-common-react-native'; import { ConstantsUtil, SendController } from '@reown/appkit-core-react-native'; -import styles from './styles'; import { getMaxAmount, getSendValue } from './utils'; +import styles from './styles'; export interface InputTokenProps { token?: Balance; sendTokenAmount?: number; gasPrice?: number; style?: StyleProp; + onTokenPress?: () => void; } -export function InputToken({ token, sendTokenAmount, gasPrice, style }: InputTokenProps) { +export function InputToken({ + token, + sendTokenAmount, + gasPrice, + style, + onTokenPress +}: InputTokenProps) { const Theme = useTheme(); const valueInputRef = useRef(null); const [inputValue, setInputValue] = useState(sendTokenAmount?.toString()); @@ -81,7 +88,7 @@ export function InputToken({ token, sendTokenAmount, gasPrice, style }: InputTok numberOfLines={1} autoFocus={!!token} /> - + {sendValue ?? ''} - - - {maxAmount ?? ''} - - Max - + {token && ( + + + {maxAmount ?? ''} + + Max + + )} ); diff --git a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx new file mode 100644 index 000000000..7f9253425 --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx @@ -0,0 +1,47 @@ +import { useSnapshot } from 'valtio'; + +import { FlexView, ListToken, Text } from '@reown/appkit-ui-react-native'; +import { + AccountController, + AssetUtil, + NetworkController, + RouterController, + SendController +} from '@reown/appkit-core-react-native'; +import { ScrollView } from 'react-native'; +import styles from './styles'; +import type { Balance } from '@reown/appkit-common-react-native'; + +export function WalletSendSelectTokenView() { + const { tokenBalance } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const networkImage = AssetUtil.getNetworkImage(caipNetwork); + + const onTokenPress = (token: Balance) => { + SendController.setToken(token); + SendController.setTokenAmount(undefined); + RouterController.goBack(); + }; + + return ( + + + + Your tokens + + {tokenBalance?.map(token => ( + onTokenPress(token)} + /> + ))} + + + ); +} diff --git a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts new file mode 100644 index 000000000..43dcd318a --- /dev/null +++ b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/styles.ts @@ -0,0 +1,11 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + container: { + minHeight: 250 + }, + title: { + marginBottom: Spacing.xs + } +}); diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx index 0ad2686e0..154e21f23 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx @@ -3,6 +3,7 @@ import { Platform, ScrollView } from 'react-native'; import { useSnapshot } from 'valtio'; import { AccountController, + CoreHelperUtil, RouterController, SendController, SwapController @@ -44,32 +45,55 @@ export function WalletSendView() { }, []); const onSendPress = () => { - if (SendController.state.loading) return; - // TODO: add validations RouterController.push('WalletSendPreview'); }; const getActionText = () => { + if (!SendController.state.token) { + return 'Select token'; + } + if ( - SendController.state.token && SendController.state.sendTokenAmount && + SendController.state.token && SendController.state.sendTokenAmount > Number(SendController.state.token.quantity.numeric) ) { - return 'Insufficient balance'; + return 'Insufficient funds'; + } + + if (!SendController.state.sendTokenAmount) { + return 'Add amount'; + } + + if (SendController.state.sendTokenAmount && SendController.state.token?.price) { + const value = SendController.state.sendTokenAmount * SendController.state.token.price; + if (!value) { + return 'Incorrect value'; + } + } + + if ( + SendController.state.receiverAddress && + !CoreHelperUtil.isAddress(SendController.state.receiverAddress) + ) { + return 'Invalid address'; } if (!SendController.state.receiverAddress) { return 'Add address'; } - return 'Preview Send'; + return 'Preview send'; }; useEffect(() => { - // TODO: check this - SendController.setToken(tokenBalance?.[0]); + if (!token) { + SendController.setToken(tokenBalance?.[0]); + } fetchNetworkPrice(); - }, [tokenBalance, fetchNetworkPrice]); + }, [token, tokenBalance, fetchNetworkPrice]); + + const actionText = getActionText(); return ( RouterController.push('WalletSendSelectToken')} /> @@ -98,7 +123,11 @@ export function WalletSendView() { style={styles.arrowIcon} /> - + ); } return ( - ); } diff --git a/packages/ui/src/composites/wui-token-button/styles.ts b/packages/ui/src/composites/wui-token-button/styles.ts index ad753204b..7ece57a06 100644 --- a/packages/ui/src/composites/wui-token-button/styles.ts +++ b/packages/ui/src/composites/wui-token-button/styles.ts @@ -2,6 +2,10 @@ import { StyleSheet } from 'react-native'; import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; export default StyleSheet.create({ + selectButton: { + height: 40, + paddingHorizontal: Spacing.m + }, container: { height: 40 }, diff --git a/packages/wallet/src/AppKitFrameProvider.ts b/packages/wallet/src/AppKitFrameProvider.ts index d09b08655..41f336a7e 100644 --- a/packages/wallet/src/AppKitFrameProvider.ts +++ b/packages/wallet/src/AppKitFrameProvider.ts @@ -453,17 +453,6 @@ export class AppKitFrameProvider { callback(frameEvent); } - private onAppEvent( - event: AppKitFrameTypes.AppEvent, - callback: (event: AppKitFrameTypes.AppEvent) => void - ) { - if (!event.type?.includes(AppKitFrameConstants.APP_EVENT_KEY)) { - return; - } - const appEvent = AppKitFrameSchema.appEvent.parse(event); - callback(appEvent); - } - private postAppEvent(event: AppKitFrameTypes.AppEvent) { if (!this.webviewRef?.current) { throw new Error('AppKitFrameProvider: webviewRef is not set'); From b03481f3895d7979978108aed59f958488cb35ec Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 23 Sep 2024 19:58:44 -0300 Subject: [PATCH 053/114] chore: ui improvements --- .../w3m-wallet-send-preview-view/index.tsx | 103 +++++++++--------- .../index.tsx | 41 +++++-- .../styles.ts | 6 +- 3 files changed, 92 insertions(+), 58 deletions(-) diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx index 5e2661ae1..0c0fd66ba 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx @@ -1,4 +1,5 @@ import { useSnapshot } from 'valtio'; +import { ScrollView } from 'react-native'; import { Avatar, Button, FlexView, Icon, Image, Text, UiUtil } from '@reown/appkit-ui-react-native'; import { NumberUtil } from '@reown/appkit-common-react-native'; import { @@ -6,11 +7,13 @@ import { RouterController, SendController } from '@reown/appkit-core-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import { PreviewSendPill } from './components/preview-send-pill'; import styles from './styles'; import { PreviewSendDetails } from './components/preview-send-details'; export function WalletSendPreviewView() { + const { padding } = useCustomDimensions(); const { caipNetwork } = useSnapshot(NetworkController.state); const { token, receiverAddress, gasPriceInUSD, loading } = useSnapshot(SendController.state); @@ -45,59 +48,61 @@ export function WalletSendPreviewView() { }; return ( - - - + + + + + + Send + + + ${getSendValue()} + + + + {token?.iconUrl ? ( + + ) : ( + + )} + + + + - Send + To - - ${getSendValue()} + + + + + + + + + Review transaction carefully - - {token?.iconUrl ? ( - - ) : ( - - )} - - - - - - To - - - - - - - - - - Review transaction carefully - - - - - + + + + - + ); } diff --git a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx index 7f9253425..80175219d 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx @@ -1,6 +1,7 @@ +import { useState } from 'react'; import { useSnapshot } from 'valtio'; - -import { FlexView, ListToken, Text } from '@reown/appkit-ui-react-native'; +import { ScrollView } from 'react-native'; +import { FlexView, InputText, ListToken, Text } from '@reown/appkit-ui-react-native'; import { AccountController, AssetUtil, @@ -8,14 +9,26 @@ import { RouterController, SendController } from '@reown/appkit-core-react-native'; -import { ScrollView } from 'react-native'; -import styles from './styles'; import type { Balance } from '@reown/appkit-common-react-native'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + export function WalletSendSelectTokenView() { + const { padding } = useCustomDimensions(); const { tokenBalance } = useSnapshot(AccountController.state); const { caipNetwork } = useSnapshot(NetworkController.state); const networkImage = AssetUtil.getNetworkImage(caipNetwork); + const [tokenSearch, setTokenSearch] = useState(''); + const [filteredTokens, setFilteredTokens] = useState(tokenBalance ?? []); + + const onSearchChange = (value: string) => { + setTokenSearch(value); + const filtered = AccountController.state.tokenBalance?.filter(token => + token.name.toLowerCase().includes(value.toLowerCase()) + ); + setFilteredTokens(filtered ?? []); + }; const onTokenPress = (token: Balance) => { SendController.setToken(token); @@ -24,14 +37,26 @@ export function WalletSendSelectTokenView() { }; return ( - - + + + + + Your tokens - {tokenBalance?.map(token => ( + {filteredTokens.map((token, index) => ( Date: Tue, 24 Sep 2024 14:46:26 -0300 Subject: [PATCH 054/114] fix: added usdt abi --- packages/common/src/contracts/usdt.ts | 192 ++++++++++++++++++ packages/common/src/index.ts | 2 + packages/common/src/utils/ConstantsUtil.ts | 5 +- packages/common/src/utils/ContractUtil.ts | 14 ++ packages/common/src/utils/NamesUtil.ts | 10 + .../controllers/BlockchainApiController.ts | 11 + .../src/controllers/ConnectionController.ts | 10 + .../core/src/controllers/EnsController.ts | 39 ++++ .../core/src/controllers/SendController.ts | 12 +- packages/core/src/index.ts | 2 + packages/core/src/utils/TypeUtil.ts | 28 +++ packages/scaffold/src/client.ts | 10 + .../src/partials/w3m-input-address/index.tsx | 29 ++- .../components/preview-send-details.tsx | 11 +- .../w3m-wallet-send-preview-view/index.tsx | 44 +++- .../src/views/w3m-wallet-send-view/index.tsx | 7 +- packages/wagmi/src/client.ts | 45 +++- 17 files changed, 444 insertions(+), 27 deletions(-) create mode 100644 packages/common/src/contracts/usdt.ts create mode 100644 packages/common/src/utils/ContractUtil.ts create mode 100644 packages/common/src/utils/NamesUtil.ts create mode 100644 packages/core/src/controllers/EnsController.ts diff --git a/packages/common/src/contracts/usdt.ts b/packages/common/src/contracts/usdt.ts new file mode 100644 index 000000000..abfe5deae --- /dev/null +++ b/packages/common/src/contracts/usdt.ts @@ -0,0 +1,192 @@ +export const usdtABI = [ + { + type: 'event', + name: 'Approval', + inputs: [ + { + indexed: true, + name: 'owner', + type: 'address' + }, + { + indexed: true, + name: 'spender', + type: 'address' + }, + { + indexed: false, + name: 'value', + type: 'uint256' + } + ] + }, + { + type: 'event', + name: 'Transfer', + inputs: [ + { + indexed: true, + name: 'from', + type: 'address' + }, + { + indexed: true, + name: 'to', + type: 'address' + }, + { + indexed: false, + name: 'value', + type: 'uint256' + } + ] + }, + { + type: 'function', + name: 'allowance', + stateMutability: 'view', + inputs: [ + { + name: 'owner', + type: 'address' + }, + { + name: 'spender', + type: 'address' + } + ], + outputs: [ + { + name: '', + type: 'uint256' + } + ] + }, + { + type: 'function', + name: 'approve', + stateMutability: 'nonpayable', + inputs: [ + { + name: 'spender', + type: 'address' + }, + { + name: 'amount', + type: 'uint256' + } + ], + outputs: [ + { + name: '', + type: 'bool' + } + ] + }, + { + type: 'function', + name: 'balanceOf', + stateMutability: 'view', + inputs: [ + { + name: 'account', + type: 'address' + } + ], + outputs: [ + { + name: '', + type: 'uint256' + } + ] + }, + { + type: 'function', + name: 'decimals', + stateMutability: 'view', + inputs: [], + outputs: [ + { + name: '', + type: 'uint8' + } + ] + }, + { + type: 'function', + name: 'name', + stateMutability: 'view', + inputs: [], + outputs: [ + { + name: '', + type: 'string' + } + ] + }, + { + type: 'function', + name: 'symbol', + stateMutability: 'view', + inputs: [], + outputs: [ + { + name: '', + type: 'string' + } + ] + }, + { + type: 'function', + name: 'totalSupply', + stateMutability: 'view', + inputs: [], + outputs: [ + { + name: '', + type: 'uint256' + } + ] + }, + { + type: 'function', + name: 'transfer', + stateMutability: 'nonpayable', + inputs: [ + { + name: 'recipient', + type: 'address' + }, + { + name: 'amount', + type: 'uint256' + } + ], + outputs: [] + }, + { + type: 'function', + name: 'transferFrom', + stateMutability: 'nonpayable', + inputs: [ + { + name: 'sender', + type: 'address' + }, + { + name: 'recipient', + type: 'address' + }, + { + name: 'amount', + type: 'uint256' + } + ], + outputs: [ + { + name: '', + type: 'bool' + } + ] + } +] as const; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 7e84124c2..13f0e979b 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -1,5 +1,7 @@ export { ConstantsUtil } from './utils/ConstantsUtil'; +export { ContractUtil } from './utils/ContractUtil'; export { DateUtil } from './utils/DateUtil'; +export { NamesUtil } from './utils/NamesUtil'; export { NetworkUtil } from './utils/NetworkUtil'; export { NumberUtil } from './utils/NumberUtil'; export { erc20ABI } from './contracts/erc20'; diff --git a/packages/common/src/utils/ConstantsUtil.ts b/packages/common/src/utils/ConstantsUtil.ts index 064d54442..bf2e03385 100644 --- a/packages/common/src/utils/ConstantsUtil.ts +++ b/packages/common/src/utils/ConstantsUtil.ts @@ -1,7 +1,10 @@ export const ConstantsUtil = { + WC_NAME_SUFFIX: '.reown.id', + WC_NAME_SUFFIX_LEGACY: '.wcn.id', BLOCKCHAIN_API_RPC_URL: 'https://rpc.walletconnect.org', PULSE_API_URL: 'https://pulse.walletconnect.org', API_URL: 'https://api.web3modal.org', COINBASE_CONNECTOR_ID: 'coinbaseWallet', - COINBASE_EXPLORER_ID: 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa' + COINBASE_EXPLORER_ID: 'fd20dc426fb37566d803205b19bbc1d4096b248ac04548e3cfb6b3a38bd033aa', + USDT_CONTRACT_ADDRESS: '0xdac17f958d2ee523a2206206994597c13d831ec7' }; diff --git a/packages/common/src/utils/ContractUtil.ts b/packages/common/src/utils/ContractUtil.ts new file mode 100644 index 000000000..3af0e3214 --- /dev/null +++ b/packages/common/src/utils/ContractUtil.ts @@ -0,0 +1,14 @@ +import { erc20ABI } from '../contracts/erc20'; +import { usdtABI } from '../contracts/usdt'; +import { ConstantsUtil } from './ConstantsUtil'; + +export const ContractUtil = { + getABI: (tokenAddress: string) => { + switch (tokenAddress) { + case ConstantsUtil.USDT_CONTRACT_ADDRESS: + return usdtABI; + default: + return erc20ABI; + } + } +}; diff --git a/packages/common/src/utils/NamesUtil.ts b/packages/common/src/utils/NamesUtil.ts new file mode 100644 index 000000000..312eda743 --- /dev/null +++ b/packages/common/src/utils/NamesUtil.ts @@ -0,0 +1,10 @@ +import { ConstantsUtil } from './ConstantsUtil'; + +export const NamesUtil = { + isReownName(value: string) { + return ( + value?.endsWith(ConstantsUtil.WC_NAME_SUFFIX_LEGACY) || + value?.endsWith(ConstantsUtil.WC_NAME_SUFFIX) + ); + } +}; diff --git a/packages/core/src/controllers/BlockchainApiController.ts b/packages/core/src/controllers/BlockchainApiController.ts index b87d7e626..ddf7eb854 100644 --- a/packages/core/src/controllers/BlockchainApiController.ts +++ b/packages/core/src/controllers/BlockchainApiController.ts @@ -8,6 +8,7 @@ import type { BlockchainApiGasPriceResponse, BlockchainApiIdentityRequest, BlockchainApiIdentityResponse, + BlockchainApiLookupEnsName, BlockchainApiTokenPriceRequest, BlockchainApiTokenPriceResponse, BlockchainApiTransactionsRequest, @@ -112,6 +113,16 @@ export const BlockchainApiController = { }); }, + async lookupEnsName(name: string) { + return state.api.get({ + path: `/v1/profile/account/${name}`, + params: { + projectId: OptionsController.state.projectId, + apiVersion: '2' + } + }); + }, + setClientId(clientId: string | null) { state.clientId = clientId; state.api = new FetchUtil({ baseUrl, clientId }); diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index 7ee531432..62faa4539 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -31,6 +31,8 @@ export interface ConnectionControllerClient { formatUnits: (value: bigint, decimals: number) => string; writeContract: (args: WriteContractArgs) => Promise<`0x${string}` | null>; disconnect: () => Promise; + getEnsAddress: (value: string) => Promise; + getEnsAvatar: (value: string) => Promise; } export interface ConnectionControllerState { @@ -147,6 +149,14 @@ export const ConnectionController = { return this._getClient().writeContract(args); }, + async getEnsAddress(value: string) { + return this._getClient().getEnsAddress(value); + }, + + async getEnsAvatar(value: string) { + return this._getClient().getEnsAvatar(value); + }, + clearUri() { state.wcUri = undefined; state.wcPairingExpiry = undefined; diff --git a/packages/core/src/controllers/EnsController.ts b/packages/core/src/controllers/EnsController.ts new file mode 100644 index 000000000..c80fadf21 --- /dev/null +++ b/packages/core/src/controllers/EnsController.ts @@ -0,0 +1,39 @@ +import { subscribeKey as subKey } from 'valtio/vanilla/utils'; +import { proxy, subscribe as sub } from 'valtio/vanilla'; +import { BlockchainApiController } from './BlockchainApiController'; +import type { BlockchainApiEnsError } from '../utils/TypeUtil'; + +// -- Types --------------------------------------------- // + +export interface EnsControllerState { + loading: boolean; +} + +type StateKey = keyof EnsControllerState; + +// -- State --------------------------------------------- // +const state = proxy({ + loading: false +}); + +// -- Controller ---------------------------------------- // +export const EnsController = { + state, + + subscribe(callback: (newState: EnsControllerState) => void) { + return sub(state, () => callback(state)); + }, + + subscribeKey(key: K, callback: (value: EnsControllerState[K]) => void) { + return subKey(state, key, callback); + }, + + async resolveName(name: string) { + try { + return await BlockchainApiController.lookupEnsName(name); + } catch (e) { + const error = e as BlockchainApiEnsError; + throw new Error(error?.reasons?.[0]?.description || 'Error resolving name'); + } + } +}; diff --git a/packages/core/src/controllers/SendController.ts b/packages/core/src/controllers/SendController.ts index 5153f4cd8..b4acf79b2 100644 --- a/packages/core/src/controllers/SendController.ts +++ b/packages/core/src/controllers/SendController.ts @@ -1,7 +1,6 @@ import { subscribeKey as subKey } from 'valtio/vanilla/utils'; import { proxy, ref, subscribe as sub } from 'valtio/vanilla'; -import { type Balance } from '@reown/appkit-common-react-native'; -import { erc20ABI } from '@reown/appkit-common-react-native'; +import { ContractUtil, type Balance } from '@reown/appkit-common-react-native'; import { AccountController } from './AccountController'; import { ConnectionController } from './ConnectionController'; import { SnackController } from './SnackController'; @@ -204,15 +203,16 @@ export const SendController = { params.receiverAddress && params.tokenAddress ) { + const tokenAddress = CoreHelperUtil.getPlainAddress( + params.tokenAddress as `${string}:${string}:${string}` + ) as `0x${string}`; await ConnectionController.writeContract({ fromAddress: AccountController.state.address as `0x${string}`, - tokenAddress: CoreHelperUtil.getPlainAddress( - params.tokenAddress as `${string}:${string}:${string}` - ) as `0x${string}`, + tokenAddress, receiverAddress: params.receiverAddress as `0x${string}`, tokenAmount: amount, method: 'transfer', - abi: erc20ABI + abi: ContractUtil.getABI(tokenAddress) }); SnackController.showSuccess('Transaction started'); this.resetSend(); diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 6b62e4e2c..3e0469362 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -47,6 +47,8 @@ export { SwapController, type SwapControllerState } from './controllers/SwapCont export { EventsController, type EventsControllerState } from './controllers/EventsController'; +export { EnsController, type EnsControllerState } from './controllers/EnsController'; + export { TransactionsController, type TransactionsControllerState diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index b3b2d54fe..4b380d0b9 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -1,5 +1,9 @@ import type { Balance, Transaction } from '@reown/appkit-common-react-native'; +export interface BaseError { + message?: string; +} + export type CaipAddress = `${string}:${string}:${string}`; export type CaipNetworkId = `${string}:${string}`; @@ -170,6 +174,30 @@ export interface BlockchainApiGasPriceResponse { instant: string; } +export interface BlockchainApiEnsError extends BaseError { + status: string; + reasons: { name: string; description: string }[]; +} + +export type ReownName = `${string}.reown.id` | `${string}.wcn.id`; + +export interface BlockchainApiLookupEnsName { + name: ReownName; + registered: number; + updated: number; + addresses: Record< + string, + { + address: string; + created: string; + } + >; + attributes: { + avatar?: string; + bio?: string; + }[]; +} + // -- OptionsController Types --------------------------------------------------- export interface Token { address: string; diff --git a/packages/scaffold/src/client.ts b/packages/scaffold/src/client.ts index 64d04d9b8..66760372f 100644 --- a/packages/scaffold/src/client.ts +++ b/packages/scaffold/src/client.ts @@ -21,6 +21,7 @@ import { BlockchainApiController, ConnectionController, ConnectorController, + EnsController, EventsController, ModalController, NetworkController, @@ -135,6 +136,15 @@ export class AppKitScaffold { return EventsController.subscribe(callback); } + public resolveReownName = async (name: string) => { + const wcNameAddress = await EnsController.resolveName(name); + const networkNameAddresses = wcNameAddress?.addresses + ? Object.values(wcNameAddress?.addresses) + : []; + + return networkNameAddresses[0]?.address || false; + }; + // -- Protected ---------------------------------------------------------------- protected setIsConnected: (typeof AccountController)['setIsConnected'] = isConnected => { AccountController.setIsConnected(isConnected); diff --git a/packages/scaffold/src/partials/w3m-input-address/index.tsx b/packages/scaffold/src/partials/w3m-input-address/index.tsx index 79a8404f1..40e67a8a2 100644 --- a/packages/scaffold/src/partials/w3m-input-address/index.tsx +++ b/packages/scaffold/src/partials/w3m-input-address/index.tsx @@ -1,8 +1,10 @@ +import { useState } from 'react'; import { TextInput } from 'react-native'; import { FlexView, useTheme } from '@reown/appkit-ui-react-native'; -import { SendController } from '@reown/appkit-core-react-native'; +import { ConnectionController, SendController } from '@reown/appkit-core-react-native'; + +import { useDebounceCallback } from '../../hooks/useDebounceCallback'; import styles from './styles'; -import { useState } from 'react'; export interface InputAddressProps { value?: string; @@ -12,11 +14,29 @@ export function InputAddress({ value }: InputAddressProps) { const Theme = useTheme(); const [inputValue, setInputValue] = useState(value); + const onSearch = async (search: string) => { + SendController.setLoading(true); + const address = await ConnectionController.getEnsAddress(search); + SendController.setLoading(false); + + if (address) { + SendController.setReceiverProfileName(search); + SendController.setReceiverAddress(address); + const avatar = await ConnectionController.getEnsAvatar(search); + SendController.setReceiverProfileImageUrl(avatar || undefined); + } else { + SendController.setReceiverAddress(search); + SendController.setReceiverProfileName(undefined); + SendController.setReceiverProfileImageUrl(undefined); + } + }; + + const onDebounceSearch = useDebounceCallback({ callback: onSearch, delay: 800 }); + const onInputChange = (address: string) => { setInputValue(address); SendController.setReceiverAddress(address); - - //TODO: Search ENS domain + onDebounceSearch(address); }; return ( @@ -45,6 +65,7 @@ export function InputAddress({ value }: InputAddressProps) { underlineColorAndroid="transparent" selectTextOnFocus={false} multiline + returnKeyLabel="Done" /> ); diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx b/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx index ceea64b03..5acc900e7 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-details.tsx @@ -12,6 +12,7 @@ import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; export interface PreviewSendDetailsProps { address?: string; + name?: string; caipNetwork?: CaipNetwork; networkFee?: number; style?: StyleProp; @@ -19,12 +20,20 @@ export interface PreviewSendDetailsProps { export function PreviewSendDetails({ address, + name, caipNetwork, networkFee, style }: PreviewSendDetailsProps) { const Theme = useTheme(); + const formattedName = UiUtil.getTruncateString({ + string: name ?? '', + charsStart: 20, + charsEnd: 0, + truncate: 'end' + }); + const formattedAddress = UiUtil.getTruncateString({ string: address ? address : '', charsStart: 6, @@ -52,7 +61,7 @@ export function PreviewSendDetails({ - Address + {formattedName ?? 'Address'} {formattedAddress} diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx index 0c0fd66ba..bb907f59a 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-preview-view/index.tsx @@ -15,7 +15,14 @@ import { PreviewSendDetails } from './components/preview-send-details'; export function WalletSendPreviewView() { const { padding } = useCustomDimensions(); const { caipNetwork } = useSnapshot(NetworkController.state); - const { token, receiverAddress, gasPriceInUSD, loading } = useSnapshot(SendController.state); + const { + token, + receiverAddress, + receiverProfileName, + receiverProfileImageUrl, + gasPriceInUSD, + loading + } = useSnapshot(SendController.state); const getSendValue = () => { if (SendController.state.token && SendController.state.sendTokenAmount) { @@ -36,17 +43,29 @@ export function WalletSendPreviewView() { return `${value} ${SendController.state.token?.symbol}`; }; - const formattedAddress = UiUtil.getTruncateString({ - string: receiverAddress ? receiverAddress : '', - charsStart: 4, - charsEnd: 4, - truncate: 'middle' - }); + const formattedAddress = receiverProfileName + ? UiUtil.getTruncateString({ + string: receiverProfileName, + charsStart: 20, + charsEnd: 0, + truncate: 'end' + }) + : UiUtil.getTruncateString({ + string: receiverAddress ? receiverAddress : '', + charsStart: 4, + charsEnd: 4, + truncate: 'middle' + }); const onSend = () => { SendController.sendToken(); }; + const onCancel = () => { + RouterController.goBack(); + SendController.setLoading(false); + }; + return ( @@ -79,13 +98,20 @@ export function WalletSendPreviewView() { To - + @@ -95,7 +121,7 @@ export function WalletSendPreviewView() { - - - What is an email wallet? - ); From 3bab13e3206e0a92d73014d437aafdfa4e2edfe0 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 30 Sep 2024 10:34:38 -0300 Subject: [PATCH 075/114] chore: header title change --- .../src/partials/w3m-header/index.tsx | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index 2a9a2dede..245f3b06c 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -1,23 +1,27 @@ +import { useSnapshot } from 'valtio'; import { RouterController, ModalController, - EventsController + EventsController, + type RouterControllerState } from '@reown/appkit-core-react-native'; import { IconLink, Text, FlexView } from '@reown/appkit-ui-react-native'; import { StringUtil } from '@reown/appkit-common-react-native'; export function Header() { + const { data, view } = useSnapshot(RouterController.state); const onHelpPress = () => { RouterController.push('WhatIsAWallet'); EventsController.sendEvent({ type: 'track', event: 'CLICK_WALLET_HELP' }); }; - const headings = () => { - const { data } = RouterController.state; - const connectorName = data?.connector?.name; - const walletName = data?.wallet?.name; - const networkName = data?.network?.name; - const socialName = undefined; + const headings = (_data: RouterControllerState['data'], _view: RouterControllerState['view']) => { + const connectorName = _data?.connector?.name; + const walletName = _data?.wallet?.name; + const networkName = _data?.network?.name; + const socialName = _data?.socialProvider + ? StringUtil.capitalize(_data?.socialProvider) + : undefined; return { Account: undefined, @@ -45,10 +49,10 @@ export function Header() { WalletSendSelectToken: 'Select token', WhatIsANetwork: 'What is a network?', WhatIsAWallet: 'What is a wallet?' - }; + }[_view]; }; - const header = headings()[RouterController.state.view]; + const header = headings(data, view); const dynamicButtonTemplate = () => { const hideBackViews = ['ConnectingSiwe']; From 1204ead13bf1797d47e6d3eee235171e2bc58232 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 30 Sep 2024 15:55:06 -0300 Subject: [PATCH 076/114] chore: added farcaster --- apps/gallery/utils/PresetUtils.ts | 3 + packages/common/src/utils/TypeUtil.ts | 9 ++- .../core/src/controllers/RouterController.ts | 2 + packages/core/src/utils/ConstantsUtil.ts | 2 +- packages/core/src/utils/TypeUtil.ts | 2 + .../scaffold/src/modal/w3m-router/index.tsx | 6 ++ .../src/partials/w3m-header/index.tsx | 2 + .../views/w3m-connect-socials-view/index.tsx | 41 ++++++++++ .../views/w3m-connect-socials-view/styles.ts | 12 +++ .../components/social-login-list.tsx | 11 ++- .../w3m-connecting-farcaster-view/index.tsx | 75 +++++++++++++++++++ .../w3m-connecting-farcaster-view/styles.ts | 15 ++++ packages/ui/src/assets/svg/Farcaster.tsx | 15 ++++ .../ui/src/assets/svg/FarcasterSquare.tsx | 15 ++++ packages/ui/src/components/wui-icon/index.tsx | 4 + .../ui/src/composites/wui-qr-code/index.tsx | 6 +- packages/ui/src/utils/TypesUtil.ts | 4 + packages/wallet/src/AppKitFrameConstants.ts | 6 ++ packages/wallet/src/AppKitFrameProvider.ts | 28 +++++++ packages/wallet/src/AppKitFrameSchema.ts | 46 +++++++++++- packages/wallet/src/AppKitFrameTypes.ts | 8 +- 21 files changed, 305 insertions(+), 7 deletions(-) create mode 100644 packages/scaffold/src/views/w3m-connect-socials-view/index.tsx create mode 100644 packages/scaffold/src/views/w3m-connect-socials-view/styles.ts create mode 100644 packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx create mode 100644 packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts create mode 100644 packages/ui/src/assets/svg/Farcaster.tsx create mode 100644 packages/ui/src/assets/svg/FarcasterSquare.tsx diff --git a/apps/gallery/utils/PresetUtils.ts b/apps/gallery/utils/PresetUtils.ts index 8c36e3aea..b792514e9 100644 --- a/apps/gallery/utils/PresetUtils.ts +++ b/apps/gallery/utils/PresetUtils.ts @@ -149,6 +149,8 @@ export const iconOptions: IconType[] = [ 'extension', 'externalLink', 'facebook', + 'farcaster', + 'farcasterSquare', 'filters', 'github', 'google', @@ -194,6 +196,7 @@ export const logoOptions: LogoType[] = [ 'apple', 'discord', 'facebook', + 'farcaster', 'github', 'google', 'more', diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index 1539688f1..ba09aaa26 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -86,4 +86,11 @@ export interface TransactionQuantity { numeric: string; } -export type SocialProvider = 'google' | 'github' | 'apple' | 'facebook' | 'x' | 'discord'; +export type SocialProvider = + | 'google' + | 'github' + | 'apple' + | 'facebook' + | 'x' + | 'discord' + | 'farcaster'; diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index adcdbbe41..7fbbd8513 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -18,9 +18,11 @@ export interface RouterControllerState { | 'AccountDefault' | 'AllWallets' | 'Connect' + | 'ConnectSocials' | 'ConnectingExternal' | 'ConnectingSiwe' | 'ConnectingSocial' + | 'ConnectingFarcaster' | 'ConnectingWalletConnect' | 'EmailVerifyDevice' | 'EmailVerifyOtp' diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index b3396ecea..a4f7ea25f 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -3,7 +3,7 @@ import type { Features } from './TypeUtil'; const defaultFeatures: Features = { email: true, emailShowWallets: true, - socials: ['x', 'discord', 'github', 'apple', 'facebook'] + socials: ['x', 'discord', 'github', 'apple', 'facebook', 'farcaster'] }; export const ConstantsUtil = { diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 18bf977cb..3837bd3ca 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -508,6 +508,8 @@ export interface AppKitFrameProvider { uri: string; }>; connectOtp(payload: { otp: string }): Promise; + connectFarcaster: () => Promise<{ username: string }>; + getFarcasterUri(): Promise<{ url: string }>; isConnected(): Promise<{ isConnected: boolean; }>; diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/scaffold/src/modal/w3m-router/index.tsx index 5ae5df623..325a07f3e 100644 --- a/packages/scaffold/src/modal/w3m-router/index.tsx +++ b/packages/scaffold/src/modal/w3m-router/index.tsx @@ -29,6 +29,8 @@ import { WhatIsANetworkView } from '../../views/w3m-what-is-a-network-view'; import { WhatIsAWalletView } from '../../views/w3m-what-is-a-wallet-view'; import { UiUtil } from '../../utils/UiUtil'; +import { ConnectingFarcasterView } from '../../views/w3m-connecting-farcaster-view'; +import { ConnectSocialsView } from '../../views/w3m-connect-socials-view'; export function AppKitRouter() { const { view } = useSnapshot(RouterController.state); @@ -47,12 +49,16 @@ export function AppKitRouter() { return AllWalletsView; case 'Connect': return ConnectView; + case 'ConnectSocials': + return ConnectSocialsView; case 'ConnectingExternal': return ConnectingExternalView; case 'ConnectingSiwe': return ConnectingSiweView; case 'ConnectingSocial': return ConnectingSocialView; + case 'ConnectingFarcaster': + return ConnectingFarcasterView; case 'ConnectingWalletConnect': return ConnectingView; case 'EmailVerifyDevice': diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index 245f3b06c..49cddef65 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -28,8 +28,10 @@ export function Header() { AccountDefault: undefined, AllWallets: 'All wallets', Connect: 'Connect wallet', + ConnectSocials: 'All socials', ConnectingExternal: connectorName ?? 'Connect wallet', ConnectingSiwe: 'Sign In', + ConnectingFarcaster: socialName ?? 'Connecting Social', ConnectingSocial: socialName ?? 'Connecting Social', ConnectingWalletConnect: walletName ?? 'WalletConnect', EmailVerifyDevice: ' ', diff --git a/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx b/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx new file mode 100644 index 000000000..d5aecad29 --- /dev/null +++ b/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx @@ -0,0 +1,41 @@ +import { useSnapshot } from 'valtio'; +import { ScrollView } from 'react-native'; +import { StringUtil, type SocialProvider } from '@reown/appkit-common-react-native'; +import { OptionsController, RouterController } from '@reown/appkit-core-react-native'; +import { FlexView, ListSocial, Text } from '@reown/appkit-ui-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function ConnectSocialsView() { + const { features } = useSnapshot(OptionsController.state); + const { padding } = useCustomDimensions(); + const socialProviders = (features?.socials ?? []) as SocialProvider[]; + + const onItemPress = (provider: SocialProvider) => { + if (provider === 'farcaster') { + RouterController.push('ConnectingFarcaster', { socialProvider: provider }); + } else { + RouterController.push('ConnectingSocial', { socialProvider: provider }); + } + }; + + return ( + + + {socialProviders.map(provider => ( + onItemPress(provider)} + style={styles.item} + > + + {StringUtil.capitalize(provider)} + + + ))} + + + ); +} diff --git a/packages/scaffold/src/views/w3m-connect-socials-view/styles.ts b/packages/scaffold/src/views/w3m-connect-socials-view/styles.ts new file mode 100644 index 000000000..be837b83c --- /dev/null +++ b/packages/scaffold/src/views/w3m-connect-socials-view/styles.ts @@ -0,0 +1,12 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + item: { + marginVertical: Spacing['3xs'], + justifyContent: 'flex-start' + }, + text: { + marginLeft: Spacing.s + } +}); diff --git a/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx index bf9c0189d..8af95e8f1 100644 --- a/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx @@ -18,7 +18,15 @@ export function SocialLoginList({ options, disabled }: SocialLoginListProps) { bottomSocials = showMoreButton ? bottomSocials.slice(0, MAX_OPTIONS - 2) : bottomSocials; const onItemPress = (social: SocialProvider) => { - RouterController.push('ConnectingSocial', { socialProvider: social }); + if (social === 'farcaster') { + RouterController.push('ConnectingFarcaster', { socialProvider: social }); + } else { + RouterController.push('ConnectingSocial', { socialProvider: social }); + } + }; + + const onMorePress = () => { + RouterController.push('ConnectSocials'); }; return ( @@ -49,6 +57,7 @@ export function SocialLoginList({ options, disabled }: SocialLoginListProps) { logo="more" disabled={disabled} style={[styles.socialItem, styles.socialItemLast]} + onPress={onMorePress} /> )} diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx new file mode 100644 index 000000000..9f4dc02ee --- /dev/null +++ b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx @@ -0,0 +1,75 @@ +import { Linking } from 'react-native'; +import { useCallback, useEffect, useState } from 'react'; +import { + ConnectionController, + ConnectorController, + ModalController, + RouterController, + SnackController, + WebviewController, + type AppKitFrameProvider +} from '@reown/appkit-core-react-native'; +import { FlexView, LoadingThumbnail, IconBox, Logo, Text } from '@reown/appkit-ui-react-native'; +import { StringUtil } from '@reown/appkit-common-react-native'; + +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import styles from './styles'; + +export function ConnectingFarcasterView() { + const { data } = RouterController.state; + const { maxWidth: width } = useCustomDimensions(); + const authConnector = ConnectorController.getAuthConnector(); + const [error, setError] = useState(false); + const socialProvider = data?.socialProvider; + const provider = authConnector?.provider as AppKitFrameProvider; + + const onConnect = useCallback(async () => { + try { + if (!WebviewController.state.connecting && provider && socialProvider && authConnector) { + setError(false); + const { url } = await provider.getFarcasterUri(); + await Linking.openURL(url); + await provider.connectFarcaster(); + await ConnectionController.connectExternal(authConnector); + WebviewController.setConnecting(false); + ModalController.close(); + } + } catch (e) { + SnackController.showError('Something went wrong'); + setError(true); + } + }, [provider, socialProvider, authConnector]); + + useEffect(() => { + onConnect(); + }, [onConnect]); + + return ( + + <> + + + {error && ( + + )} + + + {`Continue with ${StringUtil.capitalize(socialProvider)}`} + + + + ); +} diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts b/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts new file mode 100644 index 000000000..dcb555e83 --- /dev/null +++ b/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts @@ -0,0 +1,15 @@ +import { StyleSheet } from 'react-native'; +import { Spacing } from '@reown/appkit-ui-react-native'; + +export default StyleSheet.create({ + errorIcon: { + position: 'absolute', + bottom: 8, + right: 8, + zIndex: 2 + }, + continueText: { + marginTop: Spacing.m, + marginBottom: Spacing.xs + } +}); diff --git a/packages/ui/src/assets/svg/Farcaster.tsx b/packages/ui/src/assets/svg/Farcaster.tsx new file mode 100644 index 000000000..323478cca --- /dev/null +++ b/packages/ui/src/assets/svg/Farcaster.tsx @@ -0,0 +1,15 @@ +import Svg, { Path, type SvgProps, Rect } from 'react-native-svg'; +const SvgFarcaster = (props: SvgProps) => ( + + + + + +); +export default SvgFarcaster; diff --git a/packages/ui/src/assets/svg/FarcasterSquare.tsx b/packages/ui/src/assets/svg/FarcasterSquare.tsx new file mode 100644 index 000000000..e6b7062b0 --- /dev/null +++ b/packages/ui/src/assets/svg/FarcasterSquare.tsx @@ -0,0 +1,15 @@ +import Svg, { Path, type SvgProps, Rect } from 'react-native-svg'; +const SvgFarcaster = (props: SvgProps) => ( + + + + + +); +export default SvgFarcaster; diff --git a/packages/ui/src/components/wui-icon/index.tsx b/packages/ui/src/components/wui-icon/index.tsx index 11540ab21..7b1656e9a 100644 --- a/packages/ui/src/components/wui-icon/index.tsx +++ b/packages/ui/src/components/wui-icon/index.tsx @@ -30,6 +30,8 @@ import EtherscanSvg from '../../assets/svg/Etherscan'; import ExtensionSvg from '../../assets/svg/Extension'; import ExternalLinkSvg from '../../assets/svg/ExternalLink'; import FacebookSvg from '../../assets/svg/Facebook'; +import FarcasterSvg from '../../assets/svg/Farcaster'; +import FarcasterSquareSvg from '../../assets/svg/FarcasterSquare'; import FiltersSvg from '../../assets/svg/Filters'; import GithubSvg from '../../assets/svg/Github'; import GoogleSvg from '../../assets/svg/Google'; @@ -88,6 +90,8 @@ const svgOptions: Record JSX.Element> = { extension: ExtensionSvg, externalLink: ExternalLinkSvg, facebook: FacebookSvg, + farcaster: FarcasterSvg, + farcasterSquare: FarcasterSquareSvg, filters: FiltersSvg, github: GithubSvg, google: GoogleSvg, diff --git a/packages/ui/src/composites/wui-qr-code/index.tsx b/packages/ui/src/composites/wui-qr-code/index.tsx index f65ee57ff..8f49da5e2 100644 --- a/packages/ui/src/composites/wui-qr-code/index.tsx +++ b/packages/ui/src/composites/wui-qr-code/index.tsx @@ -8,18 +8,20 @@ import { Text } from '../../components/wui-text'; import { FlexView } from '../../layout/wui-flex'; import { QRCodeUtil } from '../../utils/QRCodeUtil'; import { BorderRadius, LightTheme, Spacing } from '../../utils/ThemeUtil'; +import type { IconType } from '../../utils/TypesUtil'; import styles from './styles'; export interface QrCodeProps { size: number; uri?: string; imageSrc?: string; + icon?: IconType; testID?: string; arenaClear?: boolean; style?: StyleProp; } -export function QrCode({ size, uri, imageSrc, testID, arenaClear, style }: QrCodeProps) { +export function QrCode({ size, uri, imageSrc, testID, arenaClear, icon, style }: QrCodeProps) { const Theme = LightTheme; const containerPadding = Spacing.l; const qrSize = size - containerPadding * 2; @@ -49,7 +51,7 @@ export function QrCode({ size, uri, imageSrc, testID, arenaClear, style }: QrCod return ( ({ + type: AppKitFrameConstants.APP_GET_FARCASTER_URI + } as AppKitFrameTypes.AppEvent); + + return response; + } catch (error) { + throw error; + } + } + + public async connectFarcaster() { + try { + const response = await this.appEvent<'ConnectFarcaster'>({ + type: AppKitFrameConstants.APP_CONNECT_FARCASTER + } as AppKitFrameTypes.AppEvent); + + if (response.userName) { + this.setSocialLoginSuccess(response.userName); + } + + return response; + } catch (error) { + throw error; + } + } + public async connectOtp(payload: AppKitFrameTypes.Requests['AppConnectOtpRequest']) { await this.webviewLoadPromise; diff --git a/packages/wallet/src/AppKitFrameSchema.ts b/packages/wallet/src/AppKitFrameSchema.ts index f66e4c066..70c15c36c 100644 --- a/packages/wallet/src/AppKitFrameSchema.ts +++ b/packages/wallet/src/AppKitFrameSchema.ts @@ -34,7 +34,7 @@ export const AppConnectEmailRequest = z.object({ email: z.string().email() }); export const AppConnectOtpRequest = z.object({ otp: z.string() }); export const AppConnectSocialRequest = z.object({ uri: z.string() }); export const AppGetSocialRedirectUriRequest = z.object({ - provider: z.enum(['google', 'github', 'apple', 'facebook', 'x', 'discord']) + provider: z.enum(['google', 'github', 'apple', 'facebook', 'x', 'discord', 'farcaster']) }); export const AppGetUserRequest = z.object({ chainId: z.optional(z.string().or(z.number())) }); export const AppUpdateEmailRequest = z.object({ email: z.string().email() }); @@ -98,6 +98,14 @@ export const FrameConnectSocialResponse = z.object({ userName: z.string().optional() }); +export const FrameGetFarcasterUriResponse = z.object({ + url: z.string() +}); + +export const FrameConnectFarcasterResponse = z.object({ + userName: z.string() +}); + export const RpcResponse = z.any(); export const RpcEthAccountsRequest = z.object({ @@ -316,6 +324,10 @@ export const AppKitFrameSchema = { }) ) + .or(EventSchema.extend({ type: zType('APP_GET_FARCASTER_URI') })) + + .or(EventSchema.extend({ type: zType('APP_CONNECT_FARCASTER') })) + .or(EventSchema.extend({ type: zType('APP_SIGN_OUT') })) .or(EventSchema.extend({ type: zType('APP_IS_CONNECTED'), payload: z.optional(FrameSession) })) @@ -450,6 +462,38 @@ export const AppKitFrameSchema = { }) ) + .or( + EventSchema.extend({ + type: zType('FRAME_GET_FARCASTER_URI_SUCCESS'), + payload: FrameGetFarcasterUriResponse, + origin: z.string() + }) + ) + + .or( + EventSchema.extend({ + type: zType('FRAME_GET_FARCASTER_URI_ERROR'), + payload: zError, + origin: z.string() + }) + ) + + .or( + EventSchema.extend({ + type: zType('FRAME_CONNECT_FARCASTER_SUCCESS'), + payload: FrameConnectFarcasterResponse, + origin: z.string() + }) + ) + + .or( + EventSchema.extend({ + type: zType('FRAME_CONNECT_FARCASTER_ERROR'), + payload: zError, + origin: z.string() + }) + ) + .or(EventSchema.extend({ type: zType('FRAME_CONNECT_DEVICE_SUCCESS'), origin: z.string() })) .or( diff --git a/packages/wallet/src/AppKitFrameTypes.ts b/packages/wallet/src/AppKitFrameTypes.ts index 79a74cf7a..c4e4104e3 100644 --- a/packages/wallet/src/AppKitFrameTypes.ts +++ b/packages/wallet/src/AppKitFrameTypes.ts @@ -59,7 +59,9 @@ import { FrameGetSocialRedirectUriResponse, FrameConnectSocialResponse, AppGetSocialRedirectUriRequest, - AppConnectSocialRequest + AppConnectSocialRequest, + FrameGetFarcasterUriResponse, + FrameConnectFarcasterResponse } from './AppKitFrameSchema'; export namespace AppKitFrameTypes { @@ -91,6 +93,8 @@ export namespace AppKitFrameTypes { FrameConnectOtpResponse: undefined; FrameGetSocialRedirectUriResponse: z.infer; FrameConnectSocialResponse: z.infer; + FrameGetFarcasterUriResponse: z.infer; + FrameConnectFarcasterResponse: z.infer; FrameSyncThemeResponse: undefined; FrameSyncDappDataResponse: undefined; FrameUpdateEmailPrimaryOtpResponse: undefined; @@ -159,8 +163,10 @@ export namespace AppKitFrameTypes { | 'GetUser' | 'ConnectDevice' | 'ConnectEmail' + | 'ConnectFarcaster' | 'ConnectSocial' | 'ConnectOtp' + | 'GetFarcasterUri' | 'GetSocialRedirectUri' | 'SwitchNetwork' | 'UpdateEmail' From c4a60d2ab7e1a7e59123cb7fa7e762ccbedd240f Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 30 Sep 2024 18:27:06 -0300 Subject: [PATCH 077/114] chore: show username in account view --- .../controllers/OptionsController.test.ts | 5 +- .../src/controllers/ConnectionController.ts | 12 ++++ .../core/src/controllers/WebviewController.ts | 9 ++- packages/core/src/utils/StorageUtil.ts | 30 +++++++++ packages/core/src/utils/TypeUtil.ts | 2 +- packages/scaffold/src/client.ts | 6 ++ .../components/auth-buttons.tsx | 61 +++++++++++++++++++ .../views/w3m-account-default-view/index.tsx | 43 +++++++------ .../w3m-connecting-farcaster-view/index.tsx | 1 + .../w3m-connecting-social-view/index.tsx | 3 + .../src/composites/wui-list-social/index.tsx | 27 +++++++- .../src/composites/wui-list-social/styles.ts | 6 ++ packages/wallet/src/AppKitFrameProvider.ts | 30 ++++++--- packages/wallet/src/AppKitWebview.tsx | 4 +- 14 files changed, 203 insertions(+), 36 deletions(-) create mode 100644 packages/scaffold/src/views/w3m-account-default-view/components/auth-buttons.tsx diff --git a/packages/core/src/__tests__/controllers/OptionsController.test.ts b/packages/core/src/__tests__/controllers/OptionsController.test.ts index cdcba49d6..712d52c65 100644 --- a/packages/core/src/__tests__/controllers/OptionsController.test.ts +++ b/packages/core/src/__tests__/controllers/OptionsController.test.ts @@ -1,4 +1,4 @@ -import { OptionsController } from '../../index'; +import { ConstantsUtil, OptionsController } from '../../index'; const MOCK_WALLET_IDS = [ '1ae92b26df02f0abca6304df07debccd18262fdf5fe82daa81593582dac9a369', @@ -15,7 +15,8 @@ describe('OptionsController', () => { expect(OptionsController.state).toEqual({ projectId: '', sdkType: 'appkit', - sdkVersion: 'react-native-wagmi-undefined' + sdkVersion: 'react-native-wagmi-undefined', + features: ConstantsUtil.DEFAULT_FEATURES }); }); diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index 62faa4539..18255554a 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -10,6 +10,7 @@ import type { } from '../utils/TypeUtil'; import { RouterController } from './RouterController'; import { ConnectorController } from './ConnectorController'; +import type { SocialProvider } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // export interface ConnectExternalOptions { @@ -48,6 +49,7 @@ export interface ConnectionControllerState { pressedWallet?: WcWallet; recentWallets?: WcWallet[]; connectedWalletImageUrl?: string; + connectedSocialProvider?: SocialProvider; } type StateKey = keyof ConnectionControllerState; @@ -133,6 +135,16 @@ export const ConnectionController = { } }, + setConnectedSocialProvider(provider: ConnectionControllerState['connectedSocialProvider']) { + state.connectedSocialProvider = provider; + + if (provider) { + StorageUtil.setConnectedSocialProvider(provider); + } else { + StorageUtil.removeConnectedSocialProvider(); + } + }, + parseUnits(value: string, decimals: number) { return this._getClient().parseUnits(value, decimals); }, diff --git a/packages/core/src/controllers/WebviewController.ts b/packages/core/src/controllers/WebviewController.ts index 0c3194899..f11cf7f66 100644 --- a/packages/core/src/controllers/WebviewController.ts +++ b/packages/core/src/controllers/WebviewController.ts @@ -1,3 +1,4 @@ +import type { SocialProvider } from '@reown/appkit-common-react-native'; import { proxy, subscribe as sub } from 'valtio'; // -- Types --------------------------------------------- // @@ -6,13 +7,15 @@ export interface WebviewControllerState { webviewVisible: boolean; webviewUrl?: string; connecting?: boolean; + connectingProvider?: SocialProvider; } // -- State --------------------------------------------- // const state = proxy({ frameViewVisible: false, webviewVisible: false, - connecting: false + connecting: false, + connectingProvider: undefined }); // -- Controller ---------------------------------------- // @@ -37,5 +40,9 @@ export const WebviewController = { setConnecting(connecting: WebviewControllerState['connecting']) { state.connecting = connecting; + }, + + setConnectingProvider(provider: WebviewControllerState['connectingProvider']) { + state.connectingProvider = provider; } }; diff --git a/packages/core/src/utils/StorageUtil.ts b/packages/core/src/utils/StorageUtil.ts index 36ae23a69..6bf3218d2 100644 --- a/packages/core/src/utils/StorageUtil.ts +++ b/packages/core/src/utils/StorageUtil.ts @@ -1,12 +1,14 @@ /* eslint-disable no-console */ import AsyncStorage from '@react-native-async-storage/async-storage'; import type { ConnectorType, WcWallet } from './TypeUtil'; +import type { SocialProvider } from '@reown/appkit-common-react-native'; // -- Helpers ----------------------------------------------------------------- const WC_DEEPLINK = 'WALLETCONNECT_DEEPLINK_CHOICE'; const RECENT_WALLET = '@w3m/recent'; const CONNECTED_WALLET_IMAGE_URL = '@w3m/connected_wallet_image_url'; const CONNECTED_CONNECTOR = '@w3m/connected_connector'; +const CONNECTED_SOCIAL = '@appkit/connected_social'; // -- Utility ----------------------------------------------------------------- export const StorageUtil = { @@ -134,5 +136,33 @@ export const StorageUtil = { } catch { console.info('Unable to remove Connected Wallet Image URL'); } + }, + + async setConnectedSocialProvider(provider: SocialProvider) { + try { + await AsyncStorage.setItem(CONNECTED_SOCIAL, JSON.stringify(provider)); + } catch { + console.info('Unable to set Connected Social Provider'); + } + }, + + async getConnectedSocialProvider() { + try { + const provider = (await AsyncStorage.getItem(CONNECTED_SOCIAL)) as SocialProvider; + + return provider ? JSON.parse(provider) : undefined; + } catch { + console.info('Unable to get Connected Social Provider'); + } + + return undefined; + }, + + async removeConnectedSocialProvider() { + try { + await AsyncStorage.removeItem(CONNECTED_SOCIAL); + } catch { + console.info('Unable to remove Connected Social Provider'); + } } }; diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 3837bd3ca..84d96f1f3 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -485,8 +485,8 @@ export interface AppKitFrameProvider { getSecureSiteDashboardURL(): string; getSecureSiteIconURL(): string; getSecureSiteHeaders(): Record; - getLoginEmailUsed(): Promise; getEmail(): string | undefined; + getUsername(): string | undefined; rejectRpcRequest(): void; connectEmail(payload: { email: string }): Promise<{ action: 'VERIFY_DEVICE' | 'VERIFY_OTP'; diff --git a/packages/scaffold/src/client.ts b/packages/scaffold/src/client.ts index e51a234ed..0878c85df 100644 --- a/packages/scaffold/src/client.ts +++ b/packages/scaffold/src/client.ts @@ -315,8 +315,14 @@ export class AppKitScaffold { } } + private async initSocial() { + const connectedSocialProvider = await StorageUtil.getConnectedSocialProvider(); + ConnectionController.setConnectedSocialProvider(connectedSocialProvider); + } + private async initAsyncValues(options: ScaffoldOptions) { await this.initConnectedConnector(); await this.initRecentWallets(options); + await this.initSocial(); } } diff --git a/packages/scaffold/src/views/w3m-account-default-view/components/auth-buttons.tsx b/packages/scaffold/src/views/w3m-account-default-view/components/auth-buttons.tsx new file mode 100644 index 000000000..7a1d903b2 --- /dev/null +++ b/packages/scaffold/src/views/w3m-account-default-view/components/auth-buttons.tsx @@ -0,0 +1,61 @@ +import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; +import { UpgradeWalletButton } from './upgrade-wallet-button'; +import { ListItem, ListSocial, Spacing, Text } from '@reown/appkit-ui-react-native'; +import type { SocialProvider } from '@reown/appkit-common-react-native'; + +export interface AuthButtonsProps { + onUpgradePress: () => void; + onPress: () => void; + socialProvider?: SocialProvider; + text: string; + style?: StyleProp; +} + +export function AuthButtons({ + onUpgradePress, + onPress, + socialProvider, + text, + style +}: AuthButtonsProps) { + return ( + <> + + {socialProvider ? ( + + + {text} + + + ) : ( + + + {text} + + + )} + + ); +} + +const styles = StyleSheet.create({ + actionButton: { + marginBottom: Spacing.xs + }, + upgradeButton: { + marginBottom: Spacing.s + }, + socialContainer: { + justifyContent: 'flex-start', + width: '100%' + }, + socialText: { + flex: 1, + marginLeft: Spacing.s + } +}); diff --git a/packages/scaffold/src/views/w3m-account-default-view/index.tsx b/packages/scaffold/src/views/w3m-account-default-view/index.tsx index 1dcee8bc3..b3e6805dc 100644 --- a/packages/scaffold/src/views/w3m-account-default-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-default-view/index.tsx @@ -27,8 +27,9 @@ import { ListItem } from '@reown/appkit-ui-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import { UpgradeWalletButton } from './components/upgrade-wallet-button'; + import styles from './styles'; +import { AuthButtons } from './components/auth-buttons'; export function AccountDefaultView() { const { address, profileName, profileImage, balance, balanceSymbol, addressExplorerUrl } = @@ -36,6 +37,7 @@ export function AccountDefaultView() { const [disconnecting, setDisconnecting] = useState(false); const { caipNetwork } = useSnapshot(NetworkController.state); const { connectedConnector } = useSnapshot(ConnectorController.state); + const { connectedSocialProvider } = useSnapshot(ConnectionController.state); const { history } = useSnapshot(RouterController.state); const networkImage = AssetUtil.getNetworkImage(caipNetwork); const showCopy = OptionsController.isClipboardAvailable(); @@ -71,6 +73,13 @@ export function AccountDefaultView() { return provider.getEmail(); }; + const getUsername = () => { + const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + if (!provider) return ''; + + return provider.getUsername(); + }; + const onExplorerPress = () => { if (AccountController.state.addressExplorerUrl) { Linking.openURL(AccountController.state.addressExplorerUrl); @@ -108,6 +117,7 @@ export function AccountDefaultView() { }; const onEmailPress = () => { + if (ConnectionController.state.connectedSocialProvider) return; RouterController.push('UpdateEmailWallet', { email: getUserEmail() }); }; @@ -165,25 +175,18 @@ export function AccountDefaultView() { )} {isAuth && ( - <> - - - - {UiUtil.getTruncateString({ - string: getUserEmail() || '', - charsStart: 30, - charsEnd: 0, - truncate: 'end' - })} - - - + )} void; style?: StyleProp; testID?: string; + logoWidth?: number; + logoHeight?: number; } -export function ListSocial({ logo, children, disabled, onPress, style, testID }: ListSocialProps) { +export function ListSocial({ + logo, + children, + disabled, + onPress, + style, + testID, + logoHeight = 40, + logoWidth = 40 +}: ListSocialProps) { const Theme = useTheme(); const { animatedValue, setStartValue, setEndValue } = useAnimatedValue( Theme['gray-glass-002'], @@ -34,7 +45,19 @@ export function ListSocial({ logo, children, disabled, onPress, style, testID }: onPressOut={setStartValue} testID={testID} > - + + + {children} diff --git a/packages/ui/src/composites/wui-list-social/styles.ts b/packages/ui/src/composites/wui-list-social/styles.ts index 46eaad088..83d6f63c3 100644 --- a/packages/ui/src/composites/wui-list-social/styles.ts +++ b/packages/ui/src/composites/wui-list-social/styles.ts @@ -18,5 +18,11 @@ export default StyleSheet.create({ }, disabledLogo: { opacity: 0.4 + }, + border: { + borderRadius: BorderRadius.full, + borderWidth: 2, + alignItems: 'center', + justifyContent: 'center' } }); diff --git a/packages/wallet/src/AppKitFrameProvider.ts b/packages/wallet/src/AppKitFrameProvider.ts index d7f2ea32e..5ec259a74 100644 --- a/packages/wallet/src/AppKitFrameProvider.ts +++ b/packages/wallet/src/AppKitFrameProvider.ts @@ -20,6 +20,8 @@ export class AppKitFrameProvider { private email: string | undefined; + private username: string | undefined; + private rpcRequestHandler?: (request: AppKitFrameTypes.RPCRequest) => void; private rpcSuccessHandler?: ( response: AppKitFrameTypes.RPCResponse, @@ -56,6 +58,10 @@ export class AppKitFrameProvider { this.getAsyncEmail().then(email => { this.email = email; }); + + this.getAsyncUsername().then(username => { + this.username = username; + }); } public setWebviewRef(webviewRef: RefObject) { @@ -92,16 +98,14 @@ export class AppKitFrameProvider { return { 'X-Bundle-Id': CoreHelperUtil.getBundleId() }; } - public async getLoginEmailUsed() { - const email = await AppKitFrameStorage.get(AppKitFrameConstants.EMAIL_LOGIN_USED_KEY); - - return Boolean(email); - } - public getEmail() { return this.email; } + public getUsername() { + return this.username; + } + public rejectRpcRequest() { try { this.openRpcRequests.forEach(({ abortController, method }) => { @@ -202,7 +206,7 @@ export class AppKitFrameProvider { } as AppKitFrameTypes.AppEvent); if (!response.isConnected) { - this.deleteEmailLoginCache(); + this.deleteLoginCache(); } return response; @@ -335,7 +339,7 @@ export class AppKitFrameProvider { type: AppKitFrameConstants.APP_SIGN_OUT }); - this.deleteEmailLoginCache(); + this.deleteLoginCache(); return response; } @@ -411,6 +415,7 @@ export class AppKitFrameProvider { private setSocialLoginSuccess(username: string) { AppKitFrameStorage.set(AppKitFrameConstants.SOCIAL_USERNAME, username); + this.username = username; } private setEmailLoginSuccess(email: string) { @@ -420,12 +425,13 @@ export class AppKitFrameProvider { this.email = email; } - private deleteEmailLoginCache() { + private deleteLoginCache() { AppKitFrameStorage.delete(AppKitFrameConstants.EMAIL_LOGIN_USED_KEY); AppKitFrameStorage.delete(AppKitFrameConstants.EMAIL); AppKitFrameStorage.delete(AppKitFrameConstants.LAST_USED_CHAIN_KEY); AppKitFrameStorage.delete(AppKitFrameConstants.SOCIAL_USERNAME); this.email = undefined; + this.username = undefined; } private setLastUsedChainId(chainId: number) { @@ -545,4 +551,10 @@ export class AppKitFrameProvider { return email; } + + private async getAsyncUsername() { + const username = await AppKitFrameStorage.get(AppKitFrameConstants.SOCIAL_USERNAME); + + return username; + } } diff --git a/packages/wallet/src/AppKitWebview.tsx b/packages/wallet/src/AppKitWebview.tsx index 208967de7..e6e6046a6 100644 --- a/packages/wallet/src/AppKitWebview.tsx +++ b/packages/wallet/src/AppKitWebview.tsx @@ -20,7 +20,7 @@ export function AppKitWebview() { const webviewRef = useRef(null); const Theme = useTheme(); const authConnector = ConnectorController.getAuthConnector(); - const { webviewVisible, webviewUrl } = useSnapshot(WebviewController.state); + const { webviewVisible, webviewUrl, connectingProvider } = useSnapshot(WebviewController.state); const [isBackdropVisible, setIsBackdropVisible] = useState(false); const animatedHeight = useRef(new Animated.Value(0)); const backdropOpacity = useRef(new Animated.Value(0)); @@ -35,6 +35,7 @@ export function AppKitWebview() { const onClose = () => { WebviewController.setWebviewVisible(false); WebviewController.setConnecting(false); + WebviewController.setConnectingProvider(undefined); RouterController.goBack(); }; @@ -105,6 +106,7 @@ export function AppKitWebview() { const parsedUrl = new URL(navState.url); await provider?.connectSocial(parsedUrl.search); await ConnectionController.connectExternal(authConnector); + ConnectionController.setConnectedSocialProvider(connectingProvider); WebviewController.setConnecting(false); ModalController.close(); } From 69e5b86c17ddf1d0903c29af6f29fa5135e5a7d7 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:03:28 -0300 Subject: [PATCH 078/114] chore: changes for farcaster login --- .../w3m-connecting-farcaster-view/index.tsx | 36 ++++++++++++++++--- .../w3m-connecting-farcaster-view/styles.ts | 3 ++ packages/wallet/src/AppKitFrameProvider.ts | 7 ++-- packages/wallet/src/AppKitFrameSchema.ts | 4 +-- packages/wallet/src/AppKitWebview.tsx | 10 ++++-- 5 files changed, 50 insertions(+), 10 deletions(-) diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx index 0196d3bda..6a9ade1bb 100644 --- a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx @@ -4,13 +4,20 @@ import { ConnectionController, ConnectorController, ModalController, + OptionsController, RouterController, SnackController, WebviewController, type AppKitFrameProvider } from '@reown/appkit-core-react-native'; -import { FlexView, LoadingThumbnail, IconBox, Logo, Text } from '@reown/appkit-ui-react-native'; -import { StringUtil } from '@reown/appkit-common-react-native'; +import { + FlexView, + LoadingThumbnail, + IconBox, + Logo, + Text, + Link +} from '@reown/appkit-ui-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; @@ -20,6 +27,8 @@ export function ConnectingFarcasterView() { const { maxWidth: width } = useCustomDimensions(); const authConnector = ConnectorController.getAuthConnector(); const [error, setError] = useState(false); + const [_url, setUrl] = useState(); + const showCopy = OptionsController.isClipboardAvailable(); const socialProvider = data?.socialProvider; const provider = authConnector?.provider as AppKitFrameProvider; @@ -28,7 +37,8 @@ export function ConnectingFarcasterView() { if (!WebviewController.state.connecting && provider && socialProvider && authConnector) { setError(false); const { url } = await provider.getFarcasterUri(); - await Linking.openURL(url); + setUrl(url); + Linking.openURL(url); await provider.connectFarcaster(); await ConnectionController.connectExternal(authConnector); ConnectionController.setConnectedSocialProvider(socialProvider); @@ -41,6 +51,13 @@ export function ConnectingFarcasterView() { } }, [provider, socialProvider, authConnector]); + const onCopyUrl = () => { + if (_url) { + OptionsController.copyToClipboard(_url); + SnackController.showSuccess('Link copied'); + } + }; + useEffect(() => { onConnect(); }, [onConnect]); @@ -68,8 +85,19 @@ export function ConnectingFarcasterView() { )} - {`Continue with ${StringUtil.capitalize(socialProvider)}`} + Continue in Farcaster + {showCopy && ( + + Copy link + + )} ); diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts b/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts index dcb555e83..542a8ad28 100644 --- a/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts +++ b/packages/scaffold/src/views/w3m-connecting-farcaster-view/styles.ts @@ -11,5 +11,8 @@ export default StyleSheet.create({ continueText: { marginTop: Spacing.m, marginBottom: Spacing.xs + }, + copyButton: { + marginTop: Spacing.m } }); diff --git a/packages/wallet/src/AppKitFrameProvider.ts b/packages/wallet/src/AppKitFrameProvider.ts index 5ec259a74..1f7f99f18 100644 --- a/packages/wallet/src/AppKitFrameProvider.ts +++ b/packages/wallet/src/AppKitFrameProvider.ts @@ -313,8 +313,11 @@ export class AppKitFrameProvider { payload: { ...payload, chainId } } as AppKitFrameTypes.AppEvent); - this.setEmailLoginSuccess(response.email); - this.setLastUsedChainId(response.chainId); + if (response.email) { + this.setEmailLoginSuccess(response.email); + } + + this.setLastUsedChainId(Number(response.chainId)); return response; } diff --git a/packages/wallet/src/AppKitFrameSchema.ts b/packages/wallet/src/AppKitFrameSchema.ts index 70c15c36c..329a26c75 100644 --- a/packages/wallet/src/AppKitFrameSchema.ts +++ b/packages/wallet/src/AppKitFrameSchema.ts @@ -70,9 +70,9 @@ export const FrameUpdateEmailResponse = z.object({ action: z.enum(['VERIFY_PRIMARY_OTP', 'VERIFY_SECONDARY_OTP']) }); export const FrameGetUserResponse = z.object({ - email: z.string().email(), + email: z.string().email().optional().nullable(), address: z.string(), - chainId: z.number() + chainId: z.string().or(z.number()) }); export const FrameIsConnectedResponse = z.object({ isConnected: z.boolean() }); export const FrameGetChainIdResponse = z.object({ chainId: z.number() }); diff --git a/packages/wallet/src/AppKitWebview.tsx b/packages/wallet/src/AppKitWebview.tsx index e6e6046a6..4bc73b600 100644 --- a/packages/wallet/src/AppKitWebview.tsx +++ b/packages/wallet/src/AppKitWebview.tsx @@ -90,6 +90,9 @@ export function AppKitWebview() { onPress={onClose} testID="button-close" style={styles.closeButton} + iconColor="inverse-100" + backgroundColor="gray-glass-030" + pressedColor="gray-glass-080" /> Date: Wed, 2 Oct 2024 10:54:10 -0300 Subject: [PATCH 079/114] chore: added condition in webview so connect is called once --- packages/wallet/src/AppKitWebview.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/wallet/src/AppKitWebview.tsx b/packages/wallet/src/AppKitWebview.tsx index 4bc73b600..fd74e8291 100644 --- a/packages/wallet/src/AppKitWebview.tsx +++ b/packages/wallet/src/AppKitWebview.tsx @@ -104,7 +104,7 @@ export function AppKitWebview() { ref={webviewRef} onNavigationStateChange={async navState => { try { - if (authConnector && navState.url.includes('/sdk/oauth')) { + if (authConnector && webviewVisible && navState.url.includes('/sdk/oauth')) { WebviewController.setWebviewVisible(false); const parsedUrl = new URL(navState.url); await provider?.connectSocial(parsedUrl.search); From 1dcbd2f0db92b4578a8711068cbe73948fc1371e Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Fri, 4 Oct 2024 12:05:26 -0300 Subject: [PATCH 080/114] chore: clean social provider on disconnect --- packages/core/src/controllers/ConnectionController.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index 18255554a..a7b2d1b61 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -180,10 +180,12 @@ export const ConnectionController = { this.clearUri(); state.pressedWallet = undefined; state.connectedWalletImageUrl = undefined; + state.connectedSocialProvider = undefined; ConnectorController.setConnectedConnector(undefined); StorageUtil.removeWalletConnectDeepLink(); StorageUtil.removeConnectedWalletImageUrl(); StorageUtil.removeConnectedConnector(); + StorageUtil.removeConnectedSocialProvider(); }, async disconnect() { From a13a336f77bbae11aa9d8f0dc4e7545b4d1deaa0 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 7 Oct 2024 17:51:00 -0300 Subject: [PATCH 081/114] chore: added emailShowWallets prop functionality --- apps/native/App.tsx | 7 ++- .../src/views/w3m-connect-view/index.tsx | 62 ++++++++++++------- .../src/views/w3m-connect-view/styles.ts | 7 +++ 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/apps/native/App.tsx b/apps/native/App.tsx index 79f1db3aa..d5ba189e4 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -62,7 +62,12 @@ createAppKit({ clipboardClient, customWallets, enableAnalytics: true, - metadata + metadata, + features: { + email: true, + socials: ['x', 'discord', 'apple', 'farcaster', 'facebook'], + emailShowWallets: true + } }); export default function Native() { diff --git a/packages/scaffold/src/views/w3m-connect-view/index.tsx b/packages/scaffold/src/views/w3m-connect-view/index.tsx index b68e303fa..9ab0ee53d 100644 --- a/packages/scaffold/src/views/w3m-connect-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/index.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { Platform, ScrollView } from 'react-native'; +import { Platform, ScrollView, View } from 'react-native'; import { ConnectorController, EventUtil, @@ -8,7 +8,7 @@ import { RouterController, type WcWallet } from '@reown/appkit-core-react-native'; -import { FlexView, Separator, Spacing } from '@reown/appkit-ui-react-native'; +import { FlexView, Icon, ListItem, Separator, Spacing, Text } from '@reown/appkit-ui-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import { ConnectEmailInput } from './components/connect-email-input'; import { useKeyboard } from '../../hooks/useKeyboard'; @@ -32,6 +32,8 @@ export function ConnectView() { const isCoinbaseEnabled = connectors.some(c => c.type === 'COINBASE'); const isEmailEnabled = isAuthEnabled && features?.email; const isSocialEnabled = isAuthEnabled && features?.socials && features?.socials.length > 0; + const showConnectWalletsButton = + isWalletConnectEnabled && isAuthEnabled && !features?.emailShowWallets; const showSeparator = isAuthEnabled && (isEmailEnabled || isSocialEnabled) && @@ -73,28 +75,42 @@ export function ConnectView() { {isEmailEnabled && } {isSocialEnabled && } {showSeparator && } + - - - - - + {showConnectWalletsButton ? ( + + + Connect wallet + + + ) : ( + <> + + + + + + + )} diff --git a/packages/scaffold/src/views/w3m-connect-view/styles.ts b/packages/scaffold/src/views/w3m-connect-view/styles.ts index 271762bda..819b007df 100644 --- a/packages/scaffold/src/views/w3m-connect-view/styles.ts +++ b/packages/scaffold/src/views/w3m-connect-view/styles.ts @@ -7,5 +7,12 @@ export default StyleSheet.create({ }, socialSeparator: { marginVertical: Spacing.xs + }, + connectWalletButton: { + justifyContent: 'space-between' + }, + connectWalletEmpty: { + height: 20, + width: 20 } }); From 826e28ad91ef3baf67ea0110399f4bcc7d1231f8 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 7 Oct 2024 18:32:14 -0300 Subject: [PATCH 082/114] chore: clean loading status before entering social screen --- .../views/w3m-connect-view/components/social-login-list.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx index 8af95e8f1..c36406f65 100644 --- a/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx @@ -1,7 +1,7 @@ import { StyleSheet } from 'react-native'; import { FlexView, ListSocial, LogoSelect, Spacing, Text } from '@reown/appkit-ui-react-native'; import { type SocialProvider, StringUtil } from '@reown/appkit-common-react-native'; -import { RouterController } from '@reown/appkit-core-react-native'; +import { RouterController, WebviewController } from '@reown/appkit-core-react-native'; export interface SocialLoginListProps { options: readonly SocialProvider[]; @@ -18,6 +18,7 @@ export function SocialLoginList({ options, disabled }: SocialLoginListProps) { bottomSocials = showMoreButton ? bottomSocials.slice(0, MAX_OPTIONS - 2) : bottomSocials; const onItemPress = (social: SocialProvider) => { + WebviewController.setConnecting(false); if (social === 'farcaster') { RouterController.push('ConnectingFarcaster', { socialProvider: social }); } else { From a1b042f9bbf15a4e8b1ae40bb1d4833baa4c59be Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Mon, 7 Oct 2024 19:17:55 -0300 Subject: [PATCH 083/114] chore: added create wallet view --- .../stories/composites/Chip.stories.tsx | 6 +-- apps/gallery/utils/PresetUtils.ts | 1 + packages/auth-wagmi/src/index.ts | 4 +- .../core/src/controllers/RouterController.ts | 1 + .../scaffold/src/modal/w3m-router/index.tsx | 3 ++ .../src/partials/w3m-header/index.tsx | 1 + .../components/wallet-guide.tsx | 50 +++++++++++++++++++ .../src/views/w3m-connect-view/index.tsx | 2 + .../src/views/w3m-create-view/index.tsx | 35 +++++++++++++ .../w3m-upgrade-email-wallet-view/index.tsx | 2 +- .../views/w3m-wallet-receive-view/index.tsx | 2 +- packages/ui/src/assets/svg/WalletConnect.tsx | 25 +++++++++- packages/ui/src/components/wui-icon/index.tsx | 3 +- packages/ui/src/composites/wui-chip/index.tsx | 11 ++-- packages/ui/src/utils/TypesUtil.ts | 1 + 15 files changed, 134 insertions(+), 13 deletions(-) create mode 100644 packages/scaffold/src/views/w3m-connect-view/components/wallet-guide.tsx create mode 100644 packages/scaffold/src/views/w3m-create-view/index.tsx diff --git a/apps/gallery/stories/composites/Chip.stories.tsx b/apps/gallery/stories/composites/Chip.stories.tsx index fcef69462..23419deed 100644 --- a/apps/gallery/stories/composites/Chip.stories.tsx +++ b/apps/gallery/stories/composites/Chip.stories.tsx @@ -23,7 +23,7 @@ const meta: Meta = { imageSrc: { control: { type: 'text' } }, - icon: { + rightIcon: { options: iconOptions, control: { type: 'select' } } @@ -32,7 +32,7 @@ const meta: Meta = { variant: 'fill', size: 'md', disabled: false, - icon: 'disconnect', + rightIcon: 'disconnect', label: externalLabel, imageSrc: walletImageSrc } @@ -50,7 +50,7 @@ export const Default: Story = { onPress={() => {}} label={args.label} imageSrc={args.imageSrc} - icon={args.icon} + rightIcon={args.rightIcon} /> ) }; diff --git a/apps/gallery/utils/PresetUtils.ts b/apps/gallery/utils/PresetUtils.ts index b792514e9..038fc6fd3 100644 --- a/apps/gallery/utils/PresetUtils.ts +++ b/apps/gallery/utils/PresetUtils.ts @@ -172,6 +172,7 @@ export const iconOptions: IconType[] = [ 'wallet', 'walletSmall', 'walletConnect', + 'walletConnectLightBrown', 'walletPlaceholder', 'warningCircle' ]; diff --git a/packages/auth-wagmi/src/index.ts b/packages/auth-wagmi/src/index.ts index 287aba48b..934b8bb72 100644 --- a/packages/auth-wagmi/src/index.ts +++ b/packages/auth-wagmi/src/index.ts @@ -45,9 +45,9 @@ export function authConnector(parameters: AuthConnectorOptions) { return { accounts: [address as Address], account: address as Address, - chainId, + chainId: chainId as number, chain: { - id: chainId, + id: chainId as number, unsuported: false } }; diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index 7fbbd8513..92fd80a73 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -24,6 +24,7 @@ export interface RouterControllerState { | 'ConnectingSocial' | 'ConnectingFarcaster' | 'ConnectingWalletConnect' + | 'Create' | 'EmailVerifyDevice' | 'EmailVerifyOtp' | 'GetWallet' diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/scaffold/src/modal/w3m-router/index.tsx index 325a07f3e..ff7f694e8 100644 --- a/packages/scaffold/src/modal/w3m-router/index.tsx +++ b/packages/scaffold/src/modal/w3m-router/index.tsx @@ -31,6 +31,7 @@ import { WhatIsAWalletView } from '../../views/w3m-what-is-a-wallet-view'; import { UiUtil } from '../../utils/UiUtil'; import { ConnectingFarcasterView } from '../../views/w3m-connecting-farcaster-view'; import { ConnectSocialsView } from '../../views/w3m-connect-socials-view'; +import { CreateView } from '../../views/w3m-create-view'; export function AppKitRouter() { const { view } = useSnapshot(RouterController.state); @@ -61,6 +62,8 @@ export function AppKitRouter() { return ConnectingFarcasterView; case 'ConnectingWalletConnect': return ConnectingView; + case 'Create': + return CreateView; case 'EmailVerifyDevice': return EmailVerifyDeviceView; case 'EmailVerifyOtp': diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index 49cddef65..3a7f5fd5a 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -34,6 +34,7 @@ export function Header() { ConnectingFarcaster: socialName ?? 'Connecting Social', ConnectingSocial: socialName ?? 'Connecting Social', ConnectingWalletConnect: walletName ?? 'WalletConnect', + Create: 'Create wallet', EmailVerifyDevice: ' ', EmailVerifyOtp: 'Confirm email', GetWallet: 'Get a wallet', diff --git a/packages/scaffold/src/views/w3m-connect-view/components/wallet-guide.tsx b/packages/scaffold/src/views/w3m-connect-view/components/wallet-guide.tsx new file mode 100644 index 000000000..92fdcc2de --- /dev/null +++ b/packages/scaffold/src/views/w3m-connect-view/components/wallet-guide.tsx @@ -0,0 +1,50 @@ +import { RouterController } from '@reown/appkit-core-react-native'; +import { Chip, FlexView, Link, Separator, Spacing, Text } from '@reown/appkit-ui-react-native'; +import { Linking, StyleSheet } from 'react-native'; + +export interface WalletGuideProps { + guide: 'explore' | 'get-started'; +} + +export function WalletGuide({ guide }: WalletGuideProps) { + const onExplorerPress = () => { + Linking.openURL('https://explorer.walletconnect.com'); + }; + + const onGetStartedPress = () => { + RouterController.push('Create'); + }; + + return guide === 'explore' ? ( + + + + Looking for a self-custody wallet? + + + + ) : ( + + Haven't got a wallet? + + Get started + + + ); +} + +const styles = StyleSheet.create({ + text: { + marginBottom: Spacing.xs + }, + socialSeparator: { + marginVertical: Spacing.l + } +}); diff --git a/packages/scaffold/src/views/w3m-connect-view/index.tsx b/packages/scaffold/src/views/w3m-connect-view/index.tsx index 9ab0ee53d..7b3fd4d2d 100644 --- a/packages/scaffold/src/views/w3m-connect-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/index.tsx @@ -19,6 +19,7 @@ import { AllWalletList } from './components/all-wallet-list'; import { RecentWalletList } from './components/recent-wallet-list'; import { SocialLoginList } from './components/social-login-list'; import styles from './styles'; +import { WalletGuide } from './components/wallet-guide'; export function ConnectView() { const connectors = ConnectorController.state.connectors; @@ -111,6 +112,7 @@ export function ConnectView() { /> )} + {isAuthEnabled && } diff --git a/packages/scaffold/src/views/w3m-create-view/index.tsx b/packages/scaffold/src/views/w3m-create-view/index.tsx new file mode 100644 index 000000000..dcfa09654 --- /dev/null +++ b/packages/scaffold/src/views/w3m-create-view/index.tsx @@ -0,0 +1,35 @@ +import { Platform, ScrollView } from 'react-native'; +import { useSnapshot } from 'valtio'; +import { FlexView, Spacing } from '@reown/appkit-ui-react-native'; +import { ConnectEmailInput } from '../w3m-connect-view/components/connect-email-input'; +import { SocialLoginList } from '../w3m-connect-view/components/social-login-list'; +import { WalletGuide } from '../w3m-connect-view/components/wallet-guide'; +import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { ConnectorController, OptionsController } from '@reown/appkit-core-react-native'; +import { useKeyboard } from '../../hooks/useKeyboard'; + +export function CreateView() { + const connectors = ConnectorController.state.connectors; + const { authLoading } = useSnapshot(ConnectorController.state); + const { features } = useSnapshot(OptionsController.state); + const { padding } = useCustomDimensions(); + const { keyboardShown, keyboardHeight } = useKeyboard(); + const isAuthEnabled = connectors.some(c => c.type === 'AUTH'); + const isEmailEnabled = isAuthEnabled && features?.email; + const isSocialEnabled = isAuthEnabled && features?.socials && features?.socials.length > 0; + + const paddingBottom = Platform.select({ + android: keyboardShown ? keyboardHeight + Spacing.xl : Spacing.xl, + default: Spacing.xl + }); + + return ( + + + {isEmailEnabled && } + {isSocialEnabled && } + {isAuthEnabled && } + + + ); +} diff --git a/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx index 425d9729b..d3239180a 100644 --- a/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx +++ b/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx @@ -19,7 +19,7 @@ export function UpgradeEmailWalletView() { Follow the instructions on ( ( ); export default SvgWalletConnect; + +export const WalletConnectLightBrownSvg = (props: SvgProps) => ( + + + + + + + + + + + +); diff --git a/packages/ui/src/components/wui-icon/index.tsx b/packages/ui/src/components/wui-icon/index.tsx index 7b1656e9a..56f010f5f 100644 --- a/packages/ui/src/components/wui-icon/index.tsx +++ b/packages/ui/src/components/wui-icon/index.tsx @@ -52,7 +52,7 @@ import SwapVerticalSvg from '../../assets/svg/SwapVertical'; import TelegramSvg from '../../assets/svg/Telegram'; import TwitchSvg from '../../assets/svg/Twitch'; import VerifySvg from '../../assets/svg/Verify'; -import WalletConnectSvg from '../../assets/svg/WalletConnect'; +import WalletConnectSvg, { WalletConnectLightBrownSvg } from '../../assets/svg/WalletConnect'; import WalletSvg from '../../assets/svg/Wallet'; import WalletSmallSvg from '../../assets/svg/WalletSmall'; import WarningCircleSvg from '../../assets/svg/WarningCircle'; @@ -116,6 +116,7 @@ const svgOptions: Record JSX.Element> = { walletSmall: WalletSmallSvg, warningCircle: WarningCircleSvg, walletConnect: WalletConnectSvg, + walletConnectLightBrown: WalletConnectLightBrownSvg, walletPlaceholder: WalletPlaceholderSvg, x: XSvg }; diff --git a/packages/ui/src/composites/wui-chip/index.tsx b/packages/ui/src/composites/wui-chip/index.tsx index ed3764545..e8ab92e47 100644 --- a/packages/ui/src/composites/wui-chip/index.tsx +++ b/packages/ui/src/composites/wui-chip/index.tsx @@ -12,7 +12,8 @@ const AnimatedPressable = Animated.createAnimatedComponent(Pressable); export interface ChipProps { label?: string; imageSrc?: string; - icon?: IconType; + leftIcon?: IconType; + rightIcon?: IconType; variant?: ChipType; size?: Exclude; disabled?: boolean; @@ -23,7 +24,8 @@ export interface ChipProps { export function Chip({ onPress, imageSrc, - icon, + leftIcon, + rightIcon, variant = 'fill', size = 'md', disabled, @@ -89,15 +91,16 @@ export function Chip({ source={imageSrc} /> )} + {leftIcon && } {label} - {icon && ( + {rightIcon && ( Date: Tue, 8 Oct 2024 10:14:08 -0300 Subject: [PATCH 084/114] chore: added social events --- packages/core/src/utils/TypeUtil.ts | 21 +++++++++++++++++++ .../views/w3m-connect-socials-view/index.tsx | 17 ++++++++++++++- .../components/social-login-list.tsx | 12 ++++++++++- .../w3m-connecting-farcaster-view/index.tsx | 11 ++++++++++ packages/wallet/src/AppKitWebview.tsx | 20 +++++++++++++++++- 5 files changed, 78 insertions(+), 3 deletions(-) diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 84d96f1f3..9981f4fab 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -451,6 +451,27 @@ export type Event = token: string; amount: number; }; + } + | { + type: 'track'; + event: 'SOCIAL_LOGIN_STARTED'; + properties: { + provider: SocialProvider; + }; + } + | { + type: 'track'; + event: 'SOCIAL_LOGIN_SUCCESS'; + properties: { + provider: SocialProvider; + }; + } + | { + type: 'track'; + event: 'SOCIAL_LOGIN_ERROR'; + properties: { + provider: SocialProvider; + }; }; // -- Send Controller Types ------------------------------------- diff --git a/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx b/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx index d5aecad29..668123d88 100644 --- a/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx @@ -1,7 +1,13 @@ +import { useEffect } from 'react'; import { useSnapshot } from 'valtio'; import { ScrollView } from 'react-native'; import { StringUtil, type SocialProvider } from '@reown/appkit-common-react-native'; -import { OptionsController, RouterController } from '@reown/appkit-core-react-native'; +import { + EventsController, + OptionsController, + RouterController, + WebviewController +} from '@reown/appkit-core-react-native'; import { FlexView, ListSocial, Text } from '@reown/appkit-ui-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; @@ -13,6 +19,11 @@ export function ConnectSocialsView() { const socialProviders = (features?.socials ?? []) as SocialProvider[]; const onItemPress = (provider: SocialProvider) => { + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_STARTED', + properties: { provider } + }); if (provider === 'farcaster') { RouterController.push('ConnectingFarcaster', { socialProvider: provider }); } else { @@ -20,6 +31,10 @@ export function ConnectSocialsView() { } }; + useEffect(() => { + WebviewController.setConnecting(false); + }, []); + return ( diff --git a/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx index c36406f65..cc1e45a2b 100644 --- a/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx @@ -1,7 +1,11 @@ import { StyleSheet } from 'react-native'; import { FlexView, ListSocial, LogoSelect, Spacing, Text } from '@reown/appkit-ui-react-native'; import { type SocialProvider, StringUtil } from '@reown/appkit-common-react-native'; -import { RouterController, WebviewController } from '@reown/appkit-core-react-native'; +import { + EventsController, + RouterController, + WebviewController +} from '@reown/appkit-core-react-native'; export interface SocialLoginListProps { options: readonly SocialProvider[]; @@ -18,7 +22,13 @@ export function SocialLoginList({ options, disabled }: SocialLoginListProps) { bottomSocials = showMoreButton ? bottomSocials.slice(0, MAX_OPTIONS - 2) : bottomSocials; const onItemPress = (social: SocialProvider) => { + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_STARTED', + properties: { provider: social } + }); WebviewController.setConnecting(false); + if (social === 'farcaster') { RouterController.push('ConnectingFarcaster', { socialProvider: social }); } else { diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx index 6a9ade1bb..469f9297c 100644 --- a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx @@ -3,6 +3,7 @@ import { useCallback, useEffect, useState } from 'react'; import { ConnectionController, ConnectorController, + EventsController, ModalController, OptionsController, RouterController, @@ -42,10 +43,20 @@ export function ConnectingFarcasterView() { await provider.connectFarcaster(); await ConnectionController.connectExternal(authConnector); ConnectionController.setConnectedSocialProvider(socialProvider); + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_SUCCESS', + properties: { provider: socialProvider } + }); WebviewController.setConnecting(false); ModalController.close(); } } catch (e) { + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_ERROR', + properties: { provider: socialProvider! } + }); SnackController.showError('Something went wrong'); setError(true); } diff --git a/packages/wallet/src/AppKitWebview.tsx b/packages/wallet/src/AppKitWebview.tsx index fd74e8291..90978abac 100644 --- a/packages/wallet/src/AppKitWebview.tsx +++ b/packages/wallet/src/AppKitWebview.tsx @@ -6,6 +6,7 @@ import { WebView } from 'react-native-webview'; import { ConnectionController, ConnectorController, + EventsController, ModalController, RouterController, SnackController, @@ -104,16 +105,33 @@ export function AppKitWebview() { ref={webviewRef} onNavigationStateChange={async navState => { try { - if (authConnector && webviewVisible && navState.url.includes('/sdk/oauth')) { + if ( + authConnector && + connectingProvider && + webviewVisible && + navState.url.includes('/sdk/oauth') + ) { WebviewController.setWebviewVisible(false); const parsedUrl = new URL(navState.url); await provider?.connectSocial(parsedUrl.search); await ConnectionController.connectExternal(authConnector); ConnectionController.setConnectedSocialProvider(connectingProvider); WebviewController.setConnecting(false); + + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_SUCCESS', + properties: { provider: connectingProvider } + }); + ModalController.close(); } } catch (e) { + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_ERROR', + properties: { provider: connectingProvider! } + }); onClose(); SnackController.showError('Something went wrong'); } From 973554e44690e65b99484867f86a765e3ee887ed Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:52:40 -0300 Subject: [PATCH 085/114] chore: code improvements - emit social event --- packages/core/src/utils/TypeUtil.ts | 2 + .../w3m-connecting-social-view/index.tsx | 50 ++++++++++++++++++- packages/wallet/src/AppKitFrameProvider.ts | 4 ++ packages/wallet/src/AppKitWebview.tsx | 38 ++------------ 4 files changed, 58 insertions(+), 36 deletions(-) diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 9981f4fab..03ddc6f84 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -1,3 +1,4 @@ +import { type EventEmitter } from 'events'; import type { Balance, SocialProvider, Transaction } from '@reown/appkit-common-react-native'; export interface BaseError { @@ -502,6 +503,7 @@ export interface WriteContractArgs { export interface AppKitFrameProvider { readonly id: string; readonly name: string; + getEventEmitter(): EventEmitter; getSecureSiteURL(): string; getSecureSiteDashboardURL(): string; getSecureSiteIconURL(): string; diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx index 2a4b970f5..50a8af39d 100644 --- a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx @@ -1,7 +1,10 @@ import { useSnapshot } from 'valtio'; import { useCallback, useEffect, useState } from 'react'; import { + ConnectionController, ConnectorController, + EventsController, + ModalController, RouterController, SnackController, WebviewController, @@ -43,10 +46,55 @@ export function ConnectingSocialView() { } }, [provider, socialProvider]); + const socialMessageHandler = useCallback( + async (url: string) => { + try { + if (url.includes('/sdk/oauth') && socialProvider && authConnector) { + WebviewController.setWebviewVisible(false); + const parsedUrl = new URL(url); + await provider?.connectSocial(parsedUrl.search); + await ConnectionController.connectExternal(authConnector); + ConnectionController.setConnectedSocialProvider(socialProvider); + WebviewController.setConnecting(false); + + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_SUCCESS', + properties: { provider: socialProvider } + }); + + ModalController.close(); + } + } catch (e) { + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_ERROR', + properties: { provider: socialProvider! } + }); + WebviewController.setWebviewVisible(false); + WebviewController.setConnecting(false); + WebviewController.setConnectingProvider(undefined); + RouterController.goBack(); + SnackController.showError('Something went wrong'); + } + }, + [socialProvider, authConnector, provider] + ); + useEffect(() => { onConnect(); }, [onConnect]); + useEffect(() => { + if (!provider) return; + + const unsubscribe = provider?.getEventEmitter().addListener('social', socialMessageHandler); + + return () => { + unsubscribe.removeListener('social', socialMessageHandler); + }; + }, [socialMessageHandler, provider]); + return ( - {connecting ? 'Connecting...' : 'Connect in the provider window'} + {connecting ? 'Retrieving user data' : 'Connect in the provider window'} ); diff --git a/packages/wallet/src/AppKitFrameProvider.ts b/packages/wallet/src/AppKitFrameProvider.ts index 1f7f99f18..7b26dedc2 100644 --- a/packages/wallet/src/AppKitFrameProvider.ts +++ b/packages/wallet/src/AppKitFrameProvider.ts @@ -117,6 +117,10 @@ export class AppKitFrameProvider { } catch (e) {} } + public getEventEmitter() { + return this.events; + } + public async connectEmail(payload: AppKitFrameTypes.Requests['AppConnectEmailRequest']) { await this.webviewLoadPromise; await AppKitFrameHelpers.checkIfAllowedToTriggerEmail(); diff --git a/packages/wallet/src/AppKitWebview.tsx b/packages/wallet/src/AppKitWebview.tsx index 90978abac..536ad48cc 100644 --- a/packages/wallet/src/AppKitWebview.tsx +++ b/packages/wallet/src/AppKitWebview.tsx @@ -4,12 +4,8 @@ import { Animated, SafeAreaView, StyleSheet } from 'react-native'; import { WebView } from 'react-native-webview'; import { - ConnectionController, ConnectorController, - EventsController, - ModalController, RouterController, - SnackController, WebviewController } from '@reown/appkit-core-react-native'; import { useTheme, BorderRadius, IconLink, Spacing } from '@reown/appkit-ui-react-native'; @@ -21,7 +17,7 @@ export function AppKitWebview() { const webviewRef = useRef(null); const Theme = useTheme(); const authConnector = ConnectorController.getAuthConnector(); - const { webviewVisible, webviewUrl, connectingProvider } = useSnapshot(WebviewController.state); + const { webviewVisible, webviewUrl } = useSnapshot(WebviewController.state); const [isBackdropVisible, setIsBackdropVisible] = useState(false); const animatedHeight = useRef(new Animated.Value(0)); const backdropOpacity = useRef(new Animated.Value(0)); @@ -104,36 +100,8 @@ export function AppKitWebview() { containerStyle={styles.webview} ref={webviewRef} onNavigationStateChange={async navState => { - try { - if ( - authConnector && - connectingProvider && - webviewVisible && - navState.url.includes('/sdk/oauth') - ) { - WebviewController.setWebviewVisible(false); - const parsedUrl = new URL(navState.url); - await provider?.connectSocial(parsedUrl.search); - await ConnectionController.connectExternal(authConnector); - ConnectionController.setConnectedSocialProvider(connectingProvider); - WebviewController.setConnecting(false); - - EventsController.sendEvent({ - type: 'track', - event: 'SOCIAL_LOGIN_SUCCESS', - properties: { provider: connectingProvider } - }); - - ModalController.close(); - } - } catch (e) { - EventsController.sendEvent({ - type: 'track', - event: 'SOCIAL_LOGIN_ERROR', - properties: { provider: connectingProvider! } - }); - onClose(); - SnackController.showError('Something went wrong'); + if (webviewVisible) { + provider.events.emit('social', navState.url); } }} /> From b7abd353cd87e9eae39574a69a7921289331bc0e Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:19:46 -0300 Subject: [PATCH 086/114] chore: ui improvement --- .../src/partials/w3m-account-activity/index.tsx | 13 ++++++------- .../src/partials/w3m-account-placeholder/index.tsx | 10 ++++++---- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/scaffold/src/partials/w3m-account-activity/index.tsx index f2ee5cb8a..6c5f2a89b 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-activity/index.tsx @@ -68,13 +68,12 @@ export function AccountActivity({ style }: Props) { if (!Object.keys(transactionsByYear).length) { return ( - - - + ); } diff --git a/packages/scaffold/src/partials/w3m-account-placeholder/index.tsx b/packages/scaffold/src/partials/w3m-account-placeholder/index.tsx index 63db85b11..666d19773 100644 --- a/packages/scaffold/src/partials/w3m-account-placeholder/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-placeholder/index.tsx @@ -1,15 +1,16 @@ -import { StyleSheet } from 'react-native'; +import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; import { IconBox, Text, FlexView, Spacing, type IconType } from '@reown/appkit-ui-react-native'; interface Props { icon: IconType; title: string; description: string; + style?: StyleProp; } -export function AccountPlaceholder({ icon, title, description }: Props) { +export function AccountPlaceholder({ icon, title, description, style }: Props) { return ( - + {title} @@ -23,7 +24,8 @@ export function AccountPlaceholder({ icon, title, description }: Props) { const styles = StyleSheet.create({ container: { - flex: 1 + flex: 1, + minHeight: 200 }, title: { marginTop: Spacing.xl, From ea5ed38accfdfc3498a3ca676840976d8406890c Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 9 Oct 2024 12:59:54 -0300 Subject: [PATCH 087/114] chore: removed textContentType from otp input --- packages/ui/src/composites/wui-otp/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/ui/src/composites/wui-otp/index.tsx b/packages/ui/src/composites/wui-otp/index.tsx index 713d0d21d..868101c5b 100644 --- a/packages/ui/src/composites/wui-otp/index.tsx +++ b/packages/ui/src/composites/wui-otp/index.tsx @@ -90,7 +90,6 @@ export function Otp({ length, style, onChangeText, autoFocus }: OtpProps) { inputRef={refArray[index]} onChangeText={text => _onChangeText(text, index)} onKeyPress={(e: any) => onKeyPress(e, index)} - textContentType="oneTimeCode" autoComplete={Platform.OS === 'android' ? 'sms-otp' : 'one-time-code'} /> ))} From 8e0117195f34a22f6407252e95d7adcc0f2a94a7 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 10 Oct 2024 16:05:15 -0300 Subject: [PATCH 088/114] chore: changed button --- packages/scaffold/src/views/w3m-connect-view/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/scaffold/src/views/w3m-connect-view/index.tsx b/packages/scaffold/src/views/w3m-connect-view/index.tsx index 7b3fd4d2d..4a52eca79 100644 --- a/packages/scaffold/src/views/w3m-connect-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/index.tsx @@ -81,7 +81,7 @@ export function ConnectView() { {showConnectWalletsButton ? ( - Connect wallet + Continue with a wallet ) : ( From 726b82ae4eb8fdf6d4bdaa2289ab4af3e33c7827 Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:02:17 -0300 Subject: [PATCH 089/114] fix: use custom token to get balance if provided --- packages/common/src/contracts/erc20.ts | 47 ++++++++++++++++++++++++++ packages/ethers/src/client.ts | 22 +++++++++--- packages/ethers5/src/client.ts | 21 +++++++++--- packages/wagmi/src/client.ts | 3 +- 4 files changed, 84 insertions(+), 9 deletions(-) diff --git a/packages/common/src/contracts/erc20.ts b/packages/common/src/contracts/erc20.ts index cf48b5c1e..744983257 100644 --- a/packages/common/src/contracts/erc20.ts +++ b/packages/common/src/contracts/erc20.ts @@ -44,5 +44,52 @@ export const erc20ABI = [ type: 'bool' } ] + }, + { + constant: true, + inputs: [], + name: 'symbol', + outputs: [ + { + name: '', + type: 'string' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [], + name: 'decimals', + outputs: [ + { + name: '', + type: 'uint8' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' + }, + { + constant: true, + inputs: [ + { + name: '_owner', + type: 'address' + } + ], + name: 'balanceOf', + outputs: [ + { + name: 'balance', + type: 'uint256' + } + ], + payable: false, + stateMutability: 'view', + type: 'function' } ]; diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index 284585ba2..6f6659de9 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -26,7 +26,7 @@ import { AppKitScaffold, type WriteContractArgs } from '@reown/appkit-scaffold-react-native'; -import { NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; +import { erc20ABI, NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; import { ConstantsUtil, PresetsUtil, @@ -775,16 +775,30 @@ export class AppKit extends AppKitScaffold { const chainId = EthersStoreUtil.state.chainId; if (chainId && this.chains) { const chain = this.chains.find(c => c.chainId === chainId); + const token = this.options?.tokens?.[chainId]; if (chain) { const jsonRpcProvider = new JsonRpcProvider(chain.rpcUrl, { chainId, name: chain.name }); + if (jsonRpcProvider) { - const balance = await jsonRpcProvider.getBalance(address); - const formattedBalance = formatEther(balance); - this.setBalance(formattedBalance, chain.currency); + if (token) { + // Get balance from custom token address + const erc20 = new Contract(token.address, erc20ABI, jsonRpcProvider); + // @ts-expect-error + const decimals = await erc20.decimals(); + // @ts-expect-error + const symbol = await erc20.symbol(); + // @ts-expect-error + const balanceOf = await erc20.balanceOf(address); + this.setBalance(formatUnits(balanceOf, decimals), symbol); + } else { + const balance = await jsonRpcProvider.getBalance(address); + const formattedBalance = formatEther(balance); + this.setBalance(formattedBalance, chain.currency); + } } } } diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index 2b0af1c3b..cb38f6a79 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -30,7 +30,7 @@ import { type CombinedProviderType, type AppKitFrameProvider } from '@reown/appkit-scaffold-utils-react-native'; -import { NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; +import { erc20ABI, NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider'; @@ -755,6 +755,7 @@ export class AppKit extends AppKitScaffold { const chainId = EthersStoreUtil.state.chainId; if (chainId && this.chains) { const chain = this.chains.find(c => c.chainId === chainId); + const token = this.options?.tokens?.[chainId]; if (chain) { const jsonRpcProvider = new ethers.providers.JsonRpcProvider(chain.rpcUrl, { @@ -762,9 +763,21 @@ export class AppKit extends AppKitScaffold { name: chain.name }); if (jsonRpcProvider) { - const balance = await jsonRpcProvider.getBalance(address); - const formattedBalance = utils.formatEther(balance); - this.setBalance(formattedBalance, chain.currency); + if (token) { + // Get balance from custom token address + const erc20 = new Contract(token.address, erc20ABI, jsonRpcProvider); + // @ts-expect-error + const decimals = await erc20.decimals(); + // @ts-expect-error + const symbol = await erc20.symbol(); + // @ts-expect-error + const balanceOf = await erc20.balanceOf(address); + this.setBalance(utils.formatUnits(balanceOf, decimals), symbol); + } else { + const balance = await jsonRpcProvider.getBalance(address); + const formattedBalance = utils.formatEther(balance); + this.setBalance(formattedBalance, chain.currency); + } } } } diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index 804ab2561..0f45eb838 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -486,7 +486,8 @@ export class AppKit extends AppKitScaffold { if (chain) { const balance = await getBalance(this.wagmiConfig, { address, - chainId: chain.id + chainId: chain.id, + token: this.options?.tokens?.[chainId]?.address as Hex }); const formattedBalance = formatUnits(balance.value, balance.decimals); this.setBalance(formattedBalance, balance.symbol); From 19bbffde847b8d962d30e7dedacd93a4ef0c543c Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 17 Oct 2024 17:02:49 -0300 Subject: [PATCH 090/114] chore: changeset --- .changeset/pretty-taxis-type.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .changeset/pretty-taxis-type.md diff --git a/.changeset/pretty-taxis-type.md b/.changeset/pretty-taxis-type.md new file mode 100644 index 000000000..9f56a5164 --- /dev/null +++ b/.changeset/pretty-taxis-type.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: get custom token balance if provided From fb85422e0d544efc21a686287d91b16a638cf99c Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 5 Nov 2024 11:30:26 -0300 Subject: [PATCH 091/114] chore: added changeset file --- .changeset/bright-points-burn.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .changeset/bright-points-burn.md diff --git a/.changeset/bright-points-burn.md b/.changeset/bright-points-burn.md new file mode 100644 index 000000000..cc251e262 --- /dev/null +++ b/.changeset/bright-points-burn.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': minor +'@reown/appkit-coinbase-wagmi-react-native': minor +'@reown/appkit-scaffold-utils-react-native': minor +'@reown/appkit-auth-ethers-react-native': minor +'@reown/appkit-auth-wagmi-react-native': minor +'@reown/appkit-scaffold-react-native': minor +'@reown/appkit-ethers5-react-native': minor +'@reown/appkit-common-react-native': minor +'@reown/appkit-ethers-react-native': minor +'@reown/appkit-wallet-react-native': minor +'@reown/appkit-wagmi-react-native': minor +'@reown/appkit-core-react-native': minor +'@reown/appkit-siwe-react-native': minor +'@reown/appkit-ui-react-native': minor +--- + +feat: social login From 3193c6fac3a2d7f83f53bf982180469e23f156c4 Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:19:12 -0300 Subject: [PATCH 092/114] feat: smart accounts (#266) --- .changeset/serious-tables-compare.md | 18 + apps/native/App.tsx | 4 +- apps/native/babel.config.js | 28 +- apps/native/package.json | 6 +- apps/native/src/utils/WagmiUtils.ts | 4 +- apps/native/src/views/ActionsView.tsx | 4 +- package.json | 4 +- packages/auth-wagmi/package.json | 1 + packages/auth-wagmi/src/index.ts | 71 ++- packages/common/src/utils/TypeUtil.ts | 9 +- .../controllers/AccountController.test.ts | 4 +- .../controllers/NetworkController.test.ts | 13 +- .../core/src/controllers/AccountController.ts | 18 +- .../src/controllers/ConnectionController.ts | 15 +- .../src/controllers/ConnectorController.ts | 14 +- .../core/src/controllers/NetworkController.ts | 22 +- .../core/src/controllers/OptionsController.ts | 3 +- .../core/src/controllers/RouterController.ts | 1 + .../core/src/controllers/SendController.ts | 8 +- .../src/controllers/TransactionsController.ts | 3 +- packages/core/src/utils/ConstantsUtil.ts | 2 +- packages/core/src/utils/StorageUtil.ts | 2 +- packages/core/src/utils/TypeUtil.ts | 59 +- packages/ethers/src/client.ts | 41 +- packages/ethers5/src/client.ts | 37 +- packages/scaffold/src/client.ts | 7 +- .../scaffold/src/modal/w3m-modal/index.tsx | 9 +- .../scaffold/src/modal/w3m-router/index.tsx | 9 +- .../partials/w3m-account-activity/index.tsx | 2 +- .../src/partials/w3m-account-tokens/index.tsx | 31 +- .../w3m-account-wallet-features/index.tsx | 4 +- .../partials/w3m-all-wallets-list/index.tsx | 6 +- .../src/partials/w3m-header/index.tsx | 16 +- .../src/partials/w3m-header/styles.ts | 7 +- .../src/partials/w3m-input-address/styles.ts | 2 +- .../src/partials/w3m-input-token/styles.ts | 2 +- .../views/w3m-account-default-view/index.tsx | 50 +- .../src/views/w3m-account-view/index.tsx | 16 +- .../src/views/w3m-account-view/styles.ts | 5 + .../src/views/w3m-all-wallets-view/styles.ts | 2 +- .../w3m-connecting-external-view/index.tsx | 4 +- .../w3m-connecting-farcaster-view/index.tsx | 12 +- .../w3m-connecting-social-view/index.tsx | 1 + .../src/views/w3m-connecting-view/index.tsx | 4 +- .../views/w3m-network-switch-view/index.tsx | 10 + .../src/views/w3m-networks-view/index.tsx | 8 +- .../w3m-upgrade-email-wallet-view/index.tsx | 2 +- .../index.tsx | 106 ++++ .../styles.ts | 29 + .../index.tsx | 22 +- .../views/w3m-wallet-receive-view/index.tsx | 8 +- .../components/preview-send-pill.tsx | 2 +- .../index.tsx | 31 +- .../src/views/w3m-wallet-send-view/styles.ts | 6 - .../views/w3m-connecting-siwe-view/index.tsx | 37 +- .../views/w3m-connecting-siwe-view/styles.ts | 8 + packages/ui/src/__tests__/wui-text.test.tsx | 9 + packages/ui/src/assets/visual/Google.tsx | 43 ++ packages/ui/src/assets/visual/Lightbulb.tsx | 54 ++ packages/ui/src/assets/visual/Pencil.tsx | 80 +++ packages/ui/src/components/wui-card/styles.ts | 2 +- .../ui/src/components/wui-lean-text/index.tsx | 10 + .../ui/src/components/wui-lean-view/index.tsx | 10 + packages/ui/src/components/wui-text/index.tsx | 7 +- .../ui/src/components/wui-visual/index.tsx | 6 + .../composites/wui-account-button/styles.ts | 4 +- .../src/composites/wui-account-pill/index.tsx | 8 +- .../src/composites/wui-account-pill/styles.ts | 5 +- .../ui/src/composites/wui-button/styles.ts | 2 +- packages/ui/src/composites/wui-chip/styles.ts | 4 +- .../wui-compatible-network/index.tsx | 8 +- .../composites/wui-connect-button/styles.ts | 2 +- .../src/composites/wui-input-text/styles.ts | 2 +- .../src/composites/wui-list-wallet/index.tsx | 13 +- .../composites/wui-network-button/styles.ts | 2 +- packages/ui/src/composites/wui-otp/index.tsx | 5 +- .../ui/src/composites/wui-promo/index.tsx | 42 ++ .../ui/src/composites/wui-snackbar/styles.ts | 2 +- packages/ui/src/composites/wui-tabs/styles.ts | 2 +- .../src/composites/wui-wallet-image/styles.ts | 2 +- packages/ui/src/index.ts | 1 + packages/ui/src/layout/wui-flex/index.tsx | 6 +- .../ui/src/layout/wui-separator/index.tsx | 11 +- packages/ui/src/utils/TypesUtil.ts | 12 + packages/wagmi/src/client.ts | 24 +- packages/wallet/src/AppKitAuthWebview.tsx | 127 +++-- packages/wallet/src/AppKitFrameConstants.ts | 15 +- packages/wallet/src/AppKitFrameProvider.ts | 192 ++++--- packages/wallet/src/AppKitFrameSchema.ts | 68 ++- packages/wallet/src/AppKitFrameTypes.ts | 17 +- packages/wallet/src/AppKitWebview.tsx | 6 +- yarn.lock | 520 ++++++++++++------ 92 files changed, 1626 insertions(+), 546 deletions(-) create mode 100644 .changeset/serious-tables-compare.md create mode 100644 packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx create mode 100644 packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts create mode 100644 packages/ui/src/assets/visual/Google.tsx create mode 100644 packages/ui/src/assets/visual/Lightbulb.tsx create mode 100644 packages/ui/src/assets/visual/Pencil.tsx create mode 100644 packages/ui/src/components/wui-lean-text/index.tsx create mode 100644 packages/ui/src/components/wui-lean-view/index.tsx create mode 100644 packages/ui/src/composites/wui-promo/index.tsx diff --git a/.changeset/serious-tables-compare.md b/.changeset/serious-tables-compare.md new file mode 100644 index 000000000..167a81ac8 --- /dev/null +++ b/.changeset/serious-tables-compare.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': minor +'@reown/appkit-coinbase-wagmi-react-native': minor +'@reown/appkit-scaffold-utils-react-native': minor +'@reown/appkit-auth-ethers-react-native': minor +'@reown/appkit-auth-wagmi-react-native': minor +'@reown/appkit-scaffold-react-native': minor +'@reown/appkit-ethers5-react-native': minor +'@reown/appkit-common-react-native': minor +'@reown/appkit-ethers-react-native': minor +'@reown/appkit-wallet-react-native': minor +'@reown/appkit-wagmi-react-native': minor +'@reown/appkit-core-react-native': minor +'@reown/appkit-siwe-react-native': minor +'@reown/appkit-ui-react-native': minor +--- + +feat: smart accounts for social login diff --git a/apps/native/App.tsx b/apps/native/App.tsx index d5ba189e4..d8036e3f8 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -65,8 +65,8 @@ createAppKit({ metadata, features: { email: true, - socials: ['x', 'discord', 'apple', 'farcaster', 'facebook'], - emailShowWallets: true + socials: ['x', 'farcaster', 'discord', 'apple'], + emailShowWallets: false } }); diff --git a/apps/native/babel.config.js b/apps/native/babel.config.js index 98ae29e0e..425262ac6 100644 --- a/apps/native/babel.config.js +++ b/apps/native/babel.config.js @@ -1,8 +1,11 @@ const path = require('path'); -const uipak = require('../../packages/ui/package.json'); -const corepak = require('../../packages/core/package.json'); -const scaffoldpak = require('../../packages/scaffold/package.json'); -const wagmipak = require('../../packages/wagmi/package.json'); +const uipack = require('../../packages/ui/package.json'); +const corepack = require('../../packages/core/package.json'); +const scaffoldpack = require('../../packages/scaffold/package.json'); +const wagmipack = require('../../packages/wagmi/package.json'); +const authpack = require('../../packages/auth-wagmi/package.json'); +const commonpack = require('../../packages/common/package.json'); +const siwepack = require('../../packages/siwe/package.json'); module.exports = function (api) { api.cache(true); @@ -15,11 +18,18 @@ module.exports = function (api) { { extensions: ['.tsx', '.ts', '.js', '.json'], alias: { - // For development, we want to alias the ui library to the source - [uipak.name]: path.join(__dirname, '../../packages/ui', uipak.source), - [corepak.name]: path.join(__dirname, '../../packages/core', corepak.source), - [scaffoldpak.name]: path.join(__dirname, '../../packages/scaffold', scaffoldpak.source), - [wagmipak.name]: path.join(__dirname, '../../packages/wagmi', wagmipak.source) + // For development, we want to alias the packages to the source + [uipack.name]: path.join(__dirname, '../../packages/ui', uipack.source), + [corepack.name]: path.join(__dirname, '../../packages/core', corepack.source), + [scaffoldpack.name]: path.join( + __dirname, + '../../packages/scaffold', + scaffoldpack.source + ), + [wagmipack.name]: path.join(__dirname, '../../packages/wagmi', wagmipack.source), + [authpack.name]: path.join(__dirname, '../../packages/auth-wagmi', authpack.source), + [commonpack.name]: path.join(__dirname, '../../packages/common', commonpack.source), + [siwepack.name]: path.join(__dirname, '../../packages/siwe', siwepack.source) } } ] diff --git a/apps/native/package.json b/apps/native/package.json index 1116eecfb..835192799 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -22,7 +22,7 @@ "@tanstack/query-async-storage-persister": "^5.40.0", "@tanstack/react-query": "5.56.2", "@tanstack/react-query-persist-client": "5.56.2", - "@walletconnect/react-native-compat": "2.16.1", + "@walletconnect/react-native-compat": "2.17.1", "expo": "~51.0.24", "expo-application": "~5.9.1", "expo-clipboard": "~6.0.3", @@ -37,8 +37,8 @@ "react-native-web": "~0.19.10", "react-native-webview": "13.8.6", "uuid": "3.4.0", - "viem": "2.21.6", - "wagmi": "2.12.11" + "viem": "2.21.37", + "wagmi": "2.12.25" }, "devDependencies": { "@babel/core": "^7.24.0", diff --git a/apps/native/src/utils/WagmiUtils.ts b/apps/native/src/utils/WagmiUtils.ts index ef523b153..f2b1da93c 100644 --- a/apps/native/src/utils/WagmiUtils.ts +++ b/apps/native/src/utils/WagmiUtils.ts @@ -14,7 +14,7 @@ import { sepolia } from 'wagmi/chains'; -export const chains = [ +export const chains: CreateConfigParameters['chains'] = [ mainnet, polygon, avalanche, @@ -27,4 +27,4 @@ export const chains = [ celo, aurora, sepolia -] as CreateConfigParameters['chains']; +]; diff --git a/apps/native/src/views/ActionsView.tsx b/apps/native/src/views/ActionsView.tsx index 84b68d819..39cb70c22 100644 --- a/apps/native/src/views/ActionsView.tsx +++ b/apps/native/src/views/ActionsView.tsx @@ -26,14 +26,14 @@ export function ActionsView() { - {isSuccess && Signature: {data}} + {isSuccess && Signature: {data}} {isGasError && Error estimating gas} {isError && Error signing message} {isSending && Check Wallet} - {isSendSuccess && Transaction: {JSON.stringify(sendData)}} + {isSendSuccess && Transaction: {JSON.stringify(sendData)}} ) : null; } diff --git a/package.json b/package.json index 85e3c61ab..a837e27cc 100644 --- a/package.json +++ b/package.json @@ -69,8 +69,8 @@ "tsconfig": "*", "turbo": "2.1.1", "typescript": "5.2.2", - "viem": "2.21.6", - "wagmi": "2.12.11" + "viem": "2.21.37", + "wagmi": "2.12.25" }, "packageManager": "yarn@4.0.2" } diff --git a/packages/auth-wagmi/package.json b/packages/auth-wagmi/package.json index db5386a65..23546060d 100644 --- a/packages/auth-wagmi/package.json +++ b/packages/auth-wagmi/package.json @@ -36,6 +36,7 @@ "access": "public" }, "dependencies": { + "@reown/appkit-core-react-native": "1.0.2", "@reown/appkit-wallet-react-native": "1.0.2" }, "peerDependencies": { diff --git a/packages/auth-wagmi/src/index.ts b/packages/auth-wagmi/src/index.ts index 934b8bb72..b9859d6ab 100644 --- a/packages/auth-wagmi/src/index.ts +++ b/packages/auth-wagmi/src/index.ts @@ -1,7 +1,8 @@ import { createConnector, ChainNotConfiguredError } from 'wagmi'; -import { SwitchChainError, getAddress, type Address } from 'viem'; +import { SwitchChainError, getAddress, type Address, type Hex } from 'viem'; import { AppKitFrameProvider } from '@reown/appkit-wallet-react-native'; +import { StorageUtil } from '@reown/appkit-core-react-native'; export type Metadata = { name: string; @@ -22,13 +23,15 @@ type AuthConnectorOptions = { type Provider = AppKitFrameProvider; type StorageItemMap = { - '@w3m/connected_connector'?: string; + recentConnectorId?: string; }; authConnector.type = 'appKitAuth' as const; authConnector.id = 'appKitAuth' as const; export function authConnector(parameters: AuthConnectorOptions) { let _provider: AppKitFrameProvider = {} as AppKitFrameProvider; + let _currentAddress: Address | null = null; + let _chainId: number | null = null; return createConnector(config => ({ id: authConnector.id, @@ -39,15 +42,27 @@ export function authConnector(parameters: AuthConnectorOptions) { }, async connect(options = {}) { const provider = await this.getProvider(); + let chainId = options.chainId; await provider.webviewLoadPromise; - const { address, chainId } = await provider.connect({ chainId: options.chainId }); + + if (options.isReconnecting) { + chainId = await provider.getLastUsedChainId(); + if (!chainId) { + throw new Error('ChainId not found in provider'); + } + } + + const { address, chainId: frameChainId } = await provider.connect({ chainId }); + + _chainId = frameChainId as number; + _currentAddress = address as Address; return { - accounts: [address as Address], - account: address as Address, - chainId: chainId as number, + accounts: [_currentAddress as Address], + account: _currentAddress as Address, + chainId: frameChainId as number, chain: { - id: chainId as number, + id: frameChainId as number, unsuported: false } }; @@ -56,6 +71,8 @@ export function authConnector(parameters: AuthConnectorOptions) { const provider = await this.getProvider(); await provider.webviewLoadPromise; await provider.disconnect(); + _chainId = null; + _currentAddress = null; }, async switchChain({ chainId }) { try { @@ -64,8 +81,15 @@ export function authConnector(parameters: AuthConnectorOptions) { const provider = await this.getProvider(); await provider.webviewLoadPromise; - await provider.switchNetwork(chainId); - config.emitter.emit('change', { chainId: Number(chainId) }); + + // We connect instead, since changing the chain may cause the address to change as well + const response = await provider.connect({ chainId }); + + config.emitter.emit('change', { + chainId: Number(chainId), + accounts: [response.address as Hex] + }); + _chainId = chainId; return chain; } catch (error) { @@ -76,6 +100,8 @@ export function authConnector(parameters: AuthConnectorOptions) { } }, async getAccounts() { + if (_currentAddress) return [_currentAddress]; + const provider = await this.getProvider(); await provider.webviewLoadPromise; @@ -86,6 +112,8 @@ export function authConnector(parameters: AuthConnectorOptions) { ).map(getAddress); }, async getChainId() { + if (_chainId) return _chainId; + const provider = await this.getProvider(); await provider.webviewLoadPromise; const { chainId } = await provider.getChainId(); @@ -97,31 +125,32 @@ export function authConnector(parameters: AuthConnectorOptions) { }, async isAuthorized() { try { + const connectedConnector = await StorageUtil.getConnectedConnector(); + if (connectedConnector && connectedConnector !== 'AUTH') { + return false; + } + const provider = await this.getProvider(); await provider.webviewLoadPromise; - const connectedConnector = await config.storage?.getItem('recentConnectorId'); - - if (connectedConnector !== authConnector.id) { - // isConnected still needs to be called to disable email input loader - provider.isConnected(); + const { isConnected } = await provider.isConnected(); - return false; - } else { - const { isConnected } = await provider.isConnected(); - - return isConnected; - } + return isConnected; } catch (error) { return false; } }, onAccountsChanged(accounts) { if (accounts.length === 0) config.emitter.emit('disconnect'); - else config.emitter.emit('change', { accounts: accounts.map(getAddress) }); + else { + const account = accounts[0] ? getAddress(accounts[0]) : null; + config.emitter.emit('change', { accounts: account ? [account] : undefined }); + _currentAddress = account; + } }, onChainChanged(chain) { const chainId = Number(chain); config.emitter.emit('change', { chainId }); + _chainId = chainId; }, async onDisconnect() { const provider = await this.getProvider(); diff --git a/packages/common/src/utils/TypeUtil.ts b/packages/common/src/utils/TypeUtil.ts index ba09aaa26..208e8fee2 100644 --- a/packages/common/src/utils/TypeUtil.ts +++ b/packages/common/src/utils/TypeUtil.ts @@ -86,11 +86,4 @@ export interface TransactionQuantity { numeric: string; } -export type SocialProvider = - | 'google' - | 'github' - | 'apple' - | 'facebook' - | 'x' - | 'discord' - | 'farcaster'; +export type SocialProvider = 'apple' | 'x' | 'discord' | 'farcaster'; diff --git a/packages/core/src/__tests__/controllers/AccountController.test.ts b/packages/core/src/__tests__/controllers/AccountController.test.ts index 2398d562a..4783f804c 100644 --- a/packages/core/src/__tests__/controllers/AccountController.test.ts +++ b/packages/core/src/__tests__/controllers/AccountController.test.ts @@ -9,7 +9,9 @@ const profileImage = 'https://ipfs.com/0x123.png'; const initialState = { isConnected: false, - tokenBalance: [] + tokenBalance: [], + preferredAccountType: 'eoa', + smartAccountDeployed: false }; // -- Tests -------------------------------------------------------------------- diff --git a/packages/core/src/__tests__/controllers/NetworkController.test.ts b/packages/core/src/__tests__/controllers/NetworkController.test.ts index 5018c0657..9202383c5 100644 --- a/packages/core/src/__tests__/controllers/NetworkController.test.ts +++ b/packages/core/src/__tests__/controllers/NetworkController.test.ts @@ -16,6 +16,13 @@ const client: NetworkControllerClient = { Promise.resolve({ approvedCaipNetworkIds, supportsAllNetworks: false }) }; +const initialState = { + _client: client, + supportsAllNetworks: true, + isDefaultCaipNetwork: false, + smartAccountEnabledNetworks: [] +}; + // -- Tests -------------------------------------------------------------------- describe('NetworkController', () => { it('should throw if client not set', () => { @@ -25,11 +32,7 @@ describe('NetworkController', () => { it('should have valid default state', () => { NetworkController.setClient(client); - expect(NetworkController.state).toEqual({ - _client: NetworkController._getClient(), - supportsAllNetworks: true, - isDefaultCaipNetwork: false - }); + expect(NetworkController.state).toEqual(initialState); }); it('should update state correctly on setRequestedCaipNetworks()', () => { diff --git a/packages/core/src/controllers/AccountController.ts b/packages/core/src/controllers/AccountController.ts index 344aafb87..808d38f41 100644 --- a/packages/core/src/controllers/AccountController.ts +++ b/packages/core/src/controllers/AccountController.ts @@ -3,7 +3,7 @@ import { subscribeKey as subKey } from 'valtio/utils'; import type { Balance } from '@reown/appkit-common-react-native'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; -import type { CaipAddress, ConnectedWalletInfo } from '../utils/TypeUtil'; +import type { AppKitFrameAccountType, CaipAddress, ConnectedWalletInfo } from '../utils/TypeUtil'; import { NetworkController } from './NetworkController'; import { BlockchainApiController } from './BlockchainApiController'; import { SnackController } from './SnackController'; @@ -20,6 +20,8 @@ export interface AccountControllerState { profileImage?: string; addressExplorerUrl?: string; connectedWalletInfo?: ConnectedWalletInfo; + preferredAccountType?: AppKitFrameAccountType; + smartAccountDeployed?: boolean; } type StateKey = keyof AccountControllerState; @@ -27,7 +29,9 @@ type StateKey = keyof AccountControllerState; // -- State --------------------------------------------- // const state = proxy({ isConnected: false, - tokenBalance: [] + tokenBalance: [], + smartAccountDeployed: false, + preferredAccountType: 'eoa' }); // -- Controller ---------------------------------------- // @@ -76,6 +80,14 @@ export const AccountController = { state.addressExplorerUrl = explorerUrl; }, + setPreferredAccountType(accountType: AccountControllerState['preferredAccountType']) { + state.preferredAccountType = accountType; + }, + + setSmartAccountDeployed(smartAccountDeployed: AccountControllerState['smartAccountDeployed']) { + state.smartAccountDeployed = smartAccountDeployed; + }, + async fetchTokenBalance() { const chainId = NetworkController.state.caipNetwork?.id; const address = AccountController.state.address; @@ -110,5 +122,7 @@ export const AccountController = { state.addressExplorerUrl = undefined; state.tokenBalance = []; state.connectedWalletInfo = undefined; + state.preferredAccountType = 'eoa'; + state.smartAccountDeployed = false; } }; diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index a7b2d1b61..0e3057903 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -1,5 +1,6 @@ -import { subscribeKey as subKey } from 'valtio/utils'; import { proxy, ref } from 'valtio'; +import { subscribeKey as subKey } from 'valtio/utils'; +import type { SocialProvider } from '@reown/appkit-common-react-native'; import { CoreHelperUtil } from '../utils/CoreHelperUtil'; import { StorageUtil } from '../utils/StorageUtil'; import type { @@ -10,7 +11,6 @@ import type { } from '../utils/TypeUtil'; import { RouterController } from './RouterController'; import { ConnectorController } from './ConnectorController'; -import type { SocialProvider } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // export interface ConnectExternalOptions { @@ -86,15 +86,11 @@ export const ConnectionController = { state.wcPromise = this._getClient().connectWalletConnect(uri => { state.wcUri = uri; state.wcPairingExpiry = CoreHelperUtil.getPairingExpiry(); - ConnectorController.setConnectedConnector('WALLET_CONNECT'); - StorageUtil.setConnectedConnector('WALLET_CONNECT'); }, walletUniversalLink); }, async connectExternal(options: ConnectExternalOptions) { await this._getClient().connectExternal?.(options); - ConnectorController.setConnectedConnector(options.type); - StorageUtil.setConnectedConnector(options.type); }, async signMessage(message: string) { @@ -179,13 +175,10 @@ export const ConnectionController = { resetWcConnection() { this.clearUri(); state.pressedWallet = undefined; - state.connectedWalletImageUrl = undefined; - state.connectedSocialProvider = undefined; + ConnectionController.setConnectedSocialProvider(undefined); + ConnectionController.setConnectedWalletImageUrl(undefined); ConnectorController.setConnectedConnector(undefined); StorageUtil.removeWalletConnectDeepLink(); - StorageUtil.removeConnectedWalletImageUrl(); - StorageUtil.removeConnectedConnector(); - StorageUtil.removeConnectedSocialProvider(); }, async disconnect() { diff --git a/packages/core/src/controllers/ConnectorController.ts b/packages/core/src/controllers/ConnectorController.ts index c634ae52e..a74a4c1c5 100644 --- a/packages/core/src/controllers/ConnectorController.ts +++ b/packages/core/src/controllers/ConnectorController.ts @@ -1,6 +1,7 @@ import { subscribeKey as subKey } from 'valtio/utils'; import { proxy, ref } from 'valtio'; import type { Connector, ConnectorType } from '../utils/TypeUtil'; +import { StorageUtil } from '../utils/StorageUtil'; // -- Types --------------------------------------------- // export interface ConnectorControllerState { @@ -40,8 +41,19 @@ export const ConnectorController = { return state.connectors.find(c => c.type === 'AUTH'); }, - setConnectedConnector(connectorType: ConnectorControllerState['connectedConnector']) { + setConnectedConnector( + connectorType: ConnectorControllerState['connectedConnector'], + saveStorage = true + ) { state.connectedConnector = connectorType; + + if (saveStorage) { + if (connectorType) { + StorageUtil.setConnectedConnector(connectorType); + } else { + StorageUtil.removeConnectedConnector(); + } + } }, setAuthLoading(loading: ConnectorControllerState['authLoading']) { diff --git a/packages/core/src/controllers/NetworkController.ts b/packages/core/src/controllers/NetworkController.ts index 1468cba9f..b45c01088 100644 --- a/packages/core/src/controllers/NetworkController.ts +++ b/packages/core/src/controllers/NetworkController.ts @@ -1,6 +1,7 @@ import { proxy, ref } from 'valtio'; import type { CaipNetwork, CaipNetworkId } from '../utils/TypeUtil'; import { PublicStateController } from './PublicStateController'; +import { NetworkUtil } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // export interface NetworkControllerClient { @@ -18,12 +19,14 @@ export interface NetworkControllerState { caipNetwork?: CaipNetwork; requestedCaipNetworks?: CaipNetwork[]; approvedCaipNetworkIds?: CaipNetworkId[]; + smartAccountEnabledNetworks: number[]; } // -- State --------------------------------------------- // const state = proxy({ supportsAllNetworks: true, - isDefaultCaipNetwork: false + isDefaultCaipNetwork: false, + smartAccountEnabledNetworks: [] }); // -- Controller ---------------------------------------- // @@ -57,6 +60,22 @@ export const NetworkController = { state.requestedCaipNetworks = requestedNetworks; }, + setSmartAccountEnabledNetworks( + smartAccountEnabledNetworks: NetworkControllerState['smartAccountEnabledNetworks'] + ) { + state.smartAccountEnabledNetworks = smartAccountEnabledNetworks; + }, + + checkIfSmartAccountEnabled() { + const networkId = NetworkUtil.caipNetworkIdToNumber(state.caipNetwork?.id); + + if (!networkId) { + return false; + } + + return Boolean(state.smartAccountEnabledNetworks?.includes(Number(networkId))); + }, + async getApprovedCaipNetworksData() { const data = await this._getClient().getApprovedCaipNetworksData(); state.supportsAllNetworks = data.supportsAllNetworks; @@ -81,5 +100,6 @@ export const NetworkController = { } state.approvedCaipNetworkIds = undefined; state.supportsAllNetworks = true; + state.smartAccountEnabledNetworks = []; } }; diff --git a/packages/core/src/controllers/OptionsController.ts b/packages/core/src/controllers/OptionsController.ts index acbaba1c6..7140589ae 100644 --- a/packages/core/src/controllers/OptionsController.ts +++ b/packages/core/src/controllers/OptionsController.ts @@ -4,6 +4,7 @@ import type { Features, Metadata, ProjectId, + SdkType, SdkVersion, Tokens } from '../utils/TypeUtil'; @@ -23,7 +24,7 @@ export interface OptionsControllerState { customWallets?: CustomWallet[]; tokens?: Tokens; enableAnalytics?: boolean; - sdkType: string; + sdkType: SdkType; sdkVersion: SdkVersion; metadata?: Metadata; isSiweEnabled?: boolean; diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index 92fd80a73..a472288ed 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -35,6 +35,7 @@ export interface RouterControllerState { | 'UpdateEmailSecondaryOtp' | 'UpdateEmailWallet' | 'UpgradeEmailWallet' + | 'UpgradeToSmartAccount' | 'WalletCompatibleNetworks' | 'WalletReceive' | 'WalletSend' diff --git a/packages/core/src/controllers/SendController.ts b/packages/core/src/controllers/SendController.ts index ab97b33ab..3aa0141e1 100644 --- a/packages/core/src/controllers/SendController.ts +++ b/packages/core/src/controllers/SendController.ts @@ -97,7 +97,7 @@ export const SendController = { type: 'track', event: 'SEND_INITIATED', properties: { - isSmartAccount: false, + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount', token: this.state.token.address, amount: this.state.sendTokenAmount, network: NetworkController.state.caipNetwork?.id || '' @@ -120,7 +120,7 @@ export const SendController = { type: 'track', event: 'SEND_INITIATED', properties: { - isSmartAccount: false, + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount', token: this.state.token?.symbol, amount: this.state.sendTokenAmount, network: NetworkController.state.caipNetwork?.id || '' @@ -162,7 +162,7 @@ export const SendController = { type: 'track', event: 'SEND_SUCCESS', properties: { - isSmartAccount: false, + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount', token: this.state.token?.symbol || '', amount: params.sendTokenAmount, network: NetworkController.state.caipNetwork?.id || '' @@ -175,7 +175,7 @@ export const SendController = { type: 'track', event: 'SEND_ERROR', properties: { - isSmartAccount: false, + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount', token: this.state.token?.symbol || '', amount: params.sendTokenAmount, network: NetworkController.state.caipNetwork?.id || '' diff --git a/packages/core/src/controllers/TransactionsController.ts b/packages/core/src/controllers/TransactionsController.ts index af350299f..333f1d5c9 100644 --- a/packages/core/src/controllers/TransactionsController.ts +++ b/packages/core/src/controllers/TransactionsController.ts @@ -5,6 +5,7 @@ import { EventsController } from './EventsController'; import { SnackController } from './SnackController'; import { NetworkController } from './NetworkController'; import { BlockchainApiController } from './BlockchainApiController'; +import { AccountController } from './AccountController'; // -- Types --------------------------------------------- // type TransactionByMonthMap = Record; @@ -74,7 +75,7 @@ export const TransactionsController = { address: accountAddress, projectId, cursor: state.next, - isSmartAccount: false + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' } }); SnackController.showError('Failed to fetch transactions'); diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index a4f7ea25f..8e537210f 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -3,7 +3,7 @@ import type { Features } from './TypeUtil'; const defaultFeatures: Features = { email: true, emailShowWallets: true, - socials: ['x', 'discord', 'github', 'apple', 'facebook', 'farcaster'] + socials: ['x', 'discord', 'apple', 'farcaster'] }; export const ConstantsUtil = { diff --git a/packages/core/src/utils/StorageUtil.ts b/packages/core/src/utils/StorageUtil.ts index 6bf3218d2..e340d58b6 100644 --- a/packages/core/src/utils/StorageUtil.ts +++ b/packages/core/src/utils/StorageUtil.ts @@ -92,7 +92,7 @@ export const StorageUtil = { } }, - async getConnectedConnector() { + async getConnectedConnector(): Promise { try { const connector = (await AsyncStorage.getItem(CONNECTED_CONNECTOR)) as ConnectorType; diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 03ddc6f84..6d7ce82a0 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -56,6 +56,8 @@ export type CaipNamespaces = Record< } >; +export type SdkType = 'appkit'; + export type SdkVersion = | `react-native-wagmi-${string}` | `react-native-ethers5-${string}` @@ -375,18 +377,34 @@ export type Event = | { type: 'track'; event: 'CLICK_SIGN_SIWE_MESSAGE'; + properties: { + network: string; + isSmartAccount: boolean; + }; } | { type: 'track'; event: 'CLICK_CANCEL_SIWE'; + properties: { + network: string; + isSmartAccount: boolean; + }; } | { type: 'track'; event: 'SIWE_AUTH_SUCCESS'; + properties: { + network: string; + isSmartAccount: boolean; + }; } | { type: 'track'; event: 'SIWE_AUTH_ERROR'; + properties: { + network: string; + isSmartAccount: boolean; + }; } | { type: 'track'; @@ -473,6 +491,14 @@ export type Event = properties: { provider: SocialProvider; }; + } + | { + type: 'track'; + event: 'SET_PREFERRED_ACCOUNT_TYPE'; + properties: { + accountType: AppKitFrameAccountType; + network: string; + }; }; // -- Send Controller Types ------------------------------------- @@ -500,6 +526,9 @@ export interface WriteContractArgs { * Matches type defined for packages/wallet/src/AppKitFrameProvider.ts * It's duplicated in order to decouple scaffold from email package */ + +export type AppKitFrameAccountType = 'eoa' | 'smartAccount'; + export interface AppKitFrameProvider { readonly id: string; readonly name: string; @@ -510,6 +539,7 @@ export interface AppKitFrameProvider { getSecureSiteHeaders(): Record; getEmail(): string | undefined; getUsername(): string | undefined; + getLastUsedChainId(): Promise; rejectRpcRequest(): void; connectEmail(payload: { email: string }): Promise<{ action: 'VERIFY_DEVICE' | 'VERIFY_OTP'; @@ -519,19 +549,17 @@ export interface AppKitFrameProvider { chainId: string | number; email: string; address: string; - accounts?: - | { - type: 'eoa' | 'smartAccount'; - address: string; - }[] - | undefined; - userName?: string | undefined; + accounts?: { + type: AppKitFrameAccountType; + address: string; + }[]; + userName?: string; }>; getSocialRedirectUri(payload: { provider: SocialProvider }): Promise<{ uri: string; }>; connectOtp(payload: { otp: string }): Promise; - connectFarcaster: () => Promise<{ username: string }>; + connectFarcaster: () => Promise<{ userName: string }>; getFarcasterUri(): Promise<{ url: string }>; isConnected(): Promise<{ isConnected: boolean; @@ -553,18 +581,31 @@ export interface AppKitFrameProvider { syncDappData(payload: { projectId: string; sdkVersion: SdkVersion; + sdkType: SdkType; metadata?: Metadata; }): Promise; connect(payload?: { chainId: number | undefined }): Promise<{ chainId: number; - email: string; + email?: string | null; address: string; + smartAccountDeployed: boolean; + preferredAccountType: AppKitFrameAccountType; }>; switchNetwork(chainId: number): Promise<{ chainId: number; }>; + setPreferredAccount(type: AppKitFrameAccountType): Promise<{ + type: AppKitFrameAccountType; + address: string; + }>; + getSmartAccountEnabledNetworks(): Promise<{ + smartAccountEnabledNetworks: number[]; + }>; disconnect(): Promise; request(req: any): Promise; AuthView: () => JSX.Element | null; Webview: () => JSX.Element | null; + onSetPreferredAccount: ( + callback: (values: { type: AppKitFrameAccountType; address: string }) => void + ) => void; } diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index 6f6659de9..3147c523c 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -24,7 +24,8 @@ import { type SendTransactionArgs, type Token, AppKitScaffold, - type WriteContractArgs + type WriteContractArgs, + type AppKitFrameAccountType } from '@reown/appkit-scaffold-react-native'; import { erc20ABI, NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; import { @@ -126,9 +127,9 @@ export class AppKit extends AppKitScaffold { new Promise(async resolve => { const walletChoice = await StorageUtil.getConnectedConnector(); const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; + PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]!; - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; + const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; if (walletChoice?.includes(walletConnectType)) { const provider = await this.getWalletConnectProvider(); const result = getWalletConnectCaipNetworks(provider); @@ -399,8 +400,8 @@ export class AppKit extends AppKitScaffold { this.createProvider(); - EthersStoreUtil.subscribeKey('address', () => { - this.syncAccount(); + EthersStoreUtil.subscribeKey('address', address => { + this.syncAccount({ address }); }); EthersStoreUtil.subscribeKey('chainId', () => { @@ -681,12 +682,10 @@ export class AppKit extends AppKitScaffold { } } - private async syncAccount() { - const address = EthersStoreUtil.state.address; + private async syncAccount({ address }: { address?: Address }) { const chainId = EthersStoreUtil.state.chainId; const isConnected = EthersStoreUtil.state.isConnected; - this.resetAccount(); if (isConnected && address && chainId) { const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; @@ -701,6 +700,7 @@ export class AppKit extends AppKitScaffold { ]); this.hasSyncedConnectedAccount = true; } else if (!isConnected && this.hasSyncedConnectedAccount) { + this.resetAccount(); this.resetWcConnection(); this.resetNetwork(); } @@ -874,6 +874,20 @@ export class AppKit extends AppKitScaffold { } } + private async handleAuthSetPreferredAccount(address: string, type: AppKitFrameAccountType) { + if (!address) { + return; + } + + const chainId = this.getCaipNetwork()?.id; + const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; + this.setCaipAddress(caipAddress); + this.setPreferredAccountType(type); + + await this.syncAccount({ address: address as Address }); + this.setLoading(false); + } + private syncConnectors(config: ProviderType) { const _connectors: Connector[] = []; const EXCLUDED_CONNECTORS = [ConstantsUtil.AUTH_CONNECTOR_ID]; @@ -939,5 +953,16 @@ export class AppKit extends AppKitScaffold { if (isConnected) { this.setAuthProvider(); } + + this.addAuthListeners(this.authProvider); + } + + private async addAuthListeners(authProvider: AppKitFrameProvider) { + authProvider.onSetPreferredAccount(async ({ address, type }) => { + if (address) { + await this.handleAuthSetPreferredAccount(address, type); + } + this.setLoading(false); + }); } } diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index cb38f6a79..f700f5f8a 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -1,5 +1,6 @@ import { Contract, ethers, utils } from 'ethers'; import { + type AppKitFrameAccountType, type CaipAddress, type CaipNetwork, type CaipNetworkId, @@ -113,9 +114,9 @@ export class AppKit extends AppKitScaffold { new Promise(async resolve => { const walletChoice = await StorageUtil.getConnectedConnector(); const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; + PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]!; - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; + const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; if (walletChoice?.includes(walletConnectType)) { const provider = await this.getWalletConnectProvider(); const result = getWalletConnectCaipNetworks(provider); @@ -381,8 +382,8 @@ export class AppKit extends AppKitScaffold { this.createProvider(); - EthersStoreUtil.subscribeKey('address', () => { - this.syncAccount(); + EthersStoreUtil.subscribeKey('address', address => { + this.syncAccount({ address }); }); EthersStoreUtil.subscribeKey('chainId', () => { @@ -661,12 +662,10 @@ export class AppKit extends AppKitScaffold { } } - private async syncAccount() { - const address = EthersStoreUtil.state.address; + private async syncAccount({ address }: { address?: Address }) { const chainId = EthersStoreUtil.state.chainId; const isConnected = EthersStoreUtil.state.isConnected; - this.resetAccount(); if (isConnected && address && chainId) { const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; @@ -681,6 +680,7 @@ export class AppKit extends AppKitScaffold { ]); this.hasSyncedConnectedAccount = true; } else if (!isConnected && this.hasSyncedConnectedAccount) { + this.resetAccount(); this.resetWcConnection(); this.resetNetwork(); } @@ -853,6 +853,18 @@ export class AppKit extends AppKitScaffold { } } + private async handleAuthSetPreferredAccount(address: string, type: AppKitFrameAccountType) { + if (!address) { + return; + } + const chainId = this.getCaipNetwork()?.id; + const caipAddress: CaipAddress = `${ConstantsUtil.EIP155}:${chainId}:${address}`; + this.setCaipAddress(caipAddress); + this.setPreferredAccountType(type); + await this.syncAccount({ address: address as Address }); + this.setLoading(false); + } + private syncConnectors(config: ProviderType) { const _connectors: Connector[] = []; const EXCLUDED_CONNECTORS = [ConstantsUtil.AUTH_CONNECTOR_ID]; @@ -918,5 +930,16 @@ export class AppKit extends AppKitScaffold { if (isConnected) { this.setAuthProvider(); } + + this.addAuthListeners(this.authProvider); + } + + private async addAuthListeners(authProvider: AppKitFrameProvider) { + authProvider.onSetPreferredAccount(async ({ address, type }) => { + if (address) { + await this.handleAuthSetPreferredAccount(address, type); + } + this.setLoading(false); + }); } } diff --git a/packages/scaffold/src/client.ts b/packages/scaffold/src/client.ts index 0878c85df..18b863c2c 100644 --- a/packages/scaffold/src/client.ts +++ b/packages/scaffold/src/client.ts @@ -228,6 +228,11 @@ export class AppKitScaffold { BlockchainApiController.setClientId(clientId); }; + protected setPreferredAccountType: (typeof AccountController)['setPreferredAccountType'] = + preferredAccountType => { + AccountController.setPreferredAccountType(preferredAccountType); + }; + // -- Private ------------------------------------------------------------------ private async initControllers(options: ScaffoldOptions) { this.initAsyncValues(options); @@ -311,7 +316,7 @@ export class AppKitScaffold { private async initConnectedConnector() { const connectedConnector = await StorageUtil.getConnectedConnector(); if (connectedConnector) { - ConnectorController.setConnectedConnector(connectedConnector); + ConnectorController.setConnectedConnector(connectedConnector, false); } } diff --git a/packages/scaffold/src/modal/w3m-modal/index.tsx b/packages/scaffold/src/modal/w3m-modal/index.tsx index 13b93def5..eae3b380f 100644 --- a/packages/scaffold/src/modal/w3m-modal/index.tsx +++ b/packages/scaffold/src/modal/w3m-modal/index.tsx @@ -27,7 +27,7 @@ import styles from './styles'; export function AppKit() { const { open, loading } = useSnapshot(ModalController.state); - const { connectors } = useSnapshot(ConnectorController.state); + const { connectors, connectedConnector } = useSnapshot(ConnectorController.state); const { caipAddress, isConnected } = useSnapshot(AccountController.state); const { frameViewVisible, webviewVisible } = useSnapshot(WebviewController.state); const { isSiweEnabled } = OptionsController.state; @@ -38,6 +38,7 @@ export function AppKit() { const authProvider = connectors.find(c => c.type === 'AUTH')?.provider as AppKitFrameProvider; const AuthView = authProvider?.AuthView; const SocialView = authProvider?.Webview; + const showAuth = !connectedConnector || connectedConnector === 'AUTH'; const onBackButtonPress = () => { if (RouterController.state.history.length > 1) { @@ -70,7 +71,7 @@ export function AppKit() { const newAddress = CoreHelperUtil.getPlainAddress(address); TransactionsController.resetTransactions(); - TransactionsController.fetchTransactions(newAddress); + TransactionsController.fetchTransactions(newAddress, true); if (isSiweEnabled) { const newNetworkId = CoreHelperUtil.getNetworkId(address); @@ -136,8 +137,8 @@ export function AppKit() { - {!!authProvider && AuthView && } - {!!authProvider && SocialView && } + {!!showAuth && AuthView && } + {!!showAuth && SocialView && } ); } diff --git a/packages/scaffold/src/modal/w3m-router/index.tsx b/packages/scaffold/src/modal/w3m-router/index.tsx index ff7f694e8..5cc2a4a4f 100644 --- a/packages/scaffold/src/modal/w3m-router/index.tsx +++ b/packages/scaffold/src/modal/w3m-router/index.tsx @@ -6,9 +6,12 @@ import { AccountDefaultView } from '../../views/w3m-account-default-view'; import { AccountView } from '../../views/w3m-account-view'; import { AllWalletsView } from '../../views/w3m-all-wallets-view'; import { ConnectView } from '../../views/w3m-connect-view'; +import { ConnectSocialsView } from '../../views/w3m-connect-socials-view'; import { ConnectingView } from '../../views/w3m-connecting-view'; import { ConnectingExternalView } from '../../views/w3m-connecting-external-view'; +import { ConnectingFarcasterView } from '../../views/w3m-connecting-farcaster-view'; import { ConnectingSocialView } from '../../views/w3m-connecting-social-view'; +import { CreateView } from '../../views/w3m-create-view'; import { ConnectingSiweView } from '@reown/appkit-siwe-react-native'; import { EmailVerifyOtpView } from '../../views/w3m-email-verify-otp-view'; import { EmailVerifyDeviceView } from '../../views/w3m-email-verify-device-view'; @@ -19,6 +22,7 @@ import { UpdateEmailWalletView } from '../../views/w3m-update-email-wallet-view' import { UpdateEmailPrimaryOtpView } from '../../views/w3m-update-email-primary-otp-view'; import { UpdateEmailSecondaryOtpView } from '../../views/w3m-update-email-secondary-otp-view'; import { UpgradeEmailWalletView } from '../../views/w3m-upgrade-email-wallet-view'; +import { UpgradeToSmartAccountView } from '../../views/w3m-upgrade-to-smart-account-view'; import { TransactionsView } from '../../views/w3m-transactions-view'; import { WalletCompatibleNetworks } from '../../views/w3m-wallet-compatible-networks-view'; import { WalletReceiveView } from '../../views/w3m-wallet-receive-view'; @@ -29,9 +33,6 @@ import { WhatIsANetworkView } from '../../views/w3m-what-is-a-network-view'; import { WhatIsAWalletView } from '../../views/w3m-what-is-a-wallet-view'; import { UiUtil } from '../../utils/UiUtil'; -import { ConnectingFarcasterView } from '../../views/w3m-connecting-farcaster-view'; -import { ConnectSocialsView } from '../../views/w3m-connect-socials-view'; -import { CreateView } from '../../views/w3m-create-view'; export function AppKitRouter() { const { view } = useSnapshot(RouterController.state); @@ -84,6 +85,8 @@ export function AppKitRouter() { return UpdateEmailWalletView; case 'UpgradeEmailWallet': return UpgradeEmailWalletView; + case 'UpgradeToSmartAccount': + return UpgradeToSmartAccountView; case 'WalletCompatibleNetworks': return WalletCompatibleNetworks; case 'WalletReceive': diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/scaffold/src/partials/w3m-account-activity/index.tsx index 6c5f2a89b..567b3acd9 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-activity/index.tsx @@ -43,7 +43,7 @@ export function AccountActivity({ style }: Props) { address: AccountController.state.address, projectId: OptionsController.state.projectId, cursor: TransactionsController.state.next, - isSmartAccount: false + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' } }); }; diff --git a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx index bbc8b7972..a1627cb94 100644 --- a/packages/scaffold/src/partials/w3m-account-tokens/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-tokens/index.tsx @@ -1,5 +1,11 @@ import { useCallback, useState } from 'react'; -import { RefreshControl, ScrollView, type StyleProp, type ViewStyle } from 'react-native'; +import { + RefreshControl, + ScrollView, + StyleSheet, + type StyleProp, + type ViewStyle +} from 'react-native'; import { useSnapshot } from 'valtio'; import { AccountController, @@ -7,7 +13,14 @@ import { NetworkController, RouterController } from '@reown/appkit-core-react-native'; -import { FlexView, ListItem, Text, ListToken, useTheme } from '@reown/appkit-ui-react-native'; +import { + FlexView, + ListItem, + Text, + ListToken, + useTheme, + Spacing +} from '@reown/appkit-ui-react-native'; interface Props { style?: StyleProp; @@ -32,7 +45,12 @@ export function AccountTokens({ style }: Props) { if (!tokenBalance?.length) { return ( - + Receive funds @@ -72,3 +90,10 @@ export function AccountTokens({ style }: Props) { ); } + +const styles = StyleSheet.create({ + receiveButton: { + width: 'auto', + marginHorizontal: Spacing.s + } +}); diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx index b9a35a3ad..b0a084d19 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx @@ -35,7 +35,7 @@ export function AccountWalletFeatures() { type: 'track', event: 'CLICK_TRANSACTIONS', properties: { - isSmartAccount: false + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' } }); }; @@ -46,7 +46,7 @@ export function AccountWalletFeatures() { event: 'OPEN_SEND', properties: { network: NetworkController.state.caipNetwork?.id || '', - isSmartAccount: false + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' } }); RouterController.push('WalletSend'); diff --git a/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx b/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx index f3ea27a44..bfb615385 100644 --- a/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx +++ b/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx @@ -56,18 +56,18 @@ export function AllWalletsList({ columns, itemWidth, onItemPress }: AllWalletsLi ); }; - const walletTemplate = ({ item, index }: { item: WcWallet; index: number }) => { + const walletTemplate = ({ item }: { item: WcWallet; index: number }) => { const isInstalled = ApiController.state.installed.find(wallet => wallet?.id === item?.id); if (!item?.id) { return ( - + ); } return ( - + { @@ -30,7 +32,7 @@ export function Header() { Connect: 'Connect wallet', ConnectSocials: 'All socials', ConnectingExternal: connectorName ?? 'Connect wallet', - ConnectingSiwe: 'Sign In', + ConnectingSiwe: undefined, ConnectingFarcaster: socialName ?? 'Connecting Social', ConnectingSocial: socialName ?? 'Connecting Social', ConnectingWalletConnect: walletName ?? 'WalletConnect', @@ -45,6 +47,7 @@ export function Header() { UpdateEmailSecondaryOtp: 'Confirm new email', UpdateEmailWallet: 'Edit email', UpgradeEmailWallet: 'Upgrade wallet', + UpgradeToSmartAccount: undefined, WalletCompatibleNetworks: 'Compatible networks', WalletReceive: 'Receive', WalletSend: 'Send', @@ -58,10 +61,13 @@ export function Header() { const header = headings(data, view); const dynamicButtonTemplate = () => { - const hideBackViews = ['ConnectingSiwe']; - const showBack = - RouterController.state.history.length > 1 && - !hideBackViews.includes(RouterController.state.view); + const noButtonViews = ['ConnectingSiwe']; + + if (noButtonViews.includes(RouterController.state.view)) { + return ; + } + + const showBack = RouterController.state.history.length > 1; return showBack ? ( 1; + const showSwitchAccountType = isAuth && NetworkController.checkIfSmartAccountEnabled(); const { padding } = useCustomDimensions(); async function onDisconnect() { @@ -66,6 +75,29 @@ export function AccountDefaultView() { } } + const onSwitchAccountType = async () => { + try { + if (isAuth) { + ModalController.setLoading(true); + const accountType = + AccountController.state.preferredAccountType === 'eoa' ? 'smartAccount' : 'eoa'; + const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + await provider?.setPreferredAccount(accountType); + EventsController.sendEvent({ + type: 'track', + event: 'SET_PREFERRED_ACCOUNT_TYPE', + properties: { + accountType, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + } + } catch (error) { + ModalController.setLoading(false); + SnackController.showError('Error switching account type'); + } + }; + const getUserEmail = () => { const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; if (!provider) return ''; @@ -215,6 +247,20 @@ export function AccountDefaultView() { Activity )} + {showSwitchAccountType && ( + + {`Switch to your ${ + preferredAccountType === 'eoa' ? 'smart account' : 'EOA' + }`} + + )} { if (OptionsController.isClipboardAvailable() && value) { @@ -46,6 +51,10 @@ export function AccountView() { RouterController.push('Networks'); }; + const onActivatePress = () => { + RouterController.push('UpgradeToSmartAccount'); + }; + useEffect(() => { AccountController.fetchTokenBalance(); SendController.resetSend(); @@ -72,6 +81,9 @@ export function AccountView() { + {showActivate && ( + + )} (); + const [url, setUrl] = useState(); const showCopy = OptionsController.isClipboardAvailable(); const socialProvider = data?.socialProvider; const provider = authConnector?.provider as AppKitFrameProvider; @@ -37,9 +37,9 @@ export function ConnectingFarcasterView() { try { if (!WebviewController.state.connecting && provider && socialProvider && authConnector) { setError(false); - const { url } = await provider.getFarcasterUri(); - setUrl(url); - Linking.openURL(url); + const { url: farcasterUrl } = await provider.getFarcasterUri(); + setUrl(farcasterUrl); + Linking.openURL(farcasterUrl); await provider.connectFarcaster(); await ConnectionController.connectExternal(authConnector); ConnectionController.setConnectedSocialProvider(socialProvider); @@ -63,8 +63,8 @@ export function ConnectingFarcasterView() { }, [provider, socialProvider, authConnector]); const onCopyUrl = () => { - if (_url) { - OptionsController.copyToClipboard(_url); + if (url) { + OptionsController.copyToClipboard(url); SnackController.showSuccess('Link copied'); } }; diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx index 50a8af39d..a644dd464 100644 --- a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx @@ -54,6 +54,7 @@ export function ConnectingSocialView() { const parsedUrl = new URL(url); await provider?.connectSocial(parsedUrl.search); await ConnectionController.connectExternal(authConnector); + ConnectorController.setConnectedConnector('AUTH'); ConnectionController.setConnectedSocialProvider(socialProvider); WebviewController.setConnecting(false); diff --git a/packages/scaffold/src/views/w3m-connecting-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-view/index.tsx index 7034ddd9f..82a501ce6 100644 --- a/packages/scaffold/src/views/w3m-connecting-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-view/index.tsx @@ -11,7 +11,8 @@ import { type Platform, OptionsController, ApiController, - EventsController + EventsController, + ConnectorController } from '@reown/appkit-core-react-native'; import { ConnectingQrCode } from '../../partials/w3m-connecting-qrcode'; @@ -48,6 +49,7 @@ export function ConnectingView() { ConnectionController.setWcError(false); ConnectionController.connectWalletConnect(routeData?.wallet?.link_mode ?? undefined); await ConnectionController.state.wcPromise; + ConnectorController.setConnectedConnector('WALLET_CONNECT'); AccountController.setIsConnected(true); if (OptionsController.state.isSiweEnabled) { diff --git a/packages/scaffold/src/views/w3m-network-switch-view/index.tsx b/packages/scaffold/src/views/w3m-network-switch-view/index.tsx index 52d653071..7c3739df9 100644 --- a/packages/scaffold/src/views/w3m-network-switch-view/index.tsx +++ b/packages/scaffold/src/views/w3m-network-switch-view/index.tsx @@ -5,6 +5,7 @@ import { ApiController, AssetUtil, ConnectionController, + ConnectorController, EventsController, NetworkController, RouterController @@ -23,6 +24,7 @@ export function NetworkSwitchView() { const { data } = useSnapshot(RouterController.state); const { recentWallets } = useSnapshot(ConnectionController.state); const { caipNetwork } = useSnapshot(NetworkController.state); + const isAuthConnected = ConnectorController.state.connectedConnector === 'AUTH'; const [error, setError] = useState(false); const [showRetry, setShowRetry] = useState(false); const network = data?.network!; @@ -90,6 +92,14 @@ export function NetworkSwitchView() { ); } + if (isAuthConnected) { + return ( + + Switching to {network.name} network + + ); + } + return ( <> {`Approve in ${walletName}`} diff --git a/packages/scaffold/src/views/w3m-networks-view/index.tsx b/packages/scaffold/src/views/w3m-networks-view/index.tsx index 774e8c8df..2803d2382 100644 --- a/packages/scaffold/src/views/w3m-networks-view/index.tsx +++ b/packages/scaffold/src/views/w3m-networks-view/index.tsx @@ -16,7 +16,8 @@ import { type CaipNetwork, AccountController, EventsController, - RouterUtil + RouterUtil, + ConnectorController } from '@reown/appkit-core-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; @@ -32,6 +33,7 @@ export function NetworksView() { const itemGap = Math.abs( Math.trunc((usableWidth - numColumns * CardSelectWidth) / numColumns) / 2 ); + const isAuthConnected = ConnectorController.state.connectedConnector === 'AUTH'; const onHelpPress = () => { RouterController.push('WhatIsANetwork'); @@ -55,7 +57,7 @@ export function NetworksView() { const onNetworkPress = async (network: CaipNetwork) => { if (AccountController.state.isConnected && caipNetwork?.id !== network.id) { - if (approvedCaipNetworkIds?.includes(network.id)) { + if (approvedCaipNetworkIds?.includes(network.id) && !isAuthConnected) { await NetworkController.switchActiveNetwork(network); RouterUtil.navigateAfterNetworkSwitch(['ConnectingSiwe']); @@ -66,7 +68,7 @@ export function NetworksView() { network: network.id } }); - } else if (supportsAllNetworks) { + } else if (supportsAllNetworks || isAuthConnected) { RouterController.push('SwitchNetwork', { network }); } } else if (!AccountController.state.isConnected) { diff --git a/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx index d3239180a..6b66cf5f2 100644 --- a/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx +++ b/packages/scaffold/src/views/w3m-upgrade-email-wallet-view/index.tsx @@ -18,7 +18,7 @@ export function UpgradeEmailWalletView() { Follow the instructions on { + try { + ModalController.setLoading(true); + const accountType = + AccountController.state.preferredAccountType === 'eoa' ? 'smartAccount' : 'eoa'; + const provider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + await provider?.setPreferredAccount(accountType); + EventsController.sendEvent({ + type: 'track', + event: 'SET_PREFERRED_ACCOUNT_TYPE', + properties: { + accountType, + network: NetworkController.state.caipNetwork?.id || '' + } + }); + } catch (error) { + ModalController.setLoading(false); + SnackController.showError('Error switching account type'); + } + }; + + const onClose = () => { + ModalController.close(); + ModalController.setLoading(false); + }; + + const onGoBack = () => { + RouterController.goBack(); + ModalController.setLoading(false); + }; + + const onLearnMorePress = () => { + Linking.openURL('https://reown.com/faq'); + }; + + useEffect(() => { + // Go back if the address has changed + if (address && initialAddress !== address) { + RouterController.goBack(); + } + }, [initialAddress, address]); + + return ( + <> + + + + + + + + + + Discover Smart Accounts + + + Access advanced brand new features as username, improved security and a smoother user + experience! + + + + + + + Learn more + + + + ); +} diff --git a/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts b/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts new file mode 100644 index 000000000..f2d23ef07 --- /dev/null +++ b/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/styles.ts @@ -0,0 +1,29 @@ +import { Spacing } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; + +export default StyleSheet.create({ + container: { + justifyContent: 'center', + alignItems: 'center' + }, + title: { + marginTop: Spacing.xl, + marginVertical: Spacing.s + }, + button: { + width: 110 + }, + cancelButton: { + marginRight: Spacing.m + }, + middleIcon: { + marginHorizontal: Spacing.s + }, + closeButton: { + alignSelf: 'flex-end', + right: Spacing.xl, + top: Spacing.l, + position: 'absolute', + zIndex: 2 + } +}); diff --git a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx index 0c03c449a..2e8c061b6 100644 --- a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx @@ -1,21 +1,33 @@ import { ScrollView } from 'react-native'; +import { useSnapshot } from 'valtio'; import { FlexView, Text, Banner, NetworkImage } from '@reown/appkit-ui-react-native'; -import { ApiController, AssetUtil, NetworkController } from '@reown/appkit-core-react-native'; +import { + AccountController, + ApiController, + AssetUtil, + NetworkController +} from '@reown/appkit-core-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; export function WalletCompatibleNetworks() { const { padding } = useCustomDimensions(); - const approvedNetworks = NetworkController.getApprovedCaipNetworks(); + const { preferredAccountType } = useSnapshot(AccountController.state); + const { caipNetwork } = useSnapshot(NetworkController.state); + const isSmartAccount = + preferredAccountType === 'smartAccount' && NetworkController.checkIfSmartAccountEnabled(); + const approvedNetworks = isSmartAccount + ? [caipNetwork] + : NetworkController.getApprovedCaipNetworks(); const imageHeaders = ApiController._getApiHeaders(); return ( - {approvedNetworks.map(network => ( + {approvedNetworks.map((network, index) => ( - {network.name} + {network?.name ?? 'Unknown Network'} ))} diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx index c5cf27c9d..47b613d0b 100644 --- a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx @@ -21,7 +21,7 @@ import { import { useCustomDimensions } from '../../hooks/useCustomDimensions'; export function WalletReceiveView() { - const { address, profileName } = useSnapshot(AccountController.state); + const { address, profileName, preferredAccountType } = useSnapshot(AccountController.state); const { caipNetwork } = useSnapshot(NetworkController.state); const networkImage = AssetUtil.getNetworkImage(caipNetwork); const { padding } = useCustomDimensions(); @@ -31,6 +31,8 @@ export function WalletReceiveView() { .filter(network => network?.imageId) ?.slice(0, 5) || []; const imagesArray = slicedNetworks.map(AssetUtil.getNetworkImage).filter(Boolean) as string[]; + const isSmartAccount = + preferredAccountType === 'smartAccount' && NetworkController.checkIfSmartAccountEnabled(); const label = UiUtil.getTruncateString({ string: profileName ?? address ?? '', @@ -69,7 +71,9 @@ export function WalletReceiveView() { diff --git a/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx b/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx index b3c477003..ea085ecd2 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-preview-view/components/preview-send-pill.tsx @@ -31,6 +31,6 @@ export function PreviewSendPill({ text, children }: PreviewSendPillProps) { const styles = StyleSheet.create({ pill: { borderRadius: BorderRadius.full, - borderWidth: 1 + borderWidth: StyleSheet.hairlineWidth } }); diff --git a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx index 80175219d..5d7a1c295 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-select-token-view/index.tsx @@ -12,6 +12,7 @@ import { import type { Balance } from '@reown/appkit-common-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { AccountPlaceholder } from '../../partials/w3m-account-placeholder'; import styles from './styles'; export function WalletSendSelectTokenView() { @@ -54,18 +55,26 @@ export function WalletSendSelectTokenView() { Your tokens - {filteredTokens.map((token, index) => ( - onTokenPress(token)} + {filteredTokens.length ? ( + filteredTokens.map((token, index) => ( + onTokenPress(token)} + /> + )) + ) : ( + - ))} + )} ); diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts b/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts index 7fded2195..7e52d3f4f 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts +++ b/packages/scaffold/src/views/w3m-wallet-send-view/styles.ts @@ -10,12 +10,6 @@ export default StyleSheet.create({ tokenInput: { marginBottom: Spacing.xs }, - mockInput: { - width: '100%', - borderWidth: 1, - height: 120, - borderRadius: 20 - }, arrowIcon: { position: 'absolute', top: -30, diff --git a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx index b7955c678..db9825873 100644 --- a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx +++ b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx @@ -1,10 +1,11 @@ import { useSnapshot } from 'valtio'; -import { Button, FlexView, Text } from '@reown/appkit-ui-react-native'; +import { Button, FlexView, IconLink, Text } from '@reown/appkit-ui-react-native'; import { AccountController, ConnectionController, EventsController, ModalController, + NetworkController, OptionsController, RouterController, SnackController @@ -26,14 +27,22 @@ export function ConnectingSiweView() { setIsSigning(true); EventsController.sendEvent({ event: 'CLICK_SIGN_SIWE_MESSAGE', - type: 'track' + type: 'track', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } }); try { const session = await SIWEController.signIn(); EventsController.sendEvent({ event: 'SIWE_AUTH_SUCCESS', - type: 'track' + type: 'track', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } }); return session; @@ -44,7 +53,11 @@ export function ConnectingSiweView() { return EventsController.sendEvent({ event: 'SIWE_AUTH_ERROR', - type: 'track' + type: 'track', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } }); } finally { setIsSigning(false); @@ -63,12 +76,26 @@ export function ConnectingSiweView() { } EventsController.sendEvent({ event: 'CLICK_CANCEL_SIWE', - type: 'track' + type: 'track', + properties: { + network: NetworkController.state.caipNetwork?.id || '', + isSmartAccount: AccountController.state.preferredAccountType === 'smartAccount' + } }); }; return ( + + + Sign in + {dappName} needs to connect to your wallet diff --git a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/styles.ts b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/styles.ts index 900ef0370..42d56456f 100644 --- a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/styles.ts +++ b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/styles.ts @@ -3,6 +3,7 @@ import { StyleSheet } from 'react-native'; export default StyleSheet.create({ logoContainer: { + marginTop: Spacing.xl, marginBottom: Spacing.m }, button: { @@ -14,5 +15,12 @@ export default StyleSheet.create({ subtitle: { marginHorizontal: '10%', marginVertical: Spacing.l + }, + closeButton: { + alignSelf: 'flex-end', + right: Spacing.xl, + top: Spacing.l, + position: 'absolute', + zIndex: 2 } }); diff --git a/packages/ui/src/__tests__/wui-text.test.tsx b/packages/ui/src/__tests__/wui-text.test.tsx index e39bae272..7475ecb83 100644 --- a/packages/ui/src/__tests__/wui-text.test.tsx +++ b/packages/ui/src/__tests__/wui-text.test.tsx @@ -1,6 +1,15 @@ import { render } from '@testing-library/react-native'; +import { configureInternal } from '@testing-library/react-native/build/config'; import { Text } from '../components/wui-text'; +configureInternal({ + hostComponentNames: { + text: 'RCTText', + textInput: 'RCTTextInput', + switch: 'RCTSwitch' + } +}); + test('Text render', () => { const label = 'Hello World'; const { getAllByText } = render({label}); diff --git a/packages/ui/src/assets/visual/Google.tsx b/packages/ui/src/assets/visual/Google.tsx new file mode 100644 index 000000000..486a89013 --- /dev/null +++ b/packages/ui/src/assets/visual/Google.tsx @@ -0,0 +1,43 @@ +import Svg, { Path, Rect, type SvgProps } from 'react-native-svg'; +const GoogleSvg = (props: SvgProps) => ( + + + + + + + + + + +); + +export default GoogleSvg; diff --git a/packages/ui/src/assets/visual/Lightbulb.tsx b/packages/ui/src/assets/visual/Lightbulb.tsx new file mode 100644 index 000000000..545d436be --- /dev/null +++ b/packages/ui/src/assets/visual/Lightbulb.tsx @@ -0,0 +1,54 @@ +import Svg, { ClipPath, Defs, G, Path, Rect, type SvgProps } from 'react-native-svg'; +const LightbulbSvg = (props: SvgProps) => ( + + + + + + + + + + + + + + + + + +); + +export default LightbulbSvg; diff --git a/packages/ui/src/assets/visual/Pencil.tsx b/packages/ui/src/assets/visual/Pencil.tsx new file mode 100644 index 000000000..08d189244 --- /dev/null +++ b/packages/ui/src/assets/visual/Pencil.tsx @@ -0,0 +1,80 @@ +import Svg, { ClipPath, Defs, G, Path, Rect, type SvgProps } from 'react-native-svg'; + +const PencilSvg = (props: SvgProps) => ( + + + + + + + + + + + + + + + + + + + +); + +export default PencilSvg; diff --git a/packages/ui/src/components/wui-card/styles.ts b/packages/ui/src/components/wui-card/styles.ts index b7f5974f8..86e49e888 100644 --- a/packages/ui/src/components/wui-card/styles.ts +++ b/packages/ui/src/components/wui-card/styles.ts @@ -4,7 +4,7 @@ import { BorderRadius } from '../../utils/ThemeUtil'; export default StyleSheet.create({ container: { borderRadius: BorderRadius.l, - borderWidth: 1, + borderWidth: StyleSheet.hairlineWidth, overflow: 'hidden' } }); diff --git a/packages/ui/src/components/wui-lean-text/index.tsx b/packages/ui/src/components/wui-lean-text/index.tsx new file mode 100644 index 000000000..98fe33a25 --- /dev/null +++ b/packages/ui/src/components/wui-lean-text/index.tsx @@ -0,0 +1,10 @@ +import { type ComponentType, createElement, forwardRef } from 'react'; +import type { TextProps } from 'react-native'; + +const LeanText = forwardRef((props, ref) => { + return createElement('RCTText', { ...props, ref }); +}) as ComponentType; + +LeanText.displayName = 'RCTText'; + +export { LeanText }; diff --git a/packages/ui/src/components/wui-lean-view/index.tsx b/packages/ui/src/components/wui-lean-view/index.tsx new file mode 100644 index 000000000..8bcda5fdb --- /dev/null +++ b/packages/ui/src/components/wui-lean-view/index.tsx @@ -0,0 +1,10 @@ +import { type ComponentType, createElement, forwardRef } from 'react'; +import type { ViewProps } from 'react-native'; + +const LeanView = forwardRef((props, ref) => { + return createElement('RCTView', { ...props, ref }); +}) as ComponentType; + +LeanView.displayName = 'RCTView'; + +export { LeanView }; diff --git a/packages/ui/src/components/wui-text/index.tsx b/packages/ui/src/components/wui-text/index.tsx index 41d32ef1c..a728a292f 100644 --- a/packages/ui/src/components/wui-text/index.tsx +++ b/packages/ui/src/components/wui-text/index.tsx @@ -1,5 +1,6 @@ -import { Text as NativeText, type TextProps as NativeProps } from 'react-native'; +import { type TextProps as NativeProps } from 'react-native'; import { useTheme } from '../../hooks/useTheme'; +import { LeanText } from '../wui-lean-text'; import type { ColorType, TextType } from '../../utils/TypesUtil'; import styles from './styles'; @@ -20,7 +21,7 @@ export function Text({ const Theme = useTheme(); return ( - {children} - + ); } diff --git a/packages/ui/src/components/wui-visual/index.tsx b/packages/ui/src/components/wui-visual/index.tsx index c6b849cb0..99830e391 100644 --- a/packages/ui/src/components/wui-visual/index.tsx +++ b/packages/ui/src/components/wui-visual/index.tsx @@ -14,6 +14,9 @@ import NounSvg from '../../assets/visual/Noun'; import ProfileSvg from '../../assets/visual/Profile'; import SystemSvg from '../../assets/visual/System'; import type { VisualType } from '../../utils/TypesUtil'; +import GoogleSvg from '../../assets/visual/Google'; +import LightbulbSvg from '../../assets/visual/Lightbulb'; +import PencilSvg from '../../assets/visual/Pencil'; const svgOptions: Record JSX.Element> = { browser: BrowserSvg, @@ -21,12 +24,15 @@ const svgOptions: Record JSX.Element> = { defi: DefiSvg, defiAlt: DefiAltSvg, eth: EthSvg, + google: GoogleSvg, layers: LayersSvg, + lightbulb: LightbulbSvg, lock: LockSvg, login: LoginSvg, network: NetworkSvg, nft: NftSvg, noun: NounSvg, + pencil: PencilSvg, profile: ProfileSvg, system: SystemSvg }; diff --git a/packages/ui/src/composites/wui-account-button/styles.ts b/packages/ui/src/composites/wui-account-button/styles.ts index 1eda1c32b..2e1441204 100644 --- a/packages/ui/src/composites/wui-account-button/styles.ts +++ b/packages/ui/src/composites/wui-account-button/styles.ts @@ -6,7 +6,7 @@ export default StyleSheet.create({ flexDirection: 'row', height: 40, borderRadius: BorderRadius.full, - borderWidth: 1, + borderWidth: StyleSheet.hairlineWidth, justifyContent: 'center', alignItems: 'center', paddingHorizontal: Spacing['3xs'] @@ -36,7 +36,7 @@ export default StyleSheet.create({ paddingLeft: Spacing['3xs'], paddingRight: Spacing.xs, borderRadius: BorderRadius.full, - borderWidth: 1 + borderWidth: StyleSheet.hairlineWidth }, address: { marginLeft: Spacing['3xs'] diff --git a/packages/ui/src/composites/wui-account-pill/index.tsx b/packages/ui/src/composites/wui-account-pill/index.tsx index 9b7ebd886..3f3bfc163 100644 --- a/packages/ui/src/composites/wui-account-pill/index.tsx +++ b/packages/ui/src/composites/wui-account-pill/index.tsx @@ -66,7 +66,13 @@ export function AccountPill({ truncate: 'middle' })} - + ); } diff --git a/packages/ui/src/composites/wui-account-pill/styles.ts b/packages/ui/src/composites/wui-account-pill/styles.ts index 75d118b60..7be825c94 100644 --- a/packages/ui/src/composites/wui-account-pill/styles.ts +++ b/packages/ui/src/composites/wui-account-pill/styles.ts @@ -11,9 +11,12 @@ export default StyleSheet.create({ alignItems: 'center', justifyContent: 'center', borderRadius: BorderRadius.full, - borderWidth: 1 + borderWidth: StyleSheet.hairlineWidth }, text: { marginLeft: Spacing['2xs'] + }, + copyButton: { + marginHorizontal: Spacing['3xs'] } }); diff --git a/packages/ui/src/composites/wui-button/styles.ts b/packages/ui/src/composites/wui-button/styles.ts index 8da1107cf..b9330c059 100644 --- a/packages/ui/src/composites/wui-button/styles.ts +++ b/packages/ui/src/composites/wui-button/styles.ts @@ -62,7 +62,7 @@ export default StyleSheet.create({ alignItems: 'center', justifyContent: 'center', borderRadius: BorderRadius.xs, - borderWidth: 1 + borderWidth: StyleSheet.hairlineWidth }, smButton: { height: 32, diff --git a/packages/ui/src/composites/wui-chip/styles.ts b/packages/ui/src/composites/wui-chip/styles.ts index 1d7552b2e..ac3e04789 100644 --- a/packages/ui/src/composites/wui-chip/styles.ts +++ b/packages/ui/src/composites/wui-chip/styles.ts @@ -65,11 +65,11 @@ export default StyleSheet.create({ paddingHorizontal: Spacing.xs, alignItems: 'center', borderRadius: BorderRadius.s, - borderWidth: 1 + borderWidth: StyleSheet.hairlineWidth }, image: { borderRadius: BorderRadius.full, - borderWidth: 1 + borderWidth: StyleSheet.hairlineWidth }, smImage: { width: 16, diff --git a/packages/ui/src/composites/wui-compatible-network/index.tsx b/packages/ui/src/composites/wui-compatible-network/index.tsx index af94c9358..3e1420f9e 100644 --- a/packages/ui/src/composites/wui-compatible-network/index.tsx +++ b/packages/ui/src/composites/wui-compatible-network/index.tsx @@ -13,9 +13,6 @@ export interface CompatibleNetworkProps { style?: StyleProp; } -const offset = [20, 15, 10, 5, 0]; -const zIndex = [5, 4, 3, 2, 1]; - export function CompatibleNetwork({ text, onPress, @@ -44,7 +41,7 @@ export function CompatibleNetwork({ imageHeaders={imageHeaders} borderColor={Theme['bg-200']} borderWidth={2} - style={{ left: offset[index], zIndex: zIndex[index] }} + style={[styles.item, { zIndex: networkImages.length - index }]} /> ))} @@ -60,5 +57,8 @@ const styles = StyleSheet.create({ flexDirection: 'row', justifyContent: 'space-between', paddingRight: 0 + }, + item: { + marginLeft: -5 } }); diff --git a/packages/ui/src/composites/wui-connect-button/styles.ts b/packages/ui/src/composites/wui-connect-button/styles.ts index 572cdbc20..8804ce23e 100644 --- a/packages/ui/src/composites/wui-connect-button/styles.ts +++ b/packages/ui/src/composites/wui-connect-button/styles.ts @@ -51,7 +51,7 @@ export default StyleSheet.create({ alignItems: 'center', justifyContent: 'center', borderRadius: BorderRadius.s, - borderWidth: 1 + borderWidth: StyleSheet.hairlineWidth }, smButton: { height: 32 diff --git a/packages/ui/src/composites/wui-input-text/styles.ts b/packages/ui/src/composites/wui-input-text/styles.ts index 2cf4398e6..4b18dffa5 100644 --- a/packages/ui/src/composites/wui-input-text/styles.ts +++ b/packages/ui/src/composites/wui-input-text/styles.ts @@ -7,7 +7,7 @@ const baseStyle = { borderRadius: BorderRadius.xxs, alignItems: 'center', paddingHorizontal: Spacing.xs, - borderWidth: 1 + borderWidth: StyleSheet.hairlineWidth } as ViewStyle; export const outerBorderRadius = { diff --git a/packages/ui/src/composites/wui-list-wallet/index.tsx b/packages/ui/src/composites/wui-list-wallet/index.tsx index 58b8a01b7..c8a28665d 100644 --- a/packages/ui/src/composites/wui-list-wallet/index.tsx +++ b/packages/ui/src/composites/wui-list-wallet/index.tsx @@ -1,4 +1,4 @@ -import { Animated, Pressable, View, type StyleProp, type ViewStyle } from 'react-native'; +import { Animated, Pressable, type StyleProp, type ViewStyle } from 'react-native'; import { Text } from '../../components/wui-text'; import useAnimatedValue from '../../hooks/useAnimatedValue'; import { useTheme } from '../../hooks/useTheme'; @@ -6,9 +6,10 @@ import type { IconType, TagType } from '../../utils/TypesUtil'; import { Tag } from '../wui-tag'; import { WalletImage } from '../wui-wallet-image'; import { Icon } from '../../components/wui-icon'; +import { LeanView } from '../../components/wui-lean-view'; +import { IconBox } from '../wui-icon-box'; import styles from './styles'; -import { IconBox } from '../wui-icon-box'; const AnimatedPressable = Animated.createAnimatedComponent(Pressable); @@ -67,7 +68,7 @@ export function ListWallet({ function imageTemplate() { return ( - + {templateInstalled()} - + ); } @@ -116,12 +117,12 @@ export function ListWallet({ onPressOut={setStartValue} testID={testID} > - + {imageTemplate()} {name} - + {iconTemplate()} ); diff --git a/packages/ui/src/composites/wui-network-button/styles.ts b/packages/ui/src/composites/wui-network-button/styles.ts index 199f2d29b..f2166e82e 100644 --- a/packages/ui/src/composites/wui-network-button/styles.ts +++ b/packages/ui/src/composites/wui-network-button/styles.ts @@ -7,7 +7,7 @@ export default StyleSheet.create({ height: 40, alignItems: 'center', justifyContent: 'center', - borderWidth: 1, + borderWidth: StyleSheet.hairlineWidth, borderRadius: BorderRadius.full, paddingHorizontal: Spacing['2xs'] }, diff --git a/packages/ui/src/composites/wui-otp/index.tsx b/packages/ui/src/composites/wui-otp/index.tsx index 868101c5b..ebf83152c 100644 --- a/packages/ui/src/composites/wui-otp/index.tsx +++ b/packages/ui/src/composites/wui-otp/index.tsx @@ -3,8 +3,7 @@ import { type NativeSyntheticEvent, TextInput, type TextInputKeyPressEventData, - View, - Platform + View } from 'react-native'; import { InputNumeric, type InputNumericProps } from '../wui-input-numeric'; import styles from './styles'; @@ -90,7 +89,7 @@ export function Otp({ length, style, onChangeText, autoFocus }: OtpProps) { inputRef={refArray[index]} onChangeText={text => _onChangeText(text, index)} onKeyPress={(e: any) => onKeyPress(e, index)} - autoComplete={Platform.OS === 'android' ? 'sms-otp' : 'one-time-code'} + autoComplete="off" /> ))} diff --git a/packages/ui/src/composites/wui-promo/index.tsx b/packages/ui/src/composites/wui-promo/index.tsx new file mode 100644 index 000000000..19a3597c9 --- /dev/null +++ b/packages/ui/src/composites/wui-promo/index.tsx @@ -0,0 +1,42 @@ +import { Pressable, StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; +import { Icon } from '../../components/wui-icon'; +import { Text } from '../../components/wui-text'; +import { useTheme } from '../../hooks/useTheme'; +import { FlexView } from '../../layout/wui-flex'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export interface PromoProps { + text: string; + style?: StyleProp; + onPress?: () => void; +} + +export function Promo({ text, style, onPress }: PromoProps) { + const Theme = useTheme(); + + return ( + + + + {text} + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + borderRadius: BorderRadius.full + }, + icon: { + marginLeft: Spacing['2xs'] + } +}); diff --git a/packages/ui/src/composites/wui-snackbar/styles.ts b/packages/ui/src/composites/wui-snackbar/styles.ts index 23c1b9bae..7c0ced601 100644 --- a/packages/ui/src/composites/wui-snackbar/styles.ts +++ b/packages/ui/src/composites/wui-snackbar/styles.ts @@ -7,7 +7,7 @@ export default StyleSheet.create({ flexDirection: 'row', paddingHorizontal: 8, borderRadius: BorderRadius.full, - borderWidth: 1, + borderWidth: StyleSheet.hairlineWidth, alignItems: 'center' }, text: { diff --git a/packages/ui/src/composites/wui-tabs/styles.ts b/packages/ui/src/composites/wui-tabs/styles.ts index 6a81b4fb3..0fdd348e6 100644 --- a/packages/ui/src/composites/wui-tabs/styles.ts +++ b/packages/ui/src/composites/wui-tabs/styles.ts @@ -22,7 +22,7 @@ export default StyleSheet.create({ activeMark: { position: 'absolute', height: 28, - borderWidth: 1, + borderWidth: StyleSheet.hairlineWidth, borderRadius: BorderRadius['3xl'], margin: Spacing['3xs'] } diff --git a/packages/ui/src/composites/wui-wallet-image/styles.ts b/packages/ui/src/composites/wui-wallet-image/styles.ts index 610fd4b63..1a0f1f5b3 100644 --- a/packages/ui/src/composites/wui-wallet-image/styles.ts +++ b/packages/ui/src/composites/wui-wallet-image/styles.ts @@ -32,7 +32,7 @@ export default StyleSheet.create({ borderRadius: BorderRadius.m }, border: { - borderWidth: 1, + borderWidth: StyleSheet.hairlineWidth, position: 'absolute' } }); diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index 317211753..ddea358d4 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -49,6 +49,7 @@ export { LogoSelect, type LogoSelectProps } from './composites/wui-logo-select'; export { NetworkButton, type NetworkButtonProps } from './composites/wui-network-button'; export { NetworkImage, type NetworkImageProps } from './composites/wui-network-image'; export { Otp, type OtpProps } from './composites/wui-otp'; +export { Promo, type PromoProps } from './composites/wui-promo'; export { QrCode, type QrCodeProps } from './composites/wui-qr-code'; export { SearchBar, type SearchBarProps } from './composites/wui-search-bar'; export { Snackbar, type SnackbarProps } from './composites/wui-snackbar'; diff --git a/packages/ui/src/layout/wui-flex/index.tsx b/packages/ui/src/layout/wui-flex/index.tsx index fdee104bf..c810bd6e1 100644 --- a/packages/ui/src/layout/wui-flex/index.tsx +++ b/packages/ui/src/layout/wui-flex/index.tsx @@ -1,4 +1,4 @@ -import { View, type StyleProp, type ViewStyle } from 'react-native'; +import { type StyleProp, type ViewStyle } from 'react-native'; import type { FlexAlignType, @@ -9,8 +9,8 @@ import type { FlexWrapType, SpacingType } from '../../utils/TypesUtil'; - import { UiUtil } from '../../utils/UiUtil'; +import { LeanView } from '../../components/wui-lean-view'; export interface FlexViewProps { children?: React.ReactNode; @@ -45,5 +45,5 @@ export function FlexView(props: FlexViewProps) { marginLeft: props.margin && UiUtil.getSpacingStyles(props.margin, 3) }; - return {props.children}; + return {props.children}; } diff --git a/packages/ui/src/layout/wui-separator/index.tsx b/packages/ui/src/layout/wui-separator/index.tsx index 701d00316..3dd59ff8b 100644 --- a/packages/ui/src/layout/wui-separator/index.tsx +++ b/packages/ui/src/layout/wui-separator/index.tsx @@ -1,5 +1,6 @@ -import { View, type StyleProp, type ViewStyle } from 'react-native'; +import { type StyleProp, type ViewStyle } from 'react-native'; import { Text } from '../../components/wui-text'; +import { LeanView } from '../../components/wui-lean-view'; import { FlexView } from '../../layout/wui-flex'; import { useTheme } from '../../hooks/useTheme'; import styles from './styles'; @@ -13,18 +14,20 @@ export function Separator({ text, style }: SeparatorProps) { const Theme = useTheme(); if (!text) { - return ; + return ( + + ); } return ( - {text} - diff --git a/packages/ui/src/utils/TypesUtil.ts b/packages/ui/src/utils/TypesUtil.ts index d1e264cab..a6f323441 100644 --- a/packages/ui/src/utils/TypesUtil.ts +++ b/packages/ui/src/utils/TypesUtil.ts @@ -96,6 +96,15 @@ export type ColorType = | 'fg-250' | 'fg-275' | 'fg-300' + | 'bg-100' + | 'bg-125' + | 'bg-150' + | 'bg-175' + | 'bg-200' + | 'bg-225' + | 'bg-250' + | 'bg-275' + | 'bg-300' | 'accent-glass-020' | 'accent-glass-015' | 'accent-glass-010' @@ -186,12 +195,15 @@ export type VisualType = | 'defi' | 'defiAlt' | 'eth' + | 'google' | 'layers' + | 'lightbulb' | 'lock' | 'login' | 'network' | 'nft' | 'noun' + | 'pencil' | 'profile' | 'system'; diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index 0f45eb838..a44975182 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -2,7 +2,9 @@ import { formatUnits, type Hex, parseUnits } from 'viem'; import { type GetAccountReturnType, type GetEnsAddressReturnType, + type Connector as WagmiConnector, connect, + reconnect, disconnect, signMessage, getAccount, @@ -33,7 +35,8 @@ import { type SendTransactionArgs, type Token, AppKitScaffold, - type WriteContractArgs + type WriteContractArgs, + type AppKitFrameProvider } from '@reown/appkit-scaffold-react-native'; import { ConstantsUtil, @@ -101,9 +104,9 @@ export class AppKit extends AppKitScaffold { async getApprovedCaipNetworksData() { const walletChoice = await StorageUtil.getConnectedConnector(); const walletConnectType = - PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]; + PresetsUtil.ConnectorTypesMap[ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID]!; - const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; + const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]!; if (walletChoice?.includes(walletConnectType)) { const connector = wagmiConfig.connectors.find( @@ -340,7 +343,6 @@ export class AppKit extends AppKitScaffold { this.syncRequestedNetworks([...wagmiConfig.chains]); this.syncConnectors([...wagmiConfig.connectors]); - this.listenAuthConnector([...wagmiConfig.connectors]); watchConnectors(wagmiConfig, { onChange: connectors => this.syncConnectors([...connectors]) @@ -400,7 +402,6 @@ export class AppKit extends AppKitScaffold { GetAccountReturnType, 'address' | 'isConnected' | 'chainId' | 'connector' | 'isConnecting' | 'isReconnecting' >) { - this.resetAccount(); this.syncNetwork(address, chainId, isConnected); this.setLoading(!!connector && (isConnecting || isReconnecting)); @@ -416,6 +417,7 @@ export class AppKit extends AppKitScaffold { ]); this.hasSyncedConnectedAccount = true; } else if (!isConnected && this.hasSyncedConnectedAccount) { + this.resetAccount(); this.resetWcConnection(); this.resetNetwork(); } @@ -554,15 +556,21 @@ export class AppKit extends AppKitScaffold { name: 'Auth', provider }); + this.addAuthListeners(authConnector); } } - private async listenAuthConnector(connectors: AppKitClientOptions['wagmiConfig']['connectors']) { - const connector = connectors.find(c => c.id === ConstantsUtil.AUTH_CONNECTOR_ID); - + private async addAuthListeners(connector: WagmiConnector) { const connectedConnector = await StorageUtil.getItem('@w3m/connected_connector'); if (connector && connectedConnector === 'AUTH') { super.setLoading(true); + + const provider = (await connector.getProvider()) as AppKitFrameProvider; + + provider.onSetPreferredAccount(async () => { + await reconnect(this.wagmiConfig, { connectors: [connector] }); + this.setLoading(false); + }); } } } diff --git a/packages/wallet/src/AppKitAuthWebview.tsx b/packages/wallet/src/AppKitAuthWebview.tsx index cadd052e8..c77d79d1c 100644 --- a/packages/wallet/src/AppKitAuthWebview.tsx +++ b/packages/wallet/src/AppKitAuthWebview.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { useEffect, useRef, useState } from 'react'; +import { memo, useEffect, useRef, useState } from 'react'; import { Animated, Appearance, Linking, Platform, SafeAreaView, StyleSheet } from 'react-native'; import { WebView, type WebViewMessageEvent } from 'react-native-webview'; @@ -8,9 +8,11 @@ import { OptionsController, ModalController, type OptionsControllerState, - StorageUtil, RouterController, - WebviewController + WebviewController, + AccountController, + NetworkController, + ConnectionController } from '@reown/appkit-core-react-native'; import { useTheme, BorderRadius } from '@reown/appkit-ui-react-native'; import type { AppKitFrameProvider } from './AppKitFrameProvider'; @@ -20,11 +22,13 @@ import type { AppKitFrameTypes } from './AppKitFrameTypes'; const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView); -export function AuthWebview() { +function _AuthWebview() { const webviewRef = useRef(null); const Theme = useTheme(); const authConnector = ConnectorController.getAuthConnector(); - const { projectId, sdkVersion } = useSnapshot(OptionsController.state) as OptionsControllerState; + const { projectId, sdkVersion, sdkType } = useSnapshot( + OptionsController.state + ) as OptionsControllerState; const { frameViewVisible } = useSnapshot(WebviewController.state); const [isBackdropVisible, setIsBackdropVisible] = useState(false); const animatedHeight = useRef(new Animated.Value(0)); @@ -48,53 +52,11 @@ export function AuthWebview() { }; const handleMessage = (e: WebViewMessageEvent) => { - let event = parseMessage(e); + try { + let event = parseMessage(e); - provider.onMessage(event); - - provider.onRpcRequest((request: AppKitFrameTypes.RPCRequest) => { - if (AppKitFrameHelpers.checkIfRequestExists(request)) { - if (!AppKitFrameHelpers.checkIfRequestIsAllowed(request)) { - WebviewController.setFrameViewVisible(true); - } - } - }); - - provider.onRpcSuccess((_, request) => { - const isSafeRequest = AppKitFrameHelpers.checkIfRequestIsSafe(request); - if (isSafeRequest) { - return; - } - - if (RouterController.state.transactionStack.length === 0) { - ModalController.close(); - } else { - RouterController?.popTransactionStack(); - } - WebviewController.setFrameViewVisible(false); - }); - - provider.onRpcError(() => { - if (ModalController.state.open) { - if (RouterController.state.transactionStack.length === 0) { - ModalController.close(); - } else { - RouterController?.popTransactionStack(true); - } - } - WebviewController.setFrameViewVisible(false); - }); - - provider.onIsConnected(event, () => { - ConnectorController.setAuthLoading(false); - ModalController.setLoading(false); - }); - - provider.onNotConnected(event, () => { - ConnectorController.setAuthLoading(false); - ModalController.setLoading(false); - StorageUtil.removeConnectedConnector(); - }); + provider.onMessage(event); + } catch (error) {} }; const show = animatedHeight.current.interpolate({ @@ -102,6 +64,8 @@ export function AuthWebview() { outputRange: ['0%', '80%'] }); + useEffect(() => {}, [provider]); + useEffect(() => { Animated.timing(animatedHeight.current, { toValue: frameViewVisible ? 1 : 0, @@ -127,7 +91,61 @@ export function AuthWebview() { }, [animatedHeight, backdropOpacity, frameViewVisible, setIsBackdropVisible]); useEffect(() => { - provider?.setWebviewRef(webviewRef); + if (provider) { + provider.setWebviewRef(webviewRef); + provider.onRpcRequest((request: AppKitFrameTypes.RPCRequest) => { + if (AppKitFrameHelpers.checkIfRequestExists(request)) { + if (!AppKitFrameHelpers.checkIfRequestIsAllowed(request)) { + WebviewController.setFrameViewVisible(true); + } + } + }); + + provider.onRpcSuccess((_, request) => { + const isSafeRequest = AppKitFrameHelpers.checkIfRequestIsSafe(request); + if (isSafeRequest) { + return; + } + + if (RouterController.state.transactionStack.length === 0) { + ModalController.close(); + } else { + RouterController?.popTransactionStack(); + } + WebviewController.setFrameViewVisible(false); + }); + + provider.onRpcError(() => { + if (ModalController.state.open) { + if (RouterController.state.transactionStack.length === 0) { + ModalController.close(); + } else { + RouterController?.popTransactionStack(true); + } + } + WebviewController.setFrameViewVisible(false); + }); + + provider.onIsConnected(({ smartAccountDeployed, preferredAccountType }) => { + provider.getSmartAccountEnabledNetworks(); + AccountController.setPreferredAccountType(preferredAccountType); + AccountController.setSmartAccountDeployed(smartAccountDeployed); + ConnectorController.setAuthLoading(false); + ModalController.setLoading(false); + }); + + provider.onNotConnected(() => { + ConnectorController.setAuthLoading(false); + ModalController.setLoading(false); + if (ConnectorController.state.connectedConnector === 'AUTH') { + ConnectionController.disconnect(); + } + }); + + provider.onGetSmartAccountEnabledNetworks(({ smartAccountEnabledNetworks }) => { + return NetworkController.setSmartAccountEnabledNetworks(smartAccountEnabledNetworks); + }); + } }, [provider, webviewRef]); return provider ? ( @@ -179,8 +197,9 @@ export function AuthWebview() { '--w3m-background': Theme['bg-100'] } }); - provider?.syncDappData?.({ projectId, sdkVersion }); + provider?.syncDappData?.({ projectId, sdkVersion, sdkType }); provider?.onWebviewLoaded(); + provider?.isConnected(); }, 1500); } }} @@ -193,6 +212,8 @@ export function AuthWebview() { ) : null; } +export const AuthWebview = memo(_AuthWebview); + const styles = StyleSheet.create({ backdrop: { position: 'absolute', diff --git a/packages/wallet/src/AppKitFrameConstants.ts b/packages/wallet/src/AppKitFrameConstants.ts index ddf30ed71..ac1e4844a 100644 --- a/packages/wallet/src/AppKitFrameConstants.ts +++ b/packages/wallet/src/AppKitFrameConstants.ts @@ -1,8 +1,8 @@ export const AppKitFrameConstants = { SECURE_SITE_SDK: 'https://secure-mobile.walletconnect.com/mobile-sdk', SECURE_SITE_ORIGIN: 'https://secure.walletconnect.com', - SECURE_SITE_DASHBOARD: `https://secure.walletconnect.com/dashboard`, - SECURE_SITE_ICON: `https://secure.walletconnect.com/images/favicon.png`, + SECURE_SITE_DASHBOARD: `https://secure.reown.com/dashboard`, + SECURE_SITE_ICON: `https://secure.reown.com/images/favicon.png`, APP_EVENT_KEY: '@w3m-app/', FRAME_EVENT_KEY: '@w3m-frame/', RPC_METHOD_KEY: 'RPC_', @@ -14,6 +14,7 @@ export const AppKitFrameConstants = { LAST_EMAIL_LOGIN_TIME: 'LAST_EMAIL_LOGIN_TIME', // Also present in core/src/utils/StorageUtil.ts EMAIL: 'EMAIL', SOCIAL_USERNAME: 'SOCIAL_USERNAME', + SMART_ACCOUNT_ENABLED_NETWORKS: 'SMART_ACCOUNT_ENABLED_NETWORKS', FRAME_MESSAGES_HANDLER: ` window.addEventListener('message', ({ data, origin }) => { @@ -40,6 +41,8 @@ export const AppKitFrameConstants = { APP_SYNC_DAPP_DATA: '@w3m-app/SYNC_DAPP_DATA', APP_CONNECT_FARCASTER: '@w3m-app/CONNECT_FARCASTER', APP_GET_FARCASTER_URI: '@w3m-app/GET_FARCASTER_URI', + APP_GET_SMART_ACCOUNT_ENABLED_NETWORKS: '@w3m-app/GET_SMART_ACCOUNT_ENABLED_NETWORKS', + APP_SET_PREFERRED_ACCOUNT: '@w3m-app/SET_PREFERRED_ACCOUNT', FRAME_SWITCH_NETWORK_ERROR: '@w3m-frame/SWITCH_NETWORK_ERROR', FRAME_SWITCH_NETWORK_SUCCESS: '@w3m-frame/SWITCH_NETWORK_SUCCESS', @@ -77,7 +80,13 @@ export const AppKitFrameConstants = { FRAME_SYNC_THEME_SUCCESS: '@w3m-frame/SYNC_THEME_SUCCESS', FRAME_SYNC_THEME_ERROR: '@w3m-frame/SYNC_THEME_ERROR', FRAME_SYNC_DAPP_DATA_SUCCESS: '@w3m-frame/SYNC_DAPP_DATA_SUCCESS', - FRAME_SYNC_DAPP_DATA_ERROR: '@w3m-frame/SYNC_DAPP_DATA_ERROR' + FRAME_SYNC_DAPP_DATA_ERROR: '@w3m-frame/SYNC_DAPP_DATA_ERROR', + FRAME_GET_SMART_ACCOUNT_ENABLED_NETWORKS_SUCCESS: + '@w3m-frame/GET_SMART_ACCOUNT_ENABLED_NETWORKS_SUCCESS', + FRAME_GET_SMART_ACCOUNT_ENABLED_NETWORKS_ERROR: + '@w3m-frame/GET_SMART_ACCOUNT_ENABLED_NETWORKS_ERROR', + FRAME_SET_PREFERRED_ACCOUNT_SUCCESS: '@w3m-frame/SET_PREFERRED_ACCOUNT_SUCCESS', + FRAME_SET_PREFERRED_ACCOUNT_ERROR: '@w3m-frame/SET_PREFERRED_ACCOUNT_ERROR' } as const; export const AppKitFrameRpcConstants = { diff --git a/packages/wallet/src/AppKitFrameProvider.ts b/packages/wallet/src/AppKitFrameProvider.ts index bb8096ac1..9ae9fb2a8 100644 --- a/packages/wallet/src/AppKitFrameProvider.ts +++ b/packages/wallet/src/AppKitFrameProvider.ts @@ -55,13 +55,9 @@ export class AppKitFrameProvider { this.metadata = metadata; this.projectId = projectId; - this.getAsyncEmail().then(email => { - this.email = email; - }); + this.loadAsyncValues(); - this.getAsyncUsername().then(username => { - this.username = username; - }); + this.events.setMaxListeners(Number.POSITIVE_INFINITY); } public setWebviewRef(webviewRef: RefObject) { @@ -69,7 +65,6 @@ export class AppKitFrameProvider { } public onMessage(event: AppKitFrameTypes.FrameEvent) { - // console.log('💻 received', event); // eslint-disable-line no-console this.events.emit('message', event); } @@ -146,48 +141,36 @@ export class AppKitFrameProvider { } public async connectSocial(uri: string) { - try { - const response = await this.appEvent<'ConnectSocial'>({ - type: AppKitFrameConstants.APP_CONNECT_SOCIAL, - payload: { uri } - } as AppKitFrameTypes.AppEvent); - - if (response.userName) { - this.setSocialLoginSuccess(response.userName); - } + const response = await this.appEvent<'ConnectSocial'>({ + type: AppKitFrameConstants.APP_CONNECT_SOCIAL, + payload: { uri } + } as AppKitFrameTypes.AppEvent); - return response; - } catch (error) { - throw error; + if (response.userName) { + this.setSocialLoginSuccess(response.userName); } + + return response; } public async getFarcasterUri() { - try { - const response = await this.appEvent<'GetFarcasterUri'>({ - type: AppKitFrameConstants.APP_GET_FARCASTER_URI - } as AppKitFrameTypes.AppEvent); + const response = await this.appEvent<'GetFarcasterUri'>({ + type: AppKitFrameConstants.APP_GET_FARCASTER_URI + } as AppKitFrameTypes.AppEvent); - return response; - } catch (error) { - throw error; - } + return response; } public async connectFarcaster() { - try { - const response = await this.appEvent<'ConnectFarcaster'>({ - type: AppKitFrameConstants.APP_CONNECT_FARCASTER - } as AppKitFrameTypes.AppEvent); - - if (response.userName) { - this.setSocialLoginSuccess(response.userName); - } + const response = await this.appEvent<'ConnectFarcaster'>({ + type: AppKitFrameConstants.APP_CONNECT_FARCASTER + } as AppKitFrameTypes.AppEvent); - return response; - } catch (error) { - throw error; + if (response.userName) { + this.setSocialLoginSuccess(response.userName); } + + return response; } public async connectOtp(payload: AppKitFrameTypes.Requests['AppConnectOtpRequest']) { @@ -231,14 +214,10 @@ export class AppKitFrameProvider { public async getSocialRedirectUri( payload: AppKitFrameTypes.Requests['AppGetSocialRedirectUriRequest'] ) { - try { - return this.appEvent<'GetSocialRedirectUri'>({ - type: AppKitFrameConstants.APP_GET_SOCIAL_REDIRECT_URI, - payload - } as AppKitFrameTypes.AppEvent); - } catch (error) { - throw error; - } + return this.appEvent<'GetSocialRedirectUri'>({ + type: AppKitFrameConstants.APP_GET_SOCIAL_REDIRECT_URI, + payload + } as AppKitFrameTypes.AppEvent); } public async updateEmail(payload: AppKitFrameTypes.Requests['AppUpdateEmailRequest']) { @@ -306,6 +285,29 @@ export class AppKitFrameProvider { return response; } + public async getSmartAccountEnabledNetworks() { + try { + const response = await this.appEvent<'GetSmartAccountEnabledNetworks'>({ + type: AppKitFrameConstants.APP_GET_SMART_ACCOUNT_ENABLED_NETWORKS + } as AppKitFrameTypes.AppEvent); + this.persistSmartAccountEnabledNetworks(response.smartAccountEnabledNetworks); + + return response; + } catch (error) { + this.persistSmartAccountEnabledNetworks([]); + throw error; + } + } + + public async setPreferredAccount(type: AppKitFrameTypes.AccountType) { + const response = await this.appEvent<'SetPreferredAccount'>({ + type: AppKitFrameConstants.APP_SET_PREFERRED_ACCOUNT, + payload: { type } + } as AppKitFrameTypes.AppEvent); + + return response; + } + // -- Provider Methods ------------------------------------------------ public async connect(payload?: AppKitFrameTypes.Requests['AppGetUserRequest']) { const lastUsedChain = await this.getLastUsedChainId(); @@ -385,24 +387,18 @@ export class AppKitFrameProvider { this.rpcErrorHandler = callback; } - public onRpcResponse(event: AppKitFrameTypes.FrameEvent, callback: (request: unknown) => void) { - this.onFrameEvent(event, frameEvent => { - if (frameEvent.type.includes(AppKitFrameConstants.RPC_METHOD_KEY)) { - callback(frameEvent); - } - }); - } - - public onIsConnected(event: AppKitFrameTypes.FrameEvent, callback: () => void) { - this.onFrameEvent(event, frameEvent => { + public onIsConnected( + callback: (response: AppKitFrameTypes.Responses['FrameGetUserResponse']) => void + ) { + this.onFrameEvent(frameEvent => { if (frameEvent.type === AppKitFrameConstants.FRAME_GET_USER_SUCCESS) { - callback(); + callback(frameEvent.payload); } }); } - public onNotConnected(event: AppKitFrameTypes.FrameEvent, callback: () => void) { - this.onFrameEvent(event, frameEvent => { + public onNotConnected(callback: () => void) { + this.onFrameEvent(frameEvent => { if (frameEvent.type === AppKitFrameConstants.FRAME_IS_CONNECTED_ERROR) { callback(); } @@ -415,6 +411,39 @@ export class AppKitFrameProvider { }); } + public onGetSmartAccountEnabledNetworks( + callback: ( + response: AppKitFrameTypes.Responses['FrameGetSmartAccountEnabledNetworksResponse'] + ) => void + ) { + this.onFrameEvent(frameEvent => { + if ( + frameEvent.type === AppKitFrameConstants.FRAME_GET_SMART_ACCOUNT_ENABLED_NETWORKS_SUCCESS + ) { + callback(frameEvent.payload); + } + }); + } + + public onSetPreferredAccount( + callback: (response: AppKitFrameTypes.Responses['FrameSetPreferredAccountResponse']) => void + ) { + this.onFrameEvent(frameEvent => { + if (frameEvent.type === AppKitFrameConstants.FRAME_SET_PREFERRED_ACCOUNT_SUCCESS) { + callback(frameEvent.payload); + } + }); + } + + public async getLastUsedChainId() { + const chainId = await AppKitFrameStorage.get(AppKitFrameConstants.LAST_USED_CHAIN_KEY); + if (chainId) { + return Number(chainId); + } + + return undefined; + } + // -- Private Methods ------------------------------------------------- private setNewLastEmailLoginTime() { AppKitFrameStorage.set(AppKitFrameConstants.LAST_EMAIL_LOGIN_TIME, Date.now().toString()); @@ -445,13 +474,8 @@ export class AppKitFrameProvider { AppKitFrameStorage.set(AppKitFrameConstants.LAST_USED_CHAIN_KEY, String(chainId)); } - private async getLastUsedChainId() { - const chainId = await AppKitFrameStorage.get(AppKitFrameConstants.LAST_USED_CHAIN_KEY); - if (chainId) { - return Number(chainId); - } - - return undefined; + private persistSmartAccountEnabledNetworks(networks: number[]) { + AppKitFrameStorage.set(AppKitFrameConstants.SMART_ACCOUNT_ENABLED_NETWORKS, networks.join(',')); } private async registerFrameEventHandler( @@ -518,18 +542,19 @@ export class AppKitFrameProvider { }); } - private onFrameEvent( - event: AppKitFrameTypes.FrameEvent, - callback: (event: AppKitFrameTypes.FrameEvent) => void - ) { - if ( - !event.type?.includes(AppKitFrameConstants.FRAME_EVENT_KEY) || - event.origin !== AppKitFrameConstants.SECURE_SITE_ORIGIN - ) { - return; - } - const frameEvent = AppKitFrameSchema.frameEvent.parse(event); - callback(frameEvent); + private onFrameEvent(callback: (event: AppKitFrameTypes.FrameEvent) => void) { + const eventHandler = (event: AppKitFrameTypes.FrameEvent) => { + if ( + !event.type?.includes(AppKitFrameConstants.FRAME_EVENT_KEY) || + event.origin !== AppKitFrameConstants.SECURE_SITE_ORIGIN + ) { + return; + } + // console.log('💻 received', event); // eslint-disable-line no-console + callback(event); + }; + + this.events.addListener('message', eventHandler); } private postAppEvent(event: AppKitFrameTypes.AppEvent) { @@ -554,15 +579,10 @@ export class AppKitFrameProvider { ); } - private async getAsyncEmail() { + private async loadAsyncValues() { const email = await AppKitFrameStorage.get(AppKitFrameConstants.EMAIL); - - return email; - } - - private async getAsyncUsername() { + this.email = email; const username = await AppKitFrameStorage.get(AppKitFrameConstants.SOCIAL_USERNAME); - - return username; + this.username = username; } } diff --git a/packages/wallet/src/AppKitFrameSchema.ts b/packages/wallet/src/AppKitFrameSchema.ts index 329a26c75..3d7ffe04e 100644 --- a/packages/wallet/src/AppKitFrameSchema.ts +++ b/packages/wallet/src/AppKitFrameSchema.ts @@ -29,14 +29,14 @@ export const GetTransactionByHashResponse = z.object({ v: z.string(), value: z.string() }); -export const AppSwitchNetworkRequest = z.object({ chainId: z.string().or(z.number()) }); +export const AppSwitchNetworkRequest = z.object({ chainId: z.number() }); export const AppConnectEmailRequest = z.object({ email: z.string().email() }); export const AppConnectOtpRequest = z.object({ otp: z.string() }); export const AppConnectSocialRequest = z.object({ uri: z.string() }); export const AppGetSocialRedirectUriRequest = z.object({ provider: z.enum(['google', 'github', 'apple', 'facebook', 'x', 'discord', 'farcaster']) }); -export const AppGetUserRequest = z.object({ chainId: z.optional(z.string().or(z.number())) }); +export const AppGetUserRequest = z.object({ chainId: z.optional(z.number()) }); export const AppUpdateEmailRequest = z.object({ email: z.string().email() }); export const AppUpdateEmailPrimaryOtpRequest = z.object({ otp: z.string() }); export const AppUpdateEmailSecondaryOtpRequest = z.object({ otp: z.string() }); @@ -59,10 +59,16 @@ export const AppSyncDappDataRequest = z.object({ | `react-native-ethers5-${string}` | `react-native-ethers-${string}` >, + sdkType: z.enum(['appkit']), projectId: z.string() }); export const AppSetPreferredAccountRequest = z.object({ type: z.string() }); +const AccountTypeEnum = z.enum([ + AppKitFrameRpcConstants.ACCOUNT_TYPES.EOA, + AppKitFrameRpcConstants.ACCOUNT_TYPES.SMART_ACCOUNT +]); + export const FrameConnectEmailResponse = z.object({ action: z.enum(['VERIFY_DEVICE', 'VERIFY_OTP']) }); @@ -72,7 +78,9 @@ export const FrameUpdateEmailResponse = z.object({ export const FrameGetUserResponse = z.object({ email: z.string().email().optional().nullable(), address: z.string(), - chainId: z.string().or(z.number()) + chainId: z.number(), + smartAccountDeployed: z.boolean(), + preferredAccountType: AccountTypeEnum }); export const FrameIsConnectedResponse = z.object({ isConnected: z.boolean() }); export const FrameGetChainIdResponse = z.object({ chainId: z.number() }); @@ -88,10 +96,7 @@ export const FrameConnectSocialResponse = z.object({ .array( z.object({ address: z.string(), - type: z.enum([ - AppKitFrameRpcConstants.ACCOUNT_TYPES.EOA, - AppKitFrameRpcConstants.ACCOUNT_TYPES.SMART_ACCOUNT - ]) + type: AccountTypeEnum }) ) .optional(), @@ -106,6 +111,15 @@ export const FrameConnectFarcasterResponse = z.object({ userName: z.string() }); +export const FrameSetPreferredAccountResponse = z.object({ + type: AccountTypeEnum, + address: z.string() +}); + +export const FrameGetSmartAccountEnabledNetworksResponse = z.object({ + smartAccountEnabledNetworks: z.array(z.number()) +}); + export const RpcResponse = z.any(); export const RpcEthAccountsRequest = z.object({ @@ -334,6 +348,15 @@ export const AppKitFrameSchema = { .or(EventSchema.extend({ type: zType('APP_GET_CHAIN_ID') })) + .or(EventSchema.extend({ type: zType('APP_GET_SMART_ACCOUNT_ENABLED_NETWORKS') })) + + .or( + EventSchema.extend({ + type: zType('APP_SET_PREFERRED_ACCOUNT'), + payload: AppSetPreferredAccountRequest + }) + ) + .or( EventSchema.extend({ type: zType('APP_RPC_REQUEST'), @@ -660,4 +683,35 @@ export const AppKitFrameSchema = { ) .or(EventSchema.extend({ type: zType('FRAME_SYNC_DAPP_DATA_SUCCESS'), origin: z.string() })) + + .or( + EventSchema.extend({ + type: zType('FRAME_GET_SMART_ACCOUNT_ENABLED_NETWORKS_SUCCESS'), + payload: FrameGetSmartAccountEnabledNetworksResponse, + origin: z.string() + }) + ) + + .or( + EventSchema.extend({ + type: zType('FRAME_GET_SMART_ACCOUNT_ENABLED_NETWORKS_ERROR'), + payload: zError, + origin: z.string() + }) + ) + + .or( + EventSchema.extend({ + type: zType('FRAME_SET_PREFERRED_ACCOUNT_SUCCESS'), + payload: FrameSetPreferredAccountResponse, + origin: z.string() + }) + ) + .or( + EventSchema.extend({ + type: zType('FRAME_SET_PREFERRED_ACCOUNT_ERROR'), + payload: zError, + origin: z.string() + }) + ) }; diff --git a/packages/wallet/src/AppKitFrameTypes.ts b/packages/wallet/src/AppKitFrameTypes.ts index c4e4104e3..40b712b34 100644 --- a/packages/wallet/src/AppKitFrameTypes.ts +++ b/packages/wallet/src/AppKitFrameTypes.ts @@ -61,8 +61,12 @@ import { AppGetSocialRedirectUriRequest, AppConnectSocialRequest, FrameGetFarcasterUriResponse, - FrameConnectFarcasterResponse + FrameConnectFarcasterResponse, + AppSetPreferredAccountRequest, + FrameSetPreferredAccountResponse, + FrameGetSmartAccountEnabledNetworksResponse } from './AppKitFrameSchema'; +import type { AppKitFrameRpcConstants } from './AppKitFrameConstants'; export namespace AppKitFrameTypes { export type AppEvent = z.infer; @@ -81,6 +85,8 @@ export namespace AppKitFrameTypes { AppUpdateEmailSecondaryOtpRequest: z.infer; AppGetSocialRedirectUriRequest: z.infer; AppConnectSocialRequest: z.infer; + AppSetPreferredAccountRequest: z.infer; + AppGetSmartAccountEnabledNetworksRequest: undefined; } export interface Responses { @@ -101,6 +107,10 @@ export namespace AppKitFrameTypes { FrameUpdateEmailSecondaryOtpResponse: z.infer; FrameConnectDeviceResponse: undefined; FrameSignOutResponse: undefined; + FrameGetSmartAccountEnabledNetworksResponse: z.infer< + typeof FrameGetSmartAccountEnabledNetworksResponse + >; + FrameSetPreferredAccountResponse: z.infer; FrameRpcResponse: RPCResponse; } @@ -159,6 +169,9 @@ export namespace AppKitFrameTypes { export type FrameSessionType = z.infer; + export type AccountType = + (typeof AppKitFrameRpcConstants.ACCOUNT_TYPES)[keyof typeof AppKitFrameRpcConstants.ACCOUNT_TYPES]; + export type ProviderRequestType = | 'GetUser' | 'ConnectDevice' @@ -175,6 +188,8 @@ export namespace AppKitFrameTypes { | 'UpdateEmailPrimaryOtp' | 'UpdateEmailSecondaryOtp' | 'GetChainId' + | 'GetSmartAccountEnabledNetworks' + | 'SetPreferredAccount' | 'IsConnected' | 'SignOut' | 'Rpc'; diff --git a/packages/wallet/src/AppKitWebview.tsx b/packages/wallet/src/AppKitWebview.tsx index 536ad48cc..d4c98dea5 100644 --- a/packages/wallet/src/AppKitWebview.tsx +++ b/packages/wallet/src/AppKitWebview.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { useEffect, useRef, useState } from 'react'; +import { memo, useEffect, useRef, useState } from 'react'; import { Animated, SafeAreaView, StyleSheet } from 'react-native'; import { WebView } from 'react-native-webview'; @@ -13,7 +13,7 @@ import type { AppKitFrameProvider } from './AppKitFrameProvider'; const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView); -export function AppKitWebview() { +function _AppKitWebview() { const webviewRef = useRef(null); const Theme = useTheme(); const authConnector = ConnectorController.getAuthConnector(); @@ -110,6 +110,8 @@ export function AppKitWebview() { ) : null; } +export const AppKitWebview = memo(_AppKitWebview); + const styles = StyleSheet.create({ backdrop: { position: 'absolute', diff --git a/yarn.lock b/yarn.lock index 49c489586..4f8e5d222 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,6 +38,13 @@ __metadata: languageName: node linkType: hard +"@adraffy/ens-normalize@npm:1.11.0": + version: 1.11.0 + resolution: "@adraffy/ens-normalize@npm:1.11.0" + checksum: 5111d0f1a273468cb5661ed3cf46ee58de8f32f84e2ebc2365652e66c1ead82649df94c736804e2b9cfa831d30ef24e1cc3575d970dbda583416d3a98d8870a6 + languageName: node + linkType: hard + "@ampproject/remapping@npm:^2.2.0": version: 2.2.1 resolution: "@ampproject/remapping@npm:2.2.1" @@ -97,7 +104,7 @@ __metadata: "@tanstack/react-query-persist-client": "npm:5.56.2" "@types/react": "npm:~18.2.79" "@types/react-native": "npm:0.72.2" - "@walletconnect/react-native-compat": "npm:2.16.1" + "@walletconnect/react-native-compat": "npm:2.17.1" babel-plugin-module-resolver: "npm:^5.0.0" expo: "npm:~51.0.24" expo-application: "npm:~5.9.1" @@ -114,8 +121,8 @@ __metadata: react-native-webview: "npm:13.8.6" typescript: "npm:~5.3.3" uuid: "npm:3.4.0" - viem: "npm:2.21.6" - wagmi: "npm:2.12.11" + viem: "npm:2.21.37" + wagmi: "npm:2.12.25" languageName: unknown linkType: soft @@ -3613,17 +3620,15 @@ __metadata: languageName: node linkType: hard -"@coinbase/wallet-sdk@npm:4.0.4": - version: 4.0.4 - resolution: "@coinbase/wallet-sdk@npm:4.0.4" +"@coinbase/wallet-sdk@npm:4.1.0": + version: 4.1.0 + resolution: "@coinbase/wallet-sdk@npm:4.1.0" dependencies: - buffer: "npm:^6.0.3" + "@noble/hashes": "npm:^1.4.0" clsx: "npm:^1.2.1" eventemitter3: "npm:^5.0.1" - keccak: "npm:^3.0.3" preact: "npm:^10.16.0" - sha.js: "npm:^2.4.11" - checksum: 7c8c39688c144b5305ac59d847023f7dce9ccffdd8ed6fdcc690c03980ce7cf8f88caff4e0cf0a1f081bcfd61ebe6a590970771505f86700f9b798a0e8e2dc88 + checksum: 9ccd8171e8874a357f246fc3b8b9641cb015f12e0c8912c15b77c55cdca58c00ba59c68afdc3162d26421fedcaf8164a95ee39abce96e4dcde5b391e0920ca65 languageName: node linkType: hard @@ -3636,6 +3641,15 @@ __metadata: languageName: node linkType: hard +"@ecies/ciphers@npm:^0.2.0": + version: 0.2.1 + resolution: "@ecies/ciphers@npm:0.2.1" + peerDependencies: + "@noble/ciphers": ^1.0.0 + checksum: 0ce13f5f8216047afde68afe549021c65145af2d2f08da032552487f170c47fd480c11fa358b5cdcc29e70928e5d81e037b6a5963a20ba74d3c242881ba5bb50 + languageName: node + linkType: hard + "@esbuild/aix-ppc64@npm:0.23.1": version: 0.23.1 resolution: "@esbuild/aix-ppc64@npm:0.23.1" @@ -5375,9 +5389,9 @@ __metadata: languageName: node linkType: hard -"@metamask/sdk-communication-layer@npm:0.28.2": - version: 0.28.2 - resolution: "@metamask/sdk-communication-layer@npm:0.28.2" +"@metamask/sdk-communication-layer@npm:0.30.0": + version: 0.30.0 + resolution: "@metamask/sdk-communication-layer@npm:0.30.0" dependencies: bufferutil: "npm:^4.0.8" date-fns: "npm:^2.29.3" @@ -5390,13 +5404,13 @@ __metadata: eventemitter2: ^6.4.7 readable-stream: ^3.6.2 socket.io-client: ^4.5.1 - checksum: 7d51316eb313bd4464e8e5d787c4d88228e40673414883a693f5772908cb5c17903db0d3101bc04ee9db218728525a0ad3a8545c6e7d933b48f3ae6ce8a474bc + checksum: e3f2b1a05e474142c1c92c89b4347cbefe4503143cd9e27ff961a341afe2bc2d593b111db5a9425231ff1661a9219449fb50c47c3f4ccc39c81c97e925aac477 languageName: node linkType: hard -"@metamask/sdk-install-modal-web@npm:0.28.1": - version: 0.28.1 - resolution: "@metamask/sdk-install-modal-web@npm:0.28.1" +"@metamask/sdk-install-modal-web@npm:0.30.0": + version: 0.30.0 + resolution: "@metamask/sdk-install-modal-web@npm:0.30.0" dependencies: qr-code-styling: "npm:^1.6.0-rc.1" peerDependencies: @@ -5411,24 +5425,22 @@ __metadata: optional: true react-native: optional: true - checksum: e7bc9789d6499ff1f2ec2587b0604c4df445bc35e0914165289348fe9325ccff60ef094b5ebe39310af9c68ee8d6d71ed0a6a217e2d3947a2aa92a4c7063e4a1 + checksum: b515a356148179e74c80562d6127c59a21d25bce0a83bef3b190d02785d231936cc394fb87f6141a673ba0d8ba3f443f0572540aa6883b74ea17b9f6e771dc00 languageName: node linkType: hard -"@metamask/sdk@npm:0.28.2": - version: 0.28.2 - resolution: "@metamask/sdk@npm:0.28.2" +"@metamask/sdk@npm:0.30.1": + version: 0.30.1 + resolution: "@metamask/sdk@npm:0.30.1" dependencies: "@metamask/onboarding": "npm:^1.0.1" "@metamask/providers": "npm:16.1.0" - "@metamask/sdk-communication-layer": "npm:0.28.2" - "@metamask/sdk-install-modal-web": "npm:0.28.1" - "@types/dom-screen-wake-lock": "npm:^1.0.0" - "@types/uuid": "npm:^10.0.0" + "@metamask/sdk-communication-layer": "npm:0.30.0" + "@metamask/sdk-install-modal-web": "npm:0.30.0" bowser: "npm:^2.9.0" cross-fetch: "npm:^4.0.0" debug: "npm:^4.3.4" - eciesjs: "npm:^0.3.15" + eciesjs: "npm:^0.4.8" eth-rpc-errors: "npm:^4.0.3" eventemitter2: "npm:^6.4.7" i18next: "npm:23.11.5" @@ -5438,7 +5450,6 @@ __metadata: qrcode-terminal-nooctal: "npm:^0.12.1" react-native-webview: "npm:^11.26.0" readable-stream: "npm:^3.6.2" - rollup-plugin-visualizer: "npm:^5.9.2" socket.io-client: "npm:^4.5.1" util: "npm:^0.12.4" uuid: "npm:^8.3.2" @@ -5450,7 +5461,7 @@ __metadata: optional: true react-dom: optional: true - checksum: 519240bd0729e9fbee6000d794c7071d4739917c40b3f4529f6315690b76221595110e4ede6ade10b595da8de4a152ac8a8571f6600ab16b132de61548536b37 + checksum: e42a98471adecc6c291e322ec6772fa8f8d9ae9949887e59c77f7d916ff44f5e69fc57b74449b2e04603e8ecb550e782815a6dc8f81f8afc291fc61d06fe6722 languageName: node linkType: hard @@ -5594,6 +5605,13 @@ __metadata: languageName: node linkType: hard +"@noble/ciphers@npm:^1.0.0": + version: 1.0.0 + resolution: "@noble/ciphers@npm:1.0.0" + checksum: 6c04d6e9d10a922fff170efc44622c95a25fb817f4b593e0f150dd27599576f3fe3c5b61eb02054b22d1507e3839879ddd5acb2d2acf8efbea4efab99bbcd333 + languageName: node + linkType: hard + "@noble/curves@npm:1.1.0, @noble/curves@npm:~1.1.0": version: 1.1.0 resolution: "@noble/curves@npm:1.1.0" @@ -5621,6 +5639,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:1.6.0, @noble/curves@npm:^1.6.0, @noble/curves@npm:~1.6.0": + version: 1.6.0 + resolution: "@noble/curves@npm:1.6.0" + dependencies: + "@noble/hashes": "npm:1.5.0" + checksum: f3262aa4d39148e627cd82b5ac1c93f88c5bb46dd2566b5e8e52ffac3a0fc381ad30c2111656fd2bd3b0d37d43d540543e0d93a5ff96a6cb184bc3bfe10d1cd9 + languageName: node + linkType: hard + "@noble/curves@npm:^1.4.0, @noble/curves@npm:~1.4.0": version: 1.4.2 resolution: "@noble/curves@npm:1.4.2" @@ -5651,6 +5678,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.5.0, @noble/hashes@npm:^1.5.0, @noble/hashes@npm:~1.5.0": + version: 1.5.0 + resolution: "@noble/hashes@npm:1.5.0" + checksum: 1b46539695fbfe4477c0822d90c881a04d4fa2921c08c552375b444a48cac9930cb1ee68de0a3c7859e676554d0f3771999716606dc4d8f826e414c11692cdd9 + languageName: node + linkType: hard + "@noble/hashes@npm:~1.3.1": version: 1.3.3 resolution: "@noble/hashes@npm:1.3.3" @@ -5658,13 +5692,6 @@ __metadata: languageName: node linkType: hard -"@noble/hashes@npm:~1.5.0": - version: 1.5.0 - resolution: "@noble/hashes@npm:1.5.0" - checksum: 1b46539695fbfe4477c0822d90c881a04d4fa2921c08c552375b444a48cac9930cb1ee68de0a3c7859e676554d0f3771999716606dc4d8f826e414c11692cdd9 - languageName: node - linkType: hard - "@nodelib/fs.scandir@npm:2.1.5": version: 2.1.5 resolution: "@nodelib/fs.scandir@npm:2.1.5" @@ -6731,6 +6758,7 @@ __metadata: version: 0.0.0-use.local resolution: "@reown/appkit-auth-wagmi-react-native@workspace:packages/auth-wagmi" dependencies: + "@reown/appkit-core-react-native": "npm:1.0.2" "@reown/appkit-wallet-react-native": "npm:1.0.2" peerDependencies: wagmi: ">=2" @@ -6963,6 +6991,13 @@ __metadata: languageName: node linkType: hard +"@scure/base@npm:~1.1.7": + version: 1.1.9 + resolution: "@scure/base@npm:1.1.9" + checksum: 77a06b9a2db8144d22d9bf198338893d77367c51b58c72b99df990c0a11f7cadd066d4102abb15e3ca6798d1529e3765f55c4355742465e49aed7a0c01fe76e8 + languageName: node + linkType: hard + "@scure/base@npm:~1.1.8": version: 1.1.8 resolution: "@scure/base@npm:1.1.8" @@ -6992,6 +7027,17 @@ __metadata: languageName: node linkType: hard +"@scure/bip32@npm:1.5.0": + version: 1.5.0 + resolution: "@scure/bip32@npm:1.5.0" + dependencies: + "@noble/curves": "npm:~1.6.0" + "@noble/hashes": "npm:~1.5.0" + "@scure/base": "npm:~1.1.7" + checksum: 3319beda59e7f129d770cbe49709a2d1742f2deb6989b12e37aa1a47cd128a8c943bdd9286c6a5513ef4539307c4bca8f89f9aa91f294cac4598cbf95fa0c01d + languageName: node + linkType: hard + "@scure/bip39@npm:1.2.1": version: 1.2.1 resolution: "@scure/bip39@npm:1.2.1" @@ -8066,13 +8112,6 @@ __metadata: languageName: node linkType: hard -"@types/dom-screen-wake-lock@npm:^1.0.0": - version: 1.0.3 - resolution: "@types/dom-screen-wake-lock@npm:1.0.3" - checksum: bab45f6a797de562f1bd3c095c49b7c0464ad05e571f38d00adaa35da2b02109bfe587206cc55f420377634cf0f7b07caa5acb3257e49dfd2d94dab74c617bf1 - languageName: node - linkType: hard - "@types/escodegen@npm:^0.0.6": version: 0.0.6 resolution: "@types/escodegen@npm:0.0.6" @@ -8384,15 +8423,6 @@ __metadata: languageName: node linkType: hard -"@types/secp256k1@npm:^4.0.4": - version: 4.0.6 - resolution: "@types/secp256k1@npm:4.0.6" - dependencies: - "@types/node": "npm:*" - checksum: 0e391316ae30c218779583b626382a56546ddbefb65f1ff9cf5e078af8a7118f67f3e66e30914399cc6f8710c424d0d8c3f34262ffb1f429c6ad911fd0d0bc26 - languageName: node - linkType: hard - "@types/semver@npm:^7.3.12, @types/semver@npm:^7.3.4": version: 7.5.0 resolution: "@types/semver@npm:7.5.0" @@ -8463,13 +8493,6 @@ __metadata: languageName: node linkType: hard -"@types/uuid@npm:^10.0.0": - version: 10.0.0 - resolution: "@types/uuid@npm:10.0.0" - checksum: 9a1404bf287164481cb9b97f6bb638f78f955be57c40c6513b7655160beb29df6f84c915aaf4089a1559c216557dc4d2f79b48d978742d3ae10b937420ddac60 - languageName: node - linkType: hard - "@types/uuid@npm:^9.0.1": version: 9.0.8 resolution: "@types/uuid@npm:9.0.8" @@ -8786,35 +8809,34 @@ __metadata: languageName: node linkType: hard -"@wagmi/connectors@npm:5.1.10": - version: 5.1.10 - resolution: "@wagmi/connectors@npm:5.1.10" +"@wagmi/connectors@npm:5.3.3": + version: 5.3.3 + resolution: "@wagmi/connectors@npm:5.3.3" dependencies: - "@coinbase/wallet-sdk": "npm:4.0.4" - "@metamask/sdk": "npm:0.28.2" + "@coinbase/wallet-sdk": "npm:4.1.0" + "@metamask/sdk": "npm:0.30.1" "@safe-global/safe-apps-provider": "npm:0.18.3" "@safe-global/safe-apps-sdk": "npm:9.1.0" - "@walletconnect/ethereum-provider": "npm:2.16.1" - "@walletconnect/modal": "npm:2.6.2" + "@walletconnect/ethereum-provider": "npm:2.17.0" cbw-sdk: "npm:@coinbase/wallet-sdk@3.9.3" peerDependencies: - "@wagmi/core": 2.13.5 + "@wagmi/core": 2.14.1 typescript: ">=5.0.4" viem: 2.x peerDependenciesMeta: typescript: optional: true - checksum: 9af1a06bd239f7c710ebc05a507f65d20a1860b8e05d4bf10b85cef94d8d9ae77cc7a4dbc402b9237fde0701071f46fa8e7a384fcb7873ea03493b11a6125d22 + checksum: 78789ed27fca0bc1d54a3a8282584ddcbdcee26f2d95b27d7dd9663a7cd02e2728e0bcdd0ce0aaae8983dfac3ad966c03243dd08ce11b80d908c314f0d139cb7 languageName: node linkType: hard -"@wagmi/core@npm:2.13.5": - version: 2.13.5 - resolution: "@wagmi/core@npm:2.13.5" +"@wagmi/core@npm:2.14.1": + version: 2.14.1 + resolution: "@wagmi/core@npm:2.14.1" dependencies: eventemitter3: "npm:5.0.1" mipd: "npm:0.0.7" - zustand: "npm:4.4.1" + zustand: "npm:5.0.0" peerDependencies: "@tanstack/query-core": ">=5.0.0" typescript: ">=5.0.4" @@ -8824,7 +8846,7 @@ __metadata: optional: true typescript: optional: true - checksum: e386de867acf92e6a29a6e22e2d612719a1c60cb126473d6675b8b02af2f92c13c9202b89287540872eeace15213fe309411466d5d93fe5308da3a68550aca9f + checksum: 4cde494a8fcf218e79eb4e650fd598fbb273fcaa3cab52aa0a3b41a2a104de2bac602ed19d2d2c6b130f881300770d809b5e1bfd81a8375269bed7d4e9606fb0 languageName: node linkType: hard @@ -8852,6 +8874,30 @@ __metadata: languageName: node linkType: hard +"@walletconnect/core@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/core@npm:2.17.0" + dependencies: + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/jsonrpc-ws-connection": "npm:1.0.14" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/relay-api": "npm:1.0.11" + "@walletconnect/relay-auth": "npm:1.0.4" + "@walletconnect/safe-json": "npm:1.0.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" + events: "npm:3.3.0" + lodash.isequal: "npm:4.5.0" + uint8arrays: "npm:3.1.0" + checksum: 34ae5b9b68c08c1dd3ebb2a6ebff8697307e76fbfe4d6b51d5d090da5cd1613e1c66fa5ac3a87c914333458d7b5bf075bb664292f6b2c7d438c72f706d87416d + languageName: node + linkType: hard + "@walletconnect/environment@npm:^1.0.1": version: 1.0.1 resolution: "@walletconnect/environment@npm:1.0.1" @@ -8879,6 +8925,24 @@ __metadata: languageName: node linkType: hard +"@walletconnect/ethereum-provider@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/ethereum-provider@npm:2.17.0" + dependencies: + "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/modal": "npm:2.7.0" + "@walletconnect/sign-client": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/universal-provider": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" + events: "npm:3.3.0" + checksum: b046a9c296e95b22841f0b2efd28a4ce1a38529a9ba412d3c8ffc482879d79c3d2a24b8c0ec712baecf781938b4321ab5c1ecad5573d078add7c47b0cfd08a25 + languageName: node + linkType: hard + "@walletconnect/events@npm:1.0.1, @walletconnect/events@npm:^1.0.1": version: 1.0.1 resolution: "@walletconnect/events@npm:1.0.1" @@ -9001,6 +9065,15 @@ __metadata: languageName: node linkType: hard +"@walletconnect/modal-core@npm:2.7.0": + version: 2.7.0 + resolution: "@walletconnect/modal-core@npm:2.7.0" + dependencies: + valtio: "npm:1.11.2" + checksum: 84b11735c005e37e661aa0f08b2e8c8098db3b2cacd957c4a73f4d3de11b2d5e04dd97ab970f8d22fc3e8269fea3297b9487e177343bbab8dd69b3b917fb7f60 + languageName: node + linkType: hard + "@walletconnect/modal-ui@npm:2.6.2": version: 2.6.2 resolution: "@walletconnect/modal-ui@npm:2.6.2" @@ -9013,6 +9086,18 @@ __metadata: languageName: node linkType: hard +"@walletconnect/modal-ui@npm:2.7.0": + version: 2.7.0 + resolution: "@walletconnect/modal-ui@npm:2.7.0" + dependencies: + "@walletconnect/modal-core": "npm:2.7.0" + lit: "npm:2.8.0" + motion: "npm:10.16.2" + qrcode: "npm:1.5.3" + checksum: b717f1fc9854b7d14a4364720fce2d44167f547533340704644ed2fdf9d861b3798ffd19a3b51062a366a8bc39f84b9a8bb3dd04e9e33da742192359be00b051 + languageName: node + linkType: hard + "@walletconnect/modal@npm:2.6.2": version: 2.6.2 resolution: "@walletconnect/modal@npm:2.6.2" @@ -9023,6 +9108,16 @@ __metadata: languageName: node linkType: hard +"@walletconnect/modal@npm:2.7.0": + version: 2.7.0 + resolution: "@walletconnect/modal@npm:2.7.0" + dependencies: + "@walletconnect/modal-core": "npm:2.7.0" + "@walletconnect/modal-ui": "npm:2.7.0" + checksum: 2f3074eebbca41a46e29680dc2565bc762133508774f05db0075a82b0b66ecc8defca40a94ad63669676090a7e3ef671804592b10e91636ab1cdeac014a1eb11 + languageName: node + linkType: hard + "@walletconnect/react-native-compat@npm:2.16.1": version: 2.16.1 resolution: "@walletconnect/react-native-compat@npm:2.16.1" @@ -9042,6 +9137,26 @@ __metadata: languageName: node linkType: hard +"@walletconnect/react-native-compat@npm:2.17.1": + version: 2.17.1 + resolution: "@walletconnect/react-native-compat@npm:2.17.1" + dependencies: + events: "npm:3.3.0" + fast-text-encoding: "npm:1.0.6" + react-native-url-polyfill: "npm:2.0.0" + peerDependencies: + "@react-native-async-storage/async-storage": "*" + "@react-native-community/netinfo": "*" + expo-application: "*" + react-native: "*" + react-native-get-random-values: "*" + peerDependenciesMeta: + expo-application: + optional: true + checksum: 55afa3b7de9cf71f208a10d30ac70bbef67d32013807ebfc2f99ab61cc7e27a51def1e4e795e88e9164452c0ab1d219820cf5b8e0e5e4c316a7501a508476bba + languageName: node + linkType: hard + "@walletconnect/relay-api@npm:1.0.11": version: 1.0.11 resolution: "@walletconnect/relay-api@npm:1.0.11" @@ -9091,6 +9206,23 @@ __metadata: languageName: node linkType: hard +"@walletconnect/sign-client@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/sign-client@npm:2.17.0" + dependencies: + "@walletconnect/core": "npm:2.17.0" + "@walletconnect/events": "npm:1.0.1" + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" + events: "npm:3.3.0" + checksum: 48f7d13b3db49584a40dc2653f49fabadd100a324e2213476b8d9e4d6fe0808a08ae14103d2e5b609abff3115197003d8570d606275dbd0f6774d0d49da10c61 + languageName: node + linkType: hard + "@walletconnect/time@npm:1.0.2, @walletconnect/time@npm:^1.0.2": version: 1.0.2 resolution: "@walletconnect/time@npm:1.0.2" @@ -9114,6 +9246,20 @@ __metadata: languageName: node linkType: hard +"@walletconnect/types@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/types@npm:2.17.0" + dependencies: + "@walletconnect/events": "npm:1.0.1" + "@walletconnect/heartbeat": "npm:1.2.2" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/keyvaluestorage": "npm:1.1.1" + "@walletconnect/logger": "npm:2.1.2" + events: "npm:3.3.0" + checksum: bdc0c062da1edb4410882d9cfca1bb30eb0afd7caea90d5e7a66eaf15e28380e9ef97635cd5e5a017947f4c814c1f780622b4d8946b11a335d415ae066ec7ade + languageName: node + linkType: hard + "@walletconnect/universal-provider@npm:2.16.1": version: 2.16.1 resolution: "@walletconnect/universal-provider@npm:2.16.1" @@ -9131,6 +9277,23 @@ __metadata: languageName: node linkType: hard +"@walletconnect/universal-provider@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/universal-provider@npm:2.17.0" + dependencies: + "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" + "@walletconnect/jsonrpc-provider": "npm:1.0.14" + "@walletconnect/jsonrpc-types": "npm:1.0.4" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/logger": "npm:2.1.2" + "@walletconnect/sign-client": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" + events: "npm:3.3.0" + checksum: 7c1afc79054db5add4e937d7adaadb4fc26aecffb5d749d388418fa5d4eb153807ab4de301b642cd80669b4e5c6bcae917f18cf5ce8696d87da8b3705b60d1ec + languageName: node + linkType: hard + "@walletconnect/utils@npm:2.16.1": version: 2.16.1 resolution: "@walletconnect/utils@npm:2.16.1" @@ -9155,6 +9318,30 @@ __metadata: languageName: node linkType: hard +"@walletconnect/utils@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/utils@npm:2.17.0" + dependencies: + "@stablelib/chacha20poly1305": "npm:1.0.1" + "@stablelib/hkdf": "npm:1.0.1" + "@stablelib/random": "npm:1.0.2" + "@stablelib/sha256": "npm:1.0.1" + "@stablelib/x25519": "npm:1.0.3" + "@walletconnect/relay-api": "npm:1.0.11" + "@walletconnect/relay-auth": "npm:1.0.4" + "@walletconnect/safe-json": "npm:1.0.2" + "@walletconnect/time": "npm:1.0.2" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/window-getters": "npm:1.0.1" + "@walletconnect/window-metadata": "npm:1.0.1" + detect-browser: "npm:5.3.0" + elliptic: "npm:^6.5.7" + query-string: "npm:7.1.3" + uint8arrays: "npm:3.1.0" + checksum: d1da74b2cd7af35f16d735fe408cfc820c611b2709bd00899e4e91b0b0a6dcd8f344f97df34d0ef8cabc121619a40b62118ffa2aa233ddba9863d1ba23480a0c + languageName: node + linkType: hard + "@walletconnect/window-getters@npm:1.0.1, @walletconnect/window-getters@npm:^1.0.1": version: 1.0.1 resolution: "@walletconnect/window-getters@npm:1.0.1" @@ -9375,6 +9562,21 @@ __metadata: languageName: node linkType: hard +"abitype@npm:1.0.6": + version: 1.0.6 + resolution: "abitype@npm:1.0.6" + peerDependencies: + typescript: ">=5.0.4" + zod: ^3 >=3.22.0 + peerDependenciesMeta: + typescript: + optional: true + zod: + optional: true + checksum: 30ca97010bbf34b9aaed401858eeb6bc30419f7ff11eb34adcb243522dd56c9d8a9d3d406aa5d4f60a7c263902f5136043005698e3f073ea882a4922d43a2929 + languageName: node + linkType: hard + "abort-controller@npm:^3.0.0": version: 3.0.0 resolution: "abort-controller@npm:3.0.0" @@ -9710,8 +9912,8 @@ __metadata: tsconfig: "npm:*" turbo: "npm:2.1.1" typescript: "npm:5.2.2" - viem: "npm:2.21.6" - wagmi: "npm:2.12.11" + viem: "npm:2.21.37" + wagmi: "npm:2.12.25" languageName: unknown linkType: soft @@ -12085,14 +12287,15 @@ __metadata: languageName: node linkType: hard -"eciesjs@npm:^0.3.15": - version: 0.3.18 - resolution: "eciesjs@npm:0.3.18" +"eciesjs@npm:^0.4.8": + version: 0.4.10 + resolution: "eciesjs@npm:0.4.10" dependencies: - "@types/secp256k1": "npm:^4.0.4" - futoin-hkdf: "npm:^1.5.3" - secp256k1: "npm:^5.0.0" - checksum: 88e334b1fb8ae685eadf8023bc4a5c5247c1f7e6b873b8ba8ec84a3e7890352160ca7fcc1b78f75e67e04f2142310e89788a013fbb4f272f2130e76ada5050bc + "@ecies/ciphers": "npm:^0.2.0" + "@noble/ciphers": "npm:^1.0.0" + "@noble/curves": "npm:^1.6.0" + "@noble/hashes": "npm:^1.5.0" + checksum: f9e0603a839b763c1bb0a00a64686d553f2d8c10efcfab57461d9e052ebacc80dca0e28cbad8548015a99bee90c60cb085486be4c44261283535873d97aba59e languageName: node linkType: hard @@ -12153,21 +12356,6 @@ __metadata: languageName: node linkType: hard -"elliptic@npm:^6.5.4": - version: 6.5.5 - resolution: "elliptic@npm:6.5.5" - dependencies: - bn.js: "npm:^4.11.9" - brorand: "npm:^1.1.0" - hash.js: "npm:^1.0.0" - hmac-drbg: "npm:^1.0.1" - inherits: "npm:^2.0.4" - minimalistic-assert: "npm:^1.0.1" - minimalistic-crypto-utils: "npm:^1.0.1" - checksum: 3e591e93783a1b66f234ebf5bd3a8a9a8e063a75073a35a671e03e3b25253b6e33ac121f7efe9b8808890fffb17b40596cc19d01e6e8d1fa13b9a56ff65597c8 - languageName: node - linkType: hard - "elliptic@npm:^6.5.7": version: 6.5.7 resolution: "elliptic@npm:6.5.7" @@ -14076,13 +14264,6 @@ __metadata: languageName: node linkType: hard -"futoin-hkdf@npm:^1.5.3": - version: 1.5.3 - resolution: "futoin-hkdf@npm:1.5.3" - checksum: fe87b50d2ac125ca2074e92588ca1df5016e9657267363cb77d8287080639dc31f90e7740f4737aa054c3e687b2ab3456f9b5c55950b94cd2c2010bc441aa5ae - languageName: node - linkType: hard - "gensync@npm:^1.0.0-beta.2": version: 1.0.0-beta.2 resolution: "gensync@npm:1.0.0-beta.2" @@ -15542,6 +15723,15 @@ __metadata: languageName: node linkType: hard +"isows@npm:1.0.6": + version: 1.0.6 + resolution: "isows@npm:1.0.6" + peerDependencies: + ws: "*" + checksum: f89338f63ce2f497d6cd0f86e42c634209328ebb43b3bdfdc85d8f1589ee75f02b7e6d9e1ba274101d0f6f513b1b8cbe6985e6542b4aaa1f0c5fd50d9c1be95c + languageName: node + linkType: hard + "istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.0": version: 3.2.0 resolution: "istanbul-lib-coverage@npm:3.2.0" @@ -18006,15 +18196,6 @@ __metadata: languageName: node linkType: hard -"node-addon-api@npm:^5.0.0": - version: 5.1.0 - resolution: "node-addon-api@npm:5.1.0" - dependencies: - node-gyp: "npm:latest" - checksum: 0eb269786124ba6fad9df8007a149e03c199b3e5a3038125dfb3e747c2d5113d406a4e33f4de1ea600aa2339be1f137d55eba1a73ee34e5fff06c52a5c296d1d - languageName: node - linkType: hard - "node-addon-api@npm:^7.0.0": version: 7.0.0 resolution: "node-addon-api@npm:7.0.0" @@ -18427,7 +18608,7 @@ __metadata: languageName: node linkType: hard -"open@npm:^8.0.4, open@npm:^8.3.0, open@npm:^8.4.0": +"open@npm:^8.0.4, open@npm:^8.3.0": version: 8.4.2 resolution: "open@npm:8.4.2" dependencies: @@ -20493,25 +20674,6 @@ __metadata: languageName: node linkType: hard -"rollup-plugin-visualizer@npm:^5.9.2": - version: 5.12.0 - resolution: "rollup-plugin-visualizer@npm:5.12.0" - dependencies: - open: "npm:^8.4.0" - picomatch: "npm:^2.3.1" - source-map: "npm:^0.7.4" - yargs: "npm:^17.5.1" - peerDependencies: - rollup: 2.x || 3.x || 4.x - peerDependenciesMeta: - rollup: - optional: true - bin: - rollup-plugin-visualizer: dist/bin/cli.js - checksum: 0e44a641223377ebb472bb10f2b22efa773b5f6fbe8d54f197f07c68d7a432cbf00abad79a0aa1570f70c673c792f24700d926d663ed9a4d0ad8406ae5a0f4e4 - languageName: node - linkType: hard - "run-applescript@npm:^5.0.0": version: 5.0.0 resolution: "run-applescript@npm:5.0.0" @@ -20652,18 +20814,6 @@ __metadata: languageName: node linkType: hard -"secp256k1@npm:^5.0.0": - version: 5.0.0 - resolution: "secp256k1@npm:5.0.0" - dependencies: - elliptic: "npm:^6.5.4" - node-addon-api: "npm:^5.0.0" - node-gyp: "npm:latest" - node-gyp-build: "npm:^4.2.0" - checksum: b9ab4c952babfe6103978b2f656265041ebe09b8a91b26a796cbcbe04d2252e28e12ec50d5ed3006bf2ca5feef6edcbd71c7c85122615f5ffbcd1acdd564f77f - languageName: node - linkType: hard - "selfsigned@npm:^2.4.1": version: 2.4.1 resolution: "selfsigned@npm:2.4.1" @@ -21064,7 +21214,7 @@ __metadata: languageName: node linkType: hard -"source-map@npm:^0.7.3, source-map@npm:^0.7.4": +"source-map@npm:^0.7.3": version: 0.7.4 resolution: "source-map@npm:0.7.4" checksum: dc0cf3768fe23c345ea8760487f8c97ef6fca8a73c83cd7c9bf2fde8bc2c34adb9c0824d6feb14bc4f9e37fb522e18af621543f1289038a66ac7586da29aa7dc @@ -22794,25 +22944,25 @@ __metadata: languageName: node linkType: hard -"viem@npm:2.21.6": - version: 2.21.6 - resolution: "viem@npm:2.21.6" +"viem@npm:2.21.37": + version: 2.21.37 + resolution: "viem@npm:2.21.37" dependencies: - "@adraffy/ens-normalize": "npm:1.10.0" - "@noble/curves": "npm:1.4.0" - "@noble/hashes": "npm:1.4.0" - "@scure/bip32": "npm:1.4.0" + "@adraffy/ens-normalize": "npm:1.11.0" + "@noble/curves": "npm:1.6.0" + "@noble/hashes": "npm:1.5.0" + "@scure/bip32": "npm:1.5.0" "@scure/bip39": "npm:1.4.0" - abitype: "npm:1.0.5" - isows: "npm:1.0.4" - webauthn-p256: "npm:0.0.5" - ws: "npm:8.17.1" + abitype: "npm:1.0.6" + isows: "npm:1.0.6" + webauthn-p256: "npm:0.0.10" + ws: "npm:8.18.0" peerDependencies: typescript: ">=5.0.4" peerDependenciesMeta: typescript: optional: true - checksum: 6d039e0855567fb3793aee929ecb527e04ee000504aa73967bf4bd51d90c4a2dbc88f826790a06af7011432fde97ae7a22f9e24459dde462dc0d38c5157d2dc2 + checksum: 573bacabc42f9e6b7050ce7fe62c2a41fe878bb9659888904d9f1acbc91a051fa4a4528eafd5c04242ddba99919cd2f5fab8987f299aa2936145254fb5caa951 languageName: node linkType: hard @@ -22845,12 +22995,12 @@ __metadata: languageName: node linkType: hard -"wagmi@npm:2.12.11": - version: 2.12.11 - resolution: "wagmi@npm:2.12.11" +"wagmi@npm:2.12.25": + version: 2.12.25 + resolution: "wagmi@npm:2.12.25" dependencies: - "@wagmi/connectors": "npm:5.1.10" - "@wagmi/core": "npm:2.13.5" + "@wagmi/connectors": "npm:5.3.3" + "@wagmi/core": "npm:2.14.1" use-sync-external-store: "npm:1.2.0" peerDependencies: "@tanstack/react-query": ">=5.0.0" @@ -22860,7 +23010,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: bdf424fc1521b41bc19c933d1221133f377ed2f853d85d1da33d43cd737180bf3734ba8482ea475055a480d7a4fc32626ec29097aad07249b114662827c64ca8 + checksum: 18510c1a7528c28b013130ec84fefc5792ee3d4105d60b0b598075b5b6620d51ad0e890e936c6e549e44de4eb6a36e975d14745551dcff3d1d8a963e4d29b8df languageName: node linkType: hard @@ -22899,6 +23049,16 @@ __metadata: languageName: node linkType: hard +"webauthn-p256@npm:0.0.10": + version: 0.0.10 + resolution: "webauthn-p256@npm:0.0.10" + dependencies: + "@noble/curves": "npm:^1.4.0" + "@noble/hashes": "npm:^1.4.0" + checksum: 27d836d81a1fec24a31d2d9b652f8ff6876b51940d1003bbd14dc5cfa57c58d84223b5a4eece229516522fd997bc0bc7be618ac42b129fb5fa42fa530060b16d + languageName: node + linkType: hard + "webauthn-p256@npm:0.0.5": version: 0.0.5 resolution: "webauthn-p256@npm:0.0.5" @@ -23232,6 +23392,21 @@ __metadata: languageName: node linkType: hard +"ws@npm:8.18.0": + version: 8.18.0 + resolution: "ws@npm:8.18.0" + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ">=5.0.2" + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + checksum: 25eb33aff17edcb90721ed6b0eb250976328533ad3cd1a28a274bd263682e7296a6591ff1436d6cbc50fa67463158b062f9d1122013b361cec99a05f84680e06 + languageName: node + linkType: hard + "ws@npm:8.5.0": version: 8.5.0 resolution: "ws@npm:8.5.0" @@ -23484,15 +23659,14 @@ __metadata: languageName: node linkType: hard -"zustand@npm:4.4.1": - version: 4.4.1 - resolution: "zustand@npm:4.4.1" - dependencies: - use-sync-external-store: "npm:1.2.0" +"zustand@npm:5.0.0": + version: 5.0.0 + resolution: "zustand@npm:5.0.0" peerDependencies: - "@types/react": ">=16.8" - immer: ">=9.0" - react: ">=16.8" + "@types/react": ">=18.0.0" + immer: ">=9.0.6" + react: ">=18.0.0" + use-sync-external-store: ">=1.2.0" peerDependenciesMeta: "@types/react": optional: true @@ -23500,6 +23674,8 @@ __metadata: optional: true react: optional: true - checksum: c119273886e5cdbd7a9f80c9e0fee8a2c736bb6428e283b25c6dfd428789a95e10b6ed6b18553c955ce0d5dd62e2f4a84af3e2a41f31fdb34fd25462d2b19a8c + use-sync-external-store: + optional: true + checksum: 7546df78aa512f1d2271e238c44699c0ac4bc57f12ae46fcfe8ba1e8a97686fc690596e654101acfabcd706099aa5d3519fc3f22d32b3082baa60699bb333e9a languageName: node linkType: hard From 37983e71f78f85bcbd6b3c1708ddb359b920319b Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:56:00 -0300 Subject: [PATCH 093/114] chore: bump walletconnect deps to 2.17.2 in ethers packages (#267) * chore: bump walletconnect deps to 2.17.2 in ethers packages * chore: added changeset file --- .changeset/red-ladybugs-matter.md | 18 +++ package.json | 2 +- packages/ethers/package.json | 2 +- packages/ethers5/package.json | 2 +- yarn.lock | 232 +++++++++++++++--------------- 5 files changed, 134 insertions(+), 122 deletions(-) create mode 100644 .changeset/red-ladybugs-matter.md diff --git a/.changeset/red-ladybugs-matter.md b/.changeset/red-ladybugs-matter.md new file mode 100644 index 000000000..7c994a734 --- /dev/null +++ b/.changeset/red-ladybugs-matter.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +chore: bump walletconnect deps to 2.17.2 in ethers packages diff --git a/package.json b/package.json index a837e27cc..57bc2f9c4 100644 --- a/package.json +++ b/package.json @@ -49,7 +49,7 @@ "@types/jest": "29.5.7", "@types/qrcode": "1.5.5", "@types/react": "^18.2.6", - "@walletconnect/react-native-compat": "2.16.1", + "@walletconnect/react-native-compat": "2.17.2", "babel-jest": "^29.7.0", "eslint": "^8.46.0", "eslint-plugin-ft-flow": "2.0.3", diff --git a/packages/ethers/package.json b/packages/ethers/package.json index af6fdbddf..9047e6046 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -42,7 +42,7 @@ "@reown/appkit-scaffold-react-native": "1.0.2", "@reown/appkit-scaffold-utils-react-native": "1.0.2", "@reown/appkit-siwe-react-native": "1.0.2", - "@walletconnect/ethereum-provider": "2.16.1" + "@walletconnect/ethereum-provider": "2.17.2" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.17.0", diff --git a/packages/ethers5/package.json b/packages/ethers5/package.json index aeec760fc..790554726 100644 --- a/packages/ethers5/package.json +++ b/packages/ethers5/package.json @@ -42,7 +42,7 @@ "@reown/appkit-scaffold-react-native": "1.0.2", "@reown/appkit-scaffold-utils-react-native": "1.0.2", "@reown/appkit-siwe-react-native": "1.0.2", - "@walletconnect/ethereum-provider": "2.16.1" + "@walletconnect/ethereum-provider": "2.17.2" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.17.0", diff --git a/yarn.lock b/yarn.lock index 4f8e5d222..b395190d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6814,7 +6814,7 @@ __metadata: "@reown/appkit-scaffold-react-native": "npm:1.0.2" "@reown/appkit-scaffold-utils-react-native": "npm:1.0.2" "@reown/appkit-siwe-react-native": "npm:1.0.2" - "@walletconnect/ethereum-provider": "npm:2.16.1" + "@walletconnect/ethereum-provider": "npm:2.17.2" ethers: "npm:6.10.0" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" @@ -6835,7 +6835,7 @@ __metadata: "@reown/appkit-scaffold-react-native": "npm:1.0.2" "@reown/appkit-scaffold-utils-react-native": "npm:1.0.2" "@reown/appkit-siwe-react-native": "npm:1.0.2" - "@walletconnect/ethereum-provider": "npm:2.16.1" + "@walletconnect/ethereum-provider": "npm:2.17.2" ethers: "npm:5.7.2" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" @@ -8850,9 +8850,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/core@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/core@npm:2.16.1" +"@walletconnect/core@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/core@npm:2.17.0" dependencies: "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-provider": "npm:1.0.14" @@ -8865,18 +8865,18 @@ __metadata: "@walletconnect/relay-auth": "npm:1.0.4" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.16.1" - "@walletconnect/utils": "npm:2.16.1" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" events: "npm:3.3.0" lodash.isequal: "npm:4.5.0" uint8arrays: "npm:3.1.0" - checksum: fadb2db6afe8b3da79b3c84be4e885227efdb38ec5857c66211e5a4ca2cf7b7a0204a3e336b51586bc0ebc816a03da4b4f135269877dcd1119c36385776c1db4 + checksum: 34ae5b9b68c08c1dd3ebb2a6ebff8697307e76fbfe4d6b51d5d090da5cd1613e1c66fa5ac3a87c914333458d7b5bf075bb664292f6b2c7d438c72f706d87416d languageName: node linkType: hard -"@walletconnect/core@npm:2.17.0": - version: 2.17.0 - resolution: "@walletconnect/core@npm:2.17.0" +"@walletconnect/core@npm:2.17.2": + version: 2.17.2 + resolution: "@walletconnect/core@npm:2.17.2" dependencies: "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-provider": "npm:1.0.14" @@ -8889,12 +8889,13 @@ __metadata: "@walletconnect/relay-auth": "npm:1.0.4" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.17.0" - "@walletconnect/utils": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.2" + "@walletconnect/utils": "npm:2.17.2" + "@walletconnect/window-getters": "npm:1.0.1" events: "npm:3.3.0" lodash.isequal: "npm:4.5.0" uint8arrays: "npm:3.1.0" - checksum: 34ae5b9b68c08c1dd3ebb2a6ebff8697307e76fbfe4d6b51d5d090da5cd1613e1c66fa5ac3a87c914333458d7b5bf075bb664292f6b2c7d438c72f706d87416d + checksum: 6124b81892a4e5e9350cfff22a7ce3a23a66c9589221411bd8bfd411fc392b6b343fae1634b32000d4275ba11b1a0f732cf6b7ba5da35b388854c7e7b4f2764d languageName: node linkType: hard @@ -8907,39 +8908,40 @@ __metadata: languageName: node linkType: hard -"@walletconnect/ethereum-provider@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/ethereum-provider@npm:2.16.1" +"@walletconnect/ethereum-provider@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/ethereum-provider@npm:2.17.0" dependencies: "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/modal": "npm:2.6.2" - "@walletconnect/sign-client": "npm:2.16.1" - "@walletconnect/types": "npm:2.16.1" - "@walletconnect/universal-provider": "npm:2.16.1" - "@walletconnect/utils": "npm:2.16.1" + "@walletconnect/modal": "npm:2.7.0" + "@walletconnect/sign-client": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/universal-provider": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" events: "npm:3.3.0" - checksum: 985432b2f5c3da7648640e498d92ae2da05f0a18d43055d7a930c71185d9865fc38bd0a6c4fcb6e06007a8e61084f4e682f9054abee495713e7fe425cf02463e + checksum: b046a9c296e95b22841f0b2efd28a4ce1a38529a9ba412d3c8ffc482879d79c3d2a24b8c0ec712baecf781938b4321ab5c1ecad5573d078add7c47b0cfd08a25 languageName: node linkType: hard -"@walletconnect/ethereum-provider@npm:2.17.0": - version: 2.17.0 - resolution: "@walletconnect/ethereum-provider@npm:2.17.0" +"@walletconnect/ethereum-provider@npm:2.17.2": + version: 2.17.2 + resolution: "@walletconnect/ethereum-provider@npm:2.17.2" dependencies: "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/modal": "npm:2.7.0" - "@walletconnect/sign-client": "npm:2.17.0" - "@walletconnect/types": "npm:2.17.0" - "@walletconnect/universal-provider": "npm:2.17.0" - "@walletconnect/utils": "npm:2.17.0" + "@walletconnect/sign-client": "npm:2.17.2" + "@walletconnect/types": "npm:2.17.2" + "@walletconnect/universal-provider": "npm:2.17.2" + "@walletconnect/utils": "npm:2.17.2" events: "npm:3.3.0" - checksum: b046a9c296e95b22841f0b2efd28a4ce1a38529a9ba412d3c8ffc482879d79c3d2a24b8c0ec712baecf781938b4321ab5c1ecad5573d078add7c47b0cfd08a25 + checksum: 191eb6106119a57c1e4c82212cf6650f9e9e32b26a39c5aba0745125067e9153f30ddc43254bafd95ed5f1debd5a5d08094dd5a037ff4a61e645c652ae3d022c languageName: node linkType: hard @@ -9056,15 +9058,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/modal-core@npm:2.6.2": - version: 2.6.2 - resolution: "@walletconnect/modal-core@npm:2.6.2" - dependencies: - valtio: "npm:1.11.2" - checksum: 5e3fb21a1fc923ec0d2a3e33cc360e3d56278a211609d5fd4cc4d6e3b4f1acb40b9783fcc771b259b78c7e731af3862def096aa1da2e210e7859729808304c94 - languageName: node - linkType: hard - "@walletconnect/modal-core@npm:2.7.0": version: 2.7.0 resolution: "@walletconnect/modal-core@npm:2.7.0" @@ -9074,18 +9067,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/modal-ui@npm:2.6.2": - version: 2.6.2 - resolution: "@walletconnect/modal-ui@npm:2.6.2" - dependencies: - "@walletconnect/modal-core": "npm:2.6.2" - lit: "npm:2.8.0" - motion: "npm:10.16.2" - qrcode: "npm:1.5.3" - checksum: 5d8f0a2703b9757dfa48ad3e48a40e64608f6a28db31ec93a2f10e942dcc5ee986c03ffdab94018e905836d339131fc928bc14614a94943011868cdddc36a32a - languageName: node - linkType: hard - "@walletconnect/modal-ui@npm:2.7.0": version: 2.7.0 resolution: "@walletconnect/modal-ui@npm:2.7.0" @@ -9098,16 +9079,6 @@ __metadata: languageName: node linkType: hard -"@walletconnect/modal@npm:2.6.2": - version: 2.6.2 - resolution: "@walletconnect/modal@npm:2.6.2" - dependencies: - "@walletconnect/modal-core": "npm:2.6.2" - "@walletconnect/modal-ui": "npm:2.6.2" - checksum: 1cc309f63d061e49fdf7b10d28093d7ef1a47f4624f717f8fd3bf6097ac3b00cea4acc45c50e8bd386d4bcfdf10f4dcba960f7129c557b9dc42ef7d05b970807 - languageName: node - linkType: hard - "@walletconnect/modal@npm:2.7.0": version: 2.7.0 resolution: "@walletconnect/modal@npm:2.7.0" @@ -9118,9 +9089,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/react-native-compat@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/react-native-compat@npm:2.16.1" +"@walletconnect/react-native-compat@npm:2.17.1": + version: 2.17.1 + resolution: "@walletconnect/react-native-compat@npm:2.17.1" dependencies: events: "npm:3.3.0" fast-text-encoding: "npm:1.0.6" @@ -9129,17 +9100,18 @@ __metadata: "@react-native-async-storage/async-storage": "*" "@react-native-community/netinfo": "*" expo-application: "*" + react-native: "*" react-native-get-random-values: "*" peerDependenciesMeta: expo-application: optional: true - checksum: d73dd15cb83b35ac46420b184906c0ae42216fe69b8b90d631bdc8479952703dabee7f498cbf089ad23826b3f93370b8ebbc6c7c911cdcba7cf80f41f8614370 + checksum: 55afa3b7de9cf71f208a10d30ac70bbef67d32013807ebfc2f99ab61cc7e27a51def1e4e795e88e9164452c0ab1d219820cf5b8e0e5e4c316a7501a508476bba languageName: node linkType: hard -"@walletconnect/react-native-compat@npm:2.17.1": - version: 2.17.1 - resolution: "@walletconnect/react-native-compat@npm:2.17.1" +"@walletconnect/react-native-compat@npm:2.17.2": + version: 2.17.2 + resolution: "@walletconnect/react-native-compat@npm:2.17.2" dependencies: events: "npm:3.3.0" fast-text-encoding: "npm:1.0.6" @@ -9153,7 +9125,7 @@ __metadata: peerDependenciesMeta: expo-application: optional: true - checksum: 55afa3b7de9cf71f208a10d30ac70bbef67d32013807ebfc2f99ab61cc7e27a51def1e4e795e88e9164452c0ab1d219820cf5b8e0e5e4c316a7501a508476bba + checksum: ec6e15d6966f22268a8c8f94e19d1259dfed25097632d5ff1cf8134aaf0f87c0f6a8528b3b2d39754c7d6d9d5d402ece91a7e63c8c5302be29fbf724f0cd4b25 languageName: node linkType: hard @@ -9189,37 +9161,37 @@ __metadata: languageName: node linkType: hard -"@walletconnect/sign-client@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/sign-client@npm:2.16.1" +"@walletconnect/sign-client@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/sign-client@npm:2.17.0" dependencies: - "@walletconnect/core": "npm:2.16.1" + "@walletconnect/core": "npm:2.17.0" "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/logger": "npm:2.1.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.16.1" - "@walletconnect/utils": "npm:2.16.1" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" events: "npm:3.3.0" - checksum: 88727aca13a4e4b5420bde6cb15567a5d07587e8f814025e8c11f69edf941112acf78b21487a8a1380afb42351f8057cb2fc9e92c625f5adee3d085d3efeb072 + checksum: 48f7d13b3db49584a40dc2653f49fabadd100a324e2213476b8d9e4d6fe0808a08ae14103d2e5b609abff3115197003d8570d606275dbd0f6774d0d49da10c61 languageName: node linkType: hard -"@walletconnect/sign-client@npm:2.17.0": - version: 2.17.0 - resolution: "@walletconnect/sign-client@npm:2.17.0" +"@walletconnect/sign-client@npm:2.17.2": + version: 2.17.2 + resolution: "@walletconnect/sign-client@npm:2.17.2" dependencies: - "@walletconnect/core": "npm:2.17.0" + "@walletconnect/core": "npm:2.17.2" "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/logger": "npm:2.1.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.17.0" - "@walletconnect/utils": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.2" + "@walletconnect/utils": "npm:2.17.2" events: "npm:3.3.0" - checksum: 48f7d13b3db49584a40dc2653f49fabadd100a324e2213476b8d9e4d6fe0808a08ae14103d2e5b609abff3115197003d8570d606275dbd0f6774d0d49da10c61 + checksum: 0acbda4ea34be209b1436134804e72641ca377e2bb6823b7d94177b30e50b8e6de28dfdad6ff64dac61a1305e7b6f281df2357488382c88e440a79b817d377a8 languageName: node linkType: hard @@ -9232,9 +9204,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/types@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/types@npm:2.16.1" +"@walletconnect/types@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/types@npm:2.17.0" dependencies: "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" @@ -9242,13 +9214,13 @@ __metadata: "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" events: "npm:3.3.0" - checksum: d796b934fe30771a281dd716c4a0a36992a96b201cebd1012ad2278f7aff224503af6bb18e39461498927d47368c0b7a8d0457bbb42b4fe9712f3307b5c131f7 + checksum: bdc0c062da1edb4410882d9cfca1bb30eb0afd7caea90d5e7a66eaf15e28380e9ef97635cd5e5a017947f4c814c1f780622b4d8946b11a335d415ae066ec7ade languageName: node linkType: hard -"@walletconnect/types@npm:2.17.0": - version: 2.17.0 - resolution: "@walletconnect/types@npm:2.17.0" +"@walletconnect/types@npm:2.17.2": + version: 2.17.2 + resolution: "@walletconnect/types@npm:2.17.2" dependencies: "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" @@ -9256,47 +9228,50 @@ __metadata: "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" events: "npm:3.3.0" - checksum: bdc0c062da1edb4410882d9cfca1bb30eb0afd7caea90d5e7a66eaf15e28380e9ef97635cd5e5a017947f4c814c1f780622b4d8946b11a335d415ae066ec7ade + checksum: 95bd3e4f4f2ef181ea69691800a0a06be2c4fa900ae972539851c5817a0f01b4ba9f381161d044df4db004f431bc416548ec6eca0ac523fc1fb06014386accac languageName: node linkType: hard -"@walletconnect/universal-provider@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/universal-provider@npm:2.16.1" +"@walletconnect/universal-provider@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/universal-provider@npm:2.17.0" dependencies: "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/sign-client": "npm:2.16.1" - "@walletconnect/types": "npm:2.16.1" - "@walletconnect/utils": "npm:2.16.1" + "@walletconnect/sign-client": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.0" + "@walletconnect/utils": "npm:2.17.0" events: "npm:3.3.0" - checksum: cc128497dbf8555f65d383bd1c1962ee4d84a8bdb21680820766a96157799cf498d56836fb620d5c02f9ad7691c21d6173603795df5f7e2a558be47fab4133c1 + checksum: 7c1afc79054db5add4e937d7adaadb4fc26aecffb5d749d388418fa5d4eb153807ab4de301b642cd80669b4e5c6bcae917f18cf5ce8696d87da8b3705b60d1ec languageName: node linkType: hard -"@walletconnect/universal-provider@npm:2.17.0": - version: 2.17.0 - resolution: "@walletconnect/universal-provider@npm:2.17.0" +"@walletconnect/universal-provider@npm:2.17.2": + version: 2.17.2 + resolution: "@walletconnect/universal-provider@npm:2.17.2" dependencies: + "@walletconnect/events": "npm:1.0.1" "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/sign-client": "npm:2.17.0" - "@walletconnect/types": "npm:2.17.0" - "@walletconnect/utils": "npm:2.17.0" + "@walletconnect/sign-client": "npm:2.17.2" + "@walletconnect/types": "npm:2.17.2" + "@walletconnect/utils": "npm:2.17.2" events: "npm:3.3.0" - checksum: 7c1afc79054db5add4e937d7adaadb4fc26aecffb5d749d388418fa5d4eb153807ab4de301b642cd80669b4e5c6bcae917f18cf5ce8696d87da8b3705b60d1ec + lodash: "npm:4.17.21" + checksum: afc617916ce2a8e8b669f2d5813795fe0d2cc4400dc0b3275e0b814e5c960b6bc2a1de27fa22021a5fc124aa58ec5ec6a02403fd49ddc4945e1ea941fba3c4da languageName: node linkType: hard -"@walletconnect/utils@npm:2.16.1": - version: 2.16.1 - resolution: "@walletconnect/utils@npm:2.16.1" +"@walletconnect/utils@npm:2.17.0": + version: 2.17.0 + resolution: "@walletconnect/utils@npm:2.17.0" dependencies: "@stablelib/chacha20poly1305": "npm:1.0.1" "@stablelib/hkdf": "npm:1.0.1" @@ -9307,38 +9282,42 @@ __metadata: "@walletconnect/relay-auth": "npm:1.0.4" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.16.1" + "@walletconnect/types": "npm:2.17.0" "@walletconnect/window-getters": "npm:1.0.1" "@walletconnect/window-metadata": "npm:1.0.1" detect-browser: "npm:5.3.0" elliptic: "npm:^6.5.7" query-string: "npm:7.1.3" uint8arrays: "npm:3.1.0" - checksum: 0bfbc9d5f8be999f4c3d315a5d64c59ad6fcaaa14898bcdfea3bae4cf7a79da40f26cba27ea25fea6320b608064ee42059d10ac70c87086be876f08d7ad73205 + checksum: d1da74b2cd7af35f16d735fe408cfc820c611b2709bd00899e4e91b0b0a6dcd8f344f97df34d0ef8cabc121619a40b62118ffa2aa233ddba9863d1ba23480a0c languageName: node linkType: hard -"@walletconnect/utils@npm:2.17.0": - version: 2.17.0 - resolution: "@walletconnect/utils@npm:2.17.0" +"@walletconnect/utils@npm:2.17.2": + version: 2.17.2 + resolution: "@walletconnect/utils@npm:2.17.2" dependencies: + "@ethersproject/hash": "npm:5.7.0" + "@ethersproject/transactions": "npm:5.7.0" "@stablelib/chacha20poly1305": "npm:1.0.1" "@stablelib/hkdf": "npm:1.0.1" "@stablelib/random": "npm:1.0.2" "@stablelib/sha256": "npm:1.0.1" "@stablelib/x25519": "npm:1.0.3" + "@walletconnect/jsonrpc-utils": "npm:1.0.8" + "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/relay-api": "npm:1.0.11" "@walletconnect/relay-auth": "npm:1.0.4" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.17.0" + "@walletconnect/types": "npm:2.17.2" "@walletconnect/window-getters": "npm:1.0.1" "@walletconnect/window-metadata": "npm:1.0.1" detect-browser: "npm:5.3.0" - elliptic: "npm:^6.5.7" + elliptic: "npm:6.6.0" query-string: "npm:7.1.3" uint8arrays: "npm:3.1.0" - checksum: d1da74b2cd7af35f16d735fe408cfc820c611b2709bd00899e4e91b0b0a6dcd8f344f97df34d0ef8cabc121619a40b62118ffa2aa233ddba9863d1ba23480a0c + checksum: b44c0025be12301a28715a204c037328eae4fa432f0ee1730da08b3b6583e07aeaf59efd9dcc52209f6a61b50b31c84e555028b97067dfdf9f5efe1211378fc8 languageName: node linkType: hard @@ -9892,7 +9871,7 @@ __metadata: "@types/jest": "npm:29.5.7" "@types/qrcode": "npm:1.5.5" "@types/react": "npm:^18.2.6" - "@walletconnect/react-native-compat": "npm:2.16.1" + "@walletconnect/react-native-compat": "npm:2.17.2" babel-jest: "npm:^29.7.0" eslint: "npm:^8.46.0" eslint-plugin-ft-flow: "npm:2.0.3" @@ -12356,6 +12335,21 @@ __metadata: languageName: node linkType: hard +"elliptic@npm:6.6.0": + version: 6.6.0 + resolution: "elliptic@npm:6.6.0" + dependencies: + bn.js: "npm:^4.11.9" + brorand: "npm:^1.1.0" + hash.js: "npm:^1.0.0" + hmac-drbg: "npm:^1.0.1" + inherits: "npm:^2.0.4" + minimalistic-assert: "npm:^1.0.1" + minimalistic-crypto-utils: "npm:^1.0.1" + checksum: 42eb3492e218017bf8923a5d14a86f414952f2f771361805b3ae9f380923b5da53e203d0d92be95cb0a248858a78db7db5934a346e268abb757e6fe561d401c9 + languageName: node + linkType: hard + "elliptic@npm:^6.5.7": version: 6.5.7 resolution: "elliptic@npm:6.5.7" @@ -16914,7 +16908,7 @@ __metadata: languageName: node linkType: hard -"lodash@npm:^4.17.13, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4": +"lodash@npm:4.17.21, lodash@npm:^4.17.13, lodash@npm:^4.17.20, lodash@npm:^4.17.21, lodash@npm:^4.17.4": version: 4.17.21 resolution: "lodash@npm:4.17.21" checksum: d8cbea072bb08655bb4c989da418994b073a608dffa608b09ac04b43a791b12aeae7cd7ad919aa4c925f33b48490b5cfe6c1f71d827956071dae2e7bb3a6b74c From f7bad91e91ff90025f8cb3ec3675294e7af44221 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 14:56:19 -0300 Subject: [PATCH 094/114] chore(deps): bump @babel/traverse from 7.22.10 to 7.25.6 (#249) Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.22.10 to 7.25.6. - [Release notes](https://github.com/babel/babel/releases) - [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md) - [Commits](https://github.com/babel/babel/commits/v7.25.6/packages/babel-traverse) --- updated-dependencies: - dependency-name: "@babel/traverse" dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 95 ++----------------------------------------------------- 1 file changed, 2 insertions(+), 93 deletions(-) diff --git a/yarn.lock b/yarn.lock index b395190d6..e223f6c01 100644 --- a/yarn.lock +++ b/yarn.lock @@ -387,7 +387,7 @@ __metadata: languageName: node linkType: hard -"@babel/generator@npm:^7.24.8, @babel/generator@npm:^7.24.9": +"@babel/generator@npm:^7.24.9": version: 7.24.10 resolution: "@babel/generator@npm:7.24.10" dependencies: @@ -636,16 +636,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-function-name@npm:^7.23.0": - version: 7.23.0 - resolution: "@babel/helper-function-name@npm:7.23.0" - dependencies: - "@babel/template": "npm:^7.22.15" - "@babel/types": "npm:^7.23.0" - checksum: d771dd1f3222b120518176733c52b7cadac1c256ff49b1889dbbe5e3fed81db855b8cc4e40d949c9d3eae0e795e8229c1c8c24c0e83f27cfa6ee3766696c6428 - languageName: node - linkType: hard - "@babel/helper-function-name@npm:^7.24.7": version: 7.24.7 resolution: "@babel/helper-function-name@npm:7.24.7" @@ -665,15 +655,6 @@ __metadata: languageName: node linkType: hard -"@babel/helper-hoist-variables@npm:^7.24.7": - version: 7.24.7 - resolution: "@babel/helper-hoist-variables@npm:7.24.7" - dependencies: - "@babel/types": "npm:^7.24.7" - checksum: 19ee37563bbd1219f9d98991ad0e9abef77803ee5945fd85aa7aa62a67c69efca9a801696a1b58dda27f211e878b3327789e6fd2a6f6c725ccefe36774b5ce95 - languageName: node - linkType: hard - "@babel/helper-member-expression-to-functions@npm:^7.22.15": version: 7.23.0 resolution: "@babel/helper-member-expression-to-functions@npm:7.23.0" @@ -3153,7 +3134,7 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.25.2": +"@babel/traverse@npm:^7.18.9, @babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.22.10, @babel/traverse@npm:^7.23.2, @babel/traverse@npm:^7.23.6, @babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8, @babel/traverse@npm:^7.25.2, @babel/traverse@npm:^7.7.4": version: 7.25.6 resolution: "@babel/traverse@npm:7.25.6" dependencies: @@ -3168,78 +3149,6 @@ __metadata: languageName: node linkType: hard -"@babel/traverse@npm:^7.20.0, @babel/traverse@npm:^7.22.10, @babel/traverse@npm:^7.7.4": - version: 7.22.10 - resolution: "@babel/traverse@npm:7.22.10" - dependencies: - "@babel/code-frame": "npm:^7.22.10" - "@babel/generator": "npm:^7.22.10" - "@babel/helper-environment-visitor": "npm:^7.22.5" - "@babel/helper-function-name": "npm:^7.22.5" - "@babel/helper-hoist-variables": "npm:^7.22.5" - "@babel/helper-split-export-declaration": "npm:^7.22.6" - "@babel/parser": "npm:^7.22.10" - "@babel/types": "npm:^7.22.10" - debug: "npm:^4.1.0" - globals: "npm:^11.1.0" - checksum: 8e8b63b053962908408ed9d954810e93f241122222db115327ed5876d020f420fc115ef2d79623c2a4928447ddc002ec220be2a152b241d19de2480c88e10cfb - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.23.2": - version: 7.23.2 - resolution: "@babel/traverse@npm:7.23.2" - dependencies: - "@babel/code-frame": "npm:^7.22.13" - "@babel/generator": "npm:^7.23.0" - "@babel/helper-environment-visitor": "npm:^7.22.20" - "@babel/helper-function-name": "npm:^7.23.0" - "@babel/helper-hoist-variables": "npm:^7.22.5" - "@babel/helper-split-export-declaration": "npm:^7.22.6" - "@babel/parser": "npm:^7.23.0" - "@babel/types": "npm:^7.23.0" - debug: "npm:^4.1.0" - globals: "npm:^11.1.0" - checksum: d096c7c4bab9262a2f658298a3c630ae4a15a10755bb257ae91d5ab3e3b2877438934859c8d34018b7727379fe6b26c4fa2efc81cf4c462a7fe00caf79fa02ff - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.23.6": - version: 7.23.6 - resolution: "@babel/traverse@npm:7.23.6" - dependencies: - "@babel/code-frame": "npm:^7.23.5" - "@babel/generator": "npm:^7.23.6" - "@babel/helper-environment-visitor": "npm:^7.22.20" - "@babel/helper-function-name": "npm:^7.23.0" - "@babel/helper-hoist-variables": "npm:^7.22.5" - "@babel/helper-split-export-declaration": "npm:^7.22.6" - "@babel/parser": "npm:^7.23.6" - "@babel/types": "npm:^7.23.6" - debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 5b4ebb94a00a7e1daf111e4b0b45a7998d5b7598637a14e75e855e88cc1b702789e09a958726b5d599a003be1e9032dbdfde4b88ea6061332228738950d5582d - languageName: node - linkType: hard - -"@babel/traverse@npm:^7.24.7, @babel/traverse@npm:^7.24.8": - version: 7.24.8 - resolution: "@babel/traverse@npm:7.24.8" - dependencies: - "@babel/code-frame": "npm:^7.24.7" - "@babel/generator": "npm:^7.24.8" - "@babel/helper-environment-visitor": "npm:^7.24.7" - "@babel/helper-function-name": "npm:^7.24.7" - "@babel/helper-hoist-variables": "npm:^7.24.7" - "@babel/helper-split-export-declaration": "npm:^7.24.7" - "@babel/parser": "npm:^7.24.8" - "@babel/types": "npm:^7.24.8" - debug: "npm:^4.3.1" - globals: "npm:^11.1.0" - checksum: 67a5cc35824455cdb54fb9e196a44b3186283e29018a9c2331f51763921e18e891b3c60c283615a27540ec8eb4c8b89f41c237b91f732a7aa518b2eb7a0d434d - languageName: node - linkType: hard - "@babel/types@npm:^7.0.0, @babel/types@npm:^7.20.0, @babel/types@npm:^7.20.7, @babel/types@npm:^7.22.10, @babel/types@npm:^7.22.5, @babel/types@npm:^7.3.3, @babel/types@npm:^7.4.4": version: 7.22.10 resolution: "@babel/types@npm:7.22.10" From 0d2bae4a91063fc9fc6feb10e9fe780924cb670f Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 6 Nov 2024 11:21:52 -0300 Subject: [PATCH 095/114] chore: remove nft tab (#268) --- .changeset/grumpy-poems-tease.md | 18 ++++++++++++++++++ .../src/partials/w3m-account-nfts/index.tsx | 11 ----------- .../w3m-account-wallet-features/index.tsx | 6 ++---- 3 files changed, 20 insertions(+), 15 deletions(-) create mode 100644 .changeset/grumpy-poems-tease.md delete mode 100644 packages/scaffold/src/partials/w3m-account-nfts/index.tsx diff --git a/.changeset/grumpy-poems-tease.md b/.changeset/grumpy-poems-tease.md new file mode 100644 index 000000000..0ce6e3641 --- /dev/null +++ b/.changeset/grumpy-poems-tease.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +chore: removed nft tab diff --git a/packages/scaffold/src/partials/w3m-account-nfts/index.tsx b/packages/scaffold/src/partials/w3m-account-nfts/index.tsx deleted file mode 100644 index 59643cabc..000000000 --- a/packages/scaffold/src/partials/w3m-account-nfts/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { AccountPlaceholder } from '../w3m-account-placeholder'; - -export function AccountNfts() { - return ( - - ); -} diff --git a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx index b0a084d19..462058ae7 100644 --- a/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-wallet-features/index.tsx @@ -9,7 +9,6 @@ import { RouterController } from '@reown/appkit-core-react-native'; import type { Balance as BalanceType } from '@reown/appkit-common-react-native'; -import { AccountNfts } from '../w3m-account-nfts'; import { AccountActivity } from '../w3m-account-activity'; import { AccountTokens } from '../w3m-account-tokens'; import styles from './styles'; @@ -87,12 +86,11 @@ export function AccountWalletFeatures() { /> - + {activeTab === 0 && } - {activeTab === 1 && } - {activeTab === 2 && } + {activeTab === 1 && } ); From f5ca7548115d25e121931961295d822ecd13833c Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 6 Nov 2024 14:57:11 -0300 Subject: [PATCH 096/114] fix: show wallet view for email wallets (#269) --- .changeset/witty-taxis-smell.md | 18 ++++++++++++++++++ .../src/controllers/ConnectionController.ts | 1 + .../w3m-connecting-external-view/index.tsx | 4 +--- .../views/w3m-connecting-social-view/index.tsx | 1 - 4 files changed, 20 insertions(+), 4 deletions(-) create mode 100644 .changeset/witty-taxis-smell.md diff --git a/.changeset/witty-taxis-smell.md b/.changeset/witty-taxis-smell.md new file mode 100644 index 000000000..d825b7102 --- /dev/null +++ b/.changeset/witty-taxis-smell.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: show wallet view for email wallets diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index 0e3057903..4e0087901 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -91,6 +91,7 @@ export const ConnectionController = { async connectExternal(options: ConnectExternalOptions) { await this._getClient().connectExternal?.(options); + ConnectorController.setConnectedConnector(options.type); }, async signMessage(message: string) { diff --git a/packages/scaffold/src/views/w3m-connecting-external-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-external-view/index.tsx index 8d6f8678b..e5df102ea 100644 --- a/packages/scaffold/src/views/w3m-connecting-external-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-external-view/index.tsx @@ -8,8 +8,7 @@ import { ModalController, EventsController, StorageUtil, - type WcWallet, - ConnectorController + type WcWallet } from '@reown/appkit-core-react-native'; import { Button, @@ -55,7 +54,6 @@ export function ConnectingExternalView() { try { if (connector) { await ConnectionController.connectExternal(connector); - ConnectorController.setConnectedConnector(connector.type); storeConnectedWallet(data?.wallet); ModalController.close(); EventsController.sendEvent({ diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx index a644dd464..50a8af39d 100644 --- a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx @@ -54,7 +54,6 @@ export function ConnectingSocialView() { const parsedUrl = new URL(url); await provider?.connectSocial(parsedUrl.search); await ConnectionController.connectExternal(authConnector); - ConnectorController.setConnectedConnector('AUTH'); ConnectionController.setConnectedSocialProvider(socialProvider); WebviewController.setConnecting(false); From f2a139a264b2604c3cb91c73d044d695dfec5392 Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 12 Nov 2024 11:54:22 -0300 Subject: [PATCH 097/114] fix/smart accounts (#270) --- .../core/src/controllers/NetworkController.ts | 7 ++++++ .../core/src/controllers/WebviewController.ts | 17 +++++++++++++- packages/ethers/src/client.ts | 1 + packages/ethers5/src/client.ts | 1 + .../src/partials/w3m-input-address/index.tsx | 1 - .../src/views/w3m-account-view/index.tsx | 20 ++++++---------- .../w3m-connecting-social-view/index.tsx | 23 +++++++++++++------ .../index.tsx | 3 +-- .../views/w3m-wallet-receive-view/index.tsx | 18 ++++++++------- .../src/views/w3m-wallet-send-view/index.tsx | 5 ++-- packages/ui/src/components/wui-card/index.tsx | 2 +- .../src/composites/wui-account-pill/index.tsx | 18 ++------------- .../src/composites/wui-account-pill/styles.ts | 3 ++- packages/wagmi/src/client.ts | 16 +++++++------ packages/wallet/src/AppKitFrameConstants.ts | 8 +++++-- packages/wallet/src/AppKitWebview.tsx | 6 ++++- 16 files changed, 87 insertions(+), 62 deletions(-) diff --git a/packages/core/src/controllers/NetworkController.ts b/packages/core/src/controllers/NetworkController.ts index b45c01088..8d62235f3 100644 --- a/packages/core/src/controllers/NetworkController.ts +++ b/packages/core/src/controllers/NetworkController.ts @@ -88,6 +88,13 @@ export const NetworkController = { .filter(Boolean) as CaipNetwork[]; }, + getSmartAccountEnabledNetworks() { + return this.getApprovedCaipNetworks().filter( + network => + state.smartAccountEnabledNetworks?.find(networkId => network.id === `eip155:${networkId}`) + ); + }, + async switchActiveNetwork(network: NetworkControllerState['caipNetwork']) { await this._getClient().switchCaipNetwork(network); state.caipNetwork = network; diff --git a/packages/core/src/controllers/WebviewController.ts b/packages/core/src/controllers/WebviewController.ts index f11cf7f66..3cec52ba7 100644 --- a/packages/core/src/controllers/WebviewController.ts +++ b/packages/core/src/controllers/WebviewController.ts @@ -8,6 +8,7 @@ export interface WebviewControllerState { webviewUrl?: string; connecting?: boolean; connectingProvider?: SocialProvider; + processingAuth?: boolean; } // -- State --------------------------------------------- // @@ -15,7 +16,8 @@ const state = proxy({ frameViewVisible: false, webviewVisible: false, connecting: false, - connectingProvider: undefined + connectingProvider: undefined, + processingAuth: false }); // -- Controller ---------------------------------------- // @@ -44,5 +46,18 @@ export const WebviewController = { setConnectingProvider(provider: WebviewControllerState['connectingProvider']) { state.connectingProvider = provider; + }, + + setProcessingAuth(processingAuth: WebviewControllerState['processingAuth']) { + state.processingAuth = processingAuth; + }, + + reset() { + state.frameViewVisible = false; + state.webviewVisible = false; + state.connecting = false; + state.connectingProvider = undefined; + state.processingAuth = false; + state.webviewUrl = undefined; } }; diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index 3147c523c..499cfc2aa 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -946,6 +946,7 @@ export class AppKit extends AppKitScaffold { const connectedConnector = await StorageUtil.getItem('@w3m/connected_connector'); if (connectedConnector === 'AUTH') { + // Set loader until it reconnects this.setLoading(true); } diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index f700f5f8a..56b233e77 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -923,6 +923,7 @@ export class AppKit extends AppKitScaffold { const connectedConnector = await StorageUtil.getItem('@w3m/connected_connector'); if (connectedConnector === 'AUTH') { + // Set loader until it reconnects this.setLoading(true); } diff --git a/packages/scaffold/src/partials/w3m-input-address/index.tsx b/packages/scaffold/src/partials/w3m-input-address/index.tsx index 40e67a8a2..a7b01ab1b 100644 --- a/packages/scaffold/src/partials/w3m-input-address/index.tsx +++ b/packages/scaffold/src/partials/w3m-input-address/index.tsx @@ -64,7 +64,6 @@ export function InputAddress({ value }: InputAddressProps) { selectionColor={Theme['accent-100']} underlineColorAndroid="transparent" selectTextOnFocus={false} - multiline returnKeyLabel="Done" /> diff --git a/packages/scaffold/src/views/w3m-account-view/index.tsx b/packages/scaffold/src/views/w3m-account-view/index.tsx index 1ccc45444..661e0f5d2 100644 --- a/packages/scaffold/src/views/w3m-account-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-view/index.tsx @@ -16,15 +16,13 @@ import { AssetUtil, ModalController, NetworkController, - OptionsController, RouterController, - SendController, - SnackController + SendController } from '@reown/appkit-core-react-native'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; -import styles from './styles'; import { AccountWalletFeatures } from '../../partials/w3m-account-wallet-features'; +import styles from './styles'; export function AccountView() { const Theme = useTheme(); @@ -36,13 +34,6 @@ export function AccountView() { const showActivate = preferredAccountType === 'eoa' && NetworkController.checkIfSmartAccountEnabled(); - const onCopyAddress = (value: string) => { - if (OptionsController.isClipboardAvailable() && value) { - OptionsController.copyToClipboard(value); - SnackController.showSuccess('Address copied'); - } - }; - const onProfilePress = () => { RouterController.push('AccountDefault'); }; @@ -82,13 +73,16 @@ export function AccountView() { {showActivate && ( - + )} diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx index 50a8af39d..d5eb617df 100644 --- a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx @@ -1,5 +1,6 @@ import { useSnapshot } from 'valtio'; import { useCallback, useEffect, useState } from 'react'; +import { Platform } from 'react-native'; import { ConnectionController, ConnectorController, @@ -19,7 +20,7 @@ import styles from './styles'; export function ConnectingSocialView() { const { data } = RouterController.state; const { maxWidth: width } = useCustomDimensions(); - const { connecting } = useSnapshot(WebviewController.state); + const { processingAuth } = useSnapshot(WebviewController.state); const authConnector = ConnectorController.getAuthConnector(); const [error, setError] = useState(false); const socialProvider = data?.socialProvider; @@ -32,7 +33,10 @@ export function ConnectingSocialView() { provider: socialProvider }); WebviewController.setWebviewUrl(uri); - WebviewController.setWebviewVisible(true); + + const isNativeApple = socialProvider === 'apple' && Platform.OS === 'ios'; + + WebviewController.setWebviewVisible(!isNativeApple); WebviewController.setConnecting(true); WebviewController.setConnectingProvider(socialProvider); } @@ -49,7 +53,13 @@ export function ConnectingSocialView() { const socialMessageHandler = useCallback( async (url: string) => { try { - if (url.includes('/sdk/oauth') && socialProvider && authConnector) { + if ( + url.includes('/sdk/oauth') && + socialProvider && + authConnector && + !WebviewController.state.processingAuth + ) { + WebviewController.setProcessingAuth(true); WebviewController.setWebviewVisible(false); const parsedUrl = new URL(url); await provider?.connectSocial(parsedUrl.search); @@ -64,6 +74,7 @@ export function ConnectingSocialView() { }); ModalController.close(); + WebviewController.reset(); } } catch (e) { EventsController.sendEvent({ @@ -71,9 +82,7 @@ export function ConnectingSocialView() { event: 'SOCIAL_LOGIN_ERROR', properties: { provider: socialProvider! } }); - WebviewController.setWebviewVisible(false); - WebviewController.setConnecting(false); - WebviewController.setConnectingProvider(undefined); + WebviewController.reset(); RouterController.goBack(); SnackController.showError('Something went wrong'); } @@ -120,7 +129,7 @@ export function ConnectingSocialView() { {`Continue with ${StringUtil.capitalize(socialProvider)}`} - {connecting ? 'Retrieving user data' : 'Connect in the provider window'} + {processingAuth ? 'Retrieving user data' : 'Connect in the provider window'} ); diff --git a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx index 2e8c061b6..3695bc470 100644 --- a/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-compatible-networks-view/index.tsx @@ -13,11 +13,10 @@ import styles from './styles'; export function WalletCompatibleNetworks() { const { padding } = useCustomDimensions(); const { preferredAccountType } = useSnapshot(AccountController.state); - const { caipNetwork } = useSnapshot(NetworkController.state); const isSmartAccount = preferredAccountType === 'smartAccount' && NetworkController.checkIfSmartAccountEnabled(); const approvedNetworks = isSmartAccount - ? [caipNetwork] + ? NetworkController.getSmartAccountEnabledNetworks() : NetworkController.getApprovedCaipNetworks(); const imageHeaders = ApiController._getApiHeaders(); diff --git a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx index 47b613d0b..ae41a5175 100644 --- a/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-receive-view/index.tsx @@ -26,13 +26,17 @@ export function WalletReceiveView() { const networkImage = AssetUtil.getNetworkImage(caipNetwork); const { padding } = useCustomDimensions(); const canCopy = OptionsController.isClipboardAvailable(); - const slicedNetworks = - NetworkController.getApprovedCaipNetworks() - .filter(network => network?.imageId) - ?.slice(0, 5) || []; - const imagesArray = slicedNetworks.map(AssetUtil.getNetworkImage).filter(Boolean) as string[]; const isSmartAccount = preferredAccountType === 'smartAccount' && NetworkController.checkIfSmartAccountEnabled(); + const networks = isSmartAccount + ? NetworkController.getSmartAccountEnabledNetworks() + : NetworkController.getApprovedCaipNetworks(); + + const imagesArray = networks + .filter(network => network?.imageId) + .slice(0, 5) + .map(AssetUtil.getNetworkImage) + .filter(Boolean) as string[]; const label = UiUtil.getTruncateString({ string: profileName ?? address ?? '', @@ -71,9 +75,7 @@ export function WalletReceiveView() { diff --git a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx index 8be4dd217..31d3f8509 100644 --- a/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx +++ b/packages/scaffold/src/views/w3m-wallet-send-view/index.tsx @@ -93,6 +93,7 @@ export function WalletSendView() { }, [token, tokenBalance, fetchNetworkPrice]); const actionText = getActionText(); + const disabled = actionText !== 'Preview send'; return ( {loading ? ( - + ) : ( - + {getActionText()} )} diff --git a/packages/ui/src/components/wui-card/index.tsx b/packages/ui/src/components/wui-card/index.tsx index a354f5dbd..0d0eefb39 100644 --- a/packages/ui/src/components/wui-card/index.tsx +++ b/packages/ui/src/components/wui-card/index.tsx @@ -18,7 +18,7 @@ export function Card({ children, style }: CardProps) { enabled={Platform.OS === 'ios'} style={[ styles.container, - { backgroundColor: Theme['bg-100'], borderColor: Theme['gray-glass-005'] }, + { backgroundColor: Theme['bg-100'], borderColor: Theme['gray-glass-015'] }, style ]} > diff --git a/packages/ui/src/composites/wui-account-pill/index.tsx b/packages/ui/src/composites/wui-account-pill/index.tsx index 3f3bfc163..208b80cc8 100644 --- a/packages/ui/src/composites/wui-account-pill/index.tsx +++ b/packages/ui/src/composites/wui-account-pill/index.tsx @@ -1,15 +1,14 @@ import { Animated, Pressable, type StyleProp, type ViewStyle } from 'react-native'; import { Avatar } from '../wui-avatar'; import { UiUtil } from '../../utils/UiUtil'; -import { IconLink } from '../wui-icon-link'; import { Text } from '../../components/wui-text'; import useAnimatedValue from '../../hooks/useAnimatedValue'; import { useTheme } from '../../hooks/useTheme'; import styles from './styles'; +import { Icon } from '../../components/wui-icon'; export interface AccountPillProps { onPress: () => void; - onCopy: (address: string) => void; address?: string; profileName?: string; profileImage?: string; @@ -20,7 +19,6 @@ const AnimatedPressable = Animated.createAnimatedComponent(Pressable); export function AccountPill({ onPress, - onCopy, address, profileName, profileImage, @@ -36,12 +34,6 @@ export function AccountPill({ const backgroundColor = animatedValue; const borderColor = Theme['gray-glass-005']; - const handleCopyAddress = () => { - if (address) { - onCopy(address); - } - }; - return ( - + ); } diff --git a/packages/ui/src/composites/wui-account-pill/styles.ts b/packages/ui/src/composites/wui-account-pill/styles.ts index 7be825c94..4bb2e5f1d 100644 --- a/packages/ui/src/composites/wui-account-pill/styles.ts +++ b/packages/ui/src/composites/wui-account-pill/styles.ts @@ -17,6 +17,7 @@ export default StyleSheet.create({ marginLeft: Spacing['2xs'] }, copyButton: { - marginHorizontal: Spacing['3xs'] + marginLeft: Spacing.xs, + marginRight: Spacing.s } }); diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index a44975182..f5cea75d3 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -562,15 +562,17 @@ export class AppKit extends AppKitScaffold { private async addAuthListeners(connector: WagmiConnector) { const connectedConnector = await StorageUtil.getItem('@w3m/connected_connector'); - if (connector && connectedConnector === 'AUTH') { + + if (connectedConnector === 'AUTH') { + // Set loader until it reconnects super.setLoading(true); + } - const provider = (await connector.getProvider()) as AppKitFrameProvider; + const provider = (await connector.getProvider()) as AppKitFrameProvider; - provider.onSetPreferredAccount(async () => { - await reconnect(this.wagmiConfig, { connectors: [connector] }); - this.setLoading(false); - }); - } + provider.onSetPreferredAccount(async () => { + await reconnect(this.wagmiConfig, { connectors: [connector] }); + this.setLoading(false); + }); } } diff --git a/packages/wallet/src/AppKitFrameConstants.ts b/packages/wallet/src/AppKitFrameConstants.ts index ac1e4844a..98bd1afed 100644 --- a/packages/wallet/src/AppKitFrameConstants.ts +++ b/packages/wallet/src/AppKitFrameConstants.ts @@ -18,8 +18,12 @@ export const AppKitFrameConstants = { FRAME_MESSAGES_HANDLER: ` window.addEventListener('message', ({ data, origin }) => { - window.ReactNativeWebView.postMessage(JSON.stringify({ ...data, origin })) - }) + window.ReactNativeWebView.postMessage( + JSON.stringify({ ...data, origin }, (key, value) => + typeof value === 'bigint' ? value.toString() : value + ) + ); + }); `, APP_SWITCH_NETWORK: '@w3m-app/SWITCH_NETWORK', diff --git a/packages/wallet/src/AppKitWebview.tsx b/packages/wallet/src/AppKitWebview.tsx index d4c98dea5..19b58e885 100644 --- a/packages/wallet/src/AppKitWebview.tsx +++ b/packages/wallet/src/AppKitWebview.tsx @@ -10,6 +10,7 @@ import { } from '@reown/appkit-core-react-native'; import { useTheme, BorderRadius, IconLink, Spacing } from '@reown/appkit-ui-react-native'; import type { AppKitFrameProvider } from './AppKitFrameProvider'; +import { AppKitFrameConstants } from './AppKitFrameConstants'; const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView); @@ -100,7 +101,10 @@ function _AppKitWebview() { containerStyle={styles.webview} ref={webviewRef} onNavigationStateChange={async navState => { - if (webviewVisible) { + if ( + !navState.loading && + navState.url.includes(`${AppKitFrameConstants.SECURE_SITE_ORIGIN}/sdk/oauth`) + ) { provider.events.emit('social', navState.url); } }} From 636285bb261815b6766b78728219c7820532e150 Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 19 Nov 2024 15:21:14 -0300 Subject: [PATCH 098/114] fix: error handling improvements (#271) --- .changeset/warm-camels-cheat.md | 18 +++ .changeset/young-ducks-approve.md | 18 +++ apps/native/App.tsx | 3 +- apps/native/package.json | 4 +- package.json | 4 +- packages/common/src/index.ts | 1 + packages/common/src/utils/ErrorUtil.ts | 35 ++++++ .../controllers/OptionsController.test.ts | 3 +- .../controllers/SnackController.test.ts | 29 ++++- .../core/src/controllers/ApiController.ts | 47 +++++--- .../core/src/controllers/OptionsController.ts | 8 +- .../core/src/controllers/SnackController.ts | 29 ++++- packages/core/src/utils/TypeUtil.ts | 5 +- packages/ethers/src/client.ts | 22 +++- packages/ethers5/src/client.ts | 22 +++- packages/scaffold/src/client.ts | 36 +++++- .../partials/w3m-account-activity/index.tsx | 4 +- .../w3m-account-placeholder/index.tsx | 37 ------ .../partials/w3m-all-wallets-list/index.tsx | 50 +++++++-- .../partials/w3m-all-wallets-list/styles.ts | 7 ++ .../partials/w3m-all-wallets-search/index.tsx | 60 ++++++---- .../partials/w3m-all-wallets-search/styles.ts | 8 +- .../src/partials/w3m-placeholder/index.tsx | 77 +++++++++++++ .../src/partials/w3m-snackbar/index.tsx | 25 +++-- .../src/views/w3m-all-wallets-view/index.tsx | 2 +- .../components/all-wallet-list.tsx | 35 +++--- .../src/views/w3m-connect-view/index.tsx | 26 ++++- .../index.tsx | 4 +- packages/ui/src/components/wui-icon/index.tsx | 2 +- .../ui/src/composites/wui-icon-box/index.tsx | 20 +++- .../composites/wui-list-item-loader/index.tsx | 29 +++++ .../composites/wui-list-item-loader/styles.ts | 12 ++ .../ui/src/composites/wui-qr-code/index.tsx | 6 +- packages/ui/src/index.ts | 1 + packages/ui/src/utils/ThemeUtil.ts | 3 +- packages/wagmi/src/client.ts | 29 ++++- packages/wallet/src/AppKitAuthWebview.tsx | 13 ++- packages/wallet/src/AppKitFrameProvider.ts | 42 ++++++- packages/wallet/src/AppKitWebview.tsx | 6 +- yarn.lock | 105 +++++++++++------- 40 files changed, 689 insertions(+), 198 deletions(-) create mode 100644 .changeset/warm-camels-cheat.md create mode 100644 .changeset/young-ducks-approve.md create mode 100644 packages/common/src/utils/ErrorUtil.ts delete mode 100644 packages/scaffold/src/partials/w3m-account-placeholder/index.tsx create mode 100644 packages/scaffold/src/partials/w3m-placeholder/index.tsx create mode 100644 packages/ui/src/composites/wui-list-item-loader/index.tsx create mode 100644 packages/ui/src/composites/wui-list-item-loader/styles.ts diff --git a/.changeset/warm-camels-cheat.md b/.changeset/warm-camels-cheat.md new file mode 100644 index 000000000..58b91bbf5 --- /dev/null +++ b/.changeset/warm-camels-cheat.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: show error message and retry button in case wallet fetch fails diff --git a/.changeset/young-ducks-approve.md b/.changeset/young-ducks-approve.md new file mode 100644 index 000000000..63a64ef38 --- /dev/null +++ b/.changeset/young-ducks-approve.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +feat: improved provider error handling diff --git a/apps/native/App.tsx b/apps/native/App.tsx index d8036e3f8..ae99f40cf 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -63,10 +63,11 @@ createAppKit({ customWallets, enableAnalytics: true, metadata, + debug: true, features: { email: true, socials: ['x', 'farcaster', 'discord', 'apple'], - emailShowWallets: false + emailShowWallets: true } }); diff --git a/apps/native/package.json b/apps/native/package.json index 835192799..acf2071ab 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -37,8 +37,8 @@ "react-native-web": "~0.19.10", "react-native-webview": "13.8.6", "uuid": "3.4.0", - "viem": "2.21.37", - "wagmi": "2.12.25" + "viem": "2.21.48", + "wagmi": "2.12.33" }, "devDependencies": { "@babel/core": "^7.24.0", diff --git a/package.json b/package.json index 57bc2f9c4..2a6ee1b41 100644 --- a/package.json +++ b/package.json @@ -69,8 +69,8 @@ "tsconfig": "*", "turbo": "2.1.1", "typescript": "5.2.2", - "viem": "2.21.37", - "wagmi": "2.12.25" + "viem": "2.21.48", + "wagmi": "2.12.33" }, "packageManager": "yarn@4.0.2" } diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 8f77cbeb7..13f28df44 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -5,5 +5,6 @@ export { NamesUtil } from './utils/NamesUtil'; export { NetworkUtil } from './utils/NetworkUtil'; export { NumberUtil } from './utils/NumberUtil'; export { StringUtil } from './utils/StringUtil'; +export { ErrorUtil } from './utils/ErrorUtil'; export { erc20ABI } from './contracts/erc20'; export * from './utils/TypeUtil'; diff --git a/packages/common/src/utils/ErrorUtil.ts b/packages/common/src/utils/ErrorUtil.ts new file mode 100644 index 000000000..35bf4bc27 --- /dev/null +++ b/packages/common/src/utils/ErrorUtil.ts @@ -0,0 +1,35 @@ +export const ErrorUtil = { + UniversalProviderErrors: { + UNAUTHORIZED_DOMAIN_NOT_ALLOWED: { + message: 'Unauthorized: origin not allowed', + alertErrorKey: 'INVALID_APP_CONFIGURATION' + }, + PROJECT_ID_NOT_CONFIGURED: { + message: 'Project ID is missing', + alertErrorKey: 'PROJECT_ID_NOT_CONFIGURED' + }, + JWT_VALIDATION_ERROR: { + message: 'JWT validation error: JWT Token is not yet valid', + alertErrorKey: 'JWT_TOKEN_NOT_VALID' + } + }, + ALERT_ERRORS: { + INVALID_APP_CONFIGURATION: { + shortMessage: 'Invalid App Configuration', + longMessage: `Bundle ID not found on Allowlist - Please verify that your bundle ID is allowed at https://cloud.reown.com` + }, + SOCIALS_TIMEOUT: { + shortMessage: 'Invalid App Configuration', + longMessage: + 'There was an issue loading the embedded wallet. Please verify that your bundle ID is allowed at https://cloud.reown.com' + }, + JWT_TOKEN_NOT_VALID: { + shortMessage: 'Session Expired', + longMessage: 'Invalid session found - please check your time settings and connect again' + }, + PROJECT_ID_NOT_CONFIGURED: { + shortMessage: 'Project ID Not Configured', + longMessage: 'Project ID Not Configured - update configuration' + } + } +}; diff --git a/packages/core/src/__tests__/controllers/OptionsController.test.ts b/packages/core/src/__tests__/controllers/OptionsController.test.ts index 712d52c65..e0e536a32 100644 --- a/packages/core/src/__tests__/controllers/OptionsController.test.ts +++ b/packages/core/src/__tests__/controllers/OptionsController.test.ts @@ -16,7 +16,8 @@ describe('OptionsController', () => { projectId: '', sdkType: 'appkit', sdkVersion: 'react-native-wagmi-undefined', - features: ConstantsUtil.DEFAULT_FEATURES + features: ConstantsUtil.DEFAULT_FEATURES, + debug: false }); }); diff --git a/packages/core/src/__tests__/controllers/SnackController.test.ts b/packages/core/src/__tests__/controllers/SnackController.test.ts index 2192c454f..6a9aad79c 100644 --- a/packages/core/src/__tests__/controllers/SnackController.test.ts +++ b/packages/core/src/__tests__/controllers/SnackController.test.ts @@ -1,4 +1,7 @@ -import { SnackController } from '../../index'; +import { OptionsController, SnackController } from '../../index'; + +// Setup +OptionsController.state.debug = true; // -- Tests -------------------------------------------------------------------- describe('SnackController', () => { @@ -6,7 +9,8 @@ describe('SnackController', () => { expect(SnackController.state).toEqual({ message: '', variant: 'success', - open: false + open: false, + long: false }); }); @@ -15,16 +19,18 @@ describe('SnackController', () => { expect(SnackController.state).toEqual({ message: 'Success Msg', variant: 'success', - open: true + open: true, + long: false }); }); it('should update state correctly on hide()', () => { SnackController.hide(); expect(SnackController.state).toEqual({ - message: 'Success Msg', + message: '', variant: 'success', - open: false + open: false, + long: false }); }); @@ -33,7 +39,18 @@ describe('SnackController', () => { expect(SnackController.state).toEqual({ message: 'Error Msg', variant: 'error', - open: true + open: true, + long: false + }); + }); + + it('should update state correctly on showInternalError()', () => { + SnackController.showInternalError({ shortMessage: 'Error Msg', longMessage: 'Error Msg' }); + expect(SnackController.state).toEqual({ + message: 'Error Msg', + variant: 'error', + open: true, + long: true }); }); }); diff --git a/packages/core/src/controllers/ApiController.ts b/packages/core/src/controllers/ApiController.ts index dec37f8d3..db9766252 100644 --- a/packages/core/src/controllers/ApiController.ts +++ b/packages/core/src/controllers/ApiController.ts @@ -17,6 +17,7 @@ import { OptionsController } from './OptionsController'; import { ConnectorController } from './ConnectorController'; import { ConnectionController } from './ConnectionController'; import { ApiUtil } from '../utils/ApiUtil'; +import { SnackController } from './SnackController'; // -- Helpers ------------------------------------------- // const baseUrl = CoreHelperUtil.getApiUrl(); @@ -27,6 +28,8 @@ const recommendedEntries = '4'; // -- Types --------------------------------------------- // export interface ApiControllerState { prefetchPromise?: Promise; + prefetchError?: boolean; + prefetchLoading?: boolean; page: number; count: number; featured: WcWallet[]; @@ -326,23 +329,35 @@ export const ApiController = { }, async prefetch() { - // this fetch must resolve first so we filter them in the other wallet requests - await ApiController.fetchInstalledWallets(); - - const promises = [ - ApiController.fetchFeaturedWallets(), - ApiController.fetchRecommendedWallets(), - ApiController.fetchNetworkImages(), - ApiController.fetchConnectorImages() - ]; - if (OptionsController.state.enableAnalytics === undefined) { - promises.push(ApiController.fetchAnalyticsConfig()); - } + try { + state.prefetchError = false; + state.prefetchLoading = true; + // this fetch must resolve first so we filter them in the other wallet requests + await ApiController.fetchInstalledWallets(); + + const promises = [ + ApiController.fetchFeaturedWallets(), + ApiController.fetchRecommendedWallets(), + ApiController.fetchNetworkImages(), + ApiController.fetchConnectorImages() + ]; + if (OptionsController.state.enableAnalytics === undefined) { + promises.push(ApiController.fetchAnalyticsConfig()); + } - state.prefetchPromise = Promise.race([ - CoreHelperUtil.allSettled(promises), - CoreHelperUtil.wait(3000) - ]); + state.prefetchPromise = Promise.race([ + CoreHelperUtil.allSettled(promises), + CoreHelperUtil.wait(3000) + ]); + + state.prefetchPromise.then(() => { + state.prefetchLoading = false; + }); + } catch (error) { + state.prefetchError = true; + state.prefetchLoading = false; + SnackController.showError('Failed to load wallets'); + } }, async fetchAnalyticsConfig() { diff --git a/packages/core/src/controllers/OptionsController.ts b/packages/core/src/controllers/OptionsController.ts index 7140589ae..24fde94a9 100644 --- a/packages/core/src/controllers/OptionsController.ts +++ b/packages/core/src/controllers/OptionsController.ts @@ -29,6 +29,7 @@ export interface OptionsControllerState { metadata?: Metadata; isSiweEnabled?: boolean; features?: Features; + debug?: boolean; } // -- State --------------------------------------------- // @@ -36,7 +37,8 @@ const state = proxy({ projectId: '', sdkType: 'appkit', sdkVersion: 'react-native-wagmi-undefined', - features: ConstantsUtil.DEFAULT_FEATURES + features: ConstantsUtil.DEFAULT_FEATURES, + debug: false }); // -- Controller ---------------------------------------- // @@ -91,6 +93,10 @@ export const OptionsController = { state.features = { ...ConstantsUtil.DEFAULT_FEATURES, ...features }; }, + setDebug(debug: OptionsControllerState['debug']) { + state.debug = debug; + }, + isClipboardAvailable() { return !!state._clipboardClient; }, diff --git a/packages/core/src/controllers/SnackController.ts b/packages/core/src/controllers/SnackController.ts index 331a5fd4d..8a0d91c98 100644 --- a/packages/core/src/controllers/SnackController.ts +++ b/packages/core/src/controllers/SnackController.ts @@ -1,17 +1,25 @@ import { proxy } from 'valtio'; +import { OptionsController } from './OptionsController'; // -- Types --------------------------------------------- // +interface Message { + shortMessage: string; + longMessage?: string; +} + export interface SnackControllerState { message: string; variant: 'error' | 'success'; open: boolean; + long: boolean; } // -- State --------------------------------------------- // const state = proxy({ message: '', variant: 'success', - open: false + open: false, + long: false }); // -- Controller ---------------------------------------- // @@ -30,7 +38,26 @@ export const SnackController = { state.open = true; }, + showInternalError(error: Message) { + const { debug } = OptionsController.state; + + if (debug) { + state.message = error.shortMessage; + state.variant = 'error'; + state.open = true; + state.long = true; + } + + if (error.longMessage) { + // eslint-disable-next-line no-console + console.error(error.longMessage); + } + }, + hide() { state.open = false; + state.long = false; + state.message = ''; + state.variant = 'success'; } }; diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index 6d7ce82a0..d91b2cc23 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -598,13 +598,14 @@ export interface AppKitFrameProvider { type: AppKitFrameAccountType; address: string; }>; + setOnTimeout(callback: () => void): void; getSmartAccountEnabledNetworks(): Promise<{ smartAccountEnabledNetworks: number[]; }>; disconnect(): Promise; request(req: any): Promise; - AuthView: () => JSX.Element | null; - Webview: () => JSX.Element | null; + AuthView: () => React.JSX.Element | null; + Webview: () => React.JSX.Element | null; onSetPreferredAccount: ( callback: (values: { type: AppKitFrameAccountType; address: string }) => void ) => void; diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index 499cfc2aa..0db093654 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -27,7 +27,7 @@ import { type WriteContractArgs, type AppKitFrameAccountType } from '@reown/appkit-scaffold-react-native'; -import { erc20ABI, NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; +import { erc20ABI, ErrorUtil, NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; import { ConstantsUtil, PresetsUtil, @@ -47,6 +47,7 @@ import { } from '@reown/appkit-scaffold-utils-react-native'; import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider'; +import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; import { getAuthCaipNetworks, getWalletConnectCaipNetworks } from './utils/helpers'; import type { AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; @@ -108,7 +109,7 @@ export class AppKit extends AppKitScaffold { } if (!appKitOptions.projectId) { - throw new Error('appkit:constructor - projectId is undefined'); + throw new Error(ErrorUtil.ALERT_ERRORS.PROJECT_ID_NOT_CONFIGURED.shortMessage); } const networkControllerClient: NetworkControllerClient = { @@ -506,6 +507,7 @@ export class AppKit extends AppKitScaffold { }; this.walletConnectProvider = await EthereumProvider.init(walletConnectProviderOptions); + this.addWalletConnectListeners(this.walletConnectProvider); await this.checkActiveWalletConnectProvider(); } @@ -965,5 +967,21 @@ export class AppKit extends AppKitScaffold { } this.setLoading(false); }); + + authProvider.setOnTimeout(async () => { + this.handleAlertError(ErrorUtil.ALERT_ERRORS.SOCIALS_TIMEOUT); + }); + } + + private async addWalletConnectListeners(provider: EthereumProvider) { + if (provider) { + provider.signer.client.core.relayer.on('relayer_connect', () => { + provider.signer.client.core.relayer?.provider?.on('payload', (payload: JsonRpcError) => { + if (payload?.error) { + this.handleAlertError(payload?.error.message); + } + }); + }); + } } } diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index 56b233e77..d9ade873d 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -31,9 +31,10 @@ import { type CombinedProviderType, type AppKitFrameProvider } from '@reown/appkit-scaffold-utils-react-native'; -import { erc20ABI, NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; +import { erc20ABI, ErrorUtil, NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider'; +import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; import { getAuthCaipNetworks, getWalletConnectCaipNetworks } from './utils/helpers'; import type { AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; @@ -95,7 +96,7 @@ export class AppKit extends AppKitScaffold { } if (!appKitOptions.projectId) { - throw new Error('appkit:constructor - projectId is undefined'); + throw new Error(ErrorUtil.ALERT_ERRORS.PROJECT_ID_NOT_CONFIGURED.shortMessage); } const networkControllerClient: NetworkControllerClient = { @@ -486,6 +487,7 @@ export class AppKit extends AppKitScaffold { }; this.walletConnectProvider = await EthereumProvider.init(walletConnectProviderOptions); + this.addWalletConnectListeners(this.walletConnectProvider); await this.checkActiveWalletConnectProvider(); } @@ -942,5 +944,21 @@ export class AppKit extends AppKitScaffold { } this.setLoading(false); }); + + authProvider.setOnTimeout(async () => { + this.handleAlertError(ErrorUtil.ALERT_ERRORS.SOCIALS_TIMEOUT); + }); + } + + private async addWalletConnectListeners(provider: EthereumProvider) { + if (provider) { + provider.signer.client.core.relayer.on('relayer_connect', () => { + provider.signer.client.core.relayer?.provider?.on('payload', (payload: JsonRpcError) => { + if (payload?.error) { + this.handleAlertError(payload?.error.message); + } + }); + }); + } } } diff --git a/packages/scaffold/src/client.ts b/packages/scaffold/src/client.ts index 18b863c2c..0af3bcef9 100644 --- a/packages/scaffold/src/client.ts +++ b/packages/scaffold/src/client.ts @@ -28,11 +28,12 @@ import { NetworkController, OptionsController, PublicStateController, + SnackController, StorageUtil, ThemeController, TransactionsController } from '@reown/appkit-core-react-native'; -import { ConstantsUtil } from '@reown/appkit-common-react-native'; +import { ConstantsUtil, ErrorUtil } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------------------------------- export interface LibraryOptions { @@ -49,6 +50,7 @@ export interface LibraryOptions { enableAnalytics?: OptionsControllerState['enableAnalytics']; _sdkVersion: OptionsControllerState['sdkVersion']; metadata?: OptionsControllerState['metadata']; + debug?: OptionsControllerState['debug']; features?: Features; } @@ -64,6 +66,8 @@ export interface OpenOptions { // -- Client -------------------------------------------------------------------- export class AppKitScaffold { + public reportedAlertErrors: Record = {}; + public constructor(options: ScaffoldOptions) { this.initControllers(options); } @@ -233,6 +237,35 @@ export class AppKitScaffold { AccountController.setPreferredAccountType(preferredAccountType); }; + protected handleAlertError(error?: string | { shortMessage: string; longMessage: string }) { + if (!error) return; + + if (typeof error === 'object') { + SnackController.showInternalError(error); + + return; + } + + // Check if the error is a universal provider error + const matchedUniversalProviderError = Object.entries(ErrorUtil.UniversalProviderErrors).find( + ([, { message }]) => error?.includes(message) + ); + + const [errorKey, errorValue] = matchedUniversalProviderError ?? []; + + const { message, alertErrorKey } = errorValue ?? {}; + + if (errorKey && message && !this.reportedAlertErrors[errorKey]) { + const alertError = + ErrorUtil.ALERT_ERRORS[alertErrorKey as keyof typeof ErrorUtil.ALERT_ERRORS]; + + if (alertError) { + SnackController.showInternalError(alertError); + this.reportedAlertErrors[errorKey] = true; + } + } + } + // -- Private ------------------------------------------------------------------ private async initControllers(options: ScaffoldOptions) { this.initAsyncValues(options); @@ -247,6 +280,7 @@ export class AppKitScaffold { OptionsController.setCustomWallets(options.customWallets); OptionsController.setEnableAnalytics(options.enableAnalytics); OptionsController.setSdkVersion(options._sdkVersion); + OptionsController.setDebug(options.debug); if (options.clipboardClient) { OptionsController.setClipboardClient(options.clipboardClient); diff --git a/packages/scaffold/src/partials/w3m-account-activity/index.tsx b/packages/scaffold/src/partials/w3m-account-activity/index.tsx index 567b3acd9..2e784083f 100644 --- a/packages/scaffold/src/partials/w3m-account-activity/index.tsx +++ b/packages/scaffold/src/partials/w3m-account-activity/index.tsx @@ -19,7 +19,7 @@ import { OptionsController, TransactionsController } from '@reown/appkit-core-react-native'; -import { AccountPlaceholder } from '../w3m-account-placeholder'; +import { Placeholder } from '../w3m-placeholder'; import { getTransactionListItemProps } from './utils'; import styles from './styles'; @@ -68,7 +68,7 @@ export function AccountActivity({ style }: Props) { if (!Object.keys(transactionsByYear).length) { return ( - ; -} - -export function AccountPlaceholder({ icon, title, description, style }: Props) { - return ( - - - - {title} - - - {description} - - - ); -} - -const styles = StyleSheet.create({ - container: { - flex: 1, - minHeight: 200 - }, - title: { - marginTop: Spacing.xl, - marginBottom: Spacing.xs - }, - description: { - maxWidth: '50%' - } -}); diff --git a/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx b/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx index bfb615385..fb9c009e0 100644 --- a/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx +++ b/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx @@ -1,7 +1,12 @@ import { useEffect, useState } from 'react'; import { useSnapshot } from 'valtio'; import { FlatList, View } from 'react-native'; -import { ApiController, AssetUtil, type WcWallet } from '@reown/appkit-core-react-native'; +import { + ApiController, + AssetUtil, + SnackController, + type WcWallet +} from '@reown/appkit-core-react-native'; import { CardSelect, CardSelectLoader, @@ -12,6 +17,7 @@ import { import styles from './styles'; import { UiUtil } from '../../utils/UiUtil'; import { useCustomDimensions } from '../../hooks/useCustomDimensions'; +import { Placeholder } from '../w3m-placeholder'; interface AllWalletsListProps { columns: number; @@ -21,6 +27,7 @@ interface AllWalletsListProps { export function AllWalletsList({ columns, itemWidth, onItemPress }: AllWalletsListProps) { const [loading, setLoading] = useState(false); + const [loadingError, setLoadingError] = useState(false); const [pageLoading, setPageLoading] = useState(false); const { maxWidth, padding } = useCustomDimensions(); const { installed, featured, recommended, wallets } = useSnapshot(ApiController.state); @@ -80,16 +87,28 @@ export function AllWalletsList({ columns, itemWidth, onItemPress }: AllWalletsLi }; const initialFetch = async () => { - setLoading(true); - await ApiController.fetchWallets({ page: 1 }); - UiUtil.createViewTransition(); - setLoading(false); + try { + setLoading(true); + setLoadingError(false); + await ApiController.fetchWallets({ page: 1 }); + UiUtil.createViewTransition(); + setLoading(false); + } catch (error) { + SnackController.showError('Failed to load wallets'); + setLoading(false); + setLoadingError(true); + } }; const fetchNextPage = async () => { - if (walletList.length < ApiController.state.count && !pageLoading) { - setPageLoading(true); - await ApiController.fetchWallets({ page: ApiController.state.page + 1 }); + try { + if (walletList.length < ApiController.state.count && !pageLoading) { + setPageLoading(true); + await ApiController.fetchWallets({ page: ApiController.state.page + 1 }); + setPageLoading(false); + } + } catch (error) { + SnackController.showError('Failed to load more wallets'); setPageLoading(false); } }; @@ -104,6 +123,21 @@ export function AllWalletsList({ columns, itemWidth, onItemPress }: AllWalletsLi return loadingTemplate(20); } + if (loadingError) { + return ( + + ); + } + return ( (false); + const [loadingError, setLoadingError] = useState(false); const [prevSearchQuery, setPrevSearchQuery] = useState(''); const imageHeaders = ApiController._getApiHeaders(); const { maxWidth, padding, isLandscape } = useCustomDimensions(); @@ -69,28 +74,25 @@ export function AllWalletsSearch({ const emptyTemplate = () => { return ( - - - - No wallet found - - + /> ); }; const searchFetch = useCallback(async () => { - setLoading(true); - await ApiController.searchWallet({ search: searchQuery }); - setLoading(false); + try { + setLoading(true); + setLoadingError(false); + await ApiController.searchWallet({ search: searchQuery }); + setLoading(false); + } catch (error) { + SnackController.showError('Failed to load wallets'); + setLoading(false); + setLoadingError(true); + } }, [searchQuery]); useEffect(() => { @@ -104,6 +106,21 @@ export function AllWalletsSearch({ return loadingTemplate(20); } + if (loadingError) { + return ( + + ); + } + if (ApiController.state.search.length === 0) { return emptyTemplate(); } @@ -118,7 +135,6 @@ export function AllWalletsSearch({ renderItem={walletTemplate} style={styles.container} contentContainerStyle={[styles.contentContainer, { paddingHorizontal: padding + Spacing.xs }]} - ListEmptyComponent={emptyTemplate()} keyExtractor={item => item.id} getItemLayout={(_, index) => ({ length: ITEM_HEIGHT, diff --git a/packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts b/packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts index 95a07ebed..d425dea39 100644 --- a/packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts +++ b/packages/scaffold/src/partials/w3m-all-wallets-search/styles.ts @@ -8,9 +8,13 @@ export default StyleSheet.create({ contentContainer: { paddingBottom: Spacing['2xl'] }, + placeholderContainer: { + flex: 0, + height: '90%' + }, emptyContainer: { - height: '100%', - paddingTop: '50%' + flex: 0, + height: '90%' }, emptyLandscape: { paddingTop: '10%' diff --git a/packages/scaffold/src/partials/w3m-placeholder/index.tsx b/packages/scaffold/src/partials/w3m-placeholder/index.tsx new file mode 100644 index 000000000..8fed2e110 --- /dev/null +++ b/packages/scaffold/src/partials/w3m-placeholder/index.tsx @@ -0,0 +1,77 @@ +import { StyleSheet, type StyleProp, type ViewStyle } from 'react-native'; +import { + IconBox, + Text, + FlexView, + Spacing, + type IconType, + Button, + type ColorType +} from '@reown/appkit-ui-react-native'; + +interface Props { + icon?: IconType; + iconColor?: ColorType; + title?: string; + description?: string; + style?: StyleProp; + actionIcon?: IconType; + actionPress?: () => void; + actionTitle?: string; +} + +export function Placeholder({ + icon, + iconColor = 'fg-175', + title, + description, + style, + actionPress, + actionTitle, + actionIcon +}: Props) { + return ( + + {icon && ( + + )} + {title && ( + + {title} + + )} + {description && ( + + {description} + + )} + {actionPress && ( + + )} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + minHeight: 200 + }, + icon: { + marginBottom: Spacing.l + }, + title: { + marginBottom: Spacing['2xs'] + }, + button: { + marginTop: Spacing.m + } +}); diff --git a/packages/scaffold/src/partials/w3m-snackbar/index.tsx b/packages/scaffold/src/partials/w3m-snackbar/index.tsx index 0ac420edf..7d0802c80 100644 --- a/packages/scaffold/src/partials/w3m-snackbar/index.tsx +++ b/packages/scaffold/src/partials/w3m-snackbar/index.tsx @@ -7,7 +7,7 @@ import { Snackbar as SnackbarComponent } from '@reown/appkit-ui-react-native'; import styles from './styles'; export function Snackbar() { - const { open, message, variant } = useSnapshot(SnackController.state); + const { open, message, variant, long } = useSnapshot(SnackController.state); const componentOpacity = useMemo(() => new Animated.Value(0), []); useEffect(() => { @@ -17,17 +17,20 @@ export function Snackbar() { duration: 150, useNativeDriver: true }).start(); - setTimeout(() => { - Animated.timing(componentOpacity, { - toValue: 0, - duration: 300, - useNativeDriver: true - }).start(() => { - SnackController.hide(); - }); - }, 2200); + setTimeout( + () => { + Animated.timing(componentOpacity, { + toValue: 0, + duration: 300, + useNativeDriver: true + }).start(() => { + SnackController.hide(); + }); + }, + long ? 15000 : 2200 + ); } - }, [open, componentOpacity]); + }, [open, long, componentOpacity]); return ( diff --git a/packages/scaffold/src/views/w3m-connect-view/components/all-wallet-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/all-wallet-list.tsx index daf346718..581b00b5f 100644 --- a/packages/scaffold/src/views/w3m-connect-view/components/all-wallet-list.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/components/all-wallet-list.tsx @@ -1,3 +1,4 @@ +import { type StyleProp, type ViewStyle } from 'react-native'; import { useSnapshot } from 'valtio'; import { ApiController, @@ -6,8 +7,7 @@ import { type ConnectionControllerState, type WcWallet } from '@reown/appkit-core-react-native'; -import { ListWallet } from '@reown/appkit-ui-react-native'; -import type { StyleProp, ViewStyle } from 'react-native'; +import { ListItemLoader, ListWallet } from '@reown/appkit-ui-react-native'; import { UiUtil } from '../../../utils/UiUtil'; import { filterOutRecentWallets } from '../utils'; @@ -18,7 +18,7 @@ interface Props { } export function AllWalletList({ itemStyle, onWalletPress, isWalletConnectEnabled }: Props) { - const { installed, featured, recommended } = useSnapshot(ApiController.state); + const { installed, featured, recommended, prefetchLoading } = useSnapshot(ApiController.state); const { recentWallets } = useSnapshot(ConnectionController.state) as ConnectionControllerState; const imageHeaders = ApiController._getApiHeaders(); const RECENT_COUNT = recentWallets?.length && installed.length ? 1 : recentWallets?.length ?? 0; @@ -33,15 +33,22 @@ export function AllWalletList({ itemStyle, onWalletPress, isWalletConnectEnabled return null; } - return list.map(wallet => ( - onWalletPress(wallet)} - style={itemStyle} - installed={!!installed.find(installedWallet => installedWallet.id === wallet.id)} - /> - )); + return prefetchLoading ? ( + <> + + + + ) : ( + list.map(wallet => ( + onWalletPress(wallet)} + style={itemStyle} + installed={!!installed.find(installedWallet => installedWallet.id === wallet.id)} + /> + )) + ); } diff --git a/packages/scaffold/src/views/w3m-connect-view/index.tsx b/packages/scaffold/src/views/w3m-connect-view/index.tsx index 4a52eca79..f1222cc8e 100644 --- a/packages/scaffold/src/views/w3m-connect-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/index.tsx @@ -1,6 +1,7 @@ import { useSnapshot } from 'valtio'; import { Platform, ScrollView, View } from 'react-native'; import { + ApiController, ConnectorController, EventUtil, EventsController, @@ -12,18 +13,20 @@ import { FlexView, Icon, ListItem, Separator, Spacing, Text } from '@reown/appki import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import { ConnectEmailInput } from './components/connect-email-input'; import { useKeyboard } from '../../hooks/useKeyboard'; +import { Placeholder } from '../../partials/w3m-placeholder'; import { ConnectorList } from './components/connectors-list'; import { CustomWalletList } from './components/custom-wallet-list'; import { AllWalletsButton } from './components/all-wallets-button'; import { AllWalletList } from './components/all-wallet-list'; import { RecentWalletList } from './components/recent-wallet-list'; import { SocialLoginList } from './components/social-login-list'; -import styles from './styles'; import { WalletGuide } from './components/wallet-guide'; +import styles from './styles'; export function ConnectView() { const connectors = ConnectorController.state.connectors; const { authLoading } = useSnapshot(ConnectorController.state); + const { prefetchError } = useSnapshot(ApiController.state); const { features } = useSnapshot(OptionsController.state); const { padding } = useCustomDimensions(); const { keyboardShown, keyboardHeight } = useKeyboard(); @@ -39,6 +42,8 @@ export function ConnectView() { isAuthEnabled && (isEmailEnabled || isSocialEnabled) && (isWalletConnectEnabled || isCoinbaseEnabled); + const showLoadingError = !showConnectWalletsButton && prefetchError; + const showList = !showConnectWalletsButton && !showLoadingError; const paddingBottom = Platform.select({ android: keyboardShown ? keyboardHeight + Spacing['2xl'] : Spacing['2xl'], @@ -78,13 +83,28 @@ export function ConnectView() { {showSeparator && } - {showConnectWalletsButton ? ( + {showConnectWalletsButton && ( Continue with a wallet - ) : ( + )} + {showLoadingError && ( + + + + + )} + {showList && ( <> )) ) : ( - JSX.Element> = { export type IconProps = SvgProps & { name: IconType; - size?: Exclude; + size?: SizeType; color?: ColorType; style?: SvgProps['style']; }; diff --git a/packages/ui/src/composites/wui-icon-box/index.tsx b/packages/ui/src/composites/wui-icon-box/index.tsx index 070656fc0..bbfb9e8ac 100644 --- a/packages/ui/src/composites/wui-icon-box/index.tsx +++ b/packages/ui/src/composites/wui-icon-box/index.tsx @@ -7,7 +7,7 @@ import styles from './styles'; export interface IconBoxProps { icon: IconType; - size?: Exclude; + size?: Exclude; iconColor?: ColorType; iconSize?: Exclude; background?: boolean; @@ -33,6 +33,9 @@ export function IconBox({ const Theme = useTheme(); let _iconSize: SizeType; switch (size) { + case 'xl': + _iconSize = 'xl'; + break; case 'lg': _iconSize = 'lg'; break; @@ -48,6 +51,9 @@ export function IconBox({ let boxSize: number; switch (size) { + case 'xl': + boxSize = 56; + break; case 'lg': boxSize = 40; break; @@ -58,7 +64,17 @@ export function IconBox({ boxSize = 24; } - const borderRadius = size === 'lg' ? 'xxs' : '3xl'; + let borderRadius: keyof typeof BorderRadius; + switch (size) { + case 'xl': + borderRadius = 's'; + break; + case 'lg': + borderRadius = 'xxs'; + break; + default: + borderRadius = '3xl'; + } const backgroundStyle = { backgroundColor: diff --git a/packages/ui/src/composites/wui-list-item-loader/index.tsx b/packages/ui/src/composites/wui-list-item-loader/index.tsx new file mode 100644 index 000000000..b3fb786cb --- /dev/null +++ b/packages/ui/src/composites/wui-list-item-loader/index.tsx @@ -0,0 +1,29 @@ +import type { StyleProp, ViewStyle } from 'react-native'; +import { BorderRadius, WalletImageSize } from '../../utils/ThemeUtil'; +import { useTheme } from '../../hooks/useTheme'; +import { Shimmer } from '../../components/wui-shimmer'; +import { FlexView } from '../../layout/wui-flex'; +import styles from './styles'; + +export interface ListItemLoaderProps { + style?: StyleProp; +} + +export function ListItemLoader({ style }: ListItemLoaderProps) { + const Theme = useTheme(); + + return ( + + + + + ); +} diff --git a/packages/ui/src/composites/wui-list-item-loader/styles.ts b/packages/ui/src/composites/wui-list-item-loader/styles.ts new file mode 100644 index 000000000..f5a3b9381 --- /dev/null +++ b/packages/ui/src/composites/wui-list-item-loader/styles.ts @@ -0,0 +1,12 @@ +import { StyleSheet } from 'react-native'; +import { BorderRadius, Spacing } from '../../utils/ThemeUtil'; + +export default StyleSheet.create({ + container: { + borderRadius: BorderRadius.xs, + padding: Spacing.xs + }, + text: { + marginLeft: Spacing.s + } +}); diff --git a/packages/ui/src/composites/wui-qr-code/index.tsx b/packages/ui/src/composites/wui-qr-code/index.tsx index 8f49da5e2..304ef336d 100644 --- a/packages/ui/src/composites/wui-qr-code/index.tsx +++ b/packages/ui/src/composites/wui-qr-code/index.tsx @@ -21,6 +21,8 @@ export interface QrCodeProps { style?: StyleProp; } +const LABEL_HEIGHT = 15; + export function QrCode({ size, uri, imageSrc, testID, arenaClear, icon, style }: QrCodeProps) { const Theme = LightTheme; const containerPadding = Spacing.l; @@ -75,7 +77,7 @@ export function QrCode({ size, uri, imageSrc, testID, arenaClear, icon, style }: {logoTemplate()} - + UX by{' '} Reown @@ -83,6 +85,6 @@ export function QrCode({ size, uri, imageSrc, testID, arenaClear, icon, style }: ) : ( - + ); } diff --git a/packages/ui/src/index.ts b/packages/ui/src/index.ts index ddea358d4..94c1bf2ab 100644 --- a/packages/ui/src/index.ts +++ b/packages/ui/src/index.ts @@ -40,6 +40,7 @@ export { InputNumeric, type InputNumericProps } from './composites/wui-input-num export { InputText, type InputTextProps } from './composites/wui-input-text'; export { Link, type LinkProps } from './composites/wui-link'; export { ListItem, type ListItemProps } from './composites/wui-list-item'; +export { ListItemLoader, type ListItemLoaderProps } from './composites/wui-list-item-loader'; export { ListSocial, type ListSocialProps } from './composites/wui-list-social'; export { ListToken, type ListTokenProps } from './composites/wui-list-token'; export { ListTransaction, type ListTransactionProps } from './composites/wui-list-transaction'; diff --git a/packages/ui/src/utils/ThemeUtil.ts b/packages/ui/src/utils/ThemeUtil.ts index 9c3fbcd7b..9671a12d8 100644 --- a/packages/ui/src/utils/ThemeUtil.ts +++ b/packages/ui/src/utils/ThemeUtil.ts @@ -164,7 +164,8 @@ export const IconSize = { xs: 12, sm: 14, md: 16, - lg: 20 + lg: 20, + xl: 24 }; export const SpinnerSize = { diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index f5cea75d3..46a75a3d3 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -22,7 +22,8 @@ import { } from '@wagmi/core'; import { normalize } from 'viem/ens'; import { mainnet, type Chain } from '@wagmi/core/chains'; -import { EthereumProvider, OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; +import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; +import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; import { type CaipAddress, type CaipNetwork, @@ -44,7 +45,7 @@ import { PresetsUtil, StorageUtil } from '@reown/appkit-scaffold-utils-react-native'; -import { NetworkUtil, NamesUtil } from '@reown/appkit-common-react-native'; +import { NetworkUtil, NamesUtil, ErrorUtil } from '@reown/appkit-common-react-native'; import { type AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; import { getCaipDefaultChain, @@ -90,7 +91,7 @@ export class AppKit extends AppKitScaffold { } if (!appKitOptions.projectId) { - throw new Error('appkit:constructor - projectId is undefined'); + throw new Error(ErrorUtil.ALERT_ERRORS.PROJECT_ID_NOT_CONFIGURED.shortMessage); } const networkControllerClient: NetworkControllerClient = { @@ -543,9 +544,27 @@ export class AppKit extends AppKitScaffold { }); this.setConnectors(_connectors); + this.syncWalletConnectListeners(filteredConnectors); this.syncAuthConnector(filteredConnectors); } + private async syncWalletConnectListeners( + connectors: AppKitClientOptions['wagmiConfig']['connectors'] + ) { + const connector = connectors.find(({ id }) => id === ConstantsUtil.WALLET_CONNECT_CONNECTOR_ID); + if (connector) { + const provider = (await connector.getProvider()) as EthereumProvider; + + provider.signer.client.core.relayer.on('relayer_connect', () => { + provider.signer.client.core.relayer?.provider?.on('payload', (payload: JsonRpcError) => { + if (payload?.error) { + this.handleAlertError(payload?.error.message); + } + }); + }); + } + } + private async syncAuthConnector(connectors: AppKitClientOptions['wagmiConfig']['connectors']) { const authConnector = connectors.find(({ id }) => id === ConstantsUtil.AUTH_CONNECTOR_ID); if (authConnector) { @@ -574,5 +593,9 @@ export class AppKit extends AppKitScaffold { await reconnect(this.wagmiConfig, { connectors: [connector] }); this.setLoading(false); }); + + provider.setOnTimeout(async () => { + this.handleAlertError(ErrorUtil.ALERT_ERRORS.SOCIALS_TIMEOUT); + }); } } diff --git a/packages/wallet/src/AppKitAuthWebview.tsx b/packages/wallet/src/AppKitAuthWebview.tsx index c77d79d1c..cd477f99c 100644 --- a/packages/wallet/src/AppKitAuthWebview.tsx +++ b/packages/wallet/src/AppKitAuthWebview.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { memo, useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Animated, Appearance, Linking, Platform, SafeAreaView, StyleSheet } from 'react-native'; import { WebView, type WebViewMessageEvent } from 'react-native-webview'; @@ -12,8 +12,10 @@ import { WebviewController, AccountController, NetworkController, - ConnectionController + ConnectionController, + SnackController } from '@reown/appkit-core-react-native'; +import { ErrorUtil } from '@reown/appkit-common-react-native'; import { useTheme, BorderRadius } from '@reown/appkit-ui-react-native'; import type { AppKitFrameProvider } from './AppKitFrameProvider'; import { AppKitFrameConstants } from './AppKitFrameConstants'; @@ -22,7 +24,7 @@ import type { AppKitFrameTypes } from './AppKitFrameTypes'; const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView); -function _AuthWebview() { +export function AuthWebview() { const webviewRef = useRef(null); const Theme = useTheme(); const authConnector = ConnectorController.getAuthConnector(); @@ -206,14 +208,15 @@ function _AuthWebview() { onError={({ nativeEvent }) => { provider?.onWebviewLoadError(nativeEvent.description); }} + onHttpError={() => { + SnackController.showInternalError(ErrorUtil.ALERT_ERRORS.SOCIALS_TIMEOUT); + }} /> ) : null; } -export const AuthWebview = memo(_AuthWebview); - const styles = StyleSheet.create({ backdrop: { position: 'absolute', diff --git a/packages/wallet/src/AppKitFrameProvider.ts b/packages/wallet/src/AppKitFrameProvider.ts index 9ae9fb2a8..dea444feb 100644 --- a/packages/wallet/src/AppKitFrameProvider.ts +++ b/packages/wallet/src/AppKitFrameProvider.ts @@ -10,6 +10,9 @@ import { AppKitFrameSchema } from './AppKitFrameSchema'; import { AuthWebview } from './AppKitAuthWebview'; import { AppKitWebview } from './AppKitWebview'; +// -- Types ----------------------------------------------------------- +type AppEventType = Omit; + // -- Provider -------------------------------------------------------- export class AppKitFrameProvider { private webviewRef: RefObject | undefined; @@ -23,12 +26,16 @@ export class AppKitFrameProvider { private username: string | undefined; private rpcRequestHandler?: (request: AppKitFrameTypes.RPCRequest) => void; + private rpcSuccessHandler?: ( response: AppKitFrameTypes.RPCResponse, request: AppKitFrameTypes.RPCRequest ) => void; + private rpcErrorHandler?: (error: Error, request: AppKitFrameTypes.RPCRequest) => void; + private onTimeout?: () => void; + public webviewLoadPromise: Promise; public webviewLoadPromiseResolver: @@ -76,6 +83,10 @@ export class AppKitFrameProvider { this.webviewLoadPromiseResolver?.reject(error); } + public setOnTimeout(callback: () => void) { + this.onTimeout = callback; + } + // -- Extended Methods ------------------------------------------------ public getSecureSiteURL() { return `${AppKitFrameConstants.SECURE_SITE_SDK}?projectId=${this.projectId}`; @@ -502,10 +513,34 @@ export class AppKitFrameProvider { } private async appEvent( - event: Omit + event: AppEventType ): Promise { await this.webviewLoadPromise; - const type = event.type.replace('@w3m-app/', ''); + let timer: NodeJS.Timeout; + + function replaceEventType(type: AppEventType['type']) { + return type.replace('@w3m-app/', ''); + } + + const type = replaceEventType(event.type); + + const shouldCheckForTimeout = [ + AppKitFrameConstants.APP_IS_CONNECTED, + AppKitFrameConstants.APP_GET_USER, + AppKitFrameConstants.APP_CONNECT_EMAIL, + AppKitFrameConstants.APP_CONNECT_DEVICE, + AppKitFrameConstants.APP_CONNECT_OTP, + AppKitFrameConstants.APP_CONNECT_SOCIAL, + AppKitFrameConstants.APP_GET_SOCIAL_REDIRECT_URI, + AppKitFrameConstants.APP_GET_FARCASTER_URI + ] + .map(replaceEventType) + .includes(type); + + if (shouldCheckForTimeout && this.onTimeout) { + // 15 seconds timeout + timer = setTimeout(this.onTimeout, 15000); + } return new Promise((resolve, reject) => { const id = Math.random().toString(36).substring(7); @@ -527,6 +562,9 @@ export class AppKitFrameProvider { function handler(frameEvent: AppKitFrameTypes.FrameEvent) { if (frameEvent.type === `@w3m-frame/${type}_SUCCESS`) { + if (timer) { + clearTimeout(timer); + } if ('payload' in frameEvent) { resolve(frameEvent.payload); } diff --git a/packages/wallet/src/AppKitWebview.tsx b/packages/wallet/src/AppKitWebview.tsx index 19b58e885..4c00eeb8a 100644 --- a/packages/wallet/src/AppKitWebview.tsx +++ b/packages/wallet/src/AppKitWebview.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { memo, useEffect, useRef, useState } from 'react'; +import { useEffect, useRef, useState } from 'react'; import { Animated, SafeAreaView, StyleSheet } from 'react-native'; import { WebView } from 'react-native-webview'; @@ -14,7 +14,7 @@ import { AppKitFrameConstants } from './AppKitFrameConstants'; const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView); -function _AppKitWebview() { +export function AppKitWebview() { const webviewRef = useRef(null); const Theme = useTheme(); const authConnector = ConnectorController.getAuthConnector(); @@ -114,8 +114,6 @@ function _AppKitWebview() { ) : null; } -export const AppKitWebview = memo(_AppKitWebview); - const styles = StyleSheet.create({ backdrop: { position: 'absolute', diff --git a/yarn.lock b/yarn.lock index e223f6c01..753d7b55b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -38,7 +38,7 @@ __metadata: languageName: node linkType: hard -"@adraffy/ens-normalize@npm:1.11.0": +"@adraffy/ens-normalize@npm:^1.10.1": version: 1.11.0 resolution: "@adraffy/ens-normalize@npm:1.11.0" checksum: 5111d0f1a273468cb5661ed3cf46ee58de8f32f84e2ebc2365652e66c1ead82649df94c736804e2b9cfa831d30ef24e1cc3575d970dbda583416d3a98d8870a6 @@ -121,8 +121,8 @@ __metadata: react-native-webview: "npm:13.8.6" typescript: "npm:~5.3.3" uuid: "npm:3.4.0" - viem: "npm:2.21.37" - wagmi: "npm:2.12.25" + viem: "npm:2.21.48" + wagmi: "npm:2.12.33" languageName: unknown linkType: soft @@ -3529,15 +3529,15 @@ __metadata: languageName: node linkType: hard -"@coinbase/wallet-sdk@npm:4.1.0": - version: 4.1.0 - resolution: "@coinbase/wallet-sdk@npm:4.1.0" +"@coinbase/wallet-sdk@npm:4.2.3": + version: 4.2.3 + resolution: "@coinbase/wallet-sdk@npm:4.2.3" dependencies: "@noble/hashes": "npm:^1.4.0" clsx: "npm:^1.2.1" eventemitter3: "npm:^5.0.1" - preact: "npm:^10.16.0" - checksum: 9ccd8171e8874a357f246fc3b8b9641cb015f12e0c8912c15b77c55cdca58c00ba59c68afdc3162d26421fedcaf8164a95ee39abce96e4dcde5b391e0920ca65 + preact: "npm:^10.24.2" + checksum: ce27b5bfdcbc79e896cd262baf0483073ac854986b518e29a23af9c70b3bb6a75d6c15e5e34355095249d46fa8a8eda4682b63ec82812e92cbadba56b8706190 languageName: node linkType: hard @@ -6852,13 +6852,13 @@ __metadata: languageName: node linkType: hard -"@safe-global/safe-apps-provider@npm:0.18.3": - version: 0.18.3 - resolution: "@safe-global/safe-apps-provider@npm:0.18.3" +"@safe-global/safe-apps-provider@npm:0.18.4": + version: 0.18.4 + resolution: "@safe-global/safe-apps-provider@npm:0.18.4" dependencies: "@safe-global/safe-apps-sdk": "npm:^9.1.0" events: "npm:^3.3.0" - checksum: 7209d761919969c0859e8b9df90fd46d06c3f99424ecd5fd2e0b8080355a880504ee5c46cebcbaa94739f8be272f3f7102a9f40cf18e6c1a9e1d7dd29d77ee5e + checksum: 612c9816b75b86b73b95b5df35529f4d48da1a3a59b2b999f6ef836b28b10cda2142e159dbc97f0298fa8f5b76df82a1e08e34034fdf12f148e9fd4af2f72134 languageName: node linkType: hard @@ -6936,7 +6936,7 @@ __metadata: languageName: node linkType: hard -"@scure/bip32@npm:1.5.0": +"@scure/bip32@npm:1.5.0, @scure/bip32@npm:^1.5.0": version: 1.5.0 resolution: "@scure/bip32@npm:1.5.0" dependencies: @@ -6967,7 +6967,7 @@ __metadata: languageName: node linkType: hard -"@scure/bip39@npm:1.4.0": +"@scure/bip39@npm:1.4.0, @scure/bip39@npm:^1.4.0": version: 1.4.0 resolution: "@scure/bip39@npm:1.4.0" dependencies: @@ -8718,30 +8718,30 @@ __metadata: languageName: node linkType: hard -"@wagmi/connectors@npm:5.3.3": - version: 5.3.3 - resolution: "@wagmi/connectors@npm:5.3.3" +"@wagmi/connectors@npm:5.4.0": + version: 5.4.0 + resolution: "@wagmi/connectors@npm:5.4.0" dependencies: - "@coinbase/wallet-sdk": "npm:4.1.0" + "@coinbase/wallet-sdk": "npm:4.2.3" "@metamask/sdk": "npm:0.30.1" - "@safe-global/safe-apps-provider": "npm:0.18.3" + "@safe-global/safe-apps-provider": "npm:0.18.4" "@safe-global/safe-apps-sdk": "npm:9.1.0" "@walletconnect/ethereum-provider": "npm:2.17.0" cbw-sdk: "npm:@coinbase/wallet-sdk@3.9.3" peerDependencies: - "@wagmi/core": 2.14.1 + "@wagmi/core": 2.14.6 typescript: ">=5.0.4" viem: 2.x peerDependenciesMeta: typescript: optional: true - checksum: 78789ed27fca0bc1d54a3a8282584ddcbdcee26f2d95b27d7dd9663a7cd02e2728e0bcdd0ce0aaae8983dfac3ad966c03243dd08ce11b80d908c314f0d139cb7 + checksum: 6cb88b23f44a57cad6c3ab992a31764f364e5ea1c38640d207eaa7f7955675738e99f2a9d2d74ea5295f617717fca1204903c82ec293d690a95a913a13451251 languageName: node linkType: hard -"@wagmi/core@npm:2.14.1": - version: 2.14.1 - resolution: "@wagmi/core@npm:2.14.1" +"@wagmi/core@npm:2.14.6": + version: 2.14.6 + resolution: "@wagmi/core@npm:2.14.6" dependencies: eventemitter3: "npm:5.0.1" mipd: "npm:0.0.7" @@ -8755,7 +8755,7 @@ __metadata: optional: true typescript: optional: true - checksum: 4cde494a8fcf218e79eb4e650fd598fbb273fcaa3cab52aa0a3b41a2a104de2bac602ed19d2d2c6b130f881300770d809b5e1bfd81a8375269bed7d4e9606fb0 + checksum: bc79ba678f00da5e769526875698e9dc1464fc650f3db27ecf9865b78f0690b7006bb36b0ea0acf6deb9ea5d5a84d343fc8ec6efaa9e9a73868ddca9a8eb046e languageName: node linkType: hard @@ -9450,7 +9450,7 @@ __metadata: languageName: node linkType: hard -"abitype@npm:1.0.6": +"abitype@npm:1.0.6, abitype@npm:^1.0.6": version: 1.0.6 resolution: "abitype@npm:1.0.6" peerDependencies: @@ -9800,8 +9800,8 @@ __metadata: tsconfig: "npm:*" turbo: "npm:2.1.1" typescript: "npm:5.2.2" - viem: "npm:2.21.37" - wagmi: "npm:2.12.25" + viem: "npm:2.21.48" + wagmi: "npm:2.12.33" languageName: unknown linkType: soft @@ -18610,6 +18610,26 @@ __metadata: languageName: node linkType: hard +"ox@npm:0.1.2": + version: 0.1.2 + resolution: "ox@npm:0.1.2" + dependencies: + "@adraffy/ens-normalize": "npm:^1.10.1" + "@noble/curves": "npm:^1.6.0" + "@noble/hashes": "npm:^1.5.0" + "@scure/bip32": "npm:^1.5.0" + "@scure/bip39": "npm:^1.4.0" + abitype: "npm:^1.0.6" + eventemitter3: "npm:5.0.1" + peerDependencies: + typescript: ">=5.4.0" + peerDependenciesMeta: + typescript: + optional: true + checksum: 9d0615e9a95c316063587fe08dc268476e67429eea897598b2f69cb1509ac66739f888b0b9bc1cfd0b4bd2f1a3fd0af4d3e81d40ba0bf3abd53e36a6f5b21323 + languageName: node + linkType: hard + "p-filter@npm:^2.1.0": version: 2.1.0 resolution: "p-filter@npm:2.1.0" @@ -19169,6 +19189,13 @@ __metadata: languageName: node linkType: hard +"preact@npm:^10.24.2": + version: 10.24.3 + resolution: "preact@npm:10.24.3" + checksum: c863df6d7be6a660480189762d8a8f2d4148733fc2bb9efbd9d2fd27315d2c7ede850a16077d716c91666c915c0349bd3c9699733e4f08457226a0519f408761 + languageName: node + linkType: hard + "preferred-pm@npm:^3.0.0": version: 3.1.2 resolution: "preferred-pm@npm:3.1.2" @@ -22847,17 +22874,17 @@ __metadata: languageName: node linkType: hard -"viem@npm:2.21.37": - version: 2.21.37 - resolution: "viem@npm:2.21.37" +"viem@npm:2.21.48": + version: 2.21.48 + resolution: "viem@npm:2.21.48" dependencies: - "@adraffy/ens-normalize": "npm:1.11.0" "@noble/curves": "npm:1.6.0" "@noble/hashes": "npm:1.5.0" "@scure/bip32": "npm:1.5.0" "@scure/bip39": "npm:1.4.0" abitype: "npm:1.0.6" isows: "npm:1.0.6" + ox: "npm:0.1.2" webauthn-p256: "npm:0.0.10" ws: "npm:8.18.0" peerDependencies: @@ -22865,7 +22892,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 573bacabc42f9e6b7050ce7fe62c2a41fe878bb9659888904d9f1acbc91a051fa4a4528eafd5c04242ddba99919cd2f5fab8987f299aa2936145254fb5caa951 + checksum: e9b2799535263a859bddda25d962b13d2c76aec191e1849dd0f268c32a43eb65932a05cc5be270c92e19d79aafda73884690c0b0fbdb9311266a01ea3f659082 languageName: node linkType: hard @@ -22898,12 +22925,12 @@ __metadata: languageName: node linkType: hard -"wagmi@npm:2.12.25": - version: 2.12.25 - resolution: "wagmi@npm:2.12.25" +"wagmi@npm:2.12.33": + version: 2.12.33 + resolution: "wagmi@npm:2.12.33" dependencies: - "@wagmi/connectors": "npm:5.3.3" - "@wagmi/core": "npm:2.14.1" + "@wagmi/connectors": "npm:5.4.0" + "@wagmi/core": "npm:2.14.6" use-sync-external-store: "npm:1.2.0" peerDependencies: "@tanstack/react-query": ">=5.0.0" @@ -22913,7 +22940,7 @@ __metadata: peerDependenciesMeta: typescript: optional: true - checksum: 18510c1a7528c28b013130ec84fefc5792ee3d4105d60b0b598075b5b6620d51ad0e890e936c6e549e44de4eb6a36e975d14745551dcff3d1d8a963e4d29b8df + checksum: dca024324acdc85f602c6755243dfa61ab1dee51fa844957139af7aba50217fad1e1547227a1bdc7a465fc1921710489c9bb8d5dd354c6ee01c85c73326cc279 languageName: node linkType: hard From 5f7b88c220a312b07c1787dbd8aae2ee8d3a1120 Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 20 Nov 2024 15:40:33 -0300 Subject: [PATCH 099/114] chore: small changes (#272) --- .changeset/mean-elephants-kick.md | 18 +++++++++++++++++ apps/native/index.js | 1 + apps/native/package.json | 1 + package.json | 1 + packages/core/src/utils/ApiUtil.ts | 20 +++++++++++++------ .../src/modal/w3m-account-button/index.tsx | 2 +- packages/ui/src/assets/visual/Profile.tsx | 4 ++-- .../components/wui-lean-text/index.web.tsx | 3 +++ .../components/wui-lean-view/index.web.tsx | 3 +++ .../ui/src/composites/wui-qr-code/index.tsx | 2 +- yarn.lock | 10 ++++++++++ 11 files changed, 55 insertions(+), 10 deletions(-) create mode 100644 .changeset/mean-elephants-kick.md create mode 100644 packages/ui/src/components/wui-lean-text/index.web.tsx create mode 100644 packages/ui/src/components/wui-lean-view/index.web.tsx diff --git a/.changeset/mean-elephants-kick.md b/.changeset/mean-elephants-kick.md new file mode 100644 index 000000000..4092e90d0 --- /dev/null +++ b/.changeset/mean-elephants-kick.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +chore: small changes for web diff --git a/apps/native/index.js b/apps/native/index.js index 1d6e981ef..67732634f 100644 --- a/apps/native/index.js +++ b/apps/native/index.js @@ -1,3 +1,4 @@ +import '@expo/metro-runtime'; import { registerRootComponent } from 'expo'; import App from './App'; diff --git a/apps/native/package.json b/apps/native/package.json index acf2071ab..e3a094c0c 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -15,6 +15,7 @@ "eas:update": "eas update --branch preview" }, "dependencies": { + "@expo/metro-runtime": "~3.2.3", "@react-native-async-storage/async-storage": "1.23.1", "@react-native-community/netinfo": "11.3.1", "@reown/appkit-auth-wagmi-react-native": "1.0.2", diff --git a/package.json b/package.json index 2a6ee1b41..0392016c5 100644 --- a/package.json +++ b/package.json @@ -23,6 +23,7 @@ "gallery": "turbo run dev:gallery", "android": "cd apps/native && yarn android", "ios": "cd apps/native && yarn ios", + "web": "cd apps/native && yarn web", "build": "turbo build", "build:gallery": "turbo run build:gallery", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", diff --git a/packages/core/src/utils/ApiUtil.ts b/packages/core/src/utils/ApiUtil.ts index cf689514e..bcf778f05 100644 --- a/packages/core/src/utils/ApiUtil.ts +++ b/packages/core/src/utils/ApiUtil.ts @@ -6,13 +6,21 @@ export const ApiUtil = { return CoreHelperUtil.getBundleId(); }, - getUserAgent() { - const reactNativeVersion = [ - Platform.constants.reactNativeVersion.major, - Platform.constants.reactNativeVersion.minor, - Platform.constants.reactNativeVersion.patch + getReactNativeVersion() { + return [ + Platform.constants?.reactNativeVersion?.major, + Platform.constants?.reactNativeVersion?.minor, + Platform.constants?.reactNativeVersion?.patch ].join('.'); + }, + + getUserAgent() { + const rnVersion = Platform.select({ + ios: this.getReactNativeVersion(), + android: this.getReactNativeVersion(), + default: 'undefined' + }); - return `${Platform.OS}-${Platform.Version}@rn-${reactNativeVersion}`; + return `${Platform.OS}-${Platform.Version}@rn-${rnVersion}`; } }; diff --git a/packages/scaffold/src/modal/w3m-account-button/index.tsx b/packages/scaffold/src/modal/w3m-account-button/index.tsx index 4f3186407..329ffb3b2 100644 --- a/packages/scaffold/src/modal/w3m-account-button/index.tsx +++ b/packages/scaffold/src/modal/w3m-account-button/index.tsx @@ -33,7 +33,7 @@ export function AccountButton({ balance, disabled, style, testID }: AccountButto return ( ModalController.open()} address={address} profileName={profileName} networkSrc={networkImage} diff --git a/packages/ui/src/assets/visual/Profile.tsx b/packages/ui/src/assets/visual/Profile.tsx index 3c0d3ced9..764583136 100644 --- a/packages/ui/src/assets/visual/Profile.tsx +++ b/packages/ui/src/assets/visual/Profile.tsx @@ -13,8 +13,8 @@ const SvgProfile = (props: SvgProps) => ( diff --git a/packages/ui/src/components/wui-lean-text/index.web.tsx b/packages/ui/src/components/wui-lean-text/index.web.tsx new file mode 100644 index 000000000..13643b5bb --- /dev/null +++ b/packages/ui/src/components/wui-lean-text/index.web.tsx @@ -0,0 +1,3 @@ +import { Text } from 'react-native'; + +export { Text as LeanText }; diff --git a/packages/ui/src/components/wui-lean-view/index.web.tsx b/packages/ui/src/components/wui-lean-view/index.web.tsx new file mode 100644 index 000000000..77c67cd3b --- /dev/null +++ b/packages/ui/src/components/wui-lean-view/index.web.tsx @@ -0,0 +1,3 @@ +import { View } from 'react-native'; + +export { View as LeanView }; diff --git a/packages/ui/src/composites/wui-qr-code/index.tsx b/packages/ui/src/composites/wui-qr-code/index.tsx index 304ef336d..67f0a69aa 100644 --- a/packages/ui/src/composites/wui-qr-code/index.tsx +++ b/packages/ui/src/composites/wui-qr-code/index.tsx @@ -21,7 +21,7 @@ export interface QrCodeProps { style?: StyleProp; } -const LABEL_HEIGHT = 15; +const LABEL_HEIGHT = 18; export function QrCode({ size, uri, imageSrc, testID, arenaClear, icon, style }: QrCodeProps) { const Theme = LightTheme; diff --git a/yarn.lock b/yarn.lock index 753d7b55b..a619a74b6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -95,6 +95,7 @@ __metadata: resolution: "@apps/native@workspace:apps/native" dependencies: "@babel/core": "npm:^7.24.0" + "@expo/metro-runtime": "npm:~3.2.3" "@react-native-async-storage/async-storage": "npm:1.23.1" "@react-native-community/netinfo": "npm:11.3.1" "@reown/appkit-auth-wagmi-react-native": "npm:1.0.2" @@ -4526,6 +4527,15 @@ __metadata: languageName: node linkType: hard +"@expo/metro-runtime@npm:~3.2.3": + version: 3.2.3 + resolution: "@expo/metro-runtime@npm:3.2.3" + peerDependencies: + react-native: "*" + checksum: a5357c32663e516833feed8f6fd899e1a6ab6acf79b198e860bb82076512e07f95730420eefc87a354d4004d9482b29fbecadcbdcf59b6f8737bba4da03e405e + languageName: node + linkType: hard + "@expo/osascript@npm:^2.0.31": version: 2.0.33 resolution: "@expo/osascript@npm:2.0.33" From aa3ba464c88cf38aae54b5e2185b64b513418b1b Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 20 Nov 2024 16:37:00 -0300 Subject: [PATCH 100/114] fix: refresh balance (#273) --- .changeset/gold-beers-applaud.md | 18 ++++++++++++++++++ .../src/views/w3m-account-view/index.tsx | 12 ++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 .changeset/gold-beers-applaud.md diff --git a/.changeset/gold-beers-applaud.md b/.changeset/gold-beers-applaud.md new file mode 100644 index 000000000..40ba2799d --- /dev/null +++ b/.changeset/gold-beers-applaud.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: watch token balance diff --git a/packages/scaffold/src/views/w3m-account-view/index.tsx b/packages/scaffold/src/views/w3m-account-view/index.tsx index 661e0f5d2..14e19d858 100644 --- a/packages/scaffold/src/views/w3m-account-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-view/index.tsx @@ -51,6 +51,18 @@ export function AccountView() { SendController.resetSend(); }, []); + useEffect(() => { + AccountController.fetchTokenBalance(); + + const balanceInterval = setInterval(() => { + AccountController.fetchTokenBalance(); + }, 10000); + + return () => { + clearInterval(balanceInterval); + }; + }, []); + return ( Date: Wed, 20 Nov 2024 17:20:46 -0300 Subject: [PATCH 101/114] fix: show custom wallets (#274) --- .changeset/mean-spoons-confess.md | 18 ++++++++++++++++++ .../partials/w3m-all-wallets-list/index.tsx | 4 ++++ 2 files changed, 22 insertions(+) create mode 100644 .changeset/mean-spoons-confess.md diff --git a/.changeset/mean-spoons-confess.md b/.changeset/mean-spoons-confess.md new file mode 100644 index 000000000..92772f25e --- /dev/null +++ b/.changeset/mean-spoons-confess.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: show custom wallets in all wallets view diff --git a/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx b/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx index fb9c009e0..94d20443d 100644 --- a/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx +++ b/packages/scaffold/src/partials/w3m-all-wallets-list/index.tsx @@ -4,7 +4,9 @@ import { FlatList, View } from 'react-native'; import { ApiController, AssetUtil, + OptionsController, SnackController, + type OptionsControllerState, type WcWallet } from '@reown/appkit-core-react-native'; import { @@ -31,11 +33,13 @@ export function AllWalletsList({ columns, itemWidth, onItemPress }: AllWalletsLi const [pageLoading, setPageLoading] = useState(false); const { maxWidth, padding } = useCustomDimensions(); const { installed, featured, recommended, wallets } = useSnapshot(ApiController.state); + const { customWallets } = useSnapshot(OptionsController.state) as OptionsControllerState; const imageHeaders = ApiController._getApiHeaders(); const preloadedWallets = installed.length + featured.length + recommended.length; const loadingItems = columns - ((100 + preloadedWallets) % columns); const walletList = [ + ...(customWallets ?? []), ...installed, ...featured, ...recommended, From 2cb59a46a90bea3b919f47f4be8f485d867890ef Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:28:30 -0300 Subject: [PATCH 102/114] fix: social login improvement (#275) --- .changeset/nervous-apes-laugh.md | 18 ++++++++++++++++++ .../w3m-connecting-farcaster-view/index.tsx | 11 ++++++++++- .../views/w3m-connecting-social-view/index.tsx | 8 ++++++-- packages/wallet/src/AppKitFrameProvider.ts | 2 +- 4 files changed, 35 insertions(+), 4 deletions(-) create mode 100644 .changeset/nervous-apes-laugh.md diff --git a/.changeset/nervous-apes-laugh.md b/.changeset/nervous-apes-laugh.md new file mode 100644 index 000000000..c82d4d74a --- /dev/null +++ b/.changeset/nervous-apes-laugh.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: improved loading message in social connections diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx index fa54e0797..aba7b5ff7 100644 --- a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx @@ -28,6 +28,7 @@ export function ConnectingFarcasterView() { const { maxWidth: width } = useCustomDimensions(); const authConnector = ConnectorController.getAuthConnector(); const [error, setError] = useState(false); + const [processing, setProcessing] = useState(false); const [url, setUrl] = useState(); const showCopy = OptionsController.isClipboardAvailable(); const socialProvider = data?.socialProvider; @@ -41,6 +42,7 @@ export function ConnectingFarcasterView() { setUrl(farcasterUrl); Linking.openURL(farcasterUrl); await provider.connectFarcaster(); + setProcessing(true); await ConnectionController.connectExternal(authConnector); ConnectionController.setConnectedSocialProvider(socialProvider); EventsController.sendEvent({ @@ -49,6 +51,7 @@ export function ConnectingFarcasterView() { properties: { provider: socialProvider } }); WebviewController.setConnecting(false); + setProcessing(false); ModalController.close(); } } catch (e) { @@ -59,6 +62,7 @@ export function ConnectingFarcasterView() { }); SnackController.showError('Something went wrong'); setError(true); + setProcessing(false); } }, [provider, socialProvider, authConnector]); @@ -96,7 +100,12 @@ export function ConnectingFarcasterView() { )} - Continue in Farcaster + {processing ? 'Loading user data' : 'Continue in Farcaster'} + + + {processing + ? 'Please wait a moment while we load your data' + : 'Connect in the Farcaster app'} {showCopy && ( - {`Continue with ${StringUtil.capitalize(socialProvider)}`} + {processingAuth + ? 'Loading user data' + : `Continue with ${StringUtil.capitalize(socialProvider)}`} - {processingAuth ? 'Retrieving user data' : 'Connect in the provider window'} + {processingAuth + ? 'Please wait a moment while we load your data' + : 'Connect in the provider window'} ); diff --git a/packages/wallet/src/AppKitFrameProvider.ts b/packages/wallet/src/AppKitFrameProvider.ts index dea444feb..b9d1fd2c0 100644 --- a/packages/wallet/src/AppKitFrameProvider.ts +++ b/packages/wallet/src/AppKitFrameProvider.ts @@ -539,7 +539,7 @@ export class AppKitFrameProvider { if (shouldCheckForTimeout && this.onTimeout) { // 15 seconds timeout - timer = setTimeout(this.onTimeout, 15000); + timer = setTimeout(this.onTimeout, 30000); } return new Promise((resolve, reject) => { From 1fe0f3fa0f96d14f63f2ffc96092b2bc239c4f92 Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:36:16 -0300 Subject: [PATCH 103/114] fix/siwe imports (#276) --- .changeset/big-planes-admire.md | 18 ++++++++++++++++++ packages/ethers/src/client.ts | 11 ++++++----- packages/ethers5/src/client.ts | 11 ++++++----- packages/scaffold/src/client.ts | 4 +--- .../scaffold/src/modal/w3m-modal/index.tsx | 12 +++++------- .../src/views/w3m-connecting-view/index.tsx | 2 +- packages/wagmi/src/client.ts | 11 ++++++----- 7 files changed, 43 insertions(+), 26 deletions(-) create mode 100644 .changeset/big-planes-admire.md diff --git a/.changeset/big-planes-admire.md b/.changeset/big-planes-admire.md new file mode 100644 index 000000000..eb8f422d1 --- /dev/null +++ b/.changeset/big-planes-admire.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: changed siwe imports to solve issues on some android devices diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index 0db093654..79c0370f4 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -45,12 +45,17 @@ import { type CombinedProviderType, type AppKitFrameProvider } from '@reown/appkit-scaffold-utils-react-native'; +import { + type AppKitSIWEClient, + SIWEController, + getDidChainId, + getDidAddress +} from '@reown/appkit-siwe-react-native'; import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider'; import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; import { getAuthCaipNetworks, getWalletConnectCaipNetworks } from './utils/helpers'; -import type { AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; // -- Types --------------------------------------------------------------------- export interface AppKitClientOptions extends Omit { @@ -170,9 +175,6 @@ export class AppKit extends AppKitScaffold { // SIWE const params = await siweConfig?.getMessageParams?.(); if (siweConfig?.options?.enabled && params && Object.keys(params).length > 0) { - const { SIWEController, getDidChainId, getDidAddress } = await import( - '@reown/appkit-siwe-react-native' - ); const result = await WalletConnectProvider.authenticate({ nonce: await siweConfig.getNonce(), methods: OPTIONAL_METHODS, @@ -255,7 +257,6 @@ export class AppKit extends AppKitScaffold { const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; if (siweConfig?.options?.signOutOnDisconnect) { - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); await SIWEController.signOut(); } diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index d9ade873d..0f48b56e1 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -31,13 +31,18 @@ import { type CombinedProviderType, type AppKitFrameProvider } from '@reown/appkit-scaffold-utils-react-native'; +import { + SIWEController, + getDidChainId, + getDidAddress, + type AppKitSIWEClient +} from '@reown/appkit-siwe-react-native'; import { erc20ABI, ErrorUtil, NamesUtil, NetworkUtil } from '@reown/appkit-common-react-native'; import EthereumProvider, { OPTIONAL_METHODS } from '@walletconnect/ethereum-provider'; import type { EthereumProviderOptions } from '@walletconnect/ethereum-provider'; import { type JsonRpcError } from '@walletconnect/jsonrpc-types'; import { getAuthCaipNetworks, getWalletConnectCaipNetworks } from './utils/helpers'; -import type { AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; // -- Types --------------------------------------------------------------------- export interface AppKitClientOptions extends Omit { @@ -157,9 +162,6 @@ export class AppKit extends AppKitScaffold { // SIWE const params = await siweConfig?.getMessageParams?.(); if (siweConfig?.options?.enabled && params && Object.keys(params).length > 0) { - const { SIWEController, getDidChainId, getDidAddress } = await import( - '@reown/appkit-siwe-react-native' - ); const result = await WalletConnectProvider.authenticate({ nonce: await siweConfig.getNonce(), methods: OPTIONAL_METHODS, @@ -241,7 +243,6 @@ export class AppKit extends AppKitScaffold { const authType = PresetsUtil.ConnectorTypesMap[ConstantsUtil.AUTH_CONNECTOR_ID]; if (siweConfig?.options?.signOutOnDisconnect) { - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); await SIWEController.signOut(); } diff --git a/packages/scaffold/src/client.ts b/packages/scaffold/src/client.ts index 0af3bcef9..80d682707 100644 --- a/packages/scaffold/src/client.ts +++ b/packages/scaffold/src/client.ts @@ -16,7 +16,7 @@ import type { ConnectedWalletInfo, Features } from '@reown/appkit-core-react-native'; -import type { SIWEControllerClient } from '@reown/appkit-siwe-react-native'; +import { SIWEController, type SIWEControllerClient } from '@reown/appkit-siwe-react-native'; import { AccountController, BlockchainApiController, @@ -299,8 +299,6 @@ export class AppKitScaffold { } if (options.siweControllerClient) { - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); - SIWEController.setSIWEClient(options.siweControllerClient); } diff --git a/packages/scaffold/src/modal/w3m-modal/index.tsx b/packages/scaffold/src/modal/w3m-modal/index.tsx index eae3b380f..d4a9c4cae 100644 --- a/packages/scaffold/src/modal/w3m-modal/index.tsx +++ b/packages/scaffold/src/modal/w3m-modal/index.tsx @@ -18,6 +18,7 @@ import { type AppKitFrameProvider, WebviewController } from '@reown/appkit-core-react-native'; +import { SIWEController } from '@reown/appkit-siwe-react-native'; import { AppKitRouter } from '../w3m-router'; import { Header } from '../../partials/w3m-header'; @@ -30,7 +31,6 @@ export function AppKit() { const { connectors, connectedConnector } = useSnapshot(ConnectorController.state); const { caipAddress, isConnected } = useSnapshot(AccountController.state); const { frameViewVisible, webviewVisible } = useSnapshot(WebviewController.state); - const { isSiweEnabled } = OptionsController.state; const { height } = useWindowDimensions(); const { isLandscape } = useCustomDimensions(); const portraitHeight = height - 120; @@ -54,9 +54,7 @@ export function AppKit() { }; const handleClose = async () => { - if (isSiweEnabled) { - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); - + if (OptionsController.state.isSiweEnabled) { if (SIWEController.state.status !== 'success' && AccountController.state.isConnected) { await ConnectionController.disconnect(); } @@ -73,9 +71,9 @@ export function AppKit() { TransactionsController.resetTransactions(); TransactionsController.fetchTransactions(newAddress, true); - if (isSiweEnabled) { + if (OptionsController.state.isSiweEnabled) { const newNetworkId = CoreHelperUtil.getNetworkId(address); - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); + const { signOutOnAccountChange, signOutOnNetworkChange } = SIWEController.state._client?.options ?? {}; const session = await SIWEController.getSession(); @@ -98,7 +96,7 @@ export function AppKit() { } } }, - [isSiweEnabled, isConnected, loading] + [isConnected, loading] ); const onSiweNavigation = () => { diff --git a/packages/scaffold/src/views/w3m-connecting-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-view/index.tsx index 82a501ce6..44ee537c9 100644 --- a/packages/scaffold/src/views/w3m-connecting-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-view/index.tsx @@ -14,6 +14,7 @@ import { EventsController, ConnectorController } from '@reown/appkit-core-react-native'; +import { SIWEController } from '@reown/appkit-siwe-react-native'; import { ConnectingQrCode } from '../../partials/w3m-connecting-qrcode'; import { ConnectingMobile } from '../../partials/w3m-connecting-mobile'; @@ -53,7 +54,6 @@ export function ConnectingView() { AccountController.setIsConnected(true); if (OptionsController.state.isSiweEnabled) { - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); if (SIWEController.state.status === 'success') { ModalController.close(); } else { diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index 46a75a3d3..eb5d42c79 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -46,7 +46,12 @@ import { StorageUtil } from '@reown/appkit-scaffold-utils-react-native'; import { NetworkUtil, NamesUtil, ErrorUtil } from '@reown/appkit-common-react-native'; -import { type AppKitSIWEClient } from '@reown/appkit-siwe-react-native'; +import { + SIWEController, + getDidChainId, + getDidAddress, + type AppKitSIWEClient +} from '@reown/appkit-siwe-react-native'; import { getCaipDefaultChain, getAuthCaipNetworks, @@ -159,9 +164,6 @@ export class AppKit extends AppKitScaffold { siweParams && Object.keys(siweParams || {}).length > 0 ) { - const { SIWEController, getDidChainId, getDidAddress } = await import( - '@reown/appkit-siwe-react-native' - ); // @ts-expect-error - setting requested chains beforehand avoids wagmi auto disconnecting the session when `connect` is called because it things chains are stale await connector.setRequestedChainsIds(siweParams.chains); const result = await provider.authenticate( @@ -242,7 +244,6 @@ export class AppKit extends AppKitScaffold { this.setClientId(null); if (siweConfig?.options?.signOutOnDisconnect) { - const { SIWEController } = await import('@reown/appkit-siwe-react-native'); await SIWEController.signOut(); } }, From 285a64bfb310913c79b1ba9a85a82387e41a63ff Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:52:36 -0300 Subject: [PATCH 104/114] fix: ui changes in social webview to solve android issues (#277) --- .changeset/violet-lamps-chew.md | 18 ++++++++++++++++++ packages/wallet/src/AppKitWebview.tsx | 22 ++++++---------------- 2 files changed, 24 insertions(+), 16 deletions(-) create mode 100644 .changeset/violet-lamps-chew.md diff --git a/.changeset/violet-lamps-chew.md b/.changeset/violet-lamps-chew.md new file mode 100644 index 000000000..e69e3d5f6 --- /dev/null +++ b/.changeset/violet-lamps-chew.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +fix: ui changes in social webview to solve android issues diff --git a/packages/wallet/src/AppKitWebview.tsx b/packages/wallet/src/AppKitWebview.tsx index 4c00eeb8a..df38e1ffc 100644 --- a/packages/wallet/src/AppKitWebview.tsx +++ b/packages/wallet/src/AppKitWebview.tsx @@ -20,15 +20,10 @@ export function AppKitWebview() { const authConnector = ConnectorController.getAuthConnector(); const { webviewVisible, webviewUrl } = useSnapshot(WebviewController.state); const [isBackdropVisible, setIsBackdropVisible] = useState(false); - const animatedHeight = useRef(new Animated.Value(0)); const backdropOpacity = useRef(new Animated.Value(0)); const webviewOpacity = useRef(new Animated.Value(0)); const provider = authConnector?.provider as AppKitFrameProvider; - - const show = animatedHeight.current.interpolate({ - inputRange: [0, 1], - outputRange: ['0%', '80%'] - }); + const display = webviewVisible ? 'flex' : 'none'; const onClose = () => { WebviewController.setWebviewVisible(false); @@ -38,16 +33,10 @@ export function AppKitWebview() { }; useEffect(() => { - Animated.timing(animatedHeight.current, { - toValue: webviewVisible ? 1 : 0, - duration: 200, - useNativeDriver: false - }).start(); - Animated.timing(webviewOpacity.current, { toValue: webviewVisible ? 1 : 0, duration: 300, - useNativeDriver: false + useNativeDriver: true }).start(({ finished }) => { if (finished && !webviewVisible) { WebviewController.setWebviewUrl(''); @@ -61,9 +50,9 @@ export function AppKitWebview() { Animated.timing(backdropOpacity.current, { toValue: webviewVisible ? 0.7 : 0, duration: 300, - useNativeDriver: false + useNativeDriver: true }).start(() => setIsBackdropVisible(webviewVisible)); - }, [animatedHeight, backdropOpacity, webviewVisible, setIsBackdropVisible]); + }, [backdropOpacity, webviewVisible, setIsBackdropVisible]); if (!webviewUrl) return null; @@ -79,7 +68,7 @@ export function AppKitWebview() { Date: Fri, 29 Nov 2024 14:36:52 -0300 Subject: [PATCH 105/114] chore: removed lean components in favor of compatibility (#279) * chore: removed lean components in favor of compatibility --- packages/ui/src/__tests__/wui-text.test.tsx | 9 --------- packages/ui/src/components/wui-lean-text/index.tsx | 10 ---------- .../ui/src/components/wui-lean-text/index.web.tsx | 3 --- packages/ui/src/components/wui-lean-view/index.tsx | 10 ---------- .../ui/src/components/wui-lean-view/index.web.tsx | 3 --- packages/ui/src/components/wui-text/index.tsx | 7 +++---- packages/ui/src/composites/wui-list-wallet/index.tsx | 11 +++++------ packages/ui/src/layout/wui-flex/index.tsx | 5 ++--- packages/ui/src/layout/wui-separator/index.tsx | 11 ++++------- 9 files changed, 14 insertions(+), 55 deletions(-) delete mode 100644 packages/ui/src/components/wui-lean-text/index.tsx delete mode 100644 packages/ui/src/components/wui-lean-text/index.web.tsx delete mode 100644 packages/ui/src/components/wui-lean-view/index.tsx delete mode 100644 packages/ui/src/components/wui-lean-view/index.web.tsx diff --git a/packages/ui/src/__tests__/wui-text.test.tsx b/packages/ui/src/__tests__/wui-text.test.tsx index 7475ecb83..e39bae272 100644 --- a/packages/ui/src/__tests__/wui-text.test.tsx +++ b/packages/ui/src/__tests__/wui-text.test.tsx @@ -1,15 +1,6 @@ import { render } from '@testing-library/react-native'; -import { configureInternal } from '@testing-library/react-native/build/config'; import { Text } from '../components/wui-text'; -configureInternal({ - hostComponentNames: { - text: 'RCTText', - textInput: 'RCTTextInput', - switch: 'RCTSwitch' - } -}); - test('Text render', () => { const label = 'Hello World'; const { getAllByText } = render({label}); diff --git a/packages/ui/src/components/wui-lean-text/index.tsx b/packages/ui/src/components/wui-lean-text/index.tsx deleted file mode 100644 index 98fe33a25..000000000 --- a/packages/ui/src/components/wui-lean-text/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { type ComponentType, createElement, forwardRef } from 'react'; -import type { TextProps } from 'react-native'; - -const LeanText = forwardRef((props, ref) => { - return createElement('RCTText', { ...props, ref }); -}) as ComponentType; - -LeanText.displayName = 'RCTText'; - -export { LeanText }; diff --git a/packages/ui/src/components/wui-lean-text/index.web.tsx b/packages/ui/src/components/wui-lean-text/index.web.tsx deleted file mode 100644 index 13643b5bb..000000000 --- a/packages/ui/src/components/wui-lean-text/index.web.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { Text } from 'react-native'; - -export { Text as LeanText }; diff --git a/packages/ui/src/components/wui-lean-view/index.tsx b/packages/ui/src/components/wui-lean-view/index.tsx deleted file mode 100644 index 8bcda5fdb..000000000 --- a/packages/ui/src/components/wui-lean-view/index.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import { type ComponentType, createElement, forwardRef } from 'react'; -import type { ViewProps } from 'react-native'; - -const LeanView = forwardRef((props, ref) => { - return createElement('RCTView', { ...props, ref }); -}) as ComponentType; - -LeanView.displayName = 'RCTView'; - -export { LeanView }; diff --git a/packages/ui/src/components/wui-lean-view/index.web.tsx b/packages/ui/src/components/wui-lean-view/index.web.tsx deleted file mode 100644 index 77c67cd3b..000000000 --- a/packages/ui/src/components/wui-lean-view/index.web.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import { View } from 'react-native'; - -export { View as LeanView }; diff --git a/packages/ui/src/components/wui-text/index.tsx b/packages/ui/src/components/wui-text/index.tsx index a728a292f..41d32ef1c 100644 --- a/packages/ui/src/components/wui-text/index.tsx +++ b/packages/ui/src/components/wui-text/index.tsx @@ -1,6 +1,5 @@ -import { type TextProps as NativeProps } from 'react-native'; +import { Text as NativeText, type TextProps as NativeProps } from 'react-native'; import { useTheme } from '../../hooks/useTheme'; -import { LeanText } from '../wui-lean-text'; import type { ColorType, TextType } from '../../utils/TypesUtil'; import styles from './styles'; @@ -21,7 +20,7 @@ export function Text({ const Theme = useTheme(); return ( - {children} - + ); } diff --git a/packages/ui/src/composites/wui-list-wallet/index.tsx b/packages/ui/src/composites/wui-list-wallet/index.tsx index c8a28665d..ace660e13 100644 --- a/packages/ui/src/composites/wui-list-wallet/index.tsx +++ b/packages/ui/src/composites/wui-list-wallet/index.tsx @@ -1,4 +1,4 @@ -import { Animated, Pressable, type StyleProp, type ViewStyle } from 'react-native'; +import { Animated, Pressable, type StyleProp, type ViewStyle, View } from 'react-native'; import { Text } from '../../components/wui-text'; import useAnimatedValue from '../../hooks/useAnimatedValue'; import { useTheme } from '../../hooks/useTheme'; @@ -6,7 +6,6 @@ import type { IconType, TagType } from '../../utils/TypesUtil'; import { Tag } from '../wui-tag'; import { WalletImage } from '../wui-wallet-image'; import { Icon } from '../../components/wui-icon'; -import { LeanView } from '../../components/wui-lean-view'; import { IconBox } from '../wui-icon-box'; import styles from './styles'; @@ -68,7 +67,7 @@ export function ListWallet({ function imageTemplate() { return ( - + {templateInstalled()} - + ); } @@ -117,12 +116,12 @@ export function ListWallet({ onPressOut={setStartValue} testID={testID} > - + {imageTemplate()} {name} - + {iconTemplate()} ); diff --git a/packages/ui/src/layout/wui-flex/index.tsx b/packages/ui/src/layout/wui-flex/index.tsx index c810bd6e1..e155cd4b0 100644 --- a/packages/ui/src/layout/wui-flex/index.tsx +++ b/packages/ui/src/layout/wui-flex/index.tsx @@ -1,4 +1,4 @@ -import { type StyleProp, type ViewStyle } from 'react-native'; +import { type StyleProp, type ViewStyle, View } from 'react-native'; import type { FlexAlignType, @@ -10,7 +10,6 @@ import type { SpacingType } from '../../utils/TypesUtil'; import { UiUtil } from '../../utils/UiUtil'; -import { LeanView } from '../../components/wui-lean-view'; export interface FlexViewProps { children?: React.ReactNode; @@ -45,5 +44,5 @@ export function FlexView(props: FlexViewProps) { marginLeft: props.margin && UiUtil.getSpacingStyles(props.margin, 3) }; - return {props.children}; + return {props.children}; } diff --git a/packages/ui/src/layout/wui-separator/index.tsx b/packages/ui/src/layout/wui-separator/index.tsx index 3dd59ff8b..b438c59ab 100644 --- a/packages/ui/src/layout/wui-separator/index.tsx +++ b/packages/ui/src/layout/wui-separator/index.tsx @@ -1,6 +1,5 @@ -import { type StyleProp, type ViewStyle } from 'react-native'; +import { type StyleProp, type ViewStyle, View } from 'react-native'; import { Text } from '../../components/wui-text'; -import { LeanView } from '../../components/wui-lean-view'; import { FlexView } from '../../layout/wui-flex'; import { useTheme } from '../../hooks/useTheme'; import styles from './styles'; @@ -14,20 +13,18 @@ export function Separator({ text, style }: SeparatorProps) { const Theme = useTheme(); if (!text) { - return ( - - ); + return ; } return ( - {text} - From b2f40ab57bf2ac99b3663b07cbb7551201a5f511 Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 4 Dec 2024 16:22:40 -0300 Subject: [PATCH 106/114] chore: send new social events (#280) * chore: send new social events --- .../src/controllers/ConnectionController.ts | 7 +++- .../core/src/controllers/RouterController.ts | 2 - packages/core/src/utils/TypeUtil.ts | 14 +++++++ .../src/partials/w3m-header/index.tsx | 39 +++++++++++++----- .../views/w3m-connect-socials-view/index.tsx | 6 ++- .../components/social-login-list.tsx | 12 +++--- .../w3m-connecting-farcaster-view/index.tsx | 22 +++++----- .../w3m-connecting-social-view/index.tsx | 41 ++++++++++++------- packages/wallet/src/AppKitWebview.tsx | 18 ++++++-- 9 files changed, 113 insertions(+), 48 deletions(-) diff --git a/packages/core/src/controllers/ConnectionController.ts b/packages/core/src/controllers/ConnectionController.ts index 4e0087901..f0c7c8758 100644 --- a/packages/core/src/controllers/ConnectionController.ts +++ b/packages/core/src/controllers/ConnectionController.ts @@ -48,6 +48,7 @@ export interface ConnectionControllerState { wcError?: boolean; pressedWallet?: WcWallet; recentWallets?: WcWallet[]; + selectedSocialProvider?: SocialProvider; connectedWalletImageUrl?: string; connectedSocialProvider?: SocialProvider; } @@ -122,6 +123,10 @@ export const ConnectionController = { state.recentWallets = wallets; }, + setSelectedSocialProvider(provider: ConnectionControllerState['selectedSocialProvider']) { + state.selectedSocialProvider = provider; + }, + async setConnectedWalletImageUrl(url: ConnectionControllerState['connectedWalletImageUrl']) { state.connectedWalletImageUrl = url; @@ -176,7 +181,7 @@ export const ConnectionController = { resetWcConnection() { this.clearUri(); state.pressedWallet = undefined; - ConnectionController.setConnectedSocialProvider(undefined); + state.selectedSocialProvider = undefined; ConnectionController.setConnectedWalletImageUrl(undefined); ConnectorController.setConnectedConnector(undefined); StorageUtil.removeWalletConnectDeepLink(); diff --git a/packages/core/src/controllers/RouterController.ts b/packages/core/src/controllers/RouterController.ts index a472288ed..e84195189 100644 --- a/packages/core/src/controllers/RouterController.ts +++ b/packages/core/src/controllers/RouterController.ts @@ -1,6 +1,5 @@ import { proxy } from 'valtio'; import type { WcWallet, CaipNetwork, Connector } from '../utils/TypeUtil'; -import type { SocialProvider } from '@reown/appkit-common-react-native'; // -- Types --------------------------------------------- // type TransactionAction = { @@ -50,7 +49,6 @@ export interface RouterControllerState { network?: CaipNetwork; email?: string; newEmail?: string; - socialProvider?: SocialProvider; }; transactionStack: TransactionAction[]; } diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index d91b2cc23..d0bce97b0 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -485,6 +485,20 @@ export type Event = provider: SocialProvider; }; } + | { + type: 'track'; + event: 'SOCIAL_LOGIN_REQUEST_USER_DATA'; + properties: { + provider: SocialProvider; + }; + } + | { + type: 'track'; + event: 'SOCIAL_LOGIN_CANCELED'; + properties: { + provider: SocialProvider; + }; + } | { type: 'track'; event: 'SOCIAL_LOGIN_ERROR'; diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index e084bcd37..ec39d7ddb 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -3,7 +3,8 @@ import { RouterController, ModalController, EventsController, - type RouterControllerState + type RouterControllerState, + ConnectionController } from '@reown/appkit-core-react-native'; import { IconLink, Text, FlexView } from '@reown/appkit-ui-react-native'; import { StringUtil } from '@reown/appkit-common-react-native'; @@ -21,8 +22,8 @@ export function Header() { const connectorName = _data?.connector?.name; const walletName = _data?.wallet?.name; const networkName = _data?.network?.name; - const socialName = _data?.socialProvider - ? StringUtil.capitalize(_data?.socialProvider) + const socialName = ConnectionController.state.selectedSocialProvider + ? StringUtil.capitalize(ConnectionController.state.selectedSocialProvider) : undefined; return { @@ -60,6 +61,29 @@ export function Header() { const header = headings(data, view); + const checkSocial = () => { + if ( + RouterController.state.view === 'ConnectingFarcaster' || + RouterController.state.view === 'ConnectingSocial' + ) { + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_CANCELED', + properties: { provider: ConnectionController.state.selectedSocialProvider! } + }); + } + }; + + const handleGoBack = () => { + checkSocial(); + RouterController.goBack(); + }; + + const handleClose = () => { + checkSocial(); + ModalController.close(); + }; + const dynamicButtonTemplate = () => { const noButtonViews = ['ConnectingSiwe']; @@ -70,12 +94,7 @@ export function Header() { const showBack = RouterController.state.history.length > 1; return showBack ? ( - + ) : ( ); @@ -96,7 +115,7 @@ export function Header() { {header} - + ); } diff --git a/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx b/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx index 668123d88..228bc143e 100644 --- a/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connect-socials-view/index.tsx @@ -3,6 +3,7 @@ import { useSnapshot } from 'valtio'; import { ScrollView } from 'react-native'; import { StringUtil, type SocialProvider } from '@reown/appkit-common-react-native'; import { + ConnectionController, EventsController, OptionsController, RouterController, @@ -19,15 +20,16 @@ export function ConnectSocialsView() { const socialProviders = (features?.socials ?? []) as SocialProvider[]; const onItemPress = (provider: SocialProvider) => { + ConnectionController.setSelectedSocialProvider(provider); EventsController.sendEvent({ type: 'track', event: 'SOCIAL_LOGIN_STARTED', properties: { provider } }); if (provider === 'farcaster') { - RouterController.push('ConnectingFarcaster', { socialProvider: provider }); + RouterController.push('ConnectingFarcaster'); } else { - RouterController.push('ConnectingSocial', { socialProvider: provider }); + RouterController.push('ConnectingSocial'); } }; diff --git a/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx b/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx index cc1e45a2b..ea2740dbf 100644 --- a/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx +++ b/packages/scaffold/src/views/w3m-connect-view/components/social-login-list.tsx @@ -2,6 +2,7 @@ import { StyleSheet } from 'react-native'; import { FlexView, ListSocial, LogoSelect, Spacing, Text } from '@reown/appkit-ui-react-native'; import { type SocialProvider, StringUtil } from '@reown/appkit-common-react-native'; import { + ConnectionController, EventsController, RouterController, WebviewController @@ -21,18 +22,19 @@ export function SocialLoginList({ options, disabled }: SocialLoginListProps) { let bottomSocials = showBigSocial ? options.slice(1) : options; bottomSocials = showMoreButton ? bottomSocials.slice(0, MAX_OPTIONS - 2) : bottomSocials; - const onItemPress = (social: SocialProvider) => { + const onItemPress = (provider: SocialProvider) => { + ConnectionController.setSelectedSocialProvider(provider); EventsController.sendEvent({ type: 'track', event: 'SOCIAL_LOGIN_STARTED', - properties: { provider: social } + properties: { provider } }); WebviewController.setConnecting(false); - if (social === 'farcaster') { - RouterController.push('ConnectingFarcaster', { socialProvider: social }); + if (provider === 'farcaster') { + RouterController.push('ConnectingFarcaster'); } else { - RouterController.push('ConnectingSocial', { socialProvider: social }); + RouterController.push('ConnectingSocial'); } }; diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx index aba7b5ff7..bea4029db 100644 --- a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx @@ -6,9 +6,7 @@ import { EventsController, ModalController, OptionsController, - RouterController, SnackController, - WebviewController, type AppKitFrameProvider } from '@reown/appkit-core-react-native'; import { @@ -24,33 +22,37 @@ import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; export function ConnectingFarcasterView() { - const { data } = RouterController.state; const { maxWidth: width } = useCustomDimensions(); const authConnector = ConnectorController.getAuthConnector(); const [error, setError] = useState(false); const [processing, setProcessing] = useState(false); const [url, setUrl] = useState(); const showCopy = OptionsController.isClipboardAvailable(); - const socialProvider = data?.socialProvider; const provider = authConnector?.provider as AppKitFrameProvider; const onConnect = useCallback(async () => { try { - if (!WebviewController.state.connecting && provider && socialProvider && authConnector) { + if (provider && authConnector) { setError(false); const { url: farcasterUrl } = await provider.getFarcasterUri(); setUrl(farcasterUrl); Linking.openURL(farcasterUrl); + await provider.connectFarcaster(); + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_REQUEST_USER_DATA', + properties: { provider: 'farcaster' } + }); setProcessing(true); await ConnectionController.connectExternal(authConnector); - ConnectionController.setConnectedSocialProvider(socialProvider); + ConnectionController.setConnectedSocialProvider('farcaster'); EventsController.sendEvent({ type: 'track', event: 'SOCIAL_LOGIN_SUCCESS', - properties: { provider: socialProvider } + properties: { provider: 'farcaster' } }); - WebviewController.setConnecting(false); + setProcessing(false); ModalController.close(); } @@ -58,13 +60,13 @@ export function ConnectingFarcasterView() { EventsController.sendEvent({ type: 'track', event: 'SOCIAL_LOGIN_ERROR', - properties: { provider: socialProvider! } + properties: { provider: 'farcaster' } }); SnackController.showError('Something went wrong'); setError(true); setProcessing(false); } - }, [provider, socialProvider, authConnector]); + }, [provider, authConnector]); const onCopyUrl = () => { if (url) { diff --git a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx index 1f15870d5..dc7b0aaa2 100644 --- a/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-social-view/index.tsx @@ -18,27 +18,31 @@ import { useCustomDimensions } from '../../hooks/useCustomDimensions'; import styles from './styles'; export function ConnectingSocialView() { - const { data } = RouterController.state; const { maxWidth: width } = useCustomDimensions(); const { processingAuth } = useSnapshot(WebviewController.state); + const { selectedSocialProvider } = useSnapshot(ConnectionController.state); const authConnector = ConnectorController.getAuthConnector(); const [error, setError] = useState(false); - const socialProvider = data?.socialProvider; const provider = authConnector?.provider as AppKitFrameProvider; const onConnect = useCallback(async () => { try { - if (!WebviewController.state.connecting && provider && socialProvider) { + if ( + !WebviewController.state.connecting && + provider && + ConnectionController.state.selectedSocialProvider + ) { const { uri } = await provider.getSocialRedirectUri({ - provider: socialProvider + provider: ConnectionController.state.selectedSocialProvider }); WebviewController.setWebviewUrl(uri); - const isNativeApple = socialProvider === 'apple' && Platform.OS === 'ios'; + const isNativeApple = + ConnectionController.state.selectedSocialProvider === 'apple' && Platform.OS === 'ios'; WebviewController.setWebviewVisible(!isNativeApple); WebviewController.setConnecting(true); - WebviewController.setConnectingProvider(socialProvider); + WebviewController.setConnectingProvider(ConnectionController.state.selectedSocialProvider); } } catch (e) { WebviewController.setWebviewVisible(false); @@ -48,29 +52,38 @@ export function ConnectingSocialView() { SnackController.showError('Something went wrong'); setError(true); } - }, [provider, socialProvider]); + }, [provider]); const socialMessageHandler = useCallback( async (url: string) => { try { if ( url.includes('/sdk/oauth') && - socialProvider && + ConnectionController.state.selectedSocialProvider && authConnector && !WebviewController.state.processingAuth ) { WebviewController.setProcessingAuth(true); WebviewController.setWebviewVisible(false); const parsedUrl = new URL(url); + + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_REQUEST_USER_DATA', + properties: { provider: ConnectionController.state.selectedSocialProvider } + }); + await provider?.connectSocial(parsedUrl.search); await ConnectionController.connectExternal(authConnector); - ConnectionController.setConnectedSocialProvider(socialProvider); + ConnectionController.setConnectedSocialProvider( + ConnectionController.state.selectedSocialProvider + ); WebviewController.setConnecting(false); EventsController.sendEvent({ type: 'track', event: 'SOCIAL_LOGIN_SUCCESS', - properties: { provider: socialProvider } + properties: { provider: ConnectionController.state.selectedSocialProvider } }); ModalController.close(); @@ -80,14 +93,14 @@ export function ConnectingSocialView() { EventsController.sendEvent({ type: 'track', event: 'SOCIAL_LOGIN_ERROR', - properties: { provider: socialProvider! } + properties: { provider: ConnectionController.state.selectedSocialProvider! } }); WebviewController.reset(); RouterController.goBack(); SnackController.showError('Something went wrong'); } }, - [socialProvider, authConnector, provider] + [authConnector, provider] ); useEffect(() => { @@ -112,7 +125,7 @@ export function ConnectingSocialView() { style={{ width }} > - + {error && ( {processingAuth ? 'Loading user data' - : `Continue with ${StringUtil.capitalize(socialProvider)}`} + : `Continue with ${StringUtil.capitalize(selectedSocialProvider)}`} {processingAuth diff --git a/packages/wallet/src/AppKitWebview.tsx b/packages/wallet/src/AppKitWebview.tsx index df38e1ffc..bbbd648c0 100644 --- a/packages/wallet/src/AppKitWebview.tsx +++ b/packages/wallet/src/AppKitWebview.tsx @@ -1,10 +1,11 @@ import { useSnapshot } from 'valtio'; import { useEffect, useRef, useState } from 'react'; -import { Animated, SafeAreaView, StyleSheet } from 'react-native'; +import { Animated, Pressable, SafeAreaView, StyleSheet } from 'react-native'; import { WebView } from 'react-native-webview'; - import { + ConnectionController, ConnectorController, + EventsController, RouterController, WebviewController } from '@reown/appkit-core-react-native'; @@ -13,6 +14,7 @@ import type { AppKitFrameProvider } from './AppKitFrameProvider'; import { AppKitFrameConstants } from './AppKitFrameConstants'; const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView); +const AnimatedPressable = Animated.createAnimatedComponent(Pressable); export function AppKitWebview() { const webviewRef = useRef(null); @@ -26,6 +28,12 @@ export function AppKitWebview() { const display = webviewVisible ? 'flex' : 'none'; const onClose = () => { + EventsController.sendEvent({ + type: 'track', + event: 'SOCIAL_LOGIN_CANCELED', + properties: { provider: ConnectionController.state.selectedSocialProvider! } + }); + WebviewController.setWebviewVisible(false); WebviewController.setConnecting(false); WebviewController.setConnectingProvider(undefined); @@ -58,7 +66,8 @@ export function AppKitWebview() { return provider ? ( <> - Date: Tue, 10 Dec 2024 11:09:40 -0300 Subject: [PATCH 107/114] fix: farcaster login refresh (#281) * fix: added temporary workaround to reload auth site if farcaster flow is closed * chore: code styling --- .../scaffold/src/partials/w3m-header/index.tsx | 13 ++++++++++++- .../views/w3m-connecting-farcaster-view/index.tsx | 14 ++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index ec39d7ddb..6f280bd72 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -4,7 +4,9 @@ import { ModalController, EventsController, type RouterControllerState, - ConnectionController + ConnectionController, + ConnectorController, + type AppKitFrameProvider } from '@reown/appkit-core-react-native'; import { IconLink, Text, FlexView } from '@reown/appkit-ui-react-native'; import { StringUtil } from '@reown/appkit-common-react-native'; @@ -66,6 +68,15 @@ export function Header() { RouterController.state.view === 'ConnectingFarcaster' || RouterController.state.view === 'ConnectingSocial' ) { + const socialProvider = ConnectionController.state.selectedSocialProvider; + const authProvider = ConnectorController.getAuthConnector()?.provider as AppKitFrameProvider; + + if (authProvider && socialProvider === 'farcaster') { + // TODO: remove this once Farcaster session refresh is implemented + // @ts-expect-error + authProvider.webviewRef?.current?.reload(); + } + EventsController.sendEvent({ type: 'track', event: 'SOCIAL_LOGIN_CANCELED', diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx index bea4029db..ce726591f 100644 --- a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx @@ -62,6 +62,9 @@ export function ConnectingFarcasterView() { event: 'SOCIAL_LOGIN_ERROR', properties: { provider: 'farcaster' } }); + // TODO: remove this once Farcaster session refresh is implemented + // @ts-expect-error + provider?.webviewRef?.current?.reload(); SnackController.showError('Something went wrong'); setError(true); setProcessing(false); @@ -75,6 +78,17 @@ export function ConnectingFarcasterView() { } }; + useEffect(() => { + return () => { + // TODO: remove this once Farcaster session refresh is implemented + if (!ModalController.state.open) { + // @ts-expect-error + provider.webviewRef?.current?.reload(); + } + }; + // @ts-expect-error + }, [provider.webviewRef]); + useEffect(() => { onConnect(); }, [onConnect]); From 10723571e64acc43b8c68dfb724462bcba06cf9d Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Thu, 12 Dec 2024 16:35:01 -0300 Subject: [PATCH 108/114] feat: add e2e tests (#283) * feat: add tests using playwright * chore: added github action --- .github/workflows/e2e.yml | 98 ++++-- .github/workflows/verify.yml | 8 +- .maestro/w3m-connect-flow.yaml | 12 +- .maestro/w3m-ui-flow.yaml | 6 +- apps/native/.github/workflows/playwright.yml | 27 ++ apps/native/.gitignore | 4 + apps/native/App.tsx | 22 +- apps/native/app.json | 7 +- apps/native/package.json | 11 +- apps/native/playwright.config.ts | 68 +++++ .../src/components/DisconnectButton.tsx | 21 ++ apps/native/src/components/OpenButton.tsx | 22 ++ apps/native/src/utils/ToastUtils.ts | 18 ++ apps/native/src/views/AccountView.tsx | 33 ++- apps/native/src/views/ActionsView.tsx | 65 +++- apps/native/tests/basic-tests.spec.ts | 56 ++++ apps/native/tests/shared/constants/index.ts | 11 + .../native/tests/shared/constants/timeouts.ts | 1 + .../tests/shared/fixtures/timing-fixture.ts | 11 + .../tests/shared/fixtures/w3m-fixture.ts | 34 +++ .../shared/fixtures/w3m-wallet-fixture.ts | 99 +++++++ apps/native/tests/shared/pages/ModalPage.ts | 279 ++++++++++++++++++ apps/native/tests/shared/pages/WalletPage.ts | 125 ++++++++ apps/native/tests/shared/types/index.ts | 9 + apps/native/tests/shared/utils/logs.ts | 17 ++ apps/native/tests/shared/utils/timeouts.ts | 9 + .../tests/shared/validators/ModalValidator.ts | 165 +++++++++++ .../shared/validators/WalletValidator.ts | 68 +++++ apps/native/tests/wallet.spec.ts | 126 ++++++++ .../controllers/SnackController.test.ts | 3 + packages/ethers/src/client.ts | 1 + packages/ethers5/src/client.ts | 1 + packages/scaffold/src/hooks/useTimeout.ts | 10 +- .../scaffold/src/modal/w3m-button/index.tsx | 4 +- .../scaffold/src/modal/w3m-modal/index.tsx | 1 + .../src/modal/w3m-network-button/index.tsx | 1 + .../partials/w3m-all-wallets-search/index.tsx | 1 + .../partials/w3m-connecting-qrcode/index.tsx | 2 +- .../src/partials/w3m-header/index.tsx | 6 +- .../views/w3m-account-default-view/index.tsx | 15 +- .../src/views/w3m-all-wallets-view/index.tsx | 2 +- .../components/all-wallets-button.tsx | 2 +- .../w3m-connecting-farcaster-view/index.tsx | 2 +- .../src/views/w3m-get-wallet-view/index.tsx | 7 +- .../src/views/w3m-networks-view/index.tsx | 9 +- .../index.tsx | 2 +- .../views/w3m-what-is-a-wallet-view/index.tsx | 8 +- .../views/w3m-connecting-siwe-view/index.tsx | 2 +- .../src/composites/wui-card-select/index.tsx | 5 +- .../src/composites/wui-input-text/index.tsx | 2 +- .../composites/wui-network-button/index.tsx | 5 +- .../src/composites/wui-search-bar/index.tsx | 5 +- .../ui/src/composites/wui-snackbar/index.tsx | 7 +- packages/wagmi/src/client.ts | 1 + packages/wallet/src/AppKitWebview.tsx | 2 +- yarn.lock | 165 ++++++++++- 56 files changed, 1604 insertions(+), 99 deletions(-) create mode 100644 apps/native/.github/workflows/playwright.yml create mode 100644 apps/native/playwright.config.ts create mode 100644 apps/native/src/components/DisconnectButton.tsx create mode 100644 apps/native/src/components/OpenButton.tsx create mode 100644 apps/native/src/utils/ToastUtils.ts create mode 100644 apps/native/tests/basic-tests.spec.ts create mode 100644 apps/native/tests/shared/constants/index.ts create mode 100644 apps/native/tests/shared/constants/timeouts.ts create mode 100644 apps/native/tests/shared/fixtures/timing-fixture.ts create mode 100644 apps/native/tests/shared/fixtures/w3m-fixture.ts create mode 100644 apps/native/tests/shared/fixtures/w3m-wallet-fixture.ts create mode 100644 apps/native/tests/shared/pages/ModalPage.ts create mode 100644 apps/native/tests/shared/pages/WalletPage.ts create mode 100644 apps/native/tests/shared/types/index.ts create mode 100644 apps/native/tests/shared/utils/logs.ts create mode 100644 apps/native/tests/shared/utils/timeouts.ts create mode 100644 apps/native/tests/shared/validators/ModalValidator.ts create mode 100644 apps/native/tests/shared/validators/WalletValidator.ts create mode 100644 apps/native/tests/wallet.spec.ts diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 19617cf19..2d2bf6450 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -1,38 +1,86 @@ -name: e2e +name: E2E Tests + on: workflow_dispatch: - pull_request: - branches: - - main + workflow_call: + inputs: + branch: + description: 'The branch to use' + default: 'main' + required: false + type: string + base-url: + description: 'The AppKit App url' + default: 'http://localhost:8081/' + required: false + type: string + wallet-url: + description: 'The wallet url' + default: 'https://react-wallet.walletconnect.com/' + required: false + type: string + skip_setup: + description: 'Skip setup steps if already done' + type: boolean + default: false + secrets: + CLOUD_PROJECT_ID: + required: true jobs: - maestro-cloud: + e2e_tests: + name: 'Playwright Tests' runs-on: ubuntu-latest - defaults: - run: - working-directory: apps/native-cli - outputs: - app: app/build/outputs/apk/release steps: - - name: Checkout - uses: actions/checkout@v3 - - - name: Install Java 11 - uses: actions/setup-java@v3 + - name: checkout + if: ${{ !inputs.skip_setup }} + uses: actions/checkout@v4 with: - java-version: 11 - distribution: 'temurin' - cache: gradle - - - run: touch .env && echo "PROJECT_ID=${{ secrets.CLOUD_PROJECT_ID }}" >> .env + repository: reown-com/appkit-react-native + ref: ${{ inputs.branch }} - name: Setup + if: ${{ !inputs.skip_setup }} uses: ./.github/actions/setup - - run: yarn android:build + - name: Build SDK + if: ${{ !inputs.skip_setup }} + run: | + echo "Building SDK..." + yarn build + + - name: Create ENV file in apps/native + working-directory: ./apps/native/ + run: echo "EXPO_PUBLIC_PROJECT_ID=${{ secrets.CLOUD_PROJECT_ID }}" >> .env + + - name: Install Playwright Browsers + working-directory: ./apps/native/ + run: yarn playwright install chromium + + ## Uncomment to build the web and add ./apps/native/dist to upload the artifact + # - name: Build web app + # working-directory: ./apps/native/ + # run: | + # echo "Building web app..." + # yarn build:web + + - name: Run Playwright tests + working-directory: ./apps/native/ + env: + BASE_URL: ${{ inputs.base-url }} + WALLET_URL: ${{ inputs.wallet-url }} + + ## Uncomment to see better logs in the terminal + # DEBUG: pw:api + run: | + echo "Running tests against $BASE_URL" + yarn playwright:test - - uses: mobile-dev-inc/action-maestro-cloud@v1 + - uses: actions/upload-artifact@v4 + if: failure() with: - api-key: ${{ secrets.MAESTRO_CLOUD_API_KEY }} - app-file: apps/native-cli/android/app/build/outputs/apk/release/app-release.apk - android-api-level: 31 + name: playwright-report + path: | + ./apps/native/playwright-report/ + ./apps/native/test-results/ + retention-days: 7 diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index f09faad03..95a8d345d 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -22,5 +22,11 @@ jobs: - name: Build run: yarn build - - name: Test + - name: Package Tests run: yarn test + + - name: E2E Tests + uses: ./.github/workflows/e2e.yml + with: + skip_setup: true # Skip redundant setup steps + secrets: inherit diff --git a/.maestro/w3m-connect-flow.yaml b/.maestro/w3m-connect-flow.yaml index 11cf64d54..155431e5a 100644 --- a/.maestro/w3m-connect-flow.yaml +++ b/.maestro/w3m-connect-flow.yaml @@ -9,7 +9,7 @@ appId: com.walletconnect.web3modal.rnclisdk - tapOn: 'Connect' - tapOn: - id: 'button-all-wallets' + id: 'all-wallets' - tapOn: id: 'button-qr-code' @@ -18,7 +18,7 @@ appId: com.walletconnect.web3modal.rnclisdk id: 'qr-code' - tapOn: - id: 'button-copy-uri' + id: 'copy-link' - openLink: link: https://react-wallet.walletconnect.com/walletconnect @@ -54,10 +54,10 @@ appId: com.walletconnect.web3modal.rnclisdk - back - tapOn: - id: 'button-account' + id: 'account-button' - tapOn: - id: 'button-network' + id: 'w3m-account-select-network' - tapOn: text: 'Polygon' @@ -68,7 +68,7 @@ appId: com.walletconnect.web3modal.rnclisdk text: 'Polygon' - tapOn: - id: 'button-disconnect' + id: 'disconnect-button' - assertVisible: - id: 'button-connect' + id: 'connect-button' diff --git a/.maestro/w3m-ui-flow.yaml b/.maestro/w3m-ui-flow.yaml index 465b6bf7e..161afd6bc 100644 --- a/.maestro/w3m-ui-flow.yaml +++ b/.maestro/w3m-ui-flow.yaml @@ -19,7 +19,7 @@ appId: com.walletconnect.web3modal.rnclisdk id: 'button-back' - tapOn: - id: 'button-all-wallets' + id: 'all-wallets' - tapOn: id: 'button-qr-code' @@ -31,7 +31,7 @@ appId: com.walletconnect.web3modal.rnclisdk id: 'button-back' - tapOn: - id: 'input-search' + id: 'search-wallet-input' # Get a wallet that doesn't come in the first page - inputText: 'Abra Wallet' @@ -57,7 +57,7 @@ appId: com.walletconnect.web3modal.rnclisdk text: 'Open' - tapOn: - id: 'button-close' + id: 'header-close' - assertNotVisible: text: 'Abra Wallet' diff --git a/apps/native/.github/workflows/playwright.yml b/apps/native/.github/workflows/playwright.yml new file mode 100644 index 000000000..a94b6417a --- /dev/null +++ b/apps/native/.github/workflows/playwright.yml @@ -0,0 +1,27 @@ +name: Playwright Tests +on: + push: + branches: [ main, master ] + pull_request: + branches: [ main, master ] +jobs: + test: + timeout-minutes: 60 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - name: Install dependencies + run: npm install -g yarn && yarn + - name: Install Playwright Browsers + run: yarn playwright install --with-deps + - name: Run Playwright tests + run: yarn playwright test + - uses: actions/upload-artifact@v4 + if: ${{ !cancelled() }} + with: + name: playwright-report + path: playwright-report/ + retention-days: 30 diff --git a/apps/native/.gitignore b/apps/native/.gitignore index e8be304f9..502848c55 100644 --- a/apps/native/.gitignore +++ b/apps/native/.gitignore @@ -14,3 +14,7 @@ web-build/ .DS_Store .env +/test-results/ +/playwright-report/ +/blob-report/ +/playwright/.cache/ diff --git a/apps/native/App.tsx b/apps/native/App.tsx index ae99f40cf..ea1abcdd9 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -1,9 +1,10 @@ -import { StyleSheet, View, useColorScheme } from 'react-native'; +import { Platform, SafeAreaView, StyleSheet, useColorScheme } from 'react-native'; import { StatusBar } from 'expo-status-bar'; import * as Clipboard from 'expo-clipboard'; import '@walletconnect/react-native-compat'; import { WagmiProvider } from 'wagmi'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import Toast from 'react-native-toast-message'; import { AppKit, @@ -21,6 +22,8 @@ import { AccountView } from './src/views/AccountView'; import { ActionsView } from './src/views/ActionsView'; import { getCustomWallets } from './src/utils/misc'; import { chains } from './src/utils/WagmiUtils'; +import { OpenButton } from './src/components/OpenButton'; +import { DisconnectButton } from './src/components/DisconnectButton'; const projectId = process.env.EXPO_PUBLIC_PROJECT_ID ?? ''; @@ -44,11 +47,17 @@ const clipboardClient = { const auth = authConnector({ projectId, metadata }); +const extraConnectors = Platform.select({ + ios: [auth], + android: [auth], + default: [] +}); + const wagmiConfig = defaultWagmiConfig({ chains, projectId, metadata, - extraConnectors: [auth] + extraConnectors }); const queryClient = new QueryClient(); @@ -77,7 +86,7 @@ export default function Native() { return ( - + - + + + - + + ); diff --git a/apps/native/app.json b/apps/native/app.json index 52c6b4f97..cd0024e83 100644 --- a/apps/native/app.json +++ b/apps/native/app.json @@ -74,7 +74,12 @@ } }, "web": { - "favicon": "./assets/favicon.png" + "favicon": "./assets/favicon.png", + "output": "single", + "bundler": "metro" + }, + "experiments": { + "baseUrl": "." }, "extra": { "eas": { diff --git a/apps/native/package.json b/apps/native/package.json index e3a094c0c..7afbad09e 100644 --- a/apps/native/package.json +++ b/apps/native/package.json @@ -12,7 +12,11 @@ "dev:android": "expo start --android", "eas:build": "eas build --platform all", "eas:build:local": "eas build --local --platform all", - "eas:update": "eas update --branch preview" + "eas:update": "eas update --branch preview", + "playwright:test": "playwright test", + "playwright:install": "playwright install chromium", + "deploy": "gh-pages --nojekyll -d dist", + "build:web": "expo export -p web" }, "dependencies": { "@expo/metro-runtime": "~3.2.3", @@ -35,6 +39,7 @@ "react-native-get-random-values": "~1.11.0", "react-native-modal": "13.0.1", "react-native-svg": "15.2.0", + "react-native-toast-message": "2.2.1", "react-native-web": "~0.19.10", "react-native-webview": "13.8.6", "uuid": "3.4.0", @@ -43,9 +48,13 @@ }, "devDependencies": { "@babel/core": "^7.24.0", + "@playwright/test": "^1.49.1", + "@types/gh-pages": "^6", + "@types/node": "^22.10.1", "@types/react": "~18.2.79", "@types/react-native": "0.72.2", "babel-plugin-module-resolver": "^5.0.0", + "gh-pages": "^6.2.0", "typescript": "~5.3.3" } } diff --git a/apps/native/playwright.config.ts b/apps/native/playwright.config.ts new file mode 100644 index 000000000..081c8bed3 --- /dev/null +++ b/apps/native/playwright.config.ts @@ -0,0 +1,68 @@ +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * See https://playwright.dev/docs/test-configuration. + */ +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [['html'], ['list']], + + use: { + baseURL: process.env.BASE_URL || 'http://localhost:8081', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + + /* Take a screenshot when the test fails */ + screenshot: 'only-on-failure', + + permissions: ['clipboard-read', 'clipboard-write'], + navigationTimeout: 30000, + actionTimeout: 30000, + video: 'retain-on-failure' + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'], channel: 'chromium' } + } + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'yarn web', + url: 'http://localhost:8081', + reuseExistingServer: !process.env.CI, + timeout: 30000 + + /* Uncomment to see better logs in the terminal */ + // stdout: 'pipe', + // stderr: 'pipe' + }, + + globalTimeout: 600000, + expect: { + timeout: 10000 + } +}); diff --git a/apps/native/src/components/DisconnectButton.tsx b/apps/native/src/components/DisconnectButton.tsx new file mode 100644 index 000000000..d2c002ba7 --- /dev/null +++ b/apps/native/src/components/DisconnectButton.tsx @@ -0,0 +1,21 @@ +import { StyleSheet } from 'react-native'; +import { Button } from '@reown/appkit-ui-react-native'; +import { useAccount, useDisconnect } from 'wagmi'; + +export function DisconnectButton() { + const { isConnected } = useAccount(); + const { disconnect } = useDisconnect(); + + return isConnected ? ( + + ) : null; +} + +const styles = StyleSheet.create({ + button: { + height: 40, + marginVertical: 8 + } +}); diff --git a/apps/native/src/components/OpenButton.tsx b/apps/native/src/components/OpenButton.tsx new file mode 100644 index 000000000..c79b2ed9a --- /dev/null +++ b/apps/native/src/components/OpenButton.tsx @@ -0,0 +1,22 @@ +import { StyleSheet } from 'react-native'; +import { Button } from '@reown/appkit-ui-react-native'; +import { useAppKit } from '@reown/appkit-wagmi-react-native'; +import { useAccount } from 'wagmi'; + +export function OpenButton() { + const { open } = useAppKit(); + const { isConnected } = useAccount(); + + return !isConnected ? ( + + ) : null; +} + +const styles = StyleSheet.create({ + button: { + height: 40, + marginVertical: 8 + } +}); diff --git a/apps/native/src/utils/ToastUtils.ts b/apps/native/src/utils/ToastUtils.ts new file mode 100644 index 000000000..178adc04d --- /dev/null +++ b/apps/native/src/utils/ToastUtils.ts @@ -0,0 +1,18 @@ +import Toast from 'react-native-toast-message'; + +export const ToastUtils = { + showSuccessToast: (title: string, message: string) => { + Toast.show({ + type: 'success', + text1: title, + text2: message + }); + }, + showErrorToast: (title: string, message: string) => { + Toast.show({ + type: 'error', + text1: title, + text2: message + }); + } +}; diff --git a/apps/native/src/views/AccountView.tsx b/apps/native/src/views/AccountView.tsx index 1f5c8d975..c75c2896b 100644 --- a/apps/native/src/views/AccountView.tsx +++ b/apps/native/src/views/AccountView.tsx @@ -1,5 +1,5 @@ -import { View } from 'react-native'; -import { Text } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; +import { Text, FlexView } from '@reown/appkit-ui-react-native'; import { useAccount, useBalance } from 'wagmi'; export function AccountView() { @@ -7,15 +7,28 @@ export function AccountView() { const { data, isLoading } = useBalance({ address }); return isConnected ? ( - - Wagmi Account Info - {isConnected && {address}} - {isLoading && Fetching balance...} + + Wagmi Account Info + + Address: + {isConnected && {address}} + + {isLoading && Fetching balance...} {data && ( - - Balance: {data?.formatted} {data?.symbol} - + + Balance: + + {data?.formatted} {data?.symbol} + + )} - + ) : null; } + +const styles = StyleSheet.create({ + container: { + marginTop: 32, + gap: 8 + } +}); diff --git a/apps/native/src/views/ActionsView.tsx b/apps/native/src/views/ActionsView.tsx index 39cb70c22..ba284e2b0 100644 --- a/apps/native/src/views/ActionsView.tsx +++ b/apps/native/src/views/ActionsView.tsx @@ -1,39 +1,76 @@ -import { Button, Text } from '@reown/appkit-ui-react-native'; -import { View } from 'react-native'; +import { Button, Text, FlexView } from '@reown/appkit-ui-react-native'; +import { StyleSheet } from 'react-native'; import { useSignMessage, useAccount, useSendTransaction, useEstimateGas } from 'wagmi'; import { Hex, parseEther } from 'viem'; +import { SendTransactionData, SignMessageData } from 'wagmi/query'; +import { ToastUtils } from '../utils/ToastUtils'; export function ActionsView() { const { isConnected } = useAccount(); - const { data, isError, isPending, isSuccess, signMessage } = useSignMessage(); + + const onSignSuccess = (data: SignMessageData) => { + ToastUtils.showSuccessToast('Signature successful', data); + }; + + const onSignError = (error: Error) => { + ToastUtils.showErrorToast('Signature failed', error.message); + }; + + const onSendSuccess = (data: SendTransactionData) => { + ToastUtils.showSuccessToast('Transaction successful', data); + }; + + const onSendError = (error: Error) => { + ToastUtils.showErrorToast('Transaction failed', error.message); + }; + + const { isPending, signMessage } = useSignMessage({ + mutation: { + onSuccess: onSignSuccess, + onError: onSignError + } + }); const TX = { - to: '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045' as Hex, // vitalik.eth + to: '0x704457b418E9Fb723e1Bc0cB98106a6B8Cf87689' as Hex, // Test wallet value: parseEther('0.001'), data: '0x' as Hex }; + const { data: gas, isError: isGasError } = useEstimateGas(TX); const { - data: sendData, isPending: isSending, - isSuccess: isSendSuccess, + sendTransaction - } = useSendTransaction(); + } = useSendTransaction({ + mutation: { + onSuccess: onSendSuccess, + onError: onSendError + } + }); return isConnected ? ( - - Wagmi Actions - - {isSuccess && Signature: {data}} {isGasError && Error estimating gas} - {isError && Error signing message} {isSending && Check Wallet} - {isSendSuccess && Transaction: {JSON.stringify(sendData)}} - + ) : null; } + +const styles = StyleSheet.create({ + container: { + marginTop: 16, + gap: 8 + } +}); diff --git a/apps/native/tests/basic-tests.spec.ts b/apps/native/tests/basic-tests.spec.ts new file mode 100644 index 000000000..22eacb035 --- /dev/null +++ b/apps/native/tests/basic-tests.spec.ts @@ -0,0 +1,56 @@ +import { test, BrowserContext, Page } from '@playwright/test'; +import { ModalPage } from './shared/pages/ModalPage'; +import { ModalValidator } from './shared/validators/ModalValidator'; + +const METAMASK_WALLET_ID = 'c57ca95b47569778a828d19178114f4db188b89b763c899ba0be274e97267d96'; + +let context: BrowserContext; +let browserPage: Page; +let modalPage: ModalPage; +let modalValidator: ModalValidator; + +test.beforeAll(async ({ browser }) => { + context = await browser.newContext(); + browserPage = await context.newPage(); + + modalPage = new ModalPage(browserPage); + modalValidator = new ModalValidator(modalPage.page); + + await modalPage.load(); +}); + +test('Should be able to open modal', async () => { + await modalPage.openConnectModal(); + await modalValidator.expectConnectScreen(); + await modalPage.closeModal(); +}); + +test('Should be able to open network view', async () => { + await modalPage.openNetworkModal(); + await modalValidator.expectNetworkScreen(); + await modalPage.closeModal(); +}); + +test('Should be able to open modal with the open hook', async () => { + const openHookButton = modalPage.page.getByTestId('open-hook-button'); + await openHookButton.click(); + await modalValidator.expectConnectScreen(); + await modalPage.closeModal(); +}); + +test('it should display what is a wallet view', async () => { + await modalPage.openConnectModal(); + await modalValidator.expectWhatIsAWalletButton(); + await modalPage.clickWhatIsAWalletButton(); + await modalValidator.expectWhatIsAWalletView(); + await modalPage.clickGetAWalletButton(); + await modalValidator.expectGetAWalletView(); + await modalPage.closeModal(); +}); + +test('it should search for a wallet', async () => { + await modalPage.openConnectModal(); + await modalValidator.expectConnectScreen(); + await modalPage.searchWalletFlow(modalPage, 'MetaMask', METAMASK_WALLET_ID); + await modalPage.closeModal(); +}); diff --git a/apps/native/tests/shared/constants/index.ts b/apps/native/tests/shared/constants/index.ts new file mode 100644 index 000000000..dc2550fef --- /dev/null +++ b/apps/native/tests/shared/constants/index.ts @@ -0,0 +1,11 @@ +import type { SessionParams } from '../types'; + +// Allow localhost +export const BASE_URL = process.env.BASE_URL || 'http://localhost:8081/'; +export const WALLET_URL = process.env.WALLET_URL || 'https://react-wallet.walletconnect.com/'; +export const DEFAULT_SESSION_PARAMS: SessionParams = { + reqAccounts: ['1', '2'], + optAccounts: ['1', '2'], + accept: true +}; +export const DEFAULT_CHAIN_NAME = process.env.DEFAULT_CHAIN_NAME || 'Ethereum'; diff --git a/apps/native/tests/shared/constants/timeouts.ts b/apps/native/tests/shared/constants/timeouts.ts new file mode 100644 index 000000000..03ff123ce --- /dev/null +++ b/apps/native/tests/shared/constants/timeouts.ts @@ -0,0 +1 @@ +export const MAXIMUM_WAIT_CONNECTIONS = 30 * 1000; diff --git a/apps/native/tests/shared/fixtures/timing-fixture.ts b/apps/native/tests/shared/fixtures/timing-fixture.ts new file mode 100644 index 000000000..5c4d3d2bd --- /dev/null +++ b/apps/native/tests/shared/fixtures/timing-fixture.ts @@ -0,0 +1,11 @@ +import { test as base } from '@playwright/test'; + +export type TimingRecords = { item: string; timeMs: number }[]; + +export interface TimingFixture { + timingRecords: TimingRecords; +} + +export const timingFixture = base.extend({ + timingRecords: [] +}); diff --git a/apps/native/tests/shared/fixtures/w3m-fixture.ts b/apps/native/tests/shared/fixtures/w3m-fixture.ts new file mode 100644 index 000000000..86e6ab924 --- /dev/null +++ b/apps/native/tests/shared/fixtures/w3m-fixture.ts @@ -0,0 +1,34 @@ +/* eslint no-console: 0 */ + +import { ModalPage } from '../pages/ModalPage'; +import { timeStart, timeEnd } from '../utils/logs'; +import { timingFixture } from './timing-fixture'; + +// Declare the types of fixtures to use +export interface ModalFixture { + modalPage: ModalPage; + library: string; +} + +// M -> test Modal +export const testM = timingFixture.extend({ + modalPage: async ({ page }, use) => { + timeStart('new ModalPage'); + const modalPage = new ModalPage(page); + timeEnd('new ModalPage'); + timeStart('modalPage.load'); + await modalPage.load(); + timeEnd('modalPage.load'); + await use(modalPage); + } +}); + +export const testMSiwe = timingFixture.extend({ + modalPage: async ({ page }, use) => { + const modalPage = new ModalPage(page); + await modalPage.load(); + await use(modalPage); + } +}); + +export { expect } from '@playwright/test'; diff --git a/apps/native/tests/shared/fixtures/w3m-wallet-fixture.ts b/apps/native/tests/shared/fixtures/w3m-wallet-fixture.ts new file mode 100644 index 000000000..ba2bd8ddb --- /dev/null +++ b/apps/native/tests/shared/fixtures/w3m-wallet-fixture.ts @@ -0,0 +1,99 @@ +/* eslint no-console: 0 */ + +import { testM as base, testMSiwe as siwe } from './w3m-fixture'; +import { WalletPage } from '../pages/WalletPage'; +import { WalletValidator } from '../validators/WalletValidator'; + +import { DEFAULT_SESSION_PARAMS } from '../constants'; +import { timeEnd, timeStart } from '../utils/logs'; + +// Declare the types of fixtures to use +interface ModalWalletFixture { + walletPage: WalletPage; + walletValidator: WalletValidator; +} + +// MW -> test Modal + Wallet +export const testConnectedMW = base.extend({ + walletPage: async ({ context, modalPage, timingRecords }, use) => { + // Setup + let pairingCreatedTime: Date | null = null; + let verificationStartedTime: Date | null = null; + + timeStart('new WalletPage'); + const walletPage = new WalletPage(await context.newPage()); + timeEnd('new WalletPage'); + + walletPage.page.on('console', msg => { + if (msg.text().includes('set') && msg.text().includes('core/pairing/pairing')) { + pairingCreatedTime = new Date(); + } + if (msg.text().includes('resolving attestation')) { + verificationStartedTime = new Date(); + } + if (msg.text().includes('session_proposal') && msg.text().includes('verifyContext')) { + // For some reason this log is emitted twice; so only recording the time once + if (verificationStartedTime) { + const verificationEndedTime = new Date(); + timingRecords.push({ + item: 'sessionProposalVerification', + timeMs: verificationEndedTime.getTime() - verificationStartedTime.getTime() + }); + verificationStartedTime = null; + } + } + }); + + timeStart('walletPage.load'); + await walletPage.load(); + timeEnd('walletPage.load'); + + // Initiate connection + timeStart('modalPage.getConnectUri'); + const uri = await modalPage.getConnectUri(timingRecords); + timeEnd('modalPage.getConnectUri'); + + timeStart('walletPage.connectWithUri'); + await walletPage.connectWithUri(uri); + timeEnd('walletPage.connectWithUri'); + + const connectionInitiated = new Date(); + + // Handle session proposal + timeStart('walletPage.handleSessionProposal'); + await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS); + timeEnd('walletPage.handleSessionProposal'); + + const proposalReceived = new Date(); + + timingRecords.push({ + item: 'receiveSessionProposal', + timeMs: proposalReceived.getTime() - connectionInitiated.getTime() + }); + + if (pairingCreatedTime) { + timingRecords.push({ + item: 'pairingReceiveSessionProposal', + timeMs: proposalReceived.getTime() - (pairingCreatedTime as Date).getTime() + }); + } + + const walletValidator = new WalletValidator(walletPage.page); + + timeStart('walletValidator.expectConnected'); + await walletValidator.expectConnected(); + timeEnd('walletValidator.expectConnected'); + + await use(walletPage); + } +}); + +export const testMWSiwe = siwe.extend({ + walletPage: async ({ context }, use) => { + const walletPage = new WalletPage(await context.newPage()); + await walletPage.load(); + await use(walletPage); + } +}); + +export { expect } from '@playwright/test'; diff --git a/apps/native/tests/shared/pages/ModalPage.ts b/apps/native/tests/shared/pages/ModalPage.ts new file mode 100644 index 000000000..a8840f21a --- /dev/null +++ b/apps/native/tests/shared/pages/ModalPage.ts @@ -0,0 +1,279 @@ +import type { Locator, Page } from '@playwright/test'; +import { expect } from '@playwright/test'; +import { BASE_URL, DEFAULT_SESSION_PARAMS } from '../constants'; +import { WalletValidator } from '../validators/WalletValidator'; +import { WalletPage } from './WalletPage'; +import { TimingRecords } from '../types'; +import { ModalValidator } from '../validators/ModalValidator'; + +export class ModalPage { + private readonly connectButton: Locator; + private readonly url: string; + + constructor(public readonly page: Page) { + this.connectButton = this.page.getByTestId('connect-button'); + this.url = BASE_URL; + } + + async load() { + await this.page.goto(this.url); + } + + assertDefined(value: T | undefined | null): T { + expect(value).toBeDefined(); + + return value!; + } + + async getConnectUri(timingRecords?: TimingRecords): Promise { + await this.connectButton.click(); + await this.openAllWallets(); + await this.openQrCodeView(); + const qrLoadInitiatedTime = new Date(); + + const qrCode = this.page.getByTestId('qr-code'); + await expect(qrCode).toBeVisible(); + const uri = await this.clickCopyLink(); + + const qrLoadedTime = new Date(); + if (timingRecords) { + timingRecords.push({ + item: 'qrLoad', + timeMs: qrLoadedTime.getTime() - qrLoadInitiatedTime.getTime() + }); + } + + return uri; + } + + async getImmidiateConnectUri(timingRecords?: TimingRecords): Promise { + await this.connectButton.click(); + const qrLoadInitiatedTime = new Date(); + + const qrCode = this.page.getByTestId('qr-code'); + await expect(qrCode).toBeVisible(); + const uri = await this.clickCopyLink(); + const qrLoadedTime = new Date(); + if (timingRecords) { + timingRecords.push({ + item: 'qrLoad', + timeMs: qrLoadedTime.getTime() - qrLoadInitiatedTime.getTime() + }); + } + + return uri; + } + + async qrCodeFlow(page: ModalPage, walletPage: WalletPage, immediate?: boolean): Promise { + let uri: string; + await walletPage.load(); + if (immediate) { + uri = await page.getImmidiateConnectUri(); + } else { + uri = await page.getConnectUri(); + } + await walletPage.connectWithUri(uri); + + await walletPage.handleSessionProposal(DEFAULT_SESSION_PARAMS); + const walletValidator = new WalletValidator(walletPage.page); + await walletValidator.expectConnected(); + } + + async searchWalletFlow(page: ModalPage, walletName: string, walletId: string) { + await this.openAllWallets(); + await this.search(walletName); + await this.page.waitForTimeout(1000); + const modalValidator = new ModalValidator(page.page); + await modalValidator.expectAllWalletsListSearchItem(walletId); + } + + async disconnect() { + const accountBtn = this.page.getByTestId('account-button'); + await expect(accountBtn, 'Account button should be visible').toBeVisible(); + await expect(accountBtn, 'Account button should be enabled').toBeEnabled(); + await accountBtn.click(); + const disconnectBtn = this.page.getByTestId('disconnect-button'); + await expect(disconnectBtn, 'Disconnect button should be visible').toBeVisible(); + await expect(disconnectBtn, 'Disconnect button should be enabled').toBeEnabled(); + await disconnectBtn.click(); + } + + async sign() { + const signButton = this.page.getByTestId('sign-message-button'); + await signButton.scrollIntoViewIfNeeded(); + await signButton.click(); + } + + async clickWhatIsAWalletButton() { + await this.page.getByTestId('help-button').click(); + } + + async clickGetAWalletButton() { + await this.page.getByTestId('get-a-wallet-button').click(); + } + + // async promptSiwe() { + // const siweSign = this.page.getByTestId('w3m-connecting-siwe-sign'); + // await expect(siweSign, 'Siwe prompt sign button should be visible').toBeVisible({ + // timeout: 10_000 + // }); + // await expect(siweSign, 'Siwe prompt sign button should be enabled').toBeEnabled(); + // await siweSign.click(); + // } + + // async cancelSiwe() { + // await this.page.getByTestId('w3m-connecting-siwe-cancel').click(); + // } + + async switchNetwork(network: string) { + await this.openAccountModal(); + await this.page.getByTestId('w3m-account-select-network').click(); + await this.page.getByTestId(`w3m-network-switch-${network}`).click(); + // The state is chaing too fast and test runner doesn't wait the loading page. It's fastly checking the network selection button and detect that it's switched already. + await this.page.waitForTimeout(300); + } + + // async clickWalletDeeplink() { + // await this.connectButton.click(); + // await this.page.getByTestId('wallet-selector-react-wallet-v2').click(); + // await this.page.getByTestId('tab-desktop').click(); + // } + + async openAccountModal() { + await this.page.getByTestId('account-button').click(); + } + + async openConnectModal() { + await this.page.getByTestId('connect-button').click(); + } + + async openNetworkModal() { + await this.page.getByTestId('network-button').click(); + } + + async closeModal() { + await this.page.getByTestId('header-close')?.click?.(); + // Wait for the modal fade out animation + await this.page.waitForTimeout(300); + } + + // async switchNetworkWithNetworkButton(networkName: string) { + // const networkButton = this.page.getByTestId('wui-network-button'); + // await networkButton.click(); + + // const networkToSwitchButton = this.page.getByTestId(`w3m-network-switch-${networkName}`); + // await networkToSwitchButton.click(); + // } + + async openAllWallets() { + const allWallets = this.page.getByTestId('all-wallets'); + await expect(allWallets, 'All wallets should be visible').toBeVisible(); + await allWallets.click(); + } + + async openQrCodeView() { + const qrCodeButton = this.page.getByTestId('button-qr-code'); + await expect(qrCodeButton, 'QR code view should be visible').toBeVisible(); + await qrCodeButton.click(); + } + + // async clickAllWalletsListSearchItem(id: string) { + // const allWalletsListSearchItem = this.page.getByTestId(`wallet-search-item-${id}`); + // await expect(allWalletsListSearchItem).toBeVisible(); + // await allWalletsListSearchItem.click(); + // } + + // async clickTabWebApp() { + // const tabWebApp = this.page.getByTestId('tab-webapp'); + // await expect(tabWebApp).toBeVisible(); + // await tabWebApp.click(); + // } + + async clickHookDisconnectButton() { + const disconnectHookButton = this.page.getByTestId('disconnect-hook-button'); + await expect(disconnectHookButton).toBeVisible(); + await disconnectHookButton.click(); + } + + async clickCopyLink() { + const copyLink = this.page.getByTestId('copy-link'); + await expect(copyLink).toBeVisible(); + + let hasCopied = false; + + while (!hasCopied) { + await copyLink.click(); + await this.page.waitForTimeout(500); + + const snackbarMessage = this.page.getByTestId('wui-snackbar-message'); + const snackbarMessageText = await snackbarMessage.textContent(); + + if (snackbarMessageText && snackbarMessageText.startsWith('Link copied')) { + hasCopied = true; + } + } + + return this.page.evaluate(() => navigator.clipboard.readText()); + } + + // async clickOpenWebApp() { + // let url = ''; + + // const openButton = this.page.getByTestId('w3m-connecting-widget-secondary-button'); + // await expect(openButton).toBeVisible(); + // await expect(openButton).toHaveText('Open'); + + // while (!url) { + // await openButton.click(); + // await this.page.waitForTimeout(500); + + // const pages = this.page.context().pages(); + + // // Check if more than 1 tab is open + // if (pages.length > 1) { + // const lastTab = pages[pages.length - 1]; + + // if (lastTab) { + // url = lastTab.url(); + // break; + // } + // } + // } + + // return url; + // } + + async search(value: string) { + const searchInput = this.page.getByTestId('wui-input-text'); + await expect(searchInput, 'Search input should be visible').toBeVisible(); + await searchInput.click(); + await searchInput.fill(value); + } + + async openNetworks() { + await this.page.getByTestId('w3m-account-select-network').click(); + await expect(this.page.getByText('Select network')).toBeVisible(); + } + + // async openProfileView() { + // await this.page.getByTestId('wui-profile-button').click(); + // } + + // async getAddress(): Promise<`0x${string}`> { + // const address = await this.page.getByTestId('w3m-address').textContent(); + // expect(address, 'Address should be present').toBeTruthy(); + + // return address as `0x${string}`; + // } + + // async getChainId(): Promise { + // const chainId = await this.page.getByTestId('w3m-chain-id').textContent(); + // expect(chainId, 'Chain ID should be present').toBeTruthy(); + + // return Number(chainId); + // } + + // async switchNetworkWithHook() { + // await this.page.getByTestId('switch-network-hook-button').click(); + // } +} diff --git a/apps/native/tests/shared/pages/WalletPage.ts b/apps/native/tests/shared/pages/WalletPage.ts new file mode 100644 index 000000000..49f5a2038 --- /dev/null +++ b/apps/native/tests/shared/pages/WalletPage.ts @@ -0,0 +1,125 @@ +import { expect, type Locator, type Page } from '@playwright/test'; +import { WALLET_URL } from '../constants'; +import type { SessionParams } from '../types'; + +export class WalletPage { + private readonly baseURL = WALLET_URL; + + private gotoHome: Locator; + private vercelPreview: Locator; + + public connectToSingleAccount = false; + + constructor(public page: Page) { + this.gotoHome = this.page.getByTestId('wc-connect'); + this.vercelPreview = this.page.locator('css=vercel-live-feedback'); + } + + async load() { + await this.page.goto(this.baseURL); + } + + loadNewPage(page: Page) { + this.page = page; + this.gotoHome = this.page.getByTestId('wc-connect'); + this.vercelPreview = this.page.locator('css=vercel-live-feedback'); + } + + /** + * Connect by inserting provided URI into the input element + */ + + async connectWithUri(uri: string) { + const isVercelPreview = (await this.vercelPreview.count()) > 0; + if (isVercelPreview) { + await this.vercelPreview.evaluate((iframe: HTMLIFrameElement) => iframe.remove()); + } + /* + * If connecting to a single account manually navigate. + * Otherwise click the home button. + */ + if (this.connectToSingleAccount) { + await this.page.goto(`${this.baseURL}/walletconnect?addressesToApprove=1`); + } else { + await this.gotoHome.click(); + } + const input = this.page.getByTestId('uri-input'); + await input.waitFor({ + state: 'visible', + timeout: 5000 + }); + await input.fill(uri); + const connectButton = this.page.getByTestId('uri-connect-button'); + await expect(connectButton, 'Connect button should be enabled').toBeEnabled({ + timeout: 5000 + }); + await connectButton.click(); + } + + /** + * Handle a session proposal event in the wallet + * @param reqAccounts - required account numbers to select ex/ ['1', '2'] + * @param optAccounts - optional account numbers to select ex/ ['1', '2'] + * @param accept - accept or reject the session + */ + async handleSessionProposal(opts: SessionParams) { + const variant = opts.accept ? `approve` : `reject`; + // `.click` doesn't work here, so we use `.focus` and `Space` + await this.performRequestAction(variant); + } + + async handleRequest({ accept }: { accept: boolean }) { + const variant = accept ? `approve` : `reject`; + // `.click` doesn't work here, so we use `.focus` and `Space` + await this.performRequestAction(variant); + } + + async performRequestAction(variant: string) { + await this.page.waitForLoadState(); + const btn = this.page.getByTestId(`session-${variant}-button`); + await expect(btn, `Session ${variant} element should be visible`).toBeVisible({ + timeout: 30000 + }); + await expect(btn).toBeEnabled(); + await btn.focus(); + await this.page.keyboard.press('Space'); + } + + /** + * Enables testnets in the wallet settings + */ + async enableTestnets() { + await this.page.waitForLoadState(); + const settingsButton = this.page.getByTestId('settings'); + await settingsButton.click(); + const testnetSwitch = this.page.getByTestId('settings-toggle-testnets'); + await testnetSwitch.click(); + expect(testnetSwitch).toHaveAttribute('data-state', 'checked'); + } + + /** + * Switches the network in the wallet + * @param network the network id to switch (e.g. eip155:1 for Ethereum Mainnet) + */ + async switchNetwork(network: string) { + await this.page.waitForLoadState(); + const networkButton = this.page.getByTestId('accounts'); + await networkButton.click(); + const switchNetworkButton = this.page.getByTestId(`chain-switch-button${network}`); + await switchNetworkButton.click(); + await expect(switchNetworkButton).toHaveText('✅'); + } + + /** + * Disconnects the current connection in the wallet + */ + async disconnectConnection() { + await this.page.waitForLoadState(); + const sessionsButton = this.page.getByTestId('sessions'); + await sessionsButton.click(); + const sessionCard = this.page.getByTestId(`session-card`); + await sessionCard.click(); + const disconnectButton = this.page.getByText('Delete'); + await disconnectButton.click(); + } +} diff --git a/apps/native/tests/shared/types/index.ts b/apps/native/tests/shared/types/index.ts new file mode 100644 index 000000000..abf6bc54f --- /dev/null +++ b/apps/native/tests/shared/types/index.ts @@ -0,0 +1,9 @@ +export interface SessionParams { + reqAccounts: string[]; + optAccounts: string[]; + accept: boolean; +} + +export type TimingRecords = { item: string; timeMs: number }[]; + +export type CaipNetworkId = `${string}:${string}`; diff --git a/apps/native/tests/shared/utils/logs.ts b/apps/native/tests/shared/utils/logs.ts new file mode 100644 index 000000000..7bb561477 --- /dev/null +++ b/apps/native/tests/shared/utils/logs.ts @@ -0,0 +1,17 @@ +/* eslint no-console: 0 */ + +const TIMING_LOGS_ENABLED = process.env.TIMING_LOGS === 'true' || false; + +export function timeStart(label?: string | undefined) { + if (!TIMING_LOGS_ENABLED) { + return; + } + console.time(label); +} + +export function timeEnd(label?: string | undefined) { + if (!TIMING_LOGS_ENABLED) { + return; + } + console.timeEnd(label); +} diff --git a/apps/native/tests/shared/utils/timeouts.ts b/apps/native/tests/shared/utils/timeouts.ts new file mode 100644 index 000000000..a74e5eee4 --- /dev/null +++ b/apps/native/tests/shared/utils/timeouts.ts @@ -0,0 +1,9 @@ +import { MAXIMUM_WAIT_CONNECTIONS } from '../constants/timeouts'; + +export function getMaximumWaitConnections(): number { + if (process.env.CI) { + return MAXIMUM_WAIT_CONNECTIONS; + } + + return MAXIMUM_WAIT_CONNECTIONS * 2; +} diff --git a/apps/native/tests/shared/validators/ModalValidator.ts b/apps/native/tests/shared/validators/ModalValidator.ts new file mode 100644 index 000000000..d27de8fbe --- /dev/null +++ b/apps/native/tests/shared/validators/ModalValidator.ts @@ -0,0 +1,165 @@ +import { expect } from '@playwright/test'; +import type { Page } from '@playwright/test'; +import { getMaximumWaitConnections } from '../utils/timeouts'; + +const MAX_WAIT = getMaximumWaitConnections(); + +export class ModalValidator { + constructor(public readonly page: Page) {} + + async expectConnected() { + const accountButton = this.page.getByTestId('account-button'); + await expect(accountButton, 'Account button should be present').toBeAttached({ + timeout: MAX_WAIT + }); + await expect( + this.page.getByTestId('connect-button'), + 'Connect button should not be present' + ).toBeHidden({ + timeout: MAX_WAIT + }); + await this.page.waitForTimeout(500); + } + + async expectBalanceFetched(currency: 'SOL' | 'ETH') { + const accountButton = this.page.getByTestId('account-button'); + await expect(accountButton, `Account button should show balance as ${currency}`).toContainText( + `0.000 ${currency}` + ); + } + + async expectAuthenticated() { + await expect( + this.page.getByTestId('w3m-authentication-status'), + 'Authentication status should be: authenticated' + ).toContainText('authenticated'); + } + + async expectOnSignInEventCalled(toBe: boolean) { + await expect(this.page.getByTestId('siwe-event-onSignIn')).toContainText(`${toBe}`); + } + + async expectUnauthenticated() { + await expect( + this.page.getByTestId('w3m-authentication-status'), + 'Authentication status should be: unauthenticated' + ).toContainText('unauthenticated'); + } + + async expectOnSignOutEventCalled(toBe: boolean) { + await expect(this.page.getByTestId('siwe-event-onSignOut')).toContainText(`${toBe}`); + } + + async expectSignatureDeclined() { + await expect( + this.page.getByText('Signature declined'), + 'Signature declined should be visible' + ).toBeVisible(); + } + + async expectDisconnected() { + await expect( + this.page.getByTestId('connect-button'), + 'Connect button should be present' + ).toBeVisible({ + timeout: MAX_WAIT + }); + } + + async expectConnectScreen() { + await expect(this.page.getByText('Connect wallet')).toBeVisible({ + timeout: MAX_WAIT + }); + } + + async expectNetworkScreen() { + await expect(this.page.getByTestId('what-is-a-network-button')).toBeVisible(); + } + + async expectAddress(expectedAddress: string) { + const address = this.page.getByTestId('w3m-address'); + + await expect(address, 'Correct address should be present').toHaveText(expectedAddress); + } + + // async expectNetwork(network: string) { + // const networkButton = this.page.getByTestId('w3m-account-select-network'); + // await expect(networkButton, `Network button should contain text ${network}`).toHaveText( + // network, + // { + // timeout: 5000 + // } + // ); + // } + + async expectAcceptedSign() { + await expect(this.page.getByText('Signature successful')).toBeVisible({ + timeout: 30 * 1000 + }); + } + + async expectRejectedSign() { + await expect(this.page.getByText('Signature failed')).toBeVisible(); + } + + async expectSwitchedNetwork(network: string) { + const switchNetworkButton = this.page.getByTestId(`w3m-account-select-network-text`); + await expect(switchNetworkButton).toContainText(network); + } + + async expectAllWalletsListSearchItem(id: string) { + const allWalletsListSearchItem = this.page.getByTestId(`wallet-search-item-${id}`); + await expect(allWalletsListSearchItem).toBeVisible(); + } + + async expectAllWallets() { + const allWallets = this.page.getByTestId('all-wallets'); + await expect(allWallets).toBeVisible(); + } + + async expectWhatIsAWalletButton() { + const whatIsAWalletButton = this.page.getByTestId('help-button'); + await expect(whatIsAWalletButton).toBeVisible(); + } + + async expectWhatIsAWalletView() { + const whatIsAWalletView = this.page.getByTestId('what-is-a-wallet-view'); + await expect(whatIsAWalletView).toBeVisible(); + } + + async expectGetAWalletView() { + const getAWalletView = this.page.getByTestId('get-a-wallet-view'); + await expect(getAWalletView).toBeVisible(); + } + + // async expectHeaderText(text: string) { + // const headerText = this.page.getByTestId('header-text'); + // await expect(headerText).toHaveText(text); + // } + + async expectNetworksDisabled(name: string) { + const disabledNetworkButton = this.page.getByTestId(`w3m-network-switch-${name}`); + disabledNetworkButton.click(); + await expect(this.page.getByText('Select network')).toBeVisible(); + } + + async expectToBeConnectedInstantly() { + const accountButton = this.page.getByTestId('account-button'); + await expect(accountButton, 'Account button should be present').toBeAttached({ + timeout: 1000 + }); + } + + async expectModalNotVisible() { + const modal = this.page.getByTestId('w3m-modal'); + await expect(modal).toBeHidden({ + timeout: 2000 + }); + } + + // async expectSnackbar(message: string) { + // await expect(this.page.getByTestId('wui-snackbar-message')).toHaveText(message, { + // timeout: MAX_WAIT + // }); + // } +} diff --git a/apps/native/tests/shared/validators/WalletValidator.ts b/apps/native/tests/shared/validators/WalletValidator.ts new file mode 100644 index 000000000..c6e292e58 --- /dev/null +++ b/apps/native/tests/shared/validators/WalletValidator.ts @@ -0,0 +1,68 @@ +import { expect } from '@playwright/test'; +import type { Locator, Page } from '@playwright/test'; +import { getMaximumWaitConnections } from '../utils/timeouts'; + +const MAX_WAIT = getMaximumWaitConnections(); + +export class WalletValidator { + private gotoSessions: Locator; + + constructor(public page: Page) { + this.gotoSessions = this.page.getByTestId('sessions'); + } + + loadNewPage(page: Page) { + this.page = page; + this.gotoSessions = this.page.getByTestId('sessions'); + } + + async expectConnected() { + await expect( + this.gotoSessions, + 'Approve screen should be closed and sessions tab visible' + ).toBeVisible(); + await this.gotoSessions.click(); + await this.expectSessionCard({ visible: true }); + } + + async expectSessionCard({ visible = true }: { visible?: boolean }) { + if (visible) { + await expect( + this.page.getByTestId('session-card'), + 'Session card should be visible' + ).toBeVisible({ + timeout: MAX_WAIT + }); + } else { + await expect( + this.page.getByTestId('session-card'), + 'Session card should not be visible' + ).not.toBeVisible({ + timeout: MAX_WAIT + }); + } + } + + async expectDisconnected() { + await this.gotoSessions.click(); + await expect( + this.page.getByTestId('session-card'), + 'Session card should not be visible' + ).not.toBeVisible({ + timeout: MAX_WAIT + }); + } + + async expectReceivedSign({ chainName = 'Ethereum' }) { + await expect( + this.page.getByTestId('session-approve-button'), + 'Session approve button should be visible' + ).toBeVisible({ + timeout: MAX_WAIT + }); + await expect( + this.page.getByTestId('request-details-chain'), + 'Request details should contain chain name' + ).toContainText(chainName); + } +} diff --git a/apps/native/tests/wallet.spec.ts b/apps/native/tests/wallet.spec.ts new file mode 100644 index 000000000..6f5eb68ca --- /dev/null +++ b/apps/native/tests/wallet.spec.ts @@ -0,0 +1,126 @@ +import { test, type BrowserContext } from '@playwright/test'; +import { WalletPage } from './shared/pages/WalletPage'; +import { WalletValidator } from './shared/validators/WalletValidator'; +import { ModalPage } from './shared/pages/ModalPage'; +import { ModalValidator } from './shared/validators/ModalValidator'; +import { DEFAULT_CHAIN_NAME } from './shared/constants'; + +let modalPage: ModalPage; +let modalValidator: ModalValidator; +let walletPage: WalletPage; +let walletValidator: WalletValidator; +let context: BrowserContext; + +// -- Setup -------------------------------------------------------------------- +const sampleWalletTest = test.extend<{ library: string }>({ + library: ['wagmi', { option: true }] +}); + +sampleWalletTest.describe.configure({ mode: 'serial' }); + +sampleWalletTest.beforeAll(async ({ browser }) => { + context = await browser.newContext(); + const browserPage = await context.newPage(); + + modalPage = new ModalPage(browserPage); + walletPage = new WalletPage(await context.newPage()); + modalValidator = new ModalValidator(browserPage); + walletValidator = new WalletValidator(walletPage.page); + + await modalPage.load(); + await modalPage.qrCodeFlow(modalPage, walletPage); + await modalValidator.expectConnected(); +}); + +sampleWalletTest.afterAll(async () => { + await modalPage.page.close(); +}); + +// -- Tests -------------------------------------------------------------------- +sampleWalletTest('it should be connected instantly after page refresh', async () => { + await modalPage.page.reload(); + await modalValidator.expectToBeConnectedInstantly(); +}); + +sampleWalletTest('it should show disabled networks', async () => { + const disabledNetworks = 'Gnosis'; + await modalPage.openAccountModal(); + await modalPage.openNetworks(); + await modalValidator.expectNetworksDisabled(disabledNetworks); + await modalPage.closeModal(); +}); + +sampleWalletTest('it should switch networks and sign', async () => { + const chains = ['Polygon', 'Ethereum']; + + async function processChain(index: number) { + if (index >= chains.length) { + return; + } + + const chainName = chains[index] ?? DEFAULT_CHAIN_NAME; + + // -- Switch network -------------------------------------------------------- + const chainNameOnWalletPage = chainName; + await modalPage.switchNetwork(chainName); + await modalValidator.expectSwitchedNetwork(chainName); + await modalPage.closeModal(); + + // -- Sign ------------------------------------------------------------------ + await modalPage.sign(); + await walletValidator.expectReceivedSign({ chainName: chainNameOnWalletPage }); + await walletPage.handleRequest({ accept: true }); + await modalValidator.expectAcceptedSign(); + + await processChain(index + 1); + } + + // Start processing from the first chain + await processChain(0); +}); + +sampleWalletTest('it should show last connected network after refreshing', async () => { + const chainName = 'Polygon'; + + await modalPage.switchNetwork(chainName); + await modalValidator.expectSwitchedNetwork(chainName); + await modalPage.closeModal(); + + await modalPage.page.reload(); + + await modalPage.openAccountModal(); + await modalValidator.expectSwitchedNetwork(chainName); + await modalPage.closeModal(); +}); + +sampleWalletTest('it should reject sign', async () => { + const chainName = 'Polygon'; + await modalPage.sign(); + await walletValidator.expectReceivedSign({ chainName }); + await walletPage.handleRequest({ accept: false }); + await modalValidator.expectRejectedSign(); +}); + +sampleWalletTest('it should disconnect using hook', async () => { + await modalValidator.expectConnected(); + await modalPage.clickHookDisconnectButton(); + await modalValidator.expectDisconnected(); +}); + +sampleWalletTest('it should disconnect and close modal when connecting from wallet', async () => { + await modalValidator.expectDisconnected(); + await modalPage.qrCodeFlow(modalPage, walletPage); + await modalValidator.expectConnected(); + await modalPage.openAccountModal(); + await walletPage.disconnectConnection(); + await walletValidator.expectSessionCard({ visible: false }); + await modalValidator.expectModalNotVisible(); + await walletPage.page.waitForTimeout(500); +}); + +sampleWalletTest('it should disconnect as expected', async () => { + await modalPage.qrCodeFlow(modalPage, walletPage); + await modalValidator.expectConnected(); + await modalPage.disconnect(); + await modalValidator.expectDisconnected(); +}); diff --git a/packages/core/src/__tests__/controllers/SnackController.test.ts b/packages/core/src/__tests__/controllers/SnackController.test.ts index 6a9aad79c..ec10a043b 100644 --- a/packages/core/src/__tests__/controllers/SnackController.test.ts +++ b/packages/core/src/__tests__/controllers/SnackController.test.ts @@ -3,6 +3,9 @@ import { OptionsController, SnackController } from '../../index'; // Setup OptionsController.state.debug = true; +// eslint-disable-next-line no-console +console.error = jest.fn(); + // -- Tests -------------------------------------------------------------------- describe('SnackController', () => { it('should have valid default state', () => { diff --git a/packages/ethers/src/client.ts b/packages/ethers/src/client.ts index 79c0370f4..0e564e183 100644 --- a/packages/ethers/src/client.ts +++ b/packages/ethers/src/client.ts @@ -703,6 +703,7 @@ export class AppKit extends AppKitScaffold { ]); this.hasSyncedConnectedAccount = true; } else if (!isConnected && this.hasSyncedConnectedAccount) { + this.close(); this.resetAccount(); this.resetWcConnection(); this.resetNetwork(); diff --git a/packages/ethers5/src/client.ts b/packages/ethers5/src/client.ts index 0f48b56e1..23f42933c 100644 --- a/packages/ethers5/src/client.ts +++ b/packages/ethers5/src/client.ts @@ -683,6 +683,7 @@ export class AppKit extends AppKitScaffold { ]); this.hasSyncedConnectedAccount = true; } else if (!isConnected && this.hasSyncedConnectedAccount) { + this.close(); this.resetAccount(); this.resetWcConnection(); this.resetNetwork(); diff --git a/packages/scaffold/src/hooks/useTimeout.ts b/packages/scaffold/src/hooks/useTimeout.ts index e99448a0c..90e027955 100644 --- a/packages/scaffold/src/hooks/useTimeout.ts +++ b/packages/scaffold/src/hooks/useTimeout.ts @@ -13,13 +13,19 @@ function useTimeout(delay: number) { timeLeftRef.current -= 1; setTimeLeft(timeLeftRef.current); } else { - clearInterval(interval.current); + if (typeof interval.current === 'number') { + clearInterval(interval.current); + } } }, 1000); }, []); useEffect(() => { - return () => clearInterval(interval.current); + return () => { + if (typeof interval.current === 'number') { + clearInterval(interval.current); + } + }; }, [interval]); return { timeLeft, startTimer }; diff --git a/packages/scaffold/src/modal/w3m-button/index.tsx b/packages/scaffold/src/modal/w3m-button/index.tsx index 830655db2..e6bf0481d 100644 --- a/packages/scaffold/src/modal/w3m-button/index.tsx +++ b/packages/scaffold/src/modal/w3m-button/index.tsx @@ -30,7 +30,7 @@ export function AppKitButton({ style={accountStyle} balance={balance} disabled={disabled} - testID="button-account" + testID="account-button" /> ) : ( ); } diff --git a/packages/scaffold/src/modal/w3m-modal/index.tsx b/packages/scaffold/src/modal/w3m-modal/index.tsx index d4a9c4cae..036d6a576 100644 --- a/packages/scaffold/src/modal/w3m-modal/index.tsx +++ b/packages/scaffold/src/modal/w3m-modal/index.tsx @@ -128,6 +128,7 @@ export function AppKit() { onModalHide={handleClose} onBackdropPress={ModalController.close} onBackButtonPress={onBackButtonPress} + testID="w3m-modal" >
diff --git a/packages/scaffold/src/modal/w3m-network-button/index.tsx b/packages/scaffold/src/modal/w3m-network-button/index.tsx index 89f25b6bb..ddffa20f2 100644 --- a/packages/scaffold/src/modal/w3m-network-button/index.tsx +++ b/packages/scaffold/src/modal/w3m-network-button/index.tsx @@ -36,6 +36,7 @@ export function NetworkButton({ disabled, style }: NetworkButtonProps) { style={style} onPress={onNetworkPress} loading={loading} + testID="network-button" > {caipNetwork?.name ?? (isConnected ? 'Unknown Network' : 'Select Network')} diff --git a/packages/scaffold/src/partials/w3m-all-wallets-search/index.tsx b/packages/scaffold/src/partials/w3m-all-wallets-search/index.tsx index 2e08ba2e0..172f3b09c 100644 --- a/packages/scaffold/src/partials/w3m-all-wallets-search/index.tsx +++ b/packages/scaffold/src/partials/w3m-all-wallets-search/index.tsx @@ -49,6 +49,7 @@ export function AllWalletsSearch({ name={item?.name ?? 'Unknown'} onPress={() => onItemPress(item)} installed={!!isInstalled} + testID={`wallet-search-item-${item?.id}`} /> ); diff --git a/packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx b/packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx index e887b7b1b..3a035706b 100644 --- a/packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx +++ b/packages/scaffold/src/partials/w3m-connecting-qrcode/index.tsx @@ -65,7 +65,7 @@ export function ConnectingQrCode() { color="fg-200" style={styles.copyButton} onPress={onCopyAddress} - testID="button-copy-uri" + testID="copy-link" > Copy link diff --git a/packages/scaffold/src/partials/w3m-header/index.tsx b/packages/scaffold/src/partials/w3m-header/index.tsx index 6f280bd72..846272c83 100644 --- a/packages/scaffold/src/partials/w3m-header/index.tsx +++ b/packages/scaffold/src/partials/w3m-header/index.tsx @@ -107,7 +107,7 @@ export function Header() { return showBack ? ( ) : ( - + ); }; @@ -123,10 +123,10 @@ export function Header() { padding={['l', 'xl', bottomPadding, 'xl']} > {dynamicButtonTemplate()} - + {header} - + ); } diff --git a/packages/scaffold/src/views/w3m-account-default-view/index.tsx b/packages/scaffold/src/views/w3m-account-default-view/index.tsx index 8988f3ed8..22b90082b 100644 --- a/packages/scaffold/src/views/w3m-account-default-view/index.tsx +++ b/packages/scaffold/src/views/w3m-account-default-view/index.tsx @@ -158,7 +158,12 @@ export function AccountDefaultView() { {showBack && ( )} - + @@ -227,10 +232,10 @@ export function AccountDefaultView() { imageSrc={networkImage} imageHeaders={ApiController._getApiHeaders()} onPress={onNetworkPress} - testID="button-network" + testID="w3m-account-select-network" style={styles.actionButton} > - + {caipNetwork?.name} @@ -252,7 +257,7 @@ export function AccountDefaultView() { chevron icon="swapHorizontal" onPress={onSwitchAccountType} - testID="button-account-type" + testID="account-button-type" style={styles.actionButton} loading={loading} > @@ -266,7 +271,7 @@ export function AccountDefaultView() { onPress={onDisconnect} loading={disconnecting} iconBackgroundColor="gray-glass-010" - testID="button-disconnect" + testID="disconnect-button" > Disconnect diff --git a/packages/scaffold/src/views/w3m-all-wallets-view/index.tsx b/packages/scaffold/src/views/w3m-all-wallets-view/index.tsx index 287c7bd95..bd8a76245 100644 --- a/packages/scaffold/src/views/w3m-all-wallets-view/index.tsx +++ b/packages/scaffold/src/views/w3m-all-wallets-view/index.tsx @@ -62,7 +62,7 @@ export function AllWalletsView() { { backgroundColor: Theme['bg-100'], shadowColor: Theme['bg-100'], width: maxWidth } ]} > - + ); } diff --git a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx index ce726591f..fd7cb308d 100644 --- a/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx +++ b/packages/scaffold/src/views/w3m-connecting-farcaster-view/index.tsx @@ -129,7 +129,7 @@ export function ConnectingFarcasterView() { color="fg-200" style={styles.copyButton} onPress={onCopyUrl} - testID="button-copy-uri" + testID="copy-link" > Copy link diff --git a/packages/scaffold/src/views/w3m-get-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-get-wallet-view/index.tsx index 0295b6e78..3cc7478a1 100644 --- a/packages/scaffold/src/views/w3m-get-wallet-view/index.tsx +++ b/packages/scaffold/src/views/w3m-get-wallet-view/index.tsx @@ -30,7 +30,12 @@ export function GetWalletView() { }; return ( - + {listTemplate()} Your connected wallet may not support some of the networks available for this dApp - + What is a network? diff --git a/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx b/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx index fc6e96913..ce042b10f 100644 --- a/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx +++ b/packages/scaffold/src/views/w3m-upgrade-to-smart-account-view/index.tsx @@ -67,7 +67,7 @@ export function UpgradeToSmartAccountView() { icon="close" size="md" onPress={onClose} - testID="button-close" + testID="header-close" style={styles.closeButton} /> diff --git a/packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx b/packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx index 953af32ce..17cdcc556 100644 --- a/packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx +++ b/packages/scaffold/src/views/w3m-what-is-a-wallet-view/index.tsx @@ -13,7 +13,12 @@ export function WhatIsAWalletView() { }; return ( - + @@ -53,6 +58,7 @@ export function WhatIsAWalletView() { iconLeft="walletSmall" style={styles.getWalletButton} onPress={onGetWalletPress} + testID="get-a-wallet-button" > Get a wallet diff --git a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx index db9825873..e5a56f950 100644 --- a/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx +++ b/packages/siwe/src/scaffold/views/w3m-connecting-siwe-view/index.tsx @@ -90,7 +90,7 @@ export function ConnectingSiweView() { icon="close" size="md" onPress={onCancel} - testID="button-close" + testID="header-close" style={styles.closeButton} /> diff --git a/packages/ui/src/composites/wui-card-select/index.tsx b/packages/ui/src/composites/wui-card-select/index.tsx index 3c9a2c8e8..fd4f50458 100644 --- a/packages/ui/src/composites/wui-card-select/index.tsx +++ b/packages/ui/src/composites/wui-card-select/index.tsx @@ -25,6 +25,7 @@ export interface CardSelectProps { type?: CardSelectType; onPress?: () => void; style?: StyleProp; + testID?: string; } function _CardSelect({ @@ -36,7 +37,8 @@ function _CardSelect({ disabled, installed, selected, - style + style, + testID }: CardSelectProps) { const Theme = useTheme(); const normalbackgroundColor = getBackgroundColor({ selected, disabled, pressed: false }); @@ -77,6 +79,7 @@ function _CardSelect({ onPressOut={setStartValue} disabled={disabled} style={[styles.container, { backgroundColor: animatedValue }, style]} + testID={testID} > ( style={[styles.outerBorder, { borderColor: outerBorder, borderRadius: outerRadius }]} disabled={disabled} onPress={() => inputRef.current?.focus()} - testID={rest.testID} > ( underlineColorAndroid="transparent" selectTextOnFocus={false} editable={!disabled} + testID="wui-input-text" {...rest} /> {children} diff --git a/packages/ui/src/composites/wui-network-button/index.tsx b/packages/ui/src/composites/wui-network-button/index.tsx index b5c37a01a..53a6a1e5b 100644 --- a/packages/ui/src/composites/wui-network-button/index.tsx +++ b/packages/ui/src/composites/wui-network-button/index.tsx @@ -19,6 +19,7 @@ export interface NetworkButtonProps { imageHeaders?: Record; loading?: boolean; style?: StyleProp; + testID?: string; } export function NetworkButton({ @@ -29,7 +30,8 @@ export function NetworkButton({ imageSrc, imageHeaders, loading, - style + style, + testID }: NetworkButtonProps) { const Theme = useTheme(); const textColor = disabled ? 'fg-300' : 'fg-100'; @@ -49,6 +51,7 @@ export function NetworkButton({ onPressIn={setEndValue} onPressOut={setStartValue} disabled={disabled} + testID={testID} > (null); @@ -38,7 +36,6 @@ export function SearchBar({ inputStyle={inputStyle} returnKeyType="search" disableFullscreenUI - testID={testID} > {showClear && ( - + {message} diff --git a/packages/wagmi/src/client.ts b/packages/wagmi/src/client.ts index eb5d42c79..0fde4faed 100644 --- a/packages/wagmi/src/client.ts +++ b/packages/wagmi/src/client.ts @@ -419,6 +419,7 @@ export class AppKit extends AppKitScaffold { ]); this.hasSyncedConnectedAccount = true; } else if (!isConnected && this.hasSyncedConnectedAccount) { + this.close(); this.resetAccount(); this.resetWcConnection(); this.resetNetwork(); diff --git a/packages/wallet/src/AppKitWebview.tsx b/packages/wallet/src/AppKitWebview.tsx index bbbd648c0..6682daab3 100644 --- a/packages/wallet/src/AppKitWebview.tsx +++ b/packages/wallet/src/AppKitWebview.tsx @@ -84,7 +84,7 @@ export function AppKitWebview() { icon="close" size="md" onPress={onClose} - testID="button-close" + testID="header-close" style={styles.closeButton} iconColor="inverse-100" backgroundColor="gray-glass-030" diff --git a/yarn.lock b/yarn.lock index a619a74b6..6cb7d561f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -96,6 +96,7 @@ __metadata: dependencies: "@babel/core": "npm:^7.24.0" "@expo/metro-runtime": "npm:~3.2.3" + "@playwright/test": "npm:^1.49.1" "@react-native-async-storage/async-storage": "npm:1.23.1" "@react-native-community/netinfo": "npm:11.3.1" "@reown/appkit-auth-wagmi-react-native": "npm:1.0.2" @@ -103,6 +104,8 @@ __metadata: "@tanstack/query-async-storage-persister": "npm:^5.40.0" "@tanstack/react-query": "npm:5.56.2" "@tanstack/react-query-persist-client": "npm:5.56.2" + "@types/gh-pages": "npm:^6" + "@types/node": "npm:^22.10.1" "@types/react": "npm:~18.2.79" "@types/react-native": "npm:0.72.2" "@walletconnect/react-native-compat": "npm:2.17.1" @@ -112,12 +115,14 @@ __metadata: expo-clipboard: "npm:~6.0.3" expo-status-bar: "npm:~1.12.1" expo-updates: "npm:~0.25.21" + gh-pages: "npm:^6.2.0" react: "npm:18.2.0" react-dom: "npm:18.2.0" react-native: "npm:0.74.3" react-native-get-random-values: "npm:~1.11.0" react-native-modal: "npm:13.0.1" react-native-svg: "npm:15.2.0" + react-native-toast-message: "npm:2.2.1" react-native-web: "npm:~0.19.10" react-native-webview: "npm:13.8.6" typescript: "npm:~5.3.3" @@ -5826,6 +5831,17 @@ __metadata: languageName: node linkType: hard +"@playwright/test@npm:^1.49.1": + version: 1.49.1 + resolution: "@playwright/test@npm:1.49.1" + dependencies: + playwright: "npm:1.49.1" + bin: + playwright: cli.js + checksum: 2fca0bb7b334f7a23c7c5dfa5dbe37b47794c56f39b747c8d74a2f95c339e7902a296f2f1dd32c47bdd723cfa92cee05219f1a5876725dc89a1871b9137a286d + languageName: node + linkType: hard + "@react-native-async-storage/async-storage@npm:1.23.1": version: 1.23.1 resolution: "@react-native-async-storage/async-storage@npm:1.23.1" @@ -8083,6 +8099,13 @@ __metadata: languageName: node linkType: hard +"@types/gh-pages@npm:^6": + version: 6.1.0 + resolution: "@types/gh-pages@npm:6.1.0" + checksum: d8bf644822df211accac9cff24fcc0a5155fd715d05bc1698175623f5cde1aff81c302e7e38f7105e0fa0fe7ab24d7009d8dbb875897af669f48e06c3c20484c + languageName: node + linkType: hard + "@types/graceful-fs@npm:^4.1.3": version: 4.1.6 resolution: "@types/graceful-fs@npm:4.1.6" @@ -8240,6 +8263,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:^22.10.1": + version: 22.10.1 + resolution: "@types/node@npm:22.10.1" + dependencies: + undici-types: "npm:~6.20.0" + checksum: 0fbb6d29fa35d807f0223a4db709c598ac08d66820240a2cd6a8a69b8f0bc921d65b339d850a666b43b4e779f967e6ed6cf6f0fca3575e08241e6b900364c234 + languageName: node + linkType: hard + "@types/parse-json@npm:^4.0.0": version: 4.0.0 resolution: "@types/parse-json@npm:4.0.0" @@ -10011,6 +10043,13 @@ __metadata: languageName: node linkType: hard +"async@npm:^3.2.4": + version: 3.2.6 + resolution: "async@npm:3.2.6" + checksum: 36484bb15ceddf07078688d95e27076379cc2f87b10c03b6dd8a83e89475a3c8df5848859dd06a4c95af1e4c16fc973de0171a77f18ea00be899aca2a4f85e70 + languageName: node + linkType: hard + "asynckit@npm:^0.4.0": version: 0.4.0 resolution: "asynckit@npm:0.4.0" @@ -11255,6 +11294,13 @@ __metadata: languageName: node linkType: hard +"commander@npm:^11.0.0": + version: 11.1.0 + resolution: "commander@npm:11.1.0" + checksum: 13cc6ac875e48780250f723fb81c1c1178d35c5decb1abb1b628b3177af08a8554e76b2c0f29de72d69eef7c864d12613272a71fabef8047922bc622ab75a179 + languageName: node + linkType: hard + "commander@npm:^2.20.0": version: 2.20.3 resolution: "commander@npm:2.20.3" @@ -12284,6 +12330,13 @@ __metadata: languageName: node linkType: hard +"email-addresses@npm:^5.0.0": + version: 5.0.0 + resolution: "email-addresses@npm:5.0.0" + checksum: fc8a6f84e378bbe601ce39a3d8d86bc7e4584030ae9eb1938e12943f7fb5207e5fd7ae449cced3bea70968a519ade560d55ca170208c3f1413d7d25d8613a577 + languageName: node + linkType: hard + "emittery@npm:^0.13.1": version: 0.13.1 resolution: "emittery@npm:0.13.1" @@ -12704,7 +12757,7 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:^1.0.5": +"escape-string-regexp@npm:^1.0.2, escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" checksum: a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371 @@ -13753,6 +13806,24 @@ __metadata: languageName: node linkType: hard +"filename-reserved-regex@npm:^2.0.0": + version: 2.0.0 + resolution: "filename-reserved-regex@npm:2.0.0" + checksum: 453740b7f9fd126e508da555b37e38c1f7ff19f5e9f3d297b2de1beb09854957baddd74c83235e87b16e9ce27a2368798896669edad5a81b5b7bd8cb57c942fc + languageName: node + linkType: hard + +"filenamify@npm:^4.3.0": + version: 4.3.0 + resolution: "filenamify@npm:4.3.0" + dependencies: + filename-reserved-regex: "npm:^2.0.0" + strip-outer: "npm:^1.0.1" + trim-repeated: "npm:^1.0.0" + checksum: dcfd2f116d66f78c9dd58bb0f0d9b6529d89c801a9f37a4f86e7adc0acecb6881c7fb7c3231dc9e6754b767edcfdca89cba3a492a58afd2b48479b30d14ccf8f + languageName: node + linkType: hard + "filesize@npm:^10.0.12": version: 10.1.6 resolution: "filesize@npm:10.1.6" @@ -14059,6 +14130,17 @@ __metadata: languageName: node linkType: hard +"fs-extra@npm:^11.1.1": + version: 11.2.0 + resolution: "fs-extra@npm:11.2.0" + dependencies: + graceful-fs: "npm:^4.2.0" + jsonfile: "npm:^6.0.1" + universalify: "npm:^2.0.0" + checksum: d77a9a9efe60532d2e790e938c81a02c1b24904ef7a3efb3990b835514465ba720e99a6ea56fd5e2db53b4695319b644d76d5a0e9988a2beef80aa7b1da63398 + languageName: node + linkType: hard + "fs-extra@npm:^7.0.1": version: 7.0.1 resolution: "fs-extra@npm:7.0.1" @@ -14125,7 +14207,7 @@ __metadata: languageName: node linkType: hard -"fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": +"fsevents@npm:2.3.2, fsevents@npm:^2.3.2, fsevents@npm:~2.3.2": version: 2.3.2 resolution: "fsevents@npm:2.3.2" dependencies: @@ -14135,7 +14217,7 @@ __metadata: languageName: node linkType: hard -"fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": +"fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A^2.3.2#optional!builtin, fsevents@patch:fsevents@npm%3A~2.3.2#optional!builtin": version: 2.3.2 resolution: "fsevents@patch:fsevents@npm%3A2.3.2#optional!builtin::version=2.3.2&hash=df0bf1" dependencies: @@ -14293,6 +14375,24 @@ __metadata: languageName: node linkType: hard +"gh-pages@npm:^6.2.0": + version: 6.2.0 + resolution: "gh-pages@npm:6.2.0" + dependencies: + async: "npm:^3.2.4" + commander: "npm:^11.0.0" + email-addresses: "npm:^5.0.0" + filenamify: "npm:^4.3.0" + find-cache-dir: "npm:^3.3.1" + fs-extra: "npm:^11.1.1" + globby: "npm:^11.1.0" + bin: + gh-pages: bin/gh-pages.js + gh-pages-clean: bin/gh-pages-clean.js + checksum: 30b996b3a9c3dc00d333b6fb15232b3ddc8628f9f458de871ad237b4e3414e68f5408d7525d82ae4a551e24bd7461f009908e8db7c7031dc7dc51e62e7c18ac0 + languageName: node + linkType: hard + "github-slugger@npm:^2.0.0": version: 2.0.0 resolution: "github-slugger@npm:2.0.0" @@ -19068,6 +19168,30 @@ __metadata: languageName: node linkType: hard +"playwright-core@npm:1.49.1": + version: 1.49.1 + resolution: "playwright-core@npm:1.49.1" + bin: + playwright-core: cli.js + checksum: 990b619c75715cd98b2c10c1180a126e3a454b247063b8352bc67792fe01183ec07f31d30c8714c3768cefed12886d1d64ac06da701f2baafc2cad9b439e3919 + languageName: node + linkType: hard + +"playwright@npm:1.49.1": + version: 1.49.1 + resolution: "playwright@npm:1.49.1" + dependencies: + fsevents: "npm:2.3.2" + playwright-core: "npm:1.49.1" + dependenciesMeta: + fsevents: + optional: true + bin: + playwright: cli.js + checksum: 2368762c898920d4a0a5788b153dead45f9c36c3f5cf4d2af5228d0b8ea65823e3bbe998877950a2b9bb23a211e4633996f854c6188769dc81a25543ac818ab5 + languageName: node + linkType: hard + "plist@npm:^3.0.5": version: 3.1.0 resolution: "plist@npm:3.1.0" @@ -19856,6 +19980,16 @@ __metadata: languageName: node linkType: hard +"react-native-toast-message@npm:2.2.1": + version: 2.2.1 + resolution: "react-native-toast-message@npm:2.2.1" + peerDependencies: + react: "*" + react-native: "*" + checksum: 03418b03ae345f5fe1c1747a98ae9c420a9ea37402d849920a14bfc6eb01541ad934266ae2656433604448d2fba1266ab6f6be649a49c9b22445e86444c1f6de + languageName: node + linkType: hard + "react-native-url-polyfill@npm:2.0.0": version: 2.0.0 resolution: "react-native-url-polyfill@npm:2.0.0" @@ -21504,6 +21638,15 @@ __metadata: languageName: node linkType: hard +"strip-outer@npm:^1.0.1": + version: 1.0.1 + resolution: "strip-outer@npm:1.0.1" + dependencies: + escape-string-regexp: "npm:^1.0.2" + checksum: c0f38e6f37563d878a221b1c76f0822f180ec5fc39be5ada30ee637a7d5b59d19418093bad2b4db1e69c40d7a7a7ac50828afce07276cf3d51ac8965cb140dfb + languageName: node + linkType: hard + "strnum@npm:^1.0.5": version: 1.0.5 resolution: "strnum@npm:1.0.5" @@ -21964,6 +22107,15 @@ __metadata: languageName: node linkType: hard +"trim-repeated@npm:^1.0.0": + version: 1.0.0 + resolution: "trim-repeated@npm:1.0.0" + dependencies: + escape-string-regexp: "npm:^1.0.2" + checksum: 89acada0142ed0cdb113615a3e82fdb09e7fdb0e3504ded62762dd935bc27debfcc38edefa497dc7145d8dc8602d40dd9eec891e0ea6c28fa0cc384200b692db + languageName: node + linkType: hard + "ts-api-utils@npm:^1.3.0": version: 1.3.0 resolution: "ts-api-utils@npm:1.3.0" @@ -22425,6 +22577,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~6.20.0": + version: 6.20.0 + resolution: "undici-types@npm:6.20.0" + checksum: 68e659a98898d6a836a9a59e6adf14a5d799707f5ea629433e025ac90d239f75e408e2e5ff086afc3cace26f8b26ee52155293564593fbb4a2f666af57fc59bf + languageName: node + linkType: hard + "unenv@npm:^1.8.0": version: 1.9.0 resolution: "unenv@npm:1.9.0" From 007909cec165f2d62ad8274f0bd6b5f58407c0b9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 16 Dec 2024 10:26:53 -0300 Subject: [PATCH 109/114] chore(deps): bump nanoid from 3.3.6 to 3.3.8 (#284) Bumps [nanoid](https://github.com/ai/nanoid) from 3.3.6 to 3.3.8. - [Release notes](https://github.com/ai/nanoid/releases) - [Changelog](https://github.com/ai/nanoid/blob/main/CHANGELOG.md) - [Commits](https://github.com/ai/nanoid/compare/3.3.6...3.3.8) --- updated-dependencies: - dependency-name: nanoid dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index 6cb7d561f..ca6ac19ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18107,21 +18107,12 @@ __metadata: languageName: node linkType: hard -"nanoid@npm:^3.3.6": - version: 3.3.6 - resolution: "nanoid@npm:3.3.6" - bin: - nanoid: bin/nanoid.cjs - checksum: 606b355960d0fcbe3d27924c4c52ef7d47d3b57208808ece73279420d91469b01ec1dce10fae512b6d4a8c5a5432b352b228336a8b2202a6ea68e67fa348e2ee - languageName: node - linkType: hard - -"nanoid@npm:^3.3.7": - version: 3.3.7 - resolution: "nanoid@npm:3.3.7" +"nanoid@npm:^3.3.6, nanoid@npm:^3.3.7": + version: 3.3.8 + resolution: "nanoid@npm:3.3.8" bin: nanoid: bin/nanoid.cjs - checksum: e3fb661aa083454f40500473bb69eedb85dc160e763150b9a2c567c7e9ff560ce028a9f833123b618a6ea742e311138b591910e795614a629029e86e180660f3 + checksum: 4b1bb29f6cfebf3be3bc4ad1f1296fb0a10a3043a79f34fbffe75d1621b4318319211cd420549459018ea3592f0d2f159247a6f874911d6d26eaaadda2478120 languageName: node linkType: hard From c28fcb94cea5e212d3030c7c17869b9371f188d2 Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 17 Dec 2024 11:51:28 -0300 Subject: [PATCH 110/114] chore: wrapped authview in memo (#285) * chore: wrapped authview in memo --- .github/workflows/verify.yml | 6 ----- apps/native/.github/workflows/playwright.yml | 27 -------------------- package.json | 2 +- packages/wallet/src/AppKitAuthWebview.tsx | 7 +++-- 4 files changed, 6 insertions(+), 36 deletions(-) delete mode 100644 apps/native/.github/workflows/playwright.yml diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 95a8d345d..b2629a578 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -24,9 +24,3 @@ jobs: - name: Package Tests run: yarn test - - - name: E2E Tests - uses: ./.github/workflows/e2e.yml - with: - skip_setup: true # Skip redundant setup steps - secrets: inherit diff --git a/apps/native/.github/workflows/playwright.yml b/apps/native/.github/workflows/playwright.yml deleted file mode 100644 index a94b6417a..000000000 --- a/apps/native/.github/workflows/playwright.yml +++ /dev/null @@ -1,27 +0,0 @@ -name: Playwright Tests -on: - push: - branches: [ main, master ] - pull_request: - branches: [ main, master ] -jobs: - test: - timeout-minutes: 60 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: actions/setup-node@v4 - with: - node-version: lts/* - - name: Install dependencies - run: npm install -g yarn && yarn - - name: Install Playwright Browsers - run: yarn playwright install --with-deps - - name: Run Playwright tests - run: yarn playwright test - - uses: actions/upload-artifact@v4 - if: ${{ !cancelled() }} - with: - name: playwright-report - path: playwright-report/ - retention-days: 30 diff --git a/package.json b/package.json index 0392016c5..7dbaf2c12 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "clean": "turbo clean && rm -rf node_modules && watchman watch-del-all", "format": "prettier --write \"**/*.{ts,tsx,js,jsx,json,md}\" --ignore-path .gitignore", "changeset:prepublish": "yarn run clean; yarn install; yarn version:update; yarn run lint && yarn run prettier; yarn run build; yarn run test;", - "changeset:publish": "yarn run changeset:prepublish; yarn run changeset publish", + "changeset:publish": "yarn run changeset:prepublish; yarn run changeset publish --no-git-tag", "changeset:version": "changeset version; yarn run version:update; yarn install --refresh-lockfile", "version:update": "./scripts/bump-version.sh" }, diff --git a/packages/wallet/src/AppKitAuthWebview.tsx b/packages/wallet/src/AppKitAuthWebview.tsx index cd477f99c..34544f271 100644 --- a/packages/wallet/src/AppKitAuthWebview.tsx +++ b/packages/wallet/src/AppKitAuthWebview.tsx @@ -1,5 +1,5 @@ import { useSnapshot } from 'valtio'; -import { useEffect, useRef, useState } from 'react'; +import { memo, useEffect, useRef, useState } from 'react'; import { Animated, Appearance, Linking, Platform, SafeAreaView, StyleSheet } from 'react-native'; import { WebView, type WebViewMessageEvent } from 'react-native-webview'; @@ -24,7 +24,7 @@ import type { AppKitFrameTypes } from './AppKitFrameTypes'; const AnimatedSafeAreaView = Animated.createAnimatedComponent(SafeAreaView); -export function AuthWebview() { +function _AuthWebview() { const webviewRef = useRef(null); const Theme = useTheme(); const authConnector = ConnectorController.getAuthConnector(); @@ -176,6 +176,7 @@ export function AuthWebview() { containerStyle={styles.webview} injectedJavaScript={AppKitFrameConstants.FRAME_MESSAGES_HANDLER} ref={webviewRef} + webviewDebuggingEnabled onOpenWindow={syntheticEvent => { const { nativeEvent } = syntheticEvent; const { targetUrl } = nativeEvent; @@ -217,6 +218,8 @@ export function AuthWebview() { ) : null; } +export const AuthWebview = memo(_AuthWebview); + const styles = StyleSheet.create({ backdrop: { position: 'absolute', From f8a95489558d38cf61b192542396bcb55e7c533d Mon Sep 17 00:00:00 2001 From: ignaciosantise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 17 Dec 2024 12:34:58 -0300 Subject: [PATCH 111/114] chore: removed debug flag from webview --- packages/wallet/src/AppKitAuthWebview.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/wallet/src/AppKitAuthWebview.tsx b/packages/wallet/src/AppKitAuthWebview.tsx index 34544f271..a7d20567a 100644 --- a/packages/wallet/src/AppKitAuthWebview.tsx +++ b/packages/wallet/src/AppKitAuthWebview.tsx @@ -176,7 +176,6 @@ function _AuthWebview() { containerStyle={styles.webview} injectedJavaScript={AppKitFrameConstants.FRAME_MESSAGES_HANDLER} ref={webviewRef} - webviewDebuggingEnabled onOpenWindow={syntheticEvent => { const { nativeEvent } = syntheticEvent; const { targetUrl } = nativeEvent; From 6f73551bd4aba49d7d755ee8b48c6fc3375078ba Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Tue, 17 Dec 2024 18:26:59 -0300 Subject: [PATCH 112/114] chore: added e2e in pull request action (#286) --- .github/workflows/changesets.yml | 4 ++++ .github/workflows/e2e.yml | 17 +---------------- .github/workflows/pull-request.yml | 4 ++++ 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/.github/workflows/changesets.yml b/.github/workflows/changesets.yml index 91fe32bf9..a104bc4e6 100644 --- a/.github/workflows/changesets.yml +++ b/.github/workflows/changesets.yml @@ -24,6 +24,10 @@ jobs: - name: uses: ./.github/actions/setup + - name: E2E Tests + uses: ./.github/workflows/e2e.yml + secrets: inherit + - name: Create Release Pull Request or Publish to NPM id: changesets uses: changesets/action@v1 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 2d2bf6450..3ab77cd0d 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -4,11 +4,6 @@ on: workflow_dispatch: workflow_call: inputs: - branch: - description: 'The branch to use' - default: 'main' - required: false - type: string base-url: description: 'The AppKit App url' default: 'http://localhost:8081/' @@ -19,10 +14,6 @@ on: default: 'https://react-wallet.walletconnect.com/' required: false type: string - skip_setup: - description: 'Skip setup steps if already done' - type: boolean - default: false secrets: CLOUD_PROJECT_ID: required: true @@ -32,19 +23,13 @@ jobs: name: 'Playwright Tests' runs-on: ubuntu-latest steps: - - name: checkout - if: ${{ !inputs.skip_setup }} + - name: Checkout uses: actions/checkout@v4 - with: - repository: reown-com/appkit-react-native - ref: ${{ inputs.branch }} - name: Setup - if: ${{ !inputs.skip_setup }} uses: ./.github/actions/setup - name: Build SDK - if: ${{ !inputs.skip_setup }} run: | echo "Building SDK..." yarn build diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 64e1a66f0..536d7e32c 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -12,3 +12,7 @@ jobs: name: Verify uses: ./.github/workflows/verify.yml secrets: inherit + + e2e: + uses: ./.github/workflows/e2e.yml + secrets: inherit From 4dde1c830011bdb6198d39df07527f78c41d08cf Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 18 Dec 2024 10:59:48 -0300 Subject: [PATCH 113/114] chore: disabled farcaster login (#287) --- apps/native/App.tsx | 2 +- packages/core/src/utils/ConstantsUtil.ts | 2 +- packages/core/src/utils/TypeUtil.ts | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/apps/native/App.tsx b/apps/native/App.tsx index ea1abcdd9..264779f01 100644 --- a/apps/native/App.tsx +++ b/apps/native/App.tsx @@ -75,7 +75,7 @@ createAppKit({ debug: true, features: { email: true, - socials: ['x', 'farcaster', 'discord', 'apple'], + socials: ['x', 'discord', 'apple'], emailShowWallets: true } }); diff --git a/packages/core/src/utils/ConstantsUtil.ts b/packages/core/src/utils/ConstantsUtil.ts index 8e537210f..e580d1077 100644 --- a/packages/core/src/utils/ConstantsUtil.ts +++ b/packages/core/src/utils/ConstantsUtil.ts @@ -3,7 +3,7 @@ import type { Features } from './TypeUtil'; const defaultFeatures: Features = { email: true, emailShowWallets: true, - socials: ['x', 'discord', 'apple', 'farcaster'] + socials: ['x', 'discord', 'apple'] }; export const ConstantsUtil = { diff --git a/packages/core/src/utils/TypeUtil.ts b/packages/core/src/utils/TypeUtil.ts index d0bce97b0..0cd0e363c 100644 --- a/packages/core/src/utils/TypeUtil.ts +++ b/packages/core/src/utils/TypeUtil.ts @@ -63,6 +63,8 @@ export type SdkVersion = | `react-native-ethers5-${string}` | `react-native-ethers-${string}`; +type EnabledSocials = Exclude; + export type Features = { /** * @description Enable or disable the email feature. Enabled by default. @@ -76,9 +78,9 @@ export type Features = { emailShowWallets?: boolean; /** * @description Enable or disable the socials feature. Enabled by default. - * @type {FeaturesSocials[]} + * @type {EnabledSocials[]} */ - socials?: SocialProvider[] | false; + socials?: EnabledSocials[] | false; }; // -- ApiController Types ------------------------------------------------------- From 047cb8e2d4a435e1728cf1918c3754c217a60be2 Mon Sep 17 00:00:00 2001 From: Ignacio Santise <25931366+ignaciosantise@users.noreply.github.com> Date: Wed, 18 Dec 2024 11:27:30 -0300 Subject: [PATCH 114/114] chore: updated ethereum provider version (#289) --- .changeset/weak-poems-play.md | 18 ++++++ packages/ethers/package.json | 2 +- packages/ethers5/package.json | 2 +- yarn.lock | 102 +++++++++++++++++++--------------- 4 files changed, 77 insertions(+), 47 deletions(-) create mode 100644 .changeset/weak-poems-play.md diff --git a/.changeset/weak-poems-play.md b/.changeset/weak-poems-play.md new file mode 100644 index 000000000..896f956c4 --- /dev/null +++ b/.changeset/weak-poems-play.md @@ -0,0 +1,18 @@ +--- +'@reown/appkit-coinbase-ethers-react-native': patch +'@reown/appkit-coinbase-wagmi-react-native': patch +'@reown/appkit-scaffold-utils-react-native': patch +'@reown/appkit-auth-ethers-react-native': patch +'@reown/appkit-auth-wagmi-react-native': patch +'@reown/appkit-scaffold-react-native': patch +'@reown/appkit-ethers5-react-native': patch +'@reown/appkit-common-react-native': patch +'@reown/appkit-ethers-react-native': patch +'@reown/appkit-wallet-react-native': patch +'@reown/appkit-wagmi-react-native': patch +'@reown/appkit-core-react-native': patch +'@reown/appkit-siwe-react-native': patch +'@reown/appkit-ui-react-native': patch +--- + +chore: updated ethereum provider to 2.17.3 diff --git a/packages/ethers/package.json b/packages/ethers/package.json index 9047e6046..0e6127b79 100644 --- a/packages/ethers/package.json +++ b/packages/ethers/package.json @@ -42,7 +42,7 @@ "@reown/appkit-scaffold-react-native": "1.0.2", "@reown/appkit-scaffold-utils-react-native": "1.0.2", "@reown/appkit-siwe-react-native": "1.0.2", - "@walletconnect/ethereum-provider": "2.17.2" + "@walletconnect/ethereum-provider": "2.17.3" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.17.0", diff --git a/packages/ethers5/package.json b/packages/ethers5/package.json index 790554726..2fe1786da 100644 --- a/packages/ethers5/package.json +++ b/packages/ethers5/package.json @@ -42,7 +42,7 @@ "@reown/appkit-scaffold-react-native": "1.0.2", "@reown/appkit-scaffold-utils-react-native": "1.0.2", "@reown/appkit-siwe-react-native": "1.0.2", - "@walletconnect/ethereum-provider": "2.17.2" + "@walletconnect/ethereum-provider": "2.17.3" }, "peerDependencies": { "@react-native-async-storage/async-storage": ">=1.17.0", diff --git a/yarn.lock b/yarn.lock index ca6ac19ae..d17c20ea0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6749,7 +6749,7 @@ __metadata: "@reown/appkit-scaffold-react-native": "npm:1.0.2" "@reown/appkit-scaffold-utils-react-native": "npm:1.0.2" "@reown/appkit-siwe-react-native": "npm:1.0.2" - "@walletconnect/ethereum-provider": "npm:2.17.2" + "@walletconnect/ethereum-provider": "npm:2.17.3" ethers: "npm:6.10.0" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" @@ -6770,7 +6770,7 @@ __metadata: "@reown/appkit-scaffold-react-native": "npm:1.0.2" "@reown/appkit-scaffold-utils-react-native": "npm:1.0.2" "@reown/appkit-siwe-react-native": "npm:1.0.2" - "@walletconnect/ethereum-provider": "npm:2.17.2" + "@walletconnect/ethereum-provider": "npm:2.17.3" ethers: "npm:5.7.2" peerDependencies: "@react-native-async-storage/async-storage": ">=1.17.0" @@ -8825,28 +8825,28 @@ __metadata: languageName: node linkType: hard -"@walletconnect/core@npm:2.17.2": - version: 2.17.2 - resolution: "@walletconnect/core@npm:2.17.2" +"@walletconnect/core@npm:2.17.3": + version: 2.17.3 + resolution: "@walletconnect/core@npm:2.17.3" dependencies: "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-provider": "npm:1.0.14" "@walletconnect/jsonrpc-types": "npm:1.0.4" "@walletconnect/jsonrpc-utils": "npm:1.0.8" - "@walletconnect/jsonrpc-ws-connection": "npm:1.0.14" + "@walletconnect/jsonrpc-ws-connection": "npm:1.0.16" "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" "@walletconnect/relay-api": "npm:1.0.11" "@walletconnect/relay-auth": "npm:1.0.4" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.17.2" - "@walletconnect/utils": "npm:2.17.2" + "@walletconnect/types": "npm:2.17.3" + "@walletconnect/utils": "npm:2.17.3" "@walletconnect/window-getters": "npm:1.0.1" events: "npm:3.3.0" lodash.isequal: "npm:4.5.0" uint8arrays: "npm:3.1.0" - checksum: 6124b81892a4e5e9350cfff22a7ce3a23a66c9589221411bd8bfd411fc392b6b343fae1634b32000d4275ba11b1a0f732cf6b7ba5da35b388854c7e7b4f2764d + checksum: e6a841a0d5b27922b83fbb7a1dbcb519b825d70489f9bd6a909cf0b3c543ab3a6c209a0775a95c5dc452a875757f04c9ca27d02c6f002c39974d2ce2061e5887 languageName: node linkType: hard @@ -8877,9 +8877,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/ethereum-provider@npm:2.17.2": - version: 2.17.2 - resolution: "@walletconnect/ethereum-provider@npm:2.17.2" +"@walletconnect/ethereum-provider@npm:2.17.3": + version: 2.17.3 + resolution: "@walletconnect/ethereum-provider@npm:2.17.3" dependencies: "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" "@walletconnect/jsonrpc-provider": "npm:1.0.14" @@ -8887,12 +8887,12 @@ __metadata: "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/modal": "npm:2.7.0" - "@walletconnect/sign-client": "npm:2.17.2" - "@walletconnect/types": "npm:2.17.2" - "@walletconnect/universal-provider": "npm:2.17.2" - "@walletconnect/utils": "npm:2.17.2" + "@walletconnect/sign-client": "npm:2.17.3" + "@walletconnect/types": "npm:2.17.3" + "@walletconnect/universal-provider": "npm:2.17.3" + "@walletconnect/utils": "npm:2.17.3" events: "npm:3.3.0" - checksum: 191eb6106119a57c1e4c82212cf6650f9e9e32b26a39c5aba0745125067e9153f30ddc43254bafd95ed5f1debd5a5d08094dd5a037ff4a61e645c652ae3d022c + checksum: 6ca5aaf5f72dfe0c8edd54f4bd30a55ee22e28cf766a6fe1052a22ad252f0aab4d41c9e105b97e1a4ce29f25fbb8aaed3081a447ecb1759664306b4725948774 languageName: node linkType: hard @@ -8983,6 +8983,18 @@ __metadata: languageName: node linkType: hard +"@walletconnect/jsonrpc-ws-connection@npm:1.0.16": + version: 1.0.16 + resolution: "@walletconnect/jsonrpc-ws-connection@npm:1.0.16" + dependencies: + "@walletconnect/jsonrpc-utils": "npm:^1.0.6" + "@walletconnect/safe-json": "npm:^1.0.2" + events: "npm:^3.3.0" + ws: "npm:^7.5.1" + checksum: 30a09d24ffb6b4b291e2d1263504c4ea6c6797c992f5e6eb8033e58bd24749c80fd4e5ba6ffaadb28f8ced0c6b131213195b616f8983bb9f56aa7c91e83e6218 + languageName: node + linkType: hard + "@walletconnect/keyvaluestorage@npm:1.1.1": version: 1.1.1 resolution: "@walletconnect/keyvaluestorage@npm:1.1.1" @@ -9129,20 +9141,20 @@ __metadata: languageName: node linkType: hard -"@walletconnect/sign-client@npm:2.17.2": - version: 2.17.2 - resolution: "@walletconnect/sign-client@npm:2.17.2" +"@walletconnect/sign-client@npm:2.17.3": + version: 2.17.3 + resolution: "@walletconnect/sign-client@npm:2.17.3" dependencies: - "@walletconnect/core": "npm:2.17.2" + "@walletconnect/core": "npm:2.17.3" "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/logger": "npm:2.1.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.17.2" - "@walletconnect/utils": "npm:2.17.2" + "@walletconnect/types": "npm:2.17.3" + "@walletconnect/utils": "npm:2.17.3" events: "npm:3.3.0" - checksum: 0acbda4ea34be209b1436134804e72641ca377e2bb6823b7d94177b30e50b8e6de28dfdad6ff64dac61a1305e7b6f281df2357488382c88e440a79b817d377a8 + checksum: 454afa3c933ec11f651c4cd275af88eef7da65b5d4bcf8987f768f340557492cf436d662ca42baa54ad8136e4b16f5269e0bc3e212580df09e0ee49873718b96 languageName: node linkType: hard @@ -9169,9 +9181,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/types@npm:2.17.2": - version: 2.17.2 - resolution: "@walletconnect/types@npm:2.17.2" +"@walletconnect/types@npm:2.17.3": + version: 2.17.3 + resolution: "@walletconnect/types@npm:2.17.3" dependencies: "@walletconnect/events": "npm:1.0.1" "@walletconnect/heartbeat": "npm:1.2.2" @@ -9179,7 +9191,7 @@ __metadata: "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" events: "npm:3.3.0" - checksum: 95bd3e4f4f2ef181ea69691800a0a06be2c4fa900ae972539851c5817a0f01b4ba9f381161d044df4db004f431bc416548ec6eca0ac523fc1fb06014386accac + checksum: 6e50f1f3d64f32d0fa697bb61340191b153aa0a77b8a483cacaeb62aefa190524e10f78188260b591eaae877d6bfa5ea9ffab5ed905c286151300577f2e0101f languageName: node linkType: hard @@ -9200,9 +9212,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/universal-provider@npm:2.17.2": - version: 2.17.2 - resolution: "@walletconnect/universal-provider@npm:2.17.2" +"@walletconnect/universal-provider@npm:2.17.3": + version: 2.17.3 + resolution: "@walletconnect/universal-provider@npm:2.17.3" dependencies: "@walletconnect/events": "npm:1.0.1" "@walletconnect/jsonrpc-http-connection": "npm:1.0.8" @@ -9211,12 +9223,12 @@ __metadata: "@walletconnect/jsonrpc-utils": "npm:1.0.8" "@walletconnect/keyvaluestorage": "npm:1.1.1" "@walletconnect/logger": "npm:2.1.2" - "@walletconnect/sign-client": "npm:2.17.2" - "@walletconnect/types": "npm:2.17.2" - "@walletconnect/utils": "npm:2.17.2" + "@walletconnect/sign-client": "npm:2.17.3" + "@walletconnect/types": "npm:2.17.3" + "@walletconnect/utils": "npm:2.17.3" events: "npm:3.3.0" lodash: "npm:4.17.21" - checksum: afc617916ce2a8e8b669f2d5813795fe0d2cc4400dc0b3275e0b814e5c960b6bc2a1de27fa22021a5fc124aa58ec5ec6a02403fd49ddc4945e1ea941fba3c4da + checksum: a577099e5b40fc254df56f9fa3335ff064af24804ec7db9e213ef74261076b2e92194251f56f44de3a7d980deb7cef14f76ca961399e6f6671d1a7dccbdea8d9 languageName: node linkType: hard @@ -9244,9 +9256,9 @@ __metadata: languageName: node linkType: hard -"@walletconnect/utils@npm:2.17.2": - version: 2.17.2 - resolution: "@walletconnect/utils@npm:2.17.2" +"@walletconnect/utils@npm:2.17.3": + version: 2.17.3 + resolution: "@walletconnect/utils@npm:2.17.3" dependencies: "@ethersproject/hash": "npm:5.7.0" "@ethersproject/transactions": "npm:5.7.0" @@ -9261,14 +9273,14 @@ __metadata: "@walletconnect/relay-auth": "npm:1.0.4" "@walletconnect/safe-json": "npm:1.0.2" "@walletconnect/time": "npm:1.0.2" - "@walletconnect/types": "npm:2.17.2" + "@walletconnect/types": "npm:2.17.3" "@walletconnect/window-getters": "npm:1.0.1" "@walletconnect/window-metadata": "npm:1.0.1" detect-browser: "npm:5.3.0" - elliptic: "npm:6.6.0" + elliptic: "npm:6.6.1" query-string: "npm:7.1.3" uint8arrays: "npm:3.1.0" - checksum: b44c0025be12301a28715a204c037328eae4fa432f0ee1730da08b3b6583e07aeaf59efd9dcc52209f6a61b50b31c84e555028b97067dfdf9f5efe1211378fc8 + checksum: ab08f625786eb55e0ae41075a3ccee9804750b1f20745f2d7a81569a6741d022463b250958124925e6b5f51d3a5b3ec783a23233391d8d937c4bcd76e7a8cc8c languageName: node linkType: hard @@ -12300,9 +12312,9 @@ __metadata: languageName: node linkType: hard -"elliptic@npm:6.6.0": - version: 6.6.0 - resolution: "elliptic@npm:6.6.0" +"elliptic@npm:6.6.1": + version: 6.6.1 + resolution: "elliptic@npm:6.6.1" dependencies: bn.js: "npm:^4.11.9" brorand: "npm:^1.1.0" @@ -12311,7 +12323,7 @@ __metadata: inherits: "npm:^2.0.4" minimalistic-assert: "npm:^1.0.1" minimalistic-crypto-utils: "npm:^1.0.1" - checksum: 42eb3492e218017bf8923a5d14a86f414952f2f771361805b3ae9f380923b5da53e203d0d92be95cb0a248858a78db7db5934a346e268abb757e6fe561d401c9 + checksum: 8b24ef782eec8b472053793ea1e91ae6bee41afffdfcb78a81c0a53b191e715cbe1292aa07165958a9bbe675bd0955142560b1a007ffce7d6c765bcaf951a867 languageName: node linkType: hard