Skip to content

Commit

Permalink
feat: mpc and hardware wallets
Browse files Browse the repository at this point in the history
  • Loading branch information
edgarkhanzadian committed Sep 26, 2024
1 parent 7293564 commit 3a4b659
Show file tree
Hide file tree
Showing 41 changed files with 728 additions and 113 deletions.
2 changes: 2 additions & 0 deletions apps/mobile/src/app/(home)/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,8 @@ export default function StackLayout() {
options={{ header: () => NavigationDeveloperConsole }}
/>
<Stack.Screen name="generating-wallet" options={{ headerShown: false }} />
<Stack.Screen name="mpc-wallets" options={{ header: () => NavigationBackSimple }} />
<Stack.Screen name="hardware-wallets" options={{ header: () => NavigationBackSimple }} />
</Stack>
);
}
107 changes: 107 additions & 0 deletions apps/mobile/src/app/(home)/hardware-wallets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { useRef, useState } from 'react';
import { ScrollView } from 'react-native-gesture-handler';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

import { NotifyUserSheet, OptionData } from '@/components/sheets/notify-user-sheet.layout';
import { Trans, t } from '@lingui/macro';
import { useTheme } from '@shopify/restyle';

import {
Box,
Cell,
LogoHardwareBitkey,
LogoHardwareFoundation,
LogoHardwareLedger,
LogoHardwareOnekey,
LogoHardwareRyder,
LogoHardwareTrezor,
QuestionCircleIcon,
SheetRef,
Text,
Theme,
TouchableOpacity,
} from '@leather.io/ui/native';

export default function HardwareWalletsScreen() {
const { bottom } = useSafeAreaInsets();
const theme = useTheme<Theme>();
const notifyUserSheetRef = useRef<SheetRef>(null);
const [optionData, setOptionData] = useState<OptionData | null>(null);
function onOpenNotificationsSheet(option: OptionData) {
setOptionData(option);
notifyUserSheetRef.current?.present();
}
function onCloseNotificationsSheet() {
setOptionData(null);
}
function getUnavailableFeatures() {
const UNAVAILABLE_FEATURES = {
bitgo: { title: t`Bitkey`, Icon: LogoHardwareBitkey },
capsule: { title: t`Ledger`, Icon: LogoHardwareLedger },
copper: { title: t`OneKey`, Icon: LogoHardwareOnekey },
fireblocks: { title: t`Passport`, Icon: LogoHardwareFoundation },
foredefi: { title: t`Ryder`, Icon: LogoHardwareRyder },
portal: { title: t`Trezor`, Icon: LogoHardwareTrezor },
};

return UNAVAILABLE_FEATURES;
}
return (
<>
<Box
flex={1}
backgroundColor="ink.background-primary"
style={{ paddingBottom: bottom + theme.spacing['5'] }}
>
<ScrollView contentContainerStyle={{ paddingHorizontal: theme.spacing['5'] }}>
<Box gap="3" pt="5">
<TouchableOpacity
onPress={() => {
// TODO: show some kind of a helper here
}}
p="5"
position="absolute"
right={-theme.spacing['5']}
zIndex={10}
top={theme.spacing['1']}
>
<QuestionCircleIcon color={theme.colors['ink.text-primary']} variant="small" />
</TouchableOpacity>
<Box>
<Trans>
<Text variant="heading03">CONNECT</Text>
<Text variant="heading03">HARDWARE DEVICE</Text>
</Trans>
</Box>
<Box gap="1" pt="5">
{Object.entries(getUnavailableFeatures()).map(featureEntry => {
const [featureKey, feature] = featureEntry;
const hardwareWalletName = feature.title;
function onPress() {
onOpenNotificationsSheet({
title: t`Connect hardware wallet: ${hardwareWalletName}`,
id: featureKey,
});
}
return (
<Cell
py="4"
key={featureKey}
onPress={onPress}
title={feature.title}
Icon={feature.Icon}
/>
);
})}
</Box>
</Box>
</ScrollView>
</Box>
<NotifyUserSheet
optionData={optionData}
onCloseNotificationsSheet={onCloseNotificationsSheet}
notifyUserSheetRef={notifyUserSheetRef}
/>
</>
);
}
111 changes: 111 additions & 0 deletions apps/mobile/src/app/(home)/mpc-wallets.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import { useRef, useState } from 'react';
import { ScrollView } from 'react-native-gesture-handler';
import { useSafeAreaInsets } from 'react-native-safe-area-context';

