DEV Community

Cover image for Hardhat Smartcontract Lottery (Raffle) Using TypeScript and latest versions of everything
Alex Awesome
Alex Awesome

Posted on

Hardhat Smartcontract Lottery (Raffle) Using TypeScript and latest versions of everything

I am in the progress of learning Patrick Collins' tutorial. This tutorial provided a fantastic introduction to blockchain and web3 development using JavaScript, Solidity, and Hardhat. One of the lessons aimed to develop and deploy a lottery(Raffle) smart contract using hardhat-deploy, hardhat dev testnet, and the Rinkeby testnet. Patrick also implement a typescript version of his code.

This article aims to build upon that work by updating the application to TypeScript and using the latest versions of all dependencies. I would like to use TypeScript in a stronger way. We'll also switch from the Rinkeyby testnet to the Sepolia testnet, which is more up-to-date.

It's not a complete tutorial of creating a hardhat application. It's just an extension to Patrick Collins' ultra-ultimate tutorial for year 2023 when we have ehters.js version 6.

This is the typescript branch Patric Collins' repository https://github.com/PatrickAlphaC/hardhat-smartcontract-lottery-fcc/tree/typescript

We will be making a few small corrections in line with the rapidly changing worlds of JS and blockchain.

Installation & preparations

Hardhat has great documentation https://hardhat.org/hardhat-runner/docs/getting-started#installation. I will describe hardhat things in a nutshell.

Installation and setting up

You can install hardhat globally or locally. I prefer the second option but if you desire to be a super-skilled senior smart-contract developer you probably need to have it installed globally. Anyway, we have to install hardhat somehow.

Then we should initiate the project

hardhat
npx hardhat
Enter fullscreen mode Exit fullscreen mode

Or after installing the hardhat-shorthand you can use just

hh
Enter fullscreen mode Exit fullscreen mode

To simplify the process we chose the Typescript project option:

Image description

Agree with everything and set it up.

We don't need contracts, scripts, and tests generated by Hardhat.

rm -rf contract/ scripts/ test/
Enter fullscreen mode Exit fullscreen mode

We will have the same Solidity contract as Patrik. https://github.com/shaggyrec/hardhat-smartcontract-lottery-ts/blob/main/contracts/Raffle.sol
There are no changes here.

We also have to install a couple of dependencies

npm i @nomicfoundation/hardhat-toolbox @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers dotenv hardhat-gas-reporter -D
Enter fullscreen mode Exit fullscreen mode

I usually use eslint. Patrick used prettier. It doesn't matter which tool you choose. But it would be good practice to choose something because JS allows you too much and you should have borders to have your code in great condition.

For eslint I need to make

npm i eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser -D
Enter fullscreen mode Exit fullscreen mode

and turn it on in the IDE.

We can make 'hh compile to have contract typings to write our code. But it is not necessary. The Hardhat executes this command right before the deploy.

Config

We want to use TypeScript. And we want to use it in the right way. Patrik Collins has helper-hardhat-config.ts in his repo we won't. Instead of it, we will extend the Hardhat config.

I don't want to use any additional config files. And I really want to have all configurations of the current network in the network option. This is a difference between Patrics' solution and the current solution. In the following config file you can see blockConfirmations, entranceFee, gasLane, subscriptionId, callbackGasLimit, interval option right in the network object.

import '@typechain/hardhat';
import '@nomicfoundation/hardhat-toolbox';
import '@nomiclabs/hardhat-ethers';
import '@nomicfoundation/hardhat-ethers';
import 'hardhat-deploy';
import 'hardhat-gas-reporter';
import './type-extensions';
import 'dotenv/config';
import { HardhatUserConfig } from 'hardhat/config';
import { ethers } from 'ethers';


