From 3f6c59851b574c4e353a9b40fc86c9a1218bde9e Mon Sep 17 00:00:00 2001 From: Daniel Sinclair <4412473+DanielSinclair@users.noreply.github.com> Date: Fri, 18 Oct 2024 23:12:31 -0400 Subject: [PATCH] feat: apechain support (#1737) --- lavamoat/build-webpack/policy.json | 50 +++++++----------- src/core/firebase/remoteConfig.ts | 2 + src/core/references/assets.ts | 3 ++ src/core/references/index.ts | 12 +++++ src/core/state/favorites/index.ts | 15 +++++- src/core/types/chains.ts | 8 +++ src/core/types/nfts.ts | 1 + src/core/utils/faucets.ts | 1 + src/core/utils/nfts.ts | 3 ++ src/core/utils/userChains.ts | 9 +++- src/entries/iframe/notification.tsx | 2 + .../components/ChainBadge/ChainBadge.tsx | 3 ++ .../popup/components/SideChainExplainer.tsx | 1 + .../popup/hooks/swap/useSwapSettings.ts | 5 +- .../popup/hooks/useSearchCurrencyLists.ts | 16 ++++++ static/assets/badges/apechainBadge.png | Bin 0 -> 657 bytes static/assets/badges/apechainBadge@2x.png | Bin 0 -> 1564 bytes static/assets/badges/apechainBadge@3x.png | Bin 0 -> 2469 bytes static/json/languages/en_US.json | 9 +++- 19 files changed, 102 insertions(+), 38 deletions(-) create mode 100644 static/assets/badges/apechainBadge.png create mode 100644 static/assets/badges/apechainBadge@2x.png create mode 100644 static/assets/badges/apechainBadge@3x.png diff --git a/lavamoat/build-webpack/policy.json b/lavamoat/build-webpack/policy.json index 19440ec708..7db10498a1 100644 --- a/lavamoat/build-webpack/policy.json +++ b/lavamoat/build-webpack/policy.json @@ -614,10 +614,10 @@ "eslint-config-rainbow>eslint-import-resolver-babel-module>@babel/core>@babel/helper-module-transforms>@babel/helper-module-imports": true, "eslint-config-rainbow>eslint-import-resolver-babel-module>@babel/core>@babel/helper-module-transforms>@babel/helper-simple-access": true, "eslint-config-rainbow>eslint-import-resolver-babel-module>@babel/core>@babel/helper-module-transforms>@babel/helper-split-export-declaration": true, + "eslint-config-rainbow>eslint-import-resolver-babel-module>@babel/core>@babel/helper-module-transforms>@babel/helper-validator-identifier": true, "eslint-config-rainbow>eslint-import-resolver-babel-module>@babel/core>@babel/template": true, "jest>@jest/core>jest-snapshot>@babel/traverse": true, - "jest>@jest/core>jest-snapshot>@babel/types": true, - "jest>@jest/core>jest-snapshot>@babel/types>@babel/helper-validator-identifier": true + "jest>@jest/core>jest-snapshot>@babel/types": true } }, "eslint-config-rainbow>eslint-import-resolver-babel-module>@babel/core>@babel/helper-module-transforms>@babel/helper-module-imports": { @@ -1141,9 +1141,16 @@ "console.warn": true, "process.emitWarning": true }, + "packages": { + "jest>@jest/core>jest-snapshot>@babel/traverse>@babel/code-frame>@babel/highlight": true, + "jest>@jest/core>jest-snapshot>@babel/traverse>@babel/code-frame>chalk": true + } + }, + "jest>@jest/core>jest-snapshot>@babel/traverse>@babel/code-frame>@babel/highlight": { "packages": { "jest>@jest/core>jest-snapshot>@babel/traverse>@babel/code-frame>chalk": true, - "lavamoat>@babel/highlight": true + "jest>@jest/core>jest-snapshot>@babel/traverse>@babel/types>@babel/helper-validator-identifier": true, + "react>loose-envify>js-tokens": true } }, "jest>@jest/core>jest-snapshot>@babel/traverse>@babel/code-frame>chalk": { @@ -1217,34 +1224,6 @@ "lavamoat>lavamoat-core>@babel/types>to-fast-properties": true } }, - "lavamoat>@babel/highlight": { - "packages": { - "lavamoat>@babel/highlight>@babel/helper-validator-identifier": true, - "lavamoat>@babel/highlight>chalk": true, - "react>loose-envify>js-tokens": true - } - }, - "lavamoat>@babel/highlight>chalk": { - "globals": { - "process.env.TERM": true, - "process.platform": true - }, - "packages": { - "lavamoat>@babel/highlight>chalk>ansi-styles": true, - "lavamoat>@babel/highlight>chalk>escape-string-regexp": true, - "supports-color": true - } - }, - "lavamoat>@babel/highlight>chalk>ansi-styles": { - "packages": { - "lavamoat>@babel/highlight>chalk>ansi-styles>color-convert": true - } - }, - "lavamoat>@babel/highlight>chalk>ansi-styles>color-convert": { - "packages": { - "lavamoat>@babel/highlight>chalk>ansi-styles>color-convert>color-name": true - } - }, "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-function-name": { "packages": { "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-function-name>@babel/template": true, @@ -1264,10 +1243,17 @@ "process.emitWarning": true }, "packages": { - "lavamoat>@babel/highlight": true, + "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-function-name>@babel/template>@babel/code-frame>@babel/highlight": true, "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-function-name>@babel/template>@babel/code-frame>chalk": true } }, + "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-function-name>@babel/template>@babel/code-frame>@babel/highlight": { + "packages": { + "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-function-name>@babel/template>@babel/code-frame>chalk": true, + "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-function-name>@babel/types>@babel/helper-validator-identifier": true, + "react>loose-envify>js-tokens": true + } + }, "lavamoat>lavamoat-tofu>@babel/traverse>@babel/helper-function-name>@babel/template>@babel/code-frame>chalk": { "globals": { "process.env.TERM": true, diff --git a/src/core/firebase/remoteConfig.ts b/src/core/firebase/remoteConfig.ts index 31058b5a26..320cfc2693 100644 --- a/src/core/firebase/remoteConfig.ts +++ b/src/core/firebase/remoteConfig.ts @@ -42,6 +42,7 @@ export interface RainbowConfig extends Record { [ChainName.avalanche]: number; [ChainName.blast]: number; [ChainName.degen]: number; + [ChainName.apechain]: number; }; } @@ -69,6 +70,7 @@ const DEFAULT_CONFIG = { avalanche: 200, blast: 200, degen: 200, + apechain: 200, }, }; diff --git a/src/core/references/assets.ts b/src/core/references/assets.ts index 5e4a30d7e5..b21b742d2e 100644 --- a/src/core/references/assets.ts +++ b/src/core/references/assets.ts @@ -15,6 +15,7 @@ import { coreDao, cronos, cronosTestnet, + curtis, degen, dogechain, fantom, @@ -76,6 +77,8 @@ import { import { ChainId } from '../types/chains'; export const customChainIdsToAssetNames: Record = { + 33139: 'apechain', + [curtis.id]: 'apechaincurtis', [arbitrumNova.id]: 'arbitrumnova', [aurora.id]: 'aurora', [auroraTestnet.id]: 'auroratestnet', diff --git a/src/core/references/index.ts b/src/core/references/index.ts index 1d2ae26e93..4563d7391d 100644 --- a/src/core/references/index.ts +++ b/src/core/references/index.ts @@ -52,6 +52,12 @@ export const POL_MAINNET_ADDRESS = '0x455e53CBB86018Ac2B8092FdCd39d8444aFFC3F6'; export const BNB_MAINNET_ADDRESS = '0xb8c77482e45f1f44de1745f52c74426c631bdd52'; export const SOCKS_ADDRESS = '0x23b608675a2b2fb1890d3abbd85c5775c51691d5'; export const WETH_ADDRESS = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2'; +export const APECOIN_MAINNET_ADDRESS = + '0x4d224452801aced8b2f0aebe155379bb5d594381'; + +// arbitrum +export const APECOIN_ARBITRUM_ADDRESS = + '0x7f9FBf9bDd3F4105C478b996B648FE6e828a1e98'; // optimism export const ETH_OPTIMISM_ADDRESS = AddressZero; @@ -120,6 +126,12 @@ export const USDB_BLAST_ADDRESS = '0x4300000000000000000000000000000000000003'; // degen export const DEGEN_DEGEN_ADDRESS = AddressZero; +// apechain +export const APE_APECHAIN_ADDRESS = AddressZero; +export const WAPE_APECHAIN_ADDRESS = '0x48b62137edfa95a428d35c09e44256a739f6b557'; +export const APEETH_APECHAIN_ADDRESS = '0xcF800F4948D16F23333508191B1B1591daF70438'; +export const APEUSD_APECHAIN_ADDRESS = '0xA2235d059F80e176D931Ef76b6C51953Eb3fBEf4'; + export const OVM_GAS_PRICE_ORACLE = '0x420000000000000000000000000000000000000F'; diff --git a/src/core/state/favorites/index.ts b/src/core/state/favorites/index.ts index dd3b84bf5d..6a87d6a99e 100644 --- a/src/core/state/favorites/index.ts +++ b/src/core/state/favorites/index.ts @@ -1,6 +1,9 @@ import create from 'zustand'; import { + APE_APECHAIN_ADDRESS, + APEETH_APECHAIN_ADDRESS, + APEUSD_APECHAIN_ADDRESS, AVAX_AVALANCHE_ADDRESS, BNB_BSC_ADDRESS, DAI_ADDRESS, @@ -29,6 +32,7 @@ import { USDC_BSC_ADDRESS, USDC_OPTIMISM_ADDRESS, USDC_POLYGON_ADDRESS, + WAPE_APECHAIN_ADDRESS, WAVAX_AVALANCHE_ADDRESS, WBTC_ADDRESS, WBTC_ARBITRUM_ADDRESS, @@ -107,6 +111,12 @@ const defaultFavorites = { ], [ChainId.blast]: [ETH_BLAST_ADDRESS, WETH_BLAST_ADDRESS, USDB_BLAST_ADDRESS], [ChainId.degen]: [DEGEN_DEGEN_ADDRESS], + [ChainId.apechain]: [ + APE_APECHAIN_ADDRESS, + WAPE_APECHAIN_ADDRESS, + APEETH_APECHAIN_ADDRESS, + APEUSD_APECHAIN_ADDRESS, + ], } satisfies FavoritesState['favorites']; const mergeNewOfficiallySupportedChainsState = ( @@ -151,7 +161,7 @@ export const favoritesStore = createStore( { persist: persistOptions({ name: 'favorites', - version: 4, + version: 5, migrations: [ // version 1 didn't need a migration (state: FavoritesState) => state, @@ -164,6 +174,9 @@ export const favoritesStore = createStore( // version 4 added degen (state) => mergeNewOfficiallySupportedChainsState(state, [ChainId.degen]), + // version 5 added apechain + (state) => + mergeNewOfficiallySupportedChainsState(state, [ChainId.apechain]), ], }), }, diff --git a/src/core/types/chains.ts b/src/core/types/chains.ts index f416f0874e..8b4ef81ef4 100644 --- a/src/core/types/chains.ts +++ b/src/core/types/chains.ts @@ -35,6 +35,8 @@ export const chainHardhatOptimism: Chain = { }; export enum ChainName { + apechain = 'apechain', + apechainCurtis = 'apechain-curtis', arbitrum = 'arbitrum', arbitrumNova = 'arbitrum-nova', arbitrumSepolia = 'arbitrum-sepolia', @@ -84,6 +86,8 @@ export enum ChainName { } export enum ChainId { + apechain = 33139, + apechainCurtis = chains.curtis.id, arbitrum = chains.arbitrum.id, arbitrumNova = chains.arbitrumNova.id, arbitrumSepolia = chains.arbitrumSepolia.id, @@ -137,6 +141,8 @@ export const chainNameToIdMapping: { } = { ['ethereum']: ChainId.mainnet, ['ethereum-sepolia']: ChainId.sepolia, + [ChainName.apechain]: ChainId.apechain, + [ChainName.apechainCurtis]: ChainId.apechainCurtis, [ChainName.arbitrum]: ChainId.arbitrum, [ChainName.arbitrumNova]: ChainId.arbitrumNova, [ChainName.arbitrumSepolia]: ChainId.arbitrumSepolia, @@ -188,6 +194,8 @@ export const chainNameToIdMapping: { export const chainIdToNameMapping: { [key in ChainId]: ChainName; } = { + [ChainId.apechain]: ChainName.apechain, + [ChainId.apechainCurtis]: ChainName.apechainCurtis, [ChainId.arbitrum]: ChainName.arbitrum, [ChainId.arbitrumNova]: ChainName.arbitrumNova, [ChainId.arbitrumSepolia]: ChainName.arbitrumSepolia, diff --git a/src/core/types/nfts.ts b/src/core/types/nfts.ts index 839d36dbc4..ce683399c2 100644 --- a/src/core/types/nfts.ts +++ b/src/core/types/nfts.ts @@ -9,6 +9,7 @@ export interface PolygonAllowListDictionary { export enum SimpleHashChain { AlignTestnet = 'align-testnet', AnomalyAndromedaTestnet = 'anomaly-andromeda-testnet', + Apechain = 'apechain', Arbitrum = 'arbitrum', ArbitrumNova = 'arbitrum-nova', ArbitrumSepolia = 'arbitrum-sepolia', diff --git a/src/core/utils/faucets.ts b/src/core/utils/faucets.ts index 6f6782425b..ac793b64bb 100644 --- a/src/core/utils/faucets.ts +++ b/src/core/utils/faucets.ts @@ -30,6 +30,7 @@ export const TestnetFaucet = { [ChainId.avalancheFuji]: 'https://faucet.quicknode.com/avalanche/fuji', [ChainId.blastSepolia]: 'https://faucet.quicknode.com/blast/sepolia', [ChainId.polygonAmoy]: 'https://faucet.polygon.technology', + [ChainId.apechainCurtis]: 'https://curtis.hub.caldera.xyz/', [celoAlfajores.id]: 'https://faucet.celo.org/alfajores', [fantomTestnet.id]: 'https://faucet.fantom.network', [filecoinCalibration.id]: 'https://beryx.io/faucet', diff --git a/src/core/utils/nfts.ts b/src/core/utils/nfts.ts index b0647e79ca..0736715773 100644 --- a/src/core/utils/nfts.ts +++ b/src/core/utils/nfts.ts @@ -19,6 +19,7 @@ export const ENS_NFT_CONTRACT_ADDRESS = export const simpleHashSupportedChainNames = [ 'ethereum', + ChainName.apechain, ChainName.arbitrum, ChainName.arbitrumNova, ChainName.avalanche, @@ -91,6 +92,8 @@ export function getNetworkFromSimpleHashChain( chain: SimpleHashChain, ): ChainName { switch (chain) { + case SimpleHashChain.Apechain: + return ChainName.apechain; case SimpleHashChain.Avalanche: return ChainName.avalanche; case SimpleHashChain.AvalancheFuji: diff --git a/src/core/utils/userChains.ts b/src/core/utils/userChains.ts index 57a87348ff..497c135b34 100644 --- a/src/core/utils/userChains.ts +++ b/src/core/utils/userChains.ts @@ -10,6 +10,7 @@ import { blastSepolia, bsc, bscTestnet, + curtis, degen, holesky, mainnet, @@ -37,7 +38,8 @@ export const chainIdMap: Record< | ChainId.zora | ChainId.avalanche | ChainId.blast - | ChainId.degen, + | ChainId.degen + | ChainId.apechain, ChainId[] > = { [ChainId.mainnet]: [mainnet.id, sepolia.id, holesky.id], @@ -50,6 +52,7 @@ export const chainIdMap: Record< [ChainId.avalanche]: [avalanche.id, avalancheFuji.id], [ChainId.blast]: [blast.id, blastSepolia.id], [ChainId.degen]: [degen.id], + [ChainId.apechain]: [ChainId.apechain, curtis.id], }; export const chainLabelMap: Record< @@ -61,7 +64,8 @@ export const chainLabelMap: Record< | ChainId.zora | ChainId.avalanche | ChainId.blast - | ChainId.degen, + | ChainId.degen + | ChainId.apechain, string[] > = { [ChainId.mainnet]: [chainsLabel[sepolia.id], chainsLabel[holesky.id]], @@ -74,6 +78,7 @@ export const chainLabelMap: Record< [ChainId.avalanche]: [chainsLabel[avalancheFuji.id]], [ChainId.blast]: [chainsLabel[blastSepolia.id]], [ChainId.degen]: [], + [ChainId.apechain]: [chainsLabel[curtis.id]], }; export const sortNetworks = (order: ChainId[], chains: Chain[]) => { diff --git a/src/entries/iframe/notification.tsx b/src/entries/iframe/notification.tsx index 91274a2b0c..698e4c2a0b 100644 --- a/src/entries/iframe/notification.tsx +++ b/src/entries/iframe/notification.tsx @@ -45,6 +45,8 @@ const ASSET_SOURCE = { [ChainId.avalancheFuji]: 'assets/badges/avalancheBadge@3x.png', [ChainId.blast]: 'assets/badges/blastBadge@3x.png', [ChainId.blastSepolia]: 'assets/badges/blastBadge@3x.png', + [ChainId.apechain]: 'assets/badges/apechainBadge@3x.png', + [ChainId.apechainCurtis]: 'assets/badges/apechainBadge@3x.png', }; export enum IN_DAPP_NOTIFICATION_STATUS { diff --git a/src/entries/popup/components/ChainBadge/ChainBadge.tsx b/src/entries/popup/components/ChainBadge/ChainBadge.tsx index 5717e9993b..bbfa93c43b 100644 --- a/src/entries/popup/components/ChainBadge/ChainBadge.tsx +++ b/src/entries/popup/components/ChainBadge/ChainBadge.tsx @@ -1,5 +1,6 @@ import { AddressZero } from '@ethersproject/constants'; +import ApeChainBadge from 'static/assets/badges/apechainBadge@3x.png'; import ArbitrumBadge from 'static/assets/badges/arbitrumBadge@3x.png'; import AvalancheBadge from 'static/assets/badges/avalancheBadge@3x.png'; import BaseBadge from 'static/assets/badges/baseBadge@3x.png'; @@ -62,6 +63,8 @@ const networkBadges = { [ChainId.blast]: BlastBadge, [ChainId.blastSepolia]: BlastBadge, [ChainId.degen]: DegenBadge, + [ChainId.apechain]: ApeChainBadge, + [ChainId.apechainCurtis]: ApeChainBadge, }; const ChainBadge = ({ diff --git a/src/entries/popup/components/SideChainExplainer.tsx b/src/entries/popup/components/SideChainExplainer.tsx index 0b1050f80a..937960d32e 100644 --- a/src/entries/popup/components/SideChainExplainer.tsx +++ b/src/entries/popup/components/SideChainExplainer.tsx @@ -28,6 +28,7 @@ export const getSideChainExplainerParams = ( [ChainId.avalanche]: 'avalanche', [ChainId.blast]: 'blast', [ChainId.degen]: 'degen', + [ChainId.apechain]: 'apechain', // add new chains here with unique i18n explainer keys }; diff --git a/src/entries/popup/hooks/swap/useSwapSettings.ts b/src/entries/popup/hooks/swap/useSwapSettings.ts index 4882fd2cc2..c5ce553618 100644 --- a/src/entries/popup/hooks/swap/useSwapSettings.ts +++ b/src/entries/popup/hooks/swap/useSwapSettings.ts @@ -18,6 +18,7 @@ export const DEFAULT_SLIPPAGE_BIPS = { [ChainId.avalanche]: 200, [ChainId.blast]: 200, [ChainId.degen]: 200, + [ChainId.apechain]: 200, }; export const DEFAULT_SLIPPAGE = { @@ -31,6 +32,7 @@ export const DEFAULT_SLIPPAGE = { [ChainId.avalanche]: '2', [ChainId.blast]: '2', [ChainId.degen]: '2', + [ChainId.apechain]: '2', }; const slippageInBipsToString = (slippageInBips: number) => @@ -47,7 +49,8 @@ export const getDefaultSlippage = (chainId: ChainId) => { | ChainName.bsc | ChainName.avalanche | ChainName.blast - | ChainName.degen; + | ChainName.degen + | ChainName.apechain; return slippageInBipsToString( config.default_slippage_bips[chainName] || DEFAULT_SLIPPAGE_BIPS[chainId], ); diff --git a/src/entries/popup/hooks/useSearchCurrencyLists.ts b/src/entries/popup/hooks/useSearchCurrencyLists.ts index 515820f4db..23765e61e6 100644 --- a/src/entries/popup/hooks/useSearchCurrencyLists.ts +++ b/src/entries/popup/hooks/useSearchCurrencyLists.ts @@ -212,6 +212,15 @@ export function useSearchCurrencyLists({ fromChainId, }); + const { + data: apechainVerifiedAssets, + isLoading: apechainVerifiedAssetsLoading, + } = useTokenSearch({ + chainId: ChainId.apechain, + ...VERIFIED_ASSETS_PAYLOAD, + fromChainId, + }); + // current search const { data: targetVerifiedAssets, isLoading: targetVerifiedAssetsLoading } = useTokenSearch( @@ -367,6 +376,10 @@ export function useSearchCurrencyLists({ assets: degenVerifiedAssets, loading: degenVerifiedAssetsLoading, }, + [ChainId.apechain]: { + assets: apechainVerifiedAssets, + loading: apechainVerifiedAssetsLoading, + }, }), [ mainnetVerifiedAssets, @@ -389,6 +402,8 @@ export function useSearchCurrencyLists({ blastVerifiedAssetsLoading, degenVerifiedAssets, degenVerifiedAssetsLoading, + apechainVerifiedAssets, + apechainVerifiedAssetsLoading, ], ); @@ -445,6 +460,7 @@ export function useSearchCurrencyLists({ [ChainId.avalanche]: getVerifiedAssets(ChainId.avalanche), [ChainId.blast]: getVerifiedAssets(ChainId.blast), [ChainId.degen]: getVerifiedAssets(ChainId.degen), + [ChainId.apechain]: getVerifiedAssets(ChainId.apechain), }), [getVerifiedAssets], ); diff --git a/static/assets/badges/apechainBadge.png b/static/assets/badges/apechainBadge.png new file mode 100644 index 0000000000000000000000000000000000000000..e9c29a1987dc7c593241b07ad699fdbd38e649ce GIT binary patch literal 657 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