etherscan-provider.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. "use strict";
  2. var __extends = (this && this.__extends) || (function () {
  3. var extendStatics = function (d, b) {
  4. extendStatics = Object.setPrototypeOf ||
  5. ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
  6. function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
  7. return extendStatics(d, b);
  8. };
  9. return function (d, b) {
  10. if (typeof b !== "function" && b !== null)
  11. throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
  12. extendStatics(d, b);
  13. function __() { this.constructor = d; }
  14. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  15. };
  16. })();
  17. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
  18. function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
  19. return new (P || (P = Promise))(function (resolve, reject) {
  20. function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
  21. function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
  22. function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
  23. step((generator = generator.apply(thisArg, _arguments || [])).next());
  24. });
  25. };
  26. var __generator = (this && this.__generator) || function (thisArg, body) {
  27. var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
  28. return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
  29. function verb(n) { return function (v) { return step([n, v]); }; }
  30. function step(op) {
  31. if (f) throw new TypeError("Generator is already executing.");
  32. while (_) try {
  33. if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
  34. if (y = 0, t) op = [op[0] & 2, t.value];
  35. switch (op[0]) {
  36. case 0: case 1: t = op; break;
  37. case 4: _.label++; return { value: op[1], done: false };
  38. case 5: _.label++; y = op[1]; op = [0]; continue;
  39. case 7: op = _.ops.pop(); _.trys.pop(); continue;
  40. default:
  41. if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
  42. if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
  43. if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
  44. if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
  45. if (t[2]) _.ops.pop();
  46. _.trys.pop(); continue;
  47. }
  48. op = body.call(thisArg, _);
  49. } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
  50. if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
  51. }
  52. };
  53. Object.defineProperty(exports, "__esModule", { value: true });
  54. exports.EtherscanProvider = void 0;
  55. var bytes_1 = require("@ethersproject/bytes");
  56. var properties_1 = require("@ethersproject/properties");
  57. var transactions_1 = require("@ethersproject/transactions");
  58. var web_1 = require("@ethersproject/web");
  59. var formatter_1 = require("./formatter");
  60. var logger_1 = require("@ethersproject/logger");
  61. var _version_1 = require("./_version");
  62. var logger = new logger_1.Logger(_version_1.version);
  63. var base_provider_1 = require("./base-provider");
  64. // The transaction has already been sanitized by the calls in Provider
  65. function getTransactionPostData(transaction) {
  66. var result = {};
  67. for (var key in transaction) {
  68. if (transaction[key] == null) {
  69. continue;
  70. }
  71. var value = transaction[key];
  72. if (key === "type" && value === 0) {
  73. continue;
  74. }
  75. // Quantity-types require no leading zero, unless 0
  76. if ({ type: true, gasLimit: true, gasPrice: true, maxFeePerGs: true, maxPriorityFeePerGas: true, nonce: true, value: true }[key]) {
  77. value = (0, bytes_1.hexValue)((0, bytes_1.hexlify)(value));
  78. }
  79. else if (key === "accessList") {
  80. value = "[" + (0, transactions_1.accessListify)(value).map(function (set) {
  81. return "{address:\"" + set.address + "\",storageKeys:[\"" + set.storageKeys.join('","') + "\"]}";
  82. }).join(",") + "]";
  83. }
  84. else {
  85. value = (0, bytes_1.hexlify)(value);
  86. }
  87. result[key] = value;
  88. }
  89. return result;
  90. }
  91. function getResult(result) {
  92. // getLogs, getHistory have weird success responses
  93. if (result.status == 0 && (result.message === "No records found" || result.message === "No transactions found")) {
  94. return result.result;
  95. }
  96. if (result.status != 1 || result.message != "OK") {
  97. var error = new Error("invalid response");
  98. error.result = JSON.stringify(result);
  99. if ((result.result || "").toLowerCase().indexOf("rate limit") >= 0) {
  100. error.throttleRetry = true;
  101. }
  102. throw error;
  103. }
  104. return result.result;
  105. }
  106. function getJsonResult(result) {
  107. // This response indicates we are being throttled
  108. if (result && result.status == 0 && result.message == "NOTOK" && (result.result || "").toLowerCase().indexOf("rate limit") >= 0) {
  109. var error = new Error("throttled response");
  110. error.result = JSON.stringify(result);
  111. error.throttleRetry = true;
  112. throw error;
  113. }
  114. if (result.jsonrpc != "2.0") {
  115. // @TODO: not any
  116. var error = new Error("invalid response");
  117. error.result = JSON.stringify(result);
  118. throw error;
  119. }
  120. if (result.error) {
  121. // @TODO: not any
  122. var error = new Error(result.error.message || "unknown error");
  123. if (result.error.code) {
  124. error.code = result.error.code;
  125. }
  126. if (result.error.data) {
  127. error.data = result.error.data;
  128. }
  129. throw error;
  130. }
  131. return result.result;
  132. }
  133. // The blockTag was normalized as a string by the Provider pre-perform operations
  134. function checkLogTag(blockTag) {
  135. if (blockTag === "pending") {
  136. throw new Error("pending not supported");
  137. }
  138. if (blockTag === "latest") {
  139. return blockTag;
  140. }
  141. return parseInt(blockTag.substring(2), 16);
  142. }
  143. var defaultApiKey = "9D13ZE7XSBTJ94N9BNJ2MA33VMAY2YPIRB";
  144. function checkError(method, error, transaction) {
  145. // Undo the "convenience" some nodes are attempting to prevent backwards
  146. // incompatibility; maybe for v6 consider forwarding reverts as errors
  147. if (method === "call" && error.code === logger_1.Logger.errors.SERVER_ERROR) {
  148. var e = error.error;
  149. // Etherscan keeps changing their string
  150. if (e && (e.message.match(/reverted/i) || e.message.match(/VM execution error/i))) {
  151. // Etherscan prefixes the data like "Reverted 0x1234"
  152. var data = e.data;
  153. if (data) {
  154. data = "0x" + data.replace(/^.*0x/i, "");
  155. }
  156. if ((0, bytes_1.isHexString)(data)) {
  157. return data;
  158. }
  159. logger.throwError("missing revert data in call exception", logger_1.Logger.errors.CALL_EXCEPTION, {
  160. error: error,
  161. data: "0x"
  162. });
  163. }
  164. }
  165. // Get the message from any nested error structure
  166. var message = error.message;
  167. if (error.code === logger_1.Logger.errors.SERVER_ERROR) {
  168. if (error.error && typeof (error.error.message) === "string") {
  169. message = error.error.message;
  170. }
  171. else if (typeof (error.body) === "string") {
  172. message = error.body;
  173. }
  174. else if (typeof (error.responseText) === "string") {
  175. message = error.responseText;
  176. }
  177. }
  178. message = (message || "").toLowerCase();
  179. // "Insufficient funds. The account you tried to send transaction from does not have enough funds. Required 21464000000000 and got: 0"
  180. if (message.match(/insufficient funds/)) {
  181. logger.throwError("insufficient funds for intrinsic transaction cost", logger_1.Logger.errors.INSUFFICIENT_FUNDS, {
  182. error: error,
  183. method: method,
  184. transaction: transaction
  185. });
  186. }
  187. // "Transaction with the same hash was already imported."
  188. if (message.match(/same hash was already imported|transaction nonce is too low|nonce too low/)) {
  189. logger.throwError("nonce has already been used", logger_1.Logger.errors.NONCE_EXPIRED, {
  190. error: error,
  191. method: method,
  192. transaction: transaction
  193. });
  194. }
  195. // "Transaction gas price is too low. There is another transaction with same nonce in the queue. Try increasing the gas price or incrementing the nonce."
  196. if (message.match(/another transaction with same nonce/)) {
  197. logger.throwError("replacement fee too low", logger_1.Logger.errors.REPLACEMENT_UNDERPRICED, {
  198. error: error,
  199. method: method,
  200. transaction: transaction
  201. });
  202. }
  203. if (message.match(/execution failed due to an exception|execution reverted/)) {
  204. logger.throwError("cannot estimate gas; transaction may fail or may require manual gas limit", logger_1.Logger.errors.UNPREDICTABLE_GAS_LIMIT, {
  205. error: error,
  206. method: method,
  207. transaction: transaction
  208. });
  209. }
  210. throw error;
  211. }
  212. var EtherscanProvider = /** @class */ (function (_super) {
  213. __extends(EtherscanProvider, _super);
  214. function EtherscanProvider(network, apiKey) {
  215. var _newTarget = this.constructor;
  216. var _this = this;
  217. logger.checkNew(_newTarget, EtherscanProvider);
  218. _this = _super.call(this, network) || this;
  219. (0, properties_1.defineReadOnly)(_this, "baseUrl", _this.getBaseUrl());
  220. (0, properties_1.defineReadOnly)(_this, "apiKey", apiKey || defaultApiKey);
  221. return _this;
  222. }
  223. EtherscanProvider.prototype.getBaseUrl = function () {
  224. switch (this.network ? this.network.name : "invalid") {
  225. case "homestead":
  226. return "https:/\/api.etherscan.io";
  227. case "ropsten":
  228. return "https:/\/api-ropsten.etherscan.io";
  229. case "rinkeby":
  230. return "https:/\/api-rinkeby.etherscan.io";
  231. case "kovan":
  232. return "https:/\/api-kovan.etherscan.io";
  233. case "goerli":
  234. return "https:/\/api-goerli.etherscan.io";
  235. default:
  236. }
  237. return logger.throwArgumentError("unsupported network", "network", name);
  238. };
  239. EtherscanProvider.prototype.getUrl = function (module, params) {
  240. var query = Object.keys(params).reduce(function (accum, key) {
  241. var value = params[key];
  242. if (value != null) {
  243. accum += "&" + key + "=" + value;
  244. }
  245. return accum;
  246. }, "");
  247. var apiKey = ((this.apiKey) ? "&apikey=" + this.apiKey : "");
  248. return this.baseUrl + "/api?module=" + module + query + apiKey;
  249. };
  250. EtherscanProvider.prototype.getPostUrl = function () {
  251. return this.baseUrl + "/api";
  252. };
  253. EtherscanProvider.prototype.getPostData = function (module, params) {
  254. params.module = module;
  255. params.apikey = this.apiKey;
  256. return params;
  257. };
  258. EtherscanProvider.prototype.fetch = function (module, params, post) {
  259. return __awaiter(this, void 0, void 0, function () {
  260. var url, payload, procFunc, connection, payloadStr, result;
  261. var _this = this;
  262. return __generator(this, function (_a) {
  263. switch (_a.label) {
  264. case 0:
  265. url = (post ? this.getPostUrl() : this.getUrl(module, params));
  266. payload = (post ? this.getPostData(module, params) : null);
  267. procFunc = (module === "proxy") ? getJsonResult : getResult;
  268. this.emit("debug", {
  269. action: "request",
  270. request: url,
  271. provider: this
  272. });
  273. connection = {
  274. url: url,
  275. throttleSlotInterval: 1000,
  276. throttleCallback: function (attempt, url) {
  277. if (_this.isCommunityResource()) {
  278. (0, formatter_1.showThrottleMessage)();
  279. }
  280. return Promise.resolve(true);
  281. }
  282. };
  283. payloadStr = null;
  284. if (payload) {
  285. connection.headers = { "content-type": "application/x-www-form-urlencoded; charset=UTF-8" };
  286. payloadStr = Object.keys(payload).map(function (key) {
  287. return key + "=" + payload[key];
  288. }).join("&");
  289. }
  290. return [4 /*yield*/, (0, web_1.fetchJson)(connection, payloadStr, procFunc || getJsonResult)];
  291. case 1:
  292. result = _a.sent();
  293. this.emit("debug", {
  294. action: "response",
  295. request: url,
  296. response: (0, properties_1.deepCopy)(result),
  297. provider: this
  298. });
  299. return [2 /*return*/, result];
  300. }
  301. });
  302. });
  303. };
  304. EtherscanProvider.prototype.detectNetwork = function () {
  305. return __awaiter(this, void 0, void 0, function () {
  306. return __generator(this, function (_a) {
  307. return [2 /*return*/, this.network];
  308. });
  309. });
  310. };
  311. EtherscanProvider.prototype.perform = function (method, params) {
  312. return __awaiter(this, void 0, void 0, function () {
  313. var _a, postData, error_1, postData, error_2, args, topic0, logs, blocks, i, log, block, _b;
  314. return __generator(this, function (_c) {
  315. switch (_c.label) {
  316. case 0:
  317. _a = method;
  318. switch (_a) {
  319. case "getBlockNumber": return [3 /*break*/, 1];
  320. case "getGasPrice": return [3 /*break*/, 2];
  321. case "getBalance": return [3 /*break*/, 3];
  322. case "getTransactionCount": return [3 /*break*/, 4];
  323. case "getCode": return [3 /*break*/, 5];
  324. case "getStorageAt": return [3 /*break*/, 6];
  325. case "sendTransaction": return [3 /*break*/, 7];
  326. case "getBlock": return [3 /*break*/, 8];
  327. case "getTransaction": return [3 /*break*/, 9];
  328. case "getTransactionReceipt": return [3 /*break*/, 10];
  329. case "call": return [3 /*break*/, 11];
  330. case "estimateGas": return [3 /*break*/, 15];
  331. case "getLogs": return [3 /*break*/, 19];
  332. case "getEtherPrice": return [3 /*break*/, 26];
  333. }
  334. return [3 /*break*/, 28];
  335. case 1: return [2 /*return*/, this.fetch("proxy", { action: "eth_blockNumber" })];
  336. case 2: return [2 /*return*/, this.fetch("proxy", { action: "eth_gasPrice" })];
  337. case 3:
  338. // Returns base-10 result
  339. return [2 /*return*/, this.fetch("account", {
  340. action: "balance",
  341. address: params.address,
  342. tag: params.blockTag
  343. })];
  344. case 4: return [2 /*return*/, this.fetch("proxy", {
  345. action: "eth_getTransactionCount",
  346. address: params.address,
  347. tag: params.blockTag
  348. })];
  349. case 5: return [2 /*return*/, this.fetch("proxy", {
  350. action: "eth_getCode",
  351. address: params.address,
  352. tag: params.blockTag
  353. })];
  354. case 6: return [2 /*return*/, this.fetch("proxy", {
  355. action: "eth_getStorageAt",
  356. address: params.address,
  357. position: params.position,
  358. tag: params.blockTag
  359. })];
  360. case 7: return [2 /*return*/, this.fetch("proxy", {
  361. action: "eth_sendRawTransaction",
  362. hex: params.signedTransaction
  363. }, true).catch(function (error) {
  364. return checkError("sendTransaction", error, params.signedTransaction);
  365. })];
  366. case 8:
  367. if (params.blockTag) {
  368. return [2 /*return*/, this.fetch("proxy", {
  369. action: "eth_getBlockByNumber",
  370. tag: params.blockTag,
  371. boolean: (params.includeTransactions ? "true" : "false")
  372. })];
  373. }
  374. throw new Error("getBlock by blockHash not implemented");
  375. case 9: return [2 /*return*/, this.fetch("proxy", {
  376. action: "eth_getTransactionByHash",
  377. txhash: params.transactionHash
  378. })];
  379. case 10: return [2 /*return*/, this.fetch("proxy", {
  380. action: "eth_getTransactionReceipt",
  381. txhash: params.transactionHash
  382. })];
  383. case 11:
  384. if (params.blockTag !== "latest") {
  385. throw new Error("EtherscanProvider does not support blockTag for call");
  386. }
  387. postData = getTransactionPostData(params.transaction);
  388. postData.module = "proxy";
  389. postData.action = "eth_call";
  390. _c.label = 12;
  391. case 12:
  392. _c.trys.push([12, 14, , 15]);
  393. return [4 /*yield*/, this.fetch("proxy", postData, true)];
  394. case 13: return [2 /*return*/, _c.sent()];
  395. case 14:
  396. error_1 = _c.sent();
  397. return [2 /*return*/, checkError("call", error_1, params.transaction)];
  398. case 15:
  399. postData = getTransactionPostData(params.transaction);
  400. postData.module = "proxy";
  401. postData.action = "eth_estimateGas";
  402. _c.label = 16;
  403. case 16:
  404. _c.trys.push([16, 18, , 19]);
  405. return [4 /*yield*/, this.fetch("proxy", postData, true)];
  406. case 17: return [2 /*return*/, _c.sent()];
  407. case 18:
  408. error_2 = _c.sent();
  409. return [2 /*return*/, checkError("estimateGas", error_2, params.transaction)];
  410. case 19:
  411. args = { action: "getLogs" };
  412. if (params.filter.fromBlock) {
  413. args.fromBlock = checkLogTag(params.filter.fromBlock);
  414. }
  415. if (params.filter.toBlock) {
  416. args.toBlock = checkLogTag(params.filter.toBlock);
  417. }
  418. if (params.filter.address) {
  419. args.address = params.filter.address;
  420. }
  421. // @TODO: We can handle slightly more complicated logs using the logs API
  422. if (params.filter.topics && params.filter.topics.length > 0) {
  423. if (params.filter.topics.length > 1) {
  424. logger.throwError("unsupported topic count", logger_1.Logger.errors.UNSUPPORTED_OPERATION, { topics: params.filter.topics });
  425. }
  426. if (params.filter.topics.length === 1) {
  427. topic0 = params.filter.topics[0];
  428. if (typeof (topic0) !== "string" || topic0.length !== 66) {
  429. logger.throwError("unsupported topic format", logger_1.Logger.errors.UNSUPPORTED_OPERATION, { topic0: topic0 });
  430. }
  431. args.topic0 = topic0;
  432. }
  433. }
  434. return [4 /*yield*/, this.fetch("logs", args)];
  435. case 20:
  436. logs = _c.sent();
  437. blocks = {};
  438. i = 0;
  439. _c.label = 21;
  440. case 21:
  441. if (!(i < logs.length)) return [3 /*break*/, 25];
  442. log = logs[i];
  443. if (log.blockHash != null) {
  444. return [3 /*break*/, 24];
  445. }
  446. if (!(blocks[log.blockNumber] == null)) return [3 /*break*/, 23];
  447. return [4 /*yield*/, this.getBlock(log.blockNumber)];
  448. case 22:
  449. block = _c.sent();
  450. if (block) {
  451. blocks[log.blockNumber] = block.hash;
  452. }
  453. _c.label = 23;
  454. case 23:
  455. log.blockHash = blocks[log.blockNumber];
  456. _c.label = 24;
  457. case 24:
  458. i++;
  459. return [3 /*break*/, 21];
  460. case 25: return [2 /*return*/, logs];
  461. case 26:
  462. if (this.network.name !== "homestead") {
  463. return [2 /*return*/, 0.0];
  464. }
  465. _b = parseFloat;
  466. return [4 /*yield*/, this.fetch("stats", { action: "ethprice" })];
  467. case 27: return [2 /*return*/, _b.apply(void 0, [(_c.sent()).ethusd])];
  468. case 28: return [3 /*break*/, 29];
  469. case 29: return [2 /*return*/, _super.prototype.perform.call(this, method, params)];
  470. }
  471. });
  472. });
  473. };
  474. // Note: The `page` page parameter only allows pagination within the
  475. // 10,000 window available without a page and offset parameter
  476. // Error: Result window is too large, PageNo x Offset size must
  477. // be less than or equal to 10000
  478. EtherscanProvider.prototype.getHistory = function (addressOrName, startBlock, endBlock) {
  479. return __awaiter(this, void 0, void 0, function () {
  480. var params, result;
  481. var _a;
  482. var _this = this;
  483. return __generator(this, function (_b) {
  484. switch (_b.label) {
  485. case 0:
  486. _a = {
  487. action: "txlist"
  488. };
  489. return [4 /*yield*/, this.resolveName(addressOrName)];
  490. case 1:
  491. params = (_a.address = (_b.sent()),
  492. _a.startblock = ((startBlock == null) ? 0 : startBlock),
  493. _a.endblock = ((endBlock == null) ? 99999999 : endBlock),
  494. _a.sort = "asc",
  495. _a);
  496. return [4 /*yield*/, this.fetch("account", params)];
  497. case 2:
  498. result = _b.sent();
  499. return [2 /*return*/, result.map(function (tx) {
  500. ["contractAddress", "to"].forEach(function (key) {
  501. if (tx[key] == "") {
  502. delete tx[key];
  503. }
  504. });
  505. if (tx.creates == null && tx.contractAddress != null) {
  506. tx.creates = tx.contractAddress;
  507. }
  508. var item = _this.formatter.transactionResponse(tx);
  509. if (tx.timeStamp) {
  510. item.timestamp = parseInt(tx.timeStamp);
  511. }
  512. return item;
  513. })];
  514. }
  515. });
  516. });
  517. };
  518. EtherscanProvider.prototype.isCommunityResource = function () {
  519. return (this.apiKey === defaultApiKey);
  520. };
  521. return EtherscanProvider;
  522. }(base_provider_1.BaseProvider));
  523. exports.EtherscanProvider = EtherscanProvider;
  524. //# sourceMappingURL=etherscan-provider.js.map