Skip to content

Commit

Permalink
Feat(Mobile): private key and safe account management
Browse files Browse the repository at this point in the history
* Update docs (#53)

* docs: move contributing to code-style

The contributing doc is actually a code-style doc. Once we move
everything to monorepo we will adopt the safe-wallet-web
CONTRIBUTING.md file.

* docs: add release procedure docs

(cherry picked from commit 01345f0)

* Feat: Add create/get private key hook (#59)

* feat: add necessary libraries

* feat: create useSign hook

* feat: cover sign hook with unit tests

* Update apps/mobile/src/hooks/useSign/useSign.ts

Co-authored-by: katspaugh <[email protected]>

* fix: adjust type

Co-authored-by: katspaugh <[email protected]>

* feat: add react-native-quick-crypto back

* fix: lint uintGenericArray problem

* fix: adjust export style

* fix: remove duplicated lines from gitignore

* chore: add reference link

* fix: typo on useSign unit tests

* chore: store iv in the keychain

* Update apps/mobile/src/hooks/useSign/useSign.ts

Co-authored-by: Usame Algan <[email protected]>

* fix: typo in unit tests

---------

Co-authored-by: katspaugh <[email protected]>
Co-authored-by: Usame Algan <[email protected]>
(cherry picked from commit 45e82f7)

* feat: create account management functionality (#60)

* feat: create useSign hook

* feat: add react-native-quick-crypto back

* feat: create chainsDisplay component to show the grouped chains images

* chore: move redux provider to the top of the react three to make the bottomSheet provider able to read values from redux selectors

* feat: create badge variant for using the badge component in the chainsDisplay component according to figma

* feat: add testIDs in the chainsDisplay component

* feat: allow user to provider a footer to the dropdown component

* chore: make the logo size dynamically

* feat: create AccountCard component

* feat: create AccountItem component to handle the dropdown events and specific layout

* feat: replace the image component by the chainsDisplay component in the Balances component

* feat: change the color of the active chain in the chain selection dropdown

* feat: create a footer for MyAccounts dropdown

* feat: remove unnecessary tests

* feat: add accounts management feature inside the navbar dropdown

* feat: create useInfiniteScroll hook to handle infinite scroll functionality

* feat: use activeChain information into the tokens container

* feat: make the Identicon component to be more extensible

* feat: add mocked constants inside the store folder

* feat: create safes slice to store all safes added into the app

* feat: add possibility to get all supported chains ids and get them also by id

* chore: auto generated types

* chore: remove unused types

* feat: create MyAccounts container

* feat: use isFetching instead isLoading to avoid cached result while query is being revalidated

* chore: memoize the chains manipulation in the chainsDisplay component

* chore: create an useMyAccountsService hook to handle pos-fetch logic outside the container

(cherry picked from commit be1a137b30984b8fcc866fd6767824f5f67ec659)

* chore: update yarn.lock

* chore: link code style file with CONTRIBUTING.md

---------

Co-authored-by: Daniel Dimitrov <[email protected]>
  • Loading branch information
clovisdasilvaneto and compojoom authored Dec 27, 2024
1 parent 4ee2c26 commit 0532402
Show file tree
Hide file tree
Showing 62 changed files with 2,896 additions and 407 deletions.
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ When contributing to this repository, please first discuss the change you wish t

Please note we have a Code of Conduct (see below), please follow it in all your interactions with the project.

## Code Style

More information [here](./docs/code-style.md)

## CLA

It is a requirement for all contributors to sign the [Contributor License Agreement (CLA)](https://safe.global/cla) in order to proceed with their contribution.
Expand Down
60 changes: 43 additions & 17 deletions apps/mobile/.gitignore
Original file line number Diff line number Diff line change
@@ -1,15 +1,3 @@
node_modules/
.expo/
dist/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/

# Auto generated storybook file
.storybook/storybook.requires.ts

Expand All @@ -20,11 +8,6 @@ coverage
# macOS
.DS_Store

# @generated expo-cli sync-2b81b286409207a5da26e14c78851eb30d8ccbdb
# The following patterns were generated by expo-cli

expo-env.d.ts
# @end expo-cli
/.idea
# Tamagui UI generates a lot of cache files
.tamagui
Expand All @@ -35,3 +18,46 @@ expo-env.d.ts
# Android and iOS build files
/android/*
/ios/*

# @generated expo-cli sync-8d4afeec25ea8a192358fae2f8e2fc766bdce4ec
# The following patterns were generated by expo-cli

# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files

# dependencies
node_modules/

# Expo
.expo/
dist/
web-build/
expo-env.d.ts

# Native
*.orig.*
*.
*.jks
*.p8
*.p12
*.key
*.mobileprovision

# Metro
.metro-health-check*

# debug
npm-debug.*
yarn-debug.*
yarn-error.*

# macOS
.DS_Store
*.pem

# local env files
.env*.local

# typescript
*.tsbuildinfo

# @end expo-cli
3 changes: 3 additions & 0 deletions apps/mobile/app.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ export default {
config: {
usesNonExemptEncryption: false,
},
infoPlist: {
NSFaceIDUsageDescription: 'Enabling Face ID allows you to create/access secure keys.',
},
supportsTablet: true,
appleTeamId: 'MXRS32BBL4',
bundleIdentifier: IS_DEV ? 'global.safe.mobileapp.dev' : 'global.safe.mobileapp',
Expand Down
19 changes: 11 additions & 8 deletions apps/mobile/app/_layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import { PortalProvider } from '@tamagui/portal'
import { SafeToastProvider } from '@/src/theme/provider/toastProvider'
import { configureReanimatedLogger, ReanimatedLogLevel } from 'react-native-reanimated'
import { OnboardingHeader } from '@/src/features/Onboarding/components/OnboardingHeader'
import { install } from 'react-native-quick-crypto'

install()

configureReanimatedLogger({
level: ReanimatedLogLevel.warn,
Expand All @@ -23,10 +26,10 @@ function RootLayout() {
store.dispatch(apiSliceWithChainsConfig.endpoints.getChainsConfig.initiate())

return (
<PortalProvider shouldAddRootHost>
<GestureHandlerRootView>
<BottomSheetModalProvider>
<Provider store={store}>
<Provider store={store}>
<PortalProvider shouldAddRootHost>
<GestureHandlerRootView>
<BottomSheetModalProvider>
<PersistGate loading={null} persistor={persistor}>
<SafeThemeProvider>
<SafeToastProvider>
Expand Down Expand Up @@ -60,10 +63,10 @@ function RootLayout() {
</SafeToastProvider>
</SafeThemeProvider>
</PersistGate>
</Provider>
</BottomSheetModalProvider>
</GestureHandlerRootView>
</PortalProvider>
</BottomSheetModalProvider>
</GestureHandlerRootView>
</PortalProvider>
</Provider>
)
}

Expand Down
8 changes: 8 additions & 0 deletions apps/mobile/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { registerRootComponent } from 'expo';

import App from './App';

// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);
5 changes: 5 additions & 0 deletions apps/mobile/metro.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ config.resolver.resolveRequest = (context, moduleName, platform) => {
}
}

if (moduleName === 'crypto') {
// when importing crypto, resolve to react-native-quick-crypto
return context.resolveRequest(context, 'react-native-quick-crypto', platform)
}

return defaultResolveResult
}

Expand Down
6 changes: 6 additions & 0 deletions apps/mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
},
"dependencies": {
"@cowprotocol/app-data": "^2.3.0",
"@ethersproject/shims": "^5.7.0",
"@expo/config-plugins": "^9.0.10",
"@expo/vector-icons": "^14.0.2",
"@react-native-clipboard/clipboard": "^1.15.0",
Expand All @@ -60,6 +61,7 @@
"burnt": "^0.12.2",
"date-fns": "^4.1.0",
"deepmerge": "^4.3.1",
"ethers": "^6.13.4",
"expo": "~52.0.14",
"expo-blur": "~14.0.1",
"expo-constants": "~17.0.2",
Expand All @@ -79,9 +81,13 @@
"react-dom": "^18.3.1",
"react-native": "0.76.3",
"react-native-collapsible-tab-view": "^8.0.0",
"react-native-device-crypto": "^0.1.7",
"react-native-device-info": "^14.0.1",
"react-native-gesture-handler": "~2.20.2",
"react-native-keychain": "^9.2.2",
"react-native-mmkv": "^3.1.0",
"react-native-pager-view": "6.5.1",
"react-native-quick-crypto": "^0.7.10",
"react-native-reanimated": "^3.16.2",
"react-native-safe-area-context": "4.12.0",
"react-native-screens": "^4.0.0",
Expand Down
5 changes: 4 additions & 1 deletion apps/mobile/src/components/Badge/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ interface BadgeProps {
circleProps?: Partial<CircleProps>
textContentProps?: Partial<TextProps>
circular?: boolean
testID?: string
}

export const Badge = ({
Expand All @@ -25,6 +26,7 @@ export const Badge = ({
circular = true,
circleProps,
textContentProps,
testID,
}: BadgeProps) => {
let contentToRender = content
if (typeof content === 'string') {
Expand All @@ -38,7 +40,7 @@ export const Badge = ({
if (circular) {
return (
<Theme name={themeName}>
<Circle size={circleSize} backgroundColor={'$background'} {...circleProps}>
<Circle testID={testID} size={circleSize} backgroundColor={'$background'} {...circleProps}>
{contentToRender}
</Circle>
</Theme>
Expand All @@ -47,6 +49,7 @@ export const Badge = ({
return (
<Theme name={themeName}>
<View
testID={testID}
alignSelf={'flex-start'}
paddingVertical="$1"
paddingHorizontal="$3"
Expand Down
8 changes: 8 additions & 0 deletions apps/mobile/src/components/Badge/theme.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,12 @@ export const badgeTheme = {
color: tokens.color.warning1ContrastTextDark,
background: tokens.color.warningDarkDark,
},
dark_badge_background: {
color: tokens.color.textPrimaryDark,
background: tokens.color.logoBackgroundDark,
},
light_badge_background: {
color: tokens.color.textPrimaryLight,
background: tokens.color.logoBackgroundLight,
},
}
45 changes: 45 additions & 0 deletions apps/mobile/src/components/ChainsDisplay/ChainsDisplay.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type { Meta, StoryObj } from '@storybook/react'
import { ChainsDisplay } from '@/src/components/ChainsDisplay'
import { mockedChains } from '@/src/store/constants'
import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains'

const meta: Meta<typeof ChainsDisplay> = {
title: 'ChainsDisplay',
component: ChainsDisplay,
argTypes: {},
}

export default meta

type Story = StoryObj<typeof ChainsDisplay>

export const Default: Story = {
args: {
chains: mockedChains as unknown as Chain[],
max: 3,
},
parameters: {
layout: 'fullscreen',
},
}

export const Truncated: Story = {
args: {
chains: mockedChains as unknown as Chain[],
max: 1,
},
parameters: {
layout: 'fullscreen',
},
}

export const ActiveChain: Story = {
args: {
chains: mockedChains as unknown as Chain[],
activeChainId: mockedChains[1].chainId,
max: 1,
},
parameters: {
layout: 'fullscreen',
},
}
30 changes: 30 additions & 0 deletions apps/mobile/src/components/ChainsDisplay/ChainsDisplay.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { mockedChains } from '@/src/store/constants'
import { ChainsDisplay } from './ChainsDisplay'
import { render } from '@testing-library/react-native'
import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains'

describe('ChainsDisplay', () => {
it('should render all chains next each other', () => {
const container = render(<ChainsDisplay chains={mockedChains as unknown as Chain[]} max={mockedChains.length} />)

expect(container.getAllByTestId('chain-display')).toHaveLength(3)
})
it('should truncate the chains when the provided chains length is greatter than the max', () => {
const container = render(<ChainsDisplay chains={mockedChains as unknown as Chain[]} max={2} />)
const moreChainsBadge = container.getByTestId('more-chains-badge')

expect(container.getAllByTestId('chain-display')).toHaveLength(2)
expect(moreChainsBadge).toBeVisible()
expect(moreChainsBadge).toHaveTextContent('+1')
})

it('should always show the selected chain as the first column of the row', () => {
const container = render(
<ChainsDisplay chains={mockedChains as unknown as Chain[]} max={2} activeChainId={mockedChains[2].chainId} />,
)

expect(container.getAllByTestId('chain-display')[0].children[0].props.accessibilityLabel).toBe(
mockedChains[2].chainName,
)
})
})
34 changes: 34 additions & 0 deletions apps/mobile/src/components/ChainsDisplay/ChainsDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Chain } from '@safe-global/store/gateway/AUTO_GENERATED/chains'
import React, { useMemo } from 'react'
import { View } from 'tamagui'
import { Logo } from '../Logo'
import { Badge } from '../Badge'

interface ChainsDisplayProps {
chains: Chain[]
max?: number
activeChainId?: string
}

export function ChainsDisplay({ chains, activeChainId, max }: ChainsDisplayProps) {
const orderedChains = useMemo(
() => [...chains].sort((a, b) => (a.chainId === activeChainId ? -1 : b.chainId === activeChainId ? 1 : 0)),
[chains],
)
const slicedChains = max ? orderedChains.slice(0, max) : chains
const showBadge = max && chains.length > max

return (
<View flexDirection="row">
{slicedChains.map(({ chainLogoUri, chainName, chainId }, index) => (
<View key={chainId} testID="chain-display" marginRight={(showBadge || index !== slicedChains.length - 1) && -8}>
<Logo size="$7" logoUri={chainLogoUri} accessibilityLabel={chainName} />
</View>
))}

{showBadge && (
<Badge testID="more-chains-badge" content={`+${chains.length - max}`} themeName="badge_background" />
)}
</View>
)
}
1 change: 1 addition & 0 deletions apps/mobile/src/components/ChainsDisplay/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ChainsDisplay } from './ChainsDisplay'
Loading

0 comments on commit 0532402

Please sign in to comment.