Introduction
Managing shared expenses can often lead to misunderstandings and disputes among friends or family. Traditional methods—like spreadsheets or messy apps—can be cumbersome and error-prone. Enter ExpenseShare, a decentralized application (dApp) built on blockchain technology that streamlines expense tracking and ensures transparency among all parties involved. In this article, we’ll explore the process of building such a solution, leveraging Solidity for smart contracts and React for the user interface.
Understanding the Concept
Before diving into development, it's essential to understand what ExpenseShare aims to achieve:
Transparency: All transactions are recorded on-chain, offering a clear, immutable history.
Trustlessness: Users don’t need to trust a central authority; the code governs all transactions.
User-Friendly: The application will have a straightforward frontend to facilitate easy interactions.
Tech Stack Overview
For this project, the chosen technologies include:
Ethereum: The blockchain platform where the smart contracts will live.
Solidity: The programming language for writing smart contracts on Ethereum.
React: A popular JavaScript library for building user interfaces.
Web3.js: A library to interact with Ethereum nodes from the frontend.
MetaMask: A browser extension that allows users to manage their Ethereum wallets.
Smart Contract Development
Developing the smart contracts with Solidity is the backbone of our application. Here's a simplified version of the ExpenseShare contract:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract ExpenseShare {
struct Expense {
uint id;
string description;
uint amount;
address[] participants;
mapping(address => uint) balances;
}
mapping(uint => Expense) public expenses;
uint public expenseCount;
event ExpenseCreated(uint id, string description, uint amount);
event BalanceUpdated(uint id, address participant, uint amount);
function createExpense(string memory _description, uint _amount, address[] memory _participants) public {
expenseCount++;
Expense storage newExpense = expenses[expenseCount];
newExpense.id = expenseCount;
newExpense.description = _description;
newExpense.amount = _amount;
newExpense.participants = _participants;
emit ExpenseCreated(expenseCount, _description, _amount);
}
function settleExpense(uint _expenseId) public {
Expense storage expense = expenses[_expenseId];
require(expense.id > 0, "Expense does not exist.");
uint totalParticipants = expense.participants.length;
uint share = expense.amount / totalParticipants;
for (uint i = 0; i < totalParticipants; i++) {
address participant = expense.participants[i];
expense.balances[participant] = share;
emit BalanceUpdated(_expenseId, participant, share);
}
}
function getBalance(uint _expenseId) public view returns(uint) {
return expenses[_expenseId].balances[msg.sender];
}
}Contract Breakdown
Expense Struct: Stores information about each expense, including a list of participants.
Mapping: To track all expenses and their associated IDs.
Events: To keep the frontend updated when an expense is created or settled.
Functions: For creating expenses and settling balances.
This smart contract establishes a controlled environment for expense tracking, automatically calculating shares.
Building the Frontend with React
Once the smart contract is deployed, it's time to build a user-friendly interface. Here’s a skeleton of how to set up your React component:
Setting Up a React Component
Install dependencies first:
npm install web3 ethersNow, implement the basic functionality in a React component:
import React, { useState, useEffect } from 'react';
import Web3 from 'web3';
import ExpenseShareContract from './contracts/ExpenseShare.json';
const ExpenseTracker = () => {
const [account, setAccount] = useState('');
const [expenses, setExpenses] = useState([]);
useEffect(() => {
const init = async () => {
const web3 = new Web3(Web3.givenProvider || "http://localhost:7545");
const networkId = await web3.eth.net.getId();
const deployedNetwork = ExpenseShareContract.networks[networkId];
const contract = new web3.eth.Contract(
ExpenseShareContract.abi,
deployedNetwork && deployedNetwork.address,
);
const accounts = await web3.eth.getAccounts();
setAccount(accounts[0]);
// Fetch expenses
const expenseCount = await contract.methods.expenseCount().call();
const loadedExpenses = [];
for (let