Skip to content
This repository has been archived by the owner on Feb 21, 2024. It is now read-only.

Commit

Permalink
Add events (#17)
Browse files Browse the repository at this point in the history
* adds custom events

* adds react-toastify to sandbox

* InitialState -> WalletProvider

* adds more information to event

* updates event with title and type

* moved event listener to events hook

* renames back to useListeners

* updates provider listener to update when account/chain is changed

* remove tech debt

* code cleanup

* updates README
  • Loading branch information
Da-Colon committed Jul 1, 2022
1 parent ea95146 commit 2a93d93
Show file tree
Hide file tree
Showing 21 changed files with 303 additions and 149 deletions.
23 changes: 21 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ ReactDOM.render(
`<Web3Provider>` accepts 2 props.

| name | default | type | required | description |
| ---- | :-------: | ----: | --------: | -----------: |
| :---- | :-------: | ----: | --------: | -----------: |
| config | - | [DWPConfig](./src/types/index.ts) | `true` | Provider configurations |
| theme | 'light' | `string` \| [ModalTheme](./src/types/index.ts) | `false` | [Web3Modal](https://github.com/Web3Modal/web3modal) theme settings |

Expand All @@ -55,7 +55,7 @@ ReactDOM.render(
Configuration for the wallet-provider.

| name | default | type | required | description |
| ---- | :-------: | ----: | --------: | -----------: |
| :---- | :-------: | ----: | --------: | -----------: |
| providerKeys | - | [ProviderKeys](./src/types/index.ts) | At least one key is required. | Node api keys for fallback provider |
| localChainId | `undefined` | `string` | `false` | Chain id for local node |
localProviderURL | `undefined` | `string` | `false` | providerURL for local node |
Expand Down Expand Up @@ -134,6 +134,25 @@ function Component() {
}
```

## Events

Add an event eventListener and subscribe to `wallet-provider` events. There is a Constant you can import to ensure correct subscription. For an example using React Toastify see the [useEvents](./sandbox/src/components/examples/useEvents.ts).

```tsx
import { PROVIDER_EVENT } from '@decent-org/wallet-provider';

window.addEventListener(PROVIDER_EVENT, (event: CustomEventInit<WalletProviderEvent>) => {
console.log(event.detail!.message)
return
})
}
```

| title | type | description | ex: message |
| :---- | ---: | ----------: | ----------: |
| UNSUPPORTED_CHAIN_IDS | warn | Connected chain id is not supported | Switch to a supported chain: 1, 4 |


## Utilities
Coming soon

Expand Down
34 changes: 34 additions & 0 deletions sandbox/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions sandbox/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"react-app-rewired": "^2.2.1",
"react-dom": "^18.2.0",
"react-scripts": "5.0.1",
"react-toastify": "^9.0.5",
"typescript": "^4.7.3",
"util": "^0.12.4",
"web-vitals": "^2.1.4"
Expand Down
3 changes: 3 additions & 0 deletions sandbox/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Container } from './components/Container';
import { ConnectionProperty } from './components/ConnectionProperty';
import { Button } from './components/Button';
import { useWeb3Provider } from '@decent-org/wallet-provider';
import { useEvents } from './examples/useEvents';

function App() {
const {
Expand All @@ -11,6 +12,8 @@ function App() {
disconnect,
} = useWeb3Provider();

useEvents();

const signMessage = async () => {
const msg = 'Hello World!'
const signature = await (signerOrProvider as any)!.signMessage(msg);
Expand Down
20 changes: 20 additions & 0 deletions sandbox/src/examples/useEvents.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { useRef, useEffect } from "react"
import { toast } from "react-toastify"
import { PROVIDER_EVENT, WalletProviderEvent } from '@decent-org/wallet-provider';

export function useEvents() {
const isMountedRef = useRef(false)

useEffect(() => {
const providerEvent = (event: CustomEventInit<WalletProviderEvent>) => {
toast[event.detail!.type](event.detail!.message)
}
if(isMountedRef.current) {
window.addEventListener(PROVIDER_EVENT, providerEvent)
}
isMountedRef.current = true
return () => {
window.removeEventListener(PROVIDER_EVENT, providerEvent)
}
}, [])
}
File renamed without changes.
14 changes: 12 additions & 2 deletions sandbox/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@ import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import { web3ProviderConfig } from './providerConfig';
import { web3ProviderConfig } from './examples/web3ProviderConfig';
import { Web3Provider } from '@decent-org/wallet-provider';
import { ToastContainer } from 'react-toastify';
import "react-toastify/dist/ReactToastify.css";

const root = ReactDOM.createRoot(
document.getElementById('root') as HTMLElement
Expand All @@ -13,7 +15,15 @@ const root = ReactDOM.createRoot(
root.render(
<React.StrictMode>
<Web3Provider config={web3ProviderConfig()} theme="light">
<App />
<>
<ToastContainer
position="bottom-center"
closeButton={false}
newestOnTop={false}
pauseOnFocusLoss={false}
/>
<App />
</>
</Web3Provider>
</React.StrictMode>
);
Expand Down
47 changes: 27 additions & 20 deletions src/Web3Provider.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import React, { useCallback, useEffect, useMemo, useReducer } from 'react';
import React, { ReactNode, useCallback, useEffect, useMemo, useReducer } from 'react';
import Web3Modal from 'web3modal';
import type { ConnectFn, DisconnectFn, DWPConfig, InitialState, ModalTheme } from './types';
import type { ConnectFn, DisconnectFn, DWPConfig, WalletProvider, ModalTheme } from './types';
import { ActionTypes, Web3ProviderActions } from './actions';
import { getWeb3modalOptions } from './helpers/web3ModalConfig';
import { useProviderListeners } from './hooks/useProviderListeners';
import { useListeners } from './hooks/useListeners';
import { Web3ProviderContext } from './hooks/useWeb3Provider';
import { getFallbackProvider, getLocalProvider, getProviderInfo } from './helpers';

const initialState: InitialState = {
const initialState: WalletProvider = {
account: null,
signerOrProvider: null,
connectionType: 'not connected',
Expand All @@ -24,7 +24,7 @@ const getInitialState = () => {
};
};

const reducer = (state: InitialState, action: ActionTypes) => {
const reducer = (state: WalletProvider, action: ActionTypes) => {
switch (action.type) {
case Web3ProviderActions.CONNECT: {
const { account, signerOrProvider, provider, connectionType, network, chainId } =
Expand All @@ -40,6 +40,9 @@ const reducer = (state: InitialState, action: ActionTypes) => {
isProviderLoading: false,
};
}
case Web3ProviderActions.DISCONNECT: {
return { ...initialState };
}
default:
return state;
}
Expand All @@ -52,39 +55,43 @@ export function Web3Provider({
}: {
config: DWPConfig;
theme?: string | ModalTheme;
children: any;
children: ReactNode | ReactNode[];
}) {
const [state, dispatch] = useReducer(reducer, getInitialState());
const web3Modal = useMemo(() => new Web3Modal(getWeb3modalOptions(theme)), [theme]);

const connectDefaultProvider = useCallback(async () => {
web3Modal.clearCachedProvider();
if (process.env.REACT_APP_LOCAL_PROVIDER_URL && process.env.NODE_ENV === 'development') {
const [walletProvider, provider] = await getLocalProvider(config);
dispatch({
type: Web3ProviderActions.CONNECT,
payload: await getLocalProvider(config),
payload: walletProvider,
});
return provider;
} else {
const [walletProvider, provider] = getFallbackProvider(config);
dispatch({
type: Web3ProviderActions.CONNECT,
payload: getFallbackProvider(config),
payload: walletProvider,
});
return provider;
}
}, [web3Modal, config]);

const provider = useProviderListeners(web3Modal, config, connectDefaultProvider, state.account);
const connectInjectedProvider = useCallback(
async (_provider: any) => {
const [walletProvider, provider] = await getProviderInfo(_provider, config);
dispatch({
type: Web3ProviderActions.CONNECT,
payload: walletProvider,
});
return provider;
},
[config]
);

useEffect(() => {
if (provider) {
const dispatchConnection = async () => {
dispatch({
type: Web3ProviderActions.CONNECT,
payload: await getProviderInfo(provider, config),
});
};
dispatchConnection();
}
}, [provider, config]);
useListeners(web3Modal, config, connectDefaultProvider, connectInjectedProvider);

const connect: ConnectFn = useCallback(async () => {
web3Modal.clearCachedProvider();
Expand Down
15 changes: 10 additions & 5 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { InitialState } from '../types';
import { WalletProvider } from '../types';

export enum Web3ProviderActions {
CONNECT = 'CONNECT_WALLET',
DISCONNECT = 'DISCONNECT',
}

export type ActionTypes = {
type: Web3ProviderActions.CONNECT;
payload: InitialState;
};
export type ActionTypes =
| {
type: Web3ProviderActions.CONNECT;
payload: WalletProvider;
}
| {
type: Web3ProviderActions.DISCONNECT;
};
7 changes: 7 additions & 0 deletions src/constants/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export const PROVIDER_EVENT = 'provider-event';
export const CHAIN_CHANGED = 'chainChanged';
export const ACCOUNT_CHANGED = 'accountsChanged';
export const UNSUPPORTED_CHAIN_IDS = 'UNSUPPORTED_CHAIN_IDS';
export const CONNECT = 'connect';
export const DISCONNECT = 'disconnect';
export const UNSUPPORTED_CHAINS_IDS_MESSAGE = 'Switch to a supported chain:';
1 change: 1 addition & 0 deletions src/constants/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './events';
17 changes: 17 additions & 0 deletions src/helpers/events.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import {
PROVIDER_EVENT,
UNSUPPORTED_CHAINS_IDS_MESSAGE,
UNSUPPORTED_CHAIN_IDS,
} from '../constants';
import type { WalletProviderEvent } from '../types';

export function emitUnsupportedChainEvent(event: string, supportedChainIds: string) {
const unsupportChainIdEvent = new CustomEvent<WalletProviderEvent>(PROVIDER_EVENT, {
detail: {
type: 'warn',
title: UNSUPPORTED_CHAIN_IDS,
message: `${UNSUPPORTED_CHAINS_IDS_MESSAGE}: ${supportedChainIds}`,
},
});
window.dispatchEvent(unsupportChainIdEvent);
}
58 changes: 2 additions & 56 deletions src/helpers/index.ts
Original file line number Diff line number Diff line change
@@ -1,56 +1,2 @@
import { ethers, getDefaultProvider } from 'ethers';
import { DWPConfig, InitialState, ProviderApiKeys } from '../types/index';

export const getProviderInfo = async (_provider: any, config: DWPConfig) => {
const provider = new ethers.providers.Web3Provider(_provider);
const network = await provider.getNetwork();
const signer = provider.getSigner();
const local = config.localChainId && network.chainId === parseInt(config.localChainId, 10);
const account = (await signer.getAddress()) || null;
return {
account: account,
signerOrProvider: signer,
provider: provider,
connectionType: !account ? 'fallback' : 'injected provider',
network: local ? 'localhost' : network.name,
chainId: network.chainId,
};
};

export const getLocalProvider = (config: DWPConfig): Promise<InitialState> => {
const localProvider = new ethers.providers.JsonRpcProvider(config.localProviderURL);
return new Promise<InitialState>((resolve, reject) => {
localProvider
.detectNetwork()
.then(network => {
resolve({
account: null,
provider: localProvider,
signerOrProvider: localProvider,
connectionType: 'local provider',
network: 'localhost',
chainId: network.chainId,
});
})
.catch(reject);
});
};

export const getFallbackProvider = (config: DWPConfig): InitialState => {
const providerApiKeys: ProviderApiKeys = {};
if (config.providerKeys.infura) providerApiKeys.infura = config.providerKeys.infura;
if (config.providerKeys.alchemy) providerApiKeys.alchemy = config.providerKeys.alchemy;
if (config.providerKeys.etherscan) providerApiKeys.etherscan = config.providerKeys.etherscan;

const network = ethers.providers.getNetwork(parseInt(config.fallbackChainId || '0', 10));
const defaultProvider = getDefaultProvider(network, providerApiKeys);

return {
account: null,
provider: defaultProvider,
signerOrProvider: defaultProvider,
connectionType: 'readonly provider',
network: defaultProvider.network.name,
chainId: defaultProvider.network.chainId,
};
};
export * from './events';
export * from './providers';
Loading

0 comments on commit 2a93d93

Please sign in to comment.