From eb6084af2cf911f39c9a5f9b9385bad517e2a2bc Mon Sep 17 00:00:00 2001 From: Matt Luongo Date: Mon, 11 Jul 2022 22:41:03 -0400 Subject: [PATCH 001/121] Add a warning icon for untrusted assets Currently, the icon displays for all assets in the wallet asset list, regardless of whether they're trusted. Refs #1021 --- .../AssetListItem/CommonAssetListItem.tsx | 9 +++- ui/components/Wallet/WalletAssetListItem.tsx | 46 ++++++++++++------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx b/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx index e0343a83da..9816d02dfe 100644 --- a/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx +++ b/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx @@ -39,7 +39,14 @@ export default function CommonAssetListItem({ {assetAmount.localizedDecimalAmount} - {assetAmount.asset.symbol} + {assetAmount.asset.symbol} + {initializationLoadingTimeExpired && isMissingLocalizedUserValue ? ( <> diff --git a/ui/components/Wallet/WalletAssetListItem.tsx b/ui/components/Wallet/WalletAssetListItem.tsx index 2a3c631809..fe7aaa7ab3 100644 --- a/ui/components/Wallet/WalletAssetListItem.tsx +++ b/ui/components/Wallet/WalletAssetListItem.tsx @@ -27,35 +27,47 @@ export default function WalletAssetListItem(props: Props): ReactElement { From a6930336d82070596ffd31f992f06d9147fb839b Mon Sep 17 00:00:00 2001 From: Matt Luongo Date: Wed, 13 Jul 2022 23:27:15 -0400 Subject: [PATCH 002/121] Prototype a warning slide-up for untrusted assets Refs #1021 --- ui/components/Wallet/WalletAssetList.tsx | 142 +++++++++++++++++++---- 1 file changed, 120 insertions(+), 22 deletions(-) diff --git a/ui/components/Wallet/WalletAssetList.tsx b/ui/components/Wallet/WalletAssetList.tsx index 292b09dbf1..3835320353 100644 --- a/ui/components/Wallet/WalletAssetList.tsx +++ b/ui/components/Wallet/WalletAssetList.tsx @@ -2,8 +2,40 @@ // import React, { ReactElement } from "react" import { CompleteAssetAmount } from "@tallyho/tally-background/redux-slices/accounts" +import SharedSlideUpMenu from "../Shared/SharedSlideUpMenu" +import SharedBanner from "../Shared/SharedBanner" +import SharedButton from "../Shared/SharedButton" +import SharedAddress from "../Shared/SharedAddress" import WalletAssetListItem from "./WalletAssetListItem" +type TitledSlideUpProps = Parameters[0] & { + title: string +} + +function TitledSlideUpMenu(props: TitledSlideUpProps): ReactElement { + const { title, children, ...others } = props + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + + + {title} + + {children} + + + ) +} + interface Props { assetAmounts: CompleteAssetAmount[] initializationLoadingTimeExpired: boolean @@ -13,27 +45,93 @@ export default function WalletAssetList(props: Props): ReactElement { const { assetAmounts, initializationLoadingTimeExpired } = props if (!assetAmounts) return <> return ( -
    - {assetAmounts.map((assetAmount) => ( - - ))} - {!initializationLoadingTimeExpired && ( -
  • Digging deeper...
  • - )} - -
+ <> + {}} + size="small" + title="Asset imported from transaction history" + > + + + + Asset has not been verified yet! +
+ Only transact with assets you trust. +
+
    +
  • + Name +
    + KEEP +
    +
  • +
  • + Contract address +
    + +
    +
  • +
+ + Close + +
+
    + {assetAmounts.map((assetAmount) => ( + + ))} + {!initializationLoadingTimeExpired && ( +
  • Digging deeper...
  • + )} + +
+ ) } From 5d23f61218ede6576e7d73989e30d44b2e6dbe94 Mon Sep 17 00:00:00 2001 From: Matt Luongo Date: Mon, 25 Jul 2022 13:38:41 -0400 Subject: [PATCH 003/121] Extract AssetWarningSlideUp into its own file We'll be using the component across both the asset list and the asset papge. Refs #1021 --- ui/components/Wallet/AssetWarningSlideUp.tsx | 112 +++++++++++++++++++ ui/components/Wallet/WalletAssetList.tsx | 109 ++---------------- 2 files changed, 122 insertions(+), 99 deletions(-) create mode 100644 ui/components/Wallet/AssetWarningSlideUp.tsx diff --git a/ui/components/Wallet/AssetWarningSlideUp.tsx b/ui/components/Wallet/AssetWarningSlideUp.tsx new file mode 100644 index 0000000000..01f7f9e500 --- /dev/null +++ b/ui/components/Wallet/AssetWarningSlideUp.tsx @@ -0,0 +1,112 @@ +// @ts-check + +import React, { ReactElement } from "react" +import SharedSlideUpMenu from "../Shared/SharedSlideUpMenu" +import SharedBanner from "../Shared/SharedBanner" +import SharedButton from "../Shared/SharedButton" +import SharedAddress from "../Shared/SharedAddress" + +type TitledSlideUpProps = Parameters[0] & { + title: string +} + +function TitledSlideUpMenu(props: TitledSlideUpProps): ReactElement { + const { title, children, ...others } = props + return ( + // eslint-disable-next-line react/jsx-props-no-spreading + + + {title} + + {children} + + + ) +} + +type AssetWarningSlideUpProps = { + assetName: string + contractAddress: string +} + +export default function AssetWarningSlideUp( + props: AssetWarningSlideUpProps +): ReactElement { + const { assetName, contractAddress } = props + return ( + {}} + size="small" + title="Asset imported from transaction history" + > + + + + Asset has not been verified yet! +
+ Only transact with assets you trust. +
+
    +
  • + Name +
    + {assetName} +
    +
  • +
  • + Contract address +
    + +
    +
  • +
+ + Close + +
+ ) +} diff --git a/ui/components/Wallet/WalletAssetList.tsx b/ui/components/Wallet/WalletAssetList.tsx index 3835320353..34f23a3a3c 100644 --- a/ui/components/Wallet/WalletAssetList.tsx +++ b/ui/components/Wallet/WalletAssetList.tsx @@ -1,115 +1,26 @@ // @ts-check -// + import React, { ReactElement } from "react" import { CompleteAssetAmount } from "@tallyho/tally-background/redux-slices/accounts" -import SharedSlideUpMenu from "../Shared/SharedSlideUpMenu" -import SharedBanner from "../Shared/SharedBanner" -import SharedButton from "../Shared/SharedButton" -import SharedAddress from "../Shared/SharedAddress" import WalletAssetListItem from "./WalletAssetListItem" +import AssetWarningSlideUp from "./AssetWarningSlideUp" -type TitledSlideUpProps = Parameters[0] & { - title: string -} - -function TitledSlideUpMenu(props: TitledSlideUpProps): ReactElement { - const { title, children, ...others } = props - return ( - // eslint-disable-next-line react/jsx-props-no-spreading - - - {title} - - {children} - - - ) -} - -interface Props { +type WalletAssetListProps = { assetAmounts: CompleteAssetAmount[] initializationLoadingTimeExpired: boolean } -export default function WalletAssetList(props: Props): ReactElement { +export default function WalletAssetList( + props: WalletAssetListProps +): ReactElement { const { assetAmounts, initializationLoadingTimeExpired } = props if (!assetAmounts) return <> return ( <> - {}} - size="small" - title="Asset imported from transaction history" - > - - - - Asset has not been verified yet! -
- Only transact with assets you trust. -
-
    -
  • - Name -
    - KEEP -
    -
  • -
  • - Contract address -
    - -
    -
  • -
