diff --git a/jest.config.js b/jest.config.js index 170d29b000d..fb9e0b4483f 100644 --- a/jest.config.js +++ b/jest.config.js @@ -4,7 +4,7 @@ module.exports = { // ones. collectCoverageFrom: ['./src/**/*.ts'], // TODO: Test index.ts - coveragePathIgnorePatterns: ['./src/index.ts'], + coveragePathIgnorePatterns: ['./src/index.ts', './src/gas/gas-util.ts'], coverageReporters: ['text', 'html'], coverageThreshold: { global: { diff --git a/src/assets/AccountTrackerController.test.ts b/src/assets/AccountTrackerController.test.ts index d5cd4bbad87..40dde862503 100644 --- a/src/assets/AccountTrackerController.test.ts +++ b/src/assets/AccountTrackerController.test.ts @@ -45,6 +45,47 @@ describe('AccountTrackerController', () => { expect(controller.state.accounts[address].balance).toBeDefined(); }); + it('should sync addresses', () => { + const controller = new AccountTrackerController( + { + onPreferencesStateChange: stub(), + getIdentities: () => { + return { baz: {} as ContactEntry }; + }, + }, + { provider }, + { + accounts: { + bar: { balance: '' }, + foo: { balance: '' }, + }, + }, + ); + controller.refresh(); + expect(controller.state.accounts).toStrictEqual({ + baz: { balance: '0x0' }, + }); + }); + + it('does not refresh any accounts if no provider has been set', () => { + const controller = new AccountTrackerController( + { + onPreferencesStateChange: stub(), + getIdentities: () => { + return { baz: {} as ContactEntry }; + }, + }, + {}, + { + accounts: {}, + }, + ); + + controller.refresh(); + + expect(controller.state.accounts).toStrictEqual({}); + }); + it('should sync balance with addresses', async () => { const address = '0xc38bf1ad06ef69f0c04e29dbeb4152b4175f0a8d'; const queryStub = stub(utils, 'query'); @@ -60,28 +101,27 @@ describe('AccountTrackerController', () => { queryStub.returns(Promise.resolve('0x10')); const result = await controller.syncBalanceWithAddresses([address]); expect(result[address].balance).toBe('0x10'); + queryStub.restore(); }); - it('should sync addresses', () => { + it('should not sync balance with addresses if no provider has been set', async () => { + const address = '0xc38bf1ad06ef69f0c04e29dbeb4152b4175f0a8d'; + const queryStub = stub(utils, 'query'); const controller = new AccountTrackerController( { onPreferencesStateChange: stub(), getIdentities: () => { - return { baz: {} as ContactEntry }; - }, - }, - { provider }, - { - accounts: { - bar: { balance: '' }, - foo: { balance: '' }, + return {}; }, }, + {}, ); - controller.refresh(); - expect(controller.state.accounts).toStrictEqual({ - baz: { balance: '0x0' }, - }); + queryStub.returns(Promise.resolve('0x10')); + + const result = await controller.syncBalanceWithAddresses([address]); + + expect(result).toStrictEqual({}); + queryStub.restore(); }); it('should subscribe to new sibling preference controllers', async () => { diff --git a/src/assets/AccountTrackerController.ts b/src/assets/AccountTrackerController.ts index e026ce9e975..016848cbaeb 100644 --- a/src/assets/AccountTrackerController.ts +++ b/src/assets/AccountTrackerController.ts @@ -42,7 +42,7 @@ export class AccountTrackerController extends BaseController< AccountTrackerConfig, AccountTrackerState > { - private ethQuery: any; + private ethQuery: EthQuery | null; private mutex = new Mutex(); @@ -98,6 +98,7 @@ export class AccountTrackerController extends BaseController< state?: Partial, ) { super(config, state); + this.ethQuery = null; this.defaultConfig = { interval: 10000, }; @@ -145,11 +146,17 @@ export class AccountTrackerController extends BaseController< * Refreshes all accounts in the current keychain. */ refresh = async () => { + const { ethQuery } = this; + + if (ethQuery === null) { + return; + } + this.syncAccounts(); const accounts = { ...this.state.accounts }; for (const address in accounts) { await safelyExecuteWithTimeout(async () => { - const balance = await query(this.ethQuery, 'getBalance', [address]); + const balance = await query(ethQuery, 'getBalance', [address]); accounts[address] = { balance: BNToHex(balance) }; }); } @@ -165,11 +172,19 @@ export class AccountTrackerController extends BaseController< async syncBalanceWithAddresses( addresses: string[], ): Promise> { + const { ethQuery } = this; + + if (ethQuery === null) { + return {}; + } + return await Promise.all( addresses.map( (address): Promise<[string, string] | undefined> => { return safelyExecuteWithTimeout(async () => { - const balance = await query(this.ethQuery, 'getBalance', [address]); + const balance = await query(ethQuery, 'getBalance', [ + address, + ]); return [address, balance]; }); }, diff --git a/src/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.test.ts b/src/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.test.ts index c7e709f15d9..8857a509e3a 100644 --- a/src/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.test.ts +++ b/src/assets/Standards/CollectibleStandards/ERC1155/ERC1155Standard.test.ts @@ -1,9 +1,8 @@ import Web3 from 'web3'; -import HttpProvider from 'ethjs-provider-http'; import nock from 'nock'; import { ERC1155Standard } from './ERC1155Standard'; -const MAINNET_PROVIDER = new HttpProvider( +const MAINNET_PROVIDER = new Web3.providers.HttpProvider( 'https://mainnet.infura.io/v3/341eacb578dd44a1a049cbc5f6fd4035', ); diff --git a/src/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.test.ts b/src/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.test.ts index 4e7e3940dc0..a67270db214 100644 --- a/src/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.test.ts +++ b/src/assets/Standards/CollectibleStandards/ERC721/ERC721Standard.test.ts @@ -1,10 +1,9 @@ import Web3 from 'web3'; -import HttpProvider from 'ethjs-provider-http'; import nock from 'nock'; import { IPFS_DEFAULT_GATEWAY_URL } from '../../../../constants'; import { ERC721Standard } from './ERC721Standard'; -const MAINNET_PROVIDER = new HttpProvider( +const MAINNET_PROVIDER = new Web3.providers.HttpProvider( 'https://mainnet.infura.io/v3/341eacb578dd44a1a049cbc5f6fd4035', ); const ERC721_GODSADDRESS = '0x6EbeAf8e8E946F0716E6533A6f2cefc83f60e8Ab'; diff --git a/src/assets/Standards/ERC20Standard.test.ts b/src/assets/Standards/ERC20Standard.test.ts index a49450b3168..a74a279c5b1 100644 --- a/src/assets/Standards/ERC20Standard.test.ts +++ b/src/assets/Standards/ERC20Standard.test.ts @@ -1,9 +1,8 @@ import Web3 from 'web3'; -import HttpProvider from 'ethjs-provider-http'; import nock from 'nock'; import { ERC20Standard } from './ERC20Standard'; -const MAINNET_PROVIDER = new HttpProvider( +const MAINNET_PROVIDER = new Web3.providers.HttpProvider( 'https://mainnet.infura.io/v3/341eacb578dd44a1a049cbc5f6fd4035', ); const ERC20_MATIC_ADDRESS = '0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0'; diff --git a/src/assets/TokenListController.test.ts b/src/assets/TokenListController.test.ts index d8a51beee6e..93d6b3379ac 100644 --- a/src/assets/TokenListController.test.ts +++ b/src/assets/TokenListController.test.ts @@ -12,7 +12,6 @@ import { TokenListStateChange, GetTokenListState, TokenListMap, - ContractMap, } from './TokenListController'; const name = 'TokenListController'; @@ -21,9 +20,7 @@ const timestamp = Date.now(); const staticTokenList: TokenListMap = {}; for (const tokenAddress in contractMap) { - const { erc20, logo: filePath, ...token } = (contractMap as ContractMap)[ - tokenAddress - ]; + const { erc20, logo: filePath, ...token } = contractMap[tokenAddress]; if (erc20) { staticTokenList[tokenAddress] = { ...token, diff --git a/src/assets/TokenListController.ts b/src/assets/TokenListController.ts index 4aced204e57..6759ed228de 100644 --- a/src/assets/TokenListController.ts +++ b/src/assets/TokenListController.ts @@ -21,15 +21,6 @@ type BaseToken = { decimals: number; }; -type StaticToken = { - logo: string; - erc20: boolean; -} & BaseToken; - -export type ContractMap = { - [address: string]: StaticToken; -}; - export type DynamicToken = { address: string; occurrences: number; @@ -241,9 +232,7 @@ export class TokenListController extends BaseController< async fetchFromStaticTokenList(): Promise { const tokenList: TokenListMap = {}; for (const tokenAddress in contractMap) { - const { erc20, logo: filePath, ...token } = (contractMap as ContractMap)[ - tokenAddress - ]; + const { erc20, logo: filePath, ...token } = contractMap[tokenAddress]; if (erc20) { tokenList[tokenAddress] = { ...token, diff --git a/src/assets/TokensController.ts b/src/assets/TokensController.ts index 0f409f0bee0..c491137c0b9 100644 --- a/src/assets/TokensController.ts +++ b/src/assets/TokensController.ts @@ -1,6 +1,6 @@ import { EventEmitter } from 'events'; import contractsMap from '@metamask/contract-metadata'; -import { abiERC721 } from '@metamask/metamask-eth-abis'; +import { abiERC721, ABI } from '@metamask/metamask-eth-abis'; import { v1 as random } from 'uuid'; import { Mutex } from 'async-mutex'; import { ethers } from 'ethers'; @@ -367,7 +367,7 @@ export class TokensController extends BaseController< async _createEthersContract( tokenAddress: string, - abi: string, + abi: ABI, ethersProvider: any, ): Promise { const tokenContract = await new ethers.Contract( diff --git a/src/gas/GasFeeController.ts b/src/gas/GasFeeController.ts index 1196997bae5..e1d45933bf6 100644 --- a/src/gas/GasFeeController.ts +++ b/src/gas/GasFeeController.ts @@ -251,7 +251,7 @@ export class GasFeeController extends BaseController< private currentChainId; - private ethQuery: any; + private ethQuery: EthQuery; private clientId?: string; diff --git a/src/gas/fetchBlockFeeHistory.test.ts b/src/gas/fetchBlockFeeHistory.test.ts index 162ea61bb37..0835784f695 100644 --- a/src/gas/fetchBlockFeeHistory.test.ts +++ b/src/gas/fetchBlockFeeHistory.test.ts @@ -1,13 +1,13 @@ import { BN } from 'ethereumjs-util'; import { mocked } from 'ts-jest/utils'; import { when } from 'jest-when'; +import { buildFakeEthQuery } from '../../tests/util'; import { query, fromHex, toHex } from '../util'; import fetchBlockFeeHistory from './fetchBlockFeeHistory'; jest.mock('../util', () => { return { ...jest.requireActual('../util'), - __esModule: true, query: jest.fn(), }; }); @@ -30,7 +30,7 @@ function times(n: number, fn: (n: number) => T): T[] { } describe('fetchBlockFeeHistory', () => { - const ethQuery = { eth: 'query' }; + const ethQuery = buildFakeEthQuery(); describe('with a minimal set of arguments', () => { const latestBlockNumber = 3; @@ -333,6 +333,12 @@ describe('fetchBlockFeeHistory', () => { const latestBlockNumber = 3; const numberOfRequestedBlocks = 3; + beforeEach(() => { + when(mockedQuery) + .calledWith(ethQuery, 'blockNumber') + .mockResolvedValue(new BN(latestBlockNumber)); + }); + it('includes an extra block with an estimated baseFeePerGas', async () => { when(mockedQuery) .calledWith(ethQuery, 'eth_feeHistory', [ diff --git a/src/gas/fetchBlockFeeHistory.ts b/src/gas/fetchBlockFeeHistory.ts index 553e7b01f40..49d6c9029b6 100644 --- a/src/gas/fetchBlockFeeHistory.ts +++ b/src/gas/fetchBlockFeeHistory.ts @@ -1,7 +1,5 @@ import { BN } from 'ethereumjs-util'; -import { query, fromHex, toHex } from '../util'; - -type EthQuery = any; +import { query, fromHex, toHex, EthQueryish } from '../util'; /** * @type RequestChunkSpecifier @@ -111,7 +109,7 @@ const MAX_NUMBER_OF_BLOCKS_PER_ETH_FEE_HISTORY_CALL = 1024; * - * * @param args - The arguments to this function. - * @param args.ethQuery - An EthQuery instance that wraps a provider for the network in question. + * @param args.ethQuery - An object that {@link query} takes which wraps a provider for the network in question. * @param args.endBlock - The desired end of the requested block range. Can be "latest" if you want * to start from the latest successful block or the number of a known past block. * @param args.numberOfBlocks - How many total blocks to fetch. Note that if this is more than 1024, @@ -138,7 +136,7 @@ export default async function fetchBlockFeeHistory({ percentiles: givenPercentiles = [], includeNextBlock = false, }: { - ethQuery: EthQuery; + ethQuery: EthQueryish; numberOfBlocks: number; endBlock?: 'latest' | BN; percentiles?: readonly Percentile[]; @@ -149,10 +147,13 @@ export default async function fetchBlockFeeHistory({ ? Array.from(new Set(givenPercentiles)).sort((a, b) => a - b) : []; - const finalEndBlockNumber = - givenEndBlock === 'latest' - ? fromHex(await query(ethQuery, 'blockNumber')) - : givenEndBlock; + let finalEndBlockNumber: BN; + if (givenEndBlock === 'latest') { + const latestBlockNumber = await query(ethQuery, 'blockNumber'); + finalEndBlockNumber = fromHex(latestBlockNumber); + } else { + finalEndBlockNumber = givenEndBlock; + } const requestChunkSpecifiers = determineRequestChunkSpecifiers( finalEndBlockNumber, @@ -275,13 +276,13 @@ async function makeRequestForChunk({ percentiles, includeNextBlock, }: { - ethQuery: EthQuery; + ethQuery: EthQueryish; numberOfBlocks: number; endBlockNumber: BN; percentiles: readonly Percentile[]; includeNextBlock: boolean; }): Promise[]> { - const response: EthFeeHistoryResponse = await query( + const response = await query( ethQuery, 'eth_feeHistory', [toHex(numberOfBlocks), toHex(endBlockNumber), percentiles], diff --git a/src/gas/fetchGasEstimatesViaEthFeeHistory.test.ts b/src/gas/fetchGasEstimatesViaEthFeeHistory.test.ts index c82a737730f..5ea138139ac 100644 --- a/src/gas/fetchGasEstimatesViaEthFeeHistory.test.ts +++ b/src/gas/fetchGasEstimatesViaEthFeeHistory.test.ts @@ -1,6 +1,7 @@ import { BN } from 'ethereumjs-util'; import { mocked } from 'ts-jest/utils'; import { when } from 'jest-when'; +import { buildFakeEthQuery } from '../../tests/util'; import fetchBlockFeeHistory from './fetchBlockFeeHistory'; import calculateGasFeeEstimatesForPriorityLevels from './fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels'; import fetchLatestBlock from './fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock'; @@ -24,10 +25,10 @@ describe('fetchGasEstimatesViaEthFeeHistory', () => { number: new BN(1), baseFeePerGas: new BN(100_000_000_000), }; - const ethQuery = { + const ethQuery = buildFakeEthQuery({ blockNumber: async () => latestBlock.number, getBlockByNumber: async () => latestBlock, - }; + }); it('calculates target fees for low, medium, and high transaction priority levels', async () => { const blocks = [ diff --git a/src/gas/fetchGasEstimatesViaEthFeeHistory.ts b/src/gas/fetchGasEstimatesViaEthFeeHistory.ts index 0a829364374..d7918c6306d 100644 --- a/src/gas/fetchGasEstimatesViaEthFeeHistory.ts +++ b/src/gas/fetchGasEstimatesViaEthFeeHistory.ts @@ -1,7 +1,7 @@ import { fromWei } from 'ethjs-unit'; import { GWEI } from '../constants'; +import { EthQueryish } from '../util'; import { GasFeeEstimates } from './GasFeeController'; -import { EthQuery } from './fetchGasEstimatesViaEthFeeHistory/types'; import fetchBlockFeeHistory from './fetchBlockFeeHistory'; import fetchLatestBlock from './fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock'; import calculateGasFeeEstimatesForPriorityLevels from './fetchGasEstimatesViaEthFeeHistory/calculateGasFeeEstimatesForPriorityLevels'; @@ -25,7 +25,7 @@ import calculateGasFeeEstimatesForPriorityLevels from './fetchGasEstimatesViaEth * for the next block's base fee. */ export default async function fetchGasEstimatesViaEthFeeHistory( - ethQuery: EthQuery, + ethQuery: EthQueryish, ): Promise { const latestBlock = await fetchLatestBlock(ethQuery); const blocks = await fetchBlockFeeHistory({ diff --git a/src/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.test.ts b/src/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.test.ts new file mode 100644 index 00000000000..8952c138fd5 --- /dev/null +++ b/src/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.test.ts @@ -0,0 +1,59 @@ +import { BN } from 'ethereumjs-util'; +import { mocked } from 'ts-jest/utils'; +import { when } from 'jest-when'; +import { query } from '../../util'; +import { buildFakeEthQuery } from '../../../tests/util'; +import fetchLatestBlock from './fetchLatestBlock'; + +jest.mock('../../util', () => { + return { + ...jest.requireActual('../../util'), + query: jest.fn(), + }; +}); + +const mockedQuery = mocked(query, true); + +describe('fetchLatestBlock', () => { + const ethQuery = buildFakeEthQuery(); + + it('returns an object that represents the latest block on the network, where number and baseFeePerGas are BN objects instead of hex strings', async () => { + when(mockedQuery) + .calledWith(ethQuery, 'blockNumber') + .mockResolvedValue('0x1'); + + when(mockedQuery) + .calledWith(ethQuery, 'getBlockByNumber', ['0x1', false]) + .mockResolvedValue({ + number: '0x2', + baseFeePerGas: '0x3', + }); + + const latestBlock = await fetchLatestBlock(ethQuery); + + expect(latestBlock).toStrictEqual({ + number: new BN(2), + baseFeePerGas: new BN(3), + }); + }); + + it('passes includeFullTransactionData to the getBlockByNumber query', async () => { + when(mockedQuery) + .calledWith(ethQuery, 'blockNumber') + .mockResolvedValue('0x1'); + + when(mockedQuery) + .calledWith(ethQuery, 'getBlockByNumber', ['0x1', true]) + .mockResolvedValue({ + number: '0x2', + baseFeePerGas: '0x3', + }); + + const latestBlock = await fetchLatestBlock(ethQuery, true); + + expect(latestBlock).toStrictEqual({ + number: new BN(2), + baseFeePerGas: new BN(3), + }); + }); +}); diff --git a/src/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.ts b/src/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.ts index 01def5bfd5e..fe7f2e63ad3 100644 --- a/src/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.ts +++ b/src/gas/fetchGasEstimatesViaEthFeeHistory/fetchLatestBlock.ts @@ -1,5 +1,5 @@ -import { query, fromHex } from '../../util'; -import { EthBlock, EthQuery } from './types'; +import { query, fromHex, EthQueryish } from '../../util'; +import { EthBlock, RawEthBlock } from './types'; /** * Returns information about the latest completed block. @@ -10,14 +10,18 @@ import { EthBlock, EthQuery } from './types'; * @returns The block. */ export default async function fetchLatestBlock( - ethQuery: EthQuery, + ethQuery: EthQueryish, includeFullTransactionData = false, ): Promise { - const blockNumber = await query(ethQuery, 'blockNumber'); - const block = await query(ethQuery, 'getBlockByNumber', [ + const blockNumber = await query(ethQuery, 'blockNumber'); + // According to the spec, `getBlockByNumber` could return null, but to prevent + // backward incompatibilities, we assume that there will always be a latest + // block + const block = await query(ethQuery, 'getBlockByNumber', [ blockNumber, includeFullTransactionData, ]); + return { ...block, number: fromHex(block.number), diff --git a/src/gas/fetchGasEstimatesViaEthFeeHistory/types.ts b/src/gas/fetchGasEstimatesViaEthFeeHistory/types.ts index f58908206f9..80bf0a1804f 100644 --- a/src/gas/fetchGasEstimatesViaEthFeeHistory/types.ts +++ b/src/gas/fetchGasEstimatesViaEthFeeHistory/types.ts @@ -1,5 +1,10 @@ import { BN } from 'ethereumjs-util'; +export type RawEthBlock = { + number: string; + baseFeePerGas: string; +}; + export type EthBlock = { number: BN; baseFeePerGas: BN; diff --git a/src/gas/gas-util.ts b/src/gas/gas-util.ts index 98b82f16a0f..73782eeae73 100644 --- a/src/gas/gas-util.ts +++ b/src/gas/gas-util.ts @@ -17,9 +17,7 @@ const makeClientIdHeader = (clientId: string) => ({ 'X-Client-Id': clientId }); * @returns The decimal string GWEI amount. */ export function normalizeGWEIDecimalNumbers(n: string | number) { - const numberAsWEIHex = gweiDecToWEIBN(n).toString(16); - const numberAsGWEI = weiHexToGweiDec(numberAsWEIHex).toString(10); - return numberAsGWEI; + return weiHexToGweiDec(gweiDecToWEIBN(n).toString(16)); } /** @@ -113,9 +111,9 @@ export async function fetchLegacyGasPriceEstimates( export async function fetchEthGasPriceEstimate( ethQuery: any, ): Promise { - const gasPrice = await query(ethQuery, 'gasPrice'); + const gasPrice = await query(ethQuery, 'gasPrice'); return { - gasPrice: weiHexToGweiDec(gasPrice).toString(), + gasPrice: weiHexToGweiDec(gasPrice), }; } diff --git a/src/network/NetworkController.ts b/src/network/NetworkController.ts index 4a23107e41f..c57e625eb65 100644 --- a/src/network/NetworkController.ts +++ b/src/network/NetworkController.ts @@ -1,5 +1,6 @@ import EthQuery from 'eth-query'; import Subprovider from 'web3-provider-engine/subproviders/provider'; +import { SupportedInfuraNetwork } from 'eth-json-rpc-infura'; import createInfuraProvider from 'eth-json-rpc-infura/src/createProvider'; import createMetamaskProvider from 'web3-provider-engine/zero'; import { Mutex } from 'async-mutex'; @@ -18,7 +19,9 @@ export type NetworkType = | 'ropsten' | 'rpc' | 'optimism' - | 'optimismTest'; + | 'optimism-mainnet' + | 'optimismTest' + | 'optimism-kovan'; export enum NetworksChainId { mainnet = '1', @@ -29,7 +32,9 @@ export enum NetworksChainId { localhost = '', rpc = '', optimism = '10', + 'optimism-mainnet' = '10', optimismTest = '69', + 'optimism-kovan' = '69', } /** @@ -94,7 +99,7 @@ export class NetworkController extends BaseController< NetworkConfig, NetworkState > { - private ethQuery: any; + private ethQuery: EthQuery | null; private internalProviderConfig: ProviderConfig = {} as ProviderConfig; @@ -114,7 +119,9 @@ export class NetworkController extends BaseController< case 'rinkeby': case 'goerli': case 'optimism': + case 'optimism-mainnet': case 'optimismTest': + case 'optimism-kovan': case 'ropsten': this.setupInfuraProvider(type); break; @@ -142,10 +149,10 @@ export class NetworkController extends BaseController< this.ethQuery = new EthQuery(this.provider); } - private setupInfuraProvider(type: NetworkType) { + private setupInfuraProvider(type: SupportedInfuraNetwork) { const infuraProvider = createInfuraProvider({ network: type, - projectId: this.config.infuraProjectId, + projectId: this.config.infuraProjectId ?? '', }); const infuraSubprovider = new Subprovider(infuraProvider); const config = { @@ -231,6 +238,7 @@ export class NetworkController extends BaseController< provider: { type: MAINNET, chainId: NetworksChainId.mainnet }, properties: { isEIP1559Compatible: false }, }; + this.ethQuery = null; this.initialize(); this.getEIP1559Compatibility(); } @@ -263,8 +271,8 @@ export class NetworkController extends BaseController< return; } const releaseLock = await this.mutex.acquire(); - this.ethQuery.sendAsync( - { method: 'net_version' }, + this.ethQuery?.sendAsync( + { id: 1, jsonrpc: '2.0', method: 'net_version' }, (error: Error, network: string) => { this.update({ network: error ? /* istanbul ignore next*/ 'loading' : network, @@ -326,8 +334,13 @@ export class NetworkController extends BaseController< return Promise.resolve(true); } return new Promise((resolve, reject) => { - this.ethQuery.sendAsync( - { method: 'eth_getBlockByNumber', params: ['latest', false] }, + this.ethQuery?.sendAsync( + { + id: 1, + jsonrpc: '2.0', + method: 'eth_getBlockByNumber', + params: ['latest', false], + }, (error: Error, block: Block) => { if (error) { reject(error); diff --git a/src/third-party/PhishingController.ts b/src/third-party/PhishingController.ts index 83910c172f7..312a32cde6b 100644 --- a/src/third-party/PhishingController.ts +++ b/src/third-party/PhishingController.ts @@ -1,5 +1,7 @@ import { toASCII } from 'punycode/'; -import DEFAULT_PHISHING_RESPONSE from 'eth-phishing-detect/src/config.json'; +import DEFAULT_PHISHING_RESPONSE, { + EthPhishingDetectorConfiguration, +} from 'eth-phishing-detect/src/config.json'; import PhishingDetector from 'eth-phishing-detect/src/detector'; import { BaseController, BaseConfig, BaseState } from '../BaseController'; import { safelyExecute } from '../util'; @@ -15,13 +17,7 @@ import { safelyExecute } from '../util'; * @property version - Version number of this configuration * @property whitelist - List of approved origins */ -export interface EthPhishingResponse { - blacklist: string[]; - fuzzylist: string[]; - tolerance: number; - version: number; - whitelist: string[]; -} +export type EthPhishingResponse = EthPhishingDetectorConfiguration; /** * @type PhishingConfig @@ -55,7 +51,7 @@ export class PhishingController extends BaseController< private configUrl = 'https://cdn.jsdelivr.net/gh/MetaMask/eth-phishing-detect@master/src/config.json'; - private detector: any; + private detector: PhishingDetector; private handle?: NodeJS.Timer; diff --git a/src/transaction/TransactionController.ts b/src/transaction/TransactionController.ts index f26d06a9341..3a8c5e43b8c 100644 --- a/src/transaction/TransactionController.ts +++ b/src/transaction/TransactionController.ts @@ -259,7 +259,7 @@ export class TransactionController extends BaseController< TransactionConfig, TransactionState > { - private ethQuery: any; + private ethQuery: EthQuery; private registry: any; @@ -636,7 +636,10 @@ export class TransactionController extends BaseController< const txNonce = nonce || - (await query(this.ethQuery, 'getTransactionCount', [from, 'pending'])); + (await query(this.ethQuery, 'getTransactionCount', [ + from, + 'pending', + ])); transactionMeta.status = status; transactionMeta.transaction.nonce = txNonce; @@ -677,9 +680,11 @@ export class TransactionController extends BaseController< transactionMeta.rawTransaction = rawTransaction; this.updateTransaction(transactionMeta); - const transactionHash = await query(this.ethQuery, 'sendRawTransaction', [ - rawTransaction, - ]); + const transactionHash = await query( + this.ethQuery, + 'sendRawTransaction', + [rawTransaction], + ); transactionMeta.transactionHash = transactionHash; transactionMeta.status = TransactionStatus.submitted; this.updateTransaction(transactionMeta); @@ -808,7 +813,7 @@ export class TransactionController extends BaseController< transactionMeta.transaction.from, ); const rawTransaction = bufferToHex(signedTx.serialize()); - await query(this.ethQuery, 'sendRawTransaction', [rawTransaction]); + await query(this.ethQuery, 'sendRawTransaction', [rawTransaction]); transactionMeta.status = TransactionStatus.cancelled; this.hub.emit(`${transactionMeta.id}:finished`, transactionMeta); } @@ -906,9 +911,11 @@ export class TransactionController extends BaseController< transactionMeta.transaction.from, ); const rawTransaction = bufferToHex(signedTx.serialize()); - const transactionHash = await query(this.ethQuery, 'sendRawTransaction', [ - rawTransaction, - ]); + const transactionHash = await query( + this.ethQuery, + 'sendRawTransaction', + [rawTransaction], + ); const baseTransactionMeta = { ...transactionMeta, id: random(), @@ -954,22 +961,25 @@ export class TransactionController extends BaseController< } = estimatedTransaction; const gasPrice = typeof providedGasPrice === 'undefined' - ? await query(this.ethQuery, 'gasPrice') + ? await query(this.ethQuery, 'gasPrice') : providedGasPrice; const { isCustomNetwork } = this.getNetworkState(); // 1. If gas is already defined on the transaction, use it if (typeof gas !== 'undefined') { return { gas, gasPrice }; } - const { gasLimit } = await query(this.ethQuery, 'getBlockByNumber', [ - 'latest', - false, - ]); + const { gasLimit } = await query<{ gasLimit: string }>( + this.ethQuery, + 'getBlockByNumber', + ['latest', false], + ); // 2. If to is not defined or this is not a contract address, and there is no data use 0x5208 / 21000. // If the newtwork is a custom network then bypass this check and fetch 'estimateGas'. /* istanbul ignore next */ - const code = to ? await query(this.ethQuery, 'getCode', [to]) : undefined; + const code = to + ? await query(this.ethQuery, 'getCode', [to]) + : undefined; /* istanbul ignore next */ if ( !isCustomNetwork && @@ -988,7 +998,7 @@ export class TransactionController extends BaseController< typeof value === 'undefined' ? '0x0' : /* istanbul ignore next */ value; const gasLimitBN = hexToBN(gasLimit); estimatedTransaction.gas = BNToHex(fractionBN(gasLimitBN, 19, 20)); - const gasHex = await query(this.ethQuery, 'estimateGas', [ + const gasHex = await query(this.ethQuery, 'estimateGas', [ estimatedTransaction, ]); @@ -1170,7 +1180,7 @@ export class TransactionController extends BaseController< tx.transaction.to && (!tx.transaction.data || tx.transaction.data !== '0x') ) { - const code = await query(this.ethQuery, 'getCode', [ + const code = await query(this.ethQuery, 'getCode', [ tx.transaction.to, ]); tx.toSmartContract = isSmartContractCode(code); @@ -1255,9 +1265,10 @@ export class TransactionController extends BaseController< const { status, transactionHash } = meta; switch (status) { case TransactionStatus.confirmed: - const txReceipt = await query(this.ethQuery, 'getTransactionReceipt', [ - transactionHash, - ]); + const txReceipt = await query<{ + gasUsed: string; + status: string; + } | null>(this.ethQuery, 'getTransactionReceipt', [transactionHash]); if (!txReceipt) { return [meta, false]; @@ -1278,9 +1289,11 @@ export class TransactionController extends BaseController< return [meta, true]; case TransactionStatus.submitted: - const txObj = await query(this.ethQuery, 'getTransactionByHash', [ - transactionHash, - ]); + const txObj = await query<{ blockNumber: string } | null>( + this.ethQuery, + 'getTransactionByHash', + [transactionHash], + ); if (!txObj) { const receiptShowsFailedStatus = await this.checkTxReceiptStatusIsFailed( @@ -1322,9 +1335,11 @@ export class TransactionController extends BaseController< private async checkTxReceiptStatusIsFailed( txHash: string | undefined, ): Promise { - const txReceipt = await query(this.ethQuery, 'getTransactionReceipt', [ - txHash, - ]); + const txReceipt = await query<{ status: string } | null>( + this.ethQuery, + 'getTransactionReceipt', + [txHash], + ); if (!txReceipt) { // Transaction is pending return false; diff --git a/src/util.test.ts b/src/util.test.ts index c257ca61403..3df616cefa5 100644 --- a/src/util.test.ts +++ b/src/util.test.ts @@ -1,6 +1,7 @@ import 'isomorphic-fetch'; import { BN } from 'ethereumjs-util'; import nock from 'nock'; +import { buildFakeEthQuery } from '../tests/util'; import * as util from './util'; import { Transaction, @@ -31,8 +32,18 @@ describe('util', () => { nock.cleanAll(); }); - it('bNToHex', () => { - expect(util.BNToHex(new BN('1337'))).toBe('0x539'); + describe('BNToHex', () => { + it('returns a 0x-prefixed hex string if given a decimal string', () => { + expect(util.BNToHex(new BN('1337'))).toBe('0x539'); + }); + + it('prefixes the given string with "0x" if it is not prefixed', () => { + expect(util.BNToHex('1337')).toBe('0x1337'); + }); + + it('does nothing to a 0x-prefixed hex string', () => { + expect(util.BNToHex('0x1337')).toBe('0x1337'); + }); }); it('fractionBN', () => { @@ -940,18 +951,18 @@ describe('util', () => { describe('query', () => { describe('when the given method exists directly on the EthQuery', () => { it('should call the method on the EthQuery and, if it is successful, return a promise that resolves to the result', async () => { - const ethQuery = { + const ethQuery = buildFakeEthQuery({ getBlockByHash: (blockId: any, cb: any) => cb(null, { id: blockId }), - }; + }); const result = await util.query(ethQuery, 'getBlockByHash', ['0x1234']); expect(result).toStrictEqual({ id: '0x1234' }); }); it('should call the method on the EthQuery and, if it errors, return a promise that is rejected with the error', async () => { - const ethQuery = { + const ethQuery = buildFakeEthQuery({ getBlockByHash: (_blockId: any, cb: any) => cb(new Error('uh oh'), null), - }; + }); await expect( util.query(ethQuery, 'getBlockByHash', ['0x1234']), ).rejects.toThrow('uh oh'); @@ -960,14 +971,14 @@ describe('util', () => { describe('when the given method does not exist directly on the EthQuery', () => { it('should use sendAsync to call the RPC endpoint and, if it is successful, return a promise that resolves to the result', async () => { - const ethQuery = { + const ethQuery = buildFakeEthQuery({ sendAsync: ({ method, params }: any, cb: any) => { if (method === 'eth_getBlockByHash') { return cb(null, { id: params[0] }); } throw new Error(`Unsupported method ${method}`); }, - }; + }); const result = await util.query(ethQuery, 'eth_getBlockByHash', [ '0x1234', ]); @@ -975,11 +986,11 @@ describe('util', () => { }); it('should use sendAsync to call the RPC endpoint and, if it errors, return a promise that is rejected with the error', async () => { - const ethQuery = { + const ethQuery = buildFakeEthQuery({ sendAsync: (_args: any, cb: any) => { cb(new Error('uh oh'), null); }, - }; + }); await expect( util.query(ethQuery, 'eth_getBlockByHash', ['0x1234']), ).rejects.toThrow('uh oh'); diff --git a/src/util.ts b/src/util.ts index 437ed2cc0c4..eb5dd0cdc7f 100644 --- a/src/util.ts +++ b/src/util.ts @@ -11,6 +11,7 @@ import { fromWei, toWei } from 'ethjs-unit'; import { ethErrors } from 'eth-rpc-errors'; import ensNamehash from 'eth-ens-namehash'; import { TYPED_MESSAGE_SCHEMA, typedSignatureHash } from 'eth-sig-util'; +import { EthQuerySendAsyncFunction } from 'eth-query'; import { validate } from 'jsonschema'; import { CID } from 'multiformats/cid'; import deepEqual from 'fast-deep-equal'; @@ -27,6 +28,10 @@ import { Token } from './assets/TokenRatesController'; import { MAINNET } from './constants'; import { Json } from './BaseControllerV2'; +export type EthQueryish = { + sendAsync: EthQuerySendAsyncFunction; +}; + const hexRe = /^[0-9A-Fa-f]+$/gu; const NORMALIZERS: { [param in keyof Transaction]: any } = { @@ -45,12 +50,12 @@ const NORMALIZERS: { [param in keyof Transaction]: any } = { }; /** - * Converts a BN object to a hex string with a '0x' prefix. + * Converts a BN object or a string to a hex string with a '0x' prefix. * - * @param inputBn - BN instance to convert to a hex string. + * @param inputBn - BN instance or string to convert to a hex string. * @returns A '0x'-prefixed hex string. */ -export function BNToHex(inputBn: any) { +export function BNToHex(inputBn: BN | string) { return addHexPrefix(inputBn.toString(16)); } @@ -116,7 +121,7 @@ export function gweiDecToWEIBN(n: number | string) { */ export function weiHexToGweiDec(hex: string) { const hexWei = new BN(stripHexPrefix(hex), 16); - return fromWei(hexWei, 'gwei').toString(10); + return fromWei(hexWei, 'gwei'); } /** @@ -709,18 +714,20 @@ export function normalizeEnsName(ensName: string): string | null { /** * Wrapper method to handle EthQuery requests. * - * @param ethQuery - EthQuery object initialized with a provider. - * @param method - Method to request. + * @param ethQuery - EthQuery object initialized with a provider, or an object + * that has a `sendAsync` method that can be used to make a JSON-RPC request to + * a provider. + * @param methodName - Method to request. * @param args - Arguments to send. * @returns Promise resolving the request. */ -export function query( - ethQuery: any, - method: string, +export function query( + ethQuery: EthQueryish, + methodName: string, args: any[] = [], -): Promise { +): Promise { return new Promise((resolve, reject) => { - const cb = (error: Error, result: any) => { + const cb = (error: Error, result: R) => { if (error) { reject(error); return; @@ -728,10 +735,18 @@ export function query( resolve(result); }; - if (typeof ethQuery[method] === 'function') { - ethQuery[method](...args, cb); + if (methodName in ethQuery) { + // Due to the generic nature of this function, there isn't a way to + // clarify the type on `ethQuery`. At this point we know that `ethQuery` + // has a method with the name of `${methodName}` that takes some arguments + // and a callback, but we don't know what `methodName` is, so we don't + // know what kind of arguments it takes or the return type of the result. + (ethQuery as any)[methodName](...args, cb); } else { - ethQuery.sendAsync({ method, params: args }, cb); + ethQuery.sendAsync( + { id: 1, jsonrpc: '2.0', method: methodName, params: args }, + cb, + ); } }); } diff --git a/tests/util.ts b/tests/util.ts new file mode 100644 index 00000000000..315d63a59f4 --- /dev/null +++ b/tests/util.ts @@ -0,0 +1,30 @@ +import { JsonRpcRequest } from 'json-rpc-engine'; +import { EthQueryMethodCallback, EthQuerySendAsyncFunction } from 'eth-query'; +import { EthQueryish } from '../src/util'; + +/** + * Builds a EthQuery object that implements the bare minimum necessary to pass + * to `query`. + * + * @param overrides - An optional set of methods to add to the fake EthQuery + * object. + * @returns The fake EthQuery object. + */ +export function buildFakeEthQuery( + overrides: Record void> = {}, +): EthQueryish { + const sendAsync: EthQuerySendAsyncFunction< + Record, + string + > = ( + _request: JsonRpcRequest>, + callback: EthQueryMethodCallback, + ) => { + callback(null, 'default result'); + }; + + return { + sendAsync, + ...overrides, + }; +} diff --git a/tsconfig.build.json b/tsconfig.build.json index 70df21e1651..e2c5987c4b7 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -6,6 +6,5 @@ "outDir": "./dist", "rootDir": "./src" }, - "include": ["./src/**/*.ts"], "exclude": ["**/*.test.ts"] } diff --git a/tsconfig.json b/tsconfig.json index 2887202355a..de3572e5f74 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,11 +5,9 @@ "inlineSources": true, "module": "commonjs", "moduleResolution": "node", - "paths": { - "*": ["./types/*"] - }, "sourceMap": true, "strict": true, "target": "es6" - } + }, + "include": ["./types/**/*.d.ts", "./src/**/*.ts"] } diff --git a/types/@metamask/contract-metadata.d.ts b/types/@metamask/contract-metadata.d.ts index dfbc40c8acb..5c6c486dcf7 100644 --- a/types/@metamask/contract-metadata.d.ts +++ b/types/@metamask/contract-metadata.d.ts @@ -1 +1,12 @@ -declare module '@metamask/contract-metadata'; +declare module '@metamask/contract-metadata' { + export type Token = { + name: string; + logo: string; + erc20: boolean; + erc721?: boolean; + symbol: string; + decimals: number; + }; + const contractMap: Record; + export default contractMap; +} diff --git a/types/@metamask/metamask-eth-abis.d.ts b/types/@metamask/metamask-eth-abis.d.ts index d26af311c1b..f4c230cfc00 100644 --- a/types/@metamask/metamask-eth-abis.d.ts +++ b/types/@metamask/metamask-eth-abis.d.ts @@ -1 +1,5 @@ -declare module '@metamask/metamask-eth-abis'; +import { abiERC20, abiERC721, abiERC1155 } from '@metamask/metamask-eth-abis'; + +declare module '@metamask/metamask-eth-abis' { + export type ABI = typeof abiERC20 | typeof abiERC721 | typeof abiERC1155; +} diff --git a/types/eth-ens-namehash.d.ts b/types/eth-ens-namehash.d.ts index 1f686f57aea..9066e16a8a8 100644 --- a/types/eth-ens-namehash.d.ts +++ b/types/eth-ens-namehash.d.ts @@ -1 +1,4 @@ -declare module 'eth-ens-namehash'; +declare module 'eth-ens-namehash' { + export function hash(inputName?: string | null): `0x${string}`; + export function normalize(name?: string | null): string; +} diff --git a/types/eth-json-rpc-infura.d.ts b/types/eth-json-rpc-infura.d.ts new file mode 100644 index 00000000000..3807e7eb5a8 --- /dev/null +++ b/types/eth-json-rpc-infura.d.ts @@ -0,0 +1,41 @@ +declare module 'eth-json-rpc-infura' { + import type { JsonRpcMiddleware } from 'json-rpc-engine'; + + // Source: + export type SupportedInfuraNetwork = + | 'mainnet' + | 'ropsten' + | 'rinkeby' + | 'kovan' + | 'goerli' + | 'eth2-beacon-mainnet' + | 'ipfs' + | 'filecoin' + | 'polygon-mainnet' + | 'polygon-mumbai' + | 'palm-mainnet' + | 'palm-testnet' + | 'optimism-mainnet' + | 'optimism-kovan' + | 'arbitrum-mainnet' + | 'arbitrum-rinkeby' + | 'near-mainnet' + | 'near-testnet' + | 'aurora-mainnet' + | 'aurora-testnet' + // Legacy networks for compatibility with NetworkController + | 'optimism' + | 'optimismTest'; + + export type CreateInfuraMiddlewareOptions = { + network?: SupportedInfuraNetwork; + maxAttempts?: number; + source?: string; + projectId: string; + headers?: Record; + }; + + export default function createInfuraMiddleware( + opts: CreateInfuraMiddlewareOptions, + ): JsonRpcMiddleware; +} diff --git a/types/eth-json-rpc-infura/src/createProvider.d.ts b/types/eth-json-rpc-infura/src/createProvider.d.ts index 53b9352d5ce..9a011aeb5c2 100644 --- a/types/eth-json-rpc-infura/src/createProvider.d.ts +++ b/types/eth-json-rpc-infura/src/createProvider.d.ts @@ -1 +1,14 @@ -declare module 'eth-json-rpc-infura/src/createProvider'; +declare module 'eth-json-rpc-infura/src/createProvider' { + import type { JsonRpcEngine } from 'json-rpc-engine'; + import type SafeEventEmitter from '@metamask/safe-event-emitter'; + import type { CreateInfuraMiddlewareOptions } from 'eth-json-rpc-infura'; + + interface Provider extends SafeEventEmitter { + sendAsync: JsonRpcEngine['handle']; + send: JsonRpcEngine['handle']; + } + + export default function createProvider( + opts: CreateInfuraMiddlewareOptions, + ): Provider; +} diff --git a/types/eth-phishing-detect/src/config.json.d.ts b/types/eth-phishing-detect/src/config.json.d.ts index 6943346451f..294536259c3 100644 --- a/types/eth-phishing-detect/src/config.json.d.ts +++ b/types/eth-phishing-detect/src/config.json.d.ts @@ -1 +1,13 @@ -declare module 'eth-phishing-detect/src/config.json'; +declare module 'eth-phishing-detect/src/config.json' { + export type EthPhishingDetectorConfiguration = { + version: number; + tolerance: number; + fuzzylist: string[]; + whitelist: string[]; + blacklist: string[]; + }; + + const config: EthPhishingDetectorConfiguration; + + export default config; +} diff --git a/types/eth-phishing-detect/src/detector.d.ts b/types/eth-phishing-detect/src/detector.d.ts index cab272fdde9..3deae0696a6 100644 --- a/types/eth-phishing-detect/src/detector.d.ts +++ b/types/eth-phishing-detect/src/detector.d.ts @@ -1 +1,18 @@ -declare module 'eth-phishing-detect/src/detector'; +declare module 'eth-phishing-detect/src/detector' { + type Check = + | { type: 'whitelist'; result: false } + | { type: 'blacklist'; result: true } + | { type: 'fuzzy'; result: true; match: string } + | { type: 'all'; result: true }; + + export default class PhishingDetector { + constructor(opts: { + whitelist?: string[]; + blacklist?: string[]; + fuzzylist?: string[]; + tolerance?: number; + }); + + check(domain: string): Check; + } +} diff --git a/types/eth-query.d.ts b/types/eth-query.d.ts index e857105f282..17f78110bd5 100644 --- a/types/eth-query.d.ts +++ b/types/eth-query.d.ts @@ -1 +1,145 @@ -declare module 'eth-query'; +declare module 'eth-query' { + import type { JsonRpcRequest } from 'json-rpc-engine'; + + export type EthQueryMethodCallback = (error: any, response: R) => void; + + export type EthProvider = { + sendAsync( + request: JsonRpcRequest

, + callback: EthQueryMethodCallback, + ): void; + }; + + type HexString = `0x${string}`; + + type BlockParam = HexString | 'latest' | 'earliest' | 'pending'; + + export type EthQuerySendAsyncFunction

