diff --git a/.changeset/heavy-balloons-behave.md b/.changeset/heavy-balloons-behave.md
new file mode 100644
index 00000000..8f20a981
--- /dev/null
+++ b/.changeset/heavy-balloons-behave.md
@@ -0,0 +1,6 @@
+---
+'@rosen-ui/wallet-api': minor
+---
+
+- Implement standardized error messages for wallets to centralize and streamline error reporting
+- Implement the isConnected method to verify whether the connection to the wallet extension is established
diff --git a/.changeset/proud-suits-sneeze.md b/.changeset/proud-suits-sneeze.md
new file mode 100644
index 00000000..fc9c6438
--- /dev/null
+++ b/.changeset/proud-suits-sneeze.md
@@ -0,0 +1,5 @@
+---
+'@rosen-ui/metamask-wallet': minor
+---
+
+Improve MetamaskWallet class to support all EVM chains
diff --git a/apps/rosen/.env.example b/apps/rosen/.env.example
index 7c321708..6372dfc6 100644
--- a/apps/rosen/.env.example
+++ b/apps/rosen/.env.example
@@ -1,7 +1,9 @@
CARDANO_KOIOS_API=''
ERGO_EXPLORER_API=''
BITCOIN_ESPLORA_API=''
-ETHEREUM_BLAST_API=
+ETHEREUM_RPC_API=''
+BINANCE_RPC_API=''
+NEXT_PUBLIC_BINANCE_LOCK_ADDRESS=''
NEXT_PUBLIC_ERGO_LOCK_ADDRESS=''
NEXT_PUBLIC_CARDANO_LOCK_ADDRESS=''
NEXT_PUBLIC_BITCOIN_LOCK_ADDRESS=''
diff --git a/apps/rosen/app/(bridge)/page.tsx b/apps/rosen/app/(bridge)/page.tsx
index f9e0ca3f..053ca39b 100644
--- a/apps/rosen/app/(bridge)/page.tsx
+++ b/apps/rosen/app/(bridge)/page.tsx
@@ -7,6 +7,8 @@ import { Alert, styled } from '@rosen-bridge/ui-kit';
import { NETWORKS } from '@rosen-ui/constants';
import { RosenAmountValue } from '@rosen-ui/types';
+import { WalletProvider } from '@/_hooks';
+
import { BridgeForm } from './BridgeForm';
import { BridgeTransaction } from './BridgeTransaction';
import { ConnectOrSubmitButton } from './ConnectOrSubmitButton';
@@ -79,30 +81,37 @@ const RosenBridge = () => {
<>
-
-
-
- {methods.getValues().source == NETWORKS.ETHEREUM && (
-
- If you are using Ledger, you may need to enable 'Blind
- signing' and 'Debug data' in the Ledger (Ethereum
- > Settings) due to{' '}
-
- a known issue in Ledger and MetaMask interaction
-
- .
-
- )}
-
-
+
+
+
+
+ {/*
+ TODO: Add a condition that activates this alert specifically when MetaMask is selected
+ local:ergo/rosen-bridge/ui#486
+ */}
+ {(methods.getValues().source == NETWORKS.BINANCE ||
+ methods.getValues().source == NETWORKS.ETHEREUM) && (
+
+ If you are using Ledger, you may need to enable 'Blind
+ signing' and 'Debug data' in the Ledger (Ethereum
+ > Settings) due to{' '}
+
+ a known issue in Ledger and MetaMask interaction
+
+ .
+
+ )}
+
+
+
>
);
diff --git a/apps/rosen/app/App.tsx b/apps/rosen/app/App.tsx
index 6625e7bf..302f8452 100644
--- a/apps/rosen/app/App.tsx
+++ b/apps/rosen/app/App.tsx
@@ -11,7 +11,6 @@ import { App as AppBase } from '@rosen-bridge/ui-kit';
import { theme } from '@/_theme/theme';
-import { WalletContextProvider } from './_contexts/walletContext';
import { TokenMapProvider } from './_hooks';
import { SideBar } from './SideBar';
import { Toolbar } from './Toolbar';
@@ -20,9 +19,7 @@ export const App = ({ children }: { children?: React.ReactNode }) => {
return (
} theme={theme} toolbar={}>
-
- {children}
-
+ {children}
);
diff --git a/apps/rosen/app/_actions/calculateFee.ts b/apps/rosen/app/_actions/calculateFee.ts
index efaa3562..28954eae 100644
--- a/apps/rosen/app/_actions/calculateFee.ts
+++ b/apps/rosen/app/_actions/calculateFee.ts
@@ -3,7 +3,7 @@
import { ErgoNetworkType, MinimumFeeBox } from '@rosen-bridge/minimum-fee';
import cardanoKoiosClientFactory from '@rosen-clients/cardano-koios';
import ergoExplorerClientFactory from '@rosen-clients/ergo-explorer';
-import { getHeight as ethereumGetHeight } from '@rosen-network/ethereum';
+import { EvmChains, getHeight } from '@rosen-network/evm';
import { NETWORKS, NETWORK_VALUES } from '@rosen-ui/constants';
import { Network } from '@rosen-ui/types';
import Joi from 'joi';
@@ -18,7 +18,8 @@ const ergoExplorerClient = ergoExplorerClientFactory(
);
const GetHeight = {
- [NETWORKS.ETHEREUM]: ethereumGetHeight,
+ [NETWORKS.BINANCE]: () => getHeight(EvmChains.BINANCE),
+ [NETWORKS.ETHEREUM]: () => getHeight(EvmChains.ETHEREUM),
[NETWORKS.CARDANO]: async () =>
(await cardanoKoiosClient.getTip())[0].block_no,
[NETWORKS.ERGO]: async () =>
diff --git a/apps/rosen/app/_contexts/walletContext.tsx b/apps/rosen/app/_contexts/walletContext.tsx
deleted file mode 100644
index cffc5c26..00000000
--- a/apps/rosen/app/_contexts/walletContext.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import { useReducer, createContext } from 'react';
-
-import { Wallet } from '@rosen-ui/wallet-api';
-
-type Action =
- | {
- type: 'set';
- wallet: Wallet;
- }
- | { type: 'remove' };
-type Dispatch = (action: Action) => void;
-
-type State = { selectedWallet: Wallet | null };
-
-/**
- * a context to make wallet state available to all the
- * program
- */
-export const WalletContext = createContext<
- { state: State; dispatch: Dispatch } | undefined
->(undefined);
-
-function walletReducer(state: State, action: Action) {
- switch (action.type) {
- case 'set': {
- return {
- selectedWallet: action.wallet,
- };
- }
- case 'remove': {
- return {
- selectedWallet: null,
- };
- }
- default: {
- throw new Error(`Unhandled action type`);
- }
- }
-}
-
-type WalletContextProviderProps = { children: React.ReactNode };
-
-/**
- * the context provider for the wallet state
- */
-
-function WalletContextProvider({ children }: WalletContextProviderProps) {
- const [state, dispatch] = useReducer(walletReducer, {
- selectedWallet: null,
- });
- const value = { state, dispatch };
-
- return (
- {children}
- );
-}
-
-export { WalletContextProvider };
diff --git a/apps/rosen/app/_hooks/useBridgeForm.ts b/apps/rosen/app/_hooks/useBridgeForm.ts
index cf400b46..7bd0396a 100644
--- a/apps/rosen/app/_hooks/useBridgeForm.ts
+++ b/apps/rosen/app/_hooks/useBridgeForm.ts
@@ -5,13 +5,13 @@ import { Network, RosenAmountValue } from '@rosen-ui/types';
import { getNonDecimalString } from '@rosen-ui/utils';
import { validateAddress } from '@/_actions';
-import { WalletContext } from '@/_contexts/walletContext';
import { availableNetworks } from '@/_networks';
import { unwrap } from '@/_safeServerAction';
import { getMaxTransfer, getMinTransfer } from '@/_utils';
import { useTokenMap } from './useTokenMap';
import { useTransactionFormData } from './useTransactionFormData';
+import { WalletContext } from './useWallet';
/**
* handles the form field registrations and form state changes
@@ -67,7 +67,7 @@ export const useBridgeForm = () => {
getNonDecimalString(value, decimals),
) as RosenAmountValue;
- if (walletGlobalContext?.state.selectedWallet) {
+ if (walletGlobalContext?.selectedWallet) {
// prevent user from entering more than token amount
const selectedNetwork =
@@ -76,15 +76,14 @@ export const useBridgeForm = () => {
const maxTransfer = await getMaxTransfer(
selectedNetwork,
{
- balance:
- await walletGlobalContext!.state.selectedWallet.getBalance(
- tokenField.value,
- ),
+ balance: await walletGlobalContext!.selectedWallet.getBalance(
+ tokenField.value,
+ ),
isNative: tokenField.value.metaData.type === 'native',
},
async () => ({
fromAddress:
- await walletGlobalContext!.state.selectedWallet!.getAddress(),
+ await walletGlobalContext!.selectedWallet!.getAddress(),
toAddress: addressField.value,
toChain: targetField.value as Network,
}),
diff --git a/apps/rosen/app/_hooks/useWallet.ts b/apps/rosen/app/_hooks/useWallet.ts
deleted file mode 100644
index 3f8619b6..00000000
--- a/apps/rosen/app/_hooks/useWallet.ts
+++ /dev/null
@@ -1,118 +0,0 @@
-import { useEffect, useContext, useCallback, useRef } from 'react';
-
-import { useLocalStorageManager } from '@rosen-ui/common-hooks';
-import { Wallet } from '@rosen-ui/wallet-api';
-
-import { WalletContext } from '@/_contexts/walletContext';
-
-import { useNetwork } from './useNetwork';
-
-interface WalletDescriptor {
- readonly name: string;
- readonly expDate: string;
-}
-
-/**
- * generates and return the wallet object to save in the local storage
- */
-const toWalletDescriptor = (wallet: Wallet): WalletDescriptor => {
- let expDate = new Date();
- return {
- name: wallet.name,
- expDate: expDate.setDate(expDate.getDate() + 2).toString(),
- };
-};
-
-/**
- * handles the wallet connections for all the networks
- * and reconnect to the wallet on app startup
- */
-export const useWallet = () => {
- const walletGlobalContext = useContext(WalletContext);
- const isConnecting = useRef(false);
- const { get, set } = useLocalStorageManager();
-
- const { selectedSource } = useNetwork();
-
- /**
- * searches in the available wallets in the selected network
- * and return the wallet object if it finds a match
- */
- const getWallet = useCallback(
- (name: string) => {
- return selectedSource?.wallets
- .filter((wallet) => wallet.isAvailable())
- .find((wallet) => wallet.name === name);
- },
- [selectedSource],
- );
-
- /**
- * searches in local storage for already selected wallets and
- * returns the wallet object if it finds match
- */
- const getCurrentWallet = useCallback((): Wallet | undefined => {
- const currentWalletDescriptor =
- selectedSource && get(selectedSource?.name);
-
- if (!currentWalletDescriptor) {
- return undefined;
- }
-
- return currentWalletDescriptor
- ? getWallet(currentWalletDescriptor.name)
- : undefined;
- }, [selectedSource, getWallet, get]);
-
- /**
- * disconnects the previously selected wallet and
- * calls the connection callbacks
- */
- const setSelectedWallet = useCallback(
- async (wallet: Wallet) => {
- const prevWallet = getCurrentWallet();
- const status = await wallet.connect();
-
- if (typeof status === 'boolean' && status) {
- set(selectedSource!.name, toWalletDescriptor(wallet));
- walletGlobalContext?.dispatch({ type: 'set', wallet });
- }
- },
- [selectedSource, getCurrentWallet, walletGlobalContext, set],
- );
-
- const handleConnection = useCallback(async () => {
- const selectedWallet = getCurrentWallet();
- if (
- selectedWallet?.name !== walletGlobalContext?.state.selectedWallet?.name
- ) {
- if (selectedWallet) {
- const status = await selectedWallet.connect();
- if (typeof status === 'boolean' && status) {
- walletGlobalContext?.dispatch({
- type: 'set',
- wallet: selectedWallet,
- });
- }
- isConnecting.current = false;
- } else {
- walletGlobalContext?.dispatch({ type: 'remove' });
- }
- }
- }, [walletGlobalContext, getCurrentWallet]);
-
- useEffect(() => {
- if (typeof window === 'object') {
- handleConnection();
- }
- }, [handleConnection]);
-
- return selectedSource
- ? {
- setSelectedWallet,
- selectedWallet: walletGlobalContext?.state.selectedWallet,
- wallets: selectedSource.wallets,
- getBalance: walletGlobalContext?.state.selectedWallet?.getBalance,
- }
- : {};
-};
diff --git a/apps/rosen/app/_hooks/useWallet.tsx b/apps/rosen/app/_hooks/useWallet.tsx
new file mode 100644
index 00000000..82604568
--- /dev/null
+++ b/apps/rosen/app/_hooks/useWallet.tsx
@@ -0,0 +1,103 @@
+import {
+ ReactNode,
+ createContext,
+ useCallback,
+ useContext,
+ useEffect,
+ useState,
+} from 'react';
+
+import { useSnackbar } from '@rosen-bridge/ui-kit';
+import { Wallet } from '@rosen-ui/wallet-api';
+
+import { useNetwork } from './useNetwork';
+
+/**
+ * handles the wallet connections for all the networks
+ * and reconnect to the wallet on app startup
+ */
+export const useWallet = () => {
+ const context = useContext(WalletContext);
+
+ if (!context) {
+ throw new Error('useWallet must be used within WalletProvider');
+ }
+
+ return context;
+};
+
+export type WalletContextType = {
+ setSelectedWallet: (wallet: Wallet) => Promise;
+ selectedWallet?: Wallet;
+ wallets: Wallet[];
+};
+
+export const WalletContext = createContext(null);
+
+export const WalletProvider = ({ children }: { children: ReactNode }) => {
+ const { selectedSource } = useNetwork();
+
+ const { openSnackbar } = useSnackbar();
+
+ const [selected, setSelected] = useState();
+
+ const select = useCallback(
+ async (wallet: Wallet) => {
+ try {
+ const isConnected = await wallet.connect();
+
+ if (isConnected === false) return;
+
+ await wallet.switchChain?.(selectedSource.name);
+ } catch (error: any) {
+ return openSnackbar(error.message, 'error');
+ }
+
+ setSelected(wallet);
+
+ if (!selectedSource) return;
+
+ localStorage.setItem('rosen:wallet:' + selectedSource.name, wallet.name);
+ },
+ [selectedSource, openSnackbar, setSelected],
+ );
+
+ useEffect(() => {
+ (async () => {
+ setSelected(undefined);
+
+ if (!selectedSource) return;
+
+ const name = localStorage.getItem('rosen:wallet:' + selectedSource.name);
+
+ const wallet = selectedSource.wallets.find(
+ (wallet) => wallet.name === name && wallet.isAvailable(),
+ );
+
+ if (!wallet) return;
+
+ if ((await wallet.isConnected?.()) === false) return;
+
+ try {
+ await wallet.connect();
+
+ await wallet.switchChain?.(selectedSource.name, true);
+
+ setSelected(wallet);
+ } catch (error) {
+ setSelected(undefined);
+ throw error;
+ }
+ })();
+ }, [selectedSource, setSelected]);
+
+ const state = {
+ setSelectedWallet: select,
+ selectedWallet: selected,
+ wallets: selectedSource?.wallets || [],
+ };
+
+ return (
+ {children}
+ );
+};
diff --git a/apps/rosen/app/_networks/binance/client.ts b/apps/rosen/app/_networks/binance/client.ts
new file mode 100644
index 00000000..4b762285
--- /dev/null
+++ b/apps/rosen/app/_networks/binance/client.ts
@@ -0,0 +1,32 @@
+import { BinanceIcon } from '@rosen-bridge/icons';
+import { NETWORK_LABELS, NETWORKS } from '@rosen-ui/constants';
+import { MetaMaskWallet } from '@rosen-ui/metamask-wallet';
+
+import { unwrap } from '@/_safeServerAction';
+import { getTokenMap } from '@/_tokenMap/getClientTokenMap';
+import { BinanceNetwork as BinanceNetworkType } from '@/_types';
+
+import { getMaxTransfer } from './getMaxTransfer';
+import { generateLockData, generateTxParameters } from './server';
+
+/**
+ * the main object for Binance network
+ * providing access to network info and wallets and network specific
+ * functionality
+ */
+export const BinanceNetwork: BinanceNetworkType = {
+ name: NETWORKS.BINANCE,
+ label: NETWORK_LABELS.BINANCE,
+ wallets: [
+ new MetaMaskWallet({
+ getTokenMap,
+ generateLockData: unwrap(generateLockData),
+ generateTxParameters: unwrap(generateTxParameters),
+ }),
+ ],
+ nextHeightInterval: 200,
+ logo: BinanceIcon,
+ lockAddress: process.env.NEXT_PUBLIC_BINANCE_LOCK_ADDRESS!,
+ getMaxTransfer: unwrap(getMaxTransfer),
+ toSafeAddress: (address) => address.toLowerCase(),
+};
diff --git a/apps/rosen/app/_networks/binance/getMaxTransfer.ts b/apps/rosen/app/_networks/binance/getMaxTransfer.ts
new file mode 100644
index 00000000..6c03a307
--- /dev/null
+++ b/apps/rosen/app/_networks/binance/getMaxTransfer.ts
@@ -0,0 +1,38 @@
+'use server';
+
+import {
+ EvmChains,
+ NATIVE_TOKEN_TRANSFER_GAS,
+ getFeeData,
+} from '@rosen-network/evm';
+import { NATIVE_TOKENS, NETWORKS } from '@rosen-ui/constants';
+
+import { wrap } from '@/_safeServerAction';
+import { getTokenMap } from '@/_tokenMap/getServerTokenMap';
+import { BinanceNetwork } from '@/_types';
+
+/**
+ * get max transfer for binance
+ */
+const getMaxTransferCore: BinanceNetwork['getMaxTransfer'] = async ({
+ balance,
+ isNative,
+}) => {
+ const feeData = await getFeeData(EvmChains.BINANCE);
+ if (!feeData.gasPrice) throw Error(`gas price is null`);
+ const estimatedFee = feeData.gasPrice * NATIVE_TOKEN_TRANSFER_GAS;
+ const tokenMap = getTokenMap();
+
+ const wrappedFee = tokenMap.wrapAmount(
+ NATIVE_TOKENS.BINANCE,
+ estimatedFee,
+ NETWORKS.BINANCE,
+ ).amount;
+ const offset = isNative ? wrappedFee : 0n;
+ const amount = balance - offset;
+ return amount < 0n ? 0n : amount;
+};
+
+export const getMaxTransfer = wrap(getMaxTransferCore, {
+ traceKey: 'getMaxTransferBinance',
+});
diff --git a/apps/rosen/app/_networks/binance/index.ts b/apps/rosen/app/_networks/binance/index.ts
new file mode 100644
index 00000000..4f1cce44
--- /dev/null
+++ b/apps/rosen/app/_networks/binance/index.ts
@@ -0,0 +1 @@
+export * from './client';
diff --git a/apps/rosen/app/_networks/binance/server.ts b/apps/rosen/app/_networks/binance/server.ts
new file mode 100644
index 00000000..fb0ec10e
--- /dev/null
+++ b/apps/rosen/app/_networks/binance/server.ts
@@ -0,0 +1,20 @@
+'use server';
+
+import {
+ generateLockData as generateLockDataCore,
+ generateTxParameters as generateTxParametersCore,
+} from '@rosen-network/evm';
+
+import { wrap } from '@/_safeServerAction';
+import { getTokenMap } from '@/_tokenMap/getServerTokenMap';
+
+export const generateLockData = wrap(generateLockDataCore, {
+ traceKey: 'generateLockData',
+});
+
+export const generateTxParameters = wrap(
+ generateTxParametersCore(getTokenMap()),
+ {
+ traceKey: 'generateTxParameters',
+ },
+);
diff --git a/apps/rosen/app/_networks/cardano/client.ts b/apps/rosen/app/_networks/cardano/client.ts
index 905d3e01..31ec67ab 100644
--- a/apps/rosen/app/_networks/cardano/client.ts
+++ b/apps/rosen/app/_networks/cardano/client.ts
@@ -37,7 +37,7 @@ export const CardanoNetwork: CardanoNetworkType = {
new LaceWallet(config),
new NamiWallet(config),
],
- nextHeightInterval: 25,
+ nextHeightInterval: 30,
logo: CardanoIcon,
lockAddress: process.env.NEXT_PUBLIC_CARDANO_LOCK_ADDRESS!,
getMaxTransfer: unwrap(getMaxTransfer),
diff --git a/apps/rosen/app/_networks/ethereum/client.ts b/apps/rosen/app/_networks/ethereum/client.ts
index 06c1a223..b586f265 100644
--- a/apps/rosen/app/_networks/ethereum/client.ts
+++ b/apps/rosen/app/_networks/ethereum/client.ts
@@ -25,7 +25,7 @@ export const EthereumNetwork: EthereumNetworkType = {
}),
],
logo: EthereumIcon,
- nextHeightInterval: 0,
+ nextHeightInterval: 50,
lockAddress: process.env.NEXT_PUBLIC_ETHEREUM_LOCK_ADDRESS!,
getMaxTransfer: unwrap(getMaxTransfer),
toSafeAddress: (address) => address.toLowerCase(),
diff --git a/apps/rosen/app/_networks/ethereum/getMaxTransfer.ts b/apps/rosen/app/_networks/ethereum/getMaxTransfer.ts
index c23d2f4e..70284c7a 100644
--- a/apps/rosen/app/_networks/ethereum/getMaxTransfer.ts
+++ b/apps/rosen/app/_networks/ethereum/getMaxTransfer.ts
@@ -1,6 +1,10 @@
'use server';
-import { ETH_TRANSFER_GAS, getFeeData } from '@rosen-network/ethereum';
+import {
+ EvmChains,
+ NATIVE_TOKEN_TRANSFER_GAS,
+ getFeeData,
+} from '@rosen-network/evm';
import { NATIVE_TOKENS, NETWORKS } from '@rosen-ui/constants';
import { wrap } from '@/_safeServerAction';
@@ -14,9 +18,9 @@ const getMaxTransferCore: EthereumNetwork['getMaxTransfer'] = async ({
balance,
isNative,
}) => {
- const feeData = await getFeeData();
+ const feeData = await getFeeData(EvmChains.ETHEREUM);
if (!feeData.gasPrice) throw Error(`gas price is null`);
- const estimatedFee = feeData.gasPrice * ETH_TRANSFER_GAS;
+ const estimatedFee = feeData.gasPrice * NATIVE_TOKEN_TRANSFER_GAS;
const tokenMap = getTokenMap();
const wrappedFee = tokenMap.wrapAmount(
diff --git a/apps/rosen/app/_networks/ethereum/server.ts b/apps/rosen/app/_networks/ethereum/server.ts
index d9c9a1b4..fb0ec10e 100644
--- a/apps/rosen/app/_networks/ethereum/server.ts
+++ b/apps/rosen/app/_networks/ethereum/server.ts
@@ -3,7 +3,7 @@
import {
generateLockData as generateLockDataCore,
generateTxParameters as generateTxParametersCore,
-} from '@rosen-network/ethereum';
+} from '@rosen-network/evm';
import { wrap } from '@/_safeServerAction';
import { getTokenMap } from '@/_tokenMap/getServerTokenMap';
diff --git a/apps/rosen/app/_networks/index.ts b/apps/rosen/app/_networks/index.ts
index 51b573ab..b1b50dee 100644
--- a/apps/rosen/app/_networks/index.ts
+++ b/apps/rosen/app/_networks/index.ts
@@ -1,11 +1,13 @@
import { NETWORKS } from '@rosen-ui/constants';
+import { BinanceNetwork } from './binance';
import { BitcoinNetwork } from './bitcoin';
import { CardanoNetwork } from './cardano';
import { ErgoNetwork } from './ergo';
import { EthereumNetwork } from './ethereum';
export const availableNetworks = {
+ [NETWORKS.BINANCE]: BinanceNetwork,
[NETWORKS.ERGO]: ErgoNetwork,
[NETWORKS.ETHEREUM]: EthereumNetwork,
[NETWORKS.CARDANO]: CardanoNetwork,
diff --git a/apps/rosen/app/_types/network.ts b/apps/rosen/app/_types/network.ts
index 82bedb09..25bb4de5 100644
--- a/apps/rosen/app/_types/network.ts
+++ b/apps/rosen/app/_types/network.ts
@@ -34,6 +34,7 @@ interface BitcoinMaxTransferExtra {
};
}
+export interface BinanceNetwork extends BaseNetwork<'binance'> {}
export interface EthereumNetwork extends BaseNetwork<'ethereum'> {}
export interface ErgoNetwork extends BaseNetwork<'ergo'> {}
export interface CardanoNetwork extends BaseNetwork<'cardano'> {}
diff --git a/apps/rosen/app/_utils/getMaxTransfer.ts b/apps/rosen/app/_utils/getMaxTransfer.ts
index 003201aa..3448ca6b 100644
--- a/apps/rosen/app/_utils/getMaxTransfer.ts
+++ b/apps/rosen/app/_utils/getMaxTransfer.ts
@@ -2,6 +2,7 @@ import { NETWORKS } from '@rosen-ui/constants';
import { Network, RosenAmountValue } from '@rosen-ui/types';
import {
+ BinanceNetwork,
BitcoinNetwork,
CardanoNetwork,
ErgoNetwork,
@@ -16,7 +17,12 @@ import {
* @returns THIS IS A WRAPPED-VALUE
*/
export const getMaxTransfer = async (
- network: ErgoNetwork | CardanoNetwork | BitcoinNetwork | EthereumNetwork,
+ network:
+ | BinanceNetwork
+ | ErgoNetwork
+ | CardanoNetwork
+ | BitcoinNetwork
+ | EthereumNetwork,
tokenInfo: {
balance: RosenAmountValue;
isNative: boolean;
diff --git a/apps/rosen/package.json b/apps/rosen/package.json
index c0a58cc7..3544d0c3 100644
--- a/apps/rosen/package.json
+++ b/apps/rosen/package.json
@@ -16,7 +16,7 @@
"dependencies": {
"@emurgo/cardano-serialization-lib-browser": "^11.5.0",
"@emurgo/cardano-serialization-lib-nodejs": "^11.5.0",
- "@rosen-bridge/address-codec": "^0.3.0",
+ "@rosen-bridge/address-codec": "^0.4.0",
"@rosen-bridge/bitcoin-utxo-selection": "^0.2.0",
"@rosen-bridge/cardano-utxo-selection": "^1.0.0",
"@rosen-bridge/cli": "^0.2.0",
@@ -34,7 +34,7 @@
"@rosen-network/bitcoin": "^2.0.0",
"@rosen-network/cardano": "^2.0.0",
"@rosen-network/ergo": "^2.0.0",
- "@rosen-network/ethereum": "^1.0.0",
+ "@rosen-network/evm": "^0.1.1",
"@rosen-ui/asset-calculator": "^2.0.1",
"@rosen-ui/common-hooks": "^0.1.1",
"@rosen-ui/constants": "^0.0.5",
diff --git a/build.sh b/build.sh
index 818c4402..27bd1e37 100755
--- a/build.sh
+++ b/build.sh
@@ -15,7 +15,7 @@ npm run build --workspace wallets/wallet-api
npm run build --workspace networks/bitcoin
npm run build --workspace networks/cardano
npm run build --workspace networks/ergo
-npm run build --workspace networks/ethereum
+npm run build --workspace networks/evm
npm run build --workspace wallets/eternl
npm run build --workspace wallets/lace
npm run build --workspace wallets/metamask
diff --git a/networks/bitcoin/package.json b/networks/bitcoin/package.json
index 8cc3e599..442436cf 100644
--- a/networks/bitcoin/package.json
+++ b/networks/bitcoin/package.json
@@ -14,7 +14,7 @@
"test": "vitest"
},
"dependencies": {
- "@rosen-bridge/address-codec": "^0.3.0",
+ "@rosen-bridge/address-codec": "^0.4.0",
"@rosen-bridge/bitcoin-utxo-selection": "^0.2.0",
"@rosen-ui/constants": "^0.0.5",
"axios": "^1.7.2",
diff --git a/networks/ethereum/CHANGELOG.md b/networks/evm/CHANGELOG.md
similarity index 82%
rename from networks/ethereum/CHANGELOG.md
rename to networks/evm/CHANGELOG.md
index d2cada61..7d95ed2e 100644
--- a/networks/ethereum/CHANGELOG.md
+++ b/networks/evm/CHANGELOG.md
@@ -1,15 +1,11 @@
-# @rosen-network/ethereum
-
-## 1.0.0
-
-### Major Changes
-
-- Eliminate the reliance on the @rosen-ui/wallet-api package and remove any unrelated type definitions
+# @rosen-network/evm
## 0.1.1
### Patch Changes
+- Change the name of @rosen-network/ethereum to @rosen-network/evm
+- Eliminate the reliance on the @rosen-ui/wallet-api package and remove any unrelated type definitions
- Enhance the generateUnsignedTx utility functions within the networks package
- Initialize the Ethereum network package.
- Strengthen type safety and enforce robust typing for Chain and Network types
diff --git a/networks/ethereum/package.json b/networks/evm/package.json
similarity index 83%
rename from networks/ethereum/package.json
rename to networks/evm/package.json
index f049a7a1..bbac7086 100644
--- a/networks/ethereum/package.json
+++ b/networks/evm/package.json
@@ -1,6 +1,6 @@
{
- "name": "@rosen-network/ethereum",
- "version": "1.0.0",
+ "name": "@rosen-network/evm",
+ "version": "0.1.1",
"private": true,
"description": "This is a private package utilized within Rosen Bridge UI app",
"main": "dist/src/index.js",
@@ -13,7 +13,7 @@
"type-check": "tsc --noEmit"
},
"dependencies": {
- "@rosen-bridge/address-codec": "^0.3.0",
+ "@rosen-bridge/address-codec": "^0.4.0",
"@rosen-bridge/tokens": "^1.2.1",
"ethers": "^6.13.2"
},
diff --git a/networks/ethereum/src/constants.ts b/networks/evm/src/constants.ts
similarity index 94%
rename from networks/ethereum/src/constants.ts
rename to networks/evm/src/constants.ts
index 25889678..e4f29417 100644
--- a/networks/ethereum/src/constants.ts
+++ b/networks/evm/src/constants.ts
@@ -1,7 +1,6 @@
import { InterfaceAbi } from 'ethers';
-export const ETH = 'eth';
-export const ETH_TRANSFER_GAS = 21000n;
+export const NATIVE_TOKEN_TRANSFER_GAS = 21000n;
export const transferABI: InterfaceAbi = [
{
diff --git a/networks/ethereum/src/generateTxParameters.ts b/networks/evm/src/generateTxParameters.ts
similarity index 93%
rename from networks/ethereum/src/generateTxParameters.ts
rename to networks/evm/src/generateTxParameters.ts
index e3d14a65..967c39b7 100644
--- a/networks/ethereum/src/generateTxParameters.ts
+++ b/networks/evm/src/generateTxParameters.ts
@@ -3,7 +3,7 @@ import { NETWORKS } from '@rosen-ui/constants';
import { RosenAmountValue } from '@rosen-ui/types';
import { Contract } from 'ethers';
-import { ETH, transferABI } from './constants';
+import { transferABI } from './constants';
/**
* generates ethereum lock tx
@@ -27,7 +27,7 @@ export const generateTxParameters =
).amount;
let transactionParameters;
- if (tokenId === ETH) {
+ if (token.metaData.type === 'native') {
transactionParameters = {
to: lockAddress,
from: fromAddress,
diff --git a/networks/ethereum/src/index.ts b/networks/evm/src/index.ts
similarity index 78%
rename from networks/ethereum/src/index.ts
rename to networks/evm/src/index.ts
index d1e10975..1c484809 100644
--- a/networks/ethereum/src/index.ts
+++ b/networks/evm/src/index.ts
@@ -1,3 +1,4 @@
export * from './constants';
export * from './generateTxParameters';
export * from './utils';
+export * from './types';
diff --git a/networks/evm/src/types.ts b/networks/evm/src/types.ts
new file mode 100644
index 00000000..6bf40c7b
--- /dev/null
+++ b/networks/evm/src/types.ts
@@ -0,0 +1,4 @@
+export enum EvmChains {
+ ETHEREUM = 'ethereum',
+ BINANCE = 'binance',
+}
diff --git a/networks/ethereum/src/utils.ts b/networks/evm/src/utils.ts
similarity index 65%
rename from networks/ethereum/src/utils.ts
rename to networks/evm/src/utils.ts
index 7d8bcf6a..6c3ecb59 100644
--- a/networks/ethereum/src/utils.ts
+++ b/networks/evm/src/utils.ts
@@ -3,6 +3,8 @@ import { NETWORK_VALUES } from '@rosen-ui/constants';
import { Network } from '@rosen-ui/types';
import { FeeData, isAddress, JsonRpcProvider } from 'ethers';
+import { EvmChains } from './types';
+
/**
* generates metadata for lock transaction
* @param toChain
@@ -43,21 +45,19 @@ export const generateLockData = async (
};
/**
- * gets Ethereum current block height
+ * gets EVM chain current block height
* @returns
*/
-export const getHeight = async (): Promise => {
- return await new JsonRpcProvider(
- process.env.ETHEREUM_BLAST_API,
- ).getBlockNumber();
+export const getHeight = async (chain: EvmChains): Promise => {
+ return await new JsonRpcProvider(getChainRpcUrl(chain)).getBlockNumber();
};
/**
- * gets Ethereum fee data
+ * gets EVM chain fee data
* @returns
*/
-export const getFeeData = async (): Promise => {
- return await new JsonRpcProvider(process.env.ETHEREUM_BLAST_API).getFeeData();
+export const getFeeData = async (chain: EvmChains): Promise => {
+ return await new JsonRpcProvider(getChainRpcUrl(chain)).getFeeData();
};
/**
@@ -68,3 +68,18 @@ export const getFeeData = async (): Promise => {
export const isValidAddress = (addr: string) => {
return isAddress(addr);
};
+
+/**
+ * returns the corresponding chain RPC url
+ * @param chain
+ */
+const getChainRpcUrl = (chain: EvmChains) => {
+ switch (chain) {
+ case EvmChains.ETHEREUM:
+ return process.env.ETHEREUM_RPC_API;
+ case EvmChains.BINANCE:
+ return process.env.BINANCE_RPC_API;
+ default:
+ throw Error(`chain [${chain}] is not registered as an EVM chain`);
+ }
+};
diff --git a/networks/ethereum/tsconfig.json b/networks/evm/tsconfig.json
similarity index 100%
rename from networks/ethereum/tsconfig.json
rename to networks/evm/tsconfig.json
diff --git a/package-lock.json b/package-lock.json
index 910cabba..17e13272 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -227,7 +227,7 @@
"dependencies": {
"@emurgo/cardano-serialization-lib-browser": "^11.5.0",
"@emurgo/cardano-serialization-lib-nodejs": "^11.5.0",
- "@rosen-bridge/address-codec": "^0.3.0",
+ "@rosen-bridge/address-codec": "^0.4.0",
"@rosen-bridge/bitcoin-utxo-selection": "^0.2.0",
"@rosen-bridge/cardano-utxo-selection": "^1.0.0",
"@rosen-bridge/cli": "^0.2.0",
@@ -245,7 +245,7 @@
"@rosen-network/bitcoin": "^2.0.0",
"@rosen-network/cardano": "^2.0.0",
"@rosen-network/ergo": "^2.0.0",
- "@rosen-network/ethereum": "^1.0.0",
+ "@rosen-network/evm": "^0.1.1",
"@rosen-ui/asset-calculator": "^2.0.1",
"@rosen-ui/common-hooks": "^0.1.1",
"@rosen-ui/constants": "^0.0.5",
@@ -309,6 +309,46 @@
"typescript": "^5.0.0"
}
},
+ "apps/rosen-service/node_modules/@types/node": {
+ "version": "18.19.1",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "apps/rosen-service/node_modules/tsconfig-paths": {
+ "version": "4.2.0",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json5": "^2.2.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "apps/rosen/node_modules/@rosen-bridge/address-codec": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@rosen-bridge/address-codec/-/address-codec-0.4.0.tgz",
+ "integrity": "sha512-YjHEXDytiTw7UAfDhyKbFIvrubKQr295qf5X5PF3CQjqhRC/Po1wE+j/FLc6eRK1IU++5G8AkICH8WDuek35KQ==",
+ "dependencies": {
+ "@emurgo/cardano-serialization-lib-nodejs": "^11.5.0",
+ "bitcoinjs-lib": "^6.1.5",
+ "ergo-lib-wasm-nodejs": "^0.24.1",
+ "ethers": "^6.13.2"
+ },
+ "engines": {
+ "node": ">=20.11.0"
+ }
+ },
+ "apps/rosen/node_modules/@types/node": {
+ "version": "20.5.7",
+ "dev": true,
+ "license": "MIT"
+ },
"apps/rosen/node_modules/@typescript-eslint/parser": {
"version": "6.21.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz",
@@ -749,7 +789,7 @@
"name": "@rosen-network/bitcoin",
"version": "2.0.0",
"dependencies": {
- "@rosen-bridge/address-codec": "^0.3.0",
+ "@rosen-bridge/address-codec": "^0.4.0",
"@rosen-bridge/bitcoin-utxo-selection": "^0.2.0",
"@rosen-ui/constants": "^0.0.5",
"axios": "^1.7.2",
@@ -759,6 +799,20 @@
"typescript": "^5.0.0"
}
},
+ "networks/bitcoin/node_modules/@rosen-bridge/address-codec": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@rosen-bridge/address-codec/-/address-codec-0.4.0.tgz",
+ "integrity": "sha512-YjHEXDytiTw7UAfDhyKbFIvrubKQr295qf5X5PF3CQjqhRC/Po1wE+j/FLc6eRK1IU++5G8AkICH8WDuek35KQ==",
+ "dependencies": {
+ "@emurgo/cardano-serialization-lib-nodejs": "^11.5.0",
+ "bitcoinjs-lib": "^6.1.5",
+ "ergo-lib-wasm-nodejs": "^0.24.1",
+ "ethers": "^6.13.2"
+ },
+ "engines": {
+ "node": ">=20.11.0"
+ }
+ },
"networks/cardano": {
"name": "@rosen-network/cardano",
"version": "2.0.0",
@@ -782,11 +836,11 @@
"typescript": "^5.0.0"
}
},
- "networks/ethereum": {
- "name": "@rosen-network/ethereum",
- "version": "1.0.0",
+ "networks/evm": {
+ "name": "@rosen-network/evm",
+ "version": "0.1.1",
"dependencies": {
- "@rosen-bridge/address-codec": "^0.3.0",
+ "@rosen-bridge/address-codec": "^0.4.0",
"@rosen-bridge/tokens": "^1.2.1",
"ethers": "^6.13.2"
},
@@ -794,6 +848,20 @@
"typescript": "^5.0.0"
}
},
+ "networks/evm/node_modules/@rosen-bridge/address-codec": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@rosen-bridge/address-codec/-/address-codec-0.4.0.tgz",
+ "integrity": "sha512-YjHEXDytiTw7UAfDhyKbFIvrubKQr295qf5X5PF3CQjqhRC/Po1wE+j/FLc6eRK1IU++5G8AkICH8WDuek35KQ==",
+ "dependencies": {
+ "@emurgo/cardano-serialization-lib-nodejs": "^11.5.0",
+ "bitcoinjs-lib": "^6.1.5",
+ "ergo-lib-wasm-nodejs": "^0.24.1",
+ "ethers": "^6.13.2"
+ },
+ "engines": {
+ "node": ">=20.11.0"
+ }
+ },
"node_modules/@aashutoshrathi/word-wrap": {
"version": "1.2.6",
"dev": true,
@@ -9589,8 +9657,8 @@
"resolved": "networks/ergo",
"link": true
},
- "node_modules/@rosen-network/ethereum": {
- "resolved": "networks/ethereum",
+ "node_modules/@rosen-network/evm": {
+ "resolved": "networks/evm",
"link": true
},
"node_modules/@rosen-ui/asset-calculator": {
@@ -26925,7 +26993,7 @@
"@metamask/sdk": "^0.28.2",
"@rosen-bridge/icons": "^1.0.0",
"@rosen-bridge/tokens": "^1.2.1",
- "@rosen-network/ethereum": "^1.0.0",
+ "@rosen-network/evm": "^0.1.1",
"@rosen-ui/constants": "^0.0.5",
"@rosen-ui/types": "^0.3.1",
"@rosen-ui/wallet-api": "^1.0.3"
diff --git a/package.json b/package.json
index c830c83e..88c51cc6 100644
--- a/package.json
+++ b/package.json
@@ -15,8 +15,8 @@
"coverage": "npm run test -- --coverage",
"version": "npx changeset version && npm i && npx changeset --empty",
"version:rosen": "npx changeset version --ignore @rosen-bridge/watcher-app --ignore @rosen-bridge/guard-app && npm i && npx changeset --empty",
- "version:watcher": "npx changeset version --ignore=@rosen-bridge/rosen-service --ignore @rosen-bridge/rosen-app --ignore @rosen-bridge/guard-app --ignore @rosen-network/ergo --ignore @rosen-network/cardano --ignore @rosen-network/bitcoin --ignore @rosen-network/ethereum --ignore @rosen-ui/eternl-wallet --ignore @rosen-ui/lace-wallet --ignore @rosen-ui/metamask-wallet --ignore @rosen-ui/nami-wallet --ignore @rosen-ui/nautilus-wallet --ignore @rosen-ui/okx-wallet --ignore @rosen-ui/asset-calculator --ignore @rosen-ui/wallet-api && npm i && npx changeset --empty",
- "version:guard": "npx changeset version --ignore=@rosen-bridge/rosen-service --ignore @rosen-bridge/watcher-app --ignore @rosen-bridge/rosen-app --ignore @rosen-network/ergo --ignore @rosen-network/cardano --ignore @rosen-network/bitcoin --ignore @rosen-network/ethereum --ignore @rosen-ui/eternl-wallet --ignore @rosen-ui/lace-wallet --ignore @rosen-ui/metamask-wallet --ignore @rosen-ui/nami-wallet --ignore @rosen-ui/nautilus-wallet --ignore @rosen-ui/okx-wallet --ignore @rosen-ui/asset-calculator --ignore @rosen-ui/wallet-api && npm i && npx changeset --empty"
+ "version:watcher": "npx changeset version --ignore=@rosen-bridge/rosen-service --ignore @rosen-bridge/rosen-app --ignore @rosen-bridge/guard-app --ignore @rosen-network/ergo --ignore @rosen-network/cardano --ignore @rosen-network/bitcoin --ignore @rosen-network/evm --ignore @rosen-ui/eternl-wallet --ignore @rosen-ui/lace-wallet --ignore @rosen-ui/metamask-wallet --ignore @rosen-ui/nami-wallet --ignore @rosen-ui/nautilus-wallet --ignore @rosen-ui/okx-wallet --ignore @rosen-ui/asset-calculator --ignore @rosen-ui/wallet-api && npm i && npx changeset --empty",
+ "version:guard": "npx changeset version --ignore=@rosen-bridge/rosen-service --ignore @rosen-bridge/watcher-app --ignore @rosen-bridge/rosen-app --ignore @rosen-network/ergo --ignore @rosen-network/cardano --ignore @rosen-network/bitcoin --ignore @rosen-network/evm --ignore @rosen-ui/eternl-wallet --ignore @rosen-ui/lace-wallet --ignore @rosen-ui/metamask-wallet --ignore @rosen-ui/nami-wallet --ignore @rosen-ui/nautilus-wallet --ignore @rosen-ui/okx-wallet --ignore @rosen-ui/asset-calculator --ignore @rosen-ui/wallet-api && npm i && npx changeset --empty"
},
"dependencies": {
"@changesets/cli": "^2.27.1"
diff --git a/packages/constants/src/index.ts b/packages/constants/src/index.ts
index 4607fe10..b714263b 100644
--- a/packages/constants/src/index.ts
+++ b/packages/constants/src/index.ts
@@ -17,6 +17,7 @@ export const NETWORKS = {
CARDANO: 'cardano',
BITCOIN: 'bitcoin',
ETHEREUM: 'ethereum',
+ BINANCE: 'binance',
} as const;
export const NATIVE_TOKENS = {
@@ -24,6 +25,7 @@ export const NATIVE_TOKENS = {
CARDANO: 'ada',
BITCOIN: 'btc',
ETHEREUM: 'eth',
+ BINANCE: 'bnb',
} as const;
/**
@@ -37,4 +39,5 @@ export const NETWORK_LABELS: { [key in keyof typeof NETWORKS]: string } = {
CARDANO: 'Cardano',
BITCOIN: 'Bitcoin',
ETHEREUM: 'Ethereum',
+ BINANCE: 'Binance',
};
diff --git a/packages/icons/src/networks/binance.svg b/packages/icons/src/networks/binance.svg
new file mode 100644
index 00000000..21f0556a
--- /dev/null
+++ b/packages/icons/src/networks/binance.svg
@@ -0,0 +1,10 @@
+
+
diff --git a/packages/icons/src/networks/index.ts b/packages/icons/src/networks/index.ts
index 14359298..3c72a812 100644
--- a/packages/icons/src/networks/index.ts
+++ b/packages/icons/src/networks/index.ts
@@ -1,3 +1,4 @@
+export { ReactComponent as BinanceIcon } from './binance.svg';
export { ReactComponent as BitcoinIcon } from './bitcoin.svg';
export { ReactComponent as CardanoIcon } from './cardano.svg';
export { ReactComponent as ErgoIcon } from './ergo.svg';
diff --git a/packages/utils/src/getAddressUrl.ts b/packages/utils/src/getAddressUrl.ts
index d18b67ed..63696b7a 100644
--- a/packages/utils/src/getAddressUrl.ts
+++ b/packages/utils/src/getAddressUrl.ts
@@ -2,6 +2,7 @@ import { NETWORKS } from '@rosen-ui/constants';
import { Network } from '@rosen-ui/types';
const baseAddressURLs: { [key in Network]: string } = {
+ [NETWORKS.BINANCE]: 'https://bscscan.com/address',
[NETWORKS.ERGO]: 'https://explorer.ergoplatform.com/en/addresses',
[NETWORKS.CARDANO]: 'https://cardanoscan.io/address',
[NETWORKS.BITCOIN]: 'https://mempool.space/address',
diff --git a/packages/utils/src/getTokenUrl.ts b/packages/utils/src/getTokenUrl.ts
index 22e30743..fbe3bc3b 100644
--- a/packages/utils/src/getTokenUrl.ts
+++ b/packages/utils/src/getTokenUrl.ts
@@ -2,6 +2,7 @@ import { NETWORKS } from '@rosen-ui/constants';
import { Network } from '@rosen-ui/types';
const baseTokenURLs: { [key in Network]: string } = {
+ [NETWORKS.BINANCE]: 'https://bscscan.com/token',
[NETWORKS.ERGO]: 'https://explorer.ergoplatform.com/en/token',
[NETWORKS.CARDANO]: 'https://cardanoscan.io/token',
[NETWORKS.BITCOIN]: '',
diff --git a/packages/utils/src/getTxUrl.ts b/packages/utils/src/getTxUrl.ts
index 2e1dd427..370b9e87 100644
--- a/packages/utils/src/getTxUrl.ts
+++ b/packages/utils/src/getTxUrl.ts
@@ -9,6 +9,7 @@ import { Network } from '@rosen-ui/types';
*/
const baseTxURLs: { [key in Network]: string } = {
+ [NETWORKS.BINANCE]: 'https://bscscan.com/tx',
[NETWORKS.ERGO]: 'https://explorer.ergoplatform.com/transactions',
[NETWORKS.CARDANO]: 'https://cardanoscan.io/transaction',
[NETWORKS.BITCOIN]: 'https://mempool.space/tx',
diff --git a/wallets/metamask/package.json b/wallets/metamask/package.json
index 2a1ebad3..5a601f83 100644
--- a/wallets/metamask/package.json
+++ b/wallets/metamask/package.json
@@ -19,7 +19,7 @@
"@metamask/sdk": "^0.28.2",
"@rosen-bridge/icons": "^1.0.0",
"@rosen-bridge/tokens": "^1.2.1",
- "@rosen-network/ethereum": "^1.0.0",
+ "@rosen-network/evm": "^0.1.1",
"@rosen-ui/constants": "^0.0.5",
"@rosen-ui/types": "^0.3.1",
"@rosen-ui/wallet-api": "^1.0.3"
diff --git a/wallets/metamask/src/types.ts b/wallets/metamask/src/types.ts
index 3bb8c912..b0aae09d 100644
--- a/wallets/metamask/src/types.ts
+++ b/wallets/metamask/src/types.ts
@@ -2,7 +2,7 @@ import { TokenMap } from '@rosen-bridge/tokens';
import type {
generateTxParameters,
generateLockData,
-} from '@rosen-network/ethereum';
+} from '@rosen-network/evm';
export type WalletConfig = {
getTokenMap(): Promise;
diff --git a/wallets/metamask/src/wallet.ts b/wallets/metamask/src/wallet.ts
index 108db4ca..f101d776 100644
--- a/wallets/metamask/src/wallet.ts
+++ b/wallets/metamask/src/wallet.ts
@@ -1,9 +1,20 @@
import { MetaMaskSDK } from '@metamask/sdk';
import { MetaMaskIcon } from '@rosen-bridge/icons';
import { RosenChainToken } from '@rosen-bridge/tokens';
-import { tokenABI } from '@rosen-network/ethereum/dist/src/constants';
+import { tokenABI } from '@rosen-network/evm/dist/src/constants';
import { NETWORKS } from '@rosen-ui/constants';
-import { Wallet, WalletTransferParams } from '@rosen-ui/wallet-api';
+import { Network, RosenAmountValue } from '@rosen-ui/types';
+import {
+ ChainNotAddedError,
+ ChainSwitchingRejectedError,
+ UnsupportedChainError,
+ Wallet,
+ InteractionError,
+ WalletTransferParams,
+ AddressRetrievalError,
+ ConnectionRejectedError,
+ UserDeniedTransactionSignatureError,
+} from '@rosen-ui/wallet-api';
import { BrowserProvider, Contract } from 'ethers';
import { WalletConfig } from './types';
@@ -17,8 +28,6 @@ export class MetaMaskWallet implements Wallet {
link = 'https://metamask.io/';
- connecWaiting?: Promise;
-
private api = new MetaMaskSDK({
dappMetadata: {
name: 'Rosen Bridge',
@@ -26,34 +35,58 @@ export class MetaMaskWallet implements Wallet {
enableAnalytics: false,
});
+ private get provider() {
+ const provider = this.api.getProvider();
+
+ if (!provider) throw new InteractionError(this.name);
+
+ return provider;
+ }
+
constructor(private config: WalletConfig) {}
- async connect(): Promise {
- this.connecWaiting ||= this.api.connect();
- try {
- await this.connecWaiting;
- this.connecWaiting = undefined;
- return true;
- } catch {
- this.connecWaiting = undefined;
- return false;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ private dispatchError(error: any, cases: { [key: number]: () => Error }) {
+ if (error?.code in cases) {
+ throw cases[error.code]();
+ }
+ if (error.message) {
+ throw new Error(error.message, { cause: error });
}
+ throw error;
}
- getAddress(): Promise {
- throw new Error('Not implemented');
+ private async permissions() {
+ return (await this.provider.request({
+ method: 'wallet_getPermissions',
+ params: [],
+ })) as { caveats: { type: string; value: string[] }[] }[];
}
- async getBalance(token: RosenChainToken): Promise {
- const provider = this.api.getProvider();
-
- if (!provider) return 0n;
+ async connect(): Promise {
+ try {
+ await this.api.connect();
+ } catch (error) {
+ this.dispatchError(error, {
+ 4001: () => new ConnectionRejectedError(this.name, error),
+ });
+ }
+ }
- const accounts = await provider.request({
+ async getAddress(): Promise {
+ const accounts = await this.provider.request({
method: 'eth_accounts',
});
- if (!accounts?.length) return 0n;
+ const account = accounts?.at(0);
+
+ if (!account) throw new AddressRetrievalError(this.name);
+
+ return account;
+ }
+
+ async getBalance(token: RosenChainToken): Promise {
+ const address = await this.getAddress();
const tokenMap = await this.config.getTokenMap();
@@ -61,10 +94,10 @@ export class MetaMaskWallet implements Wallet {
let amount;
- if (tokenId == 'eth') {
- amount = await provider.request({
+ if (token.metaData.type === 'native') {
+ amount = await this.provider.request({
method: 'eth_getBalance',
- params: [accounts[0], 'latest'],
+ params: [address, 'latest'],
});
} else {
const browserProvider = new BrowserProvider(window.ethereum!);
@@ -75,7 +108,7 @@ export class MetaMaskWallet implements Wallet {
await browserProvider.getSigner(),
);
- amount = await contract.balanceOf(accounts[0]);
+ amount = await contract.balanceOf(address);
}
if (!amount) return 0n;
@@ -90,27 +123,51 @@ export class MetaMaskWallet implements Wallet {
}
isAvailable(): boolean {
- return (
- typeof window.ethereum !== 'undefined' &&
- window.ethereum.isMetaMask &&
- !!window.ethereum._metamask
- );
+ return this.api.isExtensionActive();
}
- async transfer(params: WalletTransferParams): Promise {
- const provider = this.api.getProvider();
+ async isConnected(): Promise {
+ return !!(await this.permissions()).length;
+ }
- if (!provider) throw Error(`Failed to interact with metamask`);
+ async switchChain(chain: Network, silent?: boolean): Promise {
+ const chains = {
+ [NETWORKS.BINANCE]: '0x38',
+ [NETWORKS.ETHEREUM]: '0x1',
+ } as { [key in Network]?: string };
- const accounts = await provider.request({
- method: 'eth_accounts',
- });
+ const chainId = chains[chain];
- if (!accounts?.length)
- throw Error(`Failed to fetch accounts from metamask`);
+ if (!chainId) throw new UnsupportedChainError(this.name, chain);
- if (!accounts[0])
- throw Error(`Failed to get address of first account from metamask`);
+ if (silent) {
+ const has = (await this.permissions())
+ .map((permission) => permission.caveats)
+ .flat()
+ .some(
+ (caveat) =>
+ caveat.type === 'restrictNetworkSwitching' &&
+ caveat.value.includes(chainId),
+ );
+
+ if (!has) throw new Error();
+ }
+
+ try {
+ await this.provider.request({
+ method: 'wallet_switchEthereumChain',
+ params: [{ chainId }],
+ });
+ } catch (error) {
+ this.dispatchError(error, {
+ 4001: () => new ChainSwitchingRejectedError(this.name, chain, error),
+ 4902: () => new ChainNotAddedError(this.name, chain, error),
+ });
+ }
+ }
+
+ async transfer(params: WalletTransferParams): Promise {
+ const address = await this.getAddress();
const rosenData = await this.config.generateLockData(
params.toChain,
@@ -126,17 +183,23 @@ export class MetaMaskWallet implements Wallet {
const transactionParameters = await this.config.generateTxParameters(
tokenId,
params.lockAddress,
- accounts[0],
+ address,
params.amount,
rosenData,
params.token,
);
- const result = await provider.request({
- method: 'eth_sendTransaction',
- params: [transactionParameters],
- });
+ try {
+ return (await this.provider.request({
+ method: 'eth_sendTransaction',
+ params: [transactionParameters],
+ }))!;
+ } catch (error) {
+ this.dispatchError(error, {
+ 4001: () => new UserDeniedTransactionSignatureError(this.name, error),
+ });
+ }
- return result ?? '';
+ return '';
}
}
diff --git a/wallets/wallet-api/src/types/errors.ts b/wallets/wallet-api/src/types/errors.ts
new file mode 100644
index 00000000..d6b3270d
--- /dev/null
+++ b/wallets/wallet-api/src/types/errors.ts
@@ -0,0 +1,75 @@
+import { Network } from '@rosen-ui/types';
+
+export class AddressRetrievalError extends Error {
+ constructor(public wallet: string) {
+ super(`Failed to retrieve the address from the [${wallet}] wallet`);
+ }
+}
+
+export class InteractionError extends Error {
+ constructor(public wallet: string) {
+ super(`Failed to interact with the [${wallet}] wallet`);
+ }
+}
+
+export class UnsupportedChainError extends Error {
+ constructor(
+ public wallet: string,
+ public chain: Network,
+ ) {
+ super(
+ `The chain [${chain}] is not supported for switching in the [${wallet}] wallet`,
+ );
+ }
+}
+
+export class ChainSwitchingRejectedError extends Error {
+ constructor(
+ public wallet: string,
+ public chain: Network,
+ public cause: unknown,
+ ) {
+ super(
+ `The request to switch to [${chain}] in the [${wallet}] wallet was rejected by the user`,
+ { cause },
+ );
+ }
+}
+
+export class ChainNotAddedError extends Error {
+ constructor(
+ public wallet: string,
+ public chain: Network,
+ public cause: unknown,
+ ) {
+ super(
+ `The chain [${chain}] has not been added to your [${wallet}] wallet. To proceed, please add it using the ${wallet} extension and try again`,
+ { cause },
+ );
+ }
+}
+
+export class ConnectionRejectedError extends Error {
+ constructor(
+ public wallet: string,
+ public cause: unknown,
+ ) {
+ super(`User rejected the connection request for the [${wallet}] wallet`, {
+ cause,
+ });
+ }
+}
+
+export class UserDeniedTransactionSignatureError extends Error {
+ constructor(
+ public wallet: string,
+ public cause: unknown,
+ ) {
+ super(
+ `Transaction signature denied by the user for the [${wallet}] wallet`,
+ {
+ cause,
+ },
+ );
+ }
+}
diff --git a/wallets/wallet-api/src/types/index.ts b/wallets/wallet-api/src/types/index.ts
index 692a439b..681744ce 100644
--- a/wallets/wallet-api/src/types/index.ts
+++ b/wallets/wallet-api/src/types/index.ts
@@ -1,2 +1,3 @@
export * from './common';
+export * from './errors';
export * from './wallet';
diff --git a/wallets/wallet-api/src/types/wallet.ts b/wallets/wallet-api/src/types/wallet.ts
index 7a7445dc..e12fcd6d 100644
--- a/wallets/wallet-api/src/types/wallet.ts
+++ b/wallets/wallet-api/src/types/wallet.ts
@@ -12,10 +12,12 @@ export interface Wallet {
name: string;
label: string;
link: string;
- connect(): Promise;
+ connect(): Promise;
getAddress(): Promise;
getBalance(token: RosenChainToken): Promise;
isAvailable(): boolean;
+ isConnected?(): Promise;
+ switchChain?(chain: Network, silent?: boolean): Promise;
transfer(params: WalletTransferParams): Promise;
}