Skip to content

Commit

Permalink
Merge pull request #113 from lidofinance/feature/metamask-and-injecte…
Browse files Browse the repository at this point in the history
…d-adapters

MetaMask and Browser Extension wallet adapters
  • Loading branch information
alx-khramov authored Jan 15, 2024
2 parents a197cb4 + 2ffb96f commit 9082c62
Show file tree
Hide file tree
Showing 76 changed files with 787 additions and 539 deletions.
2 changes: 1 addition & 1 deletion apps/demo-react/components/ConnectDisconnect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const ConnectDisconnect = (props: { handleOpen: () => void }) => {
{account ? (
<>
<Text color="success">Wallet connected</Text>
<AddressBadge address={account} color="success" />
<AddressBadge address={account} />
</>
) : (
<Button
Expand Down
4 changes: 2 additions & 2 deletions apps/demo-react/components/MainSection.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from 'react';
import { ReactNode } from 'react';
import { Section } from '@lidofinance/lido-ui';
import { MainSection } from '../styles/global';

const Main: React.FC = ({ children }) => {
const Main = ({ children }: { children?: ReactNode }) => {
return (
<Section>
<MainSection>{children}</MainSection>
Expand Down
2 changes: 1 addition & 1 deletion apps/demo-react/components/ProviderWeb3WithProps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const client = createClient({
webSocketProvider,
});

const ProviderWeb3WithProps: React.FC = ({ children }) => {
const ProviderWeb3WithProps = ({ children }: { children: React.ReactNode }) => {
return (
<WagmiConfig client={client}>
<ProviderWeb3
Expand Down
4 changes: 2 additions & 2 deletions apps/demo-react/components/contractTesting/Wrap.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ const WrapForm = ({
const handleWrapSelectChange = (value: OptionValue) => {
setWrapSelect(value as string);
};
const handleWrapCoinChange = (value: string) => {
setWrapCoin(value);
const handleWrapCoinChange = (value: string | number) => {
setWrapCoin(`${value}`);
setInputValue('0.00001');
};
const setMaxInputValue = () => {
Expand Down
5 changes: 3 additions & 2 deletions apps/demo-react/components/info/WalletInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import React from 'react';
import {
useWeb3,
useConnectorInfo,
Expand All @@ -9,7 +10,7 @@ import { Line, Heading } from './styles';
import { BlueWrapper } from './BlueWrapper';
import { Web3ProviderInfo } from './Web3ProviderInfo';

export const WalletInfo: React.FC = (props) => {
export const WalletInfo = ({ children }: { children?: React.ReactNode }) => {
const connectorInfo = useConnectorInfo();
const supportedChainsData = useSupportedChains();
const supportedChainIds = supportedChainsData.supportedChains.map(
Expand Down Expand Up @@ -65,7 +66,7 @@ export const WalletInfo: React.FC = (props) => {
<Line>Chain is unsupported: {String(chain?.unsupported)}</Line>
</code>
</div>
{props.children}
{children}
</div>
</BlueWrapper>
);
Expand Down
2 changes: 1 addition & 1 deletion apps/demo-react/hooks/useWithdrawalsData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ export const useWithdrawalRequestMethods = () => {
);

const steth = useCallback(
async ({ requests }) => {
async ({ requests }: { requests: BigNumberish[] }) => {
const params = [requests, account || ''] as const;

const callback = async () => {
Expand Down
11 changes: 9 additions & 2 deletions apps/demo-react/next.config.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import NextBundleAnalyzer from '@next/bundle-analyzer';

const alchemyApiKey = process.env.ALCHEMY_API_KEY;
const analyzeBundle = process.env.ANALYZE_BUNDLE ?? false;

const withBundleAnalyzer = NextBundleAnalyzer({
enabled: analyzeBundle,
});

export default {
export default withBundleAnalyzer({
reactStrictMode: true,
basePath: process.env.BASE_PATH || '',
compiler: {
Expand All @@ -11,4 +18,4 @@ export default {
publicRuntimeConfig: {
alchemyApiKey,
},
}
})
2 changes: 2 additions & 0 deletions apps/demo-react/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"scripts": {
"dev": "next dev",
"build": "next build",
"build:analyze": "ANALYZE_BUNDLE=true next build",
"start": "next start",
"lint": "eslint --ext ts,tsx,js,mjs .",
"typechain": "typechain --target=ethers-v5 --out-dir ./generated ./abi/*.json"
Expand Down Expand Up @@ -38,6 +39,7 @@
"@types/lodash": "^4.14.196",
"@types/node": "^17.0.12",
"@types/react": "18.2.45",
"@next/bundle-analyzer": "^14.0.4",
"husky": "^8.0.3",
"next-transpile-modules": "9.0.0",
"tsconfig": "*",
Expand Down
6 changes: 6 additions & 0 deletions packages/connect-wallet-modal/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @reef-knot/connect-wallet-modal

## 1.17.0

### Minor Changes

- 253796d: Use MetaMask and generic browser extension wallet adapters. Do some refactoring.

## 1.16.0

### Minor Changes
Expand Down
10 changes: 4 additions & 6 deletions packages/connect-wallet-modal/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@reef-knot/connect-wallet-modal",
"version": "1.16.0",
"version": "1.17.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"exports": {
Expand Down Expand Up @@ -45,12 +45,11 @@
"@types/react-dom": "18.2.17"
},
"devDependencies": {
"@reef-knot/core-react": "^1.8.0",
"@reef-knot/core-react": "^1.8.1",
"@reef-knot/types": "^1.4.0",
"@reef-knot/ui-react": "^1.0.7",
"@reef-knot/ui-react": "^1.0.8",
"@reef-knot/wallets-helpers": "^1.1.5",
"@reef-knot/wallets-icons": "^1.5.0",
"@reef-knot/web3-react": "^1.12.0",
"@reef-knot/web3-react": "^1.13.0",
"@reef-knot/ledger-connector": "^2.0.0",
"@types/ua-parser-js": "^0.7.36",
"eslint-config-custom": "*",
Expand All @@ -64,7 +63,6 @@
"@reef-knot/types": "^1.2.1",
"@reef-knot/ui-react": "^1.0.4",
"@reef-knot/wallets-helpers": "^1.1.2",
"@reef-knot/wallets-icons": "^1.0.0",
"@reef-knot/web3-react": "^1.12.0",
"@reef-knot/ledger-connector": "^2.0.0",
"react": ">=18",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import React, {
createContext,
FC,
MutableRefObject,
ReactNode,
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import type Eth from '@ledgerhq/hw-app-eth';
import type Transport from '@ledgerhq/hw-transport';
import Eth from '@ledgerhq/hw-app-eth';
import { helpers } from '@reef-knot/web3-react';
import { getTransport, isHIDSupported } from './helpers';

export interface LedgerContextProps {
isActive: boolean;
children?: ReactNode;
}

export type LedgerContextValue = {
Expand All @@ -30,11 +31,14 @@ export type LedgerContextValue = {

export const LedgerContext = createContext({} as LedgerContextValue);

export const LedgerContextProvider: FC<LedgerContextProps> = ({
export const LedgerContextProvider = ({
isActive,
children,
}) => {
}: LedgerContextProps) => {
const transport = useRef<Transport | null>(null);
// isTransportConnecting flag helps with react v18 strict mode in the dev mode,
// which re-runs effects extra time, which breaks ledger connection process
const isTransportConnecting = useRef(false);
const ledgerAppEth = useRef<Eth | null>(null);
const [error, setError] = useState<Error | null>(null);
const [isTransportConnected, setIsTransportConnected] = useState(false);
Expand All @@ -55,8 +59,9 @@ export const LedgerContextProvider: FC<LedgerContextProps> = ({
}, []);

const connectTransport = useCallback(async () => {
if (transport.current) return;
if (isTransportConnecting.current || transport.current) return;
setError(null);
isTransportConnecting.current = true;

if (!isHIDSupported()) {
setError(
Expand All @@ -68,7 +73,9 @@ export const LedgerContextProvider: FC<LedgerContextProps> = ({
}

try {
const { default: Eth } = await import('@ledgerhq/hw-app-eth');
transport.current = await getTransport();
isTransportConnecting.current = false;
ledgerAppEth.current = new Eth(transport.current);
await ledgerAppEth.current.getAppConfiguration();
setIsTransportConnected(true);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { WalletsButtonsContainer } from './styles';
import { NOOP, useLocalStorage } from '../../helpers';
import { LedgerModal } from '../Ledger';
import { AcceptTermsModal } from './components';
import { getWalletsButtons } from './getWalletsButtons';

export function WalletsModal(props: WalletsModalProps) {
const {
Expand All @@ -23,6 +24,9 @@ export function WalletsModal(props: WalletsModalProps) {
metrics,
termsLink,
privacyNoticeLink,
buttonComponentsByConnectorId,
walletDataList,
hiddenWallets,
} = props;

const [termsChecked, setTermsChecked] = useLocalStorage(
Expand Down Expand Up @@ -133,12 +137,17 @@ export function WalletsModal(props: WalletsModalProps) {
>
<Terms {...termsProps} />
<WalletsButtonsContainer $buttonsFullWidth={buttonsFullWidth}>
{props.children(buttonsCommonProps)}
{getWalletsButtons({
commonProps: buttonsCommonProps,
buttonComponentsByConnectorId,
hiddenWallets,
walletDataList,
})}
</WalletsButtonsContainer>
</Modal>
);
}
}
// eslint-disable-next-line react/jsx-no-useless-fragment
return <></>;

return null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react';
import { useReefKnotContext } from '@reef-knot/core-react';
import {
ConnectInjected,
ConnectLedger,
ConnectWC,
ConnectCoinbase,
ConnectBrowser,
} from '../../connectButtons';
import { WalletsModal } from './WalletsModal';
import type { ButtonComponentsByConnectorId, WalletsModalProps } from './types';

const buttonComponentsByConnectorId: ButtonComponentsByConnectorId = {
default: ConnectInjected, // fallback
browserExtension: ConnectBrowser,
walletConnect: ConnectWC,
coinbaseWallet: ConnectCoinbase,
ledgerHID: ConnectLedger,
};

type WalletsModalForEthProps = Omit<
WalletsModalProps,
'buttonComponentsByConnectorId' | 'walletDataList'
>;

export function WalletsModalForEth(props: WalletsModalForEthProps) {
const { walletDataList } = useReefKnotContext();

return (
<WalletsModal
{...props}
walletDataList={walletDataList}
buttonComponentsByConnectorId={buttonComponentsByConnectorId}
/>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import React from 'react';
import {
ButtonComponentsByConnectorId,
ButtonsCommonProps,
WalletsModalProps,
} from './types';
import { WalletAdapterData } from '@reef-knot/types';

export function addWalletTo(
walletsList: string[],
walletId: string,
condition: boolean, // meant to be a wallet-detector function result
) {
// If condition is true (usually means that a wallet is detected),
// move it to the first place in the wallets list, so a user can see it right away
if (condition) {
walletsList.unshift(walletId);
} else {
walletsList.push(walletId);
}
}

export function getWalletsButtons({
commonProps,
buttonComponentsByConnectorId,
walletDataList = [],
hiddenWallets = [],
}: {
commonProps: ButtonsCommonProps;
buttonComponentsByConnectorId: ButtonComponentsByConnectorId;
walletDataList: WalletAdapterData[];
hiddenWallets: WalletsModalProps['hiddenWallets'];
}) {
let wallets: string[] = [];

walletDataList.forEach((walletData) => {
const { walletId, detector } = walletData;
addWalletTo(wallets, walletId, !!detector?.());
});

wallets = [...wallets].filter(
// Filtering wallets marked as hidden
(wallet) => !hiddenWallets.includes(wallet),
);

return wallets.map((walletId) => {
// Handle new wallet adapters
const walletData = walletDataList.find(
(data) => data.walletId === walletId,
);
if (!walletData) throw 'walletData is not found in the walletDataList';
const connectorId = walletData.connector.id;

const component =
buttonComponentsByConnectorId[connectorId] ??
buttonComponentsByConnectorId.default;

return React.createElement(component, {
key: walletId,
...commonProps,
...walletData,
});
});
}
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './WalletsModal';
export * from './WalletsModalForEth';
export * from './types';
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ReactNode } from 'react';
import React, { ReactNode } from 'react';
import { ModalProps } from '@reef-knot/ui-react';
import { WalletAdapterData } from '@reef-knot/types';

export type RequirementsData = {
icon?: ReactNode;
Expand All @@ -14,8 +15,14 @@ export type Metrics = {
};
};

export type ButtonComponentsByConnectorId = {
[K: string]: React.ComponentType;
};

export type WalletsModalProps = ModalProps & {
children: (props: ButtonsCommonProps) => ReactNode;
buttonComponentsByConnectorId: ButtonComponentsByConnectorId;
walletDataList: WalletAdapterData[];
hiddenWallets?: string[];
shouldInvertWalletIcon?: boolean;
buttonsFullWidth?: boolean;
metrics?: Metrics;
Expand Down
Loading

0 comments on commit 9082c62

Please sign in to comment.