
# Zero to Hero: Deploying Your First ERC-20 Token on Injective inEVM with Hardhat
> **TL;DR**: Hardhat + Injective inEVM = sub-second finality, EVM compatibility, and the tooling you already know. Ship faster.
**Why Hardhat + Injective inEVM?**
Injective's inEVM brings the entire Ethereum toolchain to a chain with **~1 second block times** and **instant finality**. No waiting for confirmations. No gas wars. Just deploy and iterate.
Hardhat gives you:
- Native TypeScript support
- Built-in testing framework
- Seamless contract verification
- Plugin ecosystem (ethers.js, OpenZeppelin upgrades, etc.)
**This is the stack for builders who ship.**
**Prerequisites**
```bash
node -v # v18.0.0 or higher
npm -v # v9.0.0 or higher
```
If you don't have Hardhat globally:
```bash
npm install --save-dev hardhat
```
**Project Setup**
```bash
mkdir injective-token && cd injective-token
npx hardhat init
```
Select: **Create a JavaScript project**
Install dependencies:
```bash
npm install --save-dev @nomicfoundation/hardhat-toolbox
npm install @openzeppelin/contracts
```
**Configuration: hardhat.config.js**
Replace your `hardhat.config.js` with:
```javascript
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
injectiveTestnet: {
url: "https://testnet.rpc.inevm.com",
chainId: 2424,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
gasPrice: 500000000, // 0.5 gwei
},
injectiveMainnet: {
url: "https://mainnet.rpc.inevm.com",
chainId: 2525,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
gasPrice: 500000000,
},
},
etherscan: {
apiKey: {
injectiveTestnet: "no-api-key-needed",
injectiveMainnet: "no-api-key-needed",
},
customChains: [
{
network: "injectiveTestnet",
chainId: 2424,
urls: {
apiURL: "https://testnet.explorer.inevm.com/api",
browserURL: "https://testnet.explorer.inevm.com",
},
},
{
network: "injectiveMainnet",
chainId: 2525,
urls: {
apiURL: "https://explorer.inevm.com/api",
browserURL: "https://explorer.inevm.com",
},
},
],
},
};
```
Create a `.env` file:
```bash
PRIVATE_KEY=your_private_key_here_without_0x_prefix
```
> ⚠️ **Never commit your `.env` file. Add it to `.gitignore`.**
**The Contract: contracts/InjectiveToken.sol**
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title InjectiveToken
* @dev Production-ready ERC-20 token for Injective inEVM
* @notice Includes burn functionality and EIP-2612 permit for gasless approvals
*/
contract InjectiveToken is ERC20, ERC20Burnable, ERC20Permit, Ownable {
uint8 private immutable _decimals;
/**
* @dev Constructor that mints initial supply to deployer
* @param name_ Token name
* @param symbol_ Token symbol
* @param decimals_ Token decimals (typically 18)
* @param initialSupply_ Initial token supply (in whole tokens, not wei)
*/
constructor(
string memory name_,
string memory symbol_,
uint8 decimals_,
uint256 initialSupply_
) ERC20(name_, symbol_) ERC20Permit(name_) Ownable(msg.sender) {
_decimals = decimals_;
_mint(msg.sender, initialSupply_ * 10 ** decimals_);
}
function decimals() public view virtual override returns (uint8) {
return _decimals;
}
/**
* @dev Allows owner to mint additional tokens
* @param to Recipient address
* @param amount Amount to mint (in wei)
*/
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
```
**Deployment Script: scripts/deploy.js**
```javascript
const hre = require("hardhat");
async function main() {
const [deployer] = await hre.ethers.getSigners();
console.log("Deploying contracts with account:", deployer.address);
console.log("Account balance:", (await deployer.provider.getBalance(deployer.address)).toString());
// Token parameters
const TOKEN_NAME = "Injective Builder Token";
const TOKEN_SYMBOL = "IBT";
const TOKEN_DECIMALS = 18;
const INITIAL_SUPPLY = 1_000_000; // 1 million tokens
console.log("\n📦 Deploying InjectiveToken...");
const InjectiveToken = await hre.ethers.getContractFactory("InjectiveToken");
const token = await InjectiveToken.deploy(
TOKEN_NAME,
TOKEN_SYMBOL,
TOKEN_DECIMALS,
INITIAL_SUPPLY
);
await token.waitForDeployment();
const tokenAddress = await token.getAddress();
console.log("\n✅ InjectiveToken deployed successfully!");
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("Contract Address:", tokenAddress);
console.log("Token Name:", TOKEN_NAME);
console.log("Token Symbol:", TOKEN_SYMBOL);
console.log("Total Supply:", INITIAL_SUPPLY.toLocaleString(), TOKEN_SYMBOL);
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("\n🔍 View on Explorer:");
console.log(`https://testnet.explorer.inevm.com/address/${tokenAddress}`);
// Return deployment info for verification
return {
address: tokenAddress,
constructorArgs: [TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, INITIAL_SUPPLY],
};
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
```
**Deploy to Injective Testnet**
**1. Get Testnet INJ**
Fund your wallet with testnet INJ from the [Injective Faucet](https://testnet.faucet.injective.network/).
**2. Compile**
```bash
npx hardhat compile
```
**3. Deploy**
```bash
npx hardhat run scripts/deploy.js --network injectiveTestnet
```
Expected output:
```
Deploying contracts with account: 0xYourAddress
Account balance: 1000000000000000000
📦 Deploying InjectiveToken...
✅ InjectiveToken deployed successfully!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Contract Address: 0xDeployedContractAddress
Token Name: Injective Builder Token
Token Symbol: IBT
Total Supply: 1,000,000 IBT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔍 View on Explorer:
https://testnet.explorer.inevm.com/address/0xDeployedContractAddress
```
**Contract Verification**
Verify your contract on the Injective Explorer:
```bash
npx hardhat verify --network injectiveTestnet \
DEPLOYED_CONTRACT_ADDRESS \
"Injective Builder Token" \
"IBT" \
18 \
1000000
```
Replace `DEPLOYED_CONTRACT_ADDRESS` with your actual contract address.
**Manual Verification (Alternative)**
If automated verification fails:
1. Go to [Injective Testnet Explorer](https://testnet.explorer.inevm.com)
2. Search for your contract address
3. Click "Verify & Publish"
4. Select:
- Compiler: `0.8.20`
- Optimization: `Yes (200 runs)`
- License: `MIT`
5. Flatten your contract:
```bash
npx hardhat flatten contracts/InjectiveToken.sol > Flattened.sol
```
6. Paste the flattened code and submit
**Testing Your Token**
Create `test/InjectiveToken.test.js`:
```javascript
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("InjectiveToken", function () {
let token;
let owner;
let addr1;
const NAME = "Injective Builder Token";
const SYMBOL = "IBT";
const DECIMALS = 18;
const INITIAL_SUPPLY = 1_000_000;
beforeEach(async function () {
[owner, addr1] = await ethers.getSigners();
const InjectiveToken = await ethers.getContractFactory("InjectiveToken");
token = await InjectiveToken.deploy(NAME, SYMBOL, DECIMALS, INITIAL_SUPPLY);
});
it("Should have correct name and symbol", async function () {
expect(await token.name()).to.equal(NAME);
expect(await token.symbol()).to.equal(SYMBOL);
});
it("Should mint initial supply to deployer", async function () {
const expectedSupply = ethers.parseUnits(INITIAL_SUPPLY.toString(), DECIMALS);
expect(await token.totalSupply()).to.equal(expectedSupply);
expect(await token.balanceOf(owner.address)).to.equal(expectedSupply);
});
it("Should allow owner to mint", async function () {
const mintAmount = ethers.parseUnits("1000", DECIMALS);
await token.mint(addr1.address, mintAmount);
expect(await token.balanceOf(addr1.address)).to.equal(mintAmount);
});
it("Should allow burning", async function () {
const burnAmount = ethers.parseUnits("1000", DECIMALS);
await token.burn(burnAmount);
const expectedBalance = ethers.parseUnits((INITIAL_SUPPLY - 1000).toString(), DECIMALS);
expect(await token.balanceOf(owner.address)).to.equal(expectedBalance);
});
});
```
Run tests:
```bash
npx hardhat test
```
**Quick Reference**
| Network | Chain ID | RPC URL | Explorer |
|---------|----------|---------|----------|
| Testnet | 2424 | https://testnet.rpc.inevm.com | https://testnet.explorer.inevm.com |
| Mainnet | 2525 | https://mainnet.rpc.inevm.com | https://explorer.inevm.com |
**What's Next?**
You just deployed on **the fastest EVM chain**. Here's what you can build next:
- 🔄 **DEX Integration**: List your token on Injective DEXs
- 🌉 **Cross-chain Bridges**: Connect to Ethereum, Cosmos, Solana
- 📊 **DeFi Protocols**: Build lending, staking, or yield protocols
- 🎮 **Gaming/NFTs**: Leverage instant finality for gaming assets
**Resources**
- [Injective inEVM Docs](https://docs.injective.network/develop/guides/inEVM/)
- [Hardhat Documentation](https://hardhat.org/docs)
- [OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts)
- [Injective Discord](https://discord.gg/injective)
**Built with 🥷 for the Injective ecosystem.**

# Zero to Hero: Deploying Your First ERC-20 Token on Injective inEVM with Hardhat
> **TL;DR**: Hardhat + Injective inEVM = sub-second finality, EVM compatibility, and the tooling you already know. Ship faster.
**Why Hardhat + Injective inEVM?**
Injective's inEVM brings the entire Ethereum toolchain to a chain with **~1 second block times** and **instant finality**. No waiting for confirmations. No gas wars. Just deploy and iterate.
Hardhat gives you:
- Native TypeScript support
- Built-in testing framework
- Seamless contract verification
- Plugin ecosystem (ethers.js, OpenZeppelin upgrades, etc.)
**This is the stack for builders who ship.**
**Prerequisites**
```bash
node -v # v18.0.0 or higher
npm -v # v9.0.0 or higher
```
If you don't have Hardhat globally:
```bash
npm install --save-dev hardhat
```
**Project Setup**
```bash
mkdir injective-token && cd injective-token
npx hardhat init
```
Select: **Create a JavaScript project**
Install dependencies:
```bash
npm install --save-dev @nomicfoundation/hardhat-toolbox
npm install @openzeppelin/contracts
```
**Configuration: hardhat.config.js**
Replace your `hardhat.config.js` with:
```javascript
require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config();
/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
solidity: {
version: "0.8.20",
settings: {
optimizer: {
enabled: true,
runs: 200,
},
},
},
networks: {
injectiveTestnet: {
url: "https://testnet.rpc.inevm.com",
chainId: 2424,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
gasPrice: 500000000, // 0.5 gwei
},
injectiveMainnet: {
url: "https://mainnet.rpc.inevm.com",
chainId: 2525,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
gasPrice: 500000000,
},
},
etherscan: {
apiKey: {
injectiveTestnet: "no-api-key-needed",
injectiveMainnet: "no-api-key-needed",
},
customChains: [
{
network: "injectiveTestnet",
chainId: 2424,
urls: {
apiURL: "https://testnet.explorer.inevm.com/api",
browserURL: "https://testnet.explorer.inevm.com",
},
},
{
network: "injectiveMainnet",
chainId: 2525,
urls: {
apiURL: "https://explorer.inevm.com/api",
browserURL: "https://explorer.inevm.com",
},
},
],
},
};
```
Create a `.env` file:
```bash
PRIVATE_KEY=your_private_key_here_without_0x_prefix
```
> ⚠️ **Never commit your `.env` file. Add it to `.gitignore`.**
**The Contract: contracts/InjectiveToken.sol**
```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
/**
* @title InjectiveToken
* @dev Production-ready ERC-20 token for Injective inEVM
* @notice Includes burn functionality and EIP-2612 permit for gasless approvals
*/
contract InjectiveToken is ERC20, ERC20Burnable, ERC20Permit, Ownable {
uint8 private immutable _decimals;
/**
* @dev Constructor that mints initial supply to deployer
* @param name_ Token name
* @param symbol_ Token symbol
* @param decimals_ Token decimals (typically 18)
* @param initialSupply_ Initial token supply (in whole tokens, not wei)
*/
constructor(
string memory name_,
string memory symbol_,
uint8 decimals_,
uint256 initialSupply_
) ERC20(name_, symbol_) ERC20Permit(name_) Ownable(msg.sender) {
_decimals = decimals_;
_mint(msg.sender, initialSupply_ * 10 ** decimals_);
}
function decimals() public view virtual override returns (uint8) {
return _decimals;
}
/**
* @dev Allows owner to mint additional tokens
* @param to Recipient address
* @param amount Amount to mint (in wei)
*/
function mint(address to, uint256 amount) public onlyOwner {
_mint(to, amount);
}
}
```
**Deployment Script: scripts/deploy.js**
```javascript
const hre = require("hardhat");
async function main() {
const [deployer] = await hre.ethers.getSigners();
console.log("Deploying contracts with account:", deployer.address);
console.log("Account balance:", (await deployer.provider.getBalance(deployer.address)).toString());
// Token parameters
const TOKEN_NAME = "Injective Builder Token";
const TOKEN_SYMBOL = "IBT";
const TOKEN_DECIMALS = 18;
const INITIAL_SUPPLY = 1_000_000; // 1 million tokens
console.log("\n📦 Deploying InjectiveToken...");
const InjectiveToken = await hre.ethers.getContractFactory("InjectiveToken");
const token = await InjectiveToken.deploy(
TOKEN_NAME,
TOKEN_SYMBOL,
TOKEN_DECIMALS,
INITIAL_SUPPLY
);
await token.waitForDeployment();
const tokenAddress = await token.getAddress();
console.log("\n✅ InjectiveToken deployed successfully!");
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("Contract Address:", tokenAddress);
console.log("Token Name:", TOKEN_NAME);
console.log("Token Symbol:", TOKEN_SYMBOL);
console.log("Total Supply:", INITIAL_SUPPLY.toLocaleString(), TOKEN_SYMBOL);
console.log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
console.log("\n🔍 View on Explorer:");
console.log(`https://testnet.explorer.inevm.com/address/${tokenAddress}`);
// Return deployment info for verification
return {
address: tokenAddress,
constructorArgs: [TOKEN_NAME, TOKEN_SYMBOL, TOKEN_DECIMALS, INITIAL_SUPPLY],
};
}
main()
.then(() => process.exit(0))
.catch((error) => {
console.error(error);
process.exit(1);
});
```
**Deploy to Injective Testnet**
**1. Get Testnet INJ**
Fund your wallet with testnet INJ from the [Injective Faucet](https://testnet.faucet.injective.network/).
**2. Compile**
```bash
npx hardhat compile
```
**3. Deploy**
```bash
npx hardhat run scripts/deploy.js --network injectiveTestnet
```
Expected output:
```
Deploying contracts with account: 0xYourAddress
Account balance: 1000000000000000000
📦 Deploying InjectiveToken...
✅ InjectiveToken deployed successfully!
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Contract Address: 0xDeployedContractAddress
Token Name: Injective Builder Token
Token Symbol: IBT
Total Supply: 1,000,000 IBT
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🔍 View on Explorer:
https://testnet.explorer.inevm.com/address/0xDeployedContractAddress
```
**Contract Verification**
Verify your contract on the Injective Explorer:
```bash
npx hardhat verify --network injectiveTestnet \
DEPLOYED_CONTRACT_ADDRESS \
"Injective Builder Token" \
"IBT" \
18 \
1000000
```
Replace `DEPLOYED_CONTRACT_ADDRESS` with your actual contract address.
**Manual Verification (Alternative)**
If automated verification fails:
1. Go to [Injective Testnet Explorer](https://testnet.explorer.inevm.com)
2. Search for your contract address
3. Click "Verify & Publish"
4. Select:
- Compiler: `0.8.20`
- Optimization: `Yes (200 runs)`
- License: `MIT`
5. Flatten your contract:
```bash
npx hardhat flatten contracts/InjectiveToken.sol > Flattened.sol
```
6. Paste the flattened code and submit
**Testing Your Token**
Create `test/InjectiveToken.test.js`:
```javascript
const { expect } = require("chai");
const { ethers } = require("hardhat");
describe("InjectiveToken", function () {
let token;
let owner;
let addr1;
const NAME = "Injective Builder Token";
const SYMBOL = "IBT";
const DECIMALS = 18;
const INITIAL_SUPPLY = 1_000_000;
beforeEach(async function () {
[owner, addr1] = await ethers.getSigners();
const InjectiveToken = await ethers.getContractFactory("InjectiveToken");
token = await InjectiveToken.deploy(NAME, SYMBOL, DECIMALS, INITIAL_SUPPLY);
});
it("Should have correct name and symbol", async function () {
expect(await token.name()).to.equal(NAME);
expect(await token.symbol()).to.equal(SYMBOL);
});
it("Should mint initial supply to deployer", async function () {
const expectedSupply = ethers.parseUnits(INITIAL_SUPPLY.toString(), DECIMALS);
expect(await token.totalSupply()).to.equal(expectedSupply);
expect(await token.balanceOf(owner.address)).to.equal(expectedSupply);
});
it("Should allow owner to mint", async function () {
const mintAmount = ethers.parseUnits("1000", DECIMALS);
await token.mint(addr1.address, mintAmount);
expect(await token.balanceOf(addr1.address)).to.equal(mintAmount);
});
it("Should allow burning", async function () {
const burnAmount = ethers.parseUnits("1000", DECIMALS);
await token.burn(burnAmount);
const expectedBalance = ethers.parseUnits((INITIAL_SUPPLY - 1000).toString(), DECIMALS);
expect(await token.balanceOf(owner.address)).to.equal(expectedBalance);
});
});
```
Run tests:
```bash
npx hardhat test
```
**Quick Reference**
| Network | Chain ID | RPC URL | Explorer |
|---------|----------|---------|----------|
| Testnet | 2424 | https://testnet.rpc.inevm.com | https://testnet.explorer.inevm.com |
| Mainnet | 2525 | https://mainnet.rpc.inevm.com | https://explorer.inevm.com |
**What's Next?**
You just deployed on **the fastest EVM chain**. Here's what you can build next:
- 🔄 **DEX Integration**: List your token on Injective DEXs
- 🌉 **Cross-chain Bridges**: Connect to Ethereum, Cosmos, Solana
- 📊 **DeFi Protocols**: Build lending, staking, or yield protocols
- 🎮 **Gaming/NFTs**: Leverage instant finality for gaming assets
**Resources**
- [Injective inEVM Docs](https://docs.injective.network/develop/guides/inEVM/)
- [Hardhat Documentation](https://hardhat.org/docs)
- [OpenZeppelin Contracts](https://docs.openzeppelin.com/contracts)
- [Injective Discord](https://discord.gg/injective)
**Built with 🥷 for the Injective ecosystem.**
<100 subscribers
<100 subscribers
Share Dialog
Share Dialog
No comments yet