import { NotifyUserSheet, OptionData } from '@/components/sheets/notify-user-sheet.layout';
import { Trans, t } from '@lingui/macro';
import { useTheme } from '@shopify/restyle';

import {
Box,
Cell,
LogoMPCBitgo,
LogoMPCCapsule,
LogoMPCCopper,
LogoMPCFireblocks,
LogoMPCFordefi,
LogoMPCPortal,
LogoMPCPrivy,
LogoMPCQredo,
QuestionCircleIcon,
SheetRef,
Text,
Theme,
TouchableOpacity,
} from '@leather.io/ui/native';

export default function MPCWalletsScreen() {
const { bottom } = useSafeAreaInsets();
const theme = useTheme<Theme>();
const notifyUserSheetRef = useRef<SheetRef>(null);
const [optionData, setOptionData] = useState<OptionData | null>(null);
function onOpenNotificationsSheet(option: OptionData) {
setOptionData(option);
notifyUserSheetRef.current?.present();
}
function onCloseNotificationsSheet() {
setOptionData(null);
}
function getUnavailableFeatures() {
const UNAVAILABLE_FEATURES = {
bitgo: { title: t`BitGo`, Icon: LogoMPCBitgo },
capsule: { title: t`Capsule`, Icon: LogoMPCCapsule },
copper: { title: t`Copper`, Icon: LogoMPCCopper },
fireblocks: { title: t`Fireblocks`, Icon: LogoMPCFireblocks },
foredefi: { title: t`Foredefi`, Icon: LogoMPCFordefi },
portal: { title: t`Portal`, Icon: LogoMPCPortal },
privy: { title: t`Privy`, Icon: LogoMPCPrivy },
qredo: { title: t`Qredo`, Icon: LogoMPCQredo },
};

return UNAVAILABLE_FEATURES;
}
return (
<>
<Box
flex={1}
backgroundColor="ink.background-primary"
style={{ paddingBottom: bottom + theme.spacing['5'] }}
>
<ScrollView contentContainerStyle={{ paddingHorizontal: theme.spacing['5'] }}>
<Box gap="3" pt="5">
<TouchableOpacity
onPress={() => {
// TODO: show some kind of a helper here
}}
p="5"
position="absolute"
right={-theme.spacing['5']}
zIndex={10}
top={theme.spacing['1']}
>
<QuestionCircleIcon color={theme.colors['ink.text-primary']} variant="small" />
</TouchableOpacity>
<Box>
<Trans>
<Text variant="heading03">CONNECT</Text>
<Text variant="heading03">MPC WALLET</Text>
</Trans>
</Box>
<Box gap="1" pt="5">
{Object.entries(getUnavailableFeatures()).map(featureEntry => {
const [featureKey, feature] = featureEntry;
const mpcWalletName = feature.title;
function onPress() {
onOpenNotificationsSheet({
title: t`Connect MPC wallet: ${mpcWalletName}`,
id: featureKey,
});
}
return (
<Cell
py="4"
key={featureKey}
onPress={onPress}
title={feature.title}
Icon={feature.Icon}
/>
);
})}
</Box>
</Box>
</ScrollView>
</Box>
<NotifyUserSheet
optionData={optionData}
onCloseNotificationsSheet={onCloseNotificationsSheet}
notifyUserSheetRef={notifyUserSheetRef}
/>
</>
);
}
Binary file added apps/mobile/src/assets/unavailable-feature.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
73 changes: 43 additions & 30 deletions apps/mobile/src/components/add-wallet/add-wallet-sheet.layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ import Animated, {
useSharedValue,
} from 'react-native-reanimated';

import { AppRoutes } from '@/routes';
import { t } from '@lingui/macro';
import { useTheme } from '@shopify/restyle';
import { Image } from 'expo-image';
import { useRouter } from 'expo-router';

