From d500dbcadac66abcca373d10f3a17c2e1da99c7e Mon Sep 17 00:00:00 2001 From: Sebastian Scatularo Date: Thu, 27 Jul 2023 10:17:47 -0300 Subject: [PATCH] #260 - add base support --- src/components/ShowTx.tsx | 5 ++ src/components/SmartAddress.tsx | 5 ++ src/components/TransactionProgress.tsx | 5 ++ src/hooks/useGetSourceParsedTokenAccounts.ts | 61 ++++++++++++++++++++ src/icons/base.svg | 3 + src/utils/consts.ts | 39 ++++++++++++- src/utils/metaMaskChainParameters.ts | 7 +++ 7 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/icons/base.svg diff --git a/src/components/ShowTx.tsx b/src/components/ShowTx.tsx index a02f121f8..2b14090d6 100644 --- a/src/components/ShowTx.tsx +++ b/src/components/ShowTx.tsx @@ -23,6 +23,7 @@ import { CHAIN_ID_INJECTIVE, CHAIN_ID_OPTIMISM, CHAIN_ID_SUI, + CHAIN_ID_BASE } from "@certusone/wormhole-sdk"; import { CHAIN_ID_NEAR } from "@certusone/wormhole-sdk/lib/esm"; import { Button, makeStyles, Typography } from "@material-ui/core"; @@ -135,6 +136,10 @@ export default function ShowTx({ ? `https://${ CLUSTER === "testnet" ? "moonbase." : "" }moonscan.io/tx/${tx?.id}` + : chainId === CHAIN_ID_BASE + ? `https://${CLUSTER === "testnet" ? "goerli." : ""}basescan.org/tx/${ + tx?.id + }` : chainId === CHAIN_ID_XPLA ? `https://explorer.xpla.io/${ CLUSTER === "testnet" ? "testnet" : "mainnet" diff --git a/src/components/SmartAddress.tsx b/src/components/SmartAddress.tsx index adce84996..9a5bda619 100644 --- a/src/components/SmartAddress.tsx +++ b/src/components/SmartAddress.tsx @@ -27,6 +27,7 @@ import { terra, CHAIN_ID_OPTIMISM, CHAIN_ID_SUI, + CHAIN_ID_BASE, } from "@certusone/wormhole-sdk"; import { Button, makeStyles, Tooltip, Typography } from "@material-ui/core"; import { FileCopy, OpenInNew } from "@material-ui/icons"; @@ -200,6 +201,10 @@ export default function SmartAddress({ ? `https://${CLUSTER === "testnet" ? "moonbase." : ""}moonscan.io/${ isAsset ? "token" : "address" }/${useableAddress}` + : chainId === CHAIN_ID_BASE + ? `https://${CLUSTER === "testnet" ? "goerli." : ""}basescan.org/${ + isAsset ? "token" : "address" + }/${useableAddress}` : chainId === CHAIN_ID_XPLA ? `https://explorer.xpla.io/${ CLUSTER === "testnet" ? "testnet" : "mainnet" diff --git a/src/components/TransactionProgress.tsx b/src/components/TransactionProgress.tsx index cee587007..8fca77324 100644 --- a/src/components/TransactionProgress.tsx +++ b/src/components/TransactionProgress.tsx @@ -14,6 +14,7 @@ import { CHAIN_ID_SOLANA, CHAIN_ID_OPTIMISM, isEVMChain, + CHAIN_ID_BASE, } from "@certusone/wormhole-sdk"; import { LinearProgress, makeStyles, Typography } from "@material-ui/core"; import { Connection } from "@solana/web3.js"; @@ -126,6 +127,8 @@ export default function TransactionProgress({ ? 32 : chainId === CHAIN_ID_ARBITRUM ? 64 + : chainId === CHAIN_ID_BASE + ? 124 // something to show progress : isEVMChain(chainId) ? 15 : 1; @@ -147,6 +150,8 @@ export default function TransactionProgress({ {chainId === CHAIN_ID_ARBITRUM ? `Waiting for Ethereum finality on Arbitrum block ${tx?.block}` //TODO: more advanced finality checking for Arbitrum + : chainId === CHAIN_ID_BASE + ? `Waiting for Ethereum finality on Base block ${tx?.block}` //TODO: more advanced finality checking for Base : chainId === CHAIN_ID_OPTIMISM ? `Waiting for Ethereum finality on Optimism block ${tx?.block}` : blockDiff < expectedBlocks diff --git a/src/hooks/useGetSourceParsedTokenAccounts.ts b/src/hooks/useGetSourceParsedTokenAccounts.ts index f7bf6d825..64093142e 100644 --- a/src/hooks/useGetSourceParsedTokenAccounts.ts +++ b/src/hooks/useGetSourceParsedTokenAccounts.ts @@ -26,6 +26,7 @@ import { CHAIN_ID_INJECTIVE, CHAIN_ID_SUI, CHAIN_ID_ARBITRUM, + CHAIN_ID_BASE, } from "@certusone/wormhole-sdk"; import { Dispatch } from "@reduxjs/toolkit"; import { TOKEN_PROGRAM_ID } from "@solana/spl-token"; @@ -56,6 +57,7 @@ import avaxIcon from "../icons/avax.svg"; import bnbIcon from "../icons/bnb.svg"; import celoIcon from "../icons/celo.svg"; import ethIcon from "../icons/eth.svg"; +import baseIcon from "../icons/base.svg"; import fantomIcon from "../icons/fantom.svg"; import karuraIcon from "../icons/karura.svg"; import klaytnIcon from "../icons/klaytn.svg"; @@ -134,6 +136,8 @@ import { SUI_NATIVE_TOKEN_KEY, ARBWETH_ADDRESS, ARBWETH_DECIMALS, + BASE_WETH_ADDRESS, + BASE_WETH_DECIMALS, } from "../utils/consts"; import { makeNearAccount } from "../utils/near"; import { @@ -291,6 +295,29 @@ const createNativeEthParsedTokenAccount = ( }); }; +const createNativeBaseParsedTokenAccount = ( + provider: Provider, + signerAddress: string | undefined +) => { + return !(provider && signerAddress) + ? Promise.reject() + : provider.getBalance(signerAddress).then((balanceInWei) => { + const balanceInEth = ethers.utils.formatEther(balanceInWei); + return createParsedTokenAccount( + signerAddress, //public key + BASE_WETH_ADDRESS, //Mint key, On the other side this will be WBNB, so this is hopefully a white lie. + balanceInWei.toString(), //amount, in wei + BASE_WETH_DECIMALS, //Luckily both BNB and WBNB have 18 decimals, so this should not be an issue. + parseFloat(balanceInEth), //This loses precision, but is a limitation of the current datamodel. This field is essentially deprecated + balanceInEth.toString(), //This is the actual display field, which has full precision. + "BASE", //A white lie for display purposes + "Base Coin", //A white lie for display purposes + baseIcon, + true //isNativeAsset + ); + }); +} + const createNativeBscParsedTokenAccount = ( provider: Provider, signerAddress: string | undefined @@ -1355,6 +1382,40 @@ function useGetAvailableTokens(nft: boolean = false) { }; }, [lookupChain, provider, signerAddress, nft, ethNativeAccount]); + //Base native asset load + useEffect(() => { + let cancelled = false; + if ( + signerAddress && + lookupChain === CHAIN_ID_BASE && + !ethNativeAccount && + !nft + ) { + setEthNativeAccountLoading(true); + createNativeBaseParsedTokenAccount(provider, signerAddress).then( + (result) => { + console.log("create native account returned with value", result); + if (!cancelled) { + setEthNativeAccount(result); + setEthNativeAccountLoading(false); + setEthNativeAccountError(""); + } + }, + (error) => { + if (!cancelled) { + setEthNativeAccount(undefined); + setEthNativeAccountLoading(false); + setEthNativeAccountError("Unable to retrieve your ETH balance."); + } + } + ); + } + + return () => { + cancelled = true; + }; + }, [lookupChain, provider, signerAddress, nft, ethNativeAccount]); + //Binance Smart Chain native asset load useEffect(() => { let cancelled = false; diff --git a/src/icons/base.svg b/src/icons/base.svg new file mode 100644 index 000000000..22d89358f --- /dev/null +++ b/src/icons/base.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/utils/consts.ts b/src/utils/consts.ts index 960ea03e3..4ee02d9a9 100644 --- a/src/utils/consts.ts +++ b/src/utils/consts.ts @@ -6,6 +6,7 @@ import { CHAIN_ID_ARBITRUM, CHAIN_ID_AURORA, CHAIN_ID_AVAX, + CHAIN_ID_BASE, CHAIN_ID_BSC, CHAIN_ID_BTC, CHAIN_ID_CELO, @@ -43,6 +44,7 @@ import algorandIcon from "../icons/algorand.svg"; import arbitrumIcon from "../icons/arbitrum.svg"; import auroraIcon from "../icons/aurora.svg"; import avaxIcon from "../icons/avax.svg"; +import baseIcon from '../icons/base.svg'; import bscIcon from "../icons/bsc.svg"; import celoIcon from "../icons/celo.svg"; import ethIcon from "../icons/eth.svg"; @@ -117,6 +119,11 @@ export const CHAINS: ChainInfo[] = name: "Avalanche", logo: avaxIcon, }, + { + id: CHAIN_ID_BASE, + name: "Base", + logo: baseIcon + }, { id: CHAIN_ID_BSC, name: "Binance Smart Chain", @@ -235,6 +242,11 @@ export const CHAINS: ChainInfo[] = name: "Avalanche", logo: avaxIcon, }, + { + id: CHAIN_ID_BASE, + name: "Base", + logo: baseIcon + }, { id: CHAIN_ID_BSC, name: "Binance Smart Chain", @@ -387,7 +399,8 @@ export const CHAINS_WITH_NFT_SUPPORT = CHAINS.filter( id === CHAIN_ID_MOONBEAM || id === CHAIN_ID_ARBITRUM || id === CHAIN_ID_OPTIMISM || - id === CHAIN_ID_APTOS + id === CHAIN_ID_APTOS || + id === CHAIN_ID_BASE ); export type ChainsById = { [key in ChainId]: ChainInfo }; export const CHAINS_BY_ID: ChainsById = CHAINS.reduce((obj, chain) => { @@ -495,6 +508,8 @@ export const getDefaultNativeCurrencySymbol = (chainId: ChainId) => ? "NEON" : chainId === CHAIN_ID_MOONBEAM ? "GLMR" + : chainId === CHAIN_ID_BASE + ? "ETH" : chainId === CHAIN_ID_APTOS ? "APTOS" : chainId === CHAIN_ID_ARBITRUM @@ -556,6 +571,8 @@ export const getExplorerName = (chainId: ChainId) => ? "Solscan" : chainId === CHAIN_ID_MOONBEAM ? "Moonscan" + : chainId === CHAIN_ID_BASE + ? "BaseScan" : chainId === CHAIN_ID_XPLA ? "XPLA Explorer" : chainId === CHAIN_ID_ARBITRUM @@ -612,6 +629,9 @@ export const ARBITRUM_NETWORK_CHAIN_ID = CLUSTER === "mainnet" ? 42161 : CLUSTER === "testnet" ? 421613 : 1381; export const OPTIMISM_NETWORK_CHAIN_ID = CLUSTER === "mainnet" ? 10 : CLUSTER === "testnet" ? 420 : 1381; +export const BASE_NETWORK_CHAIN_ID = + CLUSTER === "mainnet" ? 8453 : CLUSTER === "testnet" ? 84531 : 1381; + export const getEvmChainId = (chainId: ChainId) => chainId === CHAIN_ID_ETH ? ETH_NETWORK_CHAIN_ID @@ -643,6 +663,8 @@ export const getEvmChainId = (chainId: ChainId) => ? ARBITRUM_NETWORK_CHAIN_ID : chainId === CHAIN_ID_OPTIMISM ? OPTIMISM_NETWORK_CHAIN_ID + : chainId === CHAIN_ID_BASE + ? BASE_NETWORK_CHAIN_ID : undefined; export const SOLANA_HOST = process.env.REACT_APP_SOLANA_API_URL ? process.env.REACT_APP_SOLANA_API_URL @@ -1238,6 +1260,10 @@ export const COVALENT_ARBITRUM = CLUSTER === "devnet" ? null : ARBITRUM_NETWORK_CHAIN_ID; // Covalent only supports mainnet export const COVALENT_OPTIMISM = CLUSTER === "devnet" ? null : OPTIMISM_NETWORK_CHAIN_ID; // Covalent only supports mainnet + +export const COVALENT_BASE = + CLUSTER === "devnet" ? null : BASE_NETWORK_CHAIN_ID + export const COVALENT_GET_TOKENS_URL = ( chainId: ChainId, walletAddress: string, @@ -1263,6 +1289,8 @@ export const COVALENT_GET_TOKENS_URL = ( ? COVALENT_NEON : chainId === CHAIN_ID_MOONBEAM ? COVALENT_MOONBEAM + : chainId === CHAIN_ID_BASE + ? COVALENT_BASE : chainId === CHAIN_ID_ARBITRUM ? COVALENT_ARBITRUM : chainId === CHAIN_ID_OPTIMISM @@ -1332,6 +1360,15 @@ export const WETH_ADDRESS = : "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E"; export const WETH_DECIMALS = 18; +export const BASE_WETH_ADDRESS = + CLUSTER === "mainnet" + ? "" + : CLUSTER === "testnet" + ? "0x44d627f900da8adac7561bd73aa745f132450798" + : "0xDDb64fE46a91D46ee29420539FC25FD07c5FEa3E" + +export const BASE_WETH_DECIMALS = 18; + export const WBNB_ADDRESS = CLUSTER === "mainnet" ? "0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c" diff --git a/src/utils/metaMaskChainParameters.ts b/src/utils/metaMaskChainParameters.ts index f1fbc5cc3..313fa29d8 100644 --- a/src/utils/metaMaskChainParameters.ts +++ b/src/utils/metaMaskChainParameters.ts @@ -227,6 +227,13 @@ export const METAMASK_CHAIN_PARAMETERS: { rpcUrls: ["https://rpc.ankr.com/moonbeam"], blockExplorerUrls: ["https://moonscan.io"], }, + 84531: { + chainId: "0x14A33", + chainName: "Base Goerli", + nativeCurrency: { name: "Goerli Ether", symbol: "GOR", decimals: 18 }, + rpcUrls: ["https://goerli.base.org"], + blockExplorerUrls: ["https://goerli.basescan.org"], + } }; export interface EvmRpcMap {