Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion docs/02-developers/09-use-cases/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,18 @@ Explore dApp use case guides and tutorials for building DeFi on Bitcoin, AI, and
values={[
{label: 'DeFi', value: 'defi'},
{label: 'AI/MCP', value: 'ai'},
{label: 'Runes', value: 'runes'}
{label: 'Runes', value: 'runes'},
{label: 'USSD', value: 'ussd'}
]}>
<FilterItem
value="ussd"
title="USSD-Based Decentralized Finance System on Rootstock"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kindly update the title here as well

subtitle="ussd"
color="orange"
linkHref="/developers/use-cases/ussd-rootstock-defi"
description="This guide demonstrates a USSD-based integration architecture for Rootstock, enabling internet-free decentralized financial services like P2P transfers and micro-loans for users on legacy GSM phones."
/>
<FilterItem
value="advanced, defi"
title="Build Omnichain Fungible Token (OFTs) on Rootstock with Layerzero"
subtitle="DeFi"
Expand Down
356 changes: 356 additions & 0 deletions docs/02-developers/09-use-cases/ussd-rootstock-defi/Implementation
Original file line number Diff line number Diff line change
@@ -0,0 +1,356 @@
---
sidebar_label: Implementation Guide
sidebar_position: 3
title: USSD-RSK Implementation Guide
tags: [rsk, rootstock, developers, solidity, nodejs]
description: Step-by-step implementation guide for deploying the InclusiveDeFi smart contract on RSK Testnet and building the USSD relay server with Africa's Talking and ethers.js.
---

# Implementation Guide

This guide walks through the complete implementation writing and deploying the `InclusiveDeFi` smart contract on RSK Testnet, building the USSD relay server, and wiring it to the Africa's Talking USSD gateway.
:::note
`InclusiveDeFi` is just name a file name here, We encourage to name your smart contract anything!
:::
## Prerequisites

Before starting, ensure you have the following installed:

- Node.js v18 or later
- `pnpm` or `npm`
- A funded RSK Testnet wallet (get tRBTC from the [RSK Testnet Faucet](https://faucet.rootstock.io))
- An [Africa's Talking](https://africastalking.com) sandbox account

## Project Structure

```
ussd-rsk/
├── contracts/
│ └── InclusiveDeFi.sol
├── ignition/
│ └── modules/
│ └── InclusiveDeFi.ts
├── index.ts
├── hardhat.config.ts
├── .env
└── package.json
```

## Step 1: Smart Contract

### InclusiveDeFi.sol

The contract maintains two mappings: one for balances and one for active loans. It exposes four functions covering deposit, balance check, P2P transfer, and micro-loan issuance.

```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
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 — fixed 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);
}

// Balance query
function getBalance(address user) public view returns (uint256) {
return balances[user];
}

// Deposit tRBTC into the contract
function deposit() public payable {
balances[msg.sender] += msg.value;
}
}
```

:::note
The `applyForLoan()` function credits the loan amount directly to the caller's internal balance without requiring any on-chain collateral. This is intentional for the demo. In production, implement a collateral or credit-scoring mechanism before issuing loans.
:::

## Step 2: Hardhat Configuration

Install dependencies and configure Hardhat to target the RSK Testnet alongside the default local networks.

```bash
npm install
```

### hardhat.config.ts

```typescript
import hardhatToolboxMochaEthersPlugin from "@nomicfoundation/hardhat-toolbox-mocha-ethers";
import { configVariable, defineConfig } from "hardhat/config";
import * as dotenv from "dotenv";

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: {
rskTestnet: {
type: "http",
chainType: "l1",
url: "https://public-node.testnet.rsk.co",
chainId: 31,
accounts: [process.env.PRIVATE_KEY!],
},
},
});
```

## Step 3: Environment Variables

Create a `.env` file in the project root. **Never commit this file to version control.**

```bash
PRIVATE_KEY=your_relayer_wallet_private_key_here
RSK_TESTNET_RPC=https://public-node.testnet.rsk.co
```

:::warning
The private key stored here controls the relayer wallet. This wallet signs all transactions on behalf of users. Keep it funded with tRBTC and treat the key as a production secret.
:::

Ensure `.env` is in your `.gitignore`:

```
# .gitignore
node_modules/
dist/
artifacts/
cache/
.env
```

## Step 4: Deploy the Contract

### Ignition Module

Create the Hardhat Ignition deployment module:

```typescript
// ignition/modules/InclusiveDeFi.ts
import { buildModule } from "@nomicfoundation/hardhat-ignition/modules";

const InclusiveDeFiModule = buildModule("InclusiveDeFiModule", (m) => {
const inclusiveDeFi = m.contract("InclusiveDeFi");
return { inclusiveDeFi };
});

export default InclusiveDeFiModule;
```

### Deploy to RSK Testnet

```bash
npx hardhat ignition deploy --network rskTestnet ignition/modules/InclusiveDeFi.ts
```

On successful deployment, Hardhat Ignition will output the deployed contract address. Record this address — it is needed in the relay server configuration.

Example output:

```
Deployed Addresses
InclusiveDeFiModule#InclusiveDeFi: 0x636Cd39bE612d78019C534aA168B9c5969c0c966
```

You can verify the deployment on the [RSK Testnet Explorer](https://explorer.testnet.rootstock.io).

## Step 5: USSD Relay Server

The relay server is an Express application with a single `/ussd` POST endpoint. It connects to RSK via ethers.js, parses the USSD session state from the `text` field, and routes each request to the appropriate contract function.

```typescript
// index.ts
import express, { Request, Response } from 'express';
import { ethers } from 'ethers';
import * as dotenv from 'dotenv';

dotenv.config();

const app = express();
app.use(express.urlencoded({ extended: false }));

const provider = new ethers.JsonRpcProvider(process.env.RSK_TESTNET_RPC);
const wallet = new ethers.Wallet(process.env.PRIVATE_KEY as string, provider);

const CONTRACT_ADDRESS = "0x636Cd39bE612d78019C534aA168B9c5969c0c966";
const ABI = [
"function getBalance(address user) view returns (uint256)",
"function transfer(address to, uint256 amount)",
"function applyForLoan()"
];

const contract = new ethers.Contract(CONTRACT_ADDRESS, ABI, wallet);

app.post('/ussd', async (req: Request, res: Response) => {
const { text, phoneNumber } = req.body;
let response = "";
const input = text.split('*');

if (text === "") {
// Entry point — show main menu
response = `CON Rootstock DeFi (${phoneNumber})
1. My Balance
2. Send Money (P2P)
3. Request Micro-Loan`;

} else if (input[0] === "1") {
// Balance check — read call, no gas required
const bal = await contract.getBalance(wallet.address);
response = `END Your Balance: ${ethers.formatEther(bal)} tRBTC`;

} else if (input[0] === "2") {
if (!input[1]) {
response = "CON Enter Recipient Address:";
} else if (!input[2]) {
response = "CON Enter Amount:";
} else {
// Execute transfer — signed transaction broadcasted to RSK
const tx = await contract.transfer(input[1], ethers.parseEther(input[2]));
await tx.wait();
response = `END Transfer Sent! Hash: ${tx.hash.substring(0, 10)}`;
}

} else if (input[0] === "3") {
// Micro-loan — signed transaction
const tx = await contract.applyForLoan();
await tx.wait();
response = `END Loan of 0.01 tRBTC approved and deposited!`;
}

res.header('Content-Type', 'text/plain');
res.send(response);
});

app.listen(3000, () => console.log("RSK-USSD Bridge running on port 3000"));
```

### Start the Server

```bash
npm run start-bridge
```

The server starts on port `3000`. To expose it to the Africa's Talking gateway, use a tunnelling tool like [ngrok](https://ngrok.com) during local development:

```bash
ngrok http 3000
```

Copy the generated HTTPS URL (e.g., `https://abc123.ngrok.io`) — you will register this as your USSD callback URL.

## Step 6: Africa's Talking USSD Gateway Configuration

1. Log into your [Africa's Talking sandbox](https://account.africastalking.com/apps/sandbox)
2. Navigate to **USSD → Create Channel**
3. Assign a shortcode (e.g., `*384#`)
4. Set the **Callback URL** to your relay server endpoint: `https://your-ngrok-url.ngrok.io/ussd`
5. Save and activate the channel

You can now test your full USSD flow using the Africa's Talking **USSD Simulator** in the sandbox dashboard.

## Step 7: Testing the Flow

### Simulated USSD Test (curl)

You can test the relay server directly without the Africa's Talking gateway using `curl`:

```bash
# Simulate dialing — entry menu
curl -X POST http://localhost:3000/ussd \
-d "text=&phoneNumber=%2B2348012345678&sessionId=test001"

# Simulate selecting balance check
curl -X POST http://localhost:3000/ussd \
-d "text=1&phoneNumber=%2B2348012345678&sessionId=test001"

# Simulate requesting a micro-loan
curl -X POST http://localhost:3000/ussd \
-d "text=3&phoneNumber=%2B2348012345678&sessionId=test001"
```

### Hardhat Unit Tests

Run the included test suite against a local simulated network:

```bash
npx hardhat test
```

### Verify On-Chain

After running a transaction through the relay server, verify it on the RSK Testnet Explorer:

```
https://explorer.testnet.rootstock.io/address/0x636Cd39bE612d78019C534aA168B9c5969c0c966
```

## package.json Reference

```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.2",
"ethers": "^6.16.0",
"hardhat": "^3.1.9",
"tsx": "^4.21.0",
"typescript": "~5.8.0"
}
}
```

## Limitations & Future Work

The current implementation is a functional proof of concept. The following improvements are recommended before any production deployment:

**Per-user wallet mapping:** Map each `phoneNumber` to a dedicated wallet address. Store this mapping in a database (e.g., PostgreSQL or Redis) so that `getBalance()` returns the correct user's balance, not the relay wallet's.

**Loan repayment:** Add a `repayLoan()` function to `InclusiveDeFi.sol` that accepts tRBTC and clears the `loans[msg.sender]` entry, enabling reuse of the loan feature.

**Input validation:** Sanitize all user-supplied input (recipient addresses, amounts) in the relay server before passing to ethers.js. An invalid address or a non-numeric amount will cause an unhandled exception that crashes the USSD session.

**Error handling:** Wrap all `await` calls in try/catch blocks and return graceful `END` messages to the user on failure rather than timing out their session.

**Meta-transaction standard:** Consider implementing [EIP-2771](https://eips.ethereum.org/EIPS/eip-2771) to formalize the relayer pattern and allow the smart contract to identify the original user (by phone-mapped wallet) rather than the relay wallet address.

**Production secrets:** Move the relayer private key out of `.env` and into a secrets manager or HSM-backed signing service before any mainnet deployment.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@

label: USSD Rootstock DeFi
position: 4
link:
type: generated-index
slug: /developers/use-cases/ussd-rootstock-defi/
description: "Use USSD to interact with DeFi protocols Rootstock."
Loading
Loading