import {
ArrowRotateClockwiseIcon,
Expand All @@ -32,32 +34,6 @@ import { AddWalletListItem } from './add-wallet-list-item';

const AnimatedBox = Animated.createAnimatedComponent(Box);

function getUnavailableFeatures(theme: Theme) {
const UNAVAILABLE_FEATURES = {
hardwareWallet: {
title: t`Connect hardware wallet`,
subtitle: t`Ledger, Trezor, Ryder and more`,
icon: <SignalIcon color={theme.colors['ink.text-subdued']} />,
},
emailRestore: {
title: t`Create or restore via email`,
subtitle: t`Access custodial wallet`,
icon: <EmailIcon color={theme.colors['ink.text-subdued']} />,
},
mpcWallet: {
title: t`Connect MPC wallet`,
subtitle: t`Import existing accounts`,
icon: <PaletteIcon color={theme.colors['ink.text-subdued']} />,
},
watchOnlyWallet: {
title: t`Create watch-only wallet`,
subtitle: t`No key needed`,
icon: <Eye2Icon color={theme.colors['ink.text-subdued']} />,
},
};
return UNAVAILABLE_FEATURES;
}

interface AddWalletSheetBaseProps {
addWalletSheetRef: RefObject<SheetRef>;
}
Expand All @@ -78,7 +54,7 @@ export function AddWalletSheetLayout({
const [moreOptionsVisible, setMoreOptionsVisible] = useState(false);
const animatedIndex = useSharedValue<number>(CLOSED_ANIMATED_SHARED_VALUE);
const theme = useTheme<Theme>();

const router = useRouter();
function openOptions() {
setMoreOptionsVisible(!moreOptionsVisible);
}
Expand All @@ -87,6 +63,42 @@ export function AddWalletSheetLayout({
marginBottom: interpolate(animatedIndex.value, [-1, 0], [200, 0], Extrapolation.CLAMP),
}));

function getUnavailableFeatures(theme: Theme) {
const UNAVAILABLE_FEATURES = {
hardwareWallet: {
title: t`Connect hardware wallet`,
subtitle: t`Ledger, Trezor, Ryder and more`,
icon: <SignalIcon color={theme.colors['ink.text-subdued']} />,
onPress() {
router.navigate(AppRoutes.HardwareWallets);
addWalletSheetRef.current?.close();
},
},
emailRestore: {
title: t`Create or restore via email`,
subtitle: t`Access custodial wallet`,
icon: <EmailIcon color={theme.colors['ink.text-subdued']} />,
onPress: undefined,
},
mpcWallet: {
title: t`Connect MPC wallet`,
subtitle: t`Import existing accounts`,
icon: <PaletteIcon color={theme.colors['ink.text-subdued']} />,
onPress() {
router.navigate(AppRoutes.MpcWallets);
addWalletSheetRef.current?.close();
},
},
watchOnlyWallet: {
title: t`Create watch-only wallet`,
subtitle: t`No key needed`,
icon: <Eye2Icon color={theme.colors['ink.text-subdued']} />,
onPress: undefined,
},
};
return UNAVAILABLE_FEATURES;
}

return (
<Sheet
isScrollView
Expand Down Expand Up @@ -128,12 +140,13 @@ export function AddWalletSheetLayout({
? null
: Object.entries(getUnavailableFeatures(theme)).map(featureEntry => {
const [featureKey, feature] = featureEntry;
function fallbackOnPress() {
onOpenNotificationsSheet({ title: feature.title, id: featureKey });
}
return (
<AddWalletListItem
key={featureKey}
onPress={() =>
onOpenNotificationsSheet({ title: feature.title, id: featureKey })
}
onPress={feature.onPress ?? fallbackOnPress}
title={feature.title}
subtitle={feature.subtitle}
icon={feature.icon}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,6 @@ import { LockIcon, SheetRef } from '@leather.io/ui/native';

import { InputSheet } from '../sheets/input-sheet.layout';

export interface OptionData {
title: string;
id: string;
}

interface RecoverWalletSheetProps {
recoverWalletSheetRef: RefObject<SheetRef>;
passphrase: string;
Expand Down
5 changes: 0 additions & 5 deletions apps/mobile/src/components/sheets/input-sheet.layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ import {

import { TextInput } from '../text-input';

export interface OptionData {
title: string;
id: string;
}

interface InputSheetProps {
sheetRef: RefObject<SheetRef>;
initialValue: string;
Expand Down
Loading

0 comments on commit 3a4b659

Please sign in to comment.