2019-05-14 18:48:48 -04:00
|
|
|
"use strict";
|
2019-08-25 02:39:20 -04:00
|
|
|
import { getAddress, getContractAddress } from "@ethersproject/address";
|
|
|
|
import { BigNumber } from "@ethersproject/bignumber";
|
|
|
|
import { hexDataLength, hexDataSlice, hexValue, hexZeroPad, isHexString } from "@ethersproject/bytes";
|
|
|
|
import { AddressZero } from "@ethersproject/constants";
|
|
|
|
import { shallowCopy } from "@ethersproject/properties";
|
2021-03-30 15:22:45 -04:00
|
|
|
import { accessListify, parse as parseTransaction } from "@ethersproject/transactions";
|
2019-08-25 02:39:20 -04:00
|
|
|
import { Logger } from "@ethersproject/logger";
|
|
|
|
import { version } from "./_version";
|
|
|
|
const logger = new Logger(version);
|
|
|
|
export class Formatter {
|
|
|
|
constructor() {
|
|
|
|
logger.checkNew(new.target, Formatter);
|
2019-05-14 18:48:48 -04:00
|
|
|
this.formats = this.getDefaultFormats();
|
|
|
|
}
|
2019-08-25 02:39:20 -04:00
|
|
|
getDefaultFormats() {
|
2020-01-21 19:08:05 -05:00
|
|
|
const formats = ({});
|
|
|
|
const address = this.address.bind(this);
|
|
|
|
const bigNumber = this.bigNumber.bind(this);
|
|
|
|
const blockTag = this.blockTag.bind(this);
|
|
|
|
const data = this.data.bind(this);
|
|
|
|
const hash = this.hash.bind(this);
|
|
|
|
const hex = this.hex.bind(this);
|
|
|
|
const number = this.number.bind(this);
|
|
|
|
const strictData = (v) => { return this.data(v, true); };
|
2019-05-14 18:48:48 -04:00
|
|
|
formats.transaction = {
|
|
|
|
hash: hash,
|
2021-03-30 15:22:45 -04:00
|
|
|
type: Formatter.allowNull(number, null),
|
|
|
|
accessList: Formatter.allowNull(this.accessList.bind(this), null),
|
2019-05-14 18:48:48 -04:00
|
|
|
blockHash: Formatter.allowNull(hash, null),
|
|
|
|
blockNumber: Formatter.allowNull(number, null),
|
|
|
|
transactionIndex: Formatter.allowNull(number, null),
|
|
|
|
confirmations: Formatter.allowNull(number, null),
|
|
|
|
from: address,
|
|
|
|
gasPrice: bigNumber,
|
|
|
|
gasLimit: bigNumber,
|
|
|
|
to: Formatter.allowNull(address, null),
|
|
|
|
value: bigNumber,
|
|
|
|
nonce: number,
|
|
|
|
data: data,
|
|
|
|
r: Formatter.allowNull(this.uint256),
|
|
|
|
s: Formatter.allowNull(this.uint256),
|
|
|
|
v: Formatter.allowNull(number),
|
|
|
|
creates: Formatter.allowNull(address, null),
|
|
|
|
raw: Formatter.allowNull(data),
|
|
|
|
};
|
|
|
|
formats.transactionRequest = {
|
|
|
|
from: Formatter.allowNull(address),
|
|
|
|
nonce: Formatter.allowNull(number),
|
|
|
|
gasLimit: Formatter.allowNull(bigNumber),
|
|
|
|
gasPrice: Formatter.allowNull(bigNumber),
|
|
|
|
to: Formatter.allowNull(address),
|
|
|
|
value: Formatter.allowNull(bigNumber),
|
|
|
|
data: Formatter.allowNull(strictData),
|
2021-03-30 15:22:45 -04:00
|
|
|
type: Formatter.allowNull(number),
|
|
|
|
accessList: Formatter.allowNull(this.accessList.bind(this), null),
|
2019-05-14 18:48:48 -04:00
|
|
|
};
|
|
|
|
formats.receiptLog = {
|
|
|
|
transactionIndex: number,
|
|
|
|
blockNumber: number,
|
|
|
|
transactionHash: hash,
|
|
|
|
address: address,
|
|
|
|
topics: Formatter.arrayOf(hash),
|
|
|
|
data: data,
|
|
|
|
logIndex: number,
|
|
|
|
blockHash: hash,
|
|
|
|
};
|
|
|
|
formats.receipt = {
|
2019-11-20 18:57:38 +09:00
|
|
|
to: Formatter.allowNull(this.address, null),
|
|
|
|
from: Formatter.allowNull(this.address, null),
|
2019-05-14 18:48:48 -04:00
|
|
|
contractAddress: Formatter.allowNull(address, null),
|
|
|
|
transactionIndex: number,
|
2021-02-01 15:56:47 -05:00
|
|
|
// should be allowNull(hash), but broken-EIP-658 support is handled in receipt
|
|
|
|
root: Formatter.allowNull(hex),
|
2019-05-14 18:48:48 -04:00
|
|
|
gasUsed: bigNumber,
|
|
|
|
logsBloom: Formatter.allowNull(data),
|
|
|
|
blockHash: hash,
|
|
|
|
transactionHash: hash,
|
|
|
|
logs: Formatter.arrayOf(this.receiptLog.bind(this)),
|
|
|
|
blockNumber: number,
|
|
|
|
confirmations: Formatter.allowNull(number, null),
|
|
|
|
cumulativeGasUsed: bigNumber,
|
|
|
|
status: Formatter.allowNull(number)
|
|
|
|
};
|
|
|
|
formats.block = {
|
|
|
|
hash: hash,
|
|
|
|
parentHash: hash,
|
|
|
|
number: number,
|
|
|
|
timestamp: number,
|
|
|
|
nonce: Formatter.allowNull(hex),
|
|
|
|
difficulty: this.difficulty.bind(this),
|
|
|
|
gasLimit: bigNumber,
|
|
|
|
gasUsed: bigNumber,
|
|
|
|
miner: address,
|
|
|
|
extraData: data,
|
|
|
|
transactions: Formatter.allowNull(Formatter.arrayOf(hash)),
|
|
|
|
};
|
2019-08-25 02:39:20 -04:00
|
|
|
formats.blockWithTransactions = shallowCopy(formats.block);
|
2019-05-14 18:48:48 -04:00
|
|
|
formats.blockWithTransactions.transactions = Formatter.allowNull(Formatter.arrayOf(this.transactionResponse.bind(this)));
|
|
|
|
formats.filter = {
|
|
|
|
fromBlock: Formatter.allowNull(blockTag, undefined),
|
|
|
|
toBlock: Formatter.allowNull(blockTag, undefined),
|
|
|
|
blockHash: Formatter.allowNull(hash, undefined),
|
|
|
|
address: Formatter.allowNull(address, undefined),
|
|
|
|
topics: Formatter.allowNull(this.topics.bind(this), undefined),
|
|
|
|
};
|
|
|
|
formats.filterLog = {
|
|
|
|
blockNumber: Formatter.allowNull(number),
|
|
|
|
blockHash: Formatter.allowNull(hash),
|
|
|
|
transactionIndex: number,
|
|
|
|
removed: Formatter.allowNull(this.boolean.bind(this)),
|
|
|
|
address: address,
|
|
|
|
data: Formatter.allowFalsish(data, "0x"),
|
|
|
|
topics: Formatter.arrayOf(hash),
|
|
|
|
transactionHash: hash,
|
|
|
|
logIndex: number,
|
|
|
|
};
|
|
|
|
return formats;
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
2021-03-30 15:22:45 -04:00
|
|
|
accessList(accessList) {
|
|
|
|
return accessListify(accessList || []);
|
|
|
|
}
|
2019-05-14 18:48:48 -04:00
|
|
|
// Requires a BigNumberish that is within the IEEE754 safe integer range; returns a number
|
|
|
|
// Strict! Used on input.
|
2019-08-25 02:39:20 -04:00
|
|
|
number(number) {
|
2020-10-22 21:55:40 -04:00
|
|
|
if (number === "0x") {
|
|
|
|
return 0;
|
|
|
|
}
|
2019-08-25 02:39:20 -04:00
|
|
|
return BigNumber.from(number).toNumber();
|
|
|
|
}
|
2019-05-14 18:48:48 -04:00
|
|
|
// Strict! Used on input.
|
2019-08-25 02:39:20 -04:00
|
|
|
bigNumber(value) {
|
|
|
|
return BigNumber.from(value);
|
|
|
|
}
|
2019-05-14 18:48:48 -04:00
|
|
|
// Requires a boolean, "true" or "false"; returns a boolean
|
2019-08-25 02:39:20 -04:00
|
|
|
boolean(value) {
|
2019-05-14 18:48:48 -04:00
|
|
|
if (typeof (value) === "boolean") {
|
|
|
|
return value;
|
|
|
|
}
|
|
|
|
if (typeof (value) === "string") {
|
|
|
|
value = value.toLowerCase();
|
|
|
|
if (value === "true") {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (value === "false") {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2020-04-23 23:35:39 -04:00
|
|
|
throw new Error("invalid boolean - " + value);
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
hex(value, strict) {
|
2019-05-14 18:48:48 -04:00
|
|
|
if (typeof (value) === "string") {
|
|
|
|
if (!strict && value.substring(0, 2) !== "0x") {
|
|
|
|
value = "0x" + value;
|
|
|
|
}
|
2019-08-25 02:39:20 -04:00
|
|
|
if (isHexString(value)) {
|
2019-05-14 18:48:48 -04:00
|
|
|
return value.toLowerCase();
|
|
|
|
}
|
|
|
|
}
|
2019-08-02 02:10:58 -04:00
|
|
|
return logger.throwArgumentError("invalid hash", "value", value);
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
data(value, strict) {
|
2020-01-21 19:08:05 -05:00
|
|
|
const result = this.hex(value, strict);
|
2019-05-14 18:48:48 -04:00
|
|
|
if ((result.length % 2) !== 0) {
|
|
|
|
throw new Error("invalid data; odd-length - " + value);
|
|
|
|
}
|
|
|
|
return result;
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
2019-05-14 18:48:48 -04:00
|
|
|
// Requires an address
|
|
|
|
// Strict! Used on input.
|
2019-08-25 02:39:20 -04:00
|
|
|
address(value) {
|
|
|
|
return getAddress(value);
|
|
|
|
}
|
|
|
|
callAddress(value) {
|
|
|
|
if (!isHexString(value, 32)) {
|
2019-05-14 18:48:48 -04:00
|
|
|
return null;
|
|
|
|
}
|
2020-01-21 19:08:05 -05:00
|
|
|
const address = getAddress(hexDataSlice(value, 12));
|
2019-08-25 02:39:20 -04:00
|
|
|
return (address === AddressZero) ? null : address;
|
|
|
|
}
|
|
|
|
contractAddress(value) {
|
|
|
|
return getContractAddress(value);
|
|
|
|
}
|
2019-05-14 18:48:48 -04:00
|
|
|
// Strict! Used on input.
|
2019-08-25 02:39:20 -04:00
|
|
|
blockTag(blockTag) {
|
2019-05-14 18:48:48 -04:00
|
|
|
if (blockTag == null) {
|
|
|
|
return "latest";
|
|
|
|
}
|
|
|
|
if (blockTag === "earliest") {
|
|
|
|
return "0x0";
|
|
|
|
}
|
|
|
|
if (blockTag === "latest" || blockTag === "pending") {
|
|
|
|
return blockTag;
|
|
|
|
}
|
2019-08-25 02:39:20 -04:00
|
|
|
if (typeof (blockTag) === "number" || isHexString(blockTag)) {
|
|
|
|
return hexValue(blockTag);
|
2019-05-14 18:48:48 -04:00
|
|
|
}
|
|
|
|
throw new Error("invalid blockTag");
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
2019-05-14 18:48:48 -04:00
|
|
|
// Requires a hash, optionally requires 0x prefix; returns prefixed lowercase hash.
|
2019-08-25 02:39:20 -04:00
|
|
|
hash(value, strict) {
|
2020-01-21 19:08:05 -05:00
|
|
|
const result = this.hex(value, strict);
|
2019-08-25 02:39:20 -04:00
|
|
|
if (hexDataLength(result) !== 32) {
|
2019-08-02 02:10:58 -04:00
|
|
|
return logger.throwArgumentError("invalid hash", "value", value);
|
2019-05-14 18:48:48 -04:00
|
|
|
}
|
|
|
|
return result;
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
2019-05-14 18:48:48 -04:00
|
|
|
// Returns the difficulty as a number, or if too large (i.e. PoA network) null
|
2019-08-25 02:39:20 -04:00
|
|
|
difficulty(value) {
|
2020-01-21 19:08:05 -05:00
|
|
|
if (value == null) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const v = BigNumber.from(value);
|
2019-05-14 18:48:48 -04:00
|
|
|
try {
|
|
|
|
return v.toNumber();
|
|
|
|
}
|
|
|
|
catch (error) { }
|
|
|
|
return null;
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
uint256(value) {
|
|
|
|
if (!isHexString(value)) {
|
2019-05-14 18:48:48 -04:00
|
|
|
throw new Error("invalid uint256");
|
|
|
|
}
|
2019-08-25 02:39:20 -04:00
|
|
|
return hexZeroPad(value, 32);
|
|
|
|
}
|
|
|
|
_block(value, format) {
|
2019-05-14 18:48:48 -04:00
|
|
|
if (value.author != null && value.miner == null) {
|
|
|
|
value.miner = value.author;
|
|
|
|
}
|
|
|
|
return Formatter.check(format, value);
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
block(value) {
|
2019-05-14 18:48:48 -04:00
|
|
|
return this._block(value, this.formats.block);
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
blockWithTransactions(value) {
|
2019-05-14 18:48:48 -04:00
|
|
|
return this._block(value, this.formats.blockWithTransactions);
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
2019-05-14 18:48:48 -04:00
|
|
|
// Strict! Used on input.
|
2019-08-25 02:39:20 -04:00
|
|
|
transactionRequest(value) {
|
2019-05-14 18:48:48 -04:00
|
|
|
return Formatter.check(this.formats.transactionRequest, value);
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
transactionResponse(transaction) {
|
2019-05-14 18:48:48 -04:00
|
|
|
// Rename gas to gasLimit
|
|
|
|
if (transaction.gas != null && transaction.gasLimit == null) {
|
|
|
|
transaction.gasLimit = transaction.gas;
|
|
|
|
}
|
|
|
|
// Some clients (TestRPC) do strange things like return 0x0 for the
|
|
|
|
// 0 address; correct this to be a real address
|
2019-08-25 02:39:20 -04:00
|
|
|
if (transaction.to && BigNumber.from(transaction.to).isZero()) {
|
2019-05-14 18:48:48 -04:00
|
|
|
transaction.to = "0x0000000000000000000000000000000000000000";
|
|
|
|
}
|
|
|
|
// Rename input to data
|
|
|
|
if (transaction.input != null && transaction.data == null) {
|
|
|
|
transaction.data = transaction.input;
|
|
|
|
}
|
|
|
|
// If to and creates are empty, populate the creates from the transaction
|
|
|
|
if (transaction.to == null && transaction.creates == null) {
|
|
|
|
transaction.creates = this.contractAddress(transaction);
|
|
|
|
}
|
2021-03-30 15:22:45 -04:00
|
|
|
if (transaction.type === 1 && transaction.accessList == null) {
|
|
|
|
transaction.accessList = [];
|
|
|
|
}
|
2020-01-21 19:08:05 -05:00
|
|
|
const result = Formatter.check(this.formats.transaction, transaction);
|
2020-01-11 04:18:28 -05:00
|
|
|
if (transaction.chainId != null) {
|
|
|
|
let chainId = transaction.chainId;
|
|
|
|
if (isHexString(chainId)) {
|
|
|
|
chainId = BigNumber.from(chainId).toNumber();
|
2019-05-14 18:48:48 -04:00
|
|
|
}
|
2020-01-11 04:18:28 -05:00
|
|
|
result.chainId = chainId;
|
2019-05-14 18:48:48 -04:00
|
|
|
}
|
2020-01-11 04:18:28 -05:00
|
|
|
else {
|
|
|
|
let chainId = transaction.networkId;
|
|
|
|
// geth-etc returns chainId
|
|
|
|
if (chainId == null && result.v == null) {
|
|
|
|
chainId = transaction.chainId;
|
|
|
|
}
|
|
|
|
if (isHexString(chainId)) {
|
|
|
|
chainId = BigNumber.from(chainId).toNumber();
|
|
|
|
}
|
|
|
|
if (typeof (chainId) !== "number" && result.v != null) {
|
|
|
|
chainId = (result.v - 35) / 2;
|
|
|
|
if (chainId < 0) {
|
|
|
|
chainId = 0;
|
|
|
|
}
|
|
|
|
chainId = parseInt(chainId);
|
|
|
|
}
|
|
|
|
if (typeof (chainId) !== "number") {
|
|
|
|
chainId = 0;
|
|
|
|
}
|
|
|
|
result.chainId = chainId;
|
2019-05-14 18:48:48 -04:00
|
|
|
}
|
|
|
|
// 0x0000... should actually be null
|
|
|
|
if (result.blockHash && result.blockHash.replace(/0/g, "") === "x") {
|
|
|
|
result.blockHash = null;
|
|
|
|
}
|
|
|
|
return result;
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
transaction(value) {
|
|
|
|
return parseTransaction(value);
|
|
|
|
}
|
|
|
|
receiptLog(value) {
|
2019-05-14 18:48:48 -04:00
|
|
|
return Formatter.check(this.formats.receiptLog, value);
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
receipt(value) {
|
2020-01-21 19:08:05 -05:00
|
|
|
const result = Formatter.check(this.formats.receipt, value);
|
2021-02-01 15:56:47 -05:00
|
|
|
// RSK incorrectly implemented EIP-658, so we munge things a bit here for it
|
|
|
|
if (result.root != null) {
|
|
|
|
if (result.root.length <= 4) {
|
|
|
|
// Could be 0x00, 0x0, 0x01 or 0x1
|
|
|
|
const value = BigNumber.from(result.root).toNumber();
|
|
|
|
if (value === 0 || value === 1) {
|
|
|
|
// Make sure if both are specified, they match
|
|
|
|
if (result.status != null && (result.status !== value)) {
|
|
|
|
logger.throwArgumentError("alt-root-status/status mismatch", "value", { root: result.root, status: result.status });
|
|
|
|
}
|
|
|
|
result.status = value;
|
|
|
|
delete result.root;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
logger.throwArgumentError("invalid alt-root-status", "value.root", result.root);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (result.root.length !== 66) {
|
|
|
|
// Must be a valid bytes32
|
|
|
|
logger.throwArgumentError("invalid root hash", "value.root", result.root);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (result.status != null) {
|
2019-05-14 18:48:48 -04:00
|
|
|
result.byzantium = true;
|
|
|
|
}
|
|
|
|
return result;
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
topics(value) {
|
2019-05-14 18:48:48 -04:00
|
|
|
if (Array.isArray(value)) {
|
2019-08-25 02:39:20 -04:00
|
|
|
return value.map((v) => this.topics(v));
|
2019-05-14 18:48:48 -04:00
|
|
|
}
|
|
|
|
else if (value != null) {
|
|
|
|
return this.hash(value, true);
|
|
|
|
}
|
|
|
|
return null;
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
filter(value) {
|
2019-05-14 18:48:48 -04:00
|
|
|
return Formatter.check(this.formats.filter, value);
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
filterLog(value) {
|
2019-05-14 18:48:48 -04:00
|
|
|
return Formatter.check(this.formats.filterLog, value);
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
static check(format, object) {
|
2020-01-21 19:08:05 -05:00
|
|
|
const result = {};
|
|
|
|
for (const key in format) {
|
2019-05-14 18:48:48 -04:00
|
|
|
try {
|
2020-01-21 19:08:05 -05:00
|
|
|
const value = format[key](object[key]);
|
2019-05-14 18:48:48 -04:00
|
|
|
if (value !== undefined) {
|
|
|
|
result[key] = value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (error) {
|
|
|
|
error.checkKey = key;
|
|
|
|
error.checkValue = object[key];
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
2019-05-14 18:48:48 -04:00
|
|
|
// if value is null-ish, nullValue is returned
|
2019-08-25 02:39:20 -04:00
|
|
|
static allowNull(format, nullValue) {
|
2019-05-14 18:48:48 -04:00
|
|
|
return (function (value) {
|
|
|
|
if (value == null) {
|
|
|
|
return nullValue;
|
|
|
|
}
|
|
|
|
return format(value);
|
|
|
|
});
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
2019-05-14 18:48:48 -04:00
|
|
|
// If value is false-ish, replaceValue is returned
|
2019-08-25 02:39:20 -04:00
|
|
|
static allowFalsish(format, replaceValue) {
|
2019-05-14 18:48:48 -04:00
|
|
|
return (function (value) {
|
|
|
|
if (!value) {
|
|
|
|
return replaceValue;
|
|
|
|
}
|
|
|
|
return format(value);
|
|
|
|
});
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
2019-05-14 18:48:48 -04:00
|
|
|
// Requires an Array satisfying check
|
2019-08-25 02:39:20 -04:00
|
|
|
static arrayOf(format) {
|
2019-05-14 18:48:48 -04:00
|
|
|
return (function (array) {
|
|
|
|
if (!Array.isArray(array)) {
|
|
|
|
throw new Error("not an array");
|
|
|
|
}
|
2020-01-21 19:08:05 -05:00
|
|
|
const result = [];
|
2019-05-14 18:48:48 -04:00
|
|
|
array.forEach(function (value) {
|
|
|
|
result.push(format(value));
|
|
|
|
});
|
|
|
|
return result;
|
|
|
|
});
|
2019-08-25 02:39:20 -04:00
|
|
|
}
|
|
|
|
}
|
2020-10-07 20:10:50 -04:00
|
|
|
export function isCommunityResourcable(value) {
|
|
|
|
return (value && typeof (value.isCommunityResource) === "function");
|
|
|
|
}
|
|
|
|
export function isCommunityResource(value) {
|
|
|
|
return (isCommunityResourcable(value) && value.isCommunityResource());
|
|
|
|
}
|
2020-07-14 02:33:30 -04:00
|
|
|
// Show the throttle message only once
|
|
|
|
let throttleMessage = false;
|
|
|
|
export function showThrottleMessage() {
|
|
|
|
if (throttleMessage) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
throttleMessage = true;
|
|
|
|
console.log("========= NOTICE =========");
|
|
|
|
console.log("Request-Rate Exceeded (this message will not be repeated)");
|
|
|
|
console.log("");
|
|
|
|
console.log("The default API keys for each service are provided as a highly-throttled,");
|
|
|
|
console.log("community resource for low-traffic projects and early prototyping.");
|
|
|
|
console.log("");
|
|
|
|
console.log("While your application will continue to function, we highly recommended");
|
|
|
|
console.log("signing up for your own API keys to improve performance, increase your");
|
|
|
|
console.log("request rate/limit and enable other perks, such as metrics and advanced APIs.");
|
|
|
|
console.log("");
|
|
|
|
console.log("For more details: https:/\/docs.ethers.io/api-keys/");
|
|
|
|
console.log("==========================");
|
|
|
|
}
|
2020-07-13 08:03:56 -04:00
|
|
|
//# sourceMappingURL=formatter.js.map
|