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:
- Node.js (v14+)
- npm (v7+)
- HardHat (v2+)
- 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:
{
"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:
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
:
// 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 theset()
function and retrieved using theget()
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.
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
.
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:
-
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 of0
. -
Set the stored data: The user calls the
set()
function, providing auint256
value as input. The function updates thestoredData
state variable with the provided value. -
Get the stored data: The user calls the
get()
function to retrieve the current value of thestoredData
state variable. The function returns theuint256
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
:
// @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
:
// @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.
Add the following configuration to your hardhat.config.ts
:
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
:
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.
╰─ 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.
Top comments (0)