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 && (
+ <>
+
+
+ >
+ )}
+
+
+ )
+}
+
+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 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