@exodus/web3-ethereum 1.1.0 → 1.3.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/lib/provider/provider.js +24 -24
- package/lib/rpc-handlers/exposeHandlers.d.ts +4 -0
- package/lib/rpc-handlers/exposeHandlers.js +215 -0
- package/lib/rpc-handlers/index.d.ts +12 -0
- package/lib/rpc-handlers/index.js +48 -0
- package/lib/rpc-handlers/parseAndValidateTypedData.d.ts +5 -0
- package/lib/rpc-handlers/parseAndValidateTypedData.js +30 -0
- package/lib/rpc-handlers/validator.d.ts +1 -0
- package/lib/rpc-handlers/validator.js +3 -0
- package/lib/types.d.ts +30 -0
- package/lib/types.js +1 -0
- package/package.json +4 -2
package/lib/provider/provider.js
CHANGED
|
@@ -38,30 +38,30 @@ export class EthereumProvider extends BaseProvider {
|
|
|
38
38
|
const prefix = this.#chainId;
|
|
39
39
|
return super._callRpcMethod(`${prefix}_${method}`, params);
|
|
40
40
|
}
|
|
41
|
-
#assertConnected() {
|
|
41
|
+
#assertConnected = () => {
|
|
42
42
|
if (!this.isConnected()) {
|
|
43
43
|
throw ethErrors.provider.disconnected({
|
|
44
44
|
message: 'The Provider is disconnected from all chains.',
|
|
45
45
|
});
|
|
46
46
|
}
|
|
47
|
-
}
|
|
48
|
-
#assertChainSupported(chainId) {
|
|
47
|
+
};
|
|
48
|
+
#assertChainSupported = (chainId) => {
|
|
49
49
|
if (!this.#supportedChainIds.includes(chainId)) {
|
|
50
50
|
throw ethErrors.provider.custom({
|
|
51
51
|
message: 'The Provider does not support the requested chain.',
|
|
52
52
|
code: 4902,
|
|
53
53
|
});
|
|
54
54
|
}
|
|
55
|
-
}
|
|
56
|
-
async
|
|
55
|
+
};
|
|
56
|
+
#getChainId = async () => {
|
|
57
57
|
const storedChainId = await this.#storage.getChainId();
|
|
58
58
|
const defaultChainId = this.#supportedChainIds[0];
|
|
59
59
|
return storedChainId ?? defaultChainId;
|
|
60
|
-
}
|
|
61
|
-
async
|
|
60
|
+
};
|
|
61
|
+
#getAccounts = async () => {
|
|
62
62
|
return this._callRpcMethod('eth_accounts', []);
|
|
63
|
-
}
|
|
64
|
-
async
|
|
63
|
+
};
|
|
64
|
+
#initialize = async () => {
|
|
65
65
|
const chainId = await this.#getChainId();
|
|
66
66
|
this.#handleConnect(chainId);
|
|
67
67
|
this.#handleChainChanged(chainId);
|
|
@@ -69,19 +69,19 @@ export class EthereumProvider extends BaseProvider {
|
|
|
69
69
|
this.#handleAccountsChanged(accounts);
|
|
70
70
|
this.#initialized = true;
|
|
71
71
|
this.#initializePromise = null;
|
|
72
|
-
}
|
|
73
|
-
async
|
|
72
|
+
};
|
|
73
|
+
#awaitInitialized = async () => {
|
|
74
74
|
if (this.#initializePromise) {
|
|
75
75
|
await this.#initializePromise;
|
|
76
76
|
}
|
|
77
|
-
}
|
|
78
|
-
#handleConnect(chainId) {
|
|
77
|
+
};
|
|
78
|
+
#handleConnect = (chainId) => {
|
|
79
79
|
if (!this.#isConnected) {
|
|
80
80
|
this.#isConnected = true;
|
|
81
81
|
this.emit('connect', { chainId });
|
|
82
82
|
}
|
|
83
|
-
}
|
|
84
|
-
#handleChainChanged(chainId) {
|
|
83
|
+
};
|
|
84
|
+
#handleChainChanged = (chainId) => {
|
|
85
85
|
if (!chainId || typeof chainId !== 'string' || !chainId.startsWith('0x')) {
|
|
86
86
|
return;
|
|
87
87
|
}
|
|
@@ -91,8 +91,8 @@ export class EthereumProvider extends BaseProvider {
|
|
|
91
91
|
this.emit('chainChanged', chainId);
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
|
-
}
|
|
95
|
-
#handleAccountsChanged(accounts) {
|
|
94
|
+
};
|
|
95
|
+
#handleAccountsChanged = (accounts) => {
|
|
96
96
|
// `accounts` is always going to have a single account (or none) for the time being.
|
|
97
97
|
if (this.#accounts[0] !== accounts[0]) {
|
|
98
98
|
this.#accounts = accounts;
|
|
@@ -101,8 +101,8 @@ export class EthereumProvider extends BaseProvider {
|
|
|
101
101
|
this.emit('accountsChanged', accounts);
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
-
}
|
|
105
|
-
async
|
|
104
|
+
};
|
|
105
|
+
#requestAccounts = async () => {
|
|
106
106
|
const permissions = { eth_accounts: {} };
|
|
107
107
|
await this._callRpcMethod('wallet_requestPermissions', [permissions]);
|
|
108
108
|
const accounts = await this._callRpcMethod('eth_accounts', []);
|
|
@@ -115,21 +115,21 @@ export class EthereumProvider extends BaseProvider {
|
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
117
|
return accounts;
|
|
118
|
-
}
|
|
119
|
-
async
|
|
118
|
+
};
|
|
119
|
+
#addChain = async (chainId) => {
|
|
120
120
|
// Exodus does not support the addition of custom chains.
|
|
121
121
|
// If the requested chain is supported, the provider will switch to that chain.
|
|
122
122
|
// This matches MetaMask's behavior.
|
|
123
123
|
// See: https://github.com/MetaMask/metamask-extension/blob/d92936475a942fed060bb36f810ee6bfd901f4f4/app/scripts/lib/rpc-method-middleware/handlers/add-ethereum-chain.js#L167-L183.
|
|
124
124
|
return this.#switchChain(chainId);
|
|
125
|
-
}
|
|
126
|
-
async
|
|
125
|
+
};
|
|
126
|
+
#switchChain = async (chainId) => {
|
|
127
127
|
const lowerCaseChainId = chainId.toLowerCase();
|
|
128
128
|
this.#assertChainSupported(lowerCaseChainId);
|
|
129
129
|
this.#handleChainChanged(lowerCaseChainId);
|
|
130
130
|
await this.#storage.setChainId(lowerCaseChainId);
|
|
131
131
|
return null;
|
|
132
|
-
}
|
|
132
|
+
};
|
|
133
133
|
isConnected() {
|
|
134
134
|
return this.#isConnected;
|
|
135
135
|
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { getTransactionId, signMessage, signTransaction, signTypedData, } from '@exodus/web3-ethereum-utils';
|
|
2
|
+
import { AssetNotSupportedError, InvalidInputError, UnauthorizedError, UserRejectedRequestError, } from '@exodus/web3-errors';
|
|
3
|
+
import { SignTypedDataVersion } from '@metamask/eth-sig-util';
|
|
4
|
+
import { parseAndValidateTypedData } from './parseAndValidateTypedData.js';
|
|
5
|
+
// See: https://eips.ethereum.org/EIPS/eip-2255.
|
|
6
|
+
const ETH_ACCOUNTS_PERMISSION = 'eth_accounts';
|
|
7
|
+
const PROXIED_METHODS = [
|
|
8
|
+
'eth_blockNumber',
|
|
9
|
+
'eth_call',
|
|
10
|
+
'eth_coinbase',
|
|
11
|
+
'eth_createAccessList',
|
|
12
|
+
'eth_estimateGas',
|
|
13
|
+
'eth_feeHistory',
|
|
14
|
+
'eth_gasPrice',
|
|
15
|
+
'eth_getBalance',
|
|
16
|
+
'eth_getBlockByHash',
|
|
17
|
+
'eth_getBlockByNumber',
|
|
18
|
+
'eth_getBlockTransactionCountByHash',
|
|
19
|
+
'eth_getBlockTransactionCountByNumber',
|
|
20
|
+
'eth_getCode',
|
|
21
|
+
'eth_getCompilers',
|
|
22
|
+
'eth_getLogs',
|
|
23
|
+
'eth_getProof',
|
|
24
|
+
'eth_getStorageAt',
|
|
25
|
+
'eth_getTransactionByBlockHashAndIndex',
|
|
26
|
+
'eth_getTransactionByBlockNumberAndIndex',
|
|
27
|
+
'eth_getTransactionByHash',
|
|
28
|
+
'eth_getTransactionCount',
|
|
29
|
+
'eth_getTransactionReceipt',
|
|
30
|
+
'eth_getUncleByBlockHashAndIndex',
|
|
31
|
+
'eth_getUncleByBlockNumberAndIndex',
|
|
32
|
+
'eth_getUncleCountByBlockHash',
|
|
33
|
+
'eth_getWork',
|
|
34
|
+
'eth_hashrate',
|
|
35
|
+
'eth_pendingTransactions',
|
|
36
|
+
'eth_sendRawTransaction',
|
|
37
|
+
'eth_submitWork',
|
|
38
|
+
'net_version',
|
|
39
|
+
// TODO: Should we handle the response like MetaMask, e.g. "MetaMask/v10.14.7"?
|
|
40
|
+
'web3_clientVersion',
|
|
41
|
+
];
|
|
42
|
+
function typedDataToMessage(typedData,
|
|
43
|
+
// TODO: Remove eslint-disable comment when implementing.
|
|
44
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
45
|
+
version) {
|
|
46
|
+
// TODO: Return human-readable message from typed data.
|
|
47
|
+
return typedData.toString();
|
|
48
|
+
}
|
|
49
|
+
export function exposeHandlers(rpc, deps) {
|
|
50
|
+
const { chainId, isTrusted, isAutoApproved, ensureTrusted, approveConnection, approveMessage, approveTransactions, forwardRequest, getAddress, getPrivateKey, getEstimatedGas, getFeeData, getNonce, sendRawTransaction, onTransactionsSigned = () => { }, onEthWalletWatchAssetRequest = () => { }, isUnlocked = () => true, ensureUnlocked = () => true, } = deps;
|
|
51
|
+
const network = `evm:${chainId}`;
|
|
52
|
+
async function assertTrusted() {
|
|
53
|
+
const trusted = await isTrusted();
|
|
54
|
+
if (!trusted) {
|
|
55
|
+
throw new UnauthorizedError();
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function prepareTransaction(transaction) {
|
|
59
|
+
const { gasPrice, maxFeePerGas, maxPriorityFeePerGas } = await getFeeData();
|
|
60
|
+
const preparedTransaction = { ...transaction };
|
|
61
|
+
preparedTransaction.nonce = await getNonce(transaction.from);
|
|
62
|
+
if (!transaction.value) {
|
|
63
|
+
preparedTransaction.value = '0x0';
|
|
64
|
+
}
|
|
65
|
+
if (!transaction.gasPrice) {
|
|
66
|
+
preparedTransaction.gasPrice = gasPrice;
|
|
67
|
+
}
|
|
68
|
+
if (!transaction.maxFeePerGas && maxFeePerGas) {
|
|
69
|
+
preparedTransaction.maxFeePerGas = maxFeePerGas;
|
|
70
|
+
}
|
|
71
|
+
if (!transaction.maxPriorityFeePerGas && maxPriorityFeePerGas) {
|
|
72
|
+
preparedTransaction.maxPriorityFeePerGas = maxPriorityFeePerGas;
|
|
73
|
+
}
|
|
74
|
+
if (!transaction.data) {
|
|
75
|
+
preparedTransaction.data = '0x';
|
|
76
|
+
}
|
|
77
|
+
// NOTE: Keep this at the end as other parameters can affect gas estimation.
|
|
78
|
+
if (!transaction.gas) {
|
|
79
|
+
preparedTransaction.gas = await getEstimatedGas(preparedTransaction);
|
|
80
|
+
}
|
|
81
|
+
return preparedTransaction;
|
|
82
|
+
}
|
|
83
|
+
function validateTransaction(transaction) {
|
|
84
|
+
const gasKeys = [
|
|
85
|
+
'gas',
|
|
86
|
+
'gasPrice',
|
|
87
|
+
'maxFeePerGas',
|
|
88
|
+
'maxPriorityFeePerGas',
|
|
89
|
+
];
|
|
90
|
+
for (const key of gasKeys) {
|
|
91
|
+
if (transaction[key] === '0x') {
|
|
92
|
+
throw new InvalidInputError();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
async function approve(approvalFn, ...args) {
|
|
97
|
+
const autoApproved = await isAutoApproved();
|
|
98
|
+
if (autoApproved) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const approved = await approvalFn(...args);
|
|
102
|
+
if (!approved) {
|
|
103
|
+
throw new UserRejectedRequestError();
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
async function approveAndSignTransaction(transaction) {
|
|
107
|
+
await assertTrusted();
|
|
108
|
+
const preparedTransaction = await prepareTransaction(transaction);
|
|
109
|
+
validateTransaction(preparedTransaction);
|
|
110
|
+
await approve(approveTransactions, network, [preparedTransaction]);
|
|
111
|
+
const privateKey = await getPrivateKey();
|
|
112
|
+
const signedTransaction = signTransaction({ ...preparedTransaction, chainId }, privateKey);
|
|
113
|
+
const isEIP1559Enabled = !!preparedTransaction.maxFeePerGas;
|
|
114
|
+
const transactionId = getTransactionId(signedTransaction, isEIP1559Enabled);
|
|
115
|
+
onTransactionsSigned(network, [transactionId]);
|
|
116
|
+
return `0x${signedTransaction.toString('hex')}`;
|
|
117
|
+
}
|
|
118
|
+
async function approveAndSignMessage(message) {
|
|
119
|
+
await assertTrusted();
|
|
120
|
+
await approve(approveMessage, network, message);
|
|
121
|
+
const privateKey = await getPrivateKey();
|
|
122
|
+
return signMessage(message, privateKey);
|
|
123
|
+
}
|
|
124
|
+
async function approveAndSignTypedData(typedData) {
|
|
125
|
+
await assertTrusted();
|
|
126
|
+
const parsedTypedData = parseAndValidateTypedData(typedData, { chainId });
|
|
127
|
+
const message = typedDataToMessage(typedData, SignTypedDataVersion.V4);
|
|
128
|
+
await approve(approveMessage, network, message);
|
|
129
|
+
const privateKey = await getPrivateKey();
|
|
130
|
+
return signTypedData(parsedTypedData, privateKey, SignTypedDataVersion.V4);
|
|
131
|
+
}
|
|
132
|
+
function exposeEvmFunction(name,
|
|
133
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
134
|
+
fn) {
|
|
135
|
+
return rpc.exposeFunction(`${chainId}_${name}`, fn);
|
|
136
|
+
}
|
|
137
|
+
PROXIED_METHODS.forEach((method) => {
|
|
138
|
+
exposeEvmFunction(method, async (...params) => {
|
|
139
|
+
// TODO: Do we need to check `assertTrusted` here?
|
|
140
|
+
// See: https://eips.ethereum.org/EIPS/eip-1193#connectivity.
|
|
141
|
+
// TODO: Check support for named params (i.e. object).
|
|
142
|
+
const args = { method, params };
|
|
143
|
+
return forwardRequest(args);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
exposeEvmFunction('eth_accounts', async () => {
|
|
147
|
+
const unlocked = await isUnlocked();
|
|
148
|
+
if (!unlocked) {
|
|
149
|
+
return [];
|
|
150
|
+
}
|
|
151
|
+
const trusted = await isTrusted();
|
|
152
|
+
if (!trusted) {
|
|
153
|
+
return [];
|
|
154
|
+
}
|
|
155
|
+
const address = await getAddress();
|
|
156
|
+
return [address];
|
|
157
|
+
});
|
|
158
|
+
exposeEvmFunction('eth_signTransaction', approveAndSignTransaction);
|
|
159
|
+
exposeEvmFunction('eth_sendTransaction', async (transaction) => {
|
|
160
|
+
const rawTransaction = await approveAndSignTransaction(transaction);
|
|
161
|
+
return sendRawTransaction(rawTransaction);
|
|
162
|
+
});
|
|
163
|
+
exposeEvmFunction('personal_sign', approveAndSignMessage);
|
|
164
|
+
exposeEvmFunction('eth_sign', async (account, message) =>
|
|
165
|
+
// Should we really be using `personalSign` here?
|
|
166
|
+
// See: https://docs.metamask.io/guide/signing-data.html#a-brief-history.
|
|
167
|
+
approveAndSignMessage(message));
|
|
168
|
+
exposeEvmFunction('eth_signTypedData_v1', async (typedData) => {
|
|
169
|
+
await assertTrusted();
|
|
170
|
+
const message = typedDataToMessage(typedData, SignTypedDataVersion.V1);
|
|
171
|
+
await approve(approveMessage, network, message);
|
|
172
|
+
const privateKey = await getPrivateKey();
|
|
173
|
+
return signTypedData(typedData, privateKey, SignTypedDataVersion.V1);
|
|
174
|
+
});
|
|
175
|
+
exposeEvmFunction('eth_signTypedData_v3', async (account, typedData) => approveAndSignTypedData(typedData));
|
|
176
|
+
exposeEvmFunction('eth_signTypedData_v4', async (account, typedData) => approveAndSignTypedData(typedData));
|
|
177
|
+
exposeEvmFunction('eth_signTypedData', async (account, typedData) => approveAndSignTypedData(typedData));
|
|
178
|
+
exposeEvmFunction('wallet_getPermissions', async () => {
|
|
179
|
+
const permissions = [];
|
|
180
|
+
const trusted = await isTrusted();
|
|
181
|
+
if (trusted) {
|
|
182
|
+
permissions.push({ parentCapability: ETH_ACCOUNTS_PERMISSION });
|
|
183
|
+
}
|
|
184
|
+
return permissions;
|
|
185
|
+
});
|
|
186
|
+
exposeEvmFunction('wallet_requestPermissions', async (requestedPermissions) => {
|
|
187
|
+
const parentCapability = Object.keys(requestedPermissions)[0];
|
|
188
|
+
if (parentCapability !== ETH_ACCOUNTS_PERMISSION) {
|
|
189
|
+
// `eth_accounts` is currently the only permission.
|
|
190
|
+
// See: https://docs.metamask.io/guide/rpc-api.html#restricted-methods.
|
|
191
|
+
throw new InvalidInputError();
|
|
192
|
+
}
|
|
193
|
+
const unlocked = await ensureUnlocked();
|
|
194
|
+
if (!unlocked) {
|
|
195
|
+
throw new UserRejectedRequestError();
|
|
196
|
+
}
|
|
197
|
+
const trusted = await isTrusted();
|
|
198
|
+
if (!trusted) {
|
|
199
|
+
const approved = await approveConnection(network);
|
|
200
|
+
if (!approved) {
|
|
201
|
+
throw new UserRejectedRequestError();
|
|
202
|
+
}
|
|
203
|
+
await ensureTrusted();
|
|
204
|
+
}
|
|
205
|
+
return [{ parentCapability: ETH_ACCOUNTS_PERMISSION }];
|
|
206
|
+
});
|
|
207
|
+
exposeEvmFunction('wallet_watchAsset', async (assetDetails) => {
|
|
208
|
+
await assertTrusted();
|
|
209
|
+
const assetAdded = await onEthWalletWatchAssetRequest(assetDetails);
|
|
210
|
+
if (!assetAdded) {
|
|
211
|
+
throw new AssetNotSupportedError(assetDetails.type);
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type RPC from '@exodus/json-rpc';
|
|
2
|
+
import type { Dependencies } from '../types.js';
|
|
3
|
+
import type { Transport } from '@exodus/web3-types';
|
|
4
|
+
interface RpcConstructor {
|
|
5
|
+
new (...options: unknown[]): typeof RPC;
|
|
6
|
+
}
|
|
7
|
+
export declare const registerRPCHandlers: ({ deps, RPC, transport, }: {
|
|
8
|
+
deps: Dependencies;
|
|
9
|
+
RPC: RpcConstructor;
|
|
10
|
+
transport: Transport;
|
|
11
|
+
}) => any;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { validate as validateDeps, validateCommonDeps, } from '@exodus/web3-rpc-handlers';
|
|
2
|
+
import { RPC_REQUEST_TIMEOUT } from '@exodus/web3-constants';
|
|
3
|
+
import { exposeHandlers } from './exposeHandlers.js';
|
|
4
|
+
import { validateRequest } from './validator.js';
|
|
5
|
+
const validateEVMDeps = (evm) => {
|
|
6
|
+
const requiredKeys = [
|
|
7
|
+
'chainId',
|
|
8
|
+
'forwardRequest',
|
|
9
|
+
'getAddress',
|
|
10
|
+
'getPrivateKey',
|
|
11
|
+
'getEstimatedGas',
|
|
12
|
+
'getFeeData',
|
|
13
|
+
'getNonce',
|
|
14
|
+
'sendRawTransaction',
|
|
15
|
+
];
|
|
16
|
+
const optionalKeys = ['onEthWalletWatchAssetRequest'];
|
|
17
|
+
validateDeps(evm, requiredKeys, optionalKeys);
|
|
18
|
+
};
|
|
19
|
+
function validateEVMsDeps(evms) {
|
|
20
|
+
const chainIds = evms.map(({ chainId }) => chainId);
|
|
21
|
+
const uniqueChainIds = [...new Set(chainIds)];
|
|
22
|
+
if (chainIds.length !== uniqueChainIds.length) {
|
|
23
|
+
throw new Error('EVMs must have unique chain IDs');
|
|
24
|
+
}
|
|
25
|
+
evms.forEach(validateEVMDeps);
|
|
26
|
+
}
|
|
27
|
+
export const registerRPCHandlers = ({ deps, RPC, transport, }) => {
|
|
28
|
+
validateCommonDeps(deps, ['evms']);
|
|
29
|
+
validateEVMsDeps(deps.evms);
|
|
30
|
+
if (!RPC) {
|
|
31
|
+
throw new Error('"RPC" constructor parameter is required');
|
|
32
|
+
}
|
|
33
|
+
if (!transport) {
|
|
34
|
+
throw new Error('"transport" parameter is required');
|
|
35
|
+
}
|
|
36
|
+
const rpc = new RPC({
|
|
37
|
+
transport,
|
|
38
|
+
parse: validateRequest,
|
|
39
|
+
getIsDevelopmentMode: () => app.isDevelopmentEnvironment?.() ?? false,
|
|
40
|
+
requestTimeout: RPC_REQUEST_TIMEOUT,
|
|
41
|
+
});
|
|
42
|
+
const { app, evms = [], wallet } = deps;
|
|
43
|
+
for (const evm of evms) {
|
|
44
|
+
const evmDeps = { ...app, ...evm, ...wallet };
|
|
45
|
+
exposeHandlers(rpc, evmDeps);
|
|
46
|
+
}
|
|
47
|
+
return rpc;
|
|
48
|
+
};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { MessageTypes, TypedDataV1, TypedMessage } from '@metamask/eth-sig-util';
|
|
2
|
+
export declare function convertChainIdToHex(chainId: string): string;
|
|
3
|
+
export declare function parseAndValidateTypedData(typedData: string, { chainId: activeChainId }: {
|
|
4
|
+
chainId: string;
|
|
5
|
+
}): TypedDataV1 | TypedMessage<MessageTypes> | never;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { validateAndParseEthereumTypedData } from '@exodus/web3-validation';
|
|
2
|
+
import { InvalidInputError } from '@exodus/web3-errors';
|
|
3
|
+
export function convertChainIdToHex(chainId) {
|
|
4
|
+
if (typeof chainId !== 'string') {
|
|
5
|
+
throw new Error(`'chainId' should be a string, got: ${typeof chainId}`);
|
|
6
|
+
}
|
|
7
|
+
// `chainId` can be a stringified decimal
|
|
8
|
+
if (!chainId.startsWith('0x')) {
|
|
9
|
+
return '0x' + parseInt(chainId, 10).toString(16).toLowerCase();
|
|
10
|
+
}
|
|
11
|
+
return chainId.toLowerCase();
|
|
12
|
+
}
|
|
13
|
+
// Returns the parsed typed data object if it is valid
|
|
14
|
+
// Otherwise throws the InvalidInputError
|
|
15
|
+
export function parseAndValidateTypedData(typedData, { chainId: activeChainId }) {
|
|
16
|
+
let parsedTypedData;
|
|
17
|
+
try {
|
|
18
|
+
parsedTypedData = validateAndParseEthereumTypedData(typedData);
|
|
19
|
+
}
|
|
20
|
+
catch (e) {
|
|
21
|
+
throw new InvalidInputError();
|
|
22
|
+
}
|
|
23
|
+
if (parsedTypedData.domain?.chainId) {
|
|
24
|
+
const suppliedChainIdHex = convertChainIdToHex(`${parsedTypedData.domain.chainId}`);
|
|
25
|
+
if (activeChainId.toLowerCase() !== suppliedChainIdHex) {
|
|
26
|
+
throw new InvalidInputError();
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return parsedTypedData;
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const validateRequest: () => never;
|
package/lib/types.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { EthTransaction, EthTransactionHash, EthWalletWatchAsset } from '@exodus/web3-ethereum-utils';
|
|
3
|
+
import type { CommonDeps } from '@exodus/web3-rpc-handlers';
|
|
4
|
+
/**
|
|
5
|
+
* EVM-compatible chains.
|
|
6
|
+
*/
|
|
7
|
+
export interface EthRequestArguments {
|
|
8
|
+
method: string;
|
|
9
|
+
params?: unknown[] | Record<string, unknown>;
|
|
10
|
+
}
|
|
11
|
+
interface FeeData {
|
|
12
|
+
gasPrice: string;
|
|
13
|
+
maxFeePerGas?: string;
|
|
14
|
+
maxPriorityFeePerGas?: string;
|
|
15
|
+
}
|
|
16
|
+
export declare type EVMDeps = {
|
|
17
|
+
chainId: string;
|
|
18
|
+
getAddress: () => Promise<string>;
|
|
19
|
+
getPrivateKey: () => Promise<Buffer>;
|
|
20
|
+
sendRawTransaction: (rawTransaction: string) => Promise<EthTransactionHash>;
|
|
21
|
+
getEstimatedGas: (transaction: EthTransaction) => Promise<string>;
|
|
22
|
+
getFeeData: () => Promise<FeeData>;
|
|
23
|
+
getNonce: (account: string) => Promise<string>;
|
|
24
|
+
forwardRequest: (args: EthRequestArguments) => Promise<unknown>;
|
|
25
|
+
onEthWalletWatchAssetRequest?: (assetDetails: EthWalletWatchAsset) => Promise<boolean>;
|
|
26
|
+
};
|
|
27
|
+
export declare type Dependencies = {
|
|
28
|
+
evms: EVMDeps[];
|
|
29
|
+
} & CommonDeps;
|
|
30
|
+
export {};
|
package/lib/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/web3-ethereum",
|
|
3
3
|
"description": "Apps connectivity library for EVM-compatible chains.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
7
7
|
"module": "lib/index.js",
|
|
@@ -39,12 +39,14 @@
|
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@exodus/assets": "^8.0.72",
|
|
42
|
+
"@exodus/json-rpc": "^1.11.0",
|
|
42
43
|
"@exodus/schemasafe": "^1.0.0-rc.7",
|
|
43
44
|
"@exodus/web3-types": "^1.6.0",
|
|
45
|
+
"@exodus/web3-validation": "^1.27.0",
|
|
44
46
|
"@wallet-standard/base": "^1.0.1"
|
|
45
47
|
},
|
|
46
48
|
"peerDependencies": {
|
|
47
49
|
"@exodus/assets": "^8.0.72"
|
|
48
50
|
},
|
|
49
|
-
"gitHead": "
|
|
51
|
+
"gitHead": "91de31ee30652f2f8acc66f6e1d909fd27174a36"
|
|
50
52
|
}
|