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..630f9475d4
--- /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/hardware/services/KeystoneService.ts b/packages/core-mobile/app/hardware/services/KeystoneService.ts
new file mode 100644
index 0000000000..d346a41b04
--- /dev/null
+++ b/packages/core-mobile/app/hardware/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/hardware/wallet/keystoneSigner.ts b/packages/core-mobile/app/hardware/wallet/keystoneSigner.ts
new file mode 100644
index 0000000000..f9a5a8ca16
--- /dev/null
+++ b/packages/core-mobile/app/hardware/wallet/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/new/common/components/KeystoneQrScanner.tsx b/packages/core-mobile/app/new/common/components/KeystoneQrScanner.tsx
new file mode 100644
index 0000000000..2c22a5fee0
--- /dev/null
+++ b/packages/core-mobile/app/new/common/components/KeystoneQrScanner.tsx
@@ -0,0 +1,131 @@
+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)
+ } 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 5c643ee77f..f873b65c44 100644
--- a/packages/core-mobile/app/new/common/components/NavigationRedirect.tsx
+++ b/packages/core-mobile/app/new/common/components/NavigationRedirect.tsx
@@ -68,6 +68,7 @@ export const NavigationRedirect = (): null => {
} else if (
pathName === '/onboarding/mnemonic/confirmation' ||
pathName === '/onboarding/seedless/confirmation' ||
+ pathName === '/onboarding/keystone/confirmation' ||
(pathName === '/loginWithPinOrBiometry' && !isSignedIn)
) {
// must call dismissAll() here
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/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..96aba207e0
--- /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 { theme } = useTheme()
+ 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/storage/KeystoneDataStorage.ts b/packages/core-mobile/app/new/features/keystone/storage/KeystoneDataStorage.ts
new file mode 100644
index 0000000000..524a6ac0f8
--- /dev/null
+++ b/packages/core-mobile/app/new/features/keystone/storage/KeystoneDataStorage.ts
@@ -0,0 +1,37 @@
+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
+ }
+
+ static clearCache(): void {
+ this.cache = undefined
+ }
+}
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..26a8d79673
--- /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)/keystoneSigner/_layout.tsx b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/keystoneSigner/_layout.tsx
new file mode 100644
index 0000000000..4a96eda371
--- /dev/null
+++ b/packages/core-mobile/app/new/routes/(signedIn)/(modals)/keystoneSigner/_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)/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 a72831b9c0..52537d33c0 100644
--- a/packages/core-mobile/app/new/routes/(signedIn)/_layout.tsx
+++ b/packages/core-mobile/app/new/routes/(signedIn)/_layout.tsx
@@ -43,6 +43,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..58b61dac70
--- /dev/null
+++ b/packages/core-mobile/app/new/routes/onboarding/keystone/createPin.tsx
@@ -0,0 +1,55 @@
+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 { useSelector } from 'react-redux'
+import AnalyticsService from 'services/analytics/AnalyticsService'
+import { WalletType } from 'services/wallet/types'
+import { selectActiveWalletId } from 'store/wallet/slice'
+import BiometricsSDK from 'utils/BiometricsSDK'
+import Logger from 'utils/Logger'
+import { uuid } from 'utils/uuid'
+
+export default function CreatePin(): JSX.Element {
+ const { navigate } = useRouter()
+ const { onPinCreated } = useWallet()
+ const { isBiometricAvailable, useBiometrics, setUseBiometrics } =
+ useStoredBiometrics()
+ const activeWalletId = useSelector(selectActiveWalletId)
+
+ const handleEnteredValidPin = useCallback(
+ (pin: string): void => {
+ AnalyticsService.capture('OnboardingPasswordSet')
+ onPinCreated({
+ walletId: activeWalletId ?? uuid(),
+ mnemonic: uuid(),
+ pin,
+ walletType: WalletType.KEYSTONE
+ })
+ .then(() => {
+ if (useBiometrics) {
+ BiometricsSDK.enableBiometry().catch(Logger.error)
+ }
+ navigate({
+ // @ts-ignore TODO: make routes typesafe
+ pathname: '/onboarding/keystone/setWalletName'
+ })
+ })
+ .catch(Logger.error)
+ },
+ [navigate, onPinCreated, useBiometrics]
+ )
+
+ 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..8913a415b1
--- /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 'hardware/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 630db1454c..c62f234b0c 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.test.ts b/packages/core-mobile/app/services/wallet/KeystoneWallet.test.ts
new file mode 100644
index 0000000000..4b81b55117
--- /dev/null
+++ b/packages/core-mobile/app/services/wallet/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 'hardware/wallet/keystoneSigner'
+import KeystoneWallet from './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.ts b/packages/core-mobile/app/services/wallet/KeystoneWallet.ts
new file mode 100644
index 0000000000..1f522e9f85
--- /dev/null
+++ b/packages/core-mobile/app/services/wallet/KeystoneWallet.ts
@@ -0,0 +1,437 @@
+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 'hardware/wallet/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 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/WalletFactory.ts b/packages/core-mobile/app/services/wallet/WalletFactory.ts
index fa48fef2a4..3861863c07 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 './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 fa95131cec..112b419865 100644
--- a/packages/core-mobile/app/services/wallet/WalletService.tsx
+++ b/packages/core-mobile/app/services/wallet/WalletService.tsx
@@ -333,7 +333,7 @@ class WalletService {
return []
}
- if (walletType === WalletType.MNEMONIC) {
+ if ([WalletType.MNEMONIC, WalletType.KEYSTONE].includes(walletType)) {
const provXP = await NetworkService.getAvalancheProviderXP(isTestnet)
const publicKeys = await this.getPublicKey(walletId, walletType, account)
const xpubXP = publicKeys.xp
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 3116078dec..aec4b760c5 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 'hardware/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 8f76f79bb7..6592cef3da 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 0ff0c006dc..4483e38d30 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/jest.config.js b/packages/core-mobile/jest.config.js
index cef365fd75..e206b496ab 100644
--- a/packages/core-mobile/jest.config.js
+++ b/packages/core-mobile/jest.config.js
@@ -31,7 +31,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 15ecee647d..76e31f7479 100644
--- a/packages/core-mobile/package.json
+++ b/packages/core-mobile/package.json
@@ -49,15 +49,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/secp256k1": "2.1.0",
"@notifee/react-native": "9.1.8",
"@openzeppelin/contracts": "5.0.2",
@@ -180,6 +184,7 @@
"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.11",
diff --git a/yarn.lock b/yarn.lock
index 14a2e939c0..4c58c9372c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -225,16 +225,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/secp256k1": 2.1.0
"@notifee/react-native": 9.1.8
"@openzeppelin/contracts": 5.0.2
@@ -399,6 +403,7 @@ __metadata:
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.11
@@ -4157,6 +4162,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"
@@ -4592,6 +4604,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"
@@ -4603,16 +4625,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"
@@ -7113,6 +7125,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"
@@ -7782,6 +8038,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"
@@ -13753,7 +14024,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
@@ -14390,7 +14688,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:
@@ -14839,6 +15137,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"
@@ -14883,7 +15190,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
@@ -14904,6 +15211,13 @@ __metadata:
languageName: node
linkType: hard
+"bignumber.js@npm:^9.0.1":
+ version: 9.3.0
+ resolution: "bignumber.js@npm:9.3.0"
+ checksum: 580d783d60246e758e527fa879ae0d282d8f250f555dd0fcee1227d680186ceba49ed7964c6d14e2e8d8eac7a2f4dd6ef1b7925dc52f5fc28a5a87639dd2dbd1
+ 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"
@@ -15401,7 +15715,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:
@@ -15689,6 +16003,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"
@@ -16409,6 +16730,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"
@@ -17087,13 +17417,20 @@ __metadata:
languageName: node
linkType: hard
-"decimal.js@npm:^10.4.3":
+"decimal.js@npm:^10.2.0":
version: 10.5.0
resolution: "decimal.js@npm:10.5.0"
checksum: 91c6b53b5dd2f39a05535349ced6840f591d1f914e3c025c6dcec6ffada6e3cfc8dc3f560d304b716be9a9aece3567a7f80f6aff8f38d11ab6f78541c3a91a01
languageName: node
linkType: hard
+"decimal.js@npm:^10.4.3":
+ version: 10.6.0
+ resolution: "decimal.js@npm:10.6.0"
+ checksum: 9302b990cd6f4da1c7602200002e40e15d15660374432963421d3cd6d81cc6e27e0a488356b030fee64650947e32e78bdbea245d596dadfeeeb02e146d485999
+ languageName: node
+ linkType: hard
+
"decode-uri-component@npm:^0.2.2":
version: 0.2.2
resolution: "decode-uri-component@npm:0.2.2"
@@ -20829,6 +21166,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"
@@ -22715,6 +23064,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"
@@ -25727,6 +26083,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"
@@ -26696,6 +27059,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"
@@ -27477,6 +27849,17 @@ __metadata:
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 +28148,7 @@ react-native-webview@ava-labs/react-native-webview:
peerDependencies:
react: "*"
react-native: "*"
- checksum: 77324747a8b5df0a5558bb99a9a0804a5575d328e84f480e462c56417af97213f38a9930e4582fb749bec374dc4d1a8910a45b006e77af9b14a8e64057b932bf
+ checksum: 6f005151d0b9f4210ad58a55706334c950077e45bb9358a6e2d992386d4079c6f1eac04911a925ffbb839cb7ab847a0cff1177af0fc61cf3172e0a0bb018ba37
languageName: node
linkType: hard
@@ -28738,7 +29121,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:
@@ -28748,6 +29131,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"
@@ -29257,7 +29664,7 @@ react-native-webview@ava-labs/react-native-webview:
languageName: node
linkType: hard
-"sha.js@npm:2, sha.js@npm:^2.4.0, sha.js@npm:^2.4.8":
+"sha.js@npm:2, sha.js@npm:^2.4.0, sha.js@npm:^2.4.11, sha.js@npm:^2.4.8":
version: 2.4.11
resolution: "sha.js@npm:2.4.11"
dependencies:
@@ -30698,6 +31105,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"
@@ -30848,7 +31262,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