Updated dist files and dist scripts.

This commit is contained in:
Richard Moore 2017-10-20 15:44:54 -04:00
parent a5df551689
commit f140fa6017
17 changed files with 752 additions and 453 deletions

View File

@ -1,3 +1,10 @@
'use strict';
[ 'contracts', 'providers', 'utils', 'wallet'].forEach(function(name) {
var npmVersion = require('./node_modules/ethers-' + name + '/package.json').version;
var liveVersion = require('./' + name + '/package.json').version;
console.log(name, npmVersion, liveVersion);
});
var through = require('through'); var through = require('through');

View File

@ -1,7 +1,7 @@
ethers.js ethers.js
========= =========
[![npm version](https://badge.fury.io/js/ethers-wallet.svg)](https://badge.fury.io/js/ethers) [![npm version](https://badge.fury.io/js/ethers.svg)](https://badge.fury.io/js/ethers)
Complete Ethereum wallet implementation and utilities in JavaScript. Complete Ethereum wallet implementation and utilities in JavaScript.
@ -12,13 +12,14 @@ Complete Ethereum wallet implementation and utilities in JavaScript.
- Import and export BIP 39 **mnemonic phrases** (12 word backup phrases) and **HD Wallets** - Import and export BIP 39 **mnemonic phrases** (12 word backup phrases) and **HD Wallets**
- Meta-classes create JavaScript objects from any contract ABI - Meta-classes create JavaScript objects from any contract ABI
- Connect to Ethereum nodes over [JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC), [INFURA](https://infura.io) or [Etherscan](https://etherscan.io) - Connect to Ethereum nodes over [JSON-RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC), [INFURA](https://infura.io) or [Etherscan](https://etherscan.io)
- ENS names are first-class citizens; they can almost always used instead of Ethereum addresses
- **Tiny** (~77kb compressed; 227kb uncompressed) - **Tiny** (~77kb compressed; 227kb uncompressed)
- **Complete** functionality for all your Ethereum needs - **Complete** functionality for all your Ethereum needs
- Extensive [documentation](https://docs.ethers.io/ethers.js/) - Extensive [documentation](https://docs.ethers.io/ethers.js/)
- Large collection of test cases which are maintained and added to
- **MIT License** (including ALL dependencies); completely open source to do with as you please - **MIT License** (including ALL dependencies); completely open source to do with as you please
Installing Installing
---------- ----------

View File

@ -1,8 +1,8 @@
ethers-contracts ethers-contracts
================ ================
This is part of a larger umbrella package, ethers. [![npm version](https://badge.fury.io/js/ethers-contracts.svg)](https://badge.fury.io/js/ethers-contracts)
See https://www.npmjs.com/package/ethers. This is part of a larger umbrella package, ethers. See https://www.npmjs.com/package/ethers.
[Documentation](https://docs.ethers.io/ethers.js/api-contract.html) [Documentation](https://docs.ethers.io/ethers.js/html/api-contract.html)

View File

@ -1,6 +1,6 @@
{ {
"name": "ethers-contracts", "name": "ethers-contracts",
"version": "2.1.0", "version": "2.1.2",
"description": "Contract and Interface (ABI) library for Ethereum.", "description": "Contract and Interface (ABI) library for Ethereum.",
"bugs": { "bugs": {
"url": "http://github.com/ethers-io/ethers.js/issues", "url": "http://github.com/ethers-io/ethers.js/issues",
@ -8,7 +8,7 @@
}, },
"main": "index.js", "main": "index.js",
"dependencies": { "dependencies": {
"ethers-utils": "^2.0.0" "ethers-utils": "^2.1.0"
}, },
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1" "test": "echo \"Error: no test specified\" && exit 1"

View File

@ -5240,6 +5240,8 @@ var utils = (function() {
namehash: require('ethers-utils/namehash'), namehash: require('ethers-utils/namehash'),
toUtf8String: require('ethers-utils/utf8').toUtf8String,
RLP: require('ethers-utils/rlp'), RLP: require('ethers-utils/rlp'),
} }
})(); })();
@ -5957,28 +5959,34 @@ utils.defineProperty(Provider.prototype, '_resolveNames', function(object, keys)
return Promise.all(promises).then(function() { return result; }); return Promise.all(promises).then(function() { return result; });
}); });
utils.defineProperty(Provider.prototype, 'resolveName', function(name) { utils.defineProperty(Provider.prototype, '_getResolver', function(name) {
// If it is already an address, nothing to resolve
try {
return Promise.resolve(utils.getAddress(name));
} catch (error) { }
var nodeHash = utils.namehash(name); var nodeHash = utils.namehash(name);
// keccak256('resolver(bytes32)') // keccak256('resolver(bytes32)')
var data = '0x0178b8bf' + nodeHash.substring(2); var data = '0x0178b8bf' + nodeHash.substring(2);
var transaction = { to: this.ensAddress, data: data }; var transaction = { to: this.ensAddress, data: data };
var self = this;
// Get the resolver from the blockchain // Get the resolver from the blockchain
return this.call(transaction).then(function(data) { return this.call(transaction).then(function(data) {
// extract the address from the data // extract the address from the data
if (data.length != 66) { return null; } if (data.length != 66) { return null; }
return utils.getAddress('0x' + data.substring(26)); return utils.getAddress('0x' + data.substring(26));
});
});
utils.defineProperty(Provider.prototype, 'resolveName', function(name) {
// If it is already an address, nothing to resolve
try {
return Promise.resolve(utils.getAddress(name));
} catch (error) { }
var self = this;
var nodeHash = utils.namehash(name);
// Get the addr from the resovler // Get the addr from the resovler
}).then(function(resolverAddress) { return this._getResolver(name).then(function(resolverAddress) {
// keccak256('addr(bytes32)') // keccak256('addr(bytes32)')
var data = '0x3b3b57de' + nodeHash.substring(2); var data = '0x3b3b57de' + nodeHash.substring(2);
@ -5994,6 +6002,47 @@ utils.defineProperty(Provider.prototype, 'resolveName', function(name) {
}); });
}); });
utils.defineProperty(Provider.prototype, 'lookupAddress', function(address) {
address = utils.getAddress(address);
var name = address.substring(2) + '.addr.reverse'
var nodehash = utils.namehash(name);
var self = this;
return this._getResolver(name).then(function(resolverAddress) {
if (!resolverAddress) { return null; }
// keccak('name(bytes32)')
var data = '0x691f3431' + nodehash.substring(2);
var transaction = { to: resolverAddress, data: data };
return self.call(transaction);
}).then(function(data) {
// Strip off the "0x"
data = data.substring(2);
// Strip off the dynamic string pointer (0x20)
if (data.length < 64) { return null; }
data = data.substring(64);
if (data.length < 64) { return null; }
var length = utils.bigNumberify('0x' + data.substring(0, 64)).toNumber();
data = data.substring(64);
if (2 * length > data.length) { return null; }
var name = utils.toUtf8String('0x' + data.substring(0, 2 * length));
// Make sure the reverse record matches the foward record
return self.resolveName(name).then(function(addr) {
if (addr != address) { return null; }
return name;
});
});
});
utils.defineProperty(Provider.prototype, 'doPoll', function() { utils.defineProperty(Provider.prototype, 'doPoll', function() {
}); });
@ -6148,4 +6197,4 @@ utils.defineProperty(Provider.prototype, 'removeListener', function(eventName, l
module.exports = Provider; module.exports = Provider;
},{"ethers-utils/address":9,"ethers-utils/bignumber":10,"ethers-utils/contract-address":11,"ethers-utils/convert":12,"ethers-utils/namehash":14,"ethers-utils/properties":15,"ethers-utils/rlp":16,"inherits":20,"xmlhttprequest":2}]},{},[5]); },{"ethers-utils/address":9,"ethers-utils/bignumber":10,"ethers-utils/contract-address":11,"ethers-utils/convert":12,"ethers-utils/namehash":14,"ethers-utils/properties":15,"ethers-utils/rlp":16,"ethers-utils/utf8":19,"inherits":20,"xmlhttprequest":2}]},{},[5]);

