This page covers everything from scaffolding the project to deploying the `InclusiveDeFi` contract on Rootstock Testnet. By the end of this page, you will have a live contract address on Chain ID 31 that the relay server can interact with. :::note `InclusiveDeFi` is just a name of the contract; developers can change it as needed. ::: ## Prerequisites Before starting, ensure you have the following: - Node.js v18+ - `npm` or `pnpm` - A funded Rootstock Testnet wallet. Get tRBTC from the [Rootstock Testnet Faucet](https://faucet.rootstock.io). - Basic familiarity with [Solidity](https://soliditylang.org/) and [Hardhat](https://hardhat.org/) is recommended. ## Scaffolding the Project This project uses Hardhat 3 Beta with the `mocha` + `ethers` toolbox. The folder structure is automatically generated by Hardhat's initializer, so no manual setup is required. Run the following in your terminal to scaffold a new Hardhat 3 project: ```bash mkdir ussd-rsk && cd ussd-rsk npx hardhat --init ``` When prompted, select the TypeScript + mocha + ethers template. Hardhat will generate the base project structure: ```plaintext ussd-rsk/ ├── contracts/ ← Place your Solidity contracts here ├── ignition/ │ └── modules/ ← Hardhat Ignition deployment modules ├── test/ ← Mocha test files ├── hardhat.config.ts ← Network and compiler configuration ├── tsconfig.json └── package.json ``` After scaffolding, install dependencies: ```bash npm install ``` Then add the additional runtime dependencies needed for the relay server: ```bash npm install express dotenv ``` ```bash npm install --save-dev @types/express tsx ``` ```bash npm install --save-dev @nomicfoundation/hardhat-toolbox-mocha-ethers ``` Your final `package.json` should look like this: ```json { "name": "ussd-rsk", "version": "1.0.0", "type": "module", "scripts": { "start-bridge": "tsx index.ts" }, "dependencies": { "dotenv": "^17.3.1", "express": "^5.2.1" }, "devDependencies": { "@nomicfoundation/hardhat-toolbox-mocha-ethers": "^3.0.3", "@types/express": "^5.0.6", "@types/node": "^22.19.11", "chai": "^5.1.2", "ethers": "^6.16.0", "hardhat": "^3.1.11", "tsx": "^4.21.0", "typescript": "~5.8.0" } } ``` ## Environment Variables Add a `.env` file to the project root with the following variables: ```dotenv PRIVATE_KEY=your_relayer_wallet_private_key_here RSK_TESTNET_RPC=https://rpc.rootstock.io/ ``` :::warning Never commit `.env` to version control. It contains the relayer private key. ::: Add `.env` to your `.gitignore` immediately: ```gitignore node_modules/ dist/ artifacts/ cache/ .env ``` ## Hardhat Configuration Update `hardhat.config.ts` to include Rootstock(RSK) Testnet as a named HTTP network: ```typescript dotenv.config(); export default defineConfig({ plugins: [hardhatToolboxMochaEthersPlugin], solidity: { profiles: { default: { version: "0.8.28", }, production: { version: "0.8.28", settings: { optimizer: { enabled: true, runs: 200 }, }, }, }, }, networks: { // Local simulated networks for testing hardhatMainnet: { type: "edr-simulated", chainType: "l1", }, hardhatOp: { type: "edr-simulated", chainType: "op", }, // RSK Testnet - Chain ID 31 rskTestnet: { type: "http", chainType: "l1", url: "https://rpc.rootstock.io/", chainId: 31, accounts: [process.env.PRIVATE_KEY!], }, }, }); ``` :::note Rootstock is fully EVM-compatible; therefore, set `chainType: "l1"`. The public node at `https://rpc.rootstock.io/` is rate-limited and intended for development only. Use a dedicated RPC endpoint for production environments. ::: ## The Smart Contract Create `contracts/InclusiveDeFi.sol`, which serves as the on-chain core of the system. It maintains internal balance and loan state for all users who interact through the relay: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.19; contract InclusiveDeFi { mapping(address => uint256) public balances; mapping(address => uint256) public loans; event Transfer(address indexed from, address indexed to, uint256 amount); event LoanIssued(address indexed user, uint256 amount); // P2P Transfer - moves balance between two internal accounts function transfer(address to, uint256 amount) public { require(balances[msg.sender] >= amount, "Insufficient balance"); balances[msg.sender] -= amount; balances[to] += amount; emit Transfer(msg.sender, to, amount); } // Micro-Loan - issues 0.01 tRBTC, one active loan per address function applyForLoan() public { require(loans[msg.sender] == 0, "Existing loan active"); uint256 loanAmount = 0.01 ether; loans[msg.sender] = loanAmount; balances[msg.sender] += loanAmount; emit LoanIssued(msg.sender, loanAmount); } // Read - returns internal balance for any address function getBalance(address user) public view returns (uint256) { return balances[user]; } // Deposit tRBTC into the contract (used for seeding and testing) function deposit() public payable { balances[msg.sender] += msg.value; } } ``` ### Contract Design Notes The contract uses internal accounting and does not implement ERC-20 or hold externally transferred tokens. All balances tracked in `getBalance()` are denominated in wei (tRBTC) and represent funds that have been deposited via `deposit()` or credited via `applyForLoan()`. The `transfer()` function moves value between two entries in the `getBalance()` mapping. It does not send native tRBTC but updates internal records. This is intentional, as it allows the relayer to call `transfer()` on behalf of any user without requiring the user to hold tRBTC for gas. :::note `applyForLoan()` credits the loan amount to the caller's `getBalance()` entry without requiring collateral. This is suitable for a demo only a production system must add a repayment flow along with either collateral requirements or a credit-scoring oracle before enabling this feature. ::: ## Ignition Deployment Module Create `ignition/modules/InclusiveDeFi.ts` as the Hardhat Ignition module that describes how the contract should be deployed: ```typescript const InclusiveDeFiModule = buildModule("InclusiveDeFiModule", (m) => { const inclusiveDeFi = m.contract("InclusiveDeFi"); return { inclusiveDeFi }; }); export default InclusiveDeFiModule; ``` ## Deploying to Rootstock Testnet Run the following command to deploy: ```bash npx hardhat ignition deploy --network rskTestnet ignition/modules/InclusiveDeFi.ts ``` On success, Hardhat Ignition outputs the deployed contract address and writes deployment artifacts to `ignition/deployments/chain-31/`: ```plaintext Deployed Addresses InclusiveDeFiModule#InclusiveDeFi: 0xYourDeployedContractAddress ``` The `deployed_addresses.json` file under `ignition/deployments/chain-31/` will also record this address for future reference. :::note Copy this contract address. You will need to paste it into `CONTRACT_ADDRESS` in `index.ts` before starting the relay server. ::: ## Verifying the Deployment To verify your deployment, search for your contract address on the [Rootstock Testnet Explorer](https://explorer.testnet.rootstock.io) to view the transaction, bytecode, and ABI. You can also inspect `ignition/deployments/chain-31/journal.jsonl`, a successful deployment entry ends with: ```json { "futureId": "InclusiveDeFiModule#InclusiveDeFi", "result": { "address": "0xYourDeployedContractAddress", "type": "SUCCESS" }, "type": "DEPLOYMENT_EXECUTION_STATE_COMPLETE" } ``` ## Next Steps Once your contract is deployed to Rootstock Testnet, follow the [Relay Server & Gateway Integration](../relay-server) guide to build the USSD bridge for feature phone users.