This guide will walk you through the steps needed to port your Ethereum dApp to the Rootstock (RSK) blockchain using Hardhat. Rootstock is a smart contract platform that leverages the security of the Bitcoin network, allowing you to deploy and manage your Ethereum dApps on a Bitcoin sidechain. By following this guide, you’ll be able to migrate your existing Ethereum smart contracts and tests to Rootstock, configure the necessary network settings, and deploy your contracts on the Rootstock mainnet or testnet.
Before you begin, ensure you have the following prerequisites installed and configured:
- Node.js (version 14 or higher)
- npm (version 6 or higher)
- Hardhat (globally installed)
- A code editor (e.g., Visual Studio Code)
- Metamask Wallet (for managing RSK accounts and obtaining private keys)
Additionally, you should have a basic understanding of Smart Contracts and how to use Hardhat for development and deployment.
By the end of this guide, you will have:
1. Initialized a Hardhat project configured for Rootstock.
2. Installed and configured necessary dependencies.
3. Copied and adjusted your Ethereum smart contract code for Rootstock.
4. Compiled and tested your smart contracts.
5. Deployed your smart contracts to the Rootstock testnet.
6. Verified the successful deployment of your contracts on the Rootstock block explorer.
Install hardhat globally
npm i -g hardhat
mkdir rsk-hardhat-example
cd rsk-hardhat-example
npx hardhat init
888 888 888 888 888
888 888 888 888 888
888 888 888 888 888
8888888888 8888b. 888d888 .d88888 88888b. 8888b. 888888
888 888 "88b 888P" d88" 888 888 "88b "88b 888
888 888 .d888888 888 888 888 888 888 .d888888 888
888 888 888 888 888 Y88b 888 888 888 888 888 Y88b.
888 888 "Y888888 888 "Y88888 888 888 "Y888888 "Y888
👷 Welcome to Hardhat v2.22.5 👷
? What do you want to do? …
Create a JavaScript project
❯ Create a TypeScript project
Create a TypeScript project (with Viem)
Create an empty hardhat.config.js
Quit
✔ What do you want to do? · Create a TypeScript project
? Hardhat project root: › /path/to/your/project/rsk-hardhat-example
? Hardhat project root: › /path/to/your/project/rsk-hardhat-example
? Do you want to add a .gitignore? (Y/n) › y
✔ Do you want to add a .gitignore? (Y/n) · y
? Do you want to install this sample project's dependencies with npm (hardhat @nomicfoundation/hardhat-toolbox)? (Y/n) › y
For this example we'll be using Visual Studio Code but feel free to use the one you prefer
code .
By now your hardhat project should have 4 main artifacts besides the basic Node configuration:
contracts/
ignition/modules/
test/
hardhat.config.js
Note
The version of Hardhat we'll be using in this example is v2.22.5. For this version, the default tool for managing deployments is Hardhat Ignition.
npm install --save-dev @nomicfoundation/hardhat-ignition-ethers typescript
import "@nomicfoundation/hardhat-ignition-ethers";
By now your hardhat.config.ts
should look something like this
import { HardhatUserConfig } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "@nomicfoundation/hardhat-ignition-ethers";
const config: HardhatUserConfig = {
solidity: "0.8.24",
};
export default config;
In order to configure the RSK networks we'll need: a RPC url for both mainnet and testnet and a Private Key of the account that will deploy the contracts. To get the the RPCs go to the RPC API dashboard from Rootstock Labs, create an account if you don't have one and get an API key for RSK testnet and another for RSK mainnet.
The mainnet RPC url should look similar to this:
https://rpc.mainnet.rootstock.io/<API-KEY>
The testnet RPC url like this
https://rpc.testnet.rootstock.io/<API-KEY>
And if you don't know how to get the Private Key of your wallet, here's a tutorial on how to do it on Metamask. Also, if you haven't added RSK mainnet or testnet to your Metamask Wallet, you can do it by clicking the Add Rootstock or Add Rootstock Testnet buttons in the footer of mainnet explorer or testnet explorer.
For storing the RPC urls securely you can use a .env file or the hardhat configuration variables. For this example we'll be using the second one. To store the mainnet RPC url in a hardhat configuration variable, type this in the terminal of your project's root:
npx hardhat vars set MAINNET_RPC_URL
And enter the value after pressing enter. Repeat this step with the other two:
npx hardhat vars set TESTNET_RPC_URL
✔ Enter value: ********************************
npx hardhat vars set PRIVATE_KEY
✔ Enter value: *************************************
Now use all the things we've set and stored so your hardhat.config.ts
looks like this:
import { HardhatUserConfig, vars } from "hardhat/config";
import "@nomicfoundation/hardhat-toolbox";
import "@nomicfoundation/hardhat-ignition-ethers";
const MAINNET_RPC_URL = vars.get("MAINNET_RPC_URL");
const TESTNET_RPC_URL = vars.get("TESTNET_RPC_URL");
const PRIVATE_KEY = vars.get("PRIVATE_KEY");
const config: HardhatUserConfig = {
solidity: "0.8.24",
networks: {
rskMainnet: {
url: MAINNET_RPC_URL,
chainId: 30,
gasPrice: 60000000,
accounts: [PRIVATE_KEY],
},
rskTestnet: {
url: TESTNET_RPC_URL,
chainId: 31,
gasPrice: 60000000,
accounts: [PRIVATE_KEY],
},
},
};
export default config;
Congratulations! 🎉 You’ve successfully completed all the steps and finished the configuration. Now let's bring the contracts from Ethereum.
We'll copy the contracts from Ethereum and it's tests to our RSK hardhat project. The contract is the following and will be inside contracts
folder so the route would be contracts/SimpleStorage.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
contract SimpleStorage {
uint256 public favoriteNumber;
function store(uint256 _favoriteNumber) public {
favoriteNumber = _favoriteNumber;
}
}
The same with the test. The file will be named SimpleStorage.ts
inside the test
folder so the result is test/SimpleStorage.ts
import { loadFixture } from "@nomicfoundation/hardhat-toolbox/network-helpers";
import { expect } from "chai";
import hre from "hardhat";
describe("SimpleStorage", function () {
async function deploySimpleStorageFixture() {
const [owner] = await hre.ethers.getSigners();
const SimpleStorage = await hre.ethers.getContractFactory("SimpleStorage");
const simpleStorage = await SimpleStorage.deploy();
return { simpleStorage, owner };
}
describe("Deployment", function () {
it("Should deploy and initialize favoriteNumber to 0", async function () {
const { simpleStorage } = await loadFixture(deploySimpleStorageFixture);
expect(await simpleStorage.favoriteNumber()).to.equal(0);
});
});
describe("Store", function () {
it("Should store the value 42 and retrieve it", async function () {
const { simpleStorage } = await loadFixture(deploySimpleStorageFixture);
const storeTx = await simpleStorage.store(42);
await storeTx.wait();
expect(await simpleStorage.favoriteNumber()).to.equal(42);
});
it("Should store a different value and retrieve it", async function () {
const { simpleStorage } = await loadFixture(deploySimpleStorageFixture);
const storeTx = await simpleStorage.store(123);
await storeTx.wait();
expect(await simpleStorage.favoriteNumber()).to.equal(123);
});
});
});
In order to compile your contract, type this in your terminal:
npx hardhat compile
And you should get something like this:
Generating typings for: 1 artifacts in dir: typechain-types for target: ethers-v6
Successfully generated 6 typings!
Compiled 1 Solidity file successfully (evm target: paris).
For testing out your contract(s) write this command in the console:
npx hardhat test
And you should get something similar to this:
SimpleStorage
Deployment
✔ Should deploy and initialize favoriteNumber to 0
Store
✔ Should store the value 42 and retrieve it
✔ Should store a different value and retrieve it
3 passing (286ms)
To deploy on mainnet or testnet, you need to have enough balance of the native token. In this example we'll be deploying the contract on testnet.
Note
To get testnet tokens for deploying your contract go to the faucet.
Create a file named SimpleStorage.ts
inside the folder ignition/modules
. Then paste this in that file:
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";
const SimpleStorageModule = buildModule("SimpleStorageModule", (m) => {
const simpleStorage = m.contract("SimpleStorage");
return { simpleStorage };
});
export default SimpleStorageModule;
This Typescript script uses hardhat ignition to deploy the SimpleStorage
contract in a declarative way. After you paste that code and save the changes, type this in your terminal to deploy the contract:
npx hardhat ignition deploy ignition/modules/SimpleStorage.ts --network rskTestnet
And you should get something like this:
✔ Confirm deploy to network rskTestnet (31)? … yes
Hardhat Ignition 🚀
Resuming existing deployment from ./ignition/deployments/chain-31
Deploying [ SimpleStorageModule ]
Batch #1
Executed SimpleStorageModule#SimpleStorage
[ SimpleStorageModule ] successfully deployed 🚀
Deployed Addresses
SimpleStorageModule#SimpleStorage - 0x3570c42943697702bA582B1ae3093A15D8bc2115
Tip
If you get an error like IgnitionError: IGN401
try running the command again.
If you want to deploy your contract on mainnet change rskTestnet
with rskMainnet
in the last command we ran and make sure you have RBTC available on your wallet.
Now go to https://explorer.testnet.rootstock.io/ and paste your contract address in the search bar to verify the deployment was successful. If you deployed your contract on mainnet look in https://explorer.rootstock.io/