forked from tornado-packages/ethers.js
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1b4debd4a9 | ||
|
|
6017d3d39a | ||
|
|
ccac24a5b0 | ||
|
|
f6d155c820 | ||
|
|
180221574c | ||
|
|
6ee1a5f8bb |
10
CHANGELOG.md
10
CHANGELOG.md
@@ -3,6 +3,16 @@ Change Log
|
||||
|
||||
This change log is maintained by `src.ts/_admin/update-changelog.ts` but may also be manually updated.
|
||||
|
||||
ethers/v6.9.2 (2024-01-02 19:12)
|
||||
--------------------------------
|
||||
|
||||
- Fix Base58 padding for string representation of binary data ([#4527](https://github.com/ethers-io/ethers.js/issues/4527); [ccac24a](https://github.com/ethers-io/ethers.js/commit/ccac24a5b0a4d07a4b639c1c4d0a44703e32d418)).
|
||||
|
||||
ethers/v6.9.1 (2023-12-19 04:53)
|
||||
--------------------------------
|
||||
|
||||
- Fix uncatchable issue when sending transactions over JSON-RPC and provide some retry-recovery for missing v ([#4513](https://github.com/ethers-io/ethers.js/issues/4513); [1802215](https://github.com/ethers-io/ethers.js/commit/180221574c5d2af9ad85404af4fab8752d3d5029)).
|
||||
|
||||
ethers/v6.9.0 (2023-11-27 06:15)
|
||||
--------------------------------
|
||||
|
||||
|
||||
90
dist/ethers.js
vendored
90
dist/ethers.js
vendored
@@ -3,7 +3,7 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
/**
|
||||
* The current version of Ethers.
|
||||
*/
|
||||
const version = "6.9.0";
|
||||
const version = "6.9.2";
|
||||
|
||||
/**
|
||||
* Property helper functions.
|
||||
@@ -695,12 +695,20 @@ const BN_58 = BigInt(58);
|
||||
* Encode %%value%% as a Base58-encoded string.
|
||||
*/
|
||||
function encodeBase58(_value) {
|
||||
let value = toBigInt(getBytes(_value));
|
||||
const bytes = getBytes(_value);
|
||||
let value = toBigInt(bytes);
|
||||
let result = "";
|
||||
while (value) {
|
||||
result = Alphabet[Number(value % BN_58)] + result;
|
||||
value /= BN_58;
|
||||
}
|
||||
// Account for leading padding zeros
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i]) {
|
||||
break;
|
||||
}
|
||||
result = Alphabet[0] + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
@@ -18048,16 +18056,19 @@ class AbstractProvider {
|
||||
// No explicit network was set and this is our first time
|
||||
if (this.#networkPromise == null) {
|
||||
// Detect the current network (shared with all calls)
|
||||
const detectNetwork = this._detectNetwork().then((network) => {
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}, (error) => {
|
||||
// Reset the networkPromise on failure, so we will try again
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
const detectNetwork = (async () => {
|
||||
try {
|
||||
const network = await this._detectNetwork();
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
catch (error) {
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
this.#networkPromise = detectNetwork;
|
||||
return (await detectNetwork).clone();
|
||||
}
|
||||
@@ -19444,12 +19455,45 @@ class JsonRpcSigner extends AbstractSigner {
|
||||
// for it; it should show up very quickly
|
||||
return await (new Promise((resolve, reject) => {
|
||||
const timeouts = [1000, 100];
|
||||
let invalids = 0;
|
||||
const checkTx = async () => {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
try {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// If we were cancelled: stop polling.
|
||||
// If the data is bad: the node returns bad transactions
|
||||
// If the network changed: calling again will also fail
|
||||
// If unsupported: likely destroyed
|
||||
if (isError(error, "CANCELLED") || isError(error, "BAD_DATA") ||
|
||||
isError(error, "NETWORK_ERROR" )) {
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
// Stop-gap for misbehaving backends; see #4513
|
||||
if (isError(error, "INVALID_ARGUMENT")) {
|
||||
invalids++;
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
if (invalids > 10) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Notify anyone that cares; but we will try again, since
|
||||
// it is likely an intermittent service error
|
||||
this.provider.emit("error", makeError("failed to fetch transation after sending (will try again)", "UNKNOWN_ERROR", { error }));
|
||||
}
|
||||
// Wait another 4 seconds
|
||||
this.provider._setTimeout(() => { checkTx(); }, timeouts.pop() || 4000);
|
||||
@@ -19527,7 +19571,7 @@ class JsonRpcApiProvider extends AbstractProvider {
|
||||
if (this.#drainTimer) {
|
||||
return;
|
||||
}
|
||||
// If we aren't using batching, no hard in sending it immeidately
|
||||
// If we aren't using batching, no harm in sending it immediately
|
||||
const stallTime = (this._getOption("batchMaxCount") === 1) ? 0 : this._getOption("batchStallTime");
|
||||
this.#drainTimer = setTimeout(() => {
|
||||
this.#drainTimer = null;
|
||||
@@ -19690,9 +19734,15 @@ class JsonRpcApiProvider extends AbstractProvider {
|
||||
// If we are ready, use ``send``, which enabled requests to be batched
|
||||
if (this.ready) {
|
||||
this.#pendingDetectNetwork = (async () => {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
try {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
this.#pendingDetectNetwork = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
return await this.#pendingDetectNetwork;
|
||||
}
|
||||
|
||||
2
dist/ethers.js.map
vendored
2
dist/ethers.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/ethers.min.js
vendored
2
dist/ethers.min.js
vendored
File diff suppressed because one or more lines are too long
90
dist/ethers.umd.js
vendored
90
dist/ethers.umd.js
vendored
@@ -9,7 +9,7 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
/**
|
||||
* The current version of Ethers.
|
||||
*/
|
||||
const version = "6.9.0";
|
||||
const version = "6.9.2";
|
||||
|
||||
/**
|
||||
* Property helper functions.
|
||||
@@ -701,12 +701,20 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
* Encode %%value%% as a Base58-encoded string.
|
||||
*/
|
||||
function encodeBase58(_value) {
|
||||
let value = toBigInt(getBytes(_value));
|
||||
const bytes = getBytes(_value);
|
||||
let value = toBigInt(bytes);
|
||||
let result = "";
|
||||
while (value) {
|
||||
result = Alphabet[Number(value % BN_58)] + result;
|
||||
value /= BN_58;
|
||||
}
|
||||
// Account for leading padding zeros
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i]) {
|
||||
break;
|
||||
}
|
||||
result = Alphabet[0] + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
@@ -18054,16 +18062,19 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
// No explicit network was set and this is our first time
|
||||
if (this.#networkPromise == null) {
|
||||
// Detect the current network (shared with all calls)
|
||||
const detectNetwork = this._detectNetwork().then((network) => {
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}, (error) => {
|
||||
// Reset the networkPromise on failure, so we will try again
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
const detectNetwork = (async () => {
|
||||
try {
|
||||
const network = await this._detectNetwork();
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
catch (error) {
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
this.#networkPromise = detectNetwork;
|
||||
return (await detectNetwork).clone();
|
||||
}
|
||||
@@ -19450,12 +19461,45 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
// for it; it should show up very quickly
|
||||
return await (new Promise((resolve, reject) => {
|
||||
const timeouts = [1000, 100];
|
||||
let invalids = 0;
|
||||
const checkTx = async () => {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
try {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// If we were cancelled: stop polling.
|
||||
// If the data is bad: the node returns bad transactions
|
||||
// If the network changed: calling again will also fail
|
||||
// If unsupported: likely destroyed
|
||||
if (isError(error, "CANCELLED") || isError(error, "BAD_DATA") ||
|
||||
isError(error, "NETWORK_ERROR" )) {
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
// Stop-gap for misbehaving backends; see #4513
|
||||
if (isError(error, "INVALID_ARGUMENT")) {
|
||||
invalids++;
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
if (invalids > 10) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Notify anyone that cares; but we will try again, since
|
||||
// it is likely an intermittent service error
|
||||
this.provider.emit("error", makeError("failed to fetch transation after sending (will try again)", "UNKNOWN_ERROR", { error }));
|
||||
}
|
||||
// Wait another 4 seconds
|
||||
this.provider._setTimeout(() => { checkTx(); }, timeouts.pop() || 4000);
|
||||
@@ -19533,7 +19577,7 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
if (this.#drainTimer) {
|
||||
return;
|
||||
}
|
||||
// If we aren't using batching, no hard in sending it immeidately
|
||||
// If we aren't using batching, no harm in sending it immediately
|
||||
const stallTime = (this._getOption("batchMaxCount") === 1) ? 0 : this._getOption("batchStallTime");
|
||||
this.#drainTimer = setTimeout(() => {
|
||||
this.#drainTimer = null;
|
||||
@@ -19696,9 +19740,15 @@ const __$G = (typeof globalThis !== 'undefined' ? globalThis: typeof window !==
|
||||
// If we are ready, use ``send``, which enabled requests to be batched
|
||||
if (this.ready) {
|
||||
this.#pendingDetectNetwork = (async () => {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
try {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
this.#pendingDetectNetwork = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
return await this.#pendingDetectNetwork;
|
||||
}
|
||||
|
||||
2
dist/ethers.umd.js.map
vendored
2
dist/ethers.umd.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/ethers.umd.min.js
vendored
2
dist/ethers.umd.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/wordlists-extra.js
vendored
2
dist/wordlists-extra.js
vendored
@@ -100,7 +100,7 @@ const rotlBL = (h, l, s) => (h << (s - 32)) | (l >>> (64 - s));
|
||||
/**
|
||||
* The current version of Ethers.
|
||||
*/
|
||||
const version = "6.9.0";
|
||||
const version = "6.9.2";
|
||||
|
||||
/**
|
||||
* Property helper functions.
|
||||
|
||||
2
dist/wordlists-extra.js.map
vendored
2
dist/wordlists-extra.js.map
vendored
File diff suppressed because one or more lines are too long
2
dist/wordlists-extra.min.js
vendored
2
dist/wordlists-extra.min.js
vendored
File diff suppressed because one or more lines are too long
@@ -129,8 +129,8 @@ _code: @lang<script>
|
||||
|
||||
// If MetaMask is not installed, we use the default provider,
|
||||
// which is backed by a variety of third-party services (such
|
||||
// as INFURA). They do not have private keys installed so are
|
||||
// only have read-only access
|
||||
// as INFURA). They do not have private keys installed,
|
||||
// so they only have read-only access
|
||||
console.log("MetaMask not installed; using read-only defaults")
|
||||
provider = ethers.getDefaultProvider()
|
||||
|
||||
@@ -156,7 +156,7 @@ you can use the [[JsonRpcProvider]] directly, which communicates
|
||||
using the [[link-jsonrpc]] protocol.
|
||||
|
||||
When using your own Ethereum node or a developer-base blockchain,
|
||||
such as Hardhat or Ganache, you can get access the accounts with
|
||||
such as Hardhat or Ganache, you can get access to the accounts with
|
||||
[[JsonRpcProvider-getSigner]].
|
||||
|
||||
_code: connecting to a JSON-RPC URL @lang<script>
|
||||
@@ -177,7 +177,7 @@ results when performing mathematic operations.
|
||||
|
||||
As a result, the internal units used (e.g. wei) which are suited for
|
||||
machine-readable purposes and maths are often very large and not
|
||||
terribly human-readable.
|
||||
easily human-readable.
|
||||
|
||||
For example, imagine dealing with dollars and cents; you would show
|
||||
values like ``"$2.56"``. In the blockchain world, we would keep all
|
||||
|
||||
2
lib.commonjs/_tests/test-provider-jsonrpc.d.ts
vendored
Normal file
2
lib.commonjs/_tests/test-provider-jsonrpc.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=test-provider-jsonrpc.d.ts.map
|
||||
1
lib.commonjs/_tests/test-provider-jsonrpc.d.ts.map
Normal file
1
lib.commonjs/_tests/test-provider-jsonrpc.d.ts.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"test-provider-jsonrpc.d.ts","sourceRoot":"","sources":["../../src.ts/_tests/test-provider-jsonrpc.ts"],"names":[],"mappings":""}
|
||||
160
lib.commonjs/_tests/test-provider-jsonrpc.js
Normal file
160
lib.commonjs/_tests/test-provider-jsonrpc.js
Normal file
@@ -0,0 +1,160 @@
|
||||
"use strict";
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const tslib_1 = require("tslib");
|
||||
const assert_1 = tslib_1.__importDefault(require("assert"));
|
||||
const index_js_1 = require("../index.js");
|
||||
const StatusMessages = {
|
||||
200: "OK",
|
||||
400: "BAD REQUEST",
|
||||
500: "SERVER ERROR",
|
||||
};
|
||||
const wallet = new index_js_1.Wallet((0, index_js_1.id)("test"));
|
||||
function createProvider(testFunc) {
|
||||
let blockNumber = 1;
|
||||
const ticker = setInterval(() => { blockNumber++; }, 100);
|
||||
if (ticker.unref) {
|
||||
ticker.unref();
|
||||
}
|
||||
const processReq = (req) => {
|
||||
let result = testFunc(req.method, req.params, blockNumber);
|
||||
if (result === undefined) {
|
||||
switch (req.method) {
|
||||
case "eth_blockNumber":
|
||||
result = blockNumber;
|
||||
break;
|
||||
case "eth_chainId":
|
||||
result = "0x1337";
|
||||
break;
|
||||
case "eth_accounts":
|
||||
result = [wallet.address];
|
||||
break;
|
||||
default:
|
||||
console.log("****", req);
|
||||
return { id: index_js_1.id, error: "unsupported", jsonrpc: "2.0" };
|
||||
}
|
||||
}
|
||||
return { id: req.id, result, jsonrpc: "2.0" };
|
||||
};
|
||||
const req = new index_js_1.FetchRequest("http:/\/localhost:8082/");
|
||||
req.getUrlFunc = async (_req, signal) => {
|
||||
const req = JSON.parse(_req.hasBody() ? (0, index_js_1.toUtf8String)(_req.body) : "");
|
||||
let statusCode = 200;
|
||||
const headers = {};
|
||||
let resp;
|
||||
try {
|
||||
if (Array.isArray(req)) {
|
||||
resp = req.map((r) => processReq(r));
|
||||
}
|
||||
else {
|
||||
resp = processReq(req);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
statusCode = 500;
|
||||
resp = error.message;
|
||||
}
|
||||
const body = (0, index_js_1.toUtf8Bytes)(JSON.stringify(resp));
|
||||
return {
|
||||
statusCode,
|
||||
statusMessage: StatusMessages[statusCode],
|
||||
headers, body
|
||||
};
|
||||
};
|
||||
return new index_js_1.JsonRpcProvider(req, undefined, { cacheTimeout: -1 });
|
||||
}
|
||||
describe("Ensure Catchable Errors", function () {
|
||||
it("Can catch bad broadcast replies", async function () {
|
||||
this.timeout(15000);
|
||||
const txInfo = {
|
||||
chainId: 1337,
|
||||
gasLimit: 100000,
|
||||
maxFeePerGas: 2000000000,
|
||||
maxPriorityFeePerGas: 1000000000,
|
||||
to: wallet.address,
|
||||
value: 1,
|
||||
};
|
||||
const txSign = await wallet.signTransaction(txInfo);
|
||||
const txObj = index_js_1.Transaction.from(txSign);
|
||||
let count = 0;
|
||||
const provider = createProvider((method, params, blockNumber) => {
|
||||
switch (method) {
|
||||
case "eth_sendTransaction":
|
||||
return txObj.hash;
|
||||
case "eth_getTransactionByHash": {
|
||||
count++;
|
||||
// First time; fail!
|
||||
if (count === 1) {
|
||||
throw (0, index_js_1.makeError)("Faux Error", "SERVER_ERROR", {
|
||||
request: ({})
|
||||
});
|
||||
}
|
||||
// Second time; return null
|
||||
if (count === 2) {
|
||||
return null;
|
||||
}
|
||||
// Return a valid tx...
|
||||
const result = Object.assign({}, txObj.toJSON(), txObj.signature.toJSON(), { hash: txObj.hash, from: wallet.address });
|
||||
// ...eventually mined
|
||||
if (count > 4) {
|
||||
result.blockNumber = blockNumber;
|
||||
result.blockHash = (0, index_js_1.id)("test");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
const signer = await provider.getSigner();
|
||||
const tx = await signer.sendTransaction(txInfo);
|
||||
(0, assert_1.default)(tx);
|
||||
});
|
||||
it("Missing v is recovered", async function () {
|
||||
this.timeout(15000);
|
||||
const txInfo = {
|
||||
chainId: 1337,
|
||||
gasLimit: 100000,
|
||||
maxFeePerGas: 2000000000,
|
||||
maxPriorityFeePerGas: 1000000000,
|
||||
to: wallet.address,
|
||||
value: 1,
|
||||
};
|
||||
const txSign = await wallet.signTransaction(txInfo);
|
||||
const txObj = index_js_1.Transaction.from(txSign);
|
||||
let count = 0;
|
||||
// A provider which is mocked to return a "missing v"
|
||||
// in getTransaction
|
||||
const provider = createProvider((method, params, blockNumber) => {
|
||||
switch (method) {
|
||||
case "eth_sendTransaction":
|
||||
return txObj.hash;
|
||||
case "eth_getTransactionByHash": {
|
||||
count++;
|
||||
// The fully valid tx response
|
||||
const result = Object.assign({}, txObj.toJSON(), txObj.signature.toJSON(), { hash: txObj.hash, from: wallet.address, sig: null });
|
||||
// First time; fail with a missing v!
|
||||
if (count < 2) {
|
||||
delete result.v;
|
||||
}
|
||||
// Debug
|
||||
result._count = count;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
// Track any "missing v" error
|
||||
let missingV = null;
|
||||
provider.on("error", (e) => {
|
||||
if ((0, index_js_1.isError)(e, "UNKNOWN_ERROR") && (0, index_js_1.isError)(e.error, "INVALID_ARGUMENT")) {
|
||||
if (e.error.argument === "signature" && e.error.shortMessage === "missing v") {
|
||||
missingV = e.error;
|
||||
}
|
||||
}
|
||||
});
|
||||
const signer = await provider.getSigner();
|
||||
const tx = await signer.sendTransaction(txInfo);
|
||||
assert_1.default.ok(!!tx, "we got a transaction");
|
||||
assert_1.default.ok(!!missingV, "missing v error present");
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=test-provider-jsonrpc.js.map
|
||||
1
lib.commonjs/_tests/test-provider-jsonrpc.js.map
Normal file
1
lib.commonjs/_tests/test-provider-jsonrpc.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -5,5 +5,5 @@ exports.version = void 0;
|
||||
/**
|
||||
* The current version of Ethers.
|
||||
*/
|
||||
exports.version = "6.9.0";
|
||||
exports.version = "6.9.2";
|
||||
//# sourceMappingURL=_version.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -544,16 +544,19 @@ class AbstractProvider {
|
||||
// No explicit network was set and this is our first time
|
||||
if (this.#networkPromise == null) {
|
||||
// Detect the current network (shared with all calls)
|
||||
const detectNetwork = this._detectNetwork().then((network) => {
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}, (error) => {
|
||||
// Reset the networkPromise on failure, so we will try again
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
const detectNetwork = (async () => {
|
||||
try {
|
||||
const network = await this._detectNetwork();
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
catch (error) {
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
this.#networkPromise = detectNetwork;
|
||||
return (await detectNetwork).clone();
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -136,12 +136,45 @@ class JsonRpcSigner extends abstract_signer_js_1.AbstractSigner {
|
||||
// for it; it should show up very quickly
|
||||
return await (new Promise((resolve, reject) => {
|
||||
const timeouts = [1000, 100];
|
||||
let invalids = 0;
|
||||
const checkTx = async () => {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
try {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// If we were cancelled: stop polling.
|
||||
// If the data is bad: the node returns bad transactions
|
||||
// If the network changed: calling again will also fail
|
||||
// If unsupported: likely destroyed
|
||||
if ((0, index_js_5.isError)(error, "CANCELLED") || (0, index_js_5.isError)(error, "BAD_DATA") ||
|
||||
(0, index_js_5.isError)(error, "NETWORK_ERROR" || (0, index_js_5.isError)(error, "UNSUPPORTED_OPERATION"))) {
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
// Stop-gap for misbehaving backends; see #4513
|
||||
if ((0, index_js_5.isError)(error, "INVALID_ARGUMENT")) {
|
||||
invalids++;
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
if (invalids > 10) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Notify anyone that cares; but we will try again, since
|
||||
// it is likely an intermittent service error
|
||||
this.provider.emit("error", (0, index_js_5.makeError)("failed to fetch transation after sending (will try again)", "UNKNOWN_ERROR", { error }));
|
||||
}
|
||||
// Wait another 4 seconds
|
||||
this.provider._setTimeout(() => { checkTx(); }, timeouts.pop() || 4000);
|
||||
@@ -220,7 +253,7 @@ class JsonRpcApiProvider extends abstract_provider_js_1.AbstractProvider {
|
||||
if (this.#drainTimer) {
|
||||
return;
|
||||
}
|
||||
// If we aren't using batching, no hard in sending it immeidately
|
||||
// If we aren't using batching, no harm in sending it immediately
|
||||
const stallTime = (this._getOption("batchMaxCount") === 1) ? 0 : this._getOption("batchStallTime");
|
||||
this.#drainTimer = setTimeout(() => {
|
||||
this.#drainTimer = null;
|
||||
@@ -383,9 +416,15 @@ class JsonRpcApiProvider extends abstract_provider_js_1.AbstractProvider {
|
||||
// If we are ready, use ``send``, which enabled requests to be batched
|
||||
if (this.ready) {
|
||||
this.#pendingDetectNetwork = (async () => {
|
||||
const result = network_js_1.Network.from((0, index_js_5.getBigInt)(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
try {
|
||||
const result = network_js_1.Network.from((0, index_js_5.getBigInt)(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
this.#pendingDetectNetwork = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
return await this.#pendingDetectNetwork;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"version":3,"file":"base58.d.ts","sourceRoot":"","sources":["../../src.ts/utils/base58.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAsB5C;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAQtD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlD"}
|
||||
{"version":3,"file":"base58.d.ts","sourceRoot":"","sources":["../../src.ts/utils/base58.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAsB5C;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAiBtD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlD"}
|
||||
@@ -36,12 +36,20 @@ const BN_58 = BigInt(58);
|
||||
* Encode %%value%% as a Base58-encoded string.
|
||||
*/
|
||||
function encodeBase58(_value) {
|
||||
let value = (0, maths_js_1.toBigInt)((0, data_js_1.getBytes)(_value));
|
||||
const bytes = (0, data_js_1.getBytes)(_value);
|
||||
let value = (0, maths_js_1.toBigInt)(bytes);
|
||||
let result = "";
|
||||
while (value) {
|
||||
result = Alphabet[Number(value % BN_58)] + result;
|
||||
value /= BN_58;
|
||||
}
|
||||
// Account for leading padding zeros
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i]) {
|
||||
break;
|
||||
}
|
||||
result = Alphabet[0] + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
exports.encodeBase58 = encodeBase58;
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"base58.js","sourceRoot":"","sources":["../../src.ts/utils/base58.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AAEH,uCAAqC;AACrC,2CAA6C;AAC7C,yCAAsC;AAKtC,MAAM,QAAQ,GAAG,4DAA4D,CAAC;AAC9E,IAAI,MAAM,GAAkC,IAAI,CAAC;AAEjD,SAAS,QAAQ,CAAC,MAAc;IAC5B,IAAI,MAAM,IAAI,IAAI,EAAE;QAChB,MAAM,GAAG,EAAG,CAAC;QACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACtC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;SACnC;KACJ;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9B,IAAA,0BAAc,EAAC,MAAM,IAAI,IAAI,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACzE,OAAO,MAAM,CAAC;AAClB,CAAC;AAGD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;AACvB,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;AAEzB;;GAEG;AACH,SAAgB,YAAY,CAAC,MAAiB;IAC1C,IAAI,KAAK,GAAG,IAAA,mBAAQ,EAAC,IAAA,kBAAQ,EAAC,MAAM,CAAC,CAAC,CAAC;IACvC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,OAAO,KAAK,EAAE;QACV,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC;QAClD,KAAK,IAAI,KAAK,CAAC;KAClB;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AARD,oCAQC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,KAAa;IACtC,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACnC,MAAM,IAAI,KAAK,CAAC;QAChB,MAAM,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;KAChC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAPD,oCAOC"}
|
||||
{"version":3,"file":"base58.js","sourceRoot":"","sources":["../../src.ts/utils/base58.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;AAEH,uCAAqC;AACrC,2CAA6C;AAC7C,yCAAsC;AAKtC,MAAM,QAAQ,GAAG,4DAA4D,CAAC;AAC9E,IAAI,MAAM,GAAkC,IAAI,CAAC;AAEjD,SAAS,QAAQ,CAAC,MAAc;IAC5B,IAAI,MAAM,IAAI,IAAI,EAAE;QAChB,MAAM,GAAG,EAAG,CAAC;QACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACtC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;SACnC;KACJ;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9B,IAAA,0BAAc,EAAC,MAAM,IAAI,IAAI,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACzE,OAAO,MAAM,CAAC;AAClB,CAAC;AAGD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;AACvB,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;AAEzB;;GAEG;AACH,SAAgB,YAAY,CAAC,MAAiB;IAC1C,MAAM,KAAK,GAAG,IAAA,kBAAQ,EAAC,MAAM,CAAC,CAAC;IAE/B,IAAI,KAAK,GAAG,IAAA,mBAAQ,EAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,OAAO,KAAK,EAAE;QACV,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC;QAClD,KAAK,IAAI,KAAK,CAAC;KAClB;IAED,oCAAoC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACnC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE;YAAE,MAAM;SAAE;QACxB,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;KACjC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAjBD,oCAiBC;AAED;;GAEG;AACH,SAAgB,YAAY,CAAC,KAAa;IACtC,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACnC,MAAM,IAAI,KAAK,CAAC;QAChB,MAAM,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;KAChC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAPD,oCAOC"}
|
||||
2
lib.esm/_tests/test-provider-jsonrpc.d.ts
vendored
Normal file
2
lib.esm/_tests/test-provider-jsonrpc.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
export {};
|
||||
//# sourceMappingURL=test-provider-jsonrpc.d.ts.map
|
||||
1
lib.esm/_tests/test-provider-jsonrpc.d.ts.map
Normal file
1
lib.esm/_tests/test-provider-jsonrpc.d.ts.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"test-provider-jsonrpc.d.ts","sourceRoot":"","sources":["../../src.ts/_tests/test-provider-jsonrpc.ts"],"names":[],"mappings":""}
|
||||
157
lib.esm/_tests/test-provider-jsonrpc.js
Normal file
157
lib.esm/_tests/test-provider-jsonrpc.js
Normal file
@@ -0,0 +1,157 @@
|
||||
import assert from "assert";
|
||||
import { id, isError, makeError, toUtf8Bytes, toUtf8String, FetchRequest, JsonRpcProvider, Transaction, Wallet } from "../index.js";
|
||||
const StatusMessages = {
|
||||
200: "OK",
|
||||
400: "BAD REQUEST",
|
||||
500: "SERVER ERROR",
|
||||
};
|
||||
const wallet = new Wallet(id("test"));
|
||||
function createProvider(testFunc) {
|
||||
let blockNumber = 1;
|
||||
const ticker = setInterval(() => { blockNumber++; }, 100);
|
||||
if (ticker.unref) {
|
||||
ticker.unref();
|
||||
}
|
||||
const processReq = (req) => {
|
||||
let result = testFunc(req.method, req.params, blockNumber);
|
||||
if (result === undefined) {
|
||||
switch (req.method) {
|
||||
case "eth_blockNumber":
|
||||
result = blockNumber;
|
||||
break;
|
||||
case "eth_chainId":
|
||||
result = "0x1337";
|
||||
break;
|
||||
case "eth_accounts":
|
||||
result = [wallet.address];
|
||||
break;
|
||||
default:
|
||||
console.log("****", req);
|
||||
return { id, error: "unsupported", jsonrpc: "2.0" };
|
||||
}
|
||||
}
|
||||
return { id: req.id, result, jsonrpc: "2.0" };
|
||||
};
|
||||
const req = new FetchRequest("http:/\/localhost:8082/");
|
||||
req.getUrlFunc = async (_req, signal) => {
|
||||
const req = JSON.parse(_req.hasBody() ? toUtf8String(_req.body) : "");
|
||||
let statusCode = 200;
|
||||
const headers = {};
|
||||
let resp;
|
||||
try {
|
||||
if (Array.isArray(req)) {
|
||||
resp = req.map((r) => processReq(r));
|
||||
}
|
||||
else {
|
||||
resp = processReq(req);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
statusCode = 500;
|
||||
resp = error.message;
|
||||
}
|
||||
const body = toUtf8Bytes(JSON.stringify(resp));
|
||||
return {
|
||||
statusCode,
|
||||
statusMessage: StatusMessages[statusCode],
|
||||
headers, body
|
||||
};
|
||||
};
|
||||
return new JsonRpcProvider(req, undefined, { cacheTimeout: -1 });
|
||||
}
|
||||
describe("Ensure Catchable Errors", function () {
|
||||
it("Can catch bad broadcast replies", async function () {
|
||||
this.timeout(15000);
|
||||
const txInfo = {
|
||||
chainId: 1337,
|
||||
gasLimit: 100000,
|
||||
maxFeePerGas: 2000000000,
|
||||
maxPriorityFeePerGas: 1000000000,
|
||||
to: wallet.address,
|
||||
value: 1,
|
||||
};
|
||||
const txSign = await wallet.signTransaction(txInfo);
|
||||
const txObj = Transaction.from(txSign);
|
||||
let count = 0;
|
||||
const provider = createProvider((method, params, blockNumber) => {
|
||||
switch (method) {
|
||||
case "eth_sendTransaction":
|
||||
return txObj.hash;
|
||||
case "eth_getTransactionByHash": {
|
||||
count++;
|
||||
// First time; fail!
|
||||
if (count === 1) {
|
||||
throw makeError("Faux Error", "SERVER_ERROR", {
|
||||
request: ({})
|
||||
});
|
||||
}
|
||||
// Second time; return null
|
||||
if (count === 2) {
|
||||
return null;
|
||||
}
|
||||
// Return a valid tx...
|
||||
const result = Object.assign({}, txObj.toJSON(), txObj.signature.toJSON(), { hash: txObj.hash, from: wallet.address });
|
||||
// ...eventually mined
|
||||
if (count > 4) {
|
||||
result.blockNumber = blockNumber;
|
||||
result.blockHash = id("test");
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
const signer = await provider.getSigner();
|
||||
const tx = await signer.sendTransaction(txInfo);
|
||||
assert(tx);
|
||||
});
|
||||
it("Missing v is recovered", async function () {
|
||||
this.timeout(15000);
|
||||
const txInfo = {
|
||||
chainId: 1337,
|
||||
gasLimit: 100000,
|
||||
maxFeePerGas: 2000000000,
|
||||
maxPriorityFeePerGas: 1000000000,
|
||||
to: wallet.address,
|
||||
value: 1,
|
||||
};
|
||||
const txSign = await wallet.signTransaction(txInfo);
|
||||
const txObj = Transaction.from(txSign);
|
||||
let count = 0;
|
||||
// A provider which is mocked to return a "missing v"
|
||||
// in getTransaction
|
||||
const provider = createProvider((method, params, blockNumber) => {
|
||||
switch (method) {
|
||||
case "eth_sendTransaction":
|
||||
return txObj.hash;
|
||||
case "eth_getTransactionByHash": {
|
||||
count++;
|
||||
// The fully valid tx response
|
||||
const result = Object.assign({}, txObj.toJSON(), txObj.signature.toJSON(), { hash: txObj.hash, from: wallet.address, sig: null });
|
||||
// First time; fail with a missing v!
|
||||
if (count < 2) {
|
||||
delete result.v;
|
||||
}
|
||||
// Debug
|
||||
result._count = count;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
// Track any "missing v" error
|
||||
let missingV = null;
|
||||
provider.on("error", (e) => {
|
||||
if (isError(e, "UNKNOWN_ERROR") && isError(e.error, "INVALID_ARGUMENT")) {
|
||||
if (e.error.argument === "signature" && e.error.shortMessage === "missing v") {
|
||||
missingV = e.error;
|
||||
}
|
||||
}
|
||||
});
|
||||
const signer = await provider.getSigner();
|
||||
const tx = await signer.sendTransaction(txInfo);
|
||||
assert.ok(!!tx, "we got a transaction");
|
||||
assert.ok(!!missingV, "missing v error present");
|
||||
});
|
||||
});
|
||||
//# sourceMappingURL=test-provider-jsonrpc.js.map
|
||||
1
lib.esm/_tests/test-provider-jsonrpc.js.map
Normal file
1
lib.esm/_tests/test-provider-jsonrpc.js.map
Normal file
File diff suppressed because one or more lines are too long
@@ -2,5 +2,5 @@
|
||||
/**
|
||||
* The current version of Ethers.
|
||||
*/
|
||||
export const version = "6.9.0";
|
||||
export const version = "6.9.2";
|
||||
//# sourceMappingURL=_version.js.map
|
||||
File diff suppressed because one or more lines are too long
@@ -540,16 +540,19 @@ export class AbstractProvider {
|
||||
// No explicit network was set and this is our first time
|
||||
if (this.#networkPromise == null) {
|
||||
// Detect the current network (shared with all calls)
|
||||
const detectNetwork = this._detectNetwork().then((network) => {
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}, (error) => {
|
||||
// Reset the networkPromise on failure, so we will try again
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
const detectNetwork = (async () => {
|
||||
try {
|
||||
const network = await this._detectNetwork();
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
catch (error) {
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
this.#networkPromise = detectNetwork;
|
||||
return (await detectNetwork).clone();
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -16,7 +16,7 @@ import { AbiCoder } from "../abi/index.js";
|
||||
import { getAddress, resolveAddress } from "../address/index.js";
|
||||
import { TypedDataEncoder } from "../hash/index.js";
|
||||
import { accessListify } from "../transaction/index.js";
|
||||
import { defineProperties, getBigInt, hexlify, isHexString, toQuantity, toUtf8Bytes, makeError, assert, assertArgument, FetchRequest, resolveProperties } from "../utils/index.js";
|
||||
import { defineProperties, getBigInt, hexlify, isHexString, toQuantity, toUtf8Bytes, isError, makeError, assert, assertArgument, FetchRequest, resolveProperties } from "../utils/index.js";
|
||||
import { AbstractProvider, UnmanagedSubscriber } from "./abstract-provider.js";
|
||||
import { AbstractSigner } from "./abstract-signer.js";
|
||||
import { Network } from "./network.js";
|
||||
@@ -133,12 +133,45 @@ export class JsonRpcSigner extends AbstractSigner {
|
||||
// for it; it should show up very quickly
|
||||
return await (new Promise((resolve, reject) => {
|
||||
const timeouts = [1000, 100];
|
||||
let invalids = 0;
|
||||
const checkTx = async () => {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
try {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
// If we were cancelled: stop polling.
|
||||
// If the data is bad: the node returns bad transactions
|
||||
// If the network changed: calling again will also fail
|
||||
// If unsupported: likely destroyed
|
||||
if (isError(error, "CANCELLED") || isError(error, "BAD_DATA") ||
|
||||
isError(error, "NETWORK_ERROR" || isError(error, "UNSUPPORTED_OPERATION"))) {
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
// Stop-gap for misbehaving backends; see #4513
|
||||
if (isError(error, "INVALID_ARGUMENT")) {
|
||||
invalids++;
|
||||
if (error.info == null) {
|
||||
error.info = {};
|
||||
}
|
||||
error.info.sendTransactionHash = hash;
|
||||
if (invalids > 10) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Notify anyone that cares; but we will try again, since
|
||||
// it is likely an intermittent service error
|
||||
this.provider.emit("error", makeError("failed to fetch transation after sending (will try again)", "UNKNOWN_ERROR", { error }));
|
||||
}
|
||||
// Wait another 4 seconds
|
||||
this.provider._setTimeout(() => { checkTx(); }, timeouts.pop() || 4000);
|
||||
@@ -216,7 +249,7 @@ export class JsonRpcApiProvider extends AbstractProvider {
|
||||
if (this.#drainTimer) {
|
||||
return;
|
||||
}
|
||||
// If we aren't using batching, no hard in sending it immeidately
|
||||
// If we aren't using batching, no harm in sending it immediately
|
||||
const stallTime = (this._getOption("batchMaxCount") === 1) ? 0 : this._getOption("batchStallTime");
|
||||
this.#drainTimer = setTimeout(() => {
|
||||
this.#drainTimer = null;
|
||||
@@ -379,9 +412,15 @@ export class JsonRpcApiProvider extends AbstractProvider {
|
||||
// If we are ready, use ``send``, which enabled requests to be batched
|
||||
if (this.ready) {
|
||||
this.#pendingDetectNetwork = (async () => {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
try {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
}
|
||||
catch (error) {
|
||||
this.#pendingDetectNetwork = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
return await this.#pendingDetectNetwork;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1 +1 @@
|
||||
{"version":3,"file":"base58.d.ts","sourceRoot":"","sources":["../../src.ts/utils/base58.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAsB5C;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAQtD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlD"}
|
||||
{"version":3,"file":"base58.d.ts","sourceRoot":"","sources":["../../src.ts/utils/base58.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAMH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAsB5C;;GAEG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAiBtD;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOlD"}
|
||||
@@ -33,12 +33,20 @@ const BN_58 = BigInt(58);
|
||||
* Encode %%value%% as a Base58-encoded string.
|
||||
*/
|
||||
export function encodeBase58(_value) {
|
||||
let value = toBigInt(getBytes(_value));
|
||||
const bytes = getBytes(_value);
|
||||
let value = toBigInt(bytes);
|
||||
let result = "";
|
||||
while (value) {
|
||||
result = Alphabet[Number(value % BN_58)] + result;
|
||||
value /= BN_58;
|
||||
}
|
||||
// Account for leading padding zeros
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i]) {
|
||||
break;
|
||||
}
|
||||
result = Alphabet[0] + result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
/**
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"version":3,"file":"base58.js","sourceRoot":"","sources":["../../src.ts/utils/base58.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAKtC,MAAM,QAAQ,GAAG,4DAA4D,CAAC;AAC9E,IAAI,MAAM,GAAkC,IAAI,CAAC;AAEjD,SAAS,QAAQ,CAAC,MAAc;IAC5B,IAAI,MAAM,IAAI,IAAI,EAAE;QAChB,MAAM,GAAG,EAAG,CAAC;QACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACtC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;SACnC;KACJ;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9B,cAAc,CAAC,MAAM,IAAI,IAAI,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACzE,OAAO,MAAM,CAAC;AAClB,CAAC;AAGD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;AACvB,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;AAEzB;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC1C,IAAI,KAAK,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACvC,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,OAAO,KAAK,EAAE;QACV,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC;QAClD,KAAK,IAAI,KAAK,CAAC;KAClB;IACD,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACtC,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACnC,MAAM,IAAI,KAAK,CAAC;QAChB,MAAM,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;KAChC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC"}
|
||||
{"version":3,"file":"base58.js","sourceRoot":"","sources":["../../src.ts/utils/base58.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAKtC,MAAM,QAAQ,GAAG,4DAA4D,CAAC;AAC9E,IAAI,MAAM,GAAkC,IAAI,CAAC;AAEjD,SAAS,QAAQ,CAAC,MAAc;IAC5B,IAAI,MAAM,IAAI,IAAI,EAAE;QAChB,MAAM,GAAG,EAAG,CAAC;QACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;YACtC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;SACnC;KACJ;IACD,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC;IAC9B,cAAc,CAAC,MAAM,IAAI,IAAI,EAAE,sBAAsB,EAAE,QAAQ,EAAE,MAAM,CAAC,CAAC;IACzE,OAAO,MAAM,CAAC;AAClB,CAAC;AAGD,MAAM,IAAI,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC;AACvB,MAAM,KAAK,GAAG,MAAM,CAAC,EAAE,CAAC,CAAC;AAEzB;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,MAAiB;IAC1C,MAAM,KAAK,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAC;IAE/B,IAAI,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC5B,IAAI,MAAM,GAAG,EAAE,CAAC;IAChB,OAAO,KAAK,EAAE;QACV,MAAM,GAAG,QAAQ,CAAC,MAAM,CAAC,KAAK,GAAG,KAAK,CAAC,CAAC,GAAG,MAAM,CAAC;QAClD,KAAK,IAAI,KAAK,CAAC;KAClB;IAED,oCAAoC;IACpC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACnC,IAAI,KAAK,CAAC,CAAC,CAAC,EAAE;YAAE,MAAM;SAAE;QACxB,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;KACjC;IAED,OAAO,MAAM,CAAC;AAClB,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,YAAY,CAAC,KAAa;IACtC,IAAI,MAAM,GAAG,IAAI,CAAC;IAClB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;QACnC,MAAM,IAAI,KAAK,CAAC;QAChB,MAAM,IAAI,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;KAChC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC"}
|
||||
@@ -93,7 +93,7 @@
|
||||
"url": "https://www.buymeacoffee.com/ricmoo"
|
||||
}
|
||||
],
|
||||
"gitHead": "f8f11c754aa2c9b541db73d3bde66a8ffa5146f0",
|
||||
"gitHead": "ccac24a5b0a4d07a4b639c1c4d0a44703e32d418",
|
||||
"homepage": "https://ethers.org",
|
||||
"keywords": [
|
||||
"ethereum",
|
||||
@@ -128,8 +128,8 @@
|
||||
"test-browser": "node lib.esm/_admin/test-browser",
|
||||
"test-commonjs": "mocha --reporter ./reporter.cjs ./lib.commonjs/_tests/test-*.js",
|
||||
"test-coverage": "c8 -o output -r lcov -r text mocha --no-color --reporter ./reporter.cjs ./lib.esm/_tests/test-*.js | tee output/summary.txt",
|
||||
"test-esm": "mocha --reporter ./reporter.cjs ./lib.esm/_tests/test-*.js"
|
||||
"test-esm": "mocha --trace-warnings --reporter ./reporter.cjs ./lib.esm/_tests/test-*.js"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"version": "6.9.0"
|
||||
"version": "6.9.2"
|
||||
}
|
||||
|
||||
207
src.ts/_tests/test-provider-jsonrpc.ts
Normal file
207
src.ts/_tests/test-provider-jsonrpc.ts
Normal file
@@ -0,0 +1,207 @@
|
||||
import assert from "assert";
|
||||
|
||||
import {
|
||||
id, isError, makeError, toUtf8Bytes, toUtf8String,
|
||||
FetchRequest,
|
||||
JsonRpcProvider, Transaction, Wallet
|
||||
} from "../index.js";
|
||||
|
||||
const StatusMessages: Record<number, string> = {
|
||||
200: "OK",
|
||||
400: "BAD REQUEST",
|
||||
500: "SERVER ERROR",
|
||||
};
|
||||
|
||||
|
||||
type ProcessRequest = (method: string, params: Array<string>, blockNumber: number) => any;
|
||||
|
||||
const wallet = new Wallet(id("test"));
|
||||
|
||||
function createProvider(testFunc: ProcessRequest): JsonRpcProvider {
|
||||
|
||||
let blockNumber = 1;
|
||||
const ticker = setInterval(() => { blockNumber++ }, 100);
|
||||
if (ticker.unref) { ticker.unref(); }
|
||||
|
||||
const processReq = (req: { method: string, params: Array<string>, id: any }) => {
|
||||
|
||||
let result = testFunc(req.method, req.params, blockNumber);
|
||||
if (result === undefined) {
|
||||
switch (req.method) {
|
||||
case "eth_blockNumber":
|
||||
result = blockNumber;
|
||||
break;
|
||||
case "eth_chainId":
|
||||
result = "0x1337";
|
||||
break;
|
||||
case "eth_accounts":
|
||||
result = [ wallet.address ];
|
||||
break;
|
||||
default:
|
||||
console.log("****", req);
|
||||
return { id, error: "unsupported", jsonrpc: "2.0" };
|
||||
}
|
||||
}
|
||||
|
||||
return { id: req.id, result, jsonrpc: "2.0" };
|
||||
};
|
||||
|
||||
const req = new FetchRequest("http:/\/localhost:8082/");
|
||||
req.getUrlFunc = async (_req, signal) => {
|
||||
const req = JSON.parse(_req.hasBody() ? toUtf8String(_req.body): "");
|
||||
|
||||
let statusCode = 200;
|
||||
const headers = { };
|
||||
|
||||
let resp: any;
|
||||
try {
|
||||
if (Array.isArray(req)) {
|
||||
resp = req.map((r) => processReq(r));
|
||||
} else {
|
||||
resp = processReq(req);
|
||||
}
|
||||
|
||||
} catch(error: any) {
|
||||
statusCode = 500;
|
||||
resp = error.message;
|
||||
}
|
||||
|
||||
const body = toUtf8Bytes(JSON.stringify(resp));
|
||||
|
||||
return {
|
||||
statusCode,
|
||||
statusMessage: StatusMessages[statusCode],
|
||||
headers, body
|
||||
};
|
||||
};
|
||||
|
||||
return new JsonRpcProvider(req, undefined, { cacheTimeout: -1 });
|
||||
}
|
||||
|
||||
describe("Ensure Catchable Errors", function() {
|
||||
it("Can catch bad broadcast replies", async function() {
|
||||
this.timeout(15000);
|
||||
|
||||
const txInfo = {
|
||||
chainId: 1337,
|
||||
gasLimit: 100000,
|
||||
maxFeePerGas: 2000000000,
|
||||
maxPriorityFeePerGas: 1000000000,
|
||||
to: wallet.address,
|
||||
value: 1,
|
||||
};
|
||||
const txSign = await wallet.signTransaction(txInfo);
|
||||
const txObj = Transaction.from(txSign);
|
||||
|
||||
let count = 0;
|
||||
|
||||
const provider = createProvider((method, params, blockNumber) => {
|
||||
|
||||
switch (method) {
|
||||
case "eth_sendTransaction":
|
||||
return txObj.hash;
|
||||
|
||||
case "eth_getTransactionByHash": {
|
||||
count++;
|
||||
|
||||
// First time; fail!
|
||||
if (count === 1) {
|
||||
throw makeError("Faux Error", "SERVER_ERROR", {
|
||||
request: <any>({ })
|
||||
});
|
||||
}
|
||||
|
||||
// Second time; return null
|
||||
if (count === 2) { return null; }
|
||||
|
||||
// Return a valid tx...
|
||||
const result = Object.assign({ },
|
||||
txObj.toJSON(),
|
||||
txObj.signature!.toJSON(),
|
||||
{ hash: txObj.hash, from: wallet.address });
|
||||
|
||||
// ...eventually mined
|
||||
if (count > 4) {
|
||||
result.blockNumber = blockNumber;
|
||||
result.blockHash = id("test");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
const signer = await provider.getSigner();
|
||||
|
||||
const tx = await signer.sendTransaction(txInfo);
|
||||
assert(tx);
|
||||
});
|
||||
|
||||
|
||||
it("Missing v is recovered", async function() {
|
||||
this.timeout(15000);
|
||||
|
||||
const txInfo = {
|
||||
chainId: 1337,
|
||||
gasLimit: 100000,
|
||||
maxFeePerGas: 2000000000,
|
||||
maxPriorityFeePerGas: 1000000000,
|
||||
to: wallet.address,
|
||||
value: 1,
|
||||
};
|
||||
const txSign = await wallet.signTransaction(txInfo);
|
||||
const txObj = Transaction.from(txSign);
|
||||
|
||||
let count = 0;
|
||||
|
||||
// A provider which is mocked to return a "missing v"
|
||||
// in getTransaction
|
||||
|
||||
const provider = createProvider((method, params, blockNumber) => {
|
||||
|
||||
switch (method) {
|
||||
case "eth_sendTransaction":
|
||||
return txObj.hash;
|
||||
|
||||
case "eth_getTransactionByHash": {
|
||||
count++;
|
||||
|
||||
// The fully valid tx response
|
||||
const result = Object.assign({ },
|
||||
txObj.toJSON(),
|
||||
txObj.signature!.toJSON(),
|
||||
{ hash: txObj.hash, from: wallet.address, sig: null });
|
||||
|
||||
// First time; fail with a missing v!
|
||||
if (count < 2) { delete result.v; }
|
||||
|
||||
// Debug
|
||||
result._count = count;
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
|
||||
// Track any "missing v" error
|
||||
let missingV: Error | null = null;
|
||||
provider.on("error", (e) => {
|
||||
if (isError(e, "UNKNOWN_ERROR") && isError(e.error, "INVALID_ARGUMENT")) {
|
||||
if (e.error.argument === "signature" && e.error.shortMessage === "missing v") {
|
||||
missingV = e.error;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const signer = await provider.getSigner();
|
||||
|
||||
const tx = await signer.sendTransaction(txInfo);
|
||||
assert.ok(!!tx, "we got a transaction");
|
||||
assert.ok(!!missingV, "missing v error present");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,4 +3,4 @@
|
||||
/**
|
||||
* The current version of Ethers.
|
||||
*/
|
||||
export const version: string = "6.9.0";
|
||||
export const version: string = "6.9.2";
|
||||
|
||||
@@ -53,7 +53,7 @@ const paramTypeNumber = new RegExp(/^(u?int)([0-9]*)$/);
|
||||
|
||||
|
||||
let defaultCoder: null | AbiCoder = null;
|
||||
|
||||
let defaultMaxInflation = 1024;
|
||||
|
||||
function getBuiltinCallException(action: CallExceptionAction, tx: { to?: null | string, from?: null | string, data?: string }, data: null | BytesLike, abiCoder: AbiCoder): CallExceptionError {
|
||||
let message = "missing revert data";
|
||||
@@ -206,7 +206,12 @@ export class AbiCoder {
|
||||
decode(types: ReadonlyArray<string | ParamType>, data: BytesLike, loose?: boolean): Result {
|
||||
const coders: Array<Coder> = types.map((type) => this.#getCoder(ParamType.from(type)));
|
||||
const coder = new TupleCoder(coders, "_");
|
||||
return coder.decode(new Reader(data, loose));
|
||||
return coder.decode(new Reader(data, loose, defaultMaxInflation));
|
||||
}
|
||||
|
||||
static _setDefaultMaxInflation(value: number): void {
|
||||
assertArgument(typeof(value) === "number" && Number.isInteger(value), "invalid defaultMaxInflation factor", "value", value);
|
||||
defaultMaxInflation = value;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -414,10 +414,17 @@ export class Reader {
|
||||
readonly #data: Uint8Array;
|
||||
#offset: number;
|
||||
|
||||
constructor(data: BytesLike, allowLoose?: boolean) {
|
||||
#bytesRead: number;
|
||||
#parent: null | Reader;
|
||||
#maxInflation: number;
|
||||
|
||||
constructor(data: BytesLike, allowLoose?: boolean, maxInflation?: number) {
|
||||
defineProperties<Reader>(this, { allowLoose: !!allowLoose });
|
||||
|
||||
this.#data = getBytesCopy(data);
|
||||
this.#bytesRead = 0;
|
||||
this.#parent = null;
|
||||
this.#maxInflation = (maxInflation != null) ? maxInflation: 1024;
|
||||
|
||||
this.#offset = 0;
|
||||
}
|
||||
@@ -427,6 +434,21 @@ export class Reader {
|
||||
get consumed(): number { return this.#offset; }
|
||||
get bytes(): Uint8Array { return new Uint8Array(this.#data); }
|
||||
|
||||
#incrementBytesRead(count: number): void {
|
||||
if (this.#parent) { return this.#parent.#incrementBytesRead(count); }
|
||||
|
||||
this.#bytesRead += count;
|
||||
|
||||
// Check for excessive inflation (see: #4537)
|
||||
assert(this.#maxInflation < 1 || this.#bytesRead <= this.#maxInflation * this.dataLength, `compressed ABI data exceeds inflation ratio of ${ this.#maxInflation } ( see: https:/\/github.com/ethers-io/ethers.js/issues/4537 )`, "BUFFER_OVERRUN", {
|
||||
buffer: getBytesCopy(this.#data), offset: this.#offset,
|
||||
length: count, info: {
|
||||
bytesRead: this.#bytesRead,
|
||||
dataLength: this.dataLength
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#peekBytes(offset: number, length: number, loose?: boolean): Uint8Array {
|
||||
let alignedLength = Math.ceil(length / WordSize) * WordSize;
|
||||
if (this.#offset + alignedLength > this.#data.length) {
|
||||
@@ -445,12 +467,15 @@ export class Reader {
|
||||
|
||||
// Create a sub-reader with the same underlying data, but offset
|
||||
subReader(offset: number): Reader {
|
||||
return new Reader(this.#data.slice(this.#offset + offset), this.allowLoose);
|
||||
const reader = new Reader(this.#data.slice(this.#offset + offset), this.allowLoose, this.#maxInflation);
|
||||
reader.#parent = this;
|
||||
return reader;
|
||||
}
|
||||
|
||||
// Read bytes
|
||||
readBytes(length: number, loose?: boolean): Uint8Array {
|
||||
let bytes = this.#peekBytes(0, length, !!loose);
|
||||
this.#incrementBytesRead(length);
|
||||
this.#offset += bytes.length;
|
||||
// @TODO: Make sure the length..end bytes are all 0?
|
||||
return bytes.slice(0, length);
|
||||
|
||||
@@ -862,16 +862,18 @@ export class AbstractProvider implements Provider {
|
||||
if (this.#networkPromise == null) {
|
||||
|
||||
// Detect the current network (shared with all calls)
|
||||
const detectNetwork = this._detectNetwork().then((network) => {
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
}, (error) => {
|
||||
// Reset the networkPromise on failure, so we will try again
|
||||
if (this.#networkPromise === detectNetwork) {
|
||||
this.#networkPromise = null;
|
||||
const detectNetwork = (async () => {
|
||||
try {
|
||||
const network = await this._detectNetwork();
|
||||
this.emit("network", network, null);
|
||||
return network;
|
||||
} catch (error) {
|
||||
if (this.#networkPromise === detectNetwork!) {
|
||||
this.#networkPromise = null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
})();
|
||||
|
||||
this.#networkPromise = detectNetwork;
|
||||
return (await detectNetwork).clone();
|
||||
|
||||
@@ -21,7 +21,7 @@ import { TypedDataEncoder } from "../hash/index.js";
|
||||
import { accessListify } from "../transaction/index.js";
|
||||
import {
|
||||
defineProperties, getBigInt, hexlify, isHexString, toQuantity, toUtf8Bytes,
|
||||
makeError, assert, assertArgument,
|
||||
isError, makeError, assert, assertArgument,
|
||||
FetchRequest, resolveProperties
|
||||
} from "../utils/index.js";
|
||||
|
||||
@@ -41,7 +41,6 @@ import type { Signer } from "./signer.js";
|
||||
|
||||
type Timer = ReturnType<typeof setTimeout>;
|
||||
|
||||
|
||||
const Primitive = "bigint,boolean,function,number,string,symbol".split(/,/g);
|
||||
//const Methods = "getAddress,then".split(/,/g);
|
||||
function deepCopy<T = any>(value: T): T {
|
||||
@@ -363,12 +362,49 @@ export class JsonRpcSigner extends AbstractSigner<JsonRpcApiProvider> {
|
||||
// for it; it should show up very quickly
|
||||
return await (new Promise((resolve, reject) => {
|
||||
const timeouts = [ 1000, 100 ];
|
||||
let invalids = 0;
|
||||
|
||||
const checkTx = async () => {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
|
||||
try {
|
||||
// Try getting the transaction
|
||||
const tx = await this.provider.getTransaction(hash);
|
||||
|
||||
if (tx != null) {
|
||||
resolve(tx.replaceableTransaction(blockNumber));
|
||||
return;
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
// If we were cancelled: stop polling.
|
||||
// If the data is bad: the node returns bad transactions
|
||||
// If the network changed: calling again will also fail
|
||||
// If unsupported: likely destroyed
|
||||
if (isError(error, "CANCELLED") || isError(error, "BAD_DATA") ||
|
||||
isError(error, "NETWORK_ERROR" || isError(error, "UNSUPPORTED_OPERATION"))) {
|
||||
|
||||
if (error.info == null) { error.info = { }; }
|
||||
error.info.sendTransactionHash = hash;
|
||||
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Stop-gap for misbehaving backends; see #4513
|
||||
if (isError(error, "INVALID_ARGUMENT")) {
|
||||
invalids++;
|
||||
if (error.info == null) { error.info = { }; }
|
||||
error.info.sendTransactionHash = hash;
|
||||
if (invalids > 10) {
|
||||
reject(error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Notify anyone that cares; but we will try again, since
|
||||
// it is likely an intermittent service error
|
||||
this.provider.emit("error", makeError("failed to fetch transation after sending (will try again)", "UNKNOWN_ERROR", { error }));
|
||||
}
|
||||
|
||||
// Wait another 4 seconds
|
||||
@@ -468,7 +504,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider {
|
||||
#scheduleDrain(): void {
|
||||
if (this.#drainTimer) { return; }
|
||||
|
||||
// If we aren't using batching, no hard in sending it immeidately
|
||||
// If we aren't using batching, no harm in sending it immediately
|
||||
const stallTime = (this._getOption("batchMaxCount") === 1) ? 0: this._getOption("batchStallTime");
|
||||
|
||||
this.#drainTimer = setTimeout(() => {
|
||||
@@ -613,6 +649,7 @@ export abstract class JsonRpcApiProvider extends AbstractProvider {
|
||||
* and should generally call ``super._perform`` as a fallback.
|
||||
*/
|
||||
async _perform(req: PerformActionRequest): Promise<any> {
|
||||
|
||||
// Legacy networks do not like the type field being passed along (which
|
||||
// is fair), so we delete type if it is 0 and a non-EIP-1559 network
|
||||
if (req.method === "call" || req.method === "estimateGas") {
|
||||
@@ -664,9 +701,14 @@ export abstract class JsonRpcApiProvider extends AbstractProvider {
|
||||
// If we are ready, use ``send``, which enabled requests to be batched
|
||||
if (this.ready) {
|
||||
this.#pendingDetectNetwork = (async () => {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [ ])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
try {
|
||||
const result = Network.from(getBigInt(await this.send("eth_chainId", [ ])));
|
||||
this.#pendingDetectNetwork = null;
|
||||
return result;
|
||||
} catch (error) {
|
||||
this.#pendingDetectNetwork = null;
|
||||
throw error;
|
||||
}
|
||||
})();
|
||||
return await this.#pendingDetectNetwork;
|
||||
}
|
||||
@@ -1186,7 +1228,6 @@ export class JsonRpcProvider extends JsonRpcApiPollingProvider {
|
||||
const request = this._getConnection();
|
||||
request.body = JSON.stringify(payload);
|
||||
request.setHeader("content-type", "application/json");
|
||||
|
||||
const response = await request.send();
|
||||
response.assertOk();
|
||||
|
||||
|
||||
@@ -42,12 +42,21 @@ const BN_58 = BigInt(58);
|
||||
* Encode %%value%% as a Base58-encoded string.
|
||||
*/
|
||||
export function encodeBase58(_value: BytesLike): string {
|
||||
let value = toBigInt(getBytes(_value));
|
||||
const bytes = getBytes(_value);
|
||||
|
||||
let value = toBigInt(bytes);
|
||||
let result = "";
|
||||
while (value) {
|
||||
result = Alphabet[Number(value % BN_58)] + result;
|
||||
value /= BN_58;
|
||||
}
|
||||
|
||||
// Account for leading padding zeros
|
||||
for (let i = 0; i < bytes.length; i++) {
|
||||
if (bytes[i]) { break; }
|
||||
result = Alphabet[0] + result;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user