Skip to content

Commit

Permalink
Merge pull request #218 from JeremyTheintz/feature/blocknative
Browse files Browse the repository at this point in the history
Feature/blocknative
  • Loading branch information
swiiny authored Aug 6, 2023
2 parents 2116101 + dc6bd1d commit f817c9a
Show file tree
Hide file tree
Showing 74 changed files with 38,855 additions and 48,923 deletions.
279 changes: 133 additions & 146 deletions common/contexts/Web3Context/Web3Context.tsx
Original file line number Diff line number Diff line change
@@ -1,141 +1,122 @@
import Address from '@models/Address';
import WalletConnectProvider from '@walletconnect/web3-provider';
import { BrowserProvider, JsonRpcProvider } from 'ethers';
import Onboard, { WalletState } from '@web3-onboard/core';
import frameModule from '@web3-onboard/frame';
import injectedModule from '@web3-onboard/injected-wallets';
import walletConnectModule from '@web3-onboard/walletconnect';
import { IWallet } from 'interfaces/wallet';
import { FC, ReactNode, createContext, useCallback, useEffect, useState } from 'react';
import { clearLocalStorage, getLocalStorage, setLocalStorage } from 'utils/global';
import { checkIfNetworkIsValid, getWalletFromName } from './Web3Context.functions';
import { IWeb3, IWeb3Provider } from './Web3Context.type';
import { NETWORKS_RPC, WALLETS } from './Web3Context.variables';
import { IWeb3 } from './Web3Context.type';
import { NETWORKS, NETWORKS_DATA } from './Web3Context.variables';

// the key used to save the state in localStorage
const WalletLocalStorageKey = 'wallet';

export const Web3Context = createContext<IWeb3 | undefined>(undefined);

