diff --git a/backend/native/backpack-api/src/db/publicKey.ts b/backend/native/backpack-api/src/db/publicKey.ts index 97d961337..10883510b 100644 --- a/backend/native/backpack-api/src/db/publicKey.ts +++ b/backend/native/backpack-api/src/db/publicKey.ts @@ -12,7 +12,7 @@ export const getPublicKeyDetails = async ({ publicKey, }: { publicKey: string; -}): Promise<{ id: number; blockchain: "solana" | "ethereum" }> => { +}): Promise<{ id: number; blockchain: "solana" | "ethereum" | "eclipse" }> => { const publicKeyDetails = await chain("query")( { auth_public_keys: [ @@ -42,7 +42,7 @@ export const updatePublicKey = async ({ onlyInsert, }: { userId: string; - blockchain: "solana" | "ethereum"; + blockchain: "solana" | "ethereum" | "eclipse"; publicKeyId: number; onlyInsert?: boolean; }) => { diff --git a/backend/native/backpack-api/src/db/users.ts b/backend/native/backpack-api/src/db/users.ts index 8b2f4c9ce..2cb5502ff 100644 --- a/backend/native/backpack-api/src/db/users.ts +++ b/backend/native/backpack-api/src/db/users.ts @@ -267,7 +267,7 @@ export const createUser = async ( ): Promise<{ id: string; username: string; - public_keys: { blockchain: "solana" | "ethereum"; id: number }[]; + public_keys: { blockchain: "solana" | "ethereum" | "eclipse"; id: number }[]; }> => { const inviteCode = uuidv4(); await chain("mutation")( diff --git a/backend/native/backpack-api/src/routes/v1/users.ts b/backend/native/backpack-api/src/routes/v1/users.ts index a0f1c877b..c5abedc12 100644 --- a/backend/native/backpack-api/src/routes/v1/users.ts +++ b/backend/native/backpack-api/src/routes/v1/users.ts @@ -51,17 +51,39 @@ router.get("/", extractUserId, async (req, res) => { const limit: number = req.query.limit ? parseInt(req.query.limit) : 20; const isSolPublicKey = validatePublicKey(usernamePrefix, "solana"); + const isEclipsePublicKey = validatePublicKey(usernamePrefix, "eclipse"); const isEthPublicKey = validatePublicKey(usernamePrefix, "ethereum"); - let users; + let users: any = []; + + // + // SVM. + // if (isSolPublicKey) { - users = await getUserByPublicKeyAndChain(usernamePrefix, Blockchain.SOLANA); - } else if (isEthPublicKey) { + users = users.concat( + await getUserByPublicKeyAndChain(usernamePrefix, Blockchain.SOLANA) + ); + } + if (isEclipsePublicKey) { + users = users.concat( + await getUserByPublicKeyAndChain(usernamePrefix, Blockchain.ECLIPSE) + ); + } + + // + // EVM. + // + if (isEthPublicKey) { users = await getUserByPublicKeyAndChain( usernamePrefix, Blockchain.ETHEREUM ); - } else { + } + + // + // Not a pubkey so assume it's a username. + // + if (users.length === 0) { users = await getUsersByPrefix({ usernamePrefix, uuid, limit }); } @@ -88,6 +110,7 @@ router.get("/", extractUserId, async (req, res) => { remoteRequested: friendship?.remoteRequested || false, areFriends: friendship?.areFriends || false, searchedSolPubKey: isSolPublicKey ? usernamePrefix : undefined, + searchedEclipsePubKey: isEclipsePublicKey ? usernamePrefix : undefined, searchedEthPubKey: isEthPublicKey ? usernamePrefix : undefined, // TODO: fix the disambiguation with snake_case and camelCase in API responses public_keys: public_keys.map((pk) => ({ @@ -270,6 +293,29 @@ router.get("/primarySolPubkey/:username", async (req, res) => { } }); +router.get("/primaryEclipsePubkey/:username", async (req, res) => { + const username = req.params.username; + try { + const user = await getUserByUsername(username); + if (!user) { + return res.status(411).json({ msg: "User not found" }); + } + const pubKey = user.publicKeys.find( + (x) => x.blockchain === Blockchain.ECLIPSE && x.primary + ); + if (!pubKey) + return res + .status(411) + .json({ msg: "No active pubkey on Eclipse for this user" }); + + return res.json({ + publicKey: pubKey.publicKey, + }); + } catch (e) { + return res.status(411).json({ msg: "User not found" }); + } +}); + /** * Returns the primary public keys of the user with `username`. */ diff --git a/backend/native/backpack-api/src/validation/publicKey.ts b/backend/native/backpack-api/src/validation/publicKey.ts index 3ffc9eac6..b90a606c9 100644 --- a/backend/native/backpack-api/src/validation/publicKey.ts +++ b/backend/native/backpack-api/src/validation/publicKey.ts @@ -11,6 +11,14 @@ export const validatePublicKey = (address: string, chain: BlockChain) => { } return true; } + if (chain === "eclipse") { + try { + new PublicKey(address); + } catch (err) { + return false; + } + return true; + } if (chain === "ethereum") { try { ethers.utils.getAddress(address); diff --git a/backend/native/backpack-api/src/validation/signature.ts b/backend/native/backpack-api/src/validation/signature.ts index 514e7c252..a0b6b0889 100644 --- a/backend/native/backpack-api/src/validation/signature.ts +++ b/backend/native/backpack-api/src/validation/signature.ts @@ -64,6 +64,14 @@ export const validateSolanaSignature = ( } }; +export const validateEclipseSignature = ( + msg: Buffer, + encodedSignature: string, + encodedPublicKey: string +) => { + return validateSolanaSignature(msg, encodedSignature, encodedPublicKey); +}; + /** * Validate a signature * @param msg - signed message @@ -80,6 +88,7 @@ export const validateSignature = ( const validationMethod = { [Blockchain.ETHEREUM]: validateEthereumSignature, [Blockchain.SOLANA]: validateSolanaSignature, + [/*Blockchain.ECLIPSE*/ "eclipse"]: validateEclipseSignature, // todo: use enum }[blockchain]; return validationMethod(msg, signature, publicKey); diff --git a/backend/native/backpack-api/src/validation/user.ts b/backend/native/backpack-api/src/validation/user.ts index 82e4319ab..6b4cef921 100644 --- a/backend/native/backpack-api/src/validation/user.ts +++ b/backend/native/backpack-api/src/validation/user.ts @@ -35,6 +35,19 @@ export const SolanaPublicKey = z.object({ blockchain: z.literal("solana"), }); +export const EclipsePublicKey = z.object({ + publicKey: z.string().refine((str) => { + try { + new PublicKey(str); + return true; + } catch { + // Pass + } + return false; + }, "must be a valid Eclipse public key"), + blockchain: z.literal("eclipse"), +}); + export const EthereumPublicKey = z.object({ publicKey: z.string().refine((str) => { try { @@ -50,6 +63,7 @@ export const EthereumPublicKey = z.object({ export const BlockchainPublicKey = z.discriminatedUnion("blockchain", [ SolanaPublicKey, + EclipsePublicKey, EthereumPublicKey, ]); @@ -65,9 +79,14 @@ export const CreateSolanaPublicKey = SolanaPublicKey.extend({ signature: z.string(), }); +export const CreateEclipsePublicKey = EclipsePublicKey.extend({ + signature: z.string(), +}); + export const CreatePublicKeys = z.discriminatedUnion("blockchain", [ CreateEthereumPublicKey, CreateSolanaPublicKey, + CreateEclipsePublicKey, ]); export const CreateUserWithPublicKeys = BaseCreateUser.extend({ diff --git a/backend/workers/auth/src/onramp/validate/index.ts b/backend/workers/auth/src/onramp/validate/index.ts index c7eb8fc16..c85088683 100644 --- a/backend/workers/auth/src/onramp/validate/index.ts +++ b/backend/workers/auth/src/onramp/validate/index.ts @@ -4,7 +4,7 @@ import { ethers } from "ethers"; import type { BlockChain } from "../zodTypes"; export const validatePublicKey = (address: string, chain: BlockChain) => { - if (chain === "solana") { + if (chain === "solana" || chain === "eclipse") { try { new PublicKey(address); } catch (err) { diff --git a/backend/workers/auth/src/onramp/zodTypes.ts b/backend/workers/auth/src/onramp/zodTypes.ts index 8f13c3585..d80bb0608 100644 --- a/backend/workers/auth/src/onramp/zodTypes.ts +++ b/backend/workers/auth/src/onramp/zodTypes.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -const ZodChain = z.enum(["solana", "ethereum"]); +const ZodChain = z.enum(["solana", "ethereum", "eclipse"]); export type BlockChain = z.infer; export const CreateSessionRequest = z.object({ diff --git a/packages/app-extension/src/components/Onboarding/pages/BlockchainSelector.tsx b/packages/app-extension/src/components/Onboarding/pages/BlockchainSelector.tsx index a790431be..caa23ec5a 100644 --- a/packages/app-extension/src/components/Onboarding/pages/BlockchainSelector.tsx +++ b/packages/app-extension/src/components/Onboarding/pages/BlockchainSelector.tsx @@ -1,12 +1,10 @@ import { Blockchain } from "@coral-xyz/common"; import { PrimaryButton } from "@coral-xyz/react-common"; +import { useFeatureGates } from "@coral-xyz/recoil"; import { Box, Grid } from "@mui/material"; import { Header, SubtextParagraph } from "../../common"; -import { - EthereumIconOnboarding as EthereumIcon, - SolanaIconOnboarding as SolanaIcon, -} from "../../common/Icon"; +import { BLOCKCHAIN_COMPONENTS } from "../../common/Blockchains"; import { ActionCard } from "../../common/Layout/ActionCard"; export const BlockchainSelector = ({ @@ -20,6 +18,7 @@ export const BlockchainSelector = ({ onNext: () => void; isRecovery?: boolean; }) => { + const gates = useFeatureGates(); return ( - - } - checked={selectedBlockchains.includes(Blockchain.ETHEREUM)} - text="Ethereum" - onClick={() => onClick(Blockchain.ETHEREUM)} - /> - - - } - checked={selectedBlockchains.includes(Blockchain.SOLANA)} - text="Solana" - onClick={() => onClick(Blockchain.SOLANA)} - /> - + {Object.entries(BLOCKCHAIN_COMPONENTS) + .filter( + ([blockchain]) => + gates.ECLIPSE || blockchain !== Blockchain.ECLIPSE + ) + .map(([blockchain, Component]) => ( + + } + checked={selectedBlockchains.includes( + blockchain as Blockchain + )} + text={Component.Name} + onClick={() => onClick(blockchain as Blockchain)} + /> + + ))} diff --git a/packages/app-extension/src/components/Onboarding/pages/HardwareDefaultWallet.tsx b/packages/app-extension/src/components/Onboarding/pages/HardwareDefaultWallet.tsx index 7619431e5..f0e19120e 100644 --- a/packages/app-extension/src/components/Onboarding/pages/HardwareDefaultWallet.tsx +++ b/packages/app-extension/src/components/Onboarding/pages/HardwareDefaultWallet.tsx @@ -4,20 +4,18 @@ // script can't communicate with a hardware device. import { useEffect, useState } from "react"; -import type { WalletDescriptor } from "@coral-xyz/common"; +import type { Blockchain,WalletDescriptor } from "@coral-xyz/common"; import { - Blockchain, getAccountRecoveryPaths, UI_RPC_METHOD_FIND_SERVER_PUBLIC_KEY_CONFLICTS, } from "@coral-xyz/common"; import { Loading } from "@coral-xyz/react-common"; import { useBackgroundClient } from "@coral-xyz/recoil"; -import Ethereum from "@ledgerhq/hw-app-eth"; -import Solana from "@ledgerhq/hw-app-solana"; +import type Ethereum from "@ledgerhq/hw-app-eth"; +import type Solana from "@ledgerhq/hw-app-solana"; import type Transport from "@ledgerhq/hw-transport"; -import { ethers } from "ethers"; -const { base58 } = ethers.utils; +import { BLOCKCHAIN_COMPONENTS } from "../../common/Blockchains"; export const HardwareDefaultWallet = ({ blockchain, @@ -38,10 +36,8 @@ export const HardwareDefaultWallet = ({ useEffect(() => { (async () => { - const ledgerWallet = { - [Blockchain.SOLANA]: new Solana(transport), - [Blockchain.ETHEREUM]: new Ethereum(transport), - }[blockchain]; + const ledgerWallet = + BLOCKCHAIN_COMPONENTS[blockchain].LedgerApp(transport); setLedgerWallet(ledgerWallet); })(); }, [blockchain, transport]); @@ -57,13 +53,9 @@ export const HardwareDefaultWallet = ({ try { // Get the public keys for all of the recovery paths for the current account index for (const path of recoveryPaths) { - const ledgerAddress = ( - await ledgerWallet.getAddress(path.replace("m/", "")) - ).address; - const publicKey = - blockchain === Blockchain.SOLANA - ? base58.encode(ledgerAddress as Buffer) - : ledgerAddress.toString(); + const publicKey = await BLOCKCHAIN_COMPONENTS[ + blockchain + ].PublicKeyFromPath(ledgerWallet, path); publicKeys.push(publicKey); } } catch (error) { diff --git a/packages/app-extension/src/components/Onboarding/pages/HardwareDeriveWallet.tsx b/packages/app-extension/src/components/Onboarding/pages/HardwareDeriveWallet.tsx index 554c6c2b0..29278f152 100644 --- a/packages/app-extension/src/components/Onboarding/pages/HardwareDeriveWallet.tsx +++ b/packages/app-extension/src/components/Onboarding/pages/HardwareDeriveWallet.tsx @@ -1,16 +1,17 @@ import { useEffect, useState } from "react"; -import type { WalletDescriptor } from "@coral-xyz/common"; +import type { Blockchain,WalletDescriptor } from "@coral-xyz/common"; import { - Blockchain, UI_RPC_METHOD_KEYRING_READ_NEXT_DERIVATION_PATH, } from "@coral-xyz/common"; import { Loading } from "@coral-xyz/react-common"; import { useBackgroundClient } from "@coral-xyz/recoil"; -import Ethereum from "@ledgerhq/hw-app-eth"; -import Solana from "@ledgerhq/hw-app-solana"; +import type Ethereum from "@ledgerhq/hw-app-eth"; +import type Solana from "@ledgerhq/hw-app-solana"; import type Transport from "@ledgerhq/hw-transport"; import { ethers } from "ethers"; +import { BLOCKCHAIN_COMPONENTS } from "../../common/Blockchains"; + const { base58 } = ethers.utils; export const HardwareDeriveWallet = ({ @@ -31,10 +32,8 @@ export const HardwareDeriveWallet = ({ useEffect(() => { (async () => { - const ledgerWallet = { - [Blockchain.SOLANA]: new Solana(transport), - [Blockchain.ETHEREUM]: new Ethereum(transport), - }[blockchain]; + const ledgerWallet = + BLOCKCHAIN_COMPONENTS[blockchain].LedgerApp(transport); setLedgerWallet(ledgerWallet); })(); }, [blockchain, transport]); @@ -50,13 +49,10 @@ export const HardwareDeriveWallet = ({ let publicKey: string; try { - const ledgerAddress = ( - await ledgerWallet.getAddress(derivationPath.replace("m/", "")) - ).address; - publicKey = - blockchain === Blockchain.SOLANA - ? base58.encode(ledgerAddress) - : ledgerAddress.toString(); + publicKey = await BLOCKCHAIN_COMPONENTS[blockchain].PublicKeyFromPath( + ledgerWallet, + derivationPath + ); } catch (error) { if (onError) { console.debug("hardware derive wallet transport error", error); diff --git a/packages/app-extension/src/components/Onboarding/pages/HardwareSearchWallet.tsx b/packages/app-extension/src/components/Onboarding/pages/HardwareSearchWallet.tsx index 8e6faaad0..6bf880e59 100644 --- a/packages/app-extension/src/components/Onboarding/pages/HardwareSearchWallet.tsx +++ b/packages/app-extension/src/components/Onboarding/pages/HardwareSearchWallet.tsx @@ -2,20 +2,18 @@ // a loading indicator until it is found (or an error if it not found). import { useEffect, useState } from "react"; -import type { WalletDescriptor } from "@coral-xyz/common"; +import type { Blockchain,WalletDescriptor } from "@coral-xyz/common"; import { - Blockchain, formatWalletAddress, getRecoveryPaths, } from "@coral-xyz/common"; import { Loading, PrimaryButton } from "@coral-xyz/react-common"; -import Ethereum from "@ledgerhq/hw-app-eth"; -import Solana from "@ledgerhq/hw-app-solana"; import type Transport from "@ledgerhq/hw-transport"; import { Box } from "@mui/material"; import { ethers } from "ethers"; import { Header, SubtextParagraph } from "../../common"; +import { BLOCKCHAIN_COMPONENTS } from "../../common/Blockchains"; const { base58: bs58 } = ethers.utils; @@ -38,10 +36,7 @@ export const HardwareSearchWallet = ({ useEffect(() => { (async () => { - const ledger = { - [Blockchain.SOLANA]: new Solana(transport), - [Blockchain.ETHEREUM]: new Ethereum(transport), - }[blockchain]; + const ledger = BLOCKCHAIN_COMPONENTS[blockchain].LedgerApp(transport); for (const derivationPath of getRecoveryPaths(blockchain, true)) { let ledgerAddress; try { diff --git a/packages/app-extension/src/components/Unlocked/Apps/index.tsx b/packages/app-extension/src/components/Unlocked/Apps/index.tsx index 8c68e004a..4273d0944 100644 --- a/packages/app-extension/src/components/Unlocked/Apps/index.tsx +++ b/packages/app-extension/src/components/Unlocked/Apps/index.tsx @@ -20,9 +20,9 @@ import { Button, Grid, Skeleton, Typography } from "@mui/material"; import { getSvgPath } from "figma-squircle"; import { useRecoilValue, waitForAll } from "recoil"; +import { BLOCKCHAIN_COMPONENTS } from "../../common/Blockchains"; import { _BalancesTableHead, - BalancesTableHead, BalancesTableProvider, useBalancesContext, } from "../Balances/Balances"; @@ -92,24 +92,16 @@ function PluginGrid() { // if (wallets.length === 1) { const wallet = wallets[0]; - if (wallet.blockchain === Blockchain.ETHEREUM) { + if (wallet.blockchain !== Blockchain.SOLANA) { return ( } - title="Ethereum xNFTs not yet supported" + title={`${ + BLOCKCHAIN_COMPONENTS[wallet.blockchain].Name + } xNFTs not yet supported`} subtitle="Switch to Solana to use xNFTs" buttonText="" onClick={() => {}} - header={ - // Only show the wallet switcher if we are in single wallet mode. - !_isAggregateWallets ? ( - <_BalancesTableHead - blockchain={wallet.blockchain} - showContent - setShowContent={() => {}} - /> - ) : null - } /> ); } diff --git a/packages/app-extension/src/components/Unlocked/Balances/RecentSolanaActivity/NoRecentActivity.tsx b/packages/app-extension/src/components/Unlocked/Balances/RecentSolanaActivity/NoRecentActivity.tsx index 312532fde..4e7dcb351 100644 --- a/packages/app-extension/src/components/Unlocked/Balances/RecentSolanaActivity/NoRecentActivity.tsx +++ b/packages/app-extension/src/components/Unlocked/Balances/RecentSolanaActivity/NoRecentActivity.tsx @@ -22,9 +22,6 @@ export function NoRecentActivityLabel({ minimize }: { minimize: boolean }) { contentStyle={{ color: minimize ? theme.custom.colors.secondary : "inherit", }} - innerStyle={{ - marginBottom: minimize !== true ? "64px" : 0, // Tab height offset. - }} minimize={minimize} /> diff --git a/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Deposit.tsx b/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Deposit.tsx index b4c1d49a2..83d4b1620 100644 --- a/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Deposit.tsx +++ b/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Deposit.tsx @@ -1,5 +1,5 @@ import { useState } from "react"; -import { Blockchain } from "@coral-xyz/common"; +import type { Blockchain } from "@coral-xyz/common"; import { SecondaryButton } from "@coral-xyz/react-common"; import { getBlockchainLogo, @@ -11,7 +11,8 @@ import ContentCopyIcon from "@mui/icons-material/ContentCopy"; import QrCodeIcon from "@mui/icons-material/QrCode"; import { IconButton, Modal, Typography } from "@mui/material"; -import { formatWalletAddress,TextField } from "../../../common"; +import { formatWalletAddress, TextField } from "../../../common"; +import { BLOCKCHAIN_COMPONENTS } from "../../../common/Blockchains"; import { CloseButton, useDrawerContext } from "../../../common/Layout/Drawer"; import { WithCopyTooltip } from "../../../common/WithCopyTooltip"; @@ -363,6 +364,7 @@ function _Deposit({ const theme = useCustomTheme(); const [tooltipOpen, setTooltipOpen] = useState(false); const name = useWalletName(publicKey); + const bc_components = BLOCKCHAIN_COMPONENTS[blockchain]; const walletDisplay = publicKey.toString().slice(0, 12) + @@ -445,12 +447,10 @@ function _Deposit({
- {blockchain === Blockchain.SOLANA ? ( - <>This address can only receive SOL and SPL tokens on Solana. - ) : null} - {blockchain === Blockchain.ETHEREUM ? ( - <>This address can only receive ETH and ERC20 tokens on Ethereum. - ) : null} + <> + This address can only receive {bc_components.GasTokenName} and{" "} + {bc_components.AppTokenName} tokens on {bc_components.Name}. +
diff --git a/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Ethereum/index.tsx b/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Ethereum/index.tsx index f32499cb3..b2f344a71 100644 --- a/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Ethereum/index.tsx +++ b/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Ethereum/index.tsx @@ -23,13 +23,15 @@ import { Error, Sending } from "../Send"; const logger = getLogger("send-ethereum-confirmation-card"); const { base58: bs58 } = ethers.utils; -export function SendEthereumConfirmationCard({ +// Note: have not tested this for non main Ethereum chains (e.g. Polygon). +export function SendEvmConfirmationCard({ token, destinationAddress, destinationUser, amount, onComplete, onViewBalances, + blockchain, }: { token: { address: string; @@ -46,6 +48,7 @@ export function SendEthereumConfirmationCard({ amount: BigNumber; onComplete?: () => void; onViewBalances?: () => void; + blockchain: Blockchain; }) { const ethereumCtx = useEthereumCtx(); const [txSignature, setTxSignature] = useState(null); @@ -142,7 +145,7 @@ export function SendEthereumConfirmationCard({ /> ) : cardType === "sending" ? ( ) : cardType === "complete" ? ( ) : ( retry()} error={error} diff --git a/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Ramp.tsx b/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Ramp.tsx index f181fb867..5132052ec 100644 --- a/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Ramp.tsx +++ b/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Ramp.tsx @@ -1,15 +1,12 @@ import { useState } from "react"; -import { Blockchain } from "@coral-xyz/common"; +import type { Blockchain } from "@coral-xyz/common"; import { ProxyImage } from "@coral-xyz/react-common"; -import { - SOL_LOGO_URI, - useAllWalletsDisplayed, - useWalletName, -} from "@coral-xyz/recoil"; +import { useAllWalletsDisplayed, useWalletName } from "@coral-xyz/recoil"; import { styles } from "@coral-xyz/themes"; import { ListItemIcon, Typography } from "@mui/material"; import { TextField } from "../../../common"; +import { BLOCKCHAIN_COMPONENTS } from "../../../common/Blockchains"; import { useNavigation } from "../../../common/Layout/NavStack"; import { BalancesTable, @@ -89,23 +86,6 @@ const useStyles = styles((theme) => ({ }, })); -const RAMP_SUPPORTED_TOKENS = { - [Blockchain.SOLANA]: [ - { - title: "SOL", - icon: SOL_LOGO_URI, - subtitle: "Solana", - }, - ], - [Blockchain.ETHEREUM]: [ - { - title: "ETH", - subtitle: "Ethereum", - icon: "/ethereum.png", - }, - ], -}; - export function Ramp({ blockchain, publicKey, @@ -201,19 +181,17 @@ function RampCard({ - {RAMP_SUPPORTED_TOKENS[blockchain] - .filter( - ({ title, subtitle }) => - title.toLowerCase().includes(searchFilter.toLocaleLowerCase()) || - subtitle.toLowerCase().includes(searchFilter.toLowerCase()) - ) - .map((token: any) => ( - onStartRamp({ blockchain, token, publicKey })} - > - - - ))} + {BLOCKCHAIN_COMPONENTS[blockchain].RampSupportedTokens.filter( + ({ title, subtitle }) => + title.toLowerCase().includes(searchFilter.toLocaleLowerCase()) || + subtitle.toLowerCase().includes(searchFilter.toLowerCase()) + ).map((token: any) => ( + onStartRamp({ blockchain, token, publicKey })} + > + + + ))} ); diff --git a/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Send.tsx b/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Send.tsx index 3e484f9b3..3bd41ead8 100644 --- a/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Send.tsx +++ b/packages/app-extension/src/components/Unlocked/Balances/TokensWidget/Send.tsx @@ -2,10 +2,7 @@ import React, { type ChangeEvent, useEffect, useState } from "react"; import { StyleSheet, View } from "react-native"; import { Blockchain, - ETH_NATIVE_MINT, explorerUrl, - NATIVE_ACCOUNT_RENT_EXEMPTION_LAMPORTS, - SOL_NATIVE_MINT, toDisplayBalance, toTitleCase, } from "@coral-xyz/common"; @@ -37,15 +34,13 @@ import { Typography } from "@mui/material"; import { BigNumber, ethers } from "ethers"; import { ApproveTransactionDrawer } from "../../../common/ApproveTransactionDrawer"; +import { BLOCKCHAIN_COMPONENTS } from "../../../common/Blockchains"; import { CopyablePublicKey } from "../../../common/CopyablePublicKey"; import { useDrawerContext } from "../../../common/Layout/Drawer"; import { useNavigation } from "../../../common/Layout/NavStack"; import { TokenAmountHeader } from "../../../common/TokenAmountHeader"; import { TokenInputField } from "../../../common/TokenInput"; -import { SendEthereumConfirmationCard } from "./Ethereum"; -import { SendSolanaConfirmationCard } from "./Solana"; - export const useStyles = makeStyles((theme) => ({ topImage: { width: 80, @@ -131,25 +126,12 @@ export function Send({ useEffect(() => { if (!token) return; - if (token.mint === SOL_NATIVE_MINT) { - // When sending SOL, account for the tx fee and rent exempt minimum. - setFeeOffset( - BigNumber.from(5000).add( - BigNumber.from(NATIVE_ACCOUNT_RENT_EXEMPTION_LAMPORTS) - ) - ); - } else if (token.address === ETH_NATIVE_MINT) { - // 21,000 GWEI for a standard ETH transfer - setFeeOffset( - BigNumber.from("21000") - .mul(ethereumCtx?.feeData.maxFeePerGas!) - .add( - BigNumber.from("21000").mul( - ethereumCtx?.feeData.maxPriorityFeePerGas! - ) - ) - ); - } + setFeeOffset( + BLOCKCHAIN_COMPONENTS[blockchain].MaxFeeOffset( + { address: token.address, mint: token.mint }, + ethereumCtx + ) + ); }, [blockchain, token]); // eslint-disable-line const amountSubFee = BigNumber.from(token!.nativeBalance).sub(feeOffset); @@ -187,10 +169,8 @@ export function Send({ drawer.close(); }; - const SendConfirmComponent = { - [Blockchain.SOLANA]: SendSolanaConfirmationCard, - [Blockchain.ETHEREUM]: SendEthereumConfirmationCard, - }[blockchain]; + const SendConfirmComponent = + BLOCKCHAIN_COMPONENTS[blockchain].SendTokenConfirmationCard; return (
({ }, })); -export function SendSolanaConfirmationCard({ +export function SendSvmConfirmationCard({ token, destinationAddress, destinationUser, amount, onComplete, onViewBalances, + blockchain, }: { token: { address: string; @@ -48,6 +49,7 @@ export function SendSolanaConfirmationCard({ amount: BigNumber; onComplete?: (txSig?: any) => void; onViewBalances?: () => void; + blockchain: Blockchain; }) { const { txSignature, onConfirm, cardType, error } = useSolanaTransaction({ token, @@ -70,7 +72,7 @@ export function SendSolanaConfirmationCard({ /> ) : cardType === "sending" ? ( ) : cardType === "complete" ? ( ) : ( { (async () => { @@ -214,46 +216,15 @@ function SendScreen({ nft, to }: { nft: any; to: SendData }) { openDrawer={openConfirm} setOpenDrawer={setOpenConfirm} > - {nft.blockchain === Blockchain.SOLANA ? ( - ["destinationUser"] - } - destinationAddress={destinationAddress} - amount={BigNumber.from(1)} - onComplete={() => setWasSent(true)} - /> - ) : null} - {nft.blockchain === Blockchain.ETHEREUM ? ( - ["destinationUser"] + setWasSent(true)} + destinationAddress={destinationAddress} + amount={BigNumber.from(1)} + onComplete={() => setWasSent(true)} /> - ) : null} ); diff --git a/packages/app-extension/src/components/Unlocked/Nfts/index.tsx b/packages/app-extension/src/components/Unlocked/Nfts/index.tsx index 3bbb50e98..9634dc286 100644 --- a/packages/app-extension/src/components/Unlocked/Nfts/index.tsx +++ b/packages/app-extension/src/components/Unlocked/Nfts/index.tsx @@ -80,15 +80,6 @@ export function Nfts() { buttonText="Browse Magic Eden" onClick={() => window.open("https://magiceden.io")} verticallyCentered={!oneLive} - header={ - !_isAggregateWallets ? ( - <_BalancesTableHead - blockchain={activeWallet.blockchain} - showContent - setShowContent={() => {}} - /> - ) : null - } style={{ height: !oneLive.isLive ? "100%" : undefined, }} diff --git a/packages/app-extension/src/components/Unlocked/PrimaryPubkeySelector.tsx b/packages/app-extension/src/components/Unlocked/PrimaryPubkeySelector.tsx index 613fa9051..1fdc88c25 100644 --- a/packages/app-extension/src/components/Unlocked/PrimaryPubkeySelector.tsx +++ b/packages/app-extension/src/components/Unlocked/PrimaryPubkeySelector.tsx @@ -1,7 +1,8 @@ import { useState } from "react"; +import type { + Blockchain} from "@coral-xyz/common"; import { BACKEND_API_URL, - Blockchain, formatWalletAddress, toTitleCase, } from "@coral-xyz/common"; @@ -17,6 +18,7 @@ import { Box } from "@mui/material"; import { useRecoilValue, useSetRecoilState } from "recoil"; import { Header, SubtextParagraph } from "../common"; +import { BLOCKCHAIN_COMPONENTS } from "../common/Blockchains"; import { WithDrawer } from "../common/Layout/Drawer"; import { TokenBadge } from "./Balances/TokensWidget/TokenBadge"; @@ -25,12 +27,15 @@ export const PrimaryPubkeySelector = () => { const gates = useFeatureGates(); const wallets = useRecoilValue(serverPublicKeys); const primaryWallets = usePrimaryWallets(); - const blockchains: Blockchain[] = [Blockchain.SOLANA, Blockchain.ETHEREUM]; + const blockchains: Blockchain[] = Object.keys( + BLOCKCHAIN_COMPONENTS + ) as Blockchain[]; const needsMigration: Blockchain[] = []; - const [selectedAddresses, setSelectedSolAddresses] = useState({ - [Blockchain.ETHEREUM]: "", - [Blockchain.SOLANA]: "", - }); + const [selectedAddresses, setSelectedSolAddresses] = useState( + Object.fromEntries( + new Map(Object.keys(BLOCKCHAIN_COMPONENTS).map((k) => [k, ""])) + ) + ); const setServerPublicKeys = useSetRecoilState(serverPublicKeys); const [migrationDone, setMigrationDone] = useState(false); @@ -99,12 +104,17 @@ export const PrimaryPubkeySelector = () => { }} > x === Blockchain.SOLANA) && - !selectedAddresses[Blockchain.SOLANA]) || - (needsMigration.find((x) => x === Blockchain.ETHEREUM) && - !selectedAddresses[Blockchain.ETHEREUM]) - } + disabled={(() => { + Object.keys(BLOCKCHAIN_COMPONENTS).forEach((blockchain) => { + if ( + needsMigration.find((x) => x === blockchain) && + !selectedAddresses[blockchain] + ) { + return true; + } + }); + return false; + })()} label={ needsMigration.length === 1 ? "Set primary wallet" diff --git a/packages/app-extension/src/components/Unlocked/Settings/AddConnectWallet/ConnectHardware/ConnectHardwareApp.tsx b/packages/app-extension/src/components/Unlocked/Settings/AddConnectWallet/ConnectHardware/ConnectHardwareApp.tsx index 6498278a9..2822546be 100644 --- a/packages/app-extension/src/components/Unlocked/Settings/AddConnectWallet/ConnectHardware/ConnectHardwareApp.tsx +++ b/packages/app-extension/src/components/Unlocked/Settings/AddConnectWallet/ConnectHardware/ConnectHardwareApp.tsx @@ -1,12 +1,9 @@ -import { Blockchain } from "@coral-xyz/common"; -import { - EthereumIcon, - PrimaryButton, - SolanaIcon, -} from "@coral-xyz/react-common"; +import type { Blockchain } from "@coral-xyz/common"; +import { PrimaryButton } from "@coral-xyz/react-common"; import { Box } from "@mui/material"; import { Header, HeaderIcon, SubtextParagraph } from "../../../../common"; +import { BLOCKCHAIN_COMPONENTS } from "../../../../common/Blockchains"; export function ConnectHardwareApp({ blockchain, @@ -15,13 +12,7 @@ export function ConnectHardwareApp({ blockchain: Blockchain; onNext: () => void; }) { - const header = { - [Blockchain.SOLANA]: { icon: , text: "Open the Solana app" }, - [Blockchain.ETHEREUM]: { - icon: , - text: "Open the Ethereum app", - }, - }[blockchain]; + const header = BLOCKCHAIN_COMPONENTS[blockchain]; return ( - -
+ +
Make sure your wallet remains connected. diff --git a/packages/app-extension/src/components/Unlocked/Settings/Preferences/Ethereum/common.ts b/packages/app-extension/src/components/Unlocked/Settings/Preferences/Ethereum/common.ts index af467cced..f919085c1 100644 --- a/packages/app-extension/src/components/Unlocked/Settings/Preferences/Ethereum/common.ts +++ b/packages/app-extension/src/components/Unlocked/Settings/Preferences/Ethereum/common.ts @@ -1,7 +1,8 @@ import type { ChannelAppUiClient } from "@coral-xyz/common"; import { + Blockchain, + UI_RPC_METHOD_CONNECTION_URL_UPDATE, UI_RPC_METHOD_ETHEREUM_CHAIN_ID_UPDATE, - UI_RPC_METHOD_ETHEREUM_CONNECTION_URL_UPDATE, } from "@coral-xyz/common"; import { ethers } from "ethers"; const { hexlify } = ethers.utils; @@ -12,8 +13,8 @@ export const changeNetwork = async ( chainId?: string ) => { await background.request({ - method: UI_RPC_METHOD_ETHEREUM_CONNECTION_URL_UPDATE, - params: [url], + method: UI_RPC_METHOD_CONNECTION_URL_UPDATE, + params: [url, Blockchain.ETHEREUM], }); if (!chainId) { diff --git a/packages/app-extension/src/components/Unlocked/Settings/Preferences/Solana/ConnectionSwitch.tsx b/packages/app-extension/src/components/Unlocked/Settings/Preferences/Solana/ConnectionSwitch.tsx index 5761ffe6f..a69a24110 100644 --- a/packages/app-extension/src/components/Unlocked/Settings/Preferences/Solana/ConnectionSwitch.tsx +++ b/packages/app-extension/src/components/Unlocked/Settings/Preferences/Solana/ConnectionSwitch.tsx @@ -1,7 +1,8 @@ import { useEffect } from "react"; import { + Blockchain, SolanaCluster, - UI_RPC_METHOD_SOLANA_CONNECTION_URL_UPDATE, + UI_RPC_METHOD_CONNECTION_URL_UPDATE, } from "@coral-xyz/common"; import { PushDetail } from "@coral-xyz/react-common"; import { useBackgroundClient, useSolanaConnectionUrl } from "@coral-xyz/recoil"; @@ -55,8 +56,8 @@ export function PreferencesSolanaConnection() { try { background .request({ - method: UI_RPC_METHOD_SOLANA_CONNECTION_URL_UPDATE, - params: [url], + method: UI_RPC_METHOD_CONNECTION_URL_UPDATE, + params: [url, Blockchain.SOLANA], }) .catch(console.error); } catch (err) { diff --git a/packages/app-extension/src/components/Unlocked/Settings/Preferences/Solana/CustomRpcUrl.tsx b/packages/app-extension/src/components/Unlocked/Settings/Preferences/Solana/CustomRpcUrl.tsx index 397534fa4..9fc2c1160 100644 --- a/packages/app-extension/src/components/Unlocked/Settings/Preferences/Solana/CustomRpcUrl.tsx +++ b/packages/app-extension/src/components/Unlocked/Settings/Preferences/Solana/CustomRpcUrl.tsx @@ -1,5 +1,8 @@ import { useEffect, useState } from "react"; -import { UI_RPC_METHOD_SOLANA_CONNECTION_URL_UPDATE } from "@coral-xyz/common"; +import { + Blockchain, + UI_RPC_METHOD_CONNECTION_URL_UPDATE, +} from "@coral-xyz/common"; import { InputListItem, Inputs, PrimaryButton } from "@coral-xyz/react-common"; import { useBackgroundClient } from "@coral-xyz/recoil"; @@ -18,8 +21,8 @@ export function PreferenceSolanaCustomRpcUrl() { try { background .request({ - method: UI_RPC_METHOD_SOLANA_CONNECTION_URL_UPDATE, - params: [rpcUrl], + method: UI_RPC_METHOD_CONNECTION_URL_UPDATE, + params: [rpcUrl, Blockchain.SOLANA], }) .then(close) .catch(console.error); diff --git a/packages/app-extension/src/components/Unlocked/Settings/Preferences/Solana/Explorer.tsx b/packages/app-extension/src/components/Unlocked/Settings/Preferences/Solana/Explorer.tsx index c5c01d11a..25d0baa60 100644 --- a/packages/app-extension/src/components/Unlocked/Settings/Preferences/Solana/Explorer.tsx +++ b/packages/app-extension/src/components/Unlocked/Settings/Preferences/Solana/Explorer.tsx @@ -1,7 +1,8 @@ import { useEffect } from "react"; import { + Blockchain, SolanaExplorer, - UI_RPC_METHOD_SOLANA_EXPLORER_UPDATE, + UI_RPC_METHOD_EXPLORER_UPDATE, } from "@coral-xyz/common"; import { useBackgroundClient, useSolanaExplorer } from "@coral-xyz/recoil"; @@ -28,16 +29,11 @@ export function PreferencesSolanaExplorer() { "Solana Explorer": { onClick: () => changeExplorer(SolanaExplorer.SOLANA_EXPLORER), detail: - explorer === SolanaExplorer.SOLANA_EXPLORER ? ( - - ) : ( -
- ), + explorer === SolanaExplorer.SOLANA_EXPLORER ? :
, }, "Solana FM": { onClick: () => changeExplorer(SolanaExplorer.SOLANA_FM), - detail: - explorer === SolanaExplorer.SOLANA_FM ? :
, + detail: explorer === SolanaExplorer.SOLANA_FM ? :
, }, Solscan: { onClick: () => changeExplorer(SolanaExplorer.SOLSCAN), @@ -53,8 +49,8 @@ export function PreferencesSolanaExplorer() { try { background .request({ - method: UI_RPC_METHOD_SOLANA_EXPLORER_UPDATE, - params: [explorer], + method: UI_RPC_METHOD_EXPLORER_UPDATE, + params: [explorer, Blockchain.SOLANA], }) .catch(console.error); } catch (err) { diff --git a/packages/app-extension/src/components/Unlocked/Settings/YourAccount/EditWallets/RenameWallet.tsx b/packages/app-extension/src/components/Unlocked/Settings/YourAccount/EditWallets/RenameWallet.tsx index 1cfd41f77..549e0e43a 100644 --- a/packages/app-extension/src/components/Unlocked/Settings/YourAccount/EditWallets/RenameWallet.tsx +++ b/packages/app-extension/src/components/Unlocked/Settings/YourAccount/EditWallets/RenameWallet.tsx @@ -1,4 +1,5 @@ import React, { useEffect, useState } from "react"; +import type { Blockchain } from "@coral-xyz/common"; import { UI_RPC_METHOD_KEYNAME_UPDATE } from "@coral-xyz/common"; import { PrimaryButton, @@ -11,10 +12,11 @@ import { Typography } from "@mui/material"; import { useNavigation } from "../../../../common/Layout/NavStack"; -export const RenameWallet: React.FC<{ publicKey: string; name: string }> = ({ - publicKey, - name, -}) => { +export const RenameWallet: React.FC<{ + publicKey: string; + name: string; + blockchain: Blockchain; +}> = ({ publicKey, name, blockchain }) => { const [walletName, setWalletName] = useState(name); const nav = useNavigation(); const theme = useCustomTheme(); @@ -32,7 +34,7 @@ export const RenameWallet: React.FC<{ publicKey: string; name: string }> = ({ e.preventDefault(); await background.request({ method: UI_RPC_METHOD_KEYNAME_UPDATE, - params: [publicKey, walletName], + params: [publicKey, walletName, blockchain], }); nav.pop(); }; diff --git a/packages/app-extension/src/components/Unlocked/Settings/YourAccount/EditWallets/WalletDetail.tsx b/packages/app-extension/src/components/Unlocked/Settings/YourAccount/EditWallets/WalletDetail.tsx index 8b829f2ae..51c0d8713 100644 --- a/packages/app-extension/src/components/Unlocked/Settings/YourAccount/EditWallets/WalletDetail.tsx +++ b/packages/app-extension/src/components/Unlocked/Settings/YourAccount/EditWallets/WalletDetail.tsx @@ -50,7 +50,7 @@ export const WalletDetail: React.FC<{ try { keyname = await background.request({ method: UI_RPC_METHOD_KEYNAME_READ, - params: [publicKey], + params: [publicKey, blockchain], }); } catch { // No wallet name, might be dehydrated @@ -99,6 +99,7 @@ export const WalletDetail: React.FC<{ nav.push("edit-wallets-rename", { publicKey, name: walletName, + blockchain, }), }, }; diff --git a/packages/app-extension/src/components/Unlocked/Swap.tsx b/packages/app-extension/src/components/Unlocked/Swap.tsx index df08f66e8..107ac9eb4 100644 --- a/packages/app-extension/src/components/Unlocked/Swap.tsx +++ b/packages/app-extension/src/components/Unlocked/Swap.tsx @@ -50,6 +50,7 @@ import { ethers, FixedNumber } from "ethers"; import { Button as XnftButton } from "../../plugin/Component"; import { TextField } from "../common"; import { ApproveTransactionDrawer } from "../common/ApproveTransactionDrawer"; +import { BLOCKCHAIN_COMPONENTS } from "../common/Blockchains"; import { BottomCard } from "../common/Layout/BottomCard"; import { CloseButton, @@ -296,11 +297,11 @@ export function _Swap({ } }; - if (blockchain === Blockchain.ETHEREUM) { + if (blockchain !== Blockchain.SOLANA) { return ( } - title="Ethereum Swaps Soon" + title={`${BLOCKCHAIN_COMPONENTS[blockchain].Name} Swaps Soon`} subtitle="For now, please use a Solana wallet to swap" /> ); diff --git a/packages/app-extension/src/components/common/Account/ImportWallets.tsx b/packages/app-extension/src/components/common/Account/ImportWallets.tsx index ae7b3602b..6291ae10b 100644 --- a/packages/app-extension/src/components/common/Account/ImportWallets.tsx +++ b/packages/app-extension/src/components/common/Account/ImportWallets.tsx @@ -1,16 +1,6 @@ import { useEffect, useState } from "react"; -import type { WalletDescriptor } from "@coral-xyz/common"; +import type { Blockchain,WalletDescriptor } from "@coral-xyz/common"; import { - Blockchain, - DEFAULT_SOLANA_CLUSTER, - EthereumConnectionUrl, - ethereumIndexed, - legacyBip44ChangeIndexed, - legacyBip44Indexed, - legacyEthereum, - legacyLedgerIndexed, - legacyLedgerLiveAccount, - legacySolletIndexed, LOAD_PUBLIC_KEY_AMOUNT, UI_RPC_METHOD_FIND_SERVER_PUBLIC_KEY_CONFLICTS, UI_RPC_METHOD_KEYRING_STORE_READ_ALL_PUBKEYS, @@ -19,8 +9,6 @@ import { import { Loading, PrimaryButton, TextInput } from "@coral-xyz/react-common"; import { useBackgroundClient, useDehydratedWallets } from "@coral-xyz/recoil"; import { useCustomTheme } from "@coral-xyz/themes"; -import Ethereum from "@ledgerhq/hw-app-eth"; -import Solana from "@ledgerhq/hw-app-solana"; import type Transport from "@ledgerhq/hw-transport"; import { Box, @@ -29,9 +17,8 @@ import { ListItemText, MenuItem, } from "@mui/material"; -import * as anchor from "@project-serum/anchor"; -import { Connection as SolanaConnection, PublicKey } from "@solana/web3.js"; -import { BigNumber, ethers } from "ethers"; +import type { BigNumber} from "ethers"; +import { ethers } from "ethers"; import { Checkbox, @@ -39,10 +26,9 @@ import { Header, SubtextParagraph, } from "../../common"; +import { BLOCKCHAIN_COMPONENTS } from "../../common/Blockchains"; import { Scrollbar } from "../Layout/Scrollbar"; -const { base58: bs58 } = ethers.utils; - export function ImportWallets({ blockchain, mnemonic, @@ -88,56 +74,8 @@ export function ImportWallets({ [] ); - const derivationPathOptions = { - [Blockchain.SOLANA]: [ - { - path: (i: number) => legacyBip44Indexed(Blockchain.SOLANA, i), - label: "m/44/501'/x'", - }, - { - path: (i: number) => legacyBip44ChangeIndexed(Blockchain.SOLANA, i), - label: "m/44/501'/x'/0'", - }, - { - path: (i: number) => - legacyBip44ChangeIndexed(Blockchain.SOLANA, i) + "/0'", - label: "m/44/501'/x'/0'/0'", - }, - ] - // Note: We only allow importing the deprecated sollet derivation path for - // hot wallets. This UI is hidden behind a local storage flag we - // expect people to manually set, since this derivation path was only - // used by mostly technical early Solana users. - .concat( - mnemonic && window.localStorage.getItem("sollet") - ? [ - { - path: (i: number) => legacySolletIndexed(i), - label: "501'/0'/0/0 (Deprecated)", - }, - ] - : [] - ), - [Blockchain.ETHEREUM]: [ - { - path: (i: number) => legacyEthereum(i), - label: "m/44/60'/x", - }, - { - path: (i: number) => legacyLedgerIndexed(i), - label: "m/44'/60'/0'/x' - Ledger", - }, - { - path: (i: number) => legacyLedgerLiveAccount(i), - label: "m/44'/60'/x'/0/0 - Ledger Live", - }, - { - path: (i: number) => ethereumIndexed(i), - label: "m/44'/60'/0'/0/x - Ethereum Standard", - }, - ], - }[blockchain]; - + const derivationPathOptions = + BLOCKCHAIN_COMPONENTS[blockchain].DerivationPathOptions; const [derivationPathLabel, setDerivationPathLabel] = useState( derivationPathOptions[0].label ); @@ -269,48 +207,7 @@ export function ImportWallets({ // // Load balances for accounts that have been loaded // - const loadBalances = async (publicKeys: string[]) => { - if (blockchain === Blockchain.SOLANA) { - // TODO use Backpack configured value - const solanaMainnetRpc = - process.env.DEFAULT_SOLANA_CONNECTION_URL || DEFAULT_SOLANA_CLUSTER; - const solanaConnection = new SolanaConnection( - solanaMainnetRpc, - "confirmed" - ); - const accounts = ( - await anchor.utils.rpc.getMultipleAccounts( - solanaConnection, - publicKeys.map((p) => new PublicKey(p)) - ) - ).map((result, index) => { - return { - publicKey: publicKeys[index], - balance: result - ? BigNumber.from(result.account.lamports) - : BigNumber.from(0), - index, - }; - }); - return accounts; - } else if (blockchain === Blockchain.ETHEREUM) { - // TODO use Backpack configured value - const ethereumMainnetRpc = - process.env.DEFAULT_ETHEREUM_CONNECTION_URL || - EthereumConnectionUrl.MAINNET; - const ethereumProvider = new ethers.providers.JsonRpcProvider( - ethereumMainnetRpc - ); - const balances = await Promise.all( - publicKeys.map((p) => ethereumProvider.getBalance(p)) - ); - return publicKeys.map((p, index) => { - return { publicKey: p, balance: balances[index], index }; - }); - } else { - throw new Error("invalid blockchain"); - } - }; + const loadBalances = BLOCKCHAIN_COMPONENTS[blockchain].LoadBalances; // // Load accounts for the given mnemonic. This is passed to the ImportWallets @@ -335,20 +232,18 @@ export function ImportWallets({ ): Promise => { const publicKeys = []; setLedgerLocked(true); - const ledger = { - [Blockchain.SOLANA]: new Solana(transport), - [Blockchain.ETHEREUM]: new Ethereum(transport), - }[blockchain]; + const ledger = BLOCKCHAIN_COMPONENTS[blockchain].LedgerApp(transport); // Add remaining accounts for (const derivationPath of derivationPaths) { publicKeys.push( - (await ledger.getAddress(derivationPath.replace("m/", ""))).address + await BLOCKCHAIN_COMPONENTS[blockchain].PublicKeyFromPath( + ledger, + derivationPath + ) ); } setLedgerLocked(false); - return publicKeys.map((p) => - blockchain === Blockchain.SOLANA ? bs58.encode(p) : p.toString() - ); + return publicKeys; }; const isDisabledPublicKey = (pk: string): boolean => { @@ -389,16 +284,10 @@ export function ImportWallets({ }; // Symbol for balance displays - const symbol = { - [Blockchain.SOLANA]: "SOL", - [Blockchain.ETHEREUM]: "ETH", - }[blockchain]; + const symbol = BLOCKCHAIN_COMPONENTS[blockchain].GasTokenName; // Decimals for balance displays - const decimals = { - [Blockchain.SOLANA]: 9, - [Blockchain.ETHEREUM]: 18, - }[blockchain]; + const decimals = BLOCKCHAIN_COMPONENTS[blockchain].GasTokenDecimals; return ( LedgerApp; + PublicKeyFromPath: ( + ledgerWallet: LedgerApp, + path: string + ) => Promise; + Icon: any; + RampSupportedTokens: Array<{ + title: string; + icon: string; + subtitle: string; + }>; + DerivationPathOptions: Array<{ + path: (i: number) => BIPPath; + label: string; + }>; + // TODO: this should be replaced with graphql? + LoadBalances: ( + publicKeys: string[] + ) => Promise< + Array<{ publicKey: string; balance: BigNumber; index: number }> + >; + GasTokenDecimals: number; + SendTokenConfirmationCard: ({ + token, + destinationAddress, + destinationUser, + amount, + onComplete, + onViewBalances, + }: { + token: { + address: string; + logo: string; + decimals: number; + tokenId?: string; + mint?: string; + }; + destinationAddress: string; + destinationUser?: { + username: string; + walletName?: string; + image: string; + }; + amount: BigNumber; + onComplete?: (txSig?: any) => void; + onViewBalances?: () => void; + }) => any; + SendNftConfirmationCard: ({ + nft, + destinationAddress, + destinationUser, + amount, + onComplete, + }: { + nft: any; + destinationAddress: string; + destinationUser?: { + username: string; + walletName?: string; + image: string; + }; + amount: BigNumber; + onComplete?: (txSig?: any) => void; + }) => any; + // Fee amount to offset a token transfer when clicking the "max" button. + MaxFeeOffset: ( + token: { address: string; mint?: string }, + ethereumCtx?: EthereumContext + ) => BigNumber; + } +> = { + [Blockchain.ETHEREUM]: { + Name: "Ethereum", + GasTokenName: "ETH", + AppTokenName: "ERC20", + LedgerText: "Open the Ethereum app", + LedgerApp: (transport: Transport) => new Ethereum(transport), + PublicKeyFromPath: async (ledgerWallet: LedgerApp, path: string) => { + const ledgerAddress = ( + await ledgerWallet.getAddress(path.replace("m/", "")) + ).address; + const publicKey = ledgerAddress.toString(); + return publicKey; + }, + Icon: () => , + RampSupportedTokens: [ + { + title: "ETH", + subtitle: "Ethereum", + icon: "/ethereum.png", + }, + ], + DerivationPathOptions: [ + { + path: (i: number) => legacyEthereum(i), + label: "m/44/60'/x", + }, + { + path: (i: number) => legacyLedgerIndexed(i), + label: "m/44'/60'/0'/x' - Ledger", + }, + { + path: (i: number) => legacyLedgerLiveAccount(i), + label: "m/44'/60'/x'/0/0 - Ledger Live", + }, + { + path: (i: number) => ethereumIndexed(i), + label: "m/44'/60'/0'/0/x - Ethereum Standard", + }, + ], + LoadBalances: async (publicKeys: string[]) => { + // TODO use Backpack configured value + const ethereumMainnetRpc = + process.env.DEFAULT_ETHEREUM_CONNECTION_URL || + EthereumConnectionUrl.MAINNET; + const ethereumProvider = new ethers.providers.JsonRpcProvider( + ethereumMainnetRpc + ); + const balances = await Promise.all( + publicKeys.map((p) => ethereumProvider.getBalance(p)) + ); + return publicKeys.map((p, index) => { + return { publicKey: p, balance: balances[index], index }; + }); + }, + GasTokenDecimals: 18, + SendTokenConfirmationCard: ({ + token, + destinationAddress, + destinationUser, + amount, + onComplete, + onViewBalances, + }: { + token: { + address: string; + logo: string; + decimals: number; + // For ERC721 sends + tokenId?: string; + }; + destinationUser?: { + username: string; + image: string; + }; + destinationAddress: string; + amount: BigNumber; + onComplete?: () => void; + onViewBalances?: () => void; + }) => { + return ( + + ); + }, + SendNftConfirmationCard: ({ + nft, + destinationAddress, + destinationUser, + amount, + onComplete, + }: { + nft: any; // todo: type + destinationAddress: string; + destinationUser?: { + username: string; + walletName?: string; + image: string; + }; + amount: BigNumber; + onComplete?: (txSig?: any) => void; + }) => { + return ( + + ); + }, + MaxFeeOffset: ( + token: { address: string; mint?: string }, + ethereumCtx?: any + ) => { + if (token.address === ETH_NATIVE_MINT) { + // 21,000 GWEI for a standard ETH transfer + return BigNumber.from("21000") + .mul(ethereumCtx?.feeData.maxFeePerGas!) + .add( + BigNumber.from("21000").mul( + ethereumCtx?.feeData.maxPriorityFeePerGas! + ) + ); + } + return BigNumber.from(0); + }, + }, + [Blockchain.SOLANA]: { + Name: "Solana", + GasTokenName: "SOL", + AppTokenName: "SPL", + LedgerText: "Open the Solana app", + LedgerApp: (transport: Transport) => new Solana(transport), + PublicKeyFromPath: async (ledgerWallet: LedgerApp, path: string) => { + const ledgerAddress = ( + await ledgerWallet.getAddress(path.replace("m/", "")) + ).address; + const publicKey = base58.encode(ledgerAddress as Buffer); + return publicKey; + }, + Icon: () => , + RampSupportedTokens: [ + { + title: "SOL", + icon: SOL_LOGO_URI, + subtitle: "Solana", + }, + ], + DerivationPathOptions: [ + { + path: (i: number) => legacyBip44Indexed(Blockchain.SOLANA, i), + label: "m/44/501'/x'", + }, + { + path: (i: number) => legacyBip44ChangeIndexed(Blockchain.SOLANA, i), + label: "m/44/501'/x'/0'", + }, + { + path: (i: number) => + legacyBip44ChangeIndexed(Blockchain.SOLANA, i) + "/0'", + label: "m/44/501'/x'/0'/0'", + }, + ] + // Note: We only allow importing the deprecated sollet derivation path for + // hot wallets. This UI is hidden behind a local storage flag we + // expect people to manually set, since this derivation path was only + // used by mostly technical early Solana users. + .concat( + window.localStorage.getItem("sollet") + ? [ + { + path: (i: number) => legacySolletIndexed(i), + label: "501'/0'/0/0 (Deprecated)", + }, + ] + : [] + ), + LoadBalances: async (publicKeys: string[]) => { + // TODO use Backpack configured value + const solanaMainnetRpc = + process.env.DEFAULT_SOLANA_CONNECTION_URL || DEFAULT_SOLANA_CLUSTER; + const solanaConnection = new SolanaConnection( + solanaMainnetRpc, + "confirmed" + ); + const accounts = ( + await anchor.utils.rpc.getMultipleAccounts( + solanaConnection, + publicKeys.map((p) => new PublicKey(p)) + ) + ).map((result, index) => { + return { + publicKey: publicKeys[index], + balance: result + ? BigNumber.from(result.account.lamports) + : BigNumber.from(0), + index, + }; + }); + return accounts; + }, + GasTokenDecimals: 9, + SendTokenConfirmationCard: ({ + token, + destinationAddress, + destinationUser, + amount, + onComplete, + onViewBalances, + }: { + token: { + address: string; + logo: string; + decimals: number; + tokenId?: string; + mint?: string; + }; + destinationAddress: string; + destinationUser?: { + username: string; + walletName?: string; + image: string; + }; + amount: BigNumber; + onComplete?: (txSig?: any) => void; + onViewBalances?: () => void; + }) => { + return ( + + ); + }, + MaxFeeOffset: (token: { address: string; mint?: string }) => { + if (token.mint === SOL_NATIVE_MINT) { + // When sending SOL, account for the tx fee and rent exempt minimum. + return BigNumber.from(5000).add( + BigNumber.from(NATIVE_ACCOUNT_RENT_EXEMPTION_LAMPORTS) + ); + } + return BigNumber.from(0); + }, + SendNftConfirmationCard: ({ + nft, + destinationAddress, + destinationUser, + amount, + onComplete, + }: { + nft: any; // todo: type + destinationAddress: string; + destinationUser?: { + username: string; + walletName?: string; + image: string; + }; + amount: BigNumber; + onComplete?: (txSig?: any) => void; + }) => { + return ( + + ); + }, + }, + [Blockchain.ECLIPSE]: { + Name: "Eclipse", + GasTokenName: "ECL", + AppTokenName: "SPL", + LedgerText: "Open the Solana app", + LedgerApp: (transport: Transport) => new Solana(transport), + PublicKeyFromPath: async (ledgerWallet: LedgerApp, path: string) => { + const ledgerAddress = ( + await ledgerWallet.getAddress(path.replace("m/", "")) + ).address; + const publicKey = base58.encode(ledgerAddress as Buffer); + return publicKey; + }, + Icon: () => , + RampSupportedTokens: [], + DerivationPathOptions: [ + { + path: (i: number) => legacyBip44Indexed(Blockchain.ECLIPSE, i), + label: "m/44/501'/x'", + }, + { + path: (i: number) => legacyBip44ChangeIndexed(Blockchain.ECLIPSE, i), + label: "m/44/501'/x'/0'", + }, + { + path: (i: number) => + legacyBip44ChangeIndexed(Blockchain.ECLIPSE, i) + "/0'", + label: "m/44/501'/x'/0'/0'", + }, + ], + LoadBalances: async (publicKeys: string[]) => { + // TODO use Backpack configured value + const solanaMainnetRpc = + process.env.DEFAULT_ECLIPSE_CONNECTION_URL || + "https://api.injective.eclipsenetwork.xyz:8899/"; //todo + const solanaConnection = new SolanaConnection( + solanaMainnetRpc, + "confirmed" + ); + const accounts = ( + await anchor.utils.rpc.getMultipleAccounts( + solanaConnection, + publicKeys.map((p) => new PublicKey(p)) + ) + ).map((result, index) => { + return { + publicKey: publicKeys[index], + balance: result + ? BigNumber.from(result.account.lamports) + : BigNumber.from(0), + index, + }; + }); + return accounts; + }, + GasTokenDecimals: 9, + SendTokenConfirmationCard: ({ + token, + destinationAddress, + destinationUser, + amount, + onComplete, + onViewBalances, + }: { + token: { + address: string; + logo: string; + decimals: number; + tokenId?: string; + mint?: string; + }; + destinationAddress: string; + destinationUser?: { + username: string; + walletName?: string; + image: string; + }; + amount: BigNumber; + onComplete?: (txSig?: any) => void; + onViewBalances?: () => void; + }) => { + return ( + + ); + }, + MaxFeeOffset: (_token: { address: string; mint?: string }) => { + // TODO: check with eclipse team. + return BigNumber.from(0); + }, + SendNftConfirmationCard: ({ + nft, + destinationAddress, + destinationUser, + amount, + onComplete, + }: { + nft: any; // todo: type + destinationAddress: string; + destinationUser?: { + username: string; + walletName?: string; + image: string; + }; + amount: BigNumber; + onComplete?: (txSig?: any) => void; + }) => { + return ( + + ); + }, + }, +}; diff --git a/packages/app-extension/src/components/common/Icon/index.tsx b/packages/app-extension/src/components/common/Icon/index.tsx index 597123397..6f41e6092 100644 --- a/packages/app-extension/src/components/common/Icon/index.tsx +++ b/packages/app-extension/src/components/common/Icon/index.tsx @@ -127,6 +127,10 @@ export function EthereumIconOnboarding() { ); } +export function EclipseIconOnboarding() { + return
ECLIPSE
; +} + export function SolanaIconOnboarding() { return ( ({ addressButton: { @@ -405,6 +401,8 @@ function WalletSettingsButton() { export function WalletListBlockchainSelector() { const nav = useNavigation(); + const gates = useFeatureGates(); + useEffect(() => { nav.setOptions({ headerTitle: "Select a network" }); }, [nav]); @@ -418,20 +416,19 @@ export function WalletListBlockchainSelector() { return ( - - } - text="Ethereum" - onClick={() => onClick(Blockchain.ETHEREUM)} - /> - - - } - text="Solana" - onClick={() => onClick(Blockchain.SOLANA)} - /> - + {Object.entries(BLOCKCHAIN_COMPONENTS) + .filter( + ([blockchain]) => gates.ECLIPSE || blockchain !== Blockchain.ECLIPSE + ) + .map(([blockchain, Component]) => ( + + } + text={Component.Name} + onClick={() => onClick(blockchain as Blockchain)} + /> + + ))} ); @@ -686,7 +683,10 @@ function WalletListItem({ inverted?: boolean; }) { const primaryWallets = usePrimaryWallets(); - const isPrimary = primaryWallets.find((x) => x.publicKey === wallet.publicKey) + const isPrimary = primaryWallets.find( + (x) => + x.publicKey === wallet.publicKey && x.blockchain === wallet.blockchain + ) ? true : false; const theme = useCustomTheme(); diff --git a/packages/app-extension/src/eclipse.png b/packages/app-extension/src/eclipse.png new file mode 100644 index 000000000..5e97ea06b Binary files /dev/null and b/packages/app-extension/src/eclipse.png differ diff --git a/packages/app-mobile/src/navigation/AccountSettingsNavigator.tsx b/packages/app-mobile/src/navigation/AccountSettingsNavigator.tsx index 64988ee79..9a7e877dd 100644 --- a/packages/app-mobile/src/navigation/AccountSettingsNavigator.tsx +++ b/packages/app-mobile/src/navigation/AccountSettingsNavigator.tsx @@ -15,10 +15,9 @@ import { SolanaCluster, SolanaExplorer, UI_RPC_METHOD_ETHEREUM_CHAIN_ID_UPDATE, - UI_RPC_METHOD_ETHEREUM_CONNECTION_URL_UPDATE, + UI_RPC_METHOD_CONNECTION_URL_UPDATE, UI_RPC_METHOD_SOLANA_COMMITMENT_UPDATE, - UI_RPC_METHOD_SOLANA_CONNECTION_URL_UPDATE, - UI_RPC_METHOD_SOLANA_EXPLORER_UPDATE, + UI_RPC_METHOD_EXPLORER_UPDATE, formatWalletAddress, Blockchain, } from "@coral-xyz/common"; @@ -394,8 +393,8 @@ function PreferencesSolanaCustomRpcUrl({ navigation }) { const onSubmit = async ({ url }: SolanaRPCUrlFormData) => { try { await background.request({ - method: UI_RPC_METHOD_SOLANA_CONNECTION_URL_UPDATE, - params: [url], + method: UI_RPC_METHOD_CONNECTION_URL_UPDATE, + params: [url, Blockchain.SOLANA], }); } catch (err) { console.error(err); @@ -450,8 +449,8 @@ function PreferencesSolanaConnection({ navigation }) { async (url: string) => { try { await background.request({ - method: UI_RPC_METHOD_SOLANA_CONNECTION_URL_UPDATE, - params: [url], + method: UI_RPC_METHOD_CONNECTION_URL_UPDATE, + params: [url, Blockchain.SOLANA], }); } catch (err) { Alert.alert("Something went wrong", "Try again"); @@ -543,8 +542,8 @@ export function PreferencesSolanaExplorer({ navigation }) { async (explorer: string) => { try { await background.request({ - method: UI_RPC_METHOD_SOLANA_EXPLORER_UPDATE, - params: [explorer], + method: UI_RPC_METHOD_EXPLORER_UPDATE, + params: [explorer, Blockchain.SOLANA], }); } catch (err) { Alert.alert("Something went wrong", "Try again"); @@ -610,8 +609,8 @@ async function changeEthereumNetwork( chainId?: string ) { await background.request({ - method: UI_RPC_METHOD_ETHEREUM_CONNECTION_URL_UPDATE, - params: [url], + method: UI_RPC_METHOD_CONNECTION_URL_UPDATE, + params: [url, Blockchain.ETHEREUM], }); if (!chainId) { diff --git a/packages/app-mobile/src/screens/Unlocked/EditWalletDetailScreen.tsx b/packages/app-mobile/src/screens/Unlocked/EditWalletDetailScreen.tsx index 4c6972344..f218c7e29 100644 --- a/packages/app-mobile/src/screens/Unlocked/EditWalletDetailScreen.tsx +++ b/packages/app-mobile/src/screens/Unlocked/EditWalletDetailScreen.tsx @@ -11,7 +11,7 @@ import { useBackgroundClient, useWalletPublicKeys } from "@coral-xyz/recoil"; import { YStack } from "@coral-xyz/tamagui"; import { useFocusEffect } from "@react-navigation/native"; -import { Screen } from "~components/index"; +import { Margin, Screen } from "~components/index"; import { SettingsList } from "~screens/Unlocked/Settings/components/SettingsMenuList"; import { IconCopyContent } from "~screens/Unlocked/Settings/components/SettingsRow"; @@ -25,7 +25,7 @@ export function EditWalletDetailScreen({ navigation, route }): JSX.Element { (async () => { const name = await background.request({ method: UI_RPC_METHOD_KEYNAME_READ, - params: [publicKey], + params: [publicKey, blockchain], }); setName(name); @@ -54,6 +54,7 @@ export function EditWalletDetailScreen({ navigation, route }): JSX.Element { navigation.push("edit-wallets-rename", { publicKey, name, + blockchain, }), }, "Copy Address": { diff --git a/packages/app-mobile/src/screens/Unlocked/RenameWalletScreen.tsx b/packages/app-mobile/src/screens/Unlocked/RenameWalletScreen.tsx index babf522fb..40688fd8e 100644 --- a/packages/app-mobile/src/screens/Unlocked/RenameWalletScreen.tsx +++ b/packages/app-mobile/src/screens/Unlocked/RenameWalletScreen.tsx @@ -22,7 +22,7 @@ export function RenameWalletScreen({ navigation, route }): JSX.Element { const background = useBackgroundClient(); const theme = useTheme(); - const { name, publicKey } = route.params; + const { name, publicKey, blockchain } = route.params; const [walletName, setWalletName] = useState(name); const isPrimaryDisabled = walletName.trim() === ""; @@ -30,7 +30,7 @@ export function RenameWalletScreen({ navigation, route }): JSX.Element { const handleSaveName = async () => { await background.request({ method: UI_RPC_METHOD_KEYNAME_UPDATE, - params: [publicKey, walletName], + params: [publicKey, walletName, blockchain], }); navigation.goBack(); diff --git a/packages/background/src/backend/core.ts b/packages/background/src/backend/core.ts index 051348a55..11e190b97 100644 --- a/packages/background/src/backend/core.ts +++ b/packages/background/src/backend/core.ts @@ -15,6 +15,7 @@ import { BACKEND_API_URL, BACKEND_EVENT, Blockchain, + BLOCKCHAIN_COMMON, DEFAULT_DARK_MODE, defaultPreferences, deserializeTransaction, @@ -31,12 +32,13 @@ import { NOTIFICATION_AUTO_LOCK_SETTINGS_UPDATED, NOTIFICATION_BLOCKCHAIN_KEYRING_CREATED, NOTIFICATION_BLOCKCHAIN_KEYRING_DELETED, + NOTIFICATION_CONNECTION_URL_UPDATED, NOTIFICATION_DARK_MODE_UPDATED, NOTIFICATION_DEVELOPER_MODE_UPDATED, + NOTIFICATION_ECLIPSE_ACTIVE_WALLET_UPDATED, NOTIFICATION_ETHEREUM_ACTIVE_WALLET_UPDATED, NOTIFICATION_ETHEREUM_CHAIN_ID_UPDATED, - NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED, - NOTIFICATION_ETHEREUM_EXPLORER_UPDATED, + NOTIFICATION_EXPLORER_UPDATED, NOTIFICATION_FEATURE_GATES_UPDATED, NOTIFICATION_KEY_IS_COLD_UPDATE, NOTIFICATION_KEYNAME_UPDATE, @@ -55,8 +57,6 @@ import { NOTIFICATION_NAVIGATION_URL_DID_CHANGE, NOTIFICATION_SOLANA_ACTIVE_WALLET_UPDATED, NOTIFICATION_SOLANA_COMMITMENT_UPDATED, - NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED, - NOTIFICATION_SOLANA_EXPLORER_UPDATED, NOTIFICATION_USER_ACCOUNT_AUTHENTICATED, NOTIFICATION_USER_ACCOUNT_PUBLIC_KEY_CREATED, NOTIFICATION_USER_ACCOUNT_PUBLIC_KEY_DELETED, @@ -244,45 +244,40 @@ export class Backend { return blockhash; } - async solanaConnectionUrlRead(uuid: string): Promise { - let data = await secureStore.getWalletDataForUser(uuid); - - // migrate the old default RPC value, this can be removed in future - const OLD_DEFAULT = "https://solana-rpc-nodes.projectserum.com"; - if ( - // if the current default RPC does not match the old one - SolanaCluster.DEFAULT !== OLD_DEFAULT && - // and the user's RPC URL is that old default value - data.solana?.cluster === OLD_DEFAULT - ) { - // set the user's RPC URL to the new default value - data = { - ...data, - solana: { - ...data.solana, - cluster: SolanaCluster.DEFAULT, - }, - }; - await secureStore.setWalletDataForUser(uuid, data); - } - - return (data.solana && data.solana.cluster) ?? SolanaCluster.DEFAULT; + async connectionUrlRead( + uuid: string, + blockchain: Blockchain + ): Promise { + const data = await secureStore.getWalletDataForUser(uuid); + const bcData = data[blockchain]; + const defaultPreferences = BLOCKCHAIN_COMMON[blockchain].PreferencesDefault; + return (bcData.connectionUrl ?? + bcData.cluster ?? + defaultPreferences.connectionUrl ?? + defaultPreferences.cluster) as string; } // Returns true if the url changed. - async solanaConnectionUrlUpdate(cluster: string): Promise { + public async connectionUrlUpdate( + cluster: string, + blockchain: Blockchain + ): Promise { const uuid = this.keyringStore.activeUserKeyring.uuid; const data = await secureStore.getWalletDataForUser(uuid); - if (data.solana.cluster === cluster) { + // TODO: consolidate cluster and connectionUrl fields. + // @ts-ignore + if ( + data[blockchain].cluster === cluster || + data[blockchain].connectionUrl === cluster + ) { return false; } let keyring: BlockchainKeyring | null; try { - keyring = this.keyringStore.activeUserKeyring.keyringForBlockchain( - Blockchain.SOLANA - ); + keyring = + this.keyringStore.activeUserKeyring.keyringForBlockchain(blockchain); } catch { // Blockchain may be disabled keyring = null; @@ -291,44 +286,44 @@ export class Backend { await secureStore.setWalletDataForUser(uuid, { ...data, - solana: { - ...data.solana, + [blockchain]: { + ...(data[blockchain] || {}), + // TODO: consolidate cluster and connectionUrl fields. cluster, + connectionUrl: cluster, }, }); this.events.emit(BACKEND_EVENT, { - name: NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED, + name: NOTIFICATION_CONNECTION_URL_UPDATED, data: { activeWallet, url: cluster, + blockchain, }, }); return true; } - async solanaExplorerRead(uuid: string): Promise { - const data = await secureStore.getWalletDataForUser(uuid); - return data.solana && data.solana.explorer - ? data.solana.explorer - : SolanaExplorer.DEFAULT; - } - - async solanaExplorerUpdate(explorer: string): Promise { + public async explorerUpdate( + explorer: string, + blockchain: Blockchain + ): Promise { const uuid = this.keyringStore.activeUserKeyring.uuid; const data = await secureStore.getWalletDataForUser(uuid); + data[blockchain as string] = { + ...(data[blockchain] || {}), + explorer, + }; await secureStore.setWalletDataForUser(uuid, { ...data, - solana: { - ...data.solana, - explorer, - }, }); this.events.emit(BACKEND_EVENT, { - name: NOTIFICATION_SOLANA_EXPLORER_UPDATED, + name: NOTIFICATION_EXPLORER_UPDATED, data: { explorer, + blockchain, }, }); return SUCCESS_RESPONSE; @@ -399,72 +394,6 @@ export class Backend { // Ethereum. /////////////////////////////////////////////////////////////////////////////// - async ethereumExplorerRead(uuid: string): Promise { - const data = await secureStore.getWalletDataForUser(uuid); - return data.ethereum && data.ethereum.explorer - ? data.ethereum.explorer - : EthereumExplorer.DEFAULT; - } - - async ethereumExplorerUpdate(explorer: string): Promise { - const uuid = this.keyringStore.activeUserKeyring.uuid; - const data = await secureStore.getWalletDataForUser(uuid); - await secureStore.setWalletDataForUser(uuid, { - ...data, - ethereum: { - ...(data.ethereum || {}), - explorer, - }, - }); - this.events.emit(BACKEND_EVENT, { - name: NOTIFICATION_ETHEREUM_EXPLORER_UPDATED, - data: { - explorer, - }, - }); - return SUCCESS_RESPONSE; - } - - async ethereumConnectionUrlRead(uuid: string): Promise { - const data = await secureStore.getWalletDataForUser(uuid); - return data.ethereum && data.ethereum.connectionUrl - ? data.ethereum.connectionUrl - : EthereumConnectionUrl.DEFAULT; - } - - async ethereumConnectionUrlUpdate(connectionUrl: string): Promise { - const uuid = this.keyringStore.activeUserKeyring.uuid; - const data = await secureStore.getWalletDataForUser(uuid); - - await secureStore.setWalletDataForUser(uuid, { - ...data, - ethereum: { - ...(data.ethereum || {}), - connectionUrl, - }, - }); - - let keyring: BlockchainKeyring | null; - try { - keyring = this.keyringStore.activeUserKeyring.keyringForBlockchain( - Blockchain.ETHEREUM - ); - } catch { - // Blockchain may be disabled - keyring = null; - } - const activeWallet = keyring ? keyring.getActiveWallet() : null; - - this.events.emit(BACKEND_EVENT, { - name: NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED, - data: { - activeWallet, - connectionUrl, - }, - }); - return SUCCESS_RESPONSE; - } - async ethereumChainIdRead(): Promise { const data = await secureStore.getWalletDataForUser( this.keyringStore.activeUserKeyring.uuid @@ -594,8 +523,14 @@ export class Backend { name: NOTIFICATION_KEYRING_STORE_CREATED, data: { blockchainActiveWallets: await this.blockchainActiveWallets(), - ethereumConnectionUrl: await this.ethereumConnectionUrlRead(uuid), - solanaConnectionUrl: await this.solanaConnectionUrlRead(uuid), + ethereumConnectionUrl: await this.connectionUrlRead( + uuid, + Blockchain.ETHEREUM + ), + solanaConnectionUrl: await this.connectionUrlRead( + uuid, + Blockchain.SOLANA + ), solanaCommitment: await this.solanaCommitmentRead(uuid), preferences: await this.preferencesRead(uuid), }, @@ -706,12 +641,14 @@ export class Backend { await this.keyringStore.tryUnlock(userInfo); const activeUser = (await secureStore.getUserData()).activeUser; const blockchainActiveWallets = await this.blockchainActiveWallets(); - const ethereumConnectionUrl = await this.ethereumConnectionUrlRead( - userInfo.uuid + const ethereumConnectionUrl = await this.connectionUrlRead( + userInfo.uuid, + Blockchain.ETHEREUM ); const ethereumChainId = await this.ethereumChainIdRead(); - const solanaConnectionUrl = await this.solanaConnectionUrlRead( - userInfo.uuid + const solanaConnectionUrl = await this.connectionUrlRead( + userInfo.uuid, + Blockchain.SOLANA ); const solanaCommitment = await this.solanaCommitmentRead(userInfo.uuid); @@ -784,7 +721,10 @@ export class Backend { for (const publicKey of publicKeys) { namedPublicKeys[blockchain][keyring].push({ publicKey, - name: await secureStore.getKeyname(publicKey), + name: await secureStore.getKeyname( + publicKey, + blockchain as Blockchain + ), isCold: await secureStore.getIsCold(publicKey), }); } @@ -838,6 +778,14 @@ export class Backend { activeWallets: await this.activeWallets(), }, }); + } else if (blockchain === Blockchain.ECLIPSE) { + this.events.emit(BACKEND_EVENT, { + name: NOTIFICATION_ECLIPSE_ACTIVE_WALLET_UPDATED, + data: { + activeWallet: newActivePublicKey, + activeWallets: await this.activeWallets(), + }, + }); } else if (blockchain === Blockchain.ETHEREUM) { this.events.emit(BACKEND_EVENT, { name: NOTIFICATION_ETHEREUM_ACTIVE_WALLET_UPDATED, @@ -992,8 +940,11 @@ export class Backend { * Read the name associated with a public key in the local store. * @param publicKey - public key to read the name for */ - async keynameRead(publicKey: string): Promise { - return await secureStore.getKeyname(publicKey); + async keynameRead( + publicKey: string, + blockchain: Blockchain + ): Promise { + return await secureStore.getKeyname(publicKey, blockchain); } /** @@ -1001,8 +952,12 @@ export class Backend { * @param publicKey - public key to update the name for * @param newName - new name to associate with the public key */ - async keynameUpdate(publicKey: string, newName: string): Promise { - await secureStore.setKeyname(publicKey, newName); + async keynameUpdate( + publicKey: string, + newName: string, + blockchain: Blockchain + ): Promise { + await secureStore.setKeyname(publicKey, newName, blockchain); this.events.emit(BACKEND_EVENT, { name: NOTIFICATION_KEYNAME_UPDATE, data: { diff --git a/packages/background/src/backend/ethereum-connection.ts b/packages/background/src/backend/ethereum-connection.ts index 418bf51ce..c9675798b 100644 --- a/packages/background/src/backend/ethereum-connection.ts +++ b/packages/background/src/backend/ethereum-connection.ts @@ -6,9 +6,9 @@ import { getLogger, NOTIFICATION_BLOCKCHAIN_KEYRING_CREATED, NOTIFICATION_BLOCKCHAIN_KEYRING_DELETED, + NOTIFICATION_CONNECTION_URL_UPDATED, NOTIFICATION_ETHEREUM_ACTIVE_WALLET_UPDATED, NOTIFICATION_ETHEREUM_CHAIN_ID_UPDATED, - NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED, NOTIFICATION_ETHEREUM_FEE_DATA_DID_UPDATE, NOTIFICATION_ETHEREUM_TOKENS_DID_UPDATE, NOTIFICATION_KEYRING_STORE_CREATED, @@ -70,7 +70,7 @@ export class EthereumConnectionBackend { case NOTIFICATION_ETHEREUM_ACTIVE_WALLET_UPDATED: handleActiveWalletUpdated(notif); break; - case NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED: + case NOTIFICATION_CONNECTION_URL_UPDATED: handleConnectionUrlUpdated(notif); break; case NOTIFICATION_ETHEREUM_CHAIN_ID_UPDATED: @@ -119,7 +119,12 @@ export class EthereumConnectionBackend { }; const handleConnectionUrlUpdated = (notif: Notification) => { - const { connectionUrl } = notif.data; + const { connectionUrl, blockchain } = notif.data; + + if (blockchain !== Blockchain.ETHEREUM) { + return; + } + this.provider = new ethers.providers.JsonRpcProvider(connectionUrl); this.url = connectionUrl; }; diff --git a/packages/background/src/backend/solana-connection.ts b/packages/background/src/backend/solana-connection.ts index f0dc418ad..8f21a21a2 100644 --- a/packages/background/src/backend/solana-connection.ts +++ b/packages/background/src/backend/solana-connection.ts @@ -17,11 +17,11 @@ import { getLogger, NOTIFICATION_BLOCKCHAIN_KEYRING_CREATED, NOTIFICATION_BLOCKCHAIN_KEYRING_DELETED, + NOTIFICATION_CONNECTION_URL_UPDATED, NOTIFICATION_KEYRING_STORE_CREATED, NOTIFICATION_KEYRING_STORE_LOCKED, NOTIFICATION_KEYRING_STORE_UNLOCKED, NOTIFICATION_SOLANA_ACTIVE_WALLET_UPDATED, - NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED, NOTIFICATION_SOLANA_SPL_TOKENS_DID_UPDATE, } from "@coral-xyz/common"; import type { @@ -154,7 +154,7 @@ export class SolanaConnectionBackend { case NOTIFICATION_SOLANA_ACTIVE_WALLET_UPDATED: handleActiveWalletUpdated(notif); break; - case NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED: + case NOTIFICATION_CONNECTION_URL_UPDATED: handleConnectionUrlUpdated(notif); break; case NOTIFICATION_BLOCKCHAIN_KEYRING_CREATED: @@ -199,7 +199,12 @@ export class SolanaConnectionBackend { }; const handleConnectionUrlUpdated = (notif: Notification) => { - const { activeWallet, url } = notif.data; + const { activeWallet, url, blockchain } = notif.data; + + if (blockchain !== Blockchain.SOLANA) { + return; + } + this.connection = new Connection(url, this.connection!.commitment); this.url = url; // activeWallet can be null if the blockchain is disabled, in that case diff --git a/packages/background/src/frontend/server-injected.ts b/packages/background/src/frontend/server-injected.ts index 92f7aaf16..e5e87bc4b 100644 --- a/packages/background/src/frontend/server-injected.ts +++ b/packages/background/src/frontend/server-injected.ts @@ -27,14 +27,13 @@ import { EthereumChainIds, EthereumConnectionUrl, getLogger, + NOTIFICATION_CONNECTION_URL_UPDATED, NOTIFICATION_ETHEREUM_ACTIVE_WALLET_UPDATED, NOTIFICATION_ETHEREUM_CHAIN_ID_UPDATED, NOTIFICATION_ETHEREUM_CONNECTED, - NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED, NOTIFICATION_ETHEREUM_DISCONNECTED, NOTIFICATION_SOLANA_ACTIVE_WALLET_UPDATED, NOTIFICATION_SOLANA_CONNECTED, - NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED, NOTIFICATION_SOLANA_DISCONNECTED, openApprovalPopupWindow, openApproveAllTransactionsPopupWindow, @@ -108,9 +107,6 @@ export function start(cfg: Config, events: EventEmitter, b: Backend): Handle { case NOTIFICATION_ETHEREUM_ACTIVE_WALLET_UPDATED: ethereumNotificationsInjected.sendMessageActiveTab(notification); break; - case NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED: - ethereumNotificationsInjected.sendMessageActiveTab(notification); - break; case NOTIFICATION_ETHEREUM_CHAIN_ID_UPDATED: ethereumNotificationsInjected.sendMessageActiveTab(notification); break; @@ -123,7 +119,9 @@ export function start(cfg: Config, events: EventEmitter, b: Backend): Handle { case NOTIFICATION_SOLANA_ACTIVE_WALLET_UPDATED: solanaNotificationsInjected.sendMessageActiveTab(notification); break; - case NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED: + case NOTIFICATION_CONNECTION_URL_UPDATED: + // TODO: generalize this some more. + ethereumNotificationsInjected.sendMessageActiveTab(notification); solanaNotificationsInjected.sendMessageActiveTab(notification); break; default: @@ -309,8 +307,9 @@ async function handleConnect( const user = await ctx.backend.userRead(); const publicKey = ctx.backend.activeWalletForBlockchain(blockchain); if (blockchain === Blockchain.ETHEREUM) { - const connectionUrl = await ctx.backend.ethereumConnectionUrlRead( - user.uuid + const connectionUrl = await ctx.backend.connectionUrlRead( + user.uuid, + Blockchain.ETHEREUM ); const chainId = await ctx.backend.ethereumChainIdRead(); const data = { @@ -324,8 +323,9 @@ async function handleConnect( }); return [data]; } else if (blockchain === Blockchain.SOLANA) { - const connectionUrl = await ctx.backend.solanaConnectionUrlRead( - user.uuid + const connectionUrl = await ctx.backend.connectionUrlRead( + user.uuid, + Blockchain.SOLANA ); const data = { publicKey, connectionUrl }; ctx.events.emit(BACKEND_EVENT, { @@ -642,7 +642,7 @@ async function handleEthereumSwitchChain( try { // Only sign if the user clicked approve. if (didApprove) { - await ctx.backend.ethereumConnectionUrlUpdate(url); + await ctx.backend.connectionUrlUpdate(url, Blockchain.ETHEREUM); resp = await ctx.backend.ethereumChainIdUpdate(chainId); } } catch (err) { diff --git a/packages/background/src/frontend/server-ui.ts b/packages/background/src/frontend/server-ui.ts index 33c165a91..14acff04b 100644 --- a/packages/background/src/frontend/server-ui.ts +++ b/packages/background/src/frontend/server-ui.ts @@ -25,15 +25,14 @@ import { UI_RPC_METHOD_APPROVED_ORIGINS_READ, UI_RPC_METHOD_APPROVED_ORIGINS_UPDATE, UI_RPC_METHOD_BLOCKCHAIN_KEYRINGS_ADD, + UI_RPC_METHOD_CONNECTION_URL_READ, + UI_RPC_METHOD_CONNECTION_URL_UPDATE, UI_RPC_METHOD_ETHEREUM_CHAIN_ID_READ, UI_RPC_METHOD_ETHEREUM_CHAIN_ID_UPDATE, - UI_RPC_METHOD_ETHEREUM_CONNECTION_URL_READ, - UI_RPC_METHOD_ETHEREUM_CONNECTION_URL_UPDATE, - UI_RPC_METHOD_ETHEREUM_EXPLORER_READ, - UI_RPC_METHOD_ETHEREUM_EXPLORER_UPDATE, UI_RPC_METHOD_ETHEREUM_SIGN_AND_SEND_TRANSACTION, UI_RPC_METHOD_ETHEREUM_SIGN_MESSAGE, UI_RPC_METHOD_ETHEREUM_SIGN_TRANSACTION, + UI_RPC_METHOD_EXPLORER_UPDATE, UI_RPC_METHOD_FIND_SERVER_PUBLIC_KEY_CONFLICTS, UI_RPC_METHOD_FIND_WALLET_DESCRIPTOR, UI_RPC_METHOD_GET_FEATURE_GATES, @@ -87,10 +86,6 @@ import { UI_RPC_METHOD_SIGN_MESSAGE_FOR_PUBLIC_KEY, UI_RPC_METHOD_SOLANA_COMMITMENT_READ, UI_RPC_METHOD_SOLANA_COMMITMENT_UPDATE, - UI_RPC_METHOD_SOLANA_CONNECTION_URL_READ, - UI_RPC_METHOD_SOLANA_CONNECTION_URL_UPDATE, - UI_RPC_METHOD_SOLANA_EXPLORER_READ, - UI_RPC_METHOD_SOLANA_EXPLORER_UPDATE, UI_RPC_METHOD_SOLANA_SIGN_ALL_TRANSACTIONS, UI_RPC_METHOD_SOLANA_SIGN_AND_SEND_TRANSACTION, UI_RPC_METHOD_SOLANA_SIGN_MESSAGE, @@ -318,9 +313,9 @@ async function handle( // Nicknames for keys. // case UI_RPC_METHOD_KEYNAME_READ: - return await handleKeynameRead(ctx, params[0]); + return await handleKeynameRead(ctx, params[0], params[1]); case UI_RPC_METHOD_KEYNAME_UPDATE: - return await handleKeynameUpdate(ctx, params[0], params[1]); + return await handleKeynameUpdate(ctx, params[0], params[1], params[2]); // // User. // @@ -391,25 +386,15 @@ async function handle( return await handleSolanaCommitmentRead(ctx, params[0]); case UI_RPC_METHOD_SOLANA_COMMITMENT_UPDATE: return await handleSolanaCommitmentUpdate(ctx, params[0]); - case UI_RPC_METHOD_SOLANA_EXPLORER_READ: - return await handleSolanaExplorerRead(ctx, params[0]); - case UI_RPC_METHOD_SOLANA_EXPLORER_UPDATE: - return await handleSolanaExplorerUpdate(ctx, params[0]); - case UI_RPC_METHOD_SOLANA_CONNECTION_URL_READ: - return await handleSolanaConnectionUrlRead(ctx, params[0]); - case UI_RPC_METHOD_SOLANA_CONNECTION_URL_UPDATE: - return await handleSolanaConnectionUrlUpdate(ctx, params[0]); + case UI_RPC_METHOD_EXPLORER_UPDATE: + return await handleExplorerUpdate(ctx, params[0], params[1]); + case UI_RPC_METHOD_CONNECTION_URL_READ: + return await handleConnectionUrlRead(ctx, params[0], params[1]); + case UI_RPC_METHOD_CONNECTION_URL_UPDATE: + return await handleConnectionUrlUpdate(ctx, params[0], params[1]); // // Ethereum // - case UI_RPC_METHOD_ETHEREUM_EXPLORER_READ: - return await handleEthereumExplorerRead(ctx, params[0]); - case UI_RPC_METHOD_ETHEREUM_EXPLORER_UPDATE: - return await handleEthereumExplorerUpdate(ctx, params[0]); - case UI_RPC_METHOD_ETHEREUM_CONNECTION_URL_READ: - return await handleEthereumConnectionUrlRead(ctx, params[0]); - case UI_RPC_METHOD_ETHEREUM_CONNECTION_URL_UPDATE: - return await handleEthereumConnectionUrlUpdate(ctx, params[0]); case UI_RPC_METHOD_ETHEREUM_CHAIN_ID_READ: return await handleEthereumChainIdRead(ctx); case UI_RPC_METHOD_ETHEREUM_CHAIN_ID_UPDATE: @@ -549,18 +534,20 @@ async function handleKeyIsColdUpdate( async function handleKeynameRead( ctx: Context, - pubkey: string + pubkey: string, + blockchain: Blockchain ): Promise> { - const resp = await ctx.backend.keynameRead(pubkey); + const resp = await ctx.backend.keynameRead(pubkey, blockchain); return [resp]; } async function handleKeynameUpdate( ctx: Context, pubkey: string, - newName: string + newName: string, + blockchain: Blockchain ): Promise> { - const resp = await ctx.backend.keynameUpdate(pubkey, newName); + const resp = await ctx.backend.keynameUpdate(pubkey, newName, blockchain); return [resp]; } @@ -878,19 +865,27 @@ async function handleAggregateWalletsUpdate( return [resp]; } -async function handleSolanaConnectionUrlRead( +async function handleConnectionUrlRead( ctx: Context, - uuid: string + uuid: string, + blockchain: string ): Promise> { - const resp = await ctx.backend.solanaConnectionUrlRead(uuid); + const resp = await ctx.backend.connectionUrlRead( + uuid, + blockchain as Blockchain + ); return [resp]; } -async function handleSolanaConnectionUrlUpdate( +async function handleConnectionUrlUpdate( ctx: Context, - url: string + url: string, + blockchain: string ): Promise> { - const didChange = await ctx.backend.solanaConnectionUrlUpdate(url); + const didChange = await ctx.backend.connectionUrlUpdate( + url, + blockchain as Blockchain + ); return [didChange]; } @@ -912,19 +907,12 @@ async function handleSolanaCommitmentUpdate( return [resp]; } -async function handleSolanaExplorerRead( +async function handleExplorerUpdate( ctx: Context, - uuid: string -): Promise> { - const resp = await ctx.backend.solanaExplorerRead(uuid); - return [resp]; -} - -async function handleSolanaExplorerUpdate( - ctx: Context, - url: string + url: string, + blockchain: string ): Promise> { - const resp = await ctx.backend.solanaExplorerUpdate(url); + const resp = await ctx.backend.explorerUpdate(url, blockchain as Blockchain); return [resp]; } @@ -973,38 +961,6 @@ async function handleSolanaSignAndSendTransaction( return [resp]; } -async function handleEthereumExplorerRead( - ctx: Context, - uuid: string -): Promise> { - const resp = await ctx.backend.ethereumExplorerRead(uuid); - return [resp]; -} - -async function handleEthereumExplorerUpdate( - ctx: Context, - url: string -): Promise> { - const resp = await ctx.backend.ethereumExplorerUpdate(url); - return [resp]; -} - -async function handleEthereumConnectionUrlRead( - ctx: Context, - uuid: string -): Promise> { - const resp = await ctx.backend.ethereumConnectionUrlRead(uuid); - return [resp]; -} - -async function handleEthereumConnectionUrlUpdate( - ctx: Context, - url: string -): Promise> { - const resp = await ctx.backend.ethereumConnectionUrlUpdate(url); - return [resp]; -} - async function handleEthereumChainIdRead( ctx: Context ): Promise> { diff --git a/packages/common/src/blockchains/index.ts b/packages/common/src/blockchains/index.ts new file mode 100644 index 000000000..1ad250a4f --- /dev/null +++ b/packages/common/src/blockchains/index.ts @@ -0,0 +1,35 @@ +import { EthereumConnectionUrl } from "../ethereum/connection-url"; +import { EthereumExplorer } from "../ethereum/explorer"; +import { SolanaCluster } from "../solana/cluster"; +import { SolanaExplorer } from "../solana/explorer"; +import type { EclipseData, EthereumData, SolanaData } from "../types"; +import { Blockchain } from "../types"; + +export const BLOCKCHAIN_COMMON: Record< + Blockchain, + { + PreferencesDefault: SolanaData | EclipseData | EthereumData; + } +> = { + [Blockchain.ETHEREUM]: { + PreferencesDefault: { + explorer: EthereumExplorer.DEFAULT, + connectionUrl: EthereumConnectionUrl.DEFAULT, + chainId: "", // TODO(peter) default chainId? + }, + }, + [Blockchain.SOLANA]: { + PreferencesDefault: { + explorer: SolanaExplorer.DEFAULT, + cluster: SolanaCluster.DEFAULT, + commitment: "confirmed", + }, + }, + [Blockchain.ECLIPSE]: { + PreferencesDefault: { + explorer: "https://api.injective.eclipsenetwork.xyz:8899/", + cluster: SolanaCluster.DEFAULT, + commitment: "confirmed", + }, + }, +}; diff --git a/packages/common/src/constants.ts b/packages/common/src/constants.ts index d3ad0fad5..534f3ff0e 100644 --- a/packages/common/src/constants.ts +++ b/packages/common/src/constants.ts @@ -66,12 +66,10 @@ export const PLUGIN_NOTIFICATION_MOUNT = "plugin-notification-mount"; export const PLUGIN_NOTIFICATION_UPDATE_METADATA = "plugin-notification-update-metadata"; export const PLUGIN_NOTIFICATION_UNMOUNT = "plugin-notification-unmount"; -export const PLUGIN_NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED = - "plugin-notification-solana-connection-url-updated"; export const PLUGIN_NOTIFICATION_SOLANA_PUBLIC_KEY_UPDATED = "plugin-notification-solana-public-key-updated"; -export const PLUGIN_NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED = - "plugin-notification-ethereum-connection-url-updated"; +export const PLUGIN_NOTIFICATION_CONNECTION_URL_UPDATED = + "plugin-notification-connection-url-updated"; export const PLUGIN_NOTIFICATION_ETHEREUM_PUBLIC_KEY_UPDATED = "plugin-notification-ethereum-public-key-updated"; @@ -231,14 +229,11 @@ export const UI_RPC_METHOD_SOLANA_COMMITMENT_READ = "ui-rpc-method-solana-commitment-read"; export const UI_RPC_METHOD_SOLANA_COMMITMENT_UPDATE = "ui-rpc-method-solana-commitment-update"; -export const UI_RPC_METHOD_SOLANA_CONNECTION_URL_READ = - "ui-rpc-method-solana-connection-url-read"; -export const UI_RPC_METHOD_SOLANA_CONNECTION_URL_UPDATE = - "ui-rpc-method-solana-connection-url-update"; -export const UI_RPC_METHOD_SOLANA_EXPLORER_READ = - "ui-rpc-method-solana-explorer-read"; -export const UI_RPC_METHOD_SOLANA_EXPLORER_UPDATE = - "ui-rpc-method-solana-explorer-update"; +export const UI_RPC_METHOD_CONNECTION_URL_READ = + "ui-rpc-method-connection-url-read"; +export const UI_RPC_METHOD_CONNECTION_URL_UPDATE = + "ui-rpc-method-connection-url-update"; +export const UI_RPC_METHOD_EXPLORER_UPDATE = "ui-rpc-method-explorer-update"; export const UI_RPC_METHOD_SOLANA_SIGN_ALL_TRANSACTIONS = "ui-rpc-method-solana-sign-all-txs"; export const UI_RPC_METHOD_SOLANA_SIGN_AND_SEND_TRANSACTION = @@ -253,14 +248,6 @@ export const UI_RPC_METHOD_ETHEREUM_CHAIN_ID_READ = "ui-rpc-method-ethereum-chain-id-read"; export const UI_RPC_METHOD_ETHEREUM_CHAIN_ID_UPDATE = "ui-rpc-method-ethereum-chain-id-update"; -export const UI_RPC_METHOD_ETHEREUM_CONNECTION_URL_READ = - "ui-rpc-method-ethereum-connection-url-read"; -export const UI_RPC_METHOD_ETHEREUM_CONNECTION_URL_UPDATE = - "ui-rpc-method-ethereum-connection-url-update"; -export const UI_RPC_METHOD_ETHEREUM_EXPLORER_READ = - "ui-rpc-method-ethereum-explorer-read"; -export const UI_RPC_METHOD_ETHEREUM_EXPLORER_UPDATE = - "ui-rpc-method-ethereum-explorer-update"; export const UI_RPC_METHOD_ETHEREUM_SIGN_AND_SEND_TRANSACTION = "ui-rpc-method-ethereum-sign-and-send-tx"; export const UI_RPC_METHOD_ETHEREUM_SIGN_MESSAGE = @@ -334,28 +321,25 @@ export const NOTIFICATION_ETHEREUM_CHAIN_ID_UPDATED = "notification-ethereum-chain-id-updated"; export const NOTIFICATION_ETHEREUM_CONNECTED = "notification-ethereum-connected"; -export const NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED = - "notification-ethereum-connection-url-updated"; export const NOTIFICATION_ETHEREUM_DISCONNECTED = "notification-ethereum-disconnected"; -export const NOTIFICATION_ETHEREUM_EXPLORER_UPDATED = - "notification-ethereum-explorer-updated"; export const NOTIFICATION_ETHEREUM_FEE_DATA_DID_UPDATE = "notification-ethereum-fee-data-did-update"; export const NOTIFICATION_ETHEREUM_TOKENS_DID_UPDATE = "notification-ethereum-tokens-did-update"; +export const NOTIFICATION_ECLIPSE_ACTIVE_WALLET_UPDATED = + "notification-keyring-eclipse-active-wallet-updated"; // Solana specific notifications export const NOTIFICATION_SOLANA_ACTIVE_WALLET_UPDATED = "notification-keyring-solana-active-wallet-updated"; export const NOTIFICATION_SOLANA_COMMITMENT_UPDATED = "notification-solana-commitment-updated"; export const NOTIFICATION_SOLANA_CONNECTED = "notification-solana-connected"; -export const NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED = - "notification-solana-connection-url-updated"; +export const NOTIFICATION_CONNECTION_URL_UPDATED = + "notification-connection-url-updated"; export const NOTIFICATION_SOLANA_DISCONNECTED = "notification-solana-disconnected"; -export const NOTIFICATION_SOLANA_EXPLORER_UPDATED = - "notification-solana-explorer-updated"; +export const NOTIFICATION_EXPLORER_UPDATED = "notification-explorer-updated"; export const NOTIFICATION_SOLANA_SPL_TOKENS_DID_UPDATE = "notification-solana-spl-tokens-did-update"; export const NOTIFICATION_USER_ACCOUNT_AUTHENTICATED = diff --git a/packages/common/src/crypto.ts b/packages/common/src/crypto.ts index b762d341c..d00d85d86 100644 --- a/packages/common/src/crypto.ts +++ b/packages/common/src/crypto.ts @@ -9,6 +9,7 @@ export const HARDENING = 0x80000000; export const blockchainCoinType = { [Blockchain.ETHEREUM]: 60, [Blockchain.SOLANA]: 501, + [Blockchain.ECLIPSE]: 501, }; export const getCoinType = (blockchain: Blockchain) => { diff --git a/packages/common/src/feature-gates.ts b/packages/common/src/feature-gates.ts index cde033349..d2408d907 100644 --- a/packages/common/src/feature-gates.ts +++ b/packages/common/src/feature-gates.ts @@ -13,6 +13,7 @@ export const DEFAULT_FEATURE_GATES = { STICKER_ENABLED: false, STRIPE_ENABLED: false, SWAP_FEES_ENABLED: false, + ECLIPSE: false, } as const; export type FEATURE_GATES_MAP = typeof DEFAULT_FEATURE_GATES; diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 2d08d3815..5b0bba57f 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -17,6 +17,7 @@ import type { export * from "./api"; export * from "./apollo"; +export * from "./blockchains"; export * from "./browser"; export * from "./channel"; export * from "./constants"; diff --git a/packages/common/src/messages/toServer.ts b/packages/common/src/messages/toServer.ts index ec8de084b..894310f6c 100644 --- a/packages/common/src/messages/toServer.ts +++ b/packages/common/src/messages/toServer.ts @@ -1,6 +1,11 @@ import type { Blockchain } from "../types"; -import type { CHAT_MESSAGES, DELETE_MESSAGE,SUBSCRIBE, UNSUBSCRIBE } from "./fromServer"; +import type { + CHAT_MESSAGES, + DELETE_MESSAGE, + SUBSCRIBE, + UNSUBSCRIBE, +} from "./fromServer"; import { BarterOffers } from "./index"; export type SubscriptionType = "collection" | "individual"; @@ -99,6 +104,7 @@ export interface RemoteUserData { remoteRequested: boolean; username: string; searchedSolPubKey?: string; // Returns a public key if it is searched for + searchedEclipsePubKey?: string; searchedEthPubKey?: string; public_keys: { blockchain: Blockchain; diff --git a/packages/common/src/plugin.ts b/packages/common/src/plugin.ts index 66e82ea0c..258784121 100644 --- a/packages/common/src/plugin.ts +++ b/packages/common/src/plugin.ts @@ -13,10 +13,9 @@ import { ETHEREUM_RPC_METHOD_SIGN_MESSAGE as PLUGIN_ETHEREUM_RPC_METHOD_SIGN_MESSAGE, ETHEREUM_RPC_METHOD_SIGN_TX as PLUGIN_ETHEREUM_RPC_METHOD_SIGN_TX, PLUGIN_NOTIFICATION_CONNECT, - PLUGIN_NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED, + PLUGIN_NOTIFICATION_CONNECTION_URL_UPDATED, PLUGIN_NOTIFICATION_ETHEREUM_PUBLIC_KEY_UPDATED, PLUGIN_NOTIFICATION_MOUNT, - PLUGIN_NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED, PLUGIN_NOTIFICATION_SOLANA_PUBLIC_KEY_UPDATED, PLUGIN_NOTIFICATION_UNMOUNT, PLUGIN_NOTIFICATION_UPDATE_METADATA, @@ -316,9 +315,10 @@ export class Plugin { const event = { type: CHANNEL_PLUGIN_NOTIFICATION, detail: { - name: PLUGIN_NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED, + name: PLUGIN_NOTIFICATION_CONNECTION_URL_UPDATED, data: { url, + blockchain: Blockchain.SOLANA, }, }, }; @@ -354,9 +354,10 @@ export class Plugin { const event = { type: CHANNEL_PLUGIN_NOTIFICATION, detail: { - name: PLUGIN_NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED, + name: PLUGIN_NOTIFICATION_CONNECTION_URL_UPDATED, data: { url, + blockchain: Blockchain.ETHEREUM, }, }, }; diff --git a/packages/common/src/preferences.ts b/packages/common/src/preferences.ts index 612c668bf..c2a4c2efb 100644 --- a/packages/common/src/preferences.ts +++ b/packages/common/src/preferences.ts @@ -1,6 +1,6 @@ -import { EthereumConnectionUrl, EthereumExplorer } from "./ethereum"; -import { SolanaCluster, SolanaExplorer } from "./solana"; -import type { Blockchain, Preferences } from "./types"; +import { BLOCKCHAIN_COMMON } from "./blockchains"; +import type { Preferences } from "./types"; +import { Blockchain } from "./types"; export const DEFAULT_DARK_MODE = false; export const DEFAULT_DEVELOPER_MODE = false; @@ -8,6 +8,15 @@ export const DEFAULT_AGGREGATE_WALLETS = false; export const DEFAULT_AUTO_LOCK_INTERVAL_SECS = 15 * 60; export function defaultPreferences(): Preferences { + const data = Object.fromEntries( + new Map( + Object.keys(BLOCKCHAIN_COMMON).map((blockchain) => [ + blockchain, + BLOCKCHAIN_COMMON[blockchain].PreferencesDefault, + ]) + ) + ); + // @ts-ignore return { autoLockSettings: { seconds: DEFAULT_AUTO_LOCK_INTERVAL_SECS, @@ -17,15 +26,6 @@ export function defaultPreferences(): Preferences { darkMode: DEFAULT_DARK_MODE, developerMode: DEFAULT_DEVELOPER_MODE, aggregateWallets: DEFAULT_AGGREGATE_WALLETS, - solana: { - explorer: SolanaExplorer.DEFAULT, - cluster: SolanaCluster.DEFAULT, - commitment: "confirmed", - }, - ethereum: { - explorer: EthereumExplorer.DEFAULT, - connectionUrl: EthereumConnectionUrl.DEFAULT, - chainId: "", // TODO(peter) default chainId? - }, - }; + ...data, + } as Preferences; } diff --git a/packages/common/src/types.ts b/packages/common/src/types.ts index 679ba5a17..03ff969cf 100644 --- a/packages/common/src/types.ts +++ b/packages/common/src/types.ts @@ -42,7 +42,11 @@ export type RpcResponseData = { }; export enum Blockchain { + // SVM. SOLANA = "solana", + ECLIPSE = "eclipse", + + // EVM. ETHEREUM = "ethereum", } @@ -212,7 +216,9 @@ export type Preferences = { darkMode: boolean; developerMode: boolean; aggregateWallets: boolean; + // TODO: this doesn't populate automatically from the common list. solana: SolanaData; + eclipse: EclipseData; ethereum: EthereumData; } & DeprecatedWalletDataDoNotUse; @@ -228,15 +234,19 @@ export type DeprecatedWalletDataDoNotUse = { autoLockSecs?: number; // Used in releases <=0.4.0 }; -type SolanaData = { +export type SolanaData = { explorer: string; + connectionUrl?: string; // TODO: combine with EthereumData. commitment: Commitment; cluster: string; }; -type EthereumData = { +export type EclipseData = SolanaData; + +export type EthereumData = { explorer: string; connectionUrl: string; + cluster?: string; chainId: string; }; diff --git a/packages/provider-core/src/provider-ethereum-xnft.ts b/packages/provider-core/src/provider-ethereum-xnft.ts index fab0e8a56..841659e73 100644 --- a/packages/provider-core/src/provider-ethereum-xnft.ts +++ b/packages/provider-core/src/provider-ethereum-xnft.ts @@ -8,7 +8,7 @@ import { getLogger, InjectedRequestManager, PLUGIN_NOTIFICATION_CONNECT, - PLUGIN_NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED, + PLUGIN_NOTIFICATION_CONNECTION_URL_UPDATED, PLUGIN_NOTIFICATION_ETHEREUM_PUBLIC_KEY_UPDATED, } from "@coral-xyz/common"; import type { UnsignedTransaction } from "@ethersproject/transactions"; @@ -114,7 +114,7 @@ export class ProviderEthereumXnftInjection extends PrivateEventEmitter { case PLUGIN_NOTIFICATION_CONNECT: this.#handleConnect(event); break; - case PLUGIN_NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED: + case PLUGIN_NOTIFICATION_CONNECTION_URL_UPDATED: this.#handleConnectionUrlUpdated(event); break; case PLUGIN_NOTIFICATION_ETHEREUM_PUBLIC_KEY_UPDATED: @@ -135,11 +135,16 @@ export class ProviderEthereumXnftInjection extends PrivateEventEmitter { } #handleConnectionUrlUpdated(event: Event) { - const { connectionUrl } = event.data.detail.data; - this.#connectionUrl = connectionUrl; + const { url, blockchain } = event.data.detail.data; + + if (blockchain !== Blockchain.ETHEREUM) { + return; + } + + this.#connectionUrl = url; this.#provider = new BackgroundEthereumProvider( this.#connectionRequestManager, - connectionUrl + url ); this.emit("connectionUpdate", event.data.detail); } diff --git a/packages/provider-core/src/provider-ethereum.ts b/packages/provider-core/src/provider-ethereum.ts index b35d95b92..f5de1ae36 100644 --- a/packages/provider-core/src/provider-ethereum.ts +++ b/packages/provider-core/src/provider-ethereum.ts @@ -1,6 +1,7 @@ import type { Event } from "@coral-xyz/common"; import { BackgroundEthereumProvider, + Blockchain, CHANNEL_ETHEREUM_CONNECTION_INJECTED_REQUEST, CHANNEL_ETHEREUM_CONNECTION_INJECTED_RESPONSE, CHANNEL_ETHEREUM_NOTIFICATION, @@ -10,10 +11,10 @@ import { ETHEREUM_RPC_METHOD_SWITCH_CHAIN, getLogger, InjectedRequestManager, + NOTIFICATION_CONNECTION_URL_UPDATED, NOTIFICATION_ETHEREUM_ACTIVE_WALLET_UPDATED, NOTIFICATION_ETHEREUM_CHAIN_ID_UPDATED, NOTIFICATION_ETHEREUM_CONNECTED, - NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED, NOTIFICATION_ETHEREUM_DISCONNECTED, } from "@coral-xyz/common"; import { ethErrors } from "eth-rpc-errors"; @@ -357,7 +358,7 @@ export class ProviderEthereumInjection extends EventEmitter { case NOTIFICATION_ETHEREUM_DISCONNECTED: this._handleNotificationDisconnected(); break; - case NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED: + case NOTIFICATION_CONNECTION_URL_UPDATED: this._handleNotificationConnectionUrlUpdated(event); break; case NOTIFICATION_ETHEREUM_CHAIN_ID_UPDATED: @@ -410,7 +411,12 @@ export class ProviderEthereumInjection extends EventEmitter { * of the chainId/network if the change was to a different network RPC. */ _handleNotificationConnectionUrlUpdated = async (event: any) => { - const { connectionUrl } = event.data.detail.data; + const { connectionUrl, blockchain } = event.data.detail.data; + + if (blockchain !== Blockchain.ETHEREUM) { + return; + } + this.provider = new BackgroundEthereumProvider( this.connectionRequestManager, connectionUrl diff --git a/packages/provider-core/src/provider-solana-new.ts b/packages/provider-core/src/provider-solana-new.ts index 579d91936..1906d506c 100644 --- a/packages/provider-core/src/provider-solana-new.ts +++ b/packages/provider-core/src/provider-solana-new.ts @@ -11,13 +11,13 @@ import { DEFAULT_SOLANA_CLUSTER, getLogger, InjectedRequestManager, + NOTIFICATION_CONNECTION_URL_UPDATED, NOTIFICATION_SOLANA_ACTIVE_WALLET_UPDATED, NOTIFICATION_SOLANA_CONNECTED, - NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED, NOTIFICATION_SOLANA_DISCONNECTED, PLUGIN_NOTIFICATION_CONNECT, + PLUGIN_NOTIFICATION_CONNECTION_URL_UPDATED, PLUGIN_NOTIFICATION_MOUNT, - PLUGIN_NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED, PLUGIN_NOTIFICATION_SOLANA_PUBLIC_KEY_UPDATED, PLUGIN_NOTIFICATION_UNMOUNT, PLUGIN_NOTIFICATION_UPDATE_METADATA, @@ -147,7 +147,7 @@ export class ProviderSolanaInjection case NOTIFICATION_SOLANA_DISCONNECTED: this.#handleNotificationDisconnected(event); break; - case NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED: + case NOTIFICATION_CONNECTION_URL_UPDATED: this.#handleNotificationConnectionUrlUpdated(event); break; case NOTIFICATION_SOLANA_ACTIVE_WALLET_UPDATED: @@ -167,7 +167,7 @@ export class ProviderSolanaInjection case PLUGIN_NOTIFICATION_UNMOUNT: this.#handlePluginUnmount(event); break; - case PLUGIN_NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED: + case PLUGIN_NOTIFICATION_CONNECTION_URL_UPDATED: this.#handlePluginConnectionUrlUpdated(event); break; case PLUGIN_NOTIFICATION_SOLANA_PUBLIC_KEY_UPDATED: @@ -203,6 +203,9 @@ export class ProviderSolanaInjection } #handlePluginConnectionUrlUpdated(event: Event) { + if (event.data.detail.data.blockchain !== Blockchain.SOLANA) { + return; + } const connectionUrl = event.data.detail.data.url; this.#connection = new BackgroundSolanaConnection( this.#connectionRequestManager, @@ -249,6 +252,9 @@ export class ProviderSolanaInjection } #handleNotificationConnectionUrlUpdated(event: Event) { + if (event.data.detail.data.blockchain !== Blockchain.SOLANA) { + return; + } this.#secureSolanaClient = new SolanaClient( this.#secureClientSender, new Connection(event.data.detail.data.url) diff --git a/packages/provider-core/src/provider-solana.ts b/packages/provider-core/src/provider-solana.ts index a0bdaa3ed..a00c20413 100644 --- a/packages/provider-core/src/provider-solana.ts +++ b/packages/provider-core/src/provider-solana.ts @@ -13,13 +13,13 @@ import { DEFAULT_SOLANA_CLUSTER, getLogger, InjectedRequestManager, + NOTIFICATION_CONNECTION_URL_UPDATED, NOTIFICATION_SOLANA_ACTIVE_WALLET_UPDATED, NOTIFICATION_SOLANA_CONNECTED, - NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED, NOTIFICATION_SOLANA_DISCONNECTED, PLUGIN_NOTIFICATION_CONNECT, + PLUGIN_NOTIFICATION_CONNECTION_URL_UPDATED, PLUGIN_NOTIFICATION_MOUNT, - PLUGIN_NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED, PLUGIN_NOTIFICATION_SOLANA_PUBLIC_KEY_UPDATED, PLUGIN_NOTIFICATION_UNMOUNT, PLUGIN_NOTIFICATION_UPDATE_METADATA, @@ -124,7 +124,7 @@ export class ProviderSolanaInjection case NOTIFICATION_SOLANA_DISCONNECTED: this.#handleNotificationDisconnected(event); break; - case NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED: + case NOTIFICATION_CONNECTION_URL_UPDATED: this.#handleNotificationConnectionUrlUpdated(event); break; case NOTIFICATION_SOLANA_ACTIVE_WALLET_UPDATED: @@ -144,7 +144,7 @@ export class ProviderSolanaInjection case PLUGIN_NOTIFICATION_UNMOUNT: this.#handlePluginUnmount(event); break; - case PLUGIN_NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED: + case PLUGIN_NOTIFICATION_CONNECTION_URL_UPDATED: this.#handlePluginConnectionUrlUpdated(event); break; case PLUGIN_NOTIFICATION_SOLANA_PUBLIC_KEY_UPDATED: @@ -185,6 +185,9 @@ export class ProviderSolanaInjection } #handlePluginConnectionUrlUpdated(event: Event) { + if (event.data.detail.data.blockchain !== Blockchain.SOLANA) { + return; + } const connectionUrl = event.data.detail.data.url; this.#connection = new BackgroundSolanaConnection( this.#connectionRequestManager, @@ -219,6 +222,9 @@ export class ProviderSolanaInjection } #handleNotificationConnectionUrlUpdated(event: Event) { + if (event.data.detail.data.blockchain !== Blockchain.SOLANA) { + return; + } this.#connection = new BackgroundSolanaConnection( this.#connectionRequestManager, event.data.detail.data.url diff --git a/packages/react-common/src/components/Icon/index.tsx b/packages/react-common/src/components/Icon/index.tsx index ae5897f95..d75ecf008 100644 --- a/packages/react-common/src/components/Icon/index.tsx +++ b/packages/react-common/src/components/Icon/index.tsx @@ -404,6 +404,11 @@ export function SecretKeyIcon({ ); } +export function EclipseIcon() { + // TODO + return
ECL
; +} + export function SolanaIcon() { return ( ({ return p.solana.cluster; }, }); + +export const eclipseConnectionUrl = selector({ + key: "eclipseConnectionUrl", + get: ({ get }) => { + const p = get(preferences); + return p.eclipse.cluster; + }, +}); diff --git a/packages/recoil/src/context/Notifications.tsx b/packages/recoil/src/context/Notifications.tsx index a777909c1..3ebfcb6d4 100644 --- a/packages/recoil/src/context/Notifications.tsx +++ b/packages/recoil/src/context/Notifications.tsx @@ -1,11 +1,8 @@ import React, { useEffect } from "react"; -import type { - AutolockSettings, - Blockchain, - Notification, -} from "@coral-xyz/common"; +import type { AutolockSettings, Notification } from "@coral-xyz/common"; import { BackgroundSolanaConnection, + Blockchain, CHANNEL_POPUP_NOTIFICATIONS, ChannelAppUi, getLogger, @@ -15,13 +12,15 @@ import { NOTIFICATION_AUTO_LOCK_SETTINGS_UPDATED, NOTIFICATION_BLOCKCHAIN_KEYRING_CREATED, NOTIFICATION_BLOCKCHAIN_KEYRING_DELETED, + NOTIFICATION_CONNECTION_URL_UPDATED, NOTIFICATION_DARK_MODE_UPDATED, NOTIFICATION_DEVELOPER_MODE_UPDATED, + NOTIFICATION_ECLIPSE_ACTIVE_WALLET_UPDATED, NOTIFICATION_ETHEREUM_ACTIVE_WALLET_UPDATED, NOTIFICATION_ETHEREUM_CHAIN_ID_UPDATED, - NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED, NOTIFICATION_ETHEREUM_FEE_DATA_DID_UPDATE, NOTIFICATION_ETHEREUM_TOKENS_DID_UPDATE, + NOTIFICATION_EXPLORER_UPDATED, NOTIFICATION_FEATURE_GATES_UPDATED, NOTIFICATION_KEY_IS_COLD_UPDATE, NOTIFICATION_KEYNAME_UPDATE, @@ -40,8 +39,6 @@ import { NOTIFICATION_NAVIGATION_URL_DID_CHANGE, NOTIFICATION_SOLANA_ACTIVE_WALLET_UPDATED, NOTIFICATION_SOLANA_COMMITMENT_UPDATED, - NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED, - NOTIFICATION_SOLANA_EXPLORER_UPDATED, NOTIFICATION_SOLANA_SPL_TOKENS_DID_UPDATE, NOTIFICATION_USER_ACCOUNT_AUTHENTICATED, NOTIFICATION_USER_ACCOUNT_PUBLIC_KEY_CREATED, @@ -277,8 +274,8 @@ export function NotificationsProvider(props: any) { case NOTIFICATION_AGGREGATE_WALLETS_UPDATED: handleAggregateWalletsUpdated(notif); break; - case NOTIFICATION_SOLANA_EXPLORER_UPDATED: - handleSolanaExplorerUpdated(notif); + case NOTIFICATION_EXPLORER_UPDATED: + handleExplorerUpdated(notif); break; case NOTIFICATION_SOLANA_COMMITMENT_UPDATED: handleSolanaCommitmentUpdated(notif); @@ -286,18 +283,18 @@ export function NotificationsProvider(props: any) { case NOTIFICATION_SOLANA_SPL_TOKENS_DID_UPDATE: await handleSolanaSplTokensDidUpdate(notif); break; - case NOTIFICATION_SOLANA_CONNECTION_URL_UPDATED: - handleSolanaConnectionUrlUpdated(notif); + case NOTIFICATION_CONNECTION_URL_UPDATED: + handleConnectionUrlUpdated(notif); break; case NOTIFICATION_SOLANA_ACTIVE_WALLET_UPDATED: handleSolanaActiveWalletUpdated(notif); break; + case NOTIFICATION_ECLIPSE_ACTIVE_WALLET_UPDATED: + handleEclipseActiveWalletUpdated(notif); + break; case NOTIFICATION_ETHEREUM_ACTIVE_WALLET_UPDATED: handleEthereumActiveWalletUpdated(notif); break; - case NOTIFICATION_ETHEREUM_CONNECTION_URL_UPDATED: - handleEthereumConnectionUrlUpdated(notif); - break; case NOTIFICATION_ETHEREUM_CHAIN_ID_UPDATED: handleEthereumChainIdUpdated(notif); break; @@ -559,6 +556,13 @@ export function NotificationsProvider(props: any) { setActivePublicKeys(notif.data.activeWallets); }; + const handleEclipseActiveWalletUpdated = (notif: Notification) => { + allPlugins().forEach((p) => { + // TODO + }); + setActivePublicKeys(notif.data.activeWallets); + }; + const handleReset = () => { setKeyringStoreState(KeyringStoreState.NeedsOnboarding); }; @@ -591,19 +595,32 @@ export function NotificationsProvider(props: any) { setIsAggregateWallets(notif.data.aggregateWallets); }; - const handleSolanaExplorerUpdated = (notif: Notification) => { - setSolanaExplorer(notif.data.explorer); + const handleExplorerUpdated = (notif: Notification) => { + // TODO: make this impl blockchain agnostic. + if (notif.data.blockchain === Blockchain.SOLANA) { + setSolanaExplorer(notif.data.explorer); + } else { + // TODO (wasn't ever implemented). + } }; const handleSolanaCommitmentUpdated = (notif: Notification) => { setSolanaCommitment(notif.data.commitment); }; - const handleSolanaConnectionUrlUpdated = (notif: Notification) => { - setSolanaConnectionUrl(notif.data.url); - allPlugins().forEach((p) => { - p.pushSolanaConnectionChangedNotification(notif.data.url); - }); + const handleConnectionUrlUpdated = (notif: Notification) => { + // TODO: make this impl blockchain agnostic. + if (notif.data.blockchain === Blockchain.SOLANA) { + setSolanaConnectionUrl(notif.data.url); + allPlugins().forEach((p) => { + p.pushSolanaConnectionChangedNotification(notif.data.url); + }); + } else if (notif.data.blockchain === Blockchain.ETHEREUM) { + setEthereumConnectionUrl(notif.data.connectionUrl); + allPlugins().forEach((p) => { + p.pushEthereumConnectionChangedNotification(notif.data.connectionUrl); + }); + } }; const handleSolanaSplTokensDidUpdate = async (notif: Notification) => { @@ -621,7 +638,6 @@ export function NotificationsProvider(props: any) { }; const handleUserAccountAuthenticated = (notif: Notification) => { - logger.debug("dd handleUserAccountAuthenticated:notf", notif.data); setAuthenticatedUser({ username: notif.data.username, uuid: notif.data.uuid, @@ -669,13 +685,6 @@ export function NotificationsProvider(props: any) { setEthereumFeeData(notif.data.feeData); }; - const handleEthereumConnectionUrlUpdated = (notif: Notification) => { - setEthereumConnectionUrl(notif.data.connectionUrl); - allPlugins().forEach((p) => { - p.pushEthereumConnectionChangedNotification(notif.data.connectionUrl); - }); - }; - const handleEthereumChainIdUpdated = (notif: Notification) => { setEthereumChainId(notif.data.chainId); }; diff --git a/packages/recoil/src/hooks/useBlockchain.tsx b/packages/recoil/src/hooks/useBlockchain.tsx index df0b2c8d2..3da0c205c 100644 --- a/packages/recoil/src/hooks/useBlockchain.tsx +++ b/packages/recoil/src/hooks/useBlockchain.tsx @@ -19,6 +19,8 @@ export function useBlockchainExplorer(blockchain: Blockchain) { return atoms.ethereumExplorer; case Blockchain.SOLANA: return atoms.solanaExplorer; + case Blockchain.ECLIPSE: + return atoms.solanaExplorer; // TODO default: throw new Error(`invalid blockchain ${blockchain}`); } @@ -33,10 +35,13 @@ export function useBlockchainConnectionUrl(blockchain: Blockchain) { return atoms.ethereumConnectionUrl; case Blockchain.SOLANA: return atoms.solanaConnectionUrl; + case Blockchain.ECLIPSE: + return atoms.eclipseConnectionUrl; default: throw new Error(`invalid blockchain ${blockchain}`); } })(); + return useRecoilValue(value); } @@ -47,6 +52,8 @@ export function getBlockchainLogo(blockchain: Blockchain): string { return "./ethereum.png"; case Blockchain.SOLANA: return "/solana.png"; + case Blockchain.ECLIPSE: + return "/eclipse.png"; // TODO default: throw new Error(`invalid blockchain ${blockchain}`); } diff --git a/packages/recoil/src/hooks/wallet.tsx b/packages/recoil/src/hooks/wallet.tsx index 5fbd27d12..a8b98aea1 100644 --- a/packages/recoil/src/hooks/wallet.tsx +++ b/packages/recoil/src/hooks/wallet.tsx @@ -1,9 +1,11 @@ -import type { Blockchain } from "@coral-xyz/common"; +import { Blockchain } from "@coral-xyz/common"; import { useRecoilValue } from "recoil"; import * as atoms from "../atoms"; import type { Wallet, WalletPublicKeys } from "../types"; +import { useFeatureGates } from "./useFeatureGates"; + export function useActiveEthereumWallet(): { publicKey: string; name: string; @@ -76,7 +78,12 @@ export function useAllWalletsPerBlockchain(blockchain: Blockchain): Array<{ } export function useAllWallets(): Wallet[] { - return useRecoilValue(atoms.allWallets); + const gates = useFeatureGates(); + const wallets = useRecoilValue(atoms.allWallets); + if (!gates.ECLIPSE) { + return wallets.filter((w) => w.blockchain !== Blockchain.ECLIPSE); + } + return wallets; } export function useAllWalletsDisplayed(): Wallet[] { diff --git a/packages/secure-background/src/keyring/blockchain.ts b/packages/secure-background/src/keyring/blockchain.ts index bf14ceeba..0512b372d 100644 --- a/packages/secure-background/src/keyring/blockchain.ts +++ b/packages/secure-background/src/keyring/blockchain.ts @@ -1,4 +1,5 @@ import type { + Blockchain, BlockchainKeyringJson, LedgerKeyringInit, MnemonicKeyringInit, @@ -34,8 +35,10 @@ export class BlockchainKeyring { public ledgerKeyring?: LedgerKeyring; private activeWallet?: string; private deletedWallets?: Array; + private blockchain: Blockchain; constructor( + blockchain: Blockchain, store: SecureStore, hdKeyringFactory: HdKeyringFactory, keyringFactory: KeyringFactory, @@ -45,6 +48,7 @@ export class BlockchainKeyring { this.hdKeyringFactory = hdKeyringFactory; this.keyringFactory = keyringFactory; this.ledgerKeyringFactory = ledgerKeyringFactory; + this.blockchain = blockchain; } public publicKeys(): { @@ -92,7 +96,11 @@ export class BlockchainKeyring { this.ledgerKeyring = this.ledgerKeyringFactory.init([]); this.importedKeyring = this.keyringFactory.init([keyringInit.privateKey]); const name = this.store.defaultKeyname.defaultImported(1); - await this.store.setKeyname(keyringInit.publicKey, name); + await this.store.setKeyname( + keyringInit.publicKey, + name, + keyringInit.blockchain + ); newAccounts = [[name, keyringInit.publicKey]]; } else { // Init using ledger @@ -105,7 +113,11 @@ export class BlockchainKeyring { walletDescriptor, ] of keyringInit.signedWalletDescriptors.entries()) { const name = this.store.defaultKeyname.defaultLedger(index + 1); - await this.store.setKeyname(walletDescriptor.publicKey, name); + await this.store.setKeyname( + walletDescriptor.publicKey, + name, + walletDescriptor.blockchain + ); await this.store.setIsCold(walletDescriptor.publicKey, true); newAccounts.push([walletDescriptor.publicKey, name]); } @@ -132,7 +144,7 @@ export class BlockchainKeyring { const newAccounts: Array<[string, string]> = []; for (const [index, publicKey] of this.hdKeyring.publicKeys().entries()) { const name = this.store.defaultKeyname.defaultDerived(index + 1); - await this.store.setKeyname(publicKey, name); + await this.store.setKeyname(publicKey, name, this.blockchain); newAccounts.push([publicKey, name]); } return newAccounts; @@ -177,7 +189,7 @@ export class BlockchainKeyring { const name = this.store.defaultKeyname.defaultDerived( this.hdKeyring!.publicKeys().length ); - await this.store.setKeyname(publicKey, name); + await this.store.setKeyname(publicKey, name, this.blockchain); return { publicKey, derivationPath, name }; } @@ -194,7 +206,7 @@ export class BlockchainKeyring { const name = this.store.defaultKeyname.defaultDerived( this.hdKeyring.publicKeys().length ); - await this.store.setKeyname(publicKey, name); + await this.store.setKeyname(publicKey, name, this.blockchain); return { publicKey, @@ -212,7 +224,7 @@ export class BlockchainKeyring { this.importedKeyring!.publicKeys().length ); } - await this.store.setKeyname(pubkey, name); + await this.store.setKeyname(pubkey, name, this.blockchain); return [pubkey, name]; } diff --git a/packages/secure-background/src/keyring/index.ts b/packages/secure-background/src/keyring/index.ts index cfd5135f4..dc7442645 100644 --- a/packages/secure-background/src/keyring/index.ts +++ b/packages/secure-background/src/keyring/index.ts @@ -20,6 +20,7 @@ import { BlockchainKeyring } from "./blockchain"; export function hdFactoryForBlockchain(blockchain: Blockchain) { return { [Blockchain.SOLANA]: new SolanaHdKeyringFactory(), + [Blockchain.ECLIPSE]: new EclipseHdKeyringFactory(), [Blockchain.ETHEREUM]: new EthereumHdKeyringFactory(), }[blockchain]; } @@ -30,12 +31,21 @@ export function keyringForBlockchain( ): BlockchainKeyring { return { [Blockchain.SOLANA]: new BlockchainKeyring( + Blockchain.SOLANA, store, new SolanaHdKeyringFactory(), new SolanaKeyringFactory(), new SolanaLedgerKeyringFactory() ), + [Blockchain.ECLIPSE]: new BlockchainKeyring( + Blockchain.ECLIPSE, + store, + new EclipseHdKeyringFactory(), + new EclipseKeyringFactory(), + new EclipseLedgerKeyringFactory() + ), [Blockchain.ETHEREUM]: new BlockchainKeyring( + Blockchain.ETHEREUM, store, new EthereumHdKeyringFactory(), new EthereumKeyringFactory(), @@ -56,6 +66,21 @@ export function mnemonicPathToPrivateKey( return Buffer.from(deriveSolanaPrivateKey(seed, derivationPath)).toString( "hex" ); + } else if (blockchain === Blockchain.ECLIPSE) { + return Buffer.from(deriveEclipsePrivateKey(seed, derivationPath)).toString( + "hex" + ); } throw new Error("invalid blockchain"); } + +// TODO: move this elsewhere. +class EclipseHdKeyringFactory extends SolanaHdKeyringFactory {} +class EclipseKeyringFactory extends SolanaKeyringFactory {} +class EclipseLedgerKeyringFactory extends SolanaLedgerKeyringFactory {} +export function deriveEclipsePrivateKey( + seed: Buffer, + derivationPath: string +): Uint8Array { + return deriveSolanaPrivateKey(seed, derivationPath); +} diff --git a/packages/secure-background/src/store/SecureStore.ts b/packages/secure-background/src/store/SecureStore.ts index 003f3acc9..daa8f9b21 100644 --- a/packages/secure-background/src/store/SecureStore.ts +++ b/packages/secure-background/src/store/SecureStore.ts @@ -1,9 +1,9 @@ import type { - Blockchain, BlockchainKeyringJson, DeprecatedWalletDataDoNotUse, Preferences, } from "@coral-xyz/common"; +import { Blockchain } from "@coral-xyz/common"; import type { SecretPayload } from "./keyring/crypto"; import { decrypt, encrypt } from "./keyring/crypto"; @@ -114,18 +114,33 @@ export class SecureStore { return isCold; } - async setKeyname(publicKey: string, name: string) { + async setKeyname(publicKey: string, name: string, blockchain: Blockchain) { let keynames = await this.db.get(KEY_KEYNAME_STORE); if (!keynames) { - keynames = {}; + keynames = { + [Blockchain.ECLIPSE]: {}, + }; + } + if ( + blockchain !== Blockchain.ETHEREUM && + blockchain !== Blockchain.SOLANA + ) { + keynames[blockchain][publicKey] = name; + } else { + keynames[publicKey] = name; } - keynames[publicKey] = name; await this.db.set(KEY_KEYNAME_STORE, keynames); } - async getKeyname(publicKey: string): Promise { + async getKeyname(publicKey: string, blockchain: Blockchain): Promise { const names = await this.db.get(KEY_KEYNAME_STORE); - const name = names[publicKey]; + let name = names[publicKey]; + if ( + blockchain !== Blockchain.ETHEREUM && + blockchain !== Blockchain.SOLANA + ) { + name = names[blockchain][publicKey]; + } if (!name) { throw Error(`unable to find name for key: ${publicKey.toString()}`); } diff --git a/packages/secure-background/src/store/keyring/index.ts b/packages/secure-background/src/store/keyring/index.ts index c34fda172..33b5f2a86 100644 --- a/packages/secure-background/src/store/keyring/index.ts +++ b/packages/secure-background/src/store/keyring/index.ts @@ -921,7 +921,11 @@ class UserKeyring { const name = this.store.defaultKeyname.defaultLedger( ledgerKeyring.publicKeys().length ); - await this.store.setKeyname(walletDescriptor.publicKey, name); + await this.store.setKeyname( + walletDescriptor.publicKey, + name, + walletDescriptor.blockchain + ); await this.store.setIsCold(walletDescriptor.publicKey, true); } diff --git a/packages/secure-background/src/store/keyring/migrations/index.ts b/packages/secure-background/src/store/keyring/migrations/index.ts index 6bd029571..bc53f1f33 100644 --- a/packages/secure-background/src/store/keyring/migrations/index.ts +++ b/packages/secure-background/src/store/keyring/migrations/index.ts @@ -4,6 +4,7 @@ import type { MigrationPrivateStoreInterface } from "../../SecureStore"; import { migrate_0_2_0_510 } from "./migrate_0_2_0_510"; import { migrate_0_2_0_2408 } from "./migrate_0_2_0_2408"; +import { migrate_0_6_12_5798 } from "./migrate_0_6_12_5798"; const logger = getLogger("background/migrations"); @@ -41,7 +42,7 @@ async function _runMigrationsIfNeeded( }, storeInterface: MigrationPrivateStoreInterface ) { - const LATEST_MIGRATION_BUILD = 2408; // Update this everytime a migration is added. + const LATEST_MIGRATION_BUILD = 5798; // Update this everytime a migration is added. const lastMigration = await getMigration(storeInterface); logger.debug("starting migrations with last migration", lastMigration); @@ -92,6 +93,11 @@ async function _runMigrationsIfNeeded( await migrate_0_2_0_2408(userInfo, storeInterface); }); } + if ((await getMigration(storeInterface))?.build === 2408) { + await runMigration(5798, storeInterface, async () => { + await migrate_0_6_12_5798(userInfo, storeInterface); + }); + } // // Set the last migration as finalized. diff --git a/packages/secure-background/src/store/keyring/migrations/migrate_0_6_12_5798.ts b/packages/secure-background/src/store/keyring/migrations/migrate_0_6_12_5798.ts new file mode 100644 index 000000000..f66fd0a23 --- /dev/null +++ b/packages/secure-background/src/store/keyring/migrations/migrate_0_6_12_5798.ts @@ -0,0 +1,23 @@ +import type { EclipseData} from "@coral-xyz/common"; +import { Blockchain, BLOCKCHAIN_COMMON } from "@coral-xyz/common"; + +import type { MigrationPrivateStoreInterface } from "../../SecureStore"; + +export async function migrate_0_6_12_5798( + userInfo: { + uuid: string; + password: string; + }, + storeInterface: MigrationPrivateStoreInterface +) { + const walletData = await storeInterface.store.getWalletDataForUser( + userInfo.uuid + ); + + if (!walletData.eclipse) { + walletData.eclipse = BLOCKCHAIN_COMMON[Blockchain.ECLIPSE] + .PreferencesDefault as EclipseData; + } + + await storeInterface.store.setWalletDataForUser(userInfo.uuid, walletData); +} diff --git a/packages/tamagui-core/src/components/BlockchainLogo.tsx b/packages/tamagui-core/src/components/BlockchainLogo.tsx index c65494702..f71018a95 100644 --- a/packages/tamagui-core/src/components/BlockchainLogo.tsx +++ b/packages/tamagui-core/src/components/BlockchainLogo.tsx @@ -8,6 +8,8 @@ const Images = { "https://s3.us-east-1.amazonaws.com/app-assets.xnfts.dev/images/useBlockchainLogo/ethereum.png", solanaLogo: "https://s3.us-east-1.amazonaws.com/app-assets.xnfts.dev/images/useBlockchainLogo/solana.png", + eclipseLogo: + "https://s3.us-east-1.amazonaws.com/app-assets.xnfts.dev/images/useBlockchainLogo/solana.png", // todo }; function getBlockchainLogo(blockchain: Blockchain) { @@ -16,6 +18,8 @@ function getBlockchainLogo(blockchain: Blockchain) { return Images.ethereumLogo; case Blockchain.SOLANA: return Images.solanaLogo; + case Blockchain.ECLIPSE: + return Images.eclipseLogo; default: throw new Error(`invalid blockchain ${blockchain}`); }