Hardhat – How to Deploy Using a Custom Signer

contract-deploymentethers.jshardhat

Normally, to deploy contracts to a network, we specify the private keys in accounts section of the network config, Like below, and these accounts get used in signing the transactions.

module.exports = {
  defaultNetwork: "rinkeby",
  networks: {
    hardhat: {
    },
    rinkeby: {
      url: "https://eth-rinkeby.alchemyapi.io/v2/1234",
      accounts: [privateKey1, privateKey2, ...]
    }
  },

But we need to use a custom signer that will sign the transactions instead. All transactions that are part of the deployment process needs to be signed via this custom signer.

How do I do this using Hardhat/ethers.js ?

EDIT

The idea is similar to signing with a metamask wallet, difference being:

  1. Entire operation is in backend, thus no window.ethereum available
  2. This signing process needs to be called by Hardhat as well when it wants to deploy the contracts.
  3. We have been given a library, which has a sign method that needs to be called with a few arguments such as the abi, rawTransaction and the keys. I'm trying to figure out how to ask Hardhat to invoke this sign method along with required arguments during deployment.

Edit 2

Courtesy @Ad-h0c, I understand what I am really looking for is a Custom Provider that hardhat will use during the deployment process. We have been doing this in Truffle for a long time using something similar to this, but more complex as it also takes in the signing function with the tx raw arguments. Thus, the requirements become:

  1. Specify a custom provider to Hardhat to use during deployment
  2. provide capability to the custom provider function to accept the raw transaction data, which will internally be signed using our own library and then sent to the blockchain

Best Answer

If you are asking how to sign a transaction through wallets? For example, metamask, you can follow the code below.

const foo = async (params) => {
    const provider = new ethers.providers.Web3Provider(window.ethereum);
    await provider.send("eth_requestAccounts", []);
    const signer = provider.getSigner();
    const Add = "<Contract Address>";
    const contract = new ethers.Contract(
      Add,
      contractABI,
      signer
    );
    let tx = await contract.writeFoo(params);
    await tx.wait();
}

--

const foo = async (params) => {
        const provider = new ethers.providers.Web3Provider(window.ethereum);
        await provider.send("eth_requestAccounts", []);
        const Add = "<Contract Address>";
        const contract = new ethers.Contract(
          Add,
          contractABI,
          provider
        );
        let tx = await contract.readFoo(params);
        await tx.wait();
    }

If you read two code snippets carefully, you will observe that when I was trying to get contract instance, in the first snippet, I used address, contractABI, and signer. While in the second snippet, I used address, contractABI, and provider.

While you are writing a transaction, you need to sign the transaction that is why I passed the signer(if you do this, metamask will ask your permission).

While In the second contract, I am just reading(readFoo) so I do not have to sign, thus I passed provider. It is entierly upto the situation which one to use.

Tell me if it helps!

EDIT:

That is even easier than former. All you have to do is, create wallet instance and sign in through programatically even before deploying. Just know, without interface, you have to sign in using the private address.

const { ethers } = require("ethers");
require("dotenv").config();

const { GOERLI_PRIVATE_KEY, ALCHEMY_API_KEY } = process.env;

const provider = new ethers.providers.JsonRpcProvider(
  `https://eth-goerli.alchemyapi.io/v2/${ALCHEMY_API_KEY}`
);

const sender = "0xe515E4883c503584e2DfbdAF60149DAbc123657b";
const receiver = "0x2F600dFC67E8AFDbCb650790c13cB94178871C7B";

const wallet = new ethers.Wallet(GOERLI_PRIVATE_KEY, provider);

const address = "0x326C977E6efc84E512bB9C30f76E30c160eD06FB";

const abi = [
  "function balanceOf(address) view returns(uint)",
  "function transfer(address to, uint amount) returns(bool)",
];

const contract = new ethers.Contract(address, abi, provider);

const main = async () => {
  const senderBefore = await contract.balanceOf(sender);
  const receiverBefore = await contract.balanceOf(receiver);

  console.log(
    `Sender balance before the transfer is: ${ethers.utils.formatEther(
      senderBefore
    )}`
  );

  console.log(
    `Receiver balance before the transfer is: ${ethers.utils.formatUnits(
      receiverBefore,
      "ether"
    )}`
  );

  const amount = ethers.utils.parseEther("5");

  const contractWithWallet = contract.connect(wallet);

  const tx = await contractWithWallet.transfer(receiver, amount);

  await tx.wait();

  console.log(tx);

  const senderAfter = await contract.balanceOf(sender);
  const receiverAfter = await contract.balanceOf(receiver);

  console.log(
    `Sender balance before the transfer is: ${ethers.utils.formatEther(
      senderAfter
    )}`
  );

  console.log(
    `Receiver balance before the transfer is: ${ethers.utils.formatUnits(
      receiverAfter,
      "ether"
    )}`
  );
};

main().catch((error) => {
  console.log(error);
  process.exit(1);
});

This is a little big code it will answer your problem. There are few things that will change if you are signing from backend. First of all, you need node provider. I have used alchemy here, you can use one to your convience.

Second, you need your private key. In this case, I will suggest you to create .env file and store your private address, alchemy api etc.

Because it is more secure if you are uploading your app in the github and you can always add .env file to .gitignore file to stop from pushing.

So to sign from the backend first you use the ethers.wallet method with private key and provider as arguments.

However, that is just creating the wallet with your private address but you aren't really signing it.

To sign, you use the following method .connect.

const contractWithWallet = contract.connect(wallet);

With that you signed with custom address. From now on, you can use the smart contract functions that needs sign in from the user.

In your case:

const tx = await contractWithWallet.sign(abi, rawTransaction, keys);

This is it. If you want you can also pass custom private key like this.

const init = async (PRIVATE_KEY) => {
  const wallet = new ethers.Wallet(`${PRIVATE_KEY}`, provider);

  ......
  // Here you can connect the wallet with the contract and call the 
     function in the contract.
}

EDIT2:

In hardhat, as long as you have custom provider APIs, you can add as many as providers you want.

Below, I have shared my hardhat.config.json file. There you can see how I added multiple providers.

const {
  GOERLI_PRIVATE_KEY,
  ALCHEMY_API_KEY,
  QUICKNODE_API_KEY,
} = process.env;

module.exports = {
  solidity: "0.8.9",
  networks: {
    goerli: {
      url: `https://eth-goerli.alchemyapi.io/v2/${ALCHEMY_API_KEY}`,
      accounts: [GOERLI_PRIVATE_KEY],
    },
    bsctest: {
      url: `https://radial-wispy-dinghy.bsc-testnet.discover.quiknode.pro/${QUICKNODE_API_KEY}`,
      accounts: [GOERLI_PRIVATE_KEY],
    },
  },

That is how you add providers for starters. Then when you are trying to deploy the contract, it will be following steps.

  1. Write contract
  2. Create deploy.js file in scripts folder.
  3. deploy it using terminal.

From the root folder, you will run this command.

npx hardhat run scripts/deploy.js --network goerli

npx hardhat run scripts.deploy.js --network bsctest

First one for goerli. You can give the custom names you want. And second one for bsc test net.

You can also directly verify the contracts directly from the hardhat terminal without going to the etherscan or bscscan. Take a look at this.

Hardhat verify

Getting started with hardhat

Hardhat documentation

Hardhat has the best documentation. You can find most references there. Try looking there for deep understanding.

Hardhat custom provider

Tutorials - you can find most of the useful tutorisals here.

provide capability to the custom provider function to accept the raw transaction data, which will internally be signed using our own library and then sent to the blockchain

Whatever your requirements are, you will do that in deploy.js file using ether.js. You can only deploy using those commands above and you can use raw transaction data to pass to new function to sign in javascript file.

Raw Transactions

Well, is that what you are looking for?

Related Topic