Skip to content

Commit

Permalink
861, create balance-loader
Browse files Browse the repository at this point in the history
  • Loading branch information
PseudoElement committed Jan 24, 2025
1 parent 5f5bf70 commit 610212b
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 63 deletions.
130 changes: 98 additions & 32 deletions src/app/core/services/tokens/balance-loader.service.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,117 @@
import { Injectable } from '@angular/core';
import { Token } from '@app/shared/models/tokens/token';
import { TokenAmount } from '@app/shared/models/tokens/token-amount';
import { isTokenAmount } from '@app/shared/utils/is-token';
import BigNumber from 'bignumber.js';
import { List } from 'immutable';
import { BlockchainName, BlockchainsInfo, Injector, Web3Public, Web3Pure } from 'rubic-sdk';
import { AuthService } from '../auth/auth.service';
import pTimeout from 'rubic-sdk/lib/common/utils/p-timeout';
import { ChainsToLoadFirstly, isTopChain } from './constants/first-loaded-chains';
import { TokensUpdaterService } from './tokens-updater.service';
import { AssetType } from '@app/features/trade/models/asset';
import { BehaviorSubject } from 'rxjs';

type TokensListOfTopChainsWithOtherChains = {
[key in BlockchainName]: Token[];
} & {
TOP_CHAINS: {
[key in ChainsToLoadFirstly]: Token[];
};
};

