@toruslabs/ethereum-controllers 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ethereumControllers.cjs.js +6153 -0
- package/dist/ethereumControllers.cjs.js.map +1 -0
- package/dist/ethereumControllers.esm.js +5570 -0
- package/dist/ethereumControllers.esm.js.map +1 -0
- package/dist/ethereumControllers.umd.min.js +3 -0
- package/dist/ethereumControllers.umd.min.js.LICENSE.txt +38 -0
- package/dist/ethereumControllers.umd.min.js.map +1 -0
- package/dist/types/Account/AccountTrackerController.d.ts +35 -0
- package/dist/types/Block/PollingBlockTracker.d.ts +14 -0
- package/dist/types/Currency/CurrencyController.d.ts +30 -0
- package/dist/types/Gas/GasFeeController.d.ts +64 -0
- package/dist/types/Gas/IGasFeeController.d.ts +49 -0
- package/dist/types/Gas/gasUtil.d.ts +21 -0
- package/dist/types/Keyring/KeyringController.d.ts +20 -0
- package/dist/types/Message/AbstractMessageController.d.ts +36 -0
- package/dist/types/Message/DecryptMessageController.d.ts +20 -0
- package/dist/types/Message/EncryptionPublicKeyController.d.ts +20 -0
- package/dist/types/Message/MessageController.d.ts +20 -0
- package/dist/types/Message/PersonalMessageController.d.ts +20 -0
- package/dist/types/Message/TypedMessageController.d.ts +21 -0
- package/dist/types/Message/utils.d.ts +10 -0
- package/dist/types/Network/NetworkController.d.ts +40 -0
- package/dist/types/Network/createEthereumMiddleware.d.ts +66 -0
- package/dist/types/Network/createJsonRpcClient.d.ts +9 -0
- package/dist/types/Nfts/INftsController.d.ts +10 -0
- package/dist/types/Nfts/NftHandler.d.ts +35 -0
- package/dist/types/Nfts/NftsController.d.ts +40 -0
- package/dist/types/Preferences/PreferencesController.d.ts +53 -0
- package/dist/types/Tokens/ITokensController.d.ts +10 -0
- package/dist/types/Tokens/TokenHandler.d.ts +20 -0
- package/dist/types/Tokens/TokenRatesController.d.ts +42 -0
- package/dist/types/Tokens/TokensController.d.ts +42 -0
- package/dist/types/Transaction/NonceTracker.d.ts +37 -0
- package/dist/types/Transaction/PendingTransactionTracker.d.ts +32 -0
- package/dist/types/Transaction/TransactionController.d.ts +67 -0
- package/dist/types/Transaction/TransactionGasUtil.d.ts +21 -0
- package/dist/types/Transaction/TransactionStateHistoryHelper.d.ts +16 -0
- package/dist/types/Transaction/TransactionStateManager.d.ts +30 -0
- package/dist/types/Transaction/TransactionUtils.d.ts +70 -0
- package/dist/types/index.d.ts +43 -0
- package/dist/types/utils/abiDecoder.d.ts +17 -0
- package/dist/types/utils/abis.d.ts +84 -0
- package/dist/types/utils/constants.d.ts +81 -0
- package/dist/types/utils/contractAddresses.d.ts +1 -0
- package/dist/types/utils/conversionUtils.d.ts +42 -0
- package/dist/types/utils/helpers.d.ts +24 -0
- package/dist/types/utils/interfaces.d.ts +384 -0
- package/package.json +71 -0
- package/src/Account/AccountTrackerController.ts +157 -0
- package/src/Block/PollingBlockTracker.ts +89 -0
- package/src/Currency/CurrencyController.ts +117 -0
- package/src/Gas/GasFeeController.ts +254 -0
- package/src/Gas/IGasFeeController.ts +56 -0
- package/src/Gas/gasUtil.ts +163 -0
- package/src/Keyring/KeyringController.ts +118 -0
- package/src/Message/AbstractMessageController.ts +136 -0
- package/src/Message/DecryptMessageController.ts +81 -0
- package/src/Message/EncryptionPublicKeyController.ts +83 -0
- package/src/Message/MessageController.ts +74 -0
- package/src/Message/PersonalMessageController.ts +74 -0
- package/src/Message/TypedMessageController.ts +112 -0
- package/src/Message/utils.ts +107 -0
- package/src/Network/NetworkController.ts +184 -0
- package/src/Network/createEthereumMiddleware.ts +307 -0
- package/src/Network/createJsonRpcClient.ts +59 -0
- package/src/Nfts/INftsController.ts +13 -0
- package/src/Nfts/NftHandler.ts +191 -0
- package/src/Nfts/NftsController.ts +230 -0
- package/src/Preferences/PreferencesController.ts +409 -0
- package/src/Tokens/ITokensController.ts +13 -0
- package/src/Tokens/TokenHandler.ts +60 -0
- package/src/Tokens/TokenRatesController.ts +134 -0
- package/src/Tokens/TokensController.ts +278 -0
- package/src/Transaction/NonceTracker.ts +152 -0
- package/src/Transaction/PendingTransactionTracker.ts +235 -0
- package/src/Transaction/TransactionController.ts +558 -0
- package/src/Transaction/TransactionGasUtil.ts +74 -0
- package/src/Transaction/TransactionStateHistoryHelper.ts +41 -0
- package/src/Transaction/TransactionStateManager.ts +315 -0
- package/src/Transaction/TransactionUtils.ts +333 -0
- package/src/index.ts +45 -0
- package/src/utils/abiDecoder.ts +195 -0
- package/src/utils/abis.ts +677 -0
- package/src/utils/constants.ts +379 -0
- package/src/utils/contractAddresses.ts +21 -0
- package/src/utils/conversionUtils.ts +269 -0
- package/src/utils/helpers.ts +177 -0
- package/src/utils/interfaces.ts +454 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { BaseConfig, randomId } from "@toruslabs/base-controllers";
|
|
2
|
+
import { JRPCRequest } from "@toruslabs/openlogin-jrpc";
|
|
3
|
+
import log from "loglevel";
|
|
4
|
+
|
|
5
|
+
import KeyringController from "../Keyring/KeyringController";
|
|
6
|
+
import NetworkController from "../Network/NetworkController";
|
|
7
|
+
import { MessageStatus, METHOD_TYPES } from "../utils/constants";
|
|
8
|
+
import { Message, MessageParams, UserRequestApprovalParams } from "../utils/interfaces";
|
|
9
|
+
import AbstractMessageController, { MessageControllerState } from "./AbstractMessageController";
|
|
10
|
+
import { normalizeMessageData, validateSignMessageData } from "./utils";
|
|
11
|
+
|
|
12
|
+
export class PersonalMessageController extends AbstractMessageController<Message, MessageParams> {
|
|
13
|
+
override name = "PersonalMessageController";
|
|
14
|
+
|
|
15
|
+
protected signPersonalMessage: KeyringController["signPersonalMessage"];
|
|
16
|
+
|
|
17
|
+
constructor({
|
|
18
|
+
config,
|
|
19
|
+
state,
|
|
20
|
+
signPersonalMessage,
|
|
21
|
+
getNetworkIdentifier,
|
|
22
|
+
}: {
|
|
23
|
+
config: Partial<BaseConfig>;
|
|
24
|
+
state: Partial<MessageControllerState<Message>>;
|
|
25
|
+
signPersonalMessage: KeyringController["signPersonalMessage"];
|
|
26
|
+
getNetworkIdentifier: NetworkController["getNetworkIdentifier"];
|
|
27
|
+
}) {
|
|
28
|
+
super({ config, state, getNetworkIdentifier });
|
|
29
|
+
this.signPersonalMessage = signPersonalMessage;
|
|
30
|
+
this.initialize();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async processPersonalSignMessage(messageId: string): Promise<string> {
|
|
34
|
+
try {
|
|
35
|
+
const msgObject = this.getMessage(messageId);
|
|
36
|
+
const cleanMsgParams = await this.approveMessage(msgObject.messageParams);
|
|
37
|
+
const rawSig = await this.signPersonalMessage(cleanMsgParams.data, cleanMsgParams.from);
|
|
38
|
+
this.updateMessage({ ...msgObject, rawSig });
|
|
39
|
+
this.setMessageStatus(messageId, MessageStatus.SIGNED);
|
|
40
|
+
return rawSig;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
log.error(error);
|
|
43
|
+
this.setMessageStatus(messageId, MessageStatus.FAILED);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async addNewUnapprovedMessage(messageParams: MessageParams, req: JRPCRequest<MessageParams> & UserRequestApprovalParams): Promise<string> {
|
|
48
|
+
await this.addUnapprovedMessage(messageParams, req);
|
|
49
|
+
return this.waitForFinishStatus(messageParams, this.name);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async addUnapprovedMessage(messageParams: MessageParams, req: JRPCRequest<MessageParams> & UserRequestApprovalParams): Promise<string> {
|
|
53
|
+
validateSignMessageData(messageParams);
|
|
54
|
+
if (req) {
|
|
55
|
+
messageParams.origin = req.origin;
|
|
56
|
+
}
|
|
57
|
+
messageParams.data = normalizeMessageData(messageParams.data);
|
|
58
|
+
const messageId = randomId();
|
|
59
|
+
const messageData: Message = {
|
|
60
|
+
id: messageId,
|
|
61
|
+
messageParams,
|
|
62
|
+
status: MessageStatus.UNAPPROVED,
|
|
63
|
+
time: Date.now(),
|
|
64
|
+
type: METHOD_TYPES.PERSONAL_SIGN,
|
|
65
|
+
};
|
|
66
|
+
await this.addMessage(messageData);
|
|
67
|
+
this.emit(`unapprovedMessage`, messageParams);
|
|
68
|
+
return messageId;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
prepMessageForSigning(messageParams: MessageParams): Promise<MessageParams> {
|
|
72
|
+
return Promise.resolve(messageParams);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { MessageTypes, SignTypedDataVersion, TypedDataV1, TypedMessage as EthSigTypedMessage } from "@metamask/eth-sig-util";
|
|
2
|
+
import { BaseConfig, randomId } from "@toruslabs/base-controllers";
|
|
3
|
+
import { JRPCRequest } from "@toruslabs/openlogin-jrpc";
|
|
4
|
+
import log from "loglevel";
|
|
5
|
+
|
|
6
|
+
import KeyringController from "../Keyring/KeyringController";
|
|
7
|
+
import NetworkController from "../Network/NetworkController";
|
|
8
|
+
import { MessageStatus, METHOD_TYPES } from "../utils/constants";
|
|
9
|
+
import { METHOD_TYPES_TYPE, TypedMessage, TypedMessageParams, UserRequestApprovalParams } from "../utils/interfaces";
|
|
10
|
+
import AbstractMessageController, { MessageControllerState } from "./AbstractMessageController";
|
|
11
|
+
import { validateTypedSignMessageDataV1, validateTypedSignMessageDataV3V4 } from "./utils";
|
|
12
|
+
|
|
13
|
+
function getMessageType(version: SignTypedDataVersion): METHOD_TYPES_TYPE {
|
|
14
|
+
switch (version) {
|
|
15
|
+
case SignTypedDataVersion.V1:
|
|
16
|
+
return METHOD_TYPES.ETH_SIGN_TYPED_DATA;
|
|
17
|
+
case SignTypedDataVersion.V3:
|
|
18
|
+
return METHOD_TYPES.ETH_SIGN_TYPED_DATA_V3;
|
|
19
|
+
case SignTypedDataVersion.V4:
|
|
20
|
+
return METHOD_TYPES.ETH_SIGN_TYPED_DATA_V4;
|
|
21
|
+
default:
|
|
22
|
+
return METHOD_TYPES.ETH_SIGN_TYPED_DATA;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export class TypedMessageController extends AbstractMessageController<TypedMessage, TypedMessageParams> {
|
|
27
|
+
override name = "TypedMessageController";
|
|
28
|
+
|
|
29
|
+
protected signTypedData: KeyringController["signTypedData"];
|
|
30
|
+
|
|
31
|
+
constructor({
|
|
32
|
+
config,
|
|
33
|
+
state,
|
|
34
|
+
signTypedData,
|
|
35
|
+
getNetworkIdentifier,
|
|
36
|
+
}: {
|
|
37
|
+
config: Partial<BaseConfig>;
|
|
38
|
+
state: Partial<MessageControllerState<TypedMessage>>;
|
|
39
|
+
signTypedData: KeyringController["signTypedData"];
|
|
40
|
+
getNetworkIdentifier: NetworkController["getNetworkIdentifier"];
|
|
41
|
+
}) {
|
|
42
|
+
super({ config, state, getNetworkIdentifier });
|
|
43
|
+
this.signTypedData = signTypedData;
|
|
44
|
+
this.initialize();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async processPersonalSignMessage(messageId: string): Promise<string> {
|
|
48
|
+
try {
|
|
49
|
+
const msgObject = this.getMessage(messageId);
|
|
50
|
+
const cleanMsgParams = await this.approveMessage(msgObject.messageParams);
|
|
51
|
+
const rawSig = await this.signTypedData(
|
|
52
|
+
cleanMsgParams.data as TypedDataV1 | EthSigTypedMessage<MessageTypes>,
|
|
53
|
+
cleanMsgParams.from,
|
|
54
|
+
cleanMsgParams.version
|
|
55
|
+
);
|
|
56
|
+
this.updateMessage({ ...msgObject, rawSig });
|
|
57
|
+
this.setMessageStatus(messageId, MessageStatus.SIGNED);
|
|
58
|
+
return rawSig;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
log.error(error);
|
|
61
|
+
this.setMessageStatus(messageId, MessageStatus.FAILED);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async addNewUnapprovedMessage(
|
|
66
|
+
messageParams: TypedMessageParams,
|
|
67
|
+
req: JRPCRequest<TypedMessageParams> & UserRequestApprovalParams,
|
|
68
|
+
version: SignTypedDataVersion
|
|
69
|
+
): Promise<string> {
|
|
70
|
+
await this.addUnapprovedMessage(messageParams, req, version);
|
|
71
|
+
return this.waitForFinishStatus(messageParams, this.name);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async addUnapprovedMessage(
|
|
75
|
+
messageParams: TypedMessageParams,
|
|
76
|
+
req: JRPCRequest<TypedMessageParams> & UserRequestApprovalParams,
|
|
77
|
+
version: SignTypedDataVersion
|
|
78
|
+
): Promise<string> {
|
|
79
|
+
if (version === SignTypedDataVersion.V1) {
|
|
80
|
+
validateTypedSignMessageDataV1(messageParams);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (version === SignTypedDataVersion.V3 || version === SignTypedDataVersion.V4) {
|
|
84
|
+
const currentChainId = this.getNetworkIdentifier();
|
|
85
|
+
validateTypedSignMessageDataV3V4(messageParams, currentChainId);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (typeof messageParams.data !== "string" && (version === SignTypedDataVersion.V3 || version === SignTypedDataVersion.V4)) {
|
|
89
|
+
messageParams.data = JSON.stringify(messageParams.data);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
if (req) {
|
|
93
|
+
messageParams.origin = req.origin;
|
|
94
|
+
}
|
|
95
|
+
messageParams.version = version;
|
|
96
|
+
const messageId = randomId();
|
|
97
|
+
const messageData: TypedMessage = {
|
|
98
|
+
id: messageId,
|
|
99
|
+
messageParams,
|
|
100
|
+
status: MessageStatus.UNAPPROVED,
|
|
101
|
+
time: Date.now(),
|
|
102
|
+
type: getMessageType(version),
|
|
103
|
+
};
|
|
104
|
+
await this.addMessage(messageData);
|
|
105
|
+
this.emit(`unapprovedMessage`, messageParams);
|
|
106
|
+
return messageId;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
prepMessageForSigning(messageParams: TypedMessageParams): Promise<TypedMessageParams> {
|
|
110
|
+
return Promise.resolve(messageParams);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { addHexPrefix, bytesToHex, isValidAddress, stripHexPrefix } from "@ethereumjs/util";
|
|
2
|
+
import { EthEncryptedData, TYPED_MESSAGE_SCHEMA, TypedDataV1Field, typedSignatureHash } from "@metamask/eth-sig-util";
|
|
3
|
+
import { validate } from "jsonschema";
|
|
4
|
+
|
|
5
|
+
import { DecryptMessageParams, EncryptionPublicKeyParams, MessageParams, TypedMessageParams } from "../utils/interfaces";
|
|
6
|
+
|
|
7
|
+
const hexRe = /^[0-9A-Fa-f]+$/gu;
|
|
8
|
+
|
|
9
|
+
export function validateAddress(address: string, propertyName: string) {
|
|
10
|
+
if (!address || typeof address !== "string" || !isValidAddress(address)) {
|
|
11
|
+
throw new Error(`Invalid "${propertyName}" address: ${address} must be a valid string.`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function validateSignMessageData(messageData: MessageParams) {
|
|
16
|
+
const { from, data } = messageData;
|
|
17
|
+
validateAddress(from, "from");
|
|
18
|
+
|
|
19
|
+
if (!data || typeof data !== "string") {
|
|
20
|
+
throw new Error(`Invalid message "data": ${data} must be a valid string.`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function normalizeMessageData(data: string): string {
|
|
25
|
+
try {
|
|
26
|
+
const stripped = stripHexPrefix(data);
|
|
27
|
+
if (stripped.match(hexRe)) {
|
|
28
|
+
return addHexPrefix(stripped);
|
|
29
|
+
}
|
|
30
|
+
} catch (e) {}
|
|
31
|
+
return bytesToHex(Buffer.from(data, "utf8"));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function validateTypedSignMessageDataV1(messageData: TypedMessageParams) {
|
|
35
|
+
validateAddress(messageData.from, "from");
|
|
36
|
+
|
|
37
|
+
if (!messageData.data || !Array.isArray(messageData.data)) {
|
|
38
|
+
throw new Error(`Invalid message "data": ${messageData.data} must be a valid array.`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
try {
|
|
42
|
+
// typedSignatureHash will throw if the data is invalid.
|
|
43
|
+
typedSignatureHash(messageData.data as TypedDataV1Field[]);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
throw new Error(`Expected EIP712 typed data.`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function validateTypedSignMessageDataV3V4(messageData: TypedMessageParams, currentChainId: string) {
|
|
50
|
+
validateAddress(messageData.from, "from");
|
|
51
|
+
|
|
52
|
+
if (!messageData.data || Array.isArray(messageData.data) || (typeof messageData.data !== "object" && typeof messageData.data !== "string")) {
|
|
53
|
+
throw new Error(`Invalid message "data": Must be a valid string or object.`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
let data;
|
|
57
|
+
if (typeof messageData.data === "object") {
|
|
58
|
+
data = messageData.data;
|
|
59
|
+
} else {
|
|
60
|
+
try {
|
|
61
|
+
data = JSON.parse(messageData.data);
|
|
62
|
+
} catch (e) {
|
|
63
|
+
throw new Error("Data must be passed as a valid JSON string.");
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const validation = validate(data, TYPED_MESSAGE_SCHEMA);
|
|
68
|
+
if (validation.errors.length > 0) {
|
|
69
|
+
throw new Error("Data must conform to EIP-712 schema. See https://git.io/fNtcx.");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (!currentChainId) {
|
|
73
|
+
throw new Error("Current chainId cannot be null or undefined.");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let { chainId } = data.domain;
|
|
77
|
+
if (chainId) {
|
|
78
|
+
if (typeof chainId === "string") {
|
|
79
|
+
chainId = parseInt(chainId, chainId.startsWith("0x") ? 16 : 10);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const activeChainId = parseInt(currentChainId, 16);
|
|
83
|
+
if (Number.isNaN(activeChainId)) {
|
|
84
|
+
throw new Error(`Cannot sign messages for chainId "${chainId}", because MetaMask is switching networks.`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (chainId !== activeChainId) {
|
|
88
|
+
throw new Error(`Provided chainId "${chainId}" must match the active chainId "${activeChainId}"`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function validateEncryptionPublicKeyMessageData(messageData: EncryptionPublicKeyParams) {
|
|
94
|
+
const { from } = messageData;
|
|
95
|
+
validateAddress(from, "from");
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function validateDecryptedMessageData(messageData: DecryptMessageParams) {
|
|
99
|
+
const { from } = messageData;
|
|
100
|
+
validateAddress(from, "from");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function parseDecryptMessageData(data: string): EthEncryptedData {
|
|
104
|
+
const stripped = stripHexPrefix(data);
|
|
105
|
+
const buffer = Buffer.from(stripped, "hex");
|
|
106
|
+
return JSON.parse(buffer.toString("utf8")) as EthEncryptedData;
|
|
107
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { BaseController, createEventEmitterProxy, createSwappableProxy, INetworkController } from "@toruslabs/base-controllers";
|
|
2
|
+
import { JRPCEngine, JRPCMiddleware, providerFromEngine, SafeEventEmitterProvider } from "@toruslabs/openlogin-jrpc";
|
|
3
|
+
import { Mutex } from "async-mutex";
|
|
4
|
+
import log from "loglevel";
|
|
5
|
+
|
|
6
|
+
import PollingBlockTracker from "../Block/PollingBlockTracker";
|
|
7
|
+
import { SUPPORTED_NETWORKS } from "../utils/constants";
|
|
8
|
+
import { EthereumNetworkConfig, EthereumNetworkState, EthereumProviderConfig } from "../utils/interfaces";
|
|
9
|
+
import { createEthereumMiddleware, IProviderHandlers } from "./createEthereumMiddleware";
|
|
10
|
+
import { createJsonRpcClient } from "./createJsonRpcClient";
|
|
11
|
+
|
|
12
|
+
export default class NetworkController
|
|
13
|
+
extends BaseController<EthereumNetworkConfig, EthereumNetworkState>
|
|
14
|
+
implements INetworkController<EthereumNetworkConfig, EthereumNetworkState>
|
|
15
|
+
{
|
|
16
|
+
name = "NetworkController";
|
|
17
|
+
|
|
18
|
+
providerProxy: SafeEventEmitterProvider;
|
|
19
|
+
|
|
20
|
+
blockTrackerProxy: PollingBlockTracker;
|
|
21
|
+
|
|
22
|
+
private mutex: Mutex = new Mutex();
|
|
23
|
+
|
|
24
|
+
private provider?: SafeEventEmitterProvider = null;
|
|
25
|
+
|
|
26
|
+
private blockTracker?: PollingBlockTracker = null;
|
|
27
|
+
|
|
28
|
+
private baseProviderHandlers: IProviderHandlers;
|
|
29
|
+
|
|
30
|
+
constructor({ config, state }: { config?: Partial<EthereumNetworkConfig>; state?: Partial<EthereumNetworkState> }) {
|
|
31
|
+
super({ config, state });
|
|
32
|
+
|
|
33
|
+
this.defaultState = {
|
|
34
|
+
chainId: "loading",
|
|
35
|
+
properties: { EIPS_1559: undefined },
|
|
36
|
+
providerConfig: SUPPORTED_NETWORKS.mainnet,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
// when a new network is set,
|
|
40
|
+
// we set to loading first and
|
|
41
|
+
// then when connection succeeds,
|
|
42
|
+
// we update the network
|
|
43
|
+
this.initialize();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
getNetworkIdentifier(): string {
|
|
47
|
+
return this.state.chainId;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
getNetworkRPCUrl(): string {
|
|
51
|
+
return this.state.providerConfig.rpcTarget;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Called by orchestrator once while initializing the class
|
|
56
|
+
* @param providerHandlers - JRPC handlers for provider
|
|
57
|
+
* @returns - provider - Returns the providerProxy
|
|
58
|
+
*/
|
|
59
|
+
public initializeProvider(providerHandlers: IProviderHandlers): SafeEventEmitterProvider {
|
|
60
|
+
this.baseProviderHandlers = providerHandlers;
|
|
61
|
+
this.configureProvider();
|
|
62
|
+
this.lookupNetwork(); // Not awaiting this, because we don't want to block the initialization
|
|
63
|
+
return this.providerProxy;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
getProvider(): SafeEventEmitterProvider {
|
|
67
|
+
return this.providerProxy;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getBlockTracker(): PollingBlockTracker {
|
|
71
|
+
return this.blockTrackerProxy;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
getProviderConfig(): EthereumProviderConfig {
|
|
75
|
+
return this.state.providerConfig;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
setProviderConfig(config: EthereumProviderConfig): void {
|
|
79
|
+
this.update({
|
|
80
|
+
providerConfig: { ...config },
|
|
81
|
+
});
|
|
82
|
+
this.refreshNetwork();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async getEIP1559Compatibility(): Promise<boolean> {
|
|
86
|
+
const { EIPS_1559 } = this.state.properties;
|
|
87
|
+
// log.info('checking eip 1559 compatibility')
|
|
88
|
+
if (EIPS_1559 !== undefined) {
|
|
89
|
+
return EIPS_1559 as boolean;
|
|
90
|
+
}
|
|
91
|
+
const latestBlock = await this.blockTracker.getLatestBlock();
|
|
92
|
+
const supportsEIP1559 = latestBlock && latestBlock.baseFeePerGas !== undefined;
|
|
93
|
+
this.update({ properties: { EIPS_1559: supportsEIP1559 } });
|
|
94
|
+
return supportsEIP1559;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Refreshes the current network code
|
|
99
|
+
*/
|
|
100
|
+
async lookupNetwork(): Promise<void> {
|
|
101
|
+
const { chainId, rpcTarget } = this.getProviderConfig();
|
|
102
|
+
if (!chainId || !rpcTarget || !this.provider) {
|
|
103
|
+
this.update({ chainId: "loading", properties: {} });
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
const releaseLock = await this.mutex.acquire();
|
|
107
|
+
try {
|
|
108
|
+
// use eth_chainId
|
|
109
|
+
const [networkChainId] = await Promise.all([this.provider.request<never, string>({ method: "eth_chainId" }), this.getEIP1559Compatibility()]);
|
|
110
|
+
log.info("network fetched chain id", networkChainId);
|
|
111
|
+
// update chain ID
|
|
112
|
+
this.update({
|
|
113
|
+
chainId: networkChainId,
|
|
114
|
+
});
|
|
115
|
+
this.emit("networkDidChange");
|
|
116
|
+
} catch {
|
|
117
|
+
this.update({
|
|
118
|
+
chainId: "loading",
|
|
119
|
+
});
|
|
120
|
+
} finally {
|
|
121
|
+
releaseLock();
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
private configureProvider(): void {
|
|
126
|
+
const { chainId, rpcTarget, ...rest } = this.getProviderConfig();
|
|
127
|
+
if (!chainId || !rpcTarget) {
|
|
128
|
+
throw new Error("chainId and rpcTarget must be provider in providerConfig");
|
|
129
|
+
}
|
|
130
|
+
this.configureStandardProvider({ chainId, rpcTarget, ...rest });
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
private setNetworkClient({
|
|
134
|
+
networkMiddleware,
|
|
135
|
+
blockTracker,
|
|
136
|
+
}: {
|
|
137
|
+
networkMiddleware: JRPCMiddleware<unknown, unknown>;
|
|
138
|
+
blockTracker: PollingBlockTracker;
|
|
139
|
+
}): void {
|
|
140
|
+
const ethereumMiddleware = createEthereumMiddleware(this.baseProviderHandlers);
|
|
141
|
+
const engine = new JRPCEngine();
|
|
142
|
+
engine.push(ethereumMiddleware);
|
|
143
|
+
engine.push(networkMiddleware);
|
|
144
|
+
const provider = providerFromEngine(engine);
|
|
145
|
+
this.setProvider({ provider, blockTracker });
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
private setProvider({ provider, blockTracker }: { provider: SafeEventEmitterProvider; blockTracker: PollingBlockTracker }): void {
|
|
149
|
+
if (this.providerProxy) {
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
151
|
+
// @ts-ignore
|
|
152
|
+
this.providerProxy.setTarget(provider);
|
|
153
|
+
} else {
|
|
154
|
+
this.providerProxy = createSwappableProxy<SafeEventEmitterProvider>(provider);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (this.blockTrackerProxy) {
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
159
|
+
// @ts-ignore
|
|
160
|
+
this.blockTrackerProxy.setTarget(blockTracker);
|
|
161
|
+
} else {
|
|
162
|
+
this.blockTrackerProxy = createEventEmitterProxy<PollingBlockTracker>(blockTracker, {
|
|
163
|
+
eventFilter: "skipInternal",
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// set new provider and blockTracker
|
|
168
|
+
this.provider = provider;
|
|
169
|
+
provider.setMaxListeners(10);
|
|
170
|
+
this.blockTracker = blockTracker;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
private configureStandardProvider(providerConfig: EthereumProviderConfig): void {
|
|
174
|
+
const networkClient = createJsonRpcClient(providerConfig);
|
|
175
|
+
log.info("networkClient", networkClient);
|
|
176
|
+
this.setNetworkClient(networkClient);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
private refreshNetwork() {
|
|
180
|
+
this.update({ chainId: "loading", properties: {} });
|
|
181
|
+
this.configureProvider();
|
|
182
|
+
this.lookupNetwork();
|
|
183
|
+
}
|
|
184
|
+
}
|