- - Close - -
+
    {assetAmounts.map((assetAmount) => ( Date: Mon, 25 Jul 2022 17:27:27 -0400 Subject: [PATCH 004/121] Wire up asset warning slideup to the asset list Refs #1021 --- .../AssetListItem/CommonAssetListItem.tsx | 32 +++++++++++++------ ui/components/Wallet/AssetWarningSlideUp.tsx | 19 +++++++---- ui/components/Wallet/WalletAssetList.tsx | 13 ++++++-- ui/components/Wallet/WalletAssetListItem.tsx | 9 +++++- 4 files changed, 52 insertions(+), 21 deletions(-) diff --git a/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx b/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx index 9816d02dfe..63fb653da1 100644 --- a/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx +++ b/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx @@ -6,13 +6,20 @@ import SharedLoadingSpinner from "../../Shared/SharedLoadingSpinner" import SharedAssetIcon from "../../Shared/SharedAssetIcon" import styles from "./styles" -export default function CommonAssetListItem({ - assetAmount, - initializationLoadingTimeExpired, -}: { +type CommonAssetListItemProps = { assetAmount: CompleteAssetAmount initializationLoadingTimeExpired: boolean -}): ReactElement { + onUntrustedAssetWarningClick?: (asset: CompleteAssetAmount["asset"]) => void +} + +export default function CommonAssetListItem( + props: CommonAssetListItemProps +): ReactElement { + const { + assetAmount, + initializationLoadingTimeExpired, + onUntrustedAssetWarningClick, + } = props const isMissingLocalizedUserValue = typeof assetAmount.localizedMainCurrencyAmount === "undefined" @@ -40,13 +47,18 @@ export default function CommonAssetListItem({ {assetAmount.localizedDecimalAmount} {assetAmount.asset.symbol} - { + event.preventDefault() + if (onUntrustedAssetWarningClick) { + onUntrustedAssetWarningClick(assetAmount.asset) + } }} className="untrusted_asset_icon" - /> + > + Asset isn't trusted + {initializationLoadingTimeExpired && isMissingLocalizedUserValue ? ( <> diff --git a/ui/components/Wallet/AssetWarningSlideUp.tsx b/ui/components/Wallet/AssetWarningSlideUp.tsx index 01f7f9e500..2320a57c4b 100644 --- a/ui/components/Wallet/AssetWarningSlideUp.tsx +++ b/ui/components/Wallet/AssetWarningSlideUp.tsx @@ -1,6 +1,7 @@ // @ts-check import React, { ReactElement } from "react" +import { AnyAsset } from "@tallyho/tally-background/assets" import SharedSlideUpMenu from "../Shared/SharedSlideUpMenu" import SharedBanner from "../Shared/SharedBanner" import SharedButton from "../Shared/SharedButton" @@ -35,20 +36,20 @@ function TitledSlideUpMenu(props: TitledSlideUpProps): ReactElement { } type AssetWarningSlideUpProps = { - assetName: string - contractAddress: string + asset: AnyAsset | null + close: () => void } export default function AssetWarningSlideUp( props: AssetWarningSlideUpProps ): ReactElement { - const { assetName, contractAddress } = props + const { asset, close } = props return ( {}} + isOpen={asset !== null} size="small" title="Asset imported from transaction history" + close={close} > From fceecfefa4b76a96790f8e4ef5adfd6098ebfd5d Mon Sep 17 00:00:00 2001 From: Matt Luongo Date: Fri, 29 Jul 2022 16:11:34 -0400 Subject: [PATCH 005/121] Only show warnings for assets without token lists Refs #1021 --- .../AssetListItem/CommonAssetListItem.tsx | 33 ++++++++++++------- 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx b/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx index 63fb653da1..4d6349484a 100644 --- a/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx +++ b/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx @@ -23,6 +23,13 @@ export default function CommonAssetListItem( const isMissingLocalizedUserValue = typeof assetAmount.localizedMainCurrencyAmount === "undefined" + // NB: non-base assets that don't have any token lists are considered + // untrusted. Reifying base assets clearly will improve this check down the + // road. Eventually, assets can be flagged as trusted by adding them to an + // "internal" token list that users can export and share. + const numTokenLists = assetAmount?.asset?.metadata?.tokenLists?.length ?? 0 + const baseAsset = !("homeNetwork" in assetAmount?.asset) + const contractAddress = "contractAddress" in assetAmount.asset ? assetAmount.asset.contractAddress @@ -47,18 +54,20 @@ export default function CommonAssetListItem( {assetAmount.localizedDecimalAmount} {assetAmount.asset.symbol} - + {numTokenLists === 0 && !baseAsset && ( + + )} {initializationLoadingTimeExpired && isMissingLocalizedUserValue ? ( <> From 7d616a2ecdf2bf4af4cc0964a0e94d59ea2fd308 Mon Sep 17 00:00:00 2001 From: Matt Luongo Date: Sat, 6 Aug 2022 21:25:52 -0400 Subject: [PATCH 006/121] Revert "Turn off auto-add tokens from asset transfers" Now that we show warnings for tokens without a backing token list, we can open the flood gates. This reverts commit 9363d622c24d75702ea3447ccbba959e1a3cdf33. --- background/services/indexing/index.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/background/services/indexing/index.ts b/background/services/indexing/index.ts index 1159b83113..3432b89158 100644 --- a/background/services/indexing/index.ts +++ b/background/services/indexing/index.ts @@ -349,6 +349,24 @@ export default class IndexingService extends BaseService { } private async connectChainServiceEvents(): Promise { + // listen for assetTransfers, and if we find them, track those tokens + // TODO update for NFTs + this.chainService.emitter.on( + "assetTransfers", + async ({ addressNetwork, assetTransfers }) => { + assetTransfers.forEach((transfer) => { + const fungibleAsset = transfer.assetAmount + .asset as SmartContractFungibleAsset + if (fungibleAsset.contractAddress && fungibleAsset.decimals) { + this.addTokenToTrackByContract( + addressNetwork, + fungibleAsset.contractAddress + ) + } + }) + } + ) + this.chainService.emitter.on( "newAccountToTrack", async (addressOnNetwork) => { From 3a8b68f57e98768a1320ea3d3f34b2d01affbf2a Mon Sep 17 00:00:00 2001 From: Matt Luongo Date: Sat, 6 Aug 2022 21:44:25 -0400 Subject: [PATCH 007/121] Overflow with an ellipsis for long token names ... in wallet asset list warnings. --- ui/components/Wallet/AssetWarningSlideUp.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/ui/components/Wallet/AssetWarningSlideUp.tsx b/ui/components/Wallet/AssetWarningSlideUp.tsx index 2320a57c4b..fedae379b5 100644 --- a/ui/components/Wallet/AssetWarningSlideUp.tsx +++ b/ui/components/Wallet/AssetWarningSlideUp.tsx @@ -85,6 +85,13 @@ export default function AssetWarningSlideUp( display: flex; align-items: flex-end; } + ul.asset_details > li.asset_name > div { + max-width: 80%; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + -o-text-overflow: ellipsis; + } `} Asset has not been verified yet! @@ -92,7 +99,7 @@ export default function AssetWarningSlideUp( Only transact with assets you trust.
      -
    • +
    • Name
      {`${asset?.name} (${asset?.symbol})`} From fc5724463cb9490310d56368e761e2583ee4eb2b Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Mon, 12 Dec 2022 13:51:06 +0100 Subject: [PATCH 008/121] Fix asset action icons margin --- ui/components/Wallet/WalletAssetListItem.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/components/Wallet/WalletAssetListItem.tsx b/ui/components/Wallet/WalletAssetListItem.tsx index 75e1696cab..6bd5c25939 100644 --- a/ui/components/Wallet/WalletAssetListItem.tsx +++ b/ui/components/Wallet/WalletAssetListItem.tsx @@ -58,11 +58,9 @@ export default function WalletAssetListItem(props: Props): ReactElement { } .asset_icon_send { mask-image: url("./images/send_asset.svg"); - margin-left: 20px; } .asset_icon_swap { mask-image: url("./images/swap_asset.svg"); - margin-left: 20px; } .untrusted_asset_icon { display: inline-block; From b1889baf4fec5bb67d6dd61ea576fc48171f2e04 Mon Sep 17 00:00:00 2001 From: hyphenized <28708889+hyphenized@users.noreply.github.com> Date: Sun, 20 Nov 2022 02:15:16 -0500 Subject: [PATCH 009/121] Added auto-update to Swap quotes This should improve UX and prevent stale quotes, the quote should not refresh automatically if an input has focus or if it's too recent. --- ui/hooks/react-hooks.ts | 19 +++++++++++++++++++ ui/pages/Swap.tsx | 39 +++++++++++++++++++++++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/ui/hooks/react-hooks.ts b/ui/hooks/react-hooks.ts index 111ec9e74d..f4c679a7e9 100644 --- a/ui/hooks/react-hooks.ts +++ b/ui/hooks/react-hooks.ts @@ -16,6 +16,25 @@ export function useIsMounted(): React.MutableRefObject { return mountedRef } +/** + * Proper implementation of `setInterval` with cleanup on component unmount + */ +export function useInterval unknown>( + callback: F, + delay: number +): void { + const callbackRef = useRef(callback) + callbackRef.current = callback + + useEffect(() => { + const timerId = setInterval(() => callbackRef.current(), delay) + + return () => { + clearInterval(timerId) + } + }, [delay]) +} + /** * Returns an always updated ref to value */ diff --git a/ui/pages/Swap.tsx b/ui/pages/Swap.tsx index 75e01ae14e..8571d0881d 100644 --- a/ui/pages/Swap.tsx +++ b/ui/pages/Swap.tsx @@ -28,7 +28,10 @@ import { selectDefaultNetworkFeeSettings } from "@tallyho/tally-background/redux import { selectSlippageTolerance } from "@tallyho/tally-background/redux-slices/ui" import { isNetworkBaseAsset } from "@tallyho/tally-background/redux-slices/utils/asset-utils" import { ReadOnlyAccountSigner } from "@tallyho/tally-background/services/signing" -import { NETWORKS_SUPPORTING_SWAPS } from "@tallyho/tally-background/constants" +import { + NETWORKS_SUPPORTING_SWAPS, + SECOND, +} from "@tallyho/tally-background/constants" import CorePage from "../components/Core/CorePage" import SharedAssetInput from "../components/Shared/SharedAssetInput" @@ -43,9 +46,11 @@ import SharedBanner from "../components/Shared/SharedBanner" import ReadOnlyNotice from "../components/Shared/ReadOnlyNotice" import ApproveQuoteBtn from "../components/Swap/ApproveQuoteButton" import { isSameAsset, useSwapQuote } from "../utils/swap" -import { useOnMount, usePrevious } from "../hooks/react-hooks" +import { useOnMount, usePrevious, useInterval } from "../hooks/react-hooks" import SharedLoadingDoggo from "../components/Shared/SharedLoadingDoggo" +const REFRESH_QUOTE_INTERVAL = 5 * SECOND + export default function Swap(): ReactElement { const { t } = useTranslation() const dispatch = useBackgroundDispatch() @@ -344,6 +349,32 @@ export default function Swap(): ReactElement { } } + const [amountInputHasFocus, setAmountInputHasFocus] = useState(false) + + useInterval(() => { + const isRecentQuote = + quote && + // Time passed since last quote + Date.now() - quote.timestamp <= 3 * SECOND + + const skipRefresh = + loadingQuote || (isRecentQuote && quoteAppliesToCurrentAssets) + + if ( + !skipRefresh && + !amountInputHasFocus && + sellAsset && + buyAsset && + sellAmount + ) + requestQuoteUpdate({ + type: "getBuyAmount", + amount: sellAmount, + sellAsset, + buyAsset, + }) + }, REFRESH_QUOTE_INTERVAL) + useOnMount(() => { // Request a quote on mount if (sellAsset && buyAsset && sellAmount) { @@ -435,6 +466,8 @@ export default function Swap(): ReactElement { selectedAsset={sellAsset} isDisabled={loadingSellAmount} onAssetSelect={updateSellAsset} + onFocus={() => setAmountInputHasFocus(true)} + onBlur={() => setAmountInputHasFocus(false)} mainCurrencySign={mainCurrencySign} onAmountChange={(newAmount, error) => { setSellAmount(newAmount) @@ -473,6 +506,8 @@ export default function Swap(): ReactElement { assetsAndAmounts={buyAssets.map((asset) => ({ asset }))} selectedAsset={buyAsset} isDisabled={loadingBuyAmount} + onFocus={() => setAmountInputHasFocus(true)} + onBlur={() => setAmountInputHasFocus(false)} showMaxButton={false} mainCurrencySign={mainCurrencySign} onAssetSelect={updateBuyAsset} From 773e7681c64f60fc7ad67d316e8d02928b220441 Mon Sep 17 00:00:00 2001 From: hyphenized <28708889+hyphenized@users.noreply.github.com> Date: Mon, 7 Nov 2022 01:18:46 -0500 Subject: [PATCH 010/121] Feature flag quote refresh --- .env.defaults | 1 + background/features.ts | 1 + ui/_locales/en/messages.json | 3 ++- ui/pages/Swap.tsx | 2 ++ 4 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.env.defaults b/.env.defaults index 5afe5f1626..6231b7c820 100644 --- a/.env.defaults +++ b/.env.defaults @@ -25,6 +25,7 @@ SUPPORT_FORGOT_PASSWORD=false SUPPORT_AVALANCHE=true SUPPORT_BINANCE_SMART_CHAIN=false SUPPORT_ARBITRUM_NOVA=false +SUPPORT_SWAP_QUOTE_REFRESH=false ENABLE_ACHIEVEMENTS_TAB=true SUPPORT_ACHIEVEMENTS_BANNER=false SWITCH_RUNTIME_FLAGS=false diff --git a/background/features.ts b/background/features.ts index 834b9c86a9..72360ff398 100644 --- a/background/features.ts +++ b/background/features.ts @@ -35,6 +35,7 @@ export const RuntimeFlag = { process.env.SUPPORT_ACHIEVEMENTS_BANNER === "true", SUPPORT_NFT_TAB: process.env.SUPPORT_NFT_TAB === "true", SUPPORT_WALLET_CONNECT: process.env.SUPPORT_WALLET_CONNECT === "true", + SUPPORT_SWAP_QUOTE_REFRESH: process.env.SUPPORT_SWAP_QUOTE_REFRESH === "true", } as const type BuildTimeFlagType = keyof typeof BuildTimeFlag diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index 5f39aa607d..3a8129b965 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -652,7 +652,8 @@ "SUPPORT_AVALANCHE": "Enable Avalanche network", "SUPPORT_BINANCE_SMART_CHAIN": "Enable Binance Smart Chain network", "SUPPORT_ARBITRUM_NOVA": "Enable Arbitrum Nova network", - "SUPPORT_NFT_TAB": "Enable to open NFTs page from tab" + "SUPPORT_NFT_TAB": "Enable to open NFTs page from tab", + "SUPPORT_SWAP_QUOTE_REFRESH": "Enable automatic swap quote updates" } } }, diff --git a/ui/pages/Swap.tsx b/ui/pages/Swap.tsx index 8571d0881d..5066f6c211 100644 --- a/ui/pages/Swap.tsx +++ b/ui/pages/Swap.tsx @@ -352,6 +352,8 @@ export default function Swap(): ReactElement { const [amountInputHasFocus, setAmountInputHasFocus] = useState(false) useInterval(() => { + if (!isEnabled(FeatureFlags.SUPPORT_SWAP_QUOTE_REFRESH)) return + const isRecentQuote = quote && // Time passed since last quote From fe968434ac43c8139590889707b868c48a2287dc Mon Sep 17 00:00:00 2001 From: hyphenized <28708889+hyphenized@users.noreply.github.com> Date: Wed, 14 Dec 2022 23:17:45 -0500 Subject: [PATCH 011/121] Auto update quote if either buy or sell asset are present - Increased refresh time from 5 to 10 seconds - Added a wrapper to prevent submit button from moving when loader appears --- ui/_locales/en/messages.json | 1 + ui/pages/Swap.tsx | 35 +++++++++++++++++++++++------------ 2 files changed, 24 insertions(+), 12 deletions(-) diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index 3a8129b965..83c8703f71 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -508,6 +508,7 @@ "swapRewardsTeaser": "Swap rewards coming soon", "continueSwap": "Continue Swap", "exchangeRoute": "Exchange route", + "loadingQuote": "Fetching price", "rewards": { "header": "Swap rewards for community", "body": "This week, 240,000 DOGGO tokens will be equally shared as Swap Rewards.", diff --git a/ui/pages/Swap.tsx b/ui/pages/Swap.tsx index 5066f6c211..46e291c535 100644 --- a/ui/pages/Swap.tsx +++ b/ui/pages/Swap.tsx @@ -49,7 +49,7 @@ import { isSameAsset, useSwapQuote } from "../utils/swap" import { useOnMount, usePrevious, useInterval } from "../hooks/react-hooks" import SharedLoadingDoggo from "../components/Shared/SharedLoadingDoggo" -const REFRESH_QUOTE_INTERVAL = 5 * SECOND +const REFRESH_QUOTE_INTERVAL = 10 * SECOND export default function Swap(): ReactElement { const { t } = useTranslation() @@ -367,14 +367,18 @@ export default function Swap(): ReactElement { !amountInputHasFocus && sellAsset && buyAsset && - sellAmount - ) + (sellAmount || buyAmount) + ) { + const type = sellAmount ? "getBuyAmount" : "getSellAmount" + const amount = sellAmount || buyAmount + requestQuoteUpdate({ - type: "getBuyAmount", - amount: sellAmount, + type, + amount, sellAsset, buyAsset, }) + } }, REFRESH_QUOTE_INTERVAL) useOnMount(() => { @@ -530,13 +534,14 @@ export default function Swap(): ReactElement { }} label={t("swap.to")} /> - {loadingQuote && sellAsset && buyAsset && ( - - )} +
      + {loadingQuote && sellAsset && buyAsset && ( + + )} +
      {!isEnabled(FeatureFlags.HIDE_SWAP_REWARDS) ? ( @@ -603,6 +608,12 @@ export default function Swap(): ReactElement { font-weight: 500; line-height: 32px; } + + .loading_wrapper { + min-height: 73.5px; + margin: 16px 0 32px; + } + .footer { display: flex; justify-content: center; From 3b9977ec6d09296288bef768fdac6e308f622228 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 21 Dec 2022 17:05:09 +0100 Subject: [PATCH 012/121] Untrusted assets warning improvements - add translations - fix styles --- ui/_locales/en/messages.json | 9 ++++++ .../AssetListItem/CommonAssetListItem.tsx | 32 +++++++++++-------- ui/components/Wallet/AssetListItem/styles.ts | 1 + ui/components/Wallet/AssetWarningSlideUp.tsx | 23 +++++++++---- ui/components/Wallet/WalletAssetListItem.tsx | 1 - 5 files changed, 44 insertions(+), 22 deletions(-) diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index db6aa7b493..3378031eec 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -578,6 +578,15 @@ "transactionFrom": "From:", "loadingActivities": "Digging deeper..." }, + "trustedAssets": { + "notTrusted": "Asset isn't trusted", + "notVerified": "Asset has not been verified yet!", + "trustExplainer": "Only transact with assets you trust.", + "assetImported": "Asset imported from transaction history", + "name": "Name", + "contract": "Contract address", + "close": "Close" + }, "banner": { "bannerTitle": "New Odyssey week!", "emptyBannerContent": "Check out Arbitrum Odyssey campaign", diff --git a/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx b/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx index 6b41ce4af9..7b0ff47c29 100644 --- a/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx +++ b/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx @@ -2,6 +2,7 @@ import React, { ReactElement } from "react" import { Link } from "react-router-dom" import { CompleteAssetAmount } from "@tallyho/tally-background/redux-slices/accounts" +import { useTranslation } from "react-i18next" import SharedLoadingSpinner from "../../Shared/SharedLoadingSpinner" import SharedAssetIcon from "../../Shared/SharedAssetIcon" import styles from "./styles" @@ -16,6 +17,9 @@ type CommonAssetListItemProps = { export default function CommonAssetListItem( props: CommonAssetListItemProps ): ReactElement { + const { t } = useTranslation("translation", { + keyPrefix: "wallet.trustedAssets", + }) const { assetAmount, initializationLoadingTimeExpired, @@ -55,20 +59,6 @@ export default function CommonAssetListItem( {assetAmount.localizedDecimalAmount} {assetAmount.asset.symbol} - {numTokenLists === 0 && !baseAsset && ( - - )}
      {initializationLoadingTimeExpired && isMissingLocalizedUserValue ? ( <> @@ -85,6 +75,20 @@ export default function CommonAssetListItem(
      <> + {numTokenLists === 0 && !baseAsset && ( + + )} - Asset has not been verified yet! + {t("notVerified")}
      - Only transact with assets you trust. + {t("trustExplainer")}
      • - Name + {t("name")}
        {`${asset?.name} (${asset?.symbol})`}
      • - Contract address + {t("contract")}
      - - Close + + {t("close")} ) diff --git a/ui/components/Wallet/WalletAssetListItem.tsx b/ui/components/Wallet/WalletAssetListItem.tsx index 6bd5c25939..c78549e395 100644 --- a/ui/components/Wallet/WalletAssetListItem.tsx +++ b/ui/components/Wallet/WalletAssetListItem.tsx @@ -69,7 +69,6 @@ export default function WalletAssetListItem(props: Props): ReactElement { width: 22px; height: 22px; margin-left: 6px; - margin-bottom: -6px; background-color: var(--trophy-gold); font-size: 0; } From 97c707ef31af2f41f631259ac96a72e5c2475450 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 21 Dec 2022 17:18:23 +0100 Subject: [PATCH 013/121] Fix wraping assets symbols text --- ui/components/Wallet/AssetListItem/styles.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/components/Wallet/AssetListItem/styles.ts b/ui/components/Wallet/AssetListItem/styles.ts index 87b233910c..3d2e1d989d 100644 --- a/ui/components/Wallet/AssetListItem/styles.ts +++ b/ui/components/Wallet/AssetListItem/styles.ts @@ -39,6 +39,7 @@ export default ` text-transform: uppercase; margin-bottom: 8px; margin-top: -1px; + overflow-wrap: anywhere; } .bold_amount_count { width: 70px; From 2eff66b3357ba6624cec2cbf95fa492925531c69 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Fri, 23 Dec 2022 14:08:32 +0100 Subject: [PATCH 014/121] Expand NFT collection as an overlay Proof of conceft - expanding NFT collection as an overlay above other items on the list. Direction of expanding depends on the side of the list given element occupies. --- ui/components/NFTS_update/NFTCollection.tsx | 107 +++++++++++--------- 1 file changed, 60 insertions(+), 47 deletions(-) diff --git a/ui/components/NFTS_update/NFTCollection.tsx b/ui/components/NFTS_update/NFTCollection.tsx index 363872dbb1..c58420d6ed 100644 --- a/ui/components/NFTS_update/NFTCollection.tsx +++ b/ui/components/NFTS_update/NFTCollection.tsx @@ -110,56 +110,65 @@ export default function NFTCollection(props: { return ( <> -
    • - +
    • - {nfts.length === 1 ? ( - onItemClick(nfts[0])} - /> - ) : ( - - )} - {isExpanded && ( - <> - {nfts.map((nft) => ( - - ))} - + {nfts.length === 1 ? ( + onItemClick(nfts[0])} /> -
      - - )} - -
    • + ) : ( + + )} + {isExpanded && ( + <> + {nfts.map((nft) => ( + + ))} + +
      + + )} + + +
      From a847e2a62cec66925e0fb2af68b7af8918807070 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Mon, 9 Jan 2023 13:18:17 +0100 Subject: [PATCH 044/121] Change some NFTs UI text --- ui/_locales/en/messages.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index a14c1f1933..f3be950388 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -264,7 +264,7 @@ "collection_other": "Collections" }, "header": { - "title": "Estimated total NFTs value", + "title": "Total floor price NFTs value", "addAccountCTA": "Add account", "emptyTitle": "No NFTs here", "emptyDesc": "Add more accounts to see your NFTs, you can also just add read-only accounts" @@ -276,8 +276,8 @@ "filters": { "title": "Filter collections", "sortType": { - "priceDesc": "Price: Descending", - "priceAsc": "Price: Ascending", + "priceDesc": "Floor price: Descending", + "priceAsc": "Floor price: Ascending", "newestAdded": "Newest added", "oldestAdded": "Oldest added", "numberInOneCollection": "Number (in 1 collection)" From 03f1a7545858150e6e019153be967c171248a4bd Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Mon, 9 Jan 2023 13:22:45 +0100 Subject: [PATCH 045/121] Sort collection filters list alphabetically --- .../redux-slices/selectors/nftsSelectors_update.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/background/redux-slices/selectors/nftsSelectors_update.ts b/background/redux-slices/selectors/nftsSelectors_update.ts index 7075c68cc2..dc18c0031d 100644 --- a/background/redux-slices/selectors/nftsSelectors_update.ts +++ b/background/redux-slices/selectors/nftsSelectors_update.ts @@ -60,12 +60,16 @@ export const selectEnrichedNFTFilters = createSelector( return [...acc] }, []) - const collections = filters.collections.filter(({ owners }) => { - const enablingAccount = (owners ?? []).find((owner) => - accounts.find((account) => account.id === owner && account.isEnabled) + const collections = filters.collections + .filter(({ owners }) => { + const enablingAccount = (owners ?? []).find((owner) => + accounts.find((account) => account.id === owner && account.isEnabled) + ) + return !!enablingAccount + }) + .sort((collection1, collection2) => + collection1.name.localeCompare(collection2.name) ) - return !!enablingAccount - }) return { ...filters, collections, accounts } } ) From a1103c84c90d6a30f5c7752192737049b65db777 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 9 Jan 2023 13:33:57 +0100 Subject: [PATCH 046/121] Init first simple tests for populate transaction nonce --- .../chain/tests/index.integration.test.ts | 68 +++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/background/services/chain/tests/index.integration.test.ts b/background/services/chain/tests/index.integration.test.ts index 4bb311ca6c..4f4a056209 100644 --- a/background/services/chain/tests/index.integration.test.ts +++ b/background/services/chain/tests/index.integration.test.ts @@ -23,6 +23,21 @@ type ChainServiceExternalized = Omit & { } } +const initProviderForNetworkOrThrow = ( + sandbox: sinon.SinonSandbox, + chainService: ChainServiceExternalized, + chainNonce: number +): void => { + const onceSpy = sandbox.spy() + sandbox.stub(chainService, "providerForNetworkOrThrow").callsFake( + () => + ({ + getTransactionCount: async () => chainNonce, + once: onceSpy, + } as unknown as SerialFallbackProvider) + ) +} + describe("ChainService", () => { const sandbox = sinon.createSandbox() let chainService: ChainService @@ -157,4 +172,57 @@ describe("ChainService", () => { ) ).toBeTruthy() }) + + describe("populateEVMTransactionNonce", () => { + const CHAIN_NONCE = 100 + + it("if nonce is not yet populated for transaction request on chain with mempool should populate nonce", async () => { + const chainServiceExternalized = + chainService as unknown as ChainServiceExternalized + initProviderForNetworkOrThrow( + sandbox, + chainServiceExternalized, + CHAIN_NONCE + ) + const transactionRequest = createLegacyTransactionRequest({ + network: ETHEREUM, + chainID: ETHEREUM.chainID, + nonce: undefined, + }) + + const transactionWithNonce = + await chainServiceExternalized.populateEVMTransactionNonce( + transactionRequest + ) + + expect(transactionWithNonce.nonce).toBe(CHAIN_NONCE) + expect( + chainServiceExternalized.evmChainLastSeenNoncesByNormalizedAddress[ + transactionRequest.chainID + ][transactionRequest.from] + ).toBe(CHAIN_NONCE) + }) + + it("if nonce is not yet populated for transaction request on chain without mempool should populate nonce", async () => { + const chainServiceExternalized = + chainService as unknown as ChainServiceExternalized + initProviderForNetworkOrThrow( + sandbox, + chainServiceExternalized, + CHAIN_NONCE + ) + const transactionRequest = createLegacyTransactionRequest({ + network: OPTIMISM, + chainID: OPTIMISM.chainID, + nonce: undefined, + }) + + const transactionWithNonce = + await chainServiceExternalized.populateEVMTransactionNonce( + transactionRequest + ) + + expect(transactionWithNonce.nonce).toBe(CHAIN_NONCE) + }) + }) }) From 6bb288e8b2f131aac3c259f02a59b8ff868d60f0 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 9 Jan 2023 13:46:14 +0100 Subject: [PATCH 047/121] Use jest mock functions for getting transaction count --- .../chain/tests/index.integration.test.ts | 34 +++++-------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/background/services/chain/tests/index.integration.test.ts b/background/services/chain/tests/index.integration.test.ts index 4f4a056209..2b50d79d8a 100644 --- a/background/services/chain/tests/index.integration.test.ts +++ b/background/services/chain/tests/index.integration.test.ts @@ -23,21 +23,6 @@ type ChainServiceExternalized = Omit & { } } -const initProviderForNetworkOrThrow = ( - sandbox: sinon.SinonSandbox, - chainService: ChainServiceExternalized, - chainNonce: number -): void => { - const onceSpy = sandbox.spy() - sandbox.stub(chainService, "providerForNetworkOrThrow").callsFake( - () => - ({ - getTransactionCount: async () => chainNonce, - once: onceSpy, - } as unknown as SerialFallbackProvider) - ) -} - describe("ChainService", () => { const sandbox = sinon.createSandbox() let chainService: ChainService @@ -176,14 +161,18 @@ describe("ChainService", () => { describe("populateEVMTransactionNonce", () => { const CHAIN_NONCE = 100 + beforeEach(() => { + chainService.providerForNetworkOrThrow = jest.fn( + () => + ({ + getTransactionCount: async () => CHAIN_NONCE, + } as unknown as SerialFallbackProvider) + ) + }) + it("if nonce is not yet populated for transaction request on chain with mempool should populate nonce", async () => { const chainServiceExternalized = chainService as unknown as ChainServiceExternalized - initProviderForNetworkOrThrow( - sandbox, - chainServiceExternalized, - CHAIN_NONCE - ) const transactionRequest = createLegacyTransactionRequest({ network: ETHEREUM, chainID: ETHEREUM.chainID, @@ -206,11 +195,6 @@ describe("ChainService", () => { it("if nonce is not yet populated for transaction request on chain without mempool should populate nonce", async () => { const chainServiceExternalized = chainService as unknown as ChainServiceExternalized - initProviderForNetworkOrThrow( - sandbox, - chainServiceExternalized, - CHAIN_NONCE - ) const transactionRequest = createLegacyTransactionRequest({ network: OPTIMISM, chainID: OPTIMISM.chainID, From 07d65f1cb2847e645d542d5e574f661468847518 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Mon, 9 Jan 2023 14:06:41 +0100 Subject: [PATCH 048/121] Add shadow to expanded NFTs collection --- ui/components/NFTS_update/NFTCollection.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ui/components/NFTS_update/NFTCollection.tsx b/ui/components/NFTS_update/NFTCollection.tsx index c58420d6ed..ca2c81e058 100644 --- a/ui/components/NFTS_update/NFTCollection.tsx +++ b/ui/components/NFTS_update/NFTCollection.tsx @@ -177,10 +177,12 @@ export default function NFTCollection(props: { } .nft_collection.expanded { width: 352px; - z-index: 10; + z-index: 3; margin: 8px -16px; padding: 8px 16px 6px; background: var(--green-120); + box-shadow: 0 3px 7px rgb(0 20 19 / 54%), + 0 14px 16px rgb(0 20 19 / 54%), 0 32px 32px rgb(0 20 19 / 20%); border-radius: 16px; display: flex; flex-wrap: wrap; From d8e27b6dd2f471c27d43685589d5babcb7151be7 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Mon, 9 Jan 2023 14:38:47 +0100 Subject: [PATCH 049/121] Allow only one collection to be expanded --- ui/components/NFTS_update/NFTCollection.tsx | 7 ++++--- ui/components/NFTS_update/NFTList.tsx | 11 ++++++++++- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/ui/components/NFTS_update/NFTCollection.tsx b/ui/components/NFTS_update/NFTCollection.tsx index ca2c81e058..1028467cc4 100644 --- a/ui/components/NFTS_update/NFTCollection.tsx +++ b/ui/components/NFTS_update/NFTCollection.tsx @@ -13,13 +13,14 @@ import SharedSkeletonLoader from "../Shared/SharedSkeletonLoader" export default function NFTCollection(props: { collection: NFTCollectionCached + isExpanded: boolean + setExpandedID: (id: string | null) => void openPreview: (current: NFTWithCollection) => void }): ReactElement { - const { collection, openPreview } = props + const { collection, openPreview, isExpanded, setExpandedID } = props const { id, owner, network, nfts, nftCount, hasNextPage } = collection const dispatch = useBackgroundDispatch() - const [isExpanded, setIsExpanded] = useState(false) const [isLoading, setIsLoading] = useState(false) // initial update of collection const [isUpdating, setIsUpdating] = useState(false) // update on already loaded collection const [wasUpdated, setWasUpdated] = useState(false) // to fetch NFTs data only once during the component lifespan @@ -104,7 +105,7 @@ export default function NFTCollection(props: { } }, [fetchCollection, isExpanded, wasUpdated]) - const toggleCollection = () => setIsExpanded((val) => !val) + const toggleCollection = () => setExpandedID(isExpanded ? null : id) const onItemClick = (nft: NFT) => openPreview({ nft, collection }) diff --git a/ui/components/NFTS_update/NFTList.tsx b/ui/components/NFTS_update/NFTList.tsx index 7bbbab72d0..dcff2718be 100644 --- a/ui/components/NFTS_update/NFTList.tsx +++ b/ui/components/NFTS_update/NFTList.tsx @@ -3,7 +3,7 @@ import { NFTWithCollection, } from "@tallyho/tally-background/redux-slices/nfts_update" import { selectIsReloadingNFTs } from "@tallyho/tally-background/redux-slices/selectors" -import React, { ReactElement, useState } from "react" +import React, { ReactElement, useCallback, useState } from "react" import { useBackgroundSelector } from "../../hooks" import SharedLoadingDoggo from "../Shared/SharedLoadingDoggo" import SharedSlideUpMenu from "../Shared/SharedSlideUpMenu" @@ -19,6 +19,13 @@ export default function NFTList(props: { const [isPreviewOpen, setIsPreviewOpen] = useState(false) const [currentNFTPreview, setCurrentNFTPreview] = useState(null) + const [currentExpandedID, setCurrentExpandedID] = useState( + null + ) + const setExpandedID = useCallback( + (id: string | null) => setCurrentExpandedID(id), + [] + ) const isReloading = useBackgroundSelector(selectIsReloadingNFTs) @@ -60,6 +67,8 @@ export default function NFTList(props: { key={`${collection.id}_${collection.owner}`} openPreview={openPreview} collection={collection} + setExpandedID={setExpandedID} + isExpanded={collection.id === currentExpandedID} /> ) )} From c1409987a05e6ce0409ba69d1467af5774f090f9 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Mon, 9 Jan 2023 14:44:16 +0100 Subject: [PATCH 050/121] Fix snackbar blocking content behind it --- ui/components/Snackbar/Snackbar.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/ui/components/Snackbar/Snackbar.tsx b/ui/components/Snackbar/Snackbar.tsx index 45f45e11c7..601b1d0afe 100644 --- a/ui/components/Snackbar/Snackbar.tsx +++ b/ui/components/Snackbar/Snackbar.tsx @@ -52,10 +52,8 @@ export default function Snackbar(): ReactElement { }, [clearSnackbarTimeout, dispatch]) return ( -
      -
      - {displayMessage} -
      +
      +
      {displayMessage}
      + ) } From c8325e4e09e66eeb3bc6af2cf626dcde8df5496c Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 9 Jan 2023 16:54:33 +0100 Subject: [PATCH 052/121] Add transactions nonce integration tests --- .../chain/tests/index.integration.test.ts | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/background/services/chain/tests/index.integration.test.ts b/background/services/chain/tests/index.integration.test.ts index 2b50d79d8a..e99d433123 100644 --- a/background/services/chain/tests/index.integration.test.ts +++ b/background/services/chain/tests/index.integration.test.ts @@ -159,13 +159,16 @@ describe("ChainService", () => { }) describe("populateEVMTransactionNonce", () => { - const CHAIN_NONCE = 100 + // The number of transactions address has ever sent + const TRANSACTION_COUNT = 100 + // Next correct nonce for chain. This should be set to the number of transactions ever sent from this address + const CHAIN_NONCE = TRANSACTION_COUNT beforeEach(() => { chainService.providerForNetworkOrThrow = jest.fn( () => ({ - getTransactionCount: async () => CHAIN_NONCE, + getTransactionCount: async () => TRANSACTION_COUNT, } as unknown as SerialFallbackProvider) ) }) @@ -192,6 +195,23 @@ describe("ChainService", () => { ).toBe(CHAIN_NONCE) }) + it("if nonce is populated for transaction request on chain with mempool should not populate nonce", async () => { + const chainServiceExternalized = + chainService as unknown as ChainServiceExternalized + const transactionRequest = createLegacyTransactionRequest({ + network: ETHEREUM, + chainID: ETHEREUM.chainID, + nonce: CHAIN_NONCE, + }) + + const transactionWithNonce = + await chainServiceExternalized.populateEVMTransactionNonce( + transactionRequest + ) + + expect(transactionWithNonce.nonce).toBe(CHAIN_NONCE) + }) + it("if nonce is not yet populated for transaction request on chain without mempool should populate nonce", async () => { const chainServiceExternalized = chainService as unknown as ChainServiceExternalized @@ -208,5 +228,56 @@ describe("ChainService", () => { expect(transactionWithNonce.nonce).toBe(CHAIN_NONCE) }) + + it("if nonce is populated for transaction request on chain without mempool should not populate nonce", async () => { + const chainServiceExternalized = + chainService as unknown as ChainServiceExternalized + const transactionRequest = createLegacyTransactionRequest({ + network: ETHEREUM, + chainID: ETHEREUM.chainID, + nonce: CHAIN_NONCE, + }) + + const transactionWithNonce = + await chainServiceExternalized.populateEVMTransactionNonce( + transactionRequest + ) + + expect(transactionWithNonce.nonce).toBe(CHAIN_NONCE) + }) + }) + + describe("releaseEVMTransactionNonce", () => { + const LAST_SEEN_NONCE = 12 + const WRONG_NONCE = 11 + const NONCE = 10 + + it("if the nonce for transaction is below the latest allocated nonce should release all intervening nonces", async () => { + const chainServiceExternalized = + chainService as unknown as ChainServiceExternalized + const transactionRequest = createLegacyTransactionRequest({ + network: ETHEREUM, + chainID: ETHEREUM.chainID, + nonce: WRONG_NONCE, + }) as TransactionRequestWithNonce + const { chainID, from } = transactionRequest + + chainServiceExternalized.evmChainLastSeenNoncesByNormalizedAddress[ + chainID + ] ??= {} + chainServiceExternalized.evmChainLastSeenNoncesByNormalizedAddress[ + chainID + ][from] = LAST_SEEN_NONCE + + await chainServiceExternalized.releaseEVMTransactionNonce( + transactionRequest + ) + + expect( + chainServiceExternalized.evmChainLastSeenNoncesByNormalizedAddress[ + chainID + ][from] + ).toBe(NONCE) + }) }) }) From 861f6a38c04fb034ea0008990e5e5f70d352ab7c Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 10 Jan 2023 08:53:53 +0100 Subject: [PATCH 053/121] Improvements for transactions nonce integration tests --- .../chain/tests/index.integration.test.ts | 69 +++++++++++++++++-- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/background/services/chain/tests/index.integration.test.ts b/background/services/chain/tests/index.integration.test.ts index e99d433123..2732c2124e 100644 --- a/background/services/chain/tests/index.integration.test.ts +++ b/background/services/chain/tests/index.integration.test.ts @@ -161,7 +161,7 @@ describe("ChainService", () => { describe("populateEVMTransactionNonce", () => { // The number of transactions address has ever sent const TRANSACTION_COUNT = 100 - // Next correct nonce for chain. This should be set to the number of transactions ever sent from this address + // Nonce for chain. This should be set to the number of transactions ever sent from this address const CHAIN_NONCE = TRANSACTION_COUNT beforeEach(() => { @@ -209,6 +209,11 @@ describe("ChainService", () => { transactionRequest ) + expect( + chainServiceExternalized.evmChainLastSeenNoncesByNormalizedAddress[ + transactionRequest.chainID + ] + ).toBe(undefined) expect(transactionWithNonce.nonce).toBe(CHAIN_NONCE) }) @@ -227,6 +232,11 @@ describe("ChainService", () => { ) expect(transactionWithNonce.nonce).toBe(CHAIN_NONCE) + expect( + chainServiceExternalized.evmChainLastSeenNoncesByNormalizedAddress[ + transactionRequest.chainID + ] + ).toBe(undefined) }) it("if nonce is populated for transaction request on chain without mempool should not populate nonce", async () => { @@ -244,21 +254,34 @@ describe("ChainService", () => { ) expect(transactionWithNonce.nonce).toBe(CHAIN_NONCE) + expect( + chainServiceExternalized.evmChainLastSeenNoncesByNormalizedAddress[ + transactionRequest.chainID + ] + ).toBe(undefined) }) }) describe("releaseEVMTransactionNonce", () => { - const LAST_SEEN_NONCE = 12 - const WRONG_NONCE = 11 - const NONCE = 10 - it("if the nonce for transaction is below the latest allocated nonce should release all intervening nonces", async () => { + /** + * Two transactions have been sent: one approving (nonce=11) the other for the swapping (nonce=12). + * In case transaction for nonce 11 will has too small gas we should release all intervening nonces. + * Nonce for the chain is then 10. Last seen nonce should also be set to this value. + */ + // Actual Swap transaction + const LAST_SEEN_NONCE = 12 + // Approval transaction + const NONCE = 11 + // Nonce for chain + const CHAIN_NONCE = 10 + const chainServiceExternalized = chainService as unknown as ChainServiceExternalized const transactionRequest = createLegacyTransactionRequest({ network: ETHEREUM, chainID: ETHEREUM.chainID, - nonce: WRONG_NONCE, + nonce: NONCE, }) as TransactionRequestWithNonce const { chainID, from } = transactionRequest @@ -277,7 +300,39 @@ describe("ChainService", () => { chainServiceExternalized.evmChainLastSeenNoncesByNormalizedAddress[ chainID ][from] - ).toBe(NONCE) + ).toBe(CHAIN_NONCE) + }) + + it("if the nonce for transaction is equal to the value of the latest allocated nonce should release all intervening nonces", async () => { + const LAST_SEEN_NONCE = 11 + const NONCE = LAST_SEEN_NONCE + const CHAIN_NONCE = 10 + + const chainServiceExternalized = + chainService as unknown as ChainServiceExternalized + const transactionRequest = createLegacyTransactionRequest({ + network: ETHEREUM, + chainID: ETHEREUM.chainID, + nonce: NONCE, + }) as TransactionRequestWithNonce + const { chainID, from } = transactionRequest + + chainServiceExternalized.evmChainLastSeenNoncesByNormalizedAddress[ + chainID + ] ??= {} + chainServiceExternalized.evmChainLastSeenNoncesByNormalizedAddress[ + chainID + ][from] = LAST_SEEN_NONCE + + await chainServiceExternalized.releaseEVMTransactionNonce( + transactionRequest + ) + + expect( + chainServiceExternalized.evmChainLastSeenNoncesByNormalizedAddress[ + chainID + ][from] + ).toBe(CHAIN_NONCE) }) }) }) From d576f48f9f4df1fd2223b91709b794ad5cdcf3d4 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 10 Jan 2023 09:20:15 +0100 Subject: [PATCH 054/121] Fix the nonce release logic --- background/services/chain/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index 567ae4916c..972701746b 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -794,7 +794,7 @@ export default class ChainService extends BaseService { // now-released-and-therefore-never-broadcast nonce). this.evmChainLastSeenNoncesByNormalizedAddress[chainID][ normalizedAddress - ] = lastSeenNonce - 1 + ] = nonce - 1 } } } From d503a58ab667f732e7a44bfc12d73cb23aed3f01 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 10 Jan 2023 10:03:29 +0100 Subject: [PATCH 055/121] Fix issue for transactions nonce test case --- background/services/chain/tests/index.integration.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/background/services/chain/tests/index.integration.test.ts b/background/services/chain/tests/index.integration.test.ts index 2732c2124e..8540fd586f 100644 --- a/background/services/chain/tests/index.integration.test.ts +++ b/background/services/chain/tests/index.integration.test.ts @@ -243,8 +243,8 @@ describe("ChainService", () => { const chainServiceExternalized = chainService as unknown as ChainServiceExternalized const transactionRequest = createLegacyTransactionRequest({ - network: ETHEREUM, - chainID: ETHEREUM.chainID, + network: OPTIMISM, + chainID: OPTIMISM.chainID, nonce: CHAIN_NONCE, }) From ecf9585f93f900c8d5fabb1ef73a12b3f3a79042 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Tue, 10 Jan 2023 15:41:28 +0100 Subject: [PATCH 056/121] Sort NFTs within one collection according to selected order --- background/redux-slices/utils/nfts_update.ts | 36 ++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/background/redux-slices/utils/nfts_update.ts b/background/redux-slices/utils/nfts_update.ts index 2f778d7cd0..66e78cf9cb 100644 --- a/background/redux-slices/utils/nfts_update.ts +++ b/background/redux-slices/utils/nfts_update.ts @@ -1,3 +1,4 @@ +import { NFT } from "../../nfts" import { Filter, FiltersState, @@ -75,13 +76,23 @@ const sortByDate = ( return transferDate1 > transferDate2 ? 1 : -1 } +const sortNFTsByDate = (type: "new" | "old", nfts: NFT[]): NFT[] => { + const sorted = nfts.sort( + (nft1, nft2) => + new Date(nft2.transferDate ?? "").getTime() - + new Date(nft1.transferDate ?? "").getTime() + ) + + return type === "new" ? sorted : sorted.reverse() +} + const sortByNFTCount = ( collection1: NFTCollectionCached, collection2: NFTCollectionCached ): number => (Number(collection2?.nftCount) || 0) - (Number(collection1?.nftCount) || 0) -export const sortNFTS = ( +export const sortCollections = ( collection1: NFTCollectionCached, collection2: NFTCollectionCached, type: SortType @@ -102,6 +113,26 @@ export const sortNFTS = ( } } +const sortNFTs = ( + collection: NFTCollectionCached, + type: SortType +): NFTCollectionCached => { + switch (type) { + case "new": + return { + ...collection, + nfts: sortNFTsByDate("new", collection.nfts), + } + case "old": + return { + ...collection, + nfts: sortNFTsByDate("old", collection.nfts), + } + default: + return collection + } +} + export const getTotalFloorPriceInETH = ( collections: NFTCollectionCached[] ): number => @@ -127,5 +158,6 @@ export const getFilteredCollections = ( isEnabledFilter(collection.owner, filters.accounts) ) .sort((collection1, collection2) => - sortNFTS(collection1, collection2, filters.type) + sortCollections(collection1, collection2, filters.type) ) + .map((collection) => sortNFTs(collection, filters.type)) From c381bd83d16f021dc0b96bfe36fecb5f4f17c190 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 11 Jan 2023 10:08:19 +0100 Subject: [PATCH 057/121] Add a chainID to the base asset --- background/constants/base-assets.ts | 42 ++++++++++++++++++++++++++++- background/networks.ts | 1 + background/services/chain/db.ts | 2 +- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/background/constants/base-assets.ts b/background/constants/base-assets.ts index d3d52bf51e..07313e6cb6 100644 --- a/background/constants/base-assets.ts +++ b/background/constants/base-assets.ts @@ -1,42 +1,82 @@ import { NetworkBaseAsset } from "../networks" const ETH: NetworkBaseAsset = { + chainID: "1", name: "Ether", symbol: "ETH", decimals: 18, } +const ARBITRUM_ONE_ETH: NetworkBaseAsset = { + ...ETH, + chainID: "42161", +} + +const ARBITRUM_NOVA_ETH: NetworkBaseAsset = { + ...ETH, + chainID: "42170", +} + +const OPTIMISM_ETH: NetworkBaseAsset = { + ...ETH, + chainID: "10", +} + +const GOERLI_ETH: NetworkBaseAsset = { + ...ETH, + chainID: "5", +} + const RBTC: NetworkBaseAsset = { + chainID: "30", name: "RSK Token", symbol: "RBTC", decimals: 18, } const MATIC: NetworkBaseAsset = { + chainID: "137", name: "Matic Token", symbol: "MATIC", decimals: 18, } const AVAX: NetworkBaseAsset = { + chainID: "43114", name: "Avalanche", symbol: "AVAX", decimals: 18, } const BNB: NetworkBaseAsset = { + chainID: "56", name: "Binance Coin", symbol: "BNB", decimals: 18, } const BTC: NetworkBaseAsset = { + /** + * To persist base asset to indexDB chainID must be declared. + */ + chainID: "", name: "Bitcoin", symbol: "BTC", decimals: 8, } -export const BASE_ASSETS = [ETH, BTC, MATIC, RBTC, AVAX, BNB] +export const BASE_ASSETS = [ + ETH, + BTC, + MATIC, + RBTC, + AVAX, + BNB, + ARBITRUM_ONE_ETH, + ARBITRUM_NOVA_ETH, + OPTIMISM_ETH, + GOERLI_ETH, +] export const BASE_ASSETS_BY_SYMBOL = BASE_ASSETS.reduce<{ [assetSymbol: string]: NetworkBaseAsset diff --git a/background/networks.ts b/background/networks.ts index b339af29ba..675eca1b37 100644 --- a/background/networks.ts +++ b/background/networks.ts @@ -22,6 +22,7 @@ export type NetworkBaseAsset = FungibleAsset & CoinGeckoAsset & { contractAddress?: string coinType?: Slip44CoinType + chainID: string } /** diff --git a/background/services/chain/db.ts b/background/services/chain/db.ts index 8c805ce554..894bf56649 100644 --- a/background/services/chain/db.ts +++ b/background/services/chain/db.ts @@ -151,7 +151,7 @@ export class ChainDatabase extends Dexie { }) this.version(6).stores({ - baseAssets: "&symbol,name", + baseAssets: "&chainID,symbol,name", }) } From fa135cca1b5720d9a01ec8b3992ea8879edfd520 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 11 Jan 2023 11:25:51 +0100 Subject: [PATCH 058/121] Refactor NFTs collection sort to use existing NFTs sort --- background/redux-slices/utils/nfts_update.ts | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/background/redux-slices/utils/nfts_update.ts b/background/redux-slices/utils/nfts_update.ts index 66e78cf9cb..c77fadc2df 100644 --- a/background/redux-slices/utils/nfts_update.ts +++ b/background/redux-slices/utils/nfts_update.ts @@ -55,19 +55,13 @@ const sortByDate = ( collection1: NFTCollectionCached, collection2: NFTCollectionCached ): number => { - const dates1 = collection1.nfts.map(({ transferDate }) => - new Date(transferDate || "").getTime() - ) - const dates2 = collection2.nfts.map(({ transferDate }) => - new Date(transferDate || "").getTime() - ) - + // NFTs are already sorted with current sort type const transferDate1 = new Date( - type === "new" ? Math.max(...dates1) : Math.min(...dates1) - ) + collection1.nfts[0]?.transferDate ?? "" + ).getTime() const transferDate2 = new Date( - type === "new" ? Math.max(...dates2) : Math.min(...dates2) - ) + collection2.nfts[0]?.transferDate ?? "" + ).getTime() if (type === "new") { return transferDate1 > transferDate2 ? -1 : 1 @@ -157,7 +151,7 @@ export const getFilteredCollections = ( isEnabledFilter(collection.id, filters.collections) && isEnabledFilter(collection.owner, filters.accounts) ) + .map((collection) => sortNFTs(collection, filters.type)) .sort((collection1, collection2) => sortCollections(collection1, collection2, filters.type) ) - .map((collection) => sortNFTs(collection, filters.type)) From cfdfcb146114ab31f4b48335182262dcbb383498 Mon Sep 17 00:00:00 2001 From: Gergo Nagy Date: Wed, 11 Jan 2023 11:54:15 +0100 Subject: [PATCH 059/121] fix the feature flag check for daylight menu --- ui/pages/Overview.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/pages/Overview.tsx b/ui/pages/Overview.tsx index c544d37e60..1ee8738d0f 100644 --- a/ui/pages/Overview.tsx +++ b/ui/pages/Overview.tsx @@ -51,7 +51,7 @@ function Overview(): ReactElement { balance={balance} initializationTimeExpired={initializationLoadingTimeExpired} /> - {FeatureFlags.SUPPORT_ABILITIES ? : null} + {isEnabled(FeatureFlags.SUPPORT_ABILITIES) ? : null} - {FeatureFlags.SUPPORT_ABILITIES ? : null} + {isEnabled(FeatureFlags.SUPPORT_ABILITIES) ? : null} Date: Wed, 11 Jan 2023 13:58:46 +0100 Subject: [PATCH 060/121] remove unused node-fetch --- background/package.json | 1 - yarn.lock | 5 ----- 2 files changed, 6 deletions(-) diff --git a/background/package.json b/background/package.json index cb6fefb5c5..16f4512e89 100644 --- a/background/package.json +++ b/background/package.json @@ -56,7 +56,6 @@ "emittery": "^0.9.2", "ethers": "^5.5.1", "lodash": "^4.17.21", - "node-fetch": "^2.6.1", "sinon": "^14.0.0", "siwe": "^1.1.0", "util": "^0.12.4", diff --git a/yarn.lock b/yarn.lock index bf881af529..ad5c8b6fbf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10406,11 +10406,6 @@ node-addon-api@^4.2.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.3.0.tgz#52a1a0b475193e0928e98e0426a0d1254782b77f" integrity sha512-73sE9+3UaLYYFmDsFZnqCInzPyh3MqIwZO9cw58yIqAZhONrrabrYyYe3TuIqtIiOuTXVhsGau8hcrhhwSsDIQ== -node-fetch@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== - node-fetch@^2.6.7: version "2.6.7" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" From 609e2cb2a26b40ba323d3ea448ccd76196867be2 Mon Sep 17 00:00:00 2001 From: Gergo Nagy Date: Wed, 11 Jan 2023 11:54:15 +0100 Subject: [PATCH 061/121] fix the feature flag check for daylight menu --- ui/pages/Overview.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/pages/Overview.tsx b/ui/pages/Overview.tsx index c544d37e60..1ee8738d0f 100644 --- a/ui/pages/Overview.tsx +++ b/ui/pages/Overview.tsx @@ -51,7 +51,7 @@ function Overview(): ReactElement { balance={balance} initializationTimeExpired={initializationLoadingTimeExpired} /> - {FeatureFlags.SUPPORT_ABILITIES ? : null} + {isEnabled(FeatureFlags.SUPPORT_ABILITIES) ? : null} - {FeatureFlags.SUPPORT_ABILITIES ? : null} + {isEnabled(FeatureFlags.SUPPORT_ABILITIES) ? : null} Date: Tue, 10 Jan 2023 09:20:15 +0100 Subject: [PATCH 062/121] Fix the nonce release logic --- background/services/chain/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index ce4bfdf47e..bcee89f12b 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -809,7 +809,7 @@ export default class ChainService extends BaseService { // now-released-and-therefore-never-broadcast nonce). this.evmChainLastSeenNoncesByNormalizedAddress[chainID][ normalizedAddress - ] = lastSeenNonce - 1 + ] = nonce - 1 } } } From 619b126b6ae6d31d8a9f8559ec5fe9d8a2699c61 Mon Sep 17 00:00:00 2001 From: Gergo Nagy Date: Wed, 11 Jan 2023 14:19:22 +0100 Subject: [PATCH 063/121] v0.18.9 --- .github/ISSUE_TEMPLATE/BUG.yml | 1 + manifest/manifest.json | 2 +- package.json | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/BUG.yml b/.github/ISSUE_TEMPLATE/BUG.yml index 09e65546d7..21ac20af9e 100644 --- a/.github/ISSUE_TEMPLATE/BUG.yml +++ b/.github/ISSUE_TEMPLATE/BUG.yml @@ -51,6 +51,7 @@ body: label: Version description: What version of the extension are you running? options: + - v0.18.9 - v0.18.8 - v0.18.7 - v0.18.6 diff --git a/manifest/manifest.json b/manifest/manifest.json index 8719a54623..089d6de4d1 100644 --- a/manifest/manifest.json +++ b/manifest/manifest.json @@ -1,6 +1,6 @@ { "name": "Tally Ho", - "version": "0.18.8", + "version": "0.18.9", "description": "The community owned and operated Web3 wallet.", "homepage_url": "https://tally.cash", "author": "https://tally.cash", diff --git a/package.json b/package.json index 3bccc388b1..955ced22a2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@tallyho/tally-extension", "private": true, - "version": "0.18.8", + "version": "0.18.9", "description": "Tally Ho, the community owned and operated Web3 wallet.", "main": "index.js", "repository": "git@github.com:thesis/tally-extension.git", From 1b5c25d2e7e5de9e85e39156b9594b16da1e1ded Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 12 Jan 2023 09:22:49 +0100 Subject: [PATCH 064/121] Create separate base assets with chainID --- background/constants/base-assets.ts | 18 ++---- background/constants/currencies.ts | 58 ++++++++++--------- background/constants/networks.ts | 19 ++++-- background/lib/prices.ts | 9 ++- background/redux-slices/assets.ts | 14 ++--- .../selectors/accountsSelectors.ts | 21 +++---- background/redux-slices/utils/asset-utils.ts | 17 +++++- 7 files changed, 92 insertions(+), 64 deletions(-) diff --git a/background/constants/base-assets.ts b/background/constants/base-assets.ts index 07313e6cb6..4f68250f95 100644 --- a/background/constants/base-assets.ts +++ b/background/constants/base-assets.ts @@ -17,7 +17,7 @@ const ARBITRUM_NOVA_ETH: NetworkBaseAsset = { chainID: "42170", } -const OPTIMISM_ETH: NetworkBaseAsset = { +const OPTIMISTIC_ETH: NetworkBaseAsset = { ...ETH, chainID: "10", } @@ -65,7 +65,7 @@ const BTC: NetworkBaseAsset = { decimals: 8, } -export const BASE_ASSETS = [ +export const BASE_ASSETS_BY_CUSTOM_NAME = { ETH, BTC, MATIC, @@ -74,16 +74,8 @@ export const BASE_ASSETS = [ BNB, ARBITRUM_ONE_ETH, ARBITRUM_NOVA_ETH, - OPTIMISM_ETH, + OPTIMISTIC_ETH, GOERLI_ETH, -] +} -export const BASE_ASSETS_BY_SYMBOL = BASE_ASSETS.reduce<{ - [assetSymbol: string]: NetworkBaseAsset -}>((acc, asset) => { - const newAcc = { - ...acc, - } - newAcc[asset.symbol] = asset - return newAcc -}, {}) +export const BASE_ASSETS = Object.values(BASE_ASSETS_BY_CUSTOM_NAME) diff --git a/background/constants/currencies.ts b/background/constants/currencies.ts index 71491e047d..e809fd2f48 100644 --- a/background/constants/currencies.ts +++ b/background/constants/currencies.ts @@ -1,6 +1,6 @@ import { CoinGeckoAsset, FiatCurrency } from "../assets" import { NetworkBaseAsset } from "../networks" -import { BASE_ASSETS_BY_SYMBOL } from "./base-assets" +import { BASE_ASSETS_BY_CUSTOM_NAME } from "./base-assets" import { coinTypesByAssetSymbol } from "./coin-types" export const USD: FiatCurrency = { @@ -14,8 +14,7 @@ export const FIAT_CURRENCIES_SYMBOL = FIAT_CURRENCIES.map( (currency) => currency.symbol ) -export const ETH: NetworkBaseAsset & Required = { - ...BASE_ASSETS_BY_SYMBOL.ETH, +export const ETH_DATA = { coinType: coinTypesByAssetSymbol.ETH, metadata: { coinGeckoID: "ethereum", @@ -24,8 +23,13 @@ export const ETH: NetworkBaseAsset & Required = { }, } +export const ETH: NetworkBaseAsset & Required = { + ...BASE_ASSETS_BY_CUSTOM_NAME.ETH, + ...ETH_DATA, +} + export const RBTC: NetworkBaseAsset & Required = { - ...BASE_ASSETS_BY_SYMBOL.RBTC, + ...BASE_ASSETS_BY_CUSTOM_NAME.RBTC, coinType: coinTypesByAssetSymbol.RBTC, metadata: { coinGeckoID: "rootstock", @@ -35,18 +39,28 @@ export const RBTC: NetworkBaseAsset & Required = { } export const OPTIMISTIC_ETH: NetworkBaseAsset & Required = { - ...BASE_ASSETS_BY_SYMBOL.ETH, - coinType: coinTypesByAssetSymbol.ETH, + ...BASE_ASSETS_BY_CUSTOM_NAME.OPTIMISTIC_ETH, + ...ETH_DATA, contractAddress: "0xdeaddeaddeaddeaddeaddeaddeaddeaddead0000", - metadata: { - coinGeckoID: "ethereum", - tokenLists: [], - websiteURL: "https://ethereum.org", - }, +} + +export const ARBITRUM_ONE_ETH: NetworkBaseAsset & Required = { + ...BASE_ASSETS_BY_CUSTOM_NAME.ARBITRUM_ONE_ETH, + ...ETH_DATA, +} + +export const ARBITRUM_NOVA_ETH: NetworkBaseAsset & Required = { + ...BASE_ASSETS_BY_CUSTOM_NAME.ARBITRUM_NOVA_ETH, + ...ETH_DATA, +} + +export const GOERLI_ETH: NetworkBaseAsset & Required = { + ...BASE_ASSETS_BY_CUSTOM_NAME.GOERLI_ETH, + ...ETH_DATA, } export const MATIC: NetworkBaseAsset & Required = { - ...BASE_ASSETS_BY_SYMBOL.MATIC, + ...BASE_ASSETS_BY_CUSTOM_NAME.MATIC, coinType: coinTypesByAssetSymbol.MATIC, contractAddress: "0x0000000000000000000000000000000000001010", metadata: { @@ -57,7 +71,7 @@ export const MATIC: NetworkBaseAsset & Required = { } export const AVAX: NetworkBaseAsset & Required = { - ...BASE_ASSETS_BY_SYMBOL.AVAX, + ...BASE_ASSETS_BY_CUSTOM_NAME.AVAX, coinType: coinTypesByAssetSymbol.AVAX, metadata: { coinGeckoID: "avalanche-2", @@ -67,7 +81,7 @@ export const AVAX: NetworkBaseAsset & Required = { } export const BNB: NetworkBaseAsset & Required = { - ...BASE_ASSETS_BY_SYMBOL.BNB, + ...BASE_ASSETS_BY_CUSTOM_NAME.BNB, coinType: coinTypesByAssetSymbol.BNB, metadata: { coinGeckoID: "binancecoin", @@ -77,7 +91,7 @@ export const BNB: NetworkBaseAsset & Required = { } export const BTC: NetworkBaseAsset & Required = { - ...BASE_ASSETS_BY_SYMBOL.BTC, + ...BASE_ASSETS_BY_CUSTOM_NAME.BTC, coinType: coinTypesByAssetSymbol.BTC, metadata: { coinGeckoID: "bitcoin", @@ -92,17 +106,9 @@ export const BUILT_IN_NETWORK_BASE_ASSETS = [ MATIC, RBTC, OPTIMISTIC_ETH, + ARBITRUM_ONE_ETH, + ARBITRUM_NOVA_ETH, + GOERLI_ETH, AVAX, BNB, ] - -export const BUILT_IN_NETWORK_BASE_ASSETS_BY_SYMBOL = - BUILT_IN_NETWORK_BASE_ASSETS.reduce<{ - [assetSymbol: string]: NetworkBaseAsset & Required - }>((acc, asset) => { - const newAcc = { - ...acc, - } - newAcc[asset.symbol] = asset - return newAcc - }, {}) diff --git a/background/constants/networks.ts b/background/constants/networks.ts index bb3a1b7926..621afa97c6 100644 --- a/background/constants/networks.ts +++ b/background/constants/networks.ts @@ -1,6 +1,17 @@ import { FeatureFlags, isEnabled } from "../features" import { EVMNetwork, Network } from "../networks" -import { AVAX, BNB, BTC, ETH, MATIC, OPTIMISTIC_ETH, RBTC } from "./currencies" +import { + ARBITRUM_NOVA_ETH, + ARBITRUM_ONE_ETH, + AVAX, + BNB, + BTC, + ETH, + GOERLI_ETH, + MATIC, + OPTIMISTIC_ETH, + RBTC, +} from "./currencies" export const ETHEREUM: EVMNetwork = { name: "Ethereum", @@ -28,7 +39,7 @@ export const POLYGON: EVMNetwork = { export const ARBITRUM_ONE: EVMNetwork = { name: "Arbitrum", - baseAsset: ETH, + baseAsset: ARBITRUM_ONE_ETH, chainID: "42161", family: "EVM", coingeckoPlatformID: "arbitrum-one", @@ -52,7 +63,7 @@ export const BINANCE_SMART_CHAIN: EVMNetwork = { export const ARBITRUM_NOVA: EVMNetwork = { name: "Arbitrum Nova", - baseAsset: ETH, + baseAsset: ARBITRUM_NOVA_ETH, chainID: "42170", family: "EVM", coingeckoPlatformID: "arbitrum-nova", @@ -68,7 +79,7 @@ export const OPTIMISM: EVMNetwork = { export const GOERLI: EVMNetwork = { name: "Goerli", - baseAsset: ETH, + baseAsset: GOERLI_ETH, chainID: "5", family: "EVM", coingeckoPlatformID: "ethereum", diff --git a/background/lib/prices.ts b/background/lib/prices.ts index c8fa1d351c..8a7bc04662 100644 --- a/background/lib/prices.ts +++ b/background/lib/prices.ts @@ -21,7 +21,14 @@ export async function getPrices( assets: (AnyAsset & Required)[], vsCurrencies: FiatCurrency[] ): Promise { - const coinIds = assets.map((a) => a.metadata.coinGeckoID).join(",") + const coinIds = assets + .reduce((ids, asset) => { + if (ids.some((id) => id === asset.metadata.coinGeckoID)) { + return ids + } + return [...ids, asset.metadata.coinGeckoID] + }, []) + .join(",") const currencySymbols = vsCurrencies .map((c) => c.symbol.toLowerCase()) diff --git a/background/redux-slices/assets.ts b/background/redux-slices/assets.ts index 1f3c337ec7..9186c87f21 100644 --- a/background/redux-slices/assets.ts +++ b/background/redux-slices/assets.ts @@ -12,15 +12,15 @@ import { AddressOnNetwork } from "../accounts" import { findClosestAssetIndex } from "../lib/asset-similarity" import { normalizeEVMAddress } from "../lib/utils" import { createBackgroundAsyncThunk } from "./utils" -import { isNetworkBaseAssetWithCoinType } from "./utils/asset-utils" +import { + existsInBaseAssets, + isNetworkBaseAssetWithCoinType, +} from "./utils/asset-utils" import { getProvider } from "./utils/contract-utils" import { sameNetwork } from "../networks" import { ERC20_INTERFACE } from "../lib/erc20" import logger from "../lib/logger" -import { - BUILT_IN_NETWORK_BASE_ASSETS_BY_SYMBOL, - FIAT_CURRENCIES_SYMBOL, -} from "../constants" +import { FIAT_CURRENCIES_SYMBOL } from "../constants" import { convertFixedPoint } from "../lib/fixed-point" export type AssetWithRecentPrices = T & { @@ -69,8 +69,8 @@ const assetsSlice = createSlice({ normalizeEVMAddress(asset.contractAddress)) || // Only match base assets by name - since there may be // many assets that share a name and symbol across L2's - (BUILT_IN_NETWORK_BASE_ASSETS_BY_SYMBOL[a.symbol] && - BUILT_IN_NETWORK_BASE_ASSETS_BY_SYMBOL[asset.symbol] && + (existsInBaseAssets(a.symbol) && + existsInBaseAssets(asset.symbol) && a.name === asset.name) ) // if there aren't duplicates, add the asset diff --git a/background/redux-slices/selectors/accountsSelectors.ts b/background/redux-slices/selectors/accountsSelectors.ts index 75fa2773cc..24a305d2e7 100644 --- a/background/redux-slices/selectors/accountsSelectors.ts +++ b/background/redux-slices/selectors/accountsSelectors.ts @@ -6,7 +6,9 @@ import { AssetsState, selectAssetPricePoint } from "../assets" import { enrichAssetAmountWithDecimalValues, enrichAssetAmountWithMainCurrencyValues, + existsInBaseAssets, formatCurrencyAmount, + getBaseAssets, heuristicDesiredDecimalsForUnitPrice, isNetworkBaseAssetWithCoinType, } from "../utils/asset-utils" @@ -33,11 +35,7 @@ import { } from "./keyringsSelectors" import { AccountBalance, AddressOnNetwork } from "../../accounts" import { EVMNetwork, NetworkBaseAsset, sameNetwork } from "../../networks" -import { - BUILT_IN_NETWORK_BASE_ASSETS_BY_SYMBOL, - NETWORK_BY_CHAIN_ID, - TEST_NETWORK_BY_CHAIN_ID, -} from "../../constants" +import { NETWORK_BY_CHAIN_ID, TEST_NETWORK_BY_CHAIN_ID } from "../../constants" import { DOGGO } from "../../constants/assets" import { FeatureFlags, isEnabled } from "../../features" import { @@ -128,8 +126,10 @@ const computeCombinedAssetAmountsData = ( return fullyEnrichedAssetAmount }) .filter((assetAmount) => { - const baseAsset = - BUILT_IN_NETWORK_BASE_ASSETS_BY_SYMBOL[assetAmount.asset.symbol] + const baseAsset = getBaseAssets( + assetAmount.asset.symbol, + currentNetwork.chainID + ) const isForciblyDisplayed = shouldForciblyDisplayAsset( assetAmount, @@ -168,11 +168,8 @@ const computeCombinedAssetAmountsData = ( if (leftIsNetworkBaseAsset !== rightIsNetworkBaseAsset) { return leftIsNetworkBaseAsset ? -1 : 1 } - - const leftIsBaseAsset = - asset1.asset.symbol in BUILT_IN_NETWORK_BASE_ASSETS_BY_SYMBOL - const rightIsBaseAsset = - asset2.asset.symbol in BUILT_IN_NETWORK_BASE_ASSETS_BY_SYMBOL + const leftIsBaseAsset = existsInBaseAssets(asset1.asset.symbol) + const rightIsBaseAsset = existsInBaseAssets(asset2.asset.symbol) // Always sort base assets above non-base assets. if (leftIsBaseAsset !== rightIsBaseAsset) { diff --git a/background/redux-slices/utils/asset-utils.ts b/background/redux-slices/utils/asset-utils.ts index 0f7030df77..23904d54b0 100644 --- a/background/redux-slices/utils/asset-utils.ts +++ b/background/redux-slices/utils/asset-utils.ts @@ -8,8 +8,9 @@ import { FungibleAsset, UnitPricePoint, AnyAsset, + CoinGeckoAsset, } from "../../assets" -import { OPTIMISM } from "../../constants" +import { BUILT_IN_NETWORK_BASE_ASSETS, OPTIMISM } from "../../constants" import { fromFixedPointNumber } from "../../lib/fixed-point" import { AnyNetwork, NetworkBaseAsset } from "../../networks" import { hardcodedMainCurrencySign } from "./constants" @@ -248,3 +249,17 @@ export function heuristicDesiredDecimalsForUnitPrice( minimumDesiredDecimals ) } + +export function existsInBaseAssets(symbol: string): boolean { + return BUILT_IN_NETWORK_BASE_ASSETS.some( + (baseAsset) => baseAsset.symbol === symbol + ) +} +export function getBaseAssets( + symbol: string, + chainID: string +): (NetworkBaseAsset & Required) | undefined { + return BUILT_IN_NETWORK_BASE_ASSETS.find( + (baseAsset) => baseAsset.symbol === symbol && baseAsset.chainID === chainID + ) +} From 5e595a4c2568eba526e15133986dc3910ab1ab23 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 12 Jan 2023 09:40:16 +0100 Subject: [PATCH 065/121] Refactor for base asset function --- background/redux-slices/assets.ts | 4 ++-- background/redux-slices/selectors/accountsSelectors.ts | 4 ++-- background/redux-slices/utils/asset-utils.ts | 6 +++--- ui/pages/Swap.tsx | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/background/redux-slices/assets.ts b/background/redux-slices/assets.ts index 9186c87f21..dcc187cd1a 100644 --- a/background/redux-slices/assets.ts +++ b/background/redux-slices/assets.ts @@ -14,7 +14,7 @@ import { normalizeEVMAddress } from "../lib/utils" import { createBackgroundAsyncThunk } from "./utils" import { existsInBaseAssets, - isNetworkBaseAssetWithCoinType, + isBuiltInNetworkBaseAsset, } from "./utils/asset-utils" import { getProvider } from "./utils/contract-utils" import { sameNetwork } from "../networks" @@ -147,7 +147,7 @@ export const transferAsset = createBackgroundAsyncThunk( const provider = getProvider() const signer = provider.getSigner() - if (isNetworkBaseAssetWithCoinType(assetAmount.asset, fromNetwork)) { + if (isBuiltInNetworkBaseAsset(assetAmount.asset, fromNetwork)) { logger.debug( `Sending ${assetAmount.amount} ${assetAmount.asset.symbol} from ` + `${fromAddress} to ${toAddress} as a base asset transfer.` diff --git a/background/redux-slices/selectors/accountsSelectors.ts b/background/redux-slices/selectors/accountsSelectors.ts index 24a305d2e7..a75666c5de 100644 --- a/background/redux-slices/selectors/accountsSelectors.ts +++ b/background/redux-slices/selectors/accountsSelectors.ts @@ -10,7 +10,7 @@ import { formatCurrencyAmount, getBaseAssets, heuristicDesiredDecimalsForUnitPrice, - isNetworkBaseAssetWithCoinType, + isBuiltInNetworkBaseAsset, } from "../utils/asset-utils" import { AnyAsset, @@ -72,7 +72,7 @@ const shouldForciblyDisplayAsset = ( !isEnabled(FeatureFlags.HIDE_TOKEN_FEATURES) && assetAmount.asset.symbol === DOGGO.symbol - return isDoggo || isNetworkBaseAssetWithCoinType(baseAsset, network) + return isDoggo || isBuiltInNetworkBaseAsset(baseAsset, network) } const computeCombinedAssetAmountsData = ( diff --git a/background/redux-slices/utils/asset-utils.ts b/background/redux-slices/utils/asset-utils.ts index 23904d54b0..c129aa3271 100644 --- a/background/redux-slices/utils/asset-utils.ts +++ b/background/redux-slices/utils/asset-utils.ts @@ -38,7 +38,7 @@ export type AssetDecimalAmount = { localizedDecimalAmount: string } -function isBaseAssetWithCoinType(asset: AnyAsset): asset is NetworkBaseAsset { +function hasCoinType(asset: AnyAsset): asset is NetworkBaseAsset { return "coinType" in asset } @@ -58,7 +58,7 @@ function isOptimismBaseAsset(asset: AnyAsset) { * * @return True if the passed asset is the base asset for the passed network. */ -export function isNetworkBaseAssetWithCoinType( +export function isBuiltInNetworkBaseAsset( asset: AnyAsset, network: AnyNetwork ): asset is NetworkBaseAsset { @@ -67,7 +67,7 @@ export function isNetworkBaseAssetWithCoinType( } return ( - isBaseAssetWithCoinType(asset) && + hasCoinType(asset) && asset.symbol === network.baseAsset.symbol && asset.coinType === network.baseAsset.coinType && asset.name === network.baseAsset.name diff --git a/ui/pages/Swap.tsx b/ui/pages/Swap.tsx index 668c15d998..9017e6cb86 100644 --- a/ui/pages/Swap.tsx +++ b/ui/pages/Swap.tsx @@ -26,7 +26,7 @@ import { CompleteAssetAmount } from "@tallyho/tally-background/redux-slices/acco import { sameNetwork } from "@tallyho/tally-background/networks" import { selectDefaultNetworkFeeSettings } from "@tallyho/tally-background/redux-slices/selectors/transactionConstructionSelectors" import { selectSlippageTolerance } from "@tallyho/tally-background/redux-slices/ui" -import { isNetworkBaseAssetWithCoinType } from "@tallyho/tally-background/redux-slices/utils/asset-utils" +import { isBuiltInNetworkBaseAsset } from "@tallyho/tally-background/redux-slices/utils/asset-utils" import { ReadOnlyAccountSigner } from "@tallyho/tally-background/services/signing" import { NETWORKS_SUPPORTING_SWAPS, @@ -147,7 +147,7 @@ export default function Swap(): ReactElement { } if ( // Explicitly add a network's base asset. - isNetworkBaseAssetWithCoinType(asset, currentNetwork) + isBuiltInNetworkBaseAsset(asset, currentNetwork) ) { return true } From 6d97184d934caa8a8936484a5c8ab48646b2668f Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 12 Jan 2023 09:50:51 +0100 Subject: [PATCH 066/121] Rename utils functions for base assets --- background/redux-slices/assets.ts | 6 +++--- .../redux-slices/selectors/accountsSelectors.ts | 10 +++++----- background/redux-slices/utils/asset-utils.ts | 11 +++++------ 3 files changed, 13 insertions(+), 14 deletions(-) diff --git a/background/redux-slices/assets.ts b/background/redux-slices/assets.ts index dcc187cd1a..c9d6cbcb54 100644 --- a/background/redux-slices/assets.ts +++ b/background/redux-slices/assets.ts @@ -13,7 +13,7 @@ import { findClosestAssetIndex } from "../lib/asset-similarity" import { normalizeEVMAddress } from "../lib/utils" import { createBackgroundAsyncThunk } from "./utils" import { - existsInBaseAssets, + existsInNetworkBaseAssets, isBuiltInNetworkBaseAsset, } from "./utils/asset-utils" import { getProvider } from "./utils/contract-utils" @@ -69,8 +69,8 @@ const assetsSlice = createSlice({ normalizeEVMAddress(asset.contractAddress)) || // Only match base assets by name - since there may be // many assets that share a name and symbol across L2's - (existsInBaseAssets(a.symbol) && - existsInBaseAssets(asset.symbol) && + (existsInNetworkBaseAssets(a.symbol) && + existsInNetworkBaseAssets(asset.symbol) && a.name === asset.name) ) // if there aren't duplicates, add the asset diff --git a/background/redux-slices/selectors/accountsSelectors.ts b/background/redux-slices/selectors/accountsSelectors.ts index a75666c5de..563a4aee08 100644 --- a/background/redux-slices/selectors/accountsSelectors.ts +++ b/background/redux-slices/selectors/accountsSelectors.ts @@ -6,9 +6,9 @@ import { AssetsState, selectAssetPricePoint } from "../assets" import { enrichAssetAmountWithDecimalValues, enrichAssetAmountWithMainCurrencyValues, - existsInBaseAssets, + existsInNetworkBaseAssets, formatCurrencyAmount, - getBaseAssets, + getBuiltInNetworkBaseAsset, heuristicDesiredDecimalsForUnitPrice, isBuiltInNetworkBaseAsset, } from "../utils/asset-utils" @@ -126,7 +126,7 @@ const computeCombinedAssetAmountsData = ( return fullyEnrichedAssetAmount }) .filter((assetAmount) => { - const baseAsset = getBaseAssets( + const baseAsset = getBuiltInNetworkBaseAsset( assetAmount.asset.symbol, currentNetwork.chainID ) @@ -168,8 +168,8 @@ const computeCombinedAssetAmountsData = ( if (leftIsNetworkBaseAsset !== rightIsNetworkBaseAsset) { return leftIsNetworkBaseAsset ? -1 : 1 } - const leftIsBaseAsset = existsInBaseAssets(asset1.asset.symbol) - const rightIsBaseAsset = existsInBaseAssets(asset2.asset.symbol) + const leftIsBaseAsset = existsInNetworkBaseAssets(asset1.asset.symbol) + const rightIsBaseAsset = existsInNetworkBaseAssets(asset2.asset.symbol) // Always sort base assets above non-base assets. if (leftIsBaseAsset !== rightIsBaseAsset) { diff --git a/background/redux-slices/utils/asset-utils.ts b/background/redux-slices/utils/asset-utils.ts index c129aa3271..f281081462 100644 --- a/background/redux-slices/utils/asset-utils.ts +++ b/background/redux-slices/utils/asset-utils.ts @@ -250,16 +250,15 @@ export function heuristicDesiredDecimalsForUnitPrice( ) } -export function existsInBaseAssets(symbol: string): boolean { - return BUILT_IN_NETWORK_BASE_ASSETS.some( - (baseAsset) => baseAsset.symbol === symbol - ) +export function existsInNetworkBaseAssets(symbol: string): boolean { + return BUILT_IN_NETWORK_BASE_ASSETS.some((asset) => asset.symbol === symbol) } -export function getBaseAssets( + +export function getBuiltInNetworkBaseAsset( symbol: string, chainID: string ): (NetworkBaseAsset & Required) | undefined { return BUILT_IN_NETWORK_BASE_ASSETS.find( - (baseAsset) => baseAsset.symbol === symbol && baseAsset.chainID === chainID + (asset) => asset.symbol === symbol && asset.chainID === chainID ) } From 45f121d693cafb8eca8a204565af12459e927ef4 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Thu, 12 Jan 2023 11:44:45 +0100 Subject: [PATCH 067/121] Don't expand collections with the same id at the same time If multiple accounts owns the same collections then we shouldn't expand them at the same time --- ui/components/NFTS_update/NFTCollection.tsx | 5 +++-- ui/components/NFTS_update/NFTList.tsx | 7 +++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/ui/components/NFTS_update/NFTCollection.tsx b/ui/components/NFTS_update/NFTCollection.tsx index 788ceeb098..b0a7203c9d 100644 --- a/ui/components/NFTS_update/NFTCollection.tsx +++ b/ui/components/NFTS_update/NFTCollection.tsx @@ -14,7 +14,7 @@ import SharedSkeletonLoader from "../Shared/SharedSkeletonLoader" export default function NFTCollection(props: { collection: NFTCollectionCached isExpanded: boolean - setExpandedID: (id: string | null) => void + setExpandedID: (id: string | null, owner: string | null) => void openPreview: (current: NFTWithCollection) => void }): ReactElement { const { collection, openPreview, isExpanded, setExpandedID } = props @@ -105,7 +105,8 @@ export default function NFTCollection(props: { } }, [fetchCollection, isExpanded, wasUpdated]) - const toggleCollection = () => setExpandedID(isExpanded ? null : id) + const toggleCollection = () => + isExpanded ? setExpandedID(null, null) : setExpandedID(id, owner) const onItemClick = (nft: NFT) => openPreview({ nft, collection }) diff --git a/ui/components/NFTS_update/NFTList.tsx b/ui/components/NFTS_update/NFTList.tsx index dcff2718be..7c31194f05 100644 --- a/ui/components/NFTS_update/NFTList.tsx +++ b/ui/components/NFTS_update/NFTList.tsx @@ -23,7 +23,8 @@ export default function NFTList(props: { null ) const setExpandedID = useCallback( - (id: string | null) => setCurrentExpandedID(id), + (id: string | null, owner: string | null) => + setCurrentExpandedID(`${id}_${owner}`), // TODO: owner can be removed after we will merge collections owned by multiple accounts [] ) @@ -68,7 +69,9 @@ export default function NFTList(props: { openPreview={openPreview} collection={collection} setExpandedID={setExpandedID} - isExpanded={collection.id === currentExpandedID} + isExpanded={ + `${collection.id}_${collection.owner}` === currentExpandedID + } /> ) )} From 64d94b3836568280b8c782c14671e287ac9860ec Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Thu, 12 Jan 2023 11:54:13 +0100 Subject: [PATCH 068/121] Fix ButtonUp position to include expanded collections --- ui/components/Shared/SharedButtonUp.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/components/Shared/SharedButtonUp.tsx b/ui/components/Shared/SharedButtonUp.tsx index 174e0beeab..14f9bc3a37 100644 --- a/ui/components/Shared/SharedButtonUp.tsx +++ b/ui/components/Shared/SharedButtonUp.tsx @@ -45,8 +45,8 @@ export default function SharedButtonUp(props: {
      ) diff --git a/ui/hooks/nft-hooks.ts b/ui/hooks/nft-hooks.ts index 57f127a1b7..57ef224f58 100644 --- a/ui/hooks/nft-hooks.ts +++ b/ui/hooks/nft-hooks.ts @@ -3,7 +3,6 @@ import { getAssetsState, selectFilteredTotalFloorPriceInETH, selectMainCurrencySymbol, - selectTotalFloorPriceInETH, } from "@tallyho/tally-background/redux-slices/selectors" import { enrichAssetAmountWithMainCurrencyValues, @@ -18,18 +17,14 @@ import { import { useEffect } from "react" import { useBackgroundDispatch, useBackgroundSelector } from "./redux-hooks" -export const useTotalNFTsFloorPrice = ( - useTotalFloorPrice = true -): { +export const useTotalNFTsFloorPrice = (): { totalFloorPriceInETH: string totalFloorPriceInUSD: string } => { const assets = useBackgroundSelector(getAssetsState) const mainCurrencySymbol = useBackgroundSelector(selectMainCurrencySymbol) const totalFloorPriceInETH = useBackgroundSelector( - useTotalFloorPrice - ? selectTotalFloorPriceInETH - : selectFilteredTotalFloorPriceInETH + selectFilteredTotalFloorPriceInETH ) const totalFloorPriceInETHFormatted = formatCurrencyAmount( mainCurrencySymbol, From 674adca272625560dd9aded025c203709f954887 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Mon, 16 Jan 2023 13:31:19 +0100 Subject: [PATCH 076/121] Fix typos --- ui/_locales/en/messages.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index 59c4250a15..fcda4d33e7 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -264,7 +264,7 @@ "collection_other": "Collections" }, "header": { - "title": "Total NFT floor price", + "title": "Total NFTs floor price", "addAccountCTA": "Add account", "emptyTitle": "No NFTs here", "emptyDesc": "Add more accounts to see your NFTs, you can also just add read-only accounts" @@ -429,7 +429,7 @@ "assets": "Assets", "networks": "Networks", "nfts": "{{nfts}} in {{collections}} & {{badges}}", - "nftsTooltip": "NFT’s here are shown based on your filters in the NFT page.", + "nftsTooltip": "NFTs here are shown based on your filters in the NFT page.", "units": { "nft": "1 NFT", "nft_other": "{{count}} NFTs", From 3211d560c376eaa5139a118b861307b7c172908c Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 16 Jan 2023 14:02:41 +0100 Subject: [PATCH 077/121] Merge branch 'main' of github.com:tallycash/extension into persist-base-assets --- background/assets.ts | 1 + background/lib/alchemy.ts | 2 +- background/lib/asset-similarity.ts | 2 + .../lib/tests/asset-similarity.unit.test.ts | 18 +++ background/lib/token-lists.ts | 7 +- background/redux-slices/utils/asset-utils.ts | 18 ++- background/services/chain/index.ts | 10 +- background/services/enrichment/index.ts | 3 +- .../tests/transactions.integration.test.ts | 80 ++++++++++ .../services/enrichment/transactions.ts | 129 +++++++++++------ background/services/indexing/index.ts | 18 +++ background/tests/factories.ts | 49 +++++-- ui/_locales/en/messages.json | 9 ++ ui/components/NFTS_update/NFTCollection.tsx | 137 +++++++++++------- ui/components/NFTS_update/NFTList.tsx | 14 +- ui/components/Shared/SharedAssetInput.tsx | 1 + ui/components/Shared/SharedButtonUp.tsx | 4 +- .../AssetListItem/CommonAssetListItem.tsx | 51 ++++++- ui/components/Wallet/AssetListItem/styles.ts | 2 + ui/components/Wallet/AssetWarningSlideUp.tsx | 135 +++++++++++++++++ ui/components/Wallet/WalletAssetList.tsx | 68 +++++---- ui/components/Wallet/WalletAssetListItem.tsx | 53 +++++-- 22 files changed, 643 insertions(+), 168 deletions(-) create mode 100644 background/lib/tests/asset-similarity.unit.test.ts create mode 100644 background/services/enrichment/tests/transactions.integration.test.ts create mode 100644 ui/components/Wallet/AssetWarningSlideUp.tsx diff --git a/background/assets.ts b/background/assets.ts index 9e5d8e8fda..057695d2f5 100644 --- a/background/assets.ts +++ b/background/assets.ts @@ -120,6 +120,7 @@ export type AnyAsset = | FiatCurrency | FungibleAsset | SmartContractFungibleAsset + | NetworkBaseAsset /** * An asset that can be swapped with our current providers diff --git a/background/lib/alchemy.ts b/background/lib/alchemy.ts index 4e2d7496b7..761f58d290 100644 --- a/background/lib/alchemy.ts +++ b/background/lib/alchemy.ts @@ -44,7 +44,7 @@ export async function getAssetTransfers( fromBlock: number, toBlock?: number, order: "asc" | "desc" = "desc", - maxCount = 1000 + maxCount = 25 ): Promise { const { address: account, network } = addressOnNetwork diff --git a/background/lib/asset-similarity.ts b/background/lib/asset-similarity.ts index 67b87ecd89..d236c23a5a 100644 --- a/background/lib/asset-similarity.ts +++ b/background/lib/asset-similarity.ts @@ -97,6 +97,8 @@ export function findClosestAssetIndex( export function mergeAssets(asset1: AnyAsset, asset2: AnyAsset): AnyAsset { return { ...asset1, + ...("coinType" in asset1 ? { coinType: asset1.coinType } : {}), + ...("coinType" in asset2 ? { coinType: asset2.coinType } : {}), metadata: { ...asset1.metadata, ...asset2.metadata, diff --git a/background/lib/tests/asset-similarity.unit.test.ts b/background/lib/tests/asset-similarity.unit.test.ts new file mode 100644 index 0000000000..ba5fc8d3d7 --- /dev/null +++ b/background/lib/tests/asset-similarity.unit.test.ts @@ -0,0 +1,18 @@ +import { + createNetworkBaseAsset, + createSmartContractAsset, +} from "../../tests/factories" +import { mergeAssets } from "../asset-similarity" + +describe("Asset Similarity", () => { + describe("mergeAssets", () => { + it("Should preserve coinType when merging assets", () => { + const mergedAsset = mergeAssets( + createSmartContractAsset(), + createNetworkBaseAsset() + ) + + expect("coinType" in mergedAsset).toBe(true) + }) + }) +}) diff --git a/background/lib/token-lists.ts b/background/lib/token-lists.ts index 267f8a187d..b19dc97730 100644 --- a/background/lib/token-lists.ts +++ b/background/lib/token-lists.ts @@ -148,10 +148,9 @@ export function mergeAssets( metadata: { ...matchingAsset.metadata, ...asset.metadata, - tokenLists: - matchingAsset.metadata?.tokenLists?.concat( - asset.metadata?.tokenLists ?? [] - ) ?? [], + tokenLists: (matchingAsset.metadata?.tokenLists || [])?.concat( + asset.metadata?.tokenLists ?? [] + ), }, } } else { diff --git a/background/redux-slices/utils/asset-utils.ts b/background/redux-slices/utils/asset-utils.ts index d6696b92ef..b9727efb70 100644 --- a/background/redux-slices/utils/asset-utils.ts +++ b/background/redux-slices/utils/asset-utils.ts @@ -10,7 +10,11 @@ import { AnyAsset, CoinGeckoAsset, } from "../../assets" -import { BUILT_IN_NETWORK_BASE_ASSETS, OPTIMISM } from "../../constants" +import { + BUILT_IN_NETWORK_BASE_ASSETS, + OPTIMISM, + POLYGON, +} from "../../constants" import { fromFixedPointNumber } from "../../lib/fixed-point" import { AnyNetwork, NetworkBaseAsset } from "../../networks" import { hardcodedMainCurrencySign } from "./constants" @@ -48,6 +52,14 @@ function isOptimismBaseAsset(asset: AnyAsset) { asset.contractAddress === OPTIMISM.baseAsset.contractAddress ) } + +function isPolygonBaseAsset(asset: AnyAsset) { + return ( + "contractAddress" in asset && + asset.contractAddress === POLYGON.baseAsset.contractAddress + ) +} + /** * Given an asset and a network, determines whether the given asset is the base * asset for the given network. Used to special-case transactions that should @@ -66,6 +78,10 @@ export function isBuiltInNetworkBaseAsset( return true } + if (network.chainID === POLYGON.chainID && isPolygonBaseAsset(asset)) { + return true + } + return ( hasCoinType(asset) && asset.symbol === network.baseAsset.symbol && diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index a713939766..5b43bcd0d3 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -1823,9 +1823,11 @@ export default class ChainService extends BaseService { ): Promise { const provider = this.providerForNetworkOrThrow(network) const receipt = await provider.getTransactionReceipt(transaction.hash) - await this.saveTransaction( - enrichTransactionWithReceipt(transaction, receipt), - "alchemy" - ) + if (receipt) { + await this.saveTransaction( + enrichTransactionWithReceipt(transaction, receipt), + "alchemy" + ) + } } } diff --git a/background/services/enrichment/index.ts b/background/services/enrichment/index.ts index ec9f03fb7a..05372c0336 100644 --- a/background/services/enrichment/index.ts +++ b/background/services/enrichment/index.ts @@ -133,7 +133,8 @@ export default class EnrichmentService extends BaseService { (asset): asset is SmartContractFungibleAsset => { if ( typedData.domain.verifyingContract && - "contractAddress" in asset + "contractAddress" in asset && + asset.contractAddress ) { return ( normalizeHexAddress(asset.contractAddress) === diff --git a/background/services/enrichment/tests/transactions.integration.test.ts b/background/services/enrichment/tests/transactions.integration.test.ts new file mode 100644 index 0000000000..26d93dc246 --- /dev/null +++ b/background/services/enrichment/tests/transactions.integration.test.ts @@ -0,0 +1,80 @@ +import sinon from "sinon" +import { ETHEREUM } from "../../../constants" + +import { + createChainService, + createIndexingService, + createNameService, + createAnyEVMBlock, + makeSerialFallbackProvider, +} from "../../../tests/factories" +import SerialFallbackProvider from "../../chain/serial-fallback-provider" +import { annotationsFromLogs } from "../transactions" + +// These logs reference transaction https://etherscan.io/tx/0x0ba306853f8be38d54327675f14694d582a14759b851f2126dd900bef0aff840 +// prettier-ignore +const TEST_ERC20_LOGS = [ { contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e", data: "0x00000000000000000000000000000000000000000000057d723eb063126abeaf", topics: [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000009eef87f4c08d8934cb2a3309df4dec5635338115", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", ], }, { contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e", data: "0xffffffffffffffffffffffffffffffffffffffffffffcf2f0540996d1fc52a54", topics: [ "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", "0x0000000000000000000000009eef87f4c08d8934cb2a3309df4dec5635338115", "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", ], }, { contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", data: "0x000000000000000000000000000000000000000000000000000000060869fd65", topics: [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000009a834b70c07c81a9fcd6f22e842bf002fbffbe4d", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", ], }, { contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e", data: "0x00000000000000000000000000000000000000000000057d723eb063126abeaf", topics: [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", "0x0000000000000000000000009a834b70c07c81a9fcd6f22e842bf002fbffbe4d", ], }, { contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e", data: "0xffffffffffffffffffffffffffffffffffffffffffe0bf27eab0a412146f26d0", topics: [ "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", "0x000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564", ], }, { contractAddress: "0x9A834b70C07C81a9fcD6F22E842BF002fBfFbe4D", data: "0x00000000000000000000000000000000000000000000057d723eb063126abeaffffffffffffffffffffffffffffffffffffffffffffffffffffffff9f796029b0000000000000000000000000000000000000000000010c5f0437647d68615730000000000000000000000000000000000000000000002091686803a9aa2714ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbc897", topics: [ "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67", "0x000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", ], }, { contractAddress: "0x22F9dCF4647084d6C31b2765F6910cd85C178C18", data: "0x00000000000000000000000000000012556e6973776170563300000000000000000000000000000000000000853d955acef822db058eb8505911ed77f175b99e000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000057d723eb063126abeaf000000000000000000000000000000000000000000000000000000060869fd65", topics: [ "0xe59e71a14fe90157eedc866c4f8c767d3943d6b6b2e8cd64dddcc92ab4c55af8", ], }, { contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", data: "0x00000000000000000000000000000000000000000000000000000000079b57dc", topics: [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", "0x00000000000000000000000099b36fdbc582d113af36a21eba06bfeab7b9be12", ], }, { contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", data: "0x0000000000000000000000000000000000000000000000000000000600cea589", topics: [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", "0x0000000000000000000000009eef87f4c08d8934cb2a3309df4dec5635338115", ], }, { contractAddress: "0xDef1C0ded9bec7F1a1670819833240f027b25EfF", data: "0x000000000000000000000000853d955acef822db058eb8505911ed77f175b99e000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000057d723eb063126abeaf0000000000000000000000000000000000000000000000000000000600cea589", topics: [ "0x0f6672f78a59ba8e5e5b5d38df3ebc67f3c792e2c9259b8d97d7f00dd78ba1b3", "0x0000000000000000000000009eef87f4c08d8934cb2a3309df4dec5635338115", ], }, ] + +describe("Enrichment Service Transactions", () => { + const sandbox = sinon.createSandbox() + + beforeEach(async () => { + sandbox.restore() + }) + + describe("annotationsFromLogs", () => { + it("Should only create subannotations from logs with relevant addresses in them", async () => { + const chainServicePromise = createChainService() + const indexingServicePromise = createIndexingService({ + chainService: chainServicePromise, + }) + const nameServicePromise = createNameService({ + chainService: chainServicePromise, + }) + + const [chainService, indexingService, nameService] = await Promise.all([ + chainServicePromise, + indexingServicePromise, + nameServicePromise, + ]) + + await chainService.addAccountToTrack({ + address: "0x9eef87f4c08d8934cb2a3309df4dec5635338115", + network: ETHEREUM, + }) + + await indexingService.addCustomAsset({ + contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", + symbol: "USDC", + name: "USDC Coin", + decimals: 6, + homeNetwork: ETHEREUM, + }) + + await indexingService.addCustomAsset({ + contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e", + symbol: "FRAX", + name: "FRAX Token", + decimals: 18, + homeNetwork: ETHEREUM, + }) + + sandbox + .stub(chainService, "providerForNetworkOrThrow") + .returns(makeSerialFallbackProvider() as SerialFallbackProvider) + + const subannotations = await annotationsFromLogs( + chainService, + indexingService, + nameService, + TEST_ERC20_LOGS, + ETHEREUM, + 2, + Date.now(), + createAnyEVMBlock() + ) + + expect(subannotations.length).toBe(2) + }) + }) +}) diff --git a/background/services/enrichment/transactions.ts b/background/services/enrichment/transactions.ts index 9581d01b84..cbc2fdf691 100644 --- a/background/services/enrichment/transactions.ts +++ b/background/services/enrichment/transactions.ts @@ -8,6 +8,7 @@ import { import { SmartContractFungibleAsset, isSmartContractFungibleAsset, + AnyAsset, } from "../../assets" import { enrichAssetAmountWithDecimalValues } from "../../redux-slices/utils/asset-utils" @@ -20,6 +21,7 @@ import { TransactionAnnotation, PartialTransactionRequestWithFrom, EnrichedEVMTransactionRequest, + EnrichedAddressOnNetwork, } from "./types" import { getDistinctRecipentAddressesFromERC20Logs, @@ -28,64 +30,30 @@ import { import { enrichAddressOnNetwork } from "./addresses" import { OPTIMISM } from "../../constants" import { parseLogsForWrappedDepositsAndWithdrawals } from "../../lib/wrappedAsset" -import { parseERC20Tx, parseLogsForERC20Transfers } from "../../lib/erc20" +import { + ERC20TransferLog, + parseERC20Tx, + parseLogsForERC20Transfers, +} from "../../lib/erc20" import { isDefined, isFulfilledPromise } from "../../lib/utils/type-guards" import { unsignedTransactionFromEVMTransaction } from "../chain/utils" -async function annotationsFromLogs( +async function buildSubannotations( chainService: ChainService, - indexingService: IndexingService, nameService: NameService, - logs: EVMLog[], + relevantTransferLogs: ERC20TransferLog[], + assets: AnyAsset[], + addressEnrichmentsByAddress: { + [k: string]: EnrichedAddressOnNetwork + }, network: EVMNetwork, desiredDecimals: number, resolvedTime: number, block: AnyEVMBlock | undefined -): Promise { - const assets = indexingService.getCachedAssets(network) - - const accountAddresses = (await chainService.getAccountsToTrack()).map( - (account) => account.address - ) - - const tokenTransferLogs = [ - ...parseLogsForERC20Transfers(logs), - ...parseLogsForWrappedDepositsAndWithdrawals(logs), - ] - - const relevantTransferLogs = getERC20LogsForAddresses( - tokenTransferLogs, - accountAddresses - ) - const relevantAddresses = - getDistinctRecipentAddressesFromERC20Logs(relevantTransferLogs).map( - normalizeEVMAddress - ) - - // Look up transfer log names, then flatten to an address -> name map. - const addressEnrichmentsByAddress = Object.fromEntries( - ( - await Promise.allSettled( - relevantAddresses.map( - async (address) => - [ - address, - await enrichAddressOnNetwork(chainService, nameService, { - address, - network, - }), - ] as const - ) - ) - ) - .filter(isFulfilledPromise) - .map(({ value }) => value) - .filter(([, annotation]) => isDefined(annotation)) - ) - +) { const subannotations = ( await Promise.allSettled( - tokenTransferLogs.map( + relevantTransferLogs.map( async ({ contractAddress, amount, @@ -144,6 +112,73 @@ async function annotationsFromLogs( return subannotations } +export async function annotationsFromLogs( + chainService: ChainService, + indexingService: IndexingService, + nameService: NameService, + logs: EVMLog[], + network: EVMNetwork, + desiredDecimals: number, + resolvedTime: number, + block: AnyEVMBlock | undefined +): Promise { + const assets = indexingService.getCachedAssets(network) + + const accountAddresses = (await chainService.getAccountsToTrack()).map( + (account) => account.address + ) + + const tokenTransferLogs = [ + ...parseLogsForERC20Transfers(logs), + ...parseLogsForWrappedDepositsAndWithdrawals(logs), + ] + + const relevantTransferLogs = getERC20LogsForAddresses( + tokenTransferLogs, + accountAddresses + ) + + const relevantAddresses = + getDistinctRecipentAddressesFromERC20Logs(relevantTransferLogs).map( + normalizeEVMAddress + ) + + // Look up transfer log names, then flatten to an address -> name map. + const addressEnrichmentsByAddress = Object.fromEntries( + ( + await Promise.allSettled( + relevantAddresses.map( + async (address) => + [ + address, + await enrichAddressOnNetwork(chainService, nameService, { + address, + network, + }), + ] as const + ) + ) + ) + .filter(isFulfilledPromise) + .map(({ value }) => value) + .filter(([, annotation]) => isDefined(annotation)) + ) + + const subannotations = await buildSubannotations( + chainService, + nameService, + relevantTransferLogs, + assets, + addressEnrichmentsByAddress, + network, + desiredDecimals, + resolvedTime, + block + ) + + return subannotations +} + /** * Resolve an annotation for a partial transaction request, or a pending * or mined transaction. diff --git a/background/services/indexing/index.ts b/background/services/indexing/index.ts index b9c7196f2e..ef8c1148ad 100644 --- a/background/services/indexing/index.ts +++ b/background/services/indexing/index.ts @@ -412,6 +412,24 @@ export default class IndexingService extends BaseService { } private async connectChainServiceEvents(): Promise { + // listen for assetTransfers, and if we find them, track those tokens + // TODO update for NFTs + this.chainService.emitter.on( + "assetTransfers", + async ({ addressNetwork, assetTransfers }) => { + assetTransfers.forEach((transfer) => { + const fungibleAsset = transfer.assetAmount + .asset as SmartContractFungibleAsset + if (fungibleAsset.contractAddress && fungibleAsset.decimals) { + this.addTokenToTrackByContract( + addressNetwork, + fungibleAsset.contractAddress + ) + } + }) + } + ) + this.chainService.emitter.on( "newAccountToTrack", async (addressOnNetwork) => { diff --git a/background/tests/factories.ts b/background/tests/factories.ts index 3b99008681..ced119937a 100644 --- a/background/tests/factories.ts +++ b/background/tests/factories.ts @@ -29,6 +29,7 @@ import { LegacyEVMTransactionRequest, AnyEVMBlock, BlockPrices, + NetworkBaseAsset, } from "../networks" import { AnalyticsService, @@ -325,24 +326,28 @@ export const makeSerialFallbackProvider = async getFeeData() { return makeEthersFeeData() } + + async getCode() { + return "false" + } } return new MockSerialFallbackProvider() } -export const createSmartContractAsset = ( - overrides: Partial = {} -): SmartContractFungibleAsset => { - const getRandomStr = (length: number) => { - let result = "" - - while (result.length < length) { - result += Math.random().toString(36).slice(2) - } +const getRandomStr = (length: number) => { + let result = "" - return result.slice(0, length) + while (result.length < length) { + result += Math.random().toString(36).slice(2) } + return result.slice(0, length) +} + +export const createSmartContractAsset = ( + overrides: Partial = {} +): SmartContractFungibleAsset => { const symbol = getRandomStr(3) const asset = { metadata: { @@ -369,6 +374,30 @@ export const createSmartContractAsset = ( } } +export const createNetworkBaseAsset = ( + overrides: Partial = {} +): NetworkBaseAsset => { + const symbol = getRandomStr(3) + const asset: NetworkBaseAsset = { + metadata: { + coinGeckoID: "ethereum", + logoURL: "http://example.com/foo.png", + tokenLists: [], + }, + name: `${symbol} Network`, + symbol, + decimals: 18, + coinType: 60, + chainID: "1", + contractAddress: createRandom0xHash(), + } + + return { + ...asset, + ...overrides, + } +} + /** * @param asset Any type of asset * @param price Price, e.g. 1.5 => 1.5$ diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index c06382c4ab..e07d04faf3 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -701,6 +701,15 @@ "transactionFrom": "From:", "loadingActivities": "Digging deeper..." }, + "trustedAssets": { + "notTrusted": "Asset isn't trusted", + "notVerified": "Asset has not been verified yet!", + "trustExplainer": "Only transact with assets you trust.", + "assetImported": "Asset imported from transaction history", + "name": "Name", + "contract": "Contract address", + "close": "Close" + }, "banner": { "bannerTitle": "New Odyssey week!", "emptyBannerContent": "Check out Arbitrum Odyssey campaign", diff --git a/ui/components/NFTS_update/NFTCollection.tsx b/ui/components/NFTS_update/NFTCollection.tsx index 363872dbb1..b0a7203c9d 100644 --- a/ui/components/NFTS_update/NFTCollection.tsx +++ b/ui/components/NFTS_update/NFTCollection.tsx @@ -13,13 +13,14 @@ import SharedSkeletonLoader from "../Shared/SharedSkeletonLoader" export default function NFTCollection(props: { collection: NFTCollectionCached + isExpanded: boolean + setExpandedID: (id: string | null, owner: string | null) => void openPreview: (current: NFTWithCollection) => void }): ReactElement { - const { collection, openPreview } = props + const { collection, openPreview, isExpanded, setExpandedID } = props const { id, owner, network, nfts, nftCount, hasNextPage } = collection const dispatch = useBackgroundDispatch() - const [isExpanded, setIsExpanded] = useState(false) const [isLoading, setIsLoading] = useState(false) // initial update of collection const [isUpdating, setIsUpdating] = useState(false) // update on already loaded collection const [wasUpdated, setWasUpdated] = useState(false) // to fetch NFTs data only once during the component lifespan @@ -104,62 +105,72 @@ export default function NFTCollection(props: { } }, [fetchCollection, isExpanded, wasUpdated]) - const toggleCollection = () => setIsExpanded((val) => !val) + const toggleCollection = () => + isExpanded ? setExpandedID(null, null) : setExpandedID(id, owner) const onItemClick = (nft: NFT) => openPreview({ nft, collection }) return ( <> -
    • - +
    • - {nfts.length === 1 ? ( - onItemClick(nfts[0])} - /> - ) : ( - - )} - {isExpanded && ( - <> - {nfts.map((nft) => ( - - ))} - + {nfts.length === 1 ? ( + onItemClick(nfts[0])} /> -
      - - )} - -
    • + ) : ( + + )} + {isExpanded && ( + <> + {nfts.map((nft) => ( + + ))} + +
      + + )} + + +
      + ) } diff --git a/ui/components/NFTS_update/NFTList.tsx b/ui/components/NFTS_update/NFTList.tsx index 7bbbab72d0..7c31194f05 100644 --- a/ui/components/NFTS_update/NFTList.tsx +++ b/ui/components/NFTS_update/NFTList.tsx @@ -3,7 +3,7 @@ import { NFTWithCollection, } from "@tallyho/tally-background/redux-slices/nfts_update" import { selectIsReloadingNFTs } from "@tallyho/tally-background/redux-slices/selectors" -import React, { ReactElement, useState } from "react" +import React, { ReactElement, useCallback, useState } from "react" import { useBackgroundSelector } from "../../hooks" import SharedLoadingDoggo from "../Shared/SharedLoadingDoggo" import SharedSlideUpMenu from "../Shared/SharedSlideUpMenu" @@ -19,6 +19,14 @@ export default function NFTList(props: { const [isPreviewOpen, setIsPreviewOpen] = useState(false) const [currentNFTPreview, setCurrentNFTPreview] = useState(null) + const [currentExpandedID, setCurrentExpandedID] = useState( + null + ) + const setExpandedID = useCallback( + (id: string | null, owner: string | null) => + setCurrentExpandedID(`${id}_${owner}`), // TODO: owner can be removed after we will merge collections owned by multiple accounts + [] + ) const isReloading = useBackgroundSelector(selectIsReloadingNFTs) @@ -60,6 +68,10 @@ export default function NFTList(props: { key={`${collection.id}_${collection.owner}`} openPreview={openPreview} collection={collection} + setExpandedID={setExpandedID} + isExpanded={ + `${collection.id}_${collection.owner}` === currentExpandedID + } /> ) )} diff --git a/ui/components/Shared/SharedAssetInput.tsx b/ui/components/Shared/SharedAssetInput.tsx index 32e00ab2e9..8be770457d 100644 --- a/ui/components/Shared/SharedAssetInput.tsx +++ b/ui/components/Shared/SharedAssetInput.tsx @@ -141,6 +141,7 @@ function SelectAssetMenuContent( return ( asset.symbol.toLowerCase().includes(searchTerm.toLowerCase()) || ("contractAddress" in asset && + asset.contractAddress && searchTerm.startsWith("0x") && normalizeEVMAddress(asset.contractAddress).includes( // The replace handles `normalizeEVMAddress`'s diff --git a/ui/components/Shared/SharedButtonUp.tsx b/ui/components/Shared/SharedButtonUp.tsx index 174e0beeab..14f9bc3a37 100644 --- a/ui/components/Shared/SharedButtonUp.tsx +++ b/ui/components/Shared/SharedButtonUp.tsx @@ -45,8 +45,8 @@ export default function SharedButtonUp(props: { + + ) +} + +type AssetWarningSlideUpProps = { + asset: AnyAsset | null + close: () => void +} + +export default function AssetWarningSlideUp( + props: AssetWarningSlideUpProps +): ReactElement { + const { t } = useTranslation("translation", { + keyPrefix: "wallet.trustedAssets", + }) + const { asset, close } = props + return ( + + + + + {t("notVerified")} +
      + {t("trustExplainer")} +
      +
        +
      • + {t("name")} +
        + {`${asset?.name} (${asset?.symbol})`} +
        +
      • +
      • + {t("contract")} +
        + +
        +
      • +
      + + {t("close")} + +
      + ) +} diff --git a/ui/components/Wallet/WalletAssetList.tsx b/ui/components/Wallet/WalletAssetList.tsx index c15d4335bf..00dd10d27b 100644 --- a/ui/components/Wallet/WalletAssetList.tsx +++ b/ui/components/Wallet/WalletAssetList.tsx @@ -1,43 +1,59 @@ // @ts-check -// -import React, { ReactElement } from "react" + +import React, { ReactElement, useState } from "react" import { CompleteAssetAmount } from "@tallyho/tally-background/redux-slices/accounts" import { useTranslation } from "react-i18next" import WalletAssetListItem from "./WalletAssetListItem" +import AssetWarningSlideUp from "./AssetWarningSlideUp" -interface Props { +type WalletAssetListProps = { assetAmounts: CompleteAssetAmount[] initializationLoadingTimeExpired: boolean } -export default function WalletAssetList(props: Props): ReactElement { +export default function WalletAssetList( + props: WalletAssetListProps +): ReactElement { const { t } = useTranslation("translation", { keyPrefix: "wallet.activities", }) const { assetAmounts, initializationLoadingTimeExpired } = props + + const [warnedAsset, setWarnedAsset] = useState< + CompleteAssetAmount["asset"] | null + >(null) + if (!assetAmounts) return <> + return ( -
        - {assetAmounts.map((assetAmount) => ( - - ))} - {!initializationLoadingTimeExpired && ( -
      • {t("loadingActivities")}
      • - )} - -
      + <> + setWarnedAsset(null)} + /> +
        + {assetAmounts.map((assetAmount) => ( + setWarnedAsset(asset)} + /> + ))} + {!initializationLoadingTimeExpired && ( +
      • {t("loadingActivities")}
      • + )} + +
      + ) } diff --git a/ui/components/Wallet/WalletAssetListItem.tsx b/ui/components/Wallet/WalletAssetListItem.tsx index b020e851d0..c78549e395 100644 --- a/ui/components/Wallet/WalletAssetListItem.tsx +++ b/ui/components/Wallet/WalletAssetListItem.tsx @@ -7,10 +7,15 @@ import CommonAssetListItem from "./AssetListItem/CommonAssetListItem" interface Props { assetAmount: CompleteAssetAmount initializationLoadingTimeExpired: boolean + onUntrustedAssetWarningClick?: (asset: CompleteAssetAmount["asset"]) => void } export default function WalletAssetListItem(props: Props): ReactElement { - const { assetAmount, initializationLoadingTimeExpired } = props + const { + assetAmount, + initializationLoadingTimeExpired, + onUntrustedAssetWarningClick, + } = props const isDoggoAsset = assetAmount.asset.symbol === "DOGGO" @@ -22,33 +27,51 @@ export default function WalletAssetListItem(props: Props): ReactElement { )} From 163f273b583d314ed8a0a4cc83a09ba343de3617 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 16 Jan 2023 14:34:54 +0100 Subject: [PATCH 078/121] Revert "Merge branch 'main' of github.com:tallycash/extension into persist-base-assets" This reverts commit 3211d560c376eaa5139a118b861307b7c172908c. --- background/assets.ts | 1 - background/lib/alchemy.ts | 2 +- background/lib/asset-similarity.ts | 2 - .../lib/tests/asset-similarity.unit.test.ts | 18 --- background/lib/token-lists.ts | 7 +- background/redux-slices/utils/asset-utils.ts | 18 +-- background/services/chain/index.ts | 10 +- background/services/enrichment/index.ts | 3 +- .../tests/transactions.integration.test.ts | 80 ---------- .../services/enrichment/transactions.ts | 129 ++++++----------- background/services/indexing/index.ts | 18 --- background/tests/factories.ts | 49 ++----- ui/_locales/en/messages.json | 9 -- ui/components/NFTS_update/NFTCollection.tsx | 137 +++++++----------- ui/components/NFTS_update/NFTList.tsx | 14 +- ui/components/Shared/SharedAssetInput.tsx | 1 - ui/components/Shared/SharedButtonUp.tsx | 4 +- .../AssetListItem/CommonAssetListItem.tsx | 51 +------ ui/components/Wallet/AssetListItem/styles.ts | 2 - ui/components/Wallet/AssetWarningSlideUp.tsx | 135 ----------------- ui/components/Wallet/WalletAssetList.tsx | 68 ++++----- ui/components/Wallet/WalletAssetListItem.tsx | 53 ++----- 22 files changed, 168 insertions(+), 643 deletions(-) delete mode 100644 background/lib/tests/asset-similarity.unit.test.ts delete mode 100644 background/services/enrichment/tests/transactions.integration.test.ts delete mode 100644 ui/components/Wallet/AssetWarningSlideUp.tsx diff --git a/background/assets.ts b/background/assets.ts index 057695d2f5..9e5d8e8fda 100644 --- a/background/assets.ts +++ b/background/assets.ts @@ -120,7 +120,6 @@ export type AnyAsset = | FiatCurrency | FungibleAsset | SmartContractFungibleAsset - | NetworkBaseAsset /** * An asset that can be swapped with our current providers diff --git a/background/lib/alchemy.ts b/background/lib/alchemy.ts index 761f58d290..4e2d7496b7 100644 --- a/background/lib/alchemy.ts +++ b/background/lib/alchemy.ts @@ -44,7 +44,7 @@ export async function getAssetTransfers( fromBlock: number, toBlock?: number, order: "asc" | "desc" = "desc", - maxCount = 25 + maxCount = 1000 ): Promise { const { address: account, network } = addressOnNetwork diff --git a/background/lib/asset-similarity.ts b/background/lib/asset-similarity.ts index d236c23a5a..67b87ecd89 100644 --- a/background/lib/asset-similarity.ts +++ b/background/lib/asset-similarity.ts @@ -97,8 +97,6 @@ export function findClosestAssetIndex( export function mergeAssets(asset1: AnyAsset, asset2: AnyAsset): AnyAsset { return { ...asset1, - ...("coinType" in asset1 ? { coinType: asset1.coinType } : {}), - ...("coinType" in asset2 ? { coinType: asset2.coinType } : {}), metadata: { ...asset1.metadata, ...asset2.metadata, diff --git a/background/lib/tests/asset-similarity.unit.test.ts b/background/lib/tests/asset-similarity.unit.test.ts deleted file mode 100644 index ba5fc8d3d7..0000000000 --- a/background/lib/tests/asset-similarity.unit.test.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { - createNetworkBaseAsset, - createSmartContractAsset, -} from "../../tests/factories" -import { mergeAssets } from "../asset-similarity" - -describe("Asset Similarity", () => { - describe("mergeAssets", () => { - it("Should preserve coinType when merging assets", () => { - const mergedAsset = mergeAssets( - createSmartContractAsset(), - createNetworkBaseAsset() - ) - - expect("coinType" in mergedAsset).toBe(true) - }) - }) -}) diff --git a/background/lib/token-lists.ts b/background/lib/token-lists.ts index b19dc97730..267f8a187d 100644 --- a/background/lib/token-lists.ts +++ b/background/lib/token-lists.ts @@ -148,9 +148,10 @@ export function mergeAssets( metadata: { ...matchingAsset.metadata, ...asset.metadata, - tokenLists: (matchingAsset.metadata?.tokenLists || [])?.concat( - asset.metadata?.tokenLists ?? [] - ), + tokenLists: + matchingAsset.metadata?.tokenLists?.concat( + asset.metadata?.tokenLists ?? [] + ) ?? [], }, } } else { diff --git a/background/redux-slices/utils/asset-utils.ts b/background/redux-slices/utils/asset-utils.ts index b9727efb70..d6696b92ef 100644 --- a/background/redux-slices/utils/asset-utils.ts +++ b/background/redux-slices/utils/asset-utils.ts @@ -10,11 +10,7 @@ import { AnyAsset, CoinGeckoAsset, } from "../../assets" -import { - BUILT_IN_NETWORK_BASE_ASSETS, - OPTIMISM, - POLYGON, -} from "../../constants" +import { BUILT_IN_NETWORK_BASE_ASSETS, OPTIMISM } from "../../constants" import { fromFixedPointNumber } from "../../lib/fixed-point" import { AnyNetwork, NetworkBaseAsset } from "../../networks" import { hardcodedMainCurrencySign } from "./constants" @@ -52,14 +48,6 @@ function isOptimismBaseAsset(asset: AnyAsset) { asset.contractAddress === OPTIMISM.baseAsset.contractAddress ) } - -function isPolygonBaseAsset(asset: AnyAsset) { - return ( - "contractAddress" in asset && - asset.contractAddress === POLYGON.baseAsset.contractAddress - ) -} - /** * Given an asset and a network, determines whether the given asset is the base * asset for the given network. Used to special-case transactions that should @@ -78,10 +66,6 @@ export function isBuiltInNetworkBaseAsset( return true } - if (network.chainID === POLYGON.chainID && isPolygonBaseAsset(asset)) { - return true - } - return ( hasCoinType(asset) && asset.symbol === network.baseAsset.symbol && diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index 5b43bcd0d3..a713939766 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -1823,11 +1823,9 @@ export default class ChainService extends BaseService { ): Promise { const provider = this.providerForNetworkOrThrow(network) const receipt = await provider.getTransactionReceipt(transaction.hash) - if (receipt) { - await this.saveTransaction( - enrichTransactionWithReceipt(transaction, receipt), - "alchemy" - ) - } + await this.saveTransaction( + enrichTransactionWithReceipt(transaction, receipt), + "alchemy" + ) } } diff --git a/background/services/enrichment/index.ts b/background/services/enrichment/index.ts index 05372c0336..ec9f03fb7a 100644 --- a/background/services/enrichment/index.ts +++ b/background/services/enrichment/index.ts @@ -133,8 +133,7 @@ export default class EnrichmentService extends BaseService { (asset): asset is SmartContractFungibleAsset => { if ( typedData.domain.verifyingContract && - "contractAddress" in asset && - asset.contractAddress + "contractAddress" in asset ) { return ( normalizeHexAddress(asset.contractAddress) === diff --git a/background/services/enrichment/tests/transactions.integration.test.ts b/background/services/enrichment/tests/transactions.integration.test.ts deleted file mode 100644 index 26d93dc246..0000000000 --- a/background/services/enrichment/tests/transactions.integration.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import sinon from "sinon" -import { ETHEREUM } from "../../../constants" - -import { - createChainService, - createIndexingService, - createNameService, - createAnyEVMBlock, - makeSerialFallbackProvider, -} from "../../../tests/factories" -import SerialFallbackProvider from "../../chain/serial-fallback-provider" -import { annotationsFromLogs } from "../transactions" - -// These logs reference transaction https://etherscan.io/tx/0x0ba306853f8be38d54327675f14694d582a14759b851f2126dd900bef0aff840 -// prettier-ignore -const TEST_ERC20_LOGS = [ { contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e", data: "0x00000000000000000000000000000000000000000000057d723eb063126abeaf", topics: [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000009eef87f4c08d8934cb2a3309df4dec5635338115", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", ], }, { contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e", data: "0xffffffffffffffffffffffffffffffffffffffffffffcf2f0540996d1fc52a54", topics: [ "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", "0x0000000000000000000000009eef87f4c08d8934cb2a3309df4dec5635338115", "0x000000000000000000000000def1c0ded9bec7f1a1670819833240f027b25eff", ], }, { contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", data: "0x000000000000000000000000000000000000000000000000000000060869fd65", topics: [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x0000000000000000000000009a834b70c07c81a9fcd6f22e842bf002fbffbe4d", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", ], }, { contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e", data: "0x00000000000000000000000000000000000000000000057d723eb063126abeaf", topics: [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", "0x0000000000000000000000009a834b70c07c81a9fcd6f22e842bf002fbffbe4d", ], }, { contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e", data: "0xffffffffffffffffffffffffffffffffffffffffffe0bf27eab0a412146f26d0", topics: [ "0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", "0x000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564", ], }, { contractAddress: "0x9A834b70C07C81a9fcD6F22E842BF002fBfFbe4D", data: "0x00000000000000000000000000000000000000000000057d723eb063126abeaffffffffffffffffffffffffffffffffffffffffffffffffffffffff9f796029b0000000000000000000000000000000000000000000010c5f0437647d68615730000000000000000000000000000000000000000000002091686803a9aa2714ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffbc897", topics: [ "0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67", "0x000000000000000000000000e592427a0aece92de3edee1f18e0157c05861564", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", ], }, { contractAddress: "0x22F9dCF4647084d6C31b2765F6910cd85C178C18", data: "0x00000000000000000000000000000012556e6973776170563300000000000000000000000000000000000000853d955acef822db058eb8505911ed77f175b99e000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000057d723eb063126abeaf000000000000000000000000000000000000000000000000000000060869fd65", topics: [ "0xe59e71a14fe90157eedc866c4f8c767d3943d6b6b2e8cd64dddcc92ab4c55af8", ], }, { contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", data: "0x00000000000000000000000000000000000000000000000000000000079b57dc", topics: [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", "0x00000000000000000000000099b36fdbc582d113af36a21eba06bfeab7b9be12", ], }, { contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", data: "0x0000000000000000000000000000000000000000000000000000000600cea589", topics: [ "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef", "0x00000000000000000000000022f9dcf4647084d6c31b2765f6910cd85c178c18", "0x0000000000000000000000009eef87f4c08d8934cb2a3309df4dec5635338115", ], }, { contractAddress: "0xDef1C0ded9bec7F1a1670819833240f027b25EfF", data: "0x000000000000000000000000853d955acef822db058eb8505911ed77f175b99e000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000000000000000000000057d723eb063126abeaf0000000000000000000000000000000000000000000000000000000600cea589", topics: [ "0x0f6672f78a59ba8e5e5b5d38df3ebc67f3c792e2c9259b8d97d7f00dd78ba1b3", "0x0000000000000000000000009eef87f4c08d8934cb2a3309df4dec5635338115", ], }, ] - -describe("Enrichment Service Transactions", () => { - const sandbox = sinon.createSandbox() - - beforeEach(async () => { - sandbox.restore() - }) - - describe("annotationsFromLogs", () => { - it("Should only create subannotations from logs with relevant addresses in them", async () => { - const chainServicePromise = createChainService() - const indexingServicePromise = createIndexingService({ - chainService: chainServicePromise, - }) - const nameServicePromise = createNameService({ - chainService: chainServicePromise, - }) - - const [chainService, indexingService, nameService] = await Promise.all([ - chainServicePromise, - indexingServicePromise, - nameServicePromise, - ]) - - await chainService.addAccountToTrack({ - address: "0x9eef87f4c08d8934cb2a3309df4dec5635338115", - network: ETHEREUM, - }) - - await indexingService.addCustomAsset({ - contractAddress: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", - symbol: "USDC", - name: "USDC Coin", - decimals: 6, - homeNetwork: ETHEREUM, - }) - - await indexingService.addCustomAsset({ - contractAddress: "0x853d955aCEf822Db058eb8505911ED77F175b99e", - symbol: "FRAX", - name: "FRAX Token", - decimals: 18, - homeNetwork: ETHEREUM, - }) - - sandbox - .stub(chainService, "providerForNetworkOrThrow") - .returns(makeSerialFallbackProvider() as SerialFallbackProvider) - - const subannotations = await annotationsFromLogs( - chainService, - indexingService, - nameService, - TEST_ERC20_LOGS, - ETHEREUM, - 2, - Date.now(), - createAnyEVMBlock() - ) - - expect(subannotations.length).toBe(2) - }) - }) -}) diff --git a/background/services/enrichment/transactions.ts b/background/services/enrichment/transactions.ts index cbc2fdf691..9581d01b84 100644 --- a/background/services/enrichment/transactions.ts +++ b/background/services/enrichment/transactions.ts @@ -8,7 +8,6 @@ import { import { SmartContractFungibleAsset, isSmartContractFungibleAsset, - AnyAsset, } from "../../assets" import { enrichAssetAmountWithDecimalValues } from "../../redux-slices/utils/asset-utils" @@ -21,7 +20,6 @@ import { TransactionAnnotation, PartialTransactionRequestWithFrom, EnrichedEVMTransactionRequest, - EnrichedAddressOnNetwork, } from "./types" import { getDistinctRecipentAddressesFromERC20Logs, @@ -30,30 +28,64 @@ import { import { enrichAddressOnNetwork } from "./addresses" import { OPTIMISM } from "../../constants" import { parseLogsForWrappedDepositsAndWithdrawals } from "../../lib/wrappedAsset" -import { - ERC20TransferLog, - parseERC20Tx, - parseLogsForERC20Transfers, -} from "../../lib/erc20" +import { parseERC20Tx, parseLogsForERC20Transfers } from "../../lib/erc20" import { isDefined, isFulfilledPromise } from "../../lib/utils/type-guards" import { unsignedTransactionFromEVMTransaction } from "../chain/utils" -async function buildSubannotations( +async function annotationsFromLogs( chainService: ChainService, + indexingService: IndexingService, nameService: NameService, - relevantTransferLogs: ERC20TransferLog[], - assets: AnyAsset[], - addressEnrichmentsByAddress: { - [k: string]: EnrichedAddressOnNetwork - }, + logs: EVMLog[], network: EVMNetwork, desiredDecimals: number, resolvedTime: number, block: AnyEVMBlock | undefined -) { +): Promise { + const assets = indexingService.getCachedAssets(network) + + const accountAddresses = (await chainService.getAccountsToTrack()).map( + (account) => account.address + ) + + const tokenTransferLogs = [ + ...parseLogsForERC20Transfers(logs), + ...parseLogsForWrappedDepositsAndWithdrawals(logs), + ] + + const relevantTransferLogs = getERC20LogsForAddresses( + tokenTransferLogs, + accountAddresses + ) + const relevantAddresses = + getDistinctRecipentAddressesFromERC20Logs(relevantTransferLogs).map( + normalizeEVMAddress + ) + + // Look up transfer log names, then flatten to an address -> name map. + const addressEnrichmentsByAddress = Object.fromEntries( + ( + await Promise.allSettled( + relevantAddresses.map( + async (address) => + [ + address, + await enrichAddressOnNetwork(chainService, nameService, { + address, + network, + }), + ] as const + ) + ) + ) + .filter(isFulfilledPromise) + .map(({ value }) => value) + .filter(([, annotation]) => isDefined(annotation)) + ) + const subannotations = ( await Promise.allSettled( - relevantTransferLogs.map( + tokenTransferLogs.map( async ({ contractAddress, amount, @@ -112,73 +144,6 @@ async function buildSubannotations( return subannotations } -export async function annotationsFromLogs( - chainService: ChainService, - indexingService: IndexingService, - nameService: NameService, - logs: EVMLog[], - network: EVMNetwork, - desiredDecimals: number, - resolvedTime: number, - block: AnyEVMBlock | undefined -): Promise { - const assets = indexingService.getCachedAssets(network) - - const accountAddresses = (await chainService.getAccountsToTrack()).map( - (account) => account.address - ) - - const tokenTransferLogs = [ - ...parseLogsForERC20Transfers(logs), - ...parseLogsForWrappedDepositsAndWithdrawals(logs), - ] - - const relevantTransferLogs = getERC20LogsForAddresses( - tokenTransferLogs, - accountAddresses - ) - - const relevantAddresses = - getDistinctRecipentAddressesFromERC20Logs(relevantTransferLogs).map( - normalizeEVMAddress - ) - - // Look up transfer log names, then flatten to an address -> name map. - const addressEnrichmentsByAddress = Object.fromEntries( - ( - await Promise.allSettled( - relevantAddresses.map( - async (address) => - [ - address, - await enrichAddressOnNetwork(chainService, nameService, { - address, - network, - }), - ] as const - ) - ) - ) - .filter(isFulfilledPromise) - .map(({ value }) => value) - .filter(([, annotation]) => isDefined(annotation)) - ) - - const subannotations = await buildSubannotations( - chainService, - nameService, - relevantTransferLogs, - assets, - addressEnrichmentsByAddress, - network, - desiredDecimals, - resolvedTime, - block - ) - - return subannotations -} - /** * Resolve an annotation for a partial transaction request, or a pending * or mined transaction. diff --git a/background/services/indexing/index.ts b/background/services/indexing/index.ts index ef8c1148ad..b9c7196f2e 100644 --- a/background/services/indexing/index.ts +++ b/background/services/indexing/index.ts @@ -412,24 +412,6 @@ export default class IndexingService extends BaseService { } private async connectChainServiceEvents(): Promise { - // listen for assetTransfers, and if we find them, track those tokens - // TODO update for NFTs - this.chainService.emitter.on( - "assetTransfers", - async ({ addressNetwork, assetTransfers }) => { - assetTransfers.forEach((transfer) => { - const fungibleAsset = transfer.assetAmount - .asset as SmartContractFungibleAsset - if (fungibleAsset.contractAddress && fungibleAsset.decimals) { - this.addTokenToTrackByContract( - addressNetwork, - fungibleAsset.contractAddress - ) - } - }) - } - ) - this.chainService.emitter.on( "newAccountToTrack", async (addressOnNetwork) => { diff --git a/background/tests/factories.ts b/background/tests/factories.ts index ced119937a..3b99008681 100644 --- a/background/tests/factories.ts +++ b/background/tests/factories.ts @@ -29,7 +29,6 @@ import { LegacyEVMTransactionRequest, AnyEVMBlock, BlockPrices, - NetworkBaseAsset, } from "../networks" import { AnalyticsService, @@ -326,28 +325,24 @@ export const makeSerialFallbackProvider = async getFeeData() { return makeEthersFeeData() } - - async getCode() { - return "false" - } } return new MockSerialFallbackProvider() } -const getRandomStr = (length: number) => { - let result = "" - - while (result.length < length) { - result += Math.random().toString(36).slice(2) - } - - return result.slice(0, length) -} - export const createSmartContractAsset = ( overrides: Partial = {} ): SmartContractFungibleAsset => { + const getRandomStr = (length: number) => { + let result = "" + + while (result.length < length) { + result += Math.random().toString(36).slice(2) + } + + return result.slice(0, length) + } + const symbol = getRandomStr(3) const asset = { metadata: { @@ -374,30 +369,6 @@ export const createSmartContractAsset = ( } } -export const createNetworkBaseAsset = ( - overrides: Partial = {} -): NetworkBaseAsset => { - const symbol = getRandomStr(3) - const asset: NetworkBaseAsset = { - metadata: { - coinGeckoID: "ethereum", - logoURL: "http://example.com/foo.png", - tokenLists: [], - }, - name: `${symbol} Network`, - symbol, - decimals: 18, - coinType: 60, - chainID: "1", - contractAddress: createRandom0xHash(), - } - - return { - ...asset, - ...overrides, - } -} - /** * @param asset Any type of asset * @param price Price, e.g. 1.5 => 1.5$ diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index e07d04faf3..c06382c4ab 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -701,15 +701,6 @@ "transactionFrom": "From:", "loadingActivities": "Digging deeper..." }, - "trustedAssets": { - "notTrusted": "Asset isn't trusted", - "notVerified": "Asset has not been verified yet!", - "trustExplainer": "Only transact with assets you trust.", - "assetImported": "Asset imported from transaction history", - "name": "Name", - "contract": "Contract address", - "close": "Close" - }, "banner": { "bannerTitle": "New Odyssey week!", "emptyBannerContent": "Check out Arbitrum Odyssey campaign", diff --git a/ui/components/NFTS_update/NFTCollection.tsx b/ui/components/NFTS_update/NFTCollection.tsx index b0a7203c9d..363872dbb1 100644 --- a/ui/components/NFTS_update/NFTCollection.tsx +++ b/ui/components/NFTS_update/NFTCollection.tsx @@ -13,14 +13,13 @@ import SharedSkeletonLoader from "../Shared/SharedSkeletonLoader" export default function NFTCollection(props: { collection: NFTCollectionCached - isExpanded: boolean - setExpandedID: (id: string | null, owner: string | null) => void openPreview: (current: NFTWithCollection) => void }): ReactElement { - const { collection, openPreview, isExpanded, setExpandedID } = props + const { collection, openPreview } = props const { id, owner, network, nfts, nftCount, hasNextPage } = collection const dispatch = useBackgroundDispatch() + const [isExpanded, setIsExpanded] = useState(false) const [isLoading, setIsLoading] = useState(false) // initial update of collection const [isUpdating, setIsUpdating] = useState(false) // update on already loaded collection const [wasUpdated, setWasUpdated] = useState(false) // to fetch NFTs data only once during the component lifespan @@ -105,72 +104,62 @@ export default function NFTCollection(props: { } }, [fetchCollection, isExpanded, wasUpdated]) - const toggleCollection = () => - isExpanded ? setExpandedID(null, null) : setExpandedID(id, owner) + const toggleCollection = () => setIsExpanded((val) => !val) const onItemClick = (nft: NFT) => openPreview({ nft, collection }) return ( <> -
      -
    • + - - {nfts.length === 1 ? ( - onItemClick(nfts[0])} + {nfts.length === 1 ? ( + onItemClick(nfts[0])} + /> + ) : ( + + )} + {isExpanded && ( + <> + {nfts.map((nft) => ( + + ))} + - ) : ( - - )} - {isExpanded && ( - <> - {nfts.map((nft) => ( - - ))} - -
      - - )} - -
    • -
      +
      + + )} + + - ) } diff --git a/ui/components/NFTS_update/NFTList.tsx b/ui/components/NFTS_update/NFTList.tsx index 7c31194f05..7bbbab72d0 100644 --- a/ui/components/NFTS_update/NFTList.tsx +++ b/ui/components/NFTS_update/NFTList.tsx @@ -3,7 +3,7 @@ import { NFTWithCollection, } from "@tallyho/tally-background/redux-slices/nfts_update" import { selectIsReloadingNFTs } from "@tallyho/tally-background/redux-slices/selectors" -import React, { ReactElement, useCallback, useState } from "react" +import React, { ReactElement, useState } from "react" import { useBackgroundSelector } from "../../hooks" import SharedLoadingDoggo from "../Shared/SharedLoadingDoggo" import SharedSlideUpMenu from "../Shared/SharedSlideUpMenu" @@ -19,14 +19,6 @@ export default function NFTList(props: { const [isPreviewOpen, setIsPreviewOpen] = useState(false) const [currentNFTPreview, setCurrentNFTPreview] = useState(null) - const [currentExpandedID, setCurrentExpandedID] = useState( - null - ) - const setExpandedID = useCallback( - (id: string | null, owner: string | null) => - setCurrentExpandedID(`${id}_${owner}`), // TODO: owner can be removed after we will merge collections owned by multiple accounts - [] - ) const isReloading = useBackgroundSelector(selectIsReloadingNFTs) @@ -68,10 +60,6 @@ export default function NFTList(props: { key={`${collection.id}_${collection.owner}`} openPreview={openPreview} collection={collection} - setExpandedID={setExpandedID} - isExpanded={ - `${collection.id}_${collection.owner}` === currentExpandedID - } /> ) )} diff --git a/ui/components/Shared/SharedAssetInput.tsx b/ui/components/Shared/SharedAssetInput.tsx index 8be770457d..32e00ab2e9 100644 --- a/ui/components/Shared/SharedAssetInput.tsx +++ b/ui/components/Shared/SharedAssetInput.tsx @@ -141,7 +141,6 @@ function SelectAssetMenuContent( return ( asset.symbol.toLowerCase().includes(searchTerm.toLowerCase()) || ("contractAddress" in asset && - asset.contractAddress && searchTerm.startsWith("0x") && normalizeEVMAddress(asset.contractAddress).includes( // The replace handles `normalizeEVMAddress`'s diff --git a/ui/components/Shared/SharedButtonUp.tsx b/ui/components/Shared/SharedButtonUp.tsx index 14f9bc3a37..174e0beeab 100644 --- a/ui/components/Shared/SharedButtonUp.tsx +++ b/ui/components/Shared/SharedButtonUp.tsx @@ -45,8 +45,8 @@ export default function SharedButtonUp(props: { - - ) -} - -type AssetWarningSlideUpProps = { - asset: AnyAsset | null - close: () => void -} - -export default function AssetWarningSlideUp( - props: AssetWarningSlideUpProps -): ReactElement { - const { t } = useTranslation("translation", { - keyPrefix: "wallet.trustedAssets", - }) - const { asset, close } = props - return ( - - - - - {t("notVerified")} -
      - {t("trustExplainer")} -
      -
        -
      • - {t("name")} -
        - {`${asset?.name} (${asset?.symbol})`} -
        -
      • -
      • - {t("contract")} -
        - -
        -
      • -
      - - {t("close")} - -
      - ) -} diff --git a/ui/components/Wallet/WalletAssetList.tsx b/ui/components/Wallet/WalletAssetList.tsx index 00dd10d27b..c15d4335bf 100644 --- a/ui/components/Wallet/WalletAssetList.tsx +++ b/ui/components/Wallet/WalletAssetList.tsx @@ -1,59 +1,43 @@ // @ts-check - -import React, { ReactElement, useState } from "react" +// +import React, { ReactElement } from "react" import { CompleteAssetAmount } from "@tallyho/tally-background/redux-slices/accounts" import { useTranslation } from "react-i18next" import WalletAssetListItem from "./WalletAssetListItem" -import AssetWarningSlideUp from "./AssetWarningSlideUp" -type WalletAssetListProps = { +interface Props { assetAmounts: CompleteAssetAmount[] initializationLoadingTimeExpired: boolean } -export default function WalletAssetList( - props: WalletAssetListProps -): ReactElement { +export default function WalletAssetList(props: Props): ReactElement { const { t } = useTranslation("translation", { keyPrefix: "wallet.activities", }) const { assetAmounts, initializationLoadingTimeExpired } = props - - const [warnedAsset, setWarnedAsset] = useState< - CompleteAssetAmount["asset"] | null - >(null) - if (!assetAmounts) return <> - return ( - <> - setWarnedAsset(null)} - /> -
        - {assetAmounts.map((assetAmount) => ( - setWarnedAsset(asset)} - /> - ))} - {!initializationLoadingTimeExpired && ( -
      • {t("loadingActivities")}
      • - )} - -
      - +
        + {assetAmounts.map((assetAmount) => ( + + ))} + {!initializationLoadingTimeExpired && ( +
      • {t("loadingActivities")}
      • + )} + +
      ) } diff --git a/ui/components/Wallet/WalletAssetListItem.tsx b/ui/components/Wallet/WalletAssetListItem.tsx index c78549e395..b020e851d0 100644 --- a/ui/components/Wallet/WalletAssetListItem.tsx +++ b/ui/components/Wallet/WalletAssetListItem.tsx @@ -7,15 +7,10 @@ import CommonAssetListItem from "./AssetListItem/CommonAssetListItem" interface Props { assetAmount: CompleteAssetAmount initializationLoadingTimeExpired: boolean - onUntrustedAssetWarningClick?: (asset: CompleteAssetAmount["asset"]) => void } export default function WalletAssetListItem(props: Props): ReactElement { - const { - assetAmount, - initializationLoadingTimeExpired, - onUntrustedAssetWarningClick, - } = props + const { assetAmount, initializationLoadingTimeExpired } = props const isDoggoAsset = assetAmount.asset.symbol === "DOGGO" @@ -27,51 +22,33 @@ export default function WalletAssetListItem(props: Props): ReactElement { )} From 6751255592415496eac919dad9a7b9a9cef78c18 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 16 Jan 2023 14:56:49 +0100 Subject: [PATCH 079/121] Fix the issue after merging --- background/services/indexing/db.ts | 6 +++++- background/tests/factories.ts | 1 + ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx | 7 +++++-- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/background/services/indexing/db.ts b/background/services/indexing/db.ts index 56f6e31236..479934194f 100644 --- a/background/services/indexing/db.ts +++ b/background/services/indexing/db.ts @@ -64,7 +64,11 @@ export type CachedTokenList = TokenListCitation & { */ function assetID(asset: AnyAsset): string { const id = `${asset.symbol}` - if (!("contractAddress" in asset) || "coinType" in asset) { + if ( + !("contractAddress" in asset) || + !("homeNetwork" in asset) || + "coinType" in asset + ) { return id } const network = asset.homeNetwork diff --git a/background/tests/factories.ts b/background/tests/factories.ts index ddf441c14a..ced119937a 100644 --- a/background/tests/factories.ts +++ b/background/tests/factories.ts @@ -388,6 +388,7 @@ export const createNetworkBaseAsset = ( symbol, decimals: 18, coinType: 60, + chainID: "1", contractAddress: createRandom0xHash(), } diff --git a/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx b/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx index 4d0374a039..dd6ced23bd 100644 --- a/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx +++ b/ui/components/Wallet/AssetListItem/CommonAssetListItem.tsx @@ -3,7 +3,7 @@ import { Link } from "react-router-dom" import { CompleteAssetAmount } from "@tallyho/tally-background/redux-slices/accounts" import { useTranslation } from "react-i18next" -import { isNetworkBaseAsset } from "@tallyho/tally-background/redux-slices/utils/asset-utils" +import { isBuiltInNetworkBaseAsset } from "@tallyho/tally-background/redux-slices/utils/asset-utils" import { selectCurrentNetwork } from "@tallyho/tally-background/redux-slices/selectors" import SharedLoadingSpinner from "../../Shared/SharedLoadingSpinner" import SharedAssetIcon from "../../Shared/SharedAssetIcon" @@ -37,7 +37,10 @@ export default function CommonAssetListItem( // road. Eventually, assets can be flagged as trusted by adding them to an // "internal" token list that users can export and share. const numTokenLists = assetAmount?.asset?.metadata?.tokenLists?.length ?? 0 - const baseAsset = isNetworkBaseAsset(assetAmount?.asset, selectedNetwork) + const baseAsset = isBuiltInNetworkBaseAsset( + assetAmount?.asset, + selectedNetwork + ) const contractAddress = "contractAddress" in assetAmount.asset From ecbcaed7dc6e4d98e1859d5706a33f25ef125e1a Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Mon, 16 Jan 2023 15:04:26 +0100 Subject: [PATCH 080/121] Fix lint issue --- background/redux-slices/utils/asset-utils.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/background/redux-slices/utils/asset-utils.ts b/background/redux-slices/utils/asset-utils.ts index 48c12172dc..b9727efb70 100644 --- a/background/redux-slices/utils/asset-utils.ts +++ b/background/redux-slices/utils/asset-utils.ts @@ -10,7 +10,11 @@ import { AnyAsset, CoinGeckoAsset, } from "../../assets" -import { BUILT_IN_NETWORK_BASE_ASSETS, OPTIMISM, POLYGON } from "../../constants" +import { + BUILT_IN_NETWORK_BASE_ASSETS, + OPTIMISM, + POLYGON, +} from "../../constants" import { fromFixedPointNumber } from "../../lib/fixed-point" import { AnyNetwork, NetworkBaseAsset } from "../../networks" import { hardcodedMainCurrencySign } from "./constants" From 807f24f82fe66108e3b29ae0264405ca13f3a9c3 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Mon, 16 Jan 2023 17:17:25 +0100 Subject: [PATCH 081/121] Reduce number of requests to NFTs provider - avoid fetching nfts transfers during first fetch for given account - fetch incoming nfts transfers - fetch nfts from database if there are no known transfers for a given collection --- background/lib/nfts_update.ts | 6 +- background/lib/simple-hash_update.ts | 30 ++++-- background/nfts.ts | 5 +- background/redux-slices/nfts_update.ts | 4 +- background/services/nfts/index.ts | 133 ++++++++++++++++++++----- 5 files changed, 138 insertions(+), 40 deletions(-) diff --git a/background/lib/nfts_update.ts b/background/lib/nfts_update.ts index 7daa74bc8b..dc29c4c8aa 100644 --- a/background/lib/nfts_update.ts +++ b/background/lib/nfts_update.ts @@ -117,7 +117,7 @@ export function getNFTCollections( ) } -export async function getTransferredNFTs( +export async function getNFTsTransfers( accounts: AddressOnNetwork[], timestamp: UNIXTime ): Promise { @@ -134,11 +134,11 @@ export async function getTransferredNFTs( { addresses: new Set(), chains: new Set() } ) - const removedNFTs = await getSimpleHashNFTsTransfers( + const transfers = await getSimpleHashNFTsTransfers( [...addresses], [...chains], timestamp ) - return removedNFTs + return transfers } diff --git a/background/lib/simple-hash_update.ts b/background/lib/simple-hash_update.ts index c3c0f9a6d6..c8305c6639 100644 --- a/background/lib/simple-hash_update.ts +++ b/background/lib/simple-hash_update.ts @@ -68,6 +68,11 @@ type SimpleHashTransferModel = { chain: SupportedChain from_address: string | null to_address: string | null + nft_details?: { + collection?: { + collection_id: string + } + } } type SimpleHashNFTsByWalletAPIResponse = { @@ -311,6 +316,7 @@ export async function getSimpleHashNFTsTransfers( requestURL.searchParams.set("chains", getChainIDsNames(chainIDs)) requestURL.searchParams.set("wallet_addresses", addresses.join(",")) requestURL.searchParams.set("from_timestamp", fromTimestamp.toString()) + requestURL.searchParams.set("include_nft_details", "1") } try { @@ -323,32 +329,36 @@ export async function getSimpleHashNFTsTransfers( const { transfers, next } = result - const removedNFTs: TransferredNFT[] = transfers.flatMap((transfer) => - transfer.nft_id && - transfer.from_address && - addresses.some((address) => - sameEVMAddress(address, transfer.from_address) - ) + const transferDetails: TransferredNFT[] = transfers.flatMap((transfer) => + transfer.nft_id && (transfer.from_address || transfer.to_address) ? { id: transfer.nft_id, chainID: SIMPLE_HASH_CHAIN_TO_ID[transfer.chain].toString(), - address: transfer.from_address, + from: transfer.from_address, + to: transfer.to_address, + type: addresses.some((address) => + sameEVMAddress(address, transfer.from_address) + ) + ? "sell" + : "buy", + collectionID: + transfer.nft_details?.collection?.collection_id ?? null, } : [] ) if (next) { - const nextPageRemovedNFTs = await getSimpleHashNFTsTransfers( + const nextPageTransferDetails = await getSimpleHashNFTsTransfers( addresses, chainIDs, fromTimestamp, next ) - return [...removedNFTs, ...nextPageRemovedNFTs] + return [...transferDetails, ...nextPageTransferDetails] } - return removedNFTs + return transferDetails } catch (err) { logger.error("Error retrieving NFTs ", err) } diff --git a/background/nfts.ts b/background/nfts.ts index c5d500fe62..a9989e9a02 100644 --- a/background/nfts.ts +++ b/background/nfts.ts @@ -79,5 +79,8 @@ export type NFTCollection = { export type TransferredNFT = { id: string chainID: string - address: string + from: string | null + to: string | null + type: "sell" | "buy" + collectionID: string | null } diff --git a/background/redux-slices/nfts_update.ts b/background/redux-slices/nfts_update.ts index 311e2027f4..1c1981eb5f 100644 --- a/background/redux-slices/nfts_update.ts +++ b/background/redux-slices/nfts_update.ts @@ -255,7 +255,9 @@ const NFTsSlice = createSlice({ immerState, { payload: transferredNFTs }: { payload: TransferredNFT[] } ) => { - transferredNFTs.forEach(({ id: nftID, chainID, address }) => { + transferredNFTs.forEach(({ id: nftID, chainID, from: address }) => { + if (!address) return + const normalizedAddress = normalizeEVMAddress(address) Object.keys(immerState.nfts[chainID][normalizedAddress] ?? {}).forEach( (collectionID) => { diff --git a/background/services/nfts/index.ts b/background/services/nfts/index.ts index 95ee110ad0..bd090e971b 100644 --- a/background/services/nfts/index.ts +++ b/background/services/nfts/index.ts @@ -3,7 +3,7 @@ import { FeatureFlags, isEnabled } from "../../features" import { getNFTCollections, getNFTs, - getTransferredNFTs, + getNFTsTransfers, } from "../../lib/nfts_update" import { getSimpleHashNFTs } from "../../lib/simple-hash_update" import { POAP_COLLECTION_ID } from "../../lib/poap_update" @@ -13,7 +13,7 @@ import ChainService from "../chain" import { ServiceCreatorFunction, ServiceLifecycleEvents } from "../types" import { getOrCreateDB, NFTsDatabase } from "./db" -import { getUNIXTimestamp } from "../../lib/utils" +import { getUNIXTimestamp, normalizeEVMAddress } from "../../lib/utils" import { MINUTE } from "../../constants" interface Events extends ServiceLifecycleEvents { @@ -30,12 +30,17 @@ interface Events extends ServiceLifecycleEvents { } type NextPageURLsMap = { [collectionID: string]: { [address: string]: string } } +type FreshCollectionsMap = { + [collectionID: string]: { [address: string]: boolean } +} export default class NFTsService extends BaseService { #nextPageUrls: NextPageURLsMap = {} #transfersLookupTimestamp: number + #freshCollections: FreshCollectionsMap = {} + static create: ServiceCreatorFunction< Events, NFTsService, @@ -70,7 +75,8 @@ export default class NFTsService extends BaseService { private async connectChainServiceEvents(): Promise { this.chainService.emitter.once("serviceStarted").then(async () => { this.emitter.emit("isReloadingNFTs", true) - await this.refreshCollections() + await this.initializeCollections() + this.#transfersLookupTimestamp = getUNIXTimestamp(Date.now() - 5 * MINUTE) const collections = await this.db.getAllCollections() this.emitter.emit("initializeNFTs", collections) @@ -81,26 +87,34 @@ export default class NFTsService extends BaseService { "newAccountToTrack", async (addressOnNetwork) => { this.emitter.emit("isReloadingNFTs", true) - await this.refreshCollections([addressOnNetwork]) + await this.initializeCollections([addressOnNetwork]) this.emitter.emit("isReloadingNFTs", false) } ) } + async initializeCollections(accounts?: AddressOnNetwork[]): Promise { + const accountsToFetch = + accounts ?? (await this.chainService.getAccountsToTrack()) + + if (accountsToFetch.length) { + await this.fetchCollections(accountsToFetch) + await this.fetchPOAPs(accountsToFetch) + } + } + async refreshCollections(accounts?: AddressOnNetwork[]): Promise { const accountsToFetch = accounts ?? (await this.chainService.getAccountsToTrack()) if (!accountsToFetch.length) return - await this.removeTransferredNFTs(accountsToFetch) - await this.fetchCollections(accountsToFetch) - // prefetch POAPs to avoid loading empty POAPs collections from UI - await Promise.allSettled( - accountsToFetch.map((account) => - this.fetchNFTsFromCollection(POAP_COLLECTION_ID, account) - ) - ) + const transfers = await this.fetchTransferredNFTs(accountsToFetch) + + if (transfers.sold.length || transfers.bought.length) { + await this.fetchCollections(accountsToFetch) // refetch only if there are some transfers + } + await this.fetchPOAPs(accountsToFetch) } async fetchCollections(accounts: AddressOnNetwork[]): Promise { @@ -122,14 +136,42 @@ export default class NFTsService extends BaseService { collectionID: string, account: AddressOnNetwork ): Promise { + if ( + this.#freshCollections[collectionID]?.[ + normalizeEVMAddress(account.address) + ] + ) { + await this.fetchNFTsFromDatabase(collectionID, account) + } else { + await Promise.allSettled( + getNFTs([account], [collectionID]).map(async (request) => { + const { nfts, nextPageURLs } = await request + await this.updateSavedNFTs(collectionID, account, nfts, nextPageURLs) + }) + ) + } + } + + async fetchPOAPs(accounts: AddressOnNetwork[]): Promise { await Promise.allSettled( - getNFTs([account], [collectionID]).map(async (request) => { - const { nfts, nextPageURLs } = await request - await this.updateSavedNFTs(collectionID, account, nfts, nextPageURLs) - }) + accounts.map((account) => + this.fetchNFTsFromCollection(POAP_COLLECTION_ID, account) + ) ) } + async fetchNFTsFromDatabase( + collectionID: string, + account: AddressOnNetwork + ): Promise { + await this.emitter.emit("updateNFTs", { + collectionID, + account, + nfts: await this.db.getCollectionNFTsForAccount(collectionID, account), + hasNextPage: !!this.#nextPageUrls[collectionID]?.[account.address], + }) + } + async fetchNFTsFromNextPage( collectionID: string, account: AddressOnNetwork @@ -194,6 +236,8 @@ export default class NFTsService extends BaseService { this.emitter.emit("updateCollections", [updatedCollection]) } + this.setFreshCollection(collectionID, account.address, true) + const hasNextPage = !!Object.keys(nextPageURLs).length await this.emitter.emit("updateNFTs", { @@ -213,24 +257,63 @@ export default class NFTsService extends BaseService { ) } + setFreshCollection( + collectionID: string, + address: string, + isFresh: boolean + ): void { + // POAPs won't appear in transfers so we don't know if they are stale + if (collectionID === POAP_COLLECTION_ID) return + + this.#freshCollections[collectionID] ??= {} + this.#freshCollections[collectionID][normalizeEVMAddress(address)] = isFresh + } + async removeNFTsForAddress(address: string): Promise { await this.db.removeNFTsForAddress(address) } - async removeTransferredNFTs(accounts: AddressOnNetwork[]): Promise { - const removedNFTs = await getTransferredNFTs( + async fetchTransferredNFTs( + accounts: AddressOnNetwork[] + ): Promise<{ sold: TransferredNFT[]; bought: TransferredNFT[] }> { + const transfers = await getNFTsTransfers( accounts, this.#transfersLookupTimestamp ) - if (removedNFTs.length) { - // indexing transfers can take some time, let's add some margin to the timestamp - this.#transfersLookupTimestamp = getUNIXTimestamp(Date.now() - 5 * MINUTE) + // indexing transfers can take some time, let's add some margin to the timestamp + this.#transfersLookupTimestamp = getUNIXTimestamp(Date.now() - 5 * MINUTE) - await this.db.removeNFTsByIDs( - removedNFTs.map((transferred) => transferred.id) - ) - this.emitter.emit("removeTransferredNFTs", removedNFTs) + const { sold, bought } = transfers.reduce( + (acc, transfer) => { + if (transfer.type === "buy") { + acc.bought.push(transfer) + } else { + acc.sold.push(transfer) + } + return acc + }, + { sold: [], bought: [] } as { + sold: TransferredNFT[] + bought: TransferredNFT[] + } + ) + + if (bought.length) { + // mark collections with new NFTs to be refetched + bought.forEach((transfer) => { + const { collectionID, to } = transfer + if (collectionID && to) { + this.setFreshCollection(collectionID, to, false) + } + }) } + + if (sold.length) { + await this.db.removeNFTsByIDs(sold.map((transferred) => transferred.id)) + this.emitter.emit("removeTransferredNFTs", sold) + } + + return { sold, bought } } } From 0da72bad6b8d6cd240a34110eb693a9503e03cbf Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Mon, 16 Jan 2023 17:18:00 +0100 Subject: [PATCH 082/121] Fix displaying invisible, empty POAP collections --- ui/components/NFTS_update/NFTCollection.tsx | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/ui/components/NFTS_update/NFTCollection.tsx b/ui/components/NFTS_update/NFTCollection.tsx index b0a7203c9d..22e5038fe7 100644 --- a/ui/components/NFTS_update/NFTCollection.tsx +++ b/ui/components/NFTS_update/NFTCollection.tsx @@ -112,12 +112,16 @@ export default function NFTCollection(props: { return ( <> -
      +
    • Date: Tue, 17 Jan 2023 09:33:14 +0100 Subject: [PATCH 083/121] Updates for abilities header --- background/redux-slices/abilities.ts | 6 + .../redux-slices/selectors/abilities.ts | 5 + ui/_locales/en/messages.json | 7 + ui/components/Overview/AbilitiesHeader.tsx | 148 +++++++++++++----- ui/public/images/dog_abilities.svg | 19 +++ ui/public/images/tail.svg | 21 +++ 6 files changed, 163 insertions(+), 43 deletions(-) create mode 100644 ui/public/images/dog_abilities.svg create mode 100644 ui/public/images/tail.svg diff --git a/background/redux-slices/abilities.ts b/background/redux-slices/abilities.ts index c791b9bd05..c21e4172c9 100644 --- a/background/redux-slices/abilities.ts +++ b/background/redux-slices/abilities.ts @@ -11,11 +11,13 @@ type AbilitiesState = { [uuid: string]: Ability } } + hideDescription: boolean } const initialState: AbilitiesState = { filter: "incomplete", abilities: {}, + hideDescription: false, } const abilitiesSlice = createSlice({ @@ -50,6 +52,9 @@ const abilitiesSlice = createSlice({ immerState.abilities[payload.address][payload.abilityId].removedFromUi = true }, + toggleHideDescription: (immerState, { payload }: { payload: boolean }) => { + immerState.hideDescription = payload + }, }, }) @@ -58,6 +63,7 @@ export const { deleteAbility, markAbilityAsCompleted, markAbilityAsRemoved, + toggleHideDescription, } = abilitiesSlice.actions export const completeAbility = createBackgroundAsyncThunk( diff --git a/background/redux-slices/selectors/abilities.ts b/background/redux-slices/selectors/abilities.ts index 50fe826647..bc52ac79f9 100644 --- a/background/redux-slices/selectors/abilities.ts +++ b/background/redux-slices/selectors/abilities.ts @@ -35,3 +35,8 @@ export const selectAbilityCount = createSelector( selectFilteredAbilities, (abilities) => abilities.length ) + +export const selectHideDescription = createSelector( + (state: RootState) => state.abilities.hideDescription, + (hideDescription) => hideDescription +) diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index e07d04faf3..d315824993 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -440,6 +440,13 @@ "asset": "Asset", "price": "Price", "balance": "Balance" + }, + "abilities": { + "title": "Portfolio abilities", + "description": "Daylight tells you when you qualify for an airdrop, mint, or unlock.", + "seeAbilities": "See your abilities", + "new": "New", + "none": "None" } }, "passwordStrength": { diff --git a/ui/components/Overview/AbilitiesHeader.tsx b/ui/components/Overview/AbilitiesHeader.tsx index 37fbf0f5b7..af336da6fe 100644 --- a/ui/components/Overview/AbilitiesHeader.tsx +++ b/ui/components/Overview/AbilitiesHeader.tsx @@ -1,92 +1,154 @@ -import { selectAbilityCount } from "@tallyho/tally-background/redux-slices/selectors" +import { toggleHideDescription } from "@tallyho/tally-background/redux-slices/abilities" +import { + selectAbilityCount, + selectHideDescription, +} from "@tallyho/tally-background/redux-slices/selectors" +import classNames from "classnames" import React, { ReactElement } from "react" +import { useTranslation } from "react-i18next" import { useSelector } from "react-redux" import { useHistory } from "react-router-dom" +import { useBackgroundDispatch } from "../../hooks" +import SharedButton from "../Shared/SharedButton" export default function AbilitiesHeader(): ReactElement { + const { t } = useTranslation("translation", { + keyPrefix: "overview.abilities", + }) const newAbilities = useSelector(selectAbilityCount) + const hideDescription = useSelector(selectHideDescription) + const dispatch = useBackgroundDispatch() const history = useHistory() - const abilityCount = newAbilities > 0 ? `${newAbilities} New` : "None" + const ability = { + // TODO change icons + icon: newAbilities > 0 ? "dog_abilities" : "dog_abilities", + countText: newAbilities > 0 ? `${newAbilities} ${t("new")}` : t("none"), + } + + const handleClick = () => { + if (!hideDescription) { + dispatch(toggleHideDescription(true)) + } + history.push("abilities") + } return ( - <> -
      -
      -
      -
      -
      Daylight abilities
      -
      +
      +
      +
      +
      { - history.push("abilities") - }} - onKeyDown={(e) => { - if (e.key === "Enter") { - history.push("abilities") - } - }} - role="button" - tabIndex={0} - className="ability_count" + className={classNames({ + title: !hideDescription, + })} > - {abilityCount} + {t("title")}
      +
      handleClick()} + onKeyDown={(e) => { + if (e.key === "Enter") { + handleClick() + } + }} + > + {ability.countText} +
      + {!hideDescription && ( +
      +
      {t("description")}
      + handleClick()} + > + {t("seeAbilities")} + +
      + )} + - +
      ) } diff --git a/ui/public/images/dog_abilities.svg b/ui/public/images/dog_abilities.svg new file mode 100644 index 0000000000..7976e0f9fa --- /dev/null +++ b/ui/public/images/dog_abilities.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/ui/public/images/tail.svg b/ui/public/images/tail.svg new file mode 100644 index 0000000000..58af6bfee7 --- /dev/null +++ b/ui/public/images/tail.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + From 05960a34f1ba08d56a9b14f309180fd4a04d6f15 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Tue, 17 Jan 2023 10:12:40 +0100 Subject: [PATCH 084/121] Refactor for abilities header --- ui/components/Overview/AbilitiesHeader.tsx | 26 +++++++++++++++------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/ui/components/Overview/AbilitiesHeader.tsx b/ui/components/Overview/AbilitiesHeader.tsx index af336da6fe..9ad2c89f7a 100644 --- a/ui/components/Overview/AbilitiesHeader.tsx +++ b/ui/components/Overview/AbilitiesHeader.tsx @@ -37,7 +37,11 @@ export default function AbilitiesHeader(): ReactElement {
      -
      +
      ) From 1bf4e57413de92270e7fd30bee769ecba9e895cf Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Tue, 17 Jan 2023 11:02:47 +0100 Subject: [PATCH 085/121] Remove duplicated css rules --- ui/components/NFTS_update/Filters/NFTsFilters.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/ui/components/NFTS_update/Filters/NFTsFilters.tsx b/ui/components/NFTS_update/Filters/NFTsFilters.tsx index 5f49cc5190..fca9dd50b7 100644 --- a/ui/components/NFTS_update/Filters/NFTsFilters.tsx +++ b/ui/components/NFTS_update/Filters/NFTsFilters.tsx @@ -162,8 +162,6 @@ export default function NFTsFilters(): ReactElement { width: 100%; } .filter_warning { - font-size: 16px; - line-height: 24px; color: var(--green-20); } .spinner { From d9323d7bce849837e0979f84dab173cb3c4b4a43 Mon Sep 17 00:00:00 2001 From: Daedalus <0xDaedalus@users.noreply.github.com> Date: Tue, 17 Jan 2023 08:05:17 -0700 Subject: [PATCH 086/121] Separate wallet_addEthereumChain from wallet_switchEthereumChain. Now that we're building in the ability to add custom networks we need to deliniate these two methods. Lets keep wallet_switchEthereumChain functionality inside of wallet_addEthereumChain in case users try to add networks that are already supported - but give a separate path for the wallet_addEthereumChain method to interact with chainService. --- background/services/chain/index.ts | 5 + .../internal-ethereum-provider/index.ts | 103 ++++++++++++++++-- window-provider/index.ts | 2 +- 3 files changed, 102 insertions(+), 8 deletions(-) diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index 72ca9d4660..e33b498c12 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -62,6 +62,7 @@ import { OPTIMISM_GAS_ORACLE_ADDRESS, } from "./utils/optimismGasPriceOracle" import KeyringService from "../keyring" +import type { ValidatedAddEthereumChainParameter } from "../internal-ethereum-provider" // The number of blocks to query at a time for historic asset transfers. // Unfortunately there's no "right" answer here that works well across different @@ -1863,4 +1864,8 @@ export default class ChainService extends BaseService { ) } } + + addCustomChain(param: ValidatedAddEthereumChainParameter) { + console.log(param) + } } diff --git a/background/services/internal-ethereum-provider/index.ts b/background/services/internal-ethereum-provider/index.ts index f8d8802e87..ed0f552d3f 100644 --- a/background/services/internal-ethereum-provider/index.ts +++ b/background/services/internal-ethereum-provider/index.ts @@ -59,6 +59,74 @@ export type SwitchEthereumChainParameter = { chainId: string } +// https://eips.ethereum.org/EIPS/eip-3085 +export type AddEthereumChainParameter = { + chainId: string + blockExplorerUrls?: string[] + chainName?: string + iconUrls?: string[] + nativeCurrency?: { + name: string + symbol: string + decimals: number + } + rpcUrls?: string[] +} + +// Lets start with all required and work backwards +export type ValidatedAddEthereumChainParameter = { + chainId: string + blockExplorerUrl: string + chainName: string + iconUrl: string + nativeCurrency: { + name: string + symbol: string + decimals: number + } + rpcUrls: string[] +} + +const validateAddEthereumChainParameter = ({ + chainId, + chainName, + blockExplorerUrls, + iconUrls, + nativeCurrency, + rpcUrls, +}: AddEthereumChainParameter): ValidatedAddEthereumChainParameter => { + if ( + !chainId || + !chainName || + !nativeCurrency || + !blockExplorerUrls || + !blockExplorerUrls.length || + !iconUrls || + !iconUrls.length || + !rpcUrls || + !rpcUrls.length + ) { + throw new Error("Missing Chain Property") + } + + if ( + !nativeCurrency.decimals || + !nativeCurrency.name || + !nativeCurrency.symbol + ) { + throw new Error("Missing Currency Property") + } + + return { + chainId, + chainName, + nativeCurrency, + blockExplorerUrl: blockExplorerUrls[0], + iconUrl: iconUrls[0], + rpcUrls, + } +} + type DAppRequestEvent = { payload: T resolver: (result: E | PromiseLike) => void @@ -245,21 +313,33 @@ export default class InternalEthereumProviderService extends BaseService ) // TODO - actually allow adding a new ethereum chain - for now wallet_addEthereumChain // will just switch to a chain if we already support it - but not add a new one - case "wallet_addEthereumChain": + case "wallet_addEthereumChain": { + const chainInfo = params[0] as AddEthereumChainParameter + const { chainId } = chainInfo + const supportedNetwork = await this.getTrackedNetworkByChainId(chainId) + if (supportedNetwork) { + this.switchToSupportedNetwork(supportedNetwork) + return null + } + try { + const validatedParam = validateAddEthereumChainParameter(chainInfo) + return this.chainService.addCustomChain(validatedParam) + } catch (e) { + logger.error(e) + throw new EIP1193Error(EIP1193_ERROR_CODES.userRejectedRequest) + } + throw new EIP1193Error(EIP1193_ERROR_CODES.userRejectedRequest) + } case "wallet_switchEthereumChain": { const newChainId = (params[0] as SwitchEthereumChainParameter).chainId const supportedNetwork = await this.getTrackedNetworkByChainId( newChainId ) if (supportedNetwork) { - const { address } = await this.preferenceService.getSelectedAccount() - await this.chainService.markAccountActivity({ - address, - network: supportedNetwork, - }) - await this.db.setCurrentChainIdForOrigin(origin, supportedNetwork) + this.switchToSupportedNetwork(supportedNetwork) return null } + throw new EIP1193Error(EIP1193_ERROR_CODES.chainDisconnected) } case "metamask_getProviderState": // --- important MM only methods --- @@ -391,6 +471,15 @@ export default class InternalEthereumProviderService extends BaseService }) } + private async switchToSupportedNetwork(supportedNetwork: EVMNetwork) { + const { address } = await this.preferenceService.getSelectedAccount() + await this.chainService.markAccountActivity({ + address, + network: supportedNetwork, + }) + await this.db.setCurrentChainIdForOrigin(origin, supportedNetwork) + } + private async signData( { input, diff --git a/window-provider/index.ts b/window-provider/index.ts index 5dc5ecbf52..d3c3dc1d41 100644 --- a/window-provider/index.ts +++ b/window-provider/index.ts @@ -214,7 +214,7 @@ export default class TallyWindowProvider extends EventEmitter { reject(result) } - // let's emmit connected on the first successful response from background + // let's emit connected on the first successful response from background if (!this.connected) { this.connected = true this.emit("connect", { chainId: this.chainId }) From 40de8589f2f5feebbb50590c672b9fbc7c31d38e Mon Sep 17 00:00:00 2001 From: Daedalus <0xDaedalus@users.noreply.github.com> Date: Tue, 17 Jan 2023 08:23:26 -0700 Subject: [PATCH 087/121] Persist EVM Networks --- background/networks.ts | 2 +- background/services/chain/db.ts | 20 +++++++++++++++++++ background/services/chain/index.ts | 10 ++++++++-- .../internal-ethereum-provider/index.ts | 19 ++++++++++++------ 4 files changed, 42 insertions(+), 9 deletions(-) diff --git a/background/networks.ts b/background/networks.ts index 675eca1b37..3f34431aae 100644 --- a/background/networks.ts +++ b/background/networks.ts @@ -34,7 +34,7 @@ export type Network = { baseAsset: NetworkBaseAsset & CoinGeckoAsset family: NetworkFamily chainID?: string - coingeckoPlatformID: string + coingeckoPlatformID?: string } /** diff --git a/background/services/chain/db.ts b/background/services/chain/db.ts index 894bf56649..e2870a91d5 100644 --- a/background/services/chain/db.ts +++ b/background/services/chain/db.ts @@ -183,6 +183,26 @@ export class ChainDatabase extends Dexie { ) } + async addEVMNetwork( + chainName: string, + chainID: string, + decimals: number, + symbol: string, + assetName: string + ): Promise { + this.networks.put({ + name: chainName, + chainID, + family: "EVM", + baseAsset: { + decimals, + symbol, + name: assetName, + chainID, + }, + }) + } + async getAllEVMNetworks(): Promise { return this.networks.where("family").equals("EVM").toArray() } diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index e33b498c12..38bb36f55f 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -1865,7 +1865,13 @@ export default class ChainService extends BaseService { } } - addCustomChain(param: ValidatedAddEthereumChainParameter) { - console.log(param) + async addCustomChain(param: ValidatedAddEthereumChainParameter) { + await this.db.addEVMNetwork( + param.chainName, + param.chainId, + param.nativeCurrency.decimals, + param.nativeCurrency.symbol, + param.nativeCurrency.name + ) } } diff --git a/background/services/internal-ethereum-provider/index.ts b/background/services/internal-ethereum-provider/index.ts index ed0f552d3f..df8cd8b704 100644 --- a/background/services/internal-ethereum-provider/index.ts +++ b/background/services/internal-ethereum-provider/index.ts @@ -78,7 +78,7 @@ export type ValidatedAddEthereumChainParameter = { chainId: string blockExplorerUrl: string chainName: string - iconUrl: string + iconUrl?: string nativeCurrency: { name: string symbol: string @@ -101,11 +101,17 @@ const validateAddEthereumChainParameter = ({ !nativeCurrency || !blockExplorerUrls || !blockExplorerUrls.length || - !iconUrls || - !iconUrls.length || !rpcUrls || !rpcUrls.length ) { + console.log({ + chainId, + chainName, + blockExplorerUrls, + iconUrls, + nativeCurrency, + rpcUrls, + }) throw new Error("Missing Chain Property") } @@ -118,11 +124,11 @@ const validateAddEthereumChainParameter = ({ } return { - chainId, + chainId: chainId.startsWith("0x") ? String(parseInt(chainId, 16)) : chainId, chainName, nativeCurrency, blockExplorerUrl: blockExplorerUrls[0], - iconUrl: iconUrls[0], + iconUrl: iconUrls && iconUrls[0], rpcUrls, } } @@ -323,7 +329,8 @@ export default class InternalEthereumProviderService extends BaseService } try { const validatedParam = validateAddEthereumChainParameter(chainInfo) - return this.chainService.addCustomChain(validatedParam) + await this.chainService.addCustomChain(validatedParam) + return null } catch (e) { logger.error(e) throw new EIP1193Error(EIP1193_ERROR_CODES.userRejectedRequest) From 77abb2a4859bcd62ffd66c152e735b0744e8257b Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Tue, 17 Jan 2023 16:46:58 +0100 Subject: [PATCH 088/121] Fix POAP logo link Previous link become broken, let's add a local version of this thumbnail --- background/lib/poap_update.ts | 2 +- ui/public/images/poap_logo.svg | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 ui/public/images/poap_logo.svg diff --git a/background/lib/poap_update.ts b/background/lib/poap_update.ts index dcd7d73d19..558a0503c0 100644 --- a/background/lib/poap_update.ts +++ b/background/lib/poap_update.ts @@ -107,6 +107,6 @@ export async function getPoapCollections( hasBadges: true, network: ETHEREUM, floorPrice: undefined, // POAPs don't have floor prices - thumbnailURL: "https://poap.xyz/POAP.f74a7300.svg", + thumbnailURL: "images/poap_logo.svg", } } diff --git a/ui/public/images/poap_logo.svg b/ui/public/images/poap_logo.svg new file mode 100644 index 0000000000..424e99dfc7 --- /dev/null +++ b/ui/public/images/poap_logo.svg @@ -0,0 +1 @@ + \ No newline at end of file From 436d9999ea01c4614636e136f668bea573f27dc6 Mon Sep 17 00:00:00 2001 From: Daedalus <0xDaedalus@users.noreply.github.com> Date: Tue, 17 Jan 2023 09:05:44 -0700 Subject: [PATCH 089/121] Persist and retrieve RPC URLs for all networks --- background/services/chain/db.ts | 72 +++++++++++- background/services/chain/index.ts | 105 +++++++++++------- .../chain/serial-fallback-provider.ts | 14 +-- .../internal-ethereum-provider/index.ts | 1 + 4 files changed, 138 insertions(+), 54 deletions(-) diff --git a/background/services/chain/db.ts b/background/services/chain/db.ts index e2870a91d5..fc47a82faa 100644 --- a/background/services/chain/db.ts +++ b/background/services/chain/db.ts @@ -10,7 +10,13 @@ import { NetworkBaseAsset, } from "../../networks" import { FungibleAsset } from "../../assets" -import { BASE_ASSETS, DEFAULT_NETWORKS, GOERLI, POLYGON } from "../../constants" +import { + BASE_ASSETS, + CHAIN_ID_TO_RPC_URLS, + DEFAULT_NETWORKS, + GOERLI, + POLYGON, +} from "../../constants" export type Transaction = AnyEVMTransaction & { dataSource: "alchemy" | "local" @@ -73,6 +79,8 @@ export class ChainDatabase extends Dexie { private baseAssets!: Dexie.Table + private rpcUrls!: Dexie.Table<{ chainId: string; rpcUrls: string[] }, string> + constructor(options?: DexieOptions) { super("tally/chain", options) this.version(1).stores({ @@ -153,6 +161,10 @@ export class ChainDatabase extends Dexie { this.version(6).stores({ baseAssets: "&chainID,symbol,name", }) + + this.version(7).stores({ + rpcUrls: "&chainId, rpcUrls", + }) } async getLatestBlock(network: Network): Promise { @@ -188,9 +200,10 @@ export class ChainDatabase extends Dexie { chainID: string, decimals: number, symbol: string, - assetName: string + assetName: string, + rpcUrls: string[] ): Promise { - this.networks.put({ + await this.networks.put({ name: chainName, chainID, family: "EVM", @@ -201,16 +214,42 @@ export class ChainDatabase extends Dexie { chainID, }, }) + await this.addBaseAsset(assetName, symbol, chainID, decimals) + await this.addRpcUrls(chainID, rpcUrls) } async getAllEVMNetworks(): Promise { return this.networks.where("family").equals("EVM").toArray() } + private async addBaseAsset( + name: string, + symbol: string, + chainID: string, + decimals: number + ) { + await this.baseAssets.put({ + decimals, + name, + symbol, + chainID, + }) + } + async getAllBaseAssets(): Promise { return this.baseAssets.toArray() } + async initializeRPCs(): Promise { + await Promise.all( + Object.entries(CHAIN_ID_TO_RPC_URLS).map(async ([chainId, rpcUrls]) => { + if (rpcUrls) { + await this.addRpcUrls(chainId, rpcUrls) + } + }) + ) + } + async initializeBaseAssets(): Promise { await this.updateBaseAssets(BASE_ASSETS) } @@ -230,6 +269,33 @@ export class ChainDatabase extends Dexie { ) } + async getRpcUrlsByChainId(chainId: string): Promise { + const rpcUrls = await this.rpcUrls.where({ chainId }).first() + if (rpcUrls) { + return rpcUrls.rpcUrls + } + throw new Error(`No RPC Found for ${chainId}`) + } + + async addRpcUrls(chainId: string, rpcUrls: string[]): Promise { + const existingRpcUrlsForChain = await this.rpcUrls.get(chainId) + if (existingRpcUrlsForChain) { + existingRpcUrlsForChain.rpcUrls.push(...rpcUrls) + existingRpcUrlsForChain.rpcUrls = [ + ...new Set(existingRpcUrlsForChain.rpcUrls), + ] + console.log("about to put") + await this.rpcUrls.put(existingRpcUrlsForChain) + } else { + console.log("new put", { chainId, rpcUrls }) + await this.rpcUrls.put({ chainId, rpcUrls }) + } + } + + async getAllRpcUrls(): Promise<{ chainId: string; rpcUrls: string[] }[]> { + return this.rpcUrls.toArray() + } + async getAllSavedTransactionHashes(): Promise { return this.chainTransactions.orderBy("hash").keys() } diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index 38bb36f55f..66233e7e49 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -300,57 +300,72 @@ export default class ChainService extends BaseService { override async internalStartService(): Promise { await super.internalStartService() - await this.initializeBaseAssets() - await this.initializeNetworks() - const accounts = await this.getAccountsToTrack() - const trackedNetworks = await this.getTrackedNetworks() - const transactions = await this.db.getAllTransactions() - - this.emitter.emit("initializeActivities", { transactions, accounts }) - - // get the latest blocks and subscribe for all active networks - - Promise.allSettled( - accounts - .flatMap((an) => [ - // subscribe to all account transactions - this.subscribeToAccountTransactions(an).catch((e) => { - logger.error(e) - }), - // do a base-asset balance check for every account - this.getLatestBaseAccountBalance(an).catch((e) => { - logger.error(e) - }), - ]) - .concat( - // Schedule any stored unconfirmed transactions for - // retrieval---either to confirm they no longer exist, or to - // read/monitor their status. - trackedNetworks.map((network) => - this.db - .getNetworkPendingTransactions(network) - .then((pendingTransactions) => { - pendingTransactions.forEach(({ hash, firstSeen }) => { - logger.debug( - `Queuing pending transaction ${hash} for status lookup.` - ) - this.queueTransactionHashToRetrieve(network, hash, firstSeen) + try { + await this.initializeRPCs() + await this.initializeBaseAssets() + await this.initializeNetworks() + const accounts = await this.getAccountsToTrack() + const trackedNetworks = await this.getTrackedNetworks() + + const transactions = await this.db.getAllTransactions() + + this.emitter.emit("initializeActivities", { transactions, accounts }) + + // get the latest blocks and subscribe for all active networks + + Promise.allSettled( + accounts + .flatMap((an) => [ + // subscribe to all account transactions + this.subscribeToAccountTransactions(an).catch((e) => { + logger.error(e) + }), + // do a base-asset balance check for every account + this.getLatestBaseAccountBalance(an).catch((e) => { + logger.error(e) + }), + ]) + .concat( + // Schedule any stored unconfirmed transactions for + // retrieval---either to confirm they no longer exist, or to + // read/monitor their status. + trackedNetworks.map((network) => + this.db + .getNetworkPendingTransactions(network) + .then((pendingTransactions) => { + pendingTransactions.forEach(({ hash, firstSeen }) => { + logger.debug( + `Queuing pending transaction ${hash} for status lookup.` + ) + this.queueTransactionHashToRetrieve( + network, + hash, + firstSeen + ) + }) }) - }) - .catch((e) => { - logger.error(e) - }) + .catch((e) => { + logger.error(e) + }) + ) ) - ) - ) + ) + } catch (e) { + console.error(e) + } } async initializeBaseAssets(): Promise { await this.db.initializeBaseAssets() } + async initializeRPCs(): Promise { + await this.db.initializeRPCs() + } + async initializeNetworks(): Promise { await this.db.initializeEVMNetworks() + const rpcUrls = await this.db.getAllRpcUrls() if (!this.supportedNetworks.length) { this.supportedNetworks = await this.db.getAllEVMNetworks() } @@ -363,7 +378,10 @@ export default class ChainService extends BaseService { evm: Object.fromEntries( this.supportedNetworks.map((network) => [ network.chainID, - makeSerialFallbackProvider(network), + makeSerialFallbackProvider( + network, + rpcUrls.find((v) => v.chainId === network.chainID)?.rpcUrls || [] + ), ]) ), } @@ -1871,7 +1889,8 @@ export default class ChainService extends BaseService { param.chainId, param.nativeCurrency.decimals, param.nativeCurrency.symbol, - param.nativeCurrency.name + param.nativeCurrency.name, + param.rpcUrls ) } } diff --git a/background/services/chain/serial-fallback-provider.ts b/background/services/chain/serial-fallback-provider.ts index a099b4a232..4cb24267d0 100644 --- a/background/services/chain/serial-fallback-provider.ts +++ b/background/services/chain/serial-fallback-provider.ts @@ -10,7 +10,6 @@ import { utils } from "ethers" import { getNetwork } from "@ethersproject/networks" import { SECOND, - CHAIN_ID_TO_RPC_URLS, ALCHEMY_SUPPORTED_CHAIN_IDS, RPC_METHOD_PROVIDER_ROUTING, } from "../../constants" @@ -931,7 +930,8 @@ export default class SerialFallbackProvider extends JsonRpcProvider { } export function makeSerialFallbackProvider( - network: EVMNetwork + network: EVMNetwork, + rpcUrls: string[] ): SerialFallbackProvider { const alchemyProviderCreators = ALCHEMY_SUPPORTED_CHAIN_IDS.has( network.chainID @@ -956,12 +956,10 @@ export function makeSerialFallbackProvider( ] : [] - const genericProviders = (CHAIN_ID_TO_RPC_URLS[network.chainID] || []).map( - (rpcUrl) => ({ - type: "generic" as const, - creator: () => new JsonRpcProvider(rpcUrl), - }) - ) + const genericProviders = (rpcUrls || []).map((rpcUrl) => ({ + type: "generic" as const, + creator: () => new JsonRpcProvider(rpcUrl), + })) return new SerialFallbackProvider(network, [ // Prefer alchemy as the primary provider when available diff --git a/background/services/internal-ethereum-provider/index.ts b/background/services/internal-ethereum-provider/index.ts index df8cd8b704..90c1e1153a 100644 --- a/background/services/internal-ethereum-provider/index.ts +++ b/background/services/internal-ethereum-provider/index.ts @@ -327,6 +327,7 @@ export default class InternalEthereumProviderService extends BaseService this.switchToSupportedNetwork(supportedNetwork) return null } + // @TODO Feature Flag This try { const validatedParam = validateAddEthereumChainParameter(chainInfo) await this.chainService.addCustomChain(validatedParam) From 9dca2ea17b914a6d0b86122604e6d01da445e945 Mon Sep 17 00:00:00 2001 From: Daedalus <0xDaedalus@users.noreply.github.com> Date: Tue, 17 Jan 2023 09:12:47 -0700 Subject: [PATCH 090/121] Consistent Casing --- background/services/chain/db.ts | 14 ++++++-------- background/services/chain/index.ts | 3 ++- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/background/services/chain/db.ts b/background/services/chain/db.ts index fc47a82faa..7c10176cb8 100644 --- a/background/services/chain/db.ts +++ b/background/services/chain/db.ts @@ -79,7 +79,7 @@ export class ChainDatabase extends Dexie { private baseAssets!: Dexie.Table - private rpcUrls!: Dexie.Table<{ chainId: string; rpcUrls: string[] }, string> + private rpcUrls!: Dexie.Table<{ chainID: string; rpcUrls: string[] }, string> constructor(options?: DexieOptions) { super("tally/chain", options) @@ -163,7 +163,7 @@ export class ChainDatabase extends Dexie { }) this.version(7).stores({ - rpcUrls: "&chainId, rpcUrls", + rpcUrls: "&chainID, rpcUrls", }) } @@ -277,22 +277,20 @@ export class ChainDatabase extends Dexie { throw new Error(`No RPC Found for ${chainId}`) } - async addRpcUrls(chainId: string, rpcUrls: string[]): Promise { - const existingRpcUrlsForChain = await this.rpcUrls.get(chainId) + async addRpcUrls(chainID: string, rpcUrls: string[]): Promise { + const existingRpcUrlsForChain = await this.rpcUrls.get(chainID) if (existingRpcUrlsForChain) { existingRpcUrlsForChain.rpcUrls.push(...rpcUrls) existingRpcUrlsForChain.rpcUrls = [ ...new Set(existingRpcUrlsForChain.rpcUrls), ] - console.log("about to put") await this.rpcUrls.put(existingRpcUrlsForChain) } else { - console.log("new put", { chainId, rpcUrls }) - await this.rpcUrls.put({ chainId, rpcUrls }) + await this.rpcUrls.put({ chainID, rpcUrls }) } } - async getAllRpcUrls(): Promise<{ chainId: string; rpcUrls: string[] }[]> { + async getAllRpcUrls(): Promise<{ chainID: string; rpcUrls: string[] }[]> { return this.rpcUrls.toArray() } diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index 66233e7e49..73938896de 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -380,7 +380,7 @@ export default class ChainService extends BaseService { network.chainID, makeSerialFallbackProvider( network, - rpcUrls.find((v) => v.chainId === network.chainID)?.rpcUrls || [] + rpcUrls.find((v) => v.chainID === network.chainID)?.rpcUrls || [] ), ]) ), @@ -392,6 +392,7 @@ export default class ChainService extends BaseService { * provider exists. */ providerForNetwork(network: EVMNetwork): SerialFallbackProvider | undefined { + console.log(this.providers.evm) return isEnabled(FeatureFlags.USE_MAINNET_FORK) ? this.providers.evm[ETHEREUM.chainID] : this.providers.evm[network.chainID] From 0c8e78eeca18a2a9f8613213c03c90b3f374bd5c Mon Sep 17 00:00:00 2001 From: Daedalus <0xDaedalus@users.noreply.github.com> Date: Tue, 17 Jan 2023 09:38:48 -0700 Subject: [PATCH 091/121] Remove BTC from networks and assets. No plans to use BTC anytime soon as we are solely focused on EVM networks for the foreseeable future - lets remove the cruft. --- background/accounts.ts | 2 +- background/constants/base-assets.ts | 11 ----------- background/constants/coin-types.ts | 2 -- background/constants/currencies.ts | 11 ----------- background/constants/networks.ts | 10 +--------- background/networks.ts | 2 +- background/services/chain/db.ts | 2 +- background/tests/prices.test.ts | 6 +++--- 8 files changed, 7 insertions(+), 39 deletions(-) diff --git a/background/accounts.ts b/background/accounts.ts index b12bbb2411..631465661a 100644 --- a/background/accounts.ts +++ b/background/accounts.ts @@ -4,7 +4,7 @@ import { HexString } from "./types" /** * An account balance at a particular time and block height, on a particular - * network. Flexible enough to represent base assets like ETH and BTC as well + * network. Flexible enough to represent base assets like ETH as well * application-layer tokens like ERC-20s. */ export type AccountBalance = { diff --git a/background/constants/base-assets.ts b/background/constants/base-assets.ts index 4f68250f95..dd38dbe833 100644 --- a/background/constants/base-assets.ts +++ b/background/constants/base-assets.ts @@ -55,19 +55,8 @@ const BNB: NetworkBaseAsset = { decimals: 18, } -const BTC: NetworkBaseAsset = { - /** - * To persist base asset to indexDB chainID must be declared. - */ - chainID: "", - name: "Bitcoin", - symbol: "BTC", - decimals: 8, -} - export const BASE_ASSETS_BY_CUSTOM_NAME = { ETH, - BTC, MATIC, RBTC, AVAX, diff --git a/background/constants/coin-types.ts b/background/constants/coin-types.ts index 2352b5c554..0dab19b232 100644 --- a/background/constants/coin-types.ts +++ b/background/constants/coin-types.ts @@ -5,8 +5,6 @@ * Limited extension-specific list of coin types by asset symbol. */ export const coinTypesByAssetSymbol = { - BTC: 0, - "Testnet BTC": 1, ETH: 60, RBTC: 137, MATIC: 966, diff --git a/background/constants/currencies.ts b/background/constants/currencies.ts index e809fd2f48..2212272fde 100644 --- a/background/constants/currencies.ts +++ b/background/constants/currencies.ts @@ -90,19 +90,8 @@ export const BNB: NetworkBaseAsset & Required = { }, } -export const BTC: NetworkBaseAsset & Required = { - ...BASE_ASSETS_BY_CUSTOM_NAME.BTC, - coinType: coinTypesByAssetSymbol.BTC, - metadata: { - coinGeckoID: "bitcoin", - tokenLists: [], - websiteURL: "https://bitcoin.org", - }, -} - export const BUILT_IN_NETWORK_BASE_ASSETS = [ ETH, - BTC, MATIC, RBTC, OPTIMISTIC_ETH, diff --git a/background/constants/networks.ts b/background/constants/networks.ts index 621afa97c6..6e3c090c2d 100644 --- a/background/constants/networks.ts +++ b/background/constants/networks.ts @@ -1,11 +1,10 @@ import { FeatureFlags, isEnabled } from "../features" -import { EVMNetwork, Network } from "../networks" +import { EVMNetwork } from "../networks" import { ARBITRUM_NOVA_ETH, ARBITRUM_ONE_ETH, AVAX, BNB, - BTC, ETH, GOERLI_ETH, MATIC, @@ -85,13 +84,6 @@ export const GOERLI: EVMNetwork = { coingeckoPlatformID: "ethereum", } -export const BITCOIN: Network = { - name: "Bitcoin", - baseAsset: BTC, - family: "BTC", - coingeckoPlatformID: "bitcoin", -} - export const DEFAULT_NETWORKS = [ ETHEREUM, POLYGON, diff --git a/background/networks.ts b/background/networks.ts index 3f34431aae..1f800abe40 100644 --- a/background/networks.ts +++ b/background/networks.ts @@ -14,7 +14,7 @@ import type { * Each supported network family is generally incompatible with others from a * transaction, consensus, and/or wire format perspective. */ -export type NetworkFamily = "EVM" | "BTC" +export type NetworkFamily = "EVM" // Should be structurally compatible with FungibleAsset or much code will // likely explode. diff --git a/background/services/chain/db.ts b/background/services/chain/db.ts index 7c10176cb8..533ba7c87f 100644 --- a/background/services/chain/db.ts +++ b/background/services/chain/db.ts @@ -277,7 +277,7 @@ export class ChainDatabase extends Dexie { throw new Error(`No RPC Found for ${chainId}`) } - async addRpcUrls(chainID: string, rpcUrls: string[]): Promise { + private async addRpcUrls(chainID: string, rpcUrls: string[]): Promise { const existingRpcUrlsForChain = await this.rpcUrls.get(chainID) if (existingRpcUrlsForChain) { existingRpcUrlsForChain.rpcUrls.push(...rpcUrls) diff --git a/background/tests/prices.test.ts b/background/tests/prices.test.ts index 897b8a1e16..dd8e4a7b60 100644 --- a/background/tests/prices.test.ts +++ b/background/tests/prices.test.ts @@ -2,7 +2,7 @@ import * as ethers from "@ethersproject/web" // << THIS IS THE IMPORTANT TRICK import logger from "../lib/logger" -import { BTC, ETH, FIAT_CURRENCIES, USD } from "../constants" +import { ETH, FIAT_CURRENCIES, USD } from "../constants" import { getPrices } from "../lib/prices" import { isValidCoinGeckoPriceResponse } from "../lib/validate" @@ -151,7 +151,7 @@ describe("lib/prices.ts", () => { amounts: [639090000000000n, 100000000n], pair: [ { decimals: 10, name: "United States Dollar", symbol: "USD" }, - BTC, + ETH, ], time: dateNow, }, @@ -167,7 +167,7 @@ describe("lib/prices.ts", () => { jest.spyOn(ethers, "fetchJson").mockResolvedValue(fetchJsonResponse) - await expect(getPrices([BTC, ETH], FIAT_CURRENCIES)).resolves.toEqual( + await expect(getPrices([ETH], FIAT_CURRENCIES)).resolves.toEqual( getPricesResponse ) expect(ethers.fetchJson).toHaveBeenCalledTimes(1) From 2f967aeada42ba9540ef05acc1b400596042abbf Mon Sep 17 00:00:00 2001 From: Daedalus <0xDaedalus@users.noreply.github.com> Date: Tue, 17 Jan 2023 12:09:22 -0700 Subject: [PATCH 092/121] Cleanup --- background/services/chain/index.ts | 109 ++++++++---------- .../chain/serial-fallback-provider.ts | 2 +- .../internal-ethereum-provider/index.ts | 16 +-- 3 files changed, 57 insertions(+), 70 deletions(-) diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index 73938896de..f00c521556 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -300,59 +300,51 @@ export default class ChainService extends BaseService { override async internalStartService(): Promise { await super.internalStartService() - try { - await this.initializeRPCs() - await this.initializeBaseAssets() - await this.initializeNetworks() - const accounts = await this.getAccountsToTrack() - const trackedNetworks = await this.getTrackedNetworks() - - const transactions = await this.db.getAllTransactions() - - this.emitter.emit("initializeActivities", { transactions, accounts }) - - // get the latest blocks and subscribe for all active networks - - Promise.allSettled( - accounts - .flatMap((an) => [ - // subscribe to all account transactions - this.subscribeToAccountTransactions(an).catch((e) => { - logger.error(e) - }), - // do a base-asset balance check for every account - this.getLatestBaseAccountBalance(an).catch((e) => { - logger.error(e) - }), - ]) - .concat( - // Schedule any stored unconfirmed transactions for - // retrieval---either to confirm they no longer exist, or to - // read/monitor their status. - trackedNetworks.map((network) => - this.db - .getNetworkPendingTransactions(network) - .then((pendingTransactions) => { - pendingTransactions.forEach(({ hash, firstSeen }) => { - logger.debug( - `Queuing pending transaction ${hash} for status lookup.` - ) - this.queueTransactionHashToRetrieve( - network, - hash, - firstSeen - ) - }) - }) - .catch((e) => { - logger.error(e) + await this.initializeRPCs() + await this.initializeBaseAssets() + await this.initializeNetworks() + const accounts = await this.getAccountsToTrack() + const trackedNetworks = await this.getTrackedNetworks() + + const transactions = await this.db.getAllTransactions() + + this.emitter.emit("initializeActivities", { transactions, accounts }) + + // get the latest blocks and subscribe for all active networks + + Promise.allSettled( + accounts + .flatMap((an) => [ + // subscribe to all account transactions + this.subscribeToAccountTransactions(an).catch((e) => { + logger.error(e) + }), + // do a base-asset balance check for every account + this.getLatestBaseAccountBalance(an).catch((e) => { + logger.error(e) + }), + ]) + .concat( + // Schedule any stored unconfirmed transactions for + // retrieval---either to confirm they no longer exist, or to + // read/monitor their status. + trackedNetworks.map((network) => + this.db + .getNetworkPendingTransactions(network) + .then((pendingTransactions) => { + pendingTransactions.forEach(({ hash, firstSeen }) => { + logger.debug( + `Queuing pending transaction ${hash} for status lookup.` + ) + this.queueTransactionHashToRetrieve(network, hash, firstSeen) }) - ) + }) + .catch((e) => { + logger.error(e) + }) ) - ) - } catch (e) { - console.error(e) - } + ) + ) } async initializeBaseAssets(): Promise { @@ -392,7 +384,6 @@ export default class ChainService extends BaseService { * provider exists. */ providerForNetwork(network: EVMNetwork): SerialFallbackProvider | undefined { - console.log(this.providers.evm) return isEnabled(FeatureFlags.USE_MAINNET_FORK) ? this.providers.evm[ETHEREUM.chainID] : this.providers.evm[network.chainID] @@ -1884,14 +1875,14 @@ export default class ChainService extends BaseService { } } - async addCustomChain(param: ValidatedAddEthereumChainParameter) { + async addCustomChain(chainInfo: ValidatedAddEthereumChainParameter) { await this.db.addEVMNetwork( - param.chainName, - param.chainId, - param.nativeCurrency.decimals, - param.nativeCurrency.symbol, - param.nativeCurrency.name, - param.rpcUrls + chainInfo.chainName, + chainInfo.chainId, + chainInfo.nativeCurrency.decimals, + chainInfo.nativeCurrency.symbol, + chainInfo.nativeCurrency.name, + chainInfo.rpcUrls ) } } diff --git a/background/services/chain/serial-fallback-provider.ts b/background/services/chain/serial-fallback-provider.ts index 4cb24267d0..cc0651d8e4 100644 --- a/background/services/chain/serial-fallback-provider.ts +++ b/background/services/chain/serial-fallback-provider.ts @@ -956,7 +956,7 @@ export function makeSerialFallbackProvider( ] : [] - const genericProviders = (rpcUrls || []).map((rpcUrl) => ({ + const genericProviders = rpcUrls.map((rpcUrl) => ({ type: "generic" as const, creator: () => new JsonRpcProvider(rpcUrl), })) diff --git a/background/services/internal-ethereum-provider/index.ts b/background/services/internal-ethereum-provider/index.ts index 90c1e1153a..bd349d716e 100644 --- a/background/services/internal-ethereum-provider/index.ts +++ b/background/services/internal-ethereum-provider/index.ts @@ -32,6 +32,7 @@ import { TransactionAnnotation, } from "../enrichment" import { decodeJSON } from "../../lib/utils" +import { FeatureFlags } from "../../features" // A type representing the transaction requests that come in over JSON-RPC // requests like eth_sendTransaction and eth_signTransaction. These are very @@ -95,6 +96,7 @@ const validateAddEthereumChainParameter = ({ nativeCurrency, rpcUrls, }: AddEthereumChainParameter): ValidatedAddEthereumChainParameter => { + // @TODO Use AJV if ( !chainId || !chainName || @@ -104,14 +106,6 @@ const validateAddEthereumChainParameter = ({ !rpcUrls || !rpcUrls.length ) { - console.log({ - chainId, - chainName, - blockExplorerUrls, - iconUrls, - nativeCurrency, - rpcUrls, - }) throw new Error("Missing Chain Property") } @@ -327,7 +321,10 @@ export default class InternalEthereumProviderService extends BaseService this.switchToSupportedNetwork(supportedNetwork) return null } - // @TODO Feature Flag This + if (!FeatureFlags.SUPPORT_CUSTOM_NETWORKS) { + // Dissallow adding new chains until feature flag is turned on. + throw new EIP1193Error(EIP1193_ERROR_CODES.userRejectedRequest) + } try { const validatedParam = validateAddEthereumChainParameter(chainInfo) await this.chainService.addCustomChain(validatedParam) @@ -336,7 +333,6 @@ export default class InternalEthereumProviderService extends BaseService logger.error(e) throw new EIP1193Error(EIP1193_ERROR_CODES.userRejectedRequest) } - throw new EIP1193Error(EIP1193_ERROR_CODES.userRejectedRequest) } case "wallet_switchEthereumChain": { const newChainId = (params[0] as SwitchEthereumChainParameter).chainId From c9bbe77f2196c8e12c2f3ae9d40c2c2841edb9cd Mon Sep 17 00:00:00 2001 From: Daedalus <0xDaedalus@users.noreply.github.com> Date: Tue, 17 Jan 2023 13:14:28 -0700 Subject: [PATCH 093/121] Add tests for initialization and happy-path adding of a chain --- background/services/chain/db.ts | 2 + background/services/chain/index.ts | 6 ++- .../chain/tests/index.integration.test.ts | 21 +++++++++ .../tests/index.integration.test.ts | 45 +++++++++++++++++++ background/tests/factories.ts | 13 ++++++ 5 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 background/services/internal-ethereum-provider/tests/index.integration.test.ts diff --git a/background/services/chain/db.ts b/background/services/chain/db.ts index 533ba7c87f..e66b3a7884 100644 --- a/background/services/chain/db.ts +++ b/background/services/chain/db.ts @@ -214,6 +214,8 @@ export class ChainDatabase extends Dexie { chainID, }, }) + // A bit awkward that we are adding the base asset to the network as well + // as to its own separate table - but lets forge on for now. await this.addBaseAsset(assetName, symbol, chainID, decimals) await this.addRpcUrls(chainID, rpcUrls) } diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index f00c521556..72bb0607f5 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -1875,7 +1875,10 @@ export default class ChainService extends BaseService { } } - async addCustomChain(chainInfo: ValidatedAddEthereumChainParameter) { + // Used to add non-default chains via wallet_addEthereumChain + async addCustomChain( + chainInfo: ValidatedAddEthereumChainParameter + ): Promise { await this.db.addEVMNetwork( chainInfo.chainName, chainInfo.chainId, @@ -1884,5 +1887,6 @@ export default class ChainService extends BaseService { chainInfo.nativeCurrency.name, chainInfo.rpcUrls ) + this.supportedNetworks = await this.db.getAllEVMNetworks() } } diff --git a/background/services/chain/tests/index.integration.test.ts b/background/services/chain/tests/index.integration.test.ts index ecb76bbd37..9d418775b1 100644 --- a/background/services/chain/tests/index.integration.test.ts +++ b/background/services/chain/tests/index.integration.test.ts @@ -46,6 +46,27 @@ describe("ChainService", () => { ) ).toHaveLength(1) }) + + it("should initialize persisted data in the correct order", async () => { + const chainServiceInstance = await createChainService() + const initializeRPCs = sandbox.spy(chainServiceInstance, "initializeRPCs") + + const initializeBaseAssets = sandbox.spy( + chainServiceInstance, + "initializeBaseAssets" + ) + + const initializeNetworks = sandbox.spy( + chainServiceInstance, + "initializeNetworks" + ) + + await chainServiceInstance.internalStartService() + + expect(initializeRPCs.calledBefore(initializeBaseAssets)).toBe(true) + expect(initializeBaseAssets.calledBefore(initializeNetworks)).toBe(true) + expect(initializeNetworks.called).toBe(true) + }) }) it("handlePendingTransactions on chains without mempool should subscribe to transaction confirmations, and persist the transaction to indexedDB", async () => { diff --git a/background/services/internal-ethereum-provider/tests/index.integration.test.ts b/background/services/internal-ethereum-provider/tests/index.integration.test.ts new file mode 100644 index 0000000000..0a941f4d50 --- /dev/null +++ b/background/services/internal-ethereum-provider/tests/index.integration.test.ts @@ -0,0 +1,45 @@ +import sinon from "sinon" +import InternalEthereumProviderService from ".." +import { EVMNetwork } from "../../../networks" + +import { + createChainService, + createInternalEthereumProviderService, +} from "../../../tests/factories" + +describe("Internal Ethereum Provider Service", () => { + const sandbox = sinon.createSandbox() + let IEPService: InternalEthereumProviderService + + beforeEach(async () => { + sandbox.restore() + IEPService = await createInternalEthereumProviderService() + await IEPService.startService() + }) + + afterEach(async () => { + await IEPService.stopService() + }) + + it("should correctly persist chains sent in via wallet_addEthereumChain", async () => { + const chainService = createChainService() + + IEPService = await createInternalEthereumProviderService({ chainService }) + const startedChainService = await chainService + await startedChainService.startService() + await IEPService.startService() + const METHOD = "wallet_addEthereumChain" + const ORIGIN = "https://chainlist.org" + + // prettier-ignore + const EIP3085_PARAMS = [ { chainId: "0xfa", chainName: "Fantom Opera", nativeCurrency: { name: "Fantom", symbol: "FTM", decimals: 18, }, rpcUrls: [ "https://fantom-mainnet.gateway.pokt.network/v1/lb/62759259ea1b320039c9e7ac", "https://rpc.ftm.tools", "https://rpc.ankr.com/fantom", "https://rpc.fantom.network", "https://rpc2.fantom.network", "https://rpc3.fantom.network", "https://rpcapi.fantom.network", "https://fantom-mainnet.public.blastapi.io", "https://1rpc.io/ftm", ], blockExplorerUrls: ["https://ftmscan.com"], }, "0xd8da6bf26964af9d7eed9e03e53415d37aa96045", ] + + await IEPService.routeSafeRPCRequest(METHOD, EIP3085_PARAMS, ORIGIN) + + expect( + startedChainService.supportedNetworks.find( + (network: EVMNetwork) => network.name === "Fantom Opera" + ) + ).toBeTruthy() + }) +}) diff --git a/background/tests/factories.ts b/background/tests/factories.ts index c9eb65f875..dbc4e9263d 100644 --- a/background/tests/factories.ts +++ b/background/tests/factories.ts @@ -35,6 +35,7 @@ import { AnalyticsService, ChainService, IndexingService, + InternalEthereumProviderService, KeyringService, LedgerService, NameService, @@ -131,6 +132,18 @@ export const createSigningService = async ( ) } +export const createInternalEthereumProviderService = async ( + overrides: { + chainService?: Promise + preferenceService?: Promise + } = {} +): Promise => { + return InternalEthereumProviderService.create( + overrides.chainService ?? createChainService(), + overrides.preferenceService ?? createPreferenceService() + ) +} + // Copied from a legacy Optimism transaction generated with our test wallet. export const createLegacyTransactionRequest = ( overrides: Partial = {} From 0b8f967deffb51ceb489c05fde6ba6029a478295 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 18 Jan 2023 09:06:21 +0100 Subject: [PATCH 094/121] Tinny improvements for abilities header * update a comment * remove unnecessary blank space --- ui/components/Overview/AbilitiesHeader.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ui/components/Overview/AbilitiesHeader.tsx b/ui/components/Overview/AbilitiesHeader.tsx index 9ad2c89f7a..06813d59fd 100644 --- a/ui/components/Overview/AbilitiesHeader.tsx +++ b/ui/components/Overview/AbilitiesHeader.tsx @@ -21,7 +21,7 @@ export default function AbilitiesHeader(): ReactElement { const history = useHistory() const ability = { - // TODO change icons + // @TODO change icon icon: newAbilities > 0 ? "dog_abilities" : "dog_abilities", countText: newAbilities > 0 ? `${newAbilities} ${t("new")}` : t("none"), } @@ -76,7 +76,6 @@ export default function AbilitiesHeader(): ReactElement {
      )} - diff --git a/ui/public/images/tail.svg b/ui/public/images/tail.svg new file mode 100644 index 0000000000..5d8650bb8d --- /dev/null +++ b/ui/public/images/tail.svg @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + From 1042fd0ec52d4fb34605afe5e8942afe00b59b97 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 18 Jan 2023 14:24:50 +0100 Subject: [PATCH 101/121] Styles improvements for the empty abilities page --- ui/pages/Abilities.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/ui/pages/Abilities.tsx b/ui/pages/Abilities.tsx index 5e7a464562..d7540887c8 100644 --- a/ui/pages/Abilities.tsx +++ b/ui/pages/Abilities.tsx @@ -47,6 +47,11 @@ export default function Abilities(): ReactElement { flex-direction: row; align-items: center; } + h1 { + font-weight: 500; + font-size: 22px; + line-height: 32px; + } section { display: flex; flex-flow: column; @@ -69,14 +74,14 @@ export default function Abilities(): ReactElement { background-size: 39px 22px; width: 39px; height: 22px; - margin-right: 8px; + margin-right: 20px; } .empty_page { display: flex; flex-direction: column; gap: 20px; align-items: center; - margin-top: 32px; + margin-top: 38px; } .title { font-family: Quincy CF; From 11bfb2fc151889c9db1c10dab1e1ecd55c8a06cf Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 18 Jan 2023 14:41:43 +0100 Subject: [PATCH 102/121] Sort NFTs collections by floor price with other currencies Enable sorting collections by floor price with assets other than ETH, calculate their USd value to be able to compare them. --- .../selectors/nftsSelectors_update.ts | 14 ++- background/redux-slices/utils/nfts_update.ts | 93 ++++++++++++++++--- 2 files changed, 89 insertions(+), 18 deletions(-) diff --git a/background/redux-slices/selectors/nftsSelectors_update.ts b/background/redux-slices/selectors/nftsSelectors_update.ts index 40492575f7..f50333ef80 100644 --- a/background/redux-slices/selectors/nftsSelectors_update.ts +++ b/background/redux-slices/selectors/nftsSelectors_update.ts @@ -9,8 +9,8 @@ import { getNFTsCount, getTotalFloorPrice, } from "../utils/nfts_update" -import { selectAccountTotals } from "./accountsSelectors" -import { selectCurrentAccount } from "./uiSelectors" +import { getAssetsState, selectAccountTotals } from "./accountsSelectors" +import { selectCurrentAccount, selectMainCurrencySymbol } from "./uiSelectors" const selectNFTs = createSelector( (state: RootState) => state.nftsUpdate, @@ -96,13 +96,19 @@ const selectAllNFTBadgesCollections = createSelector( export const selectFilteredNFTCollections = createSelector( selectAllNFTCollections, selectNFTFilters, - (collections, filters) => getFilteredCollections(collections, filters) + getAssetsState, + selectMainCurrencySymbol, + (collections, filters, assets, mainCurrencySymbol) => + getFilteredCollections(collections, filters, assets, mainCurrencySymbol) ) export const selectFilteredNFTBadgesCollections = createSelector( selectAllNFTBadgesCollections, selectNFTFilters, - (collections, filters) => getFilteredCollections(collections, filters) + getAssetsState, + selectMainCurrencySymbol, + (collections, filters, assets, mainCurrencySymbol) => + getFilteredCollections(collections, filters, assets, mainCurrencySymbol) ) /* Counting selectors */ diff --git a/background/redux-slices/utils/nfts_update.ts b/background/redux-slices/utils/nfts_update.ts index 6e47b0106e..827db29bd4 100644 --- a/background/redux-slices/utils/nfts_update.ts +++ b/background/redux-slices/utils/nfts_update.ts @@ -1,10 +1,13 @@ +import { BUILT_IN_NETWORK_BASE_ASSETS } from "../../constants" import { NFT } from "../../nfts" +import { AssetsState, selectAssetPricePoint } from "../assets" import { Filter, FiltersState, NFTCollectionCached, SortType, } from "../nfts_update" +import { enrichAssetAmountWithMainCurrencyValues } from "./asset-utils" const ETH_SYMBOLS = ["ETH", "WETH"] @@ -14,6 +17,14 @@ export type AccountData = { avatarURL: string } +type NFTCollectionEnriched = NFTCollectionCached & { + floorPrice?: { + value: number + valueUSD?: number + tokenSymbol: string + } +} + const isEnabledFilter = (id: string, filters: Filter[]): boolean => { return !!filters.find((filter) => id === filter.id && filter.isEnabled) } @@ -33,19 +44,17 @@ export const getAdditionalDataForFilter = ( /* Items are sorted by price in ETH. All other elements are added at the end. */ const sortByPrice = ( type: "asc" | "desc", - collection1: NFTCollectionCached, - collection2: NFTCollectionCached + collection1: NFTCollectionEnriched, + collection2: NFTCollectionEnriched ): number => { - if (collection1.floorPrice && collection2.floorPrice) { - if (isETHPrice(collection1) && isETHPrice(collection2)) { - if (type === "asc") { - return collection1.floorPrice.value - collection2.floorPrice.value - } - return collection2.floorPrice.value - collection1.floorPrice.value + if (collection1.floorPrice?.valueUSD && collection2.floorPrice?.valueUSD) { + if (type === "asc") { + return collection1.floorPrice.valueUSD - collection2.floorPrice.valueUSD } + return collection2.floorPrice.valueUSD - collection1.floorPrice.valueUSD } - if (collection1.floorPrice === undefined) return 1 - if (collection2.floorPrice === undefined) return -1 + if (collection1.floorPrice?.valueUSD === undefined) return 1 + if (collection2.floorPrice?.valueUSD === undefined) return -1 return 1 } @@ -153,17 +162,73 @@ export const getTotalFloorPrice = ( export const getNFTsCount = (collections: NFTCollectionCached[]): number => collections.reduce((sum, collection) => sum + (collection.nftCount ?? 0), 0) +function enrichCollectionWithUSDFloorPrice( + collection: NFTCollectionCached, + assets: AssetsState, + mainCurrencySymbol: string +): NFTCollectionEnriched { + if (!collection.floorPrice) return collection + + const { tokenSymbol, value } = collection.floorPrice + + const baseAsset = BUILT_IN_NETWORK_BASE_ASSETS.find( + (asset) => tokenSymbol === asset.symbol + ) + + if (!baseAsset) return collection + + const pricePoint = selectAssetPricePoint( + assets, + baseAsset, + mainCurrencySymbol + ) + + const valueUSD = + enrichAssetAmountWithMainCurrencyValues( + { + asset: baseAsset, + amount: BigInt(Math.round(value * 10 ** baseAsset.decimals)), + }, + pricePoint, + 2 + ).mainCurrencyAmount ?? 0 + + return { + ...collection, + floorPrice: { + value, + valueUSD, + tokenSymbol, + }, + } +} + export const getFilteredCollections = ( collections: NFTCollectionCached[], - filters: FiltersState -): NFTCollectionCached[] => - collections + filters: FiltersState, + assets: AssetsState, + mainCurrencySymbol: string +): NFTCollectionCached[] => { + const applyPriceSort = filters.type === "asc" || filters.type === "desc" + + return collections .filter( (collection) => isEnabledFilter(collection.id, filters.collections) && isEnabledFilter(collection.owner, filters.accounts) ) - .map((collection) => sortNFTs(collection, filters.type)) + .map((collection) => { + const collectionWithSortedNFTs = sortNFTs(collection, filters.type) + + return applyPriceSort + ? enrichCollectionWithUSDFloorPrice( + collectionWithSortedNFTs, + assets, + mainCurrencySymbol + ) + : collectionWithSortedNFTs + }) .sort((collection1, collection2) => sortCollections(collection1, collection2, filters.type) ) +} From b94ab06e2d07de15126f825853eff08d99cc4659 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 18 Jan 2023 14:43:05 +0100 Subject: [PATCH 103/121] Show floor price on NFTs badges --- ui/components/NFTS_update/NFTList.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui/components/NFTS_update/NFTList.tsx b/ui/components/NFTS_update/NFTList.tsx index 7c31194f05..7a0b745cef 100644 --- a/ui/components/NFTS_update/NFTList.tsx +++ b/ui/components/NFTS_update/NFTList.tsx @@ -59,7 +59,7 @@ export default function NFTList(props: { collection.nfts.map((nft) => ( openPreview({ nft, collection })} /> )) From adb2f9d60596d139a1d16a4bdad40c7dbef63377 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 18 Jan 2023 14:45:03 +0100 Subject: [PATCH 104/121] Fix radio button hover cursor --- ui/components/Shared/SharedRadio.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/components/Shared/SharedRadio.tsx b/ui/components/Shared/SharedRadio.tsx index 2bc682e0b5..3e8f9e5daf 100644 --- a/ui/components/Shared/SharedRadio.tsx +++ b/ui/components/Shared/SharedRadio.tsx @@ -47,6 +47,7 @@ export default function SharedRadio({ line-height: normal; color: var(--white); margin-top: 0; + cursor: pointer; } .radio-label:before { content: ""; From 8539955956bfdaa28e6202ac937f69db535f2ba5 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Wed, 18 Jan 2023 14:54:30 +0100 Subject: [PATCH 105/121] Fix preview image for NFT badges Make sure badges that are not square are displayed correctly --- ui/components/NFTS_update/NFTPreview.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/ui/components/NFTS_update/NFTPreview.tsx b/ui/components/NFTS_update/NFTPreview.tsx index 464831f549..cc8d5f566e 100644 --- a/ui/components/NFTS_update/NFTPreview.tsx +++ b/ui/components/NFTS_update/NFTPreview.tsx @@ -75,6 +75,7 @@ export default function NFTPreview(props: NFTWithCollection): ReactElement { highResolutionSrc={previewURL} alt={name} width={384} + height={isBadge ? 384 : undefined} isBadge={isBadge} customStyles="border-radius: 0 0 8px 8px;" /> From 0bdeccf0239e9b18561dedba6ac99b422a6f8eb0 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 18 Jan 2023 19:48:28 +0100 Subject: [PATCH 106/121] Refactor for empty ability page * change to useBackgroundSelector * update tail icon --- ui/pages/Abilities.tsx | 4 ++-- ui/public/images/tail.svg | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ui/pages/Abilities.tsx b/ui/pages/Abilities.tsx index d7540887c8..ff7d63648c 100644 --- a/ui/pages/Abilities.tsx +++ b/ui/pages/Abilities.tsx @@ -1,15 +1,15 @@ import { selectFilteredAbilities } from "@tallyho/tally-background/redux-slices/selectors" import React, { ReactElement } from "react" import { useTranslation } from "react-i18next" -import { useSelector } from "react-redux" import SharedButton from "../components/Shared/SharedButton" import AbilityCard from "./Abilities/AbilityCard" +import { useBackgroundSelector } from "../hooks" export default function Abilities(): ReactElement { const { t } = useTranslation("translation", { keyPrefix: "abilities", }) - const abilities = useSelector(selectFilteredAbilities) + const abilities = useBackgroundSelector(selectFilteredAbilities) return ( <> diff --git a/ui/public/images/tail.svg b/ui/public/images/tail.svg index 5d8650bb8d..e287ab22af 100644 --- a/ui/public/images/tail.svg +++ b/ui/public/images/tail.svg @@ -1,9 +1,9 @@ - + - + From 7a753c50f3637ee750bedcfdf6d193c44f47da10 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 18 Jan 2023 20:08:06 +0100 Subject: [PATCH 107/121] Connect changes for the ability page * change structure for translations * update icon for ability banner --- ui/_locales/en/messages.json | 13 ++++++------- ui/components/Overview/AbilitiesHeader.tsx | 18 +++++++++++------- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index 59285c3226..d50a8148ff 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -443,13 +443,6 @@ "assets": "Assets", "price": "Price", "balance": "Balance" - }, - "abilities": { - "title": "Portfolio abilities", - "description": "Daylight tells you when you qualify for an airdrop, mint, or unlock.", - "seeAbilities": "See your abilities", - "new": "New", - "none": "None" } }, "passwordStrength": { @@ -773,6 +766,12 @@ "title": "No abilities here", "desc": "Add more accounts to see your abilities, or change filters", "addBtn": "Add account" + }, + "banner": { + "description": "Daylight tells you when you qualify for an airdrop, mint, or unlock.", + "seeAbilities": "See your abilities", + "new": "New", + "none": "None" } }, "globalError": { diff --git a/ui/components/Overview/AbilitiesHeader.tsx b/ui/components/Overview/AbilitiesHeader.tsx index 7638555a5e..52ec2de8fa 100644 --- a/ui/components/Overview/AbilitiesHeader.tsx +++ b/ui/components/Overview/AbilitiesHeader.tsx @@ -13,7 +13,7 @@ import SharedButton from "../Shared/SharedButton" export default function AbilitiesHeader(): ReactElement { const { t } = useTranslation("translation", { - keyPrefix: "overview.abilities", + keyPrefix: "abilities", }) const newAbilities = useSelector(selectAbilityCount) const hideDescription = useSelector(selectHideDescription) @@ -23,7 +23,10 @@ export default function AbilitiesHeader(): ReactElement { const ability = { // @TODO change icon icon: newAbilities > 0 ? "dog_abilities" : "dog_abilities", - countText: newAbilities > 0 ? `${newAbilities} ${t("new")}` : t("none"), + countText: + newAbilities > 0 + ? `${newAbilities} ${t("banner.new")}` + : t("banner.none"), } const handleClick = () => { @@ -48,10 +51,10 @@ export default function AbilitiesHeader(): ReactElement { />
      - {t("title")} + {t("header")}
      {!hideDescription && (
      -
      {t("description")}
      +
      {t("banner.description")}
      handleClick()} > - {t("seeAbilities")} + {t("banner.seeAbilities")}
      )} @@ -128,7 +131,7 @@ export default function AbilitiesHeader(): ReactElement { line-height: 24px; } - .title { + .header { font-weight: 600; font-size: 18px; } @@ -172,6 +175,7 @@ export default function AbilitiesHeader(): ReactElement { background-size: 36px 36px; height: 36px; margin-right: 8px; + border-radius: 24px; } `}
      From 7ab9d9c935226a037b290aa9120551338c5adef2 Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Wed, 18 Jan 2023 21:22:21 +0100 Subject: [PATCH 108/121] Fix the issue for the ability page background --- ui/pages/Abilities.tsx | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/ui/pages/Abilities.tsx b/ui/pages/Abilities.tsx index ff7d63648c..3e99d2ffc5 100644 --- a/ui/pages/Abilities.tsx +++ b/ui/pages/Abilities.tsx @@ -12,8 +12,8 @@ export default function Abilities(): ReactElement { const abilities = useBackgroundSelector(selectFilteredAbilities) return ( - <> -
      +
      +
      {/* @TODO change icon */}
      @@ -39,7 +39,7 @@ export default function Abilities(): ReactElement {
      )} -
      +
      - + ) } From cf51378d3d839567703f5b99d02bda95a7eb98fc Mon Sep 17 00:00:00 2001 From: Daedalus <0xDaedalus@users.noreply.github.com> Date: Wed, 18 Jan 2023 14:11:11 -0700 Subject: [PATCH 109/121] Only export logs from the last hour to avoid exporting useless old logs --- background/lib/logger.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/background/lib/logger.ts b/background/lib/logger.ts index 7ea6de3963..8d70bb9283 100644 --- a/background/lib/logger.ts +++ b/background/lib/logger.ts @@ -288,8 +288,16 @@ export function serializeLogs(): string { return splitLogs }) + const HOUR = 1000 * 60 * 60 return ( logEntries + // Only grab logs from the past hour + .filter((logLine) => { + return ( + new Date(logLine.substring(1, iso8601Length)) > + new Date(Date.now() - HOUR) + ) + }) // Sort by date. .sort((a, b) => { return a From b13f8cba8938cbb56925e339fb2bc7da25f8173b Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 19 Jan 2023 09:30:50 +0100 Subject: [PATCH 110/121] Update icons for Abilities page --- ui/components/Overview/AbilitiesHeader.tsx | 37 +++++++-------------- ui/pages/Abilities.tsx | 26 ++++++++++----- ui/public/images/assets/daylight.png | Bin 2003 -> 0 bytes ui/public/images/daylight.svg | 9 ----- ui/public/images/dog_abilities.svg | 19 ----------- ui/public/images/tail.svg | 30 +++++++++-------- 6 files changed, 47 insertions(+), 74 deletions(-) delete mode 100644 ui/public/images/assets/daylight.png delete mode 100644 ui/public/images/daylight.svg delete mode 100644 ui/public/images/dog_abilities.svg diff --git a/ui/components/Overview/AbilitiesHeader.tsx b/ui/components/Overview/AbilitiesHeader.tsx index 7638555a5e..59b635a017 100644 --- a/ui/components/Overview/AbilitiesHeader.tsx +++ b/ui/components/Overview/AbilitiesHeader.tsx @@ -20,11 +20,8 @@ export default function AbilitiesHeader(): ReactElement { const dispatch = useBackgroundDispatch() const history = useHistory() - const ability = { - // @TODO change icon - icon: newAbilities > 0 ? "dog_abilities" : "dog_abilities", - countText: newAbilities > 0 ? `${newAbilities} ${t("new")}` : t("none"), - } + const abilityCount = + newAbilities > 0 ? `${newAbilities} ${t("new")}` : t("none") const handleClick = () => { if (!hideDescription) { @@ -41,11 +38,7 @@ export default function AbilitiesHeader(): ReactElement { >
      -
      +
      - {ability.countText} + {abilityCount}
      {!hideDescription && ( @@ -85,7 +78,7 @@ export default function AbilitiesHeader(): ReactElement { display: flex; flex-direction: row; justify-content: space-between; - align-items: ${hideDescription ? "end" : "center"}; + align-items: center; } .abilities_header { @@ -102,7 +95,7 @@ export default function AbilitiesHeader(): ReactElement { rgba(19, 48, 46, 0.5) 100% ); - padding: 13px 16px 16px; + padding: 12px 16px 12px 12px; width: 100%; box-sizing: border-box; } @@ -120,7 +113,7 @@ export default function AbilitiesHeader(): ReactElement { .abilities_info { display: flex; flex-direction: row; - align-items: ${hideDescription ? "end" : "center"}; + align-items: center; color: var(--white); font-weight: 400; @@ -159,19 +152,13 @@ export default function AbilitiesHeader(): ReactElement { margin: 8px 0 16px; } - .icon { - background: url("./images/${ability.icon}.svg"); - background-size: 36px 30px; - width: 36px; - height: 30px; - margin-right: 14px; - } - - .icon.tail { + .icon_tail { background: url("./images/tail.svg"); - background-size: 36px 36px; - height: 36px; + background-size: 32px 32px; + width: 32px; + height: 32px; margin-right: 8px; + border-radius: 24px; } `}
      diff --git a/ui/pages/Abilities.tsx b/ui/pages/Abilities.tsx index 8284e57b10..9cbe743eb9 100644 --- a/ui/pages/Abilities.tsx +++ b/ui/pages/Abilities.tsx @@ -1,17 +1,21 @@ import { selectFilteredAbilities } from "@tallyho/tally-background/redux-slices/selectors" import React, { ReactElement } from "react" +import { useTranslation } from "react-i18next" import { useSelector } from "react-redux" import AbilityCard from "./Abilities/AbilityCard" export default function Abilities(): ReactElement { + const { t } = useTranslation("translation", { + keyPrefix: "overview.abilities", + }) const abilities = useSelector(selectFilteredAbilities) return ( <>
      -
      -

      Daylight Abilities!

      +
      +

      {t("title")}

      {abilities.map((ability) => ( @@ -24,18 +28,24 @@ export default function Abilities(): ReactElement { flex-direction: row; align-items: center; } + h1 { + font-weight: 500; + font-size: 22px; + line-height: 32px; + } section { display: flex; flex-flow: column; height: 544px; background-color: var(--hunter-green); } - .icon_daylight { - background: url("./images/assets/daylight.png"); - background-size: 39px 22px; - width: 39px; - height: 22px; - margin-right: 8px; + .icon_tail { + background: url("./images/tail.svg"); + background-size: 32px 32px; + width: 32px; + height: 32px; + margin-right: 16px; + border-radius: 24px; } `} diff --git a/ui/public/images/assets/daylight.png b/ui/public/images/assets/daylight.png deleted file mode 100644 index 420b9ec448891b8d5b497f6b7593ee33b7520024..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2003 zcmV;^2Q2uBP)hrhk@6I5p`}n1Q7MK3Vhk8R7{x^K8!rRYA~W~weICp*>)ty!y}ftNI(O!l(sYuY znRCybeb4@#|62dG*O}?+I`(ypz)?$p`sRIj%$@V8*mlqJ4tu@R-;4Uy>d4}#H~Nm) z;C(#2QTKX_x5Z9h8Jt=k`th65fIjcPw&!zX)algMcfwH%X>wsS(seuy_0EYce#@8j z6_LR`!KxP{gX7*JiH1=VkyZck7N7SH?+XSm_Wk-RG}QCa$cIOHP4@5@a7|eN}Jt6_Vb3y5}U~!YT__8-R z(e6S!^Lsk;PX94>_#IN}#3Mp|)Yo;wn8V)S^e?XiFsvvHDe?oe61o$?;9r92`RZX&9y?RwO3S>#%1oezvZAm`QM>^tz6L11 z4k*4JDBcVdZ)q|%)z4oG6ki1tuLlYjtC^|WA_a?o9q-rkJLhKw)iNi5$}Hgs{OEqKj}*$CMY!3l>LKzv6B2a-$lGm(L=wM!*~E zVLi~63MahN&(hMKPtPhmmT@RAIy*A@g`zbY=cKS+)k3jV7&by^_n7Cu3aTlfXda9V zX2IdjV?*7aoHcmdzKM-~z*|ghCi+`>dbw*)$+_?=F_lZAw43AxXO20_C8kOT|lhbLIAl)UM9?UPVc4Rg(r*PRSb^g zLgNkcGF7dW#23xbZU@SC6i`<)V2!W+94NWDAiQ-THHx=V+kJj6I9hVnN4!=46oFf# z3Y8woPwV-sapALZTom55Le&|JgTTEn^`=NLgu|*fMQ9_$EsT+F$wkVxKo$51ef&B*TLf7 z^1yMcNVkeNhq`0JM7VvFAnM&&+7;AaD({pbxq)jF+lVbdM& z2Fe}*N`DEIJ_eLM3Y0wzls!~Twdox~)$u-7@C+le_!f=yeyAxt?@H^v!7NX4@egHp zlPml}A^Ip#@;jjTS)lm2DdQQS^fxu2;`z1i3D+J+ca3Wl^aBg#OR^5{ajnDI* zPZ9=Mf!vn<3MhGI3aqK%_~+jP#ruG=J;IyuAP}dC-0KwQIi%2!i(7!hr*gq@86s#< zCO?)tY5I5_Oe3r&$H_jRR0xl^WfGI%*1lx_8f>>~1x{uR{3tJHL(g3fQQh_k5W_eq zG@AgMdOq1JRNZ!K?pYfu~nBhZL3E{y^*K5>T?8s{+R==>ZaV?n9c}x0aex3 znA38=aoA>TA~|z&?I(&Y>-)y!^)JkAXV| z?*9fiW5w~VA!mfuDvLKm7H^qmj6#;)4OzA;XY7V7c^b0#x#A;zB4_UUXQ#JLbDfiG l*a%q|%3nMGUq!P}{U2)tI6~ybZ=nDH002ovPDHLkV1giP-L(J! diff --git a/ui/public/images/daylight.svg b/ui/public/images/daylight.svg deleted file mode 100644 index ef754ac779..0000000000 --- a/ui/public/images/daylight.svg +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/ui/public/images/dog_abilities.svg b/ui/public/images/dog_abilities.svg deleted file mode 100644 index 7976e0f9fa..0000000000 --- a/ui/public/images/dog_abilities.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/ui/public/images/tail.svg b/ui/public/images/tail.svg index 58af6bfee7..aafe4f197f 100644 --- a/ui/public/images/tail.svg +++ b/ui/public/images/tail.svg @@ -1,19 +1,23 @@ - - - + + + - - - - - - - - - + + + + + + + + + + + + + - + From 1bb4224270726444ad9725522de9baa04fa56a1a Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 19 Jan 2023 10:57:51 +0100 Subject: [PATCH 111/121] Fix issue for translations --- ui/_locales/en/messages.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index d50a8148ff..7d21a4541b 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -764,11 +764,11 @@ "header": "Portfolio abilities", "emptyState": { "title": "No abilities here", - "desc": "Add more accounts to see your abilities, or change filters", + "desc": "Add more accounts to see your abilities or change filters", "addBtn": "Add account" }, "banner": { - "description": "Daylight tells you when you qualify for an airdrop, mint, or unlock.", + "description": "Daylight tells you when you qualify for an airdrop, mint or unlock.", "seeAbilities": "See your abilities", "new": "New", "none": "None" From 3f3f8db81df1706afaf88b927095bd73b9e2464b Mon Sep 17 00:00:00 2001 From: Karolina Kosiorowska Date: Thu, 19 Jan 2023 11:08:40 +0100 Subject: [PATCH 112/121] Update styles for empty ability page --- ui/pages/Abilities.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ui/pages/Abilities.tsx b/ui/pages/Abilities.tsx index 8a7648a4b5..b345c88294 100644 --- a/ui/pages/Abilities.tsx +++ b/ui/pages/Abilities.tsx @@ -78,9 +78,9 @@ export default function Abilities(): ReactElement { .empty_page { display: flex; flex-direction: column; - gap: 20px; + gap: 16px; align-items: center; - margin-top: 38px; + margin-top: 24px; } .title { font-family: Quincy CF; @@ -100,16 +100,15 @@ export default function Abilities(): ReactElement { } .icon_tail { background: url("./images/tail.svg"); - background-size: 70px 70px; - width: 64px; - height: 64px; + background-size: 82px 82px; + width: 82px; + height: 82px; border-radius: 24px; } .logo { background-size: 32px 32px; width: 32px; height: 32px; - border-radius: 24px; margin-right: 16px; } `} From d0861b91c87263cda87aa507943649b15b29be77 Mon Sep 17 00:00:00 2001 From: omahs <73983677+omahs@users.noreply.github.com> Date: Thu, 19 Jan 2023 11:16:59 +0100 Subject: [PATCH 113/121] Fix: typos Fix: typos --- ui/_locales/en/messages.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index 1ec01440bc..430189f88a 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -124,7 +124,7 @@ "connectSelectedLedger": "Connect selected", "doneMessageOne": "Congratulations!", "doneMessageTwo": "You can open Tally Ho now.", - "onboardingSuccessful": "Selected accounts were succesfully connected.", + "onboardingSuccessful": "Selected accounts were successfully connected.", "closeTab": "Close tab" }, "connectionStatus": { @@ -309,7 +309,7 @@ "tip": "Some of the code for this was written by Community contributors" }, "viewOnly": { - "tip": "A good way to take a peak at what Tally Ho offers" + "tip": "A good way to take a peek at what Tally Ho offers" }, "importSeed": { "tip": "Tally Ho offers the possibility of adding multiple recovery phrases" @@ -392,7 +392,7 @@ }, "seedVerification": { "seedIsInWrongOrder": "Wrong Order", - "seedMismatchExplainer": "We are sorry, the recovery phrase you entered did not match.You can try to re-order them, but make sure you have them written down.", + "seedMismatchExplainer": "We are sorry, the recovery phrase you entered did not match. You can try to re-order them, but make sure you have them written down.", "createNewWallet": "If you prefer you can <1><0>create a new wallet.<0>", "retryVerification": "Try again", "verifySeedPrompt": "Verify recovery phrase", @@ -642,7 +642,7 @@ "rewards": { "header": "Swap rewards for community", "body": "This week, 240,000 DOGGO tokens will be equally shared as Swap Rewards.", - "tooltip": "Tally Ho rewards it's users that use swap every week. A council decides weekly prizes and who is eligible.", + "tooltip": "Tally Ho rewards its users that use swap every week. A council decides weekly prizes and who is eligible.", "detailButton": "Details" }, "error": { @@ -751,7 +751,7 @@ "snackbar": "You can change this in Settings later", "isDefault": "is now your default wallet", "notDefault": "is not your default wallet", - "tooltip": "Setting Tally Ho as your default wallet means that everytime you connect to a dApp, Tally Ho will open instead of MetaMask or other wallets." + "tooltip": "Setting Tally Ho as your default wallet means that every time you connect to a dApp, Tally Ho will open instead of MetaMask or other wallets." }, "analyticsNotification": { "title": "Analytics are on", @@ -808,4 +808,4 @@ "earn": "Earn", "settings": "Settings" } -} \ No newline at end of file +} From 3d34f9a867c898d8169fc2c922c67f15bd781218 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Thu, 19 Jan 2023 12:00:21 +0100 Subject: [PATCH 114/121] Add unit tests for `getTotalFloorPrice()` util --- .../selectors/nftsSelectors_update.ts | 2 +- .../utils/{nfts_update.ts => nfts-utils.ts} | 0 .../utils/nfts-utils.unit.test.ts | 117 ++++++++++++++++++ 3 files changed, 118 insertions(+), 1 deletion(-) rename background/redux-slices/utils/{nfts_update.ts => nfts-utils.ts} (100%) create mode 100644 background/redux-slices/utils/nfts-utils.unit.test.ts diff --git a/background/redux-slices/selectors/nftsSelectors_update.ts b/background/redux-slices/selectors/nftsSelectors_update.ts index f50333ef80..3fd2f3f24a 100644 --- a/background/redux-slices/selectors/nftsSelectors_update.ts +++ b/background/redux-slices/selectors/nftsSelectors_update.ts @@ -8,7 +8,7 @@ import { getFilteredCollections, getNFTsCount, getTotalFloorPrice, -} from "../utils/nfts_update" +} from "../utils/nfts-utils" import { getAssetsState, selectAccountTotals } from "./accountsSelectors" import { selectCurrentAccount, selectMainCurrencySymbol } from "./uiSelectors" diff --git a/background/redux-slices/utils/nfts_update.ts b/background/redux-slices/utils/nfts-utils.ts similarity index 100% rename from background/redux-slices/utils/nfts_update.ts rename to background/redux-slices/utils/nfts-utils.ts diff --git a/background/redux-slices/utils/nfts-utils.unit.test.ts b/background/redux-slices/utils/nfts-utils.unit.test.ts new file mode 100644 index 0000000000..9b3b8eb61f --- /dev/null +++ b/background/redux-slices/utils/nfts-utils.unit.test.ts @@ -0,0 +1,117 @@ +import { ETHEREUM } from "../../constants" +import { getTotalFloorPrice } from "./nfts-utils" + +const COLLECTION_MOCK = { + id: "", + name: "", + owner: "", + network: ETHEREUM, // doesn't matter for now + hasBadges: false, + nfts: [], // doesn't matter for now + hasNextPage: false, +} + +describe("NFTs utils", () => { + describe("getTotalFloorPrice", () => { + test("should sum ETH and WETH floor prices", () => { + const collections = [ + { + ...COLLECTION_MOCK, + nftCount: 1, + floorPrice: { value: 0.001, tokenSymbol: "ETH" }, + }, + { + ...COLLECTION_MOCK, + nftCount: 1, + floorPrice: { value: 0.002, tokenSymbol: "WETH" }, + }, + ] + + expect(getTotalFloorPrice(collections)).toMatchObject({ ETH: 0.003 }) + }) + test("should sum floor prices for multiple currencies", () => { + const collections = [ + { + ...COLLECTION_MOCK, + nftCount: 1, + floorPrice: { value: 0.002, tokenSymbol: "BNB" }, + }, + { + ...COLLECTION_MOCK, + nftCount: 1, + floorPrice: { value: 0.003, tokenSymbol: "ETH" }, + }, + { + ...COLLECTION_MOCK, + nftCount: 1, + floorPrice: { value: 0.001, tokenSymbol: "BNB" }, + }, + { + ...COLLECTION_MOCK, + nftCount: 1, + floorPrice: { value: 0.002, tokenSymbol: "AVAX" }, + }, + { + ...COLLECTION_MOCK, + nftCount: 1, + floorPrice: { value: 0.001, tokenSymbol: "AVAX" }, + }, + ] + + expect(getTotalFloorPrice(collections)).toMatchObject({ + ETH: 0.003, + AVAX: 0.003, + BNB: 0.003, + }) + }) + test("should sum floor prices for collections with multiple NFTs owned", () => { + const collections = [ + { + ...COLLECTION_MOCK, + nftCount: 10, + floorPrice: { value: 0.001, tokenSymbol: "ETH" }, + }, + { + ...COLLECTION_MOCK, + nftCount: 5, + floorPrice: { value: 0.002, tokenSymbol: "WETH" }, + }, + ] + + expect(getTotalFloorPrice(collections)).toMatchObject({ ETH: 0.02 }) + }) + test("should sum correctly when some collection has 0 NFTs", () => { + const collections = [ + { + ...COLLECTION_MOCK, + floorPrice: { value: 0.001, tokenSymbol: "ETH" }, + }, + { + ...COLLECTION_MOCK, + nftCount: 0, + floorPrice: { value: 0.002, tokenSymbol: "WETH" }, + }, + { + ...COLLECTION_MOCK, + nftCount: 1, + floorPrice: { value: 0.001, tokenSymbol: "WETH" }, + }, + ] + + expect(getTotalFloorPrice(collections)).toMatchObject({ ETH: 0.001 }) + }) + test("should sum correctly if some collections have no floor prices", () => { + const collections = [ + COLLECTION_MOCK, + { + ...COLLECTION_MOCK, + nftCount: 1, + floorPrice: { value: 0.001, tokenSymbol: "WETH" }, + }, + COLLECTION_MOCK, + ] + + expect(getTotalFloorPrice(collections)).toMatchObject({ ETH: 0.001 }) + }) + }) +}) From f5370ad4d50a67725835c7818b2afe3c90b81cdd Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Thu, 19 Jan 2023 12:01:13 +0100 Subject: [PATCH 115/121] Change assets tests file name to run them with other unit tests --- background/redux-slices/{assets.test.ts => assets.unit.test.ts} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename background/redux-slices/{assets.test.ts => assets.unit.test.ts} (100%) diff --git a/background/redux-slices/assets.test.ts b/background/redux-slices/assets.unit.test.ts similarity index 100% rename from background/redux-slices/assets.test.ts rename to background/redux-slices/assets.unit.test.ts From 12e53b67736fa79ebcbf0cf6885cd8a4d5506be6 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Thu, 19 Jan 2023 13:03:01 +0100 Subject: [PATCH 116/121] Add unit tests for `enrichCollectionWithUSDFloorPrice()` util --- background/redux-slices/utils/nfts-utils.ts | 5 +- .../utils/nfts-utils.unit.test.ts | 146 +++++++++++++++++- background/tests/factories.ts | 11 +- 3 files changed, 150 insertions(+), 12 deletions(-) diff --git a/background/redux-slices/utils/nfts-utils.ts b/background/redux-slices/utils/nfts-utils.ts index 827db29bd4..910c927593 100644 --- a/background/redux-slices/utils/nfts-utils.ts +++ b/background/redux-slices/utils/nfts-utils.ts @@ -162,7 +162,7 @@ export const getTotalFloorPrice = ( export const getNFTsCount = (collections: NFTCollectionCached[]): number => collections.reduce((sum, collection) => sum + (collection.nftCount ?? 0), 0) -function enrichCollectionWithUSDFloorPrice( +export function enrichCollectionWithUSDFloorPrice( collection: NFTCollectionCached, assets: AssetsState, mainCurrencySymbol: string @@ -170,9 +170,10 @@ function enrichCollectionWithUSDFloorPrice( if (!collection.floorPrice) return collection const { tokenSymbol, value } = collection.floorPrice + const symbol = isETHPrice(collection) ? "ETH" : tokenSymbol const baseAsset = BUILT_IN_NETWORK_BASE_ASSETS.find( - (asset) => tokenSymbol === asset.symbol + (asset) => symbol === asset.symbol ) if (!baseAsset) return collection diff --git a/background/redux-slices/utils/nfts-utils.unit.test.ts b/background/redux-slices/utils/nfts-utils.unit.test.ts index 9b3b8eb61f..8b66d0ea8a 100644 --- a/background/redux-slices/utils/nfts-utils.unit.test.ts +++ b/background/redux-slices/utils/nfts-utils.unit.test.ts @@ -1,5 +1,10 @@ -import { ETHEREUM } from "../../constants" -import { getTotalFloorPrice } from "./nfts-utils" +import { AVAX, BNB, ETH, ETHEREUM, USD } from "../../constants" +import { AssetsState } from "../assets" +import { + enrichCollectionWithUSDFloorPrice, + getTotalFloorPrice, +} from "./nfts-utils" +import { createPricePoint } from "../../tests/factories" const COLLECTION_MOCK = { id: "", @@ -11,6 +16,27 @@ const COLLECTION_MOCK = { hasNextPage: false, } +const assetsState: AssetsState = [ + { + ...ETH, + recentPrices: { + USD: createPricePoint(ETH, 2000), + }, + }, + { + ...AVAX, + recentPrices: { + USD: createPricePoint(AVAX, 15), + }, + }, + { + ...BNB, + recentPrices: { + USD: createPricePoint(BNB, 50), + }, + }, +] + describe("NFTs utils", () => { describe("getTotalFloorPrice", () => { test("should sum ETH and WETH floor prices", () => { @@ -114,4 +140,120 @@ describe("NFTs utils", () => { expect(getTotalFloorPrice(collections)).toMatchObject({ ETH: 0.001 }) }) }) + + describe("enrichCollectionWithUSDFloorPrice", () => { + test("should add USD price if floor price is in ETH", () => { + const collection = { + ...COLLECTION_MOCK, + floorPrice: { + value: 1, + tokenSymbol: "ETH", + }, + } + + expect( + enrichCollectionWithUSDFloorPrice(collection, assetsState, USD.symbol) + .floorPrice + ).toMatchObject({ + value: 1, + valueUSD: 2000, + tokenSymbol: "ETH", + }) + }) + test("should add USD price if floor price is in WETH", () => { + const collection = { + ...COLLECTION_MOCK, + floorPrice: { + value: 0.5, + tokenSymbol: "WETH", + }, + } + + expect( + enrichCollectionWithUSDFloorPrice(collection, assetsState, USD.symbol) + .floorPrice + ).toMatchObject({ + value: 0.5, + valueUSD: 1000, + tokenSymbol: "WETH", + }) + }) + test("should add USD price if floor price is in AVAX", () => { + const collection = { + ...COLLECTION_MOCK, + floorPrice: { + value: 2, + tokenSymbol: "AVAX", + }, + } + + expect( + enrichCollectionWithUSDFloorPrice(collection, assetsState, USD.symbol) + .floorPrice + ).toMatchObject({ + value: 2, + valueUSD: 30, + tokenSymbol: "AVAX", + }) + }) + test("should add USD price if floor price is in BNB", () => { + const collection = { + ...COLLECTION_MOCK, + floorPrice: { + value: 0.5, + tokenSymbol: "BNB", + }, + } + + expect( + enrichCollectionWithUSDFloorPrice(collection, assetsState, USD.symbol) + .floorPrice + ).toMatchObject({ + value: 0.5, + valueUSD: 25, + tokenSymbol: "BNB", + }) + }) + test("shouldn't add USD price if base asset is not found in assets list", () => { + const collection = { + ...COLLECTION_MOCK, + floorPrice: { + value: 0.5, + tokenSymbol: "MATIC", + }, + } + + expect( + enrichCollectionWithUSDFloorPrice(collection, assetsState, USD.symbol) + .floorPrice + ).toMatchObject({ + value: 0.5, + tokenSymbol: "MATIC", + }) + }) + test("shouldn't add USD price if there is not floor price", () => { + const collection = COLLECTION_MOCK + expect( + enrichCollectionWithUSDFloorPrice(collection, assetsState, USD.symbol) + .floorPrice + ).toBeUndefined() + }) + test("shouldn't add floor price if price is not using base assets", () => { + const collection = { + ...COLLECTION_MOCK, + floorPrice: { + value: 0.5, + tokenSymbol: "XYZ", + }, + } + + expect( + enrichCollectionWithUSDFloorPrice(collection, assetsState, USD.symbol) + .floorPrice + ).toMatchObject({ + value: 0.5, + tokenSymbol: "XYZ", + }) + }) + }) }) diff --git a/background/tests/factories.ts b/background/tests/factories.ts index c9eb65f875..f0b6bdc736 100644 --- a/background/tests/factories.ts +++ b/background/tests/factories.ts @@ -11,6 +11,7 @@ import { keccak256 } from "ethers/lib/utils" import { AccountBalance, AddressOnNetwork } from "../accounts" import { AnyAsset, + flipPricePoint, isFungibleAsset, PricePoint, SmartContractFungibleAsset, @@ -418,17 +419,11 @@ export const createPricePoint = ( const pricePoint: PricePoint = { pair: [asset, USD], - amounts: [10n ** BigInt(decimals), BigInt(Math.trunc(1e11 * price))], + amounts: [10n ** BigInt(decimals), BigInt(Math.trunc(1e10 * price))], time: Math.trunc(Date.now() / 1e3), } - if (flip) { - const { pair, amounts } = pricePoint - pricePoint.pair = [pair[1], pair[0]] - pricePoint.amounts = [amounts[1], amounts[0]] - } - - return pricePoint + return flip ? flipPricePoint(pricePoint) : pricePoint } export const createArrayWith0xHash = (length: number): string[] => From 974a22c64b57a50e00c4c762a89cb62b9c588292 Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Thu, 19 Jan 2023 13:23:15 +0100 Subject: [PATCH 117/121] Fix sorting NFT collections with floor prices equal 0 --- background/redux-slices/utils/nfts-utils.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/background/redux-slices/utils/nfts-utils.ts b/background/redux-slices/utils/nfts-utils.ts index 910c927593..cf8580d223 100644 --- a/background/redux-slices/utils/nfts-utils.ts +++ b/background/redux-slices/utils/nfts-utils.ts @@ -41,22 +41,19 @@ export const getAdditionalDataForFilter = ( return a ? { name: a.name, thumbnailURL: a.avatarURL } : {} } -/* Items are sorted by price in ETH. All other elements are added at the end. */ +/* Items are sorted by price in USD. All other elements are added at the end. */ const sortByPrice = ( type: "asc" | "desc", collection1: NFTCollectionEnriched, collection2: NFTCollectionEnriched ): number => { - if (collection1.floorPrice?.valueUSD && collection2.floorPrice?.valueUSD) { - if (type === "asc") { - return collection1.floorPrice.valueUSD - collection2.floorPrice.valueUSD - } - return collection2.floorPrice.valueUSD - collection1.floorPrice.valueUSD - } if (collection1.floorPrice?.valueUSD === undefined) return 1 if (collection2.floorPrice?.valueUSD === undefined) return -1 - return 1 + if (type === "asc") { + return collection1.floorPrice.valueUSD - collection2.floorPrice.valueUSD + } + return collection2.floorPrice.valueUSD - collection1.floorPrice.valueUSD } const sortByDate = ( From 5bc5f312539ec8d7af57cbe21b4fc1faabba749b Mon Sep 17 00:00:00 2001 From: Jagoda Berry Rybacka Date: Thu, 19 Jan 2023 13:41:13 +0100 Subject: [PATCH 118/121] Add unit tests for `sortByPrice()` util --- background/redux-slices/utils/nfts-utils.ts | 2 +- .../utils/nfts-utils.unit.test.ts | 40 +++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/background/redux-slices/utils/nfts-utils.ts b/background/redux-slices/utils/nfts-utils.ts index cf8580d223..f9903c5434 100644 --- a/background/redux-slices/utils/nfts-utils.ts +++ b/background/redux-slices/utils/nfts-utils.ts @@ -42,7 +42,7 @@ export const getAdditionalDataForFilter = ( } /* Items are sorted by price in USD. All other elements are added at the end. */ -const sortByPrice = ( +export const sortByPrice = ( type: "asc" | "desc", collection1: NFTCollectionEnriched, collection2: NFTCollectionEnriched diff --git a/background/redux-slices/utils/nfts-utils.unit.test.ts b/background/redux-slices/utils/nfts-utils.unit.test.ts index 8b66d0ea8a..6c039e3e2a 100644 --- a/background/redux-slices/utils/nfts-utils.unit.test.ts +++ b/background/redux-slices/utils/nfts-utils.unit.test.ts @@ -3,6 +3,7 @@ import { AssetsState } from "../assets" import { enrichCollectionWithUSDFloorPrice, getTotalFloorPrice, + sortByPrice, } from "./nfts-utils" import { createPricePoint } from "../../tests/factories" @@ -256,4 +257,43 @@ describe("NFTs utils", () => { }) }) }) + + describe("sortByPrice", () => { + const collections = [ + { + ...COLLECTION_MOCK, + id: "cheap", + floorPrice: { value: 1, valueUSD: 1, tokenSymbol: "USDT" }, + }, + { + ...COLLECTION_MOCK, + id: "expensive", + floorPrice: { value: 100, valueUSD: 100, tokenSymbol: "USDT" }, + }, + { + ...COLLECTION_MOCK, + id: "zero", + floorPrice: { value: 0, valueUSD: 0, tokenSymbol: "USDT" }, + }, + { + ...COLLECTION_MOCK, + id: "undefined", + }, + ] + + test("should sort collection by ascending floor price", () => { + expect( + collections + .sort((a, b) => sortByPrice("asc", a, b)) + .map((collection) => collection.id) + ).toMatchObject(["zero", "cheap", "expensive", "undefined"]) + }) + test("should sort collection by descending floor price", () => { + expect( + collections + .sort((a, b) => sortByPrice("desc", a, b)) + .map((collection) => collection.id) + ).toMatchObject(["expensive", "cheap", "zero", "undefined"]) + }) + }) }) From 82fa4494e066ace0dffd81b7dedb9391051a0eb3 Mon Sep 17 00:00:00 2001 From: Daedalus <0xDaedalus@users.noreply.github.com> Date: Thu, 19 Jan 2023 07:24:24 -0700 Subject: [PATCH 119/121] Better function signature for addEVMNetwork --- background/services/chain/db.ts | 21 ++++++++++++++------- background/services/chain/index.ts | 16 ++++++++-------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/background/services/chain/db.ts b/background/services/chain/db.ts index e66b3a7884..81e6727ff8 100644 --- a/background/services/chain/db.ts +++ b/background/services/chain/db.ts @@ -195,14 +195,21 @@ export class ChainDatabase extends Dexie { ) } - async addEVMNetwork( - chainName: string, - chainID: string, - decimals: number, - symbol: string, - assetName: string, + async addEVMNetwork({ + chainName, + chainID, + decimals, + symbol, + assetName, + rpcUrls, + }: { + chainName: string + chainID: string + decimals: number + symbol: string + assetName: string rpcUrls: string[] - ): Promise { + }): Promise { await this.networks.put({ name: chainName, chainID, diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index 72bb0607f5..62814761ac 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -1879,14 +1879,14 @@ export default class ChainService extends BaseService { async addCustomChain( chainInfo: ValidatedAddEthereumChainParameter ): Promise { - await this.db.addEVMNetwork( - chainInfo.chainName, - chainInfo.chainId, - chainInfo.nativeCurrency.decimals, - chainInfo.nativeCurrency.symbol, - chainInfo.nativeCurrency.name, - chainInfo.rpcUrls - ) + await this.db.addEVMNetwork({ + chainName: chainInfo.chainName, + chainID: chainInfo.chainId, + decimals: chainInfo.nativeCurrency.decimals, + symbol: chainInfo.nativeCurrency.symbol, + assetName: chainInfo.nativeCurrency.name, + rpcUrls: chainInfo.rpcUrls, + }) this.supportedNetworks = await this.db.getAllEVMNetworks() } } From 0bc48fa7f55db70d429512cdc78b4195ed164454 Mon Sep 17 00:00:00 2001 From: Daedalus <0xDaedalus@users.noreply.github.com> Date: Thu, 19 Jan 2023 07:32:41 -0700 Subject: [PATCH 120/121] Group chain service db initialization methods into their own method --- background/services/chain/db.ts | 6 +++++ background/services/chain/index.ts | 4 +--- .../chain/tests/index.integration.test.ts | 24 +++++++++++++++---- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/background/services/chain/db.ts b/background/services/chain/db.ts index 81e6727ff8..17d5cf74e1 100644 --- a/background/services/chain/db.ts +++ b/background/services/chain/db.ts @@ -167,6 +167,12 @@ export class ChainDatabase extends Dexie { }) } + async initialize(): Promise { + await this.initializeBaseAssets() + await this.initializeRPCs() + await this.initializeEVMNetworks() + } + async getLatestBlock(network: Network): Promise { return ( ( diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index 62814761ac..f5e7bb1731 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -300,8 +300,7 @@ export default class ChainService extends BaseService { override async internalStartService(): Promise { await super.internalStartService() - await this.initializeRPCs() - await this.initializeBaseAssets() + await this.db.initialize() await this.initializeNetworks() const accounts = await this.getAccountsToTrack() const trackedNetworks = await this.getTrackedNetworks() @@ -356,7 +355,6 @@ export default class ChainService extends BaseService { } async initializeNetworks(): Promise { - await this.db.initializeEVMNetworks() const rpcUrls = await this.db.getAllRpcUrls() if (!this.supportedNetworks.length) { this.supportedNetworks = await this.db.getAllEVMNetworks() diff --git a/background/services/chain/tests/index.integration.test.ts b/background/services/chain/tests/index.integration.test.ts index 9d418775b1..3289c0feea 100644 --- a/background/services/chain/tests/index.integration.test.ts +++ b/background/services/chain/tests/index.integration.test.ts @@ -11,9 +11,11 @@ import { createChainService, createLegacyTransactionRequest, } from "../../../tests/factories" +import { ChainDatabase } from "../db" import SerialFallbackProvider from "../serial-fallback-provider" type ChainServiceExternalized = Omit & { + db: ChainDatabase handlePendingTransaction: (transaction: AnyEVMTransaction) => void populateEVMTransactionNonce: ( transactionRequest: TransactionRequest @@ -48,13 +50,23 @@ describe("ChainService", () => { }) it("should initialize persisted data in the correct order", async () => { - const chainServiceInstance = await createChainService() - const initializeRPCs = sandbox.spy(chainServiceInstance, "initializeRPCs") + const chainServiceInstance = + (await createChainService()) as unknown as ChainServiceExternalized + + const initialize = sandbox.spy(chainServiceInstance.db, "initialize") const initializeBaseAssets = sandbox.spy( - chainServiceInstance, + chainServiceInstance.db, "initializeBaseAssets" ) + const initializeRPCs = sandbox.spy( + chainServiceInstance.db, + "initializeRPCs" + ) + const initializeEVMNetworks = sandbox.spy( + chainServiceInstance.db, + "initializeEVMNetworks" + ) const initializeNetworks = sandbox.spy( chainServiceInstance, @@ -63,8 +75,10 @@ describe("ChainService", () => { await chainServiceInstance.internalStartService() - expect(initializeRPCs.calledBefore(initializeBaseAssets)).toBe(true) - expect(initializeBaseAssets.calledBefore(initializeNetworks)).toBe(true) + expect(initialize.calledBefore(initializeNetworks)).toBe(true) + expect(initializeBaseAssets.calledBefore(initializeRPCs)).toBe(true) + expect(initializeRPCs.calledBefore(initializeEVMNetworks)).toBe(true) + expect(initializeEVMNetworks.calledBefore(initializeNetworks)).toBe(true) expect(initializeNetworks.called).toBe(true) }) }) From 2acc7f7a63cde2b290ef78bf8a527ea55c81a554 Mon Sep 17 00:00:00 2001 From: Daedalus <0xDaedalus@users.noreply.github.com> Date: Thu, 19 Jan 2023 07:34:15 -0700 Subject: [PATCH 121/121] Remove unused methods --- background/services/chain/index.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index f5e7bb1731..83b9cc1a6d 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -346,14 +346,6 @@ export default class ChainService extends BaseService { ) } - async initializeBaseAssets(): Promise { - await this.db.initializeBaseAssets() - } - - async initializeRPCs(): Promise { - await this.db.initializeRPCs() - } - async initializeNetworks(): Promise { const rpcUrls = await this.db.getAllRpcUrls() if (!this.supportedNetworks.length) {