Decentralized Invoice System on Conflux eSpace

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

  1. Invoice Data Structure: Stores all invoice information
  2. State Management: Tracks invoice status and relationships
  3. Access Control: Ensures only authorized parties can perform actions
  4. Payment Processing: Handles on-chain payments and refunds
  5. 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:

  • exists flag: Prevents accessing non-existent invoices (gas optimization)
  • createdAt and paidAt: 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 require statements prevent invalid invoice creation
  • Auto-incrementing IDs: Uses nextInvoiceId to 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 call with 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

  1. Input Validation: All functions validate inputs before processing
  2. Access Control: Modifiers ensure only authorized parties can perform actions
  3. State Checks: Prevents invalid state transitions
  4. Payment Safety: Handles excess payments with refunds

Recommendations for Production

  1. ReentrancyGuard: Consider using OpenZeppelin’s ReentrancyGuard for the payInvoice function
  2. Pausable: Add pause functionality for emergency stops
  3. Upgradeability: Consider proxy patterns if contract updates are needed
  4. Rate Limiting: Prevent spam invoice creation
  5. Gas Optimization: Consider packing struct fields for storage efficiency

Use Cases

  1. Freelancer Platforms: Integrate with Web3 freelancer marketplaces
  2. DAO Payments: Manage recurring payments in decentralized organizations
  3. Service Providers: Track service invoices on-chain
  4. 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