@nomicfoundation/hardhat-ethers
Version:
Hardhat plugin for ethers
294 lines • 12.6 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SignerWithAddress = exports.HardhatEthersSigner = void 0;
const ethers_1 = require("ethers");
const util_1 = require("hardhat/internal/core/providers/util");
const ethers_utils_1 = require("./internal/ethers-utils");
const errors_1 = require("./internal/errors");
class HardhatEthersSigner {
static async create(provider, address) {
const hre = await Promise.resolve().then(() => __importStar(require("hardhat")));
// depending on the config, we set a fixed gas limit for all transactions
let gasLimit;
if (hre.network.name === "hardhat") {
// If we are connected to the in-process hardhat network and the config
// has a fixed number as the gas config, we use that.
// Hardhat core already sets this value to the block gas limit when the
// user doesn't specify a number.
if (hre.network.config.gas !== "auto") {
gasLimit = hre.network.config.gas;
}
}
else if (hre.network.name === "localhost") {
const configuredGasLimit = hre.config.networks.localhost.gas;
if (configuredGasLimit !== "auto") {
// if the resolved gas config is a number, we use that
gasLimit = configuredGasLimit;
}
else {
// if the resolved gas config is "auto", we need to check that
// the user config is undefined, because that's the default value;
// otherwise explicitly setting the gas to "auto" would have no effect
if (hre.userConfig.networks?.localhost?.gas === undefined) {
// finally, we check if we are connected to a hardhat network
let isHardhatNetwork = false;
try {
await hre.network.provider.send("hardhat_metadata");
isHardhatNetwork = true;
}
catch { }
if (isHardhatNetwork) {
// WARNING: this assumes that the hardhat node is being run in the
// same project which might be wrong
gasLimit = hre.config.networks.hardhat.blockGasLimit;
}
}
}
}
return new HardhatEthersSigner(address, provider, hre.network.config.accounts, gasLimit);
}
constructor(address, _provider, accounts, _gasLimit) {
this._gasLimit = _gasLimit;
this.address = (0, ethers_1.getAddress)(address);
this.provider = _provider;
this._accounts = accounts;
}
connect(provider) {
return new HardhatEthersSigner(this.address, provider, this._accounts);
}
async authorize(auth) {
const privateKey = this._getPrivateKey();
if (privateKey === undefined) {
throw new errors_1.HardhatEthersError(`No private key found for address ${this.address}`);
}
const wallet = new ethers_1.Wallet(privateKey, this.provider);
return wallet.authorize(auth);
}
async populateAuthorization(_auth) {
const auth = { ..._auth };
// Add a chain ID if not explicitly set to 0
if (auth.chainId === null || auth.chainId === undefined) {
auth.chainId = (await this.provider.getNetwork()).chainId;
}
if (auth.nonce === null || auth.nonce === undefined) {
auth.nonce = await this.getNonce();
}
return auth;
}
getNonce(blockTag) {
return this.provider.getTransactionCount(this.address, blockTag);
}
populateCall(tx) {
return populate(this, tx);
}
populateTransaction(tx) {
return this.populateCall(tx);
}
async estimateGas(tx) {
return this.provider.estimateGas(await this.populateCall(tx));
}
async call(tx) {
return this.provider.call(await this.populateCall(tx));
}
resolveName(name) {
return this.provider.resolveName(name);
}
async signTransaction(_tx) {
// TODO if we split the signer for the in-process and json-rpc networks,
// we can enable this method when using the in-process network or when the
// json-rpc network has a private key
throw new errors_1.NotImplementedError("HardhatEthersSigner.signTransaction");
}
async sendTransaction(tx) {
// This cannot be mined any earlier than any recent block
const blockNumber = await this.provider.getBlockNumber();
// Send the transaction
const hash = await this._sendUncheckedTransaction(tx);
// Unfortunately, JSON-RPC only provides and opaque transaction hash
// for a response, and we need the actual transaction, so we poll
// for it; it should show up very quickly
return new Promise((resolve) => {
const timeouts = [1000, 100];
const checkTx = async () => {
// Try getting the transaction
const txPolled = await this.provider.getTransaction(hash);
if (txPolled !== null) {
resolve(txPolled.replaceableTransaction(blockNumber));
return;
}
// Wait another 4 seconds
setTimeout(() => {
// eslint-disable-next-line @typescript-eslint/no-floating-promises
checkTx();
}, timeouts.pop() ?? 4000);
};
// eslint-disable-next-line @typescript-eslint/no-floating-promises
checkTx();
});
}
signMessage(message) {
const resolvedMessage = typeof message === "string" ? (0, ethers_1.toUtf8Bytes)(message) : message;
return this.provider.send("personal_sign", [
(0, ethers_1.hexlify)(resolvedMessage),
this.address.toLowerCase(),
]);
}
async signTypedData(domain, types, value) {
const copiedValue = deepCopy(value);
// Populate any ENS names (in-place)
const populated = await ethers_1.TypedDataEncoder.resolveNames(domain, types, copiedValue, async (v) => {
return v;
});
return this.provider.send("eth_signTypedData_v4", [
this.address.toLowerCase(),
JSON.stringify(ethers_1.TypedDataEncoder.getPayload(populated.domain, types, populated.value), (_k, v) => {
if (typeof v === "bigint") {
return v.toString();
}
return v;
}),
]);
}
async getAddress() {
return this.address;
}
toJSON() {
return `<SignerWithAddress ${this.address}>`;
}
_getPrivateKey() {
if (this._cachedPrivateKey === undefined) {
const privateKeys = this._getPrivateKeys();
const privateKey = privateKeys.find((key) => (0, ethers_1.computeAddress)(key) === this.address);
this._cachedPrivateKey = privateKey;
}
return this._cachedPrivateKey;
}
_getPrivateKeys() {
if (this._accounts === "remote") {
throw new errors_1.HardhatEthersError(`Tried to obtain a private key, but the network is configured to use remote accounts`);
}
if (Array.isArray(this._accounts)) {
if (typeof this._accounts[0] === "string") {
return this._accounts;
}
return this._accounts.map((acc) => acc.privateKey);
}
if ("mnemonic" in this._accounts) {
return (0, util_1.derivePrivateKeys)(this._accounts.mnemonic, this._accounts.path, this._accounts.initialIndex, this._accounts.count, this._accounts.passphrase).map((pk) => `0x${pk.toString("hex")}`);
}
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
throw new errors_1.HardhatEthersError("Assertion error: unsupported accounts type");
}
async _sendUncheckedTransaction(tx) {
const resolvedTx = deepCopy(tx);
const promises = [];
// Make sure the from matches the sender
if (resolvedTx.from !== null && resolvedTx.from !== undefined) {
const _from = resolvedTx.from;
promises.push((async () => {
const from = await (0, ethers_1.resolveAddress)(_from, this.provider);
(0, ethers_1.assertArgument)(from !== null &&
from !== undefined &&
from.toLowerCase() === this.address.toLowerCase(), "from address mismatch", "transaction", tx);
resolvedTx.from = from;
})());
}
else {
resolvedTx.from = this.address;
}
if (resolvedTx.gasLimit === null || resolvedTx.gasLimit === undefined) {
if (this._gasLimit !== undefined) {
resolvedTx.gasLimit = this._gasLimit;
}
else {
promises.push((async () => {
resolvedTx.gasLimit = await this.provider.estimateGas({
...resolvedTx,
from: this.address,
});
})());
}
}
// The address may be an ENS name or Addressable
if (resolvedTx.to !== null && resolvedTx.to !== undefined) {
const _to = resolvedTx.to;
promises.push((async () => {
resolvedTx.to = await (0, ethers_1.resolveAddress)(_to, this.provider);
})());
}
// Wait until all of our properties are filled in
if (promises.length > 0) {
await Promise.all(promises);
}
const hexTx = (0, ethers_utils_1.getRpcTransaction)(resolvedTx);
return this.provider.send("eth_sendTransaction", [hexTx]);
}
}
exports.HardhatEthersSigner = HardhatEthersSigner;
exports.SignerWithAddress = HardhatEthersSigner;
async function populate(signer, tx) {
const pop = (0, ethers_utils_1.copyRequest)(tx);
if (pop.to !== null && pop.to !== undefined) {
pop.to = (0, ethers_1.resolveAddress)(pop.to, signer);
}
if (pop.from !== null && pop.from !== undefined) {
const from = pop.from;
pop.from = Promise.all([
signer.getAddress(),
(0, ethers_1.resolveAddress)(from, signer),
]).then(([address, resolvedFrom]) => {
(0, ethers_1.assertArgument)(address.toLowerCase() === resolvedFrom.toLowerCase(), "transaction from mismatch", "tx.from", resolvedFrom);
return address;
});
}
else {
pop.from = signer.getAddress();
}
return (0, ethers_utils_1.resolveProperties)(pop);
}
const Primitive = "bigint,boolean,function,number,string,symbol".split(/,/g);
function deepCopy(value) {
if (value === null ||
value === undefined ||
Primitive.indexOf(typeof value) >= 0) {
return value;
}
// Keep any Addressable
if (typeof value.getAddress === "function") {
return value;
}
if (Array.isArray(value)) {
return value.map(deepCopy);
}
if (typeof value === "object") {
return Object.keys(value).reduce((accum, key) => {
accum[key] = value[key];
return accum;
}, {});
}
throw new errors_1.HardhatEthersError(`Assertion error: ${value} (${typeof value})`);
}
//# sourceMappingURL=signers.js.map