Sync testnets with v5 updates and update to new CALL_EXCEPTION model.

This commit is contained in:
Richard Moore 2022-10-20 04:54:26 -04:00
parent f4539e5675
commit a667dcbebe
9 changed files with 447 additions and 92 deletions

@ -7,6 +7,7 @@
// of Signer/ENS name to address so we can sync respond to listenerCount.
import { resolveAddress } from "../address/index.js";
import { Transaction } from "../transaction/index.js";
import {
concat, dataLength, dataSlice, hexlify, isHexString,
getBigInt, getBytes, getNumber,
@ -709,7 +710,7 @@ export class AbstractProvider implements Provider {
} catch (error) {
// CCIP Read OffchainLookup
if (!this.disableCcipRead && isCallException(error) && attempt >= 0 && blockTag === "latest" && transaction.to != null && dataSlice(error.data, 0, 4) === "0x556f1830") {
if (!this.disableCcipRead && isCallException(error) && error.data && attempt >= 0 && blockTag === "latest" && transaction.to != null && dataSlice(error.data, 0, 4) === "0x556f1830") {
const data = error.data;
const txSender = await resolveAddress(transaction.to, this);
@ -728,10 +729,16 @@ export class AbstractProvider implements Provider {
// Check the sender of the OffchainLookup matches the transaction
if (ccipArgs.sender.toLowerCase() !== txSender.toLowerCase()) {
return throwError("CCIP Read sender mismatch", "CALL_EXCEPTION", {
data, transaction,
errorSignature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
errorName: "OffchainLookup",
errorArgs: ccipArgs.errorArgs
action: "call",
data,
reason: "OffchainLookup",
transaction: <any>transaction, // @TODO: populate data?
invocation: null,
revert: {
signature: "OffchainLookup(address,string[],bytes,bytes4,bytes)",
name: "OffchainLookup",
args: ccipArgs.errorArgs
}
});
}
@ -802,8 +809,21 @@ export class AbstractProvider implements Provider {
// Write
async broadcastTransaction(signedTx: string): Promise<TransactionResponse> {
throw new Error();
return <TransactionResponse><unknown>{ };
const { blockNumber, hash, network } = await resolveProperties({
blockNumber: this.getBlockNumber(),
hash: this._perform({
method: "broadcastTransaction",
signedTransaction: signedTx
}),
network: this.getNetwork()
});
const tx = Transaction.from(signedTx);
if (tx.hash !== hash) {
throw new Error("@TODO: the returned hash did not match");
}
return this._wrapTransactionResponse(<any>tx, network).replaceableTransaction(blockNumber);
}
async #getBlock(block: BlockTag | string, includeTransactions: boolean): Promise<any> {

@ -32,7 +32,7 @@ export abstract class AbstractSigner<P extends null | Provider = null | Provider
return this.#checkProvider("getTransactionCount").getTransactionCount(await this.getAddress(), blockTag);
}
async #populate(op: string, tx: TransactionRequest): Promise<TransactionLike<string>> {
async #populate(op: string, tx: TransactionRequest): Promise<{ provider: Provider, pop: TransactionLike<string> }> {
const provider = this.#checkProvider(op);
//let pop: Deferrable<TransactionRequest> = Object.assign({ }, tx);
@ -60,17 +60,17 @@ export abstract class AbstractSigner<P extends null | Provider = null | Provider
});
}
return pop;
return { pop: await resolveProperties(pop), provider };
}
async populateCall(tx: TransactionRequest): Promise<TransactionLike<string>> {
const pop = await this.#populate("populateCall", tx);
const { pop } = await this.#populate("populateCall", tx);
return pop;
}
async populateTransaction(tx: TransactionRequest): Promise<TransactionLike<string>> {
const pop = await this.#populate("populateTransaction", tx);
const { pop, provider } = await this.#populate("populateTransaction", tx);
if (pop.nonce == null) {
pop.nonce = await this.getNonce("pending");
@ -91,10 +91,109 @@ export abstract class AbstractSigner<P extends null | Provider = null | Provider
pop.chainId = network.chainId;
}
// Do not allow mixing pre-eip-1559 and eip-1559 properties
const hasEip1559 = (pop.maxFeePerGas != null || pop.maxPriorityFeePerGas != null);
if (pop.gasPrice != null && (pop.type === 2 || hasEip1559)) {
throwArgumentError("eip-1559 transaction do not support gasPrice", "tx", tx);
} else if ((pop.type === 0 || pop.type === 1) && hasEip1559) {
throwArgumentError("pre-eip-1559 transaction do not support maxFeePerGas/maxPriorityFeePerGas", "tx", tx);
}
if ((pop.type === 2 || pop.type == null) && (pop.maxFeePerGas != null && pop.maxPriorityFeePerGas != null)) {
// Fully-formed EIP-1559 transaction (skip getFeeData)
pop.type = 2;
} else if (pop.type === 0 || pop.type === 1) {
// Explicit Legacy or EIP-2930 transaction
// We need to get fee data to determine things
const feeData = await provider.getFeeData();
if (feeData.gasPrice == null) {
throwError("network does not support gasPrice", "UNSUPPORTED_OPERATION", {
operation: "getGasPrice"
});
}
// Populate missing gasPrice
if (pop.gasPrice == null) { pop.gasPrice = feeData.gasPrice; }
} else {
// We need to get fee data to determine things
const feeData = await provider.getFeeData();
if (pop.type == null) {
// We need to auto-detect the intended type of this transaction...
if (feeData.maxFeePerGas != null && feeData.maxPriorityFeePerGas != null) {
// The network supports EIP-1559!
// Upgrade transaction from null to eip-1559
pop.type = 2;
if (pop.gasPrice != null) {
// Using legacy gasPrice property on an eip-1559 network,
// so use gasPrice as both fee properties
const gasPrice = pop.gasPrice;
delete pop.gasPrice;
pop.maxFeePerGas = gasPrice;
pop.maxPriorityFeePerGas = gasPrice;
} else {
// Populate missing fee data
if (pop.maxFeePerGas == null) {
pop.maxFeePerGas = feeData.maxFeePerGas;
}
if (pop.maxPriorityFeePerGas == null) {
pop.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
}
}
} else if (feeData.gasPrice != null) {
// Network doesn't support EIP-1559...
// ...but they are trying to use EIP-1559 properties
if (hasEip1559) {
throwError("network does not support EIP-1559", "UNSUPPORTED_OPERATION", {
operation: "populateTransaction"
});
}
// Populate missing fee data
if (pop.gasPrice == null) {
pop.gasPrice = feeData.gasPrice;
}
// Explicitly set untyped transaction to legacy
// @TODO: Maybe this shold allow type 1?
pop.type = 0;
} else {
// getFeeData has failed us.
throwError("failed to get consistent fee data", "UNSUPPORTED_OPERATION", {
operation: "signer.getFeeData"
});
}
} else if (pop.type === 2) {
// Explicitly using EIP-1559
// Populate missing fee data
if (pop.maxFeePerGas == null) {
pop.maxFeePerGas = feeData.maxFeePerGas;
}
if (pop.maxPriorityFeePerGas == null) {
pop.maxPriorityFeePerGas = feeData.maxPriorityFeePerGas;
}
}
}
//@TOOD: Don't await all over the place; save them up for
// the end for better batching
//@TODO: Copy type logic from AbstractSigner in v5
// Test how many batches is actually sent for sending a tx; compare before/after
return await resolveProperties(pop);
}
@ -114,7 +213,8 @@ export abstract class AbstractSigner<P extends null | Provider = null | Provider
async sendTransaction(tx: TransactionRequest): Promise<TransactionResponse> {
const provider = this.#checkProvider("sendTransaction");
const txObj = Transaction.from(await this.populateTransaction(tx));
const pop = await this.populateTransaction(tx);
const txObj = Transaction.from(pop);
return await provider.broadcastTransaction(await this.signTransaction(txObj));
}

@ -1,5 +1,5 @@
import { getAddress } from "../address/index.js";
import { ZeroHash } from "../constants/hashes.js";
import { ZeroAddress, ZeroHash } from "../constants/index.js";
import { dnsEncode, namehash } from "../hash/index.js";
import {
concat, dataSlice, getBytes, hexlify, zeroPadValue,
@ -197,6 +197,7 @@ export class EnsResolver {
const addrData = concat([ selector, namehash(this.name), parameters ]);
const tx: TransactionRequest = {
to: this.address,
from: ZeroAddress,
enableCcipRead: true,
data: addrData
};
@ -213,8 +214,9 @@ export class EnsResolver {
try {
let data = await this.provider.call(tx);
if ((getBytes(data).length % 32) === 4) {
return throwError("resolver threw error", "CALL_EXCEPTION", {
transaction: tx, data
return throwError("execution reverted during JSON-RPC call (could not parse reason; invalid data length)", "CALL_EXCEPTION", {
action: "call", data, reason: null, transaction: <any>tx,
invocation: null, revert: null
});
}
if (wrapped) { return parseBytes(data, 0); }

@ -1,6 +1,7 @@
import {
defineProperties, FetchRequest, throwArgumentError, throwError
defineProperties, resolveProperties, throwArgumentError, throwError,
FetchRequest
} from "../utils/index.js";
import { showThrottleMessage } from "./community.js";
@ -18,26 +19,21 @@ function getHost(name: string): string {
switch(name) {
case "mainnet":
return "eth-mainnet.alchemyapi.io";
case "ropsten":
return "eth-ropsten.alchemyapi.io";
case "rinkeby":
return "eth-rinkeby.alchemyapi.io";
case "goerli":
return "eth-goerli.alchemyapi.io";
case "kovan":
return "eth-kovan.alchemyapi.io";
return "eth-goerli.g.alchemy.com";
case "arbitrum":
return "arb-mainnet.g.alchemy.com";
case "arbitrum-goerli":
return "arb-goerli.g.alchemy.com";
case "matic":
return "polygon-mainnet.g.alchemy.com";
case "maticmum":
return "polygon-mumbai.g.alchemy.com";
case "arbitrum":
return "arb-mainnet.g.alchemy.com";
case "arbitrum-rinkeby":
return "arb-rinkeby.g.alchemy.com";
case "optimism":
return "opt-mainnet.g.alchemy.com";
case "optimism-kovan":
return "opt-kovan.g.alchemy.com";
case "optimism-goerli":
return "opt-goerli.g.alchemy.com";
}
return throwArgumentError("unsupported network", "network", name);
@ -67,8 +63,11 @@ export class AlchemyProvider extends JsonRpcProvider implements CommunityResourc
// https://docs.alchemy.com/reference/trace-transaction
if (req.method === "getTransactionResult") {
const trace = await this.send("trace_transaction", [ req.hash ]);
if (trace == null) { return null; }
const { trace, tx } = await resolveProperties({
trace: this.send("trace_transaction", [ req.hash ]),
tx: this.getTransaction(req.hash)
});
if (trace == null || tx == null) { return null; }
let data: undefined | string;
let error = false;
@ -80,7 +79,12 @@ export class AlchemyProvider extends JsonRpcProvider implements CommunityResourc
if (data) {
if (error) {
throwError("an error occurred during transaction executions", "CALL_EXCEPTION", {
data
action: "getTransactionResult",
data,
reason: null,
transaction: tx,
invocation: null,
revert: null // @TODO
});
}
return data;

@ -0,0 +1,120 @@
import { assertArgument } from "../utils/index.js";
import { JsonRpcApiPollingProvider } from "./provider-jsonrpc.js";
import type {
JsonRpcError, JsonRpcPayload, JsonRpcResult,
JsonRpcSigner
} from "./provider-jsonrpc.js";
import type { Networkish } from "./network.js";
export interface Eip1193Provider {
request(request: { method: string, params?: Array<any> | Record<string, any> }): Promise<any>;
};
export type DebugEventJsonRpcApiProvider = {
action: "sendEip1193Payload",
payload: { method: string, params: Array<any> }
} | {
action: "receiveEip1193Result",
result: any
} | {
action: "receiveEip1193Error",
error: Error
};
export class BrowserProvider extends JsonRpcApiPollingProvider {
#request: (method: string, params: Array<any> | Record<string, any>) => Promise<any>;
constructor(ethereum: Eip1193Provider, network?: Networkish) {
super(network, { batchMaxCount: 1 });
this.#request = async (method: string, params: Array<any> | Record<string, any>) => {
const payload = { method, params };
this.emit("debug", { action: "sendEip1193Request", payload });
try {
const result = await ethereum.request(payload);
this.emit("debug", { action: "receiveEip1193Result", result });
return result;
} catch (e: any) {
const error = new Error(e.message);
(<any>error).code = e.code;
(<any>error).data = e.data;
(<any>error).payload = payload;
this.emit("debug", { action: "receiveEip1193Error", error });
throw error;
}
};
}
async send(method: string, params: Array<any> | Record<string, any>): Promise<any> {
await this._start();
return await super.send(method, params);
}
async _send(payload: JsonRpcPayload | Array<JsonRpcPayload>): Promise<Array<JsonRpcResult | JsonRpcError>> {
assertArgument(!Array.isArray(payload), "EIP-1193 does not support batch request", "payload", payload);
try {
const result = await this.#request(payload.method, payload.params || [ ]);
return [ { id: payload.id, result } ];
} catch (e: any) {
return [ {
id: payload.id,
error: { code: e.code, data: e.data, message: e.message }
} ];
}
}
getRpcError(payload: JsonRpcPayload, error: JsonRpcError): Error {
error = JSON.parse(JSON.stringify(error));
// EIP-1193 gives us some machine-readable error codes, so rewrite
// them into
switch (error.error.code || -1) {
case 4001:
error.error.message = `ethers-user-denied: ${ error.error.message }`;
break;
case 4200:
error.error.message = `ethers-unsupported: ${ error.error.message }`;
break;
}
return super.getRpcError(payload, error);
}
async hasSigner(address: number | string): Promise<boolean> {
if (address == null) { address = 0; }
const accounts = await this.send("eth_accounts", [ ]);
if (typeof(address) === "number") {
return (accounts.length > address);
}
address = address.toLowerCase();
return accounts.filter((a: string) => (a.toLowerCase() === address)).length !== 0;
}
async getSigner(address?: number | string): Promise<JsonRpcSigner> {
if (address == null) { address = 0; }
if (!(await this.hasSigner(address))) {
try {
//const resp =
await this.#request("eth_requestAccounts", [ ]);
//console.log("RESP", resp);
} catch (error: any) {
const payload = error.payload;
throw this.getRpcError(payload, { id: payload.id, error });
}
}
return await super.getSigner(address);
}
}

@ -1,3 +1,4 @@
import { getBuiltinCallException } from "../abi/index.js";
import { accessListify } from "../transaction/index.js";
import {
defineProperties,
@ -82,14 +83,23 @@ export class BaseEtherscanProvider extends AbstractProvider {
switch(this.network.name) {
case "mainnet":
return "https:/\/api.etherscan.io";
case "ropsten":
return "https:/\/api-ropsten.etherscan.io";
case "rinkeby":
return "https:/\/api-rinkeby.etherscan.io";
case "kovan":
return "https:/\/api-kovan.etherscan.io";
case "goerli":
return "https:/\/api-goerli.etherscan.io";
case "sepolia":
return "https:/\/api-sepolia.etherscan.io";
case "arbitrum":
return "https:/\/api.arbiscan.io";
case "arbitrum-goerli":
return "https:/\/api-goerli.arbiscan.io";
case "matic":
return "https:/\/api.polygonscan.com";
case "maticmum":
return "https:/\/api-testnet.polygonscan.com";
case "optimism":
return "https:/\/api-optimistic.etherscan.io";
case "optimism-goerli":
return "https:/\/api-goerli-optimistic.etherscan.io";
default:
}
@ -246,6 +256,13 @@ export class BaseEtherscanProvider extends AbstractProvider {
_checkError(req: PerformActionRequest, error: Error, transaction: any): never {
if (req.method === "call" || req.method === "estimateGas") {
if (error.message.match(/execution reverted/i)) {
const e = getBuiltinCallException(req.method, <any>req.transaction, (<any>error).data);
e.info = { request: req, error }
throw e;
}
}
/*
let body = "";
if (isError(error, Logger.Errors.SERVER_ERROR) && error.response && error.response.hasBody()) {

@ -18,26 +18,23 @@ function getHost(name: string): string {
switch(name) {
case "mainnet":
return "mainnet.infura.io";
case "ropsten":
return "ropsten.infura.io";
case "rinkeby":
return "rinkeby.infura.io";
case "kovan":
return "kovan.infura.io";
case "goerli":
return "goerli.infura.io";
case "sepolia":
return "sepolia.infura.io";
case "arbitrum":
return "arbitrum-mainnet.infura.io";
case "arbitrum-goerli":
return "arbitrum-goerli.infura.io";
case "matic":
return "polygon-mainnet.infura.io";
case "maticmum":
return "polygon-mumbai.infura.io";
case "optimism":
return "optimism-mainnet.infura.io";
case "optimism-kovan":
return "optimism-kovan.infura.io";
case "arbitrum":
return "arbitrum-mainnet.infura.io";
case "arbitrum-rinkeby":
return "arbitrum-rinkeby.infura.io";
case "optimism-goerli":
return "optimism-goerli.infura.io";
}
return throwArgumentError("unsupported network", "network", name);

@ -3,6 +3,7 @@
// https://playground.open-rpc.org/?schemaUrl=https://raw.githubusercontent.com/ethereum/eth1.0-apis/assembled-spec/openrpc.json&uiSchema%5BappBar%5D%5Bui:splitView%5D=true&uiSchema%5BappBar%5D%5Bui:input%5D=false&uiSchema%5BappBar%5D%5Bui:examplesDropdown%5D=false
import { getBuiltinCallException } from "../abi/index.js";
import { getAddress, resolveAddress } from "../address/index.js";
import { TypedDataEncoder } from "../hash/index.js";
import { accessListify } from "../transaction/index.js";
@ -295,7 +296,7 @@ export class JsonRpcSigner extends AbstractSigner<JsonRpcApiProvider> {
}
const hexTx = this.provider.getRpcTransaction(tx);
return await this.provider.send("eth_sign_Transaction", [ hexTx ]);
return await this.provider.send("eth_signTransaction", [ hexTx ]);
}
@ -782,39 +783,114 @@ export class JsonRpcApiProvider extends AbstractProvider {
* that different nodes return, coercing them into a machine-readable
* standardized error.
*/
getRpcError(payload: JsonRpcPayload, error: JsonRpcError): Error {
getRpcError(payload: JsonRpcPayload, _error: JsonRpcError): Error {
const { method } = payload;
const { error } = _error;
if (method === "eth_call") {
const transaction = <TransactionLike<string>>((<any>payload).params[0]);
if (method === "eth_call" || method === "eth_estimateGas") {
const result = spelunkData(error);
const e = getBuiltinCallException(
(method === "eth_call") ? "call": "estimateGas",
((<any>payload).params[0]),
(result ? result.data: null)
);
e.info = { error, payload };
return e;
/*
let message = "missing revert data during JSON-RPC call";
const action = <"call" | "estimateGas" | "unknown">(({ eth_call: "call", eth_estimateGas: "estimateGas" })[method] || "unknown");
let data: null | string = null;
let reason: null | string = null;
const transaction = <{ from: string, to: string, data: string }>((<any>payload).params[0]);
const invocation = null;
let revert: null | { signature: string, name: string, args: Array<any> } = null;
if (result) {
// @TODO: Extract errorSignature, errorName, errorArgs, reason if
// it is Error(string) or Panic(uint25)
return makeError("execution reverted during JSON-RPC call", "CALL_EXCEPTION", {
data: result.data,
transaction
});
message = "execution reverted during JSON-RPC call";
data = result.data;
let bytes = getBytes(data);
if (bytes.length % 32 !== 4) {
message += " (could not parse reason; invalid data length)";
} else if (data.substring(0, 10) === "0x08c379a0") {
// Error(string)
try {
if (bytes.length < 68) { throw new Error("bad length"); }
bytes = bytes.slice(4);
const pointer = getNumber(hexlify(bytes.slice(0, 32)));
bytes = bytes.slice(pointer);
if (bytes.length < 32) { throw new Error("overrun"); }
const length = getNumber(hexlify(bytes.slice(0, 32)));
bytes = bytes.slice(32);
if (bytes.length < length) { throw new Error("overrun"); }
reason = toUtf8String(bytes.slice(0, length));
revert = {
signature: "Error(string)",
name: "Error",
args: [ reason ]
};
message += `: ${ JSON.stringify(reason) }`;
} catch (error) {
console.log(error);
message += " (could not parse reason; invalid data length)";
}
} else if (data.substring(0, 10) === "0x4e487b71") {
// Panic(uint256)
try {
if (bytes.length !== 36) { throw new Error("bad length"); }
const arg = getNumber(hexlify(bytes.slice(4)));
revert = {
signature: "Panic(uint256)",
name: "Panic",
args: [ arg ]
};
reason = `Panic due to ${ PanicReasons.get(Number(arg)) || "UNKNOWN" }(${ arg })`;
message += `: ${ reason }`;
} catch (error) {
console.log(error);
message += " (could not parse panic reason)";
}
}
}
return makeError("missing revert data during JSON-RPC call", "CALL_EXCEPTION", {
data: "0x", transaction, info: { error }
return makeError(message, "CALL_EXCEPTION", {
action, data, reason, transaction, invocation, revert,
info: { payload, error }
});
*/
}
// Only estimateGas and call can return arbitrary contract-defined text, so now we
// we can process text safely.
const message = JSON.stringify(spelunkMessage(error));
if (method === "eth_estimateGas") {
const transaction = <TransactionLike<string>>((<any>payload).params[0]);
if (typeof(error.message) === "string" && error.message.match(/user denied|ethers-user-denied/i)) {
const actionMap: Record<string, "requestAccess" | "sendTransaction" | "signMessage" | "signTransaction" | "signTypedData"> = {
eth_sign: "signMessage",
personal_sign: "signMessage",
eth_signTypedData_v4: "signTypedData",
eth_signTransaction: "signTransaction",
eth_sendTransaction: "sendTransaction",
eth_requestAccounts: "requestAccess",
wallet_requestAccounts: "requestAccess",
};
if (message.match(/gas required exceeds allowance|always failing transaction|execution reverted/)) {
return makeError("cannot estimate gas; transaction may fail or may require manual gas limit", "UNPREDICTABLE_GAS_LIMIT", {
transaction
});
}
return makeError(`user rejected action`, "ACTION_REJECTED", {
action: (actionMap[method] || "unknown") ,
reason: "rejected",
info: { payload, error }
});
}
if (method === "eth_sendRawTransaction" || method === "eth_sendTransaction") {
const transaction = <TransactionLike<string>>((<any>payload).params[0]);
@ -840,6 +916,12 @@ export class JsonRpcApiProvider extends AbstractProvider {
}
}
if (message.match(/the method .* does not exist/i)) {
return makeError("unsupported operation", "UNSUPPORTED_OPERATION", {
operation: payload.method
});
}
return makeError("could not coalesce error", "UNKNOWN_ERROR", { error });
}
@ -893,7 +975,7 @@ export class JsonRpcApiProvider extends AbstractProvider {
// Account index
if (typeof(address) === "number") {
const accounts = <Array<string>>(await accountsPromise);
if (address > accounts.length) { throw new Error("no such account"); }
if (address >= accounts.length) { throw new Error("no such account"); }
return new JsonRpcSigner(this, accounts[address]);
}
@ -914,6 +996,37 @@ export class JsonRpcApiProvider extends AbstractProvider {
}
}
export class JsonRpcApiPollingProvider extends JsonRpcApiProvider {
#pollingInterval: number;
constructor(network?: Networkish, options?: JsonRpcApiProviderOptions) {
super(network, options);
this.#pollingInterval = 4000;
}
_getSubscriber(sub: Subscription): Subscriber {
const subscriber = super._getSubscriber(sub);
if (isPollable(subscriber)) {
subscriber.pollingInterval = this.#pollingInterval;
}
return subscriber;
}
/**
* The polling interval (default: 4000 ms)
*/
get pollingInterval(): number { return this.#pollingInterval; }
set pollingInterval(value: number) {
if (!Number.isInteger(value) || value < 0) { throw new Error("invalid interval"); }
this.#pollingInterval = value;
this._forEachSubscriber((sub) => {
if (isPollable(sub)) {
sub.pollingInterval = this.#pollingInterval;
}
});
}
}
/**
* The JsonRpcProvider is one of the most common Providers,
* which performs all operations over HTTP (or HTTPS) requests.
@ -922,11 +1035,9 @@ export class JsonRpcApiProvider extends AbstractProvider {
* number; when it advances, all block-base events are then checked
* for updates.
*/
export class JsonRpcProvider extends JsonRpcApiProvider {
export class JsonRpcProvider extends JsonRpcApiPollingProvider {
#connect: FetchRequest;
#pollingInterval: number;
constructor(url?: string | FetchRequest, network?: Networkish, options?: JsonRpcApiProviderOptions) {
if (url == null) { url = "http:/\/localhost:8545"; }
super(network, options);
@ -936,8 +1047,6 @@ export class JsonRpcProvider extends JsonRpcApiProvider {
} else {
this.#connect = url.clone();
}
this.#pollingInterval = 4000;
}
_getConnection(): FetchRequest {
@ -966,20 +1075,6 @@ export class JsonRpcProvider extends JsonRpcApiProvider {
return resp;
}
/**
* The polling interval (default: 4000 ms)
*/
get pollingInterval(): number { return this.#pollingInterval; }
set pollingInterval(value: number) {
if (!Number.isInteger(value) || value < 0) { throw new Error("invalid interval"); }
this.#pollingInterval = value;
this._forEachSubscriber((sub) => {
if (isPollable(sub)) {
sub.pollingInterval = this.#pollingInterval;
}
});
}
}
function spelunkData(value: any): null | { message: string, data: string } {

@ -124,13 +124,13 @@ export function copyRequest(req: TransactionRequest): PreparedTransactionRequest
if (req.data) { result.data = hexlify(req.data); }
const bigIntKeys = "chainId,gasLimit,gasPrice,maxFeePerGas, maxPriorityFeePerGas,value".split(/,/);
for (const key in bigIntKeys) {
for (const key of bigIntKeys) {
if (!(key in req) || (<any>req)[key] == null) { continue; }
result[key] = getBigInt((<any>req)[key], `request.${ key }`);
}
const numberKeys = "type,nonce".split(/,/);
for (const key in numberKeys) {
for (const key of numberKeys) {
if (!(key in req) || (<any>req)[key] == null) { continue; }
result[key] = getNumber((<any>req)[key], `request.${ key }`);
}