diff --git a/package-lock.json b/package-lock.json index eb123ae5..ba21efcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@noble/secp256k1": "^1.5.5", "@types/big.js": "^6.1.3", - "big.js": "^6.1.1", + "big.js": "^6.2.0", "isomorphic-unfetch": "^3.1.0", "sha3": "^2.1.4" }, @@ -2865,9 +2865,9 @@ "dev": true }, "node_modules/big.js": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.1.1.tgz", - "integrity": "sha512-1vObw81a8ylZO5ePrtMay0n018TcftpTA5HFKDaSuiUDBo8biRBtjIobw60OpwuvrGk+FsxKamqN4cnmj/eXdg==", + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.0.tgz", + "integrity": "sha512-paIKvJiAaOYdLt6MfnvxkDo64lTOV257XYJyX3oJnJQocIclUn+48k6ZerH/c5FxWE6DGJu1TKDYis7tqHg9kg==", "engines": { "node": "*" }, @@ -13904,9 +13904,9 @@ "dev": true }, "big.js": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.1.1.tgz", - "integrity": "sha512-1vObw81a8ylZO5ePrtMay0n018TcftpTA5HFKDaSuiUDBo8biRBtjIobw60OpwuvrGk+FsxKamqN4cnmj/eXdg==" + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.0.tgz", + "integrity": "sha512-paIKvJiAaOYdLt6MfnvxkDo64lTOV257XYJyX3oJnJQocIclUn+48k6ZerH/c5FxWE6DGJu1TKDYis7tqHg9kg==" }, "bignumber.js": { "version": "9.0.2", diff --git a/package.json b/package.json index f0f0c4aa..6eea45f8 100644 --- a/package.json +++ b/package.json @@ -91,7 +91,7 @@ "dependencies": { "@noble/secp256k1": "^1.5.5", "@types/big.js": "^6.1.3", - "big.js": "^6.1.1", + "big.js": "^6.2.0", "isomorphic-unfetch": "^3.1.0", "sha3": "^2.1.4" }, diff --git a/readme.md b/readme.md index 8a1bd1e6..5cec39dc 100644 --- a/readme.md +++ b/readme.md @@ -77,6 +77,7 @@ - [`weiToEther`](#weitoether) - [`zeroPad`](#zeropad) - [Providers](#providers) + - [`call`](#call) - [`estimateGas`](#estimategas) - [`getBalance`](#getbalance) - [`getBlock`](#getblock) @@ -994,6 +995,32 @@ provider.getGasPrice().toNumber(); +#### [`call`](https://essential-eth.vercel.app/docs/api/classes/JsonRpcProvider#call) + +```typescript +provider.call(transaction: TransactionRequest, blockTag?: BlockTag): Promise +``` + +
+ View Example + +```js +import { JsonRpcProvider } from 'essential-eth'; +const provider = new JsonRpcProvider('RPC URL HERE' /* Try Infura or POKT */); +``` + +```javascript +await provider.call({ + to: '0x6b175474e89094c44da98b954eedeac495271d0f', + data: '0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE', +}); +// '0x0000000000000000000000000000000000000000000000000858898f93629000' +``` + +
+ +
+ #### [`estimateGas`](https://essential-eth.vercel.app/docs/api/classes/JsonRpcProvider#estimategas) ```typescript diff --git a/src/classes/utils/clean-block.ts b/src/classes/utils/clean-block.ts index 96044e51..f8ce852e 100644 --- a/src/classes/utils/clean-block.ts +++ b/src/classes/utils/clean-block.ts @@ -11,6 +11,117 @@ import { hexToDecimal } from './hex-to-decimal'; * @param block the RPCBlock to clean * @param returnTransactionObjects whether or not to return the transactions specified in this block * @returns a cleaned block + * @example + * ```js + * const rpcBlock = { + * number: '0x40f9de', + * hash: '0x4cbaa942e48a91108f38e2a250f6dbaff7fffe3027f5ebf76701929eed2b2970', + * parentHash: '0xc8de1e513f74cbc5cc77e2a39e4cada6504469a5d0e87200b708319c1e9ef154', + * sha3Uncles: '0x04d250716296c9662314b37c112e5ce8b1c9ad7fd7820a39482a3688a2078f2b', + * logsBloom: '0x0000400004000000040010100200000000000000000000000000000000000000001000000001000000000000000000000004010000000000000a800000000000040000000001000400000000000000000000000000000000000002000000000000000000000004000040000000800000000001000000000000000000000000000000000000000001000000000004200000000000000000000000000124400000000000000200100020000000000000000080000000000080001000000000000000000081000000000000000000008000000020000100000000200020100800000000000000000008002000000080001000020c00000000000000200000000000', + * transactionsRoot: '0xfc79f4f620cb69861ac67ceee5e1ddf3e45da95be230d4064be234f1ee491aa5', + * stateRoot: '0xfa5ef7e368997d70670b0ea3172aabefc2dee285786ce69c7165d8d854f8b292', + * receiptsRoot: '0x7fa0679e88fef8a8f7f5865dc4e6000ddcc267b14d2904948e3b1576a18a3bbd', + * miner: '0x1b7a75ef070ff49e6b9491a26403d799f2099ebd', + * difficulty: '0x47ede14fcbe635706e', + * totalDifficulty: '0x139e1de9b8404dedc5d30959', + * extraData: '0xce018c495249532d62613031656132', + * size: '0xb4f', + * gasLimit: '0x67c280', + * gasUsed: '0x56e2d', + * timestamp: '0x62648dc2', + * transactions: [ + * '0xd53800afc69e55cc7a64b927803b46a5b4c5ddccbaafb6b32fe0ec883739b825', + * '0x4b8b07f35a1f911a80a0ffeebe3d3c41cd393b4d5e1ce0a408026705913d6760', + * '0xa50eac0ea8005cb1e4b95be4e64c24035d9c41adb164c49477c3169870f02fb1', + * '0x413e5293786f8b63e73adf0b74ab56067da4b564d01936b88b138d48cc414a42', + * '0xd4e4969365d144b0b987632dca36ba9e272254bdc687245987a57666d6afa148' + * ], + * uncles: [ + * '0x36cd3869fd17a285b984dea8b705d34096e1fbdfe48df69ae530fbe921ba83fa' + * ], + * minimumGasPrice: '0x387ee40', + * bitcoinMergedMiningHeader: '0x0040502d717ae205da048b0ffb8e110603564d8677ca8bd3a54601000000000000000000e722e86bfebcae00bffb46c663fa0241b63a27f0c98fa710e421d5cc1afa2448d08d6462d9f809172f5e30aa', + * bitcoinMergedMiningCoinbaseTransaction: '0x00000000000001003f8757a906f0159f882f0968788a2e396b7bf8090e1b926fb2bb46789ac32d55082aca4e0800000000000000002b6a2952534b424c4f434b3a831a30935da1bd1e8631942fc7fa78f7a7b11d51ca39a1684d91a81f0040f9de00000000', + * bitcoinMergedMiningMerkleProof: '0xb53111a4e11bc19bf90268485d1b957d908ebc6a4cd9862aca3fc6ed3dcf3240b14c316de8521369d55dbfeb2b0116bcc10f40e999c4885e1bd2a08691bdea1c43862d590390a227a379d5677b958f1a23eecc16ac590ad675b8a4cea0c10da3ef597acb9ca1fe0a21fc408f09e0c7169d83aca8ddd636d8cc155f922e1d36c74b7cc11e9ee98dd1bf2100a55d59630c65da3db1575d58f165c5753c1779df90efcff9017b73cc32f4c87178bd0eae6a6dd0357047be70d6c4af17fcef097e80a9f1751447f4eee3831fc79f2d894934694149bcb99840a525f5128215eca6b54642af452ee7568a9281f40560afffd35725df31b98155d7813dea12e42f2a8052c7d98bcf62c9cdc66c40fb12b729b685a31aec4970ea5316640691ae5eb616808656a2bde4e9f5920ff178bf9d1f84e96a0d0bd048a3a8ca0d60970d02aacf7ecfb6e7feaec5c4a764873531cfd630e9430840cfe8b88154da25d6b94b706fe678d0efc1ecafed5a1f539e34552bea65622513b663e17e121f3c4548942584', + * hashForMergedMining: '0x831a30935da1bd1e8631942fc7fa78f7a7b11d51ca39a1684d91a81f0040f9de', + * paidFees: '0x1fb451615b58', + * cumulativeDifficulty: '0x8fdbe015f7248cf993' + * }; + * const returnTransactionObjects = false; + * + * await cleanBlock(rpcBlock, returnTransactionObjects); + * // { + * // number: 4258270, + * // hash: '0x4cbaa942e48a91108f38e2a250f6dbaff7fffe3027f5ebf76701929eed2b2970', + * // parentHash: '0xc8de1e513f74cbc5cc77e2a39e4cada6504469a5d0e87200b708319c1e9ef154', + * // sha3Uncles: '0x04d250716296c9662314b37c112e5ce8b1c9ad7fd7820a39482a3688a2078f2b', + * // logsBloom: '0x0000400004000000040010100200000000000000000000000000000000000000001000000001000000000000000000000004010000000000000a800000000000040000000001000400000000000000000000000000000000000002000000000000000000000004000040000000800000000001000000000000000000000000000000000000000001000000000004200000000000000000000000000124400000000000000200100020000000000000000080000000000080001000000000000000000081000000000000000000008000000020000100000000200020100800000000000000000008002000000080001000020c00000000000000200000000000', + * // transactionsRoot: '0xfc79f4f620cb69861ac67ceee5e1ddf3e45da95be230d4064be234f1ee491aa5', + * // stateRoot: '0xfa5ef7e368997d70670b0ea3172aabefc2dee285786ce69c7165d8d854f8b292', + * // receiptsRoot: '0x7fa0679e88fef8a8f7f5865dc4e6000ddcc267b14d2904948e3b1576a18a3bbd', + * // miner: '0x1b7A75Ef070Ff49E6B9491a26403D799f2099EbD', + * // difficulty: Big { + * // s: 1, + * // e: 21, + * // c: [Array], + * // constructor: [Function], + * // padAndChop: [Function (anonymous)] + * // }, + * // totalDifficulty: Big { + * // s: 1, + * // e: 27, + * // c: [Array], + * // constructor: [Function], + * // padAndChop: [Function (anonymous)] + * // }, + * // extraData: '0xce018c495249532d62613031656132', + * // size: Big { + * // s: 1, + * // e: 3, + * // c: [Array], + * // constructor: [Function], + * // padAndChop: [Function (anonymous)] + * // }, + * // gasLimit: Big { + * // s: 1, + * // e: 6, + * // c: [Array], + * // constructor: [Function], + * // padAndChop: [Function (anonymous)] + * // }, + * // gasUsed: Big { + * // s: 1, + * // e: 5, + * // c: [Array], + * // constructor: [Function], + * // padAndChop: [Function (anonymous)] + * // }, + * // timestamp: Big { + * // s: 1, + * // e: 9, + * // c: [Array], + * // constructor: [Function], + * // padAndChop: [Function (anonymous)] + * // }, + * // transactions: [ + * // '0xd53800afc69e55cc7a64b927803b46a5b4c5ddccbaafb6b32fe0ec883739b825', + * // '0x4b8b07f35a1f911a80a0ffeebe3d3c41cd393b4d5e1ce0a408026705913d6760', + * // '0xa50eac0ea8005cb1e4b95be4e64c24035d9c41adb164c49477c3169870f02fb1', + * // '0x413e5293786f8b63e73adf0b74ab56067da4b564d01936b88b138d48cc414a42', + * // '0xd4e4969365d144b0b987632dca36ba9e272254bdc687245987a57666d6afa148' + * // ], + * // uncles: [ + * // '0x36cd3869fd17a285b984dea8b705d34096e1fbdfe48df69ae530fbe921ba83fa' + * // ], + * // minimumGasPrice: '0x387ee40', + * // bitcoinMergedMiningHeader: '0x0040502d717ae205da048b0ffb8e110603564d8677ca8bd3a54601000000000000000000e722e86bfebcae00bffb46c663fa0241b63a27f0c98fa710e421d5cc1afa2448d08d6462d9f809172f5e30aa', + * // bitcoinMergedMiningCoinbaseTransaction: '0x00000000000001003f8757a906f0159f882f0968788a2e396b7bf8090e1b926fb2bb46789ac32d55082aca4e0800000000000000002b6a2952534b424c4f434b3a831a30935da1bd1e8631942fc7fa78f7a7b11d51ca39a1684d91a81f0040f9de00000000', + * // bitcoinMergedMiningMerkleProof: '0xb53111a4e11bc19bf90268485d1b957d908ebc6a4cd9862aca3fc6ed3dcf3240b14c316de8521369d55dbfeb2b0116bcc10f40e999c4885e1bd2a08691bdea1c43862d590390a227a379d5677b958f1a23eecc16ac590ad675b8a4cea0c10da3ef597acb9ca1fe0a21fc408f09e0c7169d83aca8ddd636d8cc155f922e1d36c74b7cc11e9ee98dd1bf2100a55d59630c65da3db1575d58f165c5753c1779df90efcff9017b73cc32f4c87178bd0eae6a6dd0357047be70d6c4af17fcef097e80a9f1751447f4eee3831fc79f2d894934694149bcb99840a525f5128215eca6b54642af452ee7568a9281f40560afffd35725df31b98155d7813dea12e42f2a8052c7d98bcf62c9cdc66c40fb12b729b685a31aec4970ea5316640691ae5eb616808656a2bde4e9f5920ff178bf9d1f84e96a0d0bd048a3a8ca0d60970d02aacf7ecfb6e7feaec5c4a764873531cfd630e9430840cfe8b88154da25d6b94b706fe678d0efc1ecafed5a1f539e34552bea65622513b663e17e121f3c4548942584', + * // hashForMergedMining: '0x831a30935da1bd1e8631942fc7fa78f7a7b11d51ca39a1684d91a81f0040f9de', + * // paidFees: '0x1fb451615b58', + * // cumulativeDifficulty: '0x8fdbe015f7248cf993' + * // } */ export function cleanBlock( block: RPCBlock, diff --git a/src/classes/utils/prepare-transaction.ts b/src/classes/utils/prepare-transaction.ts new file mode 100644 index 00000000..7f38be8f --- /dev/null +++ b/src/classes/utils/prepare-transaction.ts @@ -0,0 +1,49 @@ +import Big from 'big.js'; +import { TinyBig } from '../../shared/tiny-big/tiny-big'; +import { hexlify } from '../../utils/bytes'; +import { + RPCTransactionRequest, + TransactionRequest, +} from './../../types/Transaction.types'; +import { BytesLike } from './../../utils/bytes'; + +/** + * @param transaction + * @example + */ +export function prepareTransaction( + transaction: TransactionRequest, +): RPCTransactionRequest { + const preparedTransaction = { + ...transaction, + } as unknown as RPCTransactionRequest; + (Object.keys(transaction) as Array).forEach( + (key) => { + switch (key) { + case 'gas': + case 'gasPrice': + case 'nonce': + case 'maxFeePerGas': + case 'maxPriorityFeePerGas': + case 'value': { + const value = transaction[key]; + if (value instanceof TinyBig) { + preparedTransaction[key] = value.toHexString(); + } else if (value instanceof Big) { + preparedTransaction[key] = `0x${BigInt(value.toString()).toString( + 16, + )}`; + } else if (typeof transaction[key] === 'number') + preparedTransaction[key] = + '0x' + (transaction[key] as any).toString(16); + else preparedTransaction[key] = (transaction[key] as any).toString(); + break; + } + case 'data': + preparedTransaction[key] = hexlify(transaction[key] as BytesLike); + break; + } + }, + ); + return preparedTransaction; +} diff --git a/src/index.ts b/src/index.ts index 6f1470a5..11feb168 100644 --- a/src/index.ts +++ b/src/index.ts @@ -19,7 +19,9 @@ import { RPCLog, RPCTransaction, RPCTransactionReceipt, + RPCTransactionRequest, TransactionReceipt, + TransactionRequest, TransactionResponse, } from './types/Transaction.types'; import { computeAddress } from './utils/compute-address'; @@ -34,7 +36,6 @@ import { toChecksumAddress } from './utils/to-checksum-address'; import { toUtf8Bytes } from './utils/to-utf8-bytes'; import { weiToEther } from './utils/wei-to-ether'; -export * from './providers/types'; export * from './utils/bytes'; export * from './utils/hash-message'; export * from './utils/keccak256'; @@ -71,6 +72,8 @@ export { RPCBlock, RPCTransaction, RPCTransactionReceipt, + TransactionRequest, + RPCTransactionRequest, TransactionReceipt, BlockTag, RPCLog, diff --git a/src/providers/BaseProvider.ts b/src/providers/BaseProvider.ts index 1f4df93b..114e9cc1 100644 --- a/src/providers/BaseProvider.ts +++ b/src/providers/BaseProvider.ts @@ -4,6 +4,8 @@ import { cleanTransaction } from '../classes/utils/clean-transaction'; import { cleanTransactionReceipt } from '../classes/utils/clean-transaction-receipt'; import { buildRPCPostBody, post } from '../classes/utils/fetchers'; import { hexToDecimal } from '../classes/utils/hex-to-decimal'; +import { prepareTransaction } from '../classes/utils/prepare-transaction'; +import { logger } from '../logger/logger'; import { TinyBig, tinyBig } from '../shared/tiny-big/tiny-big'; import { BlockResponse, BlockTag, RPCBlock } from '../types/Block.types'; import { Filter, FilterByBlockHash } from '../types/Filter.types'; @@ -14,9 +16,9 @@ import { RPCTransaction, RPCTransactionReceipt, TransactionReceipt, + TransactionRequest, TransactionResponse, } from '../types/Transaction.types'; -import { TransactionRequest } from './types'; import chainsInfo from './utils/chains-info'; /** @@ -445,8 +447,9 @@ export abstract class BaseProvider { * ``` */ public async estimateGas(transaction: TransactionRequest): Promise { + const rpcTransaction = prepareTransaction(transaction); const gasUsed = (await this.post( - buildRPCPostBody('eth_estimateGas', [transaction]), + buildRPCPostBody('eth_estimateGas', [rpcTransaction]), )) as string; return tinyBig(hexToDecimal(gasUsed)); } @@ -506,4 +509,54 @@ export abstract class BaseProvider { const logs = rpcLogs.map((log) => cleanLog(log, false)); return logs; } + + /** + * Returns the result of adding a transaction to the blockchain without actually adding that transaction to the blockchain. + * Does not require any ether as gas. + * + * * [Identical](/docs/api#isd) to [`ethers.provider.call`](https://docs.ethers.io/v5/api/providers/provider/#Provider-call) in ethers.js + * * [Identical](/docs/api#isd) to [`web3.eth.call`](https://web3js.readthedocs.io/en/v1.7.3/web3-eth.html#call) in web3.js + * + * @param transaction the transaction object to, in theory, execute. Doesn't actually get added to the blockchain. + * @param blockTag the block to execute this transaction on + * @returns the result of executing the transaction on the specified block + * @example + * ```javascript + * await provider.call({ to: "0x6b175474e89094c44da98b954eedeac495271d0f", data: "0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE" }); + * // '0x0000000000000000000000000000000000000000000000000858898f93629000' + * ``` + */ + public async call( + transaction: TransactionRequest, + blockTag: BlockTag = 'latest', + ): Promise { + if ( + transaction.gasPrice && + (transaction.maxPriorityFeePerGas || transaction.maxFeePerGas) + ) { + logger.throwError( + 'Cannot specify both "gasPrice" and ("maxPriorityFeePerGas" or "maxFeePerGas")', + { + gasPrice: transaction.gasPrice, + maxFeePerGas: transaction.maxFeePerGas, + maxPriorityFeePerGas: transaction.maxPriorityFeePerGas, + }, + ); + } + if (transaction.maxFeePerGas && transaction.maxPriorityFeePerGas) { + logger.throwError( + 'Cannot specify both "maxFeePerGas" and "maxPriorityFeePerGas"', + { + maxFeePerGas: transaction.maxFeePerGas, + maxPriorityFeePerGas: transaction.maxPriorityFeePerGas, + }, + ); + } + blockTag = prepBlockTag(blockTag); + const rpcTransaction = prepareTransaction(transaction); + const transactionRes = (await this.post( + buildRPCPostBody('eth_call', [rpcTransaction, blockTag]), + )) as string; + return transactionRes; + } } diff --git a/src/providers/test/json-rpc-provider/call.test.ts b/src/providers/test/json-rpc-provider/call.test.ts new file mode 100644 index 00000000..b0ebdee7 --- /dev/null +++ b/src/providers/test/json-rpc-provider/call.test.ts @@ -0,0 +1,128 @@ +import Big from 'big.js'; +import { ethers } from 'ethers'; +import Web3 from 'web3'; +import { JsonRpcProvider, tinyBig } from '../../..'; +import { hexToDecimal } from '../../../classes/utils/hex-to-decimal'; +import { rpcUrls } from './../rpc-urls'; + +const rpcUrl = rpcUrls.mainnet; + +// Based on https://etherscan.io/tx/0x277c40de5bf1d4fa06e37dce8e1370dac7273a4b2a883515176f51abaa50d512 +const dataTo = { + data: '0x70a082310000000000000000000000006E0d01A76C3Cf4288372a29124A26D4353EE51BE', + to: '0x6b175474e89094c44da98b954eedeac495271d0f', +}; + +// Based on https://etherscan.io/tx/0xfc4a0544289c9eae2f94a9091208e3793ef8e9e93ea4dbaa80f70115be5e9813 +const dataFromGasTo = { + to: '0x3d13c2224a1cdd661e4cc91091f83047750270c5', + from: '0x0000000000000000000000000000000000000000', + nonce: '0x1', + gas: 999999, + data: '0x1234', + value: '0x123', + // not sure how to get "chainId" into proper format + // chainId: 1, + type: 1, + maxFeePerGas: '0xffffffffff', +}; + +describe('provider.call', () => { + const essentialEthProvider = new JsonRpcProvider(rpcUrl); + const web3Provider = new Web3(rpcUrl); + const ethersProvider = new ethers.providers.StaticJsonRpcProvider(rpcUrl); + + it('throws', async () => { + await expect( + essentialEthProvider.call({ + ...dataTo, + maxFeePerGas: '0x12', + maxPriorityFeePerGas: '0x123', + }), + ).rejects.toThrow(); + await expect( + essentialEthProvider.call({ + ...dataTo, + gasPrice: '0xfffffff', + maxFeePerGas: '0x12', + }), + ).rejects.toThrow(); + await expect( + essentialEthProvider.call({ + ...dataTo, + gasPrice: '0xfffffff', + maxPriorityFeePerGas: '0x123', + }), + ).rejects.toThrow(); + }); + + it('should match ethers.js -- data, to', async () => { + const [eeCall, ethersCall, web3Call] = await Promise.all([ + essentialEthProvider.call(dataTo), + ethersProvider.call(dataTo), + web3Provider.eth.call(dataTo), + ]); + expect(eeCall).toBe(ethersCall); + expect(eeCall).toBe(web3Call); + }); + + it('should match ethers.js -- data, to, gasPrice', async () => { + const data = { ...dataTo, gasPrice: 99999999999 }; + const [eeCall, ethersCall, web3Call] = await Promise.all([ + essentialEthProvider.call(data), + ethersProvider.call(data), + web3Provider.eth.call(data), + ]); + expect(eeCall).toBe(ethersCall); + expect(eeCall).toBe(web3Call); + }); + + it('should match ethers.js -- all mixed data as strings', async () => { + const [eeCall, ethersCall, web3Call] = await Promise.all([ + essentialEthProvider.call(dataFromGasTo), + ethersProvider.call(dataFromGasTo), + web3Provider.eth.call({ + ...dataFromGasTo, + nonce: Number(hexToDecimal(dataFromGasTo.nonce)), + }), + ]); + expect(eeCall).toBe(ethersCall); + expect(eeCall).toBe(web3Call); + }); + + it('should match ethers.js -- all mixed data as TinyBig', async () => { + const [eeCall, ethersCall, web3Call] = await Promise.all([ + essentialEthProvider.call({ + ...dataFromGasTo, + nonce: tinyBig(dataFromGasTo.nonce), + gas: tinyBig(dataFromGasTo.gas), + value: tinyBig(dataFromGasTo.value), + }), + ethersProvider.call(dataFromGasTo), + web3Provider.eth.call({ + ...dataFromGasTo, + nonce: Number(hexToDecimal(dataFromGasTo.nonce)), + }), + ]); + expect(eeCall).toBe(ethersCall); + expect(eeCall).toBe(web3Call); + }); + + it('should match ethers.js -- all mixeddata as Big', async () => { + const [eeCall, ethersCall, web3Call] = await Promise.all([ + essentialEthProvider.call({ + ...dataFromGasTo, + nonce: new Big(hexToDecimal(dataFromGasTo.nonce)), + gas: new Big(dataFromGasTo.gas), + value: new Big(hexToDecimal(dataFromGasTo.value)), + }), + ethersProvider.call(dataFromGasTo), + web3Provider.eth.call({ + ...dataFromGasTo, + nonce: Number(hexToDecimal(dataFromGasTo.nonce)), + }), + ]); + expect(eeCall).toBe(ethersCall); + expect(eeCall).toBe(web3Call); + }); +}); diff --git a/src/providers/test/json-rpc-provider/estimate-gas.test.ts b/src/providers/test/json-rpc-provider/estimate-gas.test.ts index 820f613e..de9b8390 100644 --- a/src/providers/test/json-rpc-provider/estimate-gas.test.ts +++ b/src/providers/test/json-rpc-provider/estimate-gas.test.ts @@ -1,37 +1,55 @@ -import { StaticJsonRpcProvider } from '@ethersproject/providers'; -import { etherToWei, JsonRpcProvider } from '../../..'; -import { TransactionRequest } from '../../types'; +import { ethers } from 'ethers'; +import Web3 from 'web3'; +import { JsonRpcProvider } from '../../..'; +import { etherToWei } from '../../../utils/ether-to-wei'; import { rpcUrls } from '../rpc-urls'; -async function testEstimateGas(transaction: TransactionRequest) { - const rpcUrl = rpcUrls.mainnet; - const eeProvider = new JsonRpcProvider(rpcUrl); - const ethersProvider = new StaticJsonRpcProvider(rpcUrl); - const [eeGasUsed, ethersGasUsed] = await Promise.all([ - eeProvider.estimateGas(transaction), - ethersProvider.estimateGas(transaction), - ]); +const rpcUrl = rpcUrls.mainnet; - expect(eeGasUsed.toString()).toBe(ethersGasUsed.toString()); -} +const dataTo = { + to: '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41', + data: '0x3b3b57debf074faa138b72c65adbdcfb329847e4f2c04bde7f7dd7fcad5a52d2f395a558', +}; -describe('provider.estimateGas', () => { - it('transaction with only "to" and "data"', async () => { - await testEstimateGas({ - to: '0x4976fb03C32e5B8cfe2b6cCB31c09Ba78EBaBa41', - data: '0x3b3b57debf074faa138b72c65adbdcfb329847e4f2c04bde7f7dd7fcad5a52d2f395a558', - }); - }); - it('transaction with only "to", "data", and "value"', async () => { - await testEstimateGas({ - // Wrapped ETH address - to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', +const dataToValue = { + // Wrapped ETH address + to: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + // `function deposit() payable` + data: '0xd0e30db0', + value: etherToWei(0.001).toNumber(), +}; - // `function deposit() payable` - data: '0xd0e30db0', +describe('provider.estimateGas', () => { + const essentialEthProvider = new JsonRpcProvider(rpcUrl); + const web3Provider = new Web3(rpcUrl); + const ethersProvider = new ethers.providers.StaticJsonRpcProvider(rpcUrl); - // 1 ether - value: etherToWei('1.0').toHexString(), - }); + it('should match ethers.js -- data, to', async () => { + const [eeEstimateGas, ethersEstimateGas] = await Promise.all([ + essentialEthProvider.estimateGas(dataTo), + ethersProvider.estimateGas(dataTo), + ]); + expect(eeEstimateGas.toString()).toBe(ethersEstimateGas.toString()); + }); + it('should match web3.js -- data, to', async () => { + const [eeEstimateGas, web3EstimateGas] = await Promise.all([ + essentialEthProvider.estimateGas(dataTo), + web3Provider.eth.estimateGas(dataTo), + ]); + expect(eeEstimateGas.toString()).toBe(web3EstimateGas.toString()); + }); + it('should match ethers.js -- data, to, value', async () => { + const [eeEstimateGas, ethersEstimateGas] = await Promise.all([ + essentialEthProvider.estimateGas(dataToValue), + ethersProvider.estimateGas(dataToValue), + ]); + expect(eeEstimateGas.toString()).toBe(ethersEstimateGas.toString()); + }); + it('should match web3.js -- data, to, value', async () => { + const [eeEstimateGas, web3EstimateGas] = await Promise.all([ + essentialEthProvider.estimateGas(dataToValue), + web3Provider.eth.estimateGas(dataToValue), + ]); + expect(eeEstimateGas.toString()).toBe(web3EstimateGas.toString()); }); }); diff --git a/src/providers/types.ts b/src/providers/types.ts deleted file mode 100644 index f086bcdc..00000000 --- a/src/providers/types.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { BytesLike } from '../utils/bytes'; - -export interface TransactionRequest { - to?: string; - from?: string; - data?: BytesLike; - [key: string]: any; - // nonce?: BigNumberish; - - // gasLimit?: BigNumberish; - // gasPrice?: BigNumberish; - - // value?: BigNumberish; - // chainId?: number; - - // type?: number; - // accessList?: AccessListish; - - // maxPriorityFeePerGas?: BigNumberish; - // maxFeePerGas?: BigNumberish; - - // customData?: Record; - // ccipReadEnabled?: boolean; -} diff --git a/src/providers/utils/chains-info.ts b/src/providers/utils/chains-info.ts index 0e9e2c4c..d02ceb3f 100644 --- a/src/providers/utils/chains-info.ts +++ b/src/providers/utils/chains-info.ts @@ -708,7 +708,7 @@ export default { "Fuji-EZChain" ], "3000": [ - "echelon" + "cennz-r" ], "3001": [ "cennz-n" @@ -848,6 +848,12 @@ export default { "10823": [ "CCP" ], + "10946": [ + "quadrans" + ], + "10947": [ + "quadranstestnet" + ], "11111": [ "WAGMI" ], @@ -887,6 +893,9 @@ export default { "24734": [ "mintme" ], + "26863": [ + "OAC" + ], "30067": [ "Piece" ], @@ -992,6 +1001,12 @@ export default { "71393": [ "ckb" ], + "71401": [ + "gw-testnet-v1" + ], + "71402": [ + "gw-mainnet-v1" + ], "73799": [ "vt" ], @@ -1100,9 +1115,15 @@ export default { "421611": [ "arb-rinkeby" ], + "432201": [ + "Dexalot" + ], "444900": [ "wlkt" ], + "474142": [ + "oc" + ], "512512": [ "cmp" ], @@ -1124,6 +1145,9 @@ export default { "1337702": [ "kintsugi" ], + "1337802": [ + "kiln" + ], "2203181": [ "platondev" ], @@ -1239,6 +1263,6 @@ export default { "mole" ], "868455272153094": [ - "gw-testnet-v1" + "gw-testnet-v1-deprecated" ] } \ No newline at end of file diff --git a/src/shared/tiny-big/tiny-big.test.ts b/src/shared/tiny-big/tiny-big.test.ts index 74ad32dd..993c2360 100644 --- a/src/shared/tiny-big/tiny-big.test.ts +++ b/src/shared/tiny-big/tiny-big.test.ts @@ -2,6 +2,10 @@ import { BigNumber } from 'ethers'; import { tinyBig } from './tiny-big'; describe('tiny-big', () => { + it('allows hex string input', () => { + expect(tinyBig('0xa').toString()).toBe('10'); + expect(tinyBig('10').toString()).toBe('10'); + }); it('performs toHexString properly', () => { expect(tinyBig(0).toHexString()).toBe('0x0'); expect(tinyBig(1).toHexString()).toBe('0x1'); diff --git a/src/shared/tiny-big/tiny-big.ts b/src/shared/tiny-big/tiny-big.ts index 6cdf22be..dc3b69d5 100644 --- a/src/shared/tiny-big/tiny-big.ts +++ b/src/shared/tiny-big/tiny-big.ts @@ -1,4 +1,5 @@ import Big from 'big.js'; +import { hexToDecimal } from '../../classes/utils/hex-to-decimal'; import { scientificStrToDecimalStr } from './helpers'; /** @@ -7,6 +8,9 @@ import { scientificStrToDecimalStr } from './helpers'; */ export class TinyBig extends Big { constructor(value: string | number | TinyBig | Big) { + if (typeof value === 'string' && value.startsWith('0x')) { + value = hexToDecimal(value); + } super(value); } /** diff --git a/src/types/Transaction.types.ts b/src/types/Transaction.types.ts index db08e450..527655f1 100644 --- a/src/types/Transaction.types.ts +++ b/src/types/Transaction.types.ts @@ -1,4 +1,6 @@ +import type Big from 'big.js'; import { TinyBig } from '../shared/tiny-big/tiny-big'; +import { BytesLike } from './../utils/bytes'; type Modify = Omit & R; @@ -30,7 +32,8 @@ export type TransactionResponse = Modify< /** * Type that contains information from the receipt of a transaction - * Similar to [`Type TransactionReceipt on ethers.providers`](https://docs.ethers.io/v5/api/providers/types/#providers-TransactionReceipt) + * + * * Similar to [`Type TransactionReceipt on ethers.providers`](https://docs.ethers.io/v5/api/providers/types/#providers-TransactionReceipt) */ export type TransactionReceipt = Modify< RPCTransactionReceipt, @@ -49,6 +52,33 @@ export type TransactionReceipt = Modify< } >; +// What the RPC is expecting +export interface RPCTransactionRequest { + from?: string; + to: string; + gas?: string; + gasPrice?: string; + value?: string; + data?: BytesLike; + nonce?: string /* hex number as string */; + maxPriorityFeePerGas?: string /* hex number as string */; + maxFeePerGas?: string /* hex number as string */; +} + +export interface TransactionRequest { + to?: string; + from?: string; + nonce?: TinyBig | string | Big | number; + gas?: TinyBig | number | Big | string; + gasPrice?: TinyBig | Big | string | number; + data?: BytesLike; + value?: TinyBig | string | Big | number; + chainId?: number; + type?: number; + maxPriorityFeePerGas?: TinyBig | string | Big | number; + maxFeePerGas?: TinyBig | string | Big | number; +} + /** * Type for the logs that are included in transaction receipts * Similar to [`Type Log on ethers.providers`](https://docs.ethers.io/v5/api/providers/types/#providers-Log)