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

feat: validate starknet wallet chain ID #168

Merged
merged 3 commits into from
Apr 26, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions src/components/UI/Button/Button.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const Button = ({
styles.button,
isDisabled && styles.isDisabled,
isLoading && styles.isLoading,
text && styles.hasText,
className
)}
style={styleObj}
Expand Down
8 changes: 5 additions & 3 deletions src/components/UI/Button/Button.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,10 @@
pointer-events: none;
}

img,
svg {
margin-right: 10px;
&.hasText {
img,
svg {
margin-right: 10px;
}
}
}
2 changes: 1 addition & 1 deletion src/enums/ErrorType.js → src/enums/LoginErrorType.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export const ErrorType = {
export const LoginErrorType = {
UNSUPPORTED_BROWSER: 0,
UNSUPPORTED_CHAIN_ID: 1
};
4 changes: 4 additions & 0 deletions src/enums/WalletErrorType.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export const WalletErrorType = {
CONNECTION_REJECTED_ERROR: 'ConnectionRejectedError',
CHAIN_UNSUPPORTED_ERROR: 'ChainUnsupportedError'
};
3 changes: 2 additions & 1 deletion src/enums/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ export * from './TransferError';
export * from './TransferStep';
export * from './ModalSize';
export * from './ModalType';
export * from './ErrorType';
export * from './LoginErrorType';
export * from './WalletErrorType';
13 changes: 10 additions & 3 deletions src/providers/AppProvider/app-hooks.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {useContext} from 'react';

import {WalletStatus} from '../../enums';
import {useL1Wallet, useL2Wallet} from '../WalletsProvider';
import {AppContext} from './app-context';

Expand All @@ -12,8 +13,14 @@ export const useTerms = () => {
};

export const useLogin = () => {
const {isConnected: isL1Connected} = useL1Wallet();
const {isConnected: isL2Connected} = useL2Wallet();
const {status: l1Status, config: l1Config} = useL1Wallet();
const {status: l2Status, config: l2Config} = useL2Wallet();

return {isLoggedIn: isL1Connected && isL2Connected};
return {
isLoggedIn:
l1Status === WalletStatus.CONNECTED &&
!!l1Config &&
l2Status === WalletStatus.CONNECTED &&
!!l2Config
};
};
12 changes: 3 additions & 9 deletions src/providers/EventManagerProvider/event-manager-hooks.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,9 @@ import {EventManagerContext} from './event-manager-context';
export const useEventListener = eventName => {
const {addListener, removeListeners} = useContext(EventManagerContext);

const _addListener = useCallback(
callback => addListener(eventName, callback),
[addListener]
);

const _removeListeners = useCallback(
() => removeListeners(eventName),
[removeListeners]
);
const _addListener = useCallback(callback => addListener(eventName, callback), [addListener]);

const _removeListeners = useCallback(() => removeListeners(eventName), [removeListeners]);

return {
addListener: _addListener,
Expand Down
91 changes: 32 additions & 59 deletions src/providers/WalletsProvider/WalletsProvider.js
Original file line number Diff line number Diff line change
@@ -1,72 +1,57 @@
import PropTypes from 'prop-types';
import React, {useEffect, useReducer} from 'react';
import React, {useEffect, useReducer, useState} from 'react';
import {useWallet} from 'use-wallet';

import {ChainType, WalletStatus} from '../../enums';
import {useEnvs} from '../../hooks';
import {getStarknet} from '../../libs';
import {useIsL1, useIsL2} from '../TransferProvider';
import {WalletsContext} from './wallets-context';
import {useStarknetWallet} from './wallets-hooks';
import {actions, initialState, reducer} from './wallets-reducer';

export const WalletsProvider = ({children}) => {
const {autoConnect} = useEnvs();
const [state, dispatch] = useReducer(reducer, initialState);
const {status, connect, reset, isConnected, error, account, chainId, networkName} = useWallet();
const {selectedAddress, isConnected: isL2Connected, enable} = getStarknet();
const {
status: l2Status,
connect: l2Connect,
isConnected: l2IsConnected,
error: l2Error,
account: l2Account,
chainId: l2ChainId,
networkName: l2NetworkName
} = useStarknetWallet();
const [isL1, swapToL1] = useIsL1();
const [isL2, swapToL2] = useIsL2();

// Handles starknet wallet changes
useEffect(() => {
(isL2 || state.l2Wallet.config) && maybeUpdateL2Wallet();
}, [selectedAddress, isL2Connected]);
}, [l2Status, l2Error, l2Account, l2ChainId, l2NetworkName]);

// Handles ethereum wallet changes
useEffect(() => {
(isL1 || state.l1Wallet.config) && maybeUpdateL1Wallet();
}, [status, error, account, chainId, networkName]);

const connectWallet = async walletConfig => {
if (isL1) {
return connectL1Wallet(walletConfig);
}
return connectL2Wallet(walletConfig);
};

const connectL1Wallet = async walletConfig => {
const {connectorId} = walletConfig;
await connect(connectorId);
setL1WalletConfig(walletConfig);
return isL1 ? connectL1Wallet(walletConfig) : connectL2Wallet(walletConfig);
};

const connectL2Wallet = async walletConfig => {
try {
const wallet = getStarknet();
const enabled = await wallet
.enable(!autoConnect && {showModal: true})
.then(address => !!address?.length);
if (enabled) {
walletConfig.name = wallet.name || walletConfig.name;
walletConfig.logoPath = wallet.icon || walletConfig.logoPath;
setL2WalletConfig(walletConfig);
}
// eslint-disable-next-line no-empty
} catch {}
const resetWallet = () => {
return isL1 ? resetL1Wallet() : resetL2Wallet();
};

const resetWallet = () => {
if (isL1) {
return resetL1Wallet();
}
return resetL2Wallet();
const connectL1Wallet = async walletConfig => {
const {connectorId} = walletConfig;
return connect(connectorId).then(() => setL1WalletConfig(walletConfig));
};

const resetL1Wallet = () => {
setL1WalletConfig(null);
return reset();
};

const connectL2Wallet = async walletConfig => {
return l2Connect(walletConfig).then(() => setL2WalletConfig(walletConfig));
};

const resetL2Wallet = () => {
setL2WalletConfig(null);
return null;
Expand All @@ -78,7 +63,7 @@ export const WalletsProvider = ({children}) => {
maybeUpdateL1Wallet();
} else if (state.l2Wallet.config && !state.l1Wallet.config) {
swapToL2();
await maybeUpdateL2Wallet();
maybeUpdateL2Wallet();
}
};

Expand All @@ -95,28 +80,17 @@ export const WalletsProvider = ({children}) => {
});
};

const maybeUpdateL2Wallet = async () => {
let status,
error = null;
try {
await enable();
status = WalletStatus.CONNECTED;
} catch (ex) {
error = ex;
status = WalletStatus.ERROR;
} finally {
updateL2Wallet({
status,
error,
chainId: chainId === ChainType.L1.MAIN ? ChainType.L2.MAIN : ChainType.L2.GOERLI,
isConnected: isL2Connected,
account: selectedAddress,
chainName: networkName
});
}
const maybeUpdateL2Wallet = () => {
updateL2Wallet({
status: l2Status,
error: l2Error,
chainId: l2ChainId,
isConnected: l2IsConnected,
account: l2Account,
chainName: l2NetworkName
});
};

// Dispatchers
const updateL1Wallet = payload => {
dispatch({
type: actions.UPDATE_L1_WALLET,
Expand Down Expand Up @@ -145,7 +119,6 @@ export const WalletsProvider = ({children}) => {
});
};

// context
const context = {
...state,
connectWallet,
Expand Down
106 changes: 100 additions & 6 deletions src/providers/WalletsProvider/wallets-hooks.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,24 @@
import {useCallback, useContext, useEffect, useState} from 'react';

import {ChainInfo, ChainType, WalletErrorType, WalletStatus} from '../../enums';
import {useEnvs} from '../../hooks';
import {getStarknet} from '../../libs';
import {useTransfer} from '../TransferProvider';
import {WalletsContext} from './wallets-context';

export const useWallets = () => {
const wallets = useContext(WalletsContext);
const [activeWallet, setActiveWallet] = useState(wallets.l1Wallet);
const {isL1} = useTransfer();
const [activeWallet, setActiveWallet] = useState(wallets.l1Wallet);

const connectWallet = useCallback(walletConfig => wallets.connectWallet(walletConfig), []);
const connectWallet = useCallback(
walletConfig => wallets.connectWallet(walletConfig),
[isL1, wallets]
);

const resetWallet = useCallback(() => wallets.resetWallet(), []);
const resetWallet = useCallback(() => wallets.resetWallet(), [isL1, wallets]);

const swapWallets = useCallback(() => wallets.swapWallets(), []);
const swapWallets = useCallback(() => wallets.swapWallets(), [isL1, wallets]);

useEffect(() => {
setActiveWallet(isL1 ? wallets.l1Wallet : wallets.l2Wallet);
Expand All @@ -28,7 +34,11 @@ export const useWallets = () => {

export const useL1Wallet = () => {
const wallets = useContext(WalletsContext);
const connectWallet = useCallback(walletConfig => wallets.connectL1Wallet(walletConfig), []);

const connectWallet = useCallback(
walletConfig => wallets.connectL1Wallet(walletConfig),
[wallets]
);

return {
connectWallet,
Expand All @@ -38,10 +48,94 @@ export const useL1Wallet = () => {

export const useL2Wallet = () => {
const wallets = useContext(WalletsContext);
const connectWallet = useCallback(walletConfig => wallets.connectL2Wallet(walletConfig), []);

const connectWallet = useCallback(
walletConfig => wallets.connectL2Wallet(walletConfig),
[wallets]
);

return {
connectWallet,
...wallets.l2Wallet
};
};

export const useStarknetWallet = () => {
const {autoConnect, supportedChainId} = useEnvs();
const [error, setError] = useState(null);
const [account, setAccount] = useState('');
const [chainId, setChainId] = useState('');
const [networkName, setNetworkName] = useState('');
const [status, setStatus] = useState(WalletStatus.DISCONNECTED);

const connect = async walletConfig => {
try {
setStatus(WalletStatus.CONNECTING);
const wallet = getStarknet();
const enabled = await wallet
.enable(!autoConnect && {showModal: true})
.then(address => !!address?.length);

if (enabled) {
walletConfig.name = wallet.name || walletConfig.name;
walletConfig.logoPath = wallet.icon || walletConfig.logoPath;
updateAccount();
addAccountChangedListener();
}
} catch {
setStatus(WalletStatus.ERROR);
}
};

const addAccountChangedListener = () => {
getStarknet().on('accountsChanged', () => {
updateAccount();
});
};

const updateAccount = () => {
const chainId = getCurrentChainId();
setAccount(getStarknet().selectedAddress);
setChainId(chainId);
setNetworkName(ChainInfo.L2[chainId].NAME);
handleChain(chainId);
};

const getCurrentChainId = () => {
const {baseUrl} = getStarknet().provider;
if (baseUrl.includes('alpha-mainnet.starknet.io')) {
return ChainType.L2.MAIN;
} else if (baseUrl.includes('alpha4.starknet.io')) {
return ChainType.L2.GOERLI;
} else if (baseUrl.match(/^https?:\/\/localhost.*/)) {
return 'localhost';
}
};

const isChainValid = chainId => {
return (
(chainId === ChainType.L2.MAIN && supportedChainId === ChainType.L1.MAIN) ||
(chainId === ChainType.L2.GOERLI && supportedChainId === ChainType.L1.GOERLI)
);
};

const handleChain = chainId => {
if (isChainValid(chainId)) {
setStatus(WalletStatus.CONNECTED);
setError(null);
} else {
setStatus(WalletStatus.ERROR);
setError({name: WalletErrorType.CHAIN_UNSUPPORTED_ERROR});
}
};

return {
account,
chainId,
networkName,
status,
error,
connect,
isConnected: getStarknet().isConnected
};
};
Loading