const config: HardhatUserConfig = {
    solidity: '0.8.18',
    defaultNetwork: 'hardhat',
    networks: {
        hardhat: {
            chainId: 31337,
            blockConfirmations: 1,
            entranceFee: ethers.parseEther('0.01'),
            gasLane: '0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c',
            subscriptionId: '',
            callbackGasLimit: '500000',
            interval: 30
        },
        sepolia: {
            chainId: 11155111,
            url: process.env.SEPOLIA_RPC_URL,
            accounts: [process.env.SEPOLIA_PRIVATE_KEY as string],
            blockConfirmations: 6,
            vrfCoordinator: '0x8103B0A8A00be2DDC778e6e7eaa21791Cd364625',
            entranceFee: ethers.parseEther('0.01'),
            gasLane: '0x474e34a077df58807dbe9c96d3c009b23b3c6d0cce433e59bbf5b34f823bc56c',
            subscriptionId: '3092',
            callbackGasLimit: '500000',
            interval: 30
        }
    },
    etherscan: {
        apiKey: {
            sepolia: process.env.ETHERSCAN_API_KEY || ''
        },
    },
    namedAccounts: {
        deployer: {
            default: 0
        },
        player: {
            default: 1
        }
    },
    developmentChains: ['hardhat', 'localhost'],
    organizerFee: 10,
    mocha: {
        timeout: 40000
    }
};

export default config;

Enter fullscreen mode Exit fullscreen mode

But it's not enough. TypeScript/Eslint will show the errors

Image description

One more important thing.
In ethers 6 you can no longer use ethers.utils.parseEther('0.01') the function doesn't exist. Utils isn't exit. We should use just ethers.parseEther('0.01').

I created type-extensions.ts file to extend the default config.

import 'hardhat/types/config';

declare module 'hardhat/types/config' {
    export interface HttpNetworkUserConfig {
        blockConfirmations?: number;
        vrfCoordinator?: string;
        entranceFee: bigint;
        gasLane: string;
        subscriptionId?: string;
        callbackGasLimit: string;
        interval: number;
    }

    export interface HardhatNetworkUserConfig {
        blockConfirmations?: number;
        entranceFee: bigint;
        gasLane: string;
        subscriptionId?: string;
        callbackGasLimit?: string;
        interval: number;
    }

    export interface HardhatUserConfig {
        developmentChains: string[];
        organizerFee: number;
    }

    export interface HttpNetworkConfig {
        blockConfirmations: number;
        vrfCoordinator: string;
        entranceFee: bigint;
        gasLane: string;
        subscriptionId: string;
        callbackGasLimit: string;
        interval: number;
    }

    export interface HardhatNetworkConfig {
        blockConfirmations: number;
        vrfCoordinator?: string;
        entranceFee: bigint;
        gasLane: string;
        subscriptionId: string;
        callbackGasLimit: string;
        interval: number;
    }

    export interface HardhatConfig {
        developmentChains: string[];
        organizerFee: number;
    }
}
Enter fullscreen mode Exit fullscreen mode

I believe that structure is very clear. Declarations with UserConfig suffix are for hardhat config. Declarations with Config suffix are for exposing these properties in your scripts.

Let see it. The deploy/01-deploy-raffle.ts should be similar to

import { HardhatRuntimeEnvironment } from 'hardhat/types';
import { config, network } from 'hardhat';
import { DeployFunction } from 'hardhat-deploy/dist/types';
import verify from '../utils/verify';
import { VRFCoordinatorV2Mock } from '../typechain-types';
import { EventLog } from 'ethers';

