Skip to content

Commit

Permalink
refactor(portfolio): refactor portfolio balance for pt
Browse files Browse the repository at this point in the history
  • Loading branch information
stackchain committed Apr 25, 2024
1 parent 923880b commit 7a19318
Show file tree
Hide file tree
Showing 29 changed files with 701 additions and 357 deletions.
49 changes: 49 additions & 0 deletions apps/wallet-mobile/src/features/Portfolio/common/transformers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {parseTokenId} from '@yoroi/portfolio'
import {Chain, Portfolio} from '@yoroi/types'
import {freeze} from 'immer'

import {NetworkId, RawUtxo, RemoteAsset} from '../../../yoroi-wallets/types'

export function toChainSupportedNetwork(networkId: NetworkId): Chain.SupportedNetworks {
switch (networkId) {
case 0:
case 1:
return Chain.Network.Main
case 450:
return Chain.Network.Sancho
default:
return Chain.Network.Preprod
}
}

export function toBalanceManagerArgs(rawUtxos: RawUtxo[]) {
let ptBalance = 0n
const secondary = new Map<Portfolio.Token.Id, Omit<Portfolio.Token.Balance, 'info'>>()
for (const utxo of rawUtxos) {
ptBalance += BigInt(utxo.amount)
for (const record of utxo.assets) {
const tokenId = toTokenId(record.assetId)
if (!tokenId) continue // skip invalid token ids
secondary.set(tokenId, {
balance: secondary.get(tokenId)?.balance ?? 0n + BigInt(record.amount),
})
}
}

return freeze(
{
primaryBalance: {
balance: ptBalance,
minRequiredByTokens: 0n,
lockedInBuiltTxs: 0n,
records: [],
},
secondaryBalances: new Map(secondary),
},
true,
)
}

const toTokenId = (assetId: RemoteAsset['assetId']) => {
return parseTokenId(`${assetId.slice(0, 56)}.${assetId.slice(56)}`)
}
Original file line number Diff line number Diff line change
@@ -1,73 +1,100 @@
import {mountMMKVStorage, observableStorageMaker} from '@yoroi/common'
import {createPrimaryTokenInfo, portfolioBalanceManagerMaker, portfolioBalanceStorageMaker} from '@yoroi/portfolio'
import {Portfolio} from '@yoroi/types'
import {Chain, Portfolio} from '@yoroi/types'
import * as React from 'react'

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

export const usePortfolioBalanceManager = ({
tokenManager,
walletId,
network,
}: {
tokenManager: Portfolio.Manager.Token
walletId: YoroiWallet['id']
network: Chain.Network
}) => {
return React.useMemo(() => {
const balanceStorageMounted = mountMMKVStorage<Portfolio.Token.Id>({path: `balance/${walletId}/`})
const primaryBreakdownStorageMounted = mountMMKVStorage<Portfolio.Token.Id>({
path: `/primary-breakdown/${walletId}/`,
})
return React.useMemo(
() => buildPortfolioBalanceManager({tokenManager, walletId, network}),
[network, tokenManager, walletId],
)
}

export const buildPortfolioBalanceManager = ({
tokenManager,
walletId,
network,
}: {
tokenManager: Portfolio.Manager.Token
walletId: YoroiWallet['id']
network: Chain.Network
}) => {
const primaryTokenInfo = network === Chain.Network.Main ? primaryTokenInfoMainnet : primaryTokenInfoAnyTestnet
const balanceStorageMounted = mountMMKVStorage<Portfolio.Token.Id>({path: `/balance/${walletId}/`})
const primaryBreakdownStorageMounted = mountMMKVStorage<Portfolio.Token.Id>({
path: `/primary-breakdown/${walletId}/`,
})

const balanceStorage = portfolioBalanceStorageMaker({
balanceStorage: observableStorageMaker(balanceStorageMounted),
primaryBreakdownStorage: observableStorageMaker(primaryBreakdownStorageMounted),
})
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,
},
const balanceManager = portfolioBalanceManagerMaker({
tokenManager,
storage: balanceStorage,
primaryToken: {
info: primaryTokenInfo,
discovery: {
counters: {
items: 0,
supply: 45n * BigInt(1e9),
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,
})
},
sourceId: walletId,
})

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

const primaryTokenInfo = createPrimaryTokenInfo({
const primaryTokenInfoMainnet = createPrimaryTokenInfo({
decimals: 6,
name: 'ADA',
ticker: 'ADA',
symbol: '$',
symbol: '₳',
reference: '',
tag: '',
website: 'https://www.cardano.org/',
originalImage: '',
})

const primaryTokenInfoAnyTestnet = createPrimaryTokenInfo({
decimals: 6,
name: 'TADA',
ticker: 'TADA',
symbol: '₳',
reference: '',
tag: '',
website: '',
website: 'https://www.cardano.org/',
originalImage: '',
})
Original file line number Diff line number Diff line change
@@ -1,27 +1,55 @@
import {mountMMKVStorage, observableStorageMaker} from '@yoroi/common'
import {portfolioApiMaker, portfolioTokenManagerMaker, portfolioTokenStorageMaker} from '@yoroi/portfolio'
import {Chain, Portfolio} from '@yoroi/types'
import {freeze} from 'immer'
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])
return React.useMemo(() => buildPortfolioTokenManager({network}), [network])
}

export const buildPortfolioTokenManager = ({network}: {network: Chain.Network}) => {
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}
}