const defaultProvider = { error: true };
const Web3Provider: FC<{ children: ReactNode }> = ({ children }) => {
const [provider, setProvider] = useState<IWeb3Provider>({ error: true });
const [address, setAddress] = useState<Address | undefined>(undefined);
const [provider, setProvider] = useState<WalletState['provider'] | null>(null);
const [accounts, setAccounts] = useState<WalletState['accounts']>([]);
const [networkId, setNetworkId] = useState<number | undefined>(undefined);
const [isWalletConnected, setIsWalletConnected] = useState<boolean>(false);
const [walletName, setWalletName] = useState<string | undefined>(undefined);
const [isWalletModalOpen, setIsWalletModalOpen] = useState<boolean>(false);
const [isConnectingWallet, setIsConnectingWallet] = useState<boolean>(false);
const [isValidNetwork, setIsValidNetwork] = useState<boolean>(false);
const [ens, setEns] = useState<string | undefined>(undefined);
const [isWalletModalOpen, setIsWalletModalOpen] = useState<boolean>(false);

const disconnectWallet = useCallback(() => {
setProvider(defaultProvider);
setAddress(undefined);
setEns(undefined);
setIsWalletConnected(false);
setWalletName(undefined);
setProvider(null);
setAccounts([]);

clearLocalStorage();
}, []);

const initWeb3Listeners = useCallback(
(provider: any) => {
if (provider) {
try {
provider.on('chainChanged', (chainId: number): void => {
getNetworkId(chainId);

// reload page
window.location.reload();
});

provider.on('accountsChanged', async (accounts: string[]) => {
if (accounts.length > 0) {
setAddress(Address.from(accounts[0]));
} else {
// statement called when used disconnects himself from metamask
disconnectWallet();
}
});

// used to detect if the user disconnect himself from WalletConnect
provider.on('disconnect', async () => {
// check if the wallet is WalletConnect because if it's metamask then it's triggered when change to a none supported network
if (walletName === WALLETS.walletConnect.name) {
disconnectWallet();
}
});
} catch (err) {
console.error('Error while initializing web3 listeners', err);
}
}
},
[disconnectWallet, walletName]
);

const connectWallet = useCallback(
async (wallet: IWallet) => {
setIsConnectingWallet(true);

try {
let newProvider;

switch (wallet.name) {
case WALLETS.metamask.name:
const web3Instance = window.ethereum;

const accounts = await web3Instance.request({ method: 'eth_requestAccounts' });

setAddress(Address.from(accounts[0]));

const web3provider = new BrowserProvider(web3Instance);

setProvider({
web3Provider: web3provider,
web3Instance: web3Instance
});

initWeb3Listeners(window.ethereum);
// detect injected wallet providers
const injected = injectedModule();
// Frame wallet provider
const frame = frameModule();

// the list of wallet providers displayed in the BlockNative modal
const walletProviders = [injected, frame];

// Wallet Connect related code (is optional)
if (process.env.WALLET_CONNECT_PROJECT_ID) {
// Wallet Connect wallet provider
const walletConnect = walletConnectModule({
projectId: process.env.WALLET_CONNECT_PROJECT_ID,
dappUrl: window.location.origin,
requiredChains: [NETWORKS.ethereum],
qrModalOptions: {
themeVariables: {
'--wcm-z-index': '999',
'--wcm-font-family': 'Nunito',
'--wcm-font-feature-settings': 'Nunito',
'--wcm-background-color': '#1E1F20',
'--wcm-accent-color': '#2467DF',
'--wcm-accent-fill-color': '#2467DF',
'--wcm-container-border-radius': '5px'
}
}
});

break;
case WALLETS.walletConnect.name:
newProvider = new WalletConnectProvider({
rpc: NETWORKS_RPC
});
// add Wallet Connect wallet provider to the list of providers
walletProviders.push(walletConnect);
}

// or
// newProvider = new WalletConnectProvider({
// infuraId: YOUR_INFURA_ID // Required
// });
const onboard = Onboard({
wallets: walletProviders,
theme: {
'--w3o-background-color': '#121314',
'--w3o-foreground-color': '#1E1F20',
'--w3o-text-color': '#ffffff',
'--w3o-border-color': '#979797',
'--w3o-action-color': '#24DF9C',
'--w3o-border-radius': '5px'
},
appMetadata: {
name: 'Create NextJs Dapp',
icon: '/assets/logo-placeholder.svg',
logo: '/assets/logo-placeholder.svg',
description: 'Create NextJs Dapp template'
},
connect: {
autoConnectLastWallet: false,
removeWhereIsMyWalletWarning: true
},
// activate or note the bottom right blocknative widget
accountCenter: {
desktop: {
enabled: false
},
mobile: {
enabled: false
}
},
chains: Object.values(NETWORKS_DATA)
});

await newProvider.enable();
const wallets = await onboard.connectWallet();

const web3Provider = new BrowserProvider(newProvider);
setProvider({
web3Provider,
web3Instance: newProvider
});
if (!wallets[0]) throw new Error('No wallet selected');

setAddress(Address.from(newProvider?.accounts[0]));
setLocalStorage(WalletLocalStorageKey, wallet.name);

initWeb3Listeners(newProvider);
setProvider(wallets[0].provider);
setAccounts(wallets[0].accounts);
} catch (err: any) {
console.error('Error while connecting wallet', wallet.name, err);

break;
default:
break;
if (typeof err?.message === 'string' && err.message !== 'No wallet selected') {
disconnectWallet();
}

setLocalStorage(WalletLocalStorageKey, wallet.name);
setWalletName(wallet.name);
setIsWalletModalOpen(false);
setIsWalletConnected(true);
setIsConnectingWallet(false);
} catch (err) {
console.error("Couldn't connect to wallet", wallet.name, err);
setIsConnectingWallet(false);
disconnectWallet();
}
},
[disconnectWallet, initWeb3Listeners]
[disconnectWallet]
);

/*
* get and update the network id
*/
const getNetworkId = async (chainId?: number) => {
const networkId = chainId || (await window?.ethereum?.request({ method: 'eth_chainId' })) || undefined;

Expand All @@ -154,62 +135,71 @@ const Web3Provider: FC<{ children: ReactNode }> = ({ children }) => {
}
};

/*
* function called when the app starts or on page refresh to check if the wallet is connected
*/
const checkIfWalletIsConnected = useCallback(async () => {
const savedWalletName = getLocalStorage(WalletLocalStorageKey) as string;

if (savedWalletName) {
const savedWallet = getWalletFromName(savedWalletName);
if (!savedWalletName) return;

if (savedWallet) {
connectWallet(savedWallet);
return 0;
}
const savedWallet = getWalletFromName(savedWalletName);

return 1;
}
if (!savedWallet) return;

return 0;
connectWallet(savedWallet);
}, [connectWallet]);

const checkIfUserHasEns = useCallback(
async (address: Address, web3Provider: JsonRpcProvider | BrowserProvider) => {
if (address && networkId) {
let ethereumProvider;

if (networkId === 1) {
ethereumProvider = web3Provider;
} else {
ethereumProvider = new JsonRpcProvider(process.env.RPC_ETHEREUM || 'https://rpc.ankr.com/eth');
}

const resolver = await ethereumProvider?.lookupAddress(address.toString());

if (resolver) {
setEns(resolver);
}
}
},
[networkId]
);

// update isValidNetwork when networkId changes
useEffect(() => {
setIsValidNetwork(checkIfNetworkIsValid(networkId || 0));
}, [networkId]);

// check if wallet is connected when the app starts or on page refresh
useEffect(() => {
checkIfWalletIsConnected();
}, [checkIfWalletIsConnected]);

// setup provider listeners
useEffect(() => {
if (address && provider?.web3Provider) {
checkIfUserHasEns(address, provider.web3Provider);
if (!provider) return;

try {
const handleChainChanged = (chainId: string) => {
getNetworkId(Number(chainId));
};

const handleAccountsChanged = (accounts: string[]) => {
if (accounts.length > 0) {
//setAddress(Address.from(accounts[0]));
console.log('accountsChanged', accounts);
} else {
// statement called when user disconnects their wallet from the wallet itself
disconnectWallet();
}
};

// set up listeners
provider.on('chainChanged', handleChainChanged);
provider.on('accountsChanged', handleAccountsChanged);

return () => {
provider.removeListener('chainChanged', handleChainChanged);
provider.removeListener('accountsChanged', handleAccountsChanged);
};
} catch (err) {
console.error('Error while initializing web3 listeners', err);
}
}, [address, checkIfUserHasEns, provider?.web3Provider]);
}, [disconnectWallet, provider]);

/* debug */
useEffect(() => {
console.debug('address', address?.toString());
}, [address]);
if (!accounts[0]) return;

console.debug('address', accounts[0].address);
}, [accounts]);

