Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MetaMask and Browser Extension wallet adapters #113

Merged
merged 15 commits into from
Jan 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading