Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore(portfolio): base sync portfolio #3195

Merged
merged 13 commits into from
Apr 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
{
"java.configuration.updateBuildConfiguration": "disabled",
"typescript.tsdk": "node_modules/typescript/lib"
"typescript.tsdk": "node_modules/typescript/lib",
"jestrunner.debugOptions": {
stackchain marked this conversation as resolved.
Show resolved Hide resolved
"runtimeExecutable": "${workspaceRoot}/node_modules/.bin/react-scripts",
"runtimeArgs": [
"test",
"${fileBasename}",
"--no-cache",
"--color"
]
},
}
8 changes: 4 additions & 4 deletions apps/wallet-mobile/ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -495,10 +495,10 @@ PODS:
- SentryPrivate (= 8.9.3)
- SentryPrivate (8.9.3)
- Yoga (1.14.0)
- ZXingObjC/Core (3.6.5)
- ZXingObjC/OneD (3.6.5):
- ZXingObjC/Core (3.6.9)
- ZXingObjC/OneD (3.6.9):
- ZXingObjC/Core
- ZXingObjC/PDF417 (3.6.5):
- ZXingObjC/PDF417 (3.6.9):
- ZXingObjC/Core

DEPENDENCIES:
Expand Down Expand Up @@ -858,7 +858,7 @@ SPEC CHECKSUMS:
Sentry: 97161cac725da1ecbe77d1445bf8a61c1e5667f1
SentryPrivate: 9a76def09fb08f9501997b8df946e8097947b94f
Yoga: e71803b4c1fff832ccf9b92541e00f9b873119b9
ZXingObjC: fdbb269f25dd2032da343e06f10224d62f537bdb
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5

PODFILE CHECKSUM: 31f344d67f1a9c35e34eb202e3cdfeb4907367e8

Expand Down
4 changes: 3 additions & 1 deletion apps/wallet-mobile/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -126,12 +126,13 @@
"@yoroi/common": "1.5.2",
"@yoroi/exchange": "2.0.1",
"@yoroi/links": "1.5.4",
"@yoroi/portfolio": "1.0.0",
"@yoroi/resolver": "2.0.4",
"@yoroi/setup-wallet": "1.0.0",
"@yoroi/staking": "1.5.1",
"@yoroi/swap": "1.5.2",
"@yoroi/theme": "^1.0.0",
"@yoroi/transfer": "1.0.0",
"@yoroi/setup-wallet": "1.0.0",
"add": "2.0.6",
"assert": "^2.0.0",
"axios": "^1.5.0",
Expand Down Expand Up @@ -190,6 +191,7 @@
"react-native-webview": "^11.25.0",
"react-query": "^3.39.3",
"reselect": "^4.0.0",
"rxjs": "^7.8.1",
"sentry-expo": "^7.0.1",
"stream-browserify": "3.0.0",
"tinycolor2": "1.4.2",
Expand Down
40 changes: 40 additions & 0 deletions apps/wallet-mobile/scripts/create-mocked-token-infos.js
stackchain marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const nftCryptoKitty = {
decimals: 0,
ticker: 'CryptoKitty',
name: 'CryptoKitty #1234',
symbol: 'CK',
status: 'normal',
application: 'token',
tag: '',
reference: '0xabcdef1234567890.cryptokitty1234',
website: 'https://www.cryptokitties.co',
originalImage: 'https://cdn.example.com/ck-original1234.png',
id: '14696a4676909f4e3cb1f2e60e2e08e5abed70caf5c02699be971139.43554259',
fingerprint: 'asset1s7nlt45cc82upqewvjtgu7g97l7eg483c6wu75',
nature: 'secondary',
type: 'nft',
}

function generateTokenInfos(baseToken, count) {
const tokens = []
for (let i = 0; i < count; i++) {
// Clone the base token object to create a new token
const id = `${baseToken.id.split('.')[0]}.${Buffer.from(String(i)).toString('hex')}`
const name = `${baseToken.name.split(' ')[0]} #${i}`
const reference = `${baseToken.reference.split('.')[0]}.${Buffer.from(String(i)).toString('hex')}`
const fingerprint = `asset${i}s7nlt45cc82upqewvjtgu7g97l7eg483c6wu${i}`

const newToken = {...baseToken, id, name, reference, fingerprint}

tokens.push(newToken)
}
return tokens
}

