Skip to content

Commit

Permalink
Merge pull request #3491 from Cryptorubic/new-all-chains
Browse files Browse the repository at this point in the history
New all chains
  • Loading branch information
PseudoElement authored Feb 10, 2025
2 parents a60a751 + 01ff0bc commit db12f35
Show file tree
Hide file tree
Showing 18 changed files with 2,207 additions and 2,205 deletions.
5 changes: 4 additions & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,10 @@
"karmaConfig": "src/karma.conf.js",
"styles": ["src/styles.scss"],
"scripts": [],
"assets": ["src/favicon.ico", "src/assets"]
"assets": [
"src/favicon.ico",
"src/assets"
]
}
},
"lint": {
Expand Down
33 changes: 20 additions & 13 deletions src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AfterViewInit, Component, Inject, isDevMode } from '@angular/core';
import { AfterViewInit, ChangeDetectorRef, Component, Inject, isDevMode } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { CookieService } from 'ngx-cookie-service';
Expand All @@ -8,7 +8,7 @@ import { QueryParams } from '@core/services/query-params/models/query-params';
import { QueryParamsService } from '@core/services/query-params/query-params.service';
import { GoogleTagManagerService } from '@core/services/google-tag-manager/google-tag-manager.service';
import { isSupportedLanguage } from '@shared/models/languages/supported-languages';
import { catchError, first, map, switchMap } from 'rxjs/operators';
import { catchError, first, map } from 'rxjs/operators';
import { forkJoin, Observable, of } from 'rxjs';
import { WINDOW } from '@ng-web-apis/common';
import { RubicWindow } from '@shared/utils/rubic-window';
Expand All @@ -18,6 +18,7 @@ import { WalletConnectorService } from './core/services/wallets/wallet-connector
import { TokensStoreService } from './core/services/tokens/tokens-store.service';
import { BalanceLoadingStateService } from './core/services/tokens/balance-loading-state.service';
import { AssetsSelectorStateService } from './features/trade/components/assets-selector/services/assets-selector-state/assets-selector-state.service';
import { TradePageService } from './features/trade/services/trade-page/trade-page.service';