/* debug */
useEffect(() => {
console.debug('networkId', networkId);
}, [networkId]);
Expand All @@ -218,6 +208,7 @@ const Web3Provider: FC<{ children: ReactNode }> = ({ children }) => {
console.debug('isValidNetwork', isValidNetwork);
}, [isValidNetwork]);

/* debug */
useEffect(() => {
getNetworkId();
}, []);
Expand All @@ -226,17 +217,13 @@ const Web3Provider: FC<{ children: ReactNode }> = ({ children }) => {
<Web3Context.Provider
value={{
provider,
address,
accounts,
networkId,
isWalletConnected,
connectWallet,
disconnectWallet,
walletName,
ens,
isConnectingWallet,
isValidNetwork,
isWalletModalOpen,
setIsWalletModalOpen
setIsWalletModalOpen,
isWalletModalOpen
}}
>
{children}
Expand Down
23 changes: 5 additions & 18 deletions common/contexts/Web3Context/Web3Context.type.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,15 @@
import { IWallet } from '@interfaces/wallet';
import Address from '@models/Address';
import WalletConnectProvider from '@walletconnect/web3-provider';
import { BrowserProvider, JsonRpcProvider } from 'ethers';
import { WalletState } from '@web3-onboard/core';

interface IWeb3 {
provider: IWeb3Provider;
address: Address | undefined;
provider: WalletState['provider'] | null;
accounts: WalletState['accounts'];
networkId: number | undefined;
isWalletConnected: boolean;
isWalletModalOpen: boolean;
walletName: string | undefined;
ens: string | undefined;
isConnectingWallet: boolean;
setIsWalletModalOpen: (newState: boolean) => void;
isValidNetwork: boolean;
connectWallet: (wallet: IWallet) => void;
setIsWalletModalOpen: (newState: boolean) => void;
disconnectWallet: () => void;
}

interface IWeb3Provider {
web3Provider?: JsonRpcProvider | BrowserProvider;
web3Instance?: WalletConnectProvider | any;
isWallet?: boolean;
error?: boolean;
}

export type { IWeb3Provider, IWeb3 };
export type { IWeb3 };
Loading

1 comment on commit f817c9a

@vercel
Copy link

@vercel vercel bot commented on f817c9a Aug 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

create-nextjs-dapp – ./

create-nextjs-dapp-git-main-swiiny12.vercel.app
create-nextjs-dapp-swiiny12.vercel.app
create-nextjs-d.app

Please sign in to comment.