diff --git a/packages/ui-components/package.json b/packages/ui-components/package.json index 4e8c07be..bff84248 100644 --- a/packages/ui-components/package.json +++ b/packages/ui-components/package.json @@ -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": [ diff --git a/packages/ui-components/src/components/Wallet/TokensPortfolio.tsx b/packages/ui-components/src/components/Wallet/TokensPortfolio.tsx index d19e8316..528ee20a 100644 --- a/packages/ui-components/src/components/Wallet/TokensPortfolio.tsx +++ b/packages/ui-components/src/components/Wallet/TokensPortfolio.tsx @@ -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'; @@ -165,156 +170,8 @@ export const TokensPortfolio: React.FC = ({ 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 diff --git a/packages/ui-components/src/lib/wallet/index.ts b/packages/ui-components/src/lib/wallet/index.ts index 5e0123a8..74a80260 100644 --- a/packages/ui-components/src/lib/wallet/index.ts +++ b/packages/ui-components/src/lib/wallet/index.ts @@ -1,2 +1,3 @@ export * from './login'; export * from './signer'; +export * from './tokens'; diff --git a/packages/ui-components/src/lib/wallet/tokens.ts b/packages/ui-components/src/lib/wallet/tokens.ts new file mode 100644 index 00000000..4e6b518f --- /dev/null +++ b/packages/ui-components/src/lib/wallet/tokens.ts @@ -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; +};