5. Ethers.js

Asynchronous programming in Javascript

// synchronous [solidity]
/*
1. Put popcorn in microwave -> promise
2. Wait for popcorn to finish -> await
3. Pout drinks for everyone
*/

// asynchronous [javascript]
/*
1. Put popcorn in microwave
2. Pout drinks for everyone
3. Wait for popcorn to finish
*/

// promise
/*
1. Pending
2. Fulfilled
3. Rejected
*/

async function main() {
  console.log("Joe");
}

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

Compiling our Solidity

  • We will use solc-js to compile our contract

  • https://github.com/ethereum/solc-js

  • To compile our contract we use following command

    • yarn solcjs --bin --abi --include-path node_modules --base-path . -o . SimpleStorage.sol

  • It generates two files

    • .abi file

    • .bin file

  • We can put this command inside package.json as follows, so that we can compile every time using yarn compile

  • "scripts": {
      "compile": "yarn solcjs --bin --abi --include-path node_modules --base-path . -o . SimpleStorage.sol"
    }

Ganache & Networks

  • Ganache is similar to Javascript VM in remix

  • We can deploy and run our contract on it

  • The RPC url in networks is used to make API calls to a blockchain node that someone is running

  • We can use axios to make API requests, but we will use a wrapper known as Ethers to interact with them


Introduction to Ethers.js

  • Main component that powers the hardhat environment unlike web3-js

  • In ethers, a contract factory is just used to deploy a contract (not a factory pattern)

const ethers = require("ethers");
const fs = require("fs"); // To read from file

async function main() {
  // Contract Import Methods
  // 1. Compile them in our code
  // 2. Compile them separately

  // GANACHE RPC
  const provider = new ethers.providers.JsonRpcProvider("http://0.0.0.0:7545");
  // GANACHE WALLET Private Address
  const wallet = new ethers.Wallet(
    "815446e0f6609398924bd23e92038dd0a8ce69dd50b2d6c4d5d8dc298ea03311",
    provider
  );

  // Synchronously read file
  const abi = fs.readFileSync("./SimpleStorage_sol_SimpleStorage.abi", "utf-8");
  const binary = fs.readFileSync(
    "./SimpleStorage_sol_SimpleStorage.bin",
    "utf-8"
  );

  const contractFactory = new ethers.ContractFactory(abi, binary, wallet);
  console.log("Deploying, please wait...");

  const contract = await contractFactory.deploy(); // Stop here, wait for contract to deploy
  console.log(contract);
}

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

Adding Transaction Overrides

  • Similar to remix, we can override different values for our contracts like gasPrice, gasLimit, etc

const contract = await contractFactory.deploy({ gasPrice: 1000 });

Transaction Receipts

  • We can wait for the number of blocks confirmations before we proceed

const transactionReceipt = await contract.deployTransaction.wait(1);

console.log("Here is deployment transaction (transaction response): ");
console.log(contract.deployTransaction);

console.log("Here is transaction receipt: ");
console.log(transactionReceipt);

Sending a "raw" transaction in ethersjs

  • This is pure javascript way to send raw transaction using ethers, it is not recommended in ethers and hardhat

// const contractFactory = new ethers.ContractFactory(abi, binary, wallet);
// console.log("Deploying, please wait...");

// const contract = await contractFactory.deploy();

console.log("Lets deploy with only transaction data!!!");

const nonce = await wallet.getTransactionCount();
const tx = {
nonce: nonce,
gasPrice: 20000000000,
gasLimit: "1000000",
to: null,
value: 0,
// data is the binary object copied pasted from file starting with 0x
data: "0x608060405234801561001057600080fd5b50610771806100206000396000f3fe608060405234801561001057600080fd5b50600436106100575760003560e01c80632e64cec11461005c5780636057361d1461007a5780636f760f41146100965780638bab8dd5146100b25780639e7a13ad146100e2575b600080fd5b610064610113565b604051610071919061052a565b60405180910390f35b610094600480360381019061008f919061046d565b61011c565b005b6100b060048036038101906100ab9190610411565b610126565b005b6100cc60048036038101906100c791906103c8565b6101b6565b6040516100d9919061052a565b60405180910390f35b6100fc60048036038101906100f7919061046d565b6101e4565b60405161010a929190610545565b60405180910390f35b60008054905090565b8060008190555050565b6001604051806040016040528083815260200184815250908060018154018082558091505060019003906000526020600020906002020160009091909190915060008201518160000155602082015181600101908051906020019061018c9291906102a0565b505050806002836040516101a09190610513565b9081526020016040518091039020819055505050565b6002818051602081018201805184825260208301602085012081835280955050505050506000915090505481565b600181815481106101f457600080fd5b906000526020600020906002020160009150905080600001549080600101805461021d9061063e565b80601f01602080910402602001604051908101604052809291908181526020018280546102499061063e565b80156102965780601f1061026b57610100808354040283529160200191610296565b820191906000526020600020905b81548152906001019060200180831161027957829003601f168201915b5050505050905082565b8280546102ac9061063e565b90600052602060002090601f0160209004810192826102ce5760008555610315565b82601f106102e757805160ff1916838001178555610315565b82800160010185558215610315579182015b828111156103145782518255916020019190600101906102f9565b5b5090506103229190610326565b5090565b5b8082111561033f576000816000905550600101610327565b5090565b60006103566103518461059a565b610575565b90508281526020810184848401111561037257610371610704565b5b61037d8482856105fc565b509392505050565b600082601f83011261039a576103996106ff565b5b81356103aa848260208601610343565b91505092915050565b6000813590506103c281610724565b92915050565b6000602082840312156103de576103dd61070e565b5b600082013567ffffffffffffffff8111156103fc576103fb610709565b5b61040884828501610385565b91505092915050565b600080604083850312156104285761042761070e565b5b600083013567ffffffffffffffff81111561044657610445610709565b5b61045285828601610385565b9250506020610463858286016103b3565b9150509250929050565b6000602082840312156104835761048261070e565b5b6000610491848285016103b3565b91505092915050565b60006104a5826105cb565b6104af81856105d6565b93506104bf81856020860161060b565b6104c881610713565b840191505092915050565b60006104de826105cb565b6104e881856105e7565b93506104f881856020860161060b565b80840191505092915050565b61050d816105f2565b82525050565b600061051f82846104d3565b915081905092915050565b600060208201905061053f6000830184610504565b92915050565b600060408201905061055a6000830185610504565b818103602083015261056c818461049a565b90509392505050565b600061057f610590565b905061058b8282610670565b919050565b6000604051905090565b600067ffffffffffffffff8211156105b5576105b46106d0565b5b6105be82610713565b9050602081019050919050565b600081519050919050565b600082825260208201905092915050565b600081905092915050565b6000819050919050565b82818337600083830152505050565b60005b8381101561062957808201518184015260208101905061060e565b83811115610638576000848401525b50505050565b6000600282049050600182168061065657607f821691505b6020821081141561066a576106696106a1565b5b50919050565b61067982610713565b810181811067ffffffffffffffff82111715610698576106976106d0565b5b80604052505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b600080fd5b600080fd5b600080fd5b600080fd5b6000601f19601f8301169050919050565b61072d816105f2565b811461073857600080fd5b5056fea2646970667358221220dccb37d86ec2e661f28ae43f8971ab69a82890d7233c6418e19022cea685ce3064736f6c63430008070033",
chainId: 5777, //ganache network id
};

