Skip to content

Commit

Permalink
Refactor token sorting for external use (#208)
Browse files Browse the repository at this point in the history
  • Loading branch information
qrtp authored Oct 4, 2024
1 parent 06c7ba5 commit f85d29c
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 152 deletions.
2 changes: 1 addition & 1 deletion packages/ui-components/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@unstoppabledomains/ui-components",
"version": "0.0.50-browser-extension.11",
"version": "0.0.50-browser-extension.12",
"private": true,
"description": "An open and reusable suite of Unstoppable Domains management components",
"keywords": [
Expand Down
159 changes: 8 additions & 151 deletions packages/ui-components/src/components/Wallet/TokensPortfolio.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ import React, {useEffect, useState} from 'react';
import {makeStyles} from '@unstoppabledomains/ui-kit/styles';

import type {CurrenciesType} from '../../lib';
import {TokenType, WALLET_CARD_HEIGHT, useTranslationContext} from '../../lib';
import {
TokenType,
WALLET_CARD_HEIGHT,
getSortedTokens,
useTranslationContext,
} from '../../lib';
import type {SerializedWalletBalance} from '../../lib/types/domain';
import CopyToClipboard from '../CopyToClipboard';
import {CryptoIcon} from '../Image';
Expand Down Expand Up @@ -165,156 +170,8 @@ export const TokensPortfolio: React.FC<TokensPortfolioProps> = ({
return;
}

// list of all monetized tokens, sorted by most valuable
const allTokens: TokenEntry[] = [
...(wallets || []).flatMap(wallet => {
if (
wallet.value?.history &&
wallet.value.history.length > 0 &&
wallet.value.history[wallet.value.history.length - 1].value !==
wallet.value.marketUsdAmt
) {
wallet.value.history.push({
timestamp: new Date(),
value: wallet.value.marketUsdAmt || 0,
});
}
return {
type: TokenType.Native,
name: wallet.name,
value: wallet.value?.walletUsdAmt || 0,
tokenConversionUsd: wallet.value?.marketUsdAmt || 0,
balance: wallet.balanceAmt || 0,
pctChange: wallet.value?.marketPctChange24Hr,
history: wallet.value?.history?.sort(
(a, b) =>
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),
),
symbol: wallet.symbol,
ticker: wallet.gasCurrency,
walletAddress: wallet.address,
walletBlockChainLink: wallet.blockchainScanUrl,
walletName: wallet.name,
walletType: wallet.walletType,
imageUrl: wallet.logoUrl,
};
}),
...(wallets || []).flatMap(wallet =>
(wallet?.nfts || []).map(walletNft => {
const fpEntry =
walletNft.floorPrice?.filter(
fp => fp.marketPctChange24Hr !== undefined,
) || [];
const pctChangeValue =
fpEntry.length > 0 ? fpEntry[0].marketPctChange24Hr : undefined;
if (
fpEntry.length > 0 &&
fpEntry[0].history &&
fpEntry[0].history.length > 0 &&
fpEntry[0].history[fpEntry[0].history.length - 1].value !==
fpEntry[0].value
) {
fpEntry[0].history.push({
timestamp: new Date(),
value: fpEntry[0].value || 0,
});
}
return {
type: TokenType.Nft,
name: walletNft.name,
value: walletNft.totalValueUsdAmt || 0,
balance: walletNft.ownedCount,
tokenConversionUsd:
walletNft.totalValueUsdAmt && walletNft.ownedCount
? walletNft.totalValueUsdAmt / walletNft.ownedCount
: 0,
pctChange: pctChangeValue,
history:
fpEntry.length > 0
? fpEntry[0].history?.sort(
(a, b) =>
new Date(a.timestamp).getTime() -
new Date(b.timestamp).getTime(),
)
: undefined,
symbol: wallet.symbol,
ticker: wallet.symbol,
walletAddress: wallet.address,
walletBlockChainLink: wallet.blockchainScanUrl,
walletName: wallet.name,
walletType: wallet.walletType,
imageUrl: walletNft.collectionImageUrl,
};
}),
),
...(wallets || []).flatMap(wallet =>
(wallet?.tokens || []).map(walletToken => {
return {
type: 'Token' as never,
name: walletToken.name,
value: walletToken.value?.walletUsdAmt || 0,
balance: walletToken.balanceAmt || 0,
pctChange: walletToken.value?.marketPctChange24Hr,
tokenConversionUsd: walletToken.value?.marketUsdAmt || 0,
history: walletToken.value?.history?.sort(
(a, b) =>
new Date(a.timestamp).getTime() -
new Date(b.timestamp).getTime(),
),
ticker: walletToken.symbol,
symbol: wallet.symbol,
walletAddress: wallet.address,
walletBlockChainLink: wallet.blockchainScanUrl,
walletName: wallet.name,
walletType: wallet.walletType,
imageUrl: walletToken.logoUrl,
};
}),
),
]
.filter(item => item?.value > 0.01 || item?.walletType === 'mpc')
.sort((a, b) => b.value - a.value || b.balance - a.balance)
.filter(
item =>
!filterAddress ||
(filterAddress.address.toLowerCase() ===
item.walletAddress.toLowerCase() &&
filterAddress.symbol.toLowerCase() === item.symbol.toLowerCase()),
);

// aggregate like tokens entries from different wallets
const tokens: TokenEntry[] = [];
allTokens.map(currentToken => {
// skip if this token has already been added to the list
const existingTokens = tokens.filter(
existingToken =>
existingToken.symbol === currentToken.symbol &&
existingToken.type === currentToken.type &&
existingToken.name === currentToken.name,
);
if (existingTokens.length > 0) {
return;
}

// aggregate balances from all matching tokens
const matchingTokens = allTokens.filter(
matchingToken =>
matchingToken.symbol === currentToken.symbol &&
matchingToken.type === currentToken.type &&
matchingToken.name === currentToken.name,
);
const token = {
...currentToken,
balance: matchingTokens
.map(matchingToken => matchingToken.balance)
.reduce((p, c) => p + c, 0),
value: matchingTokens
.map(matchingToken => matchingToken.value)
.reduce((p, c) => p + c, 0),
};
tokens.push(token);
});
setGroupedTokens(tokens);
// retrieve a list of sorted tokens
setGroupedTokens(getSortedTokens(wallets, filterAddress));
}, [wallets, filterAddress]);

