Skip to content
This repository was archived by the owner on Jun 7, 2024. It is now read-only.

Commit 2c9d371

Browse files
committed
#116 bug fixes and updates to contract context. Fix to EthersConnector signer bug
- signer should invalidate when chain is changed - invalidation of old contract isntances - key updates - updates to validate adaptor - fixes to sorting
1 parent 0cf4790 commit 2c9d371

13 files changed

+149
-54
lines changed

esbuild.config.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
const esbuild = require('esbuild');
1+
import esbuild from 'esbuild';
22
// Automatically exclude all node_modules from the bundled version
3-
const { nodeExternalsPlugin } = require('esbuild-node-externals');
4-
const path = require('path');
3+
import { nodeExternalsPlugin } from 'esbuild-node-externals';
4+
import path from 'path';
55

66
function tsPathResolver(content) {
77
const relativePath = path.relative(path.dirname(this.resourcePath), path.resolve(__dirname, '../src'));

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
{
22
"name": "eth-hooks",
3-
"version": "4.0.4",
3+
"version": "4.0.6",
44
"description": "A set of hooks to turbocharge buidling",
55
"author": "Austin Griffith <[email protected]>",
66
"repository": "https://github.com/austintgriffith/eth-hooks.git",
77
"main": "index.js",
88
"module": "index.js",
9+
"type": "module",
910
"types": "index.d.ts",
1011
"engines": {
1112
"node": ">=12",

src/context/app-contracts/contractsContextFactory.tsx

+27-4
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import React, {
1111
useRef,
1212
} from 'react';
1313
import { useQueryClient } from 'react-query';
14+
import invariant from 'ts-invariant';
1415

1516
import { connectToContractWithAdaptor, useEthersContext } from '~~/context';
16-
import { invalidateCache, isValidEthersAdaptor, sortContractsByChainId } from '~~/functions';
17+
import { invalidateCache, isValidEthersAdaptor, sortContractsByChainId, sortContractsByName } from '~~/functions';
1718
import { TTypedContract, TEthersAdaptor, TConnectorList } from '~~/models';
1819
import { keyNamespace } from '~~/models/constants';
1920
import { TAppContractsContext, defaultAppContractsContext, TContractsByName } from '~~/models/contractContextTypes';
@@ -120,6 +121,21 @@ export const contractsContextFactory = <
120121
return newState;
121122
};
122123

124+
const removeInvalidContracts = (
125+
state: TAppContractsContext<GContractNames>,
126+
ethersAdaptor: TEthersAdaptor | undefined
127+
): TAppContractsContext<GContractNames> => {
128+
if (ethersAdaptor?.chainId != null) {
129+
const newState = cloneContextState(state);
130+
const chainId = ethersAdaptor.chainId;
131+
delete newState.contractsByChainId[chainId];
132+
133+
newState.contractsByName = sortContractsByName(newState.contractsByChainId);
134+
return newState;
135+
}
136+
return state;
137+
};
138+
123139
/* *************** ******** ****************************************** */
124140
/* *************** Contract Action Helper Functions ****************** */
125141
/**
@@ -132,8 +148,11 @@ export const contractsContextFactory = <
132148
state: TAppContractsContext<GContractNames>,
133149
ethersAdaptor: TEthersAdaptor | undefined
134150
): TAppContractsContext<GContractNames> => {
151+
if (ethersAdaptor == null || !isValidEthersAdaptor(ethersAdaptor)) {
152+
invariant.log('connectToAllContracts: Invalid ethers adaptor');
153+
return removeInvalidContracts(state, ethersAdaptor);
154+
}
135155
const newState = cloneContextState(state);
136-
if (ethersAdaptor == null || !isValidEthersAdaptor(ethersAdaptor)) return newState;
137156

138157
const { chainId, signer, provider } = ethersAdaptor;
139158
const providerOrSigner = signer ?? provider;
@@ -161,7 +180,10 @@ export const contractsContextFactory = <
161180
contractName: GContractNames,
162181
ethersAdaptor: TEthersAdaptor | undefined
163182
): TAppContractsContext<GContractNames> => {
164-
if (ethersAdaptor == null || !isValidEthersAdaptor(ethersAdaptor)) return state;
183+
if (ethersAdaptor == null || !isValidEthersAdaptor(ethersAdaptor)) {
184+
invariant.log('connectToAllContracts: Invalid ethers adaptor');
185+
return removeInvalidContracts(state, ethersAdaptor);
186+
}
165187

166188
const newState = cloneContextState(state);
167189
const { chainId } = ethersAdaptor;
@@ -292,14 +314,15 @@ export const contractsContextFactory = <
292314
const useConnectAppContracts = (adaptor: TEthersAdaptor | undefined): void => {
293315
const actions = useAppContractsActions();
294316
const queryClient = useQueryClient();
317+
const validAdaptorState = isValidEthersAdaptor(adaptor);
295318

296319
const connect = useCallback(() => {
297320
if (adaptor?.chainId != null && actions != null) {
298321
invalidateCache(queryClient, keyNamespace.contracts);
299322
actions.dispatch({ type: 'CONNECT_TO_CONTRACTS_WITH_ADAPTOR', payload: { ethersAdaptor: adaptor } });
300323
}
301324
// eslint-disable-next-line react-hooks/exhaustive-deps
302-
}, [adaptor?.provider, adaptor?.signer, adaptor?.chainId]);
325+
}, [adaptor?.provider, adaptor?.signer, adaptor?.chainId, adaptor?.account, validAdaptorState]);
303326

304327
useEffect(() => {
305328
void connect();

src/context/ethers/EthersAppContext.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,9 @@ import { IEthersContext } from '~~/models/ethersAppContextTypes';
3636
*/
3737
export const useEthersContext = (contextKey?: string): IEthersContext => {
3838
if (contextKey === 'primary') console.warn('Do not explicitly use primary contextKey, pass in undefined instead');
39-
4039
const { connector, activate, library, account, deactivate, chainId, ...context } =
4140
useWeb3React<TEthersProvider>(contextKey);
41+
4242
if (!(connector instanceof EthersModalConnector || connector instanceof AbstractConnector) && connector != null) {
4343
throw 'Connector is not a EthersModalConnector';
4444
}

src/context/ethers/connectors/EthersModalConnector.ts

+8-1
Original file line numberDiff line numberDiff line change
@@ -125,9 +125,10 @@ export class EthersModalConnector extends AbstractConnector implements ICommonMo
125125

126126
private handleChainChanged(chainId: number | string): void {
127127
this.log(`Handling chain changed to ${chainId}! updating providers`);
128-
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
128+
this._signer = undefined;
129129
this.emitUpdate?.({ chainId, provider: this._providerBase });
130130
this.setEthersProvider();
131+
void this.getSignerFromAccount();
131132
this.maybeReload();
132133
}
133134

@@ -279,6 +280,12 @@ export class EthersModalConnector extends AbstractConnector implements ICommonMo
279280
return this._signer;
280281
}
281282

283+
public async getSignerFromAccount(): Promise<void> {
284+
const account = await this.getAccount();
285+
await this.setSignerFromAccount(account);
286+
this.emitUpdate?.({ account });
287+
}
288+
282289
/**
283290
* #### Summary
284291
* Change the current signer and account used by the connector

src/functions/ethersHelpers.ts

+11-1
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,18 @@ export const isValidEthersContext = (ethersContext: IEthersContext | undefined):
6262

6363
export const isValidEthersAdaptor = (ethersAdaptor: TEthersAdaptor | undefined): boolean => {
6464
if (ethersAdaptor != null && ethersAdaptor.chainId != null) {
65-
if (ethersAdaptor.provider != null || (ethersAdaptor.signer != null && !!ethersAdaptor.account)) return true;
65+
if (ethersAdaptor.provider != null && ethersAdaptor.provider?.network?.chainId === ethersAdaptor.chainId) {
66+
return true;
67+
} else if (
68+
ethersAdaptor.signer != null &&
69+
!!ethersAdaptor.account &&
70+
(ethersAdaptor?.signer?.provider as TEthersProvider)?.network?.chainId === ethersAdaptor.chainId
71+
) {
72+
return true;
73+
}
6674
}
75+
76+
console.log('isValidEthersAdaptorπ', false, ethersAdaptor);
6777
return false;
6878
};
6979

src/functions/index.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@ export * from './ethersHelpers';
33
export * from './hookOptionsHelpers';
44
export * from './keyHelpers';
55
export * from './parseProviderOrSigner';
6-
export * from './sortContractsByChainId';
6+
export * from './sortContracts';

src/functions/keyHelpers.ts

+12-7
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,34 @@ export type TKeyTypes = {
1919
contractFunc?: string;
2020
};
2121

22-
export const providerKey = (providerOrSigner: TEthersProviderOrSigner | undefined): Record<'provider', string> => {
23-
if (providerOrSigner == null) return { provider: 'undefined provider' };
22+
export const providerKey = (
23+
providerOrSigner: TEthersProviderOrSigner | undefined
24+
): Record<'provider' | 'signer', string> => {
25+
if (providerOrSigner == null) return { provider: 'undefined provider', signer: 'undefined signer' };
2426

2527
if (providerOrSigner instanceof Provider) {
2628
return {
2729
provider: `${providerOrSigner?.network?.chainId}_${
2830
providerOrSigner?.network?.name
2931
}_${providerOrSigner?.connection.url.substring(0, 25)}`,
32+
signer: 'isProvider',
3033
};
3134
} else {
3235
const provider = providerOrSigner.provider as TEthersProvider;
3336
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-member-access
3437
const signerStr: string = (providerOrSigner as any)?.address ?? '';
3538
if (provider && provider?.network) {
3639
return {
37-
provider: `${provider?.network?.chainId}_${signerStr}_${
38-
provider?.network?.name
39-
}_${provider?.connection.url.substring(0, 25)}`,
40+
signer: `isSigner_${providerOrSigner._isSigner}_${signerStr}`,
41+
provider: `${provider?.network?.chainId}_${provider?.network?.name}_${provider?.connection.url.substring(
42+
0,
43+
25
44+
)}`,
4045
};
4146
}
4247
}
4348

44-
return { provider: 'unknown provider' };
49+
return { provider: 'unknown provider', signer: 'unknown signer' };
4550
};
4651

4752
export const adaptorKey = (adaptor: TEthersAdaptor | undefined): Partial<Record<'adaptor' | 'provider', string>> => {
@@ -60,7 +65,7 @@ export const eventKey = (m: Event | TypedEvent<Result>): string => {
6065
return `${m.transactionHash}_${m.logIndex}`;
6166
};
6267

63-
export const contractKey = (contract: BaseContract | undefined): Record<'contract', string> => {
68+
export const contractKey = (contract: BaseContract | undefined): Partial<Record<'contract' | 'provider', string>> => {
6469
if (contract == null) return { contract: 'undefined contract' };
6570

6671
const address = contract.address;

src/functions/parseProviderOrSigner.ts

+8-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { JsonRpcProvider, StaticJsonRpcProvider, Web3Provider } from '@ethersproject/providers';
22
import { ethers, Signer } from 'ethers';
33

4+
import { isValidEthersAdaptor } from '~~/functions';
45
import { TEthersProviderOrSigner, TEthersProvider } from '~~/models';
56
import { TEthersAdaptor } from '~~/models/ethersAppContextTypes';
67

@@ -23,10 +24,9 @@ export const parseProviderOrSigner = async (
2324
let account: string | undefined;
2425

2526
if (
26-
providerOrSigner &&
27-
(providerOrSigner instanceof JsonRpcProvider ||
28-
providerOrSigner instanceof Web3Provider ||
29-
providerOrSigner instanceof StaticJsonRpcProvider)
27+
providerOrSigner instanceof JsonRpcProvider ||
28+
providerOrSigner instanceof Web3Provider ||
29+
providerOrSigner instanceof StaticJsonRpcProvider
3030
) {
3131
provider = providerOrSigner;
3232
providerNetwork = await providerOrSigner.getNetwork();
@@ -52,5 +52,8 @@ export const parseProviderOrSigner = async (
5252
chainId: providerNetwork?.chainId,
5353
account,
5454
} as const;
55-
return result;
55+
56+
if (isValidEthersAdaptor(result)) return result;
57+
58+
return undefined;
5659
};

src/functions/sortContracts.tsx

+45
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { merge } from 'merge-anything';
2+
3+
import { TContractsByName, TContractsByChainId } from '~~/models/contractContextTypes';
4+
5+
export const sortContractsByChainId = <GContractNames extends string>(
6+
contractsByName: TContractsByName<GContractNames>
7+
): TContractsByChainId<GContractNames> => {
8+
let contractsByChainId: TContractsByChainId<GContractNames> = {} as TContractsByChainId<GContractNames>;
9+
10+
for (const nameStr in contractsByName) {
11+
const name: GContractNames = nameStr;
12+
for (const chainIdStr in contractsByName[name]) {
13+
const chainId = parseInt(chainIdStr);
14+
const data: TContractsByChainId<GContractNames> = {
15+
[chainId]: { [name]: contractsByName[name][chainId] },
16+
} as TContractsByChainId<GContractNames>;
17+
const temp = contractsByChainId;
18+
// @ts-ignore
19+
contractsByChainId = merge(temp, data);
20+
}
21+
}
22+
23+
return contractsByChainId;
24+
};
25+
26+
export const sortContractsByName = <GContractNames extends string>(
27+
contractsByChainId: TContractsByChainId<GContractNames>
28+
): TContractsByName<GContractNames> => {
29+
let contractsByName: TContractsByName<GContractNames> = {} as TContractsByName<GContractNames>;
30+
31+
for (const chainIdStr in contractsByChainId) {
32+
const chainId = parseInt(chainIdStr);
33+
for (const nameStr in contractsByChainId[chainId]) {
34+
const name: GContractNames = nameStr;
35+
const data: TContractsByName<GContractNames> = {
36+
[name]: { [chainId]: contractsByChainId[chainId][name] },
37+
} as TContractsByName<GContractNames>;
38+
const temp = contractsByName;
39+
// @ts-ignore
40+
contractsByName = merge(temp, data);
41+
}
42+
}
43+
44+
return contractsByName;
45+
};

src/functions/sortContractsByChainId.tsx

-24
This file was deleted.

src/hooks/useEthersAdaptorFromProviderOrSigners.ts

+22
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
import { useEffect } from 'react';
12
import { useQuery } from 'react-query';
23

34
import {
45
isAdaptorEqual,
6+
isValidEthersAdaptor,
57
mergeDefaultUpdateOptions,
68
parseProviderOrSigner,
79
providerKey,
@@ -42,5 +44,25 @@ export const useEthersAdaptorFromProviderOrSigners = (
4244
}
4345
);
4446

47+
const validAdaptorState = isValidEthersAdaptor(data);
48+
49+
// if the adaptor is not valid, refetch when the network is obtained
50+
useEffect(() => {
51+
if (data != null && !validAdaptorState) {
52+
console.log('not valid');
53+
if (data.provider) {
54+
void data.provider
55+
.getNetwork()
56+
.then(() => refetch())
57+
.catch();
58+
} else if (data.signer && data.account) {
59+
void data.signer.provider
60+
?.getNetwork()
61+
.then(() => refetch())
62+
.catch();
63+
}
64+
}
65+
}, [data, refetch, validAdaptorState]);
66+
4567
return [data, refetch];
4668
};

src/models/contractContextTypes.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@ export type TTypedContract<
3434
? TypedContract
3535
: BaseContract;
3636

37-
export type TContractsByName<GContractNames extends string> = {
38-
[contractName in GContractNames]: { [chainId: number]: BaseContract | undefined };
39-
};
40-
export type TContractsByChainId<GContractNames extends string> = {
41-
[chainId: number]: { [contractName in GContractNames]: BaseContract | undefined };
42-
};
37+
export type TContractsByName<GContractNames extends string> = Record<
38+
GContractNames,
39+
{ [chainId: number]: BaseContract | undefined }
40+
>;
41+
42+
export type TContractsByChainId<GContractNames extends string> = Record<
43+
number,
44+
{ [contractName in GContractNames]: BaseContract | undefined }
45+
>;
4346

4447
/**
4548
*

0 commit comments

Comments
 (0)