diff --git a/.eslintrc b/.eslintrc index 91203fc36..611847f39 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,7 @@ "project": "./tsconfig.json", "parser": "@typescript-eslint/parser" }, - "plugins": ["@typescript-eslint"], + "plugins": ["@typescript-eslint", "unused-imports"], "extends": [ "react-app", "react-app/jest", @@ -21,7 +21,8 @@ "jest/no-identical-title": "warn", "jest/valid-expect": "warn", "@typescript-eslint/await-thenable": "warn", - "@typescript-eslint/no-unnecessary-condition": "warn" + "@typescript-eslint/no-unnecessary-condition": "warn", + "unused-imports/no-unused-imports": "warn" }, "overrides": [ { diff --git a/package.json b/package.json index 525be15b8..c0ba77745 100644 --- a/package.json +++ b/package.json @@ -102,6 +102,7 @@ "electronmon": "^2.0.2", "eslint": "^8.47.0", "eslint-plugin-storybook": "^0.6.13", + "eslint-plugin-unused-imports": "^3.0.0", "framer-motion": "^10.15.2", "graphql": "^16.8.0", "graphql-request": "^6.1.0", diff --git a/src/components/AccountCard/AccountCard.test.tsx b/src/components/AccountCard/AccountCard.test.tsx index 7521d168c..75f4198e1 100644 --- a/src/components/AccountCard/AccountCard.test.tsx +++ b/src/components/AccountCard/AccountCard.test.tsx @@ -20,14 +20,10 @@ import { multisigToAccount } from "../../utils/multisig/helpers"; import { Multisig } from "../../utils/multisig/types"; import multisigsSlice, { multisigActions } from "../../utils/redux/slices/multisigsSlice"; import tokensSlice from "../../utils/redux/slices/tokensSlice"; -import { TezosNetwork } from "../../types/TezosNetwork"; -const { - updateTezBalance, - updateTokenBalance, - updateTezTransfers, - updateNetwork, - updateDelegations, -} = assetsSlice.actions; +import { GHOSTNET, MAINNET } from "../../types/Network"; +import { networksActions } from "../../utils/redux/slices/networks"; +const { updateTezBalance, updateTokenBalance, updateTezTransfers, updateDelegations } = + assetsSlice.actions; const { addAccount } = accountsSlice.actions; const { setMultisigs } = multisigsSlice.actions; @@ -39,7 +35,7 @@ const mockNft = mockNFTToken(0, pkh); const SELECTED_ACCOUNT_BALANCE = 33200000000; beforeEach(() => { - store.dispatch(assetsSlice.actions.updateNetwork(TezosNetwork.MAINNET)); + store.dispatch(networksActions.setCurrent(MAINNET)); store.dispatch(setMultisigs(multisigs)); store.dispatch(addAccount([selectedAccount, mockImplicitAccount(1)])); store.dispatch(updateTezBalance([{ address: pkh, balance: SELECTED_ACCOUNT_BALANCE }])); @@ -54,7 +50,7 @@ beforeEach(() => { ); store.dispatch( tokensSlice.actions.addTokens({ - network: TezosNetwork.MAINNET, + network: MAINNET, tokens: [ hedgehoge(selectedAccount.address).token, tzBtsc(selectedAccount.address).token, @@ -90,7 +86,7 @@ describe("", () => { expect(link).toHaveProperty("href", expectedLink); { - act(() => store.dispatch(updateNetwork(TezosNetwork.GHOSTNET))); + act(() => store.dispatch(networksActions.setCurrent(GHOSTNET))); const tzktLink = screen.getByTestId("asset-panel-tablist"); const link = within(tzktLink).getByRole("link", {}); diff --git a/src/components/AccountCard/AccountCardDisplay.tsx b/src/components/AccountCard/AccountCardDisplay.tsx index ace20766e..874ade01a 100644 --- a/src/components/AccountCard/AccountCardDisplay.tsx +++ b/src/components/AccountCard/AccountCardDisplay.tsx @@ -12,7 +12,7 @@ import { AssetsPanel } from "./AssetsPanel/AssetsPanel"; import MultisigApprovers from "./MultisigApprovers"; import AddressPill from "../AddressPill/AddressPill"; import { OperationDisplay } from "../../types/Transfer"; -import { TezosNetwork } from "../../types/TezosNetwork"; +import { Network } from "../../types/Network"; import { DynamicModalContext } from "../DynamicModal"; import { useContext } from "react"; import DelegationFormPage from "../SendFlow/Delegation/FormPage"; @@ -31,7 +31,7 @@ type Props = { nfts: Array; operationDisplays: Array; account: Account; - network: TezosNetwork; + network: Network; }; const RoundButton: React.FC<{ diff --git a/src/components/AccountCard/AssetsPanel/AssetPanel.test.tsx b/src/components/AccountCard/AssetsPanel/AssetPanel.test.tsx index 1de43585f..9dfe1ca4d 100644 --- a/src/components/AccountCard/AssetsPanel/AssetPanel.test.tsx +++ b/src/components/AccountCard/AssetsPanel/AssetPanel.test.tsx @@ -1,6 +1,6 @@ import { mockMultisigAccount } from "../../../mocks/factories"; import { render, screen } from "../../../mocks/testUtils"; -import { TezosNetwork } from "../../../types/TezosNetwork"; +import { MAINNET } from "../../../types/Network"; import { AssetsPanel } from "./AssetsPanel"; describe("", () => { @@ -12,7 +12,7 @@ describe("", () => { nfts={[]} tokens={[]} operationDisplays={[]} - network={TezosNetwork.MAINNET} + network={MAINNET} /> ); diff --git a/src/components/AccountCard/AssetsPanel/AssetsPanel.tsx b/src/components/AccountCard/AssetsPanel/AssetsPanel.tsx index d5a9926ae..a215c5f67 100644 --- a/src/components/AccountCard/AssetsPanel/AssetsPanel.tsx +++ b/src/components/AccountCard/AssetsPanel/AssetsPanel.tsx @@ -13,7 +13,7 @@ import { DelegationDisplay } from "./DelegationDisplay"; import MultisigPendingAccordion from "./MultisigPendingAccordion"; import { NFTsGrid } from "./NFTsGrid"; import { TokenList } from "./TokenList"; -import { TezosNetwork } from "../../../types/TezosNetwork"; +import { Network } from "../../../types/Network"; import { useAllDelegations } from "../../../utils/hooks/assetsHooks"; export const AssetsPanel: React.FC<{ @@ -21,7 +21,7 @@ export const AssetsPanel: React.FC<{ nfts: Array; account: Account; operationDisplays: OperationDisplay[]; - network: TezosNetwork; + network: Network; }> = ({ tokens, nfts, account, operationDisplays, network }) => { const isMultisig = account.type === AccountType.MULTISIG; const rawDelegations = useAllDelegations()[account.address.pkh]; diff --git a/src/components/AccountCard/AssetsPanel/MultisigPendingAccordion/MultisigDecodedOperationItem.test.tsx b/src/components/AccountCard/AssetsPanel/MultisigPendingAccordion/MultisigDecodedOperationItem.test.tsx index f693bc43d..5c19bcdda 100644 --- a/src/components/AccountCard/AssetsPanel/MultisigPendingAccordion/MultisigDecodedOperationItem.test.tsx +++ b/src/components/AccountCard/AssetsPanel/MultisigPendingAccordion/MultisigDecodedOperationItem.test.tsx @@ -1,16 +1,17 @@ import { mockContractAddress, mockImplicitAddress } from "../../../../mocks/factories"; import { render, screen } from "../../../../mocks/testUtils"; -import { TezosNetwork } from "../../../../types/TezosNetwork"; import { RawTokenBalance } from "../../../../types/TokenBalance"; import { assetsActions } from "../../../../utils/redux/slices/assetsSlice"; import store from "../../../../utils/redux/store"; import tokensSlice from "../../../../utils/redux/slices/tokensSlice"; import MultisigDecodedOperationItem from "./MultisigDecodedOperationItem"; +import { MAINNET } from "../../../../types/Network"; +import { networksActions } from "../../../../utils/redux/slices/networks"; -const { updateTokenBalance, updateNetwork } = assetsActions; +const { updateTokenBalance } = assetsActions; beforeEach(() => { - store.dispatch(updateNetwork(TezosNetwork.MAINNET)); + store.dispatch(networksActions.setCurrent(MAINNET)); }); describe("", () => { @@ -71,7 +72,7 @@ describe("", () => { store.dispatch(updateTokenBalance([mockBalancePlayload])); store.dispatch( tokensSlice.actions.addTokens({ - network: TezosNetwork.MAINNET, + network: MAINNET, tokens: [mockBalancePlayload.token], }) ); @@ -119,7 +120,7 @@ describe("", () => { store.dispatch(updateTokenBalance([mockBalancePlayload])); store.dispatch( tokensSlice.actions.addTokens({ - network: TezosNetwork.MAINNET, + network: MAINNET, tokens: [mockBalancePlayload.token], }) ); diff --git a/src/components/AccountCard/AssetsPanel/MultisigPendingAccordion/MultisigPendingAccordionItem.test.tsx b/src/components/AccountCard/AssetsPanel/MultisigPendingAccordion/MultisigPendingAccordionItem.test.tsx index 759078b13..d970d8532 100644 --- a/src/components/AccountCard/AssetsPanel/MultisigPendingAccordion/MultisigPendingAccordionItem.test.tsx +++ b/src/components/AccountCard/AssetsPanel/MultisigPendingAccordion/MultisigPendingAccordionItem.test.tsx @@ -20,6 +20,7 @@ import { estimate, executeOperations, makeToolkit } from "../../../../utils/tezo import BigNumber from "bignumber.js"; import { makeAccountOperations } from "../../../sendForm/types"; import { makeMultisigApproveOrExecuteOperation } from "../../../../types/Operation"; +import { MAINNET } from "../../../../types/Network"; jest.mock("../../../../utils/hooks/accountUtils"); jest.mock("../../../sendForm/types"); @@ -102,7 +103,7 @@ describe("", () => { makeMultisigApproveOrExecuteOperation(multisig.address, "execute", pendingOps[0].id), ]); - expect(jest.mocked(estimate)).toHaveBeenCalledWith(operation, "mainnet"); + expect(jest.mocked(estimate)).toHaveBeenCalledWith(operation, MAINNET); fillPassword("mockPass"); @@ -150,7 +151,7 @@ describe("", () => { makeMultisigApproveOrExecuteOperation(multisig.address, "approve", pendingOps[0].id), ]); - expect(jest.mocked(estimate)).toHaveBeenCalledWith(operations, "mainnet"); + expect(jest.mocked(estimate)).toHaveBeenCalledWith(operations, MAINNET); fillPassword("mockPass"); diff --git a/src/components/AccountCard/AssetsPanel/MultisigPendingAccordion/MultisigSignerTile.tsx b/src/components/AccountCard/AssetsPanel/MultisigPendingAccordion/MultisigSignerTile.tsx index 9f5abf90a..aa3841583 100644 --- a/src/components/AccountCard/AssetsPanel/MultisigPendingAccordion/MultisigSignerTile.tsx +++ b/src/components/AccountCard/AssetsPanel/MultisigPendingAccordion/MultisigSignerTile.tsx @@ -2,7 +2,6 @@ import React, { useContext } from "react"; import { ImplicitAccount, MultisigAccount } from "../../../../types/Account"; import { ImplicitAddress } from "../../../../types/Address"; import { useGetImplicitAccountSafe } from "../../../../utils/hooks/accountHooks"; -import { useSelectedNetwork } from "../../../../utils/hooks/assetsHooks"; import { useGetContactName } from "../../../../utils/hooks/contactsHooks"; import { useAsyncActionHandler } from "../../../../utils/hooks/useAsyncActionHandler"; import { MultisigOperation } from "../../../../utils/multisig/types"; @@ -13,6 +12,7 @@ import { estimate } from "../../../../utils/tezos"; import { DynamicModalContext } from "../../../DynamicModal"; import SignPage from "../../../SendFlow/Multisig/SignPage"; import { MultisigSignerTileDisplay } from "./MultisigSignerTileDisplay"; +import { useSelectedNetwork } from "../../../../utils/hooks/networkHooks"; const MultisigSignerTile: React.FC<{ signerAddress: ImplicitAddress; diff --git a/src/components/AccountCard/index.tsx b/src/components/AccountCard/index.tsx index 03d17a1c5..d49af7a1f 100644 --- a/src/components/AccountCard/index.tsx +++ b/src/components/AccountCard/index.tsx @@ -6,12 +6,12 @@ import { useGetAccountNFTs, useGetAccountOperationDisplays, useGetDollarBalance, - useSelectedNetwork, } from "../../utils/hooks/assetsHooks"; import { DynamicModalContext } from "../DynamicModal"; import { useReceiveModal } from "../ReceiveModal"; import SendTezForm from "../SendFlow/Tez/FormPage"; import { AccountCardDisplay } from "./AccountCardDisplay"; +import { useSelectedNetwork } from "../../utils/hooks/networkHooks"; export const AccountCard: React.FC<{ account: Account }> = ({ account }) => { const accountBalance = useGetAccountBalance(); diff --git a/src/components/AddressPill/AddressPill.test.tsx b/src/components/AddressPill/AddressPill.test.tsx index c5afd9270..ae6e9fbcd 100644 --- a/src/components/AddressPill/AddressPill.test.tsx +++ b/src/components/AddressPill/AddressPill.test.tsx @@ -2,14 +2,13 @@ import { contact1 } from "../../mocks/contacts"; import { mockFA1TokenRaw, mockImplicitAddress } from "../../mocks/factories"; import { render, screen } from "../../mocks/testUtils"; import { parseContractPkh, parseImplicitPkh } from "../../types/Address"; -import { TezosNetwork } from "../../types/TezosNetwork"; -import assetsSlice from "../../utils/redux/slices/assetsSlice"; import { contactsActions } from "../../utils/redux/slices/contactsSlice"; import store from "../../utils/redux/store"; import tokensSlice from "../../utils/redux/slices/tokensSlice"; import AddressPill from "./AddressPill"; +import { MAINNET } from "../../types/Network"; +import { networksActions } from "../../utils/redux/slices/networks"; const { upsert } = contactsActions; -const { updateNetwork } = assetsSlice.actions; describe("", () => { it("displays left icon", () => { @@ -42,10 +41,8 @@ describe("", () => { it("is removable for two icons", () => { const address = mockImplicitAddress(0); const fa1 = mockFA1TokenRaw(1, address.pkh, 123); - store.dispatch(updateNetwork(TezosNetwork.MAINNET)); - store.dispatch( - tokensSlice.actions.addTokens({ network: TezosNetwork.MAINNET, tokens: [fa1.token] }) - ); + store.dispatch(networksActions.setCurrent(MAINNET)); + store.dispatch(tokensSlice.actions.addTokens({ network: MAINNET, tokens: [fa1.token] })); render( { - store.dispatch(assetsSlice.actions.updateNetwork(TezosNetwork.MAINNET)); + store.dispatch(networksActions.setCurrent(MAINNET)); }); describe("useAddressKind", () => { @@ -58,7 +59,7 @@ describe("useAddressKind", () => { delete withoutName.token.metadata?.name; store.dispatch( tokensSlice.actions.addTokens({ - network: TezosNetwork.MAINNET, + network: MAINNET, tokens: [withoutName.token], }) ); @@ -76,7 +77,7 @@ describe("useAddressKind", () => { it("returns label", () => { store.dispatch( tokensSlice.actions.addTokens({ - network: TezosNetwork.MAINNET, + network: MAINNET, tokens: [tokenBalance.token], }) ); @@ -140,7 +141,7 @@ describe("useAddressKind", () => { store.dispatch(accountsSlice.actions.addAccount([mockImplicitAccount(0)])); store.dispatch( tokensSlice.actions.addTokens({ - network: TezosNetwork.MAINNET, + network: MAINNET, tokens: [hedgehoge(mockImplicitAddress(0)).token, uUSD(mockImplicitAddress(0)).token], }) ); diff --git a/src/components/AddressPill/useAddressKind.ts b/src/components/AddressPill/useAddressKind.ts index 8bbb410d3..432bdce2b 100644 --- a/src/components/AddressPill/useAddressKind.ts +++ b/src/components/AddressPill/useAddressKind.ts @@ -2,7 +2,7 @@ import { Address } from "../../types/Address"; import { useGetContactName } from "../../utils/hooks/contactsHooks"; import { useGetOwnedAccountSafe } from "../../utils/hooks/accountHooks"; import { AccountType } from "../../types/Account"; -import { useGetBaker, useSelectedNetwork } from "../../utils/hooks/assetsHooks"; +import { useGetBaker } from "../../utils/hooks/assetsHooks"; import { AddressKind, BakerAddress, @@ -13,6 +13,7 @@ import { OwnedMultisigAccountAddress, } from "./types"; import { useGetTokenType } from "../../utils/hooks/tokensHooks"; +import { useSelectedNetwork } from "../../utils/hooks/networkHooks"; const useAddressKind = (address: Address): AddressKind => { const ownedAccount = useOwnedAccountAddressKind(address); diff --git a/src/components/BuyTez/BuyTezForm.test.tsx b/src/components/BuyTez/BuyTezForm.test.tsx index a389aad5a..0842bcb2d 100644 --- a/src/components/BuyTez/BuyTezForm.test.tsx +++ b/src/components/BuyTez/BuyTezForm.test.tsx @@ -1,9 +1,9 @@ import { Modal } from "@chakra-ui/react"; import { render, screen } from "../../mocks/testUtils"; -import { TezosNetwork } from "../../types/TezosNetwork"; -import { assetsActions } from "../../utils/redux/slices/assetsSlice"; import store from "../../utils/redux/store"; import BuyTezForm from "./BuyTezForm"; +import { GHOSTNET, MAINNET } from "../../types/Network"; +import { networksActions } from "../../utils/redux/slices/networks"; const fixture = () => ( {}}> @@ -13,7 +13,7 @@ const fixture = () => ( describe("", () => { test("renders request Tez from faucet on ghostnet", async () => { - store.dispatch(assetsActions.updateNetwork(TezosNetwork.GHOSTNET)); + store.dispatch(networksActions.setCurrent(GHOSTNET)); render(fixture()); // Async findBy because otherwise we get act warning since store.dispatch is async @@ -22,11 +22,11 @@ describe("", () => { }); test("renders Buy Tez from faucet on ghostnet", async () => { - store.dispatch(assetsActions.updateNetwork(TezosNetwork.MAINNET)); + store.dispatch(networksActions.setCurrent(MAINNET)); render(fixture()); const result = await screen.findByTestId("buy-tez-button"); expect(result).toHaveTextContent("Buy Tez"); - expect(screen.getByTestId("buy-tez-selector")).toBeTruthy(); + expect(screen.getByTestId("buy-tez-selector")).toBeInTheDocument(); }); }); diff --git a/src/components/BuyTez/BuyTezForm.tsx b/src/components/BuyTez/BuyTezForm.tsx index cc9b069f4..7f77fd620 100644 --- a/src/components/BuyTez/BuyTezForm.tsx +++ b/src/components/BuyTez/BuyTezForm.tsx @@ -9,20 +9,21 @@ import { Button, } from "@chakra-ui/react"; import { FormProvider, useForm } from "react-hook-form"; -import { TezosNetwork } from "../../types/TezosNetwork"; import { navigateToExternalLink } from "../../utils/helpers"; -import { useSelectedNetwork } from "../../utils/hooks/assetsHooks"; -import { wertUrls } from "../../utils/tezos/consts"; import { OwnedImplicitAccountsAutocomplete } from "../AddressAutocomplete"; import { FormErrorMessage } from "../FormErrorMessage"; +import { useSelectedNetwork } from "../../utils/hooks/networkHooks"; const BuyTezForm = () => { const network = useSelectedNetwork(); - const isMainnet = network === TezosNetwork.MAINNET; + const isMainnet = network.name === "mainnet"; const title = isMainnet ? "Buy Tez" : "Request Tez from faucet"; const onSubmit = async ({ recipient }: { recipient: string }) => { - let url = wertUrls[network]; + let url = network.buyTezUrl; + if (!url) { + throw new Error(`${network.name} does not have a buyTezUrl defined`); + } if (isMainnet) { url += `/default/widget/?commodity=XTZ%3ATezos&address=${recipient}`; } diff --git a/src/components/CSVFileUploader/CSVFileUploadForm.tsx b/src/components/CSVFileUploader/CSVFileUploadForm.tsx index 355cba894..52f815c27 100644 --- a/src/components/CSVFileUploader/CSVFileUploadForm.tsx +++ b/src/components/CSVFileUploader/CSVFileUploadForm.tsx @@ -18,7 +18,6 @@ import { FormProvider, useForm } from "react-hook-form"; import { Operation } from "../../types/Operation"; import { RawPkh } from "../../types/Address"; import { useGetBestSignerForAccount, useGetOwnedAccount } from "../../utils/hooks/accountHooks"; -import { useSelectedNetwork } from "../../utils/hooks/assetsHooks"; import { useGetToken } from "../../utils/hooks/tokensHooks"; import { useAppDispatch } from "../../utils/redux/hooks"; import { estimateAndUpdateBatch } from "../../utils/redux/thunks/estimateAndUpdateBatch"; @@ -29,6 +28,7 @@ import { useAsyncActionHandler } from "../../utils/hooks/useAsyncActionHandler"; import { FormErrorMessage } from "../FormErrorMessage"; import { useContext } from "react"; import { DynamicModalContext } from "../DynamicModal"; +import { useSelectedNetwork } from "../../utils/hooks/networkHooks"; type FormFields = { sender: RawPkh; diff --git a/src/components/NetworkSelector.test.tsx b/src/components/NetworkSelector.test.tsx new file mode 100644 index 000000000..13142f2d3 --- /dev/null +++ b/src/components/NetworkSelector.test.tsx @@ -0,0 +1,22 @@ +import { fireEvent, render, screen } from "../mocks/testUtils"; +import { GHOSTNET } from "../types/Network"; +import store from "../utils/redux/store"; +import NetworkSelector from "./NetworkSelector"; + +describe("", () => { + it("shows the current network", () => { + render(); + + expect(screen.getByTestId("network-selector")).toHaveValue("mainnet"); + }); + + it("changes the selected network globally", () => { + render(); + + fireEvent.change(screen.getByTestId("network-selector"), { target: { value: "ghostnet" } }); + + expect(screen.getByTestId("network-selector")).toHaveValue("ghostnet"); + + expect(store.getState().networks.current).toEqual(GHOSTNET); + }); +}); diff --git a/src/components/NetworkSelector.tsx b/src/components/NetworkSelector.tsx new file mode 100644 index 000000000..b8b926b60 --- /dev/null +++ b/src/components/NetworkSelector.tsx @@ -0,0 +1,39 @@ +import { Box, Select } from "@chakra-ui/react"; +import { Network, NetworkName } from "../types/Network"; +import { useAvailableNetworks, useSelectedNetwork } from "../utils/hooks/networkHooks"; +import { useAppDispatch } from "../utils/redux/hooks"; +import { networksActions } from "../utils/redux/slices/networks"; +import { capitalize } from "lodash"; +import colors from "../style/colors"; + +export const NetworkSelector = () => { + const currentNetwork = useSelectedNetwork(); + const availableNetworks = useAvailableNetworks(); + const dispatch = useAppDispatch(); + + const changeNetwork = (name: NetworkName) => { + const network = availableNetworks.find(network => network.name === name) as Network; + dispatch(networksActions.setCurrent(network)); + }; + + return ( + + + + ); +}; + +export default NetworkSelector; diff --git a/src/components/NetworkSelector/NetworkSelectorDisplay.tsx b/src/components/NetworkSelector/NetworkSelectorDisplay.tsx deleted file mode 100644 index 80e459330..000000000 --- a/src/components/NetworkSelector/NetworkSelectorDisplay.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import { Box, Select } from "@chakra-ui/react"; -import React from "react"; -import { TezosNetwork } from "../../types/TezosNetwork"; - -export const NetworkSelectorDisplay: React.FC<{ - value: TezosNetwork; - onChange: (val: TezosNetwork) => void; -}> = ({ value, onChange }) => { - return ( - - - - ); -}; diff --git a/src/components/NetworkSelector/index.tsx b/src/components/NetworkSelector/index.tsx deleted file mode 100644 index 3d058a88b..000000000 --- a/src/components/NetworkSelector/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { TezosNetwork } from "../../types/TezosNetwork"; -import { useSelectedNetwork } from "../../utils/hooks/assetsHooks"; -import { useAppDispatch } from "../../utils/redux/hooks"; -import assetsSlice from "../../utils/redux/slices/assetsSlice"; -import { NetworkSelectorDisplay } from "./NetworkSelectorDisplay"; - -export const NetworkSelector = () => { - const network = useSelectedNetwork(); - const dispatch = useAppDispatch(); - - const changeNetwork = (network: TezosNetwork) => { - dispatch(assetsSlice.actions.updateNetwork(network)); - }; - - return ; -}; - -export default NetworkSelector; diff --git a/src/components/Onboarding/FakeAccount.tsx b/src/components/Onboarding/FakeAccount.tsx index e1b336a1d..521feb6e6 100644 --- a/src/components/Onboarding/FakeAccount.tsx +++ b/src/components/Onboarding/FakeAccount.tsx @@ -2,11 +2,11 @@ import { Button, FormControl, FormLabel, Input, Text } from "@chakra-ui/react"; import { useForm } from "react-hook-form"; import { RpcClient } from "@taquito/rpc"; -import { nodeUrls } from "../../utils/tezos/consts"; import { SupportedIcons } from "../CircleIcon"; import ModalContentWrapper from "./ModalContentWrapper"; import { useRestoreLedger } from "../../utils/hooks/accountHooks"; import { defaultDerivationPathPattern } from "../../utils/account/derivationPathUtils"; +import { MAINNET } from "../../types/Network"; export const FakeAccount = ({ onClose }: { onClose: () => void }) => { const { @@ -17,7 +17,7 @@ export const FakeAccount = ({ onClose }: { onClose: () => void }) => { const restoreLedger = useRestoreLedger(); const onSubmit = async ({ pkh, name }: { pkh: string; name: string }) => { - const rpc = new RpcClient(nodeUrls["mainnet"]); + const rpc = new RpcClient(MAINNET.rpcUrl); const managerKey = await rpc.getManagerKey(pkh); const pk = typeof managerKey === "string" ? managerKey : managerKey.key; restoreLedger(defaultDerivationPathPattern, pk, pkh, name); diff --git a/src/components/SendFlow/onSubmitFormActionHooks.tsx b/src/components/SendFlow/onSubmitFormActionHooks.tsx index 793003ec8..cdb9be901 100644 --- a/src/components/SendFlow/onSubmitFormActionHooks.tsx +++ b/src/components/SendFlow/onSubmitFormActionHooks.tsx @@ -1,13 +1,13 @@ import { useContext } from "react"; import { BaseFormValues, FormPageProps, SignPageProps, useMakeFormOperations } from "./utils"; import { DynamicModalContext } from "../DynamicModal"; -import { useSelectedNetwork } from "../../utils/hooks/assetsHooks"; import { Operation } from "../../types/Operation"; import { useAppDispatch } from "../../utils/redux/hooks"; import { useToast } from "@chakra-ui/toast"; import { estimateAndUpdateBatch } from "../../utils/redux/thunks/estimateAndUpdateBatch"; import { useAsyncActionHandler } from "../../utils/hooks/useAsyncActionHandler"; import { estimate } from "../../utils/tezos"; +import { useSelectedNetwork } from "../../utils/hooks/networkHooks"; // This file defines hooks to create actions when form is submitted. diff --git a/src/components/SendFlow/utils.tsx b/src/components/SendFlow/utils.tsx index e14457c33..bed729b8c 100644 --- a/src/components/SendFlow/utils.tsx +++ b/src/components/SendFlow/utils.tsx @@ -6,7 +6,7 @@ import { useGetImplicitAccount, useGetOwnedAccount, } from "../../utils/hooks/accountHooks"; -import { useClearBatch, useSelectedNetwork } from "../../utils/hooks/assetsHooks"; +import { useClearBatch } from "../../utils/hooks/assetsHooks"; import { DynamicModalContext } from "../DynamicModal"; import { AccountOperations, makeAccountOperations } from "../sendForm/types"; import BigNumber from "bignumber.js"; @@ -18,6 +18,7 @@ import { SuccessStep } from "../sendForm/steps/SuccessStep"; import { estimate, executeOperations } from "../../utils/tezos"; import { useForm } from "react-hook-form"; import { repeat } from "lodash"; +import { useSelectedNetwork } from "../../utils/hooks/networkHooks"; // Convert given optional fields to required // For example: diff --git a/src/components/sendForm/SendForm.test.tsx b/src/components/sendForm/SendForm.test.tsx index ec1507ad7..c3fd6c855 100644 --- a/src/components/sendForm/SendForm.test.tsx +++ b/src/components/sendForm/SendForm.test.tsx @@ -35,8 +35,8 @@ import { multisigs } from "../../mocks/multisig"; import { parseContractPkh, parsePkh } from "../../types/Address"; import tokensSlice from "../../utils/redux/slices/tokensSlice"; import { fa1Token, fa2Token, nft } from "../../mocks/tzktResponse"; -import { TezosNetwork } from "../../types/TezosNetwork"; import { estimate, executeOperations, makeToolkit } from "../../utils/tezos"; +import { MAINNET } from "../../types/Network"; // These tests might take long in the CI jest.setTimeout(10000); @@ -65,7 +65,7 @@ beforeEach(async () => { document.getElementById("chakra-toast-portal")?.remove(); store.dispatch( tokensSlice.actions.addTokens({ - network: TezosNetwork.MAINNET, + network: MAINNET, tokens: [fa1Token.token, fa2Token.token, nft.token], }) ); @@ -159,7 +159,7 @@ describe("", () => { sender: mockImplicitAccount(2), signer: mockImplicitAccount(2), }, - "mainnet" + MAINNET ); fillPassword("mockPass"); @@ -180,10 +180,7 @@ describe("", () => { await waitFor(() => { expect(screen.getByText(/Operation Submitted/i)).toBeInTheDocument(); - expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty( - "href", - "https://mainnet.tzkt.io/mockHash" - ); + expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty("href", "https://tzkt.io/mockHash"); }); expect(jest.mocked(executeOperations)).toHaveBeenCalledWith( @@ -272,7 +269,7 @@ describe("", () => { sender: mockImplicitAccount(2), signer: mockImplicitAccount(2), }, - "mainnet" + MAINNET ); fillPassword("mockPass"); @@ -292,10 +289,7 @@ describe("", () => { await waitFor(() => { expect(screen.getByText(/Operation Submitted/i)).toBeInTheDocument(); - expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty( - "href", - "https://mainnet.tzkt.io/mockHash" - ); + expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty("href", "https://tzkt.io/mockHash"); }); expect(jest.mocked(executeOperations)).toHaveBeenCalledWith( @@ -374,10 +368,7 @@ describe("", () => { await waitFor(() => { expect(screen.getByText(/Operation Submitted/i)).toBeInTheDocument(); - expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty( - "href", - "https://mainnet.tzkt.io/mockHash" - ); + expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty("href", "https://tzkt.io/mockHash"); }); const contractAddress = nft.token.contract.address as string; @@ -491,10 +482,7 @@ describe("", () => { await waitFor(() => { expect(screen.getByText(/Operation Submitted/i)).toBeInTheDocument(); - expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty( - "href", - "https://mainnet.tzkt.io/mockHash" - ); + expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty("href", "https://tzkt.io/mockHash"); }); }); @@ -539,10 +527,7 @@ describe("", () => { await waitFor(() => { expect(screen.getByText(/Operation Submitted/i)).toBeInTheDocument(); - expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty( - "href", - "https://mainnet.tzkt.io/mockHash" - ); + expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty("href", "https://tzkt.io/mockHash"); }); }); @@ -605,10 +590,7 @@ describe("", () => { await waitFor(() => { expect(screen.getByText(/Operation Submitted/i)).toBeInTheDocument(); - expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty( - "href", - "https://mainnet.tzkt.io/mockHash" - ); + expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty("href", "https://tzkt.io/mockHash"); }); }); }); diff --git a/src/components/sendForm/SendForm.tsx b/src/components/sendForm/SendForm.tsx index f34f375f9..dc633c4f3 100644 --- a/src/components/sendForm/SendForm.tsx +++ b/src/components/sendForm/SendForm.tsx @@ -4,7 +4,7 @@ import { useEffect, useRef, useState } from "react"; import { RawPkh } from "../../types/Address"; import { Operation } from "../../types/Operation"; import { useGetBestSignerForAccount, useGetOwnedAccount } from "../../utils/hooks/accountHooks"; -import { useClearBatch, useSelectedNetwork } from "../../utils/hooks/assetsHooks"; +import { useClearBatch } from "../../utils/hooks/assetsHooks"; import { useAsyncActionHandler } from "../../utils/hooks/useAsyncActionHandler"; import { useAppDispatch } from "../../utils/redux/hooks"; import { assetsActions } from "../../utils/redux/slices/assetsSlice"; @@ -19,6 +19,7 @@ import { makeAccountOperations, SendFormMode, } from "./types"; +import { useSelectedNetwork } from "../../utils/hooks/networkHooks"; export const SendForm = ({ sender, diff --git a/src/components/sendForm/components/SignButton.tsx b/src/components/sendForm/components/SignButton.tsx index 52913493b..daa54727e 100644 --- a/src/components/sendForm/components/SignButton.tsx +++ b/src/components/sendForm/components/SignButton.tsx @@ -11,10 +11,10 @@ import { } from "../../../types/Account"; import { useGetSecretKey } from "../../../utils/hooks/accountUtils"; import { useAsyncActionHandler } from "../../../utils/hooks/useAsyncActionHandler"; -import { useSelectedNetwork } from "../../../utils/hooks/assetsHooks"; import { makeToolkit } from "../../../utils/tezos"; import { FormErrorMessage } from "../../FormErrorMessage"; import PasswordInput from "../../PasswordInput"; +import { useSelectedNetwork } from "../../../utils/hooks/networkHooks"; export const SignWithGoogleButton: React.FC< PropsWithChildren<{ diff --git a/src/components/sendForm/steps/SuccessStep.tsx b/src/components/sendForm/steps/SuccessStep.tsx index 89266a0a9..8dd8d10e3 100644 --- a/src/components/sendForm/steps/SuccessStep.tsx +++ b/src/components/sendForm/steps/SuccessStep.tsx @@ -14,8 +14,8 @@ import { Link } from "react-router-dom"; import React, { useContext } from "react"; import { getHashUrl } from "../../../views/operations/operationsUtils"; import { TzktLink } from "../../TzktLink"; -import { useSelectedNetwork } from "../../../utils/hooks/assetsHooks"; import { DynamicModalContext } from "../../DynamicModal"; +import { useSelectedNetwork } from "../../../utils/hooks/networkHooks"; export const SuccessStep: React.FC<{ hash: string }> = ({ hash }) => { const network = useSelectedNetwork(); diff --git a/src/mocks/devSignerKeys.ts b/src/mocks/devSignerKeys.ts index 66a8dfb0c..a3f54e059 100644 --- a/src/mocks/devSignerKeys.ts +++ b/src/mocks/devSignerKeys.ts @@ -1,9 +1,9 @@ import { InMemorySigner } from "@taquito/signer"; import { TezosToolkit } from "@taquito/taquito"; -import { TezosNetwork } from "../types/TezosNetwork"; import { getDefaultDerivationPath } from "../utils/account/derivationPathUtils"; import { makeToolkit } from "../utils/tezos"; import { mnemonic1 } from "./mockMnemonic"; +import { GHOSTNET } from "../types/Network"; // make the default signer used in the dev mode. // e.g. makeDefaultDevSigner(0) is equivalent to the "restored account 0". @@ -47,6 +47,6 @@ export const makeToolkitFromDefaultDevSeed = async (index: number): Promise { afterEach(() => { act(() => { store.dispatch(accountsSlice.actions.reset()); - store.dispatch(assetsSlice.actions.reset()); store.dispatch(multisigsSlice.actions.reset()); store.dispatch(contactsActions.reset()); store.dispatch(tokensActions.reset()); store.dispatch(errorsSlice.actions.reset()); + store.dispatch(networksActions.reset()); }); }); diff --git a/src/types/Network.ts b/src/types/Network.ts new file mode 100644 index 000000000..ce25b9613 --- /dev/null +++ b/src/types/Network.ts @@ -0,0 +1,27 @@ +export const MAINNET: Network = { + name: "mainnet", + rpcUrl: "https://prod.tcinfra.net/rpc/mainnet/", + tzktApiUrl: "https://api.mainnet.tzkt.io", + tzktExplorerUrl: "https://tzkt.io", + buyTezUrl: "https://widget.wert.io", +}; + +export const GHOSTNET: Network = { + name: "ghostnet", + rpcUrl: "https://ghostnet.ecadinfra.com", + tzktApiUrl: "https://api.ghostnet.tzkt.io", + tzktExplorerUrl: "https://ghostnet.tzkt.io", + buyTezUrl: "https://faucet.ghostnet.teztnets.xyz/", +}; + +export type NetworkName = string; // must be unique + +export type Network = { + name: NetworkName; + rpcUrl: string; + tzktApiUrl: string; + tzktExplorerUrl?: string; + buyTezUrl?: string; +}; + +export const DefaultNetworks: Network[] = [MAINNET, GHOSTNET]; diff --git a/src/types/SignerConfig.ts b/src/types/SignerConfig.ts index 4ab1a169c..f3eb32d35 100644 --- a/src/types/SignerConfig.ts +++ b/src/types/SignerConfig.ts @@ -1,7 +1,7 @@ import { ImplicitAccount, LedgerAccount } from "./Account"; -import { TezosNetwork } from "./TezosNetwork"; +import { Network } from "./Network"; -export type SignerConfig = { network: TezosNetwork } & ( +export type SignerConfig = { network: Network } & ( | { type: "ledger"; account: LedgerAccount } | { type: "mnemonic"; secretKey: string } | { type: "social"; secretKey: string } diff --git a/src/types/TezosNetwork.ts b/src/types/TezosNetwork.ts deleted file mode 100644 index 188c7ef41..000000000 --- a/src/types/TezosNetwork.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum TezosNetwork { - MAINNET = "mainnet", - GHOSTNET = "ghostnet", -} diff --git a/src/types/Token.test.tsx b/src/types/Token.test.tsx index 49362f5aa..276d396ae 100644 --- a/src/types/Token.test.tsx +++ b/src/types/Token.test.tsx @@ -2,6 +2,7 @@ import { tzBtsc, hedgehoge } from "../mocks/fa12Tokens"; import { uUSD } from "../mocks/fa2Tokens"; import { mockNFT, mockImplicitAddress } from "../mocks/factories"; import { fa1Token, fa2Token, nft } from "../mocks/tzktResponse"; +import { GHOSTNET, MAINNET } from "./Network"; import { FA12Token, FA2Token, @@ -18,7 +19,6 @@ import { tokenSymbolSafe, } from "./Token"; import type { Metadata } from "./Token"; -import { TezosNetwork } from "./TezosNetwork"; beforeEach(() => { jest.spyOn(console, "warn").mockImplementation(); @@ -362,10 +362,10 @@ describe("metadataUri", () => { it("returns a tzkt link", () => { const nft = mockNFT(0); - expect(metadataUri(nft, TezosNetwork.MAINNET)).toEqual( - "https://mainnet.tzkt.io/KT1QuofAgnsWffHzLA7D78rxytJruGHDe7XG/tokens/mockId0/metadata" + expect(metadataUri(nft, MAINNET)).toEqual( + "https://tzkt.io/KT1QuofAgnsWffHzLA7D78rxytJruGHDe7XG/tokens/mockId0/metadata" ); - expect(metadataUri(nft, TezosNetwork.GHOSTNET)).toEqual( + expect(metadataUri(nft, GHOSTNET)).toEqual( "https://ghostnet.tzkt.io/KT1QuofAgnsWffHzLA7D78rxytJruGHDe7XG/tokens/mockId0/metadata" ); }); diff --git a/src/types/Token.ts b/src/types/Token.ts index ba243648d..3f38866d6 100644 --- a/src/types/Token.ts +++ b/src/types/Token.ts @@ -3,7 +3,7 @@ import { z } from "zod"; import { Schema as AddressSchema } from "./Address"; import BigNumber from "bignumber.js"; import { getIPFSurl } from "../utils/token/nftUtils"; -import { TezosNetwork } from "./TezosNetwork"; +import { Network } from "./Network"; // TzKT defines metadada as any, but we need to have at least some clarity of what can be inside export type Metadata = { @@ -266,12 +266,12 @@ export const royalties = (nft: NFT): Array<{ address: string; share: number }> = return shares; }; -export const metadataUri = ({ contract, tokenId }: Token, network: TezosNetwork) => { - return `https://${network}.tzkt.io/${contract}/tokens/${tokenId}/metadata`; +export const metadataUri = (token: Token, network: Network) => { + return `${tokenUri(token, network)}/metadata`; }; -export const tokenUri = ({ contract, tokenId }: Token, network: TezosNetwork) => { - return `https://${network}.tzkt.io/${contract}/tokens/${tokenId}`; +export const tokenUri = ({ contract, tokenId }: Token, network: Network) => { + return `${network.tzktExplorerUrl}/${contract}/tokens/${tokenId}`; }; export const DEFAULT_FA1_NAME = "FA1.2 token"; diff --git a/src/utils/beacon/BeaconNotification/BeaconRequestNotification.test.tsx b/src/utils/beacon/BeaconNotification/BeaconRequestNotification.test.tsx index 343f0b0ef..28891f586 100644 --- a/src/utils/beacon/BeaconNotification/BeaconRequestNotification.test.tsx +++ b/src/utils/beacon/BeaconNotification/BeaconRequestNotification.test.tsx @@ -217,7 +217,7 @@ describe("", () => { await waitFor(() => { expect(screen.getByText(/Operation Submitted/i)).toBeInTheDocument(); }); - expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty("href", "https://mainnet.tzkt.io/bar"); + expect(screen.getByTestId(/tzkt-link/i)).toHaveProperty("href", "https://tzkt.io/bar"); expect(walletClient.respond).toHaveBeenCalledWith({ id: objectOperationBatchRequest.id, diff --git a/src/utils/hooks/assetsHooks.ts b/src/utils/hooks/assetsHooks.ts index 69bcb0b88..54a25cbd4 100644 --- a/src/utils/hooks/assetsHooks.ts +++ b/src/utils/hooks/assetsHooks.ts @@ -21,10 +21,7 @@ import { RawPkh } from "../../types/Address"; import assetsSlice from "../redux/slices/assetsSlice"; import { Account } from "../../types/Account"; import { Delegate } from "../../types/Delegate"; - -export const useSelectedNetwork = () => { - return useAppSelector(s => s.assets.network); -}; +import { useSelectedNetwork } from "./networkHooks"; export const useBlockLevel = () => useAppSelector(s => s.assets.blockLevel); diff --git a/src/utils/hooks/networkHooks.ts b/src/utils/hooks/networkHooks.ts new file mode 100644 index 000000000..9b9920a28 --- /dev/null +++ b/src/utils/hooks/networkHooks.ts @@ -0,0 +1,9 @@ +import { useAppSelector } from "../redux/hooks"; + +export const useSelectedNetwork = () => { + return useAppSelector(s => s.networks.current); +}; + +export const useAvailableNetworks = () => { + return useAppSelector(s => s.networks.available); +}; diff --git a/src/utils/hooks/tokensHooks.test.ts b/src/utils/hooks/tokensHooks.test.ts index dbff33d6c..9bb2a72ee 100644 --- a/src/utils/hooks/tokensHooks.test.ts +++ b/src/utils/hooks/tokensHooks.test.ts @@ -3,16 +3,16 @@ import { hedgehoge } from "../../mocks/fa12Tokens"; import { mockContractAddress, mockImplicitAddress } from "../../mocks/factories"; import { ReduxStore } from "../../providers/ReduxStore"; import { fromRaw } from "../../types/Token"; -import { SupportedNetworks } from "../network"; -import { assetsActions } from "../redux/slices/assetsSlice"; import store from "../redux/store"; import { tokensActions } from "../redux/slices/tokensSlice"; import { useGetToken, useGetTokenType } from "./tokensHooks"; +import { DefaultNetworks } from "../../types/Network"; +import { networksActions } from "../redux/slices/networks"; describe("useGetToken", () => { - describe.each(SupportedNetworks)("on %s", network => { + describe.each(DefaultNetworks)("on $name", network => { beforeEach(() => { - store.dispatch(assetsActions.updateNetwork(network)); + store.dispatch(networksActions.setCurrent(network)); }); it("returns undefined if token is not found", () => { const { result: getTokenRef } = renderHook(() => useGetToken(), { @@ -32,12 +32,12 @@ describe("useGetToken", () => { ); }); - SupportedNetworks.forEach(anotherNetwork => { + DefaultNetworks.forEach(anotherNetwork => { if (anotherNetwork === network) { return; } - test.each(SupportedNetworks.filter(another => another !== network))( + test.each(DefaultNetworks.filter(another => another !== network))( `can't find a token even if it exists on another network (%s)`, anotherNetwork => { const tokenBalance = hedgehoge(mockImplicitAddress(0)); @@ -58,7 +58,7 @@ describe("useGetToken", () => { }); describe("useGetTokenType", () => { - SupportedNetworks.forEach(network => { + DefaultNetworks.forEach(network => { describe(`on ${network}`, () => { it("returns undefined if contract is not found", () => { const { result: getTokenRef } = renderHook(() => useGetTokenType(network), { @@ -76,7 +76,7 @@ describe("useGetTokenType", () => { expect(getTokenRef.current(tokenBalance.token.contract.address as string)).toEqual("fa1.2"); }); - SupportedNetworks.forEach(anotherNetwork => { + DefaultNetworks.forEach(anotherNetwork => { if (anotherNetwork === network) { return; } diff --git a/src/utils/hooks/tokensHooks.ts b/src/utils/hooks/tokensHooks.ts index 2553a23c9..4be213dc4 100644 --- a/src/utils/hooks/tokensHooks.ts +++ b/src/utils/hooks/tokensHooks.ts @@ -1,22 +1,22 @@ import { get } from "lodash"; import { RawPkh } from "../../types/Address"; -import { TezosNetwork } from "../../types/TezosNetwork"; +import { Network } from "../../types/Network"; import { Token } from "../../types/Token"; import { useAppSelector } from "../redux/hooks"; -import { useSelectedNetwork } from "./assetsHooks"; +import { useSelectedNetwork } from "./networkHooks"; export type TokenLookup = (contract: RawPkh, tokenId: string) => Token | undefined; export const useGetToken = (): TokenLookup => { const network = useSelectedNetwork(); - const tokens = useAppSelector(s => s.tokens[network]); + const tokens = useAppSelector(s => s.tokens[network.name]); return (contract, tokenId) => get(tokens, [contract, tokenId]); }; -export const useGetTokenType = (network: TezosNetwork) => { - const tokens = useAppSelector(s => s.tokens[network]); +export const useGetTokenType = (network: Network) => { + const tokens = useAppSelector(s => s.tokens[network.name]); return (contract: RawPkh): Token["type"] | undefined => { - if (!(contract in tokens)) { + if (!(tokens && contract in tokens)) { return undefined; } const contractTokens = tokens[contract]; diff --git a/src/utils/mnemonic.test.ts b/src/utils/mnemonic.test.ts index 8677f3013..b6e1e0c62 100644 --- a/src/utils/mnemonic.test.ts +++ b/src/utils/mnemonic.test.ts @@ -7,6 +7,7 @@ import { defaultDerivationPathPattern, getDefaultDerivationPath, } from "./account/derivationPathUtils"; +import { MAINNET } from "../types/Network"; const addressExistsMock = jest.mocked(addressExists); const getFingerPrintMock = jest.mocked(getFingerPrint); @@ -21,7 +22,11 @@ describe("restoreAccounts", () => { addressExistsMock.mockResolvedValueOnce(true); addressExistsMock.mockResolvedValueOnce(true); addressExistsMock.mockResolvedValueOnce(false); - const result = await restoreRevealedPublickKeyPairs(mnemonic1, defaultDerivationPathPattern); + const result = await restoreRevealedPublickKeyPairs( + mnemonic1, + defaultDerivationPathPattern, + MAINNET + ); const expected = [ { pk: "edpkuwYWCugiYG7nMnVUdopFmyc3sbMSiLqsJHTQgGtVhtSdLSw6HG", @@ -41,7 +46,11 @@ describe("restoreAccounts", () => { it("should restore first account if none exists", async () => { addressExistsMock.mockResolvedValueOnce(false); - const result = await restoreRevealedPublickKeyPairs(mnemonic1, defaultDerivationPathPattern); + const result = await restoreRevealedPublickKeyPairs( + mnemonic1, + defaultDerivationPathPattern, + MAINNET + ); const expected = [ { pk: "edpkuwYWCugiYG7nMnVUdopFmyc3sbMSiLqsJHTQgGtVhtSdLSw6HG", @@ -59,7 +68,7 @@ describe("restoreEncryptedAccounts", () => { addressExistsMock.mockResolvedValueOnce(true); addressExistsMock.mockResolvedValueOnce(true); addressExistsMock.mockResolvedValueOnce(false); - const result = await restoreRevealedMnemonicAccounts(mnemonic1); + const result = await restoreRevealedMnemonicAccounts(mnemonic1, MAINNET); const expected: ImplicitAccount[] = [ { curve: "ed25519", @@ -98,7 +107,7 @@ describe("restoreEncryptedAccounts", () => { it("should restore exising accounts with a provided label", async () => { const CUSTOM_LABEL = "myLabel"; addressExistsMock.mockResolvedValueOnce(false); - const result = await restoreRevealedMnemonicAccounts(mnemonic1, CUSTOM_LABEL); + const result = await restoreRevealedMnemonicAccounts(mnemonic1, MAINNET, CUSTOM_LABEL); const expected: ImplicitAccount[] = [ expect.objectContaining({ label: CUSTOM_LABEL, @@ -109,7 +118,7 @@ describe("restoreEncryptedAccounts", () => { addressExistsMock.mockResolvedValueOnce(true); addressExistsMock.mockResolvedValueOnce(true); addressExistsMock.mockResolvedValueOnce(false); - const result2 = await restoreRevealedMnemonicAccounts(mnemonic1, CUSTOM_LABEL); + const result2 = await restoreRevealedMnemonicAccounts(mnemonic1, MAINNET, CUSTOM_LABEL); const expected2: ImplicitAccount[] = [ expect.objectContaining({ label: `${CUSTOM_LABEL} 0`, @@ -125,7 +134,12 @@ describe("restoreEncryptedAccounts", () => { addressExistsMock.mockResolvedValueOnce(true); addressExistsMock.mockResolvedValueOnce(true); addressExistsMock.mockResolvedValueOnce(false); - const result = await restoreRevealedMnemonicAccounts(mnemonic1, undefined, "44'/1729'/?'/0'"); + const result = await restoreRevealedMnemonicAccounts( + mnemonic1, + MAINNET, + undefined, + "44'/1729'/?'/0'" + ); const expected: ImplicitAccount[] = [ expect.objectContaining({ @@ -144,7 +158,7 @@ describe("restoreEncryptedAccounts", () => { addressExistsMock.mockResolvedValueOnce(true); addressExistsMock.mockResolvedValueOnce(true); addressExistsMock.mockResolvedValueOnce(false); - const result = restoreRevealedMnemonicAccounts(mnemonic1, undefined, "44'/foo'/?'/8'"); + const result = restoreRevealedMnemonicAccounts(mnemonic1, MAINNET, undefined, "44'/foo'/?'/8'"); await expect(result).rejects.toThrowError("Invalid derivation pattern: 44'/foo'/?'/8'"); }); diff --git a/src/utils/mnemonic.ts b/src/utils/mnemonic.ts index 2f388fb35..bb724575b 100644 --- a/src/utils/mnemonic.ts +++ b/src/utils/mnemonic.ts @@ -4,6 +4,7 @@ import { defaultDerivationPathPattern, makeDerivationPath } from "./account/deri import { makeMnemonicAccount } from "./account/makeMnemonicAccount"; import { addressExists, getFingerPrint } from "./tezos"; import { generateMnemonic } from "bip39"; +import { Network } from "../types/Network"; // This is put in a separate file for mocking purposes in tests export const generate24WordMnemonic = () => { @@ -41,16 +42,18 @@ export const deriveSecretKey = (mnemonic: string, derivationPath: string, curve: export const restoreRevealedPublickKeyPairs = async ( mnemonic: string, derivationPathPattern: string, + network: Network, result: PublicKeyPair[] = [], startIndex = 0 ): Promise => { const derivationPath = makeDerivationPath(derivationPathPattern, startIndex); const pubKeyPair = await derivePublicKeyPair(mnemonic, derivationPath); - if (await addressExists(pubKeyPair.pkh)) { + if (await addressExists(pubKeyPair.pkh, network)) { return restoreRevealedPublickKeyPairs( mnemonic, derivationPathPattern, + network, [...result, pubKeyPair], startIndex + 1 ); @@ -61,10 +64,15 @@ export const restoreRevealedPublickKeyPairs = async ( export const restoreRevealedMnemonicAccounts = async ( mnemonic: string, + network: Network, label = "Account", derivationPathPattern = defaultDerivationPathPattern ): Promise => { - const pubKeyPairs = await restoreRevealedPublickKeyPairs(mnemonic, derivationPathPattern); + const pubKeyPairs = await restoreRevealedPublickKeyPairs( + mnemonic, + derivationPathPattern, + network + ); const seedFingerPrint = await getFingerPrint(mnemonic); return pubKeyPairs.map(({ pk, pkh }, i) => { diff --git a/src/utils/multisig/fetch.test.ts b/src/utils/multisig/fetch.test.ts index 656a0e256..480131dc1 100644 --- a/src/utils/multisig/fetch.test.ts +++ b/src/utils/multisig/fetch.test.ts @@ -1,8 +1,7 @@ import axios from "axios"; import { ghostMultisigContracts } from "../../mocks/tzktResponse"; -import { TezosNetwork } from "../../types/TezosNetwork"; -import { tzktUrls } from "../tezos/consts"; import { getAllMultiSigContracts } from "./fetch"; +import { GHOSTNET } from "../../types/Network"; jest.mock("axios"); @@ -12,11 +11,9 @@ describe("multisig fetch", () => { test("getAllMultiSigContracts", async () => { mockedAxios.get.mockResolvedValue({ data: ghostMultisigContracts }); - const result = await getAllMultiSigContracts(TezosNetwork.GHOSTNET); + const result = await getAllMultiSigContracts(GHOSTNET); expect(mockedAxios.get).toBeCalledWith( - `${ - tzktUrls[TezosNetwork.GHOSTNET] - }/v1/contracts?typeHash=1963879877&codeHash=-1890025422&includeStorage=true&limit=10000` + `${GHOSTNET.tzktApiUrl}/v1/contracts?typeHash=1963879877&codeHash=-1890025422&includeStorage=true&limit=10000` ); expect( result.map(({ address, storage: { pending_ops, signers, threshold } }) => ({ diff --git a/src/utils/multisig/fetch.ts b/src/utils/multisig/fetch.ts index 5bc223c4c..0b7851c97 100644 --- a/src/utils/multisig/fetch.ts +++ b/src/utils/multisig/fetch.ts @@ -1,16 +1,15 @@ import axios from "axios"; -import { TezosNetwork } from "../../types/TezosNetwork"; -import { tzktUrls } from "../tezos/consts"; +import { Network } from "../../types/Network"; import { RawTzktGetBigMapKeys, RawTzktGetSameMultisigs } from "../tzkt/types"; const MULTISIG_FETCH_LIMIT = 10000; const TYPE_HASH = 1963879877; const CODE_HASH = -1890025422; export const getAllMultiSigContracts = async ( - network: TezosNetwork + network: Network ): Promise => { try { - const url = `${tzktUrls[network]}/v1/contracts?typeHash=${TYPE_HASH}&codeHash=${CODE_HASH}&includeStorage=true&limit=${MULTISIG_FETCH_LIMIT}`; + const url = `${network.tzktApiUrl}/v1/contracts?typeHash=${TYPE_HASH}&codeHash=${CODE_HASH}&includeStorage=true&limit=${MULTISIG_FETCH_LIMIT}`; const { data } = await axios.get(url); return data; @@ -22,9 +21,9 @@ export const getAllMultiSigContracts = async ( // get all pending operations for a multisig contract address export const getPendingOperations = async ( bigMaps: number[], - network: TezosNetwork + network: Network ): Promise => { - const url = `${tzktUrls[network]}/v1/bigmaps/keys?active=true&bigmap.in=${bigMaps.join( + const url = `${network.tzktApiUrl}/v1/bigmaps/keys?active=true&bigmap.in=${bigMaps.join( "," )}&limit=${MULTISIG_FETCH_LIMIT}`; const { data } = await axios.get(url); diff --git a/src/utils/multisig/helper.test.ts b/src/utils/multisig/helper.test.ts index 997cae606..00b1c31c0 100644 --- a/src/utils/multisig/helper.test.ts +++ b/src/utils/multisig/helper.test.ts @@ -6,13 +6,13 @@ import { parseMultisig, } from "./helpers"; import { tzktGetSameMultisigsResponse } from "../../mocks/tzktResponse"; -import { SupportedNetworks } from "../network"; +import { DefaultNetworks } from "../../types/Network"; jest.mock("axios"); const mockedAxios = axios as jest.Mocked; describe("multisig helpers", () => { - SupportedNetworks.forEach(async network => { + DefaultNetworks.forEach(async network => { test("getRelevantMultisigContracts", async () => { const mockResponse = { data: tzktGetSameMultisigsResponse, @@ -57,7 +57,7 @@ describe("multisig helpers", () => { ); expect(mockedAxios.get).toBeCalledWith( - `https://api.${network}.tzkt.io/v1/bigmaps/keys?active=true&bigmap.in=0,1&limit=10000` + `${network.tzktApiUrl}/v1/bigmaps/keys?active=true&bigmap.in=0,1&limit=10000` ); expect(result).toEqual([ diff --git a/src/utils/multisig/helpers.ts b/src/utils/multisig/helpers.ts index 4cd8a5f77..4bd9a0a54 100644 --- a/src/utils/multisig/helpers.ts +++ b/src/utils/multisig/helpers.ts @@ -4,7 +4,7 @@ import { parseContractPkh, parseImplicitPkh } from "../../types/Address"; import { RawTzktGetBigMapKeysItem, RawTzktGetSameMultisigsItem } from "../tzkt/types"; import { getAllMultiSigContracts, getPendingOperations } from "./fetch"; import { Multisig, MultisigOperation } from "./types"; -import { TezosNetwork } from "../../types/TezosNetwork"; +import { Network } from "../../types/Network"; export const parseMultisig = (raw: RawTzktGetSameMultisigsItem): Multisig => ({ address: parseContractPkh(raw.address), @@ -16,7 +16,7 @@ export const parseMultisig = (raw: RawTzktGetSameMultisigsItem): Multisig => ({ export const getRelevantMultisigContracts = async ( accountPkhs: Set, - network: TezosNetwork + network: Network ): Promise => getAllMultiSigContracts(network).then(multisigs => multisigs @@ -44,7 +44,7 @@ const parseMultisigOperation = (raw: RawTzktGetBigMapKeysItem): MultisigOperatio export const getPendingOperationsForMultisigs = async ( multisigs: Multisig[], - network: TezosNetwork + network: Network ): Promise => { const bigmapIds = multisigs.map(m => m.pendingOperationsBigmapId); diff --git a/src/utils/network.ts b/src/utils/network.ts deleted file mode 100644 index be7c20787..000000000 --- a/src/utils/network.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { TezosNetwork } from "../types/TezosNetwork"; - -export const SupportedNetworks = [TezosNetwork.GHOSTNET, TezosNetwork.MAINNET]; diff --git a/src/utils/redux/reducer.ts b/src/utils/redux/reducer.ts index 5ec813954..1b82f734c 100644 --- a/src/utils/redux/reducer.ts +++ b/src/utils/redux/reducer.ts @@ -7,6 +7,7 @@ import assetsSlice from "./slices/assetsSlice"; import contactsSlice from "./slices/contactsSlice"; import multisigsSlice from "./slices/multisigsSlice"; import errorsSlice from "./slices/errorsSlice"; +import { networksSlice } from "./slices/networks"; const rootPersistConfig = { key: "root", @@ -27,6 +28,7 @@ const rootReducers = combineReducers({ multisigs: multisigsSlice.reducer, tokens: tokensSlice.reducer, errors: errorsSlice.reducer, + networks: networksSlice.reducer, }); export default persistReducer(rootPersistConfig, rootReducers); diff --git a/src/utils/redux/slices/accountsSlice.test.ts b/src/utils/redux/slices/accountsSlice.test.ts index 979ac872f..eba79c477 100644 --- a/src/utils/redux/slices/accountsSlice.test.ts +++ b/src/utils/redux/slices/accountsSlice.test.ts @@ -10,6 +10,7 @@ import { deriveAccount, restoreFromMnemonic } from "../thunks/restoreMnemonicAcc import { getFingerPrint } from "../../tezos"; import { parseImplicitPkh } from "../../../types/Address"; import accountsSlice from "./accountsSlice"; +import { MAINNET } from "../../../types/Network"; const { actions: { addAccount, removeMnemonicAndAccounts }, @@ -127,6 +128,7 @@ describe("Accounts reducer", () => { expect(fakeExtraArguments.restoreRevealedMnemonicAccounts).toHaveBeenCalledWith( mnemonic1, + MAINNET, mockLabel, undefined ); diff --git a/src/utils/redux/slices/assetsSlice.test.ts b/src/utils/redux/slices/assetsSlice.test.ts index 006b2279c..1d29a016a 100644 --- a/src/utils/redux/slices/assetsSlice.test.ts +++ b/src/utils/redux/slices/assetsSlice.test.ts @@ -13,16 +13,16 @@ import { } from "../../../mocks/factories"; import accountsSlice from "./accountsSlice"; import { hedgehoge } from "../../../mocks/fa12Tokens"; -import { TezosNetwork } from "../../../types/TezosNetwork"; import { makeAccountOperations } from "../../../components/sendForm/types"; import { Operation } from "../../../types/Operation"; import { ImplicitOperations } from "../../../components/sendForm/types"; +import { GHOSTNET } from "../../../types/Network"; +import { networksActions } from "./networks"; const { actions: { updateTezBalance, updateTokenBalance, - updateNetwork, updateTezTransfers, updateTokenTransfers, clearBatch, @@ -40,7 +40,6 @@ describe("assetsSlice", () => { }, transfers: { tez: {}, tokens: {} }, delegations: {}, - network: "mainnet", conversionRate: null, bakers: [], batches: [], @@ -63,7 +62,6 @@ describe("assetsSlice", () => { }, transfers: { tez: {}, tokens: {} }, delegations: {}, - network: "mainnet", conversionRate: null, bakers: [], batches: [], @@ -90,7 +88,6 @@ describe("assetsSlice", () => { }, transfers: { tez: {}, tokens: {} }, delegations: {}, - network: "mainnet", conversionRate: null, bakers: [], batches: [], @@ -126,7 +123,6 @@ describe("assetsSlice", () => { conversionRate: null, delegations: {}, bakers: [], - network: "mainnet", transfers: { tez: {}, tokens: {} }, batches: [], blockLevel: null, @@ -155,7 +151,6 @@ describe("assetsSlice", () => { conversionRate: null, delegations: {}, bakers: [], - network: "mainnet", transfers: { tez: {}, tokens: {} }, batches: [], blockLevel: null, @@ -165,33 +160,6 @@ describe("assetsSlice", () => { }); }); - test("updating network resets operations and balances", () => { - store.dispatch( - updateTezBalance([ - { address: "bar", balance: 44 }, - { address: "baz", balance: 55 }, - ]) - ); - - store.dispatch(updateTokenBalance([hedgehoge(mockImplicitAddress(0))])); - - store.dispatch(updateNetwork(TezosNetwork.GHOSTNET)); - - expect(store.getState().assets).toEqual({ - balances: { mutez: {}, tokens: {} }, - transfers: { tez: {}, tokens: {} }, - delegations: {}, - bakers: [], - network: "ghostnet", - conversionRate: null, - batches: [], - blockLevel: null, - refetchTrigger: 0, - lastTimeUpdated: null, - isLoading: false, - }); - }); - test("reseting accounts resets assetsState", () => { store.dispatch( updateTezBalance([ @@ -209,7 +177,6 @@ describe("assetsSlice", () => { transfers: { tez: {}, tokens: {} }, delegations: {}, bakers: [], - network: "mainnet", conversionRate: null, batches: [], blockLevel: null, @@ -238,7 +205,6 @@ describe("assetsSlice", () => { }, delegations: {}, bakers: [], - network: "mainnet", batches: [], transfers: { tez: { @@ -274,7 +240,6 @@ describe("assetsSlice", () => { }, delegations: {}, bakers: [], - network: "mainnet", batches: [], transfers: { tez: { @@ -289,7 +254,7 @@ describe("assetsSlice", () => { lastTimeUpdated: null, isLoading: false, }); - store.dispatch(updateNetwork(TezosNetwork.GHOSTNET)); + store.dispatch(networksActions.setCurrent(GHOSTNET)); }); test("token transfers are upserted", () => { @@ -311,7 +276,6 @@ describe("assetsSlice", () => { }, delegations: {}, bakers: [], - network: "mainnet", batches: [], transfers: { tokens: { @@ -347,7 +311,6 @@ describe("assetsSlice", () => { }, delegations: {}, bakers: [], - network: "mainnet", transfers: { tokens: { foo: [mockTokenTransaction(4)], diff --git a/src/utils/redux/slices/assetsSlice.ts b/src/utils/redux/slices/assetsSlice.ts index 320d1a7c7..16b4e8dc8 100644 --- a/src/utils/redux/slices/assetsSlice.ts +++ b/src/utils/redux/slices/assetsSlice.ts @@ -2,7 +2,6 @@ import { createSlice } from "@reduxjs/toolkit"; import { DelegationOperation } from "@tzkt/sdk-api"; import { compact, findIndex, groupBy, mapValues } from "lodash"; import accountsSlice from "./accountsSlice"; -import { TezosNetwork } from "../../../types/TezosNetwork"; import { TezTransfer, TokenTransfer } from "../../../types/Transfer"; import { TzktAccount } from "../../tezos"; import { fromRaw, RawTokenBalance, TokenBalance } from "../../../types/TokenBalance"; @@ -11,7 +10,6 @@ import { AccountOperations } from "../../../components/sendForm/types"; import { RawPkh } from "../../../types/Address"; type State = { - network: TezosNetwork; blockLevel: number | null; balances: { mutez: Record; @@ -47,7 +45,6 @@ export type DelegationPayload = { export type ConversionRatePayload = { rate: State["conversionRate"] }; const initialState: State = { - network: TezosNetwork.MAINNET, blockLevel: null, balances: { mutez: {}, @@ -77,9 +74,6 @@ const assetsSlice = createSlice({ builder.addCase(accountsSlice.actions.reset, () => initialState), reducers: { reset: () => initialState, - updateNetwork: (_, { payload }: { type: string; payload: TezosNetwork }) => { - return { ...initialState, network: payload }; - }, updateBlockLevel: (state, { payload }: { payload: number }) => { state.blockLevel = payload; }, diff --git a/src/utils/redux/slices/networks.test.ts b/src/utils/redux/slices/networks.test.ts new file mode 100644 index 000000000..c1ab43a63 --- /dev/null +++ b/src/utils/redux/slices/networks.test.ts @@ -0,0 +1,17 @@ +import { DefaultNetworks, GHOSTNET, MAINNET } from "../../../types/Network"; +import store from "../store"; +import { networksActions } from "./networks"; + +describe("networksSlice", () => { + test("initialState", () => { + expect(store.getState().networks).toEqual({ + available: DefaultNetworks, + current: MAINNET, + }); + }); + + test("setCurrent", () => { + store.dispatch(networksActions.setCurrent(GHOSTNET)); + expect(store.getState().networks.current).toEqual(GHOSTNET); + }); +}); diff --git a/src/utils/redux/slices/networks.ts b/src/utils/redux/slices/networks.ts new file mode 100644 index 000000000..9c0869374 --- /dev/null +++ b/src/utils/redux/slices/networks.ts @@ -0,0 +1,24 @@ +import { createSlice } from "@reduxjs/toolkit"; +import { DefaultNetworks, MAINNET, Network } from "../../../types/Network"; + +type State = { + available: Network[]; + current: Network; +}; + +const initialState: State = { + available: DefaultNetworks, + current: MAINNET, +}; + +export const networksSlice = createSlice({ + name: "networks", + initialState, + reducers: { + reset: () => initialState, + setCurrent: (state, { payload }: { payload: Network }) => { + state.current = payload; + }, + }, +}); +export const networksActions = networksSlice.actions; diff --git a/src/utils/redux/slices/tokensSlice.ts b/src/utils/redux/slices/tokensSlice.ts index 8928b1bdb..52411a828 100644 --- a/src/utils/redux/slices/tokensSlice.ts +++ b/src/utils/redux/slices/tokensSlice.ts @@ -1,15 +1,12 @@ import { createSlice } from "@reduxjs/toolkit"; -import { compact, setWith } from "lodash"; +import { compact, fromPairs, setWith } from "lodash"; import { RawPkh } from "../../../types/Address"; -import { TezosNetwork } from "../../../types/TezosNetwork"; +import { DefaultNetworks, Network, NetworkName } from "../../../types/Network"; import { fromRaw, RawTokenInfo, Token, TokenId } from "../../../types/Token"; -type State = Record>>; +type State = Record> | undefined>; -const initialState: State = { - [TezosNetwork.MAINNET]: {}, - [TezosNetwork.GHOSTNET]: {}, -}; +const initialState: State = fromPairs(DefaultNetworks.map(network => [network, {}])); const tokensSlice = createSlice({ name: "tokens", @@ -18,12 +15,10 @@ const tokensSlice = createSlice({ reset: () => initialState, addTokens: ( state: State, - { - payload: { network, tokens }, - }: { payload: { network: TezosNetwork; tokens: RawTokenInfo[] } } + { payload: { network, tokens } }: { payload: { network: Network; tokens: RawTokenInfo[] } } ) => { compact(tokens.map(fromRaw)).forEach(token => { - setWith(state, [network, token.contract, token.tokenId], token, Object); + setWith(state, [network.name, token.contract, token.tokenId], token, Object); }); }, }, diff --git a/src/utils/redux/thunks/estimateAndUpdateBatch.test.ts b/src/utils/redux/thunks/estimateAndUpdateBatch.test.ts index 11da506a7..2018c3bf7 100644 --- a/src/utils/redux/thunks/estimateAndUpdateBatch.test.ts +++ b/src/utils/redux/thunks/estimateAndUpdateBatch.test.ts @@ -6,13 +6,13 @@ import { mockTezOperation, } from "../../../mocks/factories"; import { mockEstimatedFee } from "../../../mocks/helpers"; -import { SupportedNetworks } from "../../network"; import { estimate } from "../../tezos"; import store from "../store"; import { estimateAndUpdateBatch } from "./estimateAndUpdateBatch"; +import { DefaultNetworks } from "../../../types/Network"; describe("estimateAndUpdateBatch", () => { - describe.each(SupportedNetworks)("on %s", network => { + describe.each(DefaultNetworks)("on $name", network => { it("adds an operation to batch if the estimation succeeds", async () => { const operation = mockTezOperation(1); diff --git a/src/utils/redux/thunks/estimateAndUpdateBatch.ts b/src/utils/redux/thunks/estimateAndUpdateBatch.ts index 24c5c90e7..89865690f 100644 --- a/src/utils/redux/thunks/estimateAndUpdateBatch.ts +++ b/src/utils/redux/thunks/estimateAndUpdateBatch.ts @@ -1,13 +1,13 @@ import { AnyAction, ThunkAction } from "@reduxjs/toolkit"; import { AccountOperations } from "../../../components/sendForm/types"; -import { TezosNetwork } from "../../../types/TezosNetwork"; +import { Network } from "../../../types/Network"; import { estimate } from "../../tezos"; import assetsSlice from "../slices/assetsSlice"; import { RootState } from "../store"; export const estimateAndUpdateBatch = ( operations: AccountOperations, - network: TezosNetwork + network: Network ): ThunkAction, RootState, unknown, AnyAction> => { return async dispatch => { // check that the operation can be executed at least on its own diff --git a/src/utils/redux/thunks/restoreMnemonicAccounts.ts b/src/utils/redux/thunks/restoreMnemonicAccounts.ts index 166696fa3..669a8579a 100644 --- a/src/utils/redux/thunks/restoreMnemonicAccounts.ts +++ b/src/utils/redux/thunks/restoreMnemonicAccounts.ts @@ -6,6 +6,7 @@ import { EncryptedData } from "../../crypto/types"; import { getFingerPrint } from "../../tezos"; import { ExtraArgument } from "../extraArgument"; import { AppDispatch, RootState } from "../store"; +import { MAINNET } from "../../../types/Network"; export const restoreFromMnemonic = createAsyncThunk< { @@ -28,6 +29,7 @@ export const restoreFromMnemonic = createAsyncThunk< seedFingerprint: await getFingerPrint(seedPhrase), accounts: await thunkAPI.extra.restoreRevealedMnemonicAccounts( seedPhrase, + MAINNET, // TODO: consider choosing network on onboarding label, derivationPathPattern ), diff --git a/src/utils/tezos/consts.ts b/src/utils/tezos/consts.ts index 841e38033..98065eead 100644 --- a/src/utils/tezos/consts.ts +++ b/src/utils/tezos/consts.ts @@ -1,25 +1,3 @@ -import { TezosNetwork } from "../../types/TezosNetwork"; - -export const nodeUrls = { - [TezosNetwork.GHOSTNET]: `https://tezos-ghostnet-node.prod.gke.papers.tech`, - [TezosNetwork.MAINNET]: `https://mainnet.api.tez.ie`, -}; - -export const tzktUrls = { - [TezosNetwork.GHOSTNET]: `https://api.ghostnet.tzkt.io`, - [TezosNetwork.MAINNET]: `https://api.mainnet.tzkt.io`, -}; - -export const tzktExplorer = { - [TezosNetwork.GHOSTNET]: `https://ghostnet.tzkt.io`, - [TezosNetwork.MAINNET]: `https://tzkt.io`, -}; - -export const wertUrls = { - [TezosNetwork.GHOSTNET]: `https://faucet.ghostnet.teztnets.xyz/`, - [TezosNetwork.MAINNET]: `https://widget.wert.io`, -}; - export const coincapUrl = "https://api.coincap.io/v2/assets"; export const TEZ = "ꜩ"; diff --git a/src/utils/tezos/estimate.ts b/src/utils/tezos/estimate.ts index 517d21da0..94ab0eed1 100644 --- a/src/utils/tezos/estimate.ts +++ b/src/utils/tezos/estimate.ts @@ -1,11 +1,11 @@ import { AccountOperations } from "../../components/sendForm/types"; -import { TezosNetwork } from "../../types/TezosNetwork"; +import { Network } from "../../types/Network"; import { makeToolkit, operationsToBatchParams, sumTez } from "./helpers"; import BigNumber from "bignumber.js"; export const estimate = async ( operations: AccountOperations, - network: TezosNetwork + network: Network ): Promise => { const tezosToolkit = await makeToolkit({ type: "fake", signer: operations.signer, network }); diff --git a/src/utils/tezos/fetch.test.ts b/src/utils/tezos/fetch.test.ts index 7fbc088bf..b9535346e 100644 --- a/src/utils/tezos/fetch.test.ts +++ b/src/utils/tezos/fetch.test.ts @@ -8,12 +8,11 @@ import { getTokenTransfers, } from "./fetch"; import { operationsGetTransactions, tokensGetTokenTransfers } from "@tzkt/sdk-api"; -import { coincapUrl, tzktUrls } from "./consts"; +import { coincapUrl } from "./consts"; import { mockContractAddress, mockImplicitAddress } from "../../mocks/factories"; -import { SupportedNetworks } from "../network"; import { hedgehoge, tzBtsc } from "../../mocks/fa12Tokens"; import { uUSD } from "../../mocks/fa2Tokens"; -import { TezosNetwork } from "../../types/TezosNetwork"; +import { DefaultNetworks } from "../../types/Network"; jest.mock("axios"); jest.mock("@tzkt/sdk-api", () => { @@ -28,33 +27,33 @@ jest.mock("@tzkt/sdk-api", () => { const mockedAxios = axios as jest.Mocked; describe("tezos utils fetch", () => { - test("getTezosPriceInUSD", async () => { - const mockResponse = { - data: { + describe.each(DefaultNetworks)("on $name", network => { + test("getTezosPriceInUSD", async () => { + const mockResponse = { data: { - id: "tezos", - rank: "45", - symbol: "XTZ", - name: "Tezos", - supply: "934953037.6018340000000000", - maxSupply: null, - marketCapUsd: "973524588.0822762611894168", - volumeUsd24Hr: "11804202.6168944408092813", - priceUsd: "1.0412550672912714", - changePercent24Hr: "-1.7557594377565521", - vwap24Hr: "1.0421183688213239", - explorer: "https://tzkt.io/", + data: { + id: "tezos", + rank: "45", + symbol: "XTZ", + name: "Tezos", + supply: "934953037.6018340000000000", + maxSupply: null, + marketCapUsd: "973524588.0822762611894168", + volumeUsd24Hr: "11804202.6168944408092813", + priceUsd: "1.0412550672912714", + changePercent24Hr: "-1.7557594377565521", + vwap24Hr: "1.0421183688213239", + explorer: "https://tzkt.io/", + }, }, - }, - }; - mockedAxios.get.mockResolvedValue(mockResponse); - const result = await getTezosPriceInUSD(); - expect(mockedAxios.get).toBeCalledWith(`${coincapUrl}/tezos`); - expect(result).toEqual(mockResponse.data.data.priceUsd); - }); + }; + mockedAxios.get.mockResolvedValue(mockResponse); + const result = await getTezosPriceInUSD(); + expect(mockedAxios.get).toBeCalledWith(`${coincapUrl}/tezos`); + expect(result).toEqual(mockResponse.data.data.priceUsd); + }); - test("getTokenBalances", async () => { - SupportedNetworks.forEach(async network => { + test("getTokenBalances", async () => { const response = [ hedgehoge(mockImplicitAddress(0)), uUSD(mockImplicitAddress(1)), @@ -69,50 +68,46 @@ describe("tezos utils fetch", () => { ]; const res = await getTokenBalances(addresses, network); expect(mockedAxios.get).toBeCalledWith( - `https://api.${network}.tzkt.io/v1/tokens/balances?account.in=${addresses.join( - "," - )}&balance.gt=0` + `${network.tzktApiUrl}/v1/tokens/balances?account.in=${addresses.join(",")}&balance.gt=0` ); expect(res).toEqual(response); }); - }); - test("getTezTransfers", async () => { - await getTezTransfers(mockImplicitAddress(0).pkh, TezosNetwork.GHOSTNET); - expect(operationsGetTransactions).toBeCalledWith( - { - anyof: { fields: ["sender", "target"], eq: mockImplicitAddress(0).pkh }, - sort: { desc: "level" }, - limit: 10, - }, - { - baseUrl: tzktUrls[TezosNetwork.GHOSTNET], - } - ); - }); + test("getTezTransfers", async () => { + await getTezTransfers(mockImplicitAddress(0).pkh, network); + expect(operationsGetTransactions).toBeCalledWith( + { + anyof: { fields: ["sender", "target"], eq: mockImplicitAddress(0).pkh }, + sort: { desc: "level" }, + limit: 10, + }, + { + baseUrl: network.tzktApiUrl, + } + ); + }); - test("getTokenTransfers", async () => { - await getTokenTransfers(mockImplicitAddress(0).pkh, TezosNetwork.GHOSTNET); - expect(tokensGetTokenTransfers).toBeCalledWith( - { - anyof: { fields: ["from", "to"], eq: mockImplicitAddress(0).pkh }, - sort: { desc: "level" }, - limit: 10, - }, - { - baseUrl: tzktUrls[TezosNetwork.GHOSTNET], - } - ); - }); + test("getTokenTransfers", async () => { + await getTokenTransfers(mockImplicitAddress(0).pkh, network); + expect(tokensGetTokenTransfers).toBeCalledWith( + { + anyof: { fields: ["from", "to"], eq: mockImplicitAddress(0).pkh }, + sort: { desc: "level" }, + limit: 10, + }, + { + baseUrl: network.tzktApiUrl, + } + ); + }); - test("getLastDelegation", async () => { - const res = await getLastDelegation(mockImplicitAddress(0).pkh, TezosNetwork.GHOSTNET); - expect(res).toEqual({ type: "delegation" }); - }); + test("getLastDelegation", async () => { + const res = await getLastDelegation(mockImplicitAddress(0).pkh, network); + expect(res).toEqual({ type: "delegation" }); + }); - test("getAccounts", async () => { - SupportedNetworks.forEach(async network => { + test("getAccounts", async () => { mockedAxios.get.mockResolvedValue({ data: [ { address: mockImplicitAddress(0).pkh, balance: 12345 }, @@ -126,9 +121,7 @@ describe("tezos utils fetch", () => { ]; const res = await getAccounts(addresses, network); expect(mockedAxios.get).toBeCalledWith( - `https://api.${network}.tzkt.io/v1/accounts?address.in=${addresses.join( - "," - )}&select=address,balance` + `${network.tzktApiUrl}/v1/accounts?address.in=${addresses.join(",")}&select=address,balance` ); expect(res).toEqual([ diff --git a/src/utils/tezos/fetch.ts b/src/utils/tezos/fetch.ts index 02dd5dc77..668d97ed8 100644 --- a/src/utils/tezos/fetch.ts +++ b/src/utils/tezos/fetch.ts @@ -9,41 +9,35 @@ import { TokenTransfer, } from "@tzkt/sdk-api"; import axios from "axios"; -import { coincapUrl, tzktUrls } from "./consts"; +import { coincapUrl } from "./consts"; import { coinCapResponseType } from "./types"; import { TezTransfer } from "../../types/Transfer"; import { RawTokenBalance } from "../../types/TokenBalance"; -import { TezosNetwork } from "../../types/TezosNetwork"; +import { Network } from "../../types/Network"; // TzKT defines type Account = {type: string}; // whilst accountsGet returns all the info about accounts // for now we need only the balance, but we can extend it later export type TzktAccount = { address: string; balance: number }; -export const getAccounts = async ( - pkhs: string[], - network: TezosNetwork -): Promise => { +export const getAccounts = async (pkhs: string[], network: Network): Promise => { const response = await axios.get( - `${tzktUrls[network]}/v1/accounts?address.in=${pkhs.join(",")}&select=address,balance` + `${network.tzktApiUrl}/v1/accounts?address.in=${pkhs.join(",")}&select=address,balance` ); return response.data; }; export const getTokenBalances = async ( pkhs: string[], - network: TezosNetwork + network: Network ): Promise => { const response = await axios.get( - `${tzktUrls[network]}/v1/tokens/balances?account.in=${pkhs.join(",")}&balance.gt=0` + `${network.tzktApiUrl}/v1/tokens/balances?account.in=${pkhs.join(",")}&balance.gt=0` ); return response.data; }; -export const getTezTransfers = ( - address: string, - network = TezosNetwork.MAINNET -): Promise => { +export const getTezTransfers = (address: string, network: Network): Promise => { return operationsGetTransactions( { anyof: { fields: ["sender", "target"], eq: address }, @@ -51,15 +45,12 @@ export const getTezTransfers = ( limit: 10, }, { - baseUrl: tzktUrls[network], + baseUrl: network.tzktApiUrl, } ); }; -export const getTokenTransfers = ( - address: string, - network = TezosNetwork.MAINNET -): Promise => { +export const getTokenTransfers = (address: string, network: Network): Promise => { return tokensGetTokenTransfers( { anyof: { fields: ["from", "to"], eq: address }, @@ -67,14 +58,14 @@ export const getTokenTransfers = ( limit: 10, }, { - baseUrl: tzktUrls[network], + baseUrl: network.tzktApiUrl, } ); }; export const getLastDelegation = async ( address: string, - network = TezosNetwork.MAINNET + network: Network ): Promise => { return operationsGetDelegations( { @@ -83,7 +74,7 @@ export const getLastDelegation = async ( limit: 1, }, { - baseUrl: tzktUrls[network], + baseUrl: network.tzktApiUrl, } ).then(d => d[0]); }; @@ -100,13 +91,13 @@ export const getTezosPriceInUSD = async (): Promise => { return priceUsd ?? null; }; -export const getLatestBlockLevel = async (network = TezosNetwork.MAINNET): Promise => { +export const getLatestBlockLevel = async (network: Network): Promise => { return await blocksGetCount({ - baseUrl: tzktUrls[network], + baseUrl: network.tzktApiUrl, }); }; -export const getBakers = async (network: TezosNetwork): Promise => { +export const getBakers = async (network: Network): Promise => { return delegatesGet( { sort: { desc: "stakingBalance" }, @@ -115,7 +106,7 @@ export const getBakers = async (network: TezosNetwork): Promise => { select: { fields: ["address,alias,stakingBalance"] }, }, { - baseUrl: tzktUrls[network], + baseUrl: network.tzktApiUrl, } ); }; diff --git a/src/utils/tezos/helpers.test.ts b/src/utils/tezos/helpers.test.ts index 637a79c6f..b0f73d5cc 100644 --- a/src/utils/tezos/helpers.test.ts +++ b/src/utils/tezos/helpers.test.ts @@ -17,7 +17,6 @@ import { FA12Transfer, FA2Transfer, } from "../../types/Operation"; -import { tzktUrls } from "./consts"; import { addressExists, getPkAndPkhFromSk, @@ -27,6 +26,7 @@ import { operationsToBatchParams, } from "./helpers"; import { makeAccountOperations } from "../../components/sendForm/types"; +import { MAINNET } from "../../types/Network"; jest.mock("@taquito/signer"); jest.mock("./fakeSigner"); jest.mock("axios"); @@ -46,9 +46,9 @@ describe("tezos utils helpers", () => { }, }; mockedAxios.get.mockResolvedValue(mockResponse); - const result = await addressExists(mockImplicitAddress(0).pkh); + const result = await addressExists(mockImplicitAddress(0).pkh, MAINNET); expect(mockedAxios.get).toBeCalledWith( - `${tzktUrls.mainnet}/v1/accounts/${mockImplicitAddress(0).pkh}` + `${MAINNET.tzktApiUrl}/v1/accounts/${mockImplicitAddress(0).pkh}` ); expect(result).toEqual(true); }); @@ -60,9 +60,9 @@ describe("tezos utils helpers", () => { }, }; mockedAxios.get.mockResolvedValue(mockResponse); - const result = await addressExists(mockImplicitAddress(0).pkh); + const result = await addressExists(mockImplicitAddress(0).pkh, MAINNET); expect(mockedAxios.get).toBeCalledWith( - `${tzktUrls.mainnet}/v1/accounts/${mockImplicitAddress(0).pkh}` + `${MAINNET.tzktApiUrl}/v1/accounts/${mockImplicitAddress(0).pkh}` ); expect(result).toEqual(false); }); diff --git a/src/utils/tezos/helpers.ts b/src/utils/tezos/helpers.ts index 17f8ddb4c..ca33b31b6 100644 --- a/src/utils/tezos/helpers.ts +++ b/src/utils/tezos/helpers.ts @@ -11,21 +11,17 @@ import { makeMultisigProposeOperation, } from "../../types/Operation"; import { SignerConfig } from "../../types/SignerConfig"; -import { TezosNetwork } from "../../types/TezosNetwork"; import { PublicKeyPair } from "../mnemonic"; import { RawTzktGetAddressType } from "../tzkt/types"; -import { nodeUrls, tzktUrls } from "./consts"; import { FakeSigner } from "./fakeSigner"; import BigNumber from "bignumber.js"; import { OpKind, TransactionOperationParameter } from "@taquito/rpc"; import { AccountOperations } from "../../components/sendForm/types"; +import { Network } from "../../types/Network"; -export const addressExists = async ( - pkh: string, - network = TezosNetwork.MAINNET -): Promise => { +export const addressExists = async (pkh: string, network: Network): Promise => { try { - const url = `${tzktUrls[network]}/v1/accounts/${pkh}`; + const url = `${network.tzktApiUrl}/v1/accounts/${pkh}`; const { data: { type }, } = await axios.get(url); @@ -87,7 +83,7 @@ export const makeSigner = async (config: SignerConfig) => { }; export const makeToolkit = async (config: SignerConfig) => { - const toolkit = new TezosToolkit(nodeUrls[config.network]); + const toolkit = new TezosToolkit(config.network.rpcUrl); const signer = await makeSigner(config); toolkit.setSignerProvider(signer); return toolkit; diff --git a/src/utils/tzkt/helpers.test.ts b/src/utils/tzkt/helpers.test.ts index 2d5869400..6c8cf21b6 100644 --- a/src/utils/tzkt/helpers.test.ts +++ b/src/utils/tzkt/helpers.test.ts @@ -1,11 +1,9 @@ -import { TezosNetwork } from "../../types/TezosNetwork"; +import { GHOSTNET, MAINNET } from "../../types/Network"; import { buildTzktAddressUrl } from "./helpers"; test("buildTzktUrl returns the right value for a given network", () => { - expect(buildTzktAddressUrl(TezosNetwork.GHOSTNET, "mockAddress")).toEqual( + expect(buildTzktAddressUrl(GHOSTNET, "mockAddress")).toEqual( "https://ghostnet.tzkt.io/mockAddress" ); - expect(buildTzktAddressUrl(TezosNetwork.MAINNET, "mockAddress")).toEqual( - "https://tzkt.io/mockAddress" - ); + expect(buildTzktAddressUrl(MAINNET, "mockAddress")).toEqual("https://tzkt.io/mockAddress"); }); diff --git a/src/utils/tzkt/helpers.ts b/src/utils/tzkt/helpers.ts index 0cf7516b0..d8c0ef9fe 100644 --- a/src/utils/tzkt/helpers.ts +++ b/src/utils/tzkt/helpers.ts @@ -1,5 +1,4 @@ -import { TezosNetwork } from "../../types/TezosNetwork"; -import { tzktExplorer } from "../tezos/consts"; +import { Network } from "../../types/Network"; -export const buildTzktAddressUrl = (network: TezosNetwork, pkh: string) => - `${tzktExplorer[network]}/${pkh}`; +export const buildTzktAddressUrl = (network: Network, pkh: string) => + `${network.tzktExplorerUrl}/${pkh}`; diff --git a/src/utils/useAssetsPolling.test.ts b/src/utils/useAssetsPolling.test.ts index f5379fb1b..0b26226aa 100644 --- a/src/utils/useAssetsPolling.test.ts +++ b/src/utils/useAssetsPolling.test.ts @@ -1,11 +1,10 @@ import { AllTheProviders, renderHook, waitFor } from "../mocks/testUtils"; import { useAssetsPolling } from "./useAssetsPolling"; -import { SupportedNetworks } from "./network"; -import { tzktUrls } from "./tezos"; import { delegatesGet } from "@tzkt/sdk-api"; import store from "./redux/store"; -import { assetsActions } from "./redux/slices/assetsSlice"; import { mockBaker } from "../mocks/factories"; +import { DefaultNetworks } from "../types/Network"; +import { networksActions } from "./redux/slices/networks"; jest.unmock("./tezos"); jest.mock("@tzkt/sdk-api", () => { @@ -15,13 +14,13 @@ jest.mock("@tzkt/sdk-api", () => { }); describe("useAssetsPolling", () => { - describe.each(SupportedNetworks)("network: %s", network => { + describe.each(DefaultNetworks)("on $name", network => { beforeAll(() => { - store.dispatch(assetsActions.updateNetwork(network)); + store.dispatch(networksActions.setCurrent(network)); }); test("bakers", async () => { - const baseUrl = tzktUrls[network]; + const baseUrl = network.tzktApiUrl; (delegatesGet as jest.Mock).mockResolvedValue([ { ...mockBaker(0), alias: mockBaker(0).name }, { ...mockBaker(1), alias: mockBaker(1).name }, diff --git a/src/utils/useAssetsPolling.ts b/src/utils/useAssetsPolling.ts index b07f29218..3f4f77f25 100644 --- a/src/utils/useAssetsPolling.ts +++ b/src/utils/useAssetsPolling.ts @@ -1,10 +1,10 @@ import { chunk, compact } from "lodash"; import { useEffect, useRef } from "react"; import { useQuery, useQueryClient } from "react-query"; -import { TezosNetwork } from "../types/TezosNetwork"; +import { Network } from "../types/Network"; import { TokenTransfer } from "../types/Transfer"; import { useImplicitAccounts } from "./hooks/accountHooks"; -import { useRefetchTrigger, useSelectedNetwork } from "./hooks/assetsHooks"; +import { useRefetchTrigger } from "./hooks/assetsHooks"; import { getPendingOperationsForMultisigs, getRelevantMultisigContracts } from "./multisig/helpers"; import { processInBatches } from "./promise"; import { @@ -28,17 +28,18 @@ import { } from "./tezos"; import errorsSlice from "./redux/slices/errorsSlice"; import getErrorContext from "./getErrorContext"; +import { useSelectedNetwork } from "./hooks/networkHooks"; const getTezTransfersPayload = async ( pkh: string, - network: TezosNetwork + network: Network ): Promise => { const transfers = await getTezTransfers(pkh, network); return { pkh, transfers }; }; const getTokensTransfersPayload = async ( pkh: string, - network: TezosNetwork + network: Network ): Promise => { const transfers = await getTokenTransfers(pkh, network); // there are no token transfers without a token & amount assigned @@ -47,7 +48,7 @@ const getTokensTransfersPayload = async ( const getDelegationsPayload = async ( pkh: string, - network: TezosNetwork + network: Network ): Promise => { const delegation = await getLastDelegation(pkh, network); return delegation && { pkh, delegation }; diff --git a/src/views/batch/OperationView.test.tsx b/src/views/batch/OperationView.test.tsx index 3123546ca..b985bd10a 100644 --- a/src/views/batch/OperationView.test.tsx +++ b/src/views/batch/OperationView.test.tsx @@ -12,7 +12,7 @@ import { ghostnetThezard } from "../../mocks/nftTokens"; import { render, screen } from "../../mocks/testUtils"; import { parseContractPkh } from "../../types/Address"; import { FA12Transfer, FA2Transfer } from "../../types/Operation"; -import { TezosNetwork } from "../../types/TezosNetwork"; +import { MAINNET } from "../../types/Network"; import { tokensActions } from "../../utils/redux/slices/tokensSlice"; import store from "../../utils/redux/store"; import { TEZ } from "../../utils/tezos"; @@ -43,9 +43,7 @@ describe("", () => { test("fa1.2", () => { const token = hedgehoge(mockImplicitAddress(0)); - store.dispatch( - tokensActions.addTokens({ network: TezosNetwork.MAINNET, tokens: [token.token] }) - ); + store.dispatch(tokensActions.addTokens({ network: MAINNET, tokens: [token.token] })); const operation: FA12Transfer = { ...mockFA12Operation(2), contract: parseContractPkh(token.token.contract.address as string), @@ -55,7 +53,7 @@ describe("", () => { expect(screen.getByRole("heading", { name: "0.001234 Hedgehoge" })).toBeInTheDocument(); expect(screen.getByTestId("link")).toHaveAttribute( "href", - "https://mainnet.tzkt.io/KT1G1cCRNBgQ48mVDjopHjEmTN5Sbtar8nn9/tokens/0" + "https://tzkt.io/KT1G1cCRNBgQ48mVDjopHjEmTN5Sbtar8nn9/tokens/0" ); }); @@ -63,9 +61,7 @@ describe("", () => { const token = uUSD(mockImplicitAddress(0)); token.token.standard = "fa2"; token.token.tokenId = "5"; - store.dispatch( - tokensActions.addTokens({ network: TezosNetwork.MAINNET, tokens: [token.token] }) - ); + store.dispatch(tokensActions.addTokens({ network: MAINNET, tokens: [token.token] })); const operation: FA2Transfer = { ...mockFA2Operation(2), contract: parseContractPkh(token.token.contract.address as string), @@ -78,7 +74,7 @@ describe("", () => { ).toBeInTheDocument(); expect(screen.getByTestId("link")).toHaveAttribute( "href", - "https://mainnet.tzkt.io/KT1QTcAXeefhJ3iXLurRt81WRKdv7YqyYFmo/tokens/5" + "https://tzkt.io/KT1QTcAXeefhJ3iXLurRt81WRKdv7YqyYFmo/tokens/5" ); }); @@ -86,9 +82,7 @@ describe("", () => { const token = ghostnetThezard; token.token.standard = "fa2"; token.token.tokenId = "15"; - store.dispatch( - tokensActions.addTokens({ network: TezosNetwork.MAINNET, tokens: [token.token] }) - ); + store.dispatch(tokensActions.addTokens({ network: MAINNET, tokens: [token.token] })); const operation: FA2Transfer = { ...mockFA2Operation(2), contract: parseContractPkh(token.token.contract.address as string), @@ -101,7 +95,7 @@ describe("", () => { expect(screen.getByTestId("link")).toHaveAttribute( "href", - "https://mainnet.tzkt.io/KT1GVhG7dQNjPAt4FNBNmc9P9zpiQex4Mxob/tokens/15" + "https://tzkt.io/KT1GVhG7dQNjPAt4FNBNmc9P9zpiQex4Mxob/tokens/15" ); }); }); diff --git a/src/views/batch/OperationView.tsx b/src/views/batch/OperationView.tsx index 886a53a9e..fa9fb0fcb 100644 --- a/src/views/batch/OperationView.tsx +++ b/src/views/batch/OperationView.tsx @@ -1,12 +1,12 @@ import { AspectRatio, Flex, Heading, Image, Link, Tooltip } from "@chakra-ui/react"; import { Operation } from "../../types/Operation"; -import { useSelectedNetwork } from "../../utils/hooks/assetsHooks"; import { useGetToken } from "../../utils/hooks/tokensHooks"; import { prettyTezAmount } from "../../utils/format"; import colors from "../../style/colors"; import { getIPFSurl } from "../../utils/token/nftUtils"; import { thumbnailUri, tokenNameSafe, tokenUri } from "../../types/Token"; import { tokenTitle } from "./BatchView"; +import { useSelectedNetwork } from "../../utils/hooks/networkHooks"; export const OperationView = ({ operation }: { operation: Operation }) => { const getToken = useGetToken(); diff --git a/src/views/nfts/NFTsView.test.tsx b/src/views/nfts/NFTsView.test.tsx index 5641794d8..535e86087 100644 --- a/src/views/nfts/NFTsView.test.tsx +++ b/src/views/nfts/NFTsView.test.tsx @@ -5,11 +5,12 @@ import { ReduxStore } from "../../providers/ReduxStore"; import store from "../../utils/redux/store"; import tokensSlice from "../../utils/redux/slices/tokensSlice"; import NFTsViewBase from "./NftsView"; -import { TezosNetwork } from "../../types/TezosNetwork"; import assetsSlice from "../../utils/redux/slices/assetsSlice"; import accountsSlice from "../../utils/redux/slices/accountsSlice"; +import { MAINNET } from "../../types/Network"; +import { networksActions } from "../../utils/redux/slices/networks"; -const { updateTokenBalance, updateNetwork } = assetsSlice.actions; +const { updateTokenBalance } = assetsSlice.actions; beforeEach(() => { store.dispatch(accountsSlice.actions.addAccount([mockImplicitAccount(0)])); @@ -33,7 +34,7 @@ describe("NFTsView", () => { store.dispatch( accountsSlice.actions.addAccount([mockImplicitAccount(1), mockImplicitAccount(2)]) ); - store.dispatch(updateNetwork(TezosNetwork.MAINNET)); + store.dispatch(networksActions.setCurrent(MAINNET)); store.dispatch( updateTokenBalance([ mockNFTToken(1, mockImplicitAccount(1).address.pkh), @@ -44,7 +45,7 @@ describe("NFTsView", () => { ); store.dispatch( tokensSlice.actions.addTokens({ - network: TezosNetwork.MAINNET, + network: MAINNET, tokens: [ mockNFTToken(1, mockImplicitAddress(1).pkh).token, mockNFTToken(2, mockImplicitAddress(1).pkh).token, diff --git a/src/views/nfts/drawer/PropertiesAccordionItem.test.tsx b/src/views/nfts/drawer/PropertiesAccordionItem.test.tsx index 9c2f621fe..bc5abd2a2 100644 --- a/src/views/nfts/drawer/PropertiesAccordionItem.test.tsx +++ b/src/views/nfts/drawer/PropertiesAccordionItem.test.tsx @@ -84,7 +84,7 @@ describe("PropertiesAccordionItem", () => { delete nft.metadata.creators; render(fixture(nft)); - // act(() => store.dispatch(assetsActions.updateNetwork(TezosNetwork.MAINNET))); + // act(() => store.dispatch(networksActions.setCurrent("mainnet"))); expect(screen.getByTestId("nft-creator")).toHaveTextContent("Creator:"); expect(screen.queryByTestId("nft-creator-value")).toHaveTextContent("-"); }); diff --git a/src/views/nfts/drawer/PropertiesAccordionItem.tsx b/src/views/nfts/drawer/PropertiesAccordionItem.tsx index 0ed1ccf0f..5f465645b 100644 --- a/src/views/nfts/drawer/PropertiesAccordionItem.tsx +++ b/src/views/nfts/drawer/PropertiesAccordionItem.tsx @@ -16,8 +16,8 @@ import { TruncatedTextWithTooltip } from "../../../components/TruncatedTextWithT import { TzktLink } from "../../../components/TzktLink"; import { parsePkh } from "../../../types/Address"; import { metadataUri, mimeType, royalties } from "../../../types/Token"; -import { useSelectedNetwork } from "../../../utils/hooks/assetsHooks"; import { NFTBalance } from "../../../types/TokenBalance"; +import { useSelectedNetwork } from "../../../utils/hooks/networkHooks"; const CreatorElement = ({ nft }: { nft: NFTBalance }) => { if (!nft.metadata.creators || nft.metadata.creators.length === 0) { diff --git a/src/views/operations/operationUtils.test.ts b/src/views/operations/operationUtils.test.ts index c8cbbe2a8..163d29a13 100644 --- a/src/views/operations/operationUtils.test.ts +++ b/src/views/operations/operationUtils.test.ts @@ -7,468 +7,179 @@ import { rawTzktNftTransfer, } from "../../mocks/tzktResponse"; import { OperationDisplay, TezTransfer, TokenTransfer } from "../../types/Transfer"; -import { SupportedNetworks } from "../../utils/network"; import { getOperationDisplays, getTezOperationDisplay, getTokenOperationDisplay, getTransactionUrl, } from "./operationsUtils"; +import { DefaultNetworks } from "../../types/Network"; const forAddress = "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS"; -describe("getTezOperationDisplay", () => { - test("it throws for a tez transfer non related to reference address", () => { - const incomingTez: TezTransfer = { - type: "transaction", - id: 109783351820288, - level: 2213611, - timestamp: "2023-03-27T08:47:30Z", - block: "BM4xPHaLWYYw2KLwQrpUYjMYf1eN1mqfjtCEYUCgFpuyAd6u5cH", - hash: "ooZCfnsMgXvxni6umn999MKfpT6zmVJpDzZmmCCh6AZ89gvUHGM", - counter: 134162, - sender: { address: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, - gasLimit: 1101, - gasUsed: 1001, - storageLimit: 0, - storageUsed: 0, - bakerFee: 402, - storageFee: 0, - allocationFee: 0, - target: { address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - amount: 2400000, - status: "applied", - hasInternals: false, - }; - - expect(() => getTezOperationDisplay(incomingTez, mockImplicitAddress(4).pkh)).toThrowError( - "Address tz1i9imTXjMAW5HP5g3wq55Pcr43tDz8c3VZ doesn't match sender or recipient" - ); - }); - - test("returns null for a token transfer non related to reference address", () => { - const incomingKL3: TokenTransfer = { - id: 109855493849090, - level: 2215193, - timestamp: "2023-03-27T13:29:22Z", - token: { - id: 10898231001089, - contract: { - address: "KT1XZoJ3PAidWVWRiKWESmPj64eKN7CEHuWZ", - }, - tokenId: "1", - standard: "fa2", - totalSupply: "13000000000", - metadata: { - name: "Klondike3", - symbol: "KL3", - decimals: "5", - }, - }, - from: { - address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS", - }, - to: { - address: "tz1ikfEcj3LmsmxpcC1RMZNzBHbEmybCc43D", - }, - amount: "451000", - transactionId: 109855493849088, - }; - - expect(() => getTokenOperationDisplay(incomingKL3, mockImplicitAddress(4).pkh)).toThrowError( - "Address tz1i9imTXjMAW5HP5g3wq55Pcr43tDz8c3VZ doesn't match sender or recipient" - ); - }); - - test("case tez incoming", () => { - const incomingTez: TezTransfer = { - type: "transaction", - id: 109783351820288, - level: 2213611, - timestamp: "2023-03-27T08:47:30Z", - block: "BM4xPHaLWYYw2KLwQrpUYjMYf1eN1mqfjtCEYUCgFpuyAd6u5cH", - hash: "ooZCfnsMgXvxni6umn999MKfpT6zmVJpDzZmmCCh6AZ89gvUHGM", - counter: 134162, - sender: { address: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, - gasLimit: 1101, - gasUsed: 1001, - storageLimit: 0, - storageUsed: 0, - bakerFee: 402, - storageFee: 0, - allocationFee: 0, - target: { address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - amount: 2400000, - status: "applied", - hasInternals: false, - }; - - const result = getTezOperationDisplay(incomingTez, forAddress); - - const expected: OperationDisplay = { - id: 109783351820288, - amount: { prettyDisplay: "+2.400000 ꜩ" }, - fee: "0.000402 ꜩ", - prettyTimestamp: "today at 10:47 AM", - recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - sender: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, - status: "confirmed", - timestamp: "2023-03-27T08:47:30Z", - type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/ooZCfnsMgXvxni6umn999MKfpT6zmVJpDzZmmCCh6AZ89gvUHGM", - level: 2213611, - }; - expect(result).toEqual(expected); - }); - - test("case tez outgoing", () => { - const outgoingTez: TezTransfer = { - type: "transaction", - id: 109810172297216, - level: 2214204, - timestamp: "2023-03-27T10:36:40Z", - block: "BKyZ7dksWdf6sFcnWnwvS5FgQ8JgEMFxg3SsL1bWR3FBz5mSAX3", - hash: "oo3Moa2XToLeCjiVhQFHWe3aJkFtqbbW2GKvG9Zvb5aZLs6tHWZ", - counter: 10304021, - sender: { - address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS", - }, - gasLimit: 1101, - gasUsed: 1001, - storageLimit: 0, - storageUsed: 0, - bakerFee: 403, - storageFee: 0, - allocationFee: 0, - target: { - address: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3", - }, - amount: 6410000, - status: "applied", - hasInternals: false, - }; - - const result = getTezOperationDisplay(outgoingTez, forAddress); - - const expected: OperationDisplay = { - id: 109810172297216, - amount: { prettyDisplay: "-6.410000 ꜩ" }, - fee: "0.000403 ꜩ", - prettyTimestamp: "today at 12:36 PM", - recipient: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, - sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - status: "confirmed", - timestamp: "2023-03-27T10:36:40Z", - type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/oo3Moa2XToLeCjiVhQFHWe3aJkFtqbbW2GKvG9Zvb5aZLs6tHWZ", - level: 2214204, - }; - - expect(result).toEqual(expected); - }); - - test("incoming nft", () => { - const incomingNft: TokenTransfer = rawTzktNftTransfer; - - const result = getTokenOperationDisplay(incomingNft, forAddress); - - const expected = { - id: 109817445220353, - type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/transactions/109817445220352", - amount: { - id: 10899580518401, - prettyDisplay: "+1", - url: "https://ipfs.io/ipfs/zb2rhfbacgmTnG13DiCvjs6J21hzMeAueYVWg37C5owThnpfQ", - }, - prettyTimestamp: "today at 1:06 PM", - recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - sender: { type: "implicit", pkh: "tz1W5iRhKWPoLviqExtDDKJqCcPRLBWMhg6S" }, - timestamp: "2023-03-27T11:06:40Z", - level: 2214369, - }; - - expect(result).toEqual(expected); - }); - - test("incoming nft with missing from", () => { - const incomingNft = { ...rawTzktNftTransfer, from: undefined }; - - const result = getTokenOperationDisplay(incomingNft, forAddress); - - const expected = { - id: 109817445220353, - type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/transactions/109817445220352", - amount: { - id: 10899580518401, - prettyDisplay: "+1", - url: "https://ipfs.io/ipfs/zb2rhfbacgmTnG13DiCvjs6J21hzMeAueYVWg37C5owThnpfQ", - }, - prettyTimestamp: "today at 1:06 PM", - recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - sender: { type: "contract", pkh: "KT1GVhG7dQNjPAt4FNBNmc9P9zpiQex4Mxob" }, - timestamp: "2023-03-27T11:06:40Z", - level: 2214369, - }; - - expect(result).toEqual(expected); - }); - - test("outgoing Nft", () => { - const outgoingNft = { - ...rawTzktNftTransfer, - from: { - address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS", - }, - to: { - address: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3", - }, - }; - - const expected = { - id: 109817445220353, - type: "transaction", - amount: { - id: 10899580518401, - prettyDisplay: "-1", - url: "https://ipfs.io/ipfs/zb2rhfbacgmTnG13DiCvjs6J21hzMeAueYVWg37C5owThnpfQ", - }, - tzktUrl: "https://mainnet.tzkt.io/transactions/109817445220352", - prettyTimestamp: "today at 1:06 PM", - recipient: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, - sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - timestamp: "2023-03-27T11:06:40Z", - level: 2214369, - }; - - const result = getTokenOperationDisplay(outgoingNft, forAddress); - expect(result).toEqual(expected); - }); - - test("Incoming fa2 token", () => { - const incomingKL3: TokenTransfer = { - id: 109855131041793, - level: 2215185, - timestamp: "2023-03-27T13:27:13Z", - token: { - id: 10898231001089, - contract: { - address: "KT1XZoJ3PAidWVWRiKWESmPj64eKN7CEHuWZ", - }, - tokenId: "1", - standard: "fa2", - totalSupply: "13000000000", - metadata: { - name: "Klondike3", - symbol: "KL3", - decimals: "5", - }, - }, - from: { - address: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3", - }, - to: { - address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS", - }, - amount: "716850", - transactionId: 109855131041792, - }; - - const result = getTokenOperationDisplay(incomingKL3, forAddress); - const expected = { - id: 109855131041793, - type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/transactions/109855131041792", - amount: { - id: 10898231001089, - prettyDisplay: "+7.16850 KL3", - url: undefined, - }, - prettyTimestamp: "today at 3:27 PM", - recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - sender: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, - timestamp: "2023-03-27T13:27:13Z", - level: 2215185, - }; - expect(result).toEqual(expected); - }); - - test("Outgoing fa2 token", () => { - const incomingKL3: TokenTransfer = { - id: 109855493849090, - level: 2215193, - timestamp: "2023-03-27T13:29:22Z", - token: { - id: 10898231001089, - contract: { - address: "KT1XZoJ3PAidWVWRiKWESmPj64eKN7CEHuWZ", - }, - tokenId: "1", - standard: "fa2", - totalSupply: "13000000000", - metadata: { - name: "Klondike3", - symbol: "KL3", - decimals: "5", - }, - }, - from: { - address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS", - }, - to: { - address: "tz1ikfEcj3LmsmxpcC1RMZNzBHbEmybCc43D", - }, - amount: "451000", - transactionId: 109855493849088, - }; - - const result = getTokenOperationDisplay(incomingKL3, forAddress); - const expected = { - id: 109855493849090, - type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/transactions/109855493849088", - amount: { - id: 10898231001089, - prettyDisplay: "-4.51000 KL3", - url: undefined, - }, - prettyTimestamp: "today at 3:29 PM", - recipient: { type: "implicit", pkh: "tz1ikfEcj3LmsmxpcC1RMZNzBHbEmybCc43D" }, - sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - timestamp: "2023-03-27T13:29:22Z", - level: 2215193, - }; - expect(result).toEqual(expected); - }); +describe.each(DefaultNetworks)("on $name", network => { + describe("getTezOperationDisplay", () => { + test("it throws for a tez transfer non related to reference address", () => { + const incomingTez: TezTransfer = { + type: "transaction", + id: 109783351820288, + level: 2213611, + timestamp: "2023-03-27T08:47:30Z", + block: "BM4xPHaLWYYw2KLwQrpUYjMYf1eN1mqfjtCEYUCgFpuyAd6u5cH", + hash: "ooZCfnsMgXvxni6umn999MKfpT6zmVJpDzZmmCCh6AZ89gvUHGM", + counter: 134162, + sender: { address: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, + gasLimit: 1101, + gasUsed: 1001, + storageLimit: 0, + storageUsed: 0, + bakerFee: 402, + storageFee: 0, + allocationFee: 0, + target: { address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + amount: 2400000, + status: "applied", + hasInternals: false, + }; - test("Incoming fa1.2 token", () => { - const incomingFa12: TokenTransfer = { - id: 109855847219201, - level: 2215201, - timestamp: "2023-03-27T13:30:37Z", - token: { - id: 10897625972737, - contract: { - address: "KT1UCPcXExqEYRnfoXWYvBkkn5uPjn8TBTEe", - }, - tokenId: "0", - standard: "fa1.2", - totalSupply: "13000000", - }, - from: { - address: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3", - }, - to: { - address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS", - }, - amount: "27400", - transactionId: 109855847219200, - }; - - const result = getTokenOperationDisplay(incomingFa12, forAddress); - const expected = { - id: 109855847219201, - type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/transactions/109855847219200", - amount: { - id: 10897625972737, - prettyDisplay: "+27,400 FA1.2", - url: undefined, - }, - prettyTimestamp: "today at 3:30 PM", - recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - sender: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, - timestamp: "2023-03-27T13:30:37Z", - level: 2215201, - }; - expect(result).toEqual(expected); - }); -}); + expect(() => + getTezOperationDisplay(incomingTez, mockImplicitAddress(4).pkh, network) + ).toThrowError( + "Address tz1i9imTXjMAW5HP5g3wq55Pcr43tDz8c3VZ doesn't match sender or recipient" + ); + }); -describe("getOperationDisplays", () => { - it("returns the right value", () => { - const result = getOperationDisplays( - getTransactionsResult, - getTokenTransactionsResult, - getLatestDelegationResult, - forAddress - ); - - const expected: OperationDisplay[] = [ - { - id: 537704232124416, - amount: { prettyDisplay: "0.467532 ꜩ" }, - fee: "0.000396 ꜩ", - level: 3414723, - prettyTimestamp: "04/24/2023", - recipient: { type: "implicit", pkh: "tz1fHn9ZSqMwp1WNwdCLqnh52yPgzQ4QydTm" }, - sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - timestamp: "2023-04-24T09:48:17Z", - type: "delegation", - tzktUrl: "https://mainnet.tzkt.io/onxgPmNMo4756y7PhXeYethMVf2e3HUSHoZuia8rY5qFujgbqva", - }, - { - id: 109855847219201, - amount: { - id: 10897625972737, - prettyDisplay: "+27,400 FA1.2", - url: undefined, - }, - prettyTimestamp: "today at 3:30 PM", - recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - sender: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, - timestamp: "2023-03-27T13:30:37Z", - type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/transactions/109855847219200", - level: 2215201, - }, - { + test("returns null for a token transfer non related to reference address", () => { + const incomingKL3: TokenTransfer = { id: 109855493849090, - amount: { - id: 10898231001089, - prettyDisplay: "-4.51000 KL3", - url: undefined, - }, - prettyTimestamp: "today at 3:29 PM", - recipient: { type: "implicit", pkh: "tz1ikfEcj3LmsmxpcC1RMZNzBHbEmybCc43D" }, - sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - timestamp: "2023-03-27T13:29:22Z", - type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/transactions/109855493849088", level: 2215193, - }, - { - id: 109855131041793, - amount: { + timestamp: "2023-03-27T13:29:22Z", + token: { id: 10898231001089, - prettyDisplay: "+7.16850 KL3", - url: undefined, + contract: { + address: "KT1XZoJ3PAidWVWRiKWESmPj64eKN7CEHuWZ", + }, + tokenId: "1", + standard: "fa2", + totalSupply: "13000000000", + metadata: { + name: "Klondike3", + symbol: "KL3", + decimals: "5", + }, }, - prettyTimestamp: "today at 3:27 PM", + from: { + address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS", + }, + to: { + address: "tz1ikfEcj3LmsmxpcC1RMZNzBHbEmybCc43D", + }, + amount: "451000", + transactionId: 109855493849088, + }; + + expect(() => + getTokenOperationDisplay(incomingKL3, mockImplicitAddress(4).pkh, network) + ).toThrowError( + "Address tz1i9imTXjMAW5HP5g3wq55Pcr43tDz8c3VZ doesn't match sender or recipient" + ); + }); + + test("case tez incoming", () => { + const incomingTez: TezTransfer = { + type: "transaction", + id: 109783351820288, + level: 2213611, + timestamp: "2023-03-27T08:47:30Z", + block: "BM4xPHaLWYYw2KLwQrpUYjMYf1eN1mqfjtCEYUCgFpuyAd6u5cH", + hash: "ooZCfnsMgXvxni6umn999MKfpT6zmVJpDzZmmCCh6AZ89gvUHGM", + counter: 134162, + sender: { address: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, + gasLimit: 1101, + gasUsed: 1001, + storageLimit: 0, + storageUsed: 0, + bakerFee: 402, + storageFee: 0, + allocationFee: 0, + target: { address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + amount: 2400000, + status: "applied", + hasInternals: false, + }; + + const result = getTezOperationDisplay(incomingTez, forAddress, network); + + const expected: OperationDisplay = { + id: 109783351820288, + amount: { prettyDisplay: "+2.400000 ꜩ" }, + fee: "0.000402 ꜩ", + prettyTimestamp: "today at 10:47 AM", recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, sender: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, - timestamp: "2023-03-27T13:27:13Z", + status: "confirmed", + timestamp: "2023-03-27T08:47:30Z", type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/transactions/109855131041792", - level: 2215185, - }, - { - id: 109854457856001, - amount: { - id: 10899580518401, - prettyDisplay: "-1", - url: "https://ipfs.io/ipfs/zb2rhfbacgmTnG13DiCvjs6J21hzMeAueYVWg37C5owThnpfQ", + tzktUrl: `${network.tzktExplorerUrl}/ooZCfnsMgXvxni6umn999MKfpT6zmVJpDzZmmCCh6AZ89gvUHGM`, + level: 2213611, + }; + expect(result).toEqual(expected); + }); + + test("case tez outgoing", () => { + const outgoingTez: TezTransfer = { + type: "transaction", + id: 109810172297216, + level: 2214204, + timestamp: "2023-03-27T10:36:40Z", + block: "BKyZ7dksWdf6sFcnWnwvS5FgQ8JgEMFxg3SsL1bWR3FBz5mSAX3", + hash: "oo3Moa2XToLeCjiVhQFHWe3aJkFtqbbW2GKvG9Zvb5aZLs6tHWZ", + counter: 10304021, + sender: { + address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS", }, - prettyTimestamp: "today at 3:24 PM", + gasLimit: 1101, + gasUsed: 1001, + storageLimit: 0, + storageUsed: 0, + bakerFee: 403, + storageFee: 0, + allocationFee: 0, + target: { + address: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3", + }, + amount: 6410000, + status: "applied", + hasInternals: false, + }; + + const result = getTezOperationDisplay(outgoingTez, forAddress, network); + + const expected: OperationDisplay = { + id: 109810172297216, + amount: { prettyDisplay: "-6.410000 ꜩ" }, + fee: "0.000403 ꜩ", + prettyTimestamp: "today at 12:36 PM", recipient: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - timestamp: "2023-03-27T13:24:48Z", + status: "confirmed", + timestamp: "2023-03-27T10:36:40Z", type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/transactions/109854457856000", - level: 2215172, - }, - { + tzktUrl: `${network.tzktExplorerUrl}/oo3Moa2XToLeCjiVhQFHWe3aJkFtqbbW2GKvG9Zvb5aZLs6tHWZ`, + level: 2214204, + }; + + expect(result).toEqual(expected); + }); + + test("incoming nft", () => { + const incomingNft: TokenTransfer = rawTzktNftTransfer; + + const result = getTokenOperationDisplay(incomingNft, forAddress, network); + + const expected = { id: 109817445220353, + type: "transaction", + tzktUrl: `${network.tzktExplorerUrl}/transactions/109817445220352`, amount: { id: 10899580518401, prettyDisplay: "+1", @@ -478,137 +189,433 @@ describe("getOperationDisplays", () => { recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, sender: { type: "implicit", pkh: "tz1W5iRhKWPoLviqExtDDKJqCcPRLBWMhg6S" }, timestamp: "2023-03-27T11:06:40Z", + level: 2214369, + }; + + expect(result).toEqual(expected); + }); + + test("incoming nft with missing from", () => { + const incomingNft = { ...rawTzktNftTransfer, from: undefined }; + + const result = getTokenOperationDisplay(incomingNft, forAddress, network); + + const expected = { + id: 109817445220353, type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/transactions/109817445220352", + tzktUrl: `${network.tzktExplorerUrl}/transactions/109817445220352`, + amount: { + id: 10899580518401, + prettyDisplay: "+1", + url: "https://ipfs.io/ipfs/zb2rhfbacgmTnG13DiCvjs6J21hzMeAueYVWg37C5owThnpfQ", + }, + prettyTimestamp: "today at 1:06 PM", + recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + sender: { type: "contract", pkh: "KT1GVhG7dQNjPAt4FNBNmc9P9zpiQex4Mxob" }, + timestamp: "2023-03-27T11:06:40Z", level: 2214369, - }, - { - id: 109810172297216, - amount: { prettyDisplay: "-6.410000 ꜩ" }, - fee: "0.000403 ꜩ", - prettyTimestamp: "today at 12:36 PM", + }; + + expect(result).toEqual(expected); + }); + + test("outgoing Nft", () => { + const outgoingNft = { + ...rawTzktNftTransfer, + from: { + address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS", + }, + to: { + address: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3", + }, + }; + + const expected = { + id: 109817445220353, + type: "transaction", + amount: { + id: 10899580518401, + prettyDisplay: "-1", + url: "https://ipfs.io/ipfs/zb2rhfbacgmTnG13DiCvjs6J21hzMeAueYVWg37C5owThnpfQ", + }, + tzktUrl: `${network.tzktExplorerUrl}/transactions/109817445220352`, + prettyTimestamp: "today at 1:06 PM", recipient: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - status: "confirmed", - timestamp: "2023-03-27T10:36:40Z", - type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/oo3Moa2XToLeCjiVhQFHWe3aJkFtqbbW2GKvG9Zvb5aZLs6tHWZ", - level: 2214204, - }, - { - id: 109783351820288, - amount: { prettyDisplay: "+2.400000 ꜩ" }, - fee: "0.000402 ꜩ", - prettyTimestamp: "today at 10:47 AM", - recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - sender: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, - status: "confirmed", - timestamp: "2023-03-27T08:47:30Z", + timestamp: "2023-03-27T11:06:40Z", + level: 2214369, + }; + + const result = getTokenOperationDisplay(outgoingNft, forAddress, network); + expect(result).toEqual(expected); + }); + + test("Incoming fa2 token", () => { + const incomingKL3: TokenTransfer = { + id: 109855131041793, + level: 2215185, + timestamp: "2023-03-27T13:27:13Z", + token: { + id: 10898231001089, + contract: { + address: "KT1XZoJ3PAidWVWRiKWESmPj64eKN7CEHuWZ", + }, + tokenId: "1", + standard: "fa2", + totalSupply: "13000000000", + metadata: { + name: "Klondike3", + symbol: "KL3", + decimals: "5", + }, + }, + from: { + address: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3", + }, + to: { + address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS", + }, + amount: "716850", + transactionId: 109855131041792, + }; + + const result = getTokenOperationDisplay(incomingKL3, forAddress, network); + const expected = { + id: 109855131041793, type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/ooZCfnsMgXvxni6umn999MKfpT6zmVJpDzZmmCCh6AZ89gvUHGM", - level: 2213611, - }, - { - id: 109511935262721, + tzktUrl: `${network.tzktExplorerUrl}/transactions/109855131041792`, amount: { - id: 10898194300929, - prettyDisplay: "+2.10000 KL2", + id: 10898231001089, + prettyDisplay: "+7.16850 KL3", url: undefined, }, - prettyTimestamp: "yesterday at 4:38 PM", + prettyTimestamp: "today at 3:27 PM", recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - sender: { type: "implicit", pkh: "tz1ikfEcj3LmsmxpcC1RMZNzBHbEmybCc43D" }, - timestamp: "2023-03-26T14:38:48Z", - type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/transactions/109511935262720", - level: 2207656, - }, - { - id: 109510819577856, - amount: { prettyDisplay: "-0.000000 ꜩ" }, - fee: "0.000771 ꜩ", - prettyTimestamp: "yesterday at 4:34 PM", - recipient: { type: "contract", pkh: "KT1GVhG7dQNjPAt4FNBNmc9P9zpiQex4Mxob" }, - sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - status: "confirmed", - timestamp: "2023-03-26T14:34:47Z", + sender: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, + timestamp: "2023-03-27T13:27:13Z", + level: 2215185, + }; + expect(result).toEqual(expected); + }); + + test("Outgoing fa2 token", () => { + const incomingKL3: TokenTransfer = { + id: 109855493849090, + level: 2215193, + timestamp: "2023-03-27T13:29:22Z", + token: { + id: 10898231001089, + contract: { + address: "KT1XZoJ3PAidWVWRiKWESmPj64eKN7CEHuWZ", + }, + tokenId: "1", + standard: "fa2", + totalSupply: "13000000000", + metadata: { + name: "Klondike3", + symbol: "KL3", + decimals: "5", + }, + }, + from: { + address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS", + }, + to: { + address: "tz1ikfEcj3LmsmxpcC1RMZNzBHbEmybCc43D", + }, + amount: "451000", + transactionId: 109855493849088, + }; + + const result = getTokenOperationDisplay(incomingKL3, forAddress, network); + const expected = { + id: 109855493849090, type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/op9pGAxiJtPcv37KRnLWhYBDx2RRhTiBeTZNsKQAQ1Pxn8AsbUC", - level: 2207631, - }, - { - id: 109510819577858, + tzktUrl: `${network.tzktExplorerUrl}/transactions/109855493849088`, amount: { - id: 10899580518401, - prettyDisplay: "-1", - url: "https://ipfs.io/ipfs/zb2rhfbacgmTnG13DiCvjs6J21hzMeAueYVWg37C5owThnpfQ", + id: 10898231001089, + prettyDisplay: "-4.51000 KL3", + url: undefined, }, - prettyTimestamp: "yesterday at 4:34 PM", + prettyTimestamp: "today at 3:29 PM", recipient: { type: "implicit", pkh: "tz1ikfEcj3LmsmxpcC1RMZNzBHbEmybCc43D" }, sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, - timestamp: "2023-03-26T14:34:47Z", + timestamp: "2023-03-27T13:29:22Z", + level: 2215193, + }; + expect(result).toEqual(expected); + }); + + test("Incoming fa1.2 token", () => { + const incomingFa12: TokenTransfer = { + id: 109855847219201, + level: 2215201, + timestamp: "2023-03-27T13:30:37Z", + token: { + id: 10897625972737, + contract: { + address: "KT1UCPcXExqEYRnfoXWYvBkkn5uPjn8TBTEe", + }, + tokenId: "0", + standard: "fa1.2", + totalSupply: "13000000", + }, + from: { + address: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3", + }, + to: { + address: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS", + }, + amount: "27400", + transactionId: 109855847219200, + }; + + const result = getTokenOperationDisplay(incomingFa12, forAddress, network); + const expected = { + id: 109855847219201, type: "transaction", - tzktUrl: "https://mainnet.tzkt.io/transactions/109510819577856", - level: 2207631, - }, - ]; - expect(result).toEqual(expected); + tzktUrl: `${network.tzktExplorerUrl}/transactions/109855847219200`, + amount: { + id: 10897625972737, + prettyDisplay: "+27,400 FA1.2", + url: undefined, + }, + prettyTimestamp: "today at 3:30 PM", + recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + sender: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, + timestamp: "2023-03-27T13:30:37Z", + level: 2215201, + }; + expect(result).toEqual(expected); + }); }); - it("includes an active delegation", () => { - const result = getOperationDisplays( - [], - [], - { - id: 12345, - sender: { address: mockImplicitAddress(1).pkh }, - newDelegate: { address: mockImplicitAddress(1).pkh }, - timestamp: new Date().toISOString(), - amount: 100000, - hash: "mockHash", - level: 300, - bakerFee: 400, - } as DelegationOperation, - mockImplicitAddress(1).pkh - ); - expect(result).toEqual([ - { - id: 12345, - amount: { prettyDisplay: "0.100000 ꜩ" }, - fee: "0.000400 ꜩ", - level: 300, - prettyTimestamp: "today at 4:15 PM", - recipient: { type: "implicit", pkh: "tz1UZFB9kGauB6F5c2gfJo4hVcvrD8MeJ3Vf" }, - sender: { type: "implicit", pkh: "tz1UZFB9kGauB6F5c2gfJo4hVcvrD8MeJ3Vf" }, - timestamp: "2023-03-27T14:15:09.760Z", - type: "delegation", - tzktUrl: "https://mainnet.tzkt.io/mockHash", - }, - ]); - }); + describe("getOperationDisplays", () => { + it("returns the right value", () => { + const result = getOperationDisplays( + getTransactionsResult, + getTokenTransactionsResult, + getLatestDelegationResult, + forAddress, + network + ); + + const expected: OperationDisplay[] = [ + { + id: 537704232124416, + amount: { prettyDisplay: "0.467532 ꜩ" }, + fee: "0.000396 ꜩ", + level: 3414723, + prettyTimestamp: "04/24/2023", + recipient: { type: "implicit", pkh: "tz1fHn9ZSqMwp1WNwdCLqnh52yPgzQ4QydTm" }, + sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + timestamp: "2023-04-24T09:48:17Z", + type: "delegation", + tzktUrl: `${network.tzktExplorerUrl}/onxgPmNMo4756y7PhXeYethMVf2e3HUSHoZuia8rY5qFujgbqva`, + }, + { + id: 109855847219201, + amount: { + id: 10897625972737, + prettyDisplay: "+27,400 FA1.2", + url: undefined, + }, + prettyTimestamp: "today at 3:30 PM", + recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + sender: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, + timestamp: "2023-03-27T13:30:37Z", + type: "transaction", + tzktUrl: `${network.tzktExplorerUrl}/transactions/109855847219200`, + level: 2215201, + }, + { + id: 109855493849090, + amount: { + id: 10898231001089, + prettyDisplay: "-4.51000 KL3", + url: undefined, + }, + prettyTimestamp: "today at 3:29 PM", + recipient: { type: "implicit", pkh: "tz1ikfEcj3LmsmxpcC1RMZNzBHbEmybCc43D" }, + sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + timestamp: "2023-03-27T13:29:22Z", + type: "transaction", + tzktUrl: `${network.tzktExplorerUrl}/transactions/109855493849088`, + level: 2215193, + }, + { + id: 109855131041793, + amount: { + id: 10898231001089, + prettyDisplay: "+7.16850 KL3", + url: undefined, + }, + prettyTimestamp: "today at 3:27 PM", + recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + sender: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, + timestamp: "2023-03-27T13:27:13Z", + type: "transaction", + tzktUrl: `${network.tzktExplorerUrl}/transactions/109855131041792`, + level: 2215185, + }, + { + id: 109854457856001, + amount: { + id: 10899580518401, + prettyDisplay: "-1", + url: "https://ipfs.io/ipfs/zb2rhfbacgmTnG13DiCvjs6J21hzMeAueYVWg37C5owThnpfQ", + }, + prettyTimestamp: "today at 3:24 PM", + recipient: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, + sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + timestamp: "2023-03-27T13:24:48Z", + type: "transaction", + tzktUrl: `${network.tzktExplorerUrl}/transactions/109854457856000`, + level: 2215172, + }, + { + id: 109817445220353, + amount: { + id: 10899580518401, + prettyDisplay: "+1", + url: "https://ipfs.io/ipfs/zb2rhfbacgmTnG13DiCvjs6J21hzMeAueYVWg37C5owThnpfQ", + }, + prettyTimestamp: "today at 1:06 PM", + recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + sender: { type: "implicit", pkh: "tz1W5iRhKWPoLviqExtDDKJqCcPRLBWMhg6S" }, + timestamp: "2023-03-27T11:06:40Z", + type: "transaction", + tzktUrl: `${network.tzktExplorerUrl}/transactions/109817445220352`, + level: 2214369, + }, + { + id: 109810172297216, + amount: { prettyDisplay: "-6.410000 ꜩ" }, + fee: "0.000403 ꜩ", + prettyTimestamp: "today at 12:36 PM", + recipient: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, + sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + status: "confirmed", + timestamp: "2023-03-27T10:36:40Z", + type: "transaction", + tzktUrl: `${network.tzktExplorerUrl}/oo3Moa2XToLeCjiVhQFHWe3aJkFtqbbW2GKvG9Zvb5aZLs6tHWZ`, + level: 2214204, + }, + { + id: 109783351820288, + amount: { prettyDisplay: "+2.400000 ꜩ" }, + fee: "0.000402 ꜩ", + prettyTimestamp: "today at 10:47 AM", + recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + sender: { type: "implicit", pkh: "tz1UNer1ijeE9ndjzSszRduR3CzX49hoBUB3" }, + status: "confirmed", + timestamp: "2023-03-27T08:47:30Z", + type: "transaction", + tzktUrl: `${network.tzktExplorerUrl}/ooZCfnsMgXvxni6umn999MKfpT6zmVJpDzZmmCCh6AZ89gvUHGM`, + level: 2213611, + }, + { + id: 109511935262721, + amount: { + id: 10898194300929, + prettyDisplay: "+2.10000 KL2", + url: undefined, + }, + prettyTimestamp: "yesterday at 4:38 PM", + recipient: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + sender: { type: "implicit", pkh: "tz1ikfEcj3LmsmxpcC1RMZNzBHbEmybCc43D" }, + timestamp: "2023-03-26T14:38:48Z", + type: "transaction", + tzktUrl: `${network.tzktExplorerUrl}/transactions/109511935262720`, + level: 2207656, + }, + { + id: 109510819577856, + amount: { prettyDisplay: "-0.000000 ꜩ" }, + fee: "0.000771 ꜩ", + prettyTimestamp: "yesterday at 4:34 PM", + recipient: { type: "contract", pkh: "KT1GVhG7dQNjPAt4FNBNmc9P9zpiQex4Mxob" }, + sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + status: "confirmed", + timestamp: "2023-03-26T14:34:47Z", + type: "transaction", + tzktUrl: `${network.tzktExplorerUrl}/op9pGAxiJtPcv37KRnLWhYBDx2RRhTiBeTZNsKQAQ1Pxn8AsbUC`, + level: 2207631, + }, + { + id: 109510819577858, + amount: { + id: 10899580518401, + prettyDisplay: "-1", + url: "https://ipfs.io/ipfs/zb2rhfbacgmTnG13DiCvjs6J21hzMeAueYVWg37C5owThnpfQ", + }, + prettyTimestamp: "yesterday at 4:34 PM", + recipient: { type: "implicit", pkh: "tz1ikfEcj3LmsmxpcC1RMZNzBHbEmybCc43D" }, + sender: { type: "implicit", pkh: "tz1g7Vk9dxDALJUp4w1UTnC41ssvRa7Q4XyS" }, + timestamp: "2023-03-26T14:34:47Z", + type: "transaction", + tzktUrl: `${network.tzktExplorerUrl}/transactions/109510819577856`, + level: 2207631, + }, + ]; + expect(result).toEqual(expected); + }); - it("ignores an inactive delegation", () => { - const result = getOperationDisplays( - [], - [], - { - id: 12345, - sender: { address: mockImplicitAddress(1).pkh }, - timestamp: new Date().toISOString(), - amount: 100000, - hash: "mockHash", - level: 300, - bakerFee: 400, - } as DelegationOperation, - mockImplicitAddress(1).pkh - ); - expect(result).toEqual([]); + it("includes an active delegation", () => { + const result = getOperationDisplays( + [], + [], + { + id: 12345, + sender: { address: mockImplicitAddress(1).pkh }, + newDelegate: { address: mockImplicitAddress(1).pkh }, + timestamp: new Date().toISOString(), + amount: 100000, + hash: "mockHash", + level: 300, + bakerFee: 400, + } as DelegationOperation, + mockImplicitAddress(1).pkh, + network + ); + expect(result).toEqual([ + { + id: 12345, + amount: { prettyDisplay: "0.100000 ꜩ" }, + fee: "0.000400 ꜩ", + level: 300, + prettyTimestamp: "today at 4:15 PM", + recipient: { type: "implicit", pkh: "tz1UZFB9kGauB6F5c2gfJo4hVcvrD8MeJ3Vf" }, + sender: { type: "implicit", pkh: "tz1UZFB9kGauB6F5c2gfJo4hVcvrD8MeJ3Vf" }, + timestamp: "2023-03-27T14:15:09.760Z", + type: "delegation", + tzktUrl: `${network.tzktExplorerUrl}/mockHash`, + }, + ]); + }); + + it("ignores an inactive delegation", () => { + const result = getOperationDisplays( + [], + [], + { + id: 12345, + sender: { address: mockImplicitAddress(1).pkh }, + timestamp: new Date().toISOString(), + amount: 100000, + hash: "mockHash", + level: 300, + bakerFee: 400, + } as DelegationOperation, + mockImplicitAddress(1).pkh, + network + ); + expect(result).toEqual([]); + }); }); -}); -describe("getTransactionUrl", () => { - it("should return a proper URL", () => { - SupportedNetworks.forEach(network => { + describe("getTransactionUrl", () => { + it("should return a proper URL", () => { expect( getTransactionUrl({ transactionId: 123, @@ -616,7 +623,7 @@ describe("getTransactionUrl", () => { migrationId: 789, network: network, }) - ).toEqual(`https://${network}.tzkt.io/transactions/123`); + ).toEqual(`${network.tzktExplorerUrl}/transactions/123`); expect( getTransactionUrl({ transactionId: undefined, @@ -624,7 +631,7 @@ describe("getTransactionUrl", () => { migrationId: 789, network: network, }) - ).toEqual(`https://${network}.tzkt.io/originations/456`); + ).toEqual(`${network.tzktExplorerUrl}/originations/456`); expect( getTransactionUrl({ transactionId: undefined, @@ -632,7 +639,7 @@ describe("getTransactionUrl", () => { migrationId: 789, network: network, }) - ).toEqual(`https://${network}.tzkt.io/migrations/789`); + ).toEqual(`${network.tzktExplorerUrl}/migrations/789`); expect(() => getTransactionUrl({ transactionId: undefined, diff --git a/src/views/operations/operationsUtils.ts b/src/views/operations/operationsUtils.ts index 3d45fa119..798847c1f 100644 --- a/src/views/operations/operationsUtils.ts +++ b/src/views/operations/operationsUtils.ts @@ -9,10 +9,10 @@ import { BigNumber } from "bignumber.js"; import { prettyTezAmount } from "../../utils/format"; import { DelegationOperation } from "@tzkt/sdk-api"; import { parsePkh } from "../../types/Address"; -import { TezosNetwork } from "../../types/TezosNetwork"; +import { Network } from "../../types/Network"; -export const getHashUrl = (hash: string, network: TezosNetwork) => { - return `https://${network}.tzkt.io/${hash}`; +export const getHashUrl = (hash: string, network: Network) => { + return `${network.tzktExplorerUrl}/${hash}`; }; export const getTransactionUrl = ({ @@ -24,16 +24,16 @@ export const getTransactionUrl = ({ transactionId: number | undefined; originationId: number | undefined; migrationId: number | undefined; - network: TezosNetwork; + network: Network; }) => { if (transactionId) { - return `https://${network}.tzkt.io/transactions/${transactionId}`; + return `${network.tzktExplorerUrl}/transactions/${transactionId}`; } if (originationId) { - return `https://${network}.tzkt.io/originations/${originationId}`; + return `${network.tzktExplorerUrl}/originations/${originationId}`; } if (migrationId) { - return `https://${network}.tzkt.io/migrations/${migrationId}`; + return `${network.tzktExplorerUrl}/migrations/${migrationId}`; } throw new Error("Cannot find transaction TzKT URL"); }; @@ -86,7 +86,7 @@ const TezTransaction = z.object({ export const getTezOperationDisplay = ( transfer: TezTransfer, forAddress: string, - network = TezosNetwork.MAINNET + network: Network ) => { const parseResult = TezTransaction.safeParse(transfer); if (!parseResult.success) { @@ -141,7 +141,7 @@ const TokenTransaction = z.object({ export const getTokenOperationDisplay = ( transfer: TokenTransfer, forAddress: string, - network = TezosNetwork.MAINNET + network: Network ) => { const token = fromRaw(transfer.token); @@ -210,7 +210,7 @@ const DelegationSchema = z.object({ const getDelegationOperationDisplay = ( delegation: DelegationOperation, - network = TezosNetwork.MAINNET + network: Network ): OperationDisplay | null => { const parseResult = DelegationSchema.safeParse(delegation); @@ -258,13 +258,13 @@ export const getOperationDisplays = ( tokenTransfers: TokenTransfer[] = [], delegation: DelegationOperation | null = null, forAdress: string, - network: TezosNetwork = TezosNetwork.MAINNET + network: Network ) => { return sortOperationsByTimestamp( compact([ ...tezTransfers.map(t => getTezOperationDisplay(t, forAdress, network)), ...tokenTransfers.map(t => getTokenOperationDisplay(t, forAdress, network)), - delegation ? getDelegationOperationDisplay(delegation) : null, + delegation ? getDelegationOperationDisplay(delegation, network) : null, ]) ); }; diff --git a/src/views/tokens/AccountTokensTile.tsx b/src/views/tokens/AccountTokensTile.tsx index a4040f314..16f6fe4c5 100644 --- a/src/views/tokens/AccountTokensTile.tsx +++ b/src/views/tokens/AccountTokensTile.tsx @@ -24,10 +24,10 @@ import { Account } from "../../types/Account"; import { FA12TokenBalance, FA2TokenBalance } from "../../types/TokenBalance"; import { httpIconUri, tokenNameSafe, tokenPrettyAmount } from "../../types/Token"; import { formatPkh } from "../../utils/format"; -import { useSelectedNetwork } from "../../utils/hooks/assetsHooks"; import { buildTzktAddressUrl } from "../../utils/tzkt/helpers"; import { DynamicModalContext } from "../../components/DynamicModal"; import SendTokenFormPage from "../../components/SendFlow/Token/FormPage"; +import { useSelectedNetwork } from "../../utils/hooks/networkHooks"; const AccountTokensTileHeader: React.FC<{ pkh: string; diff --git a/src/views/tokens/TokensView.test.tsx b/src/views/tokens/TokensView.test.tsx index 1847944bc..81dd0ccfa 100644 --- a/src/views/tokens/TokensView.test.tsx +++ b/src/views/tokens/TokensView.test.tsx @@ -3,12 +3,13 @@ import { hedgehoge, tzBtsc } from "../../mocks/fa12Tokens"; import { uUSD } from "../../mocks/fa2Tokens"; import { mockImplicitAccount, mockImplicitAddress } from "../../mocks/factories"; import { ReduxStore } from "../../providers/ReduxStore"; -import { SupportedNetworks } from "../../utils/network"; import store from "../../utils/redux/store"; import { tokensActions } from "../../utils/redux/slices/tokensSlice"; import TokensView from "./TokensView"; import accountsSlice from "../../utils/redux/slices/accountsSlice"; -import assetsSlice, { assetsActions } from "../../utils/redux/slices/assetsSlice"; +import assetsSlice from "../../utils/redux/slices/assetsSlice"; +import { DefaultNetworks } from "../../types/Network"; +import { networksActions } from "../../utils/redux/slices/networks"; const fixture = () => ( @@ -26,8 +27,8 @@ describe("", () => { expect(screen.getByText(/no tokens found/i)).toBeInTheDocument(); }); - test.each(SupportedNetworks)("shows all available tokens from all accounts on %s", network => { - store.dispatch(assetsActions.updateNetwork(network)); + test.each(DefaultNetworks)("shows all available tokens from all accounts on $name", network => { + store.dispatch(networksActions.setCurrent(network)); store.dispatch(accountsSlice.actions.addAccount([mockImplicitAccount(1)])); const tokenBalances = [ hedgehoge(mockImplicitAddress(0)), diff --git a/yarn.lock b/yarn.lock index 8e32dd58a..cd20ce4b3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13500,6 +13500,28 @@ __metadata: languageName: node linkType: hard +"eslint-plugin-unused-imports@npm:^3.0.0": + version: 3.0.0 + resolution: "eslint-plugin-unused-imports@npm:3.0.0" + dependencies: + eslint-rule-composer: ^0.3.0 + peerDependencies: + "@typescript-eslint/eslint-plugin": ^6.0.0 + eslint: ^8.0.0 + peerDependenciesMeta: + "@typescript-eslint/eslint-plugin": + optional: true + checksum: 51666f62cc8dccba2895ced83f3c1e0b78b68c357e17360e156c4db548bfdeda34cbd8725192fb4903f22d5069400fb22ded6039631df01ee82fd618dc307247 + languageName: node + linkType: hard + +"eslint-rule-composer@npm:^0.3.0": + version: 0.3.0 + resolution: "eslint-rule-composer@npm:0.3.0" + checksum: c2f57cded8d1c8f82483e0ce28861214347e24fd79fd4144667974cd334d718f4ba05080aaef2399e3bbe36f7d6632865110227e6b176ed6daa2d676df9281b1 + languageName: node + linkType: hard + "eslint-scope@npm:5.1.1, eslint-scope@npm:^5.1.1": version: 5.1.1 resolution: "eslint-scope@npm:5.1.1" @@ -23720,6 +23742,7 @@ __metadata: electronmon: ^2.0.2 eslint: ^8.47.0 eslint-plugin-storybook: ^0.6.13 + eslint-plugin-unused-imports: ^3.0.0 framer-motion: ^10.15.2 graphql: ^16.8.0 graphql-request: ^6.1.0