UNPKG

16.7 kBJavaScriptView Raw
1"use strict";
2import { getAddress, getContractAddress } from "@ethersproject/address";
3import { BigNumber } from "@ethersproject/bignumber";
4import { hexDataLength, hexDataSlice, hexValue, hexZeroPad, isHexString } from "@ethersproject/bytes";
5import { AddressZero } from "@ethersproject/constants";
6import { shallowCopy } from "@ethersproject/properties";
7import { accessListify, parse as parseTransaction } from "@ethersproject/transactions";
8import { Logger } from "@ethersproject/logger";
9import { version } from "./_version";
10const logger = new Logger(version);
11export class Formatter {
12 constructor() {
13 this.formats = this.getDefaultFormats();
14 }
15 getDefaultFormats() {
16 const formats = ({});
17 const address = this.address.bind(this);
18 const bigNumber = this.bigNumber.bind(this);
19 const blockTag = this.blockTag.bind(this);
20 const data = this.data.bind(this);
21 const hash = this.hash.bind(this);
22 const hex = this.hex.bind(this);
23 const number = this.number.bind(this);
24 const type = this.type.bind(this);
25 const strictData = (v) => { return this.data(v, true); };
26 formats.transaction = {
27 hash: hash,
28 type: type,
29 accessList: Formatter.allowNull(this.accessList.bind(this), null),
30 blockHash: Formatter.allowNull(hash, null),
31 blockNumber: Formatter.allowNull(number, null),
32 transactionIndex: Formatter.allowNull(number, null),
33 confirmations: Formatter.allowNull(number, null),
34 from: address,
35 // either (gasPrice) or (maxPriorityFeePerGas + maxFeePerGas)
36 // must be set
37 gasPrice: Formatter.allowNull(bigNumber),
38 maxPriorityFeePerGas: Formatter.allowNull(bigNumber),
39 maxFeePerGas: Formatter.allowNull(bigNumber),
40 gasLimit: bigNumber,
41 to: Formatter.allowNull(address, null),
42 value: bigNumber,
43 nonce: number,
44 data: data,
45 r: Formatter.allowNull(this.uint256),
46 s: Formatter.allowNull(this.uint256),
47 v: Formatter.allowNull(number),
48 creates: Formatter.allowNull(address, null),
49 raw: Formatter.allowNull(data),
50 };
51 formats.transactionRequest = {
52 from: Formatter.allowNull(address),
53 nonce: Formatter.allowNull(number),
54 gasLimit: Formatter.allowNull(bigNumber),
55 gasPrice: Formatter.allowNull(bigNumber),
56 maxPriorityFeePerGas: Formatter.allowNull(bigNumber),
57 maxFeePerGas: Formatter.allowNull(bigNumber),
58 to: Formatter.allowNull(address),
59 value: Formatter.allowNull(bigNumber),
60 data: Formatter.allowNull(strictData),
61 type: Formatter.allowNull(number),
62 accessList: Formatter.allowNull(this.accessList.bind(this), null),
63 };
64 formats.receiptLog = {
65 transactionIndex: number,
66 blockNumber: number,
67 transactionHash: hash,
68 address: address,
69 topics: Formatter.arrayOf(hash),
70 data: data,
71 logIndex: number,
72 blockHash: hash,
73 };
74 formats.receipt = {
75 to: Formatter.allowNull(this.address, null),
76 from: Formatter.allowNull(this.address, null),
77 contractAddress: Formatter.allowNull(address, null),
78 transactionIndex: number,
79 // should be allowNull(hash), but broken-EIP-658 support is handled in receipt
80 root: Formatter.allowNull(hex),
81 gasUsed: bigNumber,
82 logsBloom: Formatter.allowNull(data),
83 blockHash: hash,
84 transactionHash: hash,
85 logs: Formatter.arrayOf(this.receiptLog.bind(this)),
86 blockNumber: number,
87 confirmations: Formatter.allowNull(number, null),
88 cumulativeGasUsed: bigNumber,
89 effectiveGasPrice: Formatter.allowNull(bigNumber),
90 status: Formatter.allowNull(number),
91 type: type
92 };
93 formats.block = {
94 hash: Formatter.allowNull(hash),
95 parentHash: hash,
96 number: number,
97 timestamp: number,
98 nonce: Formatter.allowNull(hex),
99 difficulty: this.difficulty.bind(this),
100 gasLimit: bigNumber,
101 gasUsed: bigNumber,
102 miner: Formatter.allowNull(address),
103 extraData: data,
104 transactions: Formatter.allowNull(Formatter.arrayOf(hash)),
105 baseFeePerGas: Formatter.allowNull(bigNumber)
106 };
107 formats.blockWithTransactions = shallowCopy(formats.block);
108 formats.blockWithTransactions.transactions = Formatter.allowNull(Formatter.arrayOf(this.transactionResponse.bind(this)));
109 formats.filter = {
110 fromBlock: Formatter.allowNull(blockTag, undefined),
111 toBlock: Formatter.allowNull(blockTag, undefined),
112 blockHash: Formatter.allowNull(hash, undefined),
113 address: Formatter.allowNull(address, undefined),
114 topics: Formatter.allowNull(this.topics.bind(this), undefined),
115 };
116 formats.filterLog = {
117 blockNumber: Formatter.allowNull(number),
118 blockHash: Formatter.allowNull(hash),
119 transactionIndex: number,
120 removed: Formatter.allowNull(this.boolean.bind(this)),
121 address: address,
122 data: Formatter.allowFalsish(data, "0x"),
123 topics: Formatter.arrayOf(hash),
124 transactionHash: hash,
125 logIndex: number,
126 };
127 return formats;
128 }
129 accessList(accessList) {
130 return accessListify(accessList || []);
131 }
132 // Requires a BigNumberish that is within the IEEE754 safe integer range; returns a number
133 // Strict! Used on input.
134 number(number) {
135 if (number === "0x") {
136 return 0;
137 }
138 return BigNumber.from(number).toNumber();
139 }
140 type(number) {
141 if (number === "0x" || number == null) {
142 return 0;
143 }
144 return BigNumber.from(number).toNumber();
145 }
146 // Strict! Used on input.
147 bigNumber(value) {
148 return BigNumber.from(value);
149 }
150 // Requires a boolean, "true" or "false"; returns a boolean
151 boolean(value) {
152 if (typeof (value) === "boolean") {
153 return value;
154 }
155 if (typeof (value) === "string") {
156 value = value.toLowerCase();
157 if (value === "true") {
158 return true;
159 }
160 if (value === "false") {
161 return false;
162 }
163 }
164 throw new Error("invalid boolean - " + value);
165 }
166 hex(value, strict) {
167 if (typeof (value) === "string") {
168 if (!strict && value.substring(0, 2) !== "0x") {
169 value = "0x" + value;
170 }
171 if (isHexString(value)) {
172 return value.toLowerCase();
173 }
174 }
175 return logger.throwArgumentError("invalid hash", "value", value);
176 }
177 data(value, strict) {
178 const result = this.hex(value, strict);
179 if ((result.length % 2) !== 0) {
180 throw new Error("invalid data; odd-length - " + value);
181 }
182 return result;
183 }
184 // Requires an address
185 // Strict! Used on input.
186 address(value) {
187 return getAddress(value);
188 }
189 callAddress(value) {
190 if (!isHexString(value, 32)) {
191 return null;
192 }
193 const address = getAddress(hexDataSlice(value, 12));
194 return (address === AddressZero) ? null : address;
195 }
196 contractAddress(value) {
197 return getContractAddress(value);
198 }
199 // Strict! Used on input.
200 blockTag(blockTag) {
201 if (blockTag == null) {
202 return "latest";
203 }
204 if (blockTag === "earliest") {
205 return "0x0";
206 }
207 if (blockTag === "latest" || blockTag === "pending") {
208 return blockTag;
209 }
210 if (typeof (blockTag) === "number" || isHexString(blockTag)) {
211 return hexValue(blockTag);
212 }
213 throw new Error("invalid blockTag");
214 }
215 // Requires a hash, optionally requires 0x prefix; returns prefixed lowercase hash.
216 hash(value, strict) {
217 const result = this.hex(value, strict);
218 if (hexDataLength(result) !== 32) {
219 return logger.throwArgumentError("invalid hash", "value", value);
220 }
221 return result;
222 }
223 // Returns the difficulty as a number, or if too large (i.e. PoA network) null
224 difficulty(value) {
225 if (value == null) {
226 return null;
227 }
228 const v = BigNumber.from(value);
229 try {
230 return v.toNumber();
231 }
232 catch (error) { }
233 return null;
234 }
235 uint256(value) {
236 if (!isHexString(value)) {
237 throw new Error("invalid uint256");
238 }
239 return hexZeroPad(value, 32);
240 }
241 _block(value, format) {
242 if (value.author != null && value.miner == null) {
243 value.miner = value.author;
244 }
245 // The difficulty may need to come from _difficulty in recursed blocks
246 const difficulty = (value._difficulty != null) ? value._difficulty : value.difficulty;
247 const result = Formatter.check(format, value);
248 result._difficulty = ((difficulty == null) ? null : BigNumber.from(difficulty));
249 return result;
250 }
251 block(value) {
252 return this._block(value, this.formats.block);
253 }
254 blockWithTransactions(value) {
255 return this._block(value, this.formats.blockWithTransactions);
256 }
257 // Strict! Used on input.
258 transactionRequest(value) {
259 return Formatter.check(this.formats.transactionRequest, value);
260 }
261 transactionResponse(transaction) {
262 // Rename gas to gasLimit
263 if (transaction.gas != null && transaction.gasLimit == null) {
264 transaction.gasLimit = transaction.gas;
265 }
266 // Some clients (TestRPC) do strange things like return 0x0 for the
267 // 0 address; correct this to be a real address
268 if (transaction.to && BigNumber.from(transaction.to).isZero()) {
269 transaction.to = "0x0000000000000000000000000000000000000000";
270 }
271 // Rename input to data
272 if (transaction.input != null && transaction.data == null) {
273 transaction.data = transaction.input;
274 }
275 // If to and creates are empty, populate the creates from the transaction
276 if (transaction.to == null && transaction.creates == null) {
277 transaction.creates = this.contractAddress(transaction);
278 }
279 if ((transaction.type === 1 || transaction.type === 2) && transaction.accessList == null) {
280 transaction.accessList = [];
281 }
282 const result = Formatter.check(this.formats.transaction, transaction);
283 if (transaction.chainId != null) {
284 let chainId = transaction.chainId;
285 if (isHexString(chainId)) {
286 chainId = BigNumber.from(chainId).toNumber();
287 }
288 result.chainId = chainId;
289 }
290 else {
291 let chainId = transaction.networkId;
292 // geth-etc returns chainId
293 if (chainId == null && result.v == null) {
294 chainId = transaction.chainId;
295 }
296 if (isHexString(chainId)) {
297 chainId = BigNumber.from(chainId).toNumber();
298 }
299 if (typeof (chainId) !== "number" && result.v != null) {
300 chainId = (result.v - 35) / 2;
301 if (chainId < 0) {
302 chainId = 0;
303 }
304 chainId = parseInt(chainId);
305 }
306 if (typeof (chainId) !== "number") {
307 chainId = 0;
308 }
309 result.chainId = chainId;
310 }
311 // 0x0000... should actually be null
312 if (result.blockHash && result.blockHash.replace(/0/g, "") === "x") {
313 result.blockHash = null;
314 }
315 return result;
316 }
317 transaction(value) {
318 return parseTransaction(value);
319 }
320 receiptLog(value) {
321 return Formatter.check(this.formats.receiptLog, value);
322 }
323 receipt(value) {
324 const result = Formatter.check(this.formats.receipt, value);
325 // RSK incorrectly implemented EIP-658, so we munge things a bit here for it
326 if (result.root != null) {
327 if (result.root.length <= 4) {
328 // Could be 0x00, 0x0, 0x01 or 0x1
329 const value = BigNumber.from(result.root).toNumber();
330 if (value === 0 || value === 1) {
331 // Make sure if both are specified, they match
332 if (result.status != null && (result.status !== value)) {
333 logger.throwArgumentError("alt-root-status/status mismatch", "value", { root: result.root, status: result.status });
334 }
335 result.status = value;
336 delete result.root;
337 }
338 else {
339 logger.throwArgumentError("invalid alt-root-status", "value.root", result.root);
340 }
341 }
342 else if (result.root.length !== 66) {
343 // Must be a valid bytes32
344 logger.throwArgumentError("invalid root hash", "value.root", result.root);
345 }
346 }
347 if (result.status != null) {
348 result.byzantium = true;
349 }
350 return result;
351 }
352 topics(value) {
353 if (Array.isArray(value)) {
354 return value.map((v) => this.topics(v));
355 }
356 else if (value != null) {
357 return this.hash(value, true);
358 }
359 return null;
360 }
361 filter(value) {
362 return Formatter.check(this.formats.filter, value);
363 }
364 filterLog(value) {
365 return Formatter.check(this.formats.filterLog, value);
366 }
367 static check(format, object) {
368 const result = {};
369 for (const key in format) {
370 try {
371 const value = format[key](object[key]);
372 if (value !== undefined) {
373 result[key] = value;
374 }
375 }
376 catch (error) {
377 error.checkKey = key;
378 error.checkValue = object[key];
379 throw error;
380 }
381 }
382 return result;
383 }
384 // if value is null-ish, nullValue is returned
385 static allowNull(format, nullValue) {
386 return (function (value) {
387 if (value == null) {
388 return nullValue;
389 }
390 return format(value);
391 });
392 }
393 // If value is false-ish, replaceValue is returned
394 static allowFalsish(format, replaceValue) {
395 return (function (value) {
396 if (!value) {
397 return replaceValue;
398 }
399 return format(value);
400 });
401 }
402 // Requires an Array satisfying check
403 static arrayOf(format) {
404 return (function (array) {
405 if (!Array.isArray(array)) {
406 throw new Error("not an array");
407 }
408 const result = [];
409 array.forEach(function (value) {
410 result.push(format(value));
411 });
412 return result;
413 });
414 }
415}
416export function isCommunityResourcable(value) {
417 return (value && typeof (value.isCommunityResource) === "function");
418}
419export function isCommunityResource(value) {
420 return (isCommunityResourcable(value) && value.isCommunityResource());
421}
422// Show the throttle message only once
423let throttleMessage = false;
424export function showThrottleMessage() {
425 if (throttleMessage) {
426 return;
427 }
428 throttleMessage = true;
429 console.log("========= NOTICE =========");
430 console.log("Request-Rate Exceeded (this message will not be repeated)");
431 console.log("");
432 console.log("The default API keys for each service are provided as a highly-throttled,");
433 console.log("community resource for low-traffic projects and early prototyping.");
434 console.log("");
435 console.log("While your application will continue to function, we highly recommended");
436 console.log("signing up for your own API keys to improve performance, increase your");
437 console.log("request rate/limit and enable other perks, such as metrics and advanced APIs.");
438 console.log("");
439 console.log("For more details: https:/\/docs.ethers.io/api-keys/");
440 console.log("==========================");
441}
442//# sourceMappingURL=formatter.js.map
\No newline at end of file