mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
Import wallet from backup and fixes
This commit is contained in:
+3
-2
@@ -68,8 +68,9 @@ function App(props: AppProps) {
|
||||
// This runs after the root store has been initialized and rehydrated from storage.
|
||||
|
||||
// Creates and opens a sqlite database that stores transactions history and user settings.
|
||||
// It triggers db migrations if database version has changed.
|
||||
Database.getDatabaseVersion()
|
||||
// It triggers db migrations if database version has changed.
|
||||
const db = Database.getInstance()
|
||||
Database.getDatabaseVersion(db)
|
||||
|
||||
// Syncs userSettings store with the database (needed?)
|
||||
userSettingsStore.loadUserSettings()
|
||||
|
||||
@@ -309,7 +309,7 @@
|
||||
"changeWalletaddress": "Change wallet address",
|
||||
"changeWalletaddressSubtext": "Change unique wallet address. Select one of randomly generated or choose your own."
|
||||
},
|
||||
"profileToRecoverDesc": "This is the wallet address linked to your seed. If you press Complete, your wallet profile will reset to above seed and address.",
|
||||
"profileToRecoverDesc": "This is the wallet address linked to your seed. If you continue, your wallet profile will reset to above seed and address.",
|
||||
"pushNotifications": "Push notifications",
|
||||
"qr": {
|
||||
"fail": "Could not display QR code, use option to copy or send the token to the payee wallet address."
|
||||
|
||||
@@ -46,7 +46,7 @@ export const RelaysStoreModel = types
|
||||
relay.url = normalized
|
||||
|
||||
if(self.alreadyExists(relay.url)) {
|
||||
log.warn('[addRelay] Relay already exists', {relay})
|
||||
log.info('[addRelay] Relay already exists', {relay})
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
@@ -16,9 +16,8 @@ import {
|
||||
} from 'mobx-state-tree'
|
||||
import { debounce } from "lodash"
|
||||
import * as Sentry from '@sentry/react-native'
|
||||
import RNExitApp from 'react-native-exit-app'
|
||||
import type {RootStore} from '../RootStore'
|
||||
import {KeyChain, MinibitsClient, MMKVStorage, NostrClient} from '../../services'
|
||||
import {KeyChain, MinibitsClient, MMKVStorage} from '../../services'
|
||||
import {Database} from '../../services'
|
||||
import { log } from '../../services/logService'
|
||||
import { rootStoreModelVersion } from '../RootStore'
|
||||
@@ -62,7 +61,7 @@ export async function setupRootStore(rootStore: RootStore) {
|
||||
|
||||
log.trace('[setupRootStore]', `Loading ${dataSize.toLocaleString()} bytes of state from MMKV took ${(mmkvLoaded - start).toLocaleString()} ms.`)
|
||||
|
||||
log.trace({restoredState})
|
||||
// log.trace({restoredState})
|
||||
|
||||
applySnapshot(rootStore, restoredState)
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ export const BackupOptionsScreen: FC<SettingsStackScreenProps<'BackupOptions'>>
|
||||
tx="backupScreen.mnemonicTitle"
|
||||
subTx="backupScreen.mnemonicDescription"
|
||||
leftIcon='faSeedling'
|
||||
leftIconColor={colors.palette.blue200}
|
||||
leftIconColor={colors.palette.orange400}
|
||||
leftIconInverse={true}
|
||||
style={$item}
|
||||
onPress={gotoMnemonic}
|
||||
|
||||
@@ -35,6 +35,8 @@ import {MMKVStorage} from '../services'
|
||||
import {maxTransactionsInModel} from '../models/TransactionsStore'
|
||||
import { LogLevel } from '../services/log/logTypes'
|
||||
import { getSnapshot } from 'mobx-state-tree'
|
||||
import { delay } from '../utils/delay'
|
||||
import RNExitApp from 'react-native-exit-app'
|
||||
|
||||
// refresh
|
||||
|
||||
@@ -50,6 +52,7 @@ export const DeveloperScreen: FC<SettingsStackScreenProps<'Developer'>> = observ
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [rnVersion, setRnVersion] = useState<string>('')
|
||||
const [walletStateSize, setWalletStateSize] = useState<number>(0)
|
||||
const [dbVersion, setDbVersion] = useState<number>(0)
|
||||
const [isLogLevelSelectorVisible, setIsLogLevelSelectorVisible] = useState<boolean>(false)
|
||||
const [selectedLogLevel, setSelectedLogLevel] = useState<LogLevel>(userSettingsStore.logLevel)
|
||||
const [error, setError] = useState<AppError | undefined>()
|
||||
@@ -62,6 +65,10 @@ export const DeveloperScreen: FC<SettingsStackScreenProps<'Developer'>> = observ
|
||||
// log.info('[SNAPSHOT]', {snapshot})
|
||||
const stateSize = Buffer.byteLength(JSON.stringify(snapshot), 'utf8')
|
||||
|
||||
const db = Database.getInstance()
|
||||
const {version} = Database.getDatabaseVersion(db)
|
||||
|
||||
setDbVersion(version)
|
||||
setWalletStateSize(stateSize)
|
||||
setRnVersion(rn)
|
||||
}
|
||||
@@ -143,6 +150,8 @@ export const DeveloperScreen: FC<SettingsStackScreenProps<'Developer'>> = observ
|
||||
Database.getInstance()
|
||||
setIsLoading(false)
|
||||
setInfo(translate("factoryResetSuccess"))
|
||||
await delay(1000)
|
||||
RNExitApp.exitApp()
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
@@ -187,20 +196,6 @@ export const DeveloperScreen: FC<SettingsStackScreenProps<'Developer'>> = observ
|
||||
bottomSeparator={true}
|
||||
onPress={toggleLogLevelSelector}
|
||||
/>
|
||||
{/*<ListItem
|
||||
tx="developerScreen.transactions"
|
||||
subText={translate(
|
||||
'developerScreen.transactionsDescription',
|
||||
{count: transactionsStore.count},
|
||||
)}
|
||||
leftIcon='faDownload'
|
||||
leftIconColor={colors.palette.iconYellow300}
|
||||
leftIconInverse={true}
|
||||
RightComponent={<View style={$rightContainer} />}
|
||||
style={$item}
|
||||
bottomSeparator={true}
|
||||
onPress={syncTransactionsFromDb}
|
||||
/>*/}
|
||||
<ListItem
|
||||
tx="showOnboarding"
|
||||
subTx="showOnboardingDesc"
|
||||
@@ -223,7 +218,7 @@ export const DeveloperScreen: FC<SettingsStackScreenProps<'Developer'>> = observ
|
||||
subText={`Environment: ${APP_ENV}
|
||||
Native version: ${NATIVE_VERSION_ANDROID}
|
||||
JS Bundle version: ${JS_BUNDLE_VERSION}
|
||||
DB version: ${Database.getDatabaseVersion().version}
|
||||
DB version: ${dbVersion}
|
||||
State size: ${walletStateSize.toLocaleString()} bytes
|
||||
React Native: ${rnVersion}
|
||||
Commit: ${COMMIT}
|
||||
|
||||
@@ -64,8 +64,11 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
useEffect(() => {
|
||||
const loadProofs = async () => {
|
||||
setIsLoading(true)
|
||||
// log.trace('[loadProofs]', {proofs: proofsStore.proofs})
|
||||
// full refresh of proofs from DB in case the state is broken
|
||||
await proofsStore.loadProofsFromDatabase()
|
||||
|
||||
// log.trace('[loadProofs]', {refreshedProofs: proofsStore.proofs})
|
||||
setIsLoading(false)
|
||||
}
|
||||
|
||||
@@ -117,7 +120,14 @@ export const ExportBackupScreen: FC<ExportBackupScreenProps> =
|
||||
}
|
||||
|
||||
if(isEcashInBackup) {
|
||||
exportedProofsStore = getSnapshot(proofsStore)
|
||||
// This is emptied in snapshot postprocess!
|
||||
exportedProofsStore = {
|
||||
proofs: getSnapshot(proofsStore.proofs),
|
||||
pendingProofs: getSnapshot(proofsStore.pendingProofs),
|
||||
pendingByMintSecrets: getSnapshot(proofsStore.pendingByMintSecrets)
|
||||
}
|
||||
|
||||
// log.trace({exportedProofsStore})
|
||||
}
|
||||
|
||||
if(isMintsInBackup) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import { btoa, atob } from 'react-native-quick-base64'
|
||||
import QuickCrypto from 'react-native-quick-crypto'
|
||||
import { wordlist } from '@scure/bip39/wordlists/english'
|
||||
import {colors, spacing, typography, useThemeColor} from '../theme'
|
||||
import {AppStackScreenProps} from '../navigation' // @demo remove-current-line
|
||||
import {AppStackScreenProps, goBack} from '../navigation' // @demo remove-current-line
|
||||
import {
|
||||
Icon,
|
||||
ListItem,
|
||||
@@ -17,30 +17,21 @@ import {
|
||||
ErrorModal,
|
||||
InfoModal,
|
||||
BottomModal,
|
||||
Button,
|
||||
$sizeStyles,
|
||||
Button,
|
||||
} from '../components'
|
||||
import {useHeader} from '../utils/useHeader'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
import { KeyChain, log, MinibitsClient, NostrClient } from '../services'
|
||||
import { Database, KeyChain, log, MinibitsClient, NostrClient } from '../services'
|
||||
import Clipboard from '@react-native-clipboard/clipboard'
|
||||
import { useStores } from '../models'
|
||||
import { MintListItem } from './Mints/MintListItem'
|
||||
import { rootStoreInstance, useStores } from '../models'
|
||||
import {MnemonicInput} from './Recovery/MnemonicInput'
|
||||
import { Mint } from '../models/Mint'
|
||||
import { MintKeyset } from '@cashu/cashu-ts'
|
||||
import { CashuUtils } from '../services/cashu/cashuUtils'
|
||||
import { Proof } from '../models/Proof'
|
||||
import { Transaction, TransactionData, TransactionRecord, TransactionStatus, TransactionType } from '../models/Transaction'
|
||||
import { TransactionData, TransactionStatus } from '../models/Transaction'
|
||||
import { ResultModalInfo } from './Wallet/ResultModalInfo'
|
||||
import { deriveSeedFromMnemonic } from '@cashu/cashu-ts'
|
||||
import { MINIBITS_NIP05_DOMAIN } from '@env'
|
||||
import { MINIBITS_MINT_URL, MINIBITS_NIP05_DOMAIN } from '@env'
|
||||
import { delay } from '../utils/utils'
|
||||
import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
|
||||
import { scale, verticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { WalletUtils } from '../services/wallet/utils'
|
||||
import { MintUnit, formatCurrency, getCurrency } from '../services/wallet/currency'
|
||||
import { isObj } from '@cashu/cashu-ts/src/utils'
|
||||
import { applySnapshot} from 'mobx-state-tree'
|
||||
import { verticalScale } from '@gocodingnow/rn-size-matters'
|
||||
import { WalletProfileRecord } from '../models/WalletProfileStore'
|
||||
import { translate } from '../i18n'
|
||||
import { ProofsStoreSnapshot } from '../models/ProofsStore'
|
||||
@@ -66,7 +57,8 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
mintsStore,
|
||||
proofsStore,
|
||||
userSettingsStore,
|
||||
transactionsStore,
|
||||
transactionsStore,
|
||||
contactsStore,
|
||||
walletProfileStore,
|
||||
walletStore
|
||||
} = useStores()
|
||||
@@ -82,7 +74,12 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
const [seed, setSeed] = useState<Uint8Array>()
|
||||
const [backup, setBackup] = useState<string>('')
|
||||
const [isValidBackup, setIsValidBackup] = useState(false)
|
||||
const [walletSnapshot, setWalletSnapshot] = useState<any | undefined>(undefined) // type tbd
|
||||
const [walletSnapshot, setWalletSnapshot] = useState<{
|
||||
proofsStore: ProofsStoreSnapshot,
|
||||
mintsStore: MintsStoreSnapshot,
|
||||
contactsStore: ContactsStoreSnapshot,
|
||||
transactionsStore: TransactionsStoreSnapshot
|
||||
} | undefined>(undefined) // type tbd
|
||||
const [profileToRecover, setProfileToRecover] = useState<WalletProfileRecord | undefined>(undefined)
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const [error, setError] = useState<AppError | undefined>()
|
||||
@@ -115,17 +112,14 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
setIsResultModalVisible(previousState => !previousState)
|
||||
}
|
||||
|
||||
|
||||
// Used by both Wallet address and Import backup flows
|
||||
const onConfirmMnemonic = async function () {
|
||||
try {
|
||||
setStatusMessage(translate("derivingSeedStatus"))
|
||||
|
||||
if(!mnemonic) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate('backupScreen.missingMnemonicError'))
|
||||
}
|
||||
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setIsLoading(true)
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
|
||||
if (!validateMnemonic(mnemonic, wordlist)) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate("recoveryInvalidMnemonicError"))
|
||||
@@ -137,15 +131,13 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
console.log(`[onConfirm] deriveSeedFromMnemonic took ${end - start} ms.`)
|
||||
|
||||
setSeed(binarySeed)
|
||||
setIsValidMnemonic(true)
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setIsLoading(false)
|
||||
setIsValidMnemonic(true)
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Used by Import backup flow
|
||||
const onPasteBackup = async function () {
|
||||
try {
|
||||
const maybeBackup = await Clipboard.getString()
|
||||
@@ -162,7 +154,7 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Used by Import backup flow
|
||||
const getWalletSnapshot = function () {
|
||||
try {
|
||||
// decode
|
||||
@@ -187,22 +179,19 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Used by Import backup flow
|
||||
const onConfirmBackup = async function () {
|
||||
try {
|
||||
if(!backup) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing backup.')
|
||||
}
|
||||
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setIsLoading(true)
|
||||
|
||||
const snapshot = getWalletSnapshot() // throws
|
||||
|
||||
setWalletSnapshot(snapshot)
|
||||
setIsValidBackup(true)
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setIsLoading(false)
|
||||
|
||||
await onFindWalletAddress()
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
@@ -213,20 +202,16 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
return navigation.goBack()
|
||||
}
|
||||
|
||||
|
||||
const startImport = async function () {
|
||||
setStatusMessage(translate("recovery.starting"))
|
||||
setIsLoading(true)
|
||||
// setTimeout(() => doRecovery(), 100)
|
||||
}
|
||||
|
||||
|
||||
// Used by both Wallet address and Import backup flows
|
||||
const onFindWalletAddress = async () => {
|
||||
try {
|
||||
if(!seed || !mnemonic) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate("backupScreen.missingMnemonicOrSeedError"))
|
||||
}
|
||||
|
||||
setStatusMessage(translate('recovery.recoveringAddress'))
|
||||
setIsLoading(true)
|
||||
|
||||
const seedHash = QuickCrypto.createHash('sha256')
|
||||
.update(seed)
|
||||
.digest('hex')
|
||||
@@ -244,14 +229,68 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
await delay(4000)
|
||||
}
|
||||
} else {
|
||||
setInfo(translate("recovery.noWalletForSeedError"))
|
||||
}
|
||||
if(isAddressOnlyRecovery) {
|
||||
setInfo(translate("recovery.noWalletForSeedError"))
|
||||
}
|
||||
}
|
||||
|
||||
setStatusMessage('')
|
||||
setIsLoading(false)
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Used by Import backup flow
|
||||
const importWallet = async function () {
|
||||
try {
|
||||
if(!walletSnapshot) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Missing wallet spnapshot decoded from backup.')
|
||||
}
|
||||
|
||||
setStatusMessage(translate("recovery.starting"))
|
||||
setIsLoading(true)
|
||||
|
||||
// import wallet snapshot into the state
|
||||
const rootStore = rootStoreInstance
|
||||
|
||||
log.trace('Before import', {rootStore})
|
||||
log.trace('Snapshot ot be imported', {walletSnapshot})
|
||||
|
||||
applySnapshot(proofsStore, walletSnapshot.proofsStore)
|
||||
applySnapshot(mintsStore, walletSnapshot.mintsStore)
|
||||
applySnapshot(contactsStore, walletSnapshot.contactsStore)
|
||||
applySnapshot(transactionsStore, walletSnapshot.transactionsStore)
|
||||
|
||||
log.trace('After import', {rootStore})
|
||||
|
||||
// import proofs into the db
|
||||
if(proofsStore.proofsCount > 0) {
|
||||
Database.addOrUpdateProofs(proofsStore.allProofs, false, false)
|
||||
}
|
||||
|
||||
if(proofsStore.pendingProofsCount > 0) {
|
||||
Database.addOrUpdateProofs(proofsStore.allPendingProofs, true, false)
|
||||
}
|
||||
|
||||
if(!mintsStore.mintExists(MINIBITS_MINT_URL)) {
|
||||
await mintsStore.addMint(MINIBITS_MINT_URL)
|
||||
}
|
||||
|
||||
if(profileToRecover) {
|
||||
// If wallet profile has been found on server, recover wallet address
|
||||
await onCompleteAddress()
|
||||
} else {
|
||||
// If there is no profile go back to the welcome screen to create a new one
|
||||
goBack()
|
||||
}
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Used by both Wallet address and Import backup flows
|
||||
const onCompleteAddress = async () => {
|
||||
try {
|
||||
if(!seed || !mnemonic || !profileToRecover) {
|
||||
@@ -265,7 +304,7 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
.update(seed)
|
||||
.digest('hex')
|
||||
|
||||
// get nostr key from current on device profile
|
||||
// get nostr key from current on device profile (if we recover to existing wallet in Settings) or create new one (if we recover to new install)
|
||||
const {publicKey: newPublicKey} = await NostrClient.getOrCreateKeyPair()
|
||||
|
||||
// delete orphaned server profile then update server profile with seedHash with pubkey + update on device wallet name and address
|
||||
@@ -275,16 +314,19 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
userSettingsStore.setWalletId(walletProfileStore.walletId)
|
||||
await KeyChain.saveMnemonic(mnemonic)
|
||||
await KeyChain.saveSeed(seed as Uint8Array)
|
||||
|
||||
await delay(1000)
|
||||
setStatusMessage(translate("recovery.completed"))
|
||||
await delay(2000)
|
||||
|
||||
|
||||
userSettingsStore.setIsOnboarded(true)
|
||||
|
||||
setStatusMessage(translate('recovery.completed'))
|
||||
await delay(1000)
|
||||
setStatusMessage('')
|
||||
setIsLoading(false)
|
||||
navigation.goBack()
|
||||
|
||||
if(isAddressOnlyRecovery) {
|
||||
// go back to Settings
|
||||
navigation.goBack()
|
||||
} else {
|
||||
navigation.navigate('Tabs', {screen: 'WalletNavigator', params: {screen: 'Wallet', params: {}}})
|
||||
}
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
@@ -301,7 +343,7 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
const inputBg = useThemeColor('background')
|
||||
const headerTitle = useThemeColor('headerTitle')
|
||||
|
||||
if(false) {
|
||||
if(mnemonicExists && !isAddressOnlyRecovery) {
|
||||
return (
|
||||
<Screen contentContainerStyle={$screen} preset="auto">
|
||||
<View style={[$headerContainer, {backgroundColor: headerBg}]}>
|
||||
@@ -340,7 +382,7 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
<Text
|
||||
preset="heading"
|
||||
text={isAddressOnlyRecovery ? "Recover address" : "Import backup"}
|
||||
style={{color: headerTitle, zIndex: 10}}
|
||||
style={{color: headerTitle, textAlign: 'center'}}
|
||||
/>
|
||||
</View>
|
||||
<View style={$contentContainer}>
|
||||
@@ -369,7 +411,7 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
ref={backupInputRef}
|
||||
onChangeText={(backup: string) => setBackup(backup)}
|
||||
value={backup}
|
||||
numberOfLines={5}
|
||||
numberOfLines={3}
|
||||
multiline={true}
|
||||
autoCapitalize='none'
|
||||
keyboardType='default'
|
||||
@@ -401,7 +443,7 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
ContentComponent={
|
||||
<ListItem
|
||||
text='Wallet backup'
|
||||
subText={`${backup.slice(0, 100)}...`}
|
||||
subText={`${backup.slice(0, 50)}...`}
|
||||
subTextStyle={{fontFamily: typography.code?.normal}}
|
||||
LeftComponent={<View style={[$numIcon, {backgroundColor: numIconColor}]}><Text text='2'/></View>}
|
||||
style={$item}
|
||||
@@ -409,14 +451,14 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{isValidMnemonic && profileToRecover && isAddressOnlyRecovery && (
|
||||
{isValidMnemonic && profileToRecover && (
|
||||
<Card
|
||||
style={$card}
|
||||
ContentComponent={
|
||||
<ListItem
|
||||
text={profileToRecover.nip05}
|
||||
subTx="profileToRecoverDesc"
|
||||
LeftComponent={<View style={[$numIcon, {backgroundColor: numIconColor}]}><Text text='2'/></View>}
|
||||
LeftComponent={<View style={[$numIcon, {backgroundColor: numIconColor}]}><Text text={isAddressOnlyRecovery ? '2' : '3'}/></View>}
|
||||
style={$item}
|
||||
/>
|
||||
}
|
||||
@@ -455,16 +497,21 @@ export const ImportBackupScreen: FC<AppStackScreenProps<'ImportBackup'>> = obser
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<Button
|
||||
text={`Import wallet`}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faDownload'
|
||||
size={spacing.medium}
|
||||
<>
|
||||
{isValidBackup && (
|
||||
<Button
|
||||
text={`Import wallet`}
|
||||
LeftAccessory={() => (
|
||||
<Icon
|
||||
icon='faDownload'
|
||||
size={spacing.medium}
|
||||
/>
|
||||
)}
|
||||
onPress={importWallet}
|
||||
/>
|
||||
)}
|
||||
onPress={startImport}
|
||||
/>
|
||||
</>
|
||||
|
||||
)}
|
||||
|
||||
</View>
|
||||
|
||||
@@ -160,7 +160,7 @@ export const RecoveryOptionsScreen: FC<AppStackScreenProps<'RecoveryOptions'>> =
|
||||
tx="recoveryOptions.fromSeed"
|
||||
subTx="recoveryOptions.fromSeedDescription"
|
||||
leftIcon='faSeedling'
|
||||
leftIconColor={colors.palette.blue200}
|
||||
leftIconColor={colors.palette.orange400}
|
||||
leftIconInverse={true}
|
||||
style={$item}
|
||||
onPress={gotoSeedRecovery}
|
||||
@@ -178,7 +178,7 @@ export const RecoveryOptionsScreen: FC<AppStackScreenProps<'RecoveryOptions'>> =
|
||||
tx="walletAddressRecovery"
|
||||
subTx="walletAddressRecoveryDesc"
|
||||
leftIcon='faCircleUser'
|
||||
leftIconColor={colors.palette.iconGreyBlue400}
|
||||
leftIconColor={colors.palette.iconViolet300}
|
||||
leftIconInverse={true}
|
||||
style={$item}
|
||||
onPress={gotoAddressRecovery}
|
||||
|
||||
@@ -32,7 +32,7 @@ import { Proof } from '../models/Proof'
|
||||
import { Transaction, TransactionData, TransactionRecord, TransactionStatus, TransactionType } from '../models/Transaction'
|
||||
import { ResultModalInfo } from './Wallet/ResultModalInfo'
|
||||
import { deriveSeedFromMnemonic } from '@cashu/cashu-ts'
|
||||
import { MINIBITS_NIP05_DOMAIN } from '@env'
|
||||
import { MINIBITS_MINT_URL, MINIBITS_NIP05_DOMAIN } from '@env'
|
||||
import { delay } from '../utils/utils'
|
||||
import { getSnapshot, isStateTreeNode } from 'mobx-state-tree'
|
||||
import { scale } from '@gocodingnow/rn-size-matters'
|
||||
@@ -143,31 +143,23 @@ export const SeedRecoveryScreen: FC<AppStackScreenProps<'SeedRecovery'>> = obser
|
||||
|
||||
const onConfirm = async function () {
|
||||
try {
|
||||
setStatusMessage(translate("derivingSeedStatus"))
|
||||
|
||||
if(!mnemonic) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate('backupScreen.missingMnemonicError'))
|
||||
}
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setIsLoading(true)
|
||||
}
|
||||
|
||||
if (!validateMnemonic(mnemonic, wordlist)) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, translate("recoveryInvalidMnemonicError"))
|
||||
}
|
||||
}
|
||||
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
|
||||
setTimeout(() => {
|
||||
const start = performance.now()
|
||||
|
||||
const binarySeed = deriveSeedFromMnemonic(mnemonic) // expensive
|
||||
|
||||
const end = performance.now()
|
||||
console.log(`[onConfirm] deriveSeedFromMnemonic took ${end - start} ms.`)
|
||||
|
||||
setSeed(binarySeed)
|
||||
setIsValidMnemonic(true)
|
||||
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
|
||||
setIsLoading(false)
|
||||
}, 200)
|
||||
const start = performance.now()
|
||||
const binarySeed = deriveSeedFromMnemonic(mnemonic)
|
||||
const end = performance.now()
|
||||
console.log(`[onConfirm] deriveSeedFromMnemonic took ${end - start} ms.`)
|
||||
|
||||
setSeed(binarySeed)
|
||||
setIsValidMnemonic(true)
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
@@ -487,12 +479,10 @@ export const SeedRecoveryScreen: FC<AppStackScreenProps<'SeedRecovery'>> = obser
|
||||
|
||||
setStatusMessage(translate('recovery.recoveringAddress'))
|
||||
setIsLoading(true)
|
||||
|
||||
await KeyChain.saveMnemonic(mnemonic)
|
||||
await KeyChain.saveSeed(seed as Uint8Array)
|
||||
|
||||
// Wallet address recovery
|
||||
const seedHash = await KeyChain.loadSeedHash()
|
||||
const seedHash = QuickCrypto.createHash('sha256')
|
||||
.update(seed)
|
||||
.digest('hex')
|
||||
|
||||
log.trace('[onComplete]', 'getWalletProfileBySeedHash')
|
||||
const profileToRecover = await MinibitsClient.getWalletProfileBySeedHash(seedHash as string)
|
||||
@@ -508,19 +498,27 @@ export const SeedRecoveryScreen: FC<AppStackScreenProps<'SeedRecovery'>> = obser
|
||||
await walletProfileStore.recover(seedHash as string, newPublicKey)
|
||||
// Align walletId in userSettings with recovered profile
|
||||
userSettingsStore.setWalletId(walletProfileStore.walletId)
|
||||
await delay(1000)
|
||||
setStatusMessage(translate('recovery.completed'))
|
||||
await delay(2000)
|
||||
|
||||
|
||||
} else {
|
||||
setInfo(translate("recovery.ownKeysImportAgain", { addr: profileToRecover.nip05 }))
|
||||
await delay(5000)
|
||||
await delay(4000)
|
||||
}
|
||||
}
|
||||
|
||||
userSettingsStore.setIsOnboarded(true)
|
||||
await KeyChain.saveMnemonic(mnemonic)
|
||||
await KeyChain.saveSeed(seed as Uint8Array)
|
||||
|
||||
if(!mintsStore.mintExists(MINIBITS_MINT_URL)) {
|
||||
await mintsStore.addMint(MINIBITS_MINT_URL)
|
||||
}
|
||||
|
||||
setStatusMessage(translate('recovery.completed'))
|
||||
await delay(2000)
|
||||
setStatusMessage('')
|
||||
setIsLoading(false)
|
||||
navigation.navigate('Tabs')
|
||||
navigation.navigate('Tabs', {screen: 'WalletNavigator', params: {screen: 'Wallet', params: {}}})
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ export const WelcomeScreen: FC<AppStackScreenProps<'Welcome'>> =
|
||||
|
||||
const [error, setError] = useState<AppError | undefined>()
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false)
|
||||
const [statusMessage, setStatusMessage] = useState<string>('')
|
||||
const [info, setInfo] = useState<string>('')
|
||||
|
||||
const gotoWallet = async function () {
|
||||
@@ -107,10 +108,13 @@ export const WelcomeScreen: FC<AppStackScreenProps<'Welcome'>> =
|
||||
|
||||
// do not overwrite if one was set during recovery
|
||||
setIsLoading(true)
|
||||
const mnemonic = await walletStore.getOrCreateMnemonic()
|
||||
setStatusMessage('Setting wallet seed...')
|
||||
|
||||
await walletStore.getOrCreateMnemonic()
|
||||
|
||||
// move new profile creation to the app start so that device token can register
|
||||
if(!walletProfileStore.pubkey || !walletProfileStore.picture) {
|
||||
if(!walletProfileStore.pubkey || !walletProfileStore.picture) {
|
||||
setStatusMessage('Creating wallet profile...')
|
||||
const walletId = userSettingsStore.walletId
|
||||
await walletProfileStore.create(walletId as string)
|
||||
}
|
||||
@@ -121,9 +125,12 @@ export const WelcomeScreen: FC<AppStackScreenProps<'Welcome'>> =
|
||||
|
||||
relaysStore.addDefaultRelays()
|
||||
|
||||
userSettingsStore.setIsOnboarded(true)
|
||||
navigation.navigate('Tabs')
|
||||
userSettingsStore.setIsOnboarded(true)
|
||||
setStatusMessage('')
|
||||
setIsLoading(false)
|
||||
|
||||
navigation.navigate('Tabs', {screen: 'WalletNavigator', params: {screen: 'Wallet', params: {}}})
|
||||
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
}
|
||||
@@ -269,7 +276,7 @@ export const WelcomeScreen: FC<AppStackScreenProps<'Welcome'>> =
|
||||
</View>
|
||||
{error && <ErrorModal error={error} />}
|
||||
{info && <InfoModal message={info} />}
|
||||
{isLoading && <Loading statusMessage={translate("welcomeScreen.generatingWalletSeedStatusMessage")} style={{backgroundColor: headerBg, opacity: 1}} textStyle={{color: 'white'}}/>}
|
||||
{isLoading && <Loading statusMessage={statusMessage} style={{backgroundColor: headerBg, opacity: 1}} textStyle={{color: 'white'}}/>}
|
||||
</Screen>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -241,6 +241,7 @@ const clearAll = function (): void {
|
||||
try {
|
||||
const storage = getInstance()
|
||||
storage.clearAll()
|
||||
log.trace('[clearAll] Wallet state cleared.')
|
||||
} catch (e: any) {
|
||||
throw new AppError(Err.DATABASE_ERROR, e.message)
|
||||
}
|
||||
|
||||
@@ -122,7 +122,7 @@ const _createOrUpdateSchema = function (db: QuickSQLiteConnection) {
|
||||
log.info('[_createOrUpdateSchema] New database schema created')
|
||||
}
|
||||
|
||||
const {version} = getDatabaseVersion()
|
||||
const {version} = getDatabaseVersion(db)
|
||||
log.info('[_createOrUpdateSchema]', `Device database version: ${version}`)
|
||||
|
||||
// Trigger migrations if there is versions mismatch
|
||||
@@ -143,7 +143,7 @@ const _createOrUpdateSchema = function (db: QuickSQLiteConnection) {
|
||||
|
||||
const _runMigrations = function (db: QuickSQLiteConnection) {
|
||||
const now = new Date()
|
||||
const {version} = getDatabaseVersion()
|
||||
const {version} = getDatabaseVersion(db)
|
||||
|
||||
let currentVersion = version
|
||||
let migrationQueries: SQLBatchTuple[] = []
|
||||
@@ -383,12 +383,12 @@ const cleanAll = function () {
|
||||
}
|
||||
|
||||
|
||||
const getDatabaseVersion = function (): {version: number} {
|
||||
const getDatabaseVersion = function (db: QuickSQLiteConnection): {version: number} {
|
||||
try {
|
||||
const query = `
|
||||
SELECT version FROM dbVersion
|
||||
`
|
||||
const db = getInstance()
|
||||
|
||||
const {rows} = db.execute(query)
|
||||
|
||||
if (!rows?.item(0)) {
|
||||
@@ -1166,7 +1166,8 @@ const addOrUpdateProofs = function (
|
||||
const db = getInstance()
|
||||
const {rowsAffected} = db.executeBatch(insertQueries)
|
||||
|
||||
const totalAmount = CashuUtils.getProofsAmount(proofs)
|
||||
// const totalAmount = CashuUtils.getProofsAmount(proofs)
|
||||
|
||||
// DO NOT log proof secrets to Sentry
|
||||
log.info('[addOrUpdateProofs]',
|
||||
`${rowsAffected}${isPending ? ' pending' : ''
|
||||
|
||||
Reference in New Issue
Block a user