const generatedTokens = generateTokenInfos(nftCryptoKitty, 50)

const apiResponseTokenInfos = generatedTokens.reduce((acc, token, index) => {
acc[token.id] = [200, token, `hash${index + 1}`, 3600]
return acc
}, {})

11 changes: 7 additions & 4 deletions apps/wallet-mobile/src/AppNavigator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {ModalScreen} from './components/Modal/ModalScreen'
import {AgreementChangedNavigator, InitializationNavigator} from './features/Initialization'
import {LegalAgreement, useLegalAgreement} from './features/Initialization/common'
import {useDeepLinkWatcher} from './features/Links/common/useDeepLinkWatcher'
import {PortfolioScreen} from './features/Portfolio/useCases/PortfolioScreen'
import {AddWalletNavigator} from './features/SetupWallet/SetupWalletNavigator'
import {CONFIG} from './legacy/config'
import {DeveloperScreen} from './legacy/DeveloperScreen'
Expand Down Expand Up @@ -160,6 +161,8 @@ export const AppNavigator = () => {
<Stack.Screen name="developer" component={DeveloperScreen} options={{headerShown: false}} />

<Stack.Screen name="storybook" component={StorybookScreen} />

<Stack.Screen name="portfolio-dashboard" component={PortfolioScreen} />
</Stack.Group>
)}
</Stack.Navigator>
Expand Down Expand Up @@ -248,9 +251,9 @@ type FirstAction = 'auth-with-pin' | 'auth-with-os' | 'request-new-pin' | 'first
const getFirstAction = (
isAuthOsSupported: boolean,
authSetting: AuthSetting,
agreement: LegalAgreement | undefined,
legalAgreement: LegalAgreement | undefined | null,
stackchain marked this conversation as resolved.
Show resolved Hide resolved
): FirstAction => {
const hasAccepted = agreement?.latestAcceptedAgreementsDate === CONFIG.AGREEMENT_DATE
const hasAccepted = legalAgreement?.latestAcceptedAgreementsDate === CONFIG.AGREEMENT_DATE

if (isString(authSetting) && !hasAccepted) return 'show-agreement-changed-notice'

Expand All @@ -264,7 +267,7 @@ const getFirstAction = (
const useFirstAction = () => {
const authSetting = useAuthSetting()
const isAuthOsSupported = useIsAuthOsSupported()
const terms = useLegalAgreement()
const legalAgreement = useLegalAgreement()

return getFirstAction(isAuthOsSupported, authSetting, terms)
return getFirstAction(isAuthOsSupported, authSetting, legalAgreement)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import {mountMMKVStorage, observableStorageMaker} from '@yoroi/common'
import {createPrimaryTokenInfo, portfolioBalanceManagerMaker, portfolioBalanceStorageMaker} from '@yoroi/portfolio'
import {Portfolio} from '@yoroi/types'
import * as React from 'react'

import {YoroiWallet} from '../../../yoroi-wallets/cardano/types'

export const usePortfolioBalanceManager = ({
tokenManager,
walletId,
}: {
tokenManager: Portfolio.Manager.Token
walletId: YoroiWallet['id']
}) => {
return React.useMemo(() => {
const balanceStorageMounted = mountMMKVStorage<Portfolio.Token.Id>({path: `balance/${walletId}/`})
stackchain marked this conversation as resolved.
Show resolved Hide resolved
const primaryBreakdownStorageMounted = mountMMKVStorage<Portfolio.Token.Id>({
path: `/primary-breakdown/${walletId}/`,
})

const balanceStorage = portfolioBalanceStorageMaker({
balanceStorage: observableStorageMaker(balanceStorageMounted),
primaryBreakdownStorage: observableStorageMaker(primaryBreakdownStorageMounted),
})

const balanceManager = portfolioBalanceManagerMaker({
tokenManager,
storage: balanceStorage,
primaryToken: {
info: primaryTokenInfo,
discovery: {
counters: {
items: 0,
supply: 0n,
totalItems: 0,
},
id: primaryTokenInfo.id,
originalMetadata: {
filteredMintMetadatum: null,
referenceDatum: null,
tokenRegistry: null,
},
properties: {},
source: {
decimals: Portfolio.Token.Source.Metadata,
name: Portfolio.Token.Source.Metadata,
ticker: Portfolio.Token.Source.Metadata,
symbol: Portfolio.Token.Source.Metadata,
image: Portfolio.Token.Source.Metadata,
},
},
},
sourceId: walletId,
})

balanceManager.hydrate()
return {
balanceManager,
balanceStorage,
}
}, [tokenManager, walletId])
}

const primaryTokenInfo = createPrimaryTokenInfo({
stackchain marked this conversation as resolved.
Show resolved Hide resolved
decimals: 6,
name: 'ADA',
ticker: 'ADA',
symbol: '$',
reference: '',
tag: '',
website: '',
originalImage: '',
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import {mountMMKVStorage, observableStorageMaker} from '@yoroi/common'
import {portfolioApiMaker, portfolioTokenManagerMaker, portfolioTokenStorageMaker} from '@yoroi/portfolio'
import {Chain, Portfolio} from '@yoroi/types'
import * as React from 'react'

export const usePortfolioTokenManager = ({network}: {network: Chain.Network}) => {
return React.useMemo(() => {
const tokenDiscoveryStorageMounted = mountMMKVStorage<Portfolio.Token.Id>({path: `${network}/token-discovery/`})
const tokenInfoStorageMounted = mountMMKVStorage<Portfolio.Token.Id>({path: `${network}/token-info/`})

const tokenStorage = portfolioTokenStorageMaker({
tokenDiscoveryStorage: observableStorageMaker(tokenDiscoveryStorageMounted),
tokenInfoStorage: observableStorageMaker(tokenInfoStorageMounted),
})
const api = portfolioApiMaker({
network,
})

const tokenManager = portfolioTokenManagerMaker({
api,
storage: tokenStorage,
})

tokenManager.hydrate({sourceId: 'initial'})
return {tokenManager, tokenStorage}
}, [network])
}
127 changes: 127 additions & 0 deletions apps/wallet-mobile/src/features/Portfolio/useCases/PortfolioScreen.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import {useNavigation} from '@react-navigation/native'
import {useObserver} from '@yoroi/common'
import {Chain} from '@yoroi/types'
import * as React from 'react'
import {Text, View} from 'react-native'
import {FlatList} from 'react-native-gesture-handler'
import {SafeAreaView} from 'react-native-safe-area-context'

import {Button, Spacer} from '../../../components'
import {useSelectedWallet} from '../../WalletManager/Context'
import {usePortfolioBalanceManager} from '../common/usePortfolioBalanceManager'
import {usePortfolioTokenManager} from '../common/usePortfolioTokenManager'

export const PortfolioScreen = () => {
const navigation = useNavigation()
const wallet = useSelectedWallet()
const {tokenManager, tokenStorage} = usePortfolioTokenManager({network: Chain.Network.Main})
const {balanceManager: bmW1, balanceStorage: bs1} = usePortfolioBalanceManager({
tokenManager,
walletId: wallet.id,
})

// wallet 2 for testing
const {balanceManager: bmW2, balanceStorage: bs2} = usePortfolioBalanceManager({
tokenManager,
walletId: 'wallet-2',
})
const {data: balancesW2, isPending: isPendingW2} = useObserver({
observable: bmW2.observable,
executor: () => bmW2.getBalances().all,
})
// end of wallet 2

const {data: balances, isPending} = useObserver({
observable: bmW1.observable,
executor: () => bmW1.getBalances().all,
})
const opacity = isPending || isPendingW2 ? 0.5 : 1

const handleOnSync = () => {
bmW1.sync({
primaryBalance: {
balance: 1n,
lockedInBuiltTxs: 2n,
minRequiredByTokens: 0n,
records: [],
},
secondaryBalances: new Map([
['14696a4676909f4e3cb1f2e60e2e08e5abed70caf5c02699be971139.34', {balance: 2n, lockedInBuiltTxs: 0n}],
['14696a4676909f4e3cb1f2e60e2e08e5abed70caf5c02699be971139.35', {balance: 3n, lockedInBuiltTxs: 0n}],
['14696a4676909f4e3cb1f2e60e2e08e5abed70caf5c02699be971139.36', {balance: 4n, lockedInBuiltTxs: 0n}],
]),
})

bmW2.sync({
primaryBalance: {
balance: 2n,
lockedInBuiltTxs: 3n,
minRequiredByTokens: 0n,
records: [],
},
secondaryBalances: new Map([
['14696a4676909f4e3cb1f2e60e2e08e5abed70caf5c02699be971139.3130', {balance: 222n, lockedInBuiltTxs: 0n}],
['14696a4676909f4e3cb1f2e60e2e08e5abed70caf5c02699be971139.35', {balance: 223n, lockedInBuiltTxs: 0n}],
['14696a4676909f4e3cb1f2e60e2e08e5abed70caf5c02699be971139.3131', {balance: 224n, lockedInBuiltTxs: 0n}],
['14696a4676909f4e3cb1f2e60e2e08e5abed70caf5c02699be971139.3132', {balance: 224n, lockedInBuiltTxs: 0n}],
]),
})
}

const handleOnReset = () => {
bs1.clear()
bs2.clear()
tokenStorage.clear()
navigation.goBack()
}

return (
<SafeAreaView edges={['bottom', 'top', 'left', 'right']}>
<View style={{padding: 16}}>
<Text>Portfolio playground</Text>

<Spacer height={16} />

<Button title="sync" onPress={handleOnSync} shelleyTheme />

<Spacer height={16} />

<Button title="reset" onPress={handleOnReset} outlineShelley />

<Spacer height={16} />

<Text>w1 {wallet.id}</Text>

<Text>all: {balances.length}</Text>

<FlatList
data={balances}
keyExtractor={(item) => item.info.id}
renderItem={({item}) => (
<View style={{padding: 8, backgroundColor: 'lightgray', marginVertical: 4, opacity}}>
<Text>{item.info.name}</Text>

<Text>{item.balance.toString()}</Text>
</View>
)}
/>

<Text>wallet 2</Text>

<Text>all: {balances.length}</Text>

<FlatList
data={balancesW2}
keyExtractor={(item) => item.info.id}
renderItem={({item}) => (
<View style={{padding: 8, backgroundColor: 'lightgray', marginVertical: 4, opacity}}>
<Text>{item.info.name}</Text>

<Text>{item.balance.toString()}</Text>
</View>
)}
/>
</View>
</SafeAreaView>
)
}
2 changes: 2 additions & 0 deletions apps/wallet-mobile/src/legacy/DeveloperScreen.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,8 @@ export const DeveloperScreen = () => {
onPress={() => storageVersionMaker(rootStorage).remove()}
/>

<Button title="Portfolio" style={styles.button} onPress={() => navigation.navigate('portfolio-dashboard')} />

<Button
title="Logout"
style={styles.button}
Expand Down
4 changes: 2 additions & 2 deletions apps/wallet-mobile/src/migrations/4_9_0.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('migrateAuthSetting', () => {

// if the store is inconsistent we favor OS, so the user can disable on device and it will ask for a new pin
it('old store is pin + os (inconsistent), method = "os"', async () => {
await rootStorage.join('appSettings/').multiSet([
await rootStorage.join('appSettings/').multiSet<string | boolean>([
stackchain marked this conversation as resolved.
Show resolved Hide resolved
['customPinHash', 'encrypted-hash'],
[OLD_OS_AUTH_KEY, true],
])
Expand All @@ -41,7 +41,7 @@ describe('migrateAuthSetting', () => {
})

it('old store is pin, method = "pin"', async () => {
await rootStorage.join('appSettings/').multiSet([
await rootStorage.join('appSettings/').multiSet<string | boolean>([
['customPinHash', 'encrypted-hash'],
[OLD_OS_AUTH_KEY, false],
])
Expand Down
Loading
Loading