export const buildPortfolioTokenManagers = () => {
const mainnetPortfolioTokenManager = buildPortfolioTokenManager({network: Chain.Network.Main})
const preprodPortfolioTokenManager = buildPortfolioTokenManager({network: Chain.Network.Preprod})
const sanchoPortfolioTokenManager = buildPortfolioTokenManager({network: Chain.Network.Sancho})

mainnetPortfolioTokenManager.tokenManager.hydrate({sourceId: 'initial'})
preprodPortfolioTokenManager.tokenManager.hydrate({sourceId: 'initial'})
sanchoPortfolioTokenManager.tokenManager.hydrate({sourceId: 'initial'})

const tokenManagers: Readonly<{
[Chain.Network.Main]: Portfolio.Manager.Token
[Chain.Network.Preprod]: Portfolio.Manager.Token
[Chain.Network.Sancho]: Portfolio.Manager.Token
}> = freeze(
{
[Chain.Network.Main]: mainnetPortfolioTokenManager.tokenManager,
[Chain.Network.Preprod]: preprodPortfolioTokenManager.tokenManager,
[Chain.Network.Sancho]: sanchoPortfolioTokenManager.tokenManager,
},
true,
)

return tokenManagers
}
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ export const PortfolioScreen = () => {
const {balanceManager: bmW1, balanceStorage: bs1} = usePortfolioBalanceManager({
tokenManager,
walletId: wallet.id,
network: Chain.Network.Main,
})

// wallet 2 for testing
const {balanceManager: bmW2, balanceStorage: bs2} = usePortfolioBalanceManager({
tokenManager,
walletId: 'wallet-2',
network: Chain.Network.Main,
})
const {data: balancesW2, isPending: isPendingW2} = useObserver({
observable: bmW2.observable,
Expand Down
40 changes: 24 additions & 16 deletions apps/wallet-mobile/src/wallet-manager/walletManager.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {parseSafe} from '@yoroi/common'
import {App} from '@yoroi/types'
import {catchError, concatMap, finalize, from, interval, of, Subject} from 'rxjs'
import {App, Chain} from '@yoroi/types'
import {catchError, concatMap, finalize, from, interval, of, startWith, Subject} from 'rxjs'
import uuid from 'uuid'

import {buildPortfolioTokenManagers} from '../features/Portfolio/common/usePortfolioTokenManager'
import {getCardanoWalletFactory} from '../yoroi-wallets/cardano/getWallet'
import {isYoroiWallet, YoroiWallet} from '../yoroi-wallets/cardano/types'
import {HWDeviceInfo} from '../yoroi-wallets/hw'
Expand All @@ -16,25 +17,36 @@ import {isWalletMeta, parseWalletMeta} from './validators'
const thirtyFiveSeconds = 35 * 1e3

export class WalletManager {
readonly #walletsRootStorage: App.Storage
readonly #rootStorage: App.Storage
static #instance: WalletManager
readonly #walletsRootStorage: App.Storage = rootStorage.join('wallet/')
readonly #rootStorage: App.Storage = rootStorage
readonly #openedWallets: Map<string, YoroiWallet> = new Map()
public readonly walletInfos$ = new Subject<WalletInfos>()
readonly #walletInfos: WalletInfos = new Map()
readonly #tokenManagersByNetwork = buildPortfolioTokenManagers()
#selectedWalletId: YoroiWallet['id'] | null = null
#subscriptions: Array<WalletManagerSubscription> = []
#isSyncing = false

constructor() {
this.#walletsRootStorage = rootStorage.join('wallet/')
this.#rootStorage = rootStorage
if (WalletManager.#instance) return WalletManager.#instance
WalletManager.#instance = this
}

static instance() {
if (!WalletManager.#instance) return new WalletManager()
return WalletManager.#instance
}

setSelectedWalletId(id: YoroiWallet['id']) {
this.#selectedWalletId = id
this._notify({type: 'selected-wallet-id', id})
}

getTokenManager(network: Chain.SupportedNetworks) {
return this.#tokenManagersByNetwork[network]
}

get selectedWalledId() {
return this.#selectedWalletId
}
Expand Down Expand Up @@ -79,7 +91,7 @@ export class WalletManager {
.subscribe()
}

const subscription = interval(thirtyFiveSeconds).subscribe(syncWallets)
const subscription = interval(thirtyFiveSeconds).pipe(startWith(0)).subscribe(syncWallets)

return {
destroy: () => {
Expand Down Expand Up @@ -185,8 +197,8 @@ export class WalletManager {
walletImplementationId: WalletImplementationId,
addressMode: AddressMode,
) {
await wallet.save()
if (!wallet.checksum) throw new Error('invalid wallet')
if (!isYoroiWallet(wallet)) throw new Error('invalid wallet')

const walletMeta: WalletMeta = {
id,
name,
Expand All @@ -198,14 +210,10 @@ export class WalletManager {
isEasyConfirmationEnabled: false,
}

await wallet.save()
await this.#walletsRootStorage.setItem(id, walletMeta)

if (isYoroiWallet(wallet)) {
this.#openedWallets.set(id, wallet)
return wallet
}

throw new Error('invalid wallet')
return wallet
}

async openWallet(walletMeta: WalletMeta): Promise<YoroiWallet> {
Expand Down Expand Up @@ -291,7 +299,7 @@ export class WalletManager {
}
}

export const walletManager = new WalletManager()
export const walletManager = WalletManager.instance()
walletManager.startSyncingAllWallets()

export default walletManager
Expand Down

0 comments on commit 7a19318

Please sign in to comment.