Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "contracts/lib/forge-std"]
path = contracts/lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "contracts/lib/chainlink-brownie-contracts"]
path = contracts/lib/chainlink-brownie-contracts
url = https://github.com/smartcontractkit/chainlink-brownie-contracts
43 changes: 43 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,46 @@ forge install
```bash
forge test
```
## Deploying to Ethereum Classic (ETC) with Foundry

This guide explains how to deploy Chainvoice smart contracts to the Ethereum Classic Mainnet using Foundry (Forge).

Prerequisites
- Foundry installed
- A funded wallet with ETC
- RPC URL (e.g. from Rivet, Ankr, or Chainstack)

1. Create .env File for Secrets

- Create a .env in your project root i.e. `contracts/`
- Copy all the varible from `contracts/.env.example` to newly created `.env`

`cp .env.example .env`
- Assign valid values to the variable.

2. Compile Contract

`forge build`
3. Load your .env in the terminal

`source .env`
4. Deploy the Contract using forge create

```
forge create contracts/src/Chainvoice.sol:Chainvoice \
--rpc-url $ETC_RPC_URL \
--private-key $PRIVATE_KEY \
--broadcast
```
5. Finally add Contract Address to Frontend `.env`
- Create a new .env file by copying .env.example:

`cp frontend/.env.example frontend/.env`
- Open the new .env file and update the variables, especially:
`VITE_CONTRACT_ADDRESS=your_deployed_contract_address_here`

Replace your_deployed_contract_address_here with the actual contract address you got after deployment.

- Save the .env file.

- Restart your frontend development server so the new environment variables are loaded.
11 changes: 11 additions & 0 deletions contracts/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Wallet Private Key
PRIVATE_KEY=your_private_key_here

# Ethereum Sepolia
SEPOLIA_RPC_URL=https://ethereum-sepolia.publicnode.com/
SEPOLIA_CHAIN_ID=11155111
SEPOLIA_EXPLORER=https://sepolia.etherscan.io/

# Ethereum Classic
ETC_RPC_URL=https://etc.rivet.link
ETC_CHAIN_ID=61
1 change: 1 addition & 0 deletions contracts/lib/forge-std
Submodule forge-std added at 3b20d6
169 changes: 69 additions & 100 deletions contracts/src/Chainvoice.sol
Original file line number Diff line number Diff line change
@@ -1,174 +1,143 @@
// SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.13;
import {Test} from "../lib/forge-std/src/Test.sol";
import {console} from "../lib/forge-std/src/console.sol";

