This tutorial will guide you through deploying your first smart contract on OPN Chain. We'll create a simple storage contract, deploy it, and interact with it using various tools.
What you'll learn:
Writing a basic smart contract
Compiling with Solidity
Deploying to OPN testnet
Interacting with your contract
Verifying your contract
Time required: 20 minutes
Prerequisites
Before starting, ensure you have:
[x] MetaMask installed and connected to OPN testnet
[x] Test OPN tokens from the faucet
[x] Node.js 16+ installed
[x] Basic command line knowledge
Step 1: Set Up Your Project
Create Project Directory
Install Dependencies
Initialize Hardhat
Select:
"Create a JavaScript project"
Press Enter for all prompts
Type 'y' to install sample project dependencies
Configure Hardhat for OPN
Create/update hardhat.config.js:
Set Up Environment Variables
Create .env file:
⚠️ Security Note: Never commit your .env file! Add it to .gitignore:
Step 2: Write Your Smart Contract
Create the Contract
Delete the sample contracts and create contracts/SimpleStorage.sol:
Understanding the Contract
This contract:
Stores a single number (storedValue)
Allows anyone to update the value
Emits events when the value changes
Provides functions to read and modify the value
Step 3: Compile the Contract
Compile
Expected output:
The compiled artifacts are saved in artifacts/ directory.
Check Compilation
You should see SimpleStorage.json containing the ABI and bytecode.
Step 4: Write Tests
Create Test File
Create test/SimpleStorage.test.js:
Run Tests
Expected output:
Step 5: Deploy to OPN Testnet
Create Deployment Script
Create scripts/deploy.js:
Deploy
Expected output:
🎉 Congratulations! You've deployed your first smart contract on OPN Chain!
Step 6: Interact with Your Contract
Create Interaction Script
Create scripts/interact.js:
Run Interaction
Step 7: Interact via Web Interface
Create Simple Web Interface
Create interface.html:
Update Contract Address
Open deployment-info.json
Copy the contract address
Replace YOUR_CONTRACT_ADDRESS_HERE in the HTML file
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
/**
* @title SimpleStorage
* @dev Store & retrieve value in a variable
* @custom:dev-run-script ./scripts/deploy.js
*/
contract SimpleStorage {
uint256 private storedValue;
// Event emitted when value changes
event ValueChanged(uint256 oldValue, uint256 newValue, address indexed changer);
// Constructor to set initial value
constructor(uint256 _initialValue) {
storedValue = _initialValue;
emit ValueChanged(0, _initialValue, msg.sender);
}
/**
* @dev Store a new value
* @param _value value to store
*/
function set(uint256 _value) public {
uint256 oldValue = storedValue;
storedValue = _value;
emit ValueChanged(oldValue, _value, msg.sender);
}
/**
* @dev Return the stored value
* @return value of 'storedValue'
*/
function get() public view returns (uint256) {
return storedValue;
}
/**
* @dev Increment stored value by one
*/
function increment() public {
uint256 oldValue = storedValue;
storedValue = storedValue + 1;
emit ValueChanged(oldValue, storedValue, msg.sender);
}
/**
* @dev Check if value equals a number
* @param _value value to compare
* @return true if values match
*/
function equals(uint256 _value) public view returns (bool) {
return storedValue == _value;
}
}
npx hardhat compile
Compiled 1 Solidity file successfully
ls artifacts/contracts/SimpleStorage.sol/
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("SimpleStorage", function () {
let simpleStorage;
let owner;
let addr1;
beforeEach(async function () {
// Get signers
[owner, addr1] = await ethers.getSigners();
// Deploy contract
const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
simpleStorage = await SimpleStorage.deploy(42);
await simpleStorage.deployed();
});
describe("Deployment", function () {
it("Should set the initial value", async function () {
expect(await simpleStorage.get()).to.equal(42);
});
it("Should emit ValueChanged event on deployment", async function () {
const SimpleStorage = await ethers.getContractFactory("SimpleStorage");
const contract = await SimpleStorage.deploy(100);
await expect(contract.deployTransaction)
.to.emit(contract, "ValueChanged")
.withArgs(0, 100, owner.address);
});
});
describe("Set function", function () {
it("Should update stored value", async function () {
await simpleStorage.set(123);
expect(await simpleStorage.get()).to.equal(123);
});
it("Should emit ValueChanged event", async function () {
await expect(simpleStorage.set(456))
.to.emit(simpleStorage, "ValueChanged")
.withArgs(42, 456, owner.address);
});
it("Should allow any address to set value", async function () {
await simpleStorage.connect(addr1).set(789);
expect(await simpleStorage.get()).to.equal(789);
});
});
describe("Increment function", function () {
it("Should increment value by 1", async function () {
await simpleStorage.increment();
expect(await simpleStorage.get()).to.equal(43);
});
it("Should handle multiple increments", async function () {
await simpleStorage.increment();
await simpleStorage.increment();
await simpleStorage.increment();
expect(await simpleStorage.get()).to.equal(45);
});
});
describe("Equals function", function () {
it("Should return true for matching value", async function () {
expect(await simpleStorage.equals(42)).to.equal(true);
});
it("Should return false for non-matching value", async function () {
expect(await simpleStorage.equals(99)).to.equal(false);
});
});
describe("Gas usage", function () {
it("Should use reasonable gas for operations", async function () {
const setTx = await simpleStorage.set(999);
const setReceipt = await setTx.wait();
console.log("Set gas used:", setReceipt.gasUsed.toString());
const incrementTx = await simpleStorage.increment();
const incrementReceipt = await incrementTx.wait();
console.log("Increment gas used:", incrementReceipt.gasUsed.toString());
});
});
});
npx hardhat test
SimpleStorage
Deployment
✓ Should set the initial value
✓ Should emit ValueChanged event on deployment
Set function
✓ Should update stored value
✓ Should emit ValueChanged event
✓ Should allow any address to set value
Increment function
✓ Should increment value by 1
✓ Should handle multiple increments
Equals function
✓ Should return true for matching value
✓ Should return false for non-matching value
Gas usage
Set gas used: 43954
Increment gas used: 41842
✓ Should use reasonable gas for operations
10 passing (2s)
// Gas-optimized version
contract OptimizedStorage {
uint256 private value;
// Pack multiple updates in one transaction
function batchUpdate(uint256[] calldata values) external {
uint256 finalValue = value;
for (uint i = 0; i < values.length; i++) {
finalValue = values[i];
}
value = finalValue;
}
}
contract EventOptimized {
// Index important parameters for filtering
event Transfer(
address indexed from,
address indexed to,
uint256 value
);
// Use events for off-chain data
event Metadata(string dataHash);
}
// Using OpenZeppelin upgradeable contracts
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
contract SimpleStorageV2 is Initializable {
uint256 private value;
function initialize(uint256 _value) public initializer {
value = _value;
}
}