Skip to content

Commit

Permalink
Resources and Accounts Fiat worth (#1034)
Browse files Browse the repository at this point in the history
Co-authored-by: kugel3 <gustaf.kugelberg@rdx.works>
  • Loading branch information
GhenadieVP and kugel3 committed Mar 15, 2024
1 parent fe5490d commit 939f698
Show file tree
Hide file tree
Showing 60 changed files with 1,461 additions and 420 deletions.
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
56 changes: 56 additions & 0 deletions RadixWallet.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ public struct AccountPortfoliosClient: Sendable {
}

extension AccountPortfoliosClient {
public typealias FetchAccountPortfolio = @Sendable (_ address: AccountAddress, _ forceResfresh: Bool) async throws -> OnLedgerEntity.Account
public typealias FetchAccountPortfolios = @Sendable (_ addresses: [AccountAddress], _ forceResfresh: Bool) async throws -> [OnLedgerEntity.Account]
public typealias PortfolioForAccount = @Sendable (_ address: AccountAddress) async -> AnyAsyncSequence<OnLedgerEntity.Account>
public typealias Portfolios = @Sendable () -> [OnLedgerEntity.Account]
public typealias FetchAccountPortfolio = @Sendable (_ address: AccountAddress, _ forceResfresh: Bool) async throws -> AccountPortfolio
public typealias FetchAccountPortfolios = @Sendable (_ addresses: [AccountAddress], _ forceResfresh: Bool) async throws -> [AccountPortfolio]
public typealias PortfolioForAccount = @Sendable (_ address: AccountAddress) async -> AnyAsyncSequence<AccountPortfolio>
public typealias Portfolios = @Sendable () -> [AccountPortfolio]
}

extension DependencyValues {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,61 +1,143 @@

// MARK: - AccountPortfoliosClient + DependencyKey
extension AccountPortfoliosClient: DependencyKey {
/// Internal state that holds all loaded portfolios.
actor State {
let portfoliosSubject: AsyncCurrentValueSubject<[AccountAddress: OnLedgerEntity.Account]> = .init([:])
public static let liveValue: AccountPortfoliosClient = {
let state = State()

func setOrUpdateAccountPortfolio(_ portfolio: OnLedgerEntity.Account) {
portfoliosSubject.value.updateValue(portfolio, forKey: portfolio.address)
}
@Dependency(\.onLedgerEntitiesClient) var onLedgerEntitiesClient
@Dependency(\.cacheClient) var cacheClient
@Dependency(\.tokenPricesClient) var tokenPricesClient
@Dependency(\.appPreferencesClient) var appPreferencesClient
@Dependency(\.gatewaysClient) var gatewaysClient

func setOrUpdateAccountPortfolios(_ portfolios: [OnLedgerEntity.Account]) {
var newValue = portfoliosSubject.value
for portfolio in portfolios {
newValue[portfolio.address] = portfolio
/// Update currency amount visibility based on the profile state
Task {
for try await isCurrencyAmountVisible in await appPreferencesClient.appPreferenceUpdates().map(\.display.isCurrencyAmountVisible) {
guard !Task.isCancelled else { return }
await state.setIsCurrencyAmountVisble(isCurrencyAmountVisible)
}
portfoliosSubject.value = newValue
}

func portfolioForAccount(_ address: AccountAddress) -> AnyAsyncSequence<OnLedgerEntity.Account> {
portfoliosSubject.compactMap { $0[address] }.eraseToAnyAsyncSequence()
/// Update used currency based on the profile state
Task {
for try await fiatCurrency in await appPreferencesClient.appPreferenceUpdates().map(\.display.fiatCurrencyPriceTarget) {
guard !Task.isCancelled else { return }
await state.setSelectedCurrency(fiatCurrency)
}
}
}

public static let liveValue: AccountPortfoliosClient = {
let state = State()
/// Fetches the pool and stake units details for a given account; Will update the portfolio accordingly
@Sendable
func fetchPoolAndStakeUnitsDetails(_ account: OnLedgerEntity.Account) async {
async let poolDetailsFetch = Task {
do {
let poolUnitDetails = try await onLedgerEntitiesClient.getOwnedPoolUnitsDetails(account)
await state.set(poolDetails: .success(poolUnitDetails), forAccount: account.address)
} catch {
await state.set(poolDetails: .failure(error), forAccount: account.address)
}
}.result
async let stakeUnitDetails = Task {
do {
let stakeUnitDetails = try await onLedgerEntitiesClient.getOwnedStakesDetails(account: account)
await state.set(stakeUnitDetails: .success(stakeUnitDetails.asIdentifiable()), forAccount: account.address)
} catch {
await state.set(stakeUnitDetails: .failure(error), forAccount: account.address)
}
}.result

@Dependency(\.onLedgerEntitiesClient) var onLedgerEntitiesClient
@Dependency(\.cacheClient) var cacheClient
_ = await (poolDetailsFetch, stakeUnitDetails)
}

return AccountPortfoliosClient(
fetchAccountPortfolios: { accountAddresses, forceRefresh in
let gateway = await gatewaysClient.getCurrentGateway()
await state.setRadixGateway(gateway)
if forceRefresh {
for accountAddress in accountAddresses {
cacheClient.removeFolder(.onLedgerEntity(.account(accountAddress.asGeneral)))
}
}

let accounts = try await onLedgerEntitiesClient.getAccounts(accountAddresses)
/// Explicetely load and set the currency target and visibility to make sure
/// it is available for usage before resources are loaded
let preferences = await appPreferencesClient.getPreferences().display
await state.setSelectedCurrency(preferences.fiatCurrencyPriceTarget)
await state.setIsCurrencyAmountVisble(preferences.isCurrencyAmountVisible)

let accounts = try await onLedgerEntitiesClient.getAccounts(accountAddresses).map(\.nonEmptyVaults)

await state.setOrUpdateAccountPortfolios(accounts)
/// Put together all resources from already fetched and new accounts
let currentAccounts = state.portfoliosSubject.value.wrappedValue.map { $0.values.map(\.account) } ?? []
let allResources: [ResourceAddress] = {
if gateway == .mainnet {
/// Only Mainnet resources have prices
return (currentAccounts + accounts)
.flatMap {
$0.allFungibleResourceAddresses +
$0.poolUnitResources.poolUnits.flatMap(\.poolResources) +
[.mainnetXRDAddress]
}
} else {
#if DEBUG
/// Helpful for testing on stokenet
return [
.mainnetXRDAddress,
try! .init(validatingAddress:
"resource_rdx1t4tjx4g3qzd98nayqxm7qdpj0a0u8ns6a0jrchq49dyfevgh6u0gj3"
),
try! .init(validatingAddress:
"resource_rdx1t45js47zxtau85v0tlyayerzrgfpmguftlfwfr5fxzu42qtu72tnt0"
),
try! .init(validatingAddress:
"resource_rdx1tk7g72c0uv2g83g3dqtkg6jyjwkre6qnusgjhrtz0cj9u54djgnk3c"
),
try! .init(validatingAddress:
"resource_rdx1tkk83magp3gjyxrpskfsqwkg4g949rmcjee4tu2xmw93ltw2cz94sq"
),
]
#else
/// No price for resources on testnets
return []
#endif
}
}()

if !allResources.isEmpty {
let prices = try? await tokenPricesClient.getTokenPrices(.init(
tokens: Array(allResources),
currency: preferences.fiatCurrencyPriceTarget
))

return accounts
if let prices {
await state.setTokenPrices(prices)
}
}

let portfolios = accounts.map { AccountPortfolio(account: $0) }
await state.handlePortfoliosUpdate(portfolios)

// Load additional details
_ = await accounts.parallelMap(fetchPoolAndStakeUnitsDetails)

return Array(state.portfoliosSubject.value.wrappedValue!.values)
},
fetchAccountPortfolio: { accountAddress, forceRefresh in
if forceRefresh {
cacheClient.removeFolder(.onLedgerEntity(.account(accountAddress.asGeneral)))
}

let portfolio = try await onLedgerEntitiesClient.getAccount(accountAddress)
await state.setOrUpdateAccountPortfolio(portfolio)
let account = try await onLedgerEntitiesClient.getAccount(accountAddress)
let portfolio = AccountPortfolio(account: account)

await state.handlePortfolioUpdate(portfolio)
await fetchPoolAndStakeUnitsDetails(account)

return portfolio
},
portfolioForAccount: { address in
await state.portfolioForAccount(address)
},
portfolios: { state.portfoliosSubject.value.map(\.value) }
portfolios: { state.portfoliosSubject.value.wrappedValue.map { Array($0.values) } ?? [] }
)
}()
}
Loading

0 comments on commit 939f698

Please sign in to comment.