const deployRaffle: DeployFunction = async function ({ getNamedAccounts, deployments, ethers }: HardhatRuntimeEnvironment) {
    const { deploy, log } = deployments;
    const { deployer } = await getNamedAccounts();
    let vrfCoordinatorV2Address = network.config.vrfCoordinator;
    let subscriptionId = network.config.subscriptionId;
    let vrfCoordinatorV2Mock: VRFCoordinatorV2Mock;

    if (config.developmentChains.includes(network.name)) {
        vrfCoordinatorV2Mock = await ethers.getContract('VRFCoordinatorV2Mock');
        vrfCoordinatorV2Address = await vrfCoordinatorV2Mock.getAddress();
        const transactionReceipt = await (await vrfCoordinatorV2Mock.createSubscription())
            .wait(1);

        subscriptionId = (transactionReceipt?.logs[0] as EventLog).args.subId;
        await vrfCoordinatorV2Mock.fundSubscription(subscriptionId, ethers.parseEther('2'));
    }

    const args = [
        vrfCoordinatorV2Address,
        network.config.entranceFee,
        network.config.gasLane,
        subscriptionId,
        network.config.callbackGasLimit,
        network.config.interval,
        config.organizerFee
    ];

    const raffle = await deploy('Raffle', {
        from: deployer,
        args,
        log: true,
        waitConfirmations: network.config.blockConfirmations || 1
    });

    if (config.developmentChains.includes(network.name)) {
        await vrfCoordinatorV2Mock!.addConsumer(subscriptionId, raffle.address);
    }

    if (!config.developmentChains.includes(network.name) && process.env.ETHERSCAN_API_KEY) {
        await verify(raffle.address, args, log);
    }

    log('----------------------------');
};

deployRaffle.tags = ['all', 'raffle'];
export default deployRaffle;

Enter fullscreen mode Exit fullscreen mode

We've imported the network from the hardhat and I can use network.config.vrfCoordinator, network.config.subscriptionId. Awesome!

Another awesome thing is that we have hintings for our contract object.

We can get contract using TypeSctipt generics thing

import { Raffle } from '../typechain-types';

const raffle = await ethers.getContract<Raffle>('Raffle');
Enter fullscreen mode Exit fullscreen mode

And now we can use this kind of magic:

Image description

All other things of the application are the same. I will attach the link to the repository in the end of this article.

Tests

Tests are almost the same. We have the difference in case of bigint interactions. But it is so easy:

const contractBalance = BigInt(raffleEntranceFee) * BigInt(additionalEntrances) + BigInt(raffleEntranceFee);
Enter fullscreen mode Exit fullscreen mode

Also, we should use instead of

raffle.callStatic.checkUpkeep("0x")
Enter fullscreen mode Exit fullscreen mode

a bit different

raffle.checkUpkeep.staticCall('0x');
Enter fullscreen mode Exit fullscreen mode

And we can replace this bulky time-machine functionality

await network.provider.send('evm_increaseTime', [interval + 1]);
                await network.provider.request({ method: 'evm_mine', params: [] });
Enter fullscreen mode Exit fullscreen mode

with laconic

import { time } from '@nomicfoundation/hardhat-toolbox/network-helpers';
...
await time.increase(interval + 1);
Enter fullscreen mode Exit fullscreen mode

I have two skipped tests because at the time of writing this article events firing doesn't work properly in the latest version. Link to the github issue

Sepolia

Patric Collins works with Rinkeby in his tutorial. Using of Sepolia is quite similar.

Creating subscription https://docs.chain.link/vrf/v2/subscription/supported-networks/#sepolia-testnet

VRF things https://vrf.chain.link/

Upkeep registering https://automation.chain.link/sepolia

Etherscan https://sepolia.etherscan.io/

Conclusion

We can deploy and enter the lottery

hh deploy --network sepolia
hh run scripts/enterRaffle.ts --network sepolia
Enter fullscreen mode Exit fullscreen mode

We used the latest version on everything and bit rewrite Freecodecamp Patrick Collins' Raffle application

We have a problem with a subscription to the events in the latest version of hardhat. But I believe that it will be fixed soon.

There you have it, a lottery smart contract built with Hardhat, TypeScript, and the latest versions of all dependencies, and deployed locally or on the Sepolia testnet.

The link to the repository of this article is

https://github.com/shaggyrec/hardhat-smartcontract-lottery-ts

  • Follow me

https://t.me/awesomeprog
https://twitter.com/shogenoff
http://facebook.com/shogentle

Top comments (0)