Building a Decentralized Invoice System on Conflux eSpace: A Technical Deep Dive
Introduction
In the rapidly evolving world of Web3, traditional business processes are being reimagined on the blockchain. One such application is invoice management—a critical component of freelancer-client relationships. This blog post explores CfxInvoice, a decentralized invoice management system built on Conflux eSpace that brings transparency, immutability, and trust to invoice processing.
Video: https://youtu.be/TL0iZuvm78A?si=GtRke0AfBfLnpGCv
GitHub: https://github.com/Vikash-8090-Yadav/CfxInvoice
Why Blockchain for Invoice Management?
Traditional invoice systems suffer from several limitations:
- Trust issues: Disputes over payment status and amounts
- Centralization: Single points of failure and control
- Lack of transparency: Limited visibility into invoice history
- Manual processes: Time-consuming reconciliation and tracking
Blockchain technology addresses these challenges by providing:
- Immutability: Once created, invoices cannot be tampered with
- Transparency: All parties can verify invoice status on-chain
- Automation: Smart contracts handle payment logic automatically
- Decentralization: No single authority controls the system
Architecture Overview
CfxInvoice is built using Solidity 0.8.20 and deployed on Conflux eSpace, an EVM-compatible blockchain. The system uses a single smart contract that manages the entire invoice lifecycle from creation to payment.
Core Components
- Invoice Data Structure: Stores all invoice information
- State Management: Tracks invoice status and relationships
- Access Control: Ensures only authorized parties can perform actions
- Payment Processing: Handles on-chain payments and refunds
- Event System: Emits events for off-chain tracking
Key Features and Implementation
1. Invoice Status Management
The system uses an enum to track invoice states, ensuring type safety and clear state transitions:
enum InvoiceStatus {
Pending,
Paid,
Overdue,
Cancelled
}
Why this matters: Using an enum prevents invalid status values and makes the contract’s state machine explicit and auditable.
2. Invoice Data Structure
Each invoice is stored as a struct containing all relevant information:
struct Invoice {
uint256 invoiceId;
address freelancer;
address client;
uint256 amount;
uint256 dueDate;
InvoiceStatus status;
string description;
bool exists;
uint256 createdAt;
uint256 paidAt;
}
Key design decisions:
-
existsflag: Prevents accessing non-existent invoices (gas optimization) -
createdAtandpaidAt: Enable time-based analytics and auditing -
description: Allows for flexible invoice details (stored as string)
3. Access Control with Modifiers
The contract implements role-based access control using Solidity modifiers:
modifier onlyFreelancer(uint256 _invoiceId) {
require(
invoices[_invoiceId].freelancer == msg.sender,
"Only the freelancer can perform this action"
);
_;
}
modifier onlyClient(uint256 _invoiceId) {
require(
invoices[_invoiceId].client == msg.sender,
"Only the client can perform this action"
);
_;
}
Security benefit: These modifiers ensure that only the invoice creator (freelancer) can cancel invoices, and only the designated client can pay them.
4. Invoice Creation
The createInvoice function is the entry point for creating new invoices:
function createInvoice(
address _client,
uint256 _amount,
uint256 _dueDate,
string memory _description
) external returns (uint256) {
require(_client != address(0), "Invalid client address");
require(_client != msg.sender, "Cannot create invoice for yourself");
require(_amount > 0, "Amount must be greater than 0");
require(_dueDate > block.timestamp, "Due date must be in the future");
uint256 invoiceId = nextInvoiceId;
nextInvoiceId++;
Invoice memory newInvoice = Invoice({
invoiceId: invoiceId,
freelancer: msg.sender,
client: _client,
amount: _amount,
dueDate: _dueDate,
status: InvoiceStatus.Pending,
description: _description,
exists: true,
createdAt: block.timestamp,
paidAt: 0
});
invoices[invoiceId] = newInvoice;
freelancerInvoices[msg.sender].push(invoiceId);
clientInvoices[_client].push(invoiceId);
emit InvoiceCreated(invoiceId, msg.sender, _client, _amount, _dueDate);
return invoiceId;
}
Important aspects:
-
Input validation: Multiple
requirestatements prevent invalid invoice creation -
Auto-incrementing IDs: Uses
nextInvoiceIdto ensure unique invoice identifiers - Bidirectional mapping: Maintains separate arrays for freelancer and client lookups
- Event emission: Enables off-chain systems to track invoice creation
5. Payment Processing
The payment function handles on-chain transfers with built-in safety checks:
function payInvoice(uint256 _invoiceId)
external
payable
invoiceExists(_invoiceId)
onlyClient(_invoiceId)
{
Invoice storage invoice = invoices[_invoiceId];
require(
invoice.status == InvoiceStatus.Pending ||
invoice.status == InvoiceStatus.Overdue,
"Invoice is not payable"
);
require(msg.value >= invoice.amount, "Insufficient payment amount");
InvoiceStatus oldStatus = invoice.status;
invoice.status = InvoiceStatus.Paid;
invoice.paidAt = block.timestamp;
// Transfer payment to freelancer
(bool success, ) = invoice.freelancer.call{value: invoice.amount}("");
require(success, "Payment transfer failed");
// Refund excess payment if any
if (msg.value > invoice.amount) {
(bool refundSuccess, ) = msg.sender.call{
value: msg.value - invoice.amount
}("");
require(refundSuccess, "Refund failed");
}
emit InvoicePaid(_invoiceId, msg.sender, invoice.amount, block.timestamp);
emit InvoiceStatusUpdated(_invoiceId, oldStatus, InvoiceStatus.Paid);
}
Critical security features:
-
Reentrancy protection: Uses
callwith explicit checks (though for production, consider using OpenZeppelin’s ReentrancyGuard) - Exact payment handling: Accepts payments >= invoice amount and refunds excess
- Status validation: Only allows payment of pending or overdue invoices
- Atomic operations: Payment and status update happen in a single transaction
6. Overdue Detection
The system allows anyone to mark invoices as overdue, enabling decentralized monitoring:
function markAsOverdue(uint256 _invoiceId)
external
invoiceExists(_invoiceId)
{
Invoice storage invoice = invoices[_invoiceId];
require(
invoice.status == InvoiceStatus.Pending,
"Invoice is not pending"
);
require(
block.timestamp > invoice.dueDate,
"Invoice is not yet overdue"
);
InvoiceStatus oldStatus = invoice.status;
invoice.status = InvoiceStatus.Overdue;
emit InvoiceStatusUpdated(_invoiceId, oldStatus, InvoiceStatus.Overdue);
}
Design choice: Making this function public allows off-chain services (like bots or monitoring systems) to automatically update invoice status without requiring the freelancer or client to take action.
7. Query Functions
The contract provides efficient view functions for retrieving invoice data:
function getInvoice(uint256 _invoiceId)
external
view
invoiceExists(_invoiceId)
returns (Invoice memory)
{
return invoices[_invoiceId];
}
function getFreelancerInvoices(address _freelancer)
external
view
returns (uint256[] memory)
{
return freelancerInvoices[_freelancer];
}
function getClientInvoices(address _client)
external
view
returns (uint256[] memory)
{
return clientInvoices[_client];
}
Gas optimization: View functions don’t modify state, so they’re free to call and perfect for frontend applications.
Event System
Events are crucial for off-chain systems to track contract state changes:
event InvoiceCreated(
uint256 indexed invoiceId,
address indexed freelancer,
address indexed client,
uint256 amount,
uint256 dueDate
);
event InvoicePaid(
uint256 indexed invoiceId,
address indexed client,
uint256 amount,
uint256 paidAt
);
event InvoiceStatusUpdated(
uint256 indexed invoiceId,
InvoiceStatus oldStatus,
InvoiceStatus newStatus
);
Indexed parameters: The indexed keyword allows efficient filtering of events by invoice ID, freelancer, or client address.
Deployment Configuration
The project uses Hardhat for development and deployment. Here’s the network configuration:
networks: {
confluxTestnet: {
url: "https://evmtestnet.confluxrpc.com",
chainId: 71,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
gasPrice: 1000000000, // 1 gwei
},
confluxMainnet: {
url: "https://evm.confluxrpc.com",
chainId: 1030,
accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [],
gasPrice: 1000000000,
},
}
Deployment script:
async function main() {
const InvoiceSystem = await hre.ethers.getContractFactory("InvoiceSystem");
const invoiceSystem = await InvoiceSystem.deploy();
await invoiceSystem.waitForDeployment();
const address = await invoiceSystem.getAddress();
console.log("Contract address:", address);
}
Security Considerations
Current Security Features
- Input Validation: All functions validate inputs before processing
- Access Control: Modifiers ensure only authorized parties can perform actions
- State Checks: Prevents invalid state transitions
- Payment Safety: Handles excess payments with refunds
Recommendations for Production
-
ReentrancyGuard: Consider using OpenZeppelin’s ReentrancyGuard for the
payInvoicefunction - Pausable: Add pause functionality for emergency stops
- Upgradeability: Consider proxy patterns if contract updates are needed
- Rate Limiting: Prevent spam invoice creation
- Gas Optimization: Consider packing struct fields for storage efficiency
Use Cases
- Freelancer Platforms: Integrate with Web3 freelancer marketplaces
- DAO Payments: Manage recurring payments in decentralized organizations
- Service Providers: Track service invoices on-chain
- Cross-border Payments: Leverage blockchain for international invoicing
Gas Cost Analysis
Typical gas costs (approximate):
- Create Invoice: ~150,000 gas
- Pay Invoice: ~80,000 gas (depending on refund logic)
- Mark Overdue: ~45,000 gas
- Cancel Invoice: ~30,000 gas
- View Functions: 0 gas (free)
Future Enhancements
Conclusion
CfxInvoice demonstrates how blockchain technology can revolutionize traditional business processes. By leveraging smart contracts, we’ve created a transparent, trustless, and automated invoice management system. The code showcases best practices in Solidity development, including proper access control, event emission, and input validation.
The system is production-ready for basic use cases, with clear paths for enhancement. As the Web3 ecosystem continues to evolve, systems like this will become the foundation for decentralized business operations.
Resources
-
Contract Code: Available in
contracts/InvoiceSystem.sol -
Deployment Script:
scripts/deploy.js - Conflux eSpace Docs: https://developers.confluxnetwork.org/
- Hardhat Documentation: https://hardhat.org/docs