#Adding config to your plugin
In this section we'll add a way to configure your plugin using the Config System.
We'll add a new myAccountIndex
property to each network config so that the user can specify which account to use.
#Extending the config types
The network config types don't have a myAccountIndex
property, so we'll need to extend them using a Type Extension.
First, open packages/plugin/src/type-extensions.ts
and replace the code above the one we added in the previous section with the following:
import "hardhat/types/config";
declare module "hardhat/types/config" {
export interface EdrNetworkUserConfig {
myAccountIndex?: number;
}
export interface EdrNetworkConfig {
myAccountIndex: number;
}
export interface HttpNetworkUserConfig {
myAccountIndex?: number;
}
export interface HttpNetworkConfig {
myAccountIndex: number;
}
}
This extends four types:
EdrNetworkUserConfig
andHttpNetworkUserConfig
: Part of theHardhatUserConfig
type, which represent the config that the user writes in theirhardhat.config.ts
file.EdrNetworkConfig
andHttpNetworkConfig
: Part of theHardhatConfig
type, which is the config that Hardhat uses internally.
We replaced an existing config extension with a new one, so you'd get compilation errors if you try to build your plugin now. Let's remove the code that depends on the old extension.
First, delete these files:
rm packages/plugin/src/types.ts
rm packages/plugin/test/config.ts
rm packages/plugin/test/example-tests.ts
Then, replace the contents of packages/plugin/src/tasks/my-task.ts
with the following:
import { HardhatRuntimeEnvironment } from "hardhat/types/hre";
interface MyTaskTaskArguments {
who: string;
}
export default async function (
taskArguments: MyTaskTaskArguments,
hre: HardhatRuntimeEnvironment
) {}
#Extending the config validation and resolution
The config that a user provides can be invalid, so we have to extend the validation process to make sure it's valid.
At the same time, we also need to resolve any default value for networks that don't have a myAccountIndex
property.
We'll do this by modifying the validatePluginConfig
and resolvePluginConfig
functions in the packages/plugin/src/config.ts
file. They are called by the validateUserConfig
and resolveUserConfig
Hook Handlers in packages/plugin/src/hooks/config.ts
.
We recommend keeping the config extension logic outside the actual Hook Handlers to make testing them easier. To learn how to test them, take a look at this file in the tutorial
branch of the template project.
Replace the contents of packages/plugin/src/config.ts
with the following:
import { HardhatUserConfig } from "hardhat/config";
import { HardhatConfig } from "hardhat/types/config";
import { HardhatUserConfigValidationError } from "hardhat/types/hooks";
const DEFAULT_MY_ACCOUNT_INDEX = 0;
/**
* This function validates the parts of the HardhatUserConfig that are relevant
* to the plugin.
*
* This function is called from the `validateUserConfig` Hook Handler.
*
* @param userConfig The HardhatUserConfig, as exported in the config file.
* @returns An array of validation errors, or an empty array if valid.
*/
export async function validatePluginConfig(
userConfig: HardhatUserConfig
): Promise<HardhatUserConfigValidationError[]> {
if (
userConfig.networks === undefined ||
typeof userConfig.networks !== "object"
) {
// If there's no networks field or it's invalid, we don't validate anything
// in this plugin
return [];
}
const errors = [];
for (const [networkName, networkConfig] of Object.entries(
userConfig.networks
)) {
if (networkConfig.myAccountIndex === undefined) {
continue;
}
if (
typeof networkConfig.myAccountIndex !== "number" ||
networkConfig.myAccountIndex < 0
) {
errors.push({
path: ["networks", networkName, "myAccountIndex"],
message: "Expected a non-negative number.",
});
}
}
return errors;
}
/**
* Resolves the plugin config, based on an already validated HardhatUserConfig
* and a partially resolved HardhatConfig.
*
* This function is called from the `resolveUserConfig` Hook Handler.
*
* @param userConfig The HardhatUserConfig.
* @param partiallyResolvedConfig The partially resolved HardhatConfig, which is
* generated by calling `next` in the `resolveUserConfig` Hook Handler.
* @returns The resolved HardhatConfig.
*/
export async function resolvePluginConfig(
userConfig: HardhatUserConfig,
partiallyResolvedConfig: HardhatConfig
): Promise<HardhatConfig> {
const networks: HardhatConfig["networks"] = {};
for (const [networkName, networkConfig] of Object.entries(
partiallyResolvedConfig.networks
)) {
const myAccountIndex =
userConfig.networks?.[networkName]?.myAccountIndex ??
DEFAULT_MY_ACCOUNT_INDEX;
networks[networkName] = {
...networkConfig,
myAccountIndex,
};
}
return {
...partiallyResolvedConfig,
networks,
};
}
The validatePluginConfig
function is in charge of validating that the fields that we added are correct. If they aren't, it returns an array of errors. We are doing it manually here, but you can use libraries like Zod or ArkType. It shouldn't validate parts of the config that are not relevant to the plugin.
The resolvePluginConfig
function is in charge of returning a new HardhatConfig
where all the myAccountIndex
properties have been added. Note that it uses the networks from the partially resolved config and not the user config, as they have already been resolved by Hardhat by the time this function is called. On the contrary, the myAccountIndex
values are read from the user config, because Hardhat doesn't know about them yet.
#Using the plugin config in the network hook
To use the config in the network hook, we just need to modify the implementation of our NetworkHooks#newConnection
Hook Handler so that it looks like this:
const connection = await next(context);
// Get the accounts from the connection
const accounts: string[] = await connection.provider.request({
method: "eth_accounts",
});
const myAccountIndex = connection.networkConfig.myAccountIndex;
if (accounts.length <= myAccountIndex) {
throw new HardhatPluginError(
`hardhat-plugin-template`,
`Invalid index ${myAccountIndex} for myAccount when connecting to network ${connection.networkName}`,
);
}
connection.myAccount = accounts[myAccountIndex];
return connection;
We access the config in the highlighted line and validate that the index is valid. If it isn't, we throw a HardhatPluginError
, which you should import from hardhat/plugins
. When you throw a HardhatPluginError
, Hardhat will show a nice error message to the user.
Finally, we use the myAccountIndex
to set the myAccount
property of the NetworkConnection
object.
#Trying out the config extension
You can try out the config extension by updating the example project's config and running the my-account-example.ts
script again.
Update your configuration in packages/example-project/hardhat.config.ts
by removing any myPlugin
field and adding this network config:
networks: {
default: {
type: "edr-simulated",
myAccountIndex: 1,
},
},
Now if you build your plugin and run the script again, you should see a different address:
pnpm hardhat run scripts/my-account-example.ts
Compiling your Solidity contracts...
Compiled 1 Solidity file with solc 0.8.29 (evm target: cancun)
connection.myAccount: 0x70997970c51812dc3a010c7d01b50e0d17dc79c8