Skip to content

Commit

Permalink
split txcache from wallet json (#2188)
Browse files Browse the repository at this point in the history
* split txcache from wallet json

* cleanup transaction cache

* save txs individually

* clean up

* clean ups
  • Loading branch information
wolverineks committed Oct 27, 2022
1 parent 2d7f025 commit 00cc23e
Show file tree
Hide file tree
Showing 15 changed files with 766 additions and 446 deletions.
6 changes: 5 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ module.exports = {
{
files: ['*.tsx'],
rules: {
'@typescript-eslint/strict-boolean-expressions': ['error', {allowString: false, allowNumber: false, allowNullableBoolean: true}],
'@typescript-eslint/strict-boolean-expressions': [
'error',
{allowString: false, allowNumber: false, allowNullableBoolean: true},
],
},
},
],
Expand Down Expand Up @@ -51,6 +54,7 @@ module.exports = {
'@typescript-eslint/no-unused-vars': ['error', {argsIgnorePattern: '^_', varsIgnorePattern: '^_'}],
'react/jsx-curly-brace-presence': ['warn', {props: 'never', children: 'never'}],
'no-return-await': 'error',
'no-template-curly-in-string': 'error',
},
globals: {
Buffer: false,
Expand Down
51 changes: 9 additions & 42 deletions src/SelectedWallet/WalletSelection/WalletSelectionScreen.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import {useFocusEffect, useNavigation} from '@react-navigation/native'
import {delay} from 'bluebird'
import {useNavigation} from '@react-navigation/native'
import React from 'react'
import {defineMessages, useIntl} from 'react-intl'
import {FlatList, Linking, RefreshControl, StyleSheet, Text, TouchableOpacity} from 'react-native'
import {SafeAreaView} from 'react-native-safe-area-context'
import {useMutation, UseMutationOptions, useQueryClient} from 'react-query'

import {useAuth} from '../../auth/AuthProvider'
import {Button, Icon, PleaseWaitModal, StatusBar} from '../../components'
import {useCloseWallet, useWalletMetas} from '../../hooks'
import {useCloseWallet, useOpenWallet, useWalletMetas} from '../../hooks'
import globalMessages, {errorMessages} from '../../i18n/global-messages'
import {showErrorDialog} from '../../legacy/actions'
import {CONFIG, isNightly} from '../../legacy/config'
Expand All @@ -18,7 +16,7 @@ import {WalletMeta} from '../../legacy/state'
import {useWalletNavigation} from '../../navigation'
import {COLORS} from '../../theme'
import {useWalletManager} from '../../WalletManager'
import {KeysAreInvalid, SystemAuthDisabled, walletManager, YoroiWallet} from '../../yoroi-wallets'
import {KeysAreInvalid, SystemAuthDisabled} from '../../yoroi-wallets'
import {useSetSelectedWallet, useSetSelectedWalletMeta} from '..'
import {WalletListItem} from './WalletListItem'

Expand All @@ -32,34 +30,31 @@ export const WalletSelectionScreen = () => {
const selectWalletMeta = useSetSelectedWalletMeta()
const selectWallet = useSetSelectedWallet()
const intl = useIntl()
const queryClient = useQueryClient()

const {logout} = useAuth()

const {closeWallet} = useCloseWallet()

useFocusEffect(closeWallet)

const {openWallet, isLoading} = useOpenWallet({
onSuccess: ({wallet, walletMeta}) => {
onSuccess: ([wallet, walletMeta]) => {
selectWalletMeta(walletMeta)
selectWallet(wallet)
wallet.subscribeOnTxHistoryUpdate(() => queryClient.invalidateQueries([wallet.id, 'lockedAmount']))

navigateToTxHistory()
},
onError: async (error) => {
closeWallet()

if (error instanceof SystemAuthDisabled) {
closeWallet()
await showErrorDialog(errorMessages.enableSystemAuthFirst, intl)
resetToWalletSelection()
} else if (error instanceof InvalidState) {
closeWallet()
await showErrorDialog(errorMessages.walletStateInvalid, intl)
} else if (error instanceof KeysAreInvalid) {
await showErrorDialog(errorMessages.walletKeysInvalidated, intl)
closeWallet()
logout()
} else {
throw error
await showErrorDialog(errorMessages.generalError, intl, {message: error.message})
}
},
})
Expand Down Expand Up @@ -231,34 +226,6 @@ const OnlyDevButton = () => {
return <Button onPress={() => navigation.navigate('developer')} title="Dev options" style={styles.button} />
}

const useOpenWallet = (
options?: UseMutationOptions<
{
wallet: YoroiWallet
walletMeta: WalletMeta
},
Error,
WalletMeta
>,
) => {
const {closeWallet} = useCloseWallet()

const mutation = useMutation({
...options,
mutationFn: async (walletMeta) => {
closeWallet()
await delay(500)
const [newWallet, newWalletMeta] = await walletManager.openWallet(walletMeta)
return {
wallet: newWallet,
walletMeta: newWalletMeta,
}
},
})

return {openWallet: mutation.mutate, ...mutation}
}

const styles = StyleSheet.create({
safeAreaView: {
flex: 1,
Expand Down
34 changes: 2 additions & 32 deletions src/TxHistory/LockedDeposit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,14 @@ import BigNumber from 'bignumber.js'
import React from 'react'
import {useIntl} from 'react-intl'
import {View} from 'react-native'
import {useQuery, UseQueryOptions} from 'react-query'

import {Boundary, Spacer, Text} from '../components'
import {useTokenInfo} from '../hooks'
import {useLockedAmount, useTokenInfo} from '../hooks'
import globalMessages from '../i18n/global-messages'
import {formatTokenWithText, formatTokenWithTextWhenHidden} from '../legacy/format'
import {isEmptyString} from '../legacy/utils'
import {useSelectedWallet} from '../SelectedWallet'
import {YoroiWallet} from '../yoroi-wallets'
import {calcLockedDeposit} from '../yoroi-wallets/cardano/assetUtils'
import {Quantity, Token} from '../yoroi-wallets/types'
import {Token} from '../yoroi-wallets/types'

type Props = {
privacyMode?: boolean
Expand Down Expand Up @@ -73,30 +70,3 @@ const useStrings = () => {
lockedDeposit: intl.formatMessage(globalMessages.lockedDeposit),
}
}

/**
* Calculate the lovelace locked up to hold utxos with assets
* Important `minAdaRequired` is missing `has_hash_data`
* which could be adding 10 in size to calc the words of the utxo
*
* @summary Returns the locked amount in Lovelace
*/
export const useLockedAmount = (
{wallet}: {wallet: YoroiWallet},
options?: UseQueryOptions<Quantity, Error, Quantity, [string, 'lockedAmount']>,
) => {
const query = useQuery({
...options,
suspense: true,
queryKey: [wallet.id, 'lockedAmount'],
queryFn: () =>
wallet
.fetchUTXOs()
.then((utxos) => calcLockedDeposit(utxos, wallet.networkId))
.then((amount) => amount.toString() as Quantity),
})

if (query.data == null) throw new Error('invalid state')

return query.data
}
70 changes: 69 additions & 1 deletion src/hooks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import AsyncStorage, {AsyncStorageStatic} from '@react-native-async-storage/async-storage'
import {useNetInfo} from '@react-native-community/netinfo'
import {useFocusEffect} from '@react-navigation/native'
import BigNumber from 'bignumber.js'
import {delay} from 'bluebird'
import cryptoRandomString from 'crypto-random-string'
import {mapValues} from 'lodash'
Expand All @@ -21,12 +22,15 @@ import {getDefaultAssetByNetworkId} from '../legacy/config'
import {ObjectValues} from '../legacy/flow'
import KeyStore from '../legacy/KeyStore'
import {HWDeviceInfo} from '../legacy/ledgerUtils'
import {getCardanoNetworkConfigById} from '../legacy/networks'
import {processTxHistoryData} from '../legacy/processTransactions'
import {WalletMeta} from '../legacy/state'
import storage from '../legacy/storage'
import {cardanoValueFromRemoteFormat} from '../legacy/utils'
import {Storage} from '../Storage'
import {
Cardano,
CardanoMobile,
NetworkId,
TxSubmissionStatus,
WalletEvent,
Expand All @@ -37,7 +41,7 @@ import {
YoroiWallet,
} from '../yoroi-wallets'
import {generateShelleyPlateFromKey} from '../yoroi-wallets/cardano/shelley/plate'
import {Token, YoroiAmounts, YoroiSignedTx, YoroiUnsignedTx} from '../yoroi-wallets/types'
import {Quantity, Token, YoroiAmounts, YoroiSignedTx, YoroiUnsignedTx} from '../yoroi-wallets/types'
import {
CurrencySymbol,
RawUtxo,
Expand Down Expand Up @@ -148,6 +152,58 @@ export const useUtxos = (
}
}

/**
* Calculate the lovelace locked up to hold utxos with assets
* Important `minAdaRequired` is missing `has_hash_data`
* which could be adding 10 in size to calc the words of the utxo
*
* @summary Returns the locked amount in Lovelace
*/
export const useLockedAmount = (
{wallet}: {wallet: YoroiWallet},
options?: UseQueryOptions<Quantity, Error, Quantity, [string, 'lockedAmount']>,
) => {
const queryClient = useQueryClient()
const query = useQuery({
...options,
suspense: true,
queryKey: [wallet.id, 'lockedAmount'],
queryFn: () =>
wallet
.fetchUTXOs()
.then((utxos) => calcLockedDeposit(utxos, wallet.networkId))
.then((amount) => amount.toString() as Quantity),
})

React.useEffect(() => {
const unsubscribe = wallet.subscribeOnTxHistoryUpdate(() =>
queryClient.invalidateQueries([wallet.id, 'lockedAmount']),
)

return () => unsubscribe()
}, [queryClient, wallet])

if (query.data == null) throw new Error('invalid state')

return query.data
}

export const calcLockedDeposit = async (utxos: RawUtxo[], networkId: NetworkId) => {
const networkConfig = getCardanoNetworkConfigById(networkId)
const minUtxoValue = await CardanoMobile.BigNum.fromStr(networkConfig.MINIMUM_UTXO_VAL)
const utxosWithAssets = utxos.filter((utxo) => utxo.assets.length > 0)

const promises = utxosWithAssets.map(async (utxo) => {
return cardanoValueFromRemoteFormat(utxo)
.then((value) => CardanoMobile.minAdaRequired(value, minUtxoValue))
.then((bigNum) => bigNum.toStr())
})
const results = await Promise.all(promises)
const totalLocked = results.reduce((result, current) => result.plus(current), new BigNumber(0)).toString() as Quantity

return totalLocked
}

export const useSync = (wallet: YoroiWallet, options?: UseMutationOptions<void, Error>) => {
const mutation = useMutation({
...options,
Expand Down Expand Up @@ -631,6 +687,18 @@ export const useHasPendingTx = (wallet: YoroiWallet) => {
}

// WALLET MANAGER
export const useOpenWallet = (options?: UseMutationOptions<[YoroiWallet, WalletMeta], Error, WalletMeta>) => {
const mutation = useMutation({
...options,
mutationFn: async (walletMeta) => walletManager.openWallet(walletMeta),
})

return {
openWallet: mutation.mutate,
...mutation,
}
}

export const useCreatePin = (storage: Storage, options: UseMutationOptions<void, Error, string>) => {
const mutation = useMutation({
mutationFn: async (pin) => {
Expand Down
16 changes: 8 additions & 8 deletions src/yoroi-wallets/Wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {IsLockedError, nonblockingSynchronize, synchronize} from '../legacy/prom
import {CardanoTypes, NetworkId, WalletImplementationId, YoroiProvider} from './cardano'
import * as api from './cardano/api'
import {AddressChain, AddressChainJSON, Addresses} from './cardano/chain'
import {TransactionCache, TransactionCacheJSON} from './cardano/shelley/transactionCache'
import {TransactionCache} from './cardano/shelley/transactionCache'
import type {BackendConfig, EncryptionMethod, Transaction} from './types/other'
import {validatePassword} from './utils/validators'

Expand All @@ -38,8 +38,6 @@ export type ShelleyWalletJSON = {
lastGeneratedAddressIndex: number
internalChain: AddressChainJSON
externalChain: AddressChainJSON

transactionCache: TransactionCacheJSON
}

export type ByronWalletJSON = Omit<ShelleyWalletJSON, 'account'>
Expand Down Expand Up @@ -205,8 +203,12 @@ export class Wallet {
this._onTxHistoryUpdateSubscriptions.forEach((handler) => handler(this))
}

subscribeOnTxHistoryUpdate(handler: () => void) {
this._onTxHistoryUpdateSubscriptions.push(handler)
subscribeOnTxHistoryUpdate(subscription: () => void) {
this._onTxHistoryUpdateSubscriptions.push(subscription)

return () => {
this._onTxHistoryUpdateSubscriptions = this._onTxHistoryUpdateSubscriptions.filter((sub) => sub !== subscription)
}
}

setupSubscriptions() {
Expand All @@ -215,7 +217,7 @@ export class Wallet {
if (!this.transactionCache) throw new Error('invalid wallet state')

this.transactionCache.subscribe(() => this.notify({type: 'transactions', transactions: this.transactions}))
this.transactionCache.subscribeOnTxHistoryUpdate(this.notifyOnTxHistoryUpdate)
this.transactionCache.subscribe(this.notifyOnTxHistoryUpdate)
this.internalChain.addSubscriberToNewAddresses(() =>
this.notify({type: 'addresses', addresses: this.internalAddresses}),
)
Expand Down Expand Up @@ -354,15 +356,13 @@ export class Wallet {
if (this.internalAddresses == null) throw new Error('invalid WalletJSON: internalAddresses')
if (this.externalChain == null) throw new Error('invalid WalletJSON: externalChain')
if (this.internalChain == null) throw new Error('invalid WalletJSON: internalChain')
if (this.transactionCache == null) throw new Error('invalid WalletJSON: transactionCache')

return {
lastGeneratedAddressIndex: this.state.lastGeneratedAddressIndex,
publicKeyHex: this.publicKeyHex,
version: this.version,
internalChain: this.internalChain.toJSON(),
externalChain: this.externalChain.toJSON(),
transactionCache: this.transactionCache.toJSON(),
networkId: this.networkId,
walletImplementationId: this.walletImplementationId,
isHW: this.isHW,
Expand Down

0 comments on commit 00cc23e

Please sign in to comment.