If you’ve shipped a contract to Ethereum, Polygon, or Arbitrum, you already know 90% of what you need to deploy on Novus. This post walks you through the rest.
By the end, you’ll have a live ERC-20 token contract running on Novus testnet, deployed from your local machine in under an hour.
Who this is for
You write Solidity. You’ve used Hardhat or Foundry before. You’re curious about deploying to a sovereign Layer 1 that runs on Cosmos SDK but speaks fluent EVM. The goal here isn’t to teach Solidity, it’s to show you exactly where Novus behaves the same as the chains you already know, and the few places it doesn’t.
What you’ll build
A standard ERC-20 token. Nothing exotic. The point is to see how Novus behaves at every step so the next contract you ship is one you actually care about.
Prerequisites
Before starting:
- Node.js 18 or later
- A wallet (MetaMask works) with testnet FWRD from the Novus faucet
- Hardhat installed in your project
- A text editor
If you’ve shipped to any EVM chain, you already have all of this.
Step 1: Configure Hardhat for Novus
Create a fresh project if you don’t have one:
bash
mkdir my-novus-token && cd my-novus-token
npm init -y
npm install --save-dev hardhat @nomicfoundation/hardhat-toolbox dotenv
npx hardhat init
Pick “Create a JavaScript project” when prompted.
Now open hardhat.config.js and add Novus testnet as a network:
javascript
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
module.exports = {
solidity: "0.8.20",
networks: {
novusTestnet: {
url: "https://rpc-testnet.novusnetworks.io",
chainId: 9001,
accounts: [process.env.PRIVATE_KEY]
}
}
};
A few things worth calling out:
- The
chainIdmatches Novus testnet exactly. Hardhat uses this to confirm you’re talking to the right chain before it broadcasts anything. - The RPC URL is the public testnet endpoint. For mainnet I’d swap this for the mainnet RPC, but I always recommend doing the first deploy on testnet.
- Never commit your private key. The
.envpattern keeps it out of git, and you’ll want a.gitignoreentry for.envas well.
Create a .env file in your project root:
PRIVATE_KEY=your_wallet_private_key_here
Step 2: Write the contract
In the contracts/ folder, create NovusToken.sol:
solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
contract NovusToken is ERC20 {
constructor(uint256 initialSupply) ERC20("Novus Sample Token", "NST") {
_mint(msg.sender, initialSupply);
}
}
Install OpenZeppelin if you haven’t:
bash
npm install @openzeppelin/contracts
This is the simplest legitimate ERC-20 you can write. It mints the initial supply to whoever deploys it. In a real project you’d want access control, mint/burn rules, and probably a permit function, but for a first deploy this is enough.
Step 3: Write the deploy script
In the scripts/ folder, create deploy.js:
javascript
const hre = require("hardhat");
async function main() {
const initialSupply = hre.ethers.parseUnits("1000000", 18);
const NovusToken = await hre.ethers.getContractFactory("NovusToken");
const token = await NovusToken.deploy(initialSupply);
await token.waitForDeployment();
console.log("NovusToken deployed to:", await token.getAddress());
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
This mints one million tokens (with 18 decimals to match the EVM convention) to the deployer wallet.
Step 4: Deploy
Compile first to catch any errors:
bash
npx hardhat compile
Then deploy to Novus testnet:
bash
npx hardhat run scripts/deploy.js --network novusTestnet
If your wallet has testnet FWRD and your config is right, you’ll see something like:
NovusToken deployed to: 0x742d35Cc6634C0532925a3b844Bc9e7595f1F1b3
Save that address. You’ll need it.
Step 5: Verify the deployment
The fastest way to confirm your contract is live is to check it on the testnet explorer:
https://testnet-explorer.novusnetworks.io/address/YOUR_CONTRACT_ADDRESS
You should see the contract code, the deployment transaction, and the initial supply minted to your wallet. If you imported your wallet into MetaMask and added the Novus testnet network, you’ll also see the tokens show up in your balance once you import the token using the contract address.
Step 6: Interact with your contract
Let’s confirm the contract actually works. Create scripts/interact.js:
javascript
const hre = require("hardhat");
async function main() {
const contractAddress = "YOUR_CONTRACT_ADDRESS_HERE";
const NovusToken = await hre.ethers.getContractFactory("NovusToken");
const token = NovusToken.attach(contractAddress);
const [signer] = await hre.ethers.getSigners();
const balance = await token.balanceOf(signer.address);
const symbol = await token.symbol();
const totalSupply = await token.totalSupply();
console.log(`Symbol: ${symbol}`);
console.log(`Your balance: ${hre.ethers.formatUnits(balance, 18)} ${symbol}`);
console.log(`Total supply: ${hre.ethers.formatUnits(totalSupply, 18)} ${symbol}`);
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Run it:
bash
npx hardhat run scripts/interact.js --network novusTestnet
You should see your token balance match the initial supply. That’s the round trip: contract deployed, state readable, ready to build on.
What’s different about Novus
Now that the contract is live, I want to flag the things that genuinely differ from a typical Ethereum L2 deployment, because they’ll matter for your next project:
Gas is paid in FWRD, not ETH. Same EVM, different fee token. Your wallet handles this transparently once the network is configured, but if you’re scripting fee estimates or building a gasless transaction layer, this is the line you’ll want to adjust.
Block times are faster than Ethereum mainnet. Expect confirmations in roughly 2 to 3 seconds rather than 12. If you have any code that polls for receipts with long timeouts, you can tighten those.
The chain runs on Cosmos SDK underneath. You don’t see this from the EVM layer (your contract has no idea it’s not on Ethereum), but it means Novus also exposes a Cosmos REST and gRPC API alongside the JSON-RPC. If you ever need to query validator sets, governance proposals, or staking positions directly, those Cosmos endpoints are available without contract calls.
Application-specific L2s are a first-class option. If your dApp grows to the point where you want your own execution environment (custom fee tokens, isolated state, tailored gas economics), Novus supports launching a purpose-built L2 underneath the main chain. That’s a much bigger topic for another post, but I want you to know the option exists.
Where to go next
You’ve shipped your first contract. The fastest ways to keep learning:
- Add access control to the token using OpenZeppelin’s
Ownable. Try restrictingmintto the owner. - Write a Hardhat test for the contract. Local Hardhat network behaves identically to Novus for unit tests.
- Build a tiny frontend that connects with MetaMask and reads the balance. Any standard ethers.js or wagmi setup works without changes.
If you hit anything that doesn’t behave like a vanilla EVM chain, that’s a great topic for the next post. Send it to the community channel and I’ll write it up.
Happy building.