@Injectable({
providedIn: 'root'
})
export class BalanceLoaderService {
constructor(private readonly authService: AuthService) {}
constructor(
private readonly authService: AuthService,
private readonly tokensUpdaterService: TokensUpdaterService
) {}

public async getTokensWithBalancesAndPatchImmediately(
public updateAllChainsTokensWithBalances(
tokensList: List<TokenAmount | Token>,
_patchTokensInGeneralList: (
patchTokensInGeneralList: (
tokensWithBalances: List<TokenAmount>,
patchAllChains: boolean
) => void
): Promise<List<TokenAmount>> {
const tokensByChain: Record<BlockchainName | 'TOP_CHAINS', Token[]> = {} as Record<
BlockchainName | 'TOP_CHAINS',
Token[]
>;
// @TODO handle TOP_CHAINS firstly
) => void,
isBalanceAlreadyCalculatedForChain: Record<AssetType, boolean>,
isBalanceLoading$: Record<AssetType, BehaviorSubject<boolean>>
): void {
const tokensByChain: TokensListOfTopChainsWithOtherChains = {
TOP_CHAINS: {}
} as TokensListOfTopChainsWithOtherChains;

for (const token of tokensList) {
const chainTokensList = tokensByChain[token.blockchain];
if (!Array.isArray(chainTokensList)) {
tokensByChain[token.blockchain] = [] as Token[];
}
if (isTokenAmount(token)) {
tokensByChain[token.blockchain].push(token);
if (isTopChain(token.blockchain)) {
if (!Array.isArray(tokensByChain.TOP_CHAINS[token.blockchain])) {
tokensByChain.TOP_CHAINS[token.blockchain] = [] as Token[];
}
tokensByChain.TOP_CHAINS[token.blockchain].push(token);
} else {
tokensByChain[token.blockchain].push(
this.getTokensWithNullBalances(List([token]), false).get(0)
);
if (!Array.isArray(tokensByChain[token.blockchain])) {
tokensByChain[token.blockchain] = [] as Token[];
}
tokensByChain[token.blockchain].push(token);
}
}

return List([]);
for (const key in tokensByChain) {
if (key === 'TOP_CHAINS') {
const topChainsTokens = tokensByChain.TOP_CHAINS;
const promises = Object.entries(topChainsTokens).map(
([chain, tokens]: [BlockchainName, Token[]]) => {
if (!this.isChainSupportedByWallet(chain)) return tokens.map(() => new BigNumber(NaN));

const web3Public = Injector.web3PublicService.getWeb3Public(chain) as Web3Public;
return web3Public
.getTokensBalances(
this.authService.userAddress,
tokens.map(t => t.address)
)
.catch(() => tokens.map(() => new BigNumber(NaN)));
}
);

Promise.all(promises).then(balances => {
const flattenBalances = balances.flat();
const flattenTokens = Object.values(topChainsTokens).flat();
const tokensWithBalances = flattenTokens.map((token, idx) => ({
...token,
amount: flattenBalances[idx]
? Web3Pure.fromWei(flattenBalances[idx], token.decimals)
: new BigNumber(NaN)
})) as TokenAmount[];

patchTokensInGeneralList(List(tokensWithBalances), true);
this.tokensUpdaterService.triggerUpdateTokens();

isBalanceAlreadyCalculatedForChain.allChains = true;
isBalanceLoading$.allChains.next(false);
});
} else {
const chain = key as Exclude<keyof TokensListOfTopChainsWithOtherChains, 'TOP_CHAINS'>;
const chainTokens = tokensByChain[chain];
const web3Public = Injector.web3PublicService.getWeb3Public(chain) as Web3Public;

web3Public
.getTokensBalances(
this.authService.userAddress,
chainTokens.map(t => t.address)
)
.catch(() => chainTokens.map(() => new BigNumber(NaN)))
.then(balances => {
const tokensWithBalances = chainTokens.map((token, idx) => ({
...token,
amount: balances[idx]
? Web3Pure.fromWei(balances[idx], token.decimals)
: new BigNumber(NaN)
})) as TokenAmount[];

patchTokensInGeneralList(List(tokensWithBalances), true);
this.tokensUpdaterService.triggerUpdateTokens();
});
}
}
}

/**
Expand All @@ -58,22 +128,13 @@ export class BalanceLoaderService {
if (!Array.isArray(chainTokensList)) {
tokensByChain[token.blockchain] = [] as Token[];
}
if (isTokenAmount(token)) {
tokensByChain[token.blockchain].push(token);
} else {
tokensByChain[token.blockchain].push(
this.getTokensWithNullBalances(List([token]), false).get(0)
);
}
tokensByChain[token.blockchain].push(token);
}

try {
const promises = Object.entries(tokensByChain).map(
([chain, tokens]: [BlockchainName, Token[]]) => {
const doesWalletSupportsTokenChain =
BlockchainsInfo.getChainType(chain) === this.authService.userChainType;
// if EVM-address used -> it will fetch only evm address etc.
if (!doesWalletSupportsTokenChain) return tokens.map(() => new BigNumber(NaN));
if (!this.isChainSupportedByWallet(chain)) return tokens.map(() => new BigNumber(NaN));

const web3Public = Injector.web3PublicService.getWeb3Public(chain) as Web3Public;
const chainBalancesPromise = web3Public.getTokensBalances(
Expand Down Expand Up @@ -121,4 +182,9 @@ export class BalanceLoaderService {
favorite: isFavorite
}));
}

/* if EVM-address used -> it will fetch only evm address etc. */
private isChainSupportedByWallet(chain: BlockchainName): boolean {
return BlockchainsInfo.getChainType(chain) === this.authService.userChainType;
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BLOCKCHAIN_NAME } from 'rubic-sdk';
import { BLOCKCHAIN_NAME, BlockchainName } from 'rubic-sdk';

export const CHAINS_TO_LOAD_FIRSTLY = [
BLOCKCHAIN_NAME.ETHEREUM,
Expand All @@ -8,3 +8,9 @@ export const CHAINS_TO_LOAD_FIRSTLY = [
BLOCKCHAIN_NAME.BASE,
BLOCKCHAIN_NAME.ZK_SYNC
] as const;

export type ChainsToLoadFirstly = (typeof CHAINS_TO_LOAD_FIRSTLY)[number];

export function isTopChain(blockchain: BlockchainName): blockchain is ChainsToLoadFirstly {
return CHAINS_TO_LOAD_FIRSTLY.some(topChain => topChain === blockchain);
}
97 changes: 67 additions & 30 deletions src/app/core/services/tokens/tokens-store.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, firstValueFrom, forkJoin, from, iif, Observable, of } from 'rxjs';
import { BehaviorSubject, firstValueFrom, forkJoin, from, Observable, of } from 'rxjs';
import { List } from 'immutable';
import { TokenAmount } from '@shared/models/tokens/token-amount';
import { catchError, debounceTime, first, map, switchMap, tap } from 'rxjs/operators';
import { map, switchMap, tap } from 'rxjs/operators';
import { TokensApiService } from '@core/services/backend/tokens-api/tokens-api.service';
import { AuthService } from '@core/services/auth/auth.service';
import { Token } from '@shared/models/tokens/token';
Expand Down Expand Up @@ -180,7 +180,45 @@ export class TokensStoreService {
).flat();
}

public startBalanceCalculating(blockchain: AssetType): void {
// public startBalanceCalculating(blockchain: AssetType): void {
// if (this.isBalanceAlreadyCalculatedForChain[blockchain]) {
// console.log(
// `%cSKIPPED FOR ${blockchain}`,
// 'color: red; font-size: 20px;',
// this.isBalanceAlreadyCalculatedForChain
// );
// return;
// }

// const tokensList$: Observable<List<TokenAmount>> = iif(
// () => blockchain === 'allChains',
// this.allChainsTokens$,
// this.tokens$.pipe(
// first(v => Boolean(v)),
// map(tokens => tokens.filter(t => t.blockchain === blockchain))
// )
// );

// forkJoin([firstValueFrom(tokensList$), firstValueFrom(this.authService.currentUser$)])
// .pipe(
// switchMap(([tokens, user]) => {
// if (!user) return of(this.balanceLoaderService.getTokensWithNullBalances(tokens, false));

// this._isBalanceLoading$[blockchain].next(true);
// this.isBalanceAlreadyCalculatedForChain[blockchain] = true;

// return this.balanceLoaderService.getTokensWithBalance(tokens);
// }),
// catchError(() => of(List()))
// )
// .subscribe((tokensWithBalances: List<TokenAmount>) => {
// this.patchTokensBalances(tokensWithBalances, blockchain === 'allChains');
// this.tokensUpdaterService.triggerUpdateTokens();
// this._isBalanceLoading$[blockchain].next(false);
// });
// }

public async startBalanceCalculating(blockchain: AssetType): Promise<void> {
if (this.isBalanceAlreadyCalculatedForChain[blockchain]) {
console.log(
`%cSKIPPED FOR ${blockchain}`,
Expand All @@ -189,34 +227,33 @@ export class TokensStoreService {
);
return;
}
const tokensList =
blockchain === 'allChains'
? this.allChainsTokens
: this.tokens.filter(t => t.blockchain === blockchain);

if (!this.authService.user) {
const nullTokens = this.balanceLoaderService.getTokensWithNullBalances(tokensList, false);
this.patchTokensBalances(nullTokens, blockchain === 'allChains');
this.tokensUpdaterService.triggerUpdateTokens();
return;
}

const tokensList$: Observable<List<TokenAmount>> = iif(
() => blockchain === 'allChains',
this.allChainsTokens$,
this.tokens$.pipe(
first(v => Boolean(v)),
map(tokens => tokens.filter(t => t.blockchain === blockchain))
)
);

forkJoin([firstValueFrom(tokensList$), firstValueFrom(this.authService.currentUser$)])
.pipe(
debounceTime(500),
switchMap(([tokens, user]) => {
if (!user) return of(this.balanceLoaderService.getTokensWithNullBalances(tokens, false));

this._isBalanceLoading$[blockchain].next(true);
this.isBalanceAlreadyCalculatedForChain[blockchain] = true;

return this.balanceLoaderService.getTokensWithBalance(tokens);
}),
catchError(() => of(List()))
)
.subscribe((tokensWithBalances: List<TokenAmount>) => {
this.patchTokensBalances(tokensWithBalances, blockchain === 'allChains');
this.tokensUpdaterService.triggerUpdateTokens();
this._isBalanceLoading$[blockchain].next(false);
});
this._isBalanceLoading$[blockchain].next(true);
if (blockchain === 'allChains') {
this.balanceLoaderService.updateAllChainsTokensWithBalances(
tokensList,
this.patchTokensBalances.bind(this),
this.isBalanceAlreadyCalculatedForChain,
this._isBalanceLoading$
);
} else {
const tokens = await this.balanceLoaderService.getTokensWithBalance(tokensList);
this.patchTokensBalances(tokens, false);
this.tokensUpdaterService.triggerUpdateTokens();
this._isBalanceLoading$[blockchain].next(false);
this.isBalanceAlreadyCalculatedForChain[blockchain] = true;
}
}

public resetBalanceCalculatingStatuses(): void {
Expand Down

0 comments on commit 610212b

Please sign in to comment.