diff --git a/package.json b/package.json index c782e791f0..ba7f060835 100644 --- a/package.json +++ b/package.json @@ -57,5 +57,13 @@ "node": ">=20.18.0", "yarn": ">=3.6.4" }, - "packageManager": "yarn@3.6.4" + "packageManager": "yarn@3.6.4", + "dependencies": { + "@ethereumjs/util": "9.1.0", + "@keystonehq/animated-qr": "0.10.0", + "@keystonehq/keystone-sdk": "0.11.3", + "@ngraveio/bc-ur": "1.1.13", + "react-native-popover-view": "6.1.0", + "react-native-progress": "5.0.1" + } } diff --git a/packages/core-mobile/app/assets/icons/keystone.svg b/packages/core-mobile/app/assets/icons/keystone.svg new file mode 100644 index 0000000000..49fa4eae90 --- /dev/null +++ b/packages/core-mobile/app/assets/icons/keystone.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/packages/core-mobile/app/assets/icons/keystone_logo_dark.svg b/packages/core-mobile/app/assets/icons/keystone_logo_dark.svg new file mode 100644 index 0000000000..327f33a89f --- /dev/null +++ b/packages/core-mobile/app/assets/icons/keystone_logo_dark.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/packages/core-mobile/app/assets/icons/keystone_logo_light.svg b/packages/core-mobile/app/assets/icons/keystone_logo_light.svg new file mode 100644 index 0000000000..5066b1532e --- /dev/null +++ b/packages/core-mobile/app/assets/icons/keystone_logo_light.svg @@ -0,0 +1,4 @@ + + + + diff --git a/packages/core-mobile/app/assets/icons/qrcode_dark.svg b/packages/core-mobile/app/assets/icons/qrcode_dark.svg new file mode 100644 index 0000000000..5b0bfae405 --- /dev/null +++ b/packages/core-mobile/app/assets/icons/qrcode_dark.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/core-mobile/app/assets/icons/qrcode_light.svg b/packages/core-mobile/app/assets/icons/qrcode_light.svg new file mode 100644 index 0000000000..77c7f7ba09 --- /dev/null +++ b/packages/core-mobile/app/assets/icons/qrcode_light.svg @@ -0,0 +1,3 @@ + + + diff --git a/packages/core-mobile/app/new/common/components/KeystoneQrScanner.tsx b/packages/core-mobile/app/new/common/components/KeystoneQrScanner.tsx new file mode 100644 index 0000000000..c7eb75b06f --- /dev/null +++ b/packages/core-mobile/app/new/common/components/KeystoneQrScanner.tsx @@ -0,0 +1,136 @@ +import React, { useState, useCallback, useEffect } from 'react' +import { UR, URDecoder } from '@ngraveio/bc-ur' +import * as Progress from 'react-native-progress' +import { View, Text, SCREEN_WIDTH, useTheme } from '@avalabs/k2-alpine' +import { showKeystoneTroubleshooting } from 'features/keystone/utils' +import { QrCodeScanner } from './QrCodeScanner' +import { Space } from './Space' + +const SCANNER_WIDTH = SCREEN_WIDTH - 64 + +interface Props { + urTypes: string[] + onSuccess: (ur: UR) => void + onError?: () => void + info?: string +} + +export const KeystoneQrScanner: (props: Props) => JSX.Element = ({ + info, + urTypes, + onSuccess, + onError +}) => { + const [urDecoder, setUrDecoder] = useState(new URDecoder()) + const [progress, setProgress] = useState(0) + const [showTroubleshooting, setShowTroubleshooting] = useState(false) + const { theme } = useTheme() + + const progressColor = theme.isDark ? theme.colors.$white : theme.colors.$black + const handleError = useCallback(() => { + setUrDecoder(new URDecoder()) + setShowTroubleshooting(true) + if (onError) { + onError() + } + }, [onError]) + + const showErrorSheet = useCallback(() => { + showKeystoneTroubleshooting({ + errorCode: -1, + retry: () => { + setShowTroubleshooting(false) + setProgress(0) + } + }) + }, []) + + useEffect(() => { + if (showTroubleshooting && !onError) { + showErrorSheet() + } + }, [showTroubleshooting, showErrorSheet, onError]) + + const handleScan = useCallback( + (code: string) => { + if (showTroubleshooting) { + return + } + try { + urDecoder.receivePart(code) + if (!urDecoder.isComplete()) { + setProgress(urDecoder.estimatedPercentComplete()) + return + } + + if (urDecoder.isError()) { + handleError() + } + + if (urDecoder.isSuccess()) { + const ur = urDecoder.resultUR() + + if (urTypes.includes(ur.type)) { + setProgress(1) + + onSuccess(ur) + + setTimeout(() => { + setUrDecoder(new URDecoder()) + setProgress(0) + }) + } else { + throw new Error('Invalid qr code') + } + } + } catch (error) { + handleError() + } + }, + [ + setProgress, + onSuccess, + urDecoder, + urTypes, + handleError, + showTroubleshooting + ] + ) + + return ( + + + + + + {info && ( + <> + + {info} + + )} + + + ) +} diff --git a/packages/core-mobile/app/new/common/components/NavigationRedirect.tsx b/packages/core-mobile/app/new/common/components/NavigationRedirect.tsx index f81ddb7671..119962be30 100644 --- a/packages/core-mobile/app/new/common/components/NavigationRedirect.tsx +++ b/packages/core-mobile/app/new/common/components/NavigationRedirect.tsx @@ -69,6 +69,7 @@ export const NavigationRedirect = (): null => { } else if ( pathName === '/onboarding/mnemonic/confirmation' || pathName === '/onboarding/seedless/confirmation' || + pathName === '/onboarding/keystone/confirmation' || (pathName === '/loginWithPinOrBiometry' && !isSignedIn) ) { // @ts-ignore TODO: make routes typesafe diff --git a/packages/core-mobile/app/new/common/components/QrCodeScanner.tsx b/packages/core-mobile/app/new/common/components/QrCodeScanner.tsx index 44374f4263..9e43d4f578 100644 --- a/packages/core-mobile/app/new/common/components/QrCodeScanner.tsx +++ b/packages/core-mobile/app/new/common/components/QrCodeScanner.tsx @@ -24,12 +24,14 @@ type Props = { onSuccess: (data: string) => void vibrate?: boolean sx?: SxProp + paused?: boolean } export const QrCodeScanner = ({ onSuccess, vibrate = false, - sx + sx, + paused = false }: Props): React.JSX.Element | undefined => { const { theme: { colors } @@ -40,20 +42,27 @@ export const QrCodeScanner = ({ ) const [data, setData] = useState() - const handleSuccess = (scanningResult: BarcodeScanningResult): void => { - // expo-camera's onBarcodeScanned callback is not debounced, so we need to debounce it ourselves - setData(scanningResult.data) - } + useEffect(() => { + if (paused) { + setData(undefined) + } + }, [paused, setData]) useEffect(() => { - if (data) { + if (data && !paused) { onSuccess(data) if (vibrate) { notificationAsync(NotificationFeedbackType.Success) } } - }, [data, onSuccess, vibrate]) + }, [data, onSuccess, vibrate, paused]) + + const handleSuccess = (scanningResult: BarcodeScanningResult): void => { + if (paused) return + // expo-camera's onBarcodeScanned callback is not debounced, so we need to debounce it ourselves + setData(scanningResult.data) + } const checkIosPermission = useCallback(async () => { if ( diff --git a/packages/core-mobile/app/new/common/hooks/useCombinedPrimaryNetworks.ts b/packages/core-mobile/app/new/common/hooks/useCombinedPrimaryNetworks.ts index 186dc9891a..bb9f606f1f 100644 --- a/packages/core-mobile/app/new/common/hooks/useCombinedPrimaryNetworks.ts +++ b/packages/core-mobile/app/new/common/hooks/useCombinedPrimaryNetworks.ts @@ -9,6 +9,8 @@ import { } from 'services/network/consts' import { selectIsSolanaSupportBlocked } from 'store/posthog/slice' import { selectIsDeveloperMode } from 'store/settings/advanced' +import { WalletType } from 'services/wallet/types' +import { useActiveWallet } from './useActiveWallet' /** * Hook to get the combined primary networks (networks are merged together with same address) @@ -21,20 +23,24 @@ export function useCombinedPrimaryNetworks(): { } { const isDeveloperMode = useSelector(selectIsDeveloperMode) const isSolanaSupportBlocked = useSelector(selectIsSolanaSupportBlocked) + const wallet = useActiveWallet() const networks = useMemo(() => { + const blockSolana = + isSolanaSupportBlocked || wallet.type === WalletType.KEYSTONE + // Test networks if (isDeveloperMode) { - return isSolanaSupportBlocked + return blockSolana ? (TEST_PRIMARY_NETWORKS as Network[]) : [...TEST_PRIMARY_NETWORKS, NETWORK_SOLANA_DEVNET] } // Main networks - return isSolanaSupportBlocked + return blockSolana ? (MAIN_PRIMARY_NETWORKS as Network[]) : [...MAIN_PRIMARY_NETWORKS, NETWORK_SOLANA] - }, [isDeveloperMode, isSolanaSupportBlocked]) + }, [isDeveloperMode, isSolanaSupportBlocked, wallet.type]) return { networks } } diff --git a/packages/core-mobile/app/new/common/hooks/useManageWallet.ts b/packages/core-mobile/app/new/common/hooks/useManageWallet.ts index a760fde5ef..438027300c 100644 --- a/packages/core-mobile/app/new/common/hooks/useManageWallet.ts +++ b/packages/core-mobile/app/new/common/hooks/useManageWallet.ts @@ -125,13 +125,18 @@ export const useManageWallet = (): { // 1. Seedless wallets cannot be removed if (wallet.type === WalletType.SEEDLESS) return false - // 2. Mnemonic wallets can be removed if there are multiple mnemonic or seedless wallets + // 2. Mnemonic wallets can be removed if there are multiple mnemonic/seedless/keystone wallets const walletCount = Object.values(wallets).filter( - w => w.type === WalletType.MNEMONIC || w.type === WalletType.SEEDLESS + w => + w.type === WalletType.MNEMONIC || + w.type === WalletType.SEEDLESS || + w.type === WalletType.KEYSTONE ).length const isLastRemovableMnemonic = - walletCount === 1 && wallet.type === WalletType.MNEMONIC + walletCount === 1 && + (wallet.type === WalletType.MNEMONIC || + wallet.type === WalletType.KEYSTONE) return !isLastRemovableMnemonic }, @@ -155,7 +160,13 @@ export const useManageWallet = (): { }) } - if ([WalletType.MNEMONIC, WalletType.SEEDLESS].includes(wallet.type)) { + if ( + [ + WalletType.MNEMONIC, + WalletType.SEEDLESS, + WalletType.KEYSTONE + ].includes(wallet.type) + ) { baseItems.push({ id: 'add_account', title: 'Add account to this wallet' diff --git a/packages/core-mobile/app/new/features/keystone/screens/KeystoneSignerScreen/index.tsx b/packages/core-mobile/app/new/features/keystone/screens/KeystoneSignerScreen/index.tsx new file mode 100644 index 0000000000..da976c6fbc --- /dev/null +++ b/packages/core-mobile/app/new/features/keystone/screens/KeystoneSignerScreen/index.tsx @@ -0,0 +1,199 @@ +import { Button, Text, useTheme, View } from '@avalabs/k2-alpine' +import React, { FC, useCallback, useEffect, useState } from 'react' +import { withWalletConnectCache } from 'common/components/withWalletConnectCache' +import { KeystoneSignerParams } from 'services/walletconnectv2/walletConnectCache/types' +import { useNavigation } from 'expo-router' +import { ScrollScreen } from 'common/components/ScrollScreen' +import { UREncoder } from '@ngraveio/bc-ur' +import { Space } from 'common/components/Space' +import QRCode from 'react-native-qrcode-svg' +import { Dimensions, BackHandler } from 'react-native' +import { KeystoneQrScanner } from 'common/components/KeystoneQrScanner' +import KeystoneLogoLight from 'assets/icons/keystone_logo_light.svg' +import KeystoneLogoDark from 'assets/icons/keystone_logo_dark.svg' +import { useSelector } from 'react-redux' +import { selectIsKeystoneBlocked } from 'store/posthog' + +enum KeystoneSignerStep { + QR, + Scanner +} + +const KeystoneSignerScreen = ({ + params +}: { + params: KeystoneSignerParams +}): JSX.Element => { + const navigation = useNavigation() + const { request, responseURTypes, onApprove, onReject } = params + const [currentStep, setCurrentStep] = useState(KeystoneSignerStep.QR) + const [signningUr, setSigningUr] = useState('(null)') + const isKeystoneBlocked = useSelector(selectIsKeystoneBlocked) + + useEffect(() => { + const urEncoder = new UREncoder(request, 150) + const timer = setInterval(() => { + setSigningUr(urEncoder.nextPart()) + }, 200) + return () => { + clearInterval(timer) + } + }, [request]) + + const rejectAndClose = useCallback( + (message?: string) => { + onReject(message) + navigation.goBack() + }, + [navigation, onReject] + ) + + useEffect(() => { + if (isKeystoneBlocked) { + rejectAndClose() + } + }, [isKeystoneBlocked, rejectAndClose]) + + useEffect(() => { + const onBackPress = (): boolean => { + // modal is being dismissed via physical back button + rejectAndClose() + return false + } + + const backHandler = BackHandler.addEventListener( + 'hardwareBackPress', + onBackPress + ) + + return () => backHandler.remove() + }, [rejectAndClose]) + + useEffect(() => { + return navigation.addListener('beforeRemove', e => { + if ( + e.data.action.type === 'POP' // gesture dismissed + ) { + // modal is being dismissed via gesture or back button + rejectAndClose() + } + }) + }, [navigation, rejectAndClose]) + + return ( + + + {currentStep === KeystoneSignerStep.QR && ( + <> + + + + )} + {currentStep === KeystoneSignerStep.Scanner && ( + <> +
Scan the QR Code
+ + + )} +
+
+ ) +} + +const { width: screenWidth } = Dimensions.get('window') + +const Header: FC<{ + children: React.ReactNode +}> = ({ children }) => { + const { theme } = useTheme() + + return ( + <> + {theme.isDark ? : } + + {children} + + + ) +} + +const QRRenderer: FC<{ + data: string +}> = ({ data }) => { + const { theme } = useTheme() + const borderWidth = 16 + const containerSize = screenWidth * 0.7 + const qrCodeSize = containerSize - borderWidth * 2 + + return ( + <> +
Scan the QR Code
+ Scan the QR code via your Keystone device + + + + + Click on the 'Get Signature' button after signing the transaction with + your Keystone device. + + + ) +} + +const QRScanner: FC> = ({ + onApprove, + responseURTypes +}) => { + const navigation = useNavigation() + + return ( + + onApprove(ur.cbor).finally(navigation.goBack)} + info={ + 'Place the QR code from your Keystone device in front of the camera.' + } + /> + + ) +} + +export default withWalletConnectCache('keystoneSignerParams')( + KeystoneSignerScreen +) diff --git a/packages/core-mobile/app/new/features/keystone/screens/keystoneTroubleshooting/index.tsx b/packages/core-mobile/app/new/features/keystone/screens/keystoneTroubleshooting/index.tsx new file mode 100644 index 0000000000..00c6473da7 --- /dev/null +++ b/packages/core-mobile/app/new/features/keystone/screens/keystoneTroubleshooting/index.tsx @@ -0,0 +1,127 @@ +import React, { useCallback, useEffect } from 'react' +import { withWalletConnectCache } from 'common/components/withWalletConnectCache' +import { KeystoneTroubleshootingParams } from 'services/walletconnectv2/walletConnectCache/types' +import { useNavigation, Link } from 'expo-router' +import { ScrollScreen } from 'common/components/ScrollScreen' +import { BackHandler } from 'react-native' +import { View, Text, SCREEN_WIDTH, Button, useTheme } from '@avalabs/k2-alpine' +import { Space } from 'common/components/Space' +import { Steps } from 'features/onboarding/components/KeystoneTroubleshooting' + +const KeystoneTroubleshootingScreen = ({ + params +}: { + params: KeystoneTroubleshootingParams +}): JSX.Element => { + const navigation = useNavigation() + const { retry } = params + + const closeAndRetry = useCallback(() => { + retry() + navigation.goBack() + }, [navigation, retry]) + + useEffect(() => { + const onBackPress = (): boolean => { + // modal is being dismissed via physical back button + closeAndRetry() + return false + } + + const backHandler = BackHandler.addEventListener( + 'hardwareBackPress', + onBackPress + ) + + return () => backHandler.remove() + }, [closeAndRetry]) + + useEffect(() => { + return navigation.addListener('beforeRemove', e => { + if ( + e.data.action.type === 'POP' // gesture dismissed + ) { + // modal is being dismissed via gesture or back button + closeAndRetry() + } + }) + }, [navigation, closeAndRetry]) + + return ( + + + + Invalid QR Code + + Please ensure you have selected a valid QR code from your Keystone + device. + + + + + + + + Keystone Support + + + + + + + + ) +} + +export default withWalletConnectCache('keystoneTroubleshootingParams')( + KeystoneTroubleshootingScreen +) diff --git a/packages/core-mobile/app/new/features/keystone/services/KeystoneService.ts b/packages/core-mobile/app/new/features/keystone/services/KeystoneService.ts new file mode 100644 index 0000000000..d346a41b04 --- /dev/null +++ b/packages/core-mobile/app/new/features/keystone/services/KeystoneService.ts @@ -0,0 +1,39 @@ +import { UR } from '@ngraveio/bc-ur' +import KeystoneSDK from '@keystonehq/keystone-sdk' +import { fromPublicKey } from 'bip32' +import { KeystoneDataStorage } from 'features/keystone/storage/KeystoneDataStorage' + +class KeystoneService { + private walletInfo = { + evm: '', + xp: '', + mfp: '' + } + + init(ur: UR): void { + const sdk = new KeystoneSDK() + const accounts = sdk.parseMultiAccounts(ur) + const mfp = accounts.masterFingerprint + const ethAccount = accounts.keys.find(key => key.chain === 'ETH') + const avaxAccount = accounts.keys.find(key => key.chain === 'AVAX') + if (!ethAccount || !avaxAccount) { + throw new Error('No ETH or AVAX account found') + } + + this.walletInfo.evm = fromPublicKey( + Buffer.from(ethAccount.publicKey, 'hex'), + Buffer.from(ethAccount.chainCode, 'hex') + ).toBase58() + this.walletInfo.xp = fromPublicKey( + Buffer.from(avaxAccount.publicKey, 'hex'), + Buffer.from(avaxAccount.chainCode, 'hex') + ).toBase58() + this.walletInfo.mfp = mfp + } + + async save(): Promise { + await KeystoneDataStorage.save(this.walletInfo) + } +} + +export default new KeystoneService() diff --git a/packages/core-mobile/app/new/features/keystone/storage/KeystoneDataStorage.ts b/packages/core-mobile/app/new/features/keystone/storage/KeystoneDataStorage.ts new file mode 100644 index 0000000000..e7c12d54b3 --- /dev/null +++ b/packages/core-mobile/app/new/features/keystone/storage/KeystoneDataStorage.ts @@ -0,0 +1,33 @@ +import SecureStorageService, { KeySlot } from 'security/SecureStorageService' +import { assertNotUndefined } from 'utils/assertions' + +export type KeystoneDataStorageType = { + evm: string + xp: string + mfp: string +} + +export class KeystoneDataStorage { + private static cache: KeystoneDataStorageType | undefined = undefined + + static async save(keystoneData: KeystoneDataStorageType): Promise { + await SecureStorageService.store(KeySlot.KeystoneData, keystoneData) + + this.cache = keystoneData + } + + static async retrieve(): Promise { + if (this.cache?.mfp && this.cache?.xp && this.cache?.evm) { + return this.cache + } + + const walletInfo = await SecureStorageService.load( + KeySlot.KeystoneData + ) + assertNotUndefined(walletInfo.mfp, 'no mfp found') + assertNotUndefined(walletInfo.xp, 'no xp found') + assertNotUndefined(walletInfo.evm, 'no evm found') + + return walletInfo + } +} diff --git a/packages/core-mobile/app/new/features/keystone/utils/index.ts b/packages/core-mobile/app/new/features/keystone/utils/index.ts new file mode 100644 index 0000000000..acf1aaef02 --- /dev/null +++ b/packages/core-mobile/app/new/features/keystone/utils/index.ts @@ -0,0 +1,24 @@ +import { router } from 'expo-router' +import { KeystoneTroubleshootingParams } from 'services/walletconnectv2/walletConnectCache/types' +import { walletConnectCache } from 'services/walletconnectv2/walletConnectCache/walletConnectCache' +import { KeystoneSignerParams } from 'services/walletconnectv2/walletConnectCache/types' + +export const showKeystoneTroubleshooting = ( + params: KeystoneTroubleshootingParams +): void => { + walletConnectCache.keystoneTroubleshootingParams.set(params) + + router.navigate({ + // @ts-ignore + pathname: '/keystoneTroubleshooting' + }) +} + +export const requestKeystoneSigner = (params: KeystoneSignerParams): void => { + walletConnectCache.keystoneSignerParams.set(params) + + router.navigate({ + // @ts-ignore + pathname: '/keystoneSigner' + }) +} diff --git a/packages/core-mobile/app/new/features/onboarding/components/KeystoneTroubleshooting.tsx b/packages/core-mobile/app/new/features/onboarding/components/KeystoneTroubleshooting.tsx new file mode 100644 index 0000000000..653c579b6f --- /dev/null +++ b/packages/core-mobile/app/new/features/onboarding/components/KeystoneTroubleshooting.tsx @@ -0,0 +1,102 @@ +import { Button, Text, useTheme, View } from '@avalabs/k2-alpine' +import { ScrollScreen } from 'common/components/ScrollScreen' +import { Link } from 'expo-router' +import React, { FC, useCallback } from 'react' + +export const KeystoneTroubleshooting = ({ + retry +}: { + retry: () => void +}): JSX.Element => { + const renderFooter = useCallback(() => { + return ( + + + + + + Keystone Support + + + + + + + ) + }, [retry]) + + return ( + + + + Please ensure you have selected a valid QR code from your Keystone + device. + + + + + ) +} + +export const Steps: FC<{ steps: string[] }> = ({ steps }) => { + const { theme } = useTheme() + + return ( + + {steps.map((step, index) => ( + + {`Step ${index + 1}`} + + {step} + + + ))} + + ) +} diff --git a/packages/core-mobile/app/new/features/onboarding/components/RecoveryUsingKeystone.tsx b/packages/core-mobile/app/new/features/onboarding/components/RecoveryUsingKeystone.tsx new file mode 100644 index 0000000000..eb555affd0 --- /dev/null +++ b/packages/core-mobile/app/new/features/onboarding/components/RecoveryUsingKeystone.tsx @@ -0,0 +1,31 @@ +import React from 'react' +import { View } from '@avalabs/k2-alpine' +import { ScrollScreen } from 'common/components/ScrollScreen' +import { UR, URType } from '@keystonehq/keystone-sdk' +import { KeystoneQrScanner } from 'common/components/KeystoneQrScanner' + +export const RecoveryUsingKeystone = ({ + onSuccess, + onError +}: { + onSuccess: (ur: UR) => void + onError: () => void +}): JSX.Element => { + return ( + + + + + + ) +} diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/securityAndPrivacy.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/securityAndPrivacy.tsx index 7c7c83489b..73558b848a 100644 --- a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/securityAndPrivacy.tsx +++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/accountSettings/securityAndPrivacy.tsx @@ -136,6 +136,11 @@ const SecurityAndPrivacyScreen = (): JSX.Element => { useBiometrics ]) + const shouldHideRecoveryData = useMemo( + () => [WalletType.KEYSTONE].includes(wallet.type), + [wallet.type] + ) + const recoveryData = useMemo(() => { const data = [ { @@ -220,16 +225,20 @@ const SecurityAndPrivacyScreen = (): JSX.Element => { }} separatorMarginRight={16} /> - - + {!shouldHideRecoveryData && ( + <> + + + + )} + + + ) +} diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/keystoneSigner/index.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/keystoneSigner/index.tsx new file mode 100644 index 0000000000..111a09e101 --- /dev/null +++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/keystoneSigner/index.tsx @@ -0,0 +1 @@ +export { default } from 'features/keystone/screens/KeystoneSignerScreen' diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/keystoneTroubleshooting/_layout.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/keystoneTroubleshooting/_layout.tsx new file mode 100644 index 0000000000..4a96eda371 --- /dev/null +++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/keystoneTroubleshooting/_layout.tsx @@ -0,0 +1,12 @@ +import React from 'react' +import { Stack } from 'common/components/Stack' +import { useModalScreenOptions } from 'common/hooks/useModalScreenOptions' +export default function KeystoneSignerLayout(): JSX.Element { + const { modalStackNavigatorScreenOptions, modalFirstScreenOptions } = + useModalScreenOptions() + return ( + + + + ) +} diff --git a/packages/core-mobile/app/new/routes/(signedIn)/(modals)/keystoneTroubleshooting/index.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/keystoneTroubleshooting/index.tsx new file mode 100644 index 0000000000..3c962c62c0 --- /dev/null +++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/keystoneTroubleshooting/index.tsx @@ -0,0 +1 @@ +export { default } from 'features/keystone/screens/keystoneTroubleshooting' diff --git a/packages/core-mobile/app/new/routes/(signedIn)/_layout.tsx b/packages/core-mobile/app/new/routes/(signedIn)/_layout.tsx index 1b907fee49..2a53b6529a 100644 --- a/packages/core-mobile/app/new/routes/(signedIn)/_layout.tsx +++ b/packages/core-mobile/app/new/routes/(signedIn)/_layout.tsx @@ -57,6 +57,14 @@ export default function WalletLayout(): JSX.Element { return modalScreensOptions }} /> + + { const { theme } = useTheme() const { navigate } = useRouter() + const isKeystoneBlocked = useSelector(selectIsKeystoneBlocked) - const handleEnterRecoveryPhrase = (): void => { + const handleEnterRecoveryPhrase = useCallback((): void => { navigate({ // @ts-ignore TODO: make routes typesafe pathname: '/onboarding/mnemonic/', params: { recovering: 'true' } }) - } + }, [navigate]) - const handleCreateMnemonicWallet = (): void => { + const handleEnterKeystone = useCallback((): void => { + navigate({ + // @ts-ignore TODO: make routes typesafe + pathname: '/onboarding/keystone/termsAndConditions/' + }) + }, [navigate]) + + const handleCreateMnemonicWallet = useCallback((): void => { navigate({ // @ts-ignore TODO: make routes typesafe pathname: '/onboarding/mnemonic/' }) - } + }, [navigate]) + + const data = useMemo(() => { + const res = [] + res.push({ + title: 'Type in a recovery phrase', + leftIcon: , + onPress: handleEnterRecoveryPhrase + }) + if (!isKeystoneBlocked) { + res.push({ + title: 'Add using Keystone', + leftIcon: , + onPress: handleEnterKeystone + }) + } + res.push({ + title: 'Create a new wallet', + leftIcon: , + onPress: handleCreateMnemonicWallet + }) + return res + }, [ + handleCreateMnemonicWallet, + handleEnterKeystone, + handleEnterRecoveryPhrase, + isKeystoneBlocked, + theme.colors + ]) return ( { style={{ marginTop: 24 }}> - , - onPress: handleEnterRecoveryPhrase - }, - { - title: 'Create a new wallet', - leftIcon: , - onPress: handleCreateMnemonicWallet - } - ]} - itemHeight={60} - /> + ) diff --git a/packages/core-mobile/app/new/routes/onboarding/keystone/_layout.tsx b/packages/core-mobile/app/new/routes/onboarding/keystone/_layout.tsx new file mode 100644 index 0000000000..c8d1a6095c --- /dev/null +++ b/packages/core-mobile/app/new/routes/onboarding/keystone/_layout.tsx @@ -0,0 +1,49 @@ +import React, { useEffect, useMemo, useState } from 'react' +import { Stack } from 'common/components/Stack' +import { PageControl } from '@avalabs/k2-alpine' +import { stackNavigatorScreenOptions } from 'common/consts/screenOptions' +import { useRootNavigationState } from 'expo-router' +import { NavigationState } from '@react-navigation/native' + +export default function KeystoneOnboardingLayout(): JSX.Element { + const [currentPage, setCurrentPage] = useState(0) + const rootState: NavigationState = useRootNavigationState() + + const screens = useMemo(() => KEYSTONE_ONBOARDING_SCREENS, []) + + useEffect(() => { + const keystoneOnboardingRoute = rootState.routes + .find(route => route.name === 'onboarding') + ?.state?.routes.find(route => route.name === 'keystone') + if (keystoneOnboardingRoute?.state?.index !== undefined) { + setCurrentPage(keystoneOnboardingRoute.state.index) + } + }, [rootState]) + + const renderPageControl = (): React.ReactNode => ( + + ) + + return ( + + {screens.map(screen => { + return + })} + + ) +} + +const KEYSTONE_ONBOARDING_SCREENS = [ + 'termsAndConditions', + 'analyticsConsent', + 'recoveryUsingKeystone', + 'keystoneTroubleshooting', + 'createPin', + 'setWalletName', + 'selectAvatar', + 'confirmation' +] diff --git a/packages/core-mobile/app/new/routes/onboarding/keystone/analyticsConsent.tsx b/packages/core-mobile/app/new/routes/onboarding/keystone/analyticsConsent.tsx new file mode 100644 index 0000000000..9278f1a5ee --- /dev/null +++ b/packages/core-mobile/app/new/routes/onboarding/keystone/analyticsConsent.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import { useAnalyticsConsent } from 'hooks/useAnalyticsConsent' +import { useRouter } from 'expo-router' +import { AnalyticsConsent as Component } from 'features/onboarding/components/AnalyticsConsent' + +export default function AnalyticsConsent(): JSX.Element { + const { navigate } = useRouter() + const { accept, reject } = useAnalyticsConsent() + + const nextPathname = './recoveryUsingKeystone' + + function handleAcceptAnalytics(): void { + accept() + navigate(nextPathname) + } + + function handleRejectAnalytics(): void { + reject() + navigate(nextPathname) + } + + return ( + + ) +} diff --git a/packages/core-mobile/app/new/routes/onboarding/keystone/confirmation.tsx b/packages/core-mobile/app/new/routes/onboarding/keystone/confirmation.tsx new file mode 100644 index 0000000000..771001240f --- /dev/null +++ b/packages/core-mobile/app/new/routes/onboarding/keystone/confirmation.tsx @@ -0,0 +1,15 @@ +import { Confirmation as Component } from 'features/onboarding/components/Confirmation' +import { useWallet } from 'hooks/useWallet' +import React from 'react' +import { WalletType } from 'services/wallet/types' +import Logger from 'utils/Logger' + +export default function Confirmation(): JSX.Element { + const { login } = useWallet() + + const handleNext = (): void => { + login(WalletType.KEYSTONE).catch(Logger.error) + } + + return +} diff --git a/packages/core-mobile/app/new/routes/onboarding/keystone/createPin.tsx b/packages/core-mobile/app/new/routes/onboarding/keystone/createPin.tsx new file mode 100644 index 0000000000..5d9473d8e5 --- /dev/null +++ b/packages/core-mobile/app/new/routes/onboarding/keystone/createPin.tsx @@ -0,0 +1,79 @@ +import { useStoredBiometrics } from 'common/hooks/useStoredBiometrics' +import { useRouter } from 'expo-router' +import { CreatePin as Component } from 'features/onboarding/components/CreatePin' +import { useWallet } from 'hooks/useWallet' +import React, { useCallback } from 'react' +import { WalletType } from 'services/wallet/types' +import AnalyticsService from 'services/analytics/AnalyticsService' +import BiometricsSDK from 'utils/BiometricsSDK' +import Logger from 'utils/Logger' +import { uuid } from 'utils/uuid' +import { useSelector } from 'react-redux' +import { selectActiveWalletId } from 'store/wallet/slice' + +export default function CreatePin(): JSX.Element { + const { navigate } = useRouter() + const { onPinCreated } = useWallet() + const { isBiometricAvailable, useBiometrics, setUseBiometrics } = + useStoredBiometrics() + const activeWalletId = useSelector(selectActiveWalletId) + + const navigateToNextStep = useCallback(() => { + // @ts-ignore TODO: make routes typesafe + navigate('/onboarding/keystone/setWalletName') + }, [navigate]) + + const handleEnteredValidPin = useCallback( + (pin: string) => { + AnalyticsService.capture('OnboardingPasswordSet') + onPinCreated({ + walletId: activeWalletId ?? uuid(), + mnemonic: uuid(), + pin, + walletType: WalletType.KEYSTONE + }) + .then(() => { + if (useBiometrics) { + BiometricsSDK.enableBiometry() + .then(enabled => { + if (enabled) { + navigateToNextStep() + } else { + // If biometrics fails to enable, disable it and continue with PIN only + setUseBiometrics(false) + navigateToNextStep() + } + }) + .catch(error => { + Logger.error(error) + // On error, disable biometrics and continue with PIN only + setUseBiometrics(false) + navigateToNextStep() + }) + } else { + navigateToNextStep() + } + }) + .catch(Logger.error) + }, + [ + onPinCreated, + useBiometrics, + setUseBiometrics, + navigateToNextStep, + activeWalletId + ] + ) + + return ( + + ) +} diff --git a/packages/core-mobile/app/new/routes/onboarding/keystone/keystoneTroubleshooting.tsx b/packages/core-mobile/app/new/routes/onboarding/keystone/keystoneTroubleshooting.tsx new file mode 100644 index 0000000000..5c031b80bf --- /dev/null +++ b/packages/core-mobile/app/new/routes/onboarding/keystone/keystoneTroubleshooting.tsx @@ -0,0 +1,16 @@ +import React, { useCallback } from 'react' +import { useRouter } from 'expo-router' +import { KeystoneTroubleshooting as Component } from 'features/onboarding/components/KeystoneTroubleshooting' + +export default function KeystoneTroubleshooting(): JSX.Element { + const { replace } = useRouter() + + const retry = useCallback(() => { + replace({ + // @ts-ignore TODO: make routes typesafe + pathname: '/onboarding/keystone/recoveryUsingKeystone' + }) + }, [replace]) + + return +} diff --git a/packages/core-mobile/app/new/routes/onboarding/keystone/recoveryUsingKeystone.tsx b/packages/core-mobile/app/new/routes/onboarding/keystone/recoveryUsingKeystone.tsx new file mode 100644 index 0000000000..91860a0a88 --- /dev/null +++ b/packages/core-mobile/app/new/routes/onboarding/keystone/recoveryUsingKeystone.tsx @@ -0,0 +1,33 @@ +import React from 'react' +import { useRouter } from 'expo-router' +import { UR } from '@ngraveio/bc-ur' +import { RecoveryUsingKeystone as Component } from 'features/onboarding/components/RecoveryUsingKeystone' +import Logger from 'utils/Logger' +import KeystoneService from 'features/keystone/services/KeystoneService' + +export default function RecoveryUsingKeystone(): JSX.Element { + const { navigate, replace } = useRouter() + + function handleNext(ur: UR): void { + try { + KeystoneService.init(ur) + + navigate({ + // @ts-ignore TODO: make routes typesafe + pathname: '/onboarding/keystone/createPin' + }) + } catch (error: any) { + Logger.error(error.message) + throw new Error('Failed to parse UR') + } + } + + function handleError(): void { + replace({ + // @ts-ignore TODO: make routes typesafe + pathname: '/onboarding/keystone/keystoneTroubleshooting' + }) + } + + return +} diff --git a/packages/core-mobile/app/new/routes/onboarding/keystone/selectAvatar.tsx b/packages/core-mobile/app/new/routes/onboarding/keystone/selectAvatar.tsx new file mode 100644 index 0000000000..47015545c2 --- /dev/null +++ b/packages/core-mobile/app/new/routes/onboarding/keystone/selectAvatar.tsx @@ -0,0 +1,40 @@ +import { SelectAvatar as Component } from 'common/components/SelectAvatar' +import { useAvatar } from 'common/hooks/useAvatar' +import { useRouter } from 'expo-router' +import { useRandomAvatar } from 'features/onboarding/hooks/useRandomAvatar' +import { useRandomizedAvatars } from 'features/onboarding/hooks/useRandomizedAvatars' +import React, { useState } from 'react' + +export default function SelectAvatar(): JSX.Element { + const { navigate } = useRouter() + const { saveLocalAvatar } = useAvatar() + + const randomizedAvatars = useRandomizedAvatars() + const randomAvatar = useRandomAvatar(randomizedAvatars) + + const [selectedAvatar, setSelectedAvatar] = useState(randomAvatar) + + const onSubmit = (): void => { + if (selectedAvatar) { + saveLocalAvatar(selectedAvatar.id) + } + + navigate({ + // @ts-ignore TODO: make routes typesafe + pathname: '/onboarding/keystone/confirmation', + params: { selectedAvatarId: selectedAvatar?.id } + }) + } + + return ( + + ) +} diff --git a/packages/core-mobile/app/new/routes/onboarding/keystone/setWalletName.tsx b/packages/core-mobile/app/new/routes/onboarding/keystone/setWalletName.tsx new file mode 100644 index 0000000000..9e68e06ac0 --- /dev/null +++ b/packages/core-mobile/app/new/routes/onboarding/keystone/setWalletName.tsx @@ -0,0 +1,25 @@ +import React, { useState } from 'react' +import AnalyticsService from 'services/analytics/AnalyticsService' +import { useDispatch } from 'react-redux' +import { useRouter } from 'expo-router' +import { SetWalletName as Component } from 'features/onboarding/components/SetWalletName' +import { setWalletName } from 'store/wallet/slice' +import { useActiveWallet } from 'common/hooks/useActiveWallet' + +export default function SetWalletName(): JSX.Element { + const [name, setName] = useState('Wallet 1') + const dispatch = useDispatch() + const { navigate } = useRouter() + const activeWallet = useActiveWallet() + + const handleNext = (): void => { + AnalyticsService.capture('Onboard:WalletNameSet') + dispatch(setWalletName({ walletId: activeWallet.id, name })) + navigate({ + // @ts-ignore TODO: make routes typesafe + pathname: '/onboarding/keystone/selectAvatar' + }) + } + + return +} diff --git a/packages/core-mobile/app/new/routes/onboarding/keystone/termsAndConditions.tsx b/packages/core-mobile/app/new/routes/onboarding/keystone/termsAndConditions.tsx new file mode 100644 index 0000000000..490bdbefad --- /dev/null +++ b/packages/core-mobile/app/new/routes/onboarding/keystone/termsAndConditions.tsx @@ -0,0 +1,14 @@ +import React from 'react' +import { TermsAndConditions as Component } from 'features/onboarding/components/TermsAndConditions' +import { useRouter } from 'expo-router' + +export default function TermsAndConditions(): JSX.Element { + const { navigate } = useRouter() + + const handleAgreeAndContinue = (): void => { + // @ts-ignore TODO: make routes typesafe + navigate('/onboarding/keystone/analyticsConsent') + } + + return +} diff --git a/packages/core-mobile/app/security/SecureStorageService.ts b/packages/core-mobile/app/security/SecureStorageService.ts index 704ae78690..6c7f32de25 100644 --- a/packages/core-mobile/app/security/SecureStorageService.ts +++ b/packages/core-mobile/app/security/SecureStorageService.ts @@ -9,7 +9,8 @@ export enum KeySlot { SignerSessionData = 'SignerSessionData', SeedlessPubKeys = 'SeedlessPubKeysV2', OidcProvider = 'OidcProvider', - OidcUserId = 'OidcUserId' + OidcUserId = 'OidcUserId', + KeystoneData = 'KeystoneData' } /** diff --git a/packages/core-mobile/app/services/posthog/types.ts b/packages/core-mobile/app/services/posthog/types.ts index 5ed7eaef21..16ff7ebaf1 100644 --- a/packages/core-mobile/app/services/posthog/types.ts +++ b/packages/core-mobile/app/services/posthog/types.ts @@ -40,6 +40,7 @@ export enum FeatureGates { MELD_ONRAMP = 'meld-onramp', MELD_OFFRAMP = 'meld-offramp', SWAP_USE_MARKR = 'swap-use-markr', + KEYSTONE = 'keystone', MELD_INTEGRATION = 'meld-integration', SWAP_FEES_JUPITER = 'swap-fees-jupiter', SWAP_SOLANA = 'swap-solana' diff --git a/packages/core-mobile/app/services/wallet/KeystoneWallet/KeystoneWallet.test.ts b/packages/core-mobile/app/services/wallet/KeystoneWallet/KeystoneWallet.test.ts new file mode 100644 index 0000000000..057f7a897b --- /dev/null +++ b/packages/core-mobile/app/services/wallet/KeystoneWallet/KeystoneWallet.test.ts @@ -0,0 +1,67 @@ +import { KeystoneDataStorageType } from 'features/keystone/storage/KeystoneDataStorage' +import { Curve } from 'utils/publicKeys' +import { BitcoinProvider } from '@avalabs/core-wallets-sdk' +import { signer } from 'services/wallet/KeystoneWallet/keystoneSigner' +import KeystoneWallet from 'services/wallet/KeystoneWallet' + +const MockedKeystoneData: KeystoneDataStorageType = { + evm: 'xpub661MyMwAqRbcGSmFWVZk2h773zMrcPFqDUWi7cFRpgPhfn7y9HEPzPsBDEXYxAWfAoGo7E7ijjYfB3xAY86MYzfvGLDHmcy2epZKNeDd4uQ', + xp: 'xpub661MyMwAqRbcFFDMuFiGQmA1EqWxxgDLdtNvxxiucf9qkfoVrvwgnYyshxWoewWtkZ1aLhKoVDrpeDvn1YRqxX2szhGKi3UiSEv1hYRMF8q', + mfp: '1250b6bc' +} + +jest.mock('./keystoneSigner.ts', () => ({ + signer: jest.fn().mockImplementation(async () => '0xmockedsignature') +})) + +describe('KeystoneWallet', () => { + let wallet: KeystoneWallet + + beforeEach(() => { + wallet = new KeystoneWallet(MockedKeystoneData) + }) + + it('should have returned the evm xpub', async () => { + expect(wallet.xpub).toEqual(MockedKeystoneData.evm) + }) + + it('should have returned the xp xpub', async () => { + expect(wallet.xpubXP).toEqual(MockedKeystoneData.xp) + }) + + it('should have returned the mfp', async () => { + expect(wallet.mfp).toEqual(MockedKeystoneData.mfp) + }) + + it('should have returned the correct public key', async () => { + const evmPublicKey = await wallet.getPublicKeyFor({ + derivationPath: `m/44'/60'/0'/0/1`, + curve: Curve.SECP256K1 + }) + expect(evmPublicKey).toEqual( + '0341f20093c553b2aa95dd57449532b85480de93a9aaa225a391dcfe8679e33f50' + ) + const xpPublicKey = await wallet.getPublicKeyFor({ + derivationPath: `m/44'/9000'/0'/0/1`, + curve: Curve.SECP256K1 + }) + expect(xpPublicKey).toEqual( + '034814b89f62338b37881a71ffe40cdd29752241560b861a7086ac711fa7a8fe79' + ) + }) + + describe('getSigner', () => { + it('should sign BTC transaction successfully', async () => { + const result = await wallet.signBtcTransaction({ + accountIndex: 0, + transaction: { inputs: [], outputs: [] }, + network: { vmName: 'BITCOIN' } as any, + provider: new BitcoinProvider() + }) + + expect(typeof result).toBe('string') + expect(signer).toHaveBeenCalled() + expect(result).toBe('0xmockedsignature') + }) + }) +}) diff --git a/packages/core-mobile/app/services/wallet/KeystoneWallet/index.ts b/packages/core-mobile/app/services/wallet/KeystoneWallet/index.ts new file mode 100644 index 0000000000..4708542b03 --- /dev/null +++ b/packages/core-mobile/app/services/wallet/KeystoneWallet/index.ts @@ -0,0 +1,441 @@ +import { + AvalancheTransactionRequest, + BtcTransactionRequest, + Wallet +} from 'services/wallet/types' +import { + TypedDataV1, + TypedData, + MessageTypes, + RpcMethod +} from '@avalabs/vm-module-types' +import { Curve } from 'utils/publicKeys' +import { assertNotUndefined } from 'utils/assertions' +import { + Avalanche, + BitcoinProvider, + createPsbt, + DerivationPath, + getAddressDerivationPath, + getAddressPublicKeyFromXPub, + JsonRpcBatchInternal +} from '@avalabs/core-wallets-sdk' +import { + CryptoPSBT, + RegistryTypes, + DataType, + ETHSignature, + EthSignRequest +} from '@keystonehq/bc-ur-registry-eth' +import { Common, Hardfork } from '@ethereumjs/common' +import { + AvalancheSignRequest, + AvalancheSignature +} from '@keystonehq/bc-ur-registry-avalanche' +import { + FeeMarketEIP1559Transaction, + FeeMarketEIP1559TxData, + LegacyTransaction +} from '@ethereumjs/tx' +import { UR } from '@ngraveio/bc-ur' +import { rlp } from 'ethereumjs-util' +import { KeystoneDataStorageType } from 'features/keystone/storage/KeystoneDataStorage' +import { Network } from '@avalabs/core-chains-sdk' +import { Psbt } from 'bitcoinjs-lib' +import { v4 } from 'uuid' +import { hexlify, Signature, TransactionRequest } from 'ethers' +import { URType } from '@keystonehq/animated-qr' +import { BytesLike, AddressLike } from '@ethereumjs/util' +import { BN } from 'bn.js' +import { isTypedData } from '@avalabs/evm-module' +import { convertTxData, makeBigIntLike } from 'services/wallet/utils' +import { signer } from 'services/wallet/KeystoneWallet/keystoneSigner' + +export const EVM_DERIVATION_PATH = `m/44'/60'/0'` +export const AVAX_DERIVATION_PATH = `m/44'/9000'/0'` + +export default class KeystoneWallet implements Wallet { + #mfp: string + #xpub: string + #xpubXP: string + + constructor(keystoneData: KeystoneDataStorageType) { + this.#mfp = keystoneData.mfp + this.#xpub = keystoneData.evm + this.#xpubXP = keystoneData.xp + } + + public get xpub(): string { + assertNotUndefined(this.#xpub, 'no public key (xpub) available') + return this.#xpub + } + + public get xpubXP(): string { + assertNotUndefined(this.#xpubXP, 'no public key (xpubXP) available') + return this.#xpubXP + } + + public get mfp(): string { + assertNotUndefined(this.#mfp, 'no master fingerprint available') + return this.#mfp + } + + public async signSvmTransaction(): Promise { + throw new Error('signSvmTransaction not implemented') + } + + private async deriveEthSignature(cbor: Buffer): Promise<{ + r: string + s: string + v: number + }> { + const signature: any = ETHSignature.fromCBOR(cbor).getSignature() + const r = hexlify(new Uint8Array(signature.slice(0, 32))) + const s = hexlify(new Uint8Array(signature.slice(32, 64))) + const v = new BN(signature.slice(64)).toNumber() + return { r, s, v } + } + + public getRawXpubXP(): string { + return this.xpubXP + } + + public async signMessage({ + rpcMethod, + data, + accountIndex + }: { + rpcMethod: RpcMethod + data: string | TypedDataV1 | TypedData + accountIndex: number + network: Network + provider: JsonRpcBatchInternal + }): Promise { + switch (rpcMethod) { + case RpcMethod.AVALANCHE_SIGN_MESSAGE: { + throw new Error( + '[KeystoneWallet-signMessage] AVALANCHE_SIGN_MESSAGE not implemented.' + ) + } + case RpcMethod.ETH_SIGN: + case RpcMethod.PERSONAL_SIGN: { + if (typeof data !== 'string') + throw new Error(`Invalid message type ${typeof data}`) + + const ur = EthSignRequest.constructETHRequest( + Buffer.from(data.replace('0x', ''), 'hex'), + DataType.personalMessage, + `${EVM_DERIVATION_PATH}/0/${accountIndex}`, + this.mfp, + crypto.randomUUID() + ).toUR() + + return await signer( + ur, + [URType.ETH_SIGNATURE, URType.EVM_SIGNATURE], + async cbor => { + const sig = await this.deriveEthSignature(cbor) + + return Signature.from(sig).serialized + } + ) + } + case RpcMethod.SIGN_TYPED_DATA: + case RpcMethod.SIGN_TYPED_DATA_V1: { + throw new Error( + '[KeystoneWallet-signMessage] SIGN_TYPED_DATA/SIGN_TYPED_DATA_V1 not implemented.' + ) + } + case RpcMethod.SIGN_TYPED_DATA_V3: + case RpcMethod.SIGN_TYPED_DATA_V4: { + if (!isTypedData(data)) throw new Error('Invalid typed data') + + const ur = EthSignRequest.constructETHRequest( + Buffer.from(JSON.stringify(data), 'utf-8'), + DataType.typedData, + `${EVM_DERIVATION_PATH}/0/${accountIndex}`, + this.mfp, + crypto.randomUUID() + ).toUR() + + return await signer( + ur, + [URType.ETH_SIGNATURE, URType.EVM_SIGNATURE], + async cbor => { + const sig = await this.deriveEthSignature(cbor) + + return Signature.from(sig).serialized + } + ) + } + default: + throw new Error('unknown method') + } + } + + public async signBtcTransaction({ + accountIndex, + transaction, + provider + }: { + accountIndex: number + transaction: BtcTransactionRequest + network: Network + provider: BitcoinProvider + }): Promise { + const { inputs, outputs } = transaction + const psbt = createPsbt(inputs, outputs, provider.getNetwork()) + + inputs.forEach((_, index) => { + psbt.updateInput(index, { + bip32Derivation: [ + { + masterFingerprint: Buffer.from(this.mfp, 'hex'), + pubkey: getAddressPublicKeyFromXPub(this.xpub, accountIndex), + path: getAddressDerivationPath( + accountIndex, + DerivationPath.BIP44, + 'EVM' + ) + } + ] + }) + }) + + const cryptoPSBT = new CryptoPSBT(psbt.toBuffer()) + const ur = cryptoPSBT.toUR() + + return await signer(ur, [RegistryTypes.CRYPTO_PSBT.getType()], cbor => { + const signedTx = CryptoPSBT.fromCBOR(cbor).getPSBT() + return Promise.resolve( + Psbt.fromBuffer(signedTx) + .finalizeAllInputs() + .extractTransaction() + .toHex() + ) + }) + } + + public async signAvalancheTransaction({ + accountIndex, + transaction + }: { + accountIndex: number + transaction: AvalancheTransactionRequest + network: Network + provider: Avalanche.JsonRpcProvider + }): Promise { + const tx = transaction.tx + const isEvmChain = tx.getVM() === 'EVM' + + const requestUR = AvalancheSignRequest.constructAvalancheRequest( + Buffer.from(tx.toBytes()), + this.mfp, + isEvmChain ? this.xpub : this.xpubXP, + accountIndex + ).toUR() + + return await signer(requestUR, ['avax-signature'], cbor => { + const response = AvalancheSignature.fromCBOR(cbor) + const sig = response.getSignature() + tx.addSignature(sig as any) + return Promise.resolve(JSON.stringify(tx.toJSON())) + }) + } + + private txRequestToFeeMarketTxData( + txRequest: TransactionRequest + ): FeeMarketEIP1559TxData { + const { + to, + nonce, + gasLimit, + value, + data, + type, + maxFeePerGas, + maxPriorityFeePerGas + } = txRequest + + return { + to: (to?.toString() || undefined) as AddressLike, + nonce: makeBigIntLike(nonce), + maxFeePerGas: makeBigIntLike(maxFeePerGas), + maxPriorityFeePerGas: makeBigIntLike(maxPriorityFeePerGas), + gasLimit: makeBigIntLike(gasLimit), + value: makeBigIntLike(value), + data: data as BytesLike, + type: type || undefined + } + } + + private async getTxFromTransactionRequest( + txRequest: TransactionRequest, + signature?: { r: string; s: string; v: number } + ): Promise { + const _signature = signature + ? { + r: makeBigIntLike(signature.r), + s: makeBigIntLike(signature.s), + v: makeBigIntLike(signature.v) + } + : {} + return typeof txRequest.gasPrice !== 'undefined' + ? LegacyTransaction.fromTxData( + { + ...convertTxData(txRequest), + ..._signature + }, + { + common: Common.custom({ + chainId: Number(txRequest.chainId) + }) + } + ) + : FeeMarketEIP1559Transaction.fromTxData( + { ...this.txRequestToFeeMarketTxData(txRequest), ..._signature }, + { + common: Common.custom( + { chainId: Number(txRequest.chainId) }, + { + // "London" hardfork introduced EIP-1559 proposal. Setting it here allows us + // to use the new TX props (maxFeePerGas and maxPriorityFeePerGas) in combination + // with the custom chainId. + hardfork: Hardfork.London + } + ) + } + ) + } + + private async buildSignatureUR( + txRequest: TransactionRequest, + fingerprint: string, + activeAccountIndex: number + ): Promise { + const chainId = txRequest.chainId + const isLegacyTx = typeof txRequest.gasPrice !== 'undefined' + + const tx = await this.getTxFromTransactionRequest(txRequest) + + const message = + tx instanceof FeeMarketEIP1559Transaction + ? tx.getMessageToSign() + : rlp.encode(tx.getMessageToSign()) // Legacy transactions are not RLP-encoded + + const dataType = isLegacyTx + ? DataType.transaction + : DataType.typedTransaction + + // The keyPath below will depend on how the user onboards and should come from WalletService probably, + // based on activeAccount.index, or fetched based on the address passed in params.from. + // This here is BIP44 for the first account (index 0). 2nd account should be M/44'/60'/0'/0/1, etc.. + const keyPath = `${EVM_DERIVATION_PATH}/0/${activeAccountIndex}` + const ethSignRequest = EthSignRequest.constructETHRequest( + Buffer.from(message as any), + dataType, + keyPath, + fingerprint, + v4(), + Number(chainId) + ) + + return ethSignRequest.toUR() + } + + public async signEvmTransaction({ + accountIndex, + transaction + }: { + accountIndex: number + transaction: TransactionRequest + network: Network + provider: JsonRpcBatchInternal + }): Promise { + const ur = await this.buildSignatureUR(transaction, this.mfp, accountIndex) + + return await signer( + ur, + [URType.ETH_SIGNATURE, URType.EVM_SIGNATURE], + async cbor => { + const sig = await this.deriveEthSignature(cbor) + + const signedTx = await this.getTxFromTransactionRequest( + transaction, + sig + ) + + return '0x' + Buffer.from(signedTx.serialize()).toString('hex') + } + ) + } + + private async getAvaSigner( + accountIndex: number, + provider: Avalanche.JsonRpcProvider + ): Promise { + const evmPub = getAddressPublicKeyFromXPub(this.xpub, accountIndex) + const xpPub = Avalanche.getAddressPublicKeyFromXpub( + this.xpubXP, + accountIndex + ) + return Avalanche.StaticSigner.fromPublicKey(xpPub, evmPub, provider) + } + + public async getReadOnlyAvaSigner({ + accountIndex, + provXP + }: { + accountIndex: number + provXP: Avalanche.JsonRpcProvider + }): Promise { + return (await this.getAvaSigner( + accountIndex, + provXP + )) as Avalanche.StaticSigner + } + + private getPublicKey(path: string): Buffer { + const accountIndex = this.getAccountIndex(path) + + if (path.startsWith(EVM_DERIVATION_PATH)) { + return getAddressPublicKeyFromXPub(this.xpub, accountIndex) + } + if (path.startsWith(AVAX_DERIVATION_PATH)) { + return Avalanche.getAddressPublicKeyFromXpub(this.xpubXP, accountIndex) + } + throw new Error(`Unknown path: ${path}`) + } + + private getAccountIndex(path: string): number { + const accountIndex = path.split('/').pop() + if (!accountIndex) { + throw new Error(`Invalid path: ${path}`) + } + return Number(accountIndex) + } + + public async getPublicKeyFor({ + derivationPath, + curve + }: { + derivationPath?: string + curve: Curve + }): Promise { + if (curve === Curve.ED25519) { + throw new Error(`ED25519 not supported for path: ${derivationPath}`) + } + if (!derivationPath) { + throw new Error(`Path is required for curve: ${curve}`) + } + const publicKey = this.getPublicKey(derivationPath).toString('hex') + + if (!publicKey) { + throw new Error( + `Public key not found for path: ${derivationPath} and curve: ${curve}` + ) + } + + return publicKey + } +} diff --git a/packages/core-mobile/app/services/wallet/KeystoneWallet/keystoneSigner.ts b/packages/core-mobile/app/services/wallet/KeystoneWallet/keystoneSigner.ts new file mode 100644 index 0000000000..f9a5a8ca16 --- /dev/null +++ b/packages/core-mobile/app/services/wallet/KeystoneWallet/keystoneSigner.ts @@ -0,0 +1,21 @@ +import { UR } from '@ngraveio/bc-ur' +import { requestKeystoneSigner } from 'features/keystone/utils' + +export const signer = async ( + request: UR, + responseURTypes: string[], + handleResult: (cbor: Buffer) => Promise +): Promise => { + return new Promise((resolve, reject) => { + requestKeystoneSigner({ + request, + responseURTypes, + onReject: (message?: string) => { + reject(message ?? 'User rejected') + }, + onApprove: (cbor: Buffer) => { + return handleResult(cbor).then(resolve).catch(reject) + } + }) + }) +} diff --git a/packages/core-mobile/app/services/wallet/WalletFactory.ts b/packages/core-mobile/app/services/wallet/WalletFactory.ts index fa48fef2a4..2125def98c 100644 --- a/packages/core-mobile/app/services/wallet/WalletFactory.ts +++ b/packages/core-mobile/app/services/wallet/WalletFactory.ts @@ -3,6 +3,8 @@ import SeedlessService from 'seedless/services/SeedlessService' import BiometricsSDK from 'utils/BiometricsSDK' import { PrivateKeyWallet } from 'services/wallet/PrivateKeyWallet' import { SeedlessPubKeysStorage } from 'seedless/services/storage/SeedlessPubKeysStorage' +import { KeystoneDataStorage } from 'features/keystone/storage/KeystoneDataStorage' +import KeystoneWallet from 'services/wallet/KeystoneWallet' import { Wallet, WalletType } from './types' import { MnemonicWallet } from './MnemonicWallet' @@ -38,6 +40,15 @@ class WalletFactory { } return new MnemonicWallet(walletSecret.value) } + case WalletType.KEYSTONE: { + const keystoneData = await KeystoneDataStorage.retrieve() + + if (!keystoneData) { + throw new Error('Keystone data not available') + } + + return new KeystoneWallet(keystoneData) + } case WalletType.PRIVATE_KEY: { const walletSecret = await BiometricsSDK.loadWalletSecret(walletId) if (!walletSecret.success) { diff --git a/packages/core-mobile/app/services/wallet/WalletService.tsx b/packages/core-mobile/app/services/wallet/WalletService.tsx index 1deb2f7042..8d3fe8fcd1 100644 --- a/packages/core-mobile/app/services/wallet/WalletService.tsx +++ b/packages/core-mobile/app/services/wallet/WalletService.tsx @@ -50,6 +50,7 @@ import { } from './utils' import WalletFactory from './WalletFactory' import { MnemonicWallet } from './MnemonicWallet' +import KeystoneWallet from './KeystoneWallet' // Tolerate 50% buffer for burn amount for EVM transactions const EVM_FEE_TOLERANCE = 50 @@ -314,7 +315,7 @@ class WalletService { walletId: string walletType: WalletType }): Promise { - if (walletType !== WalletType.MNEMONIC) { + if (![WalletType.MNEMONIC, WalletType.KEYSTONE].includes(walletType)) { throw new Error('Unable to get raw xpub XP: unsupported wallet type') } @@ -323,9 +324,12 @@ class WalletService { walletType }) - if (!(wallet instanceof MnemonicWallet)) { + if ( + !(wallet instanceof MnemonicWallet) && + !(wallet instanceof KeystoneWallet) + ) { throw new Error( - 'Unable to get raw xpub XP: Expected MnemonicWallet instance' + 'Unable to get raw xpub XP: Expected MnemonicWallet or KeystoneWallet instance' ) } @@ -355,7 +359,7 @@ class WalletService { return [] } - if (walletType === WalletType.MNEMONIC) { + if ([WalletType.MNEMONIC, WalletType.KEYSTONE].includes(walletType)) { const provXP = await NetworkService.getAvalancheProviderXP(isTestnet) const xpubXP = await this.getRawXpubXP({ walletId, walletType }) diff --git a/packages/core-mobile/app/services/wallet/types.ts b/packages/core-mobile/app/services/wallet/types.ts index 0c0ffb70d3..2e69d4103a 100644 --- a/packages/core-mobile/app/services/wallet/types.ts +++ b/packages/core-mobile/app/services/wallet/types.ts @@ -122,7 +122,8 @@ export enum WalletType { UNSET = 'UNSET', SEEDLESS = 'SEEDLESS', MNEMONIC = 'MNEMONIC', - PRIVATE_KEY = 'PRIVATE_KEY' + PRIVATE_KEY = 'PRIVATE_KEY', + KEYSTONE = 'KEYSTONE' } /** diff --git a/packages/core-mobile/app/services/wallet/utils.ts b/packages/core-mobile/app/services/wallet/utils.ts index 7701b4e50b..3f338f952f 100644 --- a/packages/core-mobile/app/services/wallet/utils.ts +++ b/packages/core-mobile/app/services/wallet/utils.ts @@ -4,6 +4,10 @@ import { TokenUnit } from '@avalabs/core-utils-sdk' import { cChainToken } from 'utils/units/knownTokens' import { DerivationPathType, NetworkVMType } from '@avalabs/vm-module-types' import ModuleManager from 'vmModule/ModuleManager' +import { BigNumberish, TransactionRequest } from 'ethers' +import { BigIntLike, BytesLike, AddressLike } from '@ethereumjs/util' +import isString from 'lodash.isstring' +import { LegacyTxData } from '@ethereumjs/tx' import { AvalancheTransactionRequest, BtcTransactionRequest, @@ -103,3 +107,30 @@ export const getAddressDerivationPath = ({ return derivationPath } + +const convertToHexString = (n: string): string => { + if (n.startsWith('0x')) return n + return `0x${n}` +} + +export function makeBigIntLike( + n: BigNumberish | undefined | null +): BigIntLike | undefined { + if (n == null) return undefined + if (isString(n)) { + n = convertToHexString(n) + } + return ('0x' + BigInt(n).toString(16)) as BigIntLike +} + +export function convertTxData(txData: TransactionRequest): LegacyTxData { + return { + to: txData.to?.toString() as AddressLike, + nonce: makeBigIntLike(txData.nonce), + gasPrice: makeBigIntLike(txData.gasPrice), + gasLimit: makeBigIntLike(txData.gasLimit), + value: makeBigIntLike(txData.value), + data: txData.data as BytesLike, + type: makeBigIntLike(txData.type) + } +} diff --git a/packages/core-mobile/app/services/walletconnectv2/walletConnectCache/types.ts b/packages/core-mobile/app/services/walletconnectv2/walletConnectCache/types.ts index 130b78d4fe..85df48eb35 100644 --- a/packages/core-mobile/app/services/walletconnectv2/walletConnectCache/types.ts +++ b/packages/core-mobile/app/services/walletconnectv2/walletConnectCache/types.ts @@ -12,6 +12,7 @@ import { Contact } from 'store/addressBook/types' import { WalletAddEthereumChainRpcRequest } from 'store/rpc/handlers/chain/wallet_addEthereumChain/wallet_addEthereumChain' import { Account } from 'store/account' import { WalletType } from 'services/wallet/types' +import { UR } from '@ngraveio/bc-ur' export type SessionProposalParams = { request: WCSessionProposal @@ -43,6 +44,18 @@ export type ApprovalParams = { onReject: (message?: string) => void } +export type KeystoneSignerParams = { + request: UR + responseURTypes: string[] + onApprove: (cbor: Buffer) => Promise + onReject: (message?: string) => void +} + +export type KeystoneTroubleshootingParams = { + errorCode: number + retry: () => void +} + export type SetDeveloperModeParams = { request: AvalancheSetDeveloperModeRpcRequest data: AvalancheSetDeveloperModeApproveData diff --git a/packages/core-mobile/app/services/walletconnectv2/walletConnectCache/walletConnectCache.ts b/packages/core-mobile/app/services/walletconnectv2/walletConnectCache/walletConnectCache.ts index e7874773e9..4c79408b65 100644 --- a/packages/core-mobile/app/services/walletconnectv2/walletConnectCache/walletConnectCache.ts +++ b/packages/core-mobile/app/services/walletconnectv2/walletConnectCache/walletConnectCache.ts @@ -3,7 +3,9 @@ import { SetDeveloperModeParams, SessionProposalParams, EditContactParams, - AddEthereumChainParams + AddEthereumChainParams, + KeystoneSignerParams, + KeystoneTroubleshootingParams } from './types' // a simple in-memory cache (no reactivity or persistence support) @@ -15,7 +17,11 @@ export const walletConnectCache = { createCache('set developer mode'), editContactParams: createCache('edit contact'), addEthereumChainParams: - createCache('add ethereum chain') + createCache('add ethereum chain'), + keystoneSignerParams: createCache('keystone signer'), + keystoneTroubleshootingParams: createCache( + 'keystone troubleshooting' + ) } function createCache(key: string): { diff --git a/packages/core-mobile/app/store/account/listeners.ts b/packages/core-mobile/app/store/account/listeners.ts index 44f006eec4..d053bc9695 100644 --- a/packages/core-mobile/app/store/account/listeners.ts +++ b/packages/core-mobile/app/store/account/listeners.ts @@ -25,6 +25,7 @@ import WalletFactory from 'services/wallet/WalletFactory' import SeedlessWallet from 'seedless/services/wallet/SeedlessWallet' import { transactionSnackbar } from 'common/utils/toast' import Logger from 'utils/Logger' +import KeystoneService from 'features/keystone/services/KeystoneService' import { pendingSeedlessWalletNameStore } from 'features/onboarding/store' import { selectAccounts, @@ -65,6 +66,14 @@ const initAccounts = async ( } } + if (activeWallet.type === WalletType.KEYSTONE) { + try { + await KeystoneService.save() + } catch (error) { + Logger.error('Failed to save public keys for Keystone wallet', error) + } + } + const acc = await accountService.createNextAccount({ index: 0, walletType: activeWallet.type, @@ -124,7 +133,8 @@ const initAccounts = async ( } } else if ( activeWallet.type === WalletType.MNEMONIC || - activeWallet.type === WalletType.PRIVATE_KEY + activeWallet.type === WalletType.PRIVATE_KEY || + activeWallet.type === WalletType.KEYSTONE ) { accounts[acc.id] = acc diff --git a/packages/core-mobile/app/store/posthog/slice.ts b/packages/core-mobile/app/store/posthog/slice.ts index 8647ec2b75..31159f6bdc 100644 --- a/packages/core-mobile/app/store/posthog/slice.ts +++ b/packages/core-mobile/app/store/posthog/slice.ts @@ -368,6 +368,14 @@ export const selectIsMeldOfframpBlocked = (state: RootState): boolean => { ) } +export const selectIsKeystoneBlocked = (state: RootState): boolean => { + const { featureFlags } = state.posthog + return ( + !featureFlags[FeatureGates.KEYSTONE] || + !featureFlags[FeatureGates.EVERYTHING] + ) +} + export const selectIsSwapUseMarkrBlocked = (state: RootState): boolean => { const { featureFlags } = state.posthog return ( diff --git a/packages/core-mobile/e2e/locators/onboarding.loc.ts b/packages/core-mobile/e2e/locators/onboarding.loc.ts index 5012b0e655..599dd07657 100644 --- a/packages/core-mobile/e2e/locators/onboarding.loc.ts +++ b/packages/core-mobile/e2e/locators/onboarding.loc.ts @@ -14,6 +14,7 @@ export default { "Add a display avatar for your wallet. You can change it at any time in the app's settings", selectedAvatar: 'selected_avatar', chooseWalletTitle: 'How would you like to access your existing wallet?', + addWalletUsingKeystone: 'Add using Keystone', typeRecoverPhase: 'Type in a recovery phrase', createNewWalletBtn: 'Create a new wallet', noThanksBtn: 'No thanks', diff --git a/packages/core-mobile/ios/Podfile.lock b/packages/core-mobile/ios/Podfile.lock index d8013d7b12..74198c43d4 100644 --- a/packages/core-mobile/ios/Podfile.lock +++ b/packages/core-mobile/ios/Podfile.lock @@ -3830,7 +3830,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: AppAuth: d4f13a8fe0baf391b2108511793e4b479691fb73 AppCheckCore: cc8fd0a3a230ddd401f326489c99990b013f0c4f - boost: 7e761d76ca2ce687f7cc98e698152abd03a18f90 + boost: 1dca942403ed9342f98334bf4c3621f011aa7946 CatCrypto: a477899b6be4954e75be4897e732da098cc0a5a8 ComputableLayout: c50faffac4ed9f8f05b0ce5e6f3a60df1f6042c8 ContextMenuAuxiliaryPreview: 20be0be795b783b68f8792732eed4bed9f202c1c @@ -3839,38 +3839,38 @@ SPEC CHECKSUMS: DatadogInternal: a514ba01c9eb99dbe1ddd4452e218706bb0ddc2b DatadogLogs: 9352c58e4ab4a88f0ed7c20f27dc8d24442acc5e DatadogRUM: ab979c800d3fb7d7b2ff364a11d137b6a4be6831 - DatadogSDKReactNative: dc0db8e0608412abaf4b0297637586a8cc0972e5 + DatadogSDKReactNative: ad2a27c00fe2fd3c9f406c78f13d9190ff8dab82 DatadogTrace: 23df8545911d219d4c15446a4c6c04862ff76175 DatadogWebViewTracking: e88b8057eb5ff06a68baf232ae37637b8bb7518b DGSwiftUtilities: 567f8d5ee618f0b7afb185b17aa45ff356315a0f - DoubleConversion: cb417026b2400c8f53ae97020b2be961b59470cb - EXApplication: 50cc8ea58c138da6e3f25cd789634219c86b90d5 - EXConstants: 9d62a46a36eae6d28cb978efcbc68aef354d1704 + DoubleConversion: f16ae600a246532c4020132d54af21d0ddb2a385 + EXApplication: 1e06972201838375ca1ec1ba34d586a98a5dc718 + EXConstants: 98bcf0f22b820f9b28f9fee55ff2daededadd2f8 EXJSONUtils: 1d3e4590438c3ee593684186007028a14b3686cd - EXManifests: f4cc4a62ee4f1c8a9cf2bb79d325eac6cb9f5684 - Expo: 666a397fcb608d72b019e16ba139b74e93e0b7d7 - expo-dev-client: f1b99dfea0c9174d2e4ec96c2c5461587dda1e86 - expo-dev-launcher: 27e8eba58d52b2f471b0c4001b0535a1c0b5610d - expo-dev-menu: 2868212810f6651bc5c30e72c636ef64de31ec6b + EXManifests: 691a779b04e4f2c96da46fb9bef4f86174fefcb5 + Expo: a40d525c930dd1c8a158e082756ee071955baccb + expo-dev-client: 9b1e78baf0dd87b005f035d180bbb07c05917fad + expo-dev-launcher: 35dc0269b5fc1f628abc00e08e5a969e7809eff4 + expo-dev-menu: 0771fa9c5c405e07aa15e55a699b8a4a984ea77a expo-dev-menu-interface: 609c35ae8b97479cdd4c9e23c8cf6adc44beea0e - ExpoAdapterGoogleSignIn: da10ae7e7c1d73a10c2facebcdfe5ebea8e073ce - ExpoAsset: 7bdbbacf4e6752ae6e3cf70555cee076f6229e6e - ExpoBlur: 846780b2c90f59e964b9a50385d4deb67174ebfb - ExpoCamera: fc1ab0e1c665b543a307c577df107e37cc2edc8e - ExpoFileSystem: 9681caebda23fa1b38a12a9c68b2bade7072ce20 - ExpoFont: 091a47eeaa1b30b0b760aa1d0a2e7814e8bf6fe6 - ExpoHaptics: 68c215e070f660e0a29c45dcda4f60eeff08aefb - ExpoHead: 7c1893efc8dc79570bdcbdadce175723f4c037ec - ExpoImage: 3099001359e4414d60addd7c3e00a5d949df41e0 - ExpoKeepAwake: e8dedc115d9f6f24b153ccd2d1d8efcdfd68a527 - ExpoLinearGradient: ce334cff9859da4635c1d8eff6e291b11b04ccbb - ExpoLinking: 343a89ea864a851831fd4495e8aea01cf0f6a36f - ExpoLocalAuthentication: 78f74d187ee51126e1a789d73fee32d6d7e60f1f - ExpoLocalization: 677e45c2536bf918119962f78d7ffeeea317e07d - ExpoModulesCore: 16f74d8df26d7e4b6bf7eb3d0effaf0f15df7b80 - ExpoSMS: de195632beeb7ef00556750da10da667596d8967 - ExpoVideo: 84edf8e48b71317fb1d7f17c3d49a310476dc0f4 - EXUpdatesInterface: 64f35449b8ef89ce08cdd8952a4d119b5de6821d + ExpoAdapterGoogleSignIn: 6393c1ad521f880f6e467bca40baf1e5dcaea4a3 + ExpoAsset: ef06e880126c375f580d4923fdd1cdf4ee6ee7d6 + ExpoBlur: 3c8885b9bf9eef4309041ec87adec48b5f1986a9 + ExpoCamera: e1879906d41184e84b57d7643119f8509414e318 + ExpoFileSystem: 7f92f7be2f5c5ed40a7c9efc8fa30821181d9d63 + ExpoFont: cf508bc2e6b70871e05386d71cab927c8524cc8e + ExpoHaptics: 0ff6e0d83cd891178a306e548da1450249d54500 + ExpoHead: a7b66cbaeeb51f4a85338d335a0f5467e29a2c90 + ExpoImage: f2c9cfd2a4cb918bba7ddbb40da851858f7aece4 + ExpoKeepAwake: bf0811570c8da182bfb879169437d4de298376e7 + ExpoLinearGradient: 7734c8059972fcf691fb4330bcdf3390960a152d + ExpoLinking: d5c183998ca6ada66ff45e407e0f965b398a8902 + ExpoLocalAuthentication: c35f18692dcb35775a1be0f37b2131096951a6bd + ExpoLocalization: 999a1ff61a7f5917d65c2bd9234883009019ca9f + ExpoModulesCore: 00a1b5c73248465bd0b93f59f8538c4573dac579 + ExpoSMS: 770f0b60a777f5d3f65fd7fc369ff62ff97e09a9 + ExpoVideo: ada534976cfd4a6bf082cc61aa1b17920a251e32 + EXUpdatesInterface: 7ff005b7af94ee63fa452ea7bb95d7a8ff40277a fast_float: 06eeec4fe712a76acc9376682e4808b05ce978b6 FBLazyVector: d2a9cd223302b6c9aa4aa34c1a775e9db609eb52 Firebase: 7a56fe4f56b5ab81b86a6822f5b8f909ae6fc7e2 @@ -3881,139 +3881,139 @@ SPEC CHECKSUMS: FirebaseCoreInternal: f47dd28ae7782e6a4738aad3106071a8fe0af604 FirebaseInstallations: d8063d302a426d114ac531cd82b1e335a0565745 FirebaseMessaging: 9f4e42053241bd45ce8565c881bfdd9c1df2f7da - fmt: a40bb5bd0294ea969aaaba240a927bd33d878cdd - glog: 5683914934d5b6e4240e497e0f4a3b42d1854183 + fmt: 01b82d4ca6470831d1cc0852a1af644be019e8f6 + glog: 08b301085f15bcbb6ff8632a8ebaf239aae04e6a GoogleDataTransport: aae35b7ea0c09004c3797d53c8c41f66f219d6a7 GoogleSignIn: d4281ab6cf21542b1cfaff85c191f230b399d2db GoogleUtilities: 00c88b9a86066ef77f0da2fab05f65d7768ed8e1 GTMAppAuth: f69bd07d68cd3b766125f7e072c45d7340dea0de GTMSessionFetcher: 5aea5ba6bd522a239e236100971f10cb71b96ab6 hermes-engine: f03b0e06d3882d71e67e45b073bb827da1a21aae - jail-monkey: a71b35d482a70ecba844a90f002994012cf12a5d + jail-monkey: 1846061ac12e861ac5a8ec7197b0daa775b83733 libavif: 84bbb62fb232c3018d6f1bab79beea87e35de7b7 libdav1d: 23581a4d8ec811ff171ed5e2e05cd27bad64c39f lottie-ios: a881093fab623c467d3bce374367755c272bdd59 - lottie-react-native: 2ed3839d12e44b5946a901687e9ab00ee9bc3232 + lottie-react-native: 8bc11e10576d1a3f77f4e0ae5b70503c5c890a09 nanopb: fad817b59e0457d11a5dfbde799381cd727c1275 OpenSSL-Universal: ebc357f1e6bc71fa463ccb2fe676756aff50e88c OpenTelemetrySwiftApi: aaee576ed961e0c348af78df58b61300e95bd104 PLCrashReporter: db59ef96fa3d25f3650040d02ec2798cffee75f2 PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 - RCT-Folly: 36fe2295e44b10d831836cc0d1daec5f8abcf809 + RCT-Folly: e78785aa9ba2ed998ea4151e314036f6c49e6d82 RCTDeprecation: 5f638f65935e273753b1f31a365db6a8d6dc53b5 RCTRequired: 8b46a520ea9071e2bc47d474aa9ca31b4a935bd8 RCTTypeSafety: cc4740278c2a52cbf740592b0a0a40df1587c9ab React: 6393ae1807614f017a84805bf2417e3497f518a6 React-callinvoker: c34f666f551f05a325b87e7e3e6df0e082fa3d99 React-Codegen: 4b8b4817cea7a54b83851d4c1f91f79aa73de30a - React-Core: fc07a4b69a963880b25142c51178f4cb75628c7d - React-CoreModules: 94d39315cfa791f6c477712fea47c34f8ecb26c6 - React-cxxreact: 628c28cdb3fdef93ee3bfc2bec8e2d776e81ae49 + React-Core: 1ba9acdf7accbd46ccaae99999443ae2722c82b7 + React-CoreModules: 3c3cf4a91257f138e3feb47169a2d7fe341b5495 + React-cxxreact: 444d518a5d3a933e029b5e5ca6d8127c2e43255c React-debug: a951cdb698321d78ebd955fc8788ebbe51af3519 - React-defaultsnativemodule: 08779733c4541be5da1f1d3ec8492300dbc3c00a - React-domnativemodule: fdd4821b9a0c44e87ed9263231225aa65fe982e0 - React-Fabric: 8d905d8c41d666bf283a5b09db56bdaccfa07c8d - React-FabricComponents: 43aab5c94c7b5bbcabc3a9821b8536a0711a0f01 - React-FabricImage: 10708fa449d3f1b4a8d6eedb97f0c6476b098bb4 + React-defaultsnativemodule: 35816c7cb315962495d815446b2c8f1f3d2396ad + React-domnativemodule: 94efa04e53aa12a6dc02d420f1564ee18f3059bd + React-Fabric: bb8ccdb10256fa8acfd98a189590e2e44878abd7 + React-FabricComponents: 60703b954ca7e3d09cdb8d6fff6a4118f3c1478f + React-FabricImage: 0a8cc153d20af111f966e14b3814faa692a6805d React-featureflags: 32d776f9bef34bdab6218ad99db535e75e5c1f4e - React-featureflagsnativemodule: 413da7bc0d21aa86315dbea0fb2b2c27cb8b4bab - React-graphics: 83c676b633acc5044b5c5dfdb7f95aa3aaf7b7a5 - React-hermes: af1b3d79491295abc9d1b11f84e77d5dc00095b6 - React-idlecallbacksnativemodule: b039a595f29d9a87bbad12e731de45879a054b33 - React-ImageManager: 81dc38602ff1e7a8fd5fe3bf54772cf1a30d49c1 - React-jserrorhandler: b230f573b63a6a2a5540054d46cfb6087d26c86c - React-jsi: e9c3019e00db5d144e0a660616a52a605e12c39a - React-jsiexecutor: 3ed70a394b76f33e6c4ec4b382a457df7309d96c - React-jsinspector: 977527f0224edb5ae0970e946411f36dd1d70f43 - React-jsinspectortracing: 64ec4bde979134830c8f937758416f8d50daa8fb - React-jsitooling: 9dd45534fd158b508f785b547bf1350933bf465a - React-jsitracing: a645b2b3c4f6aa79051d5485c67b188ef49045a0 - React-logger: e6e6164f1753e46d1b7e2c8f0949cd7937eaf31b - React-Mapbuffer: 5b4959cbd91e7e8fae42ab0f4b7c25b86fd139a1 - React-microtasksnativemodule: 1695ab137281dd03de967b7bbeb4e392601f6432 - react-native-aes: 8e4348c6e75fcb32f5a960e987df877e1062714e - react-native-bottom-tabs: 5f3293a58aa9810ccdbf76d04c87bf52ab661200 - react-native-compat: 8fd509839fcf4e5646b4ff2281912d95bbd66334 - react-native-config: 8f7283449bbb048902f4e764affbbf24504454af - react-native-get-random-values: 21325b2244dfa6b58878f51f9aa42821e7ba3d06 - react-native-ios-context-menu: f5c2bc565a9a8599bb4513c43835a38e00d73b3a - react-native-ios-utilities: 73c3e0a51544c059f80eecc226a3530609e10c42 - react-native-keyboard-controller: afd3252995bc3727ebe4b4f4997937323c2845c8 - react-native-menu: 8cc212c18ae939db6932e3c3feae9a99a80f0ecb - react-native-mmkv: 2421db62a08da22d1b97ea4d55adfee9ea0f88a1 - react-native-netinfo: f0a9899081c185db1de5bb2fdc1c88c202a059ac - react-native-pager-view: 794f015a4e3b7cfb9ede97c73eae15c0d11a3fbc - react-native-passkey: e0b0f58fa9b33c424bafaa0a93d66fb0e21dc092 - react-native-quick-base64: 2f2d797c2a3a7d49d77bdef41045f9391f3f4c09 - react-native-quick-crypto: e0b1003058f1706acd425c156f494a485d18509e - react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162 - react-native-safe-area-context: 638038e064cdf9747580a2e47717b09be309a321 - react-native-skia: d4b71ff628f69fd8e1b2d0dbd436d6ab6ee8d669 - react-native-slider: c4c1a975352113af59b59dc783abc111618ec37a - react-native-theme-control: ad1dbaa6ac374bccf0100024269cadd44c73aa33 - react-native-view-shot: 41c5c50c809f1fd61f91c99400b2222c9b80d13f - react-native-webview: f23e694e18f05a5340a3432c57b4145d520333db - React-NativeModulesApple: 3ecc647742d33ad617bd2805902e3f91f2b3008f + React-featureflagsnativemodule: dd5e1e8579d7c3e10b31969c4ca2f56ba3743ec2 + React-graphics: bce95f01799245fa58ca35bdc06a98677b67352e + React-hermes: 9ec11ce5f88c0778e027aa06a6e3e6eb19ddae09 + React-idlecallbacksnativemodule: 9d125d1b9bb3e0bb4de334fea94228e6eeac1852 + React-ImageManager: c40cb4a131371ddecbabc618ef354c57c864c550 + React-jserrorhandler: c00e040f76b32a1846d7eb43602a78ad1e1f60d1 + React-jsi: 8f065aa1ae1d35bef3c394cb1663d114c4952fd8 + React-jsiexecutor: fc8e69fb870cb6e69920fd482a76d4ae54a1c40a + React-jsinspector: 42760714871594f021b3bf223f2f9ac350183ed3 + React-jsinspectortracing: 237f149a09bab785ec6b3a15cc92fc51c0d15cc4 + React-jsitooling: ef1fca866f14d8d4bd80a9570118c19e62775f96 + React-jsitracing: cfa927f650c6f7da613da9fe2a6eeaebc6b2ad1b + React-logger: 85fa3509931497c72ccd2547fcc91e7299d8591e + React-Mapbuffer: 96a2f2a176268581733be182fa6eebab1c0193be + React-microtasksnativemodule: bda561d2648e1e52bd9e5a87f8889836bdbde2e2 + react-native-aes: e2868d9f7203f573a2c1d797d95f4e88a54fa8cd + react-native-bottom-tabs: 05d040c837f04e57e6973d553db5e69c8e279113 + react-native-compat: 4594cac40c688f5ac4984e93f8c8e31496f4e614 + react-native-config: ea75335a7cca1d3326de1da384227e580a7c082e + react-native-get-random-values: d16467cf726c618e9c7a8c3c39c31faa2244bbba + react-native-ios-context-menu: 58804cdc253f80878284abe55ac5a050d4cde96a + react-native-ios-utilities: b3cf760d44fcc8220fdc0fe832beec7453483962 + react-native-keyboard-controller: 6639909c537996e456f3cc8dcfdda3a0cbcd6fc8 + react-native-menu: be9bce4eb201a4d13734744763d36d56e6df8b4a + react-native-mmkv: d3cc73d2554fafa20dc5b86386359034d1faf8ff + react-native-netinfo: cec9c4e86083cb5b6aba0e0711f563e2fbbff187 + react-native-pager-view: f238ed7fb53458bd03366944a33686f067c83e9a + react-native-passkey: 3aac247c18127443ef4a002b59d8e12dc7e99f2e + react-native-quick-base64: 651d972291fd5d9902869636a1b3c46820324490 + react-native-quick-crypto: 71b622aa0a231ac068759f5eef37982ad5eae58c + react-native-restart: 0bc732f4461709022a742bb29bcccf6bbc5b4863 + react-native-safe-area-context: 7e926a200d4bc9c56562275743705c6b56176455 + react-native-skia: 5c086469906cf854e26126b5b88dcbb6c93eb90f + react-native-slider: 78ccabe016aef7418b1a846b31115b4165c4dde6 + react-native-theme-control: d5836bcec2a9f3c3d3fd4e38874524b19a1da359 + react-native-view-shot: 57c7b8158751f19f32cb885109574712955ea016 + react-native-webview: 85c8fc8ca09f4e67b68afbc86207a1442af5dc80 + React-NativeModulesApple: 1ecb83880dd11baf2228f8dd89d8419c387e03ad React-oscompat: 0592889a9fcf0eacb205532028e4a364e22907dd - React-perflogger: 634408a9a0f5753faa577dfa81bc009edca01062 - React-performancetimeline: faa22f963845ae2298c28ef6b84bd8b58d3d8a90 + React-perflogger: c584fa50e422a46f37404d083fad12eb289d5de4 + React-performancetimeline: 8deae06fc819e6f7d1f834818e72ab5581540e45 React-RCTActionSheet: ce67bdc050cc1d9ef673c7a93e9799288a183f24 - React-RCTAnimation: 12193c2092a78012c7f77457806dcc822cc40d2c - React-RCTAppDelegate: 7225b51d5b6d3ddd3702165d717a1ffd4a90fb71 - React-RCTBlob: 923cf9b0098b9a641cb1e454c30a444d9d3cda70 - React-RCTFabric: a280fd9f2697c144b0d835200080a09ab15b2e07 - React-RCTFBReactNativeSpec: 50eabdca1efbf6ce1d774b816a68e6cc4b2a5598 - React-RCTImage: 580a5d0a6fdf9b69629d0582e5fb5a173e152099 - React-RCTLinking: 4ed7c5667709099bfd6b2b6246b1dfd79c89f7cb - React-RCTNetwork: 06a22dd0088392694df4fd098634811aa0b3e166 - React-RCTRuntime: 17c77bab5d39bc354c9983f8f11c7d3597fa8344 - React-RCTSettings: 9dbf433f302c8ebe43b280453e74624098fbc706 - React-RCTText: 92fcd78d6c44dbe64d147bb63f53698bcba7c971 - React-RCTVibration: 513659394c92491e6c749e981424f6e1e0abdb3c + React-RCTAnimation: 8bb813eb29c6de85be99c62640f3a999df76ba02 + React-RCTAppDelegate: 0200dcd70e996a7061965cfa7f8c443013cc11a1 + React-RCTBlob: a1dd15758420b6a8154019c5c188cf90648bc487 + React-RCTFabric: c7825ff7180893c4213eae8d249b279fc6bf5253 + React-RCTFBReactNativeSpec: b42afeff81dfd0618a4d37c6c6cb99a66b93a363 + React-RCTImage: 8a4f6ce18e73a7e894b886dfb7625e9e9fbc90ef + React-RCTLinking: fa49c624cd63979e7a6295ae9b1351d23ac4395a + React-RCTNetwork: f236fd2897d18522bba24453e2995a4c83e01024 + React-RCTRuntime: 6b9e893b1d375b7a733fe26c8781e8f062f52951 + React-RCTSettings: 69e2f25a5a1bf6cb37eef2e5c3bd4bb7e848296b + React-RCTText: 515ce74ed79c31dbf509e6f12770420ebbf23755 + React-RCTVibration: ef30ada606dfed859b2c71577f6f041d47f2cfbb React-rendererconsistency: aedf87f8509bc0936ae5475d4ea1e26cb5e8def6 - React-renderercss: 71727bedda678e0918506749f94f745e1050a080 - React-rendererdebug: 81a6b97bd089b49a8e7f4f5c7fd1de588c0e8a11 + React-renderercss: 636c2fffff5334897fc7745442c5e450a90eb549 + React-rendererdebug: 9c95cda4ebc6afb3b474924bb185b42ae317c02d React-rncore: 3eb6f7bdfd181bc26f9f3edc87f70eb1a68a2f3c - React-RuntimeApple: 368e8e7b0018f9e9ca4294a6a8167e6aebc6eb87 - React-RuntimeCore: 0f9a8bb41e043f3adaea111e5128801af0dfbc34 + React-RuntimeApple: 2cf5c8e38bfccd0e6aa47e3f87a1a3e85ae7fb87 + React-RuntimeCore: 2f87f504ca55b4a2a6bda1ee50c144b33cce0a15 React-runtimeexecutor: ebfd71307b3166c73ac0c441c1ea42e0f17f821d - React-RuntimeHermes: 7f55a7285794023ccb3cfe3e89c66c632ed566b1 - React-runtimescheduler: 316243b204bb6a5fd80cea7a97df9b1614ee1b0e + React-RuntimeHermes: a8391605396019d1f72079d3c72e80fcdc79c6a2 + React-runtimescheduler: 158b956675f624b3d3158ffab8f711ebf54fb3a6 React-timing: acc3fa92c72dcc1de6300d752ebb84a1d55dc809 - React-utils: 4efa98c1c602f5eacac3cece396c0b7c7d70c1d3 - ReactAppDependencyProvider: c42e7abdd2228ae583bdabc3dcd8e5cda6bef944 - ReactCodegen: 4d001cd4fa72b876bbff500bbb3811e458bb3c72 - ReactCommon: 41137f7e87cf7fd1c041a7124dfa3d0d48aa43f3 - ReactNativePerformance: ab7dee4c4862623d72c1530a9fc71b55458edf71 - RNAppleAuthentication: e00c76acb03351f5544373c78fa7f359bef6d5d3 - RNArgon2: 1481820722fd4af1575c09f7fc9ad67c00ee8a42 - RNBootSplash: f82b177a12ac2fde212e29c13480d9a08924f624 - RNCAsyncStorage: c1bbcf629d7206d1e19310827848b98d68a4cbaf - RNCClipboard: e63f1470286d683f2ade736feb352f4f18745247 - RNCMaskedView: 3e8d6bf9764b519d077986413882959eafceffbc - RNDateTimePicker: 792e57dd210f8af940812636708d43777f1f922b - RNDeviceInfo: 55264dd7cc939dad6e9c231a7621311f5277f1dc - RNDominantColor: 7c17c31201566a592ba4b2fbe2bb7e00df468753 - RNFBApp: 5c187f161ac2b1c258c9c099d56bf5cfdb1f0bcb - RNFBAppCheck: 94c8ce0a972880b3dd1563a01e0ba3245126a9a9 - RNFBMessaging: cb53c6f68f35949983ecb768589bb1c4771bea04 - RNFlashList: 30b68f572400383347ce7df3777985af0d089624 - RNFS: 4ac0f0ea233904cb798630b3c077808c06931688 - RNGestureHandler: ccf4105b125002bd88e39d2a1f2b7e6001bcdf34 - RNGoogleSignin: 63192b9a15ec94c1fc4b0f55fba984a1d7af4ee7 - RNInAppBrowser: e36d6935517101ccba0e875bac8ad7b0cb655364 - RNKeychain: 16921786da69b6001ad2e65781b76f7af372bb10 - RNLocalize: 87712a038fae12e3ac03035ab5f0e35670a17cc3 - RNNotifee: 4a6ee5c7deaf00e005050052d73ee6315dff7ec9 - RNOS: 6f2f9a70895bbbfbdad7196abd952e7b01d45027 - RNPermissions: 5e9311c8050e355546083e45cd0b265ccbdc2a74 - RNReanimated: 6a437c90e263e4a2202a282f9d6dea0ad28d018d - RNScreens: c2e3cc506212228c607b4785b315205e28acbf0f - RNSensors: 117ba71c7eeeea0407ea0c0bb79e3495d602049b - RNSentry: a1c2583797dd0e7d154e34a47021ebc8bf9d5a06 - RNShare: c57877c95b51671130e531065ed8e9396dd2669f - RNSound: 99655e437691f6357ffdd8a45f7a3b7aae27624f - RNSVG: ee32efbed652c5151fd3f98bed13c68af285bc38 + React-utils: 525f1fe996874cff32a0ef8e523e31ebde23664d + ReactAppDependencyProvider: f3e842e6cb5a825b6918a74a38402ba1409411f8 + ReactCodegen: 6cb6e0d0b52471abc883541c76589d1c367c64c7 + ReactCommon: 1ab5451fc5da87c4cc4c3046e19a8054624ca763 + ReactNativePerformance: fa4952a58739e1a1f6b90a4e9777657d463499a3 + RNAppleAuthentication: 8d313d93fe2238d6b7ff0a39c67ebcf298d96653 + RNArgon2: 708e188b7a4d4ec8baf62463927c47abef453a94 + RNBootSplash: 295996a51f49baf0dff9f65e49b32ff93fbc7357 + RNCAsyncStorage: 39c42c1e478e1f5166d1db52b5055e090e85ad66 + RNCClipboard: b80e7bdccb8d432ab1c40189920e7af3116e074c + RNCMaskedView: ae521efb1c6c2b183ae0f8479487db03c826184c + RNDateTimePicker: a4ad049fdd568755003c479beac3bdea742ac4d0 + RNDeviceInfo: ae4e1a9b955b2a65314588cc7251795631925668 + RNDominantColor: 911de09d58e331bc8f84f22bf393e47d3cf8ca79 + RNFBApp: 33938c8ee95629bda8a14eda579e555a101212dd + RNFBAppCheck: 9135ee6833548d2dbd2cbda7e15d0d61bcedbe3c + RNFBMessaging: 1ea695eb9644bbb7aef5c459784f244af2b01cce + RNFlashList: 7ad51f0d0d51a3b7b1d1bb07947b927cb352afc4 + RNFS: 89de7d7f4c0f6bafa05343c578f61118c8282ed8 + RNGestureHandler: 7d0931a61d7ba0259f32db0ba7d0963c3ed15d2b + RNGoogleSignin: 2183722f55abd5b206d02e2edc146bec91fe5610 + RNInAppBrowser: 6d3eb68d471b9834335c664704719b8be1bfdb20 + RNKeychain: 943b4dbceef4b3a310c9122c99d1e5b0bd66b9f3 + RNLocalize: 83eda9ba7fd1cd23310dfd14e61ce5f091f888fb + RNNotifee: 5e3b271e8ea7456a36eec994085543c9adca9168 + RNOS: d07e5090b5060c6f2b83116d740a32cfdb33afe3 + RNPermissions: 4891cd00483fc6902cb92f61bd850e43badde94a + RNReanimated: 722d67dfde539bfe29b572878f4bc805f727726b + RNScreens: 482e9707f9826230810c92e765751af53826d509 + RNSensors: 111159597ac51505df10413c61b28bcd28e88983 + RNSentry: 3bc708566b3390947b6ecd785645398cd1a0049e + RNShare: 43faaefd287ef344e8379caeaca12d112f713295 + RNSound: 72c4886fb80b8a0e8c40131099223e09422aa8b4 + RNSVG: 794f269526df9ddc1f79b3d1a202b619df0368e3 SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 SDWebImageAVIFCoder: 00310d246aab3232ce77f1d8f0076f8c4b021d90 SDWebImageSVGCoder: 15a300a97ec1c8ac958f009c02220ac0402e936c @@ -4025,4 +4025,4 @@ SPEC CHECKSUMS: PODFILE CHECKSUM: ed1745b5dff04eddc5b671b6717bc6f6257e9f58 -COCOAPODS: 1.15.2 +COCOAPODS: 1.16.2 diff --git a/packages/core-mobile/jest.config.js b/packages/core-mobile/jest.config.js index 2187aa00ee..6c0967de55 100644 --- a/packages/core-mobile/jest.config.js +++ b/packages/core-mobile/jest.config.js @@ -32,7 +32,12 @@ module.exports = { 'map-obj', 'camelcase', 'quick-lru', - 'react-redux' + 'react-redux', + 'uuid', + '@keystonehq/animated-qr', + '@keystonehq/keystone-sdk', + '@keystonehq/bc-ur-registry-eth', + '@keystonehq/bc-ur-registry-avalanche' ].join('|') + ')' ] diff --git a/packages/core-mobile/package.json b/packages/core-mobile/package.json index 7d883468fa..a7caf589c0 100644 --- a/packages/core-mobile/package.json +++ b/packages/core-mobile/package.json @@ -51,15 +51,19 @@ "@date-fns/utc": "2.1.0", "@ethereumjs/common": "4.4.0", "@ethereumjs/tx": "5.4.0", + "@ethereumjs/util": "9.1.0", "@formatjs/intl-locale": "4.2.11", "@formatjs/intl-numberformat": "8.15.4", "@formatjs/intl-pluralrules": "5.4.4", "@gorhom/bottom-sheet": "4.6.4", "@hookform/resolvers": "3.9.0", "@invertase/react-native-apple-authentication": "2.4.0", + "@keystonehq/animated-qr": "0.10.0", + "@keystonehq/keystone-sdk": "0.11.3", "@lavamoat/preinstall-always-fail": "2.1.0", "@metamask/eth-sig-util": "7.0.3", "@metamask/rpc-errors": "6.3.0", + "@ngraveio/bc-ur": "1.1.13", "@noble/hashes": "1.8.0", "@noble/secp256k1": "2.1.0", "@notifee/react-native": "9.1.8", @@ -183,6 +187,8 @@ "react-native-performance": "5.1.2", "react-native-permissions": "4.1.5", "react-native-popable": "0.4.3", + "react-native-popover-view": "6.1.0", + "react-native-progress": "5.0.1", "react-native-qrcode-svg": "6.3.2", "react-native-quick-base64": "2.1.2", "react-native-quick-crypto": "0.7.15", diff --git a/yarn.lock b/yarn.lock index 89f054073c..7f31cd2a6c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -226,16 +226,20 @@ __metadata: "@dlenroc/testrail": 1.9.1 "@ethereumjs/common": 4.4.0 "@ethereumjs/tx": 5.4.0 + "@ethereumjs/util": 9.1.0 "@formatjs/intl-locale": 4.2.11 "@formatjs/intl-numberformat": 8.15.4 "@formatjs/intl-pluralrules": 5.4.4 "@gorhom/bottom-sheet": 4.6.4 "@hookform/resolvers": 3.9.0 "@invertase/react-native-apple-authentication": 2.4.0 + "@keystonehq/animated-qr": 0.10.0 + "@keystonehq/keystone-sdk": 0.11.3 "@lavamoat/allow-scripts": 3.2.1 "@lavamoat/preinstall-always-fail": 2.1.0 "@metamask/eth-sig-util": 7.0.3 "@metamask/rpc-errors": 6.3.0 + "@ngraveio/bc-ur": 1.1.13 "@noble/hashes": 1.8.0 "@noble/secp256k1": 2.1.0 "@notifee/react-native": 9.1.8 @@ -406,6 +410,8 @@ __metadata: react-native-performance: 5.1.2 react-native-permissions: 4.1.5 react-native-popable: 0.4.3 + react-native-popover-view: 6.1.0 + react-native-progress: 5.0.1 react-native-qrcode-svg: 6.3.2 react-native-quick-base64: 2.1.2 react-native-quick-crypto: 0.7.15 @@ -4258,6 +4264,13 @@ __metadata: languageName: node linkType: hard +"@bufbuild/protobuf@npm:^1.2.0": + version: 1.10.1 + resolution: "@bufbuild/protobuf@npm:1.10.1" + checksum: 403838ad278d504e33e72ec0f64ce1bac9f5025ee6396253382e821a1fe0c371a58ffe45a0e8f23306205b6890c5f83e85828168a35dae118915cd4c3d091177 + languageName: node + linkType: hard + "@coinbase/cbpay-js@npm:2.2.1": version: 2.2.1 resolution: "@coinbase/cbpay-js@npm:2.2.1" @@ -4700,6 +4713,16 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/util@npm:9.1.0, @ethereumjs/util@npm:^9.0.3, @ethereumjs/util@npm:^9.1.0": + version: 9.1.0 + resolution: "@ethereumjs/util@npm:9.1.0" + dependencies: + "@ethereumjs/rlp": ^5.0.2 + ethereum-cryptography: ^2.2.1 + checksum: 594e009c3001ca1ca658b4ded01b38e72f5dd5dd76389efd90cb020de099176a3327685557df268161ac3144333cfe8abaae68cda8ae035d9cc82409d386d79a + languageName: node + linkType: hard + "@ethereumjs/util@npm:^8.1.0": version: 8.1.0 resolution: "@ethereumjs/util@npm:8.1.0" @@ -4711,16 +4734,6 @@ __metadata: languageName: node linkType: hard -"@ethereumjs/util@npm:^9.1.0": - version: 9.1.0 - resolution: "@ethereumjs/util@npm:9.1.0" - dependencies: - "@ethereumjs/rlp": ^5.0.2 - ethereum-cryptography: ^2.2.1 - checksum: 594e009c3001ca1ca658b4ded01b38e72f5dd5dd76389efd90cb020de099176a3327685557df268161ac3144333cfe8abaae68cda8ae035d9cc82409d386d79a - languageName: node - linkType: hard - "@ethersproject/abi@npm:^5.5.0": version: 5.7.0 resolution: "@ethersproject/abi@npm:5.7.0" @@ -6859,6 +6872,250 @@ __metadata: languageName: node linkType: hard +"@keystonehq/alias-sampling@npm:^0.1.1": + version: 0.1.2 + resolution: "@keystonehq/alias-sampling@npm:0.1.2" + checksum: 4dfdfb91e070b1d9f28058c92b5b8fad81696ac63bd432cd6bd359f2ab92eb50df75e8c5da1f75a351756387e9902f043b3ecc2cbf662c9c9456ecacc848abfd + languageName: node + linkType: hard + +"@keystonehq/animated-qr-base@npm:^0.0.1": + version: 0.0.1 + resolution: "@keystonehq/animated-qr-base@npm:0.0.1" + dependencies: + "@ngraveio/bc-ur": ^1.1.13 + checksum: 5058f7e21b12f3c429e5c0c37c9c9adf718d7c397a13bd75c7bc4f81820754d6a01ca12a8f955472189b3d9fc452f117846db45dcc045108b9211abcfb2cc89c + languageName: node + linkType: hard + +"@keystonehq/animated-qr@npm:0.10.0": + version: 0.10.0 + resolution: "@keystonehq/animated-qr@npm:0.10.0" + dependencies: + "@keystonehq/animated-qr-base": ^0.0.1 + "@ngraveio/bc-ur": ^1.1.6 + "@zxing/browser": ^0.1.1 + "@zxing/library": ^0.19.1 + qrcode.react: ^3.1.0 + peerDependencies: + react: ">= 16.8" + react-dom: ">= 16.8" + checksum: f0657d3c600ea4bc3d0c76dc78c1e6242bf43f5a2ecb34bc3e1de3428dd00f8f8a2c0728a63fc2606294c51a75af50d99d8f0fe4443761cd14fd74db8bf80e76 + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-aptos@npm:^0.6.3": + version: 0.6.3 + resolution: "@keystonehq/bc-ur-registry-aptos@npm:0.6.3" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + bs58check: ^2.1.2 + uuid: ^8.3.2 + checksum: 5be87f8aaefd038121c049fd725b3fce7867122642042299b690bce7c0b40ea98cc2d5f17c187d511e03d24d3e617ef0df70fcb1d40a5b6877710c03e3630801 + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-arweave@npm:^0.5.3": + version: 0.5.3 + resolution: "@keystonehq/bc-ur-registry-arweave@npm:0.5.3" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + uuid: ^8.3.2 + checksum: 0a967f318343022dc1201561bb3cd7f5a889135bf65bb48e959153802207b0693dd9a1f6cc653eb40e0dc8e3675471df4584dc287ff7a2b7e6d6d128e38a52d4 + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-avalanche@npm:^0.0.4": + version: 0.0.4 + resolution: "@keystonehq/bc-ur-registry-avalanche@npm:0.0.4" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + buffer: ^6.0.3 + uuid: ^8.3.2 + checksum: c8bff304d1bf2430572d07408e8fdbcacf5f769fc885fc094c248c7c4a7a771cde427f385dd4bb1a38d864361829f6ba2f4f0104e46ce37c251720dfd0ec2336 + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-btc@npm:^0.1.1": + version: 0.1.1 + resolution: "@keystonehq/bc-ur-registry-btc@npm:0.1.1" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + uuid: ^8.3.2 + checksum: d0d7ec983db55374715c04226a7fd70c82b5758c64eae8e70fb3285f8fa3e6d3f124cadb409c3371f7ae18862494ed0fa60cea4c55099eb6ba9cebda9bf2c89d + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-cardano@npm:^0.5.0": + version: 0.5.0 + resolution: "@keystonehq/bc-ur-registry-cardano@npm:0.5.0" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + uuid: ^8.3.2 + checksum: b8c72bd44a086b41763f8fa3ddbe3cc7227b5e1c42fe22f76ee35abec6acc1df106d74453c928ffcc6afed75abf045cc74db82c66803d141d98be5e52d7532bf + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-cosmos@npm:^0.5.3": + version: 0.5.3 + resolution: "@keystonehq/bc-ur-registry-cosmos@npm:0.5.3" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + bs58check: ^2.1.2 + uuid: ^8.3.2 + checksum: 6ca85a739cd2c15f2534735c996fbd888a42b81691cbf34c5421bde4a1bad93cd99d1ea09b6a691305d4656aa2904f592e248498e631470a17d00ed800d6a3e0 + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-eth@npm:^0.22.0": + version: 0.22.1 + resolution: "@keystonehq/bc-ur-registry-eth@npm:0.22.1" + dependencies: + "@ethereumjs/util": ^9.0.3 + "@keystonehq/bc-ur-registry": ^0.7.0 + hdkey: ^2.0.1 + uuid: ^8.3.2 + checksum: d8effcca1443464c8cd7e2247dd7a49cc8221cb9f34377400a0bba4363a02cd818ed8125f9106a95146174d170959e917ddbe1eabb1376f07f90d71148b1aea5 + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-evm@npm:^0.5.3": + version: 0.5.3 + resolution: "@keystonehq/bc-ur-registry-evm@npm:0.5.3" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + bs58check: ^2.1.2 + uuid: ^9.0.0 + checksum: a98251b7164397edc7dcda154ebfe2adf239954bf7acb42af42162ffefec648df2384908766780ab729b8264bffc99ed4dd8de4bded74e229a281620671a326c + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-iota@npm:^0.1.4": + version: 0.1.4 + resolution: "@keystonehq/bc-ur-registry-iota@npm:0.1.4" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + uuid: ^9.0.0 + checksum: c3a3ee3573e9acb03acf8c7679af8c61c5345469ef0e2dc9f3c18c9ff921ad9e681347401af0894939dd0625b7948346dee67df8deb2e490dc3743319be174df + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-keystone@npm:^0.4.3": + version: 0.4.3 + resolution: "@keystonehq/bc-ur-registry-keystone@npm:0.4.3" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + checksum: 04848bad6fe149bebbb18113e13249acad15b1eea96cee667966f6f8ba568595d40927e46ee1202c5ea02fa475d1aff25b1c797f1ce88152804795ccb3487718 + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-near@npm:^0.9.3": + version: 0.9.3 + resolution: "@keystonehq/bc-ur-registry-near@npm:0.9.3" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + bs58check: ^2.1.2 + uuid: ^8.3.2 + checksum: 494bc0842b63c701797b6cf8d06e7e980584b8efe42f9b1f3ef2d064157c4cc1b01a3c27ab74089bbfd4111c1879ede39c697ee7a11a5556aed858220878ad3d + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-sol@npm:^0.9.3": + version: 0.9.5 + resolution: "@keystonehq/bc-ur-registry-sol@npm:0.9.5" + dependencies: + "@keystonehq/bc-ur-registry": ^0.7.0 + bs58check: ^2.1.2 + uuid: ^8.3.2 + checksum: f8002e74b4fcad5fe3d67b3d2d462d8a6e6a80ed6dc2cb14815da827ccde18772f0b6595da8e79311a3b81bf273891ab524c981d7c705b24ec8dc977508259a5 + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-stellar@npm:^0.0.4": + version: 0.0.4 + resolution: "@keystonehq/bc-ur-registry-stellar@npm:0.0.4" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + bs58check: ^2.1.2 + uuid: ^8.3.2 + checksum: 25366676d1987f05398cc7094d59020596db76c621facc1e6dc40119a3e35f231befea9911194e7c869d9177ff9704aa74033963cf90cef824c113dbffc335e5 + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-sui@npm:0.4.0-alpha.0": + version: 0.4.0-alpha.0 + resolution: "@keystonehq/bc-ur-registry-sui@npm:0.4.0-alpha.0" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + uuid: ^9.0.0 + checksum: 193385f60751fbc261299233f37e01ecc130d67c63f805b811043e03409f06e6ecc03c6d8effab17728a4174cc3eb210c3fbeeeacac058bef2bc301e47be2a1c + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry-ton@npm:^0.1.2": + version: 0.1.2 + resolution: "@keystonehq/bc-ur-registry-ton@npm:0.1.2" + dependencies: + "@keystonehq/bc-ur-registry": ^0.6.4 + uuid: ^9.0.0 + checksum: 28458a641d02366187e9ec8f2498fc0a7ba31125b072e50ec7bfc1328dcbcbd0fc005fa29411400b5ea27ab5ba1baf53e3259516aa7346c5a2556e7deb3dc431 + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry@npm:^0.6.4": + version: 0.6.4 + resolution: "@keystonehq/bc-ur-registry@npm:0.6.4" + dependencies: + "@ngraveio/bc-ur": ^1.1.5 + bs58check: ^2.1.2 + tslib: ^2.3.0 + checksum: 8b73edd304fc2c6a7faa3fae320348e9fc58493c2d75276b792ef37560534e18117c114bfb9edddd90639e81710dd660fb1a405d7c5de05e17d44613c691fdb3 + languageName: node + linkType: hard + +"@keystonehq/bc-ur-registry@npm:^0.7.0": + version: 0.7.0 + resolution: "@keystonehq/bc-ur-registry@npm:0.7.0" + dependencies: + "@ngraveio/bc-ur": ^1.1.5 + bs58check: ^2.1.2 + tslib: ^2.3.0 + checksum: d6017e8fda67fc01e28aa1c047b20cce8f07b026f110a5771920879fbd658b845f529b054d1dce2fbabadcfd8da47a2160ab50c73f0bd56678aab4d83899ffcc + languageName: node + linkType: hard + +"@keystonehq/keystone-sdk@npm:0.11.3": + version: 0.11.3 + resolution: "@keystonehq/keystone-sdk@npm:0.11.3" + dependencies: + "@bufbuild/protobuf": ^1.2.0 + "@keystonehq/bc-ur-registry": ^0.7.0 + "@keystonehq/bc-ur-registry-aptos": ^0.6.3 + "@keystonehq/bc-ur-registry-arweave": ^0.5.3 + "@keystonehq/bc-ur-registry-avalanche": ^0.0.4 + "@keystonehq/bc-ur-registry-btc": ^0.1.1 + "@keystonehq/bc-ur-registry-cardano": ^0.5.0 + "@keystonehq/bc-ur-registry-cosmos": ^0.5.3 + "@keystonehq/bc-ur-registry-eth": ^0.22.0 + "@keystonehq/bc-ur-registry-evm": ^0.5.3 + "@keystonehq/bc-ur-registry-iota": ^0.1.4 + "@keystonehq/bc-ur-registry-keystone": ^0.4.3 + "@keystonehq/bc-ur-registry-near": ^0.9.3 + "@keystonehq/bc-ur-registry-sol": ^0.9.3 + "@keystonehq/bc-ur-registry-stellar": ^0.0.4 + "@keystonehq/bc-ur-registry-sui": 0.4.0-alpha.0 + "@keystonehq/bc-ur-registry-ton": ^0.1.2 + "@ngraveio/bc-ur": ^1.1.6 + "@noble/hashes": ^1.5.0 + bs58check: ^3.0.1 + buffer: ^6.0.3 + pako: ^2.1.0 + ripple-binary-codec: ^1.4.3 + uuid: ^9.0.0 + checksum: c4acc2e14853ba70d8ab53a79ac4426bccb6c1a6b391f428cc2942ab8c0fbbfb34dde38ebe691ccf0c76a6b7ac5cd3fac4f170a59371813e94bcea749f466287 + languageName: node + linkType: hard + "@lavamoat/aa@npm:^4.3.0": version: 4.3.0 resolution: "@lavamoat/aa@npm:4.3.0" @@ -7528,6 +7785,21 @@ __metadata: languageName: node linkType: hard +"@ngraveio/bc-ur@npm:1.1.13, @ngraveio/bc-ur@npm:^1.1.13, @ngraveio/bc-ur@npm:^1.1.5, @ngraveio/bc-ur@npm:^1.1.6": + version: 1.1.13 + resolution: "@ngraveio/bc-ur@npm:1.1.13" + dependencies: + "@keystonehq/alias-sampling": ^0.1.1 + assert: ^2.0.0 + bignumber.js: ^9.0.1 + cbor-sync: ^1.0.4 + crc: ^3.8.0 + jsbi: ^3.1.5 + sha.js: ^2.4.11 + checksum: 3f8e565c6a6dd7af7489a884f7d4d85d274ce7ce41f9fdb7e362b8a75ccbb2c934b369fd4ea58b2214d6039462ee0e933de61f372c04c551a47a75e1cad14cfd + languageName: node + linkType: hard + "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1": version: 5.1.1-v1 resolution: "@nicolo-ribaudo/eslint-scope-5-internals@npm:5.1.1-v1" @@ -9678,6 +9950,13 @@ __metadata: languageName: node linkType: hard +"@react-native/normalize-color@npm:*": + version: 2.1.0 + resolution: "@react-native/normalize-color@npm:2.1.0" + checksum: 8ccbd40b3c7629f1dc97b3e9aadd95fd3507fcf2e37535a6299a70436ab891c34cbdc4240b07380553d6e85dd909e23d5773b5be1da2906b026312e0b0768838 + languageName: node + linkType: hard + "@react-native/normalize-colors@npm:0.79.5": version: 0.79.5 resolution: "@react-native/normalize-colors@npm:0.79.5" @@ -13521,7 +13800,34 @@ __metadata: languageName: node linkType: hard -"@zxing/text-encoding@npm:0.9.0": +"@zxing/browser@npm:^0.1.1": + version: 0.1.5 + resolution: "@zxing/browser@npm:0.1.5" + dependencies: + "@zxing/text-encoding": ^0.9.0 + peerDependencies: + "@zxing/library": ^0.21.0 + dependenciesMeta: + "@zxing/text-encoding": + optional: true + checksum: 27bfddd707e8e643624b432666a956f1e7e283e20963243f7050c781fd80daad9f54408acdf46d809c64586983f7b02a4981c1d3b0251519e0566c165f6b3924 + languageName: node + linkType: hard + +"@zxing/library@npm:^0.19.1": + version: 0.19.3 + resolution: "@zxing/library@npm:0.19.3" + dependencies: + "@zxing/text-encoding": ~0.9.0 + ts-custom-error: ^3.2.1 + dependenciesMeta: + "@zxing/text-encoding": + optional: true + checksum: 2a3adaccbde0e075ee4c3c73ab7fa9306be979dafeff6d373204470ea3cddab88608c6eca5e891c7d5e693c5df0f0664e14ea0a74d38e0658fc7464f5c986474 + languageName: node + linkType: hard + +"@zxing/text-encoding@npm:0.9.0, @zxing/text-encoding@npm:^0.9.0, @zxing/text-encoding@npm:~0.9.0": version: 0.9.0 resolution: "@zxing/text-encoding@npm:0.9.0" checksum: c23b12aee7639382e4949961304a1294776afaffa40f579e09ffecd0e5e68cf26ef3edd75009de46da8a536e571448755ca68b3e2ea707d53793c0edb2e2c34a @@ -14213,7 +14519,7 @@ __metadata: languageName: node linkType: hard -"assert@npm:2.1.0, assert@npm:^2.1.0": +"assert@npm:2.1.0, assert@npm:^2.0.0, assert@npm:^2.1.0": version: 2.1.0 resolution: "assert@npm:2.1.0" dependencies: @@ -14640,6 +14946,15 @@ __metadata: languageName: node linkType: hard +"base-x@npm:^3.0.9": + version: 3.0.11 + resolution: "base-x@npm:3.0.11" + dependencies: + safe-buffer: ^5.0.1 + checksum: c2e3c443fd07cb9b9d3e179a9e9c581daa31881005841fe8d6a834e534505890fedf03465ccf14512da60e3f7be00fe66167806b159ba076d2c03952ae7460c4 + languageName: node + linkType: hard + "base-x@npm:^4.0.0": version: 4.0.0 resolution: "base-x@npm:4.0.0" @@ -14684,7 +14999,7 @@ __metadata: languageName: node linkType: hard -"big-integer@npm:1.6.x, big-integer@npm:^1.6.52": +"big-integer@npm:1.6.x, big-integer@npm:^1.6.48, big-integer@npm:^1.6.52": version: 1.6.52 resolution: "big-integer@npm:1.6.52" checksum: 6e86885787a20fed96521958ae9086960e4e4b5e74d04f3ef7513d4d0ad631a9f3bde2730fc8aaa4b00419fc865f6ec573e5320234531ef37505da7da192c40b @@ -14705,6 +15020,13 @@ __metadata: languageName: node linkType: hard +"bignumber.js@npm:^9.0.1": + version: 9.3.1 + resolution: "bignumber.js@npm:9.3.1" + checksum: 6ab100271a23a75bb8b99a4b1a34a1a94967ac0b9a52a198147607bd91064e72c6f356380d7a09cd687bf50d81ad2ed1a0a8edfaa90369c9003ed8bb2440d7f0 + languageName: node + linkType: hard + "bignumber.js@npm:^9.1.1, bignumber.js@npm:^9.1.2": version: 9.1.2 resolution: "bignumber.js@npm:9.1.2" @@ -15164,7 +15486,7 @@ __metadata: languageName: node linkType: hard -"buffer@npm:^5.4.3, buffer@npm:^5.5.0": +"buffer@npm:^5.1.0, buffer@npm:^5.4.3, buffer@npm:^5.5.0": version: 5.7.1 resolution: "buffer@npm:5.7.1" dependencies: @@ -15484,6 +15806,13 @@ __metadata: languageName: node linkType: hard +"cbor-sync@npm:^1.0.4": + version: 1.0.4 + resolution: "cbor-sync@npm:1.0.4" + checksum: 147834c64b43511b2ea601f02bc2cc4190ec8d41a7b8dc3e9037c636b484ca2124bc7d49da7a0f775ea5153ff799d57e45992816851dbb1d61335f308a0d0120 + languageName: node + linkType: hard + "chalk@npm:^2.0.1, chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -15635,12 +15964,12 @@ __metadata: linkType: hard "cipher-base@npm:^1.0.0, cipher-base@npm:^1.0.1, cipher-base@npm:^1.0.3": - version: 1.0.6 - resolution: "cipher-base@npm:1.0.6" + version: 1.0.4 + resolution: "cipher-base@npm:1.0.4" dependencies: - inherits: ^2.0.4 - safe-buffer: ^5.2.1 - checksum: 64a1738a8583163cf096bc85321a69ef3075bb0873f34cf89dc705e62b9eee058dd6b2e5c672f774ede0b6bdbe56fe7b710e0d38c4f08a2f355d8ab828f05c6f + inherits: ^2.0.1 + safe-buffer: ^5.0.1 + checksum: 47d3568dbc17431a339bad1fe7dff83ac0891be8206911ace3d3b818fc695f376df809bea406e759cdea07fff4b454fa25f1013e648851bec790c1d75763032e languageName: node linkType: hard @@ -16193,6 +16522,15 @@ __metadata: languageName: node linkType: hard +"crc@npm:^3.8.0": + version: 3.8.0 + resolution: "crc@npm:3.8.0" + dependencies: + buffer: ^5.1.0 + checksum: dabbc4eba223b206068b92ca82bb471d583eb6be2384a87f5c3712730cfd6ba4b13a45e8ba3ef62174d5a781a2c5ac5c20bf36cf37bba73926899bd0aa19186f + languageName: node + linkType: hard + "create-hash@npm:1.2.0, create-hash@npm:^1.1.0, create-hash@npm:^1.1.2, create-hash@npm:^1.2.0": version: 1.2.0 resolution: "create-hash@npm:1.2.0" @@ -16891,6 +17229,13 @@ __metadata: languageName: node linkType: hard +"decimal.js@npm:^10.2.0": + version: 10.6.0 + resolution: "decimal.js@npm:10.6.0" + checksum: 9302b990cd6f4da1c7602200002e40e15d15660374432963421d3cd6d81cc6e27e0a488356b030fee64650947e32e78bdbea245d596dadfeeeb02e146d485999 + languageName: node + linkType: hard + "decimal.js@npm:^10.4.3": version: 10.5.0 resolution: "decimal.js@npm:10.5.0" @@ -17086,6 +17431,17 @@ __metadata: languageName: node linkType: hard +"deprecated-react-native-prop-types@npm:^2.3.0": + version: 2.3.0 + resolution: "deprecated-react-native-prop-types@npm:2.3.0" + dependencies: + "@react-native/normalize-color": "*" + invariant: "*" + prop-types: "*" + checksum: d14f4be1dfe780a7fa9197a31b4a9a2b409c8cf1bf677713fd92d06733dee1043578662d1a8858541cf06164ae91d295db6e595f29bf13e808d9fb37bc58c90b + languageName: node + linkType: hard + "dequal@npm:2.0.3, dequal@npm:^2.0.2": version: 2.0.3 resolution: "dequal@npm:2.0.3" @@ -20728,6 +21084,18 @@ __metadata: languageName: node linkType: hard +"hdkey@npm:^2.0.1": + version: 2.1.0 + resolution: "hdkey@npm:2.1.0" + dependencies: + bs58check: ^2.1.2 + ripemd160: ^2.0.2 + safe-buffer: ^5.1.1 + secp256k1: ^4.0.0 + checksum: 042f2d715dc4d106c868dc3791d584336845e4e53f3452e1df116d6af5d88d7084a0a73ddd8a07b4a7d9e6b29cd3b6b4174f03499f25d8ddd101642b34fabe5c + languageName: node + linkType: hard + "he@npm:1.2.0": version: 1.2.0 resolution: "he@npm:1.2.0" @@ -21219,7 +21587,7 @@ __metadata: languageName: node linkType: hard -"invariant@npm:2, invariant@npm:2.2.4, invariant@npm:^2.2.2, invariant@npm:^2.2.4": +"invariant@npm:*, invariant@npm:2, invariant@npm:2.2.4, invariant@npm:^2.2.2, invariant@npm:^2.2.4": version: 2.2.4 resolution: "invariant@npm:2.2.4" dependencies: @@ -22765,6 +23133,13 @@ __metadata: languageName: node linkType: hard +"jsbi@npm:^3.1.5": + version: 3.2.5 + resolution: "jsbi@npm:3.2.5" + checksum: 642d1bb139ad1c1e96c4907eb159565e980a0d168487626b493d0d0b7b341da0e43001089d3b21703fe17b18a7a6c0f42c92026f71d54471ed0a0d1b3015ec0f + languageName: node + linkType: hard + "jsbn@npm:1.1.0": version: 1.1.0 resolution: "jsbn@npm:1.1.0" @@ -24590,8 +24965,14 @@ __metadata: version: 0.0.0-use.local resolution: "mobile-monorepo@workspace:." dependencies: + "@ethereumjs/util": 9.1.0 + "@keystonehq/animated-qr": 0.10.0 + "@keystonehq/keystone-sdk": 0.11.3 + "@ngraveio/bc-ur": 1.1.13 husky: 9.1.6 lint-staged: 15.2.10 + react-native-popover-view: 6.1.0 + react-native-progress: 5.0.1 languageName: unknown linkType: soft @@ -25769,6 +26150,13 @@ __metadata: languageName: node linkType: hard +"pako@npm:^2.1.0": + version: 2.1.0 + resolution: "pako@npm:2.1.0" + checksum: 71666548644c9a4d056bcaba849ca6fd7242c6cf1af0646d3346f3079a1c7f4a66ffec6f7369ee0dc88f61926c10d6ab05da3e1fca44b83551839e89edd75a3e + languageName: node + linkType: hard + "parent-module@npm:^1.0.0": version: 1.0.1 resolution: "parent-module@npm:1.0.1" @@ -26494,7 +26882,7 @@ __metadata: languageName: node linkType: hard -"prop-types@npm:15.8.1, prop-types@npm:^15.5.10, prop-types@npm:^15.7.2, prop-types@npm:^15.8.0, prop-types@npm:^15.8.1": +"prop-types@npm:*, prop-types@npm:15.8.1, prop-types@npm:^15.5.10, prop-types@npm:^15.7.2, prop-types@npm:^15.8.0, prop-types@npm:^15.8.1": version: 15.8.1 resolution: "prop-types@npm:15.8.1" dependencies: @@ -26716,6 +27104,15 @@ __metadata: languageName: node linkType: hard +"qrcode.react@npm:^3.1.0": + version: 3.2.0 + resolution: "qrcode.react@npm:3.2.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 55d020ca482d57e8d73ee9e2e18f152184fd3d7d2d0742ae54ec58c5a3bab08b242a648585178d7fc91877fc75d6fbad7a35fb51bc4bddd4374e1de450ca78e7 + languageName: node + linkType: hard + "qrcode@npm:^1.5.1": version: 1.5.4 resolution: "qrcode@npm:1.5.4" @@ -27473,6 +27870,27 @@ __metadata: languageName: node linkType: hard +"react-native-popover-view@npm:6.1.0": + version: 6.1.0 + resolution: "react-native-popover-view@npm:6.1.0" + dependencies: + deprecated-react-native-prop-types: ^2.3.0 + prop-types: ^15.8.1 + checksum: acf988e2ad4600c8ad17683909e245f55a600950ad744537218e69da289aa25634be71f26c159e84c735ba54cd384e7b17570707cbdeb213db797fd0e1c6650d + languageName: node + linkType: hard + +"react-native-progress@npm:5.0.1": + version: 5.0.1 + resolution: "react-native-progress@npm:5.0.1" + dependencies: + prop-types: ^15.7.2 + peerDependencies: + react-native-svg: "*" + checksum: fc9b68f1ca381b011859f8900c89595d62461cfa5b4faf65527639e8d8247d494cc75d5eb86e6dd393bca8dec18996b451775670d7ef2ae70247fa4079c9a8cf + languageName: node + linkType: hard + "react-native-qrcode-svg@npm:6.3.2": version: 6.3.2 resolution: "react-native-qrcode-svg@npm:6.3.2" @@ -27765,7 +28183,7 @@ react-native-webview@ava-labs/react-native-webview: peerDependencies: react: "*" react-native: "*" - checksum: 3e4689674406deb2ad9efa9f529597341b4ab33e379b7c7cd72aff8d5e37862fda1a37aba984951a67cc3938969be138bc3c4469e4963dd170b172148339e056 + checksum: eed2ea660a2e7e93a4a9878d6329d21ee93c43c5443ceb724bcbbe7af6ed330e48e4d9954d294f54b6017c52759a795acfb294a789630a3649b9ff2f71684c30 languageName: node linkType: hard @@ -28720,7 +29138,7 @@ react-native-webview@ava-labs/react-native-webview: languageName: node linkType: hard -"ripemd160@npm:2, ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1": +"ripemd160@npm:2, ripemd160@npm:^2.0.0, ripemd160@npm:^2.0.1, ripemd160@npm:^2.0.2": version: 2.0.2 resolution: "ripemd160@npm:2.0.2" dependencies: @@ -28730,6 +29148,30 @@ react-native-webview@ava-labs/react-native-webview: languageName: node linkType: hard +"ripple-address-codec@npm:^4.3.1": + version: 4.3.1 + resolution: "ripple-address-codec@npm:4.3.1" + dependencies: + base-x: ^3.0.9 + create-hash: ^1.1.2 + checksum: 2961fa9ffd508137a8fbf52cc75cd34e76245f515d0f0595f3abb3a29a8df0014518c816d2db45fd6dbab433595f345a048781753fedfddeeb4a47f2d5e9c39e + languageName: node + linkType: hard + +"ripple-binary-codec@npm:^1.4.3": + version: 1.11.0 + resolution: "ripple-binary-codec@npm:1.11.0" + dependencies: + assert: ^2.0.0 + big-integer: ^1.6.48 + buffer: 6.0.3 + create-hash: ^1.2.0 + decimal.js: ^10.2.0 + ripple-address-codec: ^4.3.1 + checksum: 901f6da22bb31860e8c149974c55c72ba5a7d50d635b7efa9be81ce35cea6576a3b0c59b480069141829d73c558721ab17f34df801d4d68af8f3ae4ed0bbd42c + languageName: node + linkType: hard + "rlp@npm:^2.2.4": version: 2.2.7 resolution: "rlp@npm:2.2.7" @@ -29309,6 +29751,18 @@ react-native-webview@ava-labs/react-native-webview: linkType: hard "sha.js@npm:2, sha.js@npm:^2.4.0, sha.js@npm:^2.4.8": + version: 2.4.11 + resolution: "sha.js@npm:2.4.11" + dependencies: + inherits: ^2.0.1 + safe-buffer: ^5.0.1 + bin: + sha.js: ./bin.js + checksum: ebd3f59d4b799000699097dadb831c8e3da3eb579144fd7eb7a19484cbcbb7aca3c68ba2bb362242eb09e33217de3b4ea56e4678184c334323eca24a58e3ad07 + languageName: node + linkType: hard + +"sha.js@npm:^2.4.11": version: 2.4.12 resolution: "sha.js@npm:2.4.12" dependencies: @@ -30787,6 +31241,13 @@ react-native-webview@ava-labs/react-native-webview: languageName: node linkType: hard +"ts-custom-error@npm:^3.2.1": + version: 3.3.1 + resolution: "ts-custom-error@npm:3.3.1" + checksum: 50a1e825fced68d70049bd8d282379a635e43aa023a370fa8e736b12a6edba7f18a2d731fa194ac35303a8b625be56e121bdb31d8a0318250d1a8b277059fce3 + languageName: node + linkType: hard + "ts-dedent@npm:^2.0.0, ts-dedent@npm:^2.2.0": version: 2.2.0 resolution: "ts-dedent@npm:2.2.0" @@ -30937,7 +31398,7 @@ react-native-webview@ava-labs/react-native-webview: languageName: node linkType: hard -"tslib@npm:2.8.1, tslib@npm:^2.8.0": +"tslib@npm:2.8.1, tslib@npm:^2.3.0, tslib@npm:^2.8.0": version: 2.8.1 resolution: "tslib@npm:2.8.1" checksum: e4aba30e632b8c8902b47587fd13345e2827fa639e7c3121074d5ee0880723282411a8838f830b55100cbe4517672f84a2472667d355b81e8af165a55dc6203a