diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..5c6b89f7 --- /dev/null +++ b/.gitmodules @@ -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 diff --git a/README.md b/README.md index fc05779c..c0df07d3 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/contracts/.env.example b/contracts/.env.example new file mode 100644 index 00000000..586d96d4 --- /dev/null +++ b/contracts/.env.example @@ -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 diff --git a/contracts/lib/forge-std b/contracts/lib/forge-std new file mode 160000 index 00000000..3b20d60d --- /dev/null +++ b/contracts/lib/forge-std @@ -0,0 +1 @@ +Subproject commit 3b20d60d14b343ee4f908cb8079495c07f5e8981 diff --git a/contracts/src/Chainvoice.sol b/contracts/src/Chainvoice.sol index 9b25397e..a729f8e8 100644 --- a/contracts/src/Chainvoice.sol +++ b/contracts/src/Chainvoice.sol @@ -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"); } } diff --git a/contracts/src/MockV3Aggregator.sol b/contracts/src/MockV3Aggregator.sol deleted file mode 100644 index c00a076b..00000000 --- a/contracts/src/MockV3Aggregator.sol +++ /dev/null @@ -1,66 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.0; - -import {AggregatorV3Interface} from "../lib/chainlink-brownie-contracts/contracts/src/v0.8/shared/interfaces/AggregatorV3Interface.sol"; - -contract MockV3Aggregator is AggregatorV3Interface { - uint256 public constant version = 4; - - uint8 public decimals; - int256 public latestAnswer; - uint256 public latestTimestamp; - uint256 public latestRound; - - mapping(uint256 => int256) public getAnswer; - mapping(uint256 => uint256) public getTimestamp; - mapping(uint256 => uint256) private getStartedAt; - - constructor(uint8 _decimals, int256 _initialAnswer) { - decimals = _decimals; - updateAnswer(_initialAnswer); - } - - function updateAnswer(int256 _answer) public { - latestAnswer = _answer; - latestTimestamp = block.timestamp; - latestRound++; - getAnswer[latestRound] = _answer; - getTimestamp[latestRound] = block.timestamp; - getStartedAt[latestRound] = block.timestamp; - } - - function updateRoundData(uint80 _roundId, int256 _answer, uint256 _timestamp, uint256 _startedAt) public { - latestRound = _roundId; - latestAnswer = _answer; - latestTimestamp = _timestamp; - getAnswer[latestRound] = _answer; - getTimestamp[latestRound] = _timestamp; - getStartedAt[latestRound] = _startedAt; - } - - function getRoundData(uint80 _roundId) - external - view - returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) - { - return (_roundId, getAnswer[_roundId], getStartedAt[_roundId], getTimestamp[_roundId], _roundId); - } - - function latestRoundData() - external - view - returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) - { - return ( - uint80(latestRound), - getAnswer[latestRound], - getStartedAt[latestRound], - getTimestamp[latestRound], - uint80(latestRound) - ); - } - - function description() external pure returns (string memory) { - return "v0.6/test/mock/MockV3Aggregator.sol"; - } -} \ No newline at end of file diff --git a/frontend/.env b/frontend/.env deleted file mode 100644 index e761d5b0..00000000 --- a/frontend/.env +++ /dev/null @@ -1,18 +0,0 @@ -# Sepolia testnet rpc URL -VITE_BLOCKCHAIN_URI=https://ethereum-sepolia.publicnode.com/ -VITE_CHAIN_ID=11155111 -ViTE_EXPLORER=https://sepolia.etherscan.io/ - - -# -VITE_CONTRACT_ADDRESS=0x11B4894d1D723FB24310f28E58796d452eBb5aDe -VITE_PRICEFEED_ADDRESS=0xAE26bD30c88Cc8bcB9b9Be97De7Ad33315fb3D65 -# 0x00eca3bd631b3623680d9a511bd89dd5fc965dea79104b02cf6d74ad8eaf31a6 - -# 0x24F13d40CF7DE6a81a2a1949aA45F2242e81f1e2 - -# forge create --rpc-url https://ethereum-sepolia.publicnode.com/ --private-key d198e5f47b1b0f09b8412805be0b90b3ee1321eec231f86a5cc884d3f460f1e2 src/Chainvoice.sol:Chainvoice --broadcast --constructor-args 0x694AA1769357215DE4FAC081bf1f309aDC325306 - -# ETH/USD Mainnet: 0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419 -# ETH/USD Sepolia: 0x694AA1769357215DE4FAC081bf1f309aDC325306 -# ETH/USD Goerli: 0xD4a33860578De61DBAbDc8BFdb98FD742fA7028e diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 00000000..dd86d5ba --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1,15 @@ +# env-copy + +# Blockchain RPC URL (e.g., Sepolia, Ethereum Classic, etc.) +VITE_BLOCKCHAIN_URI=https://your-network-rpc-url/ + +# Network Chain ID (e.g., 11155111 for Sepolia, 61 for Ethereum Classic) +VITE_CHAIN_ID=your_chain_id_here + +# Block explorer URL for your network +VITE_EXPLORER=https://your-network-explorer-url/ + +# Deployed contract address (update after deployment) +VITE_CONTRACT_ADDRESS=0xYourContractAddressHere + + diff --git a/frontend/.gitignore b/frontend/.gitignore index 68b18a8f..bf1f56f8 100644 --- a/frontend/.gitignore +++ b/frontend/.gitignore @@ -11,6 +11,7 @@ node_modules dist dist-ssr *.local +.env # Editor directories and files .vscode/* @@ -22,4 +23,4 @@ dist-ssr *.ntvs* *.njsproj *.sln -*.sw? +*.sw? \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index f2069cb9..ce268161 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -12,6 +12,10 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.0", + "@lit-protocol/auth-helpers": "^7.2.0", + "@lit-protocol/constants": "^7.2.0", + "@lit-protocol/encryption": "^7.2.0", + "@lit-protocol/lit-node-client": "^7.2.0", "@mui/icons-material": "^6.4.6", "@mui/material": "^6.4.6", "@radix-ui/react-label": "^2.1.1", diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index ec859072..5edf1be7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,10 +11,9 @@ import { } from "@tanstack/react-query"; import * as chains from "wagmi/chains"; import { BrowserRouter as Router, Route, Routes } from 'react-router-dom' -import './App.css' import Landing from './page/Landing' import Applayout from './page/Applayout' - + import { citreaTestnet } from './utils/CitreaTestnet'; import Home from './page/Home'; import Feature from './page/Feature'; diff --git a/frontend/src/components/CreateInvoice.jsx b/frontend/src/components/CreateInvoice.jsx index eda38146..8053bb37 100644 --- a/frontend/src/components/CreateInvoice.jsx +++ b/frontend/src/components/CreateInvoice.jsx @@ -1,20 +1,30 @@ -import React, { useEffect, useState } from 'react'; -import { Input } from './ui/input'; -import { Button } from './ui/button'; -import { BrowserProvider, Contract, ethers } from 'ethers'; -import { useAccount, useWalletClient } from 'wagmi'; -import { ChainvoiceABI } from '../contractsABI/ChainvoiceABI'; +import React, { useEffect, useRef, useState } from "react"; +import { Input } from "./ui/input"; +import { Button } from "./ui/button"; +import { BrowserProvider, Contract, ethers, formatUnits, parseUnits } from "ethers"; +import { useAccount, useWalletClient } from "wagmi"; +import { ChainvoiceABI } from "../contractsABI/ChainvoiceABI"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; -import { CalendarIcon, Loader2 } from 'lucide-react'; -import { cn } from '@/lib/utils'; -import { format } from 'date-fns'; -import { Label } from './ui/label'; -import { useNavigate } from 'react-router-dom'; +import { CalendarIcon, Loader2 } from "lucide-react"; +import { cn } from "@/lib/utils"; +import { format } from "date-fns"; +import { Label } from "./ui/label"; +import { useNavigate } from "react-router-dom"; + +import { LitNodeClient } from "@lit-protocol/lit-node-client"; +import { encryptString } from "@lit-protocol/encryption/src/lib/encryption.js"; +import { LIT_ABILITY, LIT_NETWORK } from "@lit-protocol/constants"; +import { + createSiweMessageWithRecaps, + generateAuthSig, + LitAccessControlConditionResource, +} from "@lit-protocol/auth-helpers"; + function CreateInvoice() { const { data: walletClient } = useWalletClient(); const { isConnected } = useAccount(); @@ -23,6 +33,97 @@ function CreateInvoice() { const [issueDate, setIssueDate] = useState(new Date()); const [loading, setLoading] = useState(false); const navigate = useNavigate(); + const litClientRef = useRef(null); + + const [itemData, setItemData] = useState([ + { + description: "", + qty: "", + unitPrice: "", + discount: "", + tax: "", + amount: "", + }, + ]); + + const [totalAmountDue, setTotalAmountDue] = useState(0); + + useEffect(() => { + const total = itemData.reduce((sum, item) => { + const qty = parseUnits(item.qty || "0", 18); + const unitPrice = parseUnits(item.unitPrice || "0", 18); + const discount = parseUnits(item.discount || "0", 18); + const tax = parseUnits(item.tax || "0", 18); + // qty * price (then divide by 1e18 to cancel double scaling) + const lineTotal = qty * unitPrice / parseUnits("1", 18); + const adjusted = lineTotal - discount + tax; + + return sum + adjusted; + }, 0n); + + setTotalAmountDue(formatUnits(total, 18)); + + }, [itemData]); + + useEffect(() => { + const initLit = async () => { + if (!litClientRef.current) { + const client = new LitNodeClient({ + litNetwork: LIT_NETWORK.DatilDev, + debug: false, + }); + await client.connect(); + litClientRef.current = client; + console.log(litClientRef.current); + } + }; + initLit(); + }, []); + + const handleItemData = (e, index) => { + const { name, value } = e.target; + + setItemData((prevItemData) => + prevItemData.map((item, i) => { + if (i === index) { + const updatedItem = { ...item, [name]: value }; + if ( + name === "qty" || + name === "unitPrice" || + name === "discount" || + name === "tax" + ) { + const qty = parseUnits(updatedItem.qty || "0", 18); + const unitPrice = parseUnits(updatedItem.unitPrice || "0", 18); + const discount = parseUnits(updatedItem.discount || "0", 18); + const tax = parseUnits(updatedItem.tax || "0", 18); + + const lineTotal = (qty * unitPrice) / parseUnits("1", 18); + const finalAmount = lineTotal - discount + tax; + + updatedItem.amount = formatUnits(finalAmount, 18); + } + return updatedItem; + } + return item; + }) + ); + }; + + const addItem = () => { + setItemData((prev) => [ + ...prev, + { + description: "", + qty: "", + unitPrice: "", + discount: "", + tax: "", + amount: "", + }, + ]); + }; + const createInvoiceRequest = async (data) => { if (!isConnected || !walletClient) { alert("Please connect your wallet"); @@ -33,68 +134,137 @@ function CreateInvoice() { setLoading(true); const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); + + console.log("data >>>>>> ", data); + console.log("acc :", account.address); + // 1. Prepare invoice data + const invoicePayload = { + amountDue: totalAmountDue, + dueDate, + issueDate, + user: { + address: account?.address.toString(), + fname: data.userFname, + lname: data.userLname, + email: data.userEmail, + country: data.userCountry, + city: data.userCity, + postalcode: data.userPostalcode, + }, + client: { + address: data.clientAddress, + fname: data.clientFname, + lname: data.clientLname, + email: data.clientEmail, + country: data.clientCountry, + city: data.clientCity, + postalcode: data.clientPostalcode, + }, + items: itemData, + }; + + const invoiceString = JSON.stringify(invoicePayload); + + // 2. Setup Lit + + const litNodeClient = litClientRef.current; + if (!litNodeClient) { + alert("Lit client not initialized"); + return; + } + + const accessControlConditions = [ + { + contractAddress: "", + standardContractType: "", + chain: "ethereum", + method: "", + parameters: [":userAddress"], + returnValueTest: { + comparator: "=", + value: account.address.toLowerCase(), + }, + }, + { operator: "or" }, + { + contractAddress: "", + standardContractType: "", + chain: "ethereum", + method: "", + parameters: [":userAddress"], + returnValueTest: { + comparator: "=", + value: data.clientAddress.toLowerCase(), + }, + }, + ]; + + // 3. Encrypt + const { ciphertext, dataToEncryptHash } = await encryptString( + { + accessControlConditions, + dataToEncrypt: invoiceString, + }, + litNodeClient + ); + + const sessionSigs = await litNodeClient.getSessionSigs({ + chain: "ethereum", + resourceAbilityRequests: [ + { + resource: new LitAccessControlConditionResource("*"), + ability: LIT_ABILITY.AccessControlConditionDecryption, + }, + ], + authNeededCallback: async ({ + uri, + expiration, + resourceAbilityRequests, + }) => { + const nonce = await litNodeClient.getLatestBlockhash(); + const toSign = await createSiweMessageWithRecaps({ + uri, + expiration, + resources: resourceAbilityRequests, + walletAddress: account.address, + nonce, + litNodeClient, + }); + + return await generateAuthSig({ + signer, + toSign, + }); + }, + }); + + const encryptedStringBase64 = btoa(ciphertext); + + // 4. Send to contract const contract = new Contract( import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, signer ); - const sanitizedItemData = data.itemData.map((item) => ({ - ...item, - qty: item.qty ? String(item.qty) : "0", - unitPrice: item.unitPrice ? ethers.parseUnits(String(item.unitPrice), 18) : ethers.parseUnits("0", 18), - discount: item.discount ? String(item.discount) : "0", - tax: item.tax ? String(item.tax) : "0", - amount: item.amount ? ethers.parseUnits(String(item.amount), 18) : ethers.parseUnits("0", 18), - })); - - console.log(sanitizedItemData); - const res = await contract.createInvoice( - ethers.parseUnits(String(totalAmountDue), 18), + console.log("amt : ",ethers.parseEther(totalAmountDue.toString())); + const tx = await contract.createInvoice( data.clientAddress, - dueDate.toLocaleString(undefined,{timeZone:"Asia/Kolkata"}).toString(), - issueDate.toLocaleString(undefined,{timeZone:"Asia/Kolkata"}).toString(), - [ - data.userFname, - data.userLname, - data.userEmail, - data.userCountry, - data.userCity, - data.userPostalcode, - ], - [ - data.clientFname, - data.clientLname, - data.clientEmail, - data.clientCountry, - data.clientCity, - data.clientPostalcode, - ], - sanitizedItemData + ethers.parseEther(totalAmountDue.toString()), + encryptedStringBase64, + dataToEncryptHash ); - setTimeout(()=>{ - navigate('/home/sent'); - },4000); - console.log("Transaction successful", res); - } catch (error) { - console.error("Invoice creation error:", error); - setLoading(false); - alert("Failed to create invoice"); + + console.log("Invoice created:", tx); + setTimeout(() => navigate("/home/sent"), 4000); + } catch (err) { + console.error("Encryption or transaction failed:", err); + alert("Failed to create invoice."); } finally { setLoading(false); } }; - - const [itemData, setItemData] = useState([{ - description: '', - qty: '', - unitPrice: '', - discount: '', - tax: '', - amount: '' - }]); - - const handleSubmit = (e) => { + const handleSubmit = async (e) => { e.preventDefault(); const formData = new FormData(e.target); @@ -131,60 +301,18 @@ function CreateInvoice() { clientCountry, clientCity, clientPostalcode, - itemData + itemData, }; console.log(data); - createInvoiceRequest(data); + await createInvoiceRequest(data); }; - const [totalAmountDue, setTotalAmountDue] = useState(0); - const handleItemData = (e, index) => { - const { name, value } = e.target; - - setItemData((prevItemData) => - prevItemData.map((item, i) => { - if (i === index) { - const updatedItem = { ...item, [name]: value }; - if (name === 'qty' || name === 'unitPrice' || name === 'discount' || name === 'tax') { - const qty = parseFloat(updatedItem.qty) || 0; - const unitPrice = parseFloat(updatedItem.unitPrice) || 0; - const discount = parseFloat(updatedItem.discount) || 0; - const tax = parseFloat(updatedItem.tax) || 0; - updatedItem.amount = (qty * unitPrice - discount + tax).toString(); - } - return updatedItem; - } - return item; - }) - ); - }; - - const addItem = () => { - setItemData((prev) => [...prev, { - description: '', - qty: '', - unitPrice: '', - discount: '', - tax: '', - amount: '', - }]); - console.log(itemData.length); - }; - - useEffect(() => { - let total = itemData.reduce((sum, item) => { - return sum + ((parseFloat(item.qty) || 0) * (parseFloat(item.unitPrice) || 0) - (parseFloat(item.discount) || 0) + (parseFloat(item.tax) || 0)); - }, 0); - - setTotalAmountDue(total); - }, [itemData]); - return ( -
+

Create New Invoice Request

- - + +

Issued Date

@@ -223,106 +354,217 @@ function CreateInvoice() {
-
- -
-

From (Your Information)

- -
-

Add Your Info

-
- - +
+
+

From (Your Information)

+ +
+

Add Your Info

+
+ +
-
- - +
+ +
-
- - +
+ +
-
-

Client Information

- -
-

Add Client Info

-
- - +
+

Client Information

+ +
+

Add Client Info

+
+ +
-
- - +
+ +
-
- - +
+ +
-
-
-
DESCRIPTION
-
QTY
-
UNIT PRICE
-
DISCOUNT
-
TAX(%)
-
AMOUNT
+
+
+
DESCRIPTION
+
QTY
+
UNIT PRICE
+
DISCOUNT
+
TAX(%)
+
AMOUNT
-
- { - itemData - .map((_, index) => ( -
- handleItemData(e, index)} /> - handleItemData(e, index)} /> - handleItemData(e, index)} /> - handleItemData(e, index)} /> - handleItemData(e, index)} /> - handleItemData(e, index)} - /> - -
- )) - } +
+ {itemData.map((_, index) => ( +
+ handleItemData(e, index)} + /> + handleItemData(e, index)} + /> + handleItemData(e, index)} + /> + handleItemData(e, index)} + /> + handleItemData(e, index)} + /> + handleItemData(e, index)} + /> +
+ ))}
-

