provider-jsonrpc.js 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954
  1. /**
  2. * One of the most common ways to interact with the blockchain is
  3. * by a node running a JSON-RPC interface which can be connected to,
  4. * based on the transport, using:
  5. *
  6. * - HTTP or HTTPS - [[JsonRpcProvider]]
  7. * - WebSocket - [[WebSocketProvider]]
  8. * - IPC - [[IpcSocketProvider]]
  9. *
  10. * @_section: api/providers/jsonrpc:JSON-RPC Provider [about-jsonrpcProvider]
  11. */
  12. // @TODO:
  13. // - Add the batching API
  14. // https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/eth1.0-apis/assembled-spec/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=true&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false
  15. import { AbiCoder } from "../abi/index.js";
  16. import { getAddress, resolveAddress } from "../address/index.js";
  17. import { TypedDataEncoder } from "../hash/index.js";
  18. import { accessListify } from "../transaction/index.js";
  19. import { defineProperties, getBigInt, hexlify, isHexString, toQuantity, toUtf8Bytes, isError, makeError, assert, assertArgument, FetchRequest, resolveProperties } from "../utils/index.js";
  20. import { AbstractProvider, UnmanagedSubscriber } from "./abstract-provider.js";
  21. import { AbstractSigner } from "./abstract-signer.js";
  22. import { Network } from "./network.js";
  23. import { FilterIdEventSubscriber, FilterIdPendingSubscriber } from "./subscriber-filterid.js";
  24. import { PollingEventSubscriber } from "./subscriber-polling.js";
  25. const Primitive = "bigint,boolean,function,number,string,symbol".split(/,/g);
  26. //const Methods = "getAddress,then".split(/,/g);
  27. function deepCopy(value) {
  28. if (value == null || Primitive.indexOf(typeof (value)) >= 0) {
  29. return value;
  30. }
  31. // Keep any Addressable
  32. if (typeof (value.getAddress) === "function") {
  33. return value;
  34. }
  35. if (Array.isArray(value)) {
  36. return (value.map(deepCopy));
  37. }
  38. if (typeof (value) === "object") {
  39. return Object.keys(value).reduce((accum, key) => {
  40. accum[key] = value[key];
  41. return accum;
  42. }, {});
  43. }
  44. throw new Error(`should not happen: ${value} (${typeof (value)})`);
  45. }
  46. function stall(duration) {
  47. return new Promise((resolve) => { setTimeout(resolve, duration); });
  48. }
  49. function getLowerCase(value) {
  50. if (value) {
  51. return value.toLowerCase();
  52. }
  53. return value;
  54. }
  55. function isPollable(value) {
  56. return (value && typeof (value.pollingInterval) === "number");
  57. }
  58. const defaultOptions = {
  59. polling: false,
  60. staticNetwork: null,
  61. batchStallTime: 10,
  62. batchMaxSize: (1 << 20),
  63. batchMaxCount: 100,
  64. cacheTimeout: 250,
  65. pollingInterval: 4000
  66. };
  67. // @TODO: Unchecked Signers
  68. export class JsonRpcSigner extends AbstractSigner {
  69. address;
  70. constructor(provider, address) {
  71. super(provider);
  72. address = getAddress(address);
  73. defineProperties(this, { address });
  74. }
  75. connect(provider) {
  76. assert(false, "cannot reconnect JsonRpcSigner", "UNSUPPORTED_OPERATION", {
  77. operation: "signer.connect"
  78. });
  79. }
  80. async getAddress() {
  81. return this.address;
  82. }
  83. // JSON-RPC will automatially fill in nonce, etc. so we just check from
  84. async populateTransaction(tx) {
  85. return await this.populateCall(tx);
  86. }
  87. // Returns just the hash of the transaction after sent, which is what
  88. // the bare JSON-RPC API does;
  89. async sendUncheckedTransaction(_tx) {
  90. const tx = deepCopy(_tx);
  91. const promises = [];
  92. // Make sure the from matches the sender
  93. if (tx.from) {
  94. const _from = tx.from;
  95. promises.push((async () => {
  96. const from = await resolveAddress(_from, this.provider);
  97. assertArgument(from != null && from.toLowerCase() === this.address.toLowerCase(), "from address mismatch", "transaction", _tx);
  98. tx.from = from;
  99. })());
  100. }
  101. else {
  102. tx.from = this.address;
  103. }
  104. // The JSON-RPC for eth_sendTransaction uses 90000 gas; if the user
  105. // wishes to use this, it is easy to specify explicitly, otherwise
  106. // we look it up for them.
  107. if (tx.gasLimit == null) {
  108. promises.push((async () => {
  109. tx.gasLimit = await this.provider.estimateGas({ ...tx, from: this.address });
  110. })());
  111. }
  112. // The address may be an ENS name or Addressable
  113. if (tx.to != null) {
  114. const _to = tx.to;
  115. promises.push((async () => {
  116. tx.to = await resolveAddress(_to, this.provider);
  117. })());
  118. }
  119. // Wait until all of our properties are filled in
  120. if (promises.length) {
  121. await Promise.all(promises);
  122. }
  123. const hexTx = this.provider.getRpcTransaction(tx);
  124. return this.provider.send("eth_sendTransaction", [hexTx]);
  125. }
  126. async sendTransaction(tx) {
  127. // This cannot be mined any earlier than any recent block
  128. const blockNumber = await this.provider.getBlockNumber();
  129. // Send the transaction
  130. const hash = await this.sendUncheckedTransaction(tx);
  131. // Unfortunately, JSON-RPC only provides and opaque transaction hash
  132. // for a response, and we need the actual transaction, so we poll
  133. // for it; it should show up very quickly
  134. return await (new Promise((resolve, reject) => {
  135. const timeouts = [1000, 100];
  136. let invalids = 0;
  137. const checkTx = async () => {
  138. try {
  139. // Try getting the transaction
  140. const tx = await this.provider.getTransaction(hash);
  141. if (tx != null) {
  142. resolve(tx.replaceableTransaction(blockNumber));
  143. return;
  144. }
  145. }
  146. catch (error) {
  147. // If we were cancelled: stop polling.
  148. // If the data is bad: the node returns bad transactions
  149. // If the network changed: calling again will also fail
  150. // If unsupported: likely destroyed
  151. if (isError(error, "CANCELLED") || isError(error, "BAD_DATA") ||
  152. isError(error, "NETWORK_ERROR") || isError(error, "UNSUPPORTED_OPERATION")) {
  153. if (error.info == null) {
  154. error.info = {};
  155. }
  156. error.info.sendTransactionHash = hash;
  157. reject(error);
  158. return;
  159. }
  160. // Stop-gap for misbehaving backends; see #4513
  161. if (isError(error, "INVALID_ARGUMENT")) {
  162. invalids++;
  163. if (error.info == null) {
  164. error.info = {};
  165. }
  166. error.info.sendTransactionHash = hash;
  167. if (invalids > 10) {
  168. reject(error);
  169. return;
  170. }
  171. }
  172. // Notify anyone that cares; but we will try again, since
  173. // it is likely an intermittent service error
  174. this.provider.emit("error", makeError("failed to fetch transation after sending (will try again)", "UNKNOWN_ERROR", { error }));
  175. }
  176. // Wait another 4 seconds
  177. this.provider._setTimeout(() => { checkTx(); }, timeouts.pop() || 4000);
  178. };
  179. checkTx();
  180. }));
  181. }
  182. async signTransaction(_tx) {
  183. const tx = deepCopy(_tx);
  184. // Make sure the from matches the sender
  185. if (tx.from) {
  186. const from = await resolveAddress(tx.from, this.provider);
  187. assertArgument(from != null && from.toLowerCase() === this.address.toLowerCase(), "from address mismatch", "transaction", _tx);
  188. tx.from = from;
  189. }
  190. else {
  191. tx.from = this.address;
  192. }
  193. const hexTx = this.provider.getRpcTransaction(tx);
  194. return await this.provider.send("eth_signTransaction", [hexTx]);
  195. }
  196. async signMessage(_message) {
  197. const message = ((typeof (_message) === "string") ? toUtf8Bytes(_message) : _message);
  198. return await this.provider.send("personal_sign", [
  199. hexlify(message), this.address.toLowerCase()
  200. ]);
  201. }
  202. async signTypedData(domain, types, _value) {
  203. const value = deepCopy(_value);
  204. // Populate any ENS names (in-place)
  205. const populated = await TypedDataEncoder.resolveNames(domain, types, value, async (value) => {
  206. const address = await resolveAddress(value);
  207. assertArgument(address != null, "TypedData does not support null address", "value", value);
  208. return address;
  209. });
  210. return await this.provider.send("eth_signTypedData_v4", [
  211. this.address.toLowerCase(),
  212. JSON.stringify(TypedDataEncoder.getPayload(populated.domain, types, populated.value))
  213. ]);
  214. }
  215. async unlock(password) {
  216. return this.provider.send("personal_unlockAccount", [
  217. this.address.toLowerCase(), password, null
  218. ]);
  219. }
  220. // https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_sign
  221. async _legacySignMessage(_message) {
  222. const message = ((typeof (_message) === "string") ? toUtf8Bytes(_message) : _message);
  223. return await this.provider.send("eth_sign", [
  224. this.address.toLowerCase(), hexlify(message)
  225. ]);
  226. }
  227. }
  228. /**
  229. * The JsonRpcApiProvider is an abstract class and **MUST** be
  230. * sub-classed.
  231. *
  232. * It provides the base for all JSON-RPC-based Provider interaction.
  233. *
  234. * Sub-classing Notes:
  235. * - a sub-class MUST override _send
  236. * - a sub-class MUST call the `_start()` method once connected
  237. */
  238. export class JsonRpcApiProvider extends AbstractProvider {
  239. #options;
  240. // The next ID to use for the JSON-RPC ID field
  241. #nextId;
  242. // Payloads are queued and triggered in batches using the drainTimer
  243. #payloads;
  244. #drainTimer;
  245. #notReady;
  246. #network;
  247. #pendingDetectNetwork;
  248. #scheduleDrain() {
  249. if (this.#drainTimer) {
  250. return;
  251. }
  252. // If we aren't using batching, no harm in sending it immediately
  253. const stallTime = (this._getOption("batchMaxCount") === 1) ? 0 : this._getOption("batchStallTime");
  254. this.#drainTimer = setTimeout(() => {
  255. this.#drainTimer = null;
  256. const payloads = this.#payloads;
  257. this.#payloads = [];
  258. while (payloads.length) {
  259. // Create payload batches that satisfy our batch constraints
  260. const batch = [(payloads.shift())];
  261. while (payloads.length) {
  262. if (batch.length === this.#options.batchMaxCount) {
  263. break;
  264. }
  265. batch.push((payloads.shift()));
  266. const bytes = JSON.stringify(batch.map((p) => p.payload));
  267. if (bytes.length > this.#options.batchMaxSize) {
  268. payloads.unshift((batch.pop()));
  269. break;
  270. }
  271. }
  272. // Process the result to each payload
  273. (async () => {
  274. const payload = ((batch.length === 1) ? batch[0].payload : batch.map((p) => p.payload));
  275. this.emit("debug", { action: "sendRpcPayload", payload });
  276. try {
  277. const result = await this._send(payload);
  278. this.emit("debug", { action: "receiveRpcResult", result });
  279. // Process results in batch order
  280. for (const { resolve, reject, payload } of batch) {
  281. if (this.destroyed) {
  282. reject(makeError("provider destroyed; cancelled request", "UNSUPPORTED_OPERATION", { operation: payload.method }));
  283. continue;
  284. }
  285. // Find the matching result
  286. const resp = result.filter((r) => (r.id === payload.id))[0];
  287. // No result; the node failed us in unexpected ways
  288. if (resp == null) {
  289. const error = makeError("missing response for request", "BAD_DATA", {
  290. value: result, info: { payload }
  291. });
  292. this.emit("error", error);
  293. reject(error);
  294. continue;
  295. }
  296. // The response is an error
  297. if ("error" in resp) {
  298. reject(this.getRpcError(payload, resp));
  299. continue;
  300. }
  301. // All good; send the result
  302. resolve(resp.result);
  303. }
  304. }
  305. catch (error) {
  306. this.emit("debug", { action: "receiveRpcError", error });
  307. for (const { reject } of batch) {
  308. // @TODO: augment the error with the payload
  309. reject(error);
  310. }
  311. }
  312. })();
  313. }
  314. }, stallTime);
  315. }
  316. constructor(network, options) {
  317. super(network, options);
  318. this.#nextId = 1;
  319. this.#options = Object.assign({}, defaultOptions, options || {});
  320. this.#payloads = [];
  321. this.#drainTimer = null;
  322. this.#network = null;
  323. this.#pendingDetectNetwork = null;
  324. {
  325. let resolve = null;
  326. const promise = new Promise((_resolve) => {
  327. resolve = _resolve;
  328. });
  329. this.#notReady = { promise, resolve };
  330. }
  331. const staticNetwork = this._getOption("staticNetwork");
  332. if (typeof (staticNetwork) === "boolean") {
  333. assertArgument(!staticNetwork || network !== "any", "staticNetwork cannot be used on special network 'any'", "options", options);
  334. if (staticNetwork && network != null) {
  335. this.#network = Network.from(network);
  336. }
  337. }
  338. else if (staticNetwork) {
  339. // Make sure any static network is compatbile with the provided netwrok
  340. assertArgument(network == null || staticNetwork.matches(network), "staticNetwork MUST match network object", "options", options);
  341. this.#network = staticNetwork;
  342. }
  343. }
  344. /**
  345. * Returns the value associated with the option %%key%%.
  346. *
  347. * Sub-classes can use this to inquire about configuration options.
  348. */
  349. _getOption(key) {
  350. return this.#options[key];
  351. }
  352. /**
  353. * Gets the [[Network]] this provider has committed to. On each call, the network
  354. * is detected, and if it has changed, the call will reject.
  355. */
  356. get _network() {
  357. assert(this.#network, "network is not available yet", "NETWORK_ERROR");
  358. return this.#network;
  359. }
  360. /**
  361. * Resolves to the non-normalized value by performing %%req%%.
  362. *
  363. * Sub-classes may override this to modify behavior of actions,
  364. * and should generally call ``super._perform`` as a fallback.
  365. */
  366. async _perform(req) {
  367. // Legacy networks do not like the type field being passed along (which
  368. // is fair), so we delete type if it is 0 and a non-EIP-1559 network
  369. if (req.method === "call" || req.method === "estimateGas") {
  370. let tx = req.transaction;
  371. if (tx && tx.type != null && getBigInt(tx.type)) {
  372. // If there are no EIP-1559 or newer properties, it might be pre-EIP-1559
  373. if (tx.maxFeePerGas == null && tx.maxPriorityFeePerGas == null) {
  374. const feeData = await this.getFeeData();
  375. if (feeData.maxFeePerGas == null && feeData.maxPriorityFeePerGas == null) {
  376. // Network doesn't know about EIP-1559 (and hence type)
  377. req = Object.assign({}, req, {
  378. transaction: Object.assign({}, tx, { type: undefined })
  379. });
  380. }
  381. }
  382. }
  383. }
  384. const request = this.getRpcRequest(req);
  385. if (request != null) {
  386. return await this.send(request.method, request.args);
  387. }
  388. return super._perform(req);
  389. }
  390. /**
  391. * Sub-classes may override this; it detects the *actual* network that
  392. * we are **currently** connected to.
  393. *
  394. * Keep in mind that [[send]] may only be used once [[ready]], otherwise the
  395. * _send primitive must be used instead.
  396. */
  397. async _detectNetwork() {
  398. const network = this._getOption("staticNetwork");
  399. if (network) {
  400. if (network === true) {
  401. if (this.#network) {
  402. return this.#network;
  403. }
  404. }
  405. else {
  406. return network;
  407. }
  408. }
  409. if (this.#pendingDetectNetwork) {
  410. return await this.#pendingDetectNetwork;
  411. }
  412. // If we are ready, use ``send``, which enabled requests to be batched
  413. if (this.ready) {
  414. this.#pendingDetectNetwork = (async () => {
  415. try {
  416. const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
  417. this.#pendingDetectNetwork = null;
  418. return result;
  419. }
  420. catch (error) {
  421. this.#pendingDetectNetwork = null;
  422. throw error;
  423. }
  424. })();
  425. return await this.#pendingDetectNetwork;
  426. }
  427. // We are not ready yet; use the primitive _send
  428. this.#pendingDetectNetwork = (async () => {
  429. const payload = {
  430. id: this.#nextId++, method: "eth_chainId", params: [], jsonrpc: "2.0"
  431. };
  432. this.emit("debug", { action: "sendRpcPayload", payload });
  433. let result;
  434. try {
  435. result = (await this._send(payload))[0];
  436. this.#pendingDetectNetwork = null;
  437. }
  438. catch (error) {
  439. this.#pendingDetectNetwork = null;
  440. this.emit("debug", { action: "receiveRpcError", error });
  441. throw error;
  442. }
  443. this.emit("debug", { action: "receiveRpcResult", result });
  444. if ("result" in result) {
  445. return Network.from(getBigInt(result.result));
  446. }
  447. throw this.getRpcError(payload, result);
  448. })();
  449. return await this.#pendingDetectNetwork;
  450. }
  451. /**
  452. * Sub-classes **MUST** call this. Until [[_start]] has been called, no calls
  453. * will be passed to [[_send]] from [[send]]. If it is overridden, then
  454. * ``super._start()`` **MUST** be called.
  455. *
  456. * Calling it multiple times is safe and has no effect.
  457. */
  458. _start() {
  459. if (this.#notReady == null || this.#notReady.resolve == null) {
  460. return;
  461. }
  462. this.#notReady.resolve();
  463. this.#notReady = null;
  464. (async () => {
  465. // Bootstrap the network
  466. while (this.#network == null && !this.destroyed) {
  467. try {
  468. this.#network = await this._detectNetwork();
  469. }
  470. catch (error) {
  471. if (this.destroyed) {
  472. break;
  473. }
  474. console.log("JsonRpcProvider failed to detect network and cannot start up; retry in 1s (perhaps the URL is wrong or the node is not started)");
  475. this.emit("error", makeError("failed to bootstrap network detection", "NETWORK_ERROR", { event: "initial-network-discovery", info: { error } }));
  476. await stall(1000);
  477. }
  478. }
  479. // Start dispatching requests
  480. this.#scheduleDrain();
  481. })();
  482. }
  483. /**
  484. * Resolves once the [[_start]] has been called. This can be used in
  485. * sub-classes to defer sending data until the connection has been
  486. * established.
  487. */
  488. async _waitUntilReady() {
  489. if (this.#notReady == null) {
  490. return;
  491. }
  492. return await this.#notReady.promise;
  493. }
  494. /**
  495. * Return a Subscriber that will manage the %%sub%%.
  496. *
  497. * Sub-classes may override this to modify the behavior of
  498. * subscription management.
  499. */
  500. _getSubscriber(sub) {
  501. // Pending Filters aren't availble via polling
  502. if (sub.type === "pending") {
  503. return new FilterIdPendingSubscriber(this);
  504. }
  505. if (sub.type === "event") {
  506. if (this._getOption("polling")) {
  507. return new PollingEventSubscriber(this, sub.filter);
  508. }
  509. return new FilterIdEventSubscriber(this, sub.filter);
  510. }
  511. // Orphaned Logs are handled automatically, by the filter, since
  512. // logs with removed are emitted by it
  513. if (sub.type === "orphan" && sub.filter.orphan === "drop-log") {
  514. return new UnmanagedSubscriber("orphan");
  515. }
  516. return super._getSubscriber(sub);
  517. }
  518. /**
  519. * Returns true only if the [[_start]] has been called.
  520. */
  521. get ready() { return this.#notReady == null; }
  522. /**
  523. * Returns %%tx%% as a normalized JSON-RPC transaction request,
  524. * which has all values hexlified and any numeric values converted
  525. * to Quantity values.
  526. */
  527. getRpcTransaction(tx) {
  528. const result = {};
  529. // JSON-RPC now requires numeric values to be "quantity" values
  530. ["chainId", "gasLimit", "gasPrice", "type", "maxFeePerGas", "maxPriorityFeePerGas", "nonce", "value"].forEach((key) => {
  531. if (tx[key] == null) {
  532. return;
  533. }
  534. let dstKey = key;
  535. if (key === "gasLimit") {
  536. dstKey = "gas";
  537. }
  538. result[dstKey] = toQuantity(getBigInt(tx[key], `tx.${key}`));
  539. });
  540. // Make sure addresses and data are lowercase
  541. ["from", "to", "data"].forEach((key) => {
  542. if (tx[key] == null) {
  543. return;
  544. }
  545. result[key] = hexlify(tx[key]);
  546. });
  547. // Normalize the access list object
  548. if (tx.accessList) {
  549. result["accessList"] = accessListify(tx.accessList);
  550. }
  551. if (tx.blobVersionedHashes) {
  552. // @TODO: Remove this <any> case once EIP-4844 added to prepared tx
  553. result["blobVersionedHashes"] = tx.blobVersionedHashes.map(h => h.toLowerCase());
  554. }
  555. // @TODO: blobs should probably also be copied over, optionally
  556. // accounting for the kzg property to backfill blobVersionedHashes
  557. // using the commitment. Or should that be left as an exercise to
  558. // the caller?
  559. return result;
  560. }
  561. /**
  562. * Returns the request method and arguments required to perform
  563. * %%req%%.
  564. */
  565. getRpcRequest(req) {
  566. switch (req.method) {
  567. case "chainId":
  568. return { method: "eth_chainId", args: [] };
  569. case "getBlockNumber":
  570. return { method: "eth_blockNumber", args: [] };
  571. case "getGasPrice":
  572. return { method: "eth_gasPrice", args: [] };
  573. case "getPriorityFee":
  574. return { method: "eth_maxPriorityFeePerGas", args: [] };
  575. case "getBalance":
  576. return {
  577. method: "eth_getBalance",
  578. args: [getLowerCase(req.address), req.blockTag]
  579. };
  580. case "getTransactionCount":
  581. return {
  582. method: "eth_getTransactionCount",
  583. args: [getLowerCase(req.address), req.blockTag]
  584. };
  585. case "getCode":
  586. return {
  587. method: "eth_getCode",
  588. args: [getLowerCase(req.address), req.blockTag]
  589. };
  590. case "getStorage":
  591. return {
  592. method: "eth_getStorageAt",
  593. args: [
  594. getLowerCase(req.address),
  595. ("0x" + req.position.toString(16)),
  596. req.blockTag
  597. ]
  598. };
  599. case "broadcastTransaction":
  600. return {
  601. method: "eth_sendRawTransaction",
  602. args: [req.signedTransaction]
  603. };
  604. case "getBlock":
  605. if ("blockTag" in req) {
  606. return {
  607. method: "eth_getBlockByNumber",
  608. args: [req.blockTag, !!req.includeTransactions]
  609. };
  610. }
  611. else if ("blockHash" in req) {
  612. return {
  613. method: "eth_getBlockByHash",
  614. args: [req.blockHash, !!req.includeTransactions]
  615. };
  616. }
  617. break;
  618. case "getTransaction":
  619. return {
  620. method: "eth_getTransactionByHash",
  621. args: [req.hash]
  622. };
  623. case "getTransactionReceipt":
  624. return {
  625. method: "eth_getTransactionReceipt",
  626. args: [req.hash]
  627. };
  628. case "call":
  629. return {
  630. method: "eth_call",
  631. args: [this.getRpcTransaction(req.transaction), req.blockTag]
  632. };
  633. case "estimateGas": {
  634. return {
  635. method: "eth_estimateGas",
  636. args: [this.getRpcTransaction(req.transaction)]
  637. };
  638. }
  639. case "getLogs":
  640. if (req.filter && req.filter.address != null) {
  641. if (Array.isArray(req.filter.address)) {
  642. req.filter.address = req.filter.address.map(getLowerCase);
  643. }
  644. else {
  645. req.filter.address = getLowerCase(req.filter.address);
  646. }
  647. }
  648. return { method: "eth_getLogs", args: [req.filter] };
  649. }
  650. return null;
  651. }
  652. /**
  653. * Returns an ethers-style Error for the given JSON-RPC error
  654. * %%payload%%, coalescing the various strings and error shapes
  655. * that different nodes return, coercing them into a machine-readable
  656. * standardized error.
  657. */
  658. getRpcError(payload, _error) {
  659. const { method } = payload;
  660. const { error } = _error;
  661. if (method === "eth_estimateGas" && error.message) {
  662. const msg = error.message;
  663. if (!msg.match(/revert/i) && msg.match(/insufficient funds/i)) {
  664. return makeError("insufficient funds", "INSUFFICIENT_FUNDS", {
  665. transaction: (payload.params[0]),
  666. info: { payload, error }
  667. });
  668. }
  669. }
  670. if (method === "eth_call" || method === "eth_estimateGas") {
  671. const result = spelunkData(error);
  672. const e = AbiCoder.getBuiltinCallException((method === "eth_call") ? "call" : "estimateGas", (payload.params[0]), (result ? result.data : null));
  673. e.info = { error, payload };
  674. return e;
  675. }
  676. // Only estimateGas and call can return arbitrary contract-defined text, so now we
  677. // we can process text safely.
  678. const message = JSON.stringify(spelunkMessage(error));
  679. if (typeof (error.message) === "string" && error.message.match(/user denied|ethers-user-denied/i)) {
  680. const actionMap = {
  681. eth_sign: "signMessage",
  682. personal_sign: "signMessage",
  683. eth_signTypedData_v4: "signTypedData",
  684. eth_signTransaction: "signTransaction",
  685. eth_sendTransaction: "sendTransaction",
  686. eth_requestAccounts: "requestAccess",
  687. wallet_requestAccounts: "requestAccess",
  688. };
  689. return makeError(`user rejected action`, "ACTION_REJECTED", {
  690. action: (actionMap[method] || "unknown"),
  691. reason: "rejected",
  692. info: { payload, error }
  693. });
  694. }
  695. if (method === "eth_sendRawTransaction" || method === "eth_sendTransaction") {
  696. const transaction = (payload.params[0]);
  697. if (message.match(/insufficient funds|base fee exceeds gas limit/i)) {
  698. return makeError("insufficient funds for intrinsic transaction cost", "INSUFFICIENT_FUNDS", {
  699. transaction, info: { error }
  700. });
  701. }
  702. if (message.match(/nonce/i) && message.match(/too low/i)) {
  703. return makeError("nonce has already been used", "NONCE_EXPIRED", { transaction, info: { error } });
  704. }
  705. // "replacement transaction underpriced"
  706. if (message.match(/replacement transaction/i) && message.match(/underpriced/i)) {
  707. return makeError("replacement fee too low", "REPLACEMENT_UNDERPRICED", { transaction, info: { error } });
  708. }
  709. if (message.match(/only replay-protected/i)) {
  710. return makeError("legacy pre-eip-155 transactions not supported", "UNSUPPORTED_OPERATION", {
  711. operation: method, info: { transaction, info: { error } }
  712. });
  713. }
  714. }
  715. let unsupported = !!message.match(/the method .* does not exist/i);
  716. if (!unsupported) {
  717. if (error && error.details && error.details.startsWith("Unauthorized method:")) {
  718. unsupported = true;
  719. }
  720. }
  721. if (unsupported) {
  722. return makeError("unsupported operation", "UNSUPPORTED_OPERATION", {
  723. operation: payload.method, info: { error, payload }
  724. });
  725. }
  726. return makeError("could not coalesce error", "UNKNOWN_ERROR", { error, payload });
  727. }
  728. /**
  729. * Requests the %%method%% with %%params%% via the JSON-RPC protocol
  730. * over the underlying channel. This can be used to call methods
  731. * on the backend that do not have a high-level API within the Provider
  732. * API.
  733. *
  734. * This method queues requests according to the batch constraints
  735. * in the options, assigns the request a unique ID.
  736. *
  737. * **Do NOT override** this method in sub-classes; instead
  738. * override [[_send]] or force the options values in the
  739. * call to the constructor to modify this method's behavior.
  740. */
  741. send(method, params) {
  742. // @TODO: cache chainId?? purge on switch_networks
  743. // We have been destroyed; no operations are supported anymore
  744. if (this.destroyed) {
  745. return Promise.reject(makeError("provider destroyed; cancelled request", "UNSUPPORTED_OPERATION", { operation: method }));
  746. }
  747. const id = this.#nextId++;
  748. const promise = new Promise((resolve, reject) => {
  749. this.#payloads.push({
  750. resolve, reject,
  751. payload: { method, params, id, jsonrpc: "2.0" }
  752. });
  753. });
  754. // If there is not a pending drainTimer, set one
  755. this.#scheduleDrain();
  756. return promise;
  757. }
  758. /**
  759. * Resolves to the [[Signer]] account for %%address%% managed by
  760. * the client.
  761. *
  762. * If the %%address%% is a number, it is used as an index in the
  763. * the accounts from [[listAccounts]].
  764. *
  765. * This can only be used on clients which manage accounts (such as
  766. * Geth with imported account or MetaMask).
  767. *
  768. * Throws if the account doesn't exist.
  769. */
  770. async getSigner(address) {
  771. if (address == null) {
  772. address = 0;
  773. }
  774. const accountsPromise = this.send("eth_accounts", []);
  775. // Account index
  776. if (typeof (address) === "number") {
  777. const accounts = (await accountsPromise);
  778. if (address >= accounts.length) {
  779. throw new Error("no such account");
  780. }
  781. return new JsonRpcSigner(this, accounts[address]);
  782. }
  783. const { accounts } = await resolveProperties({
  784. network: this.getNetwork(),
  785. accounts: accountsPromise
  786. });
  787. // Account address
  788. address = getAddress(address);
  789. for (const account of accounts) {
  790. if (getAddress(account) === address) {
  791. return new JsonRpcSigner(this, address);
  792. }
  793. }
  794. throw new Error("invalid account");
  795. }
  796. async listAccounts() {
  797. const accounts = await this.send("eth_accounts", []);
  798. return accounts.map((a) => new JsonRpcSigner(this, a));
  799. }
  800. destroy() {
  801. // Stop processing requests
  802. if (this.#drainTimer) {
  803. clearTimeout(this.#drainTimer);
  804. this.#drainTimer = null;
  805. }
  806. // Cancel all pending requests
  807. for (const { payload, reject } of this.#payloads) {
  808. reject(makeError("provider destroyed; cancelled request", "UNSUPPORTED_OPERATION", { operation: payload.method }));
  809. }
  810. this.#payloads = [];
  811. // Parent clean-up
  812. super.destroy();
  813. }
  814. }
  815. // @TODO: remove this in v7, it is not exported because this functionality
  816. // is exposed in the JsonRpcApiProvider by setting polling to true. It should
  817. // be safe to remove regardless, because it isn't reachable, but just in case.
  818. /**
  819. * @_ignore:
  820. */
  821. export class JsonRpcApiPollingProvider extends JsonRpcApiProvider {
  822. #pollingInterval;
  823. constructor(network, options) {
  824. super(network, options);
  825. let pollingInterval = this._getOption("pollingInterval");
  826. if (pollingInterval == null) {
  827. pollingInterval = defaultOptions.pollingInterval;
  828. }
  829. this.#pollingInterval = pollingInterval;
  830. }
  831. _getSubscriber(sub) {
  832. const subscriber = super._getSubscriber(sub);
  833. if (isPollable(subscriber)) {
  834. subscriber.pollingInterval = this.#pollingInterval;
  835. }
  836. return subscriber;
  837. }
  838. /**
  839. * The polling interval (default: 4000 ms)
  840. */
  841. get pollingInterval() { return this.#pollingInterval; }
  842. set pollingInterval(value) {
  843. if (!Number.isInteger(value) || value < 0) {
  844. throw new Error("invalid interval");
  845. }
  846. this.#pollingInterval = value;
  847. this._forEachSubscriber((sub) => {
  848. if (isPollable(sub)) {
  849. sub.pollingInterval = this.#pollingInterval;
  850. }
  851. });
  852. }
  853. }
  854. /**
  855. * The JsonRpcProvider is one of the most common Providers,
  856. * which performs all operations over HTTP (or HTTPS) requests.
  857. *
  858. * Events are processed by polling the backend for the current block
  859. * number; when it advances, all block-base events are then checked
  860. * for updates.
  861. */
  862. export class JsonRpcProvider extends JsonRpcApiPollingProvider {
  863. #connect;
  864. constructor(url, network, options) {
  865. if (url == null) {
  866. url = "http:/\/localhost:8545";
  867. }
  868. super(network, options);
  869. if (typeof (url) === "string") {
  870. this.#connect = new FetchRequest(url);
  871. }
  872. else {
  873. this.#connect = url.clone();
  874. }
  875. }
  876. _getConnection() {
  877. return this.#connect.clone();
  878. }
  879. async send(method, params) {
  880. // All requests are over HTTP, so we can just start handling requests
  881. // We do this here rather than the constructor so that we don't send any
  882. // requests to the network (i.e. eth_chainId) until we absolutely have to.
  883. await this._start();
  884. return await super.send(method, params);
  885. }
  886. async _send(payload) {
  887. // Configure a POST connection for the requested method
  888. const request = this._getConnection();
  889. request.body = JSON.stringify(payload);
  890. request.setHeader("content-type", "application/json");
  891. const response = await request.send();
  892. response.assertOk();
  893. let resp = response.bodyJson;
  894. if (!Array.isArray(resp)) {
  895. resp = [resp];
  896. }
  897. return resp;
  898. }
  899. }
  900. function spelunkData(value) {
  901. if (value == null) {
  902. return null;
  903. }
  904. // These *are* the droids we're looking for.
  905. if (typeof (value.message) === "string" && value.message.match(/revert/i) && isHexString(value.data)) {
  906. return { message: value.message, data: value.data };
  907. }
  908. // Spelunk further...
  909. if (typeof (value) === "object") {
  910. for (const key in value) {
  911. const result = spelunkData(value[key]);
  912. if (result) {
  913. return result;
  914. }
  915. }
  916. return null;
  917. }
  918. // Might be a JSON string we can further descend...
  919. if (typeof (value) === "string") {
  920. try {
  921. return spelunkData(JSON.parse(value));
  922. }
  923. catch (error) { }
  924. }
  925. return null;
  926. }
  927. function _spelunkMessage(value, result) {
  928. if (value == null) {
  929. return;
  930. }
  931. // These *are* the droids we're looking for.
  932. if (typeof (value.message) === "string") {
  933. result.push(value.message);
  934. }
  935. // Spelunk further...
  936. if (typeof (value) === "object") {
  937. for (const key in value) {
  938. _spelunkMessage(value[key], result);
  939. }
  940. }
  941. // Might be a JSON string we can further descend...
  942. if (typeof (value) === "string") {
  943. try {
  944. return _spelunkMessage(JSON.parse(value), result);
  945. }
  946. catch (error) { }
  947. }
  948. }
  949. function spelunkMessage(value) {
  950. const result = [];
  951. _spelunkMessage(value, result);
  952. return result;
  953. }
  954. //# sourceMappingURL=provider-jsonrpc.js.map