contract Chainvoice {
struct UserDetails {
string fname;
string lname;
string email;
string country;
string city;
string postalcode;
}
struct ItemData {
string description;
int256 qty;
int256 unitPrice;
int256 discount;
int256 tax;
int256 amount;
}
struct InvoiceDetails {
uint256 id;
address from;
string dueDate;
string issueDate;
UserDetails user; // Struct to store user details
address to;
UserDetails client; // Struct to store client details
uint256 amountDue;
bool isPaid;
string encryptedInvoiceData; // Base64-encoded ciphertext
string encryptedHash;
}

InvoiceDetails[] public invoices;

mapping(address => uint256[]) public sentInvoices;
mapping(address => uint256[]) public receivedInvoices;
mapping(uint256 => ItemData[]) public itemDatas;

address public owner;
address public treasuryAddress;
uint256 public fee;
uint256 public accumulatedFees;

constructor() {
owner = msg.sender;
fee = 500000000000000 ; //0.0005 ether
}

modifier OnlyOwner() {
require(msg.sender == owner, "Only Owner is accessible");
_;
}
event InvoiceCreated(
uint256 id,
uint256 indexed id,
address indexed from,
address indexed to,
uint256 amountDue
address indexed to
);

event InvoicePaid(
uint256 id,
uint256 indexed id,
address indexed from,
address indexed to,
uint256 amountPaid
uint256 amount
);

constructor() {
owner = msg.sender;
fee = 0.0005 ether;
}

modifier onlyOwner() {
require(msg.sender == owner, "Only owner can call");
_;
}

function createInvoice(
uint256 amountDue,
address to,
string memory _dueDate,
string memory _issueDate,
UserDetails memory user,
UserDetails memory client,
ItemData[] memory _items
uint256 amountDue,
string memory encryptedInvoiceData,
string memory encryptedHash
) external {
require(amountDue > 0, "Amount must be greater than zero");
require(to != address(0), "Receiver address cannot be zero");
require(to != msg.sender, "Cannot create invoice for yourself");
require(to != address(0), "Recipient address is zero");
require(to != msg.sender, "Self-invoicing not allowed");

uint256 invoiceId = invoices.length;

invoices.push(
InvoiceDetails({
id: invoiceId,
from: msg.sender,
dueDate:_dueDate,
issueDate: _issueDate,
user: user,
to: to,
client: client,
amountDue: amountDue,
isPaid: false
isPaid: false,
encryptedInvoiceData: encryptedInvoiceData,
encryptedHash: encryptedHash
})
);

for (uint256 i = 0; i < _items.length; i++) {
itemDatas[invoiceId].push(_items[i]);
}

sentInvoices[msg.sender].push(invoiceId);
receivedInvoices[to].push(invoiceId);

emit InvoiceCreated(invoiceId, msg.sender, to, amountDue);
emit InvoiceCreated(invoiceId, msg.sender, to);
}

uint256 public accumulatedFees;

function payInvoice(uint256 invoiceId) external payable {
require(invoiceId < invoices.length, "Invalid invoice ID");

InvoiceDetails storage invoice = invoices[invoiceId];
require(msg.sender == invoice.to, "Not authorized to pay this invoice");
require(!invoice.isPaid, "Invoice already paid");
require(
msg.value >= invoice.amountDue + fee,
"Payment must cover the invoice amount and fee"
);
uint256 amountToRecipient = msg.value - fee;
(bool success, ) = payable(invoice.from).call{value: amountToRecipient}(
""
);
require(success, "Payment transfer failed");
require(msg.sender == invoice.to, "Not authorized");
require(!invoice.isPaid, "Already paid");
require(msg.value == invoice.amountDue + fee, "Incorrect payment amount");

accumulatedFees += fee;
invoice.isPaid = true;

uint256 amountToSender = msg.value - fee;
(bool sent, ) = payable(invoice.from).call{value: amountToSender}("");
require(sent, "Transfer failed");

emit InvoicePaid(invoiceId, invoice.from, invoice.to, msg.value);
}

function getSentInvoices(address _address)
external
view
returns (InvoiceDetails[] memory, ItemData[][] memory)
{
return _getInvoices(sentInvoices[_address]);
function getSentInvoices(
address user
) external view returns (InvoiceDetails[] memory) {
return _getInvoices(sentInvoices[user]);
}

function getReceivedInvoices(
address _address
) external view returns (InvoiceDetails[] memory, ItemData[][] memory) {
return _getInvoices(receivedInvoices[_address]);
address user
) external view returns (InvoiceDetails[] memory) {
return _getInvoices(receivedInvoices[user]);
}

function _getInvoices(
uint256[] storage invoiceIds
) internal view returns (InvoiceDetails[] memory, ItemData[][] memory) {
InvoiceDetails[] memory userInvoices = new InvoiceDetails[](
invoiceIds.length
);
ItemData[][] memory items = new ItemData[][](invoiceIds.length);

for (uint256 i = 0; i < invoiceIds.length; i++) {
userInvoices[i] = invoices[invoiceIds[i]];
items[i] = itemDatas[invoiceIds[i]];
uint256[] storage ids
) internal view returns (InvoiceDetails[] memory) {
InvoiceDetails[] memory result = new InvoiceDetails[](ids.length);
for (uint256 i = 0; i < ids.length; i++) {
result[i] = invoices[ids[i]];
}

return (userInvoices, items);
return result;
}

function setTreasuryAddress(address newTreauserAdd) public OnlyOwner {
require(newTreauserAdd != address(0), "Treasury Address cannot be equal to zero");
treasuryAddress = newTreauserAdd;
function getInvoice(
uint256 invoiceId
) external view returns (InvoiceDetails memory) {
require(invoiceId < invoices.length, "Invalid ID");
return invoices[invoiceId];
}


function setFeeAmount(uint256 _fee) public OnlyOwner {
function setFeeAmount(uint256 _fee) external onlyOwner {
fee = _fee;
}

function withdraw() external {
require(treasuryAddress != address(0), "Treasury address not set");
require(accumulatedFees > 0, "No fees to withdraw");

function setTreasuryAddress(address newTreasury) external onlyOwner {
require(newTreasury != address(0), "Zero address");
treasuryAddress = newTreasury;
}

function withdrawFees() external {
require(treasuryAddress != address(0), "Treasury not set");
require(accumulatedFees > 0, "No fees available");

uint256 amount = accumulatedFees;
accumulatedFees = 0;

(bool success, ) = payable(treasuryAddress).call{value: amount}("");
require(success, "Fee withdrawal failed");
require(success, "Withdraw failed");
}
}
66 changes: 0 additions & 66 deletions contracts/src/MockV3Aggregator.sol

This file was deleted.

18 changes: 0 additions & 18 deletions frontend/.env

This file was deleted.

Loading