json-rpc-provider.ts 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662
  1. "use strict";
  2. // See: https://github.com/ethereum/wiki/wiki/JSON-RPC
  3. import { Provider, TransactionRequest, TransactionResponse } from "@ethersproject/abstract-provider";
  4. import { Signer, TypedDataDomain, TypedDataField, TypedDataSigner } from "@ethersproject/abstract-signer";
  5. import { BigNumber } from "@ethersproject/bignumber";
  6. import { Bytes, hexlify, hexValue, isHexString } from "@ethersproject/bytes";
  7. import { _TypedDataEncoder } from "@ethersproject/hash";
  8. import { Network, Networkish } from "@ethersproject/networks";
  9. import { checkProperties, deepCopy, Deferrable, defineReadOnly, getStatic, resolveProperties, shallowCopy } from "@ethersproject/properties";
  10. import { toUtf8Bytes } from "@ethersproject/strings";
  11. import { AccessList, accessListify } from "@ethersproject/transactions";
  12. import { ConnectionInfo, fetchJson, poll } from "@ethersproject/web";
  13. import { Logger } from "@ethersproject/logger";
  14. import { version } from "./_version";
  15. const logger = new Logger(version);
  16. import { BaseProvider, Event } from "./base-provider";
  17. const errorGas = [ "call", "estimateGas" ];
  18. function checkError(method: string, error: any, params: any): any {
  19. // Undo the "convenience" some nodes are attempting to prevent backwards
  20. // incompatibility; maybe for v6 consider forwarding reverts as errors
  21. if (method === "call" && error.code === Logger.errors.SERVER_ERROR) {
  22. const e = error.error;
  23. if (e && e.message.match("reverted") && isHexString(e.data)) {
  24. return e.data;
  25. }
  26. logger.throwError("missing revert data in call exception", Logger.errors.CALL_EXCEPTION, {
  27. error, data: "0x"
  28. });
  29. }
  30. let message = error.message;
  31. if (error.code === Logger.errors.SERVER_ERROR && error.error && typeof(error.error.message) === "string") {
  32. message = error.error.message;
  33. } else if (typeof(error.body) === "string") {
  34. message = error.body;
  35. } else if (typeof(error.responseText) === "string") {
  36. message = error.responseText;
  37. }
  38. message = (message || "").toLowerCase();
  39. const transaction = params.transaction || params.signedTransaction;
  40. // "insufficient funds for gas * price + value + cost(data)"
  41. if (message.match(/insufficient funds|base fee exceeds gas limit/)) {
  42. logger.throwError("insufficient funds for intrinsic transaction cost", Logger.errors.INSUFFICIENT_FUNDS, {
  43. error, method, transaction
  44. });
  45. }
  46. // "nonce too low"
  47. if (message.match(/nonce too low/)) {
  48. logger.throwError("nonce has already been used", Logger.errors.NONCE_EXPIRED, {
  49. error, method, transaction
  50. });
  51. }
  52. // "replacement transaction underpriced"
  53. if (message.match(/replacement transaction underpriced/)) {
  54. logger.throwError("replacement fee too low", Logger.errors.REPLACEMENT_UNDERPRICED, {
  55. error, method, transaction
  56. });
  57. }
  58. // "replacement transaction underpriced"
  59. if (message.match(/only replay-protected/)) {
  60. logger.throwError("legacy pre-eip-155 transactions not supported", Logger.errors.UNSUPPORTED_OPERATION, {
  61. error, method, transaction
  62. });
  63. }
  64. if (errorGas.indexOf(method) >= 0 && message.match(/gas required exceeds allowance|always failing transaction|execution reverted/)) {
  65. logger.throwError("cannot estimate gas; transaction may fail or may require manual gas limit", Logger.errors.UNPREDICTABLE_GAS_LIMIT, {
  66. error, method, transaction
  67. });
  68. }
  69. throw error;
  70. }
  71. function timer(timeout: number): Promise<any> {
  72. return new Promise(function(resolve) {
  73. setTimeout(resolve, timeout);
  74. });
  75. }
  76. function getResult(payload: { error?: { code?: number, data?: any, message?: string }, result?: any }): any {
  77. if (payload.error) {
  78. // @TODO: not any
  79. const error: any = new Error(payload.error.message);
  80. error.code = payload.error.code;
  81. error.data = payload.error.data;
  82. throw error;
  83. }
  84. return payload.result;
  85. }
  86. function getLowerCase(value: string): string {
  87. if (value) { return value.toLowerCase(); }
  88. return value;
  89. }
  90. const _constructorGuard = {};
  91. export class JsonRpcSigner extends Signer implements TypedDataSigner {
  92. readonly provider: JsonRpcProvider;
  93. _index: number;
  94. _address: string;
  95. constructor(constructorGuard: any, provider: JsonRpcProvider, addressOrIndex?: string | number) {
  96. logger.checkNew(new.target, JsonRpcSigner);
  97. super();
  98. if (constructorGuard !== _constructorGuard) {
  99. throw new Error("do not call the JsonRpcSigner constructor directly; use provider.getSigner");
  100. }
  101. defineReadOnly(this, "provider", provider);
  102. if (addressOrIndex == null) { addressOrIndex = 0; }
  103. if (typeof(addressOrIndex) === "string") {
  104. defineReadOnly(this, "_address", this.provider.formatter.address(addressOrIndex));
  105. defineReadOnly(this, "_index", null);
  106. } else if (typeof(addressOrIndex) === "number") {
  107. defineReadOnly(this, "_index", addressOrIndex);
  108. defineReadOnly(this, "_address", null);
  109. } else {
  110. logger.throwArgumentError("invalid address or index", "addressOrIndex", addressOrIndex);
  111. }
  112. }
  113. connect(provider: Provider): JsonRpcSigner {
  114. return logger.throwError("cannot alter JSON-RPC Signer connection", Logger.errors.UNSUPPORTED_OPERATION, {
  115. operation: "connect"
  116. });
  117. }
  118. connectUnchecked(): JsonRpcSigner {
  119. return new UncheckedJsonRpcSigner(_constructorGuard, this.provider, this._address || this._index);
  120. }
  121. getAddress(): Promise<string> {
  122. if (this._address) {
  123. return Promise.resolve(this._address);
  124. }
  125. return this.provider.send("eth_accounts", []).then((accounts) => {
  126. if (accounts.length <= this._index) {
  127. logger.throwError("unknown account #" + this._index, Logger.errors.UNSUPPORTED_OPERATION, {
  128. operation: "getAddress"
  129. });
  130. }
  131. return this.provider.formatter.address(accounts[this._index])
  132. });
  133. }
  134. sendUncheckedTransaction(transaction: Deferrable<TransactionRequest>): Promise<string> {
  135. transaction = shallowCopy(transaction);
  136. const fromAddress = this.getAddress().then((address) => {
  137. if (address) { address = address.toLowerCase(); }
  138. return address;
  139. });
  140. // The JSON-RPC for eth_sendTransaction uses 90000 gas; if the user
  141. // wishes to use this, it is easy to specify explicitly, otherwise
  142. // we look it up for them.
  143. if (transaction.gasLimit == null) {
  144. const estimate = shallowCopy(transaction);
  145. estimate.from = fromAddress;
  146. transaction.gasLimit = this.provider.estimateGas(estimate);
  147. }
  148. if (transaction.to != null) {
  149. transaction.to = Promise.resolve(transaction.to).then(async (to) => {
  150. if (to == null) { return null; }
  151. const address = await this.provider.resolveName(to);
  152. if (address == null) {
  153. logger.throwArgumentError("provided ENS name resolves to null", "tx.to", to);
  154. }
  155. return address;
  156. });
  157. }
  158. return resolveProperties({
  159. tx: resolveProperties(transaction),
  160. sender: fromAddress
  161. }).then(({ tx, sender }) => {
  162. if (tx.from != null) {
  163. if (tx.from.toLowerCase() !== sender) {
  164. logger.throwArgumentError("from address mismatch", "transaction", transaction);
  165. }
  166. } else {
  167. tx.from = sender;
  168. }
  169. const hexTx = (<any>this.provider.constructor).hexlifyTransaction(tx, { from: true });
  170. return this.provider.send("eth_sendTransaction", [ hexTx ]).then((hash) => {
  171. return hash;
  172. }, (error) => {
  173. return checkError("sendTransaction", error, hexTx);
  174. });
  175. });
  176. }
  177. signTransaction(transaction: Deferrable<TransactionRequest>): Promise<string> {
  178. return logger.throwError("signing transactions is unsupported", Logger.errors.UNSUPPORTED_OPERATION, {
  179. operation: "signTransaction"
  180. });
  181. }
  182. async sendTransaction(transaction: Deferrable<TransactionRequest>): Promise<TransactionResponse> {
  183. // This cannot be mined any earlier than any recent block
  184. const blockNumber = await this.provider._getInternalBlockNumber(100 + 2 * this.provider.pollingInterval);
  185. // Send the transaction
  186. const hash = await this.sendUncheckedTransaction(transaction);
  187. try {
  188. // Unfortunately, JSON-RPC only provides and opaque transaction hash
  189. // for a response, and we need the actual transaction, so we poll
  190. // for it; it should show up very quickly
  191. return await poll(async () => {
  192. const tx = await this.provider.getTransaction(hash);
  193. if (tx === null) { return undefined; }
  194. return this.provider._wrapTransaction(tx, hash, blockNumber);
  195. }, { oncePoll: this.provider });
  196. } catch (error) {
  197. (<any>error).transactionHash = hash;
  198. throw error;
  199. }
  200. }
  201. async signMessage(message: Bytes | string): Promise<string> {
  202. const data = ((typeof(message) === "string") ? toUtf8Bytes(message): message);
  203. const address = await this.getAddress();
  204. return await this.provider.send("personal_sign", [ hexlify(data), address.toLowerCase() ]);
  205. }
  206. async _legacySignMessage(message: Bytes | string): Promise<string> {
  207. const data = ((typeof(message) === "string") ? toUtf8Bytes(message): message);
  208. const address = await this.getAddress();
  209. // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
  210. return await this.provider.send("eth_sign", [ address.toLowerCase(), hexlify(data) ]);
  211. }
  212. async _signTypedData(domain: TypedDataDomain, types: Record<string, Array<TypedDataField>>, value: Record<string, any>): Promise<string> {
  213. // Populate any ENS names (in-place)
  214. const populated = await _TypedDataEncoder.resolveNames(domain, types, value, (name: string) => {
  215. return this.provider.resolveName(name);
  216. });
  217. const address = await this.getAddress();
  218. return await this.provider.send("eth_signTypedData_v4", [
  219. address.toLowerCase(),
  220. JSON.stringify(_TypedDataEncoder.getPayload(populated.domain, types, populated.value))
  221. ]);
  222. }
  223. async unlock(password: string): Promise<boolean> {
  224. const provider = this.provider;
  225. const address = await this.getAddress();
  226. return provider.send("personal_unlockAccount", [ address.toLowerCase(), password, null ]);
  227. }
  228. }
  229. class UncheckedJsonRpcSigner extends JsonRpcSigner {
  230. sendTransaction(transaction: Deferrable<TransactionRequest>): Promise<TransactionResponse> {
  231. return this.sendUncheckedTransaction(transaction).then((hash) => {
  232. return <TransactionResponse>{
  233. hash: hash,
  234. nonce: null,
  235. gasLimit: null,
  236. gasPrice: null,
  237. data: null,
  238. value: null,
  239. chainId: null,
  240. confirmations: 0,
  241. from: null,
  242. wait: (confirmations?: number) => { return this.provider.waitForTransaction(hash, confirmations); }
  243. };
  244. });
  245. }
  246. }
  247. const allowedTransactionKeys: { [ key: string ]: boolean } = {
  248. chainId: true, data: true, gasLimit: true, gasPrice:true, nonce: true, to: true, value: true,
  249. type: true, accessList: true,
  250. maxFeePerGas: true, maxPriorityFeePerGas: true
  251. }
  252. export class JsonRpcProvider extends BaseProvider {
  253. readonly connection: ConnectionInfo;
  254. _pendingFilter: Promise<number>;
  255. _nextId: number;
  256. // During any given event loop, the results for a given call will
  257. // all be the same, so we can dedup the calls to save requests and
  258. // bandwidth. @TODO: Try out generalizing this against send?
  259. _eventLoopCache: Record<string, Promise<any>>;
  260. get _cache(): Record<string, Promise<any>> {
  261. if (this._eventLoopCache == null) {
  262. this._eventLoopCache = { };
  263. }
  264. return this._eventLoopCache;
  265. }
  266. constructor(url?: ConnectionInfo | string, network?: Networkish) {
  267. logger.checkNew(new.target, JsonRpcProvider);
  268. let networkOrReady: Networkish | Promise<Network> = network;
  269. // The network is unknown, query the JSON-RPC for it
  270. if (networkOrReady == null) {
  271. networkOrReady = new Promise((resolve, reject) => {
  272. setTimeout(() => {
  273. this.detectNetwork().then((network) => {
  274. resolve(network);
  275. }, (error) => {
  276. reject(error);
  277. });
  278. }, 0);
  279. });
  280. }
  281. super(networkOrReady);
  282. // Default URL
  283. if (!url) { url = getStatic<() => string>(this.constructor, "defaultUrl")(); }
  284. if (typeof(url) === "string") {
  285. defineReadOnly(this, "connection",Object.freeze({
  286. url: url
  287. }));
  288. } else {
  289. defineReadOnly(this, "connection", Object.freeze(shallowCopy(url)));
  290. }
  291. this._nextId = 42;
  292. }
  293. static defaultUrl(): string {
  294. return "http:/\/localhost:8545";
  295. }
  296. detectNetwork(): Promise<Network> {
  297. if (!this._cache["detectNetwork"]) {
  298. this._cache["detectNetwork"] = this._uncachedDetectNetwork();
  299. // Clear this cache at the beginning of the next event loop
  300. setTimeout(() => {
  301. this._cache["detectNetwork"] = null;
  302. }, 0);
  303. }
  304. return this._cache["detectNetwork"];
  305. }
  306. async _uncachedDetectNetwork(): Promise<Network> {
  307. await timer(0);
  308. let chainId = null;
  309. try {
  310. chainId = await this.send("eth_chainId", [ ]);
  311. } catch (error) {
  312. try {
  313. chainId = await this.send("net_version", [ ]);
  314. } catch (error) { }
  315. }
  316. if (chainId != null) {
  317. const getNetwork = getStatic<(network: Networkish) => Network>(this.constructor, "getNetwork");
  318. try {
  319. return getNetwork(BigNumber.from(chainId).toNumber());
  320. } catch (error) {
  321. return logger.throwError("could not detect network", Logger.errors.NETWORK_ERROR, {
  322. chainId: chainId,
  323. event: "invalidNetwork",
  324. serverError: error
  325. });
  326. }
  327. }
  328. return logger.throwError("could not detect network", Logger.errors.NETWORK_ERROR, {
  329. event: "noNetwork"
  330. });
  331. }
  332. getSigner(addressOrIndex?: string | number): JsonRpcSigner {
  333. return new JsonRpcSigner(_constructorGuard, this, addressOrIndex);
  334. }
  335. getUncheckedSigner(addressOrIndex?: string | number): UncheckedJsonRpcSigner {
  336. return this.getSigner(addressOrIndex).connectUnchecked();
  337. }
  338. listAccounts(): Promise<Array<string>> {
  339. return this.send("eth_accounts", []).then((accounts: Array<string>) => {
  340. return accounts.map((a) => this.formatter.address(a));
  341. });
  342. }
  343. send(method: string, params: Array<any>): Promise<any> {
  344. const request = {
  345. method: method,
  346. params: params,
  347. id: (this._nextId++),
  348. jsonrpc: "2.0"
  349. };
  350. this.emit("debug", {
  351. action: "request",
  352. request: deepCopy(request),
  353. provider: this
  354. });
  355. // We can expand this in the future to any call, but for now these
  356. // are the biggest wins and do not require any serializing parameters.
  357. const cache = ([ "eth_chainId", "eth_blockNumber" ].indexOf(method) >= 0);
  358. if (cache && this._cache[method]) {
  359. return this._cache[method];
  360. }
  361. const result = fetchJson(this.connection, JSON.stringify(request), getResult).then((result) => {
  362. this.emit("debug", {
  363. action: "response",
  364. request: request,
  365. response: result,
  366. provider: this
  367. });
  368. return result;
  369. }, (error) => {
  370. this.emit("debug", {
  371. action: "response",
  372. error: error,
  373. request: request,
  374. provider: this
  375. });
  376. throw error;
  377. });
  378. // Cache the fetch, but clear it on the next event loop
  379. if (cache) {
  380. this._cache[method] = result;
  381. setTimeout(() => {
  382. this._cache[method] = null;
  383. }, 0);
  384. }
  385. return result;
  386. }
  387. prepareRequest(method: string, params: any): [ string, Array<any> ] {
  388. switch (method) {
  389. case "getBlockNumber":
  390. return [ "eth_blockNumber", [] ];
  391. case "getGasPrice":
  392. return [ "eth_gasPrice", [] ];
  393. case "getBalance":
  394. return [ "eth_getBalance", [ getLowerCase(params.address), params.blockTag ] ];
  395. case "getTransactionCount":
  396. return [ "eth_getTransactionCount", [ getLowerCase(params.address), params.blockTag ] ];
  397. case "getCode":
  398. return [ "eth_getCode", [ getLowerCase(params.address), params.blockTag ] ];
  399. case "getStorageAt":
  400. return [ "eth_getStorageAt", [ getLowerCase(params.address), params.position, params.blockTag ] ];
  401. case "sendTransaction":
  402. return [ "eth_sendRawTransaction", [ params.signedTransaction ] ]
  403. case "getBlock":
  404. if (params.blockTag) {
  405. return [ "eth_getBlockByNumber", [ params.blockTag, !!params.includeTransactions ] ];
  406. } else if (params.blockHash) {
  407. return [ "eth_getBlockByHash", [ params.blockHash, !!params.includeTransactions ] ];
  408. }
  409. return null;
  410. case "getTransaction":
  411. return [ "eth_getTransactionByHash", [ params.transactionHash ] ];
  412. case "getTransactionReceipt":
  413. return [ "eth_getTransactionReceipt", [ params.transactionHash ] ];
  414. case "call": {
  415. const hexlifyTransaction = getStatic<(t: TransactionRequest, a?: { [key: string]: boolean }) => { [key: string]: string }>(this.constructor, "hexlifyTransaction");
  416. return [ "eth_call", [ hexlifyTransaction(params.transaction, { from: true }), params.blockTag ] ];
  417. }
  418. case "estimateGas": {
  419. const hexlifyTransaction = getStatic<(t: TransactionRequest, a?: { [key: string]: boolean }) => { [key: string]: string }>(this.constructor, "hexlifyTransaction");
  420. return [ "eth_estimateGas", [ hexlifyTransaction(params.transaction, { from: true }) ] ];
  421. }
  422. case "getLogs":
  423. if (params.filter && params.filter.address != null) {
  424. params.filter.address = getLowerCase(params.filter.address);
  425. }
  426. return [ "eth_getLogs", [ params.filter ] ];
  427. default:
  428. break;
  429. }
  430. return null;
  431. }
  432. async perform(method: string, params: any): Promise<any> {
  433. // Legacy networks do not like the type field being passed along (which
  434. // is fair), so we delete type if it is 0 and a non-EIP-1559 network
  435. if (method === "call" || method === "estimateGas") {
  436. const tx = params.transaction;
  437. if (tx && tx.type != null && BigNumber.from(tx.type).isZero()) {
  438. // If there are no EIP-1559 properties, it might be non-EIP-a559
  439. if (tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) {
  440. const feeData = await this.getFeeData();
  441. if (feeData.maxFeePerGas == null && feeData.maxPriorityFeePerGas == null) {
  442. // Network doesn't know about EIP-1559 (and hence type)
  443. params = shallowCopy(params);
  444. params.transaction = shallowCopy(tx);
  445. delete params.transaction.type;
  446. }
  447. }
  448. }
  449. }
  450. const args = this.prepareRequest(method, params);
  451. if (args == null) {
  452. logger.throwError(method + " not implemented", Logger.errors.NOT_IMPLEMENTED, { operation: method });
  453. }
  454. try {
  455. return await this.send(args[0], args[1])
  456. } catch (error) {
  457. return checkError(method, error, params);
  458. }
  459. }
  460. _startEvent(event: Event): void {
  461. if (event.tag === "pending") { this._startPending(); }
  462. super._startEvent(event);
  463. }
  464. _startPending(): void {
  465. if (this._pendingFilter != null) { return; }
  466. const self = this;
  467. const pendingFilter: Promise<number> = this.send("eth_newPendingTransactionFilter", []);
  468. this._pendingFilter = pendingFilter;
  469. pendingFilter.then(function(filterId) {
  470. function poll() {
  471. self.send("eth_getFilterChanges", [ filterId ]).then(function(hashes: Array<string>) {
  472. if (self._pendingFilter != pendingFilter) { return null; }
  473. let seq = Promise.resolve();
  474. hashes.forEach(function(hash) {
  475. // @TODO: This should be garbage collected at some point... How? When?
  476. self._emitted["t:" + hash.toLowerCase()] = "pending";
  477. seq = seq.then(function() {
  478. return self.getTransaction(hash).then(function(tx) {
  479. self.emit("pending", tx);
  480. return null;
  481. });
  482. });
  483. });
  484. return seq.then(function() {
  485. return timer(1000);
  486. });
  487. }).then(function() {
  488. if (self._pendingFilter != pendingFilter) {
  489. self.send("eth_uninstallFilter", [ filterId ]);
  490. return;
  491. }
  492. setTimeout(function() { poll(); }, 0);
  493. return null;
  494. }).catch((error: Error) => { });
  495. }
  496. poll();
  497. return filterId;
  498. }).catch((error: Error) => { });
  499. }
  500. _stopEvent(event: Event): void {
  501. if (event.tag === "pending" && this.listenerCount("pending") === 0) {
  502. this._pendingFilter = null;
  503. }
  504. super._stopEvent(event);
  505. }
  506. // Convert an ethers.js transaction into a JSON-RPC transaction
  507. // - gasLimit => gas
  508. // - All values hexlified
  509. // - All numeric values zero-striped
  510. // - All addresses are lowercased
  511. // NOTE: This allows a TransactionRequest, but all values should be resolved
  512. // before this is called
  513. // @TODO: This will likely be removed in future versions and prepareRequest
  514. // will be the preferred method for this.
  515. static hexlifyTransaction(transaction: TransactionRequest, allowExtra?: { [key: string]: boolean }): { [key: string]: string | AccessList } {
  516. // Check only allowed properties are given
  517. const allowed = shallowCopy(allowedTransactionKeys);
  518. if (allowExtra) {
  519. for (const key in allowExtra) {
  520. if (allowExtra[key]) { allowed[key] = true; }
  521. }
  522. }
  523. checkProperties(transaction, allowed);
  524. const result: { [key: string]: string | AccessList } = {};
  525. // JSON-RPC now requires numeric values to be "quantity" values
  526. ["chainId", "gasLimit", "gasPrice", "type", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "value"].forEach(function(key) {
  527. if ((<any>transaction)[key] == null) { return; }
  528. const value = hexValue((<any>transaction)[key]);
  529. if (key === "gasLimit") { key = "gas"; }
  530. result[key] = value;
  531. });
  532. ["from", "to", "data"].forEach(function(key) {
  533. if ((<any>transaction)[key] == null) { return; }
  534. result[key] = hexlify((<any>transaction)[key]);
  535. });
  536. if ((<any>transaction).accessList) {
  537. result["accessList"] = accessListify((<any>transaction).accessList);
  538. }
  539. return result;
  540. }
  541. }