// total value of the portfolio
Expand Down
1 change: 1 addition & 0 deletions packages/ui-components/src/lib/wallet/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './login';
export * from './signer';
export * from './tokens';
159 changes: 159 additions & 0 deletions packages/ui-components/src/lib/wallet/tokens.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
import {TokenEntry} from '../../components/Wallet/Token';
import {SerializedWalletBalance, TokenType} from '../types';

export const getSortedTokens = (
wallets: SerializedWalletBalance[],
filterAddress?: SerializedWalletBalance,
): TokenEntry[] => {
// list of all monetized tokens, sorted by most valuable
const allTokens: TokenEntry[] = [
...(wallets || []).flatMap(wallet => {
if (
wallet.value?.history &&
wallet.value.history.length > 0 &&
wallet.value.history[wallet.value.history.length - 1].value !==
wallet.value.marketUsdAmt
) {
wallet.value.history.push({
timestamp: new Date(),
value: wallet.value.marketUsdAmt || 0,
});
}
return {
type: TokenType.Native,
name: wallet.name,
value: wallet.value?.walletUsdAmt || 0,
tokenConversionUsd: wallet.value?.marketUsdAmt || 0,
balance: wallet.balanceAmt || 0,
pctChange: wallet.value?.marketPctChange24Hr,
history: wallet.value?.history?.sort(
(a, b) =>
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),
),
symbol: wallet.symbol,
ticker: wallet.gasCurrency,
walletAddress: wallet.address,
walletBlockChainLink: wallet.blockchainScanUrl,
walletName: wallet.name,
walletType: wallet.walletType,
imageUrl: wallet.logoUrl,
};
}),
...(wallets || []).flatMap(wallet =>
(wallet?.nfts || []).map(walletNft => {
const fpEntry =
walletNft.floorPrice?.filter(
fp => fp.marketPctChange24Hr !== undefined,
) || [];
const pctChangeValue =
fpEntry.length > 0 ? fpEntry[0].marketPctChange24Hr : undefined;
if (
fpEntry.length > 0 &&
fpEntry[0].history &&
fpEntry[0].history.length > 0 &&
fpEntry[0].history[fpEntry[0].history.length - 1].value !==
fpEntry[0].value
) {
fpEntry[0].history.push({
timestamp: new Date(),
value: fpEntry[0].value || 0,
});
}
return {
type: TokenType.Nft,
name: walletNft.name,
value: walletNft.totalValueUsdAmt || 0,
balance: walletNft.ownedCount,
tokenConversionUsd:
walletNft.totalValueUsdAmt && walletNft.ownedCount
? walletNft.totalValueUsdAmt / walletNft.ownedCount
: 0,
pctChange: pctChangeValue,
history:
fpEntry.length > 0
? fpEntry[0].history?.sort(
(a, b) =>
new Date(a.timestamp).getTime() -
new Date(b.timestamp).getTime(),
)
: undefined,
symbol: wallet.symbol,
ticker: wallet.symbol,
walletAddress: wallet.address,
walletBlockChainLink: wallet.blockchainScanUrl,
walletName: wallet.name,
walletType: wallet.walletType,
imageUrl: walletNft.collectionImageUrl,
};
}),
),
...(wallets || []).flatMap(wallet =>
(wallet?.tokens || []).map(walletToken => {
return {
type: 'Token' as never,
name: walletToken.name,
value: walletToken.value?.walletUsdAmt || 0,
balance: walletToken.balanceAmt || 0,
pctChange: walletToken.value?.marketPctChange24Hr,
tokenConversionUsd: walletToken.value?.marketUsdAmt || 0,
history: walletToken.value?.history?.sort(
(a, b) =>
new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime(),
),
ticker: walletToken.symbol,
symbol: wallet.symbol,
walletAddress: wallet.address,
walletBlockChainLink: wallet.blockchainScanUrl,
walletName: wallet.name,
walletType: wallet.walletType,
imageUrl: walletToken.logoUrl,
};
}),
),
]
.filter(item => item?.value > 0.01 || item?.walletType === 'mpc')
.sort((a, b) => b.value - a.value || b.balance - a.balance)
.filter(
item =>
!filterAddress ||
(filterAddress.address.toLowerCase() ===
item.walletAddress.toLowerCase() &&
filterAddress.symbol.toLowerCase() === item.symbol.toLowerCase()),
);

// aggregate like tokens entries from different wallets
const tokens: TokenEntry[] = [];
allTokens.map(currentToken => {
// skip if this token has already been added to the list
const existingTokens = tokens.filter(
existingToken =>
existingToken.symbol === currentToken.symbol &&
existingToken.type === currentToken.type &&
existingToken.name === currentToken.name,
);
if (existingTokens.length > 0) {
return;
}

// aggregate balances from all matching tokens
const matchingTokens = allTokens.filter(
matchingToken =>
matchingToken.symbol === currentToken.symbol &&
matchingToken.type === currentToken.type &&
matchingToken.name === currentToken.name,
);
const token = {
...currentToken,
balance: matchingTokens
.map(matchingToken => matchingToken.balance)
.reduce((p, c) => p + c, 0),
value: matchingTokens
.map(matchingToken => matchingToken.value)
.reduce((p, c) => p + c, 0),
};
tokens.push(token);
});

// return aggregated list
return tokens;
};

0 comments on commit f85d29c

Please sign in to comment.