Total : {totalAmountDue}

-
- - { - loading ? ( - - ) : ( - - ) - } +

Total : {totalAmountDue}

+
+ + {loading ? ( + + ) : ( + + )}
-
- ) + ); } -export default CreateInvoice - +export default CreateInvoice; diff --git a/frontend/src/contractsABI/ChainvoiceABI.js b/frontend/src/contractsABI/ChainvoiceABI.js index 4c3976a3..9baa2a01 100644 --- a/frontend/src/contractsABI/ChainvoiceABI.js +++ b/frontend/src/contractsABI/ChainvoiceABI.js @@ -1,886 +1,454 @@ export const ChainvoiceABI = [ - { - "type": "constructor", - "inputs": [ - { - "name": "_priceFeed", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "accumulatedFees", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "createInvoice", - "inputs": [ - { - "name": "amountDue", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "to", - "type": "address", - "internalType": "address" - }, - { - "name": "_dueDate", - "type": "string", - "internalType": "string" - }, - { - "name": "_issueDate", - "type": "string", - "internalType": "string" - }, - { - "name": "user", - "type": "tuple", - "internalType": "struct Chainvoice.UserDetails", - "components": [ - { - "name": "fname", - "type": "string", - "internalType": "string" - }, - { - "name": "lname", - "type": "string", - "internalType": "string" - }, - { - "name": "email", - "type": "string", - "internalType": "string" - }, - { - "name": "country", - "type": "string", - "internalType": "string" - }, - { - "name": "city", - "type": "string", - "internalType": "string" - }, - { - "name": "postalcode", - "type": "string", - "internalType": "string" - } - ] - }, - { - "name": "client", - "type": "tuple", - "internalType": "struct Chainvoice.UserDetails", - "components": [ - { - "name": "fname", - "type": "string", - "internalType": "string" - }, - { - "name": "lname", - "type": "string", - "internalType": "string" - }, - { - "name": "email", - "type": "string", - "internalType": "string" - }, - { - "name": "country", - "type": "string", - "internalType": "string" - }, - { - "name": "city", - "type": "string", - "internalType": "string" - }, - { - "name": "postalcode", - "type": "string", - "internalType": "string" - } - ] - }, - { - "name": "_items", - "type": "tuple[]", - "internalType": "struct Chainvoice.ItemData[]", - "components": [ - { - "name": "description", - "type": "string", - "internalType": "string" - }, - { - "name": "qty", - "type": "int256", - "internalType": "int256" - }, - { - "name": "unitPrice", - "type": "int256", - "internalType": "int256" - }, - { - "name": "discount", - "type": "int256", - "internalType": "int256" - }, - { - "name": "tax", - "type": "int256", - "internalType": "int256" - }, - { - "name": "amount", - "type": "int256", - "internalType": "int256" - } - ] - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "feeAmountInUSD", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getMyReceivedInvoices", - "inputs": [ - { - "name": "add", - "type": "address", - "internalType": "address" - } - ], - "outputs": [ - { - "name": "", - "type": "tuple[]", - "internalType": "struct Chainvoice.InvoiceDetails[]", - "components": [ - { - "name": "id", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "from", - "type": "address", - "internalType": "address" - }, - { - "name": "dueDate", - "type": "string", - "internalType": "string" - }, - { - "name": "issueDate", - "type": "string", - "internalType": "string" - }, - { - "name": "user", - "type": "tuple", - "internalType": "struct Chainvoice.UserDetails", - "components": [ - { - "name": "fname", - "type": "string", - "internalType": "string" - }, - { - "name": "lname", - "type": "string", - "internalType": "string" - }, - { - "name": "email", - "type": "string", - "internalType": "string" - }, - { - "name": "country", - "type": "string", - "internalType": "string" - }, - { - "name": "city", - "type": "string", - "internalType": "string" - }, - { - "name": "postalcode", - "type": "string", - "internalType": "string" - } - ] - }, - { - "name": "to", - "type": "address", - "internalType": "address" - }, - { - "name": "client", - "type": "tuple", - "internalType": "struct Chainvoice.UserDetails", - "components": [ - { - "name": "fname", - "type": "string", - "internalType": "string" - }, - { - "name": "lname", - "type": "string", - "internalType": "string" - }, - { - "name": "email", - "type": "string", - "internalType": "string" - }, - { - "name": "country", - "type": "string", - "internalType": "string" - }, - { - "name": "city", - "type": "string", - "internalType": "string" - }, - { - "name": "postalcode", - "type": "string", - "internalType": "string" - } - ] - }, - { - "name": "amountDue", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "isPaid", - "type": "bool", - "internalType": "bool" - } - ] - }, - { - "name": "", - "type": "tuple[][]", - "internalType": "struct Chainvoice.ItemData[][]", - "components": [ - { - "name": "description", - "type": "string", - "internalType": "string" - }, - { - "name": "qty", - "type": "int256", - "internalType": "int256" - }, - { - "name": "unitPrice", - "type": "int256", - "internalType": "int256" - }, - { - "name": "discount", - "type": "int256", - "internalType": "int256" - }, - { - "name": "tax", - "type": "int256", - "internalType": "int256" - }, - { - "name": "amount", - "type": "int256", - "internalType": "int256" - } - ] - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getMySentInvoices", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "tuple[]", - "internalType": "struct Chainvoice.InvoiceDetails[]", - "components": [ - { - "name": "id", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "from", - "type": "address", - "internalType": "address" - }, - { - "name": "dueDate", - "type": "string", - "internalType": "string" - }, - { - "name": "issueDate", - "type": "string", - "internalType": "string" - }, - { - "name": "user", - "type": "tuple", - "internalType": "struct Chainvoice.UserDetails", - "components": [ - { - "name": "fname", - "type": "string", - "internalType": "string" - }, - { - "name": "lname", - "type": "string", - "internalType": "string" - }, - { - "name": "email", - "type": "string", - "internalType": "string" - }, - { - "name": "country", - "type": "string", - "internalType": "string" - }, - { - "name": "city", - "type": "string", - "internalType": "string" - }, - { - "name": "postalcode", - "type": "string", - "internalType": "string" - } - ] - }, - { - "name": "to", - "type": "address", - "internalType": "address" - }, - { - "name": "client", - "type": "tuple", - "internalType": "struct Chainvoice.UserDetails", - "components": [ - { - "name": "fname", - "type": "string", - "internalType": "string" - }, - { - "name": "lname", - "type": "string", - "internalType": "string" - }, - { - "name": "email", - "type": "string", - "internalType": "string" - }, - { - "name": "country", - "type": "string", - "internalType": "string" - }, - { - "name": "city", - "type": "string", - "internalType": "string" - }, - { - "name": "postalcode", - "type": "string", - "internalType": "string" - } - ] - }, - { - "name": "amountDue", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "isPaid", - "type": "bool", - "internalType": "bool" - } - ] - }, - { - "name": "", - "type": "tuple[][]", - "internalType": "struct Chainvoice.ItemData[][]", - "components": [ - { - "name": "description", - "type": "string", - "internalType": "string" - }, - { - "name": "qty", - "type": "int256", - "internalType": "int256" - }, - { - "name": "unitPrice", - "type": "int256", - "internalType": "int256" - }, - { - "name": "discount", - "type": "int256", - "internalType": "int256" - }, - { - "name": "tax", - "type": "int256", - "internalType": "int256" - }, - { - "name": "amount", - "type": "int256", - "internalType": "int256" - } - ] - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "getTreasuryAddress", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "invoices", - "inputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "id", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "from", - "type": "address", - "internalType": "address" - }, - { - "name": "dueDate", - "type": "string", - "internalType": "string" - }, - { - "name": "issueDate", - "type": "string", - "internalType": "string" - }, - { - "name": "user", - "type": "tuple", - "internalType": "struct Chainvoice.UserDetails", - "components": [ - { - "name": "fname", - "type": "string", - "internalType": "string" - }, - { - "name": "lname", - "type": "string", - "internalType": "string" - }, - { - "name": "email", - "type": "string", - "internalType": "string" - }, - { - "name": "country", - "type": "string", - "internalType": "string" - }, - { - "name": "city", - "type": "string", - "internalType": "string" - }, - { - "name": "postalcode", - "type": "string", - "internalType": "string" - } - ] - }, - { - "name": "to", - "type": "address", - "internalType": "address" - }, - { - "name": "client", - "type": "tuple", - "internalType": "struct Chainvoice.UserDetails", - "components": [ - { - "name": "fname", - "type": "string", - "internalType": "string" - }, - { - "name": "lname", - "type": "string", - "internalType": "string" - }, - { - "name": "email", - "type": "string", - "internalType": "string" - }, - { - "name": "country", - "type": "string", - "internalType": "string" - }, - { - "name": "city", - "type": "string", - "internalType": "string" - }, - { - "name": "postalcode", - "type": "string", - "internalType": "string" - } - ] - }, - { - "name": "amountDue", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "isPaid", - "type": "bool", - "internalType": "bool" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "itemDatas", - "inputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "description", - "type": "string", - "internalType": "string" - }, - { - "name": "qty", - "type": "int256", - "internalType": "int256" - }, - { - "name": "unitPrice", - "type": "int256", - "internalType": "int256" - }, - { - "name": "discount", - "type": "int256", - "internalType": "int256" - }, - { - "name": "tax", - "type": "int256", - "internalType": "int256" - }, - { - "name": "amount", - "type": "int256", - "internalType": "int256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "owner", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "payInvoice", - "inputs": [ - { - "name": "invoiceId", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [], - "stateMutability": "payable" - }, - { - "type": "function", - "name": "receivedInvoices", - "inputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - }, - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "sentInvoices", - "inputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - }, - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "setFeeAmount", - "inputs": [ - { - "name": "fee", - "type": "uint16", - "internalType": "uint16" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "setTreasuryAddress", - "inputs": [ - { - "name": "add", - "type": "address", - "internalType": "address" - } - ], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "function", - "name": "treasuryAddress", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "usdToNativeCurrencyConversion", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, - { - "type": "function", - "name": "withdraw", - "inputs": [], - "outputs": [], - "stateMutability": "nonpayable" - }, - { - "type": "event", - "name": "InvoiceCreated", - "inputs": [ - { - "name": "id", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - }, - { - "name": "from", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "to", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "amountDue", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - }, - { - "type": "event", - "name": "InvoicePaid", - "inputs": [ - { - "name": "id", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - }, - { - "name": "from", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "to", - "type": "address", - "indexed": true, - "internalType": "address" - }, - { - "name": "amountPaid", - "type": "uint256", - "indexed": false, - "internalType": "uint256" - } - ], - "anonymous": false - } - ] \ No newline at end of file + { + type: "constructor", + inputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "accumulatedFees", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "createInvoice", + inputs: [ + { + name: "to", + type: "address", + internalType: "address", + }, + { + name: "amountDue", + type: "uint256", + internalType: "uint256", + }, + { + name: "encryptedInvoiceData", + type: "string", + internalType: "string", + }, + { + name: "encryptedHash", + type: "string", + internalType: "string", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "fee", + inputs: [], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getInvoice", + inputs: [ + { + name: "invoiceId", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "tuple", + internalType: "struct Chainvoice.InvoiceDetails", + components: [ + { + name: "id", + type: "uint256", + internalType: "uint256", + }, + { + name: "from", + type: "address", + internalType: "address", + }, + { + name: "to", + type: "address", + internalType: "address", + }, + { + name: "amountDue", + type: "uint256", + internalType: "uint256", + }, + { + name: "isPaid", + type: "bool", + internalType: "bool", + }, + { + name: "encryptedInvoiceData", + type: "string", + internalType: "string", + }, + { + name: "encryptedHash", + type: "string", + internalType: "string", + }, + ], + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getReceivedInvoices", + inputs: [ + { + name: "user", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "tuple[]", + internalType: "struct Chainvoice.InvoiceDetails[]", + components: [ + { + name: "id", + type: "uint256", + internalType: "uint256", + }, + { + name: "from", + type: "address", + internalType: "address", + }, + { + name: "to", + type: "address", + internalType: "address", + }, + { + name: "amountDue", + type: "uint256", + internalType: "uint256", + }, + { + name: "isPaid", + type: "bool", + internalType: "bool", + }, + { + name: "encryptedInvoiceData", + type: "string", + internalType: "string", + }, + { + name: "encryptedHash", + type: "string", + internalType: "string", + }, + ], + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "getSentInvoices", + inputs: [ + { + name: "user", + type: "address", + internalType: "address", + }, + ], + outputs: [ + { + name: "", + type: "tuple[]", + internalType: "struct Chainvoice.InvoiceDetails[]", + components: [ + { + name: "id", + type: "uint256", + internalType: "uint256", + }, + { + name: "from", + type: "address", + internalType: "address", + }, + { + name: "to", + type: "address", + internalType: "address", + }, + { + name: "amountDue", + type: "uint256", + internalType: "uint256", + }, + { + name: "isPaid", + type: "bool", + internalType: "bool", + }, + { + name: "encryptedInvoiceData", + type: "string", + internalType: "string", + }, + { + name: "encryptedHash", + type: "string", + internalType: "string", + }, + ], + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "invoices", + inputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "id", + type: "uint256", + internalType: "uint256", + }, + { + name: "from", + type: "address", + internalType: "address", + }, + { + name: "to", + type: "address", + internalType: "address", + }, + { + name: "amountDue", + type: "uint256", + internalType: "uint256", + }, + { + name: "isPaid", + type: "bool", + internalType: "bool", + }, + { + name: "encryptedInvoiceData", + type: "string", + internalType: "string", + }, + { + name: "encryptedHash", + type: "string", + internalType: "string", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "owner", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "payInvoice", + inputs: [ + { + name: "invoiceId", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "payable", + }, + { + type: "function", + name: "receivedInvoices", + inputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "sentInvoices", + inputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [ + { + name: "", + type: "uint256", + internalType: "uint256", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "setFeeAmount", + inputs: [ + { + name: "_fee", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "setTreasuryAddress", + inputs: [ + { + name: "newTreasury", + type: "address", + internalType: "address", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "function", + name: "treasuryAddress", + inputs: [], + outputs: [ + { + name: "", + type: "address", + internalType: "address", + }, + ], + stateMutability: "view", + }, + { + type: "function", + name: "withdrawFees", + inputs: [], + outputs: [], + stateMutability: "nonpayable", + }, + { + type: "event", + name: "InvoiceCreated", + inputs: [ + { + name: "id", + type: "uint256", + indexed: true, + internalType: "uint256", + }, + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: true, + internalType: "address", + }, + ], + anonymous: false, + }, + { + type: "event", + name: "InvoicePaid", + inputs: [ + { + name: "id", + type: "uint256", + indexed: true, + internalType: "uint256", + }, + { + name: "from", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "to", + type: "address", + indexed: true, + internalType: "address", + }, + { + name: "amount", + type: "uint256", + indexed: false, + internalType: "uint256", + }, + ], + anonymous: false, + }, +]; \ No newline at end of file diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index 9a113606..eb679cfa 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -1,37 +1,41 @@ -import Paper from '@mui/material/Paper'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TablePagination from '@mui/material/TablePagination'; -import TableRow from '@mui/material/TableRow'; -import { ChainvoiceABI } from '@/contractsABI/ChainvoiceABI'; -import { BrowserProvider, Contract, ethers } from 'ethers' -import React, { useEffect, useState } from 'react' -import { useAccount, useWalletClient } from 'wagmi' -import DescriptionIcon from '@mui/icons-material/Description'; -import SwipeableDrawer from '@mui/material/SwipeableDrawer'; -import Button from '@mui/material/Button'; +import Paper from "@mui/material/Paper"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TablePagination from "@mui/material/TablePagination"; +import TableRow from "@mui/material/TableRow"; +import { ChainvoiceABI } from "@/contractsABI/ChainvoiceABI"; +import { BrowserProvider, Contract, ethers } from "ethers"; +import React, { useEffect, useState } from "react"; +import { useAccount, useWalletClient } from "wagmi"; +import DescriptionIcon from "@mui/icons-material/Description"; +import SwipeableDrawer from "@mui/material/SwipeableDrawer"; import { useRef } from "react"; -import jsPDF from "jspdf"; import html2canvas from "html2canvas"; +import { LitNodeClient } from "@lit-protocol/lit-node-client"; +import { decryptToString } from "@lit-protocol/encryption/src/lib/encryption.js"; +import { LIT_ABILITY, LIT_NETWORK } from "@lit-protocol/constants"; +import { + createSiweMessageWithRecaps, + generateAuthSig, + LitAccessControlConditionResource, +} from "@lit-protocol/auth-helpers"; const columns = [ - { id: 'fname', label: 'First Name', minWidth: 100 }, - { id: 'lname', label: 'Last Name', minWidth: 100 }, - { id: 'to', label: 'Address', minWidth: 200 }, - { id: 'email', label: 'Email', minWidth: 170 }, + { id: "fname", label: "First Name", minWidth: 100 }, + { id: "lname", label: "Last Name", minWidth: 100 }, + { id: "to", label: "Sender's address", minWidth: 200 }, + { id: "email", label: "Email", minWidth: 170 }, // { id: 'country', label: 'Country', minWidth: 100 }, - { id: 'amountDue', label: 'Total Amount', minWidth: 100, align: 'right' }, - { id: 'isPaid', label: 'Status', minWidth: 100 }, - { id: 'detail', label: 'Detail Invoice', minWidth: 100 }, - { id: 'pay', label: 'Pay / Paid' , minWidth: 100 }, + { id: "amountDue", label: "Total Amount", minWidth: 100, align: "right" }, + { id: "isPaid", label: "Status", minWidth: 100 }, + { id: "detail", label: "Detail Invoice", minWidth: 100 }, + { id: "pay", label: "Pay / Paid", minWidth: 100 }, ]; - function ReceivedInvoice() { - const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); @@ -48,59 +52,208 @@ function ReceivedInvoice() { const { address } = useAccount(); const [loading, setLoading] = useState(false); const [receivedInvoices, setReceivedInvoice] = useState([]); - const [invoiceItems, setInvoiceItems] = useState([]); - const [fee, setFee] = useState(0); + const [litReady, setLitReady] = useState(false); + const litClientRef = useRef(null); + useEffect(() => { - if (!walletClient) return; + const initLit = async () => { + try { + setLoading(true); + if (!litClientRef.current) { + const client = new LitNodeClient({ + litNetwork: LIT_NETWORK.DatilDev, + debug: false, + }); + await client.connect(); + litClientRef.current = client; + setLitReady(true); + console.log(litClientRef.current); + } + } catch (error) { + console.error("Error while lit client initialization:", error); + } finally { + setLoading(false); + } + }; + initLit(); + }, []); + + useEffect(() => { + if (!walletClient || !litReady) return; + const fetchReceivedInvoices = async () => { try { setLoading(true); - if (!walletClient) return; + const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); - const contract = new Contract(import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, signer); - console.log(address); + + // 1. Setup Lit Node + + const litNodeClient = litClientRef.current; + if (!litNodeClient) { + alert("Lit client not initialized"); + return; + } + + // 2. Get data from contract + const contract = new Contract( + import.meta.env.VITE_CONTRACT_ADDRESS, + ChainvoiceABI, + signer + ); + const res = await contract.getReceivedInvoices(address); - const [invoiceDetails, itemData] = res; - setReceivedInvoice(invoiceDetails); - setInvoiceItems(itemData); - setLoading(false); + console.log("getReceivedInvoices raw response:", res); + + const decryptedInvoices = []; + + for (const invoice of res) { + const id = invoice[0]; + const from = invoice[1].toLowerCase(); + const to = invoice[2].toLowerCase(); + const isPaid = invoice[4]; + const encryptedStringBase64 = invoice[5]; // encryptedData + const dataToEncryptHash = invoice[6]; + + if (!encryptedStringBase64 || !dataToEncryptHash) continue; + const currentUserAddress = address.toLowerCase(); + if (currentUserAddress !== from && currentUserAddress !== to) { + console.warn( + `User ${currentUserAddress} not authorized to decrypt invoice ${id}` + ); + continue; + } + const ciphertext = atob(encryptedStringBase64); + const accessControlConditions = [ + { + contractAddress: "", + standardContractType: "", + chain: "ethereum", + method: "", + parameters: [":userAddress"], + returnValueTest: { + comparator: "=", + value: invoice[1].toLowerCase(), // from + }, + }, + { operator: "or" }, + { + contractAddress: "", + standardContractType: "", + chain: "ethereum", + method: "", + parameters: [":userAddress"], + returnValueTest: { + comparator: "=", + value: invoice[2].toLowerCase(), // to + }, + }, + ]; + + const sessionSigs = await litNodeClient.getSessionSigs({ + chain: "ethereum", + resourceAbilityRequests: [ + { + resource: new LitAccessControlConditionResource("*"), + ability: LIT_ABILITY.AccessControlConditionDecryption, + }, + ], + authNeededCallback: async ({ + uri, + expiration, + resourceAbilityRequests, + }) => { + const nonce = await litNodeClient.getLatestBlockhash(); + const toSign = await createSiweMessageWithRecaps({ + uri, + expiration, + resources: resourceAbilityRequests, + walletAddress: address, + nonce, + litNodeClient, + }); + return await generateAuthSig({ signer, toSign }); + }, + }); + + const decryptedString = await decryptToString( + { + accessControlConditions, + chain: "ethereum", + ciphertext, + dataToEncryptHash, + sessionSigs, + }, + litNodeClient + ); + + const parsed = JSON.parse(decryptedString); + parsed["id"] = id; + parsed["isPaid"] = isPaid; + decryptedInvoices.push(parsed); + } + + console.log("decrypted : ", decryptedInvoices); + setReceivedInvoice(decryptedInvoices); + const fee = await contract.fee(); - console.log(ethers.formatUnits(fee)); setFee(fee); - console.log(res); } catch (error) { - console.log(error); + console.error("Decryption error:", error); + alert("Failed to fetch or decrypt received invoices."); + } finally { + setLoading(false); } - } + }; + fetchReceivedInvoices(); - }, [walletClient]); + console.log("invoices : ", receivedInvoices); + }, [walletClient, litReady]); const payInvoice = async (id, amountDue) => { try { if (!walletClient) return; const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); - const contract = new Contract(import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, signer); - console.log(amountDue); + const contract = new Contract( + import.meta.env.VITE_CONTRACT_ADDRESS, + ChainvoiceABI, + signer + ); + console.log(ethers.parseUnits(String(amountDue), 18)); const fee = await contract.fee(); console.log(fee); - const res = await contract.payInvoice(ethers.toBigInt(id), { - value: amountDue + fee + const amountDueInWei = ethers.parseUnits(String(amountDue), 18); + const feeInWei = BigInt(fee); + const total = amountDueInWei + feeInWei; + + const res = await contract.payInvoice(BigInt(id), { + value: total, }); } catch (error) { console.log(error); } - } + }; - const [drawerState, setDrawerState] = useState({ open: false, selectedInvoice: null }); + const [drawerState, setDrawerState] = useState({ + open: false, + selectedInvoice: null, + }); const toggleDrawer = (invoice) => (event) => { - if (event && event.type === "keydown" && (event.key === "Tab" || event.key === "Shift")) { + if ( + event && + event.type === "keydown" && + (event.key === "Tab" || event.key === "Shift") + ) { return; } - setDrawerState({ open: !drawerState.open, selectedInvoice: invoice || null }); + setDrawerState({ + open: !drawerState.open, + selectedInvoice: invoice || null, + }); }; const contentRef = useRef(); @@ -134,20 +287,37 @@ function ReceivedInvoice() {

Received Invoice Request

Pay to your client request

- + {loading ? (

loading........

- ) : receivedInvoices.length > 0 ? ( + ) : receivedInvoices?.length > 0 ? ( <> - +
- + {columns.map((column) => ( {column.label} @@ -158,41 +328,69 @@ function ReceivedInvoice() { {receivedInvoices .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) .map((invoice, index) => ( - {columns.map((column) => { - console.log(invoice.to); - const value = invoice.user[column.id] || ''; - if (column.id === 'to') { + const value = invoice?.user[column.id] || ""; + if (column.id === "to") { return ( - - {invoice.to.substring(0, 10)}...{invoice.to.substring(invoice.to.length - 10)} + + {invoice.user?.address + ? `${invoice.user.address.substring( + 0, + 10 + )}...${invoice.user.address.substring( + invoice.user.address.length - 10 + )}` + : "N/A"} ); } - if (column.id === 'amountDue') { + if (column.id === "amountDue") { return ( - - {ethers.formatUnits(invoice.amountDue)} ETH + + {/* {ethers.formatUnits(invoice.amountDue)} ETH */} + {invoice.amountDue} ETH ); } - if (column.id === 'isPaid') { + if (column.id === "isPaid") { return ( - + - ) + ); } if (column.id === "detail") { return ( - + ); } - if(column.id==='pay' && invoice.isPaid) { + if (column.id === "pay" && invoice.isPaid) { return ( - +
@@ -313,27 +545,42 @@ function ReceivedInvoice() { - { - invoiceItems[drawerState.selectedInvoice.id].map((item, index) => ( - - - - - - - - - - - )) - } + {drawerState.selectedInvoice?.items?.map((item, index) => ( + + + + + + + + + + + ))}
Amount
{item.description}{item.qty.toString()}{ethers.formatUnits(item.unitPrice)}{item.discount.toString()}{item.tax.toString()} - {ethers.formatUnits(item.amount)} ETH -
{item.description}{item.qty.toString()} + {/* {ethers.formatUnits(item.unitPrice)} */} + {item.unitPrice} + {item.discount.toString()}{item.tax.toString()} + {item.amount} ETH + {/* {ethers.formatUnits(item.amount)} ETH */} +
-

Fee for invoice pay : {ethers.formatUnits(fee)} ETH

-

Amount: {ethers.formatUnits(drawerState.selectedInvoice.amountDue)} ETH

-

Total Amount: {ethers.formatUnits(drawerState.selectedInvoice.amountDue+fee)} ETH

+

+ {/* Fee for invoice pay : {ethers.formatUnits(fee)} ETH */} + Fee for invoice pay : {ethers.formatUnits(fee)} ETH +

+

+ {" "} + Amount: {drawerState.selectedInvoice.amountDue}{" "} + {/* {ethers.formatUnits(drawerState.selectedInvoice.amountDue)}{" "} */} + ETH +

+

+ Total Amount:{" "} + {parseFloat(drawerState.selectedInvoice.amountDue) + + parseFloat(ethers.formatUnits(fee))}{" "} + ETH +

Powered by

@@ -341,11 +588,10 @@ function ReceivedInvoice() {
- ) - } - + )} +
- ) + ); } -export default ReceivedInvoice \ No newline at end of file +export default ReceivedInvoice; diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index 2455da2d..277de25b 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -1,38 +1,42 @@ -import Paper from '@mui/material/Paper'; -import Table from '@mui/material/Table'; -import TableBody from '@mui/material/TableBody'; -import TableCell from '@mui/material/TableCell'; -import TableContainer from '@mui/material/TableContainer'; -import TableHead from '@mui/material/TableHead'; -import TablePagination from '@mui/material/TablePagination'; -import TableRow from '@mui/material/TableRow'; -import { BrowserProvider, Contract, ethers } from 'ethers'; -import React, { useEffect, useState, Fragment } from 'react'; -import { useAccount, useWalletClient } from 'wagmi'; -import { ChainvoiceABI } from '../contractsABI/ChainvoiceABI'; -import DescriptionIcon from '@mui/icons-material/Description'; - -import SwipeableDrawer from '@mui/material/SwipeableDrawer'; -import Button from '@mui/material/Button'; -import { useRef } from "react"; -import jsPDF from "jspdf"; +import Paper from "@mui/material/Paper"; +import Table from "@mui/material/Table"; +import TableBody from "@mui/material/TableBody"; +import TableCell from "@mui/material/TableCell"; +import TableContainer from "@mui/material/TableContainer"; +import TableHead from "@mui/material/TableHead"; +import TablePagination from "@mui/material/TablePagination"; +import TableRow from "@mui/material/TableRow"; +import { BrowserProvider, Contract, ethers } from "ethers"; +import React, { useEffect, useState, useRef } from "react"; +import { useAccount, useWalletClient } from "wagmi"; +import { ChainvoiceABI } from "../contractsABI/ChainvoiceABI"; +import DescriptionIcon from "@mui/icons-material/Description"; + +import SwipeableDrawer from "@mui/material/SwipeableDrawer"; import html2canvas from "html2canvas"; +import { LitNodeClient } from "@lit-protocol/lit-node-client"; +import { decryptToString } from "@lit-protocol/encryption/src/lib/encryption.js"; +import { LIT_ABILITY, LIT_NETWORK } from "@lit-protocol/constants"; +import { + createSiweMessageWithRecaps, + generateAuthSig, + LitAccessControlConditionResource, +} from "@lit-protocol/auth-helpers"; + const columns = [ - { id: 'fname', label: 'First Name', minWidth: 100 }, - { id: 'lname', label: 'Last Name', minWidth: 100 }, - { id: 'to', label: 'Address', minWidth: 200 }, - { id: 'email', label: 'Email', minWidth: 170 }, - { id: 'country', label: 'Country', minWidth: 100 }, - { id: 'city', label: 'City', minWidth: 100 }, - { id: 'amountDue', label: 'Total Amount', minWidth: 100, align: 'right' }, - { id: 'isPaid', label: 'Status', minWidth: 100 }, - { id: 'detail', label: 'Detail Invoice', minWidth: 100 } + { id: "fname", label: "First Name", minWidth: 100 }, + { id: "lname", label: "Last Name", minWidth: 100 }, + { id: "to", label: "Receiver's Address", minWidth: 200 }, + { id: "email", label: "Email", minWidth: 170 }, + { id: "country", label: "Country", minWidth: 100 }, + { id: "city", label: "City", minWidth: 100 }, + { id: "amountDue", label: "Total Amount", minWidth: 100, align: "right" }, + { id: "isPaid", label: "Status", minWidth: 100 }, + { id: "detail", label: "Detail Invoice", minWidth: 100 }, ]; - function SentInvoice() { - const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); const handleChangePage = (event, newPage) => { @@ -48,38 +52,192 @@ function SentInvoice() { const [invoiceItems, setInvoiceItems] = useState([]); const [loading, setLoading] = useState(false); const [fee, setFee] = useState(0); - const {address}=useAccount(); + const { address } = useAccount(); + const [litReady, setLitReady] = useState(false); + const litClientRef = useRef(null); + + useEffect(() => { + const initLit = async () => { + try { + setLoading(true); + if (!litClientRef.current) { + const client = new LitNodeClient({ + litNetwork: LIT_NETWORK.DatilDev, + debug: false, + }); + await client.connect(); + litClientRef.current = client; + setLitReady(true); + console.log(litClientRef.current); + } + } catch (error) { + console.error("Error while lit client initialization:", error); + } finally { + setLoading(false); + } + }; + initLit(); + }, []); + useEffect(() => { - if (!walletClient) return; + if (!walletClient || !litReady) return; + const fetchSentInvoices = async () => { try { setLoading(true); - if (!walletClient) return; + + // 1. Setup signer const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); - const contract = new Contract(import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, signer); + + // 2. Connect to Lit Node + + const litNodeClient = litClientRef.current; + if (!litNodeClient) { + alert("Lit client not initialized"); + return; + } + + // 3. Contract call to get encrypted invoice + const contract = new Contract( + import.meta.env.VITE_CONTRACT_ADDRESS, + ChainvoiceABI, + signer + ); + const res = await contract.getSentInvoices(address); - const [invoiceDetails, itemData] = res; - setSentInvoices(invoiceDetails); - setInvoiceItems(itemData); + console.log(res); + + if (!res || !Array.isArray(res) || res.length === 0) { + console.warn("No invoices found."); + setSentInvoices([]); + setInvoiceItems([]); + setLoading(false); + return; + } + + const decryptedInvoices = []; + + for (const invoice of res) { + const id = invoice[0]; + const from = invoice[1].toLowerCase(); + const to = invoice[2].toLowerCase(); + const isPaid = invoice[4]; + const encryptedStringBase64 = invoice[5]; // encryptedData + const dataToEncryptHash = invoice[6]; + + if (!encryptedStringBase64 || !dataToEncryptHash) continue; + const currentUserAddress = address.toLowerCase(); + if (currentUserAddress !== from && currentUserAddress !== to) { + console.warn( + `User ${currentUserAddress} not authorized to decrypt invoice ${id}` + ); + continue; + } + const ciphertext = atob(encryptedStringBase64); + const accessControlConditions = [ + { + contractAddress: "", + standardContractType: "", + chain: "ethereum", + method: "", + parameters: [":userAddress"], + returnValueTest: { + comparator: "=", + value: invoice[1].toLowerCase(), // from + }, + }, + { operator: "or" }, + { + contractAddress: "", + standardContractType: "", + chain: "ethereum", + method: "", + parameters: [":userAddress"], + returnValueTest: { + comparator: "=", + value: invoice[2].toLowerCase(), // to + }, + }, + ]; + + const sessionSigs = await litNodeClient.getSessionSigs({ + chain: "ethereum", + resourceAbilityRequests: [ + { + resource: new LitAccessControlConditionResource("*"), + ability: LIT_ABILITY.AccessControlConditionDecryption, + }, + ], + authNeededCallback: async ({ + uri, + expiration, + resourceAbilityRequests, + }) => { + const nonce = await litNodeClient.getLatestBlockhash(); + const toSign = await createSiweMessageWithRecaps({ + uri, + expiration, + resources: resourceAbilityRequests, + walletAddress: address, + nonce, + litNodeClient, + }); + return await generateAuthSig({ signer, toSign }); + }, + }); + + const decryptedString = await decryptToString( + { + accessControlConditions, + chain: "ethereum", + ciphertext, + dataToEncryptHash, + sessionSigs, + }, + litNodeClient + ); + + const parsed = JSON.parse(decryptedString); + parsed["id"] = id; + parsed["isPaid"] = isPaid; + decryptedInvoices.push(parsed); + } + + setSentInvoices(decryptedInvoices); const fee = await contract.fee(); - console.log(ethers.formatUnits(fee)); setFee(fee); - setLoading(false); } catch (error) { - alert(error); + console.error("Decryption error:", error); + alert("Failed to decrypt invoice."); + } finally { + setLoading(false); } - } + }; + fetchSentInvoices(); - }, [walletClient]); - const [drawerState, setDrawerState] = useState({ open: false, selectedInvoice: null }); + console.log("invoices : ", sentInvoices); + }, [walletClient, litReady]); + + const [drawerState, setDrawerState] = useState({ + open: false, + selectedInvoice: null, + }); const toggleDrawer = (invoice) => (event) => { - if (event && event.type === "keydown" && (event.key === "Tab" || event.key === "Shift")) { + console.log(invoice); + if ( + event && + event.type === "keydown" && + (event.key === "Tab" || event.key === "Shift") + ) { return; } - setDrawerState({ open: !drawerState.open, selectedInvoice: invoice || null }); + setDrawerState({ + open: !drawerState.open, + selectedInvoice: invoice || null, + }); }; const contentRef = useRef(); @@ -113,20 +271,37 @@ function SentInvoice() { return (

Your Sent Invoice Request

- + {loading ? (

Loading...

- ) : sentInvoices.length > 0 ? ( + ) : sentInvoices?.length > 0 ? ( <> - +
{columns.map((column) => ( {column.label} @@ -134,55 +309,108 @@ function SentInvoice() { - {sentInvoices.length > 0 && sentInvoices.slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage).map((invoice, index) => ( - - {columns.map((column) => { - const value = invoice?.client[column.id]; - if (column.id === "to") { - return ( - - {invoice.to - ? `${invoice.to.substring(0, 10)}...${invoice.to.substring(invoice.to.length - 10)}` - : "N/A"} - - ); - } - if (column.id === "amountDue") { - return ( - - {ethers.formatUnits(invoice.amountDue)} ETH - - ); - } - if (column.id === "isPaid") { - return ( - - - - ); - } - if (column.id === "detail") { - return ( - - + + ); + } + if (column.id === "detail") { + return ( + + + + ); + } + return ( + - - - - ); - } - return ( - - {value} - - ); - })} - - ))} + {value} + + ); + })} + + ))}
@@ -207,9 +435,10 @@ function SentInvoice() { "& .MuiInputBase-root": { color: "white", }, - "& .MuiTablePagination-selectLabel, & .MuiTablePagination-displayedRows": { - color: "white", - }, + "& .MuiTablePagination-selectLabel, & .MuiTablePagination-displayedRows": + { + color: "white", + }, }} /> @@ -218,35 +447,54 @@ function SentInvoice() { )}
- + {drawerState.selectedInvoice && (
none
-

Issued by {drawerState.selectedInvoice.issueDate}

-

Payment Due by {drawerState.selectedInvoice.dueDate}

+

+ Issued by {drawerState.selectedInvoice.issueDate} +

+

+ Payment Due by {drawerState.selectedInvoice.dueDate} +

-

Invoice #{drawerState.selectedInvoice.id}

+

+ Invoice # {drawerState.selectedInvoice.id.toString()} +

From

-

{drawerState.selectedInvoice.from}

+

+ {drawerState.selectedInvoice.user.address} +

{`${drawerState.selectedInvoice.user.fname} ${drawerState.selectedInvoice.user.lname}`}

-

{drawerState.selectedInvoice.user.email}

+

+ {drawerState.selectedInvoice.user.email} +

{`${drawerState.selectedInvoice.user.city}, ${drawerState.selectedInvoice.user.country} (${drawerState.selectedInvoice.user.postalcode})`}

Billed to

-

{drawerState.selectedInvoice.from}

+

+ {drawerState.selectedInvoice.client.address} +

{`${drawerState.selectedInvoice.client.fname} ${drawerState.selectedInvoice.client.lname}`}

-

{drawerState.selectedInvoice.client.email}

+

+ {drawerState.selectedInvoice.client.email} +

{`${drawerState.selectedInvoice.client.city}, ${drawerState.selectedInvoice.client.country} (${drawerState.selectedInvoice.client.postalcode})`}

@@ -260,27 +508,41 @@ function SentInvoice() { - { - invoiceItems[drawerState.selectedInvoice.id].map((item, index) => ( - - - - - - - - - - - )) - } + {drawerState.selectedInvoice?.items?.map((item, index) => ( + + + + + + + + + + + ))}
Amount
{item.description}{item.qty.toString()}{ethers.formatUnits(item.unitPrice)}{item.discount.toString()}{item.tax.toString()} - {ethers.formatUnits(item.amount)} -
{item.description}{item.qty.toString()} + {/* {ethers.formatUnits(item.unitPrice)} */} + {item.unitPrice} + {item.discount.toString()}{item.tax.toString()} + {/* {ethers.formatUnits(item.amount)} */} + {item.amount} +
-

Fee for invoice pay : {ethers.formatUnits(fee)} ETH

-

Amount: {ethers.formatUnits(drawerState.selectedInvoice.amountDue)} ETH

-

Total Amount: {ethers.formatUnits(drawerState.selectedInvoice.amountDue + fee)} ETH

+

+ Fee for invoice pay : {ethers.formatUnits(fee)} ETH +

+

+ {" "} + Amount:{" "} + {/* {ethers.formatUnits(drawerState.selectedInvoice.amountDue)}{" "} */} + {drawerState.selectedInvoice.amountDue} ETH +

+

+ Total Amount:{" "} + {parseFloat(drawerState.selectedInvoice.amountDue) + + parseFloat(ethers.formatUnits(fee))}{" "} + ETH +

Powered by

@@ -288,11 +550,10 @@ function SentInvoice() {
- ) - } -
-
- ) + )} + +
+ ); } -export default SentInvoice \ No newline at end of file +export default SentInvoice; diff --git a/frontend/vite.config.js b/frontend/vite.config.js index f6d8b315..c8482d2d 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,7 +1,7 @@ import path from "path" import react from "@vitejs/plugin-react" import { defineConfig } from "vite" -import { nodePolyfills } from "vite-plugin-node-polyfills" +import { nodePolyfills } from 'vite-plugin-node-polyfills' export default defineConfig({ plugins: [react(),nodePolyfills()],