File diff suppressed because one or more lines are too long

66
dist/ethers-utils.js vendored
View File

@ -117,7 +117,7 @@ module.exports = {
getAddress: getAddress, getAddress: getAddress,
} }
},{"./convert":6,"./keccak256":8,"./throw-error":23,"bn.js":10}],3:[function(require,module,exports){ },{"./convert":6,"./keccak256":9,"./throw-error":24,"bn.js":11}],3:[function(require,module,exports){
/** /**
* BigNumber * BigNumber
* *
@ -261,7 +261,7 @@ module.exports = {
bigNumberify: bigNumberify bigNumberify: bigNumberify
}; };
},{"./convert":6,"./properties":19,"./throw-error":23,"bn.js":10}],4:[function(require,module,exports){ },{"./convert":6,"./properties":20,"./throw-error":24,"bn.js":11}],4:[function(require,module,exports){
(function (global){ (function (global){
'use strict'; 'use strict';
@ -269,7 +269,9 @@ var defineProperty = require('./properties.js').defineProperty;
var crypto = global.crypto || global.msCrypto; var crypto = global.crypto || global.msCrypto;
if (!crypto || !crypto.getRandomValues) { if (!crypto || !crypto.getRandomValues) {
console.log('WARNING: Missing strong random number source; using weak randomBytes'); console.log('WARNING: Missing strong random number source; using weak randomBytes');
crypto = { crypto = {
getRandomValues: function(buffer) { getRandomValues: function(buffer) {
for (var round = 0; round < 20; round++) { for (var round = 0; round < 20; round++) {
@ -286,8 +288,6 @@ if (!crypto || !crypto.getRandomValues) {
}, },
_weakCrypto: true _weakCrypto: true
}; };
} else {
console.log('Found strong random number source');
} }
function randomBytes(length) { function randomBytes(length) {
@ -307,7 +307,7 @@ if (crypto._weakCrypto === true) {
module.exports = randomBytes; module.exports = randomBytes;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./properties.js":19}],5:[function(require,module,exports){ },{"./properties.js":20}],5:[function(require,module,exports){
var getAddress = require('./address').getAddress; var getAddress = require('./address').getAddress;
var convert = require('./convert'); var convert = require('./convert');
@ -329,7 +329,7 @@ module.exports = {
getContractAddress: getContractAddress, getContractAddress: getContractAddress,
} }
},{"./address":2,"./convert":6,"./keccak256":8,"./rlp":20}],6:[function(require,module,exports){ },{"./address":2,"./convert":6,"./keccak256":9,"./rlp":21}],6:[function(require,module,exports){
/** /**
* Conversion Utilities * Conversion Utilities
* *
@ -492,7 +492,19 @@ module.exports = {
isHexString: isHexString, isHexString: isHexString,
}; };
},{"./properties.js":19,"./throw-error":23}],7:[function(require,module,exports){ },{"./properties.js":20,"./throw-error":24}],7:[function(require,module,exports){
'use strict';
var keccak256 = require('./keccak256');
var utf8 = require('./utf8');
function id(text) {
return keccak256(utf8.toUtf8Bytes(text));
}
module.exports = id;
},{"./keccak256":9,"./utf8":26}],8:[function(require,module,exports){
'use strict'; 'use strict';
// This is SUPER useful, but adds 140kb (even zipped, adds 40kb) // This is SUPER useful, but adds 140kb (even zipped, adds 40kb)
@ -502,6 +514,7 @@ var address = require('./address');
var bigNumber = require('./bignumber'); var bigNumber = require('./bignumber');
var contractAddress = require('./contract-address'); var contractAddress = require('./contract-address');
var convert = require('./convert'); var convert = require('./convert');
var id = require('./id');
var keccak256 = require('./keccak256'); var keccak256 = require('./keccak256');
var namehash = require('./namehash'); var namehash = require('./namehash');
var sha256 = require('./sha2').sha256; var sha256 = require('./sha2').sha256;
@ -537,6 +550,7 @@ module.exports = {
toUtf8String: utf8.toUtf8String, toUtf8String: utf8.toUtf8String,
namehash: namehash, namehash: namehash,
id: id,
getAddress: address.getAddress, getAddress: address.getAddress,
getContractAddress: contractAddress.getContractAddress, getContractAddress: contractAddress.getContractAddress,
@ -555,7 +569,7 @@ require('./standalone')({
}); });
},{"./address":2,"./bignumber":3,"./contract-address":5,"./convert":6,"./keccak256":8,"./namehash":9,"./properties":19,"./random-bytes":4,"./rlp":20,"./sha2":21,"./standalone":22,"./units":24,"./utf8":25}],8:[function(require,module,exports){ },{"./address":2,"./bignumber":3,"./contract-address":5,"./convert":6,"./id":7,"./keccak256":9,"./namehash":10,"./properties":20,"./random-bytes":4,"./rlp":21,"./sha2":22,"./standalone":23,"./units":25,"./utf8":26}],9:[function(require,module,exports){
'use strict'; 'use strict';
var sha3 = require('js-sha3'); var sha3 = require('js-sha3');
@ -569,7 +583,7 @@ function keccak256(data) {
module.exports = keccak256; module.exports = keccak256;
},{"./convert.js":6,"js-sha3":18}],9:[function(require,module,exports){ },{"./convert.js":6,"js-sha3":19}],10:[function(require,module,exports){
'use strict'; 'use strict';
var convert = require('./convert'); var convert = require('./convert');
@ -609,7 +623,7 @@ function namehash(name, depth) {
module.exports = namehash; module.exports = namehash;
},{"./convert":6,"./keccak256":8,"./utf8":25}],10:[function(require,module,exports){ },{"./convert":6,"./keccak256":9,"./utf8":26}],11:[function(require,module,exports){
(function (module, exports) { (function (module, exports) {
'use strict'; 'use strict';
@ -4038,7 +4052,7 @@ module.exports = namehash;
}; };
})(typeof module === 'undefined' || module, this); })(typeof module === 'undefined' || module, this);
},{}],11:[function(require,module,exports){ },{}],12:[function(require,module,exports){
var hash = exports; var hash = exports;
hash.utils = require('./hash/utils'); hash.utils = require('./hash/utils');
@ -4055,7 +4069,7 @@ hash.sha384 = hash.sha.sha384;
hash.sha512 = hash.sha.sha512; hash.sha512 = hash.sha.sha512;
hash.ripemd160 = hash.ripemd.ripemd160; hash.ripemd160 = hash.ripemd.ripemd160;
},{"./hash/common":12,"./hash/hmac":13,"./hash/ripemd":14,"./hash/sha":15,"./hash/utils":16}],12:[function(require,module,exports){ },{"./hash/common":13,"./hash/hmac":14,"./hash/ripemd":15,"./hash/sha":16,"./hash/utils":17}],13:[function(require,module,exports){
var hash = require('../hash'); var hash = require('../hash');
var utils = hash.utils; var utils = hash.utils;
var assert = utils.assert; var assert = utils.assert;
@ -4148,7 +4162,7 @@ BlockHash.prototype._pad = function pad() {
return res; return res;
}; };
},{"../hash":11}],13:[function(require,module,exports){ },{"../hash":12}],14:[function(require,module,exports){
var hmac = exports; var hmac = exports;
var hash = require('../hash'); var hash = require('../hash');
@ -4198,9 +4212,9 @@ Hmac.prototype.digest = function digest(enc) {
return this.outer.digest(enc); return this.outer.digest(enc);
}; };
},{"../hash":11}],14:[function(require,module,exports){ },{"../hash":12}],15:[function(require,module,exports){
module.exports = {ripemd160: null} module.exports = {ripemd160: null}
},{}],15:[function(require,module,exports){ },{}],16:[function(require,module,exports){
var hash = require('../hash'); var hash = require('../hash');
var utils = hash.utils; var utils = hash.utils;
var assert = utils.assert; var assert = utils.assert;
@ -4766,7 +4780,7 @@ function g1_512_lo(xh, xl) {
return r; return r;
} }
},{"../hash":11}],16:[function(require,module,exports){ },{"../hash":12}],17:[function(require,module,exports){
var utils = exports; var utils = exports;
var inherits = require('inherits'); var inherits = require('inherits');
@ -5025,7 +5039,7 @@ function shr64_lo(ah, al, num) {
}; };
exports.shr64_lo = shr64_lo; exports.shr64_lo = shr64_lo;
},{"inherits":17}],17:[function(require,module,exports){ },{"inherits":18}],18:[function(require,module,exports){
if (typeof Object.create === 'function') { if (typeof Object.create === 'function') {
// implementation from standard node.js 'util' module // implementation from standard node.js 'util' module
module.exports = function inherits(ctor, superCtor) { module.exports = function inherits(ctor, superCtor) {
@ -5050,7 +5064,7 @@ if (typeof Object.create === 'function') {
} }
} }
},{}],18:[function(require,module,exports){ },{}],19:[function(require,module,exports){
(function (process,global){ (function (process,global){
/** /**
* [js-sha3]{@link https://github.com/emn178/js-sha3} * [js-sha3]{@link https://github.com/emn178/js-sha3}
@ -5529,7 +5543,7 @@ if (typeof Object.create === 'function') {
})(); })();
}).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) }).call(this,require('_process'),typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"_process":1}],19:[function(require,module,exports){ },{"_process":1}],20:[function(require,module,exports){
function defineProperty(object, name, value) { function defineProperty(object, name, value) {
Object.defineProperty(object, name, { Object.defineProperty(object, name, {
enumerable: true, enumerable: true,
@ -5542,7 +5556,7 @@ module.exports = {
defineProperty: defineProperty, defineProperty: defineProperty,
}; };
},{}],20:[function(require,module,exports){ },{}],21:[function(require,module,exports){
//See: https://github.com/ethereum/wiki/wiki/RLP //See: https://github.com/ethereum/wiki/wiki/RLP
var convert = require('./convert.js'); var convert = require('./convert.js');
@ -5686,7 +5700,7 @@ module.exports = {
decode: decode, decode: decode,
} }
},{"./convert.js":6}],21:[function(require,module,exports){ },{"./convert.js":6}],22:[function(require,module,exports){
'use strict'; 'use strict';
var hash = require('hash.js'); var hash = require('hash.js');
@ -5711,7 +5725,7 @@ module.exports = {
createSha512: hash.sha512, createSha512: hash.sha512,
} }
},{"./convert.js":6,"hash.js":11}],22:[function(require,module,exports){ },{"./convert.js":6,"hash.js":12}],23:[function(require,module,exports){
(function (global){ (function (global){
var defineProperty = require('./properties.js').defineProperty; var defineProperty = require('./properties.js').defineProperty;
@ -5738,7 +5752,7 @@ function defineEthersValues(values) {
module.exports = defineEthersValues; module.exports = defineEthersValues;
}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {})
},{"./properties.js":19}],23:[function(require,module,exports){ },{"./properties.js":20}],24:[function(require,module,exports){
'use strict'; 'use strict';
function throwError(message, params) { function throwError(message, params) {
@ -5751,7 +5765,7 @@ function throwError(message, params) {
module.exports = throwError; module.exports = throwError;
},{}],24:[function(require,module,exports){ },{}],25:[function(require,module,exports){
var bigNumberify = require('./bignumber.js').bigNumberify; var bigNumberify = require('./bignumber.js').bigNumberify;
var throwError = require('./throw-error'); var throwError = require('./throw-error');
@ -5826,7 +5840,7 @@ module.exports = {
parseEther: parseEther, parseEther: parseEther,
} }
},{"./bignumber.js":3,"./throw-error":23}],25:[function(require,module,exports){ },{"./bignumber.js":3,"./throw-error":24}],26:[function(require,module,exports){
var convert = require('./convert.js'); var convert = require('./convert.js');
@ -5941,4 +5955,4 @@ module.exports = {
toUtf8String: bytesToUtf8, toUtf8String: bytesToUtf8,
}; };
},{"./convert.js":6}]},{},[7]); },{"./convert.js":6}]},{},[8]);

File diff suppressed because one or more lines are too long

37
dist/ethers-wallet.js vendored
View File

@ -11002,6 +11002,43 @@ utils.defineProperty(Wallet.prototype, 'send', function(addressOrName, amountWei
}); });
}); });
// @TODO: this is starting to get used various places; move to utils?
function hexPad(value, length) {
while (value.length < 2 * length + 2) {
value = '0x0' + value.substring(2);
}
return value;
}
function getHash(message) {
var payload = utils.concat([
utils.toUtf8Bytes('\x19Ethereum Signed Message:\n'),
utils.toUtf8Bytes(String(message.length)),
utils.toUtf8Bytes(message)
]);
return utils.keccak256(payload);
}
utils.defineProperty(Wallet.prototype, 'signMessage', function(message) {
var signingKey = new SigningKey(this.privateKey);
var sig = signingKey.signDigest(getHash(message));
return (hexPad(sig.r) + hexPad(sig.s).substring(2) + (sig.recoveryParam ? '1c': '1b'));
});
utils.defineProperty(Wallet, 'verifyMessage', function(message, signature) {
signature = utils.hexlify(signature);
if (signature.length != 132) { throw new Error('invalid signature'); }
var digest = getHash(message);
var recoveryParam = parseInt(signature.substring(130), 16) - 27;
if (recoveryParam < 0) { throw new Error('invalid signature'); }
return SigningKey.recover(
digest,
signature.substring(0, 66),
'0x' + signature.substring(66, 130),
parseInt(signature.substring(130), 16) - 27
);
});
utils.defineProperty(Wallet.prototype, 'encrypt', function(password, options, progressCallback) { utils.defineProperty(Wallet.prototype, 'encrypt', function(password, options, progressCallback) {
if (typeof(options) === 'function' && !progressCallback) { if (typeof(options) === 'function' && !progressCallback) {

File diff suppressed because one or more lines are too long

975
dist/ethers.js vendored

File diff suppressed because one or more lines are too long

12
dist/ethers.min.js vendored

File diff suppressed because one or more lines are too long

View File

@ -1,6 +1,6 @@
{ {
"name": "ethers", "name": "ethers",
"version": "2.1.2", "version": "2.1.3",
"description": "Ethereum wallet library.", "description": "Ethereum wallet library.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {

View File

@ -1,8 +1,10 @@
ethers-providers ethers-providers
================ ================
[![npm version](https://badge.fury.io/js/ethers-providers.svg)](https://badge.fury.io/js/ethers-providers)
This is part of a larger umbrella package, ethers. This is part of a larger umbrella package, ethers.
See https://www.npmjs.com/package/ethers. See https://www.npmjs.com/package/ethers.
[Documentation](https://docs.ethers.io/ethers.js/api-providers.html) [Documentation](https://docs.ethers.io/ethers.js/html/api-providers.html)

View File

@ -1,6 +1,6 @@
{ {
"name": "ethers-providers", "name": "ethers-providers",
"version": "2.1.3", "version": "2.1.4",
"description": "Service provider for Ethereum wallet library.", "description": "Service provider for Ethereum wallet library.",
"bugs": { "bugs": {
"url": "http://github.com/ethers-io/ethers.js/issues", "url": "http://github.com/ethers-io/ethers.js/issues",

View File

@ -1,8 +1,8 @@
ethers-wallet ethers-wallet
============= =============
This is part of a larger umbrella package, ethers. [![npm version](https://badge.fury.io/js/ethers-wallet.svg)](https://badge.fury.io/js/ethers-wallet)
See https://www.npmjs.com/package/ethers. This is part of a larger umbrella package, ethers. See https://www.npmjs.com/package/ethers.
[Documentation](https://docs.ethers.io/ethers.js/api-wallet.html) [Documentation](https://docs.ethers.io/ethers.js/html/api-wallet.html)

View File

@ -1,6 +1,6 @@
{ {
"name": "ethers-wallet", "name": "ethers-wallet",
"version": "2.1.2", "version": "2.1.3",
"description": "Wallet and signing library for Ethereum.", "description": "Wallet and signing library for Ethereum.",
"bugs": { "bugs": {
"url": "http://github.com/ethers-io/ethers.js/issues", "url": "http://github.com/ethers-io/ethers.js/issues",