diff --git a/background/constants/base-assets.ts b/background/constants/base-assets.ts index ec3bbef94d..77f62aff87 100644 --- a/background/constants/base-assets.ts +++ b/background/constants/base-assets.ts @@ -47,6 +47,24 @@ const GOERLI_ETH: NetworkBaseAsset = { }, } +const SEPOLIA_ETH: NetworkBaseAsset = { + ...ETH, + chainID: "11155111", + metadata: { + coinGeckoID: "ethereum", + tokenLists: [], + }, +} + +const ARBITRUM_SEPOLIA_ETH: NetworkBaseAsset = { + ...ETH, + chainID: "421614", + metadata: { + coinGeckoID: "ethereum", + tokenLists: [], + }, +} + const RBTC: NetworkBaseAsset = { chainID: "30", name: "RSK Token", @@ -109,6 +127,8 @@ export const BASE_ASSETS_BY_CUSTOM_NAME = { ARBITRUM_NOVA_ETH, OPTIMISTIC_ETH, GOERLI_ETH, + SEPOLIA_ETH, + ARBITRUM_SEPOLIA_ETH, ZK_SYNC_ETH, } diff --git a/background/constants/currencies.ts b/background/constants/currencies.ts index ac28f0e583..d40d8dbeed 100644 --- a/background/constants/currencies.ts +++ b/background/constants/currencies.ts @@ -59,6 +59,17 @@ export const GOERLI_ETH: NetworkBaseAsset & Required = { ...ETH_DATA, } +export const SEPOLIA_ETH: NetworkBaseAsset & Required = { + ...BASE_ASSETS_BY_CUSTOM_NAME.SEPOLIA_ETH, + ...ETH_DATA, +} + +export const ARBITRUM_SEPOLIA_ETH: NetworkBaseAsset & Required = + { + ...BASE_ASSETS_BY_CUSTOM_NAME.ARBITRUM_SEPOLIA_ETH, + ...ETH_DATA, + } + export const ZK_SYNC_ETH: NetworkBaseAsset & Required = { ...BASE_ASSETS_BY_CUSTOM_NAME.ZK_SYNC_ETH, ...ETH_DATA, @@ -103,6 +114,8 @@ export const BUILT_IN_NETWORK_BASE_ASSETS = [ ARBITRUM_ONE_ETH, ARBITRUM_NOVA_ETH, GOERLI_ETH, + SEPOLIA_ETH, + ARBITRUM_SEPOLIA_ETH, AVAX, BNB, ] diff --git a/background/constants/networks.ts b/background/constants/networks.ts index 9d55718bf1..bca27482ff 100644 --- a/background/constants/networks.ts +++ b/background/constants/networks.ts @@ -7,6 +7,8 @@ import { BNB, ETH, GOERLI_ETH, + SEPOLIA_ETH, + ARBITRUM_SEPOLIA_ETH, MATIC, OPTIMISTIC_ETH, RBTC, @@ -79,13 +81,29 @@ export const OPTIMISM: EVMNetwork = { } export const GOERLI: EVMNetwork = { - name: "Goerli", + name: "Ethereum Goerli", baseAsset: GOERLI_ETH, chainID: "5", family: "EVM", coingeckoPlatformID: "ethereum", } +export const SEPOLIA: EVMNetwork = { + name: "Ethereum Sepolia", + baseAsset: SEPOLIA_ETH, + chainID: "11155111", + family: "EVM", + coingeckoPlatformID: "ethereum", +} + +export const ARBITRUM_SEPOLIA: EVMNetwork = { + name: "Arbitrum Sepolia", + baseAsset: ARBITRUM_SEPOLIA_ETH, + chainID: "421614", + family: "EVM", + coingeckoPlatformID: "ethereum", +} + export const ZK_SYNC: EVMNetwork = { name: "zkSync Era", baseAsset: ZK_SYNC_ETH, @@ -98,6 +116,8 @@ export const DEFAULT_NETWORKS = [ POLYGON, OPTIMISM, GOERLI, + SEPOLIA, + ARBITRUM_SEPOLIA, ARBITRUM_ONE, ROOTSTOCK, AVALANCHE, @@ -124,11 +144,13 @@ export const FORK: EVMNetwork = { } export const EIP_1559_COMPLIANT_CHAIN_IDS = new Set( - [ETHEREUM, POLYGON, GOERLI, AVALANCHE].map((network) => network.chainID), + [ETHEREUM, POLYGON, GOERLI, SEPOLIA, AVALANCHE].map( + (network) => network.chainID, + ), ) export const CHAINS_WITH_MEMPOOL = new Set( - [ETHEREUM, POLYGON, AVALANCHE, GOERLI, BINANCE_SMART_CHAIN].map( + [ETHEREUM, POLYGON, AVALANCHE, GOERLI, SEPOLIA, BINANCE_SMART_CHAIN].map( (network) => network.chainID, ), ) @@ -143,12 +165,14 @@ export const NETWORK_BY_CHAIN_ID = { [OPTIMISM.chainID]: OPTIMISM, [BINANCE_SMART_CHAIN.chainID]: BINANCE_SMART_CHAIN, [GOERLI.chainID]: GOERLI, + [SEPOLIA.chainID]: SEPOLIA, + [ARBITRUM_SEPOLIA.chainID]: ARBITRUM_SEPOLIA, [FORK.chainID]: FORK, [ZK_SYNC.chainID]: ZK_SYNC, } export const TEST_NETWORK_BY_CHAIN_ID = new Set( - [GOERLI].map((network) => network.chainID), + [GOERLI, SEPOLIA, ARBITRUM_SEPOLIA].map((network) => network.chainID), ) export const NETWORK_FOR_LEDGER_SIGNING = [ETHEREUM, POLYGON] @@ -162,6 +186,7 @@ export const CHAIN_ID_TO_0X_API_BASE: { [POLYGON.chainID]: "polygon.api.0x.org", [OPTIMISM.chainID]: "optimism.api.0x.org", [GOERLI.chainID]: "goerli.api.0x.org", + // TODO: Add Swap API for Sepolia once 0x supports it. [ARBITRUM_ONE.chainID]: "arbitrum.api.0x.org", [AVALANCHE.chainID]: "avalanche.api.0x.org", [BINANCE_SMART_CHAIN.chainID]: "bsc.api.0x.org", @@ -172,7 +197,8 @@ export const NETWORKS_SUPPORTING_SWAPS = new Set( ) export const ALCHEMY_SUPPORTED_CHAIN_IDS = new Set( - [ETHEREUM, POLYGON, ARBITRUM_ONE, OPTIMISM, GOERLI].map( + // TODO: Add `ARBITRUM_SEPOLIA` once Alchemy creates a dedicated RPC. + [ETHEREUM, POLYGON, ARBITRUM_ONE, OPTIMISM, GOERLI, SEPOLIA].map( (network) => network.chainID, ), ) @@ -203,6 +229,8 @@ export const CHAIN_ID_TO_RPC_URLS: { ], [ARBITRUM_NOVA.chainID]: ["https://nova.arbitrum.io/rpc "], [GOERLI.chainID]: ["https://ethereum-goerli-rpc.allthatnode.com"], + [SEPOLIA.chainID]: ["https://endpoints.omniatech.io/v1/eth/sepolia/public"], + [ARBITRUM_SEPOLIA.chainID]: ["https://sepolia-rollup.arbitrum.io/rpc"], [AVALANCHE.chainID]: [ "https://api.avax.network/ext/bc/C/rpc", "https://1rpc.io/avax/c", diff --git a/background/lib/alchemy.ts b/background/lib/alchemy.ts index 845d536c53..7851ef15f3 100644 --- a/background/lib/alchemy.ts +++ b/background/lib/alchemy.ts @@ -69,7 +69,7 @@ export async function getAssetTransfers( const category = ["external", "erc20"] if (addressOnNetwork.network.name === "Ethereum") { - // "internal" is supported only on Ethereum Mainnet and Goerli atm + // "internal" is supported only on Ethereum Mainnet, Goerli and Sepolia atm // https://docs.alchemy.com/alchemy/enhanced-apis/transfers-api#alchemy_getassettransfers-testnets-and-layer-2s category.push("internal") } diff --git a/background/lib/utils/index.ts b/background/lib/utils/index.ts index b0615c27b3..5d3087167e 100644 --- a/background/lib/utils/index.ts +++ b/background/lib/utils/index.ts @@ -2,7 +2,7 @@ import { BigNumber, ethers, utils } from "ethers" import { normalizeHexAddress, toChecksumAddress } from "@tallyho/hd-keyring" import { NormalizedEVMAddress, UNIXTime } from "../../types" import { EVMNetwork } from "../../networks" -import { ETHEREUM, GOERLI } from "../../constants" +import { ETHEREUM, GOERLI, SEPOLIA } from "../../constants" import { AddressOnNetwork } from "../../accounts" export function isValidChecksumAddress( @@ -161,6 +161,10 @@ export function getEthereumNetwork(): EVMNetwork { return GOERLI } + if (ethereumNetwork === "SEPOLIA") { + return SEPOLIA + } + // Default to mainnet return ETHEREUM } diff --git a/background/redux-slices/dapp.ts b/background/redux-slices/dapp.ts index 0f6baf86c5..73be259f3a 100644 --- a/background/redux-slices/dapp.ts +++ b/background/redux-slices/dapp.ts @@ -9,6 +9,8 @@ import { BINANCE_SMART_CHAIN, ETHEREUM, GOERLI, + SEPOLIA, + ARBITRUM_SEPOLIA, OPTIMISM, POLYGON, ROOTSTOCK, @@ -135,6 +137,8 @@ const dappSlice = createSlice({ AVALANCHE, BINANCE_SMART_CHAIN, GOERLI, + SEPOLIA, + ARBITRUM_SEPOLIA, ARBITRUM_NOVA, ].map((network) => ({ ...permission, diff --git a/background/services/chain/serial-fallback-provider.ts b/background/services/chain/serial-fallback-provider.ts index 82d87b2d9e..9abc72ef8a 100644 --- a/background/services/chain/serial-fallback-provider.ts +++ b/background/services/chain/serial-fallback-provider.ts @@ -1,6 +1,4 @@ import { - AlchemyProvider, - AlchemyWebSocketProvider, EventType, JsonRpcBatchProvider, JsonRpcProvider, @@ -27,6 +25,7 @@ import { } from "../../lib/alchemy" import { FeatureFlags, isEnabled } from "../../features" import { RpcConfig } from "./db" +import TahoAlchemyProvider from "./taho-provider" export type ProviderCreator = { type: "alchemy" | "custom" | "generic" @@ -1097,15 +1096,7 @@ export function makeSerialFallbackProvider( { type: "alchemy" as const, creator: () => - new AlchemyProvider(getNetwork(Number(chainID)), ALCHEMY_KEY), - }, - { - type: "alchemy" as const, - creator: () => - new AlchemyWebSocketProvider( - getNetwork(Number(chainID)), - ALCHEMY_KEY, - ), + new TahoAlchemyProvider(getNetwork(Number(chainID)), ALCHEMY_KEY), }, ] : [] diff --git a/background/services/chain/taho-provider.ts b/background/services/chain/taho-provider.ts new file mode 100644 index 0000000000..224c35d75a --- /dev/null +++ b/background/services/chain/taho-provider.ts @@ -0,0 +1,30 @@ +import { + AlchemyProvider, + Network, + showThrottleMessage, +} from "@ethersproject/providers" +import { ConnectionInfo } from "@ethersproject/web" + +export default class TahoAlchemyProvider extends AlchemyProvider { + static override getUrl(network: Network, apiKey: string): ConnectionInfo { + let host = null + switch (network.name) { + case "sepolia": + host = "eth-sepolia.g.alchemy.com/v2/" + break + default: + return AlchemyProvider.getUrl(network, apiKey) + } + + return { + allowGzip: true, + url: `https://${host}${apiKey}`, + throttleCallback: () => { + if (apiKey === "0") { + showThrottleMessage() + } + return Promise.resolve(true) + }, + } + } +} diff --git a/background/services/chain/tests/index.integration.test.ts b/background/services/chain/tests/index.integration.test.ts index 5d419bb312..6023ce431a 100644 --- a/background/services/chain/tests/index.integration.test.ts +++ b/background/services/chain/tests/index.integration.test.ts @@ -197,7 +197,7 @@ describe("ChainService", () => { it("Should properly update supported networks", async () => { chainService.supportedNetworks = [] await chainService.updateSupportedNetworks() - expect(chainService.supportedNetworks.length).toBe(8) + expect(chainService.supportedNetworks.length).toBe(10) }) }) @@ -205,9 +205,9 @@ describe("ChainService", () => { // prettier-ignore const FANTOM_CHAIN_PARAMS = { chainId: "250", blockExplorerUrl: "https://ftmscan.com", chainName: "Fantom Opera", nativeCurrency: { name: "Fantom", symbol: "FTM", decimals: 18, }, rpcUrls: [ "https://fantom-mainnet.gateway.pokt.network/v1/lb/62759259ea1b320039c9e7ac", "https://rpc.ftm.tools", "https://rpc.ankr.com/fantom", "https://rpc.fantom.network", "https://rpc2.fantom.network", "https://rpc3.fantom.network", "https://rpcapi.fantom.network", "https://fantom-mainnet.public.blastapi.io", "https://1rpc.io/ftm", ], blockExplorerUrls: ["https://ftmscan.com"], } it("should update supported networks after adding a chain", async () => { - expect(chainService.supportedNetworks.length).toBe(8) + expect(chainService.supportedNetworks.length).toBe(10) await chainService.addCustomChain(FANTOM_CHAIN_PARAMS) - expect(chainService.supportedNetworks.length).toBe(9) + expect(chainService.supportedNetworks.length).toBe(11) }) it("should create a provider for the new chain", async () => { diff --git a/background/services/name/resolvers/ens.ts b/background/services/name/resolvers/ens.ts index 68de4668a3..86f8e49060 100644 --- a/background/services/name/resolvers/ens.ts +++ b/background/services/name/resolvers/ens.ts @@ -6,6 +6,7 @@ import { BINANCE_SMART_CHAIN, ETHEREUM, GOERLI, + SEPOLIA, OPTIMISM, POLYGON, } from "../../../constants" @@ -18,6 +19,9 @@ const ENS_SUPPORTED_NETWORKS = [ OPTIMISM, ARBITRUM_ONE, GOERLI, + SEPOLIA, + // TODO: Add ARBITRUM_SEPOLIA once the support is added (tracked in + // https://github.com/ProjectOpenSea/opensea-js/issues/1201). AVALANCHE, BINANCE_SMART_CHAIN, ] diff --git a/e2e-tests/regular/transactions.spec.ts b/e2e-tests/regular/transactions.spec.ts index d7a637d1db..69b91cae7d 100644 --- a/e2e-tests/regular/transactions.spec.ts +++ b/e2e-tests/regular/transactions.spec.ts @@ -49,11 +49,11 @@ test.describe("Transactions", () => { */ await popup.getByTestId("top_menu_network_switcher").last().click() await popup - .getByText(/^Goerli$/) + .getByText(/^Ethereum Goerli$/) .last() .click() await walletPageHelper.assertCommonElements( - /^Goerli$/, + /^Ethereum Goerli$/, true, account2.name, ) @@ -82,7 +82,7 @@ test.describe("Transactions", () => { * isn't active. */ await transactionsHelper.assertUnfilledSendAssetScreen( - /^Goerli$/, + /^Ethereum Goerli$/, account2.name, "ETH", "(\\d|,)+(\\.\\d{0,4})*", @@ -118,7 +118,7 @@ test.describe("Transactions", () => { * Check if "Transfer" has opened and verify elements on the page. */ await transactionsHelper.assertTransferScreen( - "Goerli", + "Ethereum Goerli", "testertesting\\.eth", "0x47745a7252e119431ccf973c0ebd4279638875a6", "0x4774…875a6", @@ -150,7 +150,7 @@ test.describe("Transactions", () => { */ await expect(popup.getByTestId("activity_list")).toHaveCount(1) await assetsHelper.assertAssetDetailsPage( - /^Goerli$/, + /^Ethereum Goerli$/, account2.name, /^ETH$/, /^(\d|,)+(\.\d{0,4})*$/, @@ -207,7 +207,7 @@ test.describe("Transactions", () => { * Verify elements on the activity screen */ await walletPageHelper.assertCommonElements( - /^Goerli$/, + /^Ethereum Goerli$/, true, account2.name, ) diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index b9cada0d77..ce6aa7a228 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -545,6 +545,7 @@ "beta": "Mainnet (beta)", "testnet": "Test Network", "l2": "L2 scaling solution", + "l2Testnet": "L2 Test Network", "compatibleChain": "EVM-compatible blockchain", "avalanche": "Mainnet C-Chain", "connected": "Connected" diff --git a/ui/components/TopMenu/TopMenuProtocolList.tsx b/ui/components/TopMenu/TopMenuProtocolList.tsx index 4dccb0180e..49989587a0 100644 --- a/ui/components/TopMenu/TopMenuProtocolList.tsx +++ b/ui/components/TopMenu/TopMenuProtocolList.tsx @@ -6,6 +6,8 @@ import { BINANCE_SMART_CHAIN, ETHEREUM, GOERLI, + SEPOLIA, + ARBITRUM_SEPOLIA, isBuiltInNetwork, OPTIMISM, POLYGON, @@ -40,6 +42,16 @@ const testNetworks = [ info: i18n.t("protocol.testnet"), isDisabled: false, }, + { + network: SEPOLIA, + info: i18n.t("protocol.testnet"), + isDisabled: false, + }, + { + network: ARBITRUM_SEPOLIA, + info: i18n.t("protocol.l2Testnet"), + isDisabled: false, + }, ] type TopMenuProtocolListProps = { diff --git a/ui/public/images/networks/arbitrumsepolia-square@2x.png b/ui/public/images/networks/arbitrumsepolia-square@2x.png new file mode 100644 index 0000000000..4935d181be Binary files /dev/null and b/ui/public/images/networks/arbitrumsepolia-square@2x.png differ diff --git a/ui/public/images/networks/arbitrumsepolia@2x.png b/ui/public/images/networks/arbitrumsepolia@2x.png new file mode 100644 index 0000000000..4935d181be Binary files /dev/null and b/ui/public/images/networks/arbitrumsepolia@2x.png differ diff --git a/ui/public/images/networks/goerli-square@2x.png b/ui/public/images/networks/ethereumgoerli-square@2x.png similarity index 100% rename from ui/public/images/networks/goerli-square@2x.png rename to ui/public/images/networks/ethereumgoerli-square@2x.png diff --git a/ui/public/images/networks/goerli@2x.png b/ui/public/images/networks/ethereumgoerli@2x.png similarity index 100% rename from ui/public/images/networks/goerli@2x.png rename to ui/public/images/networks/ethereumgoerli@2x.png diff --git a/ui/public/images/networks/ethereumsepolia-square@2x.png b/ui/public/images/networks/ethereumsepolia-square@2x.png new file mode 100644 index 0000000000..2af27d97bc Binary files /dev/null and b/ui/public/images/networks/ethereumsepolia-square@2x.png differ diff --git a/ui/public/images/networks/ethereumsepolia@2x.png b/ui/public/images/networks/ethereumsepolia@2x.png new file mode 100644 index 0000000000..2af27d97bc Binary files /dev/null and b/ui/public/images/networks/ethereumsepolia@2x.png differ diff --git a/ui/utils/constants.ts b/ui/utils/constants.ts index 832af62529..0ba4d8b261 100644 --- a/ui/utils/constants.ts +++ b/ui/utils/constants.ts @@ -5,6 +5,8 @@ import { BINANCE_SMART_CHAIN, ETHEREUM, GOERLI, + SEPOLIA, + ARBITRUM_SEPOLIA, OPTIMISM, POLYGON, ROOTSTOCK, @@ -23,6 +25,14 @@ export const blockExplorer = { }, [POLYGON.chainID]: { title: "Polygonscan", url: "https://polygonscan.com" }, [GOERLI.chainID]: { title: "Etherscan", url: "https://goerli.etherscan.io/" }, + [SEPOLIA.chainID]: { + title: "Etherscan", + url: "https://sepolia.etherscan.io/", + }, + [ARBITRUM_SEPOLIA.chainID]: { + title: "Arbiscan", + url: "https://sepolia.arbiscan.io/", + }, [ARBITRUM_ONE.chainID]: { title: "Arbiscan", url: "https://arbiscan.io/" }, [AVALANCHE.chainID]: { title: "Snowtrace", url: "https://snowtrace.io/" }, [BINANCE_SMART_CHAIN.chainID]: {