@Component({
selector: 'app-root',
Expand All @@ -43,7 +44,9 @@ export class AppComponent implements AfterViewInit {
private readonly walletConnectorService: WalletConnectorService,
private readonly tokensStoreService: TokensStoreService,
private readonly balanceLoadingStateService: BalanceLoadingStateService,
private readonly assetsSelectorStateService: AssetsSelectorStateService
private readonly assetsSelectorStateService: AssetsSelectorStateService,
private readonly tradePageService: TradePageService,
private readonly cdr: ChangeDetectorRef
) {
this.printTimestamp();
this.setupLanguage();
Expand All @@ -58,16 +61,20 @@ export class AppComponent implements AfterViewInit {
}

private subscribeOnWalletChanges(): void {
this.walletConnectorService.addressChange$
.pipe(
switchMap(() => {
this.balanceLoadingStateService.resetBalanceCalculatingStatuses();
return this.tokensStoreService.startBalanceCalculating(
this.assetsSelectorStateService.assetType
);
})
)
.subscribe();
this.walletConnectorService.addressChange$.subscribe(() => {
this.balanceLoadingStateService.resetBalanceCalculatingStatuses();

// load allchains in background if token's selector closed if not loaded yet
if (
this.assetsSelectorStateService.assetType !== 'allChains' &&
this.tradePageService.formContent === 'form' &&
!this.balanceLoadingStateService.isBalanceCalculated('allChains')
) {
this.tokensStoreService.startBalanceCalculating('allChains');
}

this.tokensStoreService.startBalanceCalculating(this.assetsSelectorStateService.assetType);
});
}

/**
Expand Down
29 changes: 26 additions & 3 deletions src/app/core/services/backend/tokens-api/tokens-api.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ import { defaultTokens } from './models/default-tokens';
import { blockchainsToFetch, blockchainsWithOnePage } from './constants/fetch-blockchains';
import {
BackendBlockchain,
BLOCKCHAIN_NAME,
BlockchainName,
FROM_BACKEND_BLOCKCHAINS,
TO_BACKEND_BLOCKCHAINS
} from 'rubic-sdk';
import { ENVIRONMENT } from 'src/environments/environment';

import { compareAddresses, compareTokens } from '@app/shared/utils/utils';

/**
* Perform backend requests and transforms to get valid tokens.
*/
Expand Down Expand Up @@ -292,9 +295,29 @@ export class TokensApiService {
}

public fetchTokensListForAllChains(): Observable<List<Token>> {
return this.httpService
.get<BackendTokenForAllChains[]>('v2/tokens/allchains')
.pipe(map(backendTokens => TokensApiService.prepareTokens(backendTokens)));
return forkJoin([
this.httpService
.get<TokensBackendResponse>('v2/tokens/top')
.pipe(map(backendTokens => TokensApiService.prepareTokens(backendTokens.results))),
this.httpService
.get<BackendTokenForAllChains[]>('v2/tokens/allchains')
.pipe(map(backendTokens => TokensApiService.prepareTokens(backendTokens)))
]).pipe(
map(([topTokens, allChainsTokens]) => {
// filters unique tokens from v2/tokens/allchains and api/v2/tokens/?pageSize=5000
return topTokens.concat(allChainsTokens).reduce((acc, token) => {
// not show 2nd metis native token in selector
if (
token.blockchain === BLOCKCHAIN_NAME.METIS &&
compareAddresses(token.address, '0xdeaddeaddeaddeaddeaddeaddeaddeaddead0000')
) {
return acc;
}
const repeated = acc.find(t => compareTokens(t, token));
return repeated ? acc : acc.push(token);
}, List() as List<Token>);
})
);
}

public getFakeTokens(): BackendToken[] {
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/services/http/http.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export class HttpService {
const request$ = this.http.get<T>((path || SERVER_REST_URL) + (url || ''), {
params: data || {}
});

// @FIX set timeout to 5_000
return path ? request$ : request$.pipe(timeout(5_000), retry(1));
}

Expand Down
112 changes: 84 additions & 28 deletions src/app/core/services/tokens/balance-loader.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,20 @@ import { Token } from '@app/shared/models/tokens/token';
import { TokenAmount } from '@app/shared/models/tokens/token-amount';
import BigNumber from 'bignumber.js';
import { List } from 'immutable';
import { BlockchainName, BlockchainsInfo, Injector, Web3Public, Web3Pure } from 'rubic-sdk';
import {
BlockchainName,
BlockchainsInfo,
Injector,
waitFor,
Web3Public,
Web3Pure
} from 'rubic-sdk';
import { AuthService } from '../auth/auth.service';
import { ChainsToLoadFirstly, isTopChain } from './constants/first-loaded-chains';
import { BalanceLoadingStateService } from './balance-loading-state.service';
import { AssetType } from '@app/features/trade/models/asset';
import { getWeb3PublicSafe } from '@app/shared/utils/is-native-address-safe';
import { Iterable } from './utils/iterable';

type TokensListOfTopChainsWithOtherChains = {
[key in BlockchainName]: Token[];
Expand All @@ -29,8 +37,12 @@ export class BalanceLoaderService {

public updateBalancesForAllChains(
tokensList: List<TokenAmount | Token>,
onBalanceLoaded: (tokensWithBalances: List<TokenAmount>, patchAllChains: boolean) => void
onChainLoaded: (tokensWithBalances: List<TokenAmount>, patchAllChains: boolean) => void,
onFinish?: (allChainsTokensWithBalances: List<TokenAmount>) => void
): void {
// can be empty when v2/tokens/allchains response lower then first startBalanceCalculating call in app.component.ts
if (!tokensList.size) return;

const tokensByChain: TokensListOfTopChainsWithOtherChains = {
TOP_CHAINS: {}
} as TokensListOfTopChainsWithOtherChains;
Expand All @@ -51,6 +63,20 @@ export class BalanceLoaderService {

this.balanceLoadingStateService.setBalanceLoading('allChains', true);

const allTokensWithPositiveBalances = List([]).asMutable() as List<TokenAmount>;
const chainsCount = Object.keys(tokensByChain).length;
const iterator = new Iterable(chainsCount);

// calls onFinish() when every chain from tokensByChain loaded tokens with balances
if (onFinish) {
(async () => {
while (!iterator.done && !!this.authService.userAddress) {
await waitFor(100);
}
onFinish(allTokensWithPositiveBalances);
})();
}

for (const key in tokensByChain) {
if (key === 'TOP_CHAINS') {
const topChainsTokens = tokensByChain.TOP_CHAINS;
Expand All @@ -68,20 +94,32 @@ export class BalanceLoaderService {
}
);

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[];

onBalanceLoaded(List(tokensWithBalances), true);
this.balanceLoadingStateService.setBalanceCalculated('allChains', true);
this.balanceLoadingStateService.setBalanceLoading('allChains', false);
});
Promise.all(promises)
.then(balances => {
const flattenBalances = balances.flat();
const flattenTokens = Object.values(topChainsTokens).flat();
const tokensWithBalancesList = List(
flattenTokens.map((token, idx) => ({
...token,
amount: flattenBalances[idx]
? Web3Pure.fromWei(flattenBalances[idx], token.decimals)
: new BigNumber(NaN)
})) as TokenAmount[]
);

const tokensWithPositiveBalances = tokensWithBalancesList.filter(
t => !t.amount.isNaN() && t.amount.gt(0)
);
allTokensWithPositiveBalances.concat(tokensWithPositiveBalances);

if (balances.length) {
this.balanceLoadingStateService.setBalanceCalculated('allChains', true);
this.balanceLoadingStateService.setBalanceLoading('allChains', false);
}

onChainLoaded(tokensWithBalancesList, true);
})
.finally(() => iterator.next());
} else {
const chain = key as Exclude<keyof TokensListOfTopChainsWithOtherChains, 'TOP_CHAINS'>;
const chainTokens = tokensByChain[chain];
Expand All @@ -96,31 +134,49 @@ export class BalanceLoaderService {
.catch(() => chainTokens.map(() => new BigNumber(NaN)))
: Promise.resolve(chainTokens.map(() => new BigNumber(NaN)));

balancesPromise.then(balances => {
const tokensWithBalances = chainTokens.map((token, idx) => ({
...token,
amount: balances[idx]
? Web3Pure.fromWei(balances[idx], token.decimals)
: new BigNumber(NaN)
})) as TokenAmount[];

onBalanceLoaded(List(tokensWithBalances), true);
});
balancesPromise
.then(balances => {
const tokensWithBalancesList = List(
chainTokens.map((token, idx) => ({
...token,
amount: balances[idx]
? Web3Pure.fromWei(balances[idx], token.decimals)
: new BigNumber(NaN)
})) as TokenAmount[]
);

const tokensWithPositiveBalances = tokensWithBalancesList.filter(t => {
// not show 2nd metis native token in selector
// if (
// t.blockchain === BLOCKCHAIN_NAME.METIS &&
// compareAddresses(t.address, '0xdeaddeaddeaddeaddeaddeaddeaddeaddead0000')
// ) {
// return false;
// }
if (t.amount.isNaN() || t.amount.lte(0)) return false;

return false;
});
allTokensWithPositiveBalances.concat(tokensWithPositiveBalances);

onChainLoaded(tokensWithBalancesList, true);
})
.finally(() => iterator.next());
}
}
}

public async updateBalancesForSpecificChain(
tokensList: List<Token>,
blockchain: AssetType,
onBalanceLoaded: (tokensWithBalances: List<TokenAmount>, patchAllChains: boolean) => void
onFinish: (tokensWithBalances: List<TokenAmount>, userAddress: string | undefined) => void
): Promise<void> {
this.balanceLoadingStateService.setBalanceLoading(blockchain, true);
const tokensWithBalances = await this.getTokensWithBalance(tokensList);

onBalanceLoaded(tokensWithBalances, false);
this.balanceLoadingStateService.setBalanceCalculated(blockchain, true);
this.balanceLoadingStateService.setBalanceLoading(blockchain, false);
onFinish(tokensWithBalances, this.authService.userAddress);
}

/**
Expand Down
4 changes: 4 additions & 0 deletions src/app/core/services/tokens/balance-loading-state.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ export class BalanceLoadingStateService {
public isBalanceLoading$(blockchain: AssetType): Observable<boolean> {
return this._isBalanceLoading$[blockchain].asObservable();
}

public isBalanceLoading(blockchain: AssetType): boolean {
return this._isBalanceLoading$[blockchain].value;
}
}
51 changes: 51 additions & 0 deletions src/app/core/services/tokens/token-converters.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Injectable } from '@angular/core';
import { TokenAddress } from '@app/features/trade/components/assets-selector/services/tokens-list-service/models/tokens-list';
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 { compareAddresses } from '@app/shared/utils/utils';
import BigNumber from 'bignumber.js';
import { List } from 'immutable';
import { BLOCKCHAIN_NAME, EvmWeb3Pure } from 'rubic-sdk';

@Injectable({
providedIn: 'root'
})
export class TokenConvertersService {
constructor() {}

/**
* from https://assets.rubic.exchange/assets/ethereum-pow/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48/logo.png
* @returns ethereum-pow/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48/logo.png
*/
public getTokenKeyInMap(t: Token): string {
// show only one native token in selector for METIS
if (
t.address === BLOCKCHAIN_NAME.METIS &&
(compareAddresses(t.address, EvmWeb3Pure.nativeTokenAddress) ||
compareAddresses(t.address, '0xdeaddeaddeaddeaddeaddeaddeaddeaddead0000'))
) {
return `METIS_NATIVE`;
}

const splitted = t.image.split('/');
const imgKey = splitted.slice(splitted.length - 3).join('/');
return imgKey;
}

public convertTokensListToMap(
tokensWithBalances: List<TokenAmount | Token>
): Map<TokenAddress, TokenAmount> {
const tokensWithBalancesMap = new Map<TokenAddress, TokenAmount>();
tokensWithBalances.forEach((t: TokenAmount | Token) => {
if (isTokenAmount(t)) {
tokensWithBalancesMap.set(this.getTokenKeyInMap(t), t);
} else {
const tokenAmount = { amount: new BigNumber(NaN), favorite: false, ...t } as TokenAmount;
tokensWithBalancesMap.set(this.getTokenKeyInMap(tokenAmount), tokenAmount);
}
});

return tokensWithBalancesMap;
}
}
9 changes: 6 additions & 3 deletions src/app/core/services/tokens/tokens-network.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { List } from 'immutable';
import { Token } from '@shared/models/tokens/token';
import { BalanceLoaderService } from './balance-loader.service';
import { BalanceLoadingStateService } from './balance-loading-state.service';
import { TokensUpdaterService } from './tokens-updater.service';

@Injectable({
providedIn: 'root'
Expand Down Expand Up @@ -50,7 +51,8 @@ export class TokensNetworkService {
private readonly balanceLoaderService: BalanceLoaderService,
private readonly balanceLoadingStateService: BalanceLoadingStateService,
private readonly tokensApiService: TokensApiService,
private readonly authService: AuthService
private readonly authService: AuthService,
private readonly tokensUpdaterService: TokensUpdaterService
) {
this.setupSubscriptions();
}
Expand All @@ -63,7 +65,7 @@ export class TokensNetworkService {
}),
tap(backendTokens => {
this.tokensStoreService.updateStorageTokens(backendTokens);
this.tokensStoreService.patchTokens(backendTokens, false);
this.tokensStoreService.patchTokens(backendTokens);
}),
switchMap(backendTokens => {
const uniqueBlockchains = [...new Set(backendTokens.map(bT => bT.blockchain))];
Expand Down Expand Up @@ -142,7 +144,8 @@ export class TokensNetworkService {
})
)
.subscribe((tokens: TokenAmount[]) => {
this.tokensStoreService.patchTokens(List(tokens), false);
this.tokensStoreService.patchTokens(List(tokens));
this.tokensUpdaterService.triggerUpdateTokens();
});
}
}
Loading

0 comments on commit db12f35

Please # to comment.