Simple Storage Smart Contract on Avalanche Blockchain with Solidity, TypeScript, EVM, HardHat and EthersJS
Tags: typescript, avalanche, blockchain, web3
Published: 2023-04-23 14:40:08

# Introduction

Avalanche is a relatively new blockchain platform that aims to solve some of the scalability and efficiency issues faced by other blockchain networks like Bitcoin and Ethereum. It was created by a team of computer scientists led by Emin Gün Sirer, a well-known figure in the blockchain community.

Avalanche uses a novel consensus mechanism called Avalanche consensus, which allows for high throughput, low latency, and high transaction finality. Unlike traditional Proof-of-Work or Proof-of-Stake consensus algorithms, Avalanche relies on a random sampling of network participants to quickly reach consensus on the state of the network.

One of the key features of Avalanche is its ability to support multiple subnets or "blockchains within a blockchain." This means that different groups of users can create their own subnets with their own rules and governance structures while still being able to interact with the wider Avalanche network.

Another notable feature of Avalanche is its support for the creation of smart contracts using the Solidity programming language, which is also used by Ethereum. This allows developers to build decentralised applications (dApps) on the Avalanche platform with familiar tools and languages.

Overall, Avalanche is a promising blockchain platform that offers significant improvements in scalability, speed, and flexibility over other existing blockchain networks. Its unique consensus mechanism and support for multiple subnets make it an interesting option for developers and users looking to build decentralised applications or participate in a fast and efficient blockchain ecosystem.

# Steps

## **Prerequisites**

Before we begin, make sure you have the following tools installed:

1. Node.js (v14+)
2. npm (v7+)
3. HardHat (v2+)
4. Solidity (v0.8+)

## **Step 1: Setting up the project**

Create a new directory for the project and navigate into it:

```
mkdir simple-storage
cd simple-storage
```

Initialize a new Node.js project and install the required dependencies:

```
npm init -y
npm install --save-dev hardhat @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers typescript ts-node dotenv @types/dotenv @types/mocha @types/chai avalanche
```

Initialize HardHat:

```
npx hardhat
```

Choose "Create an empty hardhat.config.js" when prompted.

## **Step 2: Configure TypeScript and HardHat**

Create a new **`tsconfig.json`** file in the root of the project with the following content:

```json
{
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,
    "outDir": "dist"
  },
  "include": ["./scripts", "./test"],
  "files": ["./hardhat.config.ts"]
}
```

Rename **`hardhat.config.js`** to **`hardhat.config.ts`** and update its content:

```tsxcopy
import { HardhatUserConfig } from 'hardhat/config';
import '@nomiclabs/hardhat-waffle';
import '@nomiclabs/hardhat-ethers';
import 'dotenv/config';

const config: HardhatUserConfig = {
  solidity: '0.8.0',
  networks: {
    hardhat: {
      chainId: 31337,
    },
  },
  mocha: {
    timeout: 20000,
  },
};

export default config;
```

## **Step 3: Creating the Simple Storage Contract**

Create a new directory **`contracts`** and a new Solidity file **`SimpleStorage.sol`** inside it:

```
mkdir contracts
touch contracts/SimpleStorage.sol
```

Add the following code to **`SimpleStorage.sol`**:

```solidity
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

contract SimpleStorage {
  uint256 private _storedData;

  function set(uint256 value) public {
    _storedData = value;
  }

  function get() public view returns (uint256) {
    return _storedData;
  }
}
```

### **Overview**

The Simple Storage contract is designed to demonstrate the basic functionality of a smart contract by allowing users to store and retrieve data on the Ethereum blockchain. The contract consists of a single state variable that holds an unsigned integer and two functions for setting and getting the stored value.

### **State Variables**

The contract has one state variable:

- **`storedData`**: This unsigned integer (**`uint256`**) holds the stored data. It is the main data storage for the contract, and its value can be modified using the **`set()`** function and retrieved using the **`get()`** function.

### **Functions**

The contract has two main functions:

### **`set()`**

This function allows users to update the value of the **`storedData`** state variable. It takes a single input parameter, a **`uint256`** value, which represents the new data to be stored.

```solidity
function set(uint256 x) public {
    storedData = x;
}
```

When called, this function updates the **`storedData`** state variable with the provided input value.

### **`get()`**

This function allows users to retrieve the current value of the **`storedData`** state variable. It takes no input parameters and returns the **`uint256`** value stored in **`storedData`**.

```solidity
function get() public view returns (uint256) {
    return storedData;
}
```

When called, this function returns the current value of the **`storedData`** state variable without modifying it.

### **Interaction Flow**

The following steps outline the typical interaction flow for users interacting with the Simple Storage contract:

1. **Deploy the contract**: The user deploys the Simple Storage contract to the Ethereum blockchain. Upon deployment, the **`storedData`** state variable is initialized with the value of **`0`**.
2. **Set the stored data**: The user calls the **`set()`** function, providing a **`uint256`** value as input. The function updates the **`storedData`** state variable with the provided value.
3. **Get the stored data**: The user calls the **`get()`** function to retrieve the current value of the **`storedData`** state variable. The function returns the **`uint256`** value without modifying it.

### **Conclusion**

The Simple Storage smart contract demonstrates the basic functionality of a smart contract on the Ethereum blockchain. It allows users to store and retrieve an unsigned integer value through two simple functions, **`set()`** and **`get()`**. This contract serves as a starting point for understanding the fundamentals of smart contract development and can be expanded upon to implement more complex features and use cases.

## **Step 4: Writing tests for the contract**

Create a new directory **`test`** and a new TypeScript file **`SimpleStorage.test.ts`** inside it:

```
mkdir test
touch test/SimpleStorage.test.ts
```

Add the following code to **`SimpleStorage.test.ts`**:

```tsx
// @ts-ignore
import { ethers, waffle } from "hardhat";
import { Contract } from "ethers";
import { expect } from "chai";

describe("SimpleStorage", () => {
    let simpleStorage: Contract;

    beforeEach(async () => {
        const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
        simpleStorage = await SimpleStorage.deploy();
        await simpleStorage.deployed();
    });

    it("Initial stored data should be 0", async () => {
        const storedData = await simpleStorage.get();
        expect(storedData.toString()).to.equal("0");
    });

    it("Should set stored data to a new value", async () => {
        await simpleStorage.set(42);
        const storedData = await simpleStorage.get();
        expect(storedData.toString()).to.equal("42");
    });
});
```

## **Step 5: Compile the contract and run tests**

Compile the contract using HardHat:

```
npx hardhat compile
```

Run the tests:

```
npx hardhat test
```

If everything is set up correctly, you should see the tests passing.

## **Step 6: Deploy the contract on a local network**

Create a new directory **`scripts`** and a new TypeScript file **`deploy.ts`** inside it:

```
mkdir scripts
touch scripts/deploy.ts
```

Add the following code to **`deploy.ts`**:

```tsx
// @ts-ignore
import { ethers } from "hardhat";

async function main() {
    const SimpleStorageFactory = await ethers.getContractFactory("SimpleStorage");
    const simpleStorage = await SimpleStorageFactory.deploy();
    await simpleStorage.deployed();

    console.log("SimpleStorage deployed to:", simpleStorage.address);
}

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

Run the deployment script:

```
npx hardhat run scripts/deploy.ts
```

You should see the contract deployed to a local address.

## **Step 7: Deploy the contract on Avalanche Testnet using Ethers.js**

To deploy the contract on Avalanche Testnet, you need an account with test AVAX. You can get some from the **[Avalanche Faucet](https://faucet.avax-test.network/)**.

Add the following configuration to your **`hardhat.config.ts`**:

```tsx
import { HardhatUserConfig } from 'hardhat/config';
import '@nomiclabs/hardhat-waffle';
import '@nomiclabs/hardhat-ethers'; // Add this line
import 'dotenv/config';

const config: HardhatUserConfig = {
  solidity: '0.8.0',
  networks: {
    hardhat: {
      chainId: 31337,
    },
    fuji: {
      url: "https://api.avax-test.network/ext/bc/C/rpc",
      chainId: 43113,
      gasPrice: 225000000000,
      accounts: [process.env.PRIVATE_KEY || ""],
    },
  },
  mocha: {
    timeout: 20000,
  },
};

export default config;
```

Make sure to replace **`PRIVATE_KEY`** in the **`accounts`** array with your private key.

Run the deployment script on Avalanche Testnet:

```
npx hardhat run scripts/deploy.ts --network fuji
```

You should see the contract deployed to an Avalanche Testnet address.

Now you have successfully created a simple storage contract in Solidity with TypeScript, EVM, and AvalancheJS, along with HardHat local testing and deployments.

## **Step 8: Interacting with the contract using AvalancheJS**

To interact with the deployed contract, create a new TypeScript file **`interact.ts`** inside the **`scripts`** directory:

```
touch scripts/interact.ts
```

Add the following code to **`interact.ts`**:

```tsx
import {ethers} from 'ethers';
import * as dotenv from 'dotenv';

dotenv.config();

const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS as string|| '';
if (!CONTRACT_ADDRESS) {
    throw new Error('Please set your contract address in the interact.ts script');
}
// Your ABI Here
const ABI = [
    {
        "inputs": [],
        "name": "get",
        "outputs": [
            {
                "internalType": "uint256",
                "name": "",
                "type": "uint256"
            }
        ],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [
            {
                "internalType": "uint256",
                "name": "value",
                "type": "uint256"
            }
        ],
        "name": "set",
        "outputs": [],
        "stateMutability": "nonpayable",
        "type": "function"
    }
];

const FUJI_RPC_URL = "https://api.avax-test.network/ext/bc/C/rpc";
const PRIVATE_KEY = process.env.PRIVATE_KEY as string || '';

async function interact() {
    dotenv.config();
    if (!CONTRACT_ADDRESS) {
        throw new Error("Please set your contract address in the interact.ts script");
    }

    const provider = new ethers.providers.JsonRpcProvider(FUJI_RPC_URL);
    const wallet = new ethers.Wallet(PRIVATE_KEY, provider);
    console.log("Wallet address:", wallet.address);

    const simpleStorage = new ethers.Contract(CONTRACT_ADDRESS, ABI, wallet);
    console.log("Contract address:", CONTRACT_ADDRESS);
    console.log("Contract instance:", simpleStorage);

    // Read stored data
    const storedData = await simpleStorage.get();
    console.log("Stored data:", storedData.toString());

    // Set new data
    const tx = await simpleStorage.set(100);
    await tx.wait();

    // Read updated stored data
    const updatedStoredData = await simpleStorage.get();
    console.log("Updated stored data:", updatedStoredData.toString());
}

interact().then(r => console.log("Complete"));
```

Replace **`YOUR_CONTRACT_ADDRESS`** with the address of your deployed contract and add your contract's ABI.

Run the interaction script:

```
npx ts-node scripts/interact.ts
```

You should see the stored data, transaction receipt, and updated stored data in the console output.

```solidity
╰─ npx ts-node scripts/interact.ts

Wallet address: 0x2E3bE6ddaC75f8dA97d14009A540E917d881ea92
Contract address: 0x0697aBc8Dc960d53911f4A8BB8989826b78CaF61
Contract instance: Contract {
  interface: Interface {
    fragments: [ [FunctionFragment], [FunctionFragment] ],
    _abiCoder: AbiCoder { coerceFunc: null },
    functions: { 'get()': [FunctionFragment], 'set(uint256)': [FunctionFragment] },
    errors: {},
    events: {},
    structs: {},
    deploy: ConstructorFragment {
      name: null,
      type: 'constructor',
      inputs: [],
      payable: false,
      stateMutability: 'nonpayable',
      gas: null,
      _isFragment: true
    },
    _isInterface: true
  },
  provider: JsonRpcProvider {
    _isProvider: true,
    _events: [],
    _emitted: { block: -2 },
    disableCcipRead: false,
    formatter: Formatter { formats: [Object] },
    anyNetwork: false,
    _networkPromise: Promise { <pending> },
    _maxInternalBlockNumber: -1024,
    _lastBlockNumber: -2,
    _maxFilterBlockRange: 10,
    _pollingInterval: 4000,
    _fastQueryDate: 0,
    connection: { url: 'https://api.avax-test.network/ext/bc/C/rpc' },
    _nextId: 42
  },
  signer: Wallet {
    _isSigner: true,
    _signingKey: [Function (anonymous)],
    _mnemonic: [Function (anonymous)],
    address: '0x2E3bE6ddaC75f8dA97d14009A540E917d881ea92',
    provider: JsonRpcProvider {
      _isProvider: true,
      _events: [],
      _emitted: [Object],
      disableCcipRead: false,
      formatter: [Formatter],
      anyNetwork: false,
      _networkPromise: [Promise],
      _maxInternalBlockNumber: -1024,
      _lastBlockNumber: -2,
      _maxFilterBlockRange: 10,
      _pollingInterval: 4000,
      _fastQueryDate: 0,
      connection: [Object],
      _nextId: 42
    }
  },
  callStatic: {
    'get()': [Function (anonymous)],
    'set(uint256)': [Function (anonymous)],
    get: [Function (anonymous)],
    set: [Function (anonymous)]
  },
  estimateGas: {
    'get()': [Function (anonymous)],
    'set(uint256)': [Function (anonymous)],
    get: [Function (anonymous)],
    set: [Function (anonymous)]
  },
  functions: {
    'get()': [Function (anonymous)],
    'set(uint256)': [Function (anonymous)],
    get: [Function (anonymous)],
    set: [Function (anonymous)]
  },
  populateTransaction: {
    'get()': [Function (anonymous)],
    'set(uint256)': [Function (anonymous)],
    get: [Function (anonymous)],
    set: [Function (anonymous)]
  },
  filters: {},
  _runningEvents: {},
  _wrappedEmits: {},
  address: '0x0697aBc8Dc960d53911f4A8BB8989826b78CaF61',
  resolvedAddress: Promise { <pending> },
  'get()': [Function (anonymous)],
  'set(uint256)': [Function (anonymous)],
  get: [Function (anonymous)],
  set: [Function (anonymous)]
}
Stored data: 0
Updated stored data: 100
Complete
```

That's it! You have now successfully developed a simple storage contract in Solidity with TypeScript, EVM, HardHat, and EthersJS, including local testing, deployments on Avalanche Testnet, and interactions with the deployed contract.

## Source Code

[https://github.com/nawodyaishan/avalanche-simple-storage](https://github.com/nawodyaishan/avalanche-simple-storage)