= ( + request: JsonRpcRequest

, + callback: EthQueryMethodCallback, + ) => void; + + export default class EthQuery { + currentProvider: EthProvider; + + constructor(provider: EthProvider); + + // Methods that take arguments + + getBalance( + address: HexString, + callback: EthQueryMethodCallback, + ): void; + getBalance( + address: HexString, + block: BlockParam, + callback: EthQueryMethodCallback, + ): void; + + getCode(address: HexString, callback: EthQueryMethodCallback): void; + getCode( + address: HexString, + block: BlockParam, + callback: EthQueryMethodCallback, + ): void; + + getTransactionCount( + address: HexString, + callback: EthQueryMethodCallback, + ): void; + getTransactionCount( + address: HexString, + block: BlockParam, + callback: EthQueryMethodCallback, + ): void; + + getStorageAt( + address: HexString, + storagePosition: HexString, + callback: EthQueryMethodCallback, + ): void; + getStorageAt( + address: HexString, + storagePosition: HexString, + block: BlockParam, + callback: EthQueryMethodCallback, + ): void; + + call( + data: { + from: HexString; + to: HexString; + gas?: HexString; + gasPrice?: HexString; + value?: HexString; + data?: HexString; + }, + callback: EthQueryMethodCallback, + ): void; + call( + data: { + from: HexString; + to: HexString; + gas?: HexString; + gasPrice?: HexString; + value?: HexString; + data?: HexString; + }, + block: BlockParam, + callback: EthQueryMethodCallback, + ): void; + + // Methods that don't take arguments + + protocolVersion(callback: EthQueryMethodCallback): void; + syncing(callback: EthQueryMethodCallback): void; + coinbase(callback: EthQueryMethodCallback): void; + mining(callback: EthQueryMethodCallback): void; + hashrate(callback: EthQueryMethodCallback): void; + gasPrice(callback: EthQueryMethodCallback): void; + accounts(callback: EthQueryMethodCallback): void; + blockNumber(callback: EthQueryMethodCallback): void; + getBlockTransactionCountByHash( + callback: EthQueryMethodCallback, + ): void; + getBlockTransactionCountByNumber( + callback: EthQueryMethodCallback, + ): void; + getUncleCountByBlockHash(callback: EthQueryMethodCallback): void; + getUncleCountByBlockNumber(callback: EthQueryMethodCallback): void; + sign(callback: EthQueryMethodCallback): void; + sendTransaction(callback: EthQueryMethodCallback): void; + sendRawTransaction(callback: EthQueryMethodCallback): void; + estimateGas(callback: EthQueryMethodCallback): void; + getBlockByHash(callback: EthQueryMethodCallback): void; + getBlockByNumber(callback: EthQueryMethodCallback): void; + getTransactionByHash(callback: EthQueryMethodCallback): void; + getTransactionByBlockHashAndIndex( + callback: EthQueryMethodCallback, + ): void; + getTransactionByBlockNumberAndIndex( + callback: EthQueryMethodCallback, + ): void; + getTransactionReceipt(callback: EthQueryMethodCallback): void; + getUncleByBlockHashAndIndex(callback: EthQueryMethodCallback): void; + getUncleByBlockNumberAndIndex(callback: EthQueryMethodCallback): void; + getCompilers(callback: EthQueryMethodCallback): void; + compileLLL(callback: EthQueryMethodCallback): void; + compileSolidity(callback: EthQueryMethodCallback): void; + compileSerpent(callback: EthQueryMethodCallback): void; + newFilter(callback: EthQueryMethodCallback): void; + newBlockFilter(callback: EthQueryMethodCallback): void; + newPendingTransactionFilter(callback: EthQueryMethodCallback): void; + uninstallFilter(callback: EthQueryMethodCallback): void; + getFilterChanges(callback: EthQueryMethodCallback): void; + getFilterLogs(callback: EthQueryMethodCallback): void; + getLogs(callback: EthQueryMethodCallback): void; + getWork(callback: EthQueryMethodCallback): void; + submitWork(callback: EthQueryMethodCallback): void; + submitHashrate(callback: EthQueryMethodCallback): void; + + // Custom methods + + sendAsync: EthQuerySendAsyncFunction; + } +} diff --git a/types/ethjs-provider-http.d.ts b/types/ethjs-provider-http.d.ts index 6bee27c95f9..f379da96365 100644 --- a/types/ethjs-provider-http.d.ts +++ b/types/ethjs-provider-http.d.ts @@ -1 +1,16 @@ -declare module 'ethjs-provider-http'; +declare module 'ethjs-provider-http' { + import type { JsonRpcRequest } from 'json-rpc-engine'; + export type EthQueryMethodCallback = (error: any, response: R) => void; + + export default class HttpProvider { + host: string; + timeout: number; + + constructor(host: string, timeout?: number); + + sendAsync( + request: JsonRpcRequest

, + callback: EthQueryMethodCallback, + ): void; + } +} diff --git a/types/ethjs-unit.d.ts b/types/ethjs-unit.d.ts index 9030a9770f7..5d9dc8cbf3d 100644 --- a/types/ethjs-unit.d.ts +++ b/types/ethjs-unit.d.ts @@ -1 +1,64 @@ -declare module 'ethjs-unit'; +declare module 'ethjs-unit' { + import type BN from 'bn.js'; + + // This type is derived from the logic within `number-to-bn` and represents an + // object obtained via `bn.js` or `bignumber.js`. + type BigNumberish = ({ mul: any } | { dividedToIntegerBy: any }) & { + toString: (base: number) => string; + }; + + type AcceptableBNInput = string | number | BigNumberish; + + // This should be `keyof typeof unitMap` but really accepts anything + type Unitish = string | null | undefined; + + type Numberish = + | string + | number + | (({ toTwos: any } | { dividedToIntegerBy: any }) & { + toPrecision?: () => string; + toString: (base: number) => string | number; + }); + + export const unitMap: { + noether: '0'; + wei: '1'; + kwei: '1000'; + Kwei: '1000'; + babbage: '1000'; + femtoether: '1000'; + mwei: '1000000'; + Mwei: '1000000'; + lovelace: '1000000'; + picoether: '1000000'; + gwei: '1000000000'; + Gwei: '1000000000'; + shannon: '1000000000'; + nanoether: '1000000000'; + nano: '1000000000'; + szabo: '1000000000000'; + microether: '1000000000000'; + micro: '1000000000000'; + finney: '1000000000000000'; + milliether: '1000000000000000'; + milli: '1000000000000000'; + ether: '1000000000000000000'; + kether: '1000000000000000000000'; + grand: '1000000000000000000000'; + mether: '1000000000000000000000000'; + gether: '1000000000000000000000000000'; + tether: '1000000000000000000000000000000'; + }; + + export function numberToString(arg: Numberish): string; + + export function getValueOfUnit(unitInput: Unitish): BN; + + export function fromWei( + weiInput: AcceptableBNInput, + unit: Unitish, + optionsInput?: { pad?: boolean; commify?: boolean }, + ): string; + + export function toWei(etherInput: Numberish, unit: Unitish): BN; +}