// const signedTxResponse = await wallet.signTransaction(tx);
// console.log(signedTxResponse);

const sentTxResponse = await wallet.sendTransaction(tx); // Automatically signs the transaction
await sentTxResponse.wait(1); // Wait for 1 block confirmation
console.log(sentTxResponse);

Interacting with contracts in Ethersjs

  • We work with BigNumbers (from EthersJs) or strings instead of numbers in javascript due to limitations of size and floating point

const contractFactory = new ethers.ContractFactory(abi, binary, wallet);
console.log("Deploying, please wait...");

const contract = await contractFactory.deploy();
await contract.deployTransaction.wait(1);

// contract object contains all the functions contained in the abi which we can call
const currentFavouriteNumber = await contract.retrieve();
// console.log(currentFavouriteNumber);
console.log(`Current Favourite Number: ${currentFavouriteNumber.toString()}`);

const transactionResponse = await contract.store("7");
const transactionReciept = await transactionResponse.wait(1);

const updatedFavouriteNumber = await contract.retrieve();
console.log(`Updated Favourite Number: ${updatedFavouriteNumber.toString()}`);

Environmental Variables

  • We dont store API endpoints or Private keys inside code since they change, hence we use .env file

  • In node, we add a package called dotenv

.env

PRIVATE_KEY=0xed111189044f609eb233e03e7ab676af9dd47cb989050f98c4c5ce0993eae3dd

deploy.js

require("dotenv").config();

async function main() {
  const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider);
}

Better Private Key Management

  • We can encrypt private key and store encrypted key locally

encryptKey.js

const ethers = require("ethers");
const fs = require("fs");
require("dotenv").config();

async function main() {
  const wallet = new ethers.Wallet(process.env.PRIVATE_KEY);
  const encryptedJsonKey = await wallet.encrypt(
    process.env.PRIVATE_KEY_PASSWORD,
    process.env.PRIVATE_KEY
  );

  fs.writeFileSync("./encryptedKey.json", encryptedJsonKey);
}

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

deploy.js

async function main() {
  const encryptedJson = fs.readFileSync("./encryptedKey.json", "utf-8");

  // Used let here because we have to connect wallet with provider
  let wallet = new ethers.Wallet.fromEncryptedJsonSync(
    encryptedJson,
    process.env.PRIVATE_KEY_PASSWORD
  );
  wallet = await wallet.connect(provider);
}

Prettier Formatting

  • We can install node plugin via yarn add prettier prettier-plugin-solidity

  • We can write rules in .prettierrc file

{
    "tabWidth": 4,
    "semi": false,
    "useTabs": false,
    "singleQuote": false
}

Deploying to Testnet or Mainnet

  • We will use Alchemy which gives us node as a service, we use use its HTTP URL in our app

  • We will our our Metamask Wallet private key in our app


Final Codes

deploy.js

const ethers = require("ethers")
const fs = require("fs") // To read from file
require("dotenv").config()

async function main() {
    const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL)
    const wallet = new ethers.Wallet(process.env.PRIVATE_KEY, provider)

    // Synchronously read file
    const abi = fs.readFileSync(
        "./SimpleStorage_sol_SimpleStorage.abi",
        "utf-8"
    )
    const binary = fs.readFileSync(
        "./SimpleStorage_sol_SimpleStorage.bin",
        "utf-8"
    )

    const contractFactory = new ethers.ContractFactory(abi, binary, wallet)
    console.log("Deploying, please wait...")

    const contract = await contractFactory.deploy()
    await contract.deployTransaction.wait(1)

    console.log(`Contract Address: ${contract.address}`)

    const currentFavouriteNumber = await contract.retrieve()
    console.log(
        `Current Favourite Number: ${currentFavouriteNumber.toString()}`
    )

    const transactionResponse = await contract.store("7")
    const transactionReciept = await transactionResponse.wait(1)

    const updatedFavouriteNumber = await contract.retrieve()
    console.log(
        `Updated Favourite Number: ${updatedFavouriteNumber.toString()}`
    )
}

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

Custom Hardhat Tasks

Last updated