diff --git a/.env.defaults b/.env.defaults index 3e598677ce..e3a8eb39c2 100644 --- a/.env.defaults +++ b/.env.defaults @@ -23,6 +23,7 @@ SUPPORT_TABBED_ONBOARDING=false SUPPORT_KEYRING_LOCKING=true SUPPORT_FORGOT_PASSWORD=false SUPPORT_AVALANCHE=true +SUPPORT_BINANCE_SMART_CHAIN=false SUPPORT_ARBITRUM_NOVA=false ENABLE_ACHIEVEMENTS_TAB=true SUPPORT_ACHIEVEMENTS_BANNER=false diff --git a/background/constants/coin-types.ts b/background/constants/coin-types.ts index aa1932e1e9..2352b5c554 100644 --- a/background/constants/coin-types.ts +++ b/background/constants/coin-types.ts @@ -11,6 +11,7 @@ export const coinTypesByAssetSymbol = { RBTC: 137, MATIC: 966, AVAX: 9005, + BNB: 714, } as const /** diff --git a/background/constants/currencies.ts b/background/constants/currencies.ts index 11c99d0203..9d7ea3e6bc 100644 --- a/background/constants/currencies.ts +++ b/background/constants/currencies.ts @@ -75,6 +75,18 @@ export const AVAX: NetworkBaseAsset = { }, } +export const BNB: NetworkBaseAsset = { + name: "Binance Coin", + symbol: "BNB", + decimals: 18, + coinType: coinTypesByAssetSymbol.BNB, + metadata: { + coinGeckoID: "binancecoin", + tokenLists: [], + websiteURL: "https://bnbchain.org", + }, +} + export const BTC: NetworkBaseAsset = { name: "Bitcoin", symbol: "BTC", @@ -87,7 +99,7 @@ export const BTC: NetworkBaseAsset = { }, } -export const BASE_ASSETS = [ETH, BTC, MATIC, RBTC, OPTIMISTIC_ETH, AVAX] +export const BASE_ASSETS = [ETH, BTC, MATIC, RBTC, OPTIMISTIC_ETH, AVAX, BNB] export const BASE_ASSETS_BY_SYMBOL = BASE_ASSETS.reduce<{ [assetSymbol: string]: NetworkBaseAsset diff --git a/background/constants/networks.ts b/background/constants/networks.ts index f36f21edd9..f119029d64 100644 --- a/background/constants/networks.ts +++ b/background/constants/networks.ts @@ -1,5 +1,5 @@ import { EVMNetwork, Network } from "../networks" -import { AVAX, BTC, ETH, MATIC, OPTIMISTIC_ETH, RBTC } from "./currencies" +import { AVAX, BNB, BTC, ETH, MATIC, OPTIMISTIC_ETH, RBTC } from "./currencies" export const ETHEREUM: EVMNetwork = { name: "Ethereum", @@ -41,6 +41,14 @@ export const AVALANCHE: EVMNetwork = { coingeckoPlatformID: "avalanche", } +export const BINANCE_SMART_CHAIN: EVMNetwork = { + name: "BNB Chain", + baseAsset: BNB, + chainID: "56", + family: "EVM", + coingeckoPlatformID: "binance-smart-chain", +} + export const ARBITRUM_NOVA: EVMNetwork = { name: "Arbitrum Nova", baseAsset: ETH, @@ -85,7 +93,9 @@ export const EIP_1559_COMPLIANT_CHAIN_IDS = new Set( ) export const CHAINS_WITH_MEMPOOL = new Set( - [ETHEREUM, POLYGON, AVALANCHE, GOERLI].map((network) => network.chainID) + [ETHEREUM, POLYGON, AVALANCHE, GOERLI, BINANCE_SMART_CHAIN].map( + (network) => network.chainID + ) ) export const NETWORK_BY_CHAIN_ID = { @@ -96,6 +106,7 @@ export const NETWORK_BY_CHAIN_ID = { [AVALANCHE.chainID]: AVALANCHE, [ARBITRUM_NOVA.chainID]: ARBITRUM_NOVA, [OPTIMISM.chainID]: OPTIMISM, + [BINANCE_SMART_CHAIN.chainID]: BINANCE_SMART_CHAIN, [GOERLI.chainID]: GOERLI, [FORK.chainID]: FORK, } @@ -116,6 +127,7 @@ export const CHAIN_ID_TO_0X_API_BASE: { [GOERLI.chainID]: "goerli.api.0x.org", [ARBITRUM_ONE.chainID]: "arbitrum.api.0x.org", [AVALANCHE.chainID]: "avalanche.api.0x.org", + [BINANCE_SMART_CHAIN.chainID]: "bsc.api.0x.org", } export const NETWORKS_SUPPORTING_SWAPS = new Set( @@ -145,6 +157,10 @@ export const CHAIN_ID_TO_RPC_URLS: { "https://api.avax.network/ext/bc/C/rpc", "https://rpc.ankr.com/avalanche", ], + [BINANCE_SMART_CHAIN.chainID]: [ + "https://rpc.ankr.com/bsc", + "https://bsc-dataseed.binance.org", + ], } /** diff --git a/background/features.ts b/background/features.ts index 5cf207525f..3e3514f9b7 100644 --- a/background/features.ts +++ b/background/features.ts @@ -28,6 +28,8 @@ export const RuntimeFlag = { HIDE_TOKEN_FEATURES: process.env.HIDE_TOKEN_FEATURES === "true", SUPPORT_RSK: process.env.SUPPORT_RSK === "true", SUPPORT_AVALANCHE: process.env.SUPPORT_AVALANCHE === "true", + SUPPORT_BINANCE_SMART_CHAIN: + process.env.SUPPORT_BINANCE_SMART_CHAIN === "true", SUPPORT_ARBITRUM_NOVA: process.env.SUPPORT_ARBITRUM_NOVA === "true", SUPPORT_ACHIEVEMENTS_BANNER: process.env.SUPPORT_ACHIEVEMENTS_BANNER === "true", diff --git a/background/redux-slices/accounts.ts b/background/redux-slices/accounts.ts index eaede75d25..e3a7935844 100644 --- a/background/redux-slices/accounts.ts +++ b/background/redux-slices/accounts.ts @@ -2,7 +2,12 @@ import { createSlice } from "@reduxjs/toolkit" import { createBackgroundAsyncThunk } from "./utils" import { AccountBalance, AddressOnNetwork, NameOnNetwork } from "../accounts" import { EVMNetwork, Network } from "../networks" -import { AnyAsset, AnyAssetAmount, SmartContractFungibleAsset } from "../assets" +import { + AnyAsset, + AnyAssetAmount, + isFungibleAsset, + SmartContractFungibleAsset, +} from "../assets" import { AssetMainCurrencyAmount, AssetDecimalAmount, @@ -11,6 +16,7 @@ import { DomainName, HexString, URI } from "../types" import { normalizeEVMAddress } from "../lib/utils" import { AccountSigner } from "../services/signing" import { TEST_NETWORK_BY_CHAIN_ID } from "../constants" +import { convertFixedPoint } from "../lib/fixed-point" /** * The set of available UI account types. These may or may not map 1-to-1 to @@ -172,9 +178,28 @@ function updateCombinedData(immerState: AccountState) { [symbol: string]: AnyAssetAmount }>((acc, combinedAssetAmount) => { const assetSymbol = combinedAssetAmount.asset.symbol - acc[assetSymbol] = { - ...combinedAssetAmount, - amount: (acc[assetSymbol]?.amount || 0n) + combinedAssetAmount.amount, + let { amount } = combinedAssetAmount + + if (acc[assetSymbol]?.asset) { + const accAsset = acc[assetSymbol].asset + const existingDecimals = isFungibleAsset(accAsset) + ? accAsset.decimals + : 0 + const newDecimals = isFungibleAsset(combinedAssetAmount.asset) + ? combinedAssetAmount.asset.decimals + : 0 + + if (newDecimals !== existingDecimals) { + amount = convertFixedPoint(amount, newDecimals, existingDecimals) + } + } + + if (acc[assetSymbol]) { + acc[assetSymbol].amount += amount + } else { + acc[assetSymbol] = { + ...combinedAssetAmount, + } } return acc }, {}) diff --git a/background/redux-slices/assets.ts b/background/redux-slices/assets.ts index caf706097f..f49e0073d0 100644 --- a/background/redux-slices/assets.ts +++ b/background/redux-slices/assets.ts @@ -110,6 +110,13 @@ const selectPairedAssetSymbol = ( pairedAssetSymbol: string ) => pairedAssetSymbol +const selectChainID = ( + _: AssetsState, + _2: string, + _3: string, + chainID: string +) => chainID + /** * Executes an asset transfer between two addresses, for a set amount. Supports * an optional fixed gas limit. @@ -187,9 +194,21 @@ export const transferAsset = createBackgroundAsyncThunk( * the selector will return them in the order [ETH, USD]. */ export const selectAssetPricePoint = createSelector( - [selectAssetsState, selectAssetSymbol, selectPairedAssetSymbol], - (assets, assetSymbol, pairedAssetSymbol) => { - const pricedAsset = assets.find( + [ + selectAssetsState, + selectAssetSymbol, + selectPairedAssetSymbol, + selectChainID, + ], + (assets, assetSymbol, pairedAssetSymbol, chainID) => { + const pricedAsset = ( + chainID + ? assets.filter( + (asset) => + "homeNetwork" in asset && asset.homeNetwork.chainID === chainID + ) + : assets + ).find( (asset) => asset.symbol === assetSymbol && pairedAssetSymbol in asset.recentPrices && diff --git a/background/redux-slices/dapp.ts b/background/redux-slices/dapp.ts index 66cd084b3e..65d24b942a 100644 --- a/background/redux-slices/dapp.ts +++ b/background/redux-slices/dapp.ts @@ -6,6 +6,7 @@ import { keyPermissionsByChainIdAddressOrigin } from "../services/provider-bridg import { ARBITRUM_NOVA, AVALANCHE, + BINANCE_SMART_CHAIN, ETHEREUM, GOERLI, OPTIMISM, @@ -132,6 +133,7 @@ const dappSlice = createSlice({ POLYGON, OPTIMISM, AVALANCHE, + BINANCE_SMART_CHAIN, GOERLI, ARBITRUM_NOVA, ].map((network) => ({ diff --git a/background/redux-slices/selectors/accountsSelectors.ts b/background/redux-slices/selectors/accountsSelectors.ts index e46d1c0821..6f9e696001 100644 --- a/background/redux-slices/selectors/accountsSelectors.ts +++ b/background/redux-slices/selectors/accountsSelectors.ts @@ -82,7 +82,8 @@ const computeCombinedAssetAmountsData = ( assets: AssetsState, mainCurrencySymbol: string, currentNetwork: EVMNetwork, - hideDust: boolean + hideDust: boolean, + useCurrentNetwork = false ): { combinedAssetAmounts: CompleteAssetAmount[] totalMainCurrencyAmount: number | undefined @@ -98,7 +99,8 @@ const computeCombinedAssetAmountsData = ( const assetPricePoint = selectAssetPricePoint( assets, assetAmount.asset.symbol, - mainCurrencySymbol + mainCurrencySymbol, + useCurrentNetwork ? currentNetwork.chainID : undefined ) const mainCurrencyEnrichedAssetAmount = @@ -260,7 +262,8 @@ export const selectCurrentAccountBalances = createSelector( assets, mainCurrencySymbol, currentNetwork, - hideDust + hideDust, + true ) return { @@ -319,14 +322,16 @@ const getAccountType = ( const getTotalBalance = ( accountBalances: { [assetSymbol: string]: AccountBalance }, assets: AssetsState, - mainCurrencySymbol: string + mainCurrencySymbol: string, + chainID: string ) => { return Object.values(accountBalances) .map(({ assetAmount }) => { const assetPricePoint = selectAssetPricePoint( assets, assetAmount.asset.symbol, - mainCurrencySymbol + mainCurrencySymbol, + chainID ) if (typeof assetPricePoint === "undefined") { @@ -397,7 +402,12 @@ function getNetworkAccountTotalsByCategory( avatarURL: accountData.ens.avatarURL ?? accountData.defaultAvatar, localizedTotalMainCurrencyAmount: formatCurrencyAmount( mainCurrencySymbol, - getTotalBalance(accountData.balances, assets, mainCurrencySymbol), + getTotalBalance( + accountData.balances, + assets, + mainCurrencySymbol, + network.chainID + ), desiredDecimals.default ), } @@ -469,7 +479,8 @@ export const selectAccountTotalsForOverview = createSelector( accountsTotal[normalizedAddress].totals[chainID] = getTotalBalance( accountData.balances, assetsState, - mainCurrencySymbol + mainCurrencySymbol, + chainID ) }) ) diff --git a/background/redux-slices/utils/0x-swap-utils.ts b/background/redux-slices/utils/0x-swap-utils.ts index ee53ea2c4a..0579915e90 100644 --- a/background/redux-slices/utils/0x-swap-utils.ts +++ b/background/redux-slices/utils/0x-swap-utils.ts @@ -97,7 +97,8 @@ export async function getAssetAmount( const assetPricePoint = selectAssetPricePoint( assets, asset?.symbol, - hardcodedMainCurrencySymbol + hardcodedMainCurrencySymbol, + network.chainID ) return enrichAssetAmountWithMainCurrencyValues( diff --git a/background/services/chain/index.ts b/background/services/chain/index.ts index fd590968ec..1b3d1d560c 100644 --- a/background/services/chain/index.ts +++ b/background/services/chain/index.ts @@ -31,6 +31,7 @@ import { CHAINS_WITH_MEMPOOL, EIP_1559_COMPLIANT_CHAIN_IDS, AVALANCHE, + BINANCE_SMART_CHAIN, ARBITRUM_NOVA, } from "../../constants" import { FeatureFlags, isEnabled } from "../../features" @@ -273,6 +274,9 @@ export default class ChainService extends BaseService { ARBITRUM_ONE, ...(isEnabled(FeatureFlags.SUPPORT_RSK) ? [ROOTSTOCK] : []), ...(isEnabled(FeatureFlags.SUPPORT_AVALANCHE) ? [AVALANCHE] : []), + ...(isEnabled(FeatureFlags.SUPPORT_BINANCE_SMART_CHAIN) + ? [BINANCE_SMART_CHAIN] + : []), ...(isEnabled(FeatureFlags.SUPPORT_ARBITRUM_NOVA) ? [ARBITRUM_NOVA] : []), ] diff --git a/background/services/preferences/db.ts b/background/services/preferences/db.ts index 46482ced1f..c65b1127bb 100644 --- a/background/services/preferences/db.ts +++ b/background/services/preferences/db.ts @@ -257,6 +257,17 @@ export class PreferenceDatabase extends Dexie { }) }) + this.version(13).upgrade((tx) => { + return tx + .table("preferences") + .toCollection() + .modify((storedPreferences: Preferences) => { + storedPreferences.tokenLists.urls.push( + "https://tokens.pancakeswap.finance/pancakeswap-default.json" + ) + }) + }) + // This is the old version for populate // https://dexie.org/docs/Dexie/Dexie.on.populate-(old-version) // The this does not behave according the new docs, but works diff --git a/background/services/preferences/defaults.ts b/background/services/preferences/defaults.ts index cfee7d433b..473a084d75 100644 --- a/background/services/preferences/defaults.ts +++ b/background/services/preferences/defaults.ts @@ -21,6 +21,7 @@ const defaultPreferences: Preferences = { "https://static.optimism.io/optimism.tokenlist.json", // Optimism Default Tokens "https://bridge.arbitrum.io/token-list-42161.json", // Arbitrum Default tokens "https://raw.githubusercontent.com/traderjoe-xyz/joe-tokenlists/main/src/joe.tokenlist-v2.json", // Trader Joe tokens + "https://tokens.pancakeswap.finance/pancakeswap-default.json", // PancakeSwap Default List ], }, currency: USD, diff --git a/ui/_locales/en/messages.json b/ui/_locales/en/messages.json index 19cd9b7ac9..3051bfd3ed 100644 --- a/ui/_locales/en/messages.json +++ b/ui/_locales/en/messages.json @@ -642,6 +642,7 @@ "HIDE_TOKEN_FEATURES": "Hide token features", "SUPPORT_ACHIEVEMENTS_BANNER": "Enable achievements banner", "SUPPORT_AVALANCHE": "Enable Avalanche network", + "SUPPORT_BINANCE_SMART_CHAIN": "Enable Binance Smart Chain network", "SUPPORT_ARBITRUM_NOVA": "Enable Arbitrum Nova network", "SUPPORT_NFT_TAB": "Enable to open NFTs page from tab" } diff --git a/ui/components/Overview/NetworksChart.tsx b/ui/components/Overview/NetworksChart.tsx index f2ce9e5077..d484544a8f 100644 --- a/ui/components/Overview/NetworksChart.tsx +++ b/ui/components/Overview/NetworksChart.tsx @@ -5,6 +5,7 @@ import { OPTIMISM, NETWORK_BY_CHAIN_ID, POLYGON, + BINANCE_SMART_CHAIN, } from "@tallyho/tally-background/constants" import { FeatureFlags, isEnabled } from "@tallyho/tally-background/features" import { AccountTotalList } from "@tallyho/tally-background/redux-slices/selectors" @@ -18,6 +19,7 @@ const NETWORKS_CHART_COLORS = { [ARBITRUM_ONE.chainID]: "#2083C5", [OPTIMISM.chainID]: "#CD041C", [AVALANCHE.chainID]: "#E84142", + [BINANCE_SMART_CHAIN.chainID]: "#F3BA2F", } const getNetworksPercents = ( diff --git a/ui/components/Shared/SharedAssetIcon.tsx b/ui/components/Shared/SharedAssetIcon.tsx index 45a65e9009..307b5e2421 100644 --- a/ui/components/Shared/SharedAssetIcon.tsx +++ b/ui/components/Shared/SharedAssetIcon.tsx @@ -8,7 +8,7 @@ interface Props { symbol: string } -const hardcodedIcons = new Set(["ETH", "MATIC", "DOGGO", "RBTC", "AVAX"]) +const hardcodedIcons = new Set(["ETH", "MATIC", "DOGGO", "RBTC", "AVAX", "BNB"]) export default function SharedAssetIcon(props: Props): ReactElement { const { size, logoURL, symbol } = props diff --git a/ui/components/TopMenu/TopMenuProtocolList.tsx b/ui/components/TopMenu/TopMenuProtocolList.tsx index 59e4f45bdd..0d084296f1 100644 --- a/ui/components/TopMenu/TopMenuProtocolList.tsx +++ b/ui/components/TopMenu/TopMenuProtocolList.tsx @@ -3,6 +3,7 @@ import { ARBITRUM_NOVA, ARBITRUM_ONE, AVALANCHE, + BINANCE_SMART_CHAIN, ETHEREUM, GOERLI, OPTIMISM, @@ -58,7 +59,20 @@ const productionNetworks = [ isDisabled: true, }, ]), - + ...(isEnabled(FeatureFlags.SUPPORT_BINANCE_SMART_CHAIN) + ? [ + { + network: BINANCE_SMART_CHAIN, + info: i18n.t("protocol.compatibleChain"), + }, + ] + : [ + { + network: BINANCE_SMART_CHAIN, + info: i18n.t("comingSoon"), + isDisabled: true, + }, + ]), ...(isEnabled(FeatureFlags.SUPPORT_ARBITRUM_NOVA) ? [ { @@ -73,13 +87,6 @@ const productionNetworks = [ isDisabled: true, }, ]), - - // { - // name: "Binance Smart Chain", - // info: i18n.t("protocol.compatibleChain"), - // width: 24, - // height: 24, - // }, // { // name: "Celo", // info: "Global payments infrastructure", diff --git a/ui/components/TopMenu/TopMenuProtocolSwitcher.tsx b/ui/components/TopMenu/TopMenuProtocolSwitcher.tsx index 5ea711f5ac..45860711d1 100644 --- a/ui/components/TopMenu/TopMenuProtocolSwitcher.tsx +++ b/ui/components/TopMenu/TopMenuProtocolSwitcher.tsx @@ -24,6 +24,7 @@ export default function TopMenuProtocolSwitcher({ display: flex; align-items: center; user-select: none; + white-space: nowrap; } button:hover { color: #fff; diff --git a/ui/public/images/assets/bnb.png b/ui/public/images/assets/bnb.png new file mode 100644 index 0000000000..1dc1061f08 Binary files /dev/null and b/ui/public/images/assets/bnb.png differ diff --git a/ui/public/images/networks/binancesmartchain-square@2x.png b/ui/public/images/networks/bnbchain-square@2x.png similarity index 100% rename from ui/public/images/networks/binancesmartchain-square@2x.png rename to ui/public/images/networks/bnbchain-square@2x.png diff --git a/ui/public/images/networks/binancesmartchain@2x.png b/ui/public/images/networks/bnbchain@2x.png similarity index 100% rename from ui/public/images/networks/binancesmartchain@2x.png rename to ui/public/images/networks/bnbchain@2x.png diff --git a/ui/utils/constants.ts b/ui/utils/constants.ts index 1a442d926a..cc021aed0a 100644 --- a/ui/utils/constants.ts +++ b/ui/utils/constants.ts @@ -2,6 +2,7 @@ import { ARBITRUM_NOVA, ARBITRUM_ONE, AVALANCHE, + BINANCE_SMART_CHAIN, ETHEREUM, GOERLI, OPTIMISM, @@ -24,6 +25,10 @@ export const scanWebsite = { [GOERLI.chainID]: { title: "Etherscan", url: "https://goerli.etherscan.io/" }, [ARBITRUM_ONE.chainID]: { title: "Arbiscan", url: "https://arbiscan.io/" }, [AVALANCHE.chainID]: { title: "Snowtrace", url: "https://snowtrace.io/" }, + [BINANCE_SMART_CHAIN.chainID]: { + title: "BscScan", + url: "https://bscscan.com", + }, [ARBITRUM_NOVA.chainID]: { title: "Arbiscan", url: "https://nova.arbiscan.io/",