@qevm/providers 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/LICENSE.md +21 -0
  2. package/README.md +78 -0
  3. package/lib/_version.d.ts +2 -0
  4. package/lib/_version.d.ts.map +1 -0
  5. package/lib/_version.js +5 -0
  6. package/lib/_version.js.map +1 -0
  7. package/lib/alchemy-provider.d.ts +17 -0
  8. package/lib/alchemy-provider.d.ts.map +1 -0
  9. package/lib/alchemy-provider.js +112 -0
  10. package/lib/alchemy-provider.js.map +1 -0
  11. package/lib/ankr-provider.d.ts +10 -0
  12. package/lib/ankr-provider.d.ts.map +1 -0
  13. package/lib/ankr-provider.js +79 -0
  14. package/lib/ankr-provider.js.map +1 -0
  15. package/lib/base-provider.d.ts +156 -0
  16. package/lib/base-provider.d.ts.map +1 -0
  17. package/lib/base-provider.js +2585 -0
  18. package/lib/base-provider.js.map +1 -0
  19. package/lib/browser-ipc-provider.d.ts +3 -0
  20. package/lib/browser-ipc-provider.d.ts.map +1 -0
  21. package/lib/browser-ipc-provider.js +6 -0
  22. package/lib/browser-ipc-provider.js.map +1 -0
  23. package/lib/browser-net.d.ts +2 -0
  24. package/lib/browser-net.d.ts.map +1 -0
  25. package/lib/browser-net.js +6 -0
  26. package/lib/browser-net.js.map +1 -0
  27. package/lib/browser-ws.d.ts +3 -0
  28. package/lib/browser-ws.d.ts.map +1 -0
  29. package/lib/browser-ws.js +22 -0
  30. package/lib/browser-ws.js.map +1 -0
  31. package/lib/cloudflare-provider.d.ts +8 -0
  32. package/lib/cloudflare-provider.d.ts.map +1 -0
  33. package/lib/cloudflare-provider.js +100 -0
  34. package/lib/cloudflare-provider.js.map +1 -0
  35. package/lib/etherscan-provider.d.ts +18 -0
  36. package/lib/etherscan-provider.d.ts.map +1 -0
  37. package/lib/etherscan-provider.js +528 -0
  38. package/lib/etherscan-provider.js.map +1 -0
  39. package/lib/fallback-provider.d.ts +20 -0
  40. package/lib/fallback-provider.d.ts.map +1 -0
  41. package/lib/fallback-provider.js +699 -0
  42. package/lib/fallback-provider.js.map +1 -0
  43. package/lib/formatter.d.ts +60 -0
  44. package/lib/formatter.d.ts.map +1 -0
  45. package/lib/formatter.js +452 -0
  46. package/lib/formatter.js.map +1 -0
  47. package/lib/index.d.ts +23 -0
  48. package/lib/index.d.ts.map +1 -0
  49. package/lib/index.js +97 -0
  50. package/lib/index.js.map +1 -0
  51. package/lib/infura-provider.d.ts +21 -0
  52. package/lib/infura-provider.d.ts.map +1 -0
  53. package/lib/infura-provider.js +141 -0
  54. package/lib/infura-provider.js.map +1 -0
  55. package/lib/ipc-provider.d.ts +8 -0
  56. package/lib/ipc-provider.d.ts.map +1 -0
  57. package/lib/ipc-provider.js +77 -0
  58. package/lib/ipc-provider.js.map +1 -0
  59. package/lib/json-rpc-batch-provider.d.ts +17 -0
  60. package/lib/json-rpc-batch-provider.d.ts.map +1 -0
  61. package/lib/json-rpc-batch-provider.js +99 -0
  62. package/lib/json-rpc-batch-provider.js.map +1 -0
  63. package/lib/json-rpc-provider.d.ts +54 -0
  64. package/lib/json-rpc-provider.d.ts.map +1 -0
  65. package/lib/json-rpc-provider.js +855 -0
  66. package/lib/json-rpc-provider.js.map +1 -0
  67. package/lib/nodesmith-provider.d.ts +7 -0
  68. package/lib/nodesmith-provider.d.ts.map +1 -0
  69. package/lib/nodesmith-provider.js +64 -0
  70. package/lib/nodesmith-provider.js.map +1 -0
  71. package/lib/pocket-provider.d.ts +12 -0
  72. package/lib/pocket-provider.d.ts.map +1 -0
  73. package/lib/pocket-provider.js +98 -0
  74. package/lib/pocket-provider.js.map +1 -0
  75. package/lib/url-json-rpc-provider.d.ts +18 -0
  76. package/lib/url-json-rpc-provider.d.ts.map +1 -0
  77. package/lib/url-json-rpc-provider.js +153 -0
  78. package/lib/url-json-rpc-provider.js.map +1 -0
  79. package/lib/web3-provider.d.ts +28 -0
  80. package/lib/web3-provider.d.ts.map +1 -0
  81. package/lib/web3-provider.js +155 -0
  82. package/lib/web3-provider.js.map +1 -0
  83. package/lib/websocket-provider.d.ts +48 -0
  84. package/lib/websocket-provider.d.ts.map +1 -0
  85. package/lib/websocket-provider.js +384 -0
  86. package/lib/websocket-provider.js.map +1 -0
  87. package/lib/ws.d.ts +3 -0
  88. package/lib/ws.d.ts.map +1 -0
  89. package/lib/ws.js +9 -0
  90. package/lib/ws.js.map +1 -0
  91. package/package.json +57 -0
  92. package/src.ts/_version.ts +1 -0
  93. package/src.ts/alchemy-provider.ts +101 -0
  94. package/src.ts/ankr-provider.ts +68 -0
  95. package/src.ts/base-provider.ts +2216 -0
  96. package/src.ts/browser-ipc-provider.ts +7 -0
  97. package/src.ts/browser-net.ts +3 -0
  98. package/src.ts/browser-ws.ts +21 -0
  99. package/src.ts/cloudflare-provider.ts +42 -0
  100. package/src.ts/etherscan-provider.ts +454 -0
  101. package/src.ts/fallback-provider.ts +654 -0
  102. package/src.ts/formatter.ts +522 -0
  103. package/src.ts/index.ts +178 -0
  104. package/src.ts/infura-provider.ts +143 -0
  105. package/src.ts/ipc-provider.ts +72 -0
  106. package/src.ts/json-rpc-batch-provider.ts +97 -0
  107. package/src.ts/json-rpc-provider.ts +742 -0
  108. package/src.ts/nodesmith-provider.ts +50 -0
  109. package/src.ts/pocket-provider.ts +93 -0
  110. package/src.ts/url-json-rpc-provider.ts +106 -0
  111. package/src.ts/web3-provider.ts +169 -0
  112. package/src.ts/websocket-provider.ts +350 -0
  113. package/src.ts/ws.ts +3 -0
  114. package/thirdparty.d.ts +10 -0
@@ -0,0 +1,2216 @@
1
+ "use strict";
2
+
3
+ import {
4
+ Block, BlockTag, BlockWithTransactions, EventType, Filter, FilterByBlockHash, ForkEvent,
5
+ Listener, Log, Provider, TransactionReceipt, TransactionRequest, TransactionResponse
6
+ } from "@qevm/abstract-provider";
7
+ import { encode as base64Encode } from "@ethersproject/base64";
8
+ import { Base58 } from "@ethersproject/basex";
9
+ import { BigNumber, BigNumberish } from "@ethersproject/bignumber";
10
+ import { arrayify, BytesLike, concat, hexConcat, hexDataLength, hexDataSlice, hexlify, hexValue, hexZeroPad, isHexString } from "@qevm/bytes";
11
+ import { HashZero } from "@ethersproject/constants";
12
+ import { dnsEncode, namehash } from "@qevm/hash";
13
+ import { getNetwork, Network, Networkish } from "@ethersproject/networks";
14
+ import { Deferrable, defineReadOnly, getStatic, resolveProperties } from "@ethersproject/properties";
15
+ import { Transaction } from "@qevm/transactions";
16
+ import { sha256 } from "@ethersproject/sha2";
17
+ import { toUtf8Bytes, toUtf8String } from "@ethersproject/strings";
18
+ import { fetchJson, poll } from "@ethersproject/web";
19
+
20
+ import bech32 from "bech32";
21
+
22
+ import { Logger } from "@ethersproject/logger";
23
+ import { version } from "./_version";
24
+ const logger = new Logger(version);
25
+
26
+ import { Formatter } from "./formatter";
27
+
28
+ const MAX_CCIP_REDIRECTS = 10;
29
+
30
+ //////////////////////////////
31
+ // Event Serializeing
32
+
33
+ function checkTopic(topic: string): string {
34
+ if (topic == null) { return "null"; }
35
+ if (hexDataLength(topic) !== 32) {
36
+ logger.throwArgumentError("invalid topic", "topic", topic);
37
+ }
38
+ return topic.toLowerCase();
39
+ }
40
+
41
+ function serializeTopics(topics: Array<string | Array<string>>): string {
42
+ // Remove trailing null AND-topics; they are redundant
43
+ topics = topics.slice();
44
+ while (topics.length > 0 && topics[topics.length - 1] == null) { topics.pop(); }
45
+
46
+ return topics.map((topic) => {
47
+ if (Array.isArray(topic)) {
48
+
49
+ // Only track unique OR-topics
50
+ const unique: { [ topic: string ]: boolean } = { }
51
+ topic.forEach((topic) => {
52
+ unique[checkTopic(topic)] = true;
53
+ });
54
+
55
+ // The order of OR-topics does not matter
56
+ const sorted = Object.keys(unique);
57
+ sorted.sort();
58
+
59
+ return sorted.join("|");
60
+
61
+ } else {
62
+ return checkTopic(topic);
63
+ }
64
+ }).join("&");
65
+ }
66
+
67
+ function deserializeTopics(data: string): Array<string | Array<string>> {
68
+ if (data === "") { return [ ]; }
69
+
70
+ return data.split(/&/g).map((topic) => {
71
+ if (topic === "") { return [ ]; }
72
+
73
+ const comps = topic.split("|").map((topic) => {
74
+ return ((topic === "null") ? null: topic);
75
+ });
76
+
77
+ return ((comps.length === 1) ? comps[0]: comps);
78
+ });
79
+ }
80
+
81
+ function getEventTag(eventName: EventType): string {
82
+ if (typeof(eventName) === "string") {
83
+ eventName = eventName.toLowerCase();
84
+
85
+ if (hexDataLength(eventName) === 32) {
86
+ return "tx:" + eventName;
87
+ }
88
+
89
+ if (eventName.indexOf(":") === -1) {
90
+ return eventName;
91
+ }
92
+
93
+ } else if (Array.isArray(eventName)) {
94
+ return "filter:*:" + serializeTopics(eventName);
95
+
96
+ } else if (ForkEvent.isForkEvent(eventName)) {
97
+ logger.warn("not implemented");
98
+ throw new Error("not implemented");
99
+
100
+ } else if (eventName && typeof(eventName) === "object") {
101
+ return "filter:" + (eventName.address || "*") + ":" + serializeTopics(eventName.topics || []);
102
+ }
103
+
104
+ throw new Error("invalid event - " + eventName);
105
+ }
106
+
107
+ //////////////////////////////
108
+ // Helper Object
109
+
110
+ function getTime() {
111
+ return (new Date()).getTime();
112
+ }
113
+
114
+ function stall(duration: number): Promise<void> {
115
+ return new Promise((resolve) => {
116
+ setTimeout(resolve, duration);
117
+ });
118
+ }
119
+
120
+ //////////////////////////////
121
+ // Provider Object
122
+
123
+
124
+ /**
125
+ * EventType
126
+ * - "block"
127
+ * - "poll"
128
+ * - "didPoll"
129
+ * - "pending"
130
+ * - "error"
131
+ * - "network"
132
+ * - filter
133
+ * - topics array
134
+ * - transaction hash
135
+ */
136
+
137
+ const PollableEvents = [ "block", "network", "pending", "poll" ];
138
+
139
+ export class Event {
140
+ readonly listener: Listener;
141
+ readonly once: boolean;
142
+ readonly tag: string;
143
+
144
+ _lastBlockNumber: number
145
+ _inflight: boolean;
146
+
147
+ constructor(tag: string, listener: Listener, once: boolean) {
148
+ defineReadOnly(this, "tag", tag);
149
+ defineReadOnly(this, "listener", listener);
150
+ defineReadOnly(this, "once", once);
151
+
152
+ this._lastBlockNumber = -2;
153
+ this._inflight = false;
154
+ }
155
+
156
+ get event(): EventType {
157
+ switch (this.type) {
158
+ case "tx":
159
+ return this.hash;
160
+ case "filter":
161
+ return this.filter;
162
+ }
163
+ return this.tag;
164
+ }
165
+
166
+ get type(): string {
167
+ return this.tag.split(":")[0]
168
+ }
169
+
170
+ get hash(): string {
171
+ const comps = this.tag.split(":");
172
+ if (comps[0] !== "tx") { return null; }
173
+ return comps[1];
174
+ }
175
+
176
+ get filter(): Filter {
177
+ const comps = this.tag.split(":");
178
+ if (comps[0] !== "filter") { return null; }
179
+ const address = comps[1];
180
+
181
+ const topics = deserializeTopics(comps[2]);
182
+ const filter: Filter = { };
183
+
184
+ if (topics.length > 0) { filter.topics = topics; }
185
+ if (address && address !== "*") { filter.address = address; }
186
+
187
+ return filter;
188
+ }
189
+
190
+ pollable(): boolean {
191
+ return (this.tag.indexOf(":") >= 0 || PollableEvents.indexOf(this.tag) >= 0);
192
+ }
193
+ }
194
+
195
+ export interface EnsResolver {
196
+
197
+ // Name this Resolver is associated with
198
+ readonly name: string;
199
+
200
+ // The address of the resolver
201
+ readonly address: string;
202
+
203
+ // Multichain address resolution (also normal address resolution)
204
+ // See: https://eips.ethereum.org/EIPS/eip-2304
205
+ getAddress(coinType?: 60): Promise<null | string>
206
+
207
+ // Contenthash field
208
+ // See: https://eips.ethereum.org/EIPS/eip-1577
209
+ getContentHash(): Promise<null | string>;
210
+
211
+ // Storage of text records
212
+ // See: https://eips.ethereum.org/EIPS/eip-634
213
+ getText(key: string): Promise<null | string>;
214
+ };
215
+
216
+ export interface EnsProvider {
217
+ resolveName(name: string): Promise<null | string>;
218
+ lookupAddress(address: string): Promise<null | string>;
219
+ getResolver(name: string): Promise<null | EnsResolver>;
220
+ }
221
+
222
+ type CoinInfo = {
223
+ symbol: string,
224
+ ilk?: string, // General family
225
+ prefix?: string, // Bech32 prefix
226
+ p2pkh?: number, // Pay-to-Public-Key-Hash Version
227
+ p2sh?: number, // Pay-to-Script-Hash Version
228
+ };
229
+
230
+ // https://github.com/satoshilabs/slips/blob/master/slip-0044.md
231
+ const coinInfos: { [ coinType: string ]: CoinInfo } = {
232
+ "0": { symbol: "btc", p2pkh: 0x00, p2sh: 0x05, prefix: "bc" },
233
+ "2": { symbol: "ltc", p2pkh: 0x30, p2sh: 0x32, prefix: "ltc" },
234
+ "3": { symbol: "doge", p2pkh: 0x1e, p2sh: 0x16 },
235
+ "60": { symbol: "eth", ilk: "eth" },
236
+ "61": { symbol: "etc", ilk: "eth" },
237
+ "700": { symbol: "xdai", ilk: "eth" },
238
+ };
239
+
240
+ function bytes32ify(value: number): string {
241
+ return hexZeroPad(BigNumber.from(value).toHexString(), 32);
242
+ }
243
+
244
+ // Compute the Base58Check encoded data (checksum is first 4 bytes of sha256d)
245
+ function base58Encode(data: Uint8Array): string {
246
+ return Base58.encode(concat([ data, hexDataSlice(sha256(sha256(data)), 0, 4) ]));
247
+ }
248
+
249
+ export interface Avatar {
250
+ url: string;
251
+ linkage: Array<{ type: string, content: string }>;
252
+ }
253
+
254
+ const matcherIpfs = new RegExp("^(ipfs):/\/(.*)$", "i");
255
+ const matchers = [
256
+ new RegExp("^(https):/\/(.*)$", "i"),
257
+ new RegExp("^(data):(.*)$", "i"),
258
+ matcherIpfs,
259
+ new RegExp("^eip155:[0-9]+/(erc[0-9]+):(.*)$", "i"),
260
+ ];
261
+
262
+ function _parseString(result: string, start: number): null | string {
263
+ try {
264
+ return toUtf8String(_parseBytes(result, start));
265
+ } catch(error) { }
266
+ return null;
267
+ }
268
+
269
+ function _parseBytes(result: string, start: number): null | string {
270
+ if (result === "0x") { return null; }
271
+
272
+ const offset = BigNumber.from(hexDataSlice(result, start, start + 32)).toNumber();
273
+ const length = BigNumber.from(hexDataSlice(result, offset, offset + 32)).toNumber();
274
+
275
+ return hexDataSlice(result, offset + 32, offset + 32 + length);
276
+ }
277
+
278
+ // Trim off the ipfs:// prefix and return the default gateway URL
279
+ function getIpfsLink(link: string): string {
280
+ if (link.match(/^ipfs:\/\/ipfs\//i)) {
281
+ link = link.substring(12);
282
+ } else if (link.match(/^ipfs:\/\//i)) {
283
+ link = link.substring(7);
284
+ } else {
285
+ logger.throwArgumentError("unsupported IPFS format", "link", link);
286
+ }
287
+
288
+ return `https:/\/gateway.ipfs.io/ipfs/${ link }`;
289
+ }
290
+
291
+ function numPad(value: number): Uint8Array {
292
+ const result = arrayify(value);
293
+ if (result.length > 32) { throw new Error("internal; should not happen"); }
294
+
295
+ const padded = new Uint8Array(32);
296
+ padded.set(result, 32 - result.length);
297
+ return padded;
298
+ }
299
+
300
+ function bytesPad(value: Uint8Array): Uint8Array {
301
+ if ((value.length % 32) === 0) { return value; }
302
+
303
+ const result = new Uint8Array(Math.ceil(value.length / 32) * 32);
304
+ result.set(value);
305
+ return result;
306
+ }
307
+
308
+ // ABI Encodes a series of (bytes, bytes, ...)
309
+ function encodeBytes(datas: Array<BytesLike>) {
310
+ const result: Array<Uint8Array> = [ ];
311
+
312
+ let byteCount = 0;
313
+
314
+ // Add place-holders for pointers as we add items
315
+ for (let i = 0; i < datas.length; i++) {
316
+ result.push(null);
317
+ byteCount += 32;
318
+ }
319
+
320
+ for (let i = 0; i < datas.length; i++) {
321
+ const data = arrayify(datas[i]);
322
+
323
+ // Update the bytes offset
324
+ result[i] = numPad(byteCount);
325
+
326
+ // The length and padded value of data
327
+ result.push(numPad(data.length));
328
+ result.push(bytesPad(data));
329
+ byteCount += 32 + Math.ceil(data.length / 32) * 32;
330
+ }
331
+
332
+ return hexConcat(result);
333
+ }
334
+
335
+ export class Resolver implements EnsResolver {
336
+ readonly provider: BaseProvider;
337
+
338
+ readonly name: string;
339
+ readonly address: string;
340
+
341
+ readonly _resolvedAddress: null | string;
342
+
343
+ // For EIP-2544 names, the ancestor that provided the resolver
344
+ _supportsEip2544: null | Promise<boolean>;
345
+
346
+ // The resolvedAddress is only for creating a ReverseLookup resolver
347
+ constructor(provider: BaseProvider, address: string, name: string, resolvedAddress?: string) {
348
+ defineReadOnly(this, "provider", provider);
349
+ defineReadOnly(this, "name", name);
350
+ defineReadOnly(this, "address", provider.formatter.address(address));
351
+ defineReadOnly(this, "_resolvedAddress", resolvedAddress);
352
+ }
353
+
354
+ supportsWildcard(): Promise<boolean> {
355
+ if (!this._supportsEip2544) {
356
+ // supportsInterface(bytes4 = selector("resolve(bytes,bytes)"))
357
+ this._supportsEip2544 = this.provider.call({
358
+ to: this.address,
359
+ data: "0x01ffc9a79061b92300000000000000000000000000000000000000000000000000000000"
360
+ }).then((result) => {
361
+ return BigNumber.from(result).eq(1);
362
+ }).catch((error) => {
363
+ if (error.code === Logger.errors.CALL_EXCEPTION) { return false; }
364
+ // Rethrow the error: link is down, etc. Let future attempts retry.
365
+ this._supportsEip2544 = null;
366
+ throw error;
367
+ });
368
+ }
369
+
370
+ return this._supportsEip2544;
371
+ }
372
+
373
+ async _fetch(selector: string, parameters?: string): Promise<null | string> {
374
+
375
+ // e.g. keccak256("addr(bytes32,uint256)")
376
+ const tx = {
377
+ to: this.address,
378
+ ccipReadEnabled: true,
379
+ data: hexConcat([ selector, namehash(this.name), (parameters || "0x") ])
380
+ };
381
+
382
+ // Wildcard support; use EIP-2544 to resolve the request
383
+ let parseBytes = false;
384
+ if (await this.supportsWildcard()) {
385
+ parseBytes = true;
386
+
387
+ // selector("resolve(bytes,bytes)")
388
+ tx.data = hexConcat([ "0x9061b923", encodeBytes([ dnsEncode(this.name), tx.data ]) ]);
389
+ }
390
+
391
+ try {
392
+ let result = await this.provider.call(tx);
393
+ if ((arrayify(result).length % 32) === 4) {
394
+ logger.throwError("resolver threw error", Logger.errors.CALL_EXCEPTION, {
395
+ transaction: tx, data: result
396
+ });
397
+ }
398
+ if (parseBytes) { result = _parseBytes(result, 0); }
399
+ return result;
400
+ } catch (error) {
401
+ if (error.code === Logger.errors.CALL_EXCEPTION) { return null; }
402
+ throw error;
403
+ }
404
+ }
405
+
406
+ async _fetchBytes(selector: string, parameters?: string): Promise<null | string> {
407
+ const result = await this._fetch(selector, parameters);
408
+ if (result != null) { return _parseBytes(result, 0); }
409
+ return null;
410
+ }
411
+
412
+ _getAddress(coinType: number, hexBytes: string): string {
413
+ const coinInfo = coinInfos[String(coinType)];
414
+
415
+ if (coinInfo == null) {
416
+ logger.throwError(`unsupported coin type: ${ coinType }`, Logger.errors.UNSUPPORTED_OPERATION, {
417
+ operation: `getAddress(${ coinType })`
418
+ });
419
+ }
420
+
421
+ if (coinInfo.ilk === "eth") {
422
+ return this.provider.formatter.address(hexBytes);
423
+ }
424
+
425
+ const bytes = arrayify(hexBytes);
426
+
427
+ // P2PKH: OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG
428
+ if (coinInfo.p2pkh != null) {
429
+ const p2pkh = hexBytes.match(/^0x76a9([0-9a-f][0-9a-f])([0-9a-f]*)88ac$/);
430
+ if (p2pkh) {
431
+ const length = parseInt(p2pkh[1], 16);
432
+ if (p2pkh[2].length === length * 2 && length >= 1 && length <= 75) {
433
+ return base58Encode(concat([ [ coinInfo.p2pkh ], ("0x" + p2pkh[2]) ]));
434
+ }
435
+ }
436
+ }
437
+
438
+ // P2SH: OP_HASH160 <scriptHash> OP_EQUAL
439
+ if (coinInfo.p2sh != null) {
440
+ const p2sh = hexBytes.match(/^0xa9([0-9a-f][0-9a-f])([0-9a-f]*)87$/);
441
+ if (p2sh) {
442
+ const length = parseInt(p2sh[1], 16);
443
+ if (p2sh[2].length === length * 2 && length >= 1 && length <= 75) {
444
+ return base58Encode(concat([ [ coinInfo.p2sh ], ("0x" + p2sh[2]) ]));
445
+ }
446
+ }
447
+ }
448
+
449
+ // Bech32
450
+ if (coinInfo.prefix != null) {
451
+ const length = bytes[1];
452
+
453
+ // https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#witness-program
454
+ let version = bytes[0];
455
+ if (version === 0x00) {
456
+ if (length !== 20 && length !== 32) {
457
+ version = -1;
458
+ }
459
+ } else {
460
+ version = -1;
461
+ }
462
+
463
+ if (version >= 0 && bytes.length === 2 + length && length >= 1 && length <= 75) {
464
+ const words = bech32.toWords(bytes.slice(2));
465
+ words.unshift(version);
466
+ return bech32.encode(coinInfo.prefix, words);
467
+ }
468
+ }
469
+
470
+ return null;
471
+ }
472
+
473
+
474
+ async getAddress(coinType?: number): Promise<string> {
475
+ if (coinType == null) { coinType = 60; }
476
+
477
+ // If Ethereum, use the standard `addr(bytes32)`
478
+ if (coinType === 60) {
479
+ try {
480
+ // keccak256("addr(bytes32)")
481
+ const result = await this._fetch("0x3b3b57de");
482
+
483
+ // No address
484
+ if (result === "0x" || result === HashZero) { return null; }
485
+
486
+ return this.provider.formatter.callAddress(result);
487
+ } catch (error) {
488
+ if (error.code === Logger.errors.CALL_EXCEPTION) { return null; }
489
+ throw error;
490
+ }
491
+ }
492
+
493
+ // keccak256("addr(bytes32,uint256")
494
+ const hexBytes = await this._fetchBytes("0xf1cb7e06", bytes32ify(coinType));
495
+
496
+ // No address
497
+ if (hexBytes == null || hexBytes === "0x") { return null; }
498
+
499
+ // Compute the address
500
+ const address = this._getAddress(coinType, hexBytes);
501
+
502
+ if (address == null) {
503
+ logger.throwError(`invalid or unsupported coin data`, Logger.errors.UNSUPPORTED_OPERATION, {
504
+ operation: `getAddress(${ coinType })`,
505
+ coinType: coinType,
506
+ data: hexBytes
507
+ });
508
+ }
509
+
510
+ return address;
511
+ }
512
+
513
+ async getAvatar(): Promise<null | Avatar> {
514
+ const linkage: Array<{ type: string, content: string }> = [ { type: "name", content: this.name } ];
515
+ try {
516
+ // test data for ricmoo.eth
517
+ //const avatar = "eip155:1/erc721:0x265385c7f4132228A0d54EB1A9e7460b91c0cC68/29233";
518
+ const avatar = await this.getText("avatar");
519
+ if (avatar == null) { return null; }
520
+
521
+ for (let i = 0; i < matchers.length; i++) {
522
+ const match = avatar.match(matchers[i]);
523
+ if (match == null) { continue; }
524
+
525
+ const scheme = match[1].toLowerCase();
526
+
527
+ switch (scheme) {
528
+ case "https":
529
+ linkage.push({ type: "url", content: avatar });
530
+ return { linkage, url: avatar };
531
+
532
+ case "data":
533
+ linkage.push({ type: "data", content: avatar });
534
+ return { linkage, url: avatar };
535
+
536
+ case "ipfs":
537
+ linkage.push({ type: "ipfs", content: avatar });
538
+ return { linkage, url: getIpfsLink(avatar) };
539
+
540
+ case "erc721":
541
+ case "erc1155": {
542
+ // Depending on the ERC type, use tokenURI(uint256) or url(uint256)
543
+ const selector = (scheme === "erc721") ? "0xc87b56dd": "0x0e89341c";
544
+ linkage.push({ type: scheme, content: avatar });
545
+
546
+ // The owner of this name
547
+ const owner = (this._resolvedAddress || await this.getAddress());
548
+
549
+ const comps = (match[2] || "").split("/");
550
+ if (comps.length !== 2) { return null; }
551
+
552
+ const addr = await this.provider.formatter.address(comps[0]);
553
+ const tokenId = hexZeroPad(BigNumber.from(comps[1]).toHexString(), 32);
554
+
555
+ // Check that this account owns the token
556
+ if (scheme === "erc721") {
557
+ // ownerOf(uint256 tokenId)
558
+ const tokenOwner = this.provider.formatter.callAddress(await this.provider.call({
559
+ to: addr, data: hexConcat([ "0x6352211e", tokenId ])
560
+ }));
561
+ if (owner !== tokenOwner) { return null; }
562
+ linkage.push({ type: "owner", content: tokenOwner });
563
+
564
+ } else if (scheme === "erc1155") {
565
+ // balanceOf(address owner, uint256 tokenId)
566
+ const balance = BigNumber.from(await this.provider.call({
567
+ to: addr, data: hexConcat([ "0x00fdd58e", hexZeroPad(owner, 32), tokenId ])
568
+ }));
569
+ if (balance.isZero()) { return null; }
570
+ linkage.push({ type: "balance", content: balance.toString() });
571
+ }
572
+
573
+ // Call the token contract for the metadata URL
574
+ const tx = {
575
+ to: this.provider.formatter.address(comps[0]),
576
+ data: hexConcat([ selector, tokenId ])
577
+ };
578
+
579
+ let metadataUrl = _parseString(await this.provider.call(tx), 0);
580
+ if (metadataUrl == null) { return null; }
581
+ linkage.push({ type: "metadata-url-base", content: metadataUrl });
582
+
583
+ // ERC-1155 allows a generic {id} in the URL
584
+ if (scheme === "erc1155") {
585
+ metadataUrl = metadataUrl.replace("{id}", tokenId.substring(2));
586
+ linkage.push({ type: "metadata-url-expanded", content: metadataUrl });
587
+ }
588
+
589
+ // Transform IPFS metadata links
590
+ if (metadataUrl.match(/^ipfs:/i)) {
591
+ metadataUrl = getIpfsLink(metadataUrl);
592
+ }
593
+
594
+ linkage.push({ type: "metadata-url", content: metadataUrl });
595
+
596
+ // Get the token metadata
597
+ const metadata = await fetchJson(metadataUrl);
598
+ if (!metadata) { return null; }
599
+ linkage.push({ type: "metadata", content: JSON.stringify(metadata) });
600
+
601
+ // Pull the image URL out
602
+ let imageUrl = metadata.image;
603
+ if (typeof(imageUrl) !== "string") { return null; }
604
+
605
+ if (imageUrl.match(/^(https:\/\/|data:)/i)) {
606
+ // Allow
607
+ } else {
608
+ // Transform IPFS link to gateway
609
+ const ipfs = imageUrl.match(matcherIpfs);
610
+ if (ipfs == null) { return null; }
611
+
612
+ linkage.push({ type: "url-ipfs", content: imageUrl });
613
+ imageUrl = getIpfsLink(imageUrl);
614
+ }
615
+
616
+ linkage.push({ type: "url", content: imageUrl });
617
+
618
+ return { linkage, url: imageUrl };
619
+ }
620
+ }
621
+ }
622
+ } catch (error) { }
623
+
624
+ return null;
625
+ }
626
+
627
+ async getContentHash(): Promise<string> {
628
+
629
+ // keccak256("contenthash()")
630
+ const hexBytes = await this._fetchBytes("0xbc1c58d1");
631
+
632
+ // No contenthash
633
+ if (hexBytes == null || hexBytes === "0x") { return null; }
634
+
635
+ // IPFS (CID: 1, Type: DAG-PB)
636
+ const ipfs = hexBytes.match(/^0xe3010170(([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f]*))$/);
637
+ if (ipfs) {
638
+ const length = parseInt(ipfs[3], 16);
639
+ if (ipfs[4].length === length * 2) {
640
+ return "ipfs:/\/" + Base58.encode("0x" + ipfs[1]);
641
+ }
642
+ }
643
+
644
+ // IPNS (CID: 1, Type: libp2p-key)
645
+ const ipns = hexBytes.match(/^0xe5010172(([0-9a-f][0-9a-f])([0-9a-f][0-9a-f])([0-9a-f]*))$/);
646
+ if (ipns) {
647
+ const length = parseInt(ipns[3], 16);
648
+ if (ipns[4].length === length * 2) {
649
+ return "ipns:/\/" + Base58.encode("0x" + ipns[1]);
650
+ }
651
+ }
652
+
653
+ // Swarm (CID: 1, Type: swarm-manifest; hash/length hard-coded to keccak256/32)
654
+ const swarm = hexBytes.match(/^0xe40101fa011b20([0-9a-f]*)$/)
655
+ if (swarm) {
656
+ if (swarm[1].length === (32 * 2)) {
657
+ return "bzz:/\/" + swarm[1]
658
+ }
659
+ }
660
+
661
+ const skynet = hexBytes.match(/^0x90b2c605([0-9a-f]*)$/);
662
+ if (skynet) {
663
+ if (skynet[1].length === (34 * 2)) {
664
+ // URL Safe base64; https://datatracker.ietf.org/doc/html/rfc4648#section-5
665
+ const urlSafe: Record<string, string> = { "=": "", "+": "-", "/": "_" };
666
+ const hash = base64Encode("0x" + skynet[1]).replace(/[=+\/]/g, (a) => (urlSafe[a]));
667
+ return "sia:/\/" + hash;
668
+ }
669
+ }
670
+
671
+ return logger.throwError(`invalid or unsupported content hash data`, Logger.errors.UNSUPPORTED_OPERATION, {
672
+ operation: "getContentHash()",
673
+ data: hexBytes
674
+ });
675
+ }
676
+
677
+ async getText(key: string): Promise<string> {
678
+
679
+ // The key encoded as parameter to fetchBytes
680
+ let keyBytes = toUtf8Bytes(key);
681
+
682
+ // The nodehash consumes the first slot, so the string pointer targets
683
+ // offset 64, with the length at offset 64 and data starting at offset 96
684
+ keyBytes = concat([ bytes32ify(64), bytes32ify(keyBytes.length), keyBytes ]);
685
+
686
+ // Pad to word-size (32 bytes)
687
+ if ((keyBytes.length % 32) !== 0) {
688
+ keyBytes = concat([ keyBytes, hexZeroPad("0x", 32 - (key.length % 32)) ])
689
+ }
690
+
691
+ const hexBytes = await this._fetchBytes("0x59d1d43c", hexlify(keyBytes));
692
+ if (hexBytes == null || hexBytes === "0x") { return null; }
693
+
694
+ return toUtf8String(hexBytes);
695
+ }
696
+ }
697
+
698
+ let defaultFormatter: Formatter = null;
699
+
700
+ let nextPollId = 1;
701
+
702
+ export class BaseProvider extends Provider implements EnsProvider {
703
+ _networkPromise: Promise<Network>;
704
+ _network: Network;
705
+
706
+ _events: Array<Event>;
707
+
708
+ formatter: Formatter;
709
+
710
+ // To help mitigate the eventually consistent nature of the blockchain
711
+ // we keep a mapping of events we emit. If we emit an event X, we expect
712
+ // that a user should be able to query for that event in the callback,
713
+ // if the node returns null, we stall the response until we get back a
714
+ // meaningful value, since we may be hitting a re-org, or a node that
715
+ // has not indexed the event yet.
716
+ // Events:
717
+ // - t:{hash} - Transaction hash
718
+ // - b:{hash} - BlockHash
719
+ // - block - The most recent emitted block
720
+ _emitted: { [ eventName: string ]: number | "pending" };
721
+
722
+ _pollingInterval: number;
723
+ _poller: NodeJS.Timer;
724
+ _bootstrapPoll: NodeJS.Timer;
725
+
726
+ _lastBlockNumber: number;
727
+ _maxFilterBlockRange: number;
728
+
729
+ _fastBlockNumber: number;
730
+ _fastBlockNumberPromise: Promise<number>;
731
+ _fastQueryDate: number;
732
+
733
+ _maxInternalBlockNumber: number;
734
+ _internalBlockNumber: Promise<{ blockNumber: number, reqTime: number, respTime: number }>;
735
+
736
+ readonly anyNetwork: boolean;
737
+
738
+ disableCcipRead: boolean;
739
+
740
+
741
+ /**
742
+ * ready
743
+ *
744
+ * A Promise<Network> that resolves only once the provider is ready.
745
+ *
746
+ * Sub-classes that call the super with a network without a chainId
747
+ * MUST set this. Standard named networks have a known chainId.
748
+ *
749
+ */
750
+
751
+ constructor(network: Networkish | Promise<Network>) {
752
+ super();
753
+
754
+ // Events being listened to
755
+ this._events = [];
756
+
757
+ this._emitted = { block: -2 };
758
+
759
+ this.disableCcipRead = false;
760
+
761
+ this.formatter = new.target.getFormatter();
762
+
763
+ // If network is any, this Provider allows the underlying
764
+ // network to change dynamically, and we auto-detect the
765
+ // current network
766
+ defineReadOnly(this, "anyNetwork", (network === "any"));
767
+ if (this.anyNetwork) { network = this.detectNetwork(); }
768
+
769
+ if (network instanceof Promise) {
770
+ this._networkPromise = network;
771
+
772
+ // Squash any "unhandled promise" errors; that do not need to be handled
773
+ network.catch((error) => { });
774
+
775
+ // Trigger initial network setting (async)
776
+ this._ready().catch((error) => { });
777
+
778
+ } else {
779
+ const knownNetwork = getStatic<(network: Networkish) => Network>(new.target, "getNetwork")(network);
780
+ if (knownNetwork) {
781
+ defineReadOnly(this, "_network", knownNetwork);
782
+ this.emit("network", knownNetwork, null);
783
+
784
+ } else {
785
+ logger.throwArgumentError("invalid network", "network", network);
786
+ }
787
+ }
788
+
789
+ this._maxInternalBlockNumber = -1024;
790
+
791
+ this._lastBlockNumber = -2;
792
+ this._maxFilterBlockRange = 10;
793
+
794
+ this._pollingInterval = 4000;
795
+
796
+ this._fastQueryDate = 0;
797
+ }
798
+
799
+ async _ready(): Promise<Network> {
800
+ if (this._network == null) {
801
+ let network: Network = null;
802
+ if (this._networkPromise) {
803
+ try {
804
+ network = await this._networkPromise;
805
+ } catch (error) { }
806
+ }
807
+
808
+ // Try the Provider's network detection (this MUST throw if it cannot)
809
+ if (network == null) {
810
+ network = await this.detectNetwork();
811
+ }
812
+
813
+ // This should never happen; every Provider sub-class should have
814
+ // suggested a network by here (or have thrown).
815
+ if (!network) {
816
+ logger.throwError("no network detected", Logger.errors.UNKNOWN_ERROR, { });
817
+ }
818
+
819
+ // Possible this call stacked so do not call defineReadOnly again
820
+ if (this._network == null) {
821
+ if (this.anyNetwork) {
822
+ this._network = network;
823
+ } else {
824
+ defineReadOnly(this, "_network", network);
825
+ }
826
+ this.emit("network", network, null);
827
+ }
828
+ }
829
+
830
+ return this._network;
831
+ }
832
+
833
+ // This will always return the most recently established network.
834
+ // For "any", this can change (a "network" event is emitted before
835
+ // any change is reflected); otherwise this cannot change
836
+ get ready(): Promise<Network> {
837
+ return poll(() => {
838
+ return this._ready().then((network) => {
839
+ return network;
840
+ }, (error): any => {
841
+ // If the network isn't running yet, we will wait
842
+ if (error.code === Logger.errors.NETWORK_ERROR && error.event === "noNetwork") {
843
+ return undefined;
844
+ }
845
+ throw error;
846
+ });
847
+ });
848
+ }
849
+
850
+ // @TODO: Remove this and just create a singleton formatter
851
+ static getFormatter(): Formatter {
852
+ if (defaultFormatter == null) {
853
+ defaultFormatter = new Formatter();
854
+ }
855
+ return defaultFormatter;
856
+ }
857
+
858
+ // @TODO: Remove this and just use getNetwork
859
+ static getNetwork(network: Networkish): Network {
860
+ return getNetwork((network == null) ? "homestead": network);
861
+ }
862
+
863
+ async ccipReadFetch(tx: Transaction, calldata: string, urls: Array<string>): Promise<null | string> {
864
+ if (this.disableCcipRead || urls.length === 0) { return null; }
865
+
866
+ const sender = tx.to.toLowerCase();
867
+ const data = calldata.toLowerCase();
868
+
869
+ const errorMessages: Array<string> = [ ];
870
+
871
+ for (let i = 0; i < urls.length; i++) {
872
+ const url = urls[i];
873
+
874
+ // URL expansion
875
+ const href = url.replace("{sender}", sender).replace("{data}", data);
876
+
877
+ // If no {data} is present, use POST; otherwise GET
878
+ const json: string | null = (url.indexOf("{data}") >= 0) ? null: JSON.stringify({ data, sender });
879
+
880
+ const result = await fetchJson({ url: href, errorPassThrough: true }, json, (value, response) => {
881
+ value.status = response.statusCode;
882
+ return value;
883
+ });
884
+
885
+ if (result.data) { return result.data; }
886
+
887
+ const errorMessage = (result.message || "unknown error");
888
+
889
+ // 4xx indicates the result is not present; stop
890
+ if (result.status >= 400 && result.status < 500) {
891
+ return logger.throwError(`response not found during CCIP fetch: ${ errorMessage }`, Logger.errors.SERVER_ERROR, { url, errorMessage });
892
+ }
893
+
894
+ // 5xx indicates server issue; try the next url
895
+ errorMessages.push(errorMessage);
896
+ }
897
+
898
+ return logger.throwError(`error encountered during CCIP fetch: ${ errorMessages.map((m) => JSON.stringify(m)).join(", ") }`, Logger.errors.SERVER_ERROR, {
899
+ urls, errorMessages
900
+ });
901
+ }
902
+
903
+ // Fetches the blockNumber, but will reuse any result that is less
904
+ // than maxAge old or has been requested since the last request
905
+ async _getInternalBlockNumber(maxAge: number): Promise<number> {
906
+ await this._ready();
907
+
908
+ // Allowing stale data up to maxAge old
909
+ if (maxAge > 0) {
910
+
911
+ // While there are pending internal block requests...
912
+ while (this._internalBlockNumber) {
913
+
914
+ // ..."remember" which fetch we started with
915
+ const internalBlockNumber = this._internalBlockNumber;
916
+
917
+ try {
918
+ // Check the result is not too stale
919
+ const result = await internalBlockNumber;
920
+ if ((getTime() - result.respTime) <= maxAge) {
921
+ return result.blockNumber;
922
+ }
923
+
924
+ // Too old; fetch a new value
925
+ break;
926
+
927
+ } catch(error) {
928
+
929
+ // The fetch rejected; if we are the first to get the
930
+ // rejection, drop through so we replace it with a new
931
+ // fetch; all others blocked will then get that fetch
932
+ // which won't match the one they "remembered" and loop
933
+ if (this._internalBlockNumber === internalBlockNumber) {
934
+ break;
935
+ }
936
+ }
937
+ }
938
+ }
939
+
940
+ const reqTime = getTime();
941
+
942
+ const checkInternalBlockNumber = resolveProperties({
943
+ blockNumber: this.perform("getBlockNumber", { }),
944
+ networkError: this.getNetwork().then((network): any => (null), (error) => (error))
945
+ }).then(({ blockNumber, networkError }) => {
946
+ if (networkError) {
947
+ // Unremember this bad internal block number
948
+ if (this._internalBlockNumber === checkInternalBlockNumber) {
949
+ this._internalBlockNumber = null;
950
+ }
951
+ throw networkError;
952
+ }
953
+
954
+ const respTime = getTime();
955
+
956
+ blockNumber = BigNumber.from(blockNumber).toNumber();
957
+ if (blockNumber < this._maxInternalBlockNumber) { blockNumber = this._maxInternalBlockNumber; }
958
+
959
+ this._maxInternalBlockNumber = blockNumber;
960
+ this._setFastBlockNumber(blockNumber); // @TODO: Still need this?
961
+ return { blockNumber, reqTime, respTime };
962
+ });
963
+
964
+ this._internalBlockNumber = checkInternalBlockNumber;
965
+
966
+ // Swallow unhandled exceptions; if needed they are handled else where
967
+ checkInternalBlockNumber.catch((error) => {
968
+ // Don't null the dead (rejected) fetch, if it has already been updated
969
+ if (this._internalBlockNumber === checkInternalBlockNumber) {
970
+ this._internalBlockNumber = null;
971
+ }
972
+ });
973
+
974
+ return (await checkInternalBlockNumber).blockNumber;
975
+ }
976
+
977
+ async poll(): Promise<void> {
978
+ const pollId = nextPollId++;
979
+
980
+ // Track all running promises, so we can trigger a post-poll once they are complete
981
+ const runners: Array<Promise<void>> = [];
982
+
983
+ let blockNumber: number = null;
984
+ try {
985
+ blockNumber = await this._getInternalBlockNumber(100 + this.pollingInterval / 2);
986
+ } catch (error) {
987
+ this.emit("error", error);
988
+ return;
989
+ }
990
+ this._setFastBlockNumber(blockNumber);
991
+
992
+ // Emit a poll event after we have the latest (fast) block number
993
+ this.emit("poll", pollId, blockNumber);
994
+
995
+ // If the block has not changed, meh.
996
+ if (blockNumber === this._lastBlockNumber) {
997
+ this.emit("didPoll", pollId);
998
+ return;
999
+ }
1000
+
1001
+ // First polling cycle, trigger a "block" events
1002
+ if (this._emitted.block === -2) {
1003
+ this._emitted.block = blockNumber - 1;
1004
+ }
1005
+
1006
+ if (Math.abs((<number>(this._emitted.block)) - blockNumber) > 1000) {
1007
+ logger.warn(`network block skew detected; skipping block events (emitted=${ this._emitted.block } blockNumber${ blockNumber })`);
1008
+ this.emit("error", logger.makeError("network block skew detected", Logger.errors.NETWORK_ERROR, {
1009
+ blockNumber: blockNumber,
1010
+ event: "blockSkew",
1011
+ previousBlockNumber: this._emitted.block
1012
+ }));
1013
+ this.emit("block", blockNumber);
1014
+
1015
+ } else {
1016
+ // Notify all listener for each block that has passed
1017
+ for (let i = (<number>this._emitted.block) + 1; i <= blockNumber; i++) {
1018
+ this.emit("block", i);
1019
+ }
1020
+ }
1021
+
1022
+ // The emitted block was updated, check for obsolete events
1023
+ if ((<number>this._emitted.block) !== blockNumber) {
1024
+ this._emitted.block = blockNumber;
1025
+
1026
+ Object.keys(this._emitted).forEach((key) => {
1027
+ // The block event does not expire
1028
+ if (key === "block") { return; }
1029
+
1030
+ // The block we were at when we emitted this event
1031
+ const eventBlockNumber = this._emitted[key];
1032
+
1033
+ // We cannot garbage collect pending transactions or blocks here
1034
+ // They should be garbage collected by the Provider when setting
1035
+ // "pending" events
1036
+ if (eventBlockNumber === "pending") { return; }
1037
+
1038
+ // Evict any transaction hashes or block hashes over 12 blocks
1039
+ // old, since they should not return null anyways
1040
+ if (blockNumber - eventBlockNumber > 12) {
1041
+ delete this._emitted[key];
1042
+ }
1043
+ });
1044
+ }
1045
+
1046
+ // First polling cycle
1047
+ if (this._lastBlockNumber === -2) {
1048
+ this._lastBlockNumber = blockNumber - 1;
1049
+ }
1050
+ // Find all transaction hashes we are waiting on
1051
+ this._events.forEach((event) => {
1052
+ switch (event.type) {
1053
+ case "tx": {
1054
+ const hash = event.hash;
1055
+ let runner = this.getTransactionReceipt(hash).then((receipt): any => {
1056
+ if (!receipt || receipt.blockNumber == null) { return null; }
1057
+ this._emitted["t:" + hash] = receipt.blockNumber;
1058
+ this.emit(hash, receipt);
1059
+ return null;
1060
+ }).catch((error: Error) => { this.emit("error", error); });
1061
+
1062
+ runners.push(runner);
1063
+
1064
+ break;
1065
+ }
1066
+
1067
+ case "filter": {
1068
+ // We only allow a single getLogs to be in-flight at a time
1069
+ if (!event._inflight) {
1070
+ event._inflight = true;
1071
+
1072
+ // This is the first filter for this event, so we want to
1073
+ // restrict events to events that happened no earlier than now
1074
+ if (event._lastBlockNumber === -2) {
1075
+ event._lastBlockNumber = blockNumber - 1;
1076
+ }
1077
+
1078
+ // Filter from the last *known* event; due to load-balancing
1079
+ // and some nodes returning updated block numbers before
1080
+ // indexing events, a logs result with 0 entries cannot be
1081
+ // trusted and we must retry a range which includes it again
1082
+ const filter = event.filter;
1083
+ filter.fromBlock = event._lastBlockNumber + 1;
1084
+ filter.toBlock = blockNumber;
1085
+
1086
+ // Prevent fitler ranges from growing too wild, since it is quite
1087
+ // likely there just haven't been any events to move the lastBlockNumber.
1088
+ const minFromBlock = filter.toBlock - this._maxFilterBlockRange;
1089
+ if (minFromBlock > filter.fromBlock) { filter.fromBlock = minFromBlock; }
1090
+
1091
+ if (filter.fromBlock < 0) { filter.fromBlock = 0; }
1092
+
1093
+ const runner = this.getLogs(filter).then((logs) => {
1094
+ // Allow the next getLogs
1095
+ event._inflight = false;
1096
+
1097
+ if (logs.length === 0) { return; }
1098
+
1099
+ logs.forEach((log: Log) => {
1100
+ // Only when we get an event for a given block number
1101
+ // can we trust the events are indexed
1102
+ if (log.blockNumber > event._lastBlockNumber) {
1103
+ event._lastBlockNumber = log.blockNumber;
1104
+ }
1105
+
1106
+ // Make sure we stall requests to fetch blocks and txs
1107
+ this._emitted["b:" + log.blockHash] = log.blockNumber;
1108
+ this._emitted["t:" + log.transactionHash] = log.blockNumber;
1109
+
1110
+ this.emit(filter, log);
1111
+ });
1112
+ }).catch((error: Error) => {
1113
+ this.emit("error", error);
1114
+
1115
+ // Allow another getLogs (the range was not updated)
1116
+ event._inflight = false;
1117
+ });
1118
+ runners.push(runner);
1119
+ }
1120
+
1121
+ break;
1122
+ }
1123
+ }
1124
+ });
1125
+
1126
+ this._lastBlockNumber = blockNumber;
1127
+
1128
+ // Once all events for this loop have been processed, emit "didPoll"
1129
+ Promise.all(runners).then(() => {
1130
+ this.emit("didPoll", pollId);
1131
+ }).catch((error) => { this.emit("error", error); });
1132
+
1133
+ return;
1134
+ }
1135
+
1136
+ // Deprecated; do not use this
1137
+ resetEventsBlock(blockNumber: number): void {
1138
+ this._lastBlockNumber = blockNumber - 1;
1139
+ if (this.polling) { this.poll(); }
1140
+ }
1141
+
1142
+ get network(): Network {
1143
+ return this._network;
1144
+ }
1145
+
1146
+ // This method should query the network if the underlying network
1147
+ // can change, such as when connected to a JSON-RPC backend
1148
+ async detectNetwork(): Promise<Network> {
1149
+ return logger.throwError("provider does not support network detection", Logger.errors.UNSUPPORTED_OPERATION, {
1150
+ operation: "provider.detectNetwork"
1151
+ });
1152
+ }
1153
+
1154
+ async getNetwork(): Promise<Network> {
1155
+ const network = await this._ready();
1156
+
1157
+ // Make sure we are still connected to the same network; this is
1158
+ // only an external call for backends which can have the underlying
1159
+ // network change spontaneously
1160
+ const currentNetwork = await this.detectNetwork();
1161
+ if (network.chainId !== currentNetwork.chainId) {
1162
+
1163
+ // We are allowing network changes, things can get complex fast;
1164
+ // make sure you know what you are doing if you use "any"
1165
+ if (this.anyNetwork) {
1166
+ this._network = currentNetwork;
1167
+
1168
+ // Reset all internal block number guards and caches
1169
+ this._lastBlockNumber = -2;
1170
+ this._fastBlockNumber = null;
1171
+ this._fastBlockNumberPromise = null;
1172
+ this._fastQueryDate = 0;
1173
+ this._emitted.block = -2;
1174
+ this._maxInternalBlockNumber = -1024;
1175
+ this._internalBlockNumber = null;
1176
+
1177
+ // The "network" event MUST happen before this method resolves
1178
+ // so any events have a chance to unregister, so we stall an
1179
+ // additional event loop before returning from /this/ call
1180
+ this.emit("network", currentNetwork, network);
1181
+ await stall(0);
1182
+
1183
+ return this._network;
1184
+ }
1185
+
1186
+ const error = logger.makeError("underlying network changed", Logger.errors.NETWORK_ERROR, {
1187
+ event: "changed",
1188
+ network: network,
1189
+ detectedNetwork: currentNetwork
1190
+ });
1191
+
1192
+ this.emit("error", error);
1193
+ throw error;
1194
+ }
1195
+
1196
+ return network;
1197
+ }
1198
+
1199
+ get blockNumber(): number {
1200
+ this._getInternalBlockNumber(100 + this.pollingInterval / 2).then((blockNumber) => {
1201
+ this._setFastBlockNumber(blockNumber);
1202
+ }, (error) => { });
1203
+
1204
+ return (this._fastBlockNumber != null) ? this._fastBlockNumber: -1;
1205
+ }
1206
+
1207
+ get polling(): boolean {
1208
+ return (this._poller != null);
1209
+ }
1210
+
1211
+ set polling(value: boolean) {
1212
+ if (value && !this._poller) {
1213
+ this._poller = setInterval(() => { this.poll(); }, this.pollingInterval);
1214
+
1215
+ if (!this._bootstrapPoll) {
1216
+ this._bootstrapPoll = setTimeout(() => {
1217
+ this.poll();
1218
+
1219
+ // We block additional polls until the polling interval
1220
+ // is done, to prevent overwhelming the poll function
1221
+ this._bootstrapPoll = setTimeout(() => {
1222
+ // If polling was disabled, something may require a poke
1223
+ // since starting the bootstrap poll and it was disabled
1224
+ if (!this._poller) { this.poll(); }
1225
+
1226
+ // Clear out the bootstrap so we can do another
1227
+ this._bootstrapPoll = null;
1228
+ }, this.pollingInterval);
1229
+ }, 0);
1230
+ }
1231
+
1232
+ } else if (!value && this._poller) {
1233
+ clearInterval(this._poller);
1234
+ this._poller = null;
1235
+ }
1236
+ }
1237
+
1238
+ get pollingInterval(): number {
1239
+ return this._pollingInterval;
1240
+ }
1241
+
1242
+ set pollingInterval(value: number) {
1243
+ if (typeof(value) !== "number" || value <= 0 || parseInt(String(value)) != value) {
1244
+ throw new Error("invalid polling interval");
1245
+ }
1246
+
1247
+ this._pollingInterval = value;
1248
+
1249
+ if (this._poller) {
1250
+ clearInterval(this._poller);
1251
+ this._poller = setInterval(() => { this.poll(); }, this._pollingInterval);
1252
+ }
1253
+ }
1254
+
1255
+ _getFastBlockNumber(): Promise<number> {
1256
+ const now = getTime();
1257
+
1258
+ // Stale block number, request a newer value
1259
+ if ((now - this._fastQueryDate) > 2 * this._pollingInterval) {
1260
+ this._fastQueryDate = now;
1261
+ this._fastBlockNumberPromise = this.getBlockNumber().then((blockNumber) => {
1262
+ if (this._fastBlockNumber == null || blockNumber > this._fastBlockNumber) {
1263
+ this._fastBlockNumber = blockNumber;
1264
+ }
1265
+ return this._fastBlockNumber;
1266
+ });
1267
+ }
1268
+
1269
+ return this._fastBlockNumberPromise;
1270
+ }
1271
+
1272
+ _setFastBlockNumber(blockNumber: number): void {
1273
+ // Older block, maybe a stale request
1274
+ if (this._fastBlockNumber != null && blockNumber < this._fastBlockNumber) { return; }
1275
+
1276
+ // Update the time we updated the blocknumber
1277
+ this._fastQueryDate = getTime();
1278
+
1279
+ // Newer block number, use it
1280
+ if (this._fastBlockNumber == null || blockNumber > this._fastBlockNumber) {
1281
+ this._fastBlockNumber = blockNumber;
1282
+ this._fastBlockNumberPromise = Promise.resolve(blockNumber);
1283
+ }
1284
+ }
1285
+
1286
+ async waitForTransaction(transactionHash: string, confirmations?: number, timeout?: number): Promise<TransactionReceipt> {
1287
+ return this._waitForTransaction(transactionHash, (confirmations == null) ? 1: confirmations, timeout || 0, null);
1288
+ }
1289
+
1290
+ async _waitForTransaction(transactionHash: string, confirmations: number, timeout: number, replaceable: { data: string, from: string, nonce: number, to: string, value: BigNumber, startBlock: number }): Promise<TransactionReceipt> {
1291
+ const receipt = await this.getTransactionReceipt(transactionHash);
1292
+
1293
+ // Receipt is already good
1294
+ if ((receipt ? receipt.confirmations: 0) >= confirmations) { return receipt; }
1295
+
1296
+ // Poll until the receipt is good...
1297
+ return new Promise((resolve, reject) => {
1298
+ const cancelFuncs: Array<() => void> = [];
1299
+
1300
+ let done = false;
1301
+ const alreadyDone = function() {
1302
+ if (done) { return true; }
1303
+ done = true;
1304
+ cancelFuncs.forEach((func) => { func(); });
1305
+ return false;
1306
+ };
1307
+
1308
+ const minedHandler = (receipt: TransactionReceipt) => {
1309
+ if (receipt.confirmations < confirmations) { return; }
1310
+ if (alreadyDone()) { return; }
1311
+ resolve(receipt);
1312
+ }
1313
+ this.on(transactionHash, minedHandler);
1314
+ cancelFuncs.push(() => { this.removeListener(transactionHash, minedHandler); });
1315
+
1316
+ if (replaceable) {
1317
+ let lastBlockNumber = replaceable.startBlock;
1318
+ let scannedBlock: number = null;
1319
+ const replaceHandler = async (blockNumber: number) => {
1320
+ if (done) { return; }
1321
+
1322
+ // Wait 1 second; this is only used in the case of a fault, so
1323
+ // we will trade off a little bit of latency for more consistent
1324
+ // results and fewer JSON-RPC calls
1325
+ await stall(1000);
1326
+
1327
+ this.getTransactionCount(replaceable.from).then(async (nonce) => {
1328
+ if (done) { return; }
1329
+
1330
+ if (nonce <= replaceable.nonce) {
1331
+ lastBlockNumber = blockNumber;
1332
+
1333
+ } else {
1334
+ // First check if the transaction was mined
1335
+ {
1336
+ const mined = await this.getTransaction(transactionHash);
1337
+ if (mined && mined.blockNumber != null) { return; }
1338
+ }
1339
+
1340
+ // First time scanning. We start a little earlier for some
1341
+ // wiggle room here to handle the eventually consistent nature
1342
+ // of blockchain (e.g. the getTransactionCount was for a
1343
+ // different block)
1344
+ if (scannedBlock == null) {
1345
+ scannedBlock = lastBlockNumber - 3;
1346
+ if (scannedBlock < replaceable.startBlock) {
1347
+ scannedBlock = replaceable.startBlock;
1348
+ }
1349
+ }
1350
+
1351
+ while (scannedBlock <= blockNumber) {
1352
+ if (done) { return; }
1353
+
1354
+ const block = await this.getBlockWithTransactions(scannedBlock);
1355
+ for (let ti = 0; ti < block.transactions.length; ti++) {
1356
+ const tx = block.transactions[ti];
1357
+
1358
+ // Successfully mined!
1359
+ if (tx.hash === transactionHash) { return; }
1360
+
1361
+ // Matches our transaction from and nonce; its a replacement
1362
+ if (tx.from === replaceable.from && tx.nonce === replaceable.nonce) {
1363
+ if (done) { return; }
1364
+
1365
+ // Get the receipt of the replacement
1366
+ const receipt = await this.waitForTransaction(tx.hash, confirmations);
1367
+
1368
+ // Already resolved or rejected (prolly a timeout)
1369
+ if (alreadyDone()) { return; }
1370
+
1371
+ // The reason we were replaced
1372
+ let reason = "replaced";
1373
+ if (tx.data === replaceable.data && tx.to === replaceable.to && tx.value.eq(replaceable.value)) {
1374
+ reason = "repriced";
1375
+ } else if (tx.data === "0x" && tx.from === tx.to && tx.value.isZero()) {
1376
+ reason = "cancelled"
1377
+ }
1378
+
1379
+ // Explain why we were replaced
1380
+ reject(logger.makeError("transaction was replaced", Logger.errors.TRANSACTION_REPLACED, {
1381
+ cancelled: (reason === "replaced" || reason === "cancelled"),
1382
+ reason,
1383
+ replacement: this._wrapTransaction(tx),
1384
+ hash: transactionHash,
1385
+ receipt
1386
+ }));
1387
+
1388
+ return;
1389
+ }
1390
+ }
1391
+ scannedBlock++;
1392
+ }
1393
+ }
1394
+
1395
+ if (done) { return; }
1396
+ this.once("block", replaceHandler);
1397
+
1398
+ }, (error) => {
1399
+ if (done) { return; }
1400
+ this.once("block", replaceHandler);
1401
+ });
1402
+ };
1403
+
1404
+ if (done) { return; }
1405
+ this.once("block", replaceHandler);
1406
+
1407
+ cancelFuncs.push(() => {
1408
+ this.removeListener("block", replaceHandler);
1409
+ });
1410
+ }
1411
+
1412
+ if (typeof(timeout) === "number" && timeout > 0) {
1413
+ const timer = setTimeout(() => {
1414
+ if (alreadyDone()) { return; }
1415
+ reject(logger.makeError("timeout exceeded", Logger.errors.TIMEOUT, { timeout: timeout }));
1416
+ }, timeout);
1417
+ if (timer.unref) { timer.unref(); }
1418
+
1419
+ cancelFuncs.push(() => { clearTimeout(timer); });
1420
+ }
1421
+ });
1422
+ }
1423
+
1424
+ async getBlockNumber(): Promise<number> {
1425
+ return this._getInternalBlockNumber(0);
1426
+ }
1427
+
1428
+ async getGasPrice(): Promise<BigNumber> {
1429
+ await this.getNetwork();
1430
+
1431
+ const result = await this.perform("getGasPrice", { });
1432
+ try {
1433
+ return BigNumber.from(result);
1434
+ } catch (error) {
1435
+ return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
1436
+ method: "getGasPrice",
1437
+ result, error
1438
+ });
1439
+ }
1440
+ }
1441
+
1442
+ async getBalance(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<BigNumber> {
1443
+ await this.getNetwork();
1444
+ const params = await resolveProperties({
1445
+ address: this._getAddress(addressOrName),
1446
+ blockTag: this._getBlockTag(blockTag)
1447
+ });
1448
+
1449
+ const result = await this.perform("getBalance", params);
1450
+ try {
1451
+ return BigNumber.from(result);
1452
+ } catch (error) {
1453
+ return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
1454
+ method: "getBalance",
1455
+ params, result, error
1456
+ });
1457
+ }
1458
+ }
1459
+
1460
+ async getTransactionCount(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<number> {
1461
+ await this.getNetwork();
1462
+ const params = await resolveProperties({
1463
+ address: this._getAddress(addressOrName),
1464
+ blockTag: this._getBlockTag(blockTag)
1465
+ });
1466
+
1467
+ const result = await this.perform("getTransactionCount", params);
1468
+ try {
1469
+ return BigNumber.from(result).toNumber();
1470
+ } catch (error) {
1471
+ return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
1472
+ method: "getTransactionCount",
1473
+ params, result, error
1474
+ });
1475
+ }
1476
+ }
1477
+
1478
+ async getCode(addressOrName: string | Promise<string>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string> {
1479
+ await this.getNetwork();
1480
+ const params = await resolveProperties({
1481
+ address: this._getAddress(addressOrName),
1482
+ blockTag: this._getBlockTag(blockTag)
1483
+ });
1484
+
1485
+ const result = await this.perform("getCode", params);
1486
+ try {
1487
+ return hexlify(result);
1488
+ } catch (error) {
1489
+ return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
1490
+ method: "getCode",
1491
+ params, result, error
1492
+ });
1493
+ }
1494
+ }
1495
+
1496
+ async getStorageAt(addressOrName: string | Promise<string>, position: BigNumberish | Promise<BigNumberish>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string> {
1497
+ await this.getNetwork();
1498
+ const params = await resolveProperties({
1499
+ address: this._getAddress(addressOrName),
1500
+ blockTag: this._getBlockTag(blockTag),
1501
+ position: Promise.resolve(position).then((p) => hexValue(p))
1502
+ });
1503
+ const result = await this.perform("getStorageAt", params);
1504
+ try {
1505
+ return hexlify(result);
1506
+ } catch (error) {
1507
+ return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
1508
+ method: "getStorageAt",
1509
+ params, result, error
1510
+ });
1511
+ }
1512
+ }
1513
+
1514
+ // This should be called by any subclass wrapping a TransactionResponse
1515
+ _wrapTransaction(tx: Transaction, hash?: string, startBlock?: number): TransactionResponse {
1516
+ if (hash != null && hexDataLength(hash) !== 32) { throw new Error("invalid response - sendTransaction"); }
1517
+
1518
+ const result = <TransactionResponse>tx;
1519
+
1520
+ // Check the hash we expect is the same as the hash the server reported
1521
+ if (hash != null && tx.hash !== hash) {
1522
+ logger.throwError("Transaction hash mismatch from Provider.sendTransaction.", Logger.errors.UNKNOWN_ERROR, { expectedHash: tx.hash, returnedHash: hash });
1523
+ }
1524
+
1525
+ result.wait = async (confirms?: number, timeout?: number) => {
1526
+ if (confirms == null) { confirms = 1; }
1527
+ if (timeout == null) { timeout = 0; }
1528
+
1529
+ // Get the details to detect replacement
1530
+ let replacement = undefined;
1531
+ if (confirms !== 0 && startBlock != null) {
1532
+ replacement = {
1533
+ data: tx.data,
1534
+ from: tx.from,
1535
+ nonce: tx.nonce,
1536
+ to: tx.to,
1537
+ value: tx.value,
1538
+ startBlock
1539
+ };
1540
+ }
1541
+
1542
+ const receipt = await this._waitForTransaction(tx.hash, confirms, timeout, replacement);
1543
+ if (receipt == null && confirms === 0) { return null; }
1544
+
1545
+ // No longer pending, allow the polling loop to garbage collect this
1546
+ this._emitted["t:" + tx.hash] = receipt.blockNumber;
1547
+
1548
+ if (receipt.status === 0) {
1549
+ logger.throwError("transaction failed", Logger.errors.CALL_EXCEPTION, {
1550
+ transactionHash: tx.hash,
1551
+ transaction: tx,
1552
+ receipt: receipt
1553
+ });
1554
+ }
1555
+ return receipt;
1556
+ };
1557
+
1558
+ return result;
1559
+ }
1560
+
1561
+ async sendTransaction(signedTransaction: string | Promise<string>): Promise<TransactionResponse> {
1562
+ await this.getNetwork();
1563
+ const hexTx = await Promise.resolve(signedTransaction).then(t => hexlify(t));
1564
+ const tx = this.formatter.transaction(signedTransaction);
1565
+ if (tx.confirmations == null) { tx.confirmations = 0; }
1566
+ const blockNumber = await this._getInternalBlockNumber(100 + 2 * this.pollingInterval);
1567
+ try {
1568
+ const hash = await this.perform("sendTransaction", { signedTransaction: hexTx });
1569
+ return this._wrapTransaction(tx, hash, blockNumber);
1570
+ } catch (error) {
1571
+ (<any>error).transaction = tx;
1572
+ (<any>error).transactionHash = tx.hash;
1573
+ throw error;
1574
+ }
1575
+ }
1576
+
1577
+ async _getTransactionRequest(transaction: Deferrable<TransactionRequest>): Promise<Transaction> {
1578
+ const values: any = await transaction;
1579
+
1580
+ const tx: any = { };
1581
+
1582
+ ["from", "to"].forEach((key) => {
1583
+ if (values[key] == null) { return; }
1584
+ tx[key] = Promise.resolve(values[key]).then((v) => (v ? this._getAddress(v): null))
1585
+ });
1586
+
1587
+ ["gasLimit", "gasPrice", "maxFeePerGas", "maxPriorityFeePerGas", "value"].forEach((key) => {
1588
+ if (values[key] == null) { return; }
1589
+ tx[key] = Promise.resolve(values[key]).then((v) => (v ? BigNumber.from(v): null));
1590
+ });
1591
+
1592
+ ["type"].forEach((key) => {
1593
+ if (values[key] == null) { return; }
1594
+ tx[key] = Promise.resolve(values[key]).then((v) => ((v != null) ? v: null));
1595
+ });
1596
+
1597
+ if (values.accessList) {
1598
+ tx.accessList = this.formatter.accessList(values.accessList);
1599
+ }
1600
+
1601
+ ["data"].forEach((key) => {
1602
+ if (values[key] == null) { return; }
1603
+ tx[key] = Promise.resolve(values[key]).then((v) => (v ? hexlify(v): null));
1604
+ });
1605
+
1606
+ return this.formatter.transactionRequest(await resolveProperties(tx));
1607
+ }
1608
+
1609
+ async _getFilter(filter: Filter | FilterByBlockHash | Promise<Filter | FilterByBlockHash>): Promise<Filter | FilterByBlockHash> {
1610
+ filter = await filter;
1611
+
1612
+ const result: any = { };
1613
+
1614
+ if (filter.address != null) {
1615
+ result.address = this._getAddress(filter.address);
1616
+ }
1617
+
1618
+ ["blockHash", "topics"].forEach((key) => {
1619
+ if ((<any>filter)[key] == null) { return; }
1620
+ result[key] = (<any>filter)[key];
1621
+ });
1622
+
1623
+ ["fromBlock", "toBlock"].forEach((key) => {
1624
+ if ((<any>filter)[key] == null) { return; }
1625
+ result[key] = this._getBlockTag((<any>filter)[key]);
1626
+ });
1627
+
1628
+ return this.formatter.filter(await resolveProperties(result));
1629
+ }
1630
+
1631
+ async _call(transaction: TransactionRequest, blockTag: BlockTag, attempt: number): Promise<string> {
1632
+ if (attempt >= MAX_CCIP_REDIRECTS) {
1633
+ logger.throwError("CCIP read exceeded maximum redirections", Logger.errors.SERVER_ERROR, {
1634
+ redirects: attempt, transaction
1635
+ });
1636
+ }
1637
+
1638
+ const txSender = transaction.to;
1639
+
1640
+ const result = await this.perform("call", { transaction, blockTag });
1641
+
1642
+ // CCIP Read request via OffchainLookup(address,string[],bytes,bytes4,bytes)
1643
+ if (attempt >= 0 && blockTag === "latest" && txSender != null && result.substring(0, 10) === "0x556f1830" && (hexDataLength(result) % 32 === 4)) {
1644
+ try {
1645
+ const data = hexDataSlice(result, 4);
1646
+
1647
+ // Check the sender of the OffchainLookup matches the transaction
1648
+ const sender = hexDataSlice(data, 0, 32);
1649
+ if (!BigNumber.from(sender).eq(txSender)) {
1650
+ logger.throwError("CCIP Read sender did not match", Logger.errors.CALL_EXCEPTION, {
1651
+ name: "OffchainLookup",
1652
+ signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
1653
+ transaction, data: result
1654
+ });
1655
+ }
1656
+
1657
+ // Read the URLs from the response
1658
+ const urls: Array<string> = [];
1659
+ const urlsOffset = BigNumber.from(hexDataSlice(data, 32, 64)).toNumber();
1660
+ const urlsLength = BigNumber.from(hexDataSlice(data, urlsOffset, urlsOffset + 32)).toNumber();
1661
+ const urlsData = hexDataSlice(data, urlsOffset + 32);
1662
+ for (let u = 0; u < urlsLength; u++) {
1663
+ const url = _parseString(urlsData, u * 32);
1664
+ if (url == null) {
1665
+ logger.throwError("CCIP Read contained corrupt URL string", Logger.errors.CALL_EXCEPTION, {
1666
+ name: "OffchainLookup",
1667
+ signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
1668
+ transaction, data: result
1669
+ });
1670
+ }
1671
+ urls.push(url);
1672
+ }
1673
+
1674
+ // Get the CCIP calldata to forward
1675
+ const calldata = _parseBytes(data, 64);
1676
+
1677
+ // Get the callbackSelector (bytes4)
1678
+ if (!BigNumber.from(hexDataSlice(data, 100, 128)).isZero()) {
1679
+ logger.throwError("CCIP Read callback selector included junk", Logger.errors.CALL_EXCEPTION, {
1680
+ name: "OffchainLookup",
1681
+ signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
1682
+ transaction, data: result
1683
+ });
1684
+ }
1685
+ const callbackSelector = hexDataSlice(data, 96, 100);
1686
+
1687
+ // Get the extra data to send back to the contract as context
1688
+ const extraData = _parseBytes(data, 128);
1689
+
1690
+ const ccipResult = await this.ccipReadFetch(<Transaction>transaction, calldata, urls);
1691
+ if (ccipResult == null) {
1692
+ logger.throwError("CCIP Read disabled or provided no URLs", Logger.errors.CALL_EXCEPTION, {
1693
+ name: "OffchainLookup",
1694
+ signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
1695
+ transaction, data: result
1696
+ });
1697
+ }
1698
+
1699
+ const tx = {
1700
+ to: txSender,
1701
+ data: hexConcat([ callbackSelector, encodeBytes([ ccipResult, extraData ]) ])
1702
+ };
1703
+
1704
+ return this._call(tx, blockTag, attempt + 1);
1705
+
1706
+ } catch (error) {
1707
+ if (error.code === Logger.errors.SERVER_ERROR) { throw error; }
1708
+ }
1709
+ }
1710
+
1711
+ try {
1712
+ return hexlify(result);
1713
+ } catch (error) {
1714
+ return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
1715
+ method: "call",
1716
+ params: { transaction, blockTag }, result, error
1717
+ });
1718
+ }
1719
+
1720
+ }
1721
+
1722
+ async call(transaction: Deferrable<TransactionRequest>, blockTag?: BlockTag | Promise<BlockTag>): Promise<string> {
1723
+ await this.getNetwork();
1724
+ const resolved = await resolveProperties({
1725
+ transaction: this._getTransactionRequest(transaction),
1726
+ blockTag: this._getBlockTag(blockTag),
1727
+ ccipReadEnabled: Promise.resolve(transaction.ccipReadEnabled)
1728
+ });
1729
+ return this._call(resolved.transaction, resolved.blockTag, resolved.ccipReadEnabled ? 0: -1);
1730
+ }
1731
+
1732
+ async estimateGas(transaction: Deferrable<TransactionRequest>): Promise<BigNumber> {
1733
+ await this.getNetwork();
1734
+ const params = await resolveProperties({
1735
+ transaction: this._getTransactionRequest(transaction)
1736
+ });
1737
+
1738
+ const result = await this.perform("estimateGas", params);
1739
+ try {
1740
+ return BigNumber.from(result);
1741
+ } catch (error) {
1742
+ return logger.throwError("bad result from backend", Logger.errors.SERVER_ERROR, {
1743
+ method: "estimateGas",
1744
+ params, result, error
1745
+ });
1746
+ }
1747
+ }
1748
+
1749
+ async _getAddress(addressOrName: string | Promise<string>): Promise<string> {
1750
+ addressOrName = await addressOrName;
1751
+ if (typeof(addressOrName) !== "string") {
1752
+ logger.throwArgumentError("invalid address or ENS name", "name", addressOrName);
1753
+ }
1754
+
1755
+ const address = await this.resolveName(addressOrName);
1756
+ if (address == null) {
1757
+ logger.throwError("ENS name not configured", Logger.errors.UNSUPPORTED_OPERATION, {
1758
+ operation: `resolveName(${ JSON.stringify(addressOrName) })`
1759
+ });
1760
+ }
1761
+ return address;
1762
+ }
1763
+
1764
+ async _getBlock(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>, includeTransactions?: boolean): Promise<Block | BlockWithTransactions> {
1765
+ await this.getNetwork();
1766
+
1767
+ blockHashOrBlockTag = await blockHashOrBlockTag;
1768
+
1769
+ // If blockTag is a number (not "latest", etc), this is the block number
1770
+ let blockNumber = -128;
1771
+
1772
+ const params: { [key: string]: any } = {
1773
+ includeTransactions: !!includeTransactions
1774
+ };
1775
+
1776
+ if (isHexString(blockHashOrBlockTag, 32)) {
1777
+ params.blockHash = blockHashOrBlockTag;
1778
+ } else {
1779
+ try {
1780
+ params.blockTag = await this._getBlockTag(blockHashOrBlockTag);
1781
+ if (isHexString(params.blockTag)) {
1782
+ blockNumber = parseInt(params.blockTag.substring(2), 16);
1783
+ }
1784
+ } catch (error) {
1785
+ logger.throwArgumentError("invalid block hash or block tag", "blockHashOrBlockTag", blockHashOrBlockTag);
1786
+ }
1787
+ }
1788
+
1789
+ return poll(async () => {
1790
+ const block = await this.perform("getBlock", params);
1791
+
1792
+ // Block was not found
1793
+ if (block == null) {
1794
+
1795
+ // For blockhashes, if we didn't say it existed, that blockhash may
1796
+ // not exist. If we did see it though, perhaps from a log, we know
1797
+ // it exists, and this node is just not caught up yet.
1798
+ if (params.blockHash != null) {
1799
+ if (this._emitted["b:" + params.blockHash] == null) { return null; }
1800
+ }
1801
+
1802
+ // For block tags, if we are asking for a future block, we return null
1803
+ if (params.blockTag != null) {
1804
+ if (blockNumber > (this._emitted.block as number)) { return null; }
1805
+ }
1806
+
1807
+ // Retry on the next block
1808
+ return undefined;
1809
+ }
1810
+
1811
+ // Add transactions
1812
+ if (includeTransactions) {
1813
+ let blockNumber: number = null;
1814
+ for (let i = 0; i < block.transactions.length; i++) {
1815
+ const tx = block.transactions[i];
1816
+ if (tx.blockNumber == null) {
1817
+ tx.confirmations = 0;
1818
+
1819
+ } else if (tx.confirmations == null) {
1820
+ if (blockNumber == null) {
1821
+ blockNumber = await this._getInternalBlockNumber(100 + 2 * this.pollingInterval);
1822
+ }
1823
+
1824
+ // Add the confirmations using the fast block number (pessimistic)
1825
+ let confirmations = (blockNumber - tx.blockNumber) + 1;
1826
+ if (confirmations <= 0) { confirmations = 1; }
1827
+ tx.confirmations = confirmations;
1828
+ }
1829
+ }
1830
+
1831
+ const blockWithTxs: any = this.formatter.blockWithTransactions(block);
1832
+ blockWithTxs.transactions = blockWithTxs.transactions.map((tx: TransactionResponse) => this._wrapTransaction(tx));
1833
+ return blockWithTxs;
1834
+ }
1835
+ return this.formatter.block(block);
1836
+
1837
+ }, { oncePoll: this });
1838
+ }
1839
+
1840
+ getBlock(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>): Promise<Block> {
1841
+ return <Promise<Block>>(this._getBlock(blockHashOrBlockTag, false));
1842
+ }
1843
+
1844
+ getBlockWithTransactions(blockHashOrBlockTag: BlockTag | string | Promise<BlockTag | string>): Promise<BlockWithTransactions> {
1845
+ return <Promise<BlockWithTransactions>>(this._getBlock(blockHashOrBlockTag, true));
1846
+ }
1847
+
1848
+ async getTransaction(transactionHash: string | Promise<string>): Promise<TransactionResponse> {
1849
+ await this.getNetwork();
1850
+ transactionHash = await transactionHash;
1851
+
1852
+ const params = { transactionHash: this.formatter.hash(transactionHash, true) };
1853
+
1854
+ return poll(async () => {
1855
+ const result = await this.perform("getTransaction", params);
1856
+
1857
+ if (result == null) {
1858
+ if (this._emitted["t:" + transactionHash] == null) {
1859
+ return null;
1860
+ }
1861
+ return undefined;
1862
+ }
1863
+
1864
+ const tx = this.formatter.transactionResponse(result);
1865
+
1866
+ if (tx.blockNumber == null) {
1867
+ tx.confirmations = 0;
1868
+
1869
+ } else if (tx.confirmations == null) {
1870
+ const blockNumber = await this._getInternalBlockNumber(100 + 2 * this.pollingInterval);
1871
+
1872
+ // Add the confirmations using the fast block number (pessimistic)
1873
+ let confirmations = (blockNumber - tx.blockNumber) + 1;
1874
+ if (confirmations <= 0) { confirmations = 1; }
1875
+ tx.confirmations = confirmations;
1876
+ }
1877
+
1878
+ return this._wrapTransaction(tx);
1879
+ }, { oncePoll: this });
1880
+ }
1881
+
1882
+ async getTransactionReceipt(transactionHash: string | Promise<string>): Promise<TransactionReceipt> {
1883
+ await this.getNetwork();
1884
+
1885
+ transactionHash = await transactionHash;
1886
+
1887
+ const params = { transactionHash: this.formatter.hash(transactionHash, true) };
1888
+
1889
+ return poll(async () => {
1890
+ const result = await this.perform("getTransactionReceipt", params);
1891
+
1892
+ if (result == null) {
1893
+ if (this._emitted["t:" + transactionHash] == null) {
1894
+ return null;
1895
+ }
1896
+ return undefined;
1897
+ }
1898
+
1899
+ // "geth-etc" returns receipts before they are ready
1900
+ if (result.blockHash == null) { return undefined; }
1901
+
1902
+ const receipt = this.formatter.receipt(result);
1903
+
1904
+ if (receipt.blockNumber == null) {
1905
+ receipt.confirmations = 0;
1906
+
1907
+ } else if (receipt.confirmations == null) {
1908
+ const blockNumber = await this._getInternalBlockNumber(100 + 2 * this.pollingInterval);
1909
+
1910
+ // Add the confirmations using the fast block number (pessimistic)
1911
+ let confirmations = (blockNumber - receipt.blockNumber) + 1;
1912
+ if (confirmations <= 0) { confirmations = 1; }
1913
+ receipt.confirmations = confirmations;
1914
+ }
1915
+
1916
+ return receipt;
1917
+ }, { oncePoll: this });
1918
+ }
1919
+
1920
+ async getLogs(filter: Filter | FilterByBlockHash | Promise<Filter | FilterByBlockHash>): Promise<Array<Log>> {
1921
+ await this.getNetwork();
1922
+ const params = await resolveProperties({ filter: this._getFilter(filter) });
1923
+ const logs: Array<Log> = await this.perform("getLogs", params);
1924
+ logs.forEach((log) => {
1925
+ if (log.removed == null) { log.removed = false; }
1926
+ });
1927
+ return Formatter.arrayOf(this.formatter.filterLog.bind(this.formatter))(logs);
1928
+ }
1929
+
1930
+ async getEtherPrice(): Promise<number> {
1931
+ await this.getNetwork();
1932
+ return this.perform("getEtherPrice", { });
1933
+ }
1934
+
1935
+ async _getBlockTag(blockTag: BlockTag | Promise<BlockTag>): Promise<BlockTag> {
1936
+ blockTag = await blockTag;
1937
+
1938
+ if (typeof(blockTag) === "number" && blockTag < 0) {
1939
+ if (blockTag % 1) {
1940
+ logger.throwArgumentError("invalid BlockTag", "blockTag", blockTag);
1941
+ }
1942
+
1943
+ let blockNumber = await this._getInternalBlockNumber(100 + 2 * this.pollingInterval);
1944
+ blockNumber += blockTag;
1945
+ if (blockNumber < 0) { blockNumber = 0; }
1946
+ return this.formatter.blockTag(blockNumber)
1947
+ }
1948
+
1949
+ return this.formatter.blockTag(blockTag);
1950
+ }
1951
+
1952
+
1953
+ async getResolver(name: string): Promise<null | Resolver> {
1954
+ let currentName = name;
1955
+ while (true) {
1956
+ if (currentName === "" || currentName === ".") { return null; }
1957
+
1958
+ // Optimization since the eth node cannot change and does
1959
+ // not have a wildcard resolver
1960
+ if (name !== "eth" && currentName === "eth") { return null; }
1961
+
1962
+ // Check the current node for a resolver
1963
+ const addr = await this._getResolver(currentName, "getResolver");
1964
+
1965
+ // Found a resolver!
1966
+ if (addr != null) {
1967
+ const resolver = new Resolver(this, addr, name);
1968
+
1969
+ // Legacy resolver found, using EIP-2544 so it isn't safe to use
1970
+ if (currentName !== name && !(await resolver.supportsWildcard())) { return null; }
1971
+
1972
+ return resolver;
1973
+ }
1974
+
1975
+ // Get the parent node
1976
+ currentName = currentName.split(".").slice(1).join(".");
1977
+ }
1978
+
1979
+ }
1980
+
1981
+ async _getResolver(name: string, operation?: string): Promise<string> {
1982
+ if (operation == null) { operation = "ENS"; }
1983
+
1984
+ const network = await this.getNetwork();
1985
+
1986
+ // No ENS...
1987
+ if (!network.ensAddress) {
1988
+ logger.throwError(
1989
+ "network does not support ENS",
1990
+ Logger.errors.UNSUPPORTED_OPERATION,
1991
+ { operation, network: network.name }
1992
+ );
1993
+ }
1994
+
1995
+ try {
1996
+ // keccak256("resolver(bytes32)")
1997
+ const addrData = await this.call({
1998
+ to: network.ensAddress,
1999
+ data: ("0x0178b8bf" + namehash(name).substring(2))
2000
+ });
2001
+ return this.formatter.callAddress(addrData);
2002
+ } catch (error) {
2003
+ // ENS registry cannot throw errors on resolver(bytes32)
2004
+ }
2005
+
2006
+ return null;
2007
+ }
2008
+
2009
+ async resolveName(name: string | Promise<string>): Promise<null | string> {
2010
+ name = await name;
2011
+
2012
+ // If it is already an address, nothing to resolve
2013
+ try {
2014
+ return Promise.resolve(this.formatter.address(name));
2015
+ } catch (error) {
2016
+ // If is is a hexstring, the address is bad (See #694)
2017
+ if (isHexString(name)) { throw error; }
2018
+ }
2019
+
2020
+ if (typeof(name) !== "string") {
2021
+ logger.throwArgumentError("invalid ENS name", "name", name);
2022
+ }
2023
+
2024
+ // Get the addr from the resolver
2025
+ const resolver = await this.getResolver(name);
2026
+ if (!resolver) { return null; }
2027
+
2028
+ return await resolver.getAddress();
2029
+ }
2030
+
2031
+ async lookupAddress(address: string | Promise<string>): Promise<null | string> {
2032
+ address = await address;
2033
+ address = this.formatter.address(address);
2034
+
2035
+ const node = address.substring(2).toLowerCase() + ".addr.reverse";
2036
+
2037
+ const resolverAddr = await this._getResolver(node, "lookupAddress");
2038
+ if (resolverAddr == null) { return null; }
2039
+
2040
+ // keccak("name(bytes32)")
2041
+ const name = _parseString(await this.call({
2042
+ to: resolverAddr,
2043
+ data: ("0x691f3431" + namehash(node).substring(2))
2044
+ }), 0);
2045
+
2046
+ const addr = await this.resolveName(name);
2047
+ if (addr != address) { return null; }
2048
+
2049
+ return name;
2050
+ }
2051
+
2052
+ async getAvatar(nameOrAddress: string): Promise<null | string> {
2053
+ let resolver: Resolver = null;
2054
+ if (isHexString(nameOrAddress)) {
2055
+ // Address; reverse lookup
2056
+ const address = this.formatter.address(nameOrAddress);
2057
+
2058
+ const node = address.substring(2).toLowerCase() + ".addr.reverse";
2059
+
2060
+ const resolverAddress = await this._getResolver(node, "getAvatar");
2061
+ if (!resolverAddress) { return null; }
2062
+
2063
+ // Try resolving the avatar against the addr.reverse resolver
2064
+ resolver = new Resolver(this, resolverAddress, node);
2065
+ try {
2066
+ const avatar = await resolver.getAvatar();
2067
+ if (avatar) { return avatar.url; }
2068
+ } catch (error) {
2069
+ if (error.code !== Logger.errors.CALL_EXCEPTION) { throw error; }
2070
+ }
2071
+
2072
+ // Try getting the name and performing forward lookup; allowing wildcards
2073
+ try {
2074
+ // keccak("name(bytes32)")
2075
+ const name = _parseString(await this.call({
2076
+ to: resolverAddress,
2077
+ data: ("0x691f3431" + namehash(node).substring(2))
2078
+ }), 0);
2079
+ resolver = await this.getResolver(name);
2080
+ } catch (error) {
2081
+ if (error.code !== Logger.errors.CALL_EXCEPTION) { throw error; }
2082
+ return null;
2083
+ }
2084
+
2085
+ } else {
2086
+ // ENS name; forward lookup with wildcard
2087
+ resolver = await this.getResolver(nameOrAddress);
2088
+ if (!resolver) { return null; }
2089
+ }
2090
+
2091
+ const avatar = await resolver.getAvatar();
2092
+ if (avatar == null) { return null; }
2093
+
2094
+ return avatar.url;
2095
+ }
2096
+
2097
+ perform(method: string, params: any): Promise<any> {
2098
+ return logger.throwError(method + " not implemented", Logger.errors.NOT_IMPLEMENTED, { operation: method });
2099
+ }
2100
+
2101
+ _startEvent(event: Event): void {
2102
+ this.polling = (this._events.filter((e) => e.pollable()).length > 0);
2103
+ }
2104
+
2105
+ _stopEvent(event: Event): void {
2106
+ this.polling = (this._events.filter((e) => e.pollable()).length > 0);
2107
+ }
2108
+
2109
+ _addEventListener(eventName: EventType, listener: Listener, once: boolean): this {
2110
+ const event = new Event(getEventTag(eventName), listener, once)
2111
+ this._events.push(event);
2112
+ this._startEvent(event);
2113
+
2114
+ return this;
2115
+ }
2116
+
2117
+ on(eventName: EventType, listener: Listener): this {
2118
+ return this._addEventListener(eventName, listener, false);
2119
+ }
2120
+
2121
+ once(eventName: EventType, listener: Listener): this {
2122
+ return this._addEventListener(eventName, listener, true);
2123
+ }
2124
+
2125
+
2126
+ emit(eventName: EventType, ...args: Array<any>): boolean {
2127
+ let result = false;
2128
+
2129
+ let stopped: Array<Event> = [ ];
2130
+
2131
+ let eventTag = getEventTag(eventName);
2132
+ this._events = this._events.filter((event) => {
2133
+ if (event.tag !== eventTag) { return true; }
2134
+
2135
+ setTimeout(() => {
2136
+ event.listener.apply(this, args);
2137
+ }, 0);
2138
+
2139
+ result = true;
2140
+
2141
+ if (event.once) {
2142
+ stopped.push(event);
2143
+ return false;
2144
+ }
2145
+
2146
+ return true;
2147
+ });
2148
+
2149
+ stopped.forEach((event) => { this._stopEvent(event); });
2150
+
2151
+ return result;
2152
+ }
2153
+
2154
+ listenerCount(eventName?: EventType): number {
2155
+ if (!eventName) { return this._events.length; }
2156
+
2157
+ let eventTag = getEventTag(eventName);
2158
+ return this._events.filter((event) => {
2159
+ return (event.tag === eventTag);
2160
+ }).length;
2161
+ }
2162
+
2163
+ listeners(eventName?: EventType): Array<Listener> {
2164
+ if (eventName == null) {
2165
+ return this._events.map((event) => event.listener);
2166
+ }
2167
+
2168
+ let eventTag = getEventTag(eventName);
2169
+ return this._events
2170
+ .filter((event) => (event.tag === eventTag))
2171
+ .map((event) => event.listener);
2172
+ }
2173
+
2174
+ off(eventName: EventType, listener?: Listener): this {
2175
+ if (listener == null) {
2176
+ return this.removeAllListeners(eventName);
2177
+ }
2178
+
2179
+ const stopped: Array<Event> = [ ];
2180
+
2181
+ let found = false;
2182
+
2183
+ let eventTag = getEventTag(eventName);
2184
+ this._events = this._events.filter((event) => {
2185
+ if (event.tag !== eventTag || event.listener != listener) { return true; }
2186
+ if (found) { return true; }
2187
+ found = true;
2188
+ stopped.push(event);
2189
+ return false;
2190
+ });
2191
+
2192
+ stopped.forEach((event) => { this._stopEvent(event); });
2193
+
2194
+ return this;
2195
+ }
2196
+
2197
+ removeAllListeners(eventName?: EventType): this {
2198
+ let stopped: Array<Event> = [ ];
2199
+ if (eventName == null) {
2200
+ stopped = this._events;
2201
+
2202
+ this._events = [ ];
2203
+ } else {
2204
+ const eventTag = getEventTag(eventName);
2205
+ this._events = this._events.filter((event) => {
2206
+ if (event.tag !== eventTag) { return true; }
2207
+ stopped.push(event);
2208
+ return false;
2209
+ });
2210
+ }
2211
+
2212
+ stopped.forEach((event) => { this._stopEvent(event); });
2213
+
2214
+ return this;
2215
+ }
2216
+ }