Tools & Softwares:
- VS Code / Cursor
- NodeJS (22.10.0 or a later LTS version)
Accounts:
Prerequisites
node --version
npx --version
Overview
.
├── artifacts
│ ├── artifacts.d.ts
│ ├── build-info
│ │ ├── solc-0_8_28-5d5e77a08f43f8366c520176a292b83d65132357.json
│ │ └── solc-0_8_28-5d5e77a08f43f8366c520176a292b83d65132357.output.json
│ └── contracts
│ └── Counter.sol
│ ├── artifacts.d.ts
│ └── Counter.json
├── cache
│ ├── build-info
│ ├── compile-cache.json
│ └── test-artifacts
│ ├── build-info
│ │ ├── solc-0_8_28-58a47ee13fa4eb204cf4c42c730b3f2707de3d94.json
│ │ └── solc-0_8_28-58a47ee13fa4eb204cf4c42c730b3f2707de3d94.output.json
│ └── contracts
│ └── Counter.t.sol
│ └── CounterTest.json
├── contracts
│ ├── Counter.sol
│ └── Counter.t.sol
├── coverage
│ ├── data
│ │ └── solidity
│ │ └── a23a0f3b-7ab8-4f87-8e19-8279db5ae7a4.json
│ └── lcov.info
├── hardhat.config.ts
├── ignition
│ ├── deployments
│ │ ├── chain-1
│ │ │ ├── artifacts
│ │ │ │ └── CounterModule#Counter.json
│ │ │ ├── build-info
│ │ │ │ └── solc-0_8_28-5d5e77a08f43f8366c520176a292b83d65132357.json
│ │ │ └── journal.jsonl
│ │ ├── chain-11155111
│ │ │ ├── artifacts
│ │ │ │ └── CounterModule#Counter.json
│ │ │ ├── build-info
│ │ │ │ └── solc-0_8_28-5d5e77a08f43f8366c520176a292b83d65132357.json
│ │ │ ├── deployed_addresses.json
│ │ │ └── journal.jsonl
│ │ └── chain-31337
│ │ ├── artifacts
│ │ │ └── CounterModule#Counter.json
│ │ ├── build-info
│ │ │ └── solc-0_8_28-5d5e77a08f43f8366c520176a292b83d65132357.json
│ │ ├── deployed_addresses.json
│ │ └── journal.jsonl
│ └── modules
│ └── Counter.ts
├── package-lock.json
├── package.json
├── README.md
├── test
│ └── Counter.ts
└── tsconfig.json
Project creation
mkdir hardhat-tutorial
cd hardhat-tutorial
npx hardhat --init
Writing a smart contract
contracts/Counter.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;
contract Counter {
uint public x;
event Increment(uint by);
function inc() public {
x += 1;
emit Increment(1);
}
function incBy(uint by) public {
require(by > 0, "incBy: increment should be positive");
x += by;
emit Increment(by);
}
}
// Random number to make this contract file unique: 185640209
♾️ Compile the project
npx hardhat build
Test writing
contracts/Couter.t.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.28;
import { Counter } from "./Counter.sol";
contract CounterTest {
Counter counter;
function setUp() public {
counter = new Counter();
}
function test_InitialValueIsZero() public view {
require(counter.x() == 0, "x should start at 0");
}
function test_IncIncreasesByOne() public {
counter.inc();
require(counter.x() == 1, "inc should increase x by 1");
}
function test_IncByIncreasesByGivenAmount() public {
counter.incBy(3);
require(counter.x() == 3, "incBy should increase x by the given amount");
}
}
🧪 Testing
npx hardhat test
npx hardhat test solidity
Use Hardhat
👷 Install
npm add --save-dev @nomicfoundation/hardhat-toolbox-viem @nomicfoundation/hardhat-ignition viem
➕ Add to hardhat config
hardhat.config.ts
import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";
import { defineConfig } from "hardhat/config";
export default defineConfig({
plugins: [hardhatToolboxViemPlugin],
solidity: {
version: "0.8.28",
},
});
Writing a test
test/Counter.ts
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { network } from "hardhat";
describe("Counter", async function () {
const { viem } = await network.connect();
const publicClient = await viem.getPublicClient();
it("The sum of the Increment events should match the current value", async function () {
const counter = await viem.deployContract("Counter");
const deploymentBlockNumber = await publicClient.getBlockNumber();
// run a series of increments
for (let i = 1n; i <= 10n; i++) {
await counter.write.incBy([i]);
}
const events = await publicClient.getContractEvents({
address: counter.address,
abi: counter.abi,
eventName: "Increment",
fromBlock: deploymentBlockNumber,
strict: true,
});
// check that the aggregated events match the current value
let total = 0n;
for (const event of events) {
total += event.args.by;
}
assert.equal(total, await counter.read.x());
});
});
🏃♂️ Run the test
npx hardhat test nodejs
Deploy locally
ignition/modules/Counter.ts
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
export default buildModule("CounterModule", (m) => {
const counter = m.contract("Counter");
m.call(counter, "incBy", [5n]);
return { counter };
});
🧪 Try
npx hardhat ignition deploy ignition/modules/Counter.ts
🚀 Run the local network
npx hardhat node
🚀 Deploy Contract locally
npx hardhat ignition deploy ignition/modules/Counter.ts --network localhost
Deploy live
Prerequisites
Create Alchemy Account
Create Meta Mask Account
Install dotenvnpm install dotenv
Code
hardhat.config.ts
import hardhatToolboxViemPlugin from "@nomicfoundation/hardhat-toolbox-viem";
import { defineConfig } from "hardhat/config";
import dotenv from "dotenv";
dotenv.config();
export default defineConfig({
plugins: [hardhatToolboxViemPlugin],
solidity: {
version: "0.8.28",
},
networks: {
sepolia: {
type: "http",
url: process.env.SEPOLIA_RPC_URL || "",
accounts: process.env.SEPOLIA_PRIVATE_KEY ? [process.env.SEPOLIA_PRIVATE_KEY] : [],
},
},
});
🏃♂️ Run
npx hardhat ignition deploy ignition/modules/Counter.ts --network sepolia



On Oasis Network you can deploy Solidity smart contracts the same way . Oasis has an EVM‑compatible ParaTime (Emerald/Sapphire) where you compile & deploy via Remix/Hardhat/Foundry to the network (set RPC & chain ID in MetaMask or your scripts, get test OETH/ROSE for gas, then deploy).
Oasis Sapphire adds confidential execution if you need privacy, but the Solidity deployment flow stays familiar to Ethereum devs.