From 505f6d98c8e56dd890aa14145c19abb8ddaba225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Wed, 4 Jun 2025 14:47:53 +0530 Subject: [PATCH 01/39] - --- .gitmodules | 6 +++ contracts/lib/chainlink-brownie-contracts | 1 + contracts/lib/forge-std | 1 + frontend/.env | 2 +- frontend/src/App.css | 0 frontend/src/App.jsx | 2 +- frontend/src/contractsABI/ChainvoiceABI.js | 58 ++++++---------------- 7 files changed, 26 insertions(+), 44 deletions(-) create mode 100644 .gitmodules create mode 160000 contracts/lib/chainlink-brownie-contracts create mode 160000 contracts/lib/forge-std delete mode 100644 frontend/src/App.css 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/contracts/lib/chainlink-brownie-contracts b/contracts/lib/chainlink-brownie-contracts new file mode 160000 index 00000000..67887b84 --- /dev/null +++ b/contracts/lib/chainlink-brownie-contracts @@ -0,0 +1 @@ +Subproject commit 67887b84d3add02a25ef4145fc014e2f549509da 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/frontend/.env b/frontend/.env index e761d5b0..8a6d8689 100644 --- a/frontend/.env +++ b/frontend/.env @@ -5,7 +5,7 @@ ViTE_EXPLORER=https://sepolia.etherscan.io/ # -VITE_CONTRACT_ADDRESS=0x11B4894d1D723FB24310f28E58796d452eBb5aDe +VITE_CONTRACT_ADDRESS=0x6E7362a86192F0A0A9AF5F0804EcEEDF92dDb0e7 VITE_PRICEFEED_ADDRESS=0xAE26bD30c88Cc8bcB9b9Be97De7Ad33315fb3D65 # 0x00eca3bd631b3623680d9a511bd89dd5fc965dea79104b02cf6d74ad8eaf31a6 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..939f0854 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -14,7 +14,7 @@ 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/contractsABI/ChainvoiceABI.js b/frontend/src/contractsABI/ChainvoiceABI.js index 4c3976a3..07fb7160 100644 --- a/frontend/src/contractsABI/ChainvoiceABI.js +++ b/frontend/src/contractsABI/ChainvoiceABI.js @@ -1,13 +1,7 @@ export const ChainvoiceABI = [ { "type": "constructor", - "inputs": [ - { - "name": "_priceFeed", - "type": "address", - "internalType": "address" - } - ], + "inputs": [], "stateMutability": "nonpayable" }, { @@ -164,7 +158,7 @@ export const ChainvoiceABI = [ }, { "type": "function", - "name": "feeAmountInUSD", + "name": "fee", "inputs": [], "outputs": [ { @@ -177,10 +171,10 @@ export const ChainvoiceABI = [ }, { "type": "function", - "name": "getMyReceivedInvoices", + "name": "getReceivedInvoices", "inputs": [ { - "name": "add", + "name": "_address", "type": "address", "internalType": "address" } @@ -344,8 +338,14 @@ export const ChainvoiceABI = [ }, { "type": "function", - "name": "getMySentInvoices", - "inputs": [], + "name": "getSentInvoices", + "inputs": [ + { + "name": "_address", + "type": "address", + "internalType": "address" + } + ], "outputs": [ { "name": "", @@ -503,19 +503,6 @@ export const ChainvoiceABI = [ ], "stateMutability": "view" }, - { - "type": "function", - "name": "getTreasuryAddress", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "address", - "internalType": "address" - } - ], - "stateMutability": "view" - }, { "type": "function", "name": "invoices", @@ -767,9 +754,9 @@ export const ChainvoiceABI = [ "name": "setFeeAmount", "inputs": [ { - "name": "fee", - "type": "uint16", - "internalType": "uint16" + "name": "_fee", + "type": "uint256", + "internalType": "uint256" } ], "outputs": [], @@ -780,7 +767,7 @@ export const ChainvoiceABI = [ "name": "setTreasuryAddress", "inputs": [ { - "name": "add", + "name": "newTreauserAdd", "type": "address", "internalType": "address" } @@ -801,19 +788,6 @@ export const ChainvoiceABI = [ ], "stateMutability": "view" }, - { - "type": "function", - "name": "usdToNativeCurrencyConversion", - "inputs": [], - "outputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - } - ], - "stateMutability": "view" - }, { "type": "function", "name": "withdraw", From 5a178ba47bd838baa35c4451d873c446ea583db6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Mon, 9 Jun 2025 13:53:28 +0530 Subject: [PATCH 02/39] removed env --- frontend/.env | 18 ------------------ frontend/.env-copy | 0 frontend/.gitignore | 3 ++- 3 files changed, 2 insertions(+), 19 deletions(-) create mode 100644 frontend/.env-copy diff --git a/frontend/.env b/frontend/.env index 8a6d8689..e69de29b 100644 --- a/frontend/.env +++ b/frontend/.env @@ -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=0x6E7362a86192F0A0A9AF5F0804EcEEDF92dDb0e7 -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-copy b/frontend/.env-copy new file mode 100644 index 00000000..e69de29b 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 From 61894ff43bd9662fc00e591ba5304af2e909dcf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Mon, 9 Jun 2025 13:55:14 +0530 Subject: [PATCH 03/39] removed chainlink --- contracts/lib/chainlink-brownie-contracts | 1 - 1 file changed, 1 deletion(-) delete mode 160000 contracts/lib/chainlink-brownie-contracts diff --git a/contracts/lib/chainlink-brownie-contracts b/contracts/lib/chainlink-brownie-contracts deleted file mode 160000 index 67887b84..00000000 --- a/contracts/lib/chainlink-brownie-contracts +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 67887b84d3add02a25ef4145fc014e2f549509da From a6c2fac16c927f1703986e3802cf92c3c21b658b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Mon, 9 Jun 2025 13:57:23 +0530 Subject: [PATCH 04/39] removed chainlink --- frontend/.env | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 frontend/.env diff --git a/frontend/.env b/frontend/.env deleted file mode 100644 index e69de29b..00000000 From 3231f303c837344f3b0e0ad876edb5c6a06767dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Mon, 9 Jun 2025 14:00:02 +0530 Subject: [PATCH 05/39] added env-copy --- frontend/.env-copy | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frontend/.env-copy b/frontend/.env-copy index e69de29b..dd86d5ba 100644 --- a/frontend/.env-copy +++ b/frontend/.env-copy @@ -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 + + From 8a05a038c8890b8bc5162dbca723d0ab8379f8d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Mon, 9 Jun 2025 14:05:28 +0530 Subject: [PATCH 06/39] removed unecessary folder --- contracts/src/MockV3Aggregator.sol | 66 ------------------------------ 1 file changed, 66 deletions(-) delete mode 100644 contracts/src/MockV3Aggregator.sol 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 From 91c19254066c93ffd05ce9523253dd083f7f18c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Mon, 9 Jun 2025 14:24:51 +0530 Subject: [PATCH 07/39] changed app.jsx --- frontend/src/App.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 939f0854..5edf1be7 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -11,7 +11,6 @@ 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' From 86982f753b6d45350f82fda148163e88a5e015fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Mon, 9 Jun 2025 18:26:48 +0530 Subject: [PATCH 08/39] readme update --- README.md | 43 +++++++++++++++++++++++++++++++++++++++++++ contracts/.env-copy | 11 +++++++++++ 2 files changed, 54 insertions(+) create mode 100644 contracts/.env-copy diff --git a/README.md b/README.md index fc05779c..485a9fee 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-copy` to newly created `.env` + + `cp .env-copy .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 src/Contract.sol:Contract \ + --rpc-url $SEPOLIA_RPC_URL \ + --private-key $PRIVATE_KEY \ + --broadcast \ +``` +5. Finally add Contract Address to Frontend `.env` + - Create a new .env file by copying .env-copy: + + `cp frontend/.env-copy 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. \ No newline at end of file diff --git a/contracts/.env-copy b/contracts/.env-copy new file mode 100644 index 00000000..586d96d4 --- /dev/null +++ b/contracts/.env-copy @@ -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 From e7c3f4120944978774d565a365b37ba00e764e30 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Mon, 9 Jun 2025 18:36:06 +0530 Subject: [PATCH 09/39] readme update --- README.md | 10 +++++----- contracts/{.env-copy => .env.example} | 0 frontend/{.env-copy => .env.example} | 0 3 files changed, 5 insertions(+), 5 deletions(-) rename contracts/{.env-copy => .env.example} (100%) rename frontend/{.env-copy => .env.example} (100%) diff --git a/README.md b/README.md index 485a9fee..4984b471 100644 --- a/README.md +++ b/README.md @@ -57,9 +57,9 @@ Prerequisites 1. Create .env File for Secrets - Create a .env in your project root i.e. `contracts/` - - Copy all the varible from `contracts/.env-copy` to newly created `.env` + - Copy all the varible from `contracts/.env.example` to newly created `.env` - `cp .env-copy .env` + `cp .env.example .env` - Assign valid values to the variable. 2. Compile Contract @@ -77,9 +77,9 @@ forge create src/Contract.sol:Contract \ --broadcast \ ``` 5. Finally add Contract Address to Frontend `.env` - - Create a new .env file by copying .env-copy: + - Create a new .env file by copying .env.example: - `cp frontend/.env-copy frontend/.env` + `cp frontend/.env.example frontend/.env` - Open the new .env file and update the variables, especially: `VITE_CONTRACT_ADDRESS=your_deployed_contract_address_here` @@ -87,4 +87,4 @@ forge create src/Contract.sol:Contract \ - Save the .env file. - - Restart your frontend development server so the new environment variables are loaded. \ No newline at end of file + - Restart your frontend development server so the new environment variables are loaded. diff --git a/contracts/.env-copy b/contracts/.env.example similarity index 100% rename from contracts/.env-copy rename to contracts/.env.example diff --git a/frontend/.env-copy b/frontend/.env.example similarity index 100% rename from frontend/.env-copy rename to frontend/.env.example From 77f3b144c048ca499a0d91aad8a23ba9c4f16214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Tue, 17 Jun 2025 01:33:25 +0530 Subject: [PATCH 10/39] updated contract --- contracts/src/Chainvoice.sol | 171 ++++++++++++++--------------------- 1 file changed, 69 insertions(+), 102 deletions(-) diff --git a/contracts/src/Chainvoice.sol b/contracts/src/Chainvoice.sol index 9b25397e..ec9c8cee 100644 --- a/contracts/src/Chainvoice.sol +++ b/contracts/src/Chainvoice.sol @@ -1,174 +1,141 @@ // 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 + 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 > fee, "Insufficient payment for invoice + fee"); + + uint256 amountToSender = msg.value - fee; accumulatedFees += fee; + + (bool sent, ) = payable(invoice.from).call{value: amountToSender}(""); + require(sent, "Transfer failed"); + invoice.isPaid = true; + + 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"); } } From 3c9220a2b93abe56f094dd027ab794a2e54f862b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Tue, 17 Jun 2025 01:34:46 +0530 Subject: [PATCH 11/39] updated UI and contract ABI --- frontend/package.json | 4 + frontend/src/components/CreateInvoice.jsx | 601 ++++++++++++++------- frontend/src/contractsABI/ChainvoiceABI.js | 564 +++---------------- frontend/src/page/ReceivedInvoice.jsx | 461 ++++++++++++---- frontend/src/page/SentInvoice.jsx | 490 ++++++++++++----- frontend/vite.config.js | 2 +- 6 files changed, 1186 insertions(+), 936 deletions(-) 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/components/CreateInvoice.jsx b/frontend/src/components/CreateInvoice.jsx index eda38146..c37f5141 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, 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 { 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,74 @@ function CreateInvoice() { const [issueDate, setIssueDate] = useState(new Date()); const [loading, setLoading] = useState(false); const navigate = useNavigate(); + + const [itemData, setItemData] = useState([ + { + description: "", + qty: "", + unitPrice: "", + discount: "", + tax: "", + amount: "", + }, + ]); + + const [totalAmountDue, setTotalAmountDue] = useState(0); + + 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]); + + 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); + }; + const createInvoiceRequest = async (data) => { if (!isConnected || !walletClient) { alert("Please connect your wallet"); @@ -33,68 +111,135 @@ 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 = new LitNodeClient({ + litNetwork: LIT_NETWORK.DatilDev, + debug: false, + }); + await litNodeClient.connect(); + + 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), + + 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 + 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 +276,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 +329,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 07fb7160..93e18b37 100644 --- a/frontend/src/contractsABI/ChainvoiceABI.js +++ b/frontend/src/contractsABI/ChainvoiceABI.js @@ -21,136 +21,20 @@ export const ChainvoiceABI = [ "type": "function", "name": "createInvoice", "inputs": [ - { - "name": "amountDue", - "type": "uint256", - "internalType": "uint256" - }, { "name": "to", "type": "address", "internalType": "address" }, { - "name": "_dueDate", + "name": "encryptedInvoiceData", "type": "string", "internalType": "string" }, { - "name": "_issueDate", + "name": "encryptedHash", "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": [], @@ -171,19 +55,19 @@ export const ChainvoiceABI = [ }, { "type": "function", - "name": "getReceivedInvoices", + "name": "getInvoice", "inputs": [ { - "name": "_address", - "type": "address", - "internalType": "address" + "name": "invoiceId", + "type": "uint256", + "internalType": "uint256" } ], "outputs": [ { "name": "", - "type": "tuple[]", - "internalType": "struct Chainvoice.InvoiceDetails[]", + "type": "tuple", + "internalType": "struct Chainvoice.InvoiceDetails", "components": [ { "name": "id", @@ -195,141 +79,25 @@ export const ChainvoiceABI = [ "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", + "name": "encryptedInvoiceData", "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" + "name": "encryptedHash", + "type": "string", + "internalType": "string" } ] } @@ -338,10 +106,10 @@ export const ChainvoiceABI = [ }, { "type": "function", - "name": "getSentInvoices", + "name": "getReceivedInvoices", "inputs": [ { - "name": "_address", + "name": "user", "type": "address", "internalType": "address" } @@ -362,141 +130,25 @@ export const ChainvoiceABI = [ "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", + "name": "encryptedInvoiceData", "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" + "name": "encryptedHash", + "type": "string", + "internalType": "string" } ] } @@ -505,136 +157,59 @@ export const ChainvoiceABI = [ }, { "type": "function", - "name": "invoices", + "name": "getSentInvoices", "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" - }, + } + ], + "outputs": [ { - "name": "client", - "type": "tuple", - "internalType": "struct Chainvoice.UserDetails", + "name": "", + "type": "tuple[]", + "internalType": "struct Chainvoice.InvoiceDetails[]", "components": [ { - "name": "fname", - "type": "string", - "internalType": "string" + "name": "id", + "type": "uint256", + "internalType": "uint256" }, { - "name": "lname", - "type": "string", - "internalType": "string" + "name": "from", + "type": "address", + "internalType": "address" }, { - "name": "email", - "type": "string", - "internalType": "string" + "name": "to", + "type": "address", + "internalType": "address" }, { - "name": "country", - "type": "string", - "internalType": "string" + "name": "isPaid", + "type": "bool", + "internalType": "bool" }, { - "name": "city", + "name": "encryptedInvoiceData", "type": "string", "internalType": "string" }, { - "name": "postalcode", + "name": "encryptedHash", "type": "string", "internalType": "string" } ] - }, - { - "name": "amountDue", - "type": "uint256", - "internalType": "uint256" - }, - { - "name": "isPaid", - "type": "bool", - "internalType": "bool" } ], "stateMutability": "view" }, { "type": "function", - "name": "itemDatas", + "name": "invoices", "inputs": [ - { - "name": "", - "type": "uint256", - "internalType": "uint256" - }, { "name": "", "type": "uint256", @@ -643,34 +218,34 @@ export const ChainvoiceABI = [ ], "outputs": [ { - "name": "description", - "type": "string", - "internalType": "string" + "name": "id", + "type": "uint256", + "internalType": "uint256" }, { - "name": "qty", - "type": "int256", - "internalType": "int256" + "name": "from", + "type": "address", + "internalType": "address" }, { - "name": "unitPrice", - "type": "int256", - "internalType": "int256" + "name": "to", + "type": "address", + "internalType": "address" }, { - "name": "discount", - "type": "int256", - "internalType": "int256" + "name": "isPaid", + "type": "bool", + "internalType": "bool" }, { - "name": "tax", - "type": "int256", - "internalType": "int256" + "name": "encryptedInvoiceData", + "type": "string", + "internalType": "string" }, { - "name": "amount", - "type": "int256", - "internalType": "int256" + "name": "encryptedHash", + "type": "string", + "internalType": "string" } ], "stateMutability": "view" @@ -767,7 +342,7 @@ export const ChainvoiceABI = [ "name": "setTreasuryAddress", "inputs": [ { - "name": "newTreauserAdd", + "name": "newTreasury", "type": "address", "internalType": "address" } @@ -790,7 +365,7 @@ export const ChainvoiceABI = [ }, { "type": "function", - "name": "withdraw", + "name": "withdrawFees", "inputs": [], "outputs": [], "stateMutability": "nonpayable" @@ -802,7 +377,7 @@ export const ChainvoiceABI = [ { "name": "id", "type": "uint256", - "indexed": false, + "indexed": true, "internalType": "uint256" }, { @@ -816,12 +391,6 @@ export const ChainvoiceABI = [ "type": "address", "indexed": true, "internalType": "address" - }, - { - "name": "amountDue", - "type": "uint256", - "indexed": false, - "internalType": "uint256" } ], "anonymous": false @@ -833,7 +402,7 @@ export const ChainvoiceABI = [ { "name": "id", "type": "uint256", - "indexed": false, + "indexed": true, "internalType": "uint256" }, { @@ -849,7 +418,7 @@ export const ChainvoiceABI = [ "internalType": "address" }, { - "name": "amountPaid", + "name": "amount", "type": "uint256", "indexed": false, "internalType": "uint256" @@ -857,4 +426,5 @@ export const ChainvoiceABI = [ ], "anonymous": false } - ] \ No newline at end of file + ] + \ No newline at end of file diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index 9a113606..7c157e5e 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); @@ -51,30 +55,141 @@ function ReceivedInvoice() { const [invoiceItems, setInvoiceItems] = useState([]); const [fee, setFee] = useState(0); + useEffect(() => { if (!walletClient) 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 = new LitNodeClient({ + litNetwork: LIT_NETWORK.DatilDev, + }); + await litNodeClient.connect(); + + // 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); + + console.log(typeof res); + // if (!res || !Array.isArray(res) || res.length !== 2) { + // console.warn("Unexpected contract response format."); + // setReceivedInvoice([]); + // setInvoiceItems([]); + // setLoading(false); + // return; + // } + + const decryptedInvoices = []; + const allItems = []; + + for (const invoice of res) { + const id = invoice[0]; + const isPaid = invoice[3]; + const encryptedStringBase64 = invoice[4]; // encryptedData + const dataToEncryptHash = invoice[5]; + + if (!encryptedStringBase64 || !dataToEncryptHash) 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); + allItems.push(null); // placeholder + } + + console.log("decrypted : ", decryptedInvoices); + setReceivedInvoice(decryptedInvoices); + setInvoiceItems(allItems); + 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(); + console.log("invoices : ", receivedInvoices); }, [walletClient]); const payInvoice = async (id, amountDue) => { @@ -82,25 +197,43 @@ function ReceivedInvoice() { 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 +267,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 +308,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 +525,45 @@ 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 : {parseFloat( + 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 +571,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..3eac96f0 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, Fragment, 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,160 @@ function SentInvoice() { const [invoiceItems, setInvoiceItems] = useState([]); const [loading, setLoading] = useState(false); const [fee, setFee] = useState(0); - const {address}=useAccount(); + const { address } = useAccount(); + useEffect(() => { if (!walletClient) 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 = new LitNodeClient({ + litNetwork: LIT_NETWORK.DatilDev, + }); + await litNodeClient.connect(); + + // 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 = []; + const allItems = []; + + for (const invoice of res) { + const id = invoice[0]; + const isPaid = invoice[3]; + const encryptedStringBase64 = invoice[4]; // encryptedData + const dataToEncryptHash = invoice[5]; + + if (!encryptedStringBase64 || !dataToEncryptHash) 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); + allItems.push(null); // placeholder + } + + setSentInvoices(decryptedInvoices); + setInvoiceItems(allItems); // if items are separate, replace nulls 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(); + + console.log("invoices : ", sentInvoices); }, [walletClient]); - 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")) { + 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 +239,37 @@ function SentInvoice() { return (

Your Sent Invoice Request

- + {loading ? (

Loading...

- ) : sentInvoices.length > 0 ? ( + ) : sentInvoices?.length > 0 ? ( <> - +
{columns.map((column) => ( {column.label} @@ -134,55 +277,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 +403,10 @@ function SentInvoice() { "& .MuiInputBase-root": { color: "white", }, - "& .MuiTablePagination-selectLabel, & .MuiTablePagination-displayedRows": { - color: "white", - }, + "& .MuiTablePagination-selectLabel, & .MuiTablePagination-displayedRows": + { + color: "white", + }, }} /> @@ -218,35 +415,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 +476,42 @@ 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 : {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 +519,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()], From 1b7aecd74bec04fb9d245da72a4b987d14a0ffec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Thu, 3 Jul 2025 19:51:35 +0530 Subject: [PATCH 12/39] refactor: optimize Lit client init and invoice fetch --- frontend/src/components/CreateInvoice.jsx | 29 +++++++++++--- frontend/src/page/ReceivedInvoice.jsx | 47 +++++++++++++---------- frontend/src/page/SentInvoice.jsx | 38 +++++++++++++----- 3 files changed, 78 insertions(+), 36 deletions(-) diff --git a/frontend/src/components/CreateInvoice.jsx b/frontend/src/components/CreateInvoice.jsx index c37f5141..8fbc1b6b 100644 --- a/frontend/src/components/CreateInvoice.jsx +++ b/frontend/src/components/CreateInvoice.jsx @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; import { Input } from "./ui/input"; import { Button } from "./ui/button"; import { BrowserProvider, Contract, ethers } from "ethers"; @@ -33,6 +33,7 @@ function CreateInvoice() { const [issueDate, setIssueDate] = useState(new Date()); const [loading, setLoading] = useState(false); const navigate = useNavigate(); + const litClientRef = useRef(null); const [itemData, setItemData] = useState([ { @@ -60,6 +61,21 @@ function CreateInvoice() { setTotalAmountDue(total); }, [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; @@ -143,11 +159,12 @@ function CreateInvoice() { const invoiceString = JSON.stringify(invoicePayload); // 2. Setup Lit - const litNodeClient = new LitNodeClient({ - litNetwork: LIT_NETWORK.DatilDev, - debug: false, - }); - await litNodeClient.connect(); + + const litNodeClient = litClientRef.current; + if (!litNodeClient) { + alert("Lit client not initialized"); + return; + } const accessControlConditions = [ { diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index 7c157e5e..4263c1e8 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -52,12 +52,29 @@ 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(() => { + const initLit = async () => { + 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); + } + }; + setLoading(true); + initLit(); + }, []); useEffect(() => { - if (!walletClient) return; + if (!walletClient || !litReady) return; const fetchReceivedInvoices = async () => { try { @@ -67,10 +84,12 @@ function ReceivedInvoice() { const signer = await provider.getSigner(); // 1. Setup Lit Node - const litNodeClient = new LitNodeClient({ - litNetwork: LIT_NETWORK.DatilDev, - }); - await litNodeClient.connect(); + + const litNodeClient = litClientRef.current; + if (!litNodeClient) { + alert("Lit client not initialized"); + return; + } // 2. Get data from contract const contract = new Contract( @@ -82,17 +101,7 @@ function ReceivedInvoice() { const res = await contract.getReceivedInvoices(address); console.log("getReceivedInvoices raw response:", res); - console.log(typeof res); - // if (!res || !Array.isArray(res) || res.length !== 2) { - // console.warn("Unexpected contract response format."); - // setReceivedInvoice([]); - // setInvoiceItems([]); - // setLoading(false); - // return; - // } - const decryptedInvoices = []; - const allItems = []; for (const invoice of res) { const id = invoice[0]; @@ -171,12 +180,10 @@ function ReceivedInvoice() { parsed["id"] = id; parsed["isPaid"] = isPaid; decryptedInvoices.push(parsed); - allItems.push(null); // placeholder } console.log("decrypted : ", decryptedInvoices); setReceivedInvoice(decryptedInvoices); - setInvoiceItems(allItems); const fee = await contract.fee(); setFee(fee); @@ -190,7 +197,7 @@ function ReceivedInvoice() { fetchReceivedInvoices(); console.log("invoices : ", receivedInvoices); - }, [walletClient]); + }, [walletClient,litReady]); const payInvoice = async (id, amountDue) => { try { diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index 3eac96f0..3d6a2d55 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -7,7 +7,7 @@ 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, useRef } from "react"; +import React, { useEffect, useState, useRef } from "react"; import { useAccount, useWalletClient } from "wagmi"; import { ChainvoiceABI } from "../contractsABI/ChainvoiceABI"; import DescriptionIcon from "@mui/icons-material/Description"; @@ -53,9 +53,28 @@ function SentInvoice() { const [loading, setLoading] = useState(false); const [fee, setFee] = useState(0); const { address } = useAccount(); + const [litReady, setLitReady] = useState(false); + const litClientRef = useRef(null); useEffect(() => { - if (!walletClient) return; + const initLit = async () => { + 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); + } + }; + setLoading(true); + initLit(); + }, []); + + useEffect(() => { + if (!walletClient || !litReady) return; const fetchSentInvoices = async () => { try { @@ -66,10 +85,12 @@ function SentInvoice() { const signer = await provider.getSigner(); // 2. Connect to Lit Node - const litNodeClient = new LitNodeClient({ - litNetwork: LIT_NETWORK.DatilDev, - }); - await litNodeClient.connect(); + + const litNodeClient = litClientRef.current; + if (!litNodeClient) { + alert("Lit client not initialized"); + return; + } // 3. Contract call to get encrypted invoice const contract = new Contract( @@ -90,7 +111,6 @@ function SentInvoice() { } const decryptedInvoices = []; - const allItems = []; for (const invoice of res) { const id = invoice[0]; @@ -168,11 +188,9 @@ function SentInvoice() { parsed["id"] = id; parsed["isPaid"] = isPaid; decryptedInvoices.push(parsed); - allItems.push(null); // placeholder } setSentInvoices(decryptedInvoices); - setInvoiceItems(allItems); // if items are separate, replace nulls const fee = await contract.fee(); setFee(fee); } catch (error) { @@ -186,7 +204,7 @@ function SentInvoice() { fetchSentInvoices(); console.log("invoices : ", sentInvoices); - }, [walletClient]); + }, [walletClient, litReady]); const [drawerState, setDrawerState] = useState({ open: false, From ce51cbe0b3b25f64082352e43f2c5bbe77f1a7d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Thu, 3 Jul 2025 20:16:15 +0530 Subject: [PATCH 13/39] update readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4984b471..05c34c01 100644 --- a/README.md +++ b/README.md @@ -71,17 +71,17 @@ Prerequisites 4. Deploy the Contract using forge create ``` -forge create src/Contract.sol:Contract \ - --rpc-url $SEPOLIA_RPC_URL \ +forge create contracts/src/Chainvoice.sol:Chainvoice \ + --rpc-url $ETC_RPC_URL \ --private-key $PRIVATE_KEY \ - --broadcast \ + --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` + `VITE_TRACT_ADDRESS=your_deployed_contract_address_here` Replace your_deployed_contract_address_here with the actual contract address you got after deployment. From e412537c4afd7cd02983f8c2d3c4a6dfe4112dfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Thu, 3 Jul 2025 23:27:04 +0530 Subject: [PATCH 14/39] updated contract --- contracts/src/Chainvoice.sol | 12 +- frontend/src/components/CreateInvoice.jsx | 4 +- frontend/src/contractsABI/ChainvoiceABI.js | 882 +++++++++++---------- frontend/src/page/ReceivedInvoice.jsx | 42 +- frontend/src/page/SentInvoice.jsx | 18 +- 5 files changed, 496 insertions(+), 462 deletions(-) diff --git a/contracts/src/Chainvoice.sol b/contracts/src/Chainvoice.sol index ec9c8cee..a729f8e8 100644 --- a/contracts/src/Chainvoice.sol +++ b/contracts/src/Chainvoice.sol @@ -6,6 +6,7 @@ contract Chainvoice { uint256 id; address from; address to; + uint256 amountDue; bool isPaid; string encryptedInvoiceData; // Base64-encoded ciphertext string encryptedHash; @@ -46,6 +47,7 @@ contract Chainvoice { function createInvoice( address to, + uint256 amountDue, string memory encryptedInvoiceData, string memory encryptedHash ) external { @@ -59,9 +61,10 @@ contract Chainvoice { id: invoiceId, from: msg.sender, to: to, + amountDue: amountDue, isPaid: false, encryptedInvoiceData: encryptedInvoiceData, - encryptedHash:encryptedHash + encryptedHash: encryptedHash }) ); @@ -77,16 +80,15 @@ contract Chainvoice { InvoiceDetails storage invoice = invoices[invoiceId]; require(msg.sender == invoice.to, "Not authorized"); require(!invoice.isPaid, "Already paid"); - require(msg.value > fee, "Insufficient payment for invoice + fee"); + require(msg.value == invoice.amountDue + fee, "Incorrect payment amount"); - uint256 amountToSender = msg.value - fee; accumulatedFees += fee; + invoice.isPaid = true; + uint256 amountToSender = msg.value - fee; (bool sent, ) = payable(invoice.from).call{value: amountToSender}(""); require(sent, "Transfer failed"); - invoice.isPaid = true; - emit InvoicePaid(invoiceId, invoice.from, invoice.to, msg.value); } diff --git a/frontend/src/components/CreateInvoice.jsx b/frontend/src/components/CreateInvoice.jsx index 8fbc1b6b..af4872f4 100644 --- a/frontend/src/components/CreateInvoice.jsx +++ b/frontend/src/components/CreateInvoice.jsx @@ -114,7 +114,6 @@ function CreateInvoice() { amount: "", }, ]); - console.log(itemData.length); }; const createInvoiceRequest = async (data) => { @@ -239,9 +238,10 @@ function CreateInvoice() { ChainvoiceABI, signer ); - + console.log("amt : ",ethers.parseEther(totalAmountDue.toString())); const tx = await contract.createInvoice( data.clientAddress, + ethers.parseEther(totalAmountDue.toString()), encryptedStringBase64, dataToEncryptHash ); diff --git a/frontend/src/contractsABI/ChainvoiceABI.js b/frontend/src/contractsABI/ChainvoiceABI.js index 93e18b37..9baa2a01 100644 --- a/frontend/src/contractsABI/ChainvoiceABI.js +++ b/frontend/src/contractsABI/ChainvoiceABI.js @@ -1,430 +1,454 @@ export const ChainvoiceABI = [ - { - "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": "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": "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": "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": "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": "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 + { + 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 4263c1e8..4be168b6 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -57,21 +57,25 @@ function ReceivedInvoice() { const litClientRef = useRef(null); useEffect(() => { - const initLit = async () => { - 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); - } - }; - setLoading(true); - initLit(); - }, []); + 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.log("error while lit client initialization"); + } + }; + initLit(); + }, []); useEffect(() => { if (!walletClient || !litReady) return; @@ -105,9 +109,9 @@ function ReceivedInvoice() { for (const invoice of res) { const id = invoice[0]; - const isPaid = invoice[3]; - const encryptedStringBase64 = invoice[4]; // encryptedData - const dataToEncryptHash = invoice[5]; + const isPaid = invoice[4]; + const encryptedStringBase64 = invoice[5]; // encryptedData + const dataToEncryptHash = invoice[6]; if (!encryptedStringBase64 || !dataToEncryptHash) continue; @@ -197,7 +201,7 @@ function ReceivedInvoice() { fetchReceivedInvoices(); console.log("invoices : ", receivedInvoices); - }, [walletClient,litReady]); + }, [walletClient, litReady]); const payInvoice = async (id, amountDue) => { try { diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index 3d6a2d55..f9aa73e7 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -57,7 +57,9 @@ function SentInvoice() { const litClientRef = useRef(null); useEffect(() => { - const initLit = async () => { + const initLit = async () => { + try { + setLoading(true); if (!litClientRef.current) { const client = new LitNodeClient({ litNetwork: LIT_NETWORK.DatilDev, @@ -68,9 +70,11 @@ function SentInvoice() { setLitReady(true); console.log(litClientRef.current); } - }; - setLoading(true); - initLit(); + } catch (error) { + console.log("error while lit client initialization"); + } + }; + initLit(); }, []); useEffect(() => { @@ -114,9 +118,9 @@ function SentInvoice() { for (const invoice of res) { const id = invoice[0]; - const isPaid = invoice[3]; - const encryptedStringBase64 = invoice[4]; // encryptedData - const dataToEncryptHash = invoice[5]; + const isPaid = invoice[4]; + const encryptedStringBase64 = invoice[5]; // encryptedData + const dataToEncryptHash = invoice[6]; if (!encryptedStringBase64 || !dataToEncryptHash) continue; From 54fefc308df1c7734d4c964f2c1f40e21fb727cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Fri, 4 Jul 2025 00:33:39 +0530 Subject: [PATCH 15/39] removed calculation error --- frontend/src/components/CreateInvoice.jsx | 26 +++++++++++++---------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/frontend/src/components/CreateInvoice.jsx b/frontend/src/components/CreateInvoice.jsx index af4872f4..48cdb3cf 100644 --- a/frontend/src/components/CreateInvoice.jsx +++ b/frontend/src/components/CreateInvoice.jsx @@ -1,7 +1,7 @@ import React, { useEffect, useRef, useState } from "react"; import { Input } from "./ui/input"; import { Button } from "./ui/button"; -import { BrowserProvider, Contract, ethers } from "ethers"; +import { BrowserProvider, Contract, ethers, formatUnits, parseUnits } from "ethers"; import { useAccount, useWalletClient } from "wagmi"; import { ChainvoiceABI } from "../contractsABI/ChainvoiceABI"; import { @@ -49,16 +49,20 @@ function CreateInvoice() { const [totalAmountDue, setTotalAmountDue] = useState(0); 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); + 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(() => { From 3aa274ff0a4398b6bdaecd1a97398253c94a3684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Fri, 4 Jul 2025 00:37:22 +0530 Subject: [PATCH 16/39] removed calculation error --- frontend/src/page/ReceivedInvoice.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index 4be168b6..182e7f2d 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -537,7 +537,7 @@ function ReceivedInvoice() { {drawerState.selectedInvoice?.items?.map((item, index) => ( - + {item.description} {item.qty.toString()} From 4bc2776dfcbd4597d3ef7f473081302eac496393 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Fri, 4 Jul 2025 00:38:11 +0530 Subject: [PATCH 17/39] correct typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 05c34c01..c0df07d3 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ forge create contracts/src/Chainvoice.sol:Chainvoice \ `cp frontend/.env.example frontend/.env` - Open the new .env file and update the variables, especially: - `VITE_TRACT_ADDRESS=your_deployed_contract_address_here` + `VITE_CONTRACT_ADDRESS=your_deployed_contract_address_here` Replace your_deployed_contract_address_here with the actual contract address you got after deployment. From ab1bafcc78c6ac80c28e2caa6e8dd0c7f9089174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Fri, 4 Jul 2025 01:27:42 +0530 Subject: [PATCH 18/39] Security concern: Insufficient access control validation --- frontend/src/components/CreateInvoice.jsx | 14 ++++-- frontend/src/page/ReceivedInvoice.jsx | 60 +++++++++++++---------- frontend/src/page/SentInvoice.jsx | 23 ++++++--- 3 files changed, 58 insertions(+), 39 deletions(-) diff --git a/frontend/src/components/CreateInvoice.jsx b/frontend/src/components/CreateInvoice.jsx index 48cdb3cf..8053bb37 100644 --- a/frontend/src/components/CreateInvoice.jsx +++ b/frontend/src/components/CreateInvoice.jsx @@ -93,11 +93,15 @@ function CreateInvoice() { 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(); + 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; } diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index 182e7f2d..eb679cfa 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -57,25 +57,27 @@ function ReceivedInvoice() { 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.log("error while lit client initialization"); - } - }; - initLit(); - }, []); + 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; @@ -109,14 +111,21 @@ function ReceivedInvoice() { 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: "", @@ -324,7 +333,7 @@ function ReceivedInvoice() { className="hover:bg-[#32363F] transition duration-300" > {columns.map((column) => { - const value = invoice.user[column.id] || ""; + const value = invoice?.user[column.id] || ""; if (column.id === "to") { return ( - {invoice.user.address + {invoice.user?.address ? `${invoice.user.address.substring( 0, 10 @@ -558,10 +567,7 @@ function ReceivedInvoice() {

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

{" "} diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index f9aa73e7..277de25b 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -71,12 +71,14 @@ function SentInvoice() { console.log(litClientRef.current); } } catch (error) { - console.log("error while lit client initialization"); - } + console.error("Error while lit client initialization:", error); + } finally { + setLoading(false); + } }; initLit(); }, []); - + useEffect(() => { if (!walletClient || !litReady) return; @@ -118,12 +120,20 @@ function SentInvoice() { 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 = [ { @@ -322,7 +332,7 @@ function SentInvoice() { borderColor: "#25272b", }} > - {invoice.client.address + {invoice.client?.address ? `${invoice.client.address.substring( 0, 10 @@ -519,8 +529,7 @@ function SentInvoice() {

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

{" "} From 673ffa10fc1f820d1c0d9d36ec6b6ff9b77fb9cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Fri, 4 Jul 2025 16:33:45 +0530 Subject: [PATCH 19/39] Updated dashboard,treasury account and routes --- frontend/package.json | 3 + frontend/public/logo.png | Bin 0 -> 25311 bytes frontend/src/App.jsx | 115 ++--- frontend/src/components/CreateInvoice.jsx | 593 ++++++++++++++-------- frontend/src/components/Navbar.jsx | 306 +++++++---- frontend/src/page/Applayout.jsx | 23 +- frontend/src/page/Home.jsx | 186 ++++--- frontend/src/page/Landing.jsx | 357 +++++++------ frontend/src/page/ReceivedInvoice.jsx | 57 ++- frontend/src/page/SentInvoice.jsx | 200 ++++---- frontend/src/page/Treasure.jsx | 250 ++++++--- frontend/{ | 0 12 files changed, 1295 insertions(+), 795 deletions(-) create mode 100644 frontend/public/logo.png create mode 100644 frontend/{ diff --git a/frontend/package.json b/frontend/package.json index ce268161..84beb46c 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -30,6 +30,7 @@ "eth-crypto": "^2.7.0", "ethereum-unit-converter": "^0.0.17", "ethers": "^6.13.5", + "framer-motion": "^12.23.0", "html2canvas": "^1.4.1", "jspdf": "^3.0.0", "lucide-react": "^0.471.1", @@ -37,6 +38,8 @@ "react-day-picker": "^8.10.1", "react-dom": "^18.3.1", "react-hook-form": "^7.54.2", + "react-hot-toast": "^2.5.2", + "react-icons": "^5.5.0", "react-router-dom": "^7.1.1", "react-to-print": "^3.0.5", "tailwind-merge": "^2.6.0", diff --git a/frontend/public/logo.png b/frontend/public/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..46809fdde63a420f1ea002156bb6e5d99fb06b12 GIT binary patch literal 25311 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

j&?*{ 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 - - - -

- - - }> - } /> - } > - }/> - }/> - }/> +
+ + + + +
+ + + }> + } /> + }> + } /> + } /> + } /> + + } /> + } /> + } /> + } /> - } /> - } /> - } /> - } /> - - - -
-
-
-
+ + +
+ + +
- ) + ); } -export default App; \ No newline at end of file +export default App; diff --git a/frontend/src/components/CreateInvoice.jsx b/frontend/src/components/CreateInvoice.jsx index 8053bb37..a496f2a2 100644 --- a/frontend/src/components/CreateInvoice.jsx +++ b/frontend/src/components/CreateInvoice.jsx @@ -10,7 +10,7 @@ import { PopoverTrigger, } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; -import { CalendarIcon, Loader2 } from "lucide-react"; +import { CalendarIcon, Loader2, PlusIcon } from "lucide-react"; import { cn } from "@/lib/utils"; import { format } from "date-fns"; import { Label } from "./ui/label"; @@ -168,11 +168,12 @@ function CreateInvoice() { // 2. Setup Lit const litNodeClient = litClientRef.current; + if (!litNodeClient) { alert("Lit client not initialized"); return; } - + const accessControlConditions = [ { contractAddress: "", @@ -198,7 +199,7 @@ function CreateInvoice() { }, }, ]; - + // 3. Encrypt const { ciphertext, dataToEncryptHash } = await encryptString( { @@ -207,7 +208,7 @@ function CreateInvoice() { }, litNodeClient ); - + const sessionSigs = await litNodeClient.getSessionSigs({ chain: "ethereum", resourceAbilityRequests: [ @@ -239,7 +240,7 @@ function CreateInvoice() { }); const encryptedStringBase64 = btoa(ciphertext); - + // 4. Send to contract const contract = new Contract( import.meta.env.VITE_CONTRACT_ADDRESS, @@ -254,8 +255,10 @@ function CreateInvoice() { dataToEncryptHash ); - console.log("Invoice created:", tx); - setTimeout(() => navigate("/home/sent"), 4000); + const receipt = await tx.wait(); + + console.log("Transaction receipt:", receipt); + setTimeout(() => navigate("/dashboard/sent"), 4000); } catch (err) { console.error("Encryption or transaction failed:", err); alert("Failed to create invoice."); @@ -309,258 +312,408 @@ function CreateInvoice() { return (
-

Create New Invoice Request

-
- - -

Issued Date

- - -

Due Date

- - - - - - - - +

+ Create New Invoice Request +

+ +
+
+ + +
+ +
+ + +
+ +
+ + + + + + + { + if (date) { + setDueDate(date); + document.dispatchEvent( + new KeyboardEvent("keydown", { key: "Escape" }) + ); + } + }} + initialFocus + disabled={(date) => date < new Date()} + /> + + +
+
-
-
-

From (Your Information)

+
+ {/* Your Information */} +
+

+ From (Your Information) +

-
-

Add Your Info

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

Client Information

+ {/* Client Information */} +
+

+ Client Information +

-
-

Add Client Info

-
- - + +
+
+
+ + +
+
+ + +
-
- - +
+
+ + +
+
+ + +
-
- - + +
+
+ + +
+
+ + +
-
-
-
DESCRIPTION
-
QTY
-
UNIT PRICE
-
DISCOUNT
-
TAX(%)
-
AMOUNT
+ {/* Invoice Items Section */} +
+
+
DESCRIPTION
+
QTY
+
UNIT PRICE
+
DISCOUNT
+
TAX(%)
+
AMOUNT
+ {/*
AMOUNT
*/}
-
+ +
{itemData.map((_, index) => ( -
- handleItemData(e, index)} - /> - handleItemData(e, index)} - /> - handleItemData(e, index)} - /> - handleItemData(e, index)} - /> - handleItemData(e, index)} - /> - handleItemData(e, index)} - /> +
+ {/* Item Fields */} +
+ handleItemData(e, index)} + /> +
+
+ handleItemData(e, index)} + /> +
+
+ handleItemData(e, index)} + /> +
+
+ handleItemData(e, index)} + /> +
+
+ handleItemData(e, index)} + /> +
+
+ +
+ + {index > 0 && ( + + )}
))}
-

Total : {totalAmountDue}

-
+
+ +
+
+ Total: + + {/* {totalAmountDue} ETH */} + {totalAmountDue} cBTC + +
+
+
+
+ + {/* Form Actions */} +
+ +
+ + Creating Invoice... +
) : ( - + "Create Invoice" )} -
+
diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index a8067e25..08cd5d7d 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -1,103 +1,237 @@ -import { ConnectButton } from '@rainbow-me/rainbowkit'; -import React, { useEffect } from 'react' -import { Link, useNavigate } from 'react-router-dom'; -import { useAccount } from 'wagmi'; -import AccountBalanceWalletIcon from '@mui/icons-material/AccountBalanceWallet'; -import HomeIcon from '@mui/icons-material/Home'; +// Updated Navbar.js +import { ConnectButton } from "@rainbow-me/rainbowkit"; +import React, { useEffect, useState } from "react"; +import { Link, useNavigate, useLocation } from "react-router-dom"; +import { useAccount } from "wagmi"; +import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet"; +import HomeIcon from "@mui/icons-material/Home"; +import DashboardIcon from "@mui/icons-material/Dashboard"; +import FeaturedPlayListIcon from "@mui/icons-material/FeaturedPlayList"; +import DesignServicesIcon from "@mui/icons-material/DesignServices"; +import { motion } from "framer-motion"; function Navbar() { - const { address } = useAccount(); - const navigate = useNavigate() - useEffect(() => { - if (!address) navigate('/') - }, [address]); - const handleScrollToFeature = () => { - const featureSection = document.getElementById("feature-section"); - const offset = 200; - const position = featureSection.offsetTop - offset; + const { address, isConnected } = useAccount(); + const navigate = useNavigate(); + const location = useLocation(); + const [hasConnected, setHasConnected] = useState(false); + const [isScrolled, setIsScrolled] = useState(false); - window.scrollTo({ - top: position, - behavior: "smooth", - }); + // Improved active route detection + const isActive = (path) => { + if (path.startsWith("/#")) { + return location.pathname === "/" && location.hash === path.substring(1); + } + return ( + location.pathname.startsWith(path) && + (path !== "/" || location.pathname === "/") + ); }; - const handleScrollToHome = () => { - const featureSection = document.getElementById("home-section"); - const offset = 200; - const position = featureSection.offsetTop - offset; + useEffect(() => { + if ( + address && + !hasConnected && + !location.pathname.startsWith("/dashboard") + ) { + navigate("/dashboard/create"); + setHasConnected(true); + } + if (!address) { + navigate("/"); + setHasConnected(false); + } + + const handleScroll = () => { + setIsScrolled(window.scrollY > 10); + }; + window.addEventListener("scroll", handleScroll); + return () => window.removeEventListener("scroll", handleScroll); + }, [isConnected, hasConnected, navigate, location.pathname]); - window.scrollTo({ - top: position, - behavior: "smooth", - }); + const handleScroll = (sectionId) => { + if (location.pathname !== "/") { + navigate("/"); + setTimeout(() => { + const section = document.getElementById(sectionId); + if (section) { + window.scrollTo({ + top: section.offsetTop - 200, + behavior: "smooth", + }); + } + }, 100); + } else { + const section = document.getElementById(sectionId); + if (section) { + window.scrollTo({ + top: section.offsetTop - 200, + behavior: "smooth", + }); + } + } }; - const handleScrollToService = () => { - const featureSection = document.getElementById("service-section"); - const offset = 200; - const position = featureSection.offsetTop - offset; - window.scrollTo({ - top: position, - behavior: "smooth", - }); - }; + const navItems = [ + { + name: "Home", + icon: , + action: () => handleScroll("home-section"), + path: "/", + }, + { + name: "Features", + icon: , + action: () => handleScroll("feature-section"), + path: "/#feature-section", + }, + { + name: "Services", + icon: , + action: () => handleScroll("service-section"), + path: "/#service-section", + }, + ]; + + const appItems = [ + { + name: "Dashboard", + icon: , + path: "/dashboard", + activePaths: [ + "/dashboard/create", + "/dashboard/sent", + "/dashboard/pending", + ], + }, + { + name: "Treasure", + icon: , + path: "/treasure", + }, + ]; return ( - <> -
-
navigate('/')} >Chainvoice
-
-
    - { - address && ( -
  • - - Home -
  • - ) - } - { - !address && ( -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
    + +
    +
    + navigate("/")} + > + logo +

    + Chainvoice +

    +
    + +
    + {navItems.map((item) => ( + + {item.icon} + {item.name} + + ))} - ) - } - {/*
  • - How It Works -
  • */} + {isConnected && + appItems.map((item) => ( + + {item?.path === "/dashboard" ? ( + + {item.icon} + {item.name} + + ) : ( + + {item.icon} + {item.name} + + )} + + ))} - { - address && ( -
  • - - Treasure -
  • - ) - } - {/*
  • - About Us -
  • */} - + + + +
    -
+ {/* Mobile menu button */} +
+ + +
- - {/* Add padding to prevent content from being hidden behind the navbar */} -
- - - ) + + ); } export default Navbar; diff --git a/frontend/src/page/Applayout.jsx b/frontend/src/page/Applayout.jsx index 41ae62e2..8b459d0c 100644 --- a/frontend/src/page/Applayout.jsx +++ b/frontend/src/page/Applayout.jsx @@ -1,20 +1,19 @@ +// AppLayout.js import Footer from "@/components/Footer"; import Navbar from "@/components/Navbar"; import React from "react"; import { Outlet } from "react-router-dom"; function Applayout() { - return ( - <> -
- -
- -
-
-
- - ) + return ( +
+ +
+ +
+
+
+ ); } -export default Applayout; \ No newline at end of file +export default Applayout; diff --git a/frontend/src/page/Home.jsx b/frontend/src/page/Home.jsx index 24381b36..14b602ec 100644 --- a/frontend/src/page/Home.jsx +++ b/frontend/src/page/Home.jsx @@ -1,111 +1,141 @@ -import * as React from 'react'; -import Box from '@mui/material/Box'; -import Drawer from '@mui/material/Drawer'; -import List from '@mui/material/List'; -import ListItem from '@mui/material/ListItem'; -import ListItemButton from '@mui/material/ListItemButton'; -import ListItemIcon from '@mui/material/ListItemIcon'; -import ListItemText from '@mui/material/ListItemText'; -import AddCircleIcon from '@mui/icons-material/AddCircle'; -import PendingIcon from '@mui/icons-material/Pending'; -import MarkEmailReadIcon from '@mui/icons-material/MarkEmailRead'; -import { Outlet, useNavigate } from 'react-router-dom'; +// Home.js +import * as React from "react"; +import Box from "@mui/material/Box"; +import Drawer from "@mui/material/Drawer"; +import List from "@mui/material/List"; +import ListItem from "@mui/material/ListItem"; +import ListItemButton from "@mui/material/ListItemButton"; +import ListItemIcon from "@mui/material/ListItemIcon"; +import ListItemText from "@mui/material/ListItemText"; +import MailOutlineIcon from "@mui/icons-material/MailOutline"; +import DraftsIcon from "@mui/icons-material/Drafts"; +import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline"; +import { Outlet, useNavigate, useLocation } from "react-router-dom"; + export default function Home() { const navigate = useNavigate(); + const location = useLocation(); - const list = [ + const menuItems = [ { - text: 'Sent Payment Requests', - icon: , - route: 'sent' + text: "Sent Requests", + icon: , + route: "sent", + color: "#4ade80", }, { - text: 'Received Payment Requests', - icon: , - route: 'pending' + text: "Received Requests", + icon: , + route: "pending", + color: "#60a5fa", }, { - text: 'Create New Invoice Request', - icon: , - route: 'create' + text: "New Invoice", + icon: , + route: "create", + color: "#f472b6", }, - ] - return ( - <> -
+ ]; -

- Welcome Back! -

+ return ( +
+
+ {" "} +

+ {" "} + Welcome Back! +

+
- - - + {/* Sidebar Navigation */} + + + -
- +
); } diff --git a/frontend/src/page/Landing.jsx b/frontend/src/page/Landing.jsx index 5636c72c..e52d7b45 100644 --- a/frontend/src/page/Landing.jsx +++ b/frontend/src/page/Landing.jsx @@ -2,161 +2,220 @@ import { ConnectButton } from "@rainbow-me/rainbowkit"; import React, { useEffect } from "react"; import { useNavigate } from "react-router-dom"; import { useAccount } from "wagmi"; -import ShieldIcon from '@mui/icons-material/Shield'; -import EmailIcon from '@mui/icons-material/Email'; -import GavelIcon from '@mui/icons-material/Gavel'; -import LeaderboardIcon from '@mui/icons-material/Leaderboard'; +import ShieldIcon from "@mui/icons-material/Shield"; +import EmailIcon from "@mui/icons-material/Email"; +import GavelIcon from "@mui/icons-material/Gavel"; +import LeaderboardIcon from "@mui/icons-material/Leaderboard"; function Landing() { - const Account = useAccount(); - const navigate = useNavigate(); + const Account = useAccount(); + const navigate = useNavigate(); - useEffect(() => { - if (Account.address) navigate('/home/sent'); - }, [Account, Account.address]) - return ( - <> -
-
-

Decentralized Payment Requests &
Invoice Automation

-

One click to effortless invoicing — process payments in any ERC20 token with transparency and trustless efficiency!

- {/*

Only with

-
Chainvoice
*/} + // useEffect(() => { + // if (Account.address) navigate("/home/sent"); + // }, [Account, Account.address]); -

Connect with your wallet and Get Started!

-
- -
+ return ( + <> +
+
+

+ Decentralized Payment Requests & +
+ Invoice Automation +

+

+ One click to effortless invoicing — process payments in any ERC20 + token with transparency and trustless efficiency! +

+ {/*

Only with

+
Chainvoice
*/} -
-

Trusted by 1000+ users

-

Smart Contract Driven 100% Secure

-
-
-
- -
-
-
+

+ Connect with your wallet and Get Started! +

+
+ +
-
-

- A powerful and secure{" "} - - invoicing solution - {" "} - designed for growing businesses. -

-
- {[ - { - title: "Secure and Transparent Transactions", - description: - "Leverage blockchain technology to ensure encrypted, tamper-proof, and immutable transactions. Provide complete transparency for invoice verification.", - icon: , - }, - { - title: "Send and Receive Invoices", - description: - "Effortlessly create and manage invoices with a few clicks. Track real-time status and maintain a comprehensive invoice dashboard.", - icon: , - }, - { - title: "Smart Contract Integration", - description: - "Automate payment processes with secure smart contracts. Ensure funds are released only when invoice conditions are met, reducing intermediary dependencies.", - icon: , - }, - { - title: "Comprehensive Invoice Tracking", - description: - "Gain complete visibility into your invoice lifecycle. Monitor payment statuses, track financial performance, and manage all transactions seamlessly.", - icon: , - }, - ].map((feature, index) => ( -
-
- {feature.icon} -
-

{feature.title}

-

{feature.description}

-
- ))} -
-
-
- Invoice Illustration +
+

+ + Trusted by 1000+ users +

+

+ + Smart Contract Driven 100% Secure +

+
+
+
+ +
+
+
+
+

+ A powerful and secure{" "} + + invoicing solution + {" "} + designed for growing businesses. +

+
+ {[ + { + title: "Secure and Transparent Transactions", + description: + "Leverage blockchain technology to ensure encrypted, tamper-proof, and immutable transactions. Provide complete transparency for invoice verification.", + icon: , + }, + { + title: "Send and Receive Invoices", + description: + "Effortlessly create and manage invoices with a few clicks. Track real-time status and maintain a comprehensive invoice dashboard.", + icon: , + }, + { + title: "Smart Contract Integration", + description: + "Automate payment processes with secure smart contracts. Ensure funds are released only when invoice conditions are met, reducing intermediary dependencies.", + icon: , + }, + { + title: "Comprehensive Invoice Tracking", + description: + "Gain complete visibility into your invoice lifecycle. Monitor payment statuses, track financial performance, and manage all transactions seamlessly.", + icon: , + }, + ].map((feature, index) => ( +
+
+ {feature.icon}
-
-
-
- Aeroplane 2 -
-

Start Sending Your

-

Invoice Today!

-
- Aeroplane 1 -
- -
-
-
-
Chainvoice
-

Secure & Smart Invoicing

-
-
- -
-

Quick Links

-
    - {["Home", "Feature", "Treasure", "Service", "Invoice"].map((link) => ( -
  • - - {link} - -
  • - ))} -
-
-
-

Services

-
    - {["Blog & Article", "Terms ans Conditions", "Privacy Policy", "Contact Us", "Invoice"].map((link) => ( -
  • - - {link} - -
  • - ))} -
-
-
-

Contact

-
    - {["chainvoice@gmail.com"].map((link) => ( -
  • - - {link} - -
  • - ))} -
-
-
-
-
-
+

+ {feature.title} +

+

{feature.description}

+
+ ))} +
+
+
+ Invoice Illustration +
+ +
+
+ Aeroplane 2 +
+

+ {" "} + Start Sending Your +

+

+ Invoice Today! +

+
+ Aeroplane 1 +
- - - ) +
+
+
+
+ logo +

+ Chain + voice +

+
+

+ Secure & Smart Invoicing +

+
+
+
+

Quick Links

+
    + {["Home", "Feature", "Treasure", "Service", "Invoice"].map( + (link) => ( +
  • + + {link} + +
  • + ) + )} +
+
+
+

Services

+
    + {[ + "Blog & Article", + "Terms ans Conditions", + "Privacy Policy", + "Contact Us", + "Invoice", + ].map((link) => ( +
  • + + {link} + +
  • + ))} +
+
+
+

Contact

+
    + {["chainvoice@gmail.com"].map((link) => ( +
  • + + {link} + +
  • + ))} +
+
+
+
+
+
+ + ); } export default Landing; diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index eb679cfa..8a2b2f3c 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -38,6 +38,14 @@ const columns = [ function ReceivedInvoice() { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); + const { data: walletClient } = useWalletClient(); + const { address } = useAccount(); + const [loading, setLoading] = useState(false); + const [receivedInvoices, setReceivedInvoice] = useState([]); + const [fee, setFee] = useState(0); + const [error, setError] = useState(null); + const [litReady, setLitReady] = useState(false); + const litClientRef = useRef(null); const handleChangePage = (event, newPage) => { setPage(newPage); @@ -48,14 +56,6 @@ function ReceivedInvoice() { setPage(0); }; - const { data: walletClient } = useWalletClient(); - const { address } = useAccount(); - const [loading, setLoading] = useState(false); - const [receivedInvoices, setReceivedInvoice] = useState([]); - const [fee, setFee] = useState(0); - const [litReady, setLitReady] = useState(false); - const litClientRef = useRef(null); - useEffect(() => { const initLit = async () => { try { @@ -80,15 +80,24 @@ function ReceivedInvoice() { }, []); useEffect(() => { - if (!walletClient || !litReady) return; + if (!walletClient || !address || !litReady) return; const fetchReceivedInvoices = async () => { try { setLoading(true); - + setError(null); const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); + const network = await provider.getNetwork(); + if (network.chainId != 5115) { + setError( + `Failed to load invoices. You're connected to the "${network.name}" network, but your invoices are on the "Citrea" testnet. Please switch to Sepolia and try again.` + ); + + setLoading(false); + return; + } // 1. Setup Lit Node const litNodeClient = litClientRef.current; @@ -106,6 +115,13 @@ function ReceivedInvoice() { const res = await contract.getReceivedInvoices(address); console.log("getReceivedInvoices raw response:", res); + // First check if user has any invoices + if (!res || !Array.isArray(res) || res.length === 0) { + setReceivedInvoice([]); + const fee = await contract.fee(); + setFee(fee); + return; + } const decryptedInvoices = []; @@ -297,8 +313,12 @@ function ReceivedInvoice() { }} > {loading ? ( -

loading........

- ) : receivedInvoices?.length > 0 ? ( +

Loading invoices...

+ ) : error ? ( +

{error}

+ ) : receivedInvoices.length === 0 ? ( +

No invoices found

+ ) : ( <> {columns.map((column) => { - const value = invoice?.user[column.id] || ""; + const value = invoice.user[column.id] || ""; if (column.id === "to") { return ( - {invoice.user?.address + {invoice.user.address ? `${invoice.user.address.substring( 0, 10 @@ -479,8 +499,6 @@ function ReceivedInvoice() { }} /> - ) : ( -

No invoices found

)} @@ -546,7 +564,7 @@ function ReceivedInvoice() { {drawerState.selectedInvoice?.items?.map((item, index) => ( -
+ @@ -567,7 +585,10 @@ function ReceivedInvoice() {

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

{" "} diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index 277de25b..350b0615 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -52,6 +52,7 @@ function SentInvoice() { const [invoiceItems, setInvoiceItems] = useState([]); const [loading, setLoading] = useState(false); const [fee, setFee] = useState(0); + const [error, setError] = useState(null); const { address } = useAccount(); const [litReady, setLitReady] = useState(false); const litClientRef = useRef(null); @@ -270,7 +271,7 @@ function SentInvoice() { return (

-

Your Sent Invoice Request

+

Your Sent Invoice Request

{loading ? ( -

Loading...

- ) : sentInvoices?.length > 0 ? ( +

Loading invoices...

+ ) : error ? ( +

{error}

+ ) : sentInvoices.length === 0 ? ( +

No invoices found

+ ) : ( <>
{item.description} {item.qty.toString()}
- {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.client?.address - ? `${invoice.client.address.substring( - 0, - 10 - )}...${invoice.client.address.substring( - invoice.client.address.length - 10 - )}` - : "N/A"} - - ); - } - if (column.id === "amountDue") { - return ( - - {invoice.amountDue} ETH - {/* {ethers.formatUnits(invoice.amountDue)} ETH */} - - ); - } - if (column.id === "isPaid") { - return ( - - - - ); - } - if (column.id === "detail") { - return ( - ( + + {columns.map((column) => { + const value = invoice?.client[column.id]; + if (column.id === "to") { + return ( + + {invoice.client.address + ? `${invoice.client.address.substring( + 0, + 10 + )}...${invoice.client.address.substring( + invoice.client.address.length - 10 + )}` + : "N/A"} + + ); + } + if (column.id === "amountDue") { + return ( + + {invoice.amountDue} ETH + + ); + } + if (column.id === "isPaid") { + return ( + + - - ); - } + {invoice.isPaid ? "Paid" : "Not Paid"} + + + ); + } + if (column.id === "detail") { return ( - {value} + ); - })} - - ))} + } + return ( + + {value} + + ); + })} + + ))}
@@ -442,8 +442,6 @@ function SentInvoice() { }} /> - ) : ( -

No invoices found

)} @@ -529,7 +527,9 @@ function SentInvoice() {

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

{" "} diff --git a/frontend/src/page/Treasure.jsx b/frontend/src/page/Treasure.jsx index 8ccac602..9b66ae51 100644 --- a/frontend/src/page/Treasure.jsx +++ b/frontend/src/page/Treasure.jsx @@ -4,11 +4,24 @@ import { ChainvoiceABI } from "@/contractsABI/ChainvoiceABI"; import { BrowserProvider, Contract, ethers } from "ethers"; import { useState, useEffect } from "react"; import { useWalletClient } from "wagmi"; +import { + Loader2, + Shield, + Banknote, + Key, + Wallet, + DollarSignIcon, +} from "lucide-react"; const Treasure = () => { const [treasureAmount, setTreasureAmount] = useState(0); + const [fee, setFee] = useState(0); const { data: walletClient } = useWalletClient(); - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState({ + fetch: false, + setAddress: false, + withdraw: false, + }); const [treasuryAddress, setTreasuryAddress] = useState(""); const [newTreasuryAddress, setNewTreasuryAddress] = useState(""); @@ -16,18 +29,26 @@ const Treasure = () => { const fetchTreasureAmount = async () => { try { if (!walletClient) return; - setLoading(true); + setLoading((prev) => ({ ...prev, fetch: true })); const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); - const contract = new Contract(import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, signer); - const amt = await contract.accumulatedFees(); + const contract = new Contract( + import.meta.env.VITE_CONTRACT_ADDRESS, + ChainvoiceABI, + signer + ); + const [amt, add, feeAmt] = await Promise.all([ + contract.accumulatedFees(), + contract.treasuryAddress(), + contract.fee(), + ]); setTreasureAmount(ethers.formatUnits(amt)); - const add = await contract.treasuryAddress(); setTreasuryAddress(add); + setFee(ethers.formatUnits(feeAmt)); } catch (error) { console.error("Error fetching treasure amount:", error); } finally { - setLoading(false); + setLoading((prev) => ({ ...prev, fetch: false })); } }; @@ -36,111 +57,194 @@ const Treasure = () => { const handleSetTreasuryAddress = async () => { if (!ethers.isAddress(newTreasuryAddress)) { - console.error("Invalid address:", newTreasuryAddress); + alert("Please enter a valid Ethereum address"); return; } try { - if (!walletClient || !newTreasuryAddress) return; - setLoading(true); + if (!walletClient) return; + setLoading((prev) => ({ ...prev, setAddress: true })); const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); - const contract = new Contract(import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, signer); + const contract = new Contract( + import.meta.env.VITE_CONTRACT_ADDRESS, + ChainvoiceABI, + signer + ); const tx = await contract.setTreasuryAddress(newTreasuryAddress); await tx.wait(); setTreasuryAddress(newTreasuryAddress); setNewTreasuryAddress(""); + alert("Treasury address updated successfully!"); } catch (error) { console.error("Error setting treasury address:", error); + alert(error.message || "Failed to update treasury address"); } finally { - setLoading(false); + setLoading((prev) => ({ ...prev, setAddress: false })); } }; const handleWithdrawCollection = async () => { try { if (!walletClient) return; - setLoading(true); + setLoading((prev) => ({ ...prev, withdraw: true })); const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); - const contract = new Contract(import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, signer); - const amt = await contract.withdraw(); - setTreasureAmount(ethers.formatUnits(amt)); + const contract = new Contract( + import.meta.env.VITE_CONTRACT_ADDRESS, + ChainvoiceABI, + signer + ); + const tx = await contract.withdraw(); + await tx.wait(); + const newAmt = await contract.accumulatedFees(); + setTreasureAmount(ethers.formatUnits(newAmt)); + alert("Funds withdrawn successfully!"); } catch (error) { console.error("Error withdrawing collection:", error); + alert(error.message || "Failed to withdraw funds"); } finally { - setLoading(false); + setLoading((prev) => ({ ...prev, withdraw: false })); } }; return ( -

- -
-

- Treasure Account -

-

- The treasure account is the account where all the fees collected from the platform are stored. -

- -
-

Treasure Amount:

-

- {loading ? "Loading..." : `${treasureAmount} ETH`} -

+
+
+
+
+
+
+
+
+ +
+

+ Treasury Vault +

+
+
+ + + Current Balance: + + + {loading.fetch ? ( + + ) : ( + `${treasureAmount} cBTC` + )} + +
+
+ + + Fee Per Transaction: + + + {loading.fetch ? ( + + ) : ( + `${fee} cBTC` + )} + +
+
+ + + Admin Access Only + +
+
+
+
+
-
-

Treasury Address:

-

- {loading ? "Loading..." : treasuryAddress} + {/* Content */} +

+

+ Treasury Controls +

+

+ Secure management of platform funds and treasury settings

-
-
-

Set New Treasury Address

-
- setNewTreasuryAddress(e.target.value)} - /> +
+
+ +

Current Treasury

+
+
+

+ {loading.fetch ? ( + + ) : treasuryAddress ? ( + treasuryAddress + ) : ( + "Not configured" + )} +

+
+
+ +
+
+ +

+ Update Treasury Address +

+
+
+ setNewTreasuryAddress(e.target.value)} + className="flex-1 bg-gray-800 border-gray-700 text-white font-mono text-sm" + /> + +
+

+ Requires contract owner privileges +

+
+ + {/* Withdraw */} +
+
+ +

Funds Withdrawal

+
+

+ Will be sent to the current treasury address +

-

- Disclaimer: Only the owner of the contract can set the treasury address. -

- -
-

Withdraw Collection

- -
-
- -
- Treasure Illustration
); }; -export default Treasure; \ No newline at end of file +export default Treasure; diff --git a/frontend/{ b/frontend/{ new file mode 100644 index 00000000..e69de29b From 4cd4795bab012833d7e8f315aafedcd17fc2c72c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Sat, 5 Jul 2025 14:32:50 +0530 Subject: [PATCH 20/39] correct some error --- frontend/src/page/Landing.jsx | 2 +- frontend/src/page/Treasure.jsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/src/page/Landing.jsx b/frontend/src/page/Landing.jsx index e52d7b45..4f87f6a5 100644 --- a/frontend/src/page/Landing.jsx +++ b/frontend/src/page/Landing.jsx @@ -180,7 +180,7 @@ function Landing() {
    {[ "Blog & Article", - "Terms ans Conditions", + "Terms and Conditions", "Privacy Policy", "Contact Us", "Invoice", diff --git a/frontend/src/page/Treasure.jsx b/frontend/src/page/Treasure.jsx index 9b66ae51..e2cbaf7b 100644 --- a/frontend/src/page/Treasure.jsx +++ b/frontend/src/page/Treasure.jsx @@ -94,7 +94,7 @@ const Treasure = () => { ChainvoiceABI, signer ); - const tx = await contract.withdraw(); + const tx = await contract.withdrawFees(); await tx.wait(); const newAmt = await contract.accumulatedFees(); setTreasureAmount(ethers.formatUnits(newAmt)); From a54324ccb66f067fe1f3512638369a097358276f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Tue, 8 Jul 2025 12:27:49 +0530 Subject: [PATCH 21/39] added mobile menu --- frontend/src/components/CreateInvoice.jsx | 11 -- frontend/src/components/Navbar.jsx | 127 ++++++++++++++++++---- frontend/src/page/Home.jsx | 50 +++++---- frontend/src/page/ReceivedInvoice.jsx | 2 +- frontend/src/page/SentInvoice.jsx | 6 - 5 files changed, 134 insertions(+), 62 deletions(-) diff --git a/frontend/src/components/CreateInvoice.jsx b/frontend/src/components/CreateInvoice.jsx index 2d32b94e..642b0868 100644 --- a/frontend/src/components/CreateInvoice.jsx +++ b/frontend/src/components/CreateInvoice.jsx @@ -135,8 +135,6 @@ function CreateInvoice() { 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, @@ -243,7 +241,6 @@ function CreateInvoice() { ChainvoiceABI, signer ); - console.log("amt : ",ethers.parseEther(totalAmountDue.toString())); const tx = await contract.createInvoice( data.clientAddress, ethers.parseEther(totalAmountDue.toString()), @@ -252,8 +249,6 @@ function CreateInvoice() { ); const receipt = await tx.wait(); - - console.log("Transaction receipt:", receipt); setTimeout(() => navigate("/dashboard/sent"), 4000); } catch (err) { @@ -450,10 +445,6 @@ function CreateInvoice() { @@ -647,10 +638,8 @@ function CreateInvoice() { + > + + + + {isMobileMenuOpen ? ( + + ) : ( + + )} +
+ + {/* Mobile Menu */} + + {isMobileMenuOpen && ( + +
+ {/* Navigation Links */} + {navItems.map((item) => ( + { + item.action(); + closeMobileMenu(); + }} + className={`w-full flex items-center px-4 py-3 rounded-lg transition-colors ${ + isActive(item.path) + ? "bg-green-900/30 text-green-400 font-medium" + : "text-white hover:bg-gray-800" + }`} + > + {item.icon} + {item.name} + + ))} + + {isConnected && + appItems.map((item) => ( + +
+ {item.icon} + {item.name} +
+ + ))} + +
+ +
+
+
+ )} +
); } -export default Navbar; +export default Navbar; \ No newline at end of file diff --git a/frontend/src/page/Home.jsx b/frontend/src/page/Home.jsx index 14b602ec..cc80256b 100644 --- a/frontend/src/page/Home.jsx +++ b/frontend/src/page/Home.jsx @@ -39,19 +39,27 @@ export default function Home() { return (
-
- {" "} -

- {" "} +
+

Welcome Back!

- + {/* Sidebar Navigation */} - + + {/* Main Content */} diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index cc4e4ec8..daade8b6 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -536,7 +536,7 @@ function ReceivedInvoice() {

From

- {drawerState.selectedInvoice.client.from} + {drawerState.selectedInvoice.user.address}

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

diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index 350b0615..f24eb7f4 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -217,8 +217,6 @@ function SentInvoice() { }; fetchSentInvoices(); - - console.log("invoices : ", sentInvoices); }, [walletClient, litReady]); const [drawerState, setDrawerState] = useState({ @@ -512,13 +510,11 @@ function SentInvoice() { {item.description} {item.qty.toString()} - {/* {ethers.formatUnits(item.unitPrice)} */} {item.unitPrice} {item.discount.toString()} {item.tax.toString()} - {/* {ethers.formatUnits(item.amount)} */} {item.amount} @@ -529,12 +525,10 @@ function SentInvoice() {

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

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

From 538e82ce3d0c36a6e6e3797738861cf4a4931cc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Fri, 11 Jul 2025 03:39:34 +0530 Subject: [PATCH 22/39] added erc20 token,updated contract,erc20abi,frontend --- frontend/package.json | 1 + frontend/public/tokenImages/cin.png | Bin 0 -> 8961 bytes frontend/public/tokenImages/eth.png | Bin 0 -> 21779 bytes frontend/public/tokenImages/generic.png | Bin 0 -> 15910 bytes frontend/src/App.jsx | 2 +- .../components/TokenIntegrationRequest.jsx | 16 + frontend/src/components/TokenSelector.jsx | 331 ++++++++ frontend/src/components/ui/select.jsx | 119 +++ frontend/src/contractsABI/ERC20_ABI.js | 10 + .../{components => page}/CreateInvoice.jsx | 393 +++++++++- frontend/src/page/ReceivedInvoice.jsx | 97 ++- frontend/src/utils/erc20_token.js | 709 ++++++++++++++++++ 12 files changed, 1649 insertions(+), 29 deletions(-) create mode 100644 frontend/public/tokenImages/cin.png create mode 100644 frontend/public/tokenImages/eth.png create mode 100644 frontend/public/tokenImages/generic.png create mode 100644 frontend/src/components/TokenIntegrationRequest.jsx create mode 100644 frontend/src/components/TokenSelector.jsx create mode 100644 frontend/src/components/ui/select.jsx create mode 100644 frontend/src/contractsABI/ERC20_ABI.js rename frontend/src/{components => page}/CreateInvoice.jsx (58%) create mode 100644 frontend/src/utils/erc20_token.js diff --git a/frontend/package.json b/frontend/package.json index 84beb46c..4d32287a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -20,6 +20,7 @@ "@mui/material": "^6.4.6", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.5", + "@radix-ui/react-select": "^2.2.5", "@radix-ui/react-slot": "^1.1.1", "@rainbow-me/rainbowkit": "^2.2.2", "@tanstack/react-query": "^5.64.1", diff --git a/frontend/public/tokenImages/cin.png b/frontend/public/tokenImages/cin.png new file mode 100644 index 0000000000000000000000000000000000000000..6a822a0d79bcb686164362beeb2b1e59338fadfc GIT binary patch literal 8961 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 literal 0 HcmV?d00001 diff --git a/frontend/public/tokenImages/eth.png b/frontend/public/tokenImages/eth.png new file mode 100644 index 0000000000000000000000000000000000000000..45002fe3f3e1168d66f0da37c11cb570a0e80ea1 GIT binary patch literal 21779 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 + Using this token frequently?{" "} + + Request adding to default list + +

+ ); +} + +export default TokenIntegrationRequest; diff --git a/frontend/src/components/TokenSelector.jsx b/frontend/src/components/TokenSelector.jsx new file mode 100644 index 00000000..4fbcf087 --- /dev/null +++ b/frontend/src/components/TokenSelector.jsx @@ -0,0 +1,331 @@ +import React, { useRef, useState } from "react"; +import { TOKEN_PRESETS } from "@/utils/erc20_token"; +import { Label } from "@radix-ui/react-label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { Input } from "./ui/input"; +import { PlusIcon } from "lucide-react"; +import { Button } from "./ui/button"; +const POPULAR_TOKENS = [ + "0x0000000000000000000000000000000000000000", // ETH + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC + "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT + "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI +]; + +function TokenSelector() { + const [loading, setLoading] = useState(false); + const [selectedToken, setSelectedToken] = useState(TOKEN_PRESETS[0]); + const [customTokenAddress, setCustomTokenAddress] = useState(""); + const [useCustomToken, setUseCustomToken] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const inputRef = useRef(null); + const filteredTokens = TOKEN_PRESETS.filter( + (token) => + token.name.toLowerCase().includes(searchTerm.toLowerCase()) || + token.symbol.toLowerCase().includes(searchTerm.toLowerCase()) + ); + return ( + <> +
+

+ + + + + + + Payment Currency +

+ +
+
+
+ + setSearchTerm(e.target.value)} + /> +
+ {!searchTerm && ( +
+
+ Popular +
+ {TOKEN_PRESETS.filter((token) => + POPULAR_TOKENS.includes(token.address) + ).map((token) => ( + +
+
+ {token.name} { + e.currentTarget.src = + "/tokenImages/generic.png"; + }} + /> +
+
+ + {token.name} + + + {token.symbol} + +
+
+
+ ))} +
+ )} +
+
+ {searchTerm ? "Search Results" : "All Tokens"} +
+
+ {filteredTokens + .filter( + (token) => !POPULAR_TOKENS.includes(token.address) + ) + .map((token) => ( + +
+
+ {token.name} { + e.currentTarget.src = + "/tokenImages/generic.png"; + }} + /> +
+
+ + {token.name} + + + {token.symbol} + +
+
+
+ ))} +
+ {filteredTokens.length === 0 && ( +
+ No tokens found +
+ )} +
+ + +
+
+ +
+ + Custom Token + +
+
+ + +
+ + {!useCustomToken && ( +
+ +
+
+
+ {selectedToken.name} +
+
+ + {selectedToken.name} + + + {selectedToken.symbol} + +
+ {selectedToken.address} +
+
+
+
+
+ )} +
+ + {useCustomToken && ( +
+
+
+ + setCustomTokenAddress(e.target.value)} + disabled={loading} + /> +

+ Enter a valid ERC-20 token contract address. Make sure to + verify the token details before proceeding. +

+
+ {customTokenAddress && ( +
+
+
+
+ + + + + +
+
+

+ Custom Token Selected +

+

+ {customTokenAddress} +

+
+
+
+ +
+ )} +
+
+ )} + +
+

+ {useCustomToken ? ( + "Please verify the token contract address before creating the invoice." + ) : ( + <> + Note:{" "} + Payments will be processed in {selectedToken.symbol}. Ensure + your client has sufficient balance of this token. + + )} +

+
+
+

+ + ); +} + +export default TokenSelector; diff --git a/frontend/src/components/ui/select.jsx b/frontend/src/components/ui/select.jsx new file mode 100644 index 00000000..b1367bf2 --- /dev/null +++ b/frontend/src/components/ui/select.jsx @@ -0,0 +1,119 @@ +import * as React from "react" +import * as SelectPrimitive from "@radix-ui/react-select" +import { Check, ChevronDown, ChevronUp } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Select = SelectPrimitive.Root + +const SelectGroup = SelectPrimitive.Group + +const SelectValue = SelectPrimitive.Value + +const SelectTrigger = React.forwardRef(({ className, children, ...props }, ref) => ( + span]:line-clamp-1", + className + )} + {...props}> + {children} + + + + +)) +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName + +const SelectScrollUpButton = React.forwardRef(({ className, ...props }, ref) => ( + + + +)) +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName + +const SelectScrollDownButton = React.forwardRef(({ className, ...props }, ref) => ( + + + +)) +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName + +const SelectContent = React.forwardRef(({ className, children, position = "popper", ...props }, ref) => ( + + + + + {children} + + + + +)) +SelectContent.displayName = SelectPrimitive.Content.displayName + +const SelectLabel = React.forwardRef(({ className, ...props }, ref) => ( + +)) +SelectLabel.displayName = SelectPrimitive.Label.displayName + +const SelectItem = React.forwardRef(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)) +SelectItem.displayName = SelectPrimitive.Item.displayName + +const SelectSeparator = React.forwardRef(({ className, ...props }, ref) => ( + +)) +SelectSeparator.displayName = SelectPrimitive.Separator.displayName + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +} diff --git a/frontend/src/contractsABI/ERC20_ABI.js b/frontend/src/contractsABI/ERC20_ABI.js new file mode 100644 index 00000000..b1f283c8 --- /dev/null +++ b/frontend/src/contractsABI/ERC20_ABI.js @@ -0,0 +1,10 @@ +export const ERC20_ABI = [ + "function approve(address spender, uint256 amount) external returns (bool)", + "function transfer(address recipient, uint256 amount) external returns (bool)", + "function transferFrom(address sender, address recipient, uint256 amount) external returns (bool)", + "function balanceOf(address account) external view returns (uint256)", + "function allowance(address owner, address spender) external view returns (uint256)", + "function name() view returns (string)", + "function symbol() view returns (string)", + "function decimals() view returns (uint8)", +]; diff --git a/frontend/src/components/CreateInvoice.jsx b/frontend/src/page/CreateInvoice.jsx similarity index 58% rename from frontend/src/components/CreateInvoice.jsx rename to frontend/src/page/CreateInvoice.jsx index 642b0868..ba69bedf 100644 --- a/frontend/src/components/CreateInvoice.jsx +++ b/frontend/src/page/CreateInvoice.jsx @@ -1,7 +1,13 @@ 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 { Input } from "../components/ui/input"; +import { Button } from "../components/ui/button"; +import { + BrowserProvider, + Contract, + ethers, + formatUnits, + parseUnits, +} from "ethers"; import { useAccount, useWalletClient } from "wagmi"; import { ChainvoiceABI } from "../contractsABI/ChainvoiceABI"; import { @@ -10,10 +16,16 @@ import { PopoverTrigger, } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; -import { CalendarIcon, Loader2, PlusIcon } from "lucide-react"; +import { + CalendarIcon, + CheckCircle2, + Loader2, + PlusIcon, + XCircle, +} from "lucide-react"; import { cn } from "@/lib/utils"; import { format } from "date-fns"; -import { Label } from "./ui/label"; +import { Label } from "../components/ui/label"; import { useNavigate } from "react-router-dom"; import { LitNodeClient } from "@lit-protocol/lit-node-client"; @@ -25,6 +37,17 @@ import { LitAccessControlConditionResource, } from "@lit-protocol/auth-helpers"; +// Add this near the top with other imports +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { TOKEN_PRESETS } from "@/utils/erc20_token"; +import TokenIntegrationRequest from "@/components/TokenIntegrationRequest"; +import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; function CreateInvoice() { const { data: walletClient } = useWalletClient(); const { isConnected } = useAccount(); @@ -35,6 +58,26 @@ function CreateInvoice() { const navigate = useNavigate(); const litClientRef = useRef(null); + // Add these state variables + const [selectedToken, setSelectedToken] = useState(TOKEN_PRESETS[0]); + const [customTokenAddress, setCustomTokenAddress] = useState(""); + const [useCustomToken, setUseCustomToken] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const inputRef = useRef(null); + const [tokenVerificationState, setTokenVerificationState] = useState("idle"); + const [verifiedToken, setVerifiedToken] = useState(null); + const filteredTokens = TOKEN_PRESETS.filter( + (token) => + token.name.toLowerCase().includes(searchTerm.toLowerCase()) || + token.symbol.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const POPULAR_TOKENS = [ + "0x0000000000000000000000000000000000000000", // ETH + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC + "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT + "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI + ]; const [itemData, setItemData] = useState([ { description: "", @@ -50,19 +93,18 @@ function CreateInvoice() { useEffect(() => { const total = itemData.reduce((sum, item) => { - const qty = parseUnits(item.qty || "0", 18); + 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 lineTotal = (qty * unitPrice) / parseUnits("1", 18); const adjusted = lineTotal - discount + tax; - + return sum + adjusted; - }, 0n); + }, 0n); setTotalAmountDue(formatUnits(total, 18)); - }, [itemData]); useEffect(() => { @@ -124,6 +166,27 @@ function CreateInvoice() { ]); }; + const verifyToken = async (address) => { + setTokenVerificationState("verifying"); + + try { + const provider = new BrowserProvider(walletClient); + const contract = new ethers.Contract(address, ERC20_ABI, provider); + + const [symbol, name, decimals] = await Promise.all([ + contract.symbol().catch(() => "UNKNOWN"), + contract.name().catch(() => "Unknown Token"), + contract.decimals().catch(() => 18), + ]); + console.log({ address, symbol, name, decimals }); + setVerifiedToken({ address, symbol, name, decimals }); + setTokenVerificationState("success"); + } catch (error) { + console.error("Verification failed:", error); + setTokenVerificationState("error"); + } + }; + const createInvoiceRequest = async (data) => { if (!isConnected || !walletClient) { alert("Please connect your wallet"); @@ -135,11 +198,19 @@ function CreateInvoice() { const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); + // Determine the token to use + const paymentToken = useCustomToken ? verifiedToken : selectedToken; + // 1. Prepare invoice data const invoicePayload = { amountDue: totalAmountDue, dueDate, issueDate, + paymentToken: { + address: paymentToken.address, + symbol: paymentToken.symbol, + decimals: paymentToken.decimals, + }, user: { address: account?.address.toString(), fname: data.userFname, @@ -164,6 +235,7 @@ function CreateInvoice() { const invoiceString = JSON.stringify(invoicePayload); // 2. Setup Lit + const litNodeClient = litClientRef.current; if (!litNodeClient) { alert("Lit client not initialized"); return; @@ -243,14 +315,14 @@ function CreateInvoice() { ); const tx = await contract.createInvoice( data.clientAddress, - ethers.parseEther(totalAmountDue.toString()), + ethers.parseUnits(totalAmountDue.toString(), paymentToken.decimals), + paymentToken.address, encryptedStringBase64, dataToEncryptHash ); const receipt = await tx.wait(); setTimeout(() => navigate("/dashboard/sent"), 4000); - } catch (err) { console.error("Encryption or transaction failed:", err); alert("Failed to create invoice."); @@ -553,7 +625,300 @@ function CreateInvoice() {
+
+

+ + + + + + + Payment Currency +

+ +
+
+
+ + setSearchTerm(e.target.value)} + /> +
+ {!searchTerm && ( +
+
+ Popular +
+ {TOKEN_PRESETS.filter((token) => + POPULAR_TOKENS.includes(token.address) + ).map((token) => ( + +
+
+ {token.name} { + e.currentTarget.src = + "/tokenImages/generic.png"; + }} + /> +
+
+ + {token.name} + + + {token.symbol} + +
+
+
+ ))} +
+ )} +
+
+ {searchTerm ? "Search Results" : "All Tokens"} +
+
+ {filteredTokens + .filter( + (token) => !POPULAR_TOKENS.includes(token.address) + ) + .map((token) => ( + +
+
+ {token.name} { + e.currentTarget.src = + "/tokenImages/generic.png"; + }} + /> +
+
+ + {token.name} + + + {token.symbol} + +
+
+
+ ))} +
+ {filteredTokens.length === 0 && ( +
+ No tokens found +
+ )} +
+ + +
+
+ +
+ + Custom Token + +
+
+ + +
+ + {!useCustomToken && ( +
+ +
+
+
+ {selectedToken.name} +
+
+ + {selectedToken.name} + + + {selectedToken.symbol} + +
+ {selectedToken.address} +
+
+
+
+
+ )} +
+ + {useCustomToken && ( +
+
+ + { + const address = e.target.value; + setCustomTokenAddress(address); + if (!address || !ethers.isAddress(address)) { + setTokenVerificationState("idle"); + setVerifiedToken(null); + } else if (ethers.isAddress(address)) { + verifyToken(address); + } + }} + className="h-10 bg-gray-50 text-gray-700 border-gray-200 focus:ring-2 focus:ring-blue-100" + disabled={loading} + /> +

+ Enter a valid ERC-20 token contract address +

+
+ + {tokenVerificationState === "verifying" && ( +
+ + Verifying token... +
+ )} + {tokenVerificationState === "success" && verifiedToken && ( +
+
+
+ +
+

+ {verifiedToken.name} ({verifiedToken.symbol}) +

+

+ {verifiedToken.address} +

+

+

Decimals: {String(verifiedToken.decimals)}

+

+
+
+
+ +
+ )} + + {tokenVerificationState === "error" && ( +
+
+ +

+ Failed to verify token. Please check the address. +

+
+
+ )} +
+ )} + +
+

+ {useCustomToken ? ( + verifiedToken ? ( + <> + Note:{" "} + Payments will be processed in {verifiedToken?.symbol}. + Ensure your client has sufficient balance of this token. + + ) : ( + "" + ) + ) : ( + <> + Note:{" "} + Payments will be processed in {selectedToken.symbol}. Ensure + your client has sufficient balance of this token. + + )} +

+
+
+
{/* Invoice Items Section */}
@@ -676,8 +1041,8 @@ function CreateInvoice() {
Total: - {/* {totalAmountDue} ETH */} - {totalAmountDue} cBTC + {totalAmountDue}{" "} + {useCustomToken ? "TOKENS" : selectedToken.symbol}
diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index daade8b6..3c410554 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -22,6 +22,7 @@ import { generateAuthSig, LitAccessControlConditionResource, } from "@lit-protocol/auth-helpers"; +import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; const columns = [ { id: "fname", label: "First Name", minWidth: 100 }, @@ -230,9 +231,38 @@ function ReceivedInvoice() { console.log("invoices : ", receivedInvoices); }, [walletClient, litReady]); - const payInvoice = async (id, amountDue) => { + // 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(ethers.parseUnits(String(amountDue), 18)); + // const fee = await contract.fee(); + // console.log(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 payInvoice = async (invoiceId, amountDue, tokenAddress) => { + if (!walletClient) { + console.error("Wallet not connected"); + return; + } + try { - if (!walletClient) return; const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); const contract = new Contract( @@ -240,21 +270,60 @@ function ReceivedInvoice() { ChainvoiceABI, signer ); - console.log(ethers.parseUnits(String(amountDue), 18)); + const fee = await contract.fee(); - console.log(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, - }); + const isNativeToken = tokenAddress === ethers.ZeroAddress; + + if (!isNativeToken) { + const tokenContract = new Contract( + tokenAddress, + ERC20_ABI, + signer + ); + + const currentAllowance = await tokenContract.allowance( + await signer.getAddress(), + contract.address + ); + + const amountDueInWei = ethers.parseUnits( + String(amountDue), + await tokenContract.decimals() + ); + + if (currentAllowance < amountDueInWei) { + const approveTx = await tokenContract.approve( + contract.address, + amountDueInWei + ); + await approveTx.wait(); + } + + const tx = await contract.payInvoice(BigInt(invoiceId), { + value: fee, + }); + await tx.wait(); + } else { + const amountDueInWei = ethers.parseUnits(String(amountDue), 18); + const total = amountDueInWei + BigInt(fee); + + const tx = await contract.payInvoice(BigInt(invoiceId), { + value: total, + }); + await tx.wait(); + } + console.log("Payment successful!"); } catch (error) { - console.log(error); + console.error("Payment failed:", error); + if (error.code === "ACTION_REJECTED") { + alert("Transaction was rejected by user"); + } else if (error.message.includes("insufficient balance")) { + alert("Insufficient balance for this transaction"); + } else { + alert("Payment failed: " + error.message); + } } }; - const [drawerState, setDrawerState] = useState({ open: false, selectedInvoice: null, @@ -434,7 +503,7 @@ function ReceivedInvoice() {
diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index 3c410554..1f83eba5 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -91,9 +91,9 @@ function ReceivedInvoice() { const signer = await provider.getSigner(); const network = await provider.getNetwork(); - if (network.chainId != 5115) { + if (network.chainId != 11155111) { setError( - `Failed to load invoices. You're connected to the "${network.name}" network, but your invoices are on the "Citrea" testnet. Please switch to Sepolia and try again.` + `Failed to load invoices. You're connected to the "${network.name}" network, but your invoices are on the "Sepolia" testnet. Please switch to Sepolia and try again.` ); setLoading(false); @@ -132,9 +132,9 @@ function ReceivedInvoice() { 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]; + const isPaid = invoice[5]; + const encryptedStringBase64 = invoice[6]; // encryptedData + const dataToEncryptHash = invoice[7]; if (!encryptedStringBase64 || !dataToEncryptHash) continue; const currentUserAddress = address.toLowerCase(); @@ -231,31 +231,6 @@ function ReceivedInvoice() { 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(ethers.parseUnits(String(amountDue), 18)); - // const fee = await contract.fee(); - // console.log(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 payInvoice = async (invoiceId, amountDue, tokenAddress) => { if (!walletClient) { console.error("Wallet not connected"); @@ -270,30 +245,31 @@ function ReceivedInvoice() { ChainvoiceABI, signer ); - + const fee = await contract.fee(); const isNativeToken = tokenAddress === ethers.ZeroAddress; - + + if (!ethers.isAddress(tokenAddress)) { + throw new Error(`Invalid token address: ${tokenAddress}`); + } if (!isNativeToken) { const tokenContract = new Contract( tokenAddress, ERC20_ABI, signer ); - + const currentAllowance = await tokenContract.allowance( await signer.getAddress(), - contract.address + import.meta.env.VITE_CONTRACT_ADDRESS ); - const amountDueInWei = ethers.parseUnits( String(amountDue), await tokenContract.decimals() ); - if (currentAllowance < amountDueInWei) { const approveTx = await tokenContract.approve( - contract.address, + import.meta.env.VITE_CONTRACT_ADDRESS, amountDueInWei ); await approveTx.wait(); @@ -312,7 +288,6 @@ function ReceivedInvoice() { }); await tx.wait(); } - console.log("Payment successful!"); } catch (error) { console.error("Payment failed:", error); if (error.code === "ACTION_REJECTED") { @@ -424,7 +399,6 @@ function ReceivedInvoice() { className="hover:bg-[#32363F] transition duration-300" > {columns.map((column) => { - const value = invoice?.user[column.id] || ""; if (column.id === "to") { @@ -453,7 +427,8 @@ function ReceivedInvoice() { sx={{ color: "white", borderColor: "#25272b" }} > {/* {ethers.formatUnits(invoice.amountDue)} ETH */} - {invoice.amountDue} ETH + {invoice.amountDue}{" "} + {invoice.paymentToken?.symbol} ); } @@ -503,7 +478,11 @@ function ReceivedInvoice() { + +
)} diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index f48e2efc..ec02ea8e 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -6,15 +6,14 @@ 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, useRef } from "react"; +import React, { useEffect, useState } 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 { useRef } from "react"; 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"; @@ -23,22 +22,45 @@ import { generateAuthSig, LitAccessControlConditionResource, } from "@lit-protocol/auth-helpers"; +import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; +import { + CircularProgress, + Skeleton, + Chip, + Avatar, + Tooltip, + IconButton, +} from "@mui/material"; +import PaidIcon from "@mui/icons-material/CheckCircle"; +import UnpaidIcon from "@mui/icons-material/Pending"; +import DownloadIcon from "@mui/icons-material/Download"; +import CurrencyExchangeIcon from "@mui/icons-material/CurrencyExchange"; +import { TOKEN_PRESETS } from "@/utils/erc20_token"; const columns = [ - { 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 }, + { id: "fname", label: "Client", minWidth: 120 }, + { id: "to", label: "Receiver", minWidth: 150 }, + { id: "amountDue", label: "Amount", minWidth: 100, align: "right" }, + { id: "status", label: "Status", minWidth: 100 }, + { id: "date", label: "Date", minWidth: 100 }, + { id: "actions", label: "Actions", minWidth: 150 }, ]; function SentInvoice() { const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); + const [rowsPerPage, setRowsPerPage] = useState(10); + const { data: walletClient } = useWalletClient(); + const { address } = useAccount(); + const [loading, setLoading] = useState(true); + const [sentInvoices, setSentInvoices] = useState([]); + const [fee, setFee] = useState(0); + const [error, setError] = useState(null); + const [litReady, setLitReady] = useState(false); + const litClientRef = useRef(null); + const [paymentLoading, setPaymentLoading] = useState({}); + const [networkLoading, setNetworkLoading] = useState(false); + + const handleChangePage = (event, newPage) => { setPage(newPage); }; @@ -46,51 +68,46 @@ function SentInvoice() { setRowsPerPage(+event.target.value); setPage(0); }; - - const { data: walletClient } = useWalletClient(); - const [sentInvoices, setSentInvoices] = useState([]); - const [invoiceItems, setInvoiceItems] = useState([]); - const [loading, setLoading] = useState(false); - const [fee, setFee] = useState(0); - const [error, setError] = useState(null); - 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(() => { + 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); + } + } catch (error) { + console.error("Error initializing Lit client:", error); + } finally { + setLoading(false); + } + }; + initLit(); + }, []); useEffect(() => { - if (!walletClient || !litReady) return; + if (!walletClient || !address || !litReady) return; const fetchSentInvoices = async () => { try { setLoading(true); - - // 1. Setup signer + setError(null); const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); + const network = await provider.getNetwork(); + if (network.chainId != 11155111) { + setError( + `You're connected to ${network.name}. Please switch to Sepolia network to view your invoices.` + ); + setLoading(false); + return; + } // 2. Connect to Lit Node const litNodeClient = litClientRef.current; @@ -107,12 +124,11 @@ function SentInvoice() { ); const res = await contract.getSentInvoices(address); - console.log(res); + console.log("Raw invoices data:", res); if (!res || !Array.isArray(res) || res.length === 0) { console.warn("No invoices found."); setSentInvoices([]); - setInvoiceItems([]); setLoading(false); return; } @@ -120,89 +136,106 @@ function SentInvoice() { const decryptedInvoices = []; for (const invoice of res) { - const id = invoice[0]; - const from = invoice[1].toLowerCase(); - const to = invoice[2].toLowerCase(); - const isPaid = invoice[5]; - const encryptedStringBase64 = invoice[6]; // encryptedData - const dataToEncryptHash = invoice[7]; - - 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 + try { + const id = invoice[0]; + const from = invoice[1].toLowerCase(); + const to = invoice[2].toLowerCase(); + const isPaid = invoice[5]; + const encryptedStringBase64 = invoice[6]; + const dataToEncryptHash = invoice[7]; + + if (!encryptedStringBase64 || !dataToEncryptHash) continue; + + const currentUserAddress = address.toLowerCase(); + if (currentUserAddress !== from && currentUserAddress !== to) { + console.warn(`Unauthorized access attempt for invoice ${id}`); + continue; + } + const ciphertext = atob(encryptedStringBase64); + const accessControlConditions = [ + { + contractAddress: "", + standardContractType: "", + chain: "ethereum", + method: "", + parameters: [":userAddress"], + returnValueTest: { + comparator: "=", + value: from, + }, + }, + { operator: "or" }, + { + contractAddress: "", + standardContractType: "", + chain: "ethereum", + method: "", + parameters: [":userAddress"], + returnValueTest: { + comparator: "=", + value: to, + }, }, - }, - { operator: "or" }, - { - contractAddress: "", - standardContractType: "", + ]; + + const sessionSigs = await litNodeClient.getSessionSigs({ chain: "ethereum", - method: "", - parameters: [":userAddress"], - returnValueTest: { - comparator: "=", - value: invoice[2].toLowerCase(), // to + 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 sessionSigs = await litNodeClient.getSessionSigs({ - chain: "ethereum", - resourceAbilityRequests: [ + const decryptedString = await decryptToString( { - resource: new LitAccessControlConditionResource("*"), - ability: LIT_ABILITY.AccessControlConditionDecryption, + accessControlConditions, + chain: "ethereum", + ciphertext, + dataToEncryptHash, + sessionSigs, }, - ], - 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 - ); + litNodeClient + ); - const parsed = JSON.parse(decryptedString); - parsed["id"] = id; - parsed["isPaid"] = isPaid; - decryptedInvoices.push(parsed); + const parsed = JSON.parse(decryptedString); + parsed["id"] = id; + parsed["isPaid"] = isPaid; + if (parsed.paymentToken?.address) { + const tokenInfo = TOKEN_PRESETS.find( + (t) => + t.address.toLowerCase() === + parsed.paymentToken.address.toLowerCase() + ); + if (tokenInfo) { + parsed.paymentToken = { + ...parsed.paymentToken, + logo: tokenInfo.logo, + decimals: tokenInfo.decimals, + }; + } + } + decryptedInvoices.push(parsed); + } catch (err) { + console.error(`Error processing invoice ${invoice[0]}:`, err); + } } setSentInvoices(decryptedInvoices); @@ -210,15 +243,13 @@ function SentInvoice() { setFee(fee); } catch (error) { console.error("Decryption error:", error); - alert("Failed to decrypt invoice."); } finally { setLoading(false); - console.log(sentInvoices); } }; fetchSentInvoices(); - }, [walletClient, litReady]); + }, [walletClient, litReady, address]); const [drawerState, setDrawerState] = useState({ open: false, @@ -226,7 +257,6 @@ function SentInvoice() { }); const toggleDrawer = (invoice) => (event) => { - console.log(invoice); if ( event && event.type === "keydown" && @@ -240,327 +270,515 @@ function SentInvoice() { }); }; - const contentRef = useRef(); const handlePrint = async () => { - const element = contentRef.current; - if (!element) { - return; - } + const element = document.getElementById("invoice-print"); + if (!element) return; - const canvas = await html2canvas(element, { - scale: 2, - }); + const canvas = await html2canvas(element, { scale: 2 }); const data = canvas.toDataURL("image/png"); - // download feature (implement later on) - // const pdf = new jsPDF({ - // orientation: "portrait", - // unit: "px", - // format: "a4", - // }); + const link = document.createElement("a"); + link.download = `invoice-${drawerState.selectedInvoice.id}.png`; + link.href = data; + link.click(); + }; - // const imgProperties = pdf.getImageProperties(data); - // const pdfWidth = pdf.internal.pageSize.getWidth(); + const switchNetwork = async () => { + try { + setNetworkLoading(true); + await window.ethereum.request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: "0xaa36a7" }], // Sepolia chain ID + }); + setError(null); + } catch (error) { + console.error("Network switch failed:", error); + alert("Failed to switch network. Please switch to Sepolia manually."); + } finally { + setNetworkLoading(false); + } + }; - // const pdfHeight = (imgProperties.height * pdfWidth) / imgProperties.width; + const formatAddress = (address) => { + return `${address.substring(0, 10)}...${address.substring( + address.length - 10 + )}`; + }; - // pdf.addImage(data, "PNG", 0, 0, pdfWidth, pdfHeight); - // pdf.save("invoice.pdf"); + const formatDate = (issueDate) => { + const date = new Date(issueDate); + return date.toLocaleString(); }; return ( -
-

Your Sent Invoice Request

- - {loading ? ( -

Loading invoices...

- ) : error ? ( -

{error}

- ) : sentInvoices.length === 0 ? ( -

No invoices found

- ) : ( - <> - - - - - {columns.map((column) => ( - - {column.label} - - ))} - - - - {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.client.address - ? `${invoice.client.address.substring( - 0, - 10 - )}...${invoice.client.address.substring( - invoice.client.address.length - 10 - )}` - : "N/A"} - - ); - } - if (column.id === "amountDue") { - return ( - +
+
+
+

Sent Invoices

+
+ {error && ( + + )} +
+ + + {loading ? ( +
+
+ + +
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ ) : error ? ( +
+
+

{error}

+
+
+ ) : sentInvoices.length === 0 ? ( +
+
+ +

+ No Invoices Found +

+

+ You haven't sent any invoices yet. +

+
+
+ ) : ( + <> + +
+ + + {columns.map((column) => ( + + {column.label} + + ))} + + + + {sentInvoices + .slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage + ) + .map((invoice) => ( + + {/* Client Column */} + +
+ + {invoice.client?.fname?.charAt(0) || "C"} + +
+
+ {invoice.client?.fname}{" "} + {invoice.client?.lname} +
+
+ {invoice.client?.email} +
+
+
+
+ + {/* Sender Column */} + + + + {formatAddress(invoice.client?.address)} + + + + + {/* Amount Column */} + +
+ {invoice.paymentToken?.logo ? ( + {invoice.paymentToken.symbol} + ) : ( + + )} + {invoice.amountDue}{" "} {invoice.paymentToken?.symbol} - - ); - } - if (column.id === "isPaid") { - return ( - - - - ); - } - if (column.id === "detail") { - return ( - -
+
+ + {/* Status Column */} + + : + } + label={invoice.isPaid ? "Paid" : "Pending"} + color={invoice.isPaid ? "success" : "warning"} + size="small" + variant="outlined" + /> + + + {/* Date Column */} + + + + {formatDate(invoice.issueDate)} + + + + + +
+ + - - - - ); - } - return ( - - {value} - - ); - })} - - ))} - -
-
- - + + +
+ + + ))} + + + + - - )} - - + "& .MuiSelect-icon": { + color: "#64748b", + }, + }} + /> + + )} + +
+ + {/* Invoice Detail Drawer */} {drawerState.selectedInvoice && ( -
-
-
- none -
-

- Issued by {drawerState.selectedInvoice.issueDate} -

-

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

-
+
+
+
+ Company Logo +

+ Powered by Chainvoice +

-
-

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

+
+

INVOICE

+

+ #{drawerState.selectedInvoice.id.toString().padStart(6, "0")} +

+
+ +
+
-
-

From

-

+

+
+

+ From +

+

+ {drawerState.selectedInvoice.user.fname}{" "} + {drawerState.selectedInvoice.user.lname} +

+

{drawerState.selectedInvoice.user.address}

-

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

-

+

+ {drawerState.selectedInvoice.user.city},{" "} + {drawerState.selectedInvoice.user.country},{" "} + {drawerState.selectedInvoice.user.postalcode} +

+

{drawerState.selectedInvoice.user.email}

-

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

-
-

Billed to

-

+

+

+ Bill To +

+

+ {drawerState.selectedInvoice.client.fname}{" "} + {drawerState.selectedInvoice.client.lname} +

+

{drawerState.selectedInvoice.client.address}

-

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

-

+

+ {drawerState.selectedInvoice.client.city},{" "} + {drawerState.selectedInvoice.client.country},{" "} + {drawerState.selectedInvoice.client.postalcode} +

+

{drawerState.selectedInvoice.client.email}

-

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

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

+ Payment Currency +

+
+ {drawerState.selectedInvoice.paymentToken?.logo ? ( + {drawerState.selectedInvoice.paymentToken.symbol} + ) : ( +
+ +
+ )} +
+

+ {drawerState.selectedInvoice.paymentToken?.name || "Ether "} + {"("} + {drawerState.selectedInvoice.paymentToken?.symbol || "ETH"} + {")"} +

+

+ {drawerState.selectedInvoice.paymentToken?.address + ? `${drawerState.selectedInvoice.paymentToken.address.substring( + 0, + 10 + )}......${drawerState.selectedInvoice.paymentToken.address.substring( + 33 + )}` + : "Native Currency"} +

+
+
+ {drawerState.selectedInvoice.paymentToken?.address && ( +
+

Chain: Sepolia Testnet

+
+ )} +
+
+
+ + Issued:{" "} + {new Date( + drawerState.selectedInvoice.issueDate + ).toLocaleDateString()} + + + Due:{" "} + {new Date( + drawerState.selectedInvoice.dueDate + ).toLocaleDateString()} + +
+
+ +
+
DescriptionQTYUnit PriceDiscountTaxAmount
+ + + + + + + + - {drawerState.selectedInvoice?.items?.map((item, index) => ( - - - - - + {drawerState.selectedInvoice.items?.map((item, index) => ( + + + + - - - - - ))} + ))} +
DescriptionQtyPriceDiscountTaxAmount
- {item.description} - - {item.qty.toString()} - - {item.unitPrice} +
{item.description}{item.qty} + {item.unitPrice}{" "} + {drawerState.selectedInvoice.paymentToken?.symbol} - {item.discount.toString() == "" - ? "NIL" - : item.discount.toString()} + + {item.discount || "0"} - {item.tax.toString() == "" - ? "NIL" - : item.tax.toString()} + + {item.tax || "0%"} + {item.amount}{" "} - {drawerState.selectedInvoice?.paymentToken?.symbol} + {drawerState.selectedInvoice.paymentToken?.symbol}
-
-

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

-

- {" "} - Amount: {drawerState.selectedInvoice.amountDue}{" "} - {drawerState.selectedInvoice?.paymentToken?.symbol} -

-

- Total Amount:{" "} - {drawerState.selectedInvoice?.paymentToken?.symbol == "ETH" - ? parseFloat(drawerState.selectedInvoice.amountDue) + - parseFloat(ethers.formatUnits(fee)) - : `${parseFloat(drawerState.selectedInvoice.amountDue)} ${ - drawerState.selectedInvoice?.paymentToken?.symbol - } + ${parseFloat(ethers.formatUnits(fee))} ETH`} -

+
+ +
+
+ Subtotal: + + {drawerState.selectedInvoice.amountDue}{" "} + {drawerState.selectedInvoice.paymentToken?.symbol} + +
+
+ Network Fee: + + {ethers.formatUnits(fee)} ETH +
-
-

Powered by

- +
+ Total Amount: + + {drawerState.selectedInvoice.paymentToken?.symbol === "ETH" + ? `${( + parseFloat(drawerState.selectedInvoice.amountDue) + + parseFloat(ethers.formatUnits(fee)) + ).toFixed(6)} ETH` + : `${drawerState.selectedInvoice.amountDue} ${ + drawerState.selectedInvoice.paymentToken?.symbol + } + ${ethers.formatUnits(fee)} ETH`} +
+ +
+ + +
)} diff --git a/frontend/src/page/Treasure.jsx b/frontend/src/page/Treasure.jsx index e2cbaf7b..5cb9a422 100644 --- a/frontend/src/page/Treasure.jsx +++ b/frontend/src/page/Treasure.jsx @@ -134,7 +134,7 @@ const Treasure = () => { {loading.fetch ? ( ) : ( - `${treasureAmount} cBTC` + `${treasureAmount} ETH` )}
@@ -147,7 +147,7 @@ const Treasure = () => { {loading.fetch ? ( ) : ( - `${fee} cBTC` + `${fee} ETH` )}
@@ -235,7 +235,7 @@ const Treasure = () => { {loading.withdraw ? ( ) : null} - Transfer {treasureAmount} cBTC + Transfer {treasureAmount} ETH

Will be sent to the current treasury address From 99defe673cab102f79f0203654af1f18e3ada6d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Sun, 13 Jul 2025 18:56:13 +0530 Subject: [PATCH 26/39] treasure fee set --- frontend/public/dashboard.png | Bin 0 -> 300370 bytes frontend/public/lit-protocol-diagram.png | Bin 0 -> 32040 bytes frontend/public/lit-protocol-logo.png | Bin 0 -> 3847 bytes frontend/public/token-select.png | Bin 0 -> 47673 bytes frontend/src/components/Footer.jsx | 4 +- frontend/src/components/Navbar.jsx | 46 +- frontend/src/components/TokenCrousel.jsx | 84 ++++ frontend/src/page/Landing.jsx | 541 +++++++++++++++-------- frontend/src/page/ReceivedInvoice.jsx | 13 +- frontend/src/page/SentInvoice.jsx | 14 +- frontend/src/page/Treasure.jsx | 216 ++++++--- 11 files changed, 646 insertions(+), 272 deletions(-) create mode 100644 frontend/public/dashboard.png create mode 100644 frontend/public/lit-protocol-diagram.png create mode 100644 frontend/public/lit-protocol-logo.png create mode 100644 frontend/public/token-select.png create mode 100644 frontend/src/components/TokenCrousel.jsx diff --git a/frontend/public/dashboard.png b/frontend/public/dashboard.png new file mode 100644 index 0000000000000000000000000000000000000000..e21458469fb2c949e4700628b5dc3aa0ff0172ab GIT binary patch literal 300370 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 >iMJ);?Nswh*B=r_(T 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 JmO~8u{~yeqS)u>{ literal 0 HcmV?d00001 diff --git a/frontend/public/lit-protocol-logo.png b/frontend/public/lit-protocol-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..c01a54874490bd0f19ccfb964481af9942150972 GIT binary patch literal 3847 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 JPDHLkV1k}AUe*8r literal 0 HcmV?d00001 diff --git a/frontend/public/token-select.png b/frontend/public/token-select.png new file mode 100644 index 0000000000000000000000000000000000000000..c73e61baa786df90506fbf221669dc0fa8736be3 GIT binary patch literal 47673 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 TU-TCQ{8M

*/} -

+ {/*

© {new Date().getFullYear()} Chainvoice. All rights reserved. -

+

*/}
diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index a88bc0fe..168ab4ca 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -75,28 +75,34 @@ function Navbar() { } }; - const navItems = [ + // const navItems = [ + // { + // name: "Home", + // icon: , + // action: () => handleScroll("home-section"), + // path: "/", + // }, + // { + // name: "Features", + // icon: , + // action: () => handleScroll("feature-section"), + // path: "/#feature-section", + // }, + // { + // name: "Services", + // icon: , + // action: () => handleScroll("service-section"), + // path: "/#service-section", + // }, + // ]; + + const appItems = [ { name: "Home", icon: , action: () => handleScroll("home-section"), path: "/", }, - { - name: "Features", - icon: , - action: () => handleScroll("feature-section"), - path: "/#feature-section", - }, - { - name: "Services", - icon: , - action: () => handleScroll("service-section"), - path: "/#service-section", - }, - ]; - - const appItems = [ { name: "Dashboard", icon: , @@ -152,7 +158,7 @@ function Navbar() { {/* Desktop Navigation */}
- {navItems.map((item) => ( + {/* {navItems.map((item) => ( {item.icon} {item.name} - ))} + ))} */} {isConnected && appItems.map((item) => ( @@ -261,7 +267,7 @@ function Navbar() { >
{/* Navigation Links */} - {navItems.map((item) => ( + {/* {navItems.map((item) => ( {item.icon} {item.name} - ))} + ))} */} {isConnected && appItems.map((item) => ( diff --git a/frontend/src/components/TokenCrousel.jsx b/frontend/src/components/TokenCrousel.jsx new file mode 100644 index 00000000..ef11603f --- /dev/null +++ b/frontend/src/components/TokenCrousel.jsx @@ -0,0 +1,84 @@ +import { useEffect, useRef } from "react"; +import { motion } from "framer-motion"; +import { SiEthereum } from "react-icons/si"; +import { TOKEN_PRESETS } from "@/utils/erc20_token"; + +const TokenCarousel = () => { + const carouselRef = useRef(); + const duplicatedTokens = [...TOKEN_PRESETS, ...TOKEN_PRESETS]; // Double the tokens for seamless loop + + useEffect(() => { + const carousel = carouselRef.current; + let animationFrame; + let speed = 1; // Pixels per frame + let position = 0; + + const animate = () => { + position -= speed; + if (position <= -carousel.scrollWidth / 2) { + position = 0; + } + carousel.style.transform = `translateX(${position}px)`; + animationFrame = requestAnimationFrame(animate); + }; + + animate(); + + // Pause on hover + const pause = () => cancelAnimationFrame(animationFrame); + const resume = () => { + cancelAnimationFrame(animationFrame); + animate(); + }; + + carousel.addEventListener("mouseenter", pause); + carousel.addEventListener("mouseleave", resume); + + return () => { + cancelAnimationFrame(animationFrame); + carousel.removeEventListener("mouseenter", pause); + carousel.removeEventListener("mouseleave", resume); + }; + }, []); + + return ( +
+
+ + {duplicatedTokens.map((token, index) => ( + +
+
+ {token.symbol} { + e.target.src = "/tokenImages/default.png"; + }} + /> + {token.address === + "0x0000000000000000000000000000000000000000" && ( +
+ +
+ )} +
+
+

{token.symbol}

+

{token.name}

+
+
+
+ ))} +
+
+
+ ); +}; + +export default TokenCarousel; diff --git a/frontend/src/page/Landing.jsx b/frontend/src/page/Landing.jsx index 4f87f6a5..34054e44 100644 --- a/frontend/src/page/Landing.jsx +++ b/frontend/src/page/Landing.jsx @@ -1,221 +1,406 @@ import { ConnectButton } from "@rainbow-me/rainbowkit"; import React, { useEffect } from "react"; -import { useNavigate } from "react-router-dom"; -import { useAccount } from "wagmi"; -import ShieldIcon from "@mui/icons-material/Shield"; -import EmailIcon from "@mui/icons-material/Email"; -import GavelIcon from "@mui/icons-material/Gavel"; -import LeaderboardIcon from "@mui/icons-material/Leaderboard"; -function Landing() { - const Account = useAccount(); - const navigate = useNavigate(); +import { motion } from "framer-motion"; +import { + FiShield, + FiMail, + FiCode, + FiTrendingUp, + FiLock, + FiZap, +} from "react-icons/fi"; +import { SiEthereum } from "react-icons/si"; +import { LockIcon } from "lucide-react"; +import TokenCarousel from "@/components/TokenCrousel"; - // useEffect(() => { - // if (Account.address) navigate("/home/sent"); - // }, [Account, Account.address]); +function Landing() { + useEffect(() => { + // Smooth scroll for anchor links + document.querySelectorAll('a[href^="#"]').forEach((anchor) => { + anchor.addEventListener("click", function (e) { + e.preventDefault(); + document.querySelector(this.getAttribute("href")).scrollIntoView({ + behavior: "smooth", + }); + }); + }); + }, []); return ( - <> -
-
-

- Decentralized Payment Requests & -
- Invoice Automation -

-

- One click to effortless invoicing — process payments in any ERC20 - token with transparency and trustless efficiency! -

- {/*

Only with

-
Chainvoice
*/} - -

- Connect with your wallet and Get Started! -

-
- -
+
+ {/* Hero Section */} +
+
+
+
-
-

- - Trusted by 1000+ users -

-

- - Smart Contract Driven 100% Secure -

+
+
+
+ + + Web3 Invoicing + {" "} +
+ Made Simple +
+ +

+ End-to-end encrypted, multi-chain invoicing with Lit Protocol + and support for 1000+ ERC20 tokens. +

+ +
+
+
+ Lit Protocol + Lit Protocol Encrypted +
+
+ + Multi-chain Support +
+
+ + End-to-End Security +
+
+
+
+ +
+ + Secure Invoice Dashboard +
+
+ Lit Protocol +

+ Encrypted with Lit Protocol +

+
+
+
+
-
- -
-
-
-
-

- A powerful and secure{" "} - - invoicing solution - {" "} - designed for growing businesses. -

-
+
+ + {/* Security Section */} +
+
+
+ + Military-Grade Security{" "} + Powered by Lit Protocol + +
+ +
{[ { - title: "Secure and Transparent Transactions", + icon: , + title: "Decentralized Encryption", description: - "Leverage blockchain technology to ensure encrypted, tamper-proof, and immutable transactions. Provide complete transparency for invoice verification.", - icon: , + "Invoice data is encrypted using Lit Protocol's distributed key management system, ensuring no single party can access sensitive information without proper authorization.", }, { - title: "Send and Receive Invoices", + icon: , + title: "Conditional Access", description: - "Effortlessly create and manage invoices with a few clicks. Track real-time status and maintain a comprehensive invoice dashboard.", - icon: , + "Define precise access conditions using blockchain parameters. Payments automatically decrypt invoice details when conditions are met.", }, { - title: "Smart Contract Integration", + icon: , + title: "Cross-Chain Compatibility", description: - "Automate payment processes with secure smart contracts. Ensure funds are released only when invoice conditions are met, reducing intermediary dependencies.", - icon: , - }, - { - title: "Comprehensive Invoice Tracking", - description: - "Gain complete visibility into your invoice lifecycle. Monitor payment statuses, track financial performance, and manage all transactions seamlessly.", - icon: , + "Our Lit Protocol integration works seamlessly across all supported chains, maintaining security consistency throughout the ecosystem.", }, ].map((feature, index) => ( -
-
- {feature.icon} +
+ {feature.icon}
-

- {feature.title} -

-

{feature.description}

-
+

{feature.title}

+

+ {feature.description} +

+ ))}
-
- Invoice Illustration -
-
-
- Aeroplane 2 -
-

- {" "} - Start Sending Your -

-

- Invoice Today! + + {/* Token Support Section */} +

+
+
+ + Universal Token Support + +

+ Accept payments in any ERC20 token while maintaining full + encryption and security through Lit Protocol

- Aeroplane 1 + + + +
+
+ + Seamless Multi-Token{" "} + Payments + + +

+ Chainvoice's smart contract architecture automatically handles + token conversions and verifications, while Lit Protocol ensures + all payment details remain encrypted until settlement. Our + system supports: +

+ +
    +
  • + + All standard ERC20 tokens across EVM chains +
  • +
  • + + Native chain currencies (ETH, MATIC, etc.) +
  • +
  • + + Stablecoins with automatic price feeds +
  • +
  • + + Custom token whitelisting for enterprise clients +
  • +
+
+ + + Token Payment Flow +
+ 1000+ ERC20 Token +
+
+
+
+
+ + {/* CTA Section */} +
+
+
+
+ +
+ +

+ Ready to Experience{" "} + Next-Gen Invoicing? +

+ +

+ Join thousands of Web3-native businesses already using Chainvoice + for secure, encrypted invoicing with support for all major ERC20 + tokens across multiple chains. +

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

+ © {new Date().getFullYear()} Chainvoice. All rights reserved. +

+ +
+
+ +
); } + export default Landing; diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index 556e3574..186a837a 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -408,9 +408,7 @@ function ReceivedInvoice() {
-

- Received Invoices -

+

Received Invoices

Manage and pay your incoming invoices

@@ -697,7 +695,14 @@ function ReceivedInvoice() { >
- Company Logo +
+ Chainvoice +

+ Chain + voice +

+
+

Powered by Chainvoice

diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index ec02ea8e..5a1759f0 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -464,7 +464,7 @@ function SentInvoice() { {invoice.paymentToken.symbol} ) : (
- Company Logo -

- Powered by Chainvoice -

+ Powered by +
+ Chainvoice +

+ Chain + voice +

+
diff --git a/frontend/src/page/Treasure.jsx b/frontend/src/page/Treasure.jsx index 5cb9a422..7e340b52 100644 --- a/frontend/src/page/Treasure.jsx +++ b/frontend/src/page/Treasure.jsx @@ -10,8 +10,11 @@ import { Banknote, Key, Wallet, - DollarSignIcon, + DollarSign, + Settings, + ChevronRight, } from "lucide-react"; +import { motion } from "framer-motion"; const Treasure = () => { const [treasureAmount, setTreasureAmount] = useState(0); @@ -21,9 +24,11 @@ const Treasure = () => { fetch: false, setAddress: false, withdraw: false, + feeUpdate: false, }); const [treasuryAddress, setTreasuryAddress] = useState(""); const [newTreasuryAddress, setNewTreasuryAddress] = useState(""); + const [newFee, setNewFee] = useState(""); useEffect(() => { const fetchTreasureAmount = async () => { @@ -107,30 +112,71 @@ const Treasure = () => { } }; + const handleUpdateFee = async () => { + if (!newFee || isNaN(newFee)) { + alert("Please enter a valid fee amount"); + return; + } + try { + if (!walletClient) return; + setLoading((prev) => ({ ...prev, feeUpdate: true })); + const provider = new BrowserProvider(walletClient); + const signer = await provider.getSigner(); + const contract = new Contract( + import.meta.env.VITE_CONTRACT_ADDRESS, + ChainvoiceABI, + signer + ); + const tx = await contract.setFeeAmount( + ethers.parseUnits(newFee, "ether") + ); + await tx.wait(); + const updatedFee = await contract.fee(); + setFee(ethers.formatUnits(updatedFee)); + setNewFee(""); + alert("Fee updated successfully!"); + } catch (error) { + console.error("Error updating fee:", error); + alert(error.message || "Failed to update fee"); + } finally { + setLoading((prev) => ({ ...prev, feeUpdate: false })); + } + }; + return (
-
-
+ + {/* Treasury Overview Card */} +
-
-
+
+
-
+
-

+

Treasury Vault

-
-
- - - Current Balance: - - + +
+
+
+ + Current Balance +
+ {loading.fetch ? ( ) : ( @@ -138,12 +184,13 @@ const Treasure = () => { )}
-
- - - Fee Per Transaction: - - + +
+
+ + Transaction Fee +
+ {loading.fetch ? ( ) : ( @@ -151,33 +198,43 @@ const Treasure = () => { )}
-
- - - Admin Access Only - + +
+ + Admin Access Only
-
+ - {/* Content */} -
-

- Treasury Controls -

+ {/* Control Panel */} + +
+ +

+ Treasury Controls +

+

- Secure management of platform funds and treasury settings + Manage platform funds and configuration settings

-
-
- -

Current Treasury

+ {/* Treasury Address Section */} +
+
+ +

+ Treasury Address +

-
+

{loading.fetch ? ( @@ -188,61 +245,94 @@ const Treasure = () => { )}

-
-
-
- -

- Update Treasury Address -

-
-
+
setNewTreasuryAddress(e.target.value)} - className="flex-1 bg-gray-800 border-gray-700 text-white font-mono text-sm" + className="bg-gray-800 border-gray-700 text-white font-mono" /> +

+ Requires contract owner privileges +

+
+
+ + {/* Fee Adjustment Section */} +
+
+ +

+ Transaction Fee +

+
+
+ setNewFee(e.target.value)} + className="bg-gray-800 border-gray-700 text-white font-mono" + /> + +

+ Applies to all future transactions +

-

- Requires contract owner privileges -

- {/* Withdraw */} + {/* Withdrawal Section */}
- -

Funds Withdrawal

+ +

Withdraw Funds

- Will be sent to the current treasury address + Funds will be sent to the current treasury address

-
-
+ +
); }; From 545031b61a505fd6fe90aca64f4fbb36ef6a6ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Thu, 14 Aug 2025 12:22:00 +0530 Subject: [PATCH 27/39] contract update with cancel property --- contracts/src/Chainvoice.sol | 71 +++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/contracts/src/Chainvoice.sol b/contracts/src/Chainvoice.sol index 83afcb42..662a07ad 100644 --- a/contracts/src/Chainvoice.sol +++ b/contracts/src/Chainvoice.sol @@ -22,6 +22,7 @@ contract Chainvoice { uint256 amountDue; address tokenAddress; bool isPaid; + bool isCancelled; string encryptedInvoiceData; // Base64-encoded ciphertext string encryptedHash; } @@ -51,6 +52,13 @@ contract Chainvoice { address tokenAddress ); + event InvoiceCancelled( + uint256 indexed id, + address indexed from, + address indexed to, + address tokenAddress + ); + constructor() { owner = msg.sender; fee = 0.0005 ether; @@ -71,9 +79,9 @@ contract Chainvoice { require(to != address(0), "Recipient address is zero"); require(to != msg.sender, "Self-invoicing not allowed"); - if (tokenAddress != address(0)) { + if (tokenAddress != address(0)) { require(tokenAddress.code.length > 0, "Not a contract address"); - (bool success,) = tokenAddress.staticcall( + (bool success, ) = tokenAddress.staticcall( abi.encodeWithSignature("balanceOf(address)", address(this)) ); require(success, "Not an ERC20 token"); @@ -88,6 +96,7 @@ contract Chainvoice { amountDue: amountDue, tokenAddress: tokenAddress, isPaid: false, + isCancelled: false, encryptedInvoiceData: encryptedInvoiceData, encryptedHash: encryptedHash }) @@ -99,25 +108,50 @@ contract Chainvoice { emit InvoiceCreated(invoiceId, msg.sender, to, tokenAddress); } + function cancelInvoice(uint256 invoiceId) external { + require(invoiceId < invoices.length, "Invalid invoice ID"); + InvoiceDetails storage invoice = invoices[invoiceId]; + + require(msg.sender == invoice.from, "Only invoice creator can cancel"); + require( + !invoice.isPaid && !invoice.isCancelled, + "Invoice not cancellable" + ); + invoice.isCancelled = true; + emit InvoiceCancelled(invoiceId, invoice.from, invoice.to, invoice.tokenAddress); + } + 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"); require(!invoice.isPaid, "Already paid"); + require(!invoice.isCancelled, "Invoice is cancelled"); if (invoice.tokenAddress == address(0)) { // Native token (ETH) payment - require(msg.value == invoice.amountDue + fee,"Incorrect payment amount"); + require( + msg.value == invoice.amountDue + fee, + "Incorrect payment amount" + ); accumulatedFees += fee; uint256 amountToSender = msg.value - fee; - (bool sent, ) = payable(invoice.from).call{value: amountToSender}(""); + (bool sent, ) = payable(invoice.from).call{value: amountToSender}( + "" + ); require(sent, "Transfer failed"); } else { // ERC20 token payment require(msg.value == fee, "Must pay fee in native token"); - require(IERC20(invoice.tokenAddress).allowance(msg.sender,address(this)) >= invoice.amountDue,"Insufficient allowance"); + require( + IERC20(invoice.tokenAddress).allowance( + msg.sender, + address(this) + ) >= invoice.amountDue, + "Insufficient allowance" + ); accumulatedFees += fee; bool transferSuccess = IERC20(invoice.tokenAddress).transferFrom( @@ -138,14 +172,20 @@ contract Chainvoice { ); } - function getPaymentStatus(uint256 invoiceId, address payer) external view returns ( - bool canPay, - uint256 payerBalance, - uint256 allowanceAmount - ) { + function getPaymentStatus( + uint256 invoiceId, + address payer + ) + external + view + returns (bool canPay, uint256 payerBalance, uint256 allowanceAmount) + { require(invoiceId < invoices.length, "Invalid invoice ID"); InvoiceDetails memory invoice = invoices[invoiceId]; - + if (invoice.isCancelled) { + return (false, (payer).balance, 0); + } + if (invoice.tokenAddress == address(0)) { return ( payer.balance >= invoice.amountDue + fee, @@ -154,8 +194,13 @@ contract Chainvoice { ); } else { return ( - IERC20(invoice.tokenAddress).balanceOf(payer) >= invoice.amountDue && - IERC20(invoice.tokenAddress).allowance(payer, address(this)) >= invoice.amountDue, + IERC20(invoice.tokenAddress).balanceOf(payer) >= + invoice.amountDue && + IERC20(invoice.tokenAddress).allowance( + payer, + address(this) + ) >= + invoice.amountDue, IERC20(invoice.tokenAddress).balanceOf(payer), IERC20(invoice.tokenAddress).allowance(payer, address(this)) ); From e46a4f39111e22119ba1ed5162c1c35d267a82c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Thu, 14 Aug 2025 12:23:02 +0530 Subject: [PATCH 28/39] frontend update with cancel property --- frontend/src/contractsABI/ChainvoiceABI.js | 64 +++++++++++++++ frontend/src/page/SentInvoice.jsx | 90 ++++++++++++++++++---- 2 files changed, 140 insertions(+), 14 deletions(-) diff --git a/frontend/src/contractsABI/ChainvoiceABI.js b/frontend/src/contractsABI/ChainvoiceABI.js index 79af44d4..2639dae6 100644 --- a/frontend/src/contractsABI/ChainvoiceABI.js +++ b/frontend/src/contractsABI/ChainvoiceABI.js @@ -17,6 +17,19 @@ export const ChainvoiceABI = [ ], stateMutability: "view", }, + { + type: "function", + name: "cancelInvoice", + inputs: [ + { + name: "invoiceId", + type: "uint256", + internalType: "uint256", + }, + ], + outputs: [], + stateMutability: "nonpayable", + }, { type: "function", name: "createInvoice", @@ -109,6 +122,11 @@ export const ChainvoiceABI = [ type: "bool", internalType: "bool", }, + { + name: "isCancelled", + type: "bool", + internalType: "bool", + }, { name: "encryptedInvoiceData", type: "string", @@ -204,6 +222,11 @@ export const ChainvoiceABI = [ type: "bool", internalType: "bool", }, + { + name: "isCancelled", + type: "bool", + internalType: "bool", + }, { name: "encryptedInvoiceData", type: "string", @@ -265,6 +288,11 @@ export const ChainvoiceABI = [ type: "bool", internalType: "bool", }, + { + name: "isCancelled", + type: "bool", + internalType: "bool", + }, { name: "encryptedInvoiceData", type: "string", @@ -321,6 +349,11 @@ export const ChainvoiceABI = [ type: "bool", internalType: "bool", }, + { + name: "isCancelled", + type: "bool", + internalType: "bool", + }, { name: "encryptedInvoiceData", type: "string", @@ -454,6 +487,37 @@ export const ChainvoiceABI = [ outputs: [], stateMutability: "nonpayable", }, + { + type: "event", + name: "InvoiceCancelled", + 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: "tokenAddress", + type: "address", + indexed: false, + internalType: "address", + }, + ], + anonymous: false, + }, { type: "event", name: "InvoiceCreated", diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index 5a1759f0..52f7ffa9 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -34,14 +34,14 @@ import { import PaidIcon from "@mui/icons-material/CheckCircle"; import UnpaidIcon from "@mui/icons-material/Pending"; import DownloadIcon from "@mui/icons-material/Download"; +import CancelIcon from "@mui/icons-material/Cancel"; import CurrencyExchangeIcon from "@mui/icons-material/CurrencyExchange"; import { TOKEN_PRESETS } from "@/utils/erc20_token"; - const columns = [ { id: "fname", label: "Client", minWidth: 120 }, { id: "to", label: "Receiver", minWidth: 150 }, { id: "amountDue", label: "Amount", minWidth: 100, align: "right" }, - { id: "status", label: "Status", minWidth: 100 }, + { id: "status", label: "Status", minWidth: 120 }, { id: "date", label: "Date", minWidth: 100 }, { id: "actions", label: "Actions", minWidth: 150 }, ]; @@ -282,7 +282,33 @@ function SentInvoice() { link.href = data; link.click(); }; - + const handleCancelInvoice = async (invoiceId) => { + try { + setPaymentLoading((prev) => ({ ...prev, [invoiceId]: true })); + const provider = new BrowserProvider(walletClient); + const signer = await provider.getSigner(); + const contract = new Contract( + import.meta.env.VITE_CONTRACT_ADDRESS, + ChainvoiceABI, + signer + ); + + const tx = await contract.cancelInvoice(invoiceId); + await tx.wait(); + setSentInvoices((prev) => + prev.map((inv) => + inv.id === invoiceId ? { ...inv, isCancelled: true } : inv + ) + ); + + toast.success("Invoice cancelled successfully"); + } catch (error) { + console.error("Cancellation failed:", error); + toast.error("Failed to cancel invoice"); + } finally { + setPaymentLoading((prev) => ({ ...prev, [invoiceId]: false })); + } + }; const switchNetwork = async () => { try { setNetworkLoading(true); @@ -481,15 +507,31 @@ function SentInvoice() { {/* Status Column */} - : - } - label={invoice.isPaid ? "Paid" : "Pending"} - color={invoice.isPaid ? "success" : "warning"} - size="small" - variant="outlined" - /> + {invoice.isCancelled ? ( + } + label="Cancelled" + color="error" + size="small" + variant="outlined" + /> + ) : invoice.isPaid ? ( + } + label="Paid" + color="success" + size="small" + variant="outlined" + /> + ) : ( + } + label="Pending" + color="warning" + size="small" + variant="outlined" + /> + )} {/* Date Column */} @@ -506,7 +548,26 @@ function SentInvoice() { -
+
+ {!invoice.isPaid && !invoice.isCancelled && ( + + + handleCancelInvoice(invoice.id) + } + sx={{ + backgroundColor: "#fee2e2", + "&:hover": { backgroundColor: "#fecaca" }, + }} + > + + + + )} Chainvoice

- Chain + Cha + in voice

From 9a5ae47ca6ed11186a1b720295fbd0984aeabbe6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Thu, 14 Aug 2025 18:39:18 +0530 Subject: [PATCH 29/39] frontend update with cancel property --- frontend/package.json | 1 + frontend/src/page/ReceivedInvoice.jsx | 141 +++++++++++++----- frontend/src/page/SentInvoice.jsx | 197 +++++++++++++++++++------- 3 files changed, 257 insertions(+), 82 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 4d32287a..4abc1634 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -43,6 +43,7 @@ "react-icons": "^5.5.0", "react-router-dom": "^7.1.1", "react-to-print": "^3.0.5", + "react-toastify": "^11.0.5", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "viem": "^2.22.9", diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index 186a837a..06a0141a 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -23,6 +23,10 @@ import { LitAccessControlConditionResource, } from "@lit-protocol/auth-helpers"; import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; +import { toast } from "react-toastify"; +import "react-toastify/dist/ReactToastify.css"; +import CancelIcon from "@mui/icons-material/Cancel"; + import { CircularProgress, Skeleton, @@ -30,6 +34,7 @@ import { Avatar, Tooltip, IconButton, + Typography, } from "@mui/material"; import PaidIcon from "@mui/icons-material/CheckCircle"; import UnpaidIcon from "@mui/icons-material/Pending"; @@ -127,7 +132,7 @@ function ReceivedInvoice() { if (!res || !Array.isArray(res) || res.length === 0) { console.warn("No invoices found."); - setSentInvoices([]); + setReceivedInvoice([]); setLoading(false); return; } @@ -140,8 +145,9 @@ function ReceivedInvoice() { const from = invoice[1].toLowerCase(); const to = invoice[2].toLowerCase(); const isPaid = invoice[5]; - const encryptedStringBase64 = invoice[6]; - const dataToEncryptHash = invoice[7]; + const isCancelled = invoice[6]; + const encryptedStringBase64 = invoice[7]; + const dataToEncryptHash = invoice[8]; if (!encryptedStringBase64 || !dataToEncryptHash) continue; @@ -150,7 +156,6 @@ function ReceivedInvoice() { console.warn(`Unauthorized access attempt for invoice ${id}`); continue; } - const ciphertext = atob(encryptedStringBase64); const accessControlConditions = [ { @@ -177,7 +182,6 @@ function ReceivedInvoice() { }, }, ]; - const sessionSigs = await litNodeClient.getSessionSigs({ chain: "ethereum", resourceAbilityRequests: [ @@ -203,7 +207,6 @@ function ReceivedInvoice() { return await generateAuthSig({ signer, toSign }); }, }); - const decryptedString = await decryptToString( { accessControlConditions, @@ -214,12 +217,11 @@ function ReceivedInvoice() { }, litNodeClient ); - const parsed = JSON.parse(decryptedString); parsed["id"] = id; parsed["isPaid"] = isPaid; - console.log("parse ; ", parsed); - + parsed["isCancelled"] = isCancelled; + // Enhance with token details if (parsed.paymentToken?.address) { const tokenInfo = TOKEN_PRESETS.find( @@ -271,7 +273,10 @@ function ReceivedInvoice() { ChainvoiceABI, signer ); - + const invoice = receivedInvoices.find((inv) => inv.id === invoiceId); + if (invoice?.isCancelled) { + throw new Error("Cannot pay a cancelled invoice"); + } const fee = await contract.fee(); const isNativeToken = tokenAddress === ethers.ZeroAddress; @@ -333,11 +338,13 @@ function ReceivedInvoice() { } catch (error) { console.error("Payment failed:", error); if (error.code === "ACTION_REJECTED") { - alert("Transaction was rejected by user"); + toast.error("Transaction was rejected by user"); } else if (error.message.includes("insufficient balance")) { - alert("Insufficient balance for this transaction"); + toast.error("Insufficient balance for this transaction"); + } else if (error.message.includes("cancelled")) { + toast.error("Cannot pay a cancelled invoice"); } else { - alert(`Payment failed: ${error.message}`); + toast.error(`Payment failed: ${error.reason || error.message}`); } } finally { setPaymentLoading((prev) => ({ ...prev, [invoiceId]: false })); @@ -576,18 +583,34 @@ function ReceivedInvoice() { {/* Status Column */} - : - } - label={invoice.isPaid ? "Paid" : "Pending"} - color={invoice.isPaid ? "success" : "warning"} - size="small" - variant="outlined" - /> + {invoice.isCancelled ? ( + } + label="Cancelled" + color="error" + size="small" + variant="outlined" + /> + ) : invoice.isPaid ? ( + } + label="Paid" + color="success" + size="small" + variant="outlined" + /> + ) : ( + } + label="Cancelled" + color="error" + size="small" + variant="outlined" + /> + )} - {/* Date Column */} + - {!invoice.isPaid && ( + {!invoice.isPaid && !invoice.isCancelled && ( )} + {invoice.isCancelled && ( + + )}
@@ -698,9 +729,11 @@ function ReceivedInvoice() {
Chainvoice

- Chain + Cha + in voice

+

@@ -714,19 +747,59 @@ function ReceivedInvoice() { #{drawerState.selectedInvoice.id.toString().padStart(6, "0")}

- + {drawerState.selectedInvoice.isCancelled ? ( + } + /> + ) : drawerState.selectedInvoice.isPaid ? ( + } + /> + ) : ( + } + /> + )}
+ {drawerState.selectedInvoice.isCancelled && ( +
+
+
+ + Invoice Cancelled by{" "} + {drawerState.selectedInvoice.user?.fname || "The sender"}{" "} + {drawerState.selectedInvoice.user?.lname || ""}{" "} + + + You no longer need to make payment for this invoice. + +
+
+ {!drawerState.selectedInvoice.isPaid && ( +
+ + Note: This invoice was cancelled before payment was + completed + +
+ )} +
+ )}

diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index 52f7ffa9..2376af3c 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -30,6 +30,14 @@ import { Avatar, Tooltip, IconButton, + Typography, + Dialog, + DialogTitle, + DialogContent, + DialogContentText, + DialogActions, + Button, + Alert, } from "@mui/material"; import PaidIcon from "@mui/icons-material/CheckCircle"; import UnpaidIcon from "@mui/icons-material/Pending"; @@ -37,6 +45,7 @@ import DownloadIcon from "@mui/icons-material/Download"; import CancelIcon from "@mui/icons-material/Cancel"; import CurrencyExchangeIcon from "@mui/icons-material/CurrencyExchange"; import { TOKEN_PRESETS } from "@/utils/erc20_token"; + const columns = [ { id: "fname", label: "Client", minWidth: 120 }, { id: "to", label: "Receiver", minWidth: 150 }, @@ -48,19 +57,19 @@ const columns = [ function SentInvoice() { const [page, setPage] = useState(0); - const [rowsPerPage, setRowsPerPage] = useState(10); - const { data: walletClient } = useWalletClient(); - const { address } = useAccount(); - const [loading, setLoading] = useState(true); - const [sentInvoices, setSentInvoices] = useState([]); - const [fee, setFee] = useState(0); - const [error, setError] = useState(null); - const [litReady, setLitReady] = useState(false); - const litClientRef = useRef(null); - const [paymentLoading, setPaymentLoading] = useState({}); - const [networkLoading, setNetworkLoading] = useState(false); - - + const [rowsPerPage, setRowsPerPage] = useState(10); + const { data: walletClient } = useWalletClient(); + const { address } = useAccount(); + const [loading, setLoading] = useState(true); + const [sentInvoices, setSentInvoices] = useState([]); + const [fee, setFee] = useState(0); + const [error, setError] = useState(null); + const [litReady, setLitReady] = useState(false); + const litClientRef = useRef(null); + const [paymentLoading, setPaymentLoading] = useState({}); + const [networkLoading, setNetworkLoading] = useState(false); + const [cancelConfirmOpen, setCancelConfirmOpen] = useState(false); + const [invoiceToCancel, setInvoiceToCancel] = useState(null); const handleChangePage = (event, newPage) => { setPage(newPage); }; @@ -68,27 +77,27 @@ function SentInvoice() { setRowsPerPage(+event.target.value); setPage(0); }; - 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); - } - } catch (error) { - console.error("Error initializing Lit client:", error); - } finally { - setLoading(false); - } - }; - initLit(); - }, []); + 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); + } + } catch (error) { + console.error("Error initializing Lit client:", error); + } finally { + setLoading(false); + } + }; + initLit(); + }, []); useEffect(() => { if (!walletClient || !address || !litReady) return; @@ -141,8 +150,9 @@ function SentInvoice() { const from = invoice[1].toLowerCase(); const to = invoice[2].toLowerCase(); const isPaid = invoice[5]; - const encryptedStringBase64 = invoice[6]; - const dataToEncryptHash = invoice[7]; + const isCancelled = invoice[6]; + const encryptedStringBase64 = invoice[7]; + const dataToEncryptHash = invoice[8]; if (!encryptedStringBase64 || !dataToEncryptHash) continue; @@ -218,6 +228,7 @@ function SentInvoice() { const parsed = JSON.parse(decryptedString); parsed["id"] = id; parsed["isPaid"] = isPaid; + parsed["isCancelled"] = isCancelled; if (parsed.paymentToken?.address) { const tokenInfo = TOKEN_PRESETS.find( (t) => @@ -244,6 +255,7 @@ function SentInvoice() { } catch (error) { console.error("Decryption error:", error); } finally { + console.log(sentInvoices); setLoading(false); } }; @@ -553,9 +565,10 @@ function SentInvoice() { - handleCancelInvoice(invoice.id) - } + onClick={() => { + setInvoiceToCancel(invoice); + setCancelConfirmOpen(true); + }} sx={{ backgroundColor: "#fee2e2", "&:hover": { backgroundColor: "#fecaca" }, @@ -647,19 +660,60 @@ function SentInvoice() { #{drawerState.selectedInvoice.id.toString().padStart(6, "0")}

- + {drawerState.selectedInvoice.isCancelled ? ( + } + /> + ) : drawerState.selectedInvoice.isPaid ? ( + } + /> + ) : ( + } + /> + )}

+ {drawerState.selectedInvoice.isCancelled && ( +
+
+
+ + You Cancelled This Invoice + + + {drawerState.selectedInvoice.client?.fname || + "The recipient"}{" "} + {drawerState.selectedInvoice.client?.lname || ""}{" "} + has been notified and cannot pay this invoice. + +
+
+ + {drawerState.selectedInvoice.isPaid && ( +
+ + Note: Payment was already completed before cancellation + +
+ )} +
+ )}

@@ -848,6 +902,53 @@ function SentInvoice() {

)} + setCancelConfirmOpen(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + + Confirm Invoice Cancellation + + + + + You're about to cancel this invoice sent to{" "} + + {invoiceToCancel?.client.fname} {invoiceToCancel?.client.lname} + + . + + {invoiceToCancel?.isPaid ? ( + + Payment was already received - cancelling will not reverse the + transaction + + ) : ( + + This action cannot be undone + + )} + + + + + + +
); } From c43085ebfc88c60568442abaa86074ce560fd1cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Thu, 14 Aug 2025 19:58:37 +0530 Subject: [PATCH 30/39] - --- frontend/src/page/ReceivedInvoice.jsx | 11 +++++------ frontend/src/page/SentInvoice.jsx | 6 ------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index 0ad08928..e55bac40 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -609,11 +609,10 @@ function ReceivedInvoice() { /> ) : ( } - label="Cancelled" - color="error" + label="UNPAID" + color="warning" size="small" - variant="outlined" + icon={} /> )} @@ -655,7 +654,8 @@ function ReceivedInvoice() { payInvoice( invoice.id, invoice.amountDue, - invoice.paymentToken.address + invoice.paymentToken?.address ?? + ethers.ZeroAddress ) } disabled={paymentLoading[invoice.id]} @@ -741,7 +741,6 @@ function ReceivedInvoice() { in voice

-

diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index e674cedb..95f5a188 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -548,15 +548,9 @@ function SentInvoice() { {/* Date Column */} - {formatDate(invoice.issueDate)} - From 363b696ed50b135ecd08677513d2252598fc6ddf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Thu, 14 Aug 2025 20:11:03 +0530 Subject: [PATCH 31/39] - --- frontend/src/page/ReceivedInvoice.jsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index e55bac40..054cf636 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -619,15 +619,9 @@ function ReceivedInvoice() { {/* Date Column */} - {formatDate(invoice.issueDate)} - From bfc9f7abe45fb80ca2ae6f22a23054490afbde52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Thu, 21 Aug 2025 23:22:16 +0530 Subject: [PATCH 32/39] prefilled url feature open --- frontend/src/App.jsx | 27 +- frontend/src/components/CreateInvoice.jsx | 709 ---------- frontend/src/components/Navbar.jsx | 10 +- .../src/components/WalletConnectionAlert.jsx | 58 + frontend/src/page/CreateInvoice.jsx | 1231 +++++++++-------- frontend/src/page/GenerateLink.jsx | 185 +++ frontend/src/page/Home.jsx | 196 +-- frontend/src/page/ReceivedInvoice.jsx | 1092 ++++++++------- frontend/src/page/SentInvoice.jsx | 1122 +++++++-------- frontend/{ | 0 10 files changed, 2130 insertions(+), 2500 deletions(-) delete mode 100644 frontend/src/components/CreateInvoice.jsx create mode 100644 frontend/src/components/WalletConnectionAlert.jsx create mode 100644 frontend/src/page/GenerateLink.jsx delete mode 100644 frontend/{ diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 2e40df4b..67637337 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -32,11 +32,35 @@ export const config = getDefaultConfig({ }); const queryClient = new QueryClient(); import { Toaster } from "react-hot-toast"; +import GenerateLink from "./page/GenerateLink"; function App() { return (

- + } /> } /> } /> + }/> } /> } /> diff --git a/frontend/src/components/CreateInvoice.jsx b/frontend/src/components/CreateInvoice.jsx deleted file mode 100644 index 642b0868..00000000 --- a/frontend/src/components/CreateInvoice.jsx +++ /dev/null @@ -1,709 +0,0 @@ -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, PlusIcon } 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(); - const account = useAccount(); - const [dueDate, setDueDate] = useState(new Date()); - 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"); - return; - } - - try { - setLoading(true); - const provider = new BrowserProvider(walletClient); - const signer = await provider.getSigner(); - - // 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 - 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 tx = await contract.createInvoice( - data.clientAddress, - ethers.parseEther(totalAmountDue.toString()), - encryptedStringBase64, - dataToEncryptHash - ); - - const receipt = await tx.wait(); - setTimeout(() => navigate("/dashboard/sent"), 4000); - - } catch (err) { - console.error("Encryption or transaction failed:", err); - alert("Failed to create invoice."); - } finally { - setLoading(false); - } - }; - - const handleSubmit = async (e) => { - e.preventDefault(); - const formData = new FormData(e.target); - - // User detail - const userAddress = formData.get("userAddress"); - const userFname = formData.get("userFname"); - const userLname = formData.get("userLname"); - const userEmail = formData.get("userEmail"); - const userCountry = formData.get("userCountry"); - const userCity = formData.get("userCity"); - const userPostalcode = formData.get("userPostalcode"); - - // Client detail - const clientAddress = formData.get("clientAddress"); - const clientFname = formData.get("clientFname"); - const clientLname = formData.get("clientLname"); - const clientEmail = formData.get("clientEmail"); - const clientCountry = formData.get("clientCountry"); - const clientCity = formData.get("clientCity"); - const clientPostalcode = formData.get("clientPostalcode"); - - const data = { - userAddress, - userFname, - userLname, - userEmail, - userCountry, - userCity, - userPostalcode, - clientAddress, - clientFname, - clientLname, - clientEmail, - clientCountry, - clientCity, - clientPostalcode, - itemData, - }; - console.log(data); - await createInvoiceRequest(data); - }; - - return ( -
-

- Create New Invoice Request -

- -
-
- - -
- -
- - -
- -
- - - - - - - { - if (date) { - setDueDate(date); - document.dispatchEvent( - new KeyboardEvent("keydown", { key: "Escape" }) - ); - } - }} - initialFocus - disabled={(date) => date < new Date()} - /> - - -
-
- -
-
- {/* Your Information */} -
-

- From (Your Information) -

- - -
-
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
-
-
- {/* Client Information */} -
-

- Client Information -

- - -
-
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
-
-
-
- - - {/* Invoice Items Section */} -
-
-
DESCRIPTION
-
QTY
-
UNIT PRICE
-
DISCOUNT
-
TAX(%)
-
AMOUNT
- {/*
AMOUNT
*/} -
- -
- {itemData.map((_, index) => ( -
- {/* Item Fields */} -
- handleItemData(e, index)} - /> -
-
- handleItemData(e, index)} - /> -
-
- handleItemData(e, index)} - /> -
-
- handleItemData(e, index)} - /> -
-
- handleItemData(e, index)} - /> -
-
- -
- - {index > 0 && ( - - )} -
- ))} -
-
- - -
-
- Total: - - {/* {totalAmountDue} ETH */} - {totalAmountDue} cBTC - -
-
-
-
- - {/* Form Actions */} -
- -
-
-
- ); -} - -export default CreateInvoice; diff --git a/frontend/src/components/Navbar.jsx b/frontend/src/components/Navbar.jsx index e53e3a9d..3e2f5c37 100644 --- a/frontend/src/components/Navbar.jsx +++ b/frontend/src/components/Navbar.jsx @@ -32,18 +32,10 @@ function Navbar() { }; useEffect(() => { - if ( - address && - !hasConnected && - !location.pathname.startsWith("/dashboard") - ) { + if (address && !hasConnected && location.pathname === "/") { navigate("/dashboard/create"); setHasConnected(true); } - if (!address) { - navigate("/"); - setHasConnected(false); - } const handleScroll = () => { setIsScrolled(window.scrollY > 10); diff --git a/frontend/src/components/WalletConnectionAlert.jsx b/frontend/src/components/WalletConnectionAlert.jsx new file mode 100644 index 00000000..ffe69451 --- /dev/null +++ b/frontend/src/components/WalletConnectionAlert.jsx @@ -0,0 +1,58 @@ +// components/WalletConnectionAlert.jsx +import React from "react"; +import { ConnectButton } from "@rainbow-me/rainbowkit"; +import { FileText, X } from "lucide-react"; +import { motion, AnimatePresence } from "framer-motion"; +import AccountBalanceWalletIcon from "@mui/icons-material/AccountBalanceWallet"; +const WalletConnectionAlert = ({ show, onDismiss }) => { + return ( + + {show && ( + <> + {/* Optional: backdrop overlay */} + + + {/* Alert box */} + +
+
+ +
+

+ Wallet Connection Required +

+

+ Connect your wallet to create and manage invoices +

+
+ {/* Optional close button */} + +
+
+
+ + )} +
+ ); +}; + +export default WalletConnectionAlert; diff --git a/frontend/src/page/CreateInvoice.jsx b/frontend/src/page/CreateInvoice.jsx index d994bd95..6879d8ef 100644 --- a/frontend/src/page/CreateInvoice.jsx +++ b/frontend/src/page/CreateInvoice.jsx @@ -48,6 +48,9 @@ import { import { TOKEN_PRESETS } from "@/utils/erc20_token"; import TokenIntegrationRequest from "@/components/TokenIntegrationRequest"; import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; + +import WalletConnectionAlert from "../components/WalletConnectionAlert"; + function CreateInvoice() { const { data: walletClient } = useWalletClient(); const { isConnected } = useAccount(); @@ -66,6 +69,7 @@ function CreateInvoice() { const inputRef = useRef(null); const [tokenVerificationState, setTokenVerificationState] = useState("idle"); const [verifiedToken, setVerifiedToken] = useState(null); + const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); const filteredTokens = TOKEN_PRESETS.filter( (token) => token.name.toLowerCase().includes(searchTerm.toLowerCase()) || @@ -122,6 +126,10 @@ function CreateInvoice() { initLit(); }, []); + useEffect(() => { + setShowWalletAlert(!isConnected); + }, [isConnected]); + const handleItemData = (e, index) => { const { name, value } = e.target; @@ -374,369 +382,339 @@ function CreateInvoice() { }; return ( -
-

- Create New Invoice Request -

+ <> +
+ setShowWalletAlert(false)} + /> +
+
+

+ Create New Invoice Request +

-
-
- - -
+
+
+ + +
-
- - -
+
+ + +
-
- - - - - - - { - if (date) { - setDueDate(date); - document.dispatchEvent( - new KeyboardEvent("keydown", { key: "Escape" }) - ); - } - }} - initialFocus - disabled={(date) => date < new Date()} - /> - - +
+ + + + + + + { + if (date) { + setDueDate(date); + document.dispatchEvent( + new KeyboardEvent("keydown", { key: "Escape" }) + ); + } + }} + initialFocus + disabled={(date) => date < new Date()} + /> + + +
-
-
-
- {/* Your Information */} -
-

- From (Your Information) -

- + +
+ {/* Your Information */} +
+

+ From (Your Information) +

+ -
-
-
- - -
-
- - +
+
+
+ + +
+
+ + +
-
-
-
- - -
-
- - +
+
+ + +
+
+ + +
-
-
-
- - -
-
- - +
+
+ + +
+
+ + +
-
- {/* Client Information */} -
-

- Client Information -

- + {/* Client Information */} +
+

+ Client Information +

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

- - - - - - - Payment Currency -

+
+

+ + + + + + + Payment Currency +

-
-
-
- - { + if (value === "custom") { + setUseCustomToken(true); + setSelectedToken(null); + } else { + setUseCustomToken(false); + const token = TOKEN_PRESETS.find( + (t) => t.address === value + ); + if (token) { + setSelectedToken(token); + setCustomTokenAddress(""); + setTokenVerificationState("idle"); + setVerifiedToken(null); + } } - } - }} - disabled={loading} - > - - - - - {/* Search input for filtering */} -
- setSearchTerm(e.target.value)} - /> -
- {!searchTerm && ( -
-
- Popular -
- {TOKEN_PRESETS.filter((token) => - POPULAR_TOKENS.includes(token.address) - ).map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))} -
- )} -
-
- {searchTerm ? "Search Results" : "All Tokens"} + }} + disabled={loading} + > + + + + + {/* Search input for filtering */} +
+ setSearchTerm(e.target.value)} + />
-
- {filteredTokens - .filter( - (token) => !POPULAR_TOKENS.includes(token.address) - ) - .map((token) => ( + {!searchTerm && ( +
+
+ Popular +
+ {TOKEN_PRESETS.filter((token) => + POPULAR_TOKENS.includes(token.address) + ).map((token) => ( ))} -
- {filteredTokens.length === 0 && ( -
- No tokens found
)} -
- - -
-
- +
+
+ {searchTerm ? "Search Results" : "All Tokens"}
- - Custom Token - -
- - - -
- - {!useCustomToken && ( -
- -
-
-
- {selectedToken.name} -
-
- - {selectedToken.name} - - - {selectedToken.symbol} - -
- {selectedToken.address} +
+ {filteredTokens + .filter( + (token) => !POPULAR_TOKENS.includes(token.address) + ) + .map((token) => ( + +
+
+ {token.name} { + e.currentTarget.src = + "/tokenImages/generic.png"; + }} + /> +
+
+ + {token.name} + + + {token.symbol} + +
+
+
+ ))}
+ {filteredTokens.length === 0 && ( +
+ No tokens found +
+ )}
-
-
-
- )} -
- {useCustomToken && ( -
-
- - { - const address = e.target.value; - setCustomTokenAddress(address); - if (!address || !ethers.isAddress(address)) { - setTokenVerificationState("idle"); - setVerifiedToken(null); - } else if (ethers.isAddress(address)) { - verifyToken(address); - } - }} - className="h-10 bg-gray-50 text-gray-700 border-gray-200 focus:ring-2 focus:ring-blue-100" - disabled={loading} - /> -

- Enter a valid ERC-20 token contract address -

+ +
+
+ +
+ + Custom Token + +
+
+ +
- {tokenVerificationState === "verifying" && ( -
- - Verifying token... + {!useCustomToken && ( +
+ +
+
+
+ {selectedToken.name} +
+
+ + {selectedToken.name} + + + {selectedToken.symbol} + +
+ {selectedToken.address} +
+
+
+
)} +
- {tokenVerificationState === "success" && verifiedToken && ( + {useCustomToken && ( +
-
-
- -
-

- {verifiedToken.name} ({verifiedToken.symbol}) -

-

- {verifiedToken.address} -

-

-

Decimals: {String(verifiedToken.decimals)}

-

+ + { + const address = e.target.value; + setCustomTokenAddress(address); + if (!address || !ethers.isAddress(address)) { + setTokenVerificationState("idle"); + setVerifiedToken(null); + } else if (ethers.isAddress(address)) { + verifyToken(address); + } + }} + className="h-10 bg-gray-50 text-gray-700 border-gray-200 focus:ring-2 focus:ring-blue-100" + disabled={loading} + /> +

+ Enter a valid ERC-20 token contract address +

+
+ + {tokenVerificationState === "verifying" && ( +
+ + Verifying token... +
+ )} + + {tokenVerificationState === "success" && verifiedToken && ( +
+
+
+ +
+

+ {verifiedToken.name} ({verifiedToken.symbol}) +

+

+ {verifiedToken.address} +

+

+

Decimals: {String(verifiedToken.decimals)}

+

+
+
- -
- )} + )} - {tokenVerificationState === "error" && ( -
-
- -

- Failed to verify token. Please check the address. -

+ {tokenVerificationState === "error" && ( +
+
+ +

+ Failed to verify token. Please check the address. +

+
-
- )} -
- )} + )} +
+ )} -
-

- {useCustomToken ? ( - verifiedToken ? ( +

+

+ {useCustomToken ? ( + verifiedToken ? ( + <> + Note:{" "} + Payments will be processed in {verifiedToken?.symbol}. + Ensure your client has sufficient balance of this token. + + ) : ( + "" + ) + ) : ( <> Note:{" "} - Payments will be processed in {verifiedToken?.symbol}. + Payments will be processed in {selectedToken.symbol}. Ensure your client has sufficient balance of this token. - ) : ( - "" - ) - ) : ( - <> - Note:{" "} - Payments will be processed in {selectedToken.symbol}. Ensure - your client has sufficient balance of this token. - - )} -

+ )} +

+
-
- {/* Invoice Items Section */} -
-
-
DESCRIPTION
-
QTY
-
UNIT PRICE
-
DISCOUNT
-
TAX(%)
-
AMOUNT
- {/*
AMOUNT
*/} -
+ {/* Invoice Items Section */} +
+
+
DESCRIPTION
+
QTY
+
UNIT PRICE
+
DISCOUNT
+
TAX(%)
+
AMOUNT
+ {/*
AMOUNT
*/} +
-
- {itemData.map((_, index) => ( -
- {/* Item Fields */} -
- handleItemData(e, index)} - /> -
-
- handleItemData(e, index)} - /> -
-
- handleItemData(e, index)} - /> -
-
- handleItemData(e, index)} - /> -
-
- handleItemData(e, index)} - /> -
-
- -
+
+ {itemData.map((_, index) => ( +
+ {/* Item Fields */} +
+ handleItemData(e, index)} + /> +
+
+ handleItemData(e, index)} + /> +
+
+ handleItemData(e, index)} + /> +
+
+ handleItemData(e, index)} + /> +
+
+ handleItemData(e, index)} + /> +
+
+ +
- {index > 0 && ( - - )} + + + + + )} +
+ ))} +
+
+ + +
+
+ Total: + + {totalAmountDue}{" "} + {useCustomToken + ? verifiedToken?.symbol + : selectedToken.symbol} + +
- ))} +
-
+ + {/* Form Actions */} +
- -
-
- Total: - - {totalAmountDue}{" "} - {useCustomToken ? verifiedToken?.symbol : selectedToken.symbol} - -
-
-
- - {/* Form Actions */} -
- -
- -
+ +
+ ); } diff --git a/frontend/src/page/GenerateLink.jsx b/frontend/src/page/GenerateLink.jsx new file mode 100644 index 00000000..d8185eca --- /dev/null +++ b/frontend/src/page/GenerateLink.jsx @@ -0,0 +1,185 @@ +// pages/GeneratePrefilledLink.jsx +import React, { useState, useEffect } from "react"; +import { useAccount } from "wagmi"; +import { Copy, Link, Check } from "lucide-react"; +import { Button } from "../components/ui/button"; +import { Input } from "../components/ui/input"; +import { Label } from "../components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; +import { TOKEN_PRESETS } from "@/utils/erc20_token"; +import WalletConnectionAlert from "@/components/WalletConnectionAlert"; + +const GenerateLink = () => { + const { address, isConnected } = useAccount(); + const [copied, setCopied] = useState(false); + const [selectedToken, setSelectedToken] = useState(TOKEN_PRESETS[0]); + const [amount, setAmount] = useState(""); + const [description, setDescription] = useState(""); + const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); + + useEffect(() => { + setShowWalletAlert(!isConnected); + }, [isConnected]); + + // Generate the prefilled link + const generateLink = () => { + const params = new URLSearchParams({ + clientAddress: address || "", + tokenAddress: selectedToken.address, + chain: "1", // Ethereum mainnet, adjust as needed + amount: amount || "", + description: description || "", + }); + + return `${window.location.origin}/dashboard/create?${params.toString()}`; + }; + + const copyToClipboard = async () => { + try { + await navigator.clipboard.writeText(generateLink()); + setCopied(true); + setTimeout(() => setCopied(false), 2000); + } catch (err) { + console.error("Failed to copy: ", err); + } + }; + + return ( + <> +
+ setShowWalletAlert(false)} + /> +
+
+
+

+ Generate Prefilled Invoice Link +

+

+ Create a shareable link that pre-fills the invoice form with your + details. +

+
+ +
+
+ {/* Your Address (Auto-filled) */} +
+ + +
+ + {/* Token Selection */} +
+ + +
+ + {/* Optional: Default Amount */} +
+ + setAmount(e.target.value)} + className="bg-gray-700 text-white border-gray-600" + /> +
+ + {/* Optional: Default Description */} +
+ + setDescription(e.target.value)} + className="bg-gray-700 text-white border-gray-600" + /> +
+
+
+ + {/* Generated Link Preview */} +
+ +
+ + +
+

+ Share this link with clients to let them create invoices with your + details pre-filled. +

+
+
+ + + ); +}; + +export default GenerateLink; diff --git a/frontend/src/page/Home.jsx b/frontend/src/page/Home.jsx index b6dbdebf..67188092 100644 --- a/frontend/src/page/Home.jsx +++ b/frontend/src/page/Home.jsx @@ -1,4 +1,4 @@ -// Home.js +// Home.js - Complete Updated Version with Generate Prefilled Link import * as React from "react"; import Box from "@mui/material/Box"; import Drawer from "@mui/material/Drawer"; @@ -10,6 +10,7 @@ import ListItemText from "@mui/material/ListItemText"; import MailOutlineIcon from "@mui/icons-material/MailOutline"; import DraftsIcon from "@mui/icons-material/Drafts"; import AddCircleOutlineIcon from "@mui/icons-material/AddCircleOutline"; +import LinkIcon from "@mui/icons-material/Link"; import { Outlet, useNavigate, useLocation } from "react-router-dom"; export default function Home() { @@ -35,115 +36,122 @@ export default function Home() { route: "create", color: "#f472b6", }, + { + text: "Generate Link", + icon: , + route: "generate-link", + color: "#a78bfa", + }, ]; return ( -
-
-

- Welcome Back! -

-
+ <> +
+
+

+ Welcome Back! +

+
- - {/* Sidebar Navigation */} - - - {menuItems.map((item) => ( - - navigate(item.route)} - selected={location.pathname.includes(item.route)} - sx={{ - borderRadius: "8px", - transition: "all 0.2s ease", - backgroundColor: location.pathname.includes(item.route) - ? "rgba(255, 255, 255, 0.08)" - : "transparent", - "&:hover": { - backgroundColor: "rgba(255, 255, 255, 0.05)", - transform: "translateX(4px)", - }, - "&.Mui-selected": { - borderLeft: `4px solid ${item.color}`, - }, - padding: "12px 16px", - }} + + + {menuItems.map((item) => ( + - navigate(item.route)} + selected={location.pathname.includes(item.route)} sx={{ - minWidth: "36px", - color: item.color, - fontSize: "1.25rem", + borderRadius: "8px", + transition: "all 0.2s ease", + backgroundColor: location.pathname.includes(item.route) + ? "rgba(255, 255, 255, 0.08)" + : "transparent", + "&:hover": { + backgroundColor: "rgba(255, 255, 255, 0.05)", + transform: "translateX(4px)", + }, + "&.Mui-selected": { + borderLeft: `4px solid ${item.color}`, + }, + padding: "12px 16px", }} > - {item.icon} - - - - - ))} - - - + + {item.icon} + + + + + ))} + + + - {/* Main Content */} - - + {/* Main Content */} + + + - -
+
+ ); } diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index 054cf636..6001eb08 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -41,6 +41,7 @@ import UnpaidIcon from "@mui/icons-material/Pending"; import DownloadIcon from "@mui/icons-material/Download"; import CurrencyExchangeIcon from "@mui/icons-material/CurrencyExchange"; import { TOKEN_PRESETS } from "@/utils/erc20_token"; +import WalletConnectionAlert from "@/components/WalletConnectionAlert"; const columns = [ { id: "fname", label: "Client", minWidth: 120 }, @@ -55,7 +56,7 @@ function ReceivedInvoice() { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); const { data: walletClient } = useWalletClient(); - const { address } = useAccount(); + const { address, isConnected } = useAccount(); const [loading, setLoading] = useState(true); const [receivedInvoices, setReceivedInvoice] = useState([]); const [fee, setFee] = useState(0); @@ -64,6 +65,7 @@ function ReceivedInvoice() { const litClientRef = useRef(null); const [paymentLoading, setPaymentLoading] = useState({}); const [networkLoading, setNetworkLoading] = useState(false); + const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); const handleChangePage = (event, newPage) => { setPage(newPage); @@ -95,6 +97,9 @@ function ReceivedInvoice() { }; initLit(); }, []); + useEffect(() => { + setShowWalletAlert(!isConnected); + }, [isConnected]); useEffect(() => { if (!walletClient || !address || !litReady) return; @@ -419,580 +424,597 @@ function ReceivedInvoice() { }; return ( -
-
-
-
-

Received Invoices

-

- Manage and pay your incoming invoices -

+ <> +
+ setShowWalletAlert(false)} + /> +
+
+
+
+
+

+ Received Invoices +

+

+ Manage and pay your incoming invoices +

+
+ {error && ( + + )}
- {error && ( - - )} -
- - {loading ? ( -
-
- - + + {loading ? ( +
+
+ + +
+ {[...Array(5)].map((_, i) => ( + + ))}
- {[...Array(5)].map((_, i) => ( - - ))} -
- ) : error ? ( -
-
-

{error}

+ ) : error ? ( +
+
+

{error}

+
-
- ) : receivedInvoices.length === 0 ? ( -
-
- -

- No Invoices Found -

-

- You don't have any received invoices yet. -

+ ) : receivedInvoices.length === 0 ? ( +
+
+ +

+ No Invoices Found +

+

+ You don't have any received invoices yet. +

+
-
- ) : ( - <> - - - - - {columns.map((column) => ( - - {column.label} - - ))} - - - - {receivedInvoices - .slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ) - .map((invoice) => ( - - {/* Client Column */} - -
- - {invoice.user?.fname?.charAt(0) || "C"} - -
-
- {invoice.user?.fname} {invoice.user?.lname} -
-
- {invoice.user?.email} + ) : ( + <> + +
+ + + {columns.map((column) => ( + + {column.label} + + ))} + + + + {receivedInvoices + .slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage + ) + .map((invoice) => ( + + {/* Client Column */} + +
+ + {invoice.user?.fname?.charAt(0) || "C"} + +
+
+ {invoice.user?.fname} {invoice.user?.lname} +
+
+ {invoice.user?.email} +
- -
- - {/* Sender Column */} - - - - {formatAddress(invoice.user?.address)} - - - - - {/* Amount Column */} - -
- {invoice.paymentToken?.logo ? ( - {invoice.paymentToken.symbol} + + {/* Sender Column */} + + + + {formatAddress(invoice.user?.address)} + + + + + {/* Amount Column */} + +
+ {invoice.paymentToken?.logo ? ( + {invoice.paymentToken.symbol} + ) : ( + + )} + + {invoice.amountDue}{" "} + {invoice.paymentToken?.symbol} + +
+
+ + {/* Status Column */} + + {invoice.isCancelled ? ( + } + label="Cancelled" + color="error" + size="small" + variant="outlined" + /> + ) : invoice.isPaid ? ( + } + label="Paid" + color="success" + size="small" + variant="outlined" /> ) : ( - } /> )} - - {invoice.amountDue}{" "} - {invoice.paymentToken?.symbol} - -
-
+ + {/* Date Column */} - {/* Status Column */} - - {invoice.isCancelled ? ( - } - label="Cancelled" - color="error" - size="small" - variant="outlined" - /> - ) : invoice.isPaid ? ( - } - label="Paid" - color="success" - size="small" - variant="outlined" - /> - ) : ( - } - /> - )} - - {/* Date Column */} - - + {formatDate(invoice.issueDate)} - - - -
- - - - - + + + +
+ + + + + + + {!invoice.isPaid && !invoice.isCancelled && ( + + )} + {invoice.isCancelled && ( + + )} +
+
+ + ))} + +
+
+ + + )} + +
- {!invoice.isPaid && !invoice.isCancelled && ( - - )} - {invoice.isCancelled && ( - - )} -
- - - ))} - - - - - - )} - -
+ {/* Invoice Detail Drawer */} + + {drawerState.selectedInvoice && ( +
+
+
+
+ Chainvoice +

+ Cha + + in + + voice +

+
- {/* Invoice Detail Drawer */} - - {drawerState.selectedInvoice && ( -
-
-
-
- Chainvoice -

- Cha - in - voice +

+ Powered by Chainvoice

-

- Powered by Chainvoice -

+
+

INVOICE

+

+ # + {drawerState.selectedInvoice.id.toString().padStart(6, "0")} +

+
+ {drawerState.selectedInvoice.isCancelled ? ( + } + /> + ) : drawerState.selectedInvoice.isPaid ? ( + } + /> + ) : ( + } + /> + )} +
+
+ {drawerState.selectedInvoice.isCancelled && ( +
+
+
+ + Invoice Cancelled by{" "} + {drawerState.selectedInvoice.user?.fname || + "The sender"}{" "} + {drawerState.selectedInvoice.user?.lname || ""}{" "} + + + You no longer need to make payment for this invoice. + +
+
-
-

INVOICE

-

- #{drawerState.selectedInvoice.id.toString().padStart(6, "0")} -

-
- {drawerState.selectedInvoice.isCancelled ? ( - } - /> - ) : drawerState.selectedInvoice.isPaid ? ( - } - /> - ) : ( - } - /> + {!drawerState.selectedInvoice.isPaid && ( +
+ + Note: This invoice was cancelled before payment was + completed + +
)}
+ )} +
+
+

+ From +

+

+ {drawerState.selectedInvoice.user.fname}{" "} + {drawerState.selectedInvoice.user.lname} +

+

+ {drawerState.selectedInvoice.user.address} +

+

+ {drawerState.selectedInvoice.user.city},{" "} + {drawerState.selectedInvoice.user.country},{" "} + {drawerState.selectedInvoice.user.postalcode} +

+

+ {drawerState.selectedInvoice.user.email} +

+
+ +
+

+ Bill To +

+

+ {drawerState.selectedInvoice.client.fname}{" "} + {drawerState.selectedInvoice.client.lname} +

+

+ {drawerState.selectedInvoice.client.address} +

+

+ {drawerState.selectedInvoice.client.city},{" "} + {drawerState.selectedInvoice.client.country},{" "} + {drawerState.selectedInvoice.client.postalcode} +

+

+ {drawerState.selectedInvoice.client.email} +

+
-
- {drawerState.selectedInvoice.isCancelled && ( -
-
+
+

+ Payment Currency +

+
+ {drawerState.selectedInvoice.paymentToken?.logo ? ( + {drawerState.selectedInvoice.paymentToken.symbol} + ) : ( +
+ +
+ )}
- - Invoice Cancelled by{" "} - {drawerState.selectedInvoice.user?.fname || "The sender"}{" "} - {drawerState.selectedInvoice.user?.lname || ""}{" "} - - - You no longer need to make payment for this invoice. - +

+ {drawerState.selectedInvoice.paymentToken?.name || + "Ether "} + {"("} + {drawerState.selectedInvoice.paymentToken?.symbol || + "ETH"} + {")"} +

+

+ {drawerState.selectedInvoice.paymentToken?.address + ? `${drawerState.selectedInvoice.paymentToken.address.substring( + 0, + 10 + )}......${drawerState.selectedInvoice.paymentToken.address.substring( + 33 + )}` + : "Native Currency"} +

- - {!drawerState.selectedInvoice.isPaid && ( -
- - Note: This invoice was cancelled before payment was - completed - + {drawerState.selectedInvoice.paymentToken?.address && ( +
+

+ Decimals:{" "} + {drawerState.selectedInvoice.paymentToken.decimals || 18} +

+

Chain: Sepolia Testnet

)}
- )} -
-
-

- From -

-

- {drawerState.selectedInvoice.user.fname}{" "} - {drawerState.selectedInvoice.user.lname} -

-

- {drawerState.selectedInvoice.user.address} -

-

- {drawerState.selectedInvoice.user.city},{" "} - {drawerState.selectedInvoice.user.country},{" "} - {drawerState.selectedInvoice.user.postalcode} -

-

- {drawerState.selectedInvoice.user.email} -

+
+
+ + Issued:{" "} + {new Date( + drawerState.selectedInvoice.issueDate + ).toLocaleDateString()} + + + Due:{" "} + {new Date( + drawerState.selectedInvoice.dueDate + ).toLocaleDateString()} + +
+
+
+ + + + + + + + + + + + + {drawerState.selectedInvoice.items?.map((item, index) => ( + + + + + + + + + ))} + +
DescriptionQtyPriceDiscountTaxAmount
{item.description}{item.qty} + {item.unitPrice}{" "} + {drawerState.selectedInvoice.paymentToken?.symbol} + + {item.discount || "0"} + + {item.tax || "0%"} + + {item.amount}{" "} + {drawerState.selectedInvoice.paymentToken?.symbol} +
-

- Bill To -

-

- {drawerState.selectedInvoice.client.fname}{" "} - {drawerState.selectedInvoice.client.lname} -

-

- {drawerState.selectedInvoice.client.address} -

-

- {drawerState.selectedInvoice.client.city},{" "} - {drawerState.selectedInvoice.client.country},{" "} - {drawerState.selectedInvoice.client.postalcode} -

-

- {drawerState.selectedInvoice.client.email} -

-
-
-
-

- Payment Currency -

-
- {drawerState.selectedInvoice.paymentToken?.logo ? ( - {drawerState.selectedInvoice.paymentToken.symbol} - ) : ( -
- -
- )} -
-

- {drawerState.selectedInvoice.paymentToken?.name || "Ether "} - {"("} - {drawerState.selectedInvoice.paymentToken?.symbol || "ETH"} - {")"} -

-

- {drawerState.selectedInvoice.paymentToken?.address - ? `${drawerState.selectedInvoice.paymentToken.address.substring( - 0, - 10 - )}......${drawerState.selectedInvoice.paymentToken.address.substring( - 33 - )}` - : "Native Currency"} -

+
+ Subtotal: + + {drawerState.selectedInvoice.amountDue}{" "} + {drawerState.selectedInvoice.paymentToken?.symbol} +
-
- {drawerState.selectedInvoice.paymentToken?.address && ( -
-

- Decimals:{" "} - {drawerState.selectedInvoice.paymentToken.decimals || 18} -

-

Chain: Sepolia Testnet

+
+ Network Fee: + + {ethers.formatUnits(fee)} ETH + +
+
+ Total Amount: + + {drawerState.selectedInvoice.paymentToken?.symbol === "ETH" + ? `${( + parseFloat(drawerState.selectedInvoice.amountDue) + + parseFloat(ethers.formatUnits(fee)) + ).toFixed(6)} ETH` + : `${drawerState.selectedInvoice.amountDue} ${ + drawerState.selectedInvoice.paymentToken?.symbol + } + ${ethers.formatUnits(fee)} ETH`} +
- )} -
-
-
- - Issued:{" "} - {new Date( - drawerState.selectedInvoice.issueDate - ).toLocaleDateString()} - - - Due:{" "} - {new Date( - drawerState.selectedInvoice.dueDate - ).toLocaleDateString()} -
-
-
- - - - - - - - - - - - - {drawerState.selectedInvoice.items?.map((item, index) => ( - - - - - - - - - ))} - -
DescriptionQtyPriceDiscountTaxAmount
{item.description}{item.qty} - {item.unitPrice}{" "} - {drawerState.selectedInvoice.paymentToken?.symbol} - - {item.discount || "0"} - - {item.tax || "0%"} - - {item.amount}{" "} - {drawerState.selectedInvoice.paymentToken?.symbol} -
-
-
-
- Subtotal: - - {drawerState.selectedInvoice.amountDue}{" "} - {drawerState.selectedInvoice.paymentToken?.symbol} - +
+ +
-
- Network Fee: - - {ethers.formatUnits(fee)} ETH - -
-
- Total Amount: - - {drawerState.selectedInvoice.paymentToken?.symbol === "ETH" - ? `${( - parseFloat(drawerState.selectedInvoice.amountDue) + - parseFloat(ethers.formatUnits(fee)) - ).toFixed(6)} ETH` - : `${drawerState.selectedInvoice.amountDue} ${ - drawerState.selectedInvoice.paymentToken?.symbol - } + ${ethers.formatUnits(fee)} ETH`} - -
-
- -
- -
-
- )} - -
+ )} + +
+ ); } diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index 95f5a188..e4510d42 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -45,6 +45,7 @@ import DownloadIcon from "@mui/icons-material/Download"; import CancelIcon from "@mui/icons-material/Cancel"; import CurrencyExchangeIcon from "@mui/icons-material/CurrencyExchange"; import { TOKEN_PRESETS } from "@/utils/erc20_token"; +import WalletConnectionAlert from "@/components/WalletConnectionAlert"; const columns = [ { id: "fname", label: "Client", minWidth: 120 }, @@ -59,7 +60,7 @@ function SentInvoice() { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); const { data: walletClient } = useWalletClient(); - const { address } = useAccount(); + const { address, isConnected } = useAccount(); const [loading, setLoading] = useState(true); const [sentInvoices, setSentInvoices] = useState([]); const [fee, setFee] = useState(0); @@ -70,6 +71,8 @@ function SentInvoice() { const [networkLoading, setNetworkLoading] = useState(false); const [cancelConfirmOpen, setCancelConfirmOpen] = useState(false); const [invoiceToCancel, setInvoiceToCancel] = useState(null); + const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); + const handleChangePage = (event, newPage) => { setPage(newPage); }; @@ -98,6 +101,9 @@ function SentInvoice() { }; initLit(); }, []); + useEffect(() => { + setShowWalletAlert(!isConnected); + }, [isConnected]); useEffect(() => { if (!walletClient || !address || !litReady) return; @@ -349,600 +355,620 @@ function SentInvoice() { }; return ( -
-
-
-
-

Sent Invoices

+ <> +
+ setShowWalletAlert(false)} + /> +
+
+
+
+
+

Sent Invoices

+
+ {error && ( + + )}
- {error && ( - - )} -
- - {loading ? ( -
-
- - + + {loading ? ( +
+
+ + +
+ {[...Array(5)].map((_, i) => ( + + ))}
- {[...Array(5)].map((_, i) => ( - - ))} -
- ) : error ? ( -
-
-

{error}

+ ) : error ? ( +
+
+

{error}

+
-
- ) : sentInvoices.length === 0 ? ( -
-
- -

- No Invoices Found -

-

- You haven't sent any invoices yet. -

+ ) : sentInvoices.length === 0 ? ( +
+
+ +

+ No Invoices Found +

+

+ You haven't sent any invoices yet. +

+
-
- ) : ( - <> - - - - - {columns.map((column) => ( - - {column.label} - - ))} - - - - {sentInvoices - .slice( - page * rowsPerPage, - page * rowsPerPage + rowsPerPage - ) - .map((invoice) => ( - - {/* Client Column */} - -
- - {invoice.client?.fname?.charAt(0) || "C"} - -
-
- {invoice.client?.fname}{" "} - {invoice.client?.lname} -
-
- {invoice.client?.email} + ) : ( + <> + +
+ + + {columns.map((column) => ( + + {column.label} + + ))} + + + + {sentInvoices + .slice( + page * rowsPerPage, + page * rowsPerPage + rowsPerPage + ) + .map((invoice) => ( + + {/* Client Column */} + +
+ + {invoice.client?.fname?.charAt(0) || "C"} + +
+
+ {invoice.client?.fname}{" "} + {invoice.client?.lname} +
+
+ {invoice.client?.email} +
- -
- - {/* Sender Column */} - - - - {formatAddress(invoice.client?.address)} - - - - - {/* Amount Column */} - -
- {invoice.paymentToken?.logo ? ( - {invoice.paymentToken.symbol} + + {/* Sender Column */} + + + + {formatAddress(invoice.client?.address)} + + + + + {/* Amount Column */} + +
+ {invoice.paymentToken?.logo ? ( + {invoice.paymentToken.symbol} + ) : ( + + )} + + {invoice.amountDue}{" "} + {invoice.paymentToken?.symbol} + +
+
+ + {/* Status Column */} + + {invoice.isCancelled ? ( + } + label="Cancelled" + color="error" + size="small" + variant="outlined" + /> + ) : invoice.isPaid ? ( + } + label="Paid" + color="success" + size="small" + variant="outlined" /> ) : ( - } + label="Pending" + color="warning" + size="small" + variant="outlined" /> )} - - {invoice.amountDue}{" "} - {invoice.paymentToken?.symbol} - -
-
- - {/* Status Column */} - - {invoice.isCancelled ? ( - } - label="Cancelled" - color="error" - size="small" - variant="outlined" - /> - ) : invoice.isPaid ? ( - } - label="Paid" - color="success" - size="small" - variant="outlined" - /> - ) : ( - } - label="Pending" - color="warning" - size="small" - variant="outlined" - /> - )} - + - {/* Date Column */} - + {/* Date Column */} + {formatDate(invoice.issueDate)} - - - -
- {!invoice.isPaid && !invoice.isCancelled && ( - + + + +
+ {!invoice.isPaid && !invoice.isCancelled && ( + + { + setInvoiceToCancel(invoice); + setCancelConfirmOpen(true); + }} + sx={{ + backgroundColor: "#fee2e2", + "&:hover": { + backgroundColor: "#fecaca", + }, + }} + > + + + + )} + { - setInvoiceToCancel(invoice); - setCancelConfirmOpen(true); - }} + onClick={toggleDrawer(invoice)} sx={{ - backgroundColor: "#fee2e2", - "&:hover": { backgroundColor: "#fecaca" }, + backgroundColor: "#e0f2fe", + "&:hover": { backgroundColor: "#bae6fd" }, }} > - - )} - - - - - -
-
- - ))} - -
-
- - - )} - -
- {/* Invoice Detail Drawer */} - - {drawerState.selectedInvoice && ( -
-
-
- Powered by -
- Chainvoice -

- Cha - in - voice +

+ + + ))} + + + + + + )} + +
+ {/* Invoice Detail Drawer */} + + {drawerState.selectedInvoice && ( +
+
+
+ Powered by +
+ Chainvoice +

+ Cha + + in + + voice +

+
+
+ +
+

INVOICE

+

+ # + {drawerState.selectedInvoice.id.toString().padStart(6, "0")}

+
+ {drawerState.selectedInvoice.isCancelled ? ( + } + /> + ) : drawerState.selectedInvoice.isPaid ? ( + } + /> + ) : ( + } + /> + )} +
-
-

INVOICE

-

- #{drawerState.selectedInvoice.id.toString().padStart(6, "0")} -

-
- {drawerState.selectedInvoice.isCancelled ? ( - } - /> - ) : drawerState.selectedInvoice.isPaid ? ( - } - /> - ) : ( - } - /> + {drawerState.selectedInvoice.isCancelled && ( +
+
+
+ + You Cancelled This Invoice + + + {drawerState.selectedInvoice.client?.fname || + "The recipient"}{" "} + {drawerState.selectedInvoice.client?.lname || ""} has + been notified and cannot pay this invoice. + +
+
+ + {drawerState.selectedInvoice.isPaid && ( +
+ + Note: Payment was already completed before cancellation + +
)}
-
-
+ )} +
+
+

+ From +

+

+ {drawerState.selectedInvoice.user.fname}{" "} + {drawerState.selectedInvoice.user.lname} +

+

+ {drawerState.selectedInvoice.user.address} +

+

+ {drawerState.selectedInvoice.user.city},{" "} + {drawerState.selectedInvoice.user.country},{" "} + {drawerState.selectedInvoice.user.postalcode} +

+

+ {drawerState.selectedInvoice.user.email} +

+
- {drawerState.selectedInvoice.isCancelled && ( -
-
+
+

+ Bill To +

+

+ {drawerState.selectedInvoice.client.fname}{" "} + {drawerState.selectedInvoice.client.lname} +

+

+ {drawerState.selectedInvoice.client.address} +

+

+ {drawerState.selectedInvoice.client.city},{" "} + {drawerState.selectedInvoice.client.country},{" "} + {drawerState.selectedInvoice.client.postalcode} +

+

+ {drawerState.selectedInvoice.client.email} +

+
+
+
+

+ Payment Currency +

+
+ {drawerState.selectedInvoice.paymentToken?.logo ? ( + {drawerState.selectedInvoice.paymentToken.symbol} + ) : ( +
+ +
+ )}
- - You Cancelled This Invoice - - - {drawerState.selectedInvoice.client?.fname || - "The recipient"}{" "} - {drawerState.selectedInvoice.client?.lname || ""}{" "} - has been notified and cannot pay this invoice. - +

+ {drawerState.selectedInvoice.paymentToken?.name || + "Ether "} + {"("} + {drawerState.selectedInvoice.paymentToken?.symbol || + "ETH"} + {")"} +

+

+ {drawerState.selectedInvoice.paymentToken?.address + ? `${drawerState.selectedInvoice.paymentToken.address.substring( + 0, + 10 + )}......${drawerState.selectedInvoice.paymentToken.address.substring( + 33 + )}` + : "Native Currency"} +

- - {drawerState.selectedInvoice.isPaid && ( -
- - Note: Payment was already completed before cancellation - + {drawerState.selectedInvoice.paymentToken?.address && ( +
+

Chain: Sepolia Testnet

)}
- )} -
-
-

- From -

-

- {drawerState.selectedInvoice.user.fname}{" "} - {drawerState.selectedInvoice.user.lname} -

-

- {drawerState.selectedInvoice.user.address} -

-

- {drawerState.selectedInvoice.user.city},{" "} - {drawerState.selectedInvoice.user.country},{" "} - {drawerState.selectedInvoice.user.postalcode} -

-

- {drawerState.selectedInvoice.user.email} -

+
+
+ + Issued:{" "} + {new Date( + drawerState.selectedInvoice.issueDate + ).toLocaleDateString()} + + + Due:{" "} + {new Date( + drawerState.selectedInvoice.dueDate + ).toLocaleDateString()} + +
-
-

- Bill To -

-

- {drawerState.selectedInvoice.client.fname}{" "} - {drawerState.selectedInvoice.client.lname} -

-

- {drawerState.selectedInvoice.client.address} -

-

- {drawerState.selectedInvoice.client.city},{" "} - {drawerState.selectedInvoice.client.country},{" "} - {drawerState.selectedInvoice.client.postalcode} -

-

- {drawerState.selectedInvoice.client.email} -

+
+ + + + + + + + + + + + + {drawerState.selectedInvoice.items?.map((item, index) => ( + + + + + + + + + ))} + +
DescriptionQtyPriceDiscountTaxAmount
{item.description}{item.qty} + {item.unitPrice}{" "} + {drawerState.selectedInvoice.paymentToken?.symbol} + + {item.discount || "0"} + + {item.tax || "0%"} + + {item.amount}{" "} + {drawerState.selectedInvoice.paymentToken?.symbol} +
-
-
-

- Payment Currency -

-
- {drawerState.selectedInvoice.paymentToken?.logo ? ( - {drawerState.selectedInvoice.paymentToken.symbol} - ) : ( -
- -
- )} -
-

- {drawerState.selectedInvoice.paymentToken?.name || "Ether "} - {"("} - {drawerState.selectedInvoice.paymentToken?.symbol || "ETH"} - {")"} -

-

- {drawerState.selectedInvoice.paymentToken?.address - ? `${drawerState.selectedInvoice.paymentToken.address.substring( - 0, - 10 - )}......${drawerState.selectedInvoice.paymentToken.address.substring( - 33 - )}` - : "Native Currency"} -

+ +
+
+ Subtotal: + + {drawerState.selectedInvoice.amountDue}{" "} + {drawerState.selectedInvoice.paymentToken?.symbol} +
-
- {drawerState.selectedInvoice.paymentToken?.address && ( -
-

Chain: Sepolia Testnet

+
+ Network Fee: + + {ethers.formatUnits(fee)} ETH + +
+
+ Total Amount: + + {drawerState.selectedInvoice.paymentToken?.symbol === "ETH" + ? `${( + parseFloat(drawerState.selectedInvoice.amountDue) + + parseFloat(ethers.formatUnits(fee)) + ).toFixed(6)} ETH` + : `${drawerState.selectedInvoice.amountDue} ${ + drawerState.selectedInvoice.paymentToken?.symbol + } + ${ethers.formatUnits(fee)} ETH`} +
- )} -
-
-
- - Issued:{" "} - {new Date( - drawerState.selectedInvoice.issueDate - ).toLocaleDateString()} - - - Due:{" "} - {new Date( - drawerState.selectedInvoice.dueDate - ).toLocaleDateString()} -
-
-
- - - - - - - - - - - - - {drawerState.selectedInvoice.items?.map((item, index) => ( - - - - - - - - - ))} - -
DescriptionQtyPriceDiscountTaxAmount
{item.description}{item.qty} - {item.unitPrice}{" "} - {drawerState.selectedInvoice.paymentToken?.symbol} - - {item.discount || "0"} - - {item.tax || "0%"} - - {item.amount}{" "} - {drawerState.selectedInvoice.paymentToken?.symbol} -
-
- -
-
- Subtotal: - - {drawerState.selectedInvoice.amountDue}{" "} - {drawerState.selectedInvoice.paymentToken?.symbol} - +
+ +
-
- Network Fee: +
+ )} + + setCancelConfirmOpen(false)} + aria-labelledby="alert-dialog-title" + aria-describedby="alert-dialog-description" + > + + Confirm Invoice Cancellation + + + + + You're about to cancel this invoice sent to{" "} - {ethers.formatUnits(fee)} ETH + {invoiceToCancel?.client.fname}{" "} + {invoiceToCancel?.client.lname} -
-
- Total Amount: - - {drawerState.selectedInvoice.paymentToken?.symbol === "ETH" - ? `${( - parseFloat(drawerState.selectedInvoice.amountDue) + - parseFloat(ethers.formatUnits(fee)) - ).toFixed(6)} ETH` - : `${drawerState.selectedInvoice.amountDue} ${ - drawerState.selectedInvoice.paymentToken?.symbol - } + ${ethers.formatUnits(fee)} ETH`} - -
-
- -
- - -
-
- )} - - setCancelConfirmOpen(false)} - aria-labelledby="alert-dialog-title" - aria-describedby="alert-dialog-description" - > - - Confirm Invoice Cancellation - - - - - You're about to cancel this invoice sent to{" "} - - {invoiceToCancel?.client.fname} {invoiceToCancel?.client.lname} - - . - - {invoiceToCancel?.isPaid ? ( - - Payment was already received - cancelling will not reverse the - transaction - - ) : ( - - This action cannot be undone - - )} - - - - - - - -
+ . + + {invoiceToCancel?.isPaid ? ( + + Payment was already received - cancelling will not reverse the + transaction + + ) : ( + + This action cannot be undone + + )} + + + + + + + +
+ ); } diff --git a/frontend/{ b/frontend/{ deleted file mode 100644 index e69de29b..00000000 From 2670801dec9c5f06f6929488b9316486c24e552d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Fri, 22 Aug 2025 16:51:59 +0530 Subject: [PATCH 33/39] prefilled url feature complete --- frontend/public/tokenImages/cin.png | Bin 8961 -> 25311 bytes frontend/src/page/CreateInvoice.jsx | 274 ++++++++----- frontend/src/page/GenerateLink.jsx | 606 ++++++++++++++++++++++------ frontend/src/utils/erc20_token.js | 7 + 4 files changed, 669 insertions(+), 218 deletions(-) diff --git a/frontend/public/tokenImages/cin.png b/frontend/public/tokenImages/cin.png index 6a822a0d79bcb686164362beeb2b1e59338fadfc..46809fdde63a420f1ea002156bb6e5d99fb06b12 100644 GIT binary patch literal 25311 zcmbSTV{;}zvyF`>wx8IxZDV668{0NFwl=nHZm_X!+t%Ip;t$*pGgCDmx_YXnXL`;# z(~(LFl8A7)a3CNch|*GGD*wjWe{B#3>R*o!eDMD_fI6#4ih$Hi;h%zl5Q9jI39EVN zU-&|&>x-{G@t3x?+AEU78VVJ?MydcY#n4gW#kEOc3BVu#p-QO%=*nOrnE)69gce0* z8zB%-5(qJ`fQ8AW*4*cZx9@x79-_|=SI*AsL_}|=%M{(!4DS04*5`~2i(C<=rx+5Z zu)cTzv1kRqh9^QGGX#@FLA~_9IRFYa*(?AQzfvHq4?A-Bs1F-VRLn3&OccVQ>J1yr z1c;jjZUTdV63C2-h|*VoW&#tFU}=N&&z=~>uoRmZ$Vf#T$$yM-|No4^p*AzxTe$LP zIGIQ?{|JP^RW#-R>eJO#;nhiu{x53;O~+xZhS^yk67?~jZ)LT-k$>Ua3~d{4Eg zNn!jFHszghIwnwoH~>uI*|-`VlZf}6!rhrb`-|VP(>xFweC4K%%ZVT^i6n5oIO6mk zemc*)`*ZisdgU_9hucs+A&!4L24dTYG?g-2INc_))z9~@>#M&_Z~G4?aMA|8atHGv zM-?Ch5?Ts4SYB(M;O*32xY^5GumvX`%_k8?Udp zZ*k@Wtmt@Ksy)2&0hSy^njK0;uG7baV9NG&&ueuRYMtH5$RaUZR|r9gufS1EyqRsJ z?@=<|SNqX4(M%5SOyY!`3w=WY|CvY_7_m^%B7>WL>+=Zrl?+w`V|5bHIvJt0kf zsYa!*RZ&qEK@oh#5L_AowwY4obB3gZku-Q^*XXt1i|%PYaY3K4I*Q$7g3{S~bH+=@ zqIGn_O0F9#!Dyi&Wf;TRDk`ahUPq7jmal`UIon?f1zxck<~lgAEQ2Tzx%QxPhUoO> zU|CD7))$<4eBNWAO&`0hQ(1R(9A6@tTTGD&ugOH7#KM+sB^Y-x2!D)yt~L{Im!ADv zHjn!AK5oWp@ghC6InoT}rXytBn5WEfBLGsQsxf?5-GAR&k2f0!kM8#EvmTwex{cgZ z@A;P-jenVl4Ghhovm1`*XHnn%IsVn=w|8{6zU_Qx{RhhJ7>K9-g98pc)H2onUKfJ) z4~1^)@}2YNit8bc`|}Ks_r_h$G93K|WlAdgUpGi?mDm9$mr9I5^hj)WW@+x(Q~%TQ zRnD5C!{?sUknNNGl+Q}5D!3oiO%JG93giP(hYhd_foe$IOHLoJ+l0RR@J$~*$T{xL ztrvOuMafFfh@*(O=b=d1PP4K@R>=>lDhxz9x1HCz-n8`J#X9C&T0c$W+*EC7q@spm z2K(r7x1NmtVpcd7`>U2;qpkkj_B<#es@l|sX;x1-SEuPCI0$9Jh)OxiJS9j7m7h83N;BvxLwJtRW>%ds@E;m zK;XPbLfdb+0jYdWYxBVT#iltiyokwg1yDddxTI$p2=?7=e0^@QwcX$ubn$wFV|~}| z!7GkttN?VBW@POe?ML1xrs63Y8)x=^Uy8jrVtmXU@9pf*{Vji!ZH4_tft_JSupJ1L zjsOpuF`7}^p{U{W+Ld_wrMTfEC%kFm!+{F0er-}LcoT)`t9C-05J zXn-oBiwm>=tp`3~C?9Z-aBDiJWTxo9HyYZyIJ{%w?7vm%5&j1ip7#kf7g7j-OV|`2 zTof)*m}&jK$XQ#x^mp6$Xkp}6@9Ae;qLL0`B#&M1$3qw_TJ9LFM{7}<`6Kt6F&g39 zqu&UkuXOy2J5GXkEGUUGItw2q6aKVQs@|^Ni1~|@F`m|BRtdXa+sUY`rm|98=k-Tp zr|73r@S}}{89(cd2q|ntaQdN0s%49zVsz$p$N7uwm(cjM=S!yE_HF0U8ZPZy;N=b9 zv2zdr6YdW%QmZyYxxifK<8)f_>o(8wb^p58Cyg^m-npA@@EsU~p&X?*9w7xo5d$Mx z;l~BpuIa~q{Fe3eHRIV&(|7iw7nJ+QuJpm3&xJ$+B~-4WSOJAc(#jZ(=Tn>oV~_8B z(aXgo<;I!m4}qr*;s{*{S*jr<@Pc=J7HW!$C=ze~jK-6P$wNWkNon{`Ol5XHG8syC zfe5)hi5?>90+pI{T@e{(UTd`JuSHJAvtrjL1DCs>o_|(n;d8hWRJf;IMTV4#Bb->^ zq+%kDDdA3|{aqiL^_o5cqZRWyml&@zY!uY#HILCy_S^HZP~;~&^!q$D05+wuy*F`^ zdh7C$pP}zl+zs2*1gLGfX@4oSHfQ?lP^3pundQRc%-EZA)^7GCkvpFp*S<)`6}Wv& zeiKdB4eg8f!*dTmr2I@W&Au`D|B~ zLR;Xhikvj3+BVX#qTQC=6|%X(-IW6z*V0l;WFItK&cbWQnZe{wR5ks9;k2zu#V3_z z{_oB38XW0zK^9cK*M(w&+~ekh&Pwvm3p^*f&&Sk8tWP&!D3SN_iKJmIv?AYcx^=+> zGwwnFNz-ylCri)U4x00In_*EAo-(Sh>yl=i^+va&x|7HoNOPbhOTt^xJQ_=i!Y3AI zj?A`2>_=H0wN*^a~Btf#TL7Uvt)7s z_29S=2`4crmqmF^--4m_yfOLFYE>shfBy5vkyD~PBZ8!dLLtM_!%LB*4875wEM&so zGYj_UWG{X)k%C`l%cb-xdK#bR2%=`dJhAz?I5TkyYxD?cM{i- zmcoqPI|lqd6^}Q6a2_uHXJOlVU(l-LXyYB7Ca0fK4@TohyHJi*-OLazz27TDPKaaI z?9zyJUrV10e;ZS$%CKnu<7A#bfANM4Aw8=;$F`22+$~4$%V3Vs~# z76k|%o^*b+%ElNvClsvsQCDzAp|*JsH9}95QsfUWOuC4csVV=NN?3Qo#bJ3VdlgTV zw@N&3j)`|q>+UxlcT`sw7pC*)9R}aY%>@z*HlVH3q&@QMZ0IAZWz}XsiO`6JnaME( zS)HP6%Rzdq*LJ>&i4#kn#*08BghB#99Azav5Zloqnu2uPmyH(q%}c{wIW!QqtU;$G z?2e(6HotM__B5YV!eie27uMHpyK-b>k&nw1O26X@_;%GTsKQxaOnX1Jb75szh_V*h zD{yhf3%&KHKJif~fUxI%q_#JCo=J6_DoJlUP*P-+8l%G^D1plZ2x5dShT?j@OU%TV z9R=xu!Xks3zdJbIi8OKYBDWouSFKfNEXKeI9Wrv~*0O5yIeY|_IHT zz)$Kr8B%9(MTPcyF?FucGzrnB|MKhpg535I_jgB9gxg2@`H>!BToU7~FJ8lB?(KY1 zZDl)H96wn&NRHY$XF_y)E)PTrEJ)#N|$Pi z<^7Nl6~$!pfiT*~^@I=x8LV^AYkiuZS&4~K4lSr?-p<0v8O{=-ND#x9*bxH!VJ4N9 zkgg?8lM3(vZ6glrEVn7giwR>;xm1T*G38jpy#be3FC%OewXI{1z@WyJ@JX}-Ni zdD(j$(M#RPZ!gR`fl0)tNdOn#6$%3R7wbVi0VEMYFc5UuVZr-cGpN+*zu-q)`yKJs z)FDSuReHIkz}BS?`sZ%BT!*vx}D`mmAX8QN43U)5vvtoEM25@95qDDP48 z{Q-F|%pEQx6CtIU$ddn*DQE6CCti)iZ|}p)`f!yXhFZNmvhU5#TI$LohhU08jEmbv zAuK+6GI?Jik^wHynv19g2+WvX3&h6D*_D*J)Fac7RG*yC;*HlhdI?lh+_aG zkg(U#jhPTstwo8!@Hay(odNH`cyoH2$8ir&HJF)IL%sN87o`J1>z{Aj`pOllhht9nKjLy+<`vKRLIsv4uNNI{SXPvMw z0z?39gb>0aJ1+nnP@f4>7gDM&h-Ek{lob4t;IdIB8~6f>{#&`1d}M_^t64vhDN*$I z9bYIg%7G{Fhz5j48(|Z52v0O8jA+$!&Pf7k(#ezbepmUt6LQ?Skb})VSxNe-MQfD> zUvevhEt2#SKU;m*sZ=${qIJIKay0wi9(^$1jrGmvGZPk#MkTf^j}RP8jLMMVVSJx; zkr-o1PJJX-&X}B4=Y-s(^!65| zGLaRTYGx`9_qBf3()2bz_;cTqEO)sZxD&nuH~VsBpVyrHJ?79@vFNMAnTVb_1ea%X z%>)-!s749nj{rBIFpxL zHEy!j^2wbp=Y_~q=f@&MMR2*1+MLo8D~ao5nPL%JauZJ|yG5vSs2x{2TfKGfwTLPy z0)mNO-nMh(Ka}sR?C`^Pmek_>9k05deKsgR9L5Fr zM=hv+}On2d*CzVp!Cs$2t_>g#hMi*8`CBJVgVlTgqXGBC+uJPQ{^tE z^?esiExQ0uG3xy68<2_W?MmXDlDDQwcYnP8I1SOcCO{`P3H_!A%WFTY z%jRqTS)H_t)`DNKO%rrze1PkGLVrL)1?QmTKJ(;v3?Wz~G%o{wtDx@GXuU*#yl z`klB;(|n>GB6WkZO5v0Vx9lbafuw=mU&WIiZxy_*33}I0iwZt2Sn+aQNhYwyGyGj_(+cv0W`*?k7Waq`A z_C(#Zv)toYf3K4U%)}?Z31Pw6FdPP^dm3+Qb_s{#bi9*=dNlMDzHflB{ z9fKekVNe?Jfx=-4jKa)XrX8<3FMDmCgYv+Ny(io(*ZOMVU$MWv|5W`ce9t`@{debG zDN5_2vNDIb9?qxc`QCp+*wiiu*o|NM?F|e%p}AXMJ)p+Rr34~jfua!;gL*^;+OF?l z6KYeD;8?z_j-N~Xp7gkO-@O<%#C@*dWS?EbLeHn+lR5}QB!e)DkV5a5 zWvS?ZB~Jyo;z)7rRabH-a5=Zpi1fbN(Ah)iLCqlM3s1}^n~evdg+HNwAWixk z^bC`eUAJ{1*+J~HJDs-CYg|;f`reQse}sNZ{!`mc#J(6!TSVx@GaelUay|qPy#0vd zMI9P-(k-bqP^Rug2d^ZjkzFeQ8M73H1m$u;(~nK{k%D|Qip>5qe8MI8@@Ax+Po303 z6%XEbW_OHks0e`r36KCoNZO_hBCKJEMQ;j@W0MB%nbf!qk0q!wPS2J&y(O5rYRbN2 z=Bi?zJ?VXegC$llwkq<%ti&u=R18rR0Lm;1fq|tRI-%=%*8PmEDR`P%gB&|C#S0#X z|0=a9Hz^S!_7HTXh{jpr)W_UK%VQ!e10^V5hO4vMTwY2Fg})sifbxe*Lw4s>BhrMp zf<|r-5)FQoSvnq(XOI02v{tvFD^>Qi(BouNogAlDrG!mxfg6ioLU1TVyAc67==Tx* zI3$FKoNsbK**e?Y=GL!izwcdbhN67FWFfzCw_f9hHOiQpg4@5`$Uat7m!F*-|KiKn z-{E2Q_GKu}>%QB`xhUuB;+gP8x*K8*bG^8P?;y#lzQD_&9nf(ekboAi4r_BUh^*U2 z7_W+l^1u!70w2wJia9>seqNA_g(go-G1qs)4TUwiYxBl^kD+@m*4@8~W zKIgq|Z@kviVhn6)b}OewUgcN4VPUSQQoZL}{OM%I4!g+)kVI0^##LYsu)K-C57 zyh(#k^EIWN5sd5i)10)6p}b|}Mw}Qjwh5GlV~=*oJ^uazg#k2zsfd8-l7}^o`JMbh zyW%mYGLcr_u=i9+nYLu9-}_2i{}=DT3Y_08N60qReA{gD5flnu=2MBx>+PTu@oPAi zBP9xy;6GX`49%dJ^S4TffMjtn#FVNh>nm~8<*>v#Na^fTU(mTo4@ zI5luODX_wcfkdxmT4q2N>EsHYxm5`<0e~7f8xQGioY01@viT8R_6;@m!`mciPW4=2@ z_}cfk!Dj-=EZ3Kl>`gD_IOzq0$r5~{U2m;Em@WL}97e>&%uH>ifC(ye0g&XzIGza% zEi9hA#@x_KgT5#oip$y+$hr0|=)OAL+JSMBHxitiut9MZ+9sT7d_!o%45HkYIqtb5 zX~>P@1Qe$GBlQ@T=j$|;h}B{#$IFvW@N~){l90sB%d!1>X1j*j%o-r$N{O;Lg%P=h zQr!0XfXZ~%8s~dE#7l%Pv@MV-P8ArEGxAt{|E^V>;m1dY9R?YgW|iOu93b6Zhg46R zp*mLbs3y`5^!fVpiY&;g4Z^!qQn*$X)25J}yxUm_M%p2dSePlOGSOg2hUCaDL8F-a z!*wGkZ=1CJ-{bK%&u3ab#(Kvz&Dr2t0-9lii(Fo+zemFLfHgZ90I zV@W(M@9&oz>5b$t*Ww~*%@W7Z0quCHutSt!@e^h#JU)HymnZtH!hgvTrTgZ=JL+Ir z&ra{hWLBt`QIi<~zL>pbQC{z*VcxNMn{~x6(0pvd&zHznKE4PF0Zj37Ooq3Rxj_Zl z+v`J4;*^O6-L~@17Az=3R3}^ZC>vzCRmow~oo@Y&=UUB=kJVYY8N28;|8d1>d{Y|2 z4=%9~fK99z1PRK_4$Wv+@%7WY>a~|^9g7NvyW<|0!B1O|>-YgI19^7a+t)L}uC6O! z!L{JtlQSb-X+fojxKPEwgrCO}yGadz2p8K6r`z~_D{)gQ5n~Lj&}jh#w`ZS>vQ&wk zo~Z$5-ibscZ01^6S8wgSm`BS5*{uPo_(NZi&xfzmd4JgIZ}^}xoVX!?1P<2KRFXn1 zVGDcO|E^7^W-Iae!fDay9XfM@mJW+>qjgiYFjH)VE}OHM_A&>JM4=`0tRYUZ<8Y7tgn@vS z=I_auxww&9ciqN*E7E=4SYkut8<%ffFvmU-c+)b-yR$%hw#)iI5D~&W4Cll;y8a+Z zMq(PIYseLbWn2{B?a%6+xnPJwN7Xf>nqTh|Wm%6Ag)BEk*sHo=#a9YabbYK4aLOne z#y6iNquS@J(|T`7^u2Bb~ zau4Hp{o?h~T;cJU(HJd19qFnQgM z7Zh$1oKIuaN?ZaAIgD?>w}Me^T8)p(3c*>Jd^)}5iL<-^F^zSBM}Cf};V?%!aR>m- zfg})p6kxlRZu(FH&1-;Ao{v;l6`pt?RYw{Ka zh?eVhW#U}p?F@gjWnSs?dbMiDMooJV2_Ehdv4Y%?>_7s(df4~;3bTE&<-DjK*V9!U zSa;p9A4lDq2u&IpJ{=B%M;s;ak{|-=lfl}+s5pVJ^fNBea&tFQNQ%<3h~#`)a*M@_ zU+1*vnK5Z{JSN0BvWkXjS>ss1|IN15#yZJ}>JGo>p~=vizV%|V1b{x)qYfMQNM6o;v#2C#2)xdJ|HR>l@zxdnz?CZ?HI_yZWv#OmM6@M$N z6@VyuLjs-rL(YAYo@|ydho|s{#(HSDVi1$^jGgmLp&;0=Q;JS~SuXI5aQ9CnhyDNRM>0sqpu>$xABzObL z=a0+BNM|Sl|NO$>RkP%E?z{o*rPy*W>wRp+;_#$3&y6j$uE*>}79G)#5D4 z(O%urs(t`mMHUI30zxfZd@W_SdLCKZ^X#)1@1b&SeN~ZwlqYhSjk1F-_vI+oHS65f z+M{u1Aupn-0t+{BQ}}*GY;&t#G0$T?9pm}8#h(`SKs%l9HqmDtH+Z)had;zq67vl0 zCbd1Z2+Uw8onLM4u^Um&R}9D+URJ!NikA0D^xPfv2uZUPjx)GDpgm3h4$vPJX#2Ej z32e?N%{k`=syw%+tXTeCjp5&)8~2POCnkviuQ&ui&@A{uT}h>&p0Ae*_OHuz=w0ZZAny!IG&OaXpJD+$D*9 z?}398y4<5CS(#zM7hG2)Y$S|{58f%PLrl~9)!TC7qGCVr(OB-FVdnxwVoVG_L1_8X z#Hst-ubw{P^uCpdlQuiCT@s7WV+fS79%c_j7<*YNL$s*^`4047j`64`@J`;K~p4!X^w4%STdhA$KCzmZEV`SZn;g6!&WF)c1WBiQ(T9WT6*DCn`EPNviT zZ}slSOm3YOhCdeFl#mjs!pR395I2RtF<&X8j6Li^Y|=xTn7gjpxm*3+;SIpOVs6%! z=o1tFD%g!7uH#l1;51v1!VPd@<+Q6VjN+e>$asaG^iUpe-3_M(HL@@B?xZHUv10Iz zvyI6JG{sxW7w+NQpe!VU!=oC=BFV8U|1m~6l|%hyB6{F4q_1^PWF^Ph=c=bA3<6Mr z|5A`ccH#~x8p+BIYFw|l7D(RCbbN95U3Wg6Rcu5{diSK+aqy#8FwmRLyL0Z*zwJ%AJ+kLgWAb%U(n&Za3+^0VQ!&lWM zczudy-{aV%5ahpj{oT>h9h&mTz)5~hKMb3cojN==Mc*Tt54Bt#RVGGzK`}LyIHI|? zoYw^(!mt4QCpYMPQ@=E6#T2x3nC~_;J`t+?KDJXlKjBk}6)|r5_gx0^r^k#tB@y$9 zH)F=VwUPGI4r%3}vCT?Punh%tw+d(w2z`b*dMC~3(W;I?y6-cN=qUqvLi4$T#Dr+F zPtDuJZnT7FQy#v50_A!dRc2JK`+cJU@BWqTKx2wkz*oGEyRP}MM7)BXg7zTda4OB#REnLYH#Suo#4}?8u=-pRul*3eL05J5Nf1`V8 zO)mq7zbOhvv!E5a^&|7U^)bnxOS{EAoR0sTcZ%!vN?=t8M;$O#d#F^AsZSrCFmTUw zug)fg*th<=lw!d*$BK?>T(Tq)nag#=LgLL(pdjFAgB=ANwBb24h;nmX;wqQ* z{iTv#L{oY{3RfFf2^!p8>v_6NW3(u4d`nwc&{9JuA`CA9(gp$B``OKByw6)~2k!rL zIf5i56;3Mu2nFk&w`%gjG_i#XizLmCWT}4fNIAH3jwk&4r`vG}*QPvhtlap;#lQ2B zKVx~HQL&Klf*^&3TJ6E0u>?qw9Qd%nrb+Ac*etnM$iczLL{JhP=#UGjmQf8u{Xim3 ztPf^Ip$YKAYMooU04C0C<=dqhGhUgn0|Th(G#6`zc#d+-SZ3{a(+yp zU*`Ia1^k(PpkW!HlEA)chN(fp6S;#?p@{aVZg=}fh$`Z+1X_Y8i;0z6Ws*j7$V?OZ z>9}dFePYEf=<#yyk0I5}t@{5|bv{EKZ=9XiHZWRdLq_ECleR0QJW1ZDblLVa_44k+yr$4&o%cUKfe z@&hQ$1k}Sx;Tud|dL$<@O}T2v-Xb&ojSz07ehcTN40tIm&Iawa=1KE(we(Ic1ExSO zpa{0IDh5=Eg)o4N38uf1uPq`_@0J*Xk3|H`&EjykXjVoSV%7^_+oFygDRvV@Zz>*LTZ`xMtc6B%R+Uwz~1p^U4gUM4G z>yA6j+2}YHtIzde?p3LmR(eH?;`-NHJc%KmqJ*(kh8#xkiFrD&!)3wtm?}`_M}u;9 zI{M#wa5A|5I#QqmRZ6rJyE#Brr5!1|Are1N1XtSY-fziZso7^|j29*;SM(}-UBk30 zslfb^g;UsYp!#)v#_%bl1VKgvvSnWstr5l*>xWc)R6pZk>(=LJ?J!Ff@C>!w)a)>VF^L&~kAB5HFUVob-pybGsSB}dfp=#&RwX&rv z2f!e2!OSF2{;sUWiYf@OfCL(&9KFBxmPGA>(uO&diq?Do4F78;&fZo;=eg6sBGN{c z{6;)Rnhcb$-4iv10QM6z@DhuE&pw>RhWo0~&iQdp2_)p@b{B@z=cAtQa4$^xxh)3M zkrOvO1hIpa-4#np77+8@+({!7a&DMNG3v;cw<@OSu*^%d-wo9)-AmF(QgIn{*iB(^ zjBO#C{fJNO?tjkf*x4Eh!)x+t=GXJ8p<3r#Ar!AfjhGIRVNgf{rT-O0 zO(D3o<-_vna|?xXZn;KqSIi{SNwW8-FNuFh4kQJ`aJKI~gQ6`JzH^V5Br@QqC{;Le z72i|75#`MHL@2iDa1>HYM@Lo_k6ENaEw{>Zily_&&nXSoaaLQriYqesvot|xuE0!3 zTW{Hi@)E=xqsZwdQ>$Y_D-7dXsPB^d z@D2WJtzP+#Uf!BMV}4*FoJ%)CWJzh}rkBE|w^##0Zf9E2NBF0aT zrPK7(u3}E3enn;`8-m2XU@o>{jy@PLU8{ryD+rxi6#KG|gW^?WLiX(a$KJJq+X%*U z;!BUD%HwQ2X4F}4jKb2;cR7-CbuD@onHY7bqK^nW zIE26>6l@5qP;a`|w=>Z*`66(8uG1pRsD>g%wjByGt$OglI`+kGI-2H`ME$0ufBl+dlF(R9ZlCE?1$$}qABR<)3N_2`|A1E){|b(0-$|@}v6GQnKEJcQ zY96mi0YScs*mqNN)gO4Q@G+i6^&b|L>H>tIisIFvR9faJ$TTOuN!5_QZUp z7z8Crk@ed?04NwGX@BI&=^BXi-b|{M*lso9sQ5+WU00p}=Yl(O(^sJEF=5h*^Ive< z`n-WVY62WwVjgRvm ztKX<(V`M#Edgnz+quRan}Q#>1jU0+(t^Bb(c_opxz_fK9*& z6^v6Qux&FT{p*YSk3Yz9D{v3fKJR;oludYO?T5nAF>pTU2)GZ*AkakRArQ+A<$8!* znqoA6L9Vlw`MlSvvFu3f1=%hiHVV%l!f>^GN~fbHM%haL9EWvhy{`d3Wc z@5TbZ`qP3;wsPBDGd^eN@hND}I}7+mOvTYBS=87;15|IRYU27T|CA3C3*TyiS&_9qTS z+uJFj>3C1^IY;y~)yamak;rKcRO`eDh0y^(azy+#?I4zJO618Mzo*uW=hnEkb*Sb& zq!6KTAThS%>*>>@I`(nvUsxys+Q}@2e^FK*m%i-vZgw=1Okb?sb3MD}E9lPeWFm?H zxG- zELMc!#DXj6E%WYoj@VUq7p;uYB_?AB)4- z#KL!I8QGBp&~HJGfBefXrp5T}?tT0dz1r=W%AnY1D?w!PV6Q2g{9y>p;kl%Le_baRSh@>kYU~!S) z>>#Y=|DcZFCSx&pmAMC)uK?59iqyDhJ=sGLNb4PCVfZlYw6L*7u23JKiY7~&d z7J~O=^QP%)g&X;L3JRokwQ4slwaGqihFiwa0tfaF!gptu)?BwW_x?=IEy@m1txq6< zjkmkj+=tk|iCa^S-;&opT|6!zsZTm#*;jGOYJ9WVH$y{F{Di?qF+w)vvZn2gSgejXIi#XIls7$&}IR5 zEb-jWuSM+-qM~G=M;xd@OrM<-jgw~G{=O#SH_p$xq0+r_PYf`02NXpMA97mBkh`2%4(rwSj)E(w{ z9x~AaY}4f|3qNsNDauUU(-Z^3@4{#3x%OQeQG&AzX&LukLlz3WJ_ImJD@;1r1O*2w zjQg26?sReZ1~OslA*m;^cRt6h22ktWul8DlwyR`4+WDd0BUTc+VUa|MPuO5>2h{m` zVQkDF+|VkZaJBXhsvM1a{8LiF{qYK2g0Tj^n7jg>-Q}Up@Vc9>)|hw;V7xB;rp>^J z<3*O$Kjg^9xu2T{$?fVWC&6(~2sK=0U3r-+a3#1yioW|^xKR7Srfq{CF))z=@B7>q zR(Uqv`kV*`XN}W-Zx7^T=1+~wDUzJlo$NMeNr){pZ!lb7(uqbGa+ZlO%QTAxcTDvV zTYDr}IPtMhU$o)7gqCsSl-)QA(+bNtXmGH75)6FPq1kFmj`VE`0byeBANpuEwFF0S zrgu@-f-~F`CVan-r~-4d>z?EsW!}PE-LQtBs*k=1nRO?Fmzg%TZMyDC{F=xc<1YZZ zv+pLXwM0$q)k0aJ#lW*|*Qo1gh;pq+O+p`4dQWZsTojVoTv>(o_>9D!sOW%`*ef)B zuH&uFGm;6x*4GrGNgHFgv8wBR#B#t7KqKx3Q-E@fp^PdX%_|tXIv65yX&6nAJm!#s ze_koHBl5%b!OC^TYHYW>V;l$jpjwQdY1TYdvbXhWe>tzGbnTo6Su;i?_3O=`>O*VVC zzl~~ab)ry~WsFcyps7{z!x|SQy%FI|7R_u1#-ro2ngrDuF3YDLctXE&I{L)R&aY)vrs*RgQ zzfAC%#kLmuWpSm%gfvCNpm0b))g%H;Wr({4(wpBff9^t&j%=k7ESZ|VJ{O(JM;Z^* znvH3klJI{K3*^+`S;}z)r9%{qf$629xO1=9>M{*c{r=8(Zub6NzUe%uu{&&RNm8U7 z%7&ya8Q#alW(BS_(BFvf5Eue0gI2ZpN;ynhWnfxfM>OM)>TTCh99jDK=4-p zVhKV@z4dXVK;HcCH!vhG0d_CjV=LNO#VLVl`4B-nRCdm{^I@_tpRJoD%j

j&?*{ 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 diff --git a/frontend/src/page/CreateInvoice.jsx b/frontend/src/page/CreateInvoice.jsx index 6879d8ef..05cd2c82 100644 --- a/frontend/src/page/CreateInvoice.jsx +++ b/frontend/src/page/CreateInvoice.jsx @@ -26,7 +26,7 @@ import { import { cn } from "@/lib/utils"; import { format } from "date-fns"; import { Label } from "../components/ui/label"; -import { useNavigate } from "react-router-dom"; +import { useNavigate, useSearchParams } from "react-router-dom"; import { LitNodeClient } from "@lit-protocol/lit-node-client"; import { encryptString } from "@lit-protocol/encryption/src/lib/encryption.js"; @@ -37,7 +37,6 @@ import { LitAccessControlConditionResource, } from "@lit-protocol/auth-helpers"; -// Add this near the top with other imports import { Select, SelectContent, @@ -61,7 +60,11 @@ function CreateInvoice() { const navigate = useNavigate(); const litClientRef = useRef(null); - // Add these state variables + const [searchParams] = useSearchParams(); + + const [clientAddress, setClientAddress] = useState(""); + + // Token selection state const [selectedToken, setSelectedToken] = useState(TOKEN_PRESETS[0]); const [customTokenAddress, setCustomTokenAddress] = useState(""); const [useCustomToken, setUseCustomToken] = useState(false); @@ -70,6 +73,7 @@ function CreateInvoice() { const [tokenVerificationState, setTokenVerificationState] = useState("idle"); const [verifiedToken, setVerifiedToken] = useState(null); const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); + const filteredTokens = TOKEN_PRESETS.filter( (token) => token.name.toLowerCase().includes(searchTerm.toLowerCase()) || @@ -82,6 +86,8 @@ function CreateInvoice() { "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI ]; + + const TESTNET_TOKEN = ["0xB5E9C6e57C9d312937A059089B547d0036c155C7"]; //sepolia based chainvoice test token (CIN) const [itemData, setItemData] = useState([ { description: "", @@ -95,16 +101,46 @@ function CreateInvoice() { const [totalAmountDue, setTotalAmountDue] = useState(0); + useEffect(() => { + console.log("account address : ", account.address); + const urlClientAddress = searchParams.get("clientAddress"); + const urlTokenAddress = searchParams.get("tokenAddress"); + const isCustomFromURL = searchParams.get("customToken") === "true"; + + if (urlClientAddress) { + setClientAddress(urlClientAddress); + } + + if (urlTokenAddress) { + if (isCustomFromURL) { + setUseCustomToken(true); + setCustomTokenAddress(urlTokenAddress); + verifyToken(urlTokenAddress); + } else { + const preselectedToken = TOKEN_PRESETS.find( + (token) => + token.address.toLowerCase() === urlTokenAddress.toLowerCase() + ); + if (preselectedToken) { + setSelectedToken(preselectedToken); + setUseCustomToken(false); + } else { + setUseCustomToken(true); + setCustomTokenAddress(urlTokenAddress); + verifyToken(urlTokenAddress); + } + } + } + }, [searchParams]); + 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); @@ -120,7 +156,6 @@ function CreateInvoice() { }); await client.connect(); litClientRef.current = client; - console.log(litClientRef.current); } }; initLit(); @@ -176,18 +211,23 @@ function CreateInvoice() { const verifyToken = async (address) => { setTokenVerificationState("verifying"); - try { - const provider = new BrowserProvider(walletClient); - const contract = new ethers.Contract(address, ERC20_ABI, provider); - - const [symbol, name, decimals] = await Promise.all([ - contract.symbol().catch(() => "UNKNOWN"), - contract.name().catch(() => "Unknown Token"), - contract.decimals().catch(() => 18), - ]); - setVerifiedToken({ address, symbol, name, decimals }); - setTokenVerificationState("success"); + if (typeof window !== "undefined" && window.ethereum) { + const provider = new BrowserProvider(window.ethereum); + const contract = new ethers.Contract(address, ERC20_ABI, provider); + + const [symbol, name, decimals] = await Promise.all([ + contract.symbol().catch(() => "UNKNOWN"), + contract.name().catch(() => "Unknown Token"), + contract.decimals().catch(() => 18), + ]); + + setVerifiedToken({ address, symbol, name, decimals }); + setTokenVerificationState("success"); + } else { + console.error("No Ethereum provider found"); + setTokenVerificationState("error"); + } } catch (error) { console.error("Verification failed:", error); setTokenVerificationState("error"); @@ -205,10 +245,8 @@ function CreateInvoice() { const provider = new BrowserProvider(walletClient); const signer = await provider.getSigner(); - // Determine the token to use const paymentToken = useCustomToken ? verifiedToken : selectedToken; - // 1. Prepare invoice data const invoicePayload = { amountDue: totalAmountDue.toString(), dueDate, @@ -241,12 +279,12 @@ function CreateInvoice() { const invoiceString = JSON.stringify(invoicePayload); - // 2. Setup Lit const litNodeClient = litClientRef.current; if (!litNodeClient) { alert("Lit client not initialized"); return; } + const accessControlConditions = [ { contractAddress: "", @@ -273,7 +311,6 @@ function CreateInvoice() { }, ]; - // 3. Encrypt const { ciphertext, dataToEncryptHash } = await encryptString( { accessControlConditions, @@ -314,12 +351,12 @@ function CreateInvoice() { const encryptedStringBase64 = btoa(ciphertext); - // 4. Send to contract const contract = new Contract( import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, signer ); + const tx = await contract.createInvoice( data.clientAddress, ethers.parseUnits(totalAmountDue.toString(), paymentToken.decimals), @@ -342,42 +379,24 @@ function CreateInvoice() { e.preventDefault(); const formData = new FormData(e.target); - // User detail - const userAddress = formData.get("userAddress"); - const userFname = formData.get("userFname"); - const userLname = formData.get("userLname"); - const userEmail = formData.get("userEmail"); - const userCountry = formData.get("userCountry"); - const userCity = formData.get("userCity"); - const userPostalcode = formData.get("userPostalcode"); - - // Client detail - const clientAddress = formData.get("clientAddress"); - const clientFname = formData.get("clientFname"); - const clientLname = formData.get("clientLname"); - const clientEmail = formData.get("clientEmail"); - const clientCountry = formData.get("clientCountry"); - const clientCity = formData.get("clientCity"); - const clientPostalcode = formData.get("clientPostalcode"); - const data = { - userAddress, - userFname, - userLname, - userEmail, - userCountry, - userCity, - userPostalcode, - clientAddress, - clientFname, - clientLname, - clientEmail, - clientCountry, - clientCity, - clientPostalcode, + userAddress: formData.get("userAddress"), + userFname: formData.get("userFname"), + userLname: formData.get("userLname"), + userEmail: formData.get("userEmail"), + userCountry: formData.get("userCountry"), + userCity: formData.get("userCity"), + userPostalcode: formData.get("userPostalcode"), + clientAddress: formData.get("clientAddress"), + clientFname: formData.get("clientFname"), + clientLname: formData.get("clientLname"), + clientEmail: formData.get("clientEmail"), + clientCountry: formData.get("clientCountry"), + clientCity: formData.get("clientCity"), + clientPostalcode: formData.get("clientPostalcode"), itemData, }; - console.log(data); + await createInvoiceRequest(data); }; @@ -390,7 +409,27 @@ function CreateInvoice() { onDismiss={() => setShowWalletAlert(false)} />

-
+ +
+ {(searchParams.get("clientAddress") || + searchParams.get("amount") || + searchParams.get("description")) && ( +
+
+ +
+

+ Form Pre-filled from Link +

+

+ Some fields have been automatically filled based on the shared + link. You can modify them if needed. +

+
+
+
+ )} +

Create New Invoice Request

@@ -465,14 +504,13 @@ function CreateInvoice() {
- {/* Your Information */}

From (Your Information)

@@ -563,6 +601,8 @@ function CreateInvoice() { placeholder="Client Wallet Address" className="w-full mb-4 border-gray-300 text-black" name="clientAddress" + value={clientAddress} + onChange={(e) => setClientAddress(e.target.value)} />
@@ -708,44 +748,84 @@ function CreateInvoice() { />
{!searchTerm && ( -
-
- Popular -
- {TOKEN_PRESETS.filter((token) => - POPULAR_TOKENS.includes(token.address) - ).map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> + <> +
+
+ Popular +
+ {TOKEN_PRESETS.filter((token) => + POPULAR_TOKENS.includes(token.address) + ).map((token) => ( + +
+
+ {token.name} { + e.currentTarget.src = + "/tokenImages/generic.png"; + }} + /> +
+
+ + {token.name} + + + {token.symbol} + +
-
- - {token.name} - - - {token.symbol} - + + ))} +
+
+
+ Testnet Token +
+ {TOKEN_PRESETS.filter((token) => + TESTNET_TOKEN.includes(token.address) + ).map((token) => ( + +
+
+ {token.name} { + e.currentTarget.src = + "/tokenImages/generic.png"; + }} + /> +
+
+ + {token.name} + + + {token.symbol} + +
-
-
- ))} -
+ + ))} +
+ )}
@@ -754,7 +834,9 @@ function CreateInvoice() {
{filteredTokens .filter( - (token) => !POPULAR_TOKENS.includes(token.address) + (token) => + !POPULAR_TOKENS.includes(token.address) && + !TESTNET_TOKEN.includes(token.address) ) .map((token) => ( {loading ? (
diff --git a/frontend/src/page/GenerateLink.jsx b/frontend/src/page/GenerateLink.jsx index d8185eca..e4fbb238 100644 --- a/frontend/src/page/GenerateLink.jsx +++ b/frontend/src/page/GenerateLink.jsx @@ -1,7 +1,15 @@ -// pages/GeneratePrefilledLink.jsx -import React, { useState, useEffect } from "react"; -import { useAccount } from "wagmi"; -import { Copy, Link, Check } from "lucide-react"; +import React, { useState, useEffect, useRef } from "react"; +import { useAccount, useWalletClient } from "wagmi"; +import { + Copy, + Link, + Check, + Wallet, + PlusIcon, + Loader2, + CheckCircle2, + XCircle, +} from "lucide-react"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Label } from "../components/ui/label"; @@ -12,31 +20,73 @@ import { SelectTrigger, SelectValue, } from "@/components/ui/select"; + import { TOKEN_PRESETS } from "@/utils/erc20_token"; import WalletConnectionAlert from "@/components/WalletConnectionAlert"; +import TokenIntegrationRequest from "@/components/TokenIntegrationRequest"; +import { BrowserProvider, ethers } from "ethers"; +import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; const GenerateLink = () => { const { address, isConnected } = useAccount(); + const { data: walletClient } = useWalletClient(); const [copied, setCopied] = useState(false); - const [selectedToken, setSelectedToken] = useState(TOKEN_PRESETS[0]); const [amount, setAmount] = useState(""); const [description, setDescription] = useState(""); const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); - useEffect(() => { - setShowWalletAlert(!isConnected); - }, [isConnected]); - - // Generate the prefilled link + // Token selection state + const [selectedToken, setSelectedToken] = useState(TOKEN_PRESETS[0]); + const [customTokenAddress, setCustomTokenAddress] = useState(""); + const [useCustomToken, setUseCustomToken] = useState(false); + const [searchTerm, setSearchTerm] = useState(""); + const inputRef = useRef(null); + const [tokenVerificationState, setTokenVerificationState] = useState("idle"); + const [verifiedToken, setVerifiedToken] = useState(null); + const [loading, setLoading] = useState(false); + + const filteredTokens = TOKEN_PRESETS.filter( + (token) => + token.name.toLowerCase().includes(searchTerm.toLowerCase()) || + token.symbol.toLowerCase().includes(searchTerm.toLowerCase()) + ); + + const POPULAR_TOKENS = [ + "0x0000000000000000000000000000000000000000", // ETH + "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC + "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT + "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI + ]; + + const TESTNET_TOKEN = ["0xB5E9C6e57C9d312937A059089B547d0036c155C7"]; + + useEffect(() => { + setShowWalletAlert(!isConnected); + }, [isConnected]); + const generateLink = () => { + const tokenToUse = useCustomToken ? verifiedToken : selectedToken; + + if (!tokenToUse) { + return `${window.location.origin}/dashboard/create?clientAddress=${ + address || "" + }`; + } + const params = new URLSearchParams({ clientAddress: address || "", - tokenAddress: selectedToken.address, - chain: "1", // Ethereum mainnet, adjust as needed - amount: amount || "", - description: description || "", + tokenAddress: tokenToUse.address, + customToken: useCustomToken ? true : false, + chain: "1", }); + if (amount) { + params.append("amount", amount); + } + if (description) { + params.append("description", description); + } + return `${window.location.origin}/dashboard/create?${params.toString()}`; }; @@ -50,135 +100,447 @@ const GenerateLink = () => { } }; + const verifyToken = async (address) => { + if (!walletClient) return; + + setTokenVerificationState("verifying"); + + try { + const provider = new BrowserProvider(walletClient); + const contract = new ethers.Contract(address, ERC20_ABI, provider); + + const [symbol, name, decimals] = await Promise.all([ + contract.symbol().catch(() => "UNKNOWN"), + contract.name().catch(() => "Unknown Token"), + contract.decimals().catch(() => 18), + ]); + setVerifiedToken({ address, symbol, name, decimals }); + setTokenVerificationState("success"); + } catch (error) { + console.error("Verification failed:", error); + setTokenVerificationState("error"); + } + }; return ( <>
- setShowWalletAlert(false)} + setShowWalletAlert(false)} + /> +
+ +
+
+

+
+ +
+ Generate Prefilled Invoice Link +

+

+ Create a shareable link that pre-fills the invoice form with your + details. +

+
+
+
+
+ +
-
-
-

- Generate Prefilled Invoice Link -

-

- Create a shareable link that pre-fills the invoice form with your - details. -

-
-
-
- {/* Your Address (Auto-filled) */} -
- - -
+ {/* Token Selection */} +
+
+
+
+ + setSearchTerm(e.target.value)} + /> +
+ {!searchTerm && ( + <> +
+
+ Popular +
+ {TOKEN_PRESETS.filter((token) => + POPULAR_TOKENS.includes(token.address) + ).map((token) => ( + +
+
+ {token.name} { + e.currentTarget.src = + "/tokenImages/generic.png"; + }} + /> +
+
+ + {token.name} + + + {token.symbol} + +
+
+
+ ))} +
+
+
+ Testnet Token +
+ {TOKEN_PRESETS.filter((token) => + TESTNET_TOKEN.includes(token.address) + ).map((token) => ( + +
+
+ {token.name} { + e.currentTarget.src = + "/tokenImages/generic.png"; + }} + /> +
+
+ + {token.name} + + + {token.symbol} + +
+
+
+ ))} +
+ + )} +
+
+ {searchTerm ? "Search Results" : "All Tokens"} +
+
+ {filteredTokens + .filter( + (token) => + !POPULAR_TOKENS.includes(token.address) && + !TESTNET_TOKEN.includes(token.address) + ) + .map((token) => ( + +
+
+ {token.name} { + e.currentTarget.src = + "/tokenImages/generic.png"; + }} + /> +
+
+ + {token.name} + + + {token.symbol} + +
+
+
+ ))} +
+ {filteredTokens.length === 0 && ( +
+ No tokens found +
+ )} +
- {/* Token Selection */} -
- - +
+ + {!useCustomToken && selectedToken && ( +
+ +
+
+
+ {selectedToken.name} +
+
+ + {selectedToken.name} + + + {selectedToken.symbol} + +
+ {selectedToken.address} +
+
+
+
+
+ )} +
+ + {useCustomToken && ( +
+
+ + { + const address = e.target.value; + setCustomTokenAddress(address); + if (!address || !ethers.isAddress(address)) { + setTokenVerificationState("idle"); + setVerifiedToken(null); + } else if (ethers.isAddress(address)) { + verifyToken(address); + } + }} + className="h-10 bg-gray-50 text-gray-700 border-gray-200 focus:ring-2 focus:ring-blue-100" + disabled={loading} /> - {token.name} ({token.symbol}) +

+ Enter a valid ERC-20 token contract address +

- - ))} - - + + {tokenVerificationState === "verifying" && ( +
+ + Verifying token... +
+ )} + + {tokenVerificationState === "success" && verifiedToken && ( +
+
+
+ +
+

+ {verifiedToken.name} ({verifiedToken.symbol}) +

+

+ {verifiedToken.address} +

+

+

+ Decimals: {String(verifiedToken.decimals)} +

+

+
+
+
+ +
+ )} + + {tokenVerificationState === "error" && ( +
+
+ +

+ Failed to verify token. Please check the address. +

+
+
+ )} +
+ )} +
+
+
- {/* Optional: Default Amount */} -
-
+
From 622f85b8e759b866c2204f636c84178177ee3df0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Tue, 26 Aug 2025 18:14:19 +0530 Subject: [PATCH 35/39] token selector improvement --- frontend/package.json | 8 +- frontend/src/components/TokenPicker.jsx | 384 ++++++++++++ frontend/src/components/ui/avatar.jsx | 19 + frontend/src/components/ui/badge.jsx | 43 +- frontend/src/components/ui/copyButton.jsx | 41 ++ frontend/src/components/ui/dialog.jsx | 94 +++ frontend/src/components/ui/separator.jsx | 23 + frontend/src/hooks/useTokenList.js | 86 +++ frontend/src/hooks/useTokenSearch.js | 118 ++++ frontend/src/page/CreateInvoice.jsx | 475 +++++--------- frontend/src/page/GenerateLink.jsx | 4 +- frontend/src/page/ReceivedInvoice.jsx | 30 +- frontend/src/utils/erc20_token.js | 716 ---------------------- 13 files changed, 953 insertions(+), 1088 deletions(-) create mode 100644 frontend/src/components/TokenPicker.jsx create mode 100644 frontend/src/components/ui/avatar.jsx create mode 100644 frontend/src/components/ui/copyButton.jsx create mode 100644 frontend/src/components/ui/dialog.jsx create mode 100644 frontend/src/components/ui/separator.jsx create mode 100644 frontend/src/hooks/useTokenList.js create mode 100644 frontend/src/hooks/useTokenSearch.js delete mode 100644 frontend/src/utils/erc20_token.js diff --git a/frontend/package.json b/frontend/package.json index 4abc1634..61a2806e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,10 +18,13 @@ "@lit-protocol/lit-node-client": "^7.2.0", "@mui/icons-material": "^6.4.6", "@mui/material": "^6.4.6", + "@radix-ui/react-avatar": "^1.1.10", + "@radix-ui/react-dialog": "^1.1.15", "@radix-ui/react-label": "^2.1.1", "@radix-ui/react-popover": "^1.1.5", "@radix-ui/react-select": "^2.2.5", - "@radix-ui/react-slot": "^1.1.1", + "@radix-ui/react-separator": "^1.1.7", + "@radix-ui/react-slot": "^1.2.3", "@rainbow-me/rainbowkit": "^2.2.2", "@tanstack/react-query": "^5.64.1", "class-variance-authority": "^0.7.1", @@ -31,7 +34,7 @@ "eth-crypto": "^2.7.0", "ethereum-unit-converter": "^0.0.17", "ethers": "^6.13.5", - "framer-motion": "^12.23.0", + "framer-motion": "^12.23.12", "html2canvas": "^1.4.1", "jspdf": "^3.0.0", "lucide-react": "^0.471.1", @@ -44,6 +47,7 @@ "react-router-dom": "^7.1.1", "react-to-print": "^3.0.5", "react-toastify": "^11.0.5", + "react-window": "^1.8.11", "tailwind-merge": "^2.6.0", "tailwindcss-animate": "^1.0.7", "viem": "^2.22.9", diff --git a/frontend/src/components/TokenPicker.jsx b/frontend/src/components/TokenPicker.jsx new file mode 100644 index 00000000..90379628 --- /dev/null +++ b/frontend/src/components/TokenPicker.jsx @@ -0,0 +1,384 @@ +import React, { useState, useEffect, useRef, useCallback, memo } from "react"; +import { + Search, + X, + AlertCircle, + Loader2, + ChevronDown, + Coins, + ToggleLeft, + ToggleRight, +} from "lucide-react"; +import { cn } from "@/lib/utils"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { useTokenList } from "../hooks/useTokenList"; +import { useTokenSearch } from "../hooks/useTokenSearch"; +import { CopyButton } from "./ui/copyButton"; +import { Avatar } from "./ui/avatar"; +import { Badge } from "./ui/badge"; + +// Simple Modal Component (unchanged) +const Modal = ({ isOpen, onClose, children }) => { + useEffect(() => { + if (isOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "unset"; + } + return () => { + document.body.style.overflow = "unset"; + }; + }, [isOpen]); + + if (!isOpen) return null; + + return ( +
+
+
+ {children} +
+
+ ); +}; + +// Toggle Switch Component +export const ToggleSwitch = ({ enabled, onChange, leftLabel, rightLabel }) => ( +
+ + {leftLabel} + + + + {rightLabel} + +
+); + +// Highlight Match Component (unchanged) +function HighlightMatch({ text, query }) { + if (!query.trim()) return <>{text}; + + const regex = new RegExp( + `(${query.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, + "gi" + ); + const parts = text.split(regex); + + return ( + <> + {parts.map((part, index) => + regex.test(part) ? ( + + {part} + + ) : ( + part + ) + )} + + ); +} + +// Token Item Component (unchanged) +const TokenItem = memo(function TokenItem({ + token, + query, + isSelected, + onSelect, +}) { + const handleClick = useCallback(() => { + onSelect(token); + }, [onSelect, token]); + + return ( + + ); +}); + +// Main TokenPicker Component +export function TokenPicker({ + selected, + onSelect, + placeholder = "Search tokens", + chainId, + className, + disabled = false, + allowCustom = true, + onCustomTokenClick, +}) { + const [open, setOpen] = useState(false); + const inputRef = useRef(null); + const { + tokens, + loading: tokensLoading, + error: tokensError, + } = useTokenList(chainId); + + const { tokens: filteredTokens, query, setQuery } = useTokenSearch(tokens); + + useEffect(() => { + if (open && inputRef.current) { + setTimeout(() => inputRef.current?.focus(), 100); + } + }, [open]); + + useEffect(() => { + if (!open) { + setQuery(""); + } + }, [open, setQuery]); + + const handleSelect = (token) => { + onSelect(token); + setOpen(false); + }; + + const handleCustomTokenClick = () => { + if (onCustomTokenClick) { + onCustomTokenClick(); + } + setOpen(false); + }; + + return ( + <> + + + setOpen(false)}> +
+
+ +

+ Select Token +

+ +
+ +
+
+ + setQuery(e.target.value)} + className="pl-10 pr-10 h-12 border-gray-300 text-black" + /> + {query && ( + + )} +
+
+ +
+
+ {tokensLoading ? ( +
+
+ + + Loading tokens... + +
+
+ ) : tokensError ? ( +
+
+ + + Failed to load tokens + + + {tokensError} + +
+
+ ) : filteredTokens.length === 0 ? ( +
+
+ + No tokens found + {query && ( + + Try a different search term + + )} +
+
+ ) : ( +
+ {filteredTokens.map((token) => ( + + ))} +
+ )} +
+ + {allowCustom && ( +
+ +
+ )} +
+
+
+ + ); +} + +export default TokenPicker; diff --git a/frontend/src/components/ui/avatar.jsx b/frontend/src/components/ui/avatar.jsx new file mode 100644 index 00000000..47effbf4 --- /dev/null +++ b/frontend/src/components/ui/avatar.jsx @@ -0,0 +1,19 @@ +const Avatar = ({ src, alt, className = "", children, onError }) => ( +
+ {src ? ( + {alt} + ) : ( +
+ {children} +
+ )} +
+); +export {Avatar} \ No newline at end of file diff --git a/frontend/src/components/ui/badge.jsx b/frontend/src/components/ui/badge.jsx index a687ebad..75dfca81 100644 --- a/frontend/src/components/ui/badge.jsx +++ b/frontend/src/components/ui/badge.jsx @@ -1,34 +1,9 @@ -import * as React from "react" -import { cva } from "class-variance-authority"; - -import { cn } from "@/lib/utils" - -const badgeVariants = cva( - "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", - { - variants: { - variant: { - default: - "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", - secondary: - "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", - destructive: - "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", - outline: "text-foreground", - }, - }, - defaultVariants: { - variant: "default", - }, - } -) - -function Badge({ - className, - variant, - ...props -}) { - return (
); -} - -export { Badge, badgeVariants } +const Badge = ({ className = "", children }) => ( + + {children} + +); + +export {Badge} \ No newline at end of file diff --git a/frontend/src/components/ui/copyButton.jsx b/frontend/src/components/ui/copyButton.jsx new file mode 100644 index 00000000..397f5d52 --- /dev/null +++ b/frontend/src/components/ui/copyButton.jsx @@ -0,0 +1,41 @@ +import { Check, Copy } from "lucide-react"; +import { useState } from "react"; + +// Copy Button Component +const CopyButton = ({ textToCopy, className = "" }) => { + const [copied, setCopied] = useState(false); + + const handleCopy = async (e) => { + e.stopPropagation(); + try { + await navigator.clipboard.writeText(textToCopy); + setCopied(true); + setTimeout(() => setCopied(false), 500); + } catch (err) { + console.error("Failed to copy:", err); + } + }; + + return ( + + ); +}; + +export {CopyButton} \ No newline at end of file diff --git a/frontend/src/components/ui/dialog.jsx b/frontend/src/components/ui/dialog.jsx new file mode 100644 index 00000000..b41b8e1a --- /dev/null +++ b/frontend/src/components/ui/dialog.jsx @@ -0,0 +1,94 @@ +import * as React from "react" +import * as DialogPrimitive from "@radix-ui/react-dialog" +import { X } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Dialog = DialogPrimitive.Root + +const DialogTrigger = DialogPrimitive.Trigger + +const DialogPortal = DialogPrimitive.Portal + +const DialogClose = DialogPrimitive.Close + +const DialogOverlay = React.forwardRef(({ className, ...props }, ref) => ( + +)) +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName + +const DialogContent = React.forwardRef(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)) +DialogContent.displayName = DialogPrimitive.Content.displayName + +const DialogHeader = ({ + className, + ...props +}) => ( +
+) +DialogHeader.displayName = "DialogHeader" + +const DialogFooter = ({ + className, + ...props +}) => ( +
+) +DialogFooter.displayName = "DialogFooter" + +const DialogTitle = React.forwardRef(({ className, ...props }, ref) => ( + +)) +DialogTitle.displayName = DialogPrimitive.Title.displayName + +const DialogDescription = React.forwardRef(({ className, ...props }, ref) => ( + +)) +DialogDescription.displayName = DialogPrimitive.Description.displayName + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogTrigger, + DialogClose, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +} diff --git a/frontend/src/components/ui/separator.jsx b/frontend/src/components/ui/separator.jsx new file mode 100644 index 00000000..c40b8883 --- /dev/null +++ b/frontend/src/components/ui/separator.jsx @@ -0,0 +1,23 @@ +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef(( + { className, orientation = "horizontal", decorative = true, ...props }, + ref +) => ( + +)) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/frontend/src/hooks/useTokenList.js b/frontend/src/hooks/useTokenList.js new file mode 100644 index 00000000..9c42cf7b --- /dev/null +++ b/frontend/src/hooks/useTokenList.js @@ -0,0 +1,86 @@ +import { useState, useEffect } from "react"; + +// Cache object to store tokens by chainId +const tokenCache = {}; + +// Add testnet chain IDs +const TESTNET_CHAIN_IDS = new Set([11155111, 5]); // Sepolia, Goerli + +// Helper function to check if a chain is testnet +const isTestnet = (chainId) => TESTNET_CHAIN_IDS.has(chainId); + +export const ChainIdToName = { + 1: "ethereum", + 61:"ethereum-classic", + 11155111: "sepolia", // For demo purposes +}; + +export function useTokenList(chainId) { + const [tokens, setTokens] = useState([]); + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchTokens = async () => { + // Return cached tokens if available + if (tokenCache[chainId]) { + setTokens(tokenCache[chainId]); + return; + } + + setLoading(true); + setError(null); + + // Check if chain is testnet - use local preset for testnets + if (isTestnet(chainId)) { + setError(`Please manually input the token's contract address instead.`); + setLoading(false); + return; + } + + // Check if chain is supported + if (!ChainIdToName[chainId]) { + setError(`Chain ID ${chainId} is not supported yet`); + setLoading(false); + return; + } + + try { + const dataUrl = `https://raw.githubusercontent.com/StabilityNexus/TokenList/main/${ChainIdToName[chainId]}-tokens.json`; + const response = await fetch(dataUrl, { + headers: { + Accept: "application/json", + }, + }); + + if (!response.ok) { + throw new Error(`Failed to fetch tokens: ${response.statusText}`); + } + + const data = await response.json(); + console.log(data); + // Transform data to match expected format + const transformedData = data.map((token) => ({ + contract_address: token.contract_address || token.address, + symbol: token.symbol, + name: token.name, + image: token.image || token.logo || "/tokenImages/generic.png", + })); + + // Cache the tokens + tokenCache[chainId] = transformedData; + setTokens(transformedData); + } catch (error) { + console.error("Token fetch error:", error); + setError( + error instanceof Error ? error.message : "Failed to fetch tokens" + ); + } finally { + setLoading(false); + } + }; + + fetchTokens(); + }, [chainId]); + return { tokens, loading, error }; +} diff --git a/frontend/src/hooks/useTokenSearch.js b/frontend/src/hooks/useTokenSearch.js new file mode 100644 index 00000000..92bcf509 --- /dev/null +++ b/frontend/src/hooks/useTokenSearch.js @@ -0,0 +1,118 @@ +import { useState, useEffect, useMemo } from "react"; + +// Create indexes for token data +function createSearchIndexes(tokens) { + const indexes = { + bySymbol: new Map(), + bySymbolPrefix: new Map(), + byNamePrefix: new Map(), + byAddress: new Map(), + }; + + tokens.forEach((token) => { + const symbol = token.symbol.toLowerCase(); + const name = token.name.toLowerCase(); + const address = token.contract_address.toLowerCase(); + + // Exact symbol match index + indexes.bySymbol.set(symbol, token); + + // Symbol prefix index + for (let i = 1; i <= symbol.length; i++) { + const prefix = symbol.slice(0, i); + if (!indexes.bySymbolPrefix.has(prefix)) { + indexes.bySymbolPrefix.set(prefix, new Set()); + } + indexes.bySymbolPrefix.get(prefix).add(token); + } + + // Name prefix index + for (let i = 1; i <= name.length; i++) { + const prefix = name.slice(0, i); + if (!indexes.byNamePrefix.has(prefix)) { + indexes.byNamePrefix.set(prefix, new Set()); + } + indexes.byNamePrefix.get(prefix).add(token); + } + + // Address index + indexes.byAddress.set(address, token); + }); + + return indexes; +} + +export function useTokenSearch(tokens, pageSize = 250) { + const [loading, setLoading] = useState(false); + const [error, setError] = useState(null); + const [query, setQuery] = useState(""); + const [page, setPage] = useState(1); + + // For debouncing search + const [debouncedQuery, setDebouncedQuery] = useState(""); + useEffect(() => { + const timer = setTimeout(() => { + setDebouncedQuery(query); + setPage(1); // Reset pagination when query changes + }, 250); + return () => clearTimeout(timer); + }, [query]); + + // Create search indexes + const indexes = useMemo(() => createSearchIndexes(tokens), [tokens]); + + const filteredTokens = useMemo(() => { + if (!debouncedQuery.trim()) { + return tokens.slice(0, page * pageSize); + } + + const lowerQuery = debouncedQuery.toLowerCase(); + const results = new Set(); + + // Check exact symbol match + const exactSymbol = indexes.bySymbol.get(lowerQuery); + if (exactSymbol) { + results.add(exactSymbol); + } + + // Check symbol prefix matches + const symbolPrefixMatches = indexes.bySymbolPrefix.get(lowerQuery); + if (symbolPrefixMatches) { + symbolPrefixMatches.forEach((token) => results.add(token)); + } + + // Check name prefix matches + const namePrefixMatches = indexes.byNamePrefix.get(lowerQuery); + if (namePrefixMatches) { + namePrefixMatches.forEach((token) => results.add(token)); + } + + // Check address matches + if (lowerQuery.length >= 2) { + // Only search addresses for queries >= 2 chars + for (const [address, token] of indexes.byAddress.entries()) { + if (address.includes(lowerQuery)) { + results.add(token); + } + } + } + + return Array.from(results).slice(0, page * pageSize); + }, [tokens, debouncedQuery, indexes, page, pageSize]); + + const loadMore = () => { + setPage((prev) => prev + 1); + }; + + const hasMore = filteredTokens.length === page * pageSize; + + return { + tokens: filteredTokens, + loading, + error, + query, + setQuery, + loadMore, + hasMore, + }; +} diff --git a/frontend/src/page/CreateInvoice.jsx b/frontend/src/page/CreateInvoice.jsx index 5013cc8f..9c846b0b 100644 --- a/frontend/src/page/CreateInvoice.jsx +++ b/frontend/src/page/CreateInvoice.jsx @@ -17,8 +17,10 @@ import { } from "@/components/ui/popover"; import { Calendar } from "@/components/ui/calendar"; import { + Badge, CalendarIcon, CheckCircle2, + Coins, Loader2, PlusIcon, XCircle, @@ -37,18 +39,12 @@ import { LitAccessControlConditionResource, } from "@lit-protocol/auth-helpers"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; -import { TOKEN_PRESETS } from "@/utils/erc20_token"; import TokenIntegrationRequest from "@/components/TokenIntegrationRequest"; import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; import WalletConnectionAlert from "../components/WalletConnectionAlert"; +import TokenPicker, { ToggleSwitch } from "@/components/TokenPicker"; +import { CopyButton } from "@/components/ui/copyButton"; function CreateInvoice() { const { data: walletClient } = useWalletClient(); @@ -64,27 +60,15 @@ function CreateInvoice() { const [clientAddress, setClientAddress] = useState(""); // Token selection state - const [selectedToken, setSelectedToken] = useState(TOKEN_PRESETS[0]); + const [selectedToken, setSelectedToken] = useState(null); const [customTokenAddress, setCustomTokenAddress] = useState(""); const [useCustomToken, setUseCustomToken] = useState(false); - const [searchTerm, setSearchTerm] = useState(""); - const inputRef = useRef(null); + const [tokenVerificationState, setTokenVerificationState] = useState("idle"); const [verifiedToken, setVerifiedToken] = useState(null); const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); - const filteredTokens = TOKEN_PRESETS.filter( - (token) => - token.name.toLowerCase().includes(searchTerm.toLowerCase()) || - token.symbol.toLowerCase().includes(searchTerm.toLowerCase()) - ); - const POPULAR_TOKENS = [ - "0x0000000000000000000000000000000000000000", // ETH - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC - "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT - "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI - ]; const TESTNET_TOKEN = ["0xB5E9C6e57C9d312937A059089B547d0036c155C7"]; //sepolia based chainvoice test token (CIN) const [itemData, setItemData] = useState([ @@ -602,7 +586,7 @@ function CreateInvoice() { placeholder="Client Wallet Address" className="w-full mb-4 border-gray-300 text-black" name="clientAddress" - value={clientAddress} + value={clientAddress} onChange={(e) => setClientAddress(e.target.value)} /> @@ -686,7 +670,7 @@ function CreateInvoice() {
-

+

+ {/* Toggle Switch */} + +
- - setSearchTerm(e.target.value)} - /> -
- {!searchTerm && ( - <> -
-
- Popular -
- {TOKEN_PRESETS.filter((token) => - POPULAR_TOKENS.includes(token.address) - ).map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))} + {!useCustomToken ? ( + <> + + { + setSelectedToken({ + address: token.contract_address, + symbol: token.symbol, + name: token.name, + logo: token.image, + decimals: 18, + }); + }} + chainId={account?.chainId || 1} + disabled={loading} + className="w-full" + allowCustom={false} // Remove custom token option from picker since we have toggle + /> + + ) : ( + <> + + + {/* Custom Token Instructions */} +
+
+
+
-
-
- Testnet Token -
- {TOKEN_PRESETS.filter((token) => - TESTNET_TOKEN.includes(token.address) - ).map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))} +
+

+ Custom Token Setup +

+

+ Enter the contract address of the ERC-20 token you + want to use for payments. +

+
    +
  • + • Make sure the token contract is deployed and + verified +
  • +
  • + • Address should start with "0x" followed by 40 + characters +
  • +
  • + • Token will be verified automatically after + entering +
  • +
- - )} -
-
- {searchTerm ? "Search Results" : "All Tokens"} -
-
- {filteredTokens - .filter( - (token) => - !POPULAR_TOKENS.includes(token.address) && - !TESTNET_TOKEN.includes(token.address) - ) - .map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))}
- {filteredTokens.length === 0 && ( -
- No tokens found -
- )}
- -
-
- -
- - Custom Token + { + const address = e.target.value; + setCustomTokenAddress(address); + if (!address || !ethers.isAddress(address)) { + setTokenVerificationState("idle"); + setVerifiedToken(null); + } else if (ethers.isAddress(address)) { + verifyToken(address); + } + }} + className="h-12 bg-gray-50 text-gray-700 border-gray-200" + disabled={loading} + /> + + {tokenVerificationState === "verifying" && ( +
+ + + Verifying token contract...
- - - -
+ )} - {!useCustomToken && ( -
- -
-
-
- {selectedToken.name} -
-
- - {selectedToken.name} - - - {selectedToken.symbol} - -
- {selectedToken.address} + {tokenVerificationState === "success" && + verifiedToken && ( +
+
+
+ +
+
+

+ {verifiedToken.name} ( + {verifiedToken.symbol}) +

+ + Verified ✓ + +
+
+ + {verifiedToken.address} + + +
+

+ Decimals: {String(verifiedToken.decimals)} • + Contract verified and ready to use +

+
+
+
+
-
-
-
-
- )} -
- - {useCustomToken && ( -
-
- - { - const address = e.target.value; - setCustomTokenAddress(address); - if (!address || !ethers.isAddress(address)) { - setTokenVerificationState("idle"); - setVerifiedToken(null); - } else if (ethers.isAddress(address)) { - verifyToken(address); - } - }} - className="h-10 bg-gray-50 text-gray-700 border-gray-200 focus:ring-2 focus:ring-blue-100" - disabled={loading} - /> -

- Enter a valid ERC-20 token contract address -

-
- - {tokenVerificationState === "verifying" && ( -
- - Verifying token... -
- )} + )} - {tokenVerificationState === "success" && verifiedToken && ( -
-
-
- -
-

- {verifiedToken.name} ({verifiedToken.symbol}) -

-

- {verifiedToken.address} -

-

-

Decimals: {String(verifiedToken.decimals)}

-

+ {tokenVerificationState === "error" && ( +
+
+ +
+

+ Token verification failed +

+

+ Please check the contract address and try again. + Make sure it's a valid ERC-20 token. +

+
-
- -
- )} - - {tokenVerificationState === "error" && ( -
-
- -

- Failed to verify token. Please check the address. -

-
-
+ )} + )}
- )} +

@@ -1004,23 +852,41 @@ function CreateInvoice() { verifiedToken ? ( <> Note:{" "} - Payments will be processed in {verifiedToken?.symbol}. - Ensure your client has sufficient balance of this token. + Your client will need to have sufficient balance of the + chosen token to be able to pay your invoice. + + ) : customTokenAddress ? ( + <> + Note:{" "} + Please wait for token verification to complete before + proceeding. ) : ( - "" + <> + Note:{" "} + Enter a valid ERC-20 token contract address above to + proceed. + ) + ) : selectedToken ? ( + <> + Note:{" "} + Your client will need to have sufficient balance of{" "} + {selectedToken.symbol} to be able to pay + your invoice. + ) : ( <> Note:{" "} - Payments will be processed in {selectedToken.symbol}. - Ensure your client has sufficient balance of this token. + Please select a payment token to continue with invoice + creation. )}

+ {/* Invoice Items Section */}
@@ -1145,8 +1011,8 @@ function CreateInvoice() { {totalAmountDue}{" "} {useCustomToken - ? verifiedToken?.symbol - : selectedToken.symbol} + ? verifiedToken?.symbol || "TOKEN" + : selectedToken?.symbol || "TOKEN"}
@@ -1170,7 +1036,6 @@ function CreateInvoice() { )}
-
diff --git a/frontend/src/page/GenerateLink.jsx b/frontend/src/page/GenerateLink.jsx index e4fbb238..474c1ec0 100644 --- a/frontend/src/page/GenerateLink.jsx +++ b/frontend/src/page/GenerateLink.jsx @@ -132,9 +132,7 @@ const GenerateLink = () => {

diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index e3892524..b81af6e4 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -1019,36 +1019,10 @@ function ReceivedInvoice() { Download Invoice

-
- Total Amount: - - {drawerState.selectedInvoice.paymentToken?.symbol === "ETH" - ? `${( - parseFloat(drawerState.selectedInvoice.amountDue) + - parseFloat(ethers.formatUnits(fee)) - ).toFixed(6)} ETH` - : `${drawerState.selectedInvoice.amountDue} ${ - drawerState.selectedInvoice.paymentToken?.symbol - } + ${ethers.formatUnits(fee)} ETH`} - -
+
-
- - -
+ )} diff --git a/frontend/src/utils/erc20_token.js b/frontend/src/utils/erc20_token.js deleted file mode 100644 index db81f84e..00000000 --- a/frontend/src/utils/erc20_token.js +++ /dev/null @@ -1,716 +0,0 @@ -// { -// name: "Chainvoice", -// symbol: "CIN", -// address: "0xB5E9C6e57C9d312937A059089B547d0036c155C7", -// decimals: 18, -// }, - -export const TOKEN_PRESETS = [ - { - name: "Chainvoice Testnet Token", - symbol: "CIN", - address: "0xB5E9C6e57C9d312937A059089B547d0036c155C7", - decimals: 18, - logo: "/tokenImages/cin.png", - }, - { - name: "Ethereum Mainnet", - symbol: "ETH", - address: "0x0000000000000000000000000000000000000000", - decimals: 18, - logo: "/tokenImages/eth.png", - }, - { - name: "Tether USD", - symbol: "USDT", - address: "0xdac17f958d2ee523a2206206994597c13d831ec7", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x636c00eefcf239bf56cc07dfc8ec2a0d26ecc765805f8d9e62c866b0164ccb62.png", - }, - { - name: "BNB", - symbol: "BNB", - address: "0xb8c77482e45f1f44de1745f52c74426c631bdd52", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x533cc6c47bad45528f615b1f66023b1f7c429d4304e2d1ddb84918077e8ead8b.png", - }, - { - name: "USD Coin", - symbol: "USDC", - address: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xda4a7aa3c2c6966c74c4a8446b6348c3e397491e14b2874719192fa5a4c71cab.png", - }, - { - name: "Liquid staked Ether 2.0", - symbol: "stETH", - address: "0xae7ab96520de3a18e5e111b5eaab095312d7fe84", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x4e05bd4f46441ab8812874ddb4a8e6ace80ab398e09c07086a93e60ff4b7f4bb.png", - }, - { - name: "Wrapped BTC", - symbol: "WBTC", - address: "0x2260fac5e5542a773aa44fbcfedf7c193bc2c599", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xbd2860a79e7bc89d86922c8a80cdeb9a32a0082045fb0da24c31e3871916e0d3.png", - }, - { - name: "Wrapped liquid staked Ether 2.0", - symbol: "wstETH", - address: "0x7f39c581f595b53c5cb19bd0b3f8da6c935e2ca0", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3454daef9545c526928d9a01030752ff1995f65f4ec044a0b127b16f913e4065.png", - }, - { - name: "ChainLink Token", - symbol: "LINK", - address: "0x514910771af9ca656af840dff83e8264ecf986ca", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x0d5bccf38448bc46b1a49419cfa43d1d569dbce172c214bacde2d2e22b005dc0.png", - }, - { - name: "Bitfinex LEO Token", - symbol: "LEO", - address: "0x2af5d2ad76741191d15dfe7bf6ac92d4bd912ca3", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x0f5926985ff75e8c8caaa95501ea575923c317bb847421d4d00dbf5964a1feea.png", - }, - { - name: "USDS Stablecoin", - symbol: "USDS", - address: "0xdc035d45d973e3ec169d2276ddab16f1e407384f", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xb547d1b9a9e8f112d440784ffaed3dbdc433a70820a359614664ec981752ae4e.png", - }, - { - name: "SHIBA INU", - symbol: "SHIB", - address: "0x95ad61b0a150d79219dcf64e1e6cc01f0b64c4ce", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x934042eb1c76a54280d40bebcc15138690c0b719eaaa4bd07529ce5d8dede7e6.png", - }, - { - name: "EtherFi wrapped ETH", - symbol: "weETH", - address: "0xcd5fe23c85820f7b72d0926fc9b05b43e359b7ee", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x8a7377a984da522f6099b1a7d8314a6fb527d07cfd637ababfbab4b94bfe693a.png", - }, - { - name: "Wrapped TON Coin", - symbol: "TONCOIN", - address: "0x582d872a1b094fc48f5de31d3b73f2d9be47def1", - decimal: "9", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x5b41e3f6b7eaa6d3ae160a0d3525678c720b1948ca6734dd4d998f2d6310ff1b.png", - }, - { - name: "Wrapped Ether", - symbol: "WETH", - address: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x7f71adae0bcc9186e966ba6fe0be5dfd303f6ccba90512991f91babf1f333625.png", - }, - { - name: "WBT", - symbol: "WBT", - address: "0x925206b8a707096ed26ae47c84747fe0bb734f59", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xdeea9fdad65441cbb8ed321961502ca3f60ce0e6939d6f3761a6c1d4989a61ec.png", - }, - { - name: "Coinbase Wrapped BTC", - symbol: "cbBTC", - address: "0xcbb7c0000ab88b473b1f5afd9ef808440eed33bf", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x12601a638d95d1bed9ee4d1973daad3d89925f85ea42a09951daceb9494645a6.png", - }, - { - name: "USDe", - symbol: "USDe", - address: "0x4c9edd5852cd905f086c759e8383e09bff1e68b3", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x26c646b32ea6353a192f6c760b97beb706772d0e28099f504520f70a31e3a466.png", - }, - { - name: "BitgetToken", - symbol: "BGB", - address: "0x19de6b897ed14a376dda0fe53a5420d2ac828a28", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xef3652281dd019ae4dd24ec58d13d40c6d0115eb6ed972108808e361bd9db3e1.png", - }, - { - name: "Uniswap", - symbol: "UNI", - address: "0x1f9840a85d5af5bf1d1762f925bdaddc4201f984", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xc53061256eb8145b0a928ac83ee76b055f7c563ad686521367bd16dd20ec3bed.png", - }, - { - name: "Aave Token", - symbol: "AAVE", - address: "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xeab69f3d8298f4b4b826af873d42063dbb05350742033769837aa0be6fbb9b64.png", - }, - { - name: "Pepe", - symbol: "PEPE", - address: "0x6982508145454ce325ddbe47a25d4ec3d2311933", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x673e4e814b3ed3008059e214b80fba443dae310acc14e66f707360067b4424a9.png", - }, - { - name: "Dai Stablecoin", - symbol: "DAI", - address: "0x6b175474e89094c44da98b954eedeac495271d0f", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xea5be93d23c21846ac28d0096ce859aceaac4471a4492818d06c0c5a5e540644.png", - }, - { - name: "Staked USDe", - symbol: "sUSDe", - address: "0x9d39a5de30e57443bff2a8307a4256c8797a3497", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3abf76c30b11bc875bc5460a1f9b52e9d8a203f93c1983eb395afc5a8ba7027f.png", - }, - { - name: "CRO", - symbol: "CRO", - address: "0xa0b73e1ff0b80914ab6fe0444e65848c4c34450b", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x25095b588aae7d80740f215b6e69966926d3a8994f141e1593a5c3294f042297.png", - }, - { - name: "OKB", - symbol: "OKB", - address: "0x75231f58b43240c9718dd58b4967c5114342a86c", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x80ca2e5c6480ba7147551c1327ac2bfd1214607e90348784d37d84d724b8c5e5.png", - }, - { - name: "BlackRock USD Institutional Digital Liquidity Fund", - symbol: "BUIDL", - address: "0x7712c34205737192402172409a8f7ccef8aa2aec", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xe7568c777cc874c0f00c7528f1c03bc2d9c51ede922116ce57e0941c8b0100b8.png", - }, - { - name: "NEAR", - symbol: "NEAR", - address: "0x85f17cf997934a597031b2e18a9ab6ebd4b9f6a4", - decimal: "24", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xc75188cae4539f032ee7e1174ba4c7c9154e1dfbfb855c861f96efaab6ec7258.png", - }, - { - name: "Ondo", - symbol: "ONDO", - address: "0xfaba6f8e4a5e8ab82f62fe7c39859fa577269be3", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x1fb261c892aa9b241652d883258be36cc4b87a6f34674e7ea7dc334523b80121.png", - }, - { - name: "Savings USDS", - symbol: "sUSDS", - address: "0xa3931d71877c0e7a3148cb7eb4463524fec27fbd", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xd3a649f8632bc78ce570ce63e898c6b1b0e13082bdfd528f82d46d27b8c09114.png", - }, - { - name: "World Liberty Financial USD", - symbol: "USD1", - address: "0x8d0d000ee44948fc98c9b98a4fa4921476f08b0d", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xf53fefeddd8477c2879e714bfae64d77fd05a5099c91d3a88502f4b404b5a710.png", - }, - { - name: "Mantle", - symbol: "MNT", - address: "0x3c3a81e81dc49a522a592e7622a7e711c06bf354", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x22f5817a14887c89254d451c8f2fbcd605d6c9b922fa1897bd0b83f9b335a56b.png", - }, - { - name: "Fasttoken", - symbol: "FTN", - address: "0xaedf386b755465871ff874e3e37af5976e247064", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x53ff0289d0573bc4b047b0fd00e4d7a0b724533af5400f544009f4c503308b5e.png", - }, - { - name: "GateChainToken", - symbol: "GT", - address: "0xe66747a101bff2dba3697199dcce5b743b454759", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x28dee88c251fffa5e1c340e8ddd0689c0d1d0b5d225d47ef709a30aed05879c9.png", - }, - { - name: "Polygon Ecosystem Token", - symbol: "POL", - address: "0x455e53cbb86018ac2b8092fdcd39d8444affc3f6", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xf5dfa373e5ab92008519c13cddc2505a8679c49cf490ba06ba8365936a06701b.png", - }, - { - name: "Fetch", - symbol: "FET", - address: "0xaea46a60368a7bd060eec7df8cba43b7ef41ad85", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x86707a37ebedf7ec6fc725027f04d0c38cd720d0dc38795877093b97613afbd8.png", - }, - { - name: "ENA", - symbol: "ENA", - address: "0x57e114b691db790c35207b2e685d4a43181e6061", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x90dde53b21e0318435509c0e32190cf31da0971ba874f695b02107f992c9fdc5.png", - }, - { - name: "SKY Governance Token", - symbol: "SKY", - address: "0x56072c95faa701256059aa122697b133aded9279", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x1f6ec6ef067dc18c953e2e43c86923a53b9c9a966166502b3f3c4bb463eed240.png", - }, - { - name: "Render Token", - symbol: "RNDR", - address: "0x6de037ef9ad2725eb40118bb1702ebb27e4aeb24", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x6e69899378586303df5fcb70a6488e60828c68706fd1b83a8954853fe40222f2.png", - }, - { - name: "Arbitrum", - symbol: "ARB", - address: "0xb50721bcf8d664c30412cfbc6cf7a15145234ad1", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xa11a48d3dac41fa9f07bee709903fbf6405f7ce48c6e6dc797e532efeda337d9.png", - }, - { - name: "Lombard Staked Bitcoin", - symbol: "LBTC", - address: "0x8236a87084f8b84306f72007f36f2618a5634494", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3e5e975c15cd33f005ecaa28f7e23cddb1af50d3f85090fdf28e80c77e089a29.png", - }, - { - name: "Quant", - symbol: "QNT", - address: "0x4a220e6096b25eadb88358cb44068a3248254675", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xae8315d6bbe7c190cc699b64db3ecdbc0bce3db790732e7bb67718b58e3c0426.png", - }, - { - name: "Bonk", - symbol: "Bonk", - address: "0x1151cb3d861920e07a38e03eead12c32178567f6", - decimal: "5", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x35fcbf9794295309281d4c0cc759e240a586111e49cd6cdf0cb82499c37dacc0.png", - }, - { - name: "Worldcoin", - symbol: "WLD", - address: "0x163f8c2467924be0ae7b5347228cabf260318753", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xb444942fc451b3f471944b51d9be5ada722671cba9b9bce652f95ed1d01ef387.png", - }, - { - name: "USDtb", - symbol: "USDtb", - address: "0xc139190f447e929f090edeb554d95abb8b18ac1c", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x80db354588d993be46ec08fdadf0fe3954140e29d185b5c75217ac9556f06420.png", - }, - { - name: "First Digital USD", - symbol: "FDUSD", - address: "0xc5f0f7b66764f6ec8c8dff7ba683102295e16409", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3dba518552999dd3a3b923b18ce39deba017998d7862bffaaabf10f3ebb74470.png", - }, - { - name: "SPX6900", - symbol: "SPX", - address: "0xe0f63a424a4439cbe457d80e4f4b51ad25b2c56c", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x7c69f4632587621b26bd220686a2bd668f1fdf1937b28167d08b075e9d9da281.png", - }, - { - name: "rsETH", - symbol: "rsETH", - address: "0xa1290d69c65a6fe4df752f95823fae25cb99e5a7", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x4d7847e6cfdf7a7b5d1b9d6c7bfc63e067d14ff2dfc6aac731302dec0f16f5a0.png", - }, - { - name: "Rocket Pool ETH", - symbol: "rETH", - address: "0xae78736cd615f374d3085123a210448e74fc6393", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x9ae7abade50f86dc63e72cf2add44c92ab2d3950f42245cb76ffe7dc2e77cb6d.png", - }, - { - name: "Nexo", - symbol: "NEXO", - address: "0xb62132e35a6c13ee1ee0f84dc5d40bad8d815206", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xaf742d00a2e8d58b2c9341fd73f42cfc2f2e5ae39005533c2aa8d1eeed0a489d.png", - }, - { - name: "Injective Token", - symbol: "INJ", - address: "0xe28b3b32b6c345a34ff64674606124dd5aceca30", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x5aabea3f7de09d6b2a3e861cd97902ba0d341e8f5daa71c9e9ede234d56cf9a5.png", - }, - { - name: "mETH", - symbol: "mETH", - address: "0xd5f7838f5c461feff7fe49ea5ebaf7728bb0adfa", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x65a213ed5544f36d32261e2f1b45a2aa8ce18e674c788687bafbe1d7b303acac.png", - }, - { - name: "Staked ETH", - symbol: "osETH", - address: "0xf1c9acdc66974dfb6decb12aa385b9cd01190e38", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xfb9cdd2c50e3cf5ad5452714caaa98a8076975a9bd07d69068f6be235fe814aa.png", - }, - { - name: "Solv BTC", - symbol: "SolvBTC", - address: "0x7a56e1c57c7475ccf742a1832b028f0456652f97", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xaab141f44b3f0d76f3315866e19ef7631d74f58c22916af091896824b59e3496.png", - }, - { - name: "Tokenize Emblem", - symbol: "TKX", - address: "0x667102bd3413bfeaa3dffb48fa8288819e480a88", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xf7fed58cf6ffda940c013bbb37bfbf319c0c155f54a2a9073156735c0c55a3f0.png", - }, - { - name: "Virtual Protocol", - symbol: "VIRTUAL", - address: "0x44ff8620b8ca30902395a7bd3f2407e1a091bf73", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xab89b951d480f6a2548f6fd2abfe8f13ecc4189fd811cd44c7ff41fa8b5cb1c6.png", - }, - { - name: "Syrup USDC", - symbol: "syrupUSDC", - address: "0x80ac24aa929eaf5013f6436cda2a7ba190f5cc0b", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xb7f804c79a04a9b48473c29cdfc51e9780a52623c5672abb8a48d4056d17beb6.png", - }, - { - name: "Paxos Gold", - symbol: "PAXG", - address: "0x45804880de22913dafe09f4980848ece6ecbaf78", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xb95043411156e0cf0a718f9938a17c09a077d3bb7c2b23e87086388a50e0ad69.png", - }, - { - name: "PayPal USD", - symbol: "PYUSD", - address: "0x6c3ea9036406852006290770bedfcaba0e23a0e8", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x61d41978836bfe9cd6810f33bc4833cdde140fbfe1ddd5d5a0cf23d80d9e3c61.png", - }, - { - name: "FLOKI", - symbol: "FLOKI", - address: "0xcf0c122c6b73ff809c693db761e7baebe62b6a2e", - decimal: "9", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xef24c2a03a5e2659fe073768e7b266215411dee6bb50bfa6ae88e0b0b3ed5915.png", - }, - { - name: "Graph Token", - symbol: "GRT", - address: "0xc944e90c64b2c07662a292be6244bdf05cda44a7", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xbcf1ff7bcf39ea3fdc2944b48e335f8a016ca1390c2f9ab3375d592c2a7a284e.png", - }, - { - name: "clBTC", - symbol: "clBTC", - address: "0xe7ae30c03395d66f30a26c49c91edae151747911", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xf5890b52fc0e86a586337ccc8e49e4f5f512ab5c00778f5642b3de7cdcf66d4b.png", - }, - { - name: "Tether Gold", - symbol: "XAUt", - address: "0x68749665ff8d2d112fa859aa293f07a622782f38", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xeb1e86b05e387d0ba5081ba78b6d6e42043b36555e443a1293f0bc476afa124c.png", - }, - { - name: "Immutable X", - symbol: "IMX", - address: "0xf57e7e7c23978c3caec3c3548e3d615c346e79ff", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x582820f7558c1a76a1edf7e58df994e265f323bf4020d62d344093e9c176154b.png", - }, - { - name: "PancakeSwap Token", - symbol: "Cake", - address: "0x152649ea73beab28c5b49b26eb48f7ead6d4c898", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x11d48fd825024aa8cfa4d43cc6bd330b0e6a21d5e265a0fbd3d5bb255eb9e69d.png", - }, - { - name: "Liquid Staked ETH", - symbol: "LsETH", - address: "0x8c1bed5b9a0928467c9b1341da1d7bd5e10b6549", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x0d8f81393bb459317dac5773b83bb2d790b8a4567b58daf941ea06ff69a56fa9.png", - }, - { - name: "Curve DAO Token", - symbol: "CRV", - address: "0xd533a949740bb3306d119cc777fa900ba034cd52", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xc6a36535595fc2a85d6c076ec06df02d3cc28d2399b409c7a7ae58e04367d976.png", - }, - { - name: "Ondo Short-Term U.S. Government Bond Fund", - symbol: "OUSG", - address: "0x1b19c19393e2d034d8ff31ff34c81252fcbbee92", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x368c45405136c537b8cd38c87e5495d88399b388e15a8c05065b548b8de5c7a0.png", - }, - { - name: "Superstate Short Duration US Government Securities Fund", - symbol: "USTB", - address: "0x43415eb6ff9db7e26a15b704e7a3edce97d31c4e", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3a42a31b349dbcc4545784c9083717db6eb96117bd5e046e9c90f8a5121d3fb5.png", - }, - { - name: "USDX", - symbol: "USDX", - address: "0xf3527ef8de265eaa3716fb312c12847bfba66cef", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xaebcf05a1df94e754c2f21331f1b10056204bd810e2a015c2ec6e78ba2ba25e0.png", - }, - { - name: "Lido DAO Token", - symbol: "LDO", - address: "0x5a98fcbea516cf06857215779fd812ca3bef1b32", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xb003c5c36c3bc6ab6a04cf816bcadf7c69763f51e95169652e8dbd51c4bf09ef.png", - }, - { - name: "Gala", - symbol: "GALA", - address: "0xd1d2eb1b1e90b638588728b4130137d262c87cae", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x4031e94d1b2221342425981938d79c0824065b9e0ca604da7594c2ffeb66fe52.png", - }, - { - name: "Ethereum Name Service", - symbol: "ENS", - address: "0xc18360217d8f7ab5e7c516566761ea12ce7f9d72", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x8b1881f819ba5e5e8b986832b569d7c2e0ce1453572ce65d8d41709bec018d94.png", - }, - { - name: "Ondo U.S. Dollar Yield", - symbol: "USDY", - address: "0x96f6ef951840721adbf46ac996b59e0235cb985c", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xfd4029f0337c74afcab7b96cf7491132f4653636b3d7944acae9cc27ee23e776.png", - }, - { - name: "SAND", - symbol: "SAND", - address: "0x3845badade8e6dff049820680d1f14bd3903a5d0", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3d3aa642e221f1362236429e6c61d46559bb680fe5e30891e8495471a1c9cb6d.png", - }, - { - name: "BitTorrent", - symbol: "BTT", - address: "0xc669928185dbce49d2230cc9b0979be6dc797957", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xc4ba29a5dc7368f01ce67ed8ebc9019f0f67bde7cbfabe8ff6157da67aeb99e0.png", - }, - { - name: "JasmyCoin", - symbol: "JASMY", - address: "0x7420b4b9a0110cdc71fb720908340c03f9bc03ec", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xf02f7e6ab17a0563cb628067a587959fa6875b4c489a004ba078030583e19ad7.png", - }, - { - name: "Pendle", - symbol: "PENDLE", - address: "0x808507121b80c02388fad14726482e061b8da827", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x93bfdf82043b3dcfd0ecbde84ec9ec332b832168f479c17ceda39ee9429e0338.png", - }, - { - name: "Usual USD", - symbol: "USD0", - address: "0x73a15fed60bf67631dc6cd7bc5b6e8da8190acf5", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x0e1df7dd5a2d894870a16cd5ccaeb51a240c1f473761915219565a6a993857e5.png", - }, - { - name: "cmETH", - symbol: "cmETH", - address: "0xe6829d9a7ee3040e1276fa75293bde931859e8fa", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x2db801d03445f6a78a4eaa4c5f1ac075c66e89fb42f1312d454a7e759df774d5.png", - }, - { - name: "SolvBTC Babylon", - symbol: "SolvBTC.BBN", - address: "0xd9d920aa40f578ab794426f5c90f6c731d159def", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x07bd4c851a9dd9bf2731542a39e6986017f2dc41350a6ff4447d84e36655fe1a.png", - }, - { - name: "tBTC v2", - symbol: "tBTC", - address: "0x18084fba666a33d37592fa2633fd49a74dd93a88", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x374c55831315eece6acdf73dd0ed4342ca11ba7a43146a2814f75b3cd47f02b4.png", - }, - { - name: "cgETH Hashkey Cloud", - symbol: "cgETH.hashkey", - address: "0xc60a9145d9e9f1152218e7da6df634b7a74ae444", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x518d0de9c0610837259b1bafb438bec7e0d08d6d50472fd1f16fe150c9c686b9.png", - }, - { - name: "Syrup Token", - symbol: "SYRUP", - address: "0x643c4e15d7d62ad0abec4a9bd4b001aa3ef52d66", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x53a946528e8af04d1cf5aeb25bff5b60cb3d619a771a47ae8573de1c52fcff54.png", - }, - { - name: "Decentraland MANA", - symbol: "MANA", - address: "0x0f5d2fb29fb7d3cfee444a200298f468908cc942", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3de2fd9036d3330aa44d05bed8e7d42c78763c78b7e9c67ec9e57763cfe007ab.png", - }, - { - name: "", - symbol: "", - address: "0xcfd748b9de538c9f5b1805e8db9e1d4671f7f2ec", - decimal: "0", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x11e6eff08e149134d03f121f37913a360168286298c72c99aa2c7a6e78182205.png", - }, - { - name: "TrueUSD", - symbol: "TUSD", - address: "0x0000000000085d4780b73119b644ae5ecd22b376", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xfdee06ccdc1b94b47884cb37f623663824e23ad500f601cc50c7419b266b68d9.png", - }, - { - name: "ApeCoin", - symbol: "APE", - address: "0x4d224452801aced8b2f0aebe155379bb5d594381", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x97863e7e4f8a71de7dffecb21913a33f7af57a7170fbfeff05870f2acb645da2.png", - }, - { - name: "Chain", - symbol: "XCN", - address: "0xa2cd3d43c775978a96bdbf12d733d5a1ed94fb18", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x321ed57969e51fa8315f370d76414baa8fe2fb9b079ce3ca5bfaccead729fca5.png", - }, - { - name: "Morpho Token", - symbol: "MORPHO", - address: "0x9994e35db50125e0df82e4c2dde62496ce330999", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xe90be6230ef4ccee86e8b87c0ca8d24f8cf14bead8f1621ce3456135ffd3cc85.png", - }, - { - name: "Decentralized USD", - symbol: "USDD", - address: "0x0c10bf8fcb7bf5412187a595ab97a3609160b5c6", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xac24f1bff2cf09d84ce133faf5e5428994ad07efc23b65d8779c09dd8c0f7cff.png", - }, - { - name: "Dexe", - symbol: "DEXE", - address: "0xde4ee8057785a7e8e800db58f9784845a5c2cbd6", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x95ccb485493b37dbf2e6a86496688d00ef15614710b2841904dd1a9371fbfd66.png", - }, - { - name: "Mog Coin", - symbol: "Mog", - address: "0xaaee1a9723aadb7afa2810263653a34ba2c21c7a", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x2901a28b39db8fc38d87ca82a3a54efcfa312e737501dd4ca68dda7cf8fbd275.png", - }, - { - name: "APENFT", - symbol: "NFT", - address: "0x198d14f2ad9ce69e76ea330b374de4957c3f850a", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0xf48a34674e86da1610277474d53e92a21d74b5385215c84dc874c6098cd41f80.png", - }, - { - name: "Reserve Rights", - symbol: "RSR", - address: "0x320623b8e4ff03373931769a31fc52a4e78b5d70", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x79fb26c2f551cc14b32575f693298d3d30e2a87c242344cca29e527fa39a7786.png", - }, - { - name: "StarkNet Token", - symbol: "STRK", - address: "0xca14007eff0db1f8135f4c25b34de49ab0d42766", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x9a122beff8685e1b1ccd7a2b55b1f923b74eb7c37a77e58fca918937fe72c4ae.png", - }, - { - name: "ETHx", - symbol: "ETHx", - address: "0xa35b1b31ce002fbf2058d22f30f95d405200a15b", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x7ecb25bb3cb2e648b6d601ab906e5bc27a43709ff0818d471043828f7738b98f.png", - }, - { - name: "US Yield Coin", - symbol: "USYC", - address: "0x136471a34f6ef19fe571effc1ca711fdb8e49f2b", - decimal: "6", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x3fc57e0034e59dfc92e4a39bb7deb3238d876295a1a565b1985fb9b69ec261d2.png", - }, - { - name: "Compound", - symbol: "COMP", - address: "0xc00e94cb662c3520282e6f5717214004a7f26888", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x6bfc3cb11dcccb2a934dbad676e90523308cf1a9e07402f6850d6c1ee84d967b.png", - }, - { - name: "Movement", - symbol: "MOVE", - address: "0x3073f7aaa4db83f95e9fff17424f71d4751a3073", - decimal: "8", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x54afe78e77ea2aeac01a26c6e24da8a0d8829c4b11385df5e1b71db741c40bad.png", - }, - { - name: "ether.fi ETH", - symbol: "eETH", - address: "0x35fa164735182de50811e8e2e824cfb9b6118ac2", - decimal: "18", - logo: "https://market-data-images.s3.us-east-1.amazonaws.com/tokenImages/0x02eb37d0c375737c34d9057a6c89d563d7cb96ea30f155bcecea5040bf92a640.png", - }, -]; From 391783874a8ca848ca27ac2d11e79916d5527d1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Tue, 26 Aug 2025 19:47:33 +0530 Subject: [PATCH 36/39] token selector improvement --- frontend/src/components/TokenCrousel.jsx | 2 +- frontend/src/page/GenerateLink.jsx | 593 +++++++++++------------ frontend/src/page/ReceivedInvoice.jsx | 130 +++-- frontend/src/page/SentInvoice.jsx | 107 +++- 4 files changed, 476 insertions(+), 356 deletions(-) diff --git a/frontend/src/components/TokenCrousel.jsx b/frontend/src/components/TokenCrousel.jsx index ef11603f..d2e5aa53 100644 --- a/frontend/src/components/TokenCrousel.jsx +++ b/frontend/src/components/TokenCrousel.jsx @@ -1,7 +1,7 @@ import { useEffect, useRef } from "react"; import { motion } from "framer-motion"; import { SiEthereum } from "react-icons/si"; -import { TOKEN_PRESETS } from "@/utils/erc20_token"; +// import { TOKEN_PRESETS } from "@/utils/erc20_token"; const TokenCarousel = () => { const carouselRef = useRef(); diff --git a/frontend/src/page/GenerateLink.jsx b/frontend/src/page/GenerateLink.jsx index 474c1ec0..89d8fc12 100644 --- a/frontend/src/page/GenerateLink.jsx +++ b/frontend/src/page/GenerateLink.jsx @@ -1,5 +1,5 @@ import React, { useState, useEffect, useRef } from "react"; -import { useAccount, useWalletClient } from "wagmi"; +import { useAccount, useWalletClient } from "wagmi"; import { Copy, Link, @@ -9,54 +9,55 @@ import { Loader2, CheckCircle2, XCircle, -} from "lucide-react"; + Coins, +} from "lucide-react"; import { Button } from "../components/ui/button"; import { Input } from "../components/ui/input"; import { Label } from "../components/ui/label"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@/components/ui/select"; - -import { TOKEN_PRESETS } from "@/utils/erc20_token"; +import { cn } from "@/lib/utils"; + import WalletConnectionAlert from "@/components/WalletConnectionAlert"; import TokenIntegrationRequest from "@/components/TokenIntegrationRequest"; -import { BrowserProvider, ethers } from "ethers"; +import TokenPicker, { ToggleSwitch } from "@/components/TokenPicker"; +import { CopyButton } from "@/components/ui/copyButton"; +import { Badge } from "@/components/ui/badge"; +import { BrowserProvider, ethers } from "ethers"; import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; +import { useTokenList } from "../hooks/useTokenList"; // Import the hook instead of TOKEN_PRESETS + const GenerateLink = () => { - const { address, isConnected } = useAccount(); - const { data: walletClient } = useWalletClient(); + const { address, isConnected, chainId } = useAccount(); + const { data: walletClient } = useWalletClient(); const [copied, setCopied] = useState(false); const [amount, setAmount] = useState(""); const [description, setDescription] = useState(""); const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); + // Get tokens from the new hook + const { tokens } = useTokenList(chainId || 1); + // Token selection state - const [selectedToken, setSelectedToken] = useState(TOKEN_PRESETS[0]); + const [selectedToken, setSelectedToken] = useState(null); const [customTokenAddress, setCustomTokenAddress] = useState(""); const [useCustomToken, setUseCustomToken] = useState(false); - const [searchTerm, setSearchTerm] = useState(""); - const inputRef = useRef(null); const [tokenVerificationState, setTokenVerificationState] = useState("idle"); const [verifiedToken, setVerifiedToken] = useState(null); const [loading, setLoading] = useState(false); - const filteredTokens = TOKEN_PRESETS.filter( - (token) => - token.name.toLowerCase().includes(searchTerm.toLowerCase()) || - token.symbol.toLowerCase().includes(searchTerm.toLowerCase()) - ); - - const POPULAR_TOKENS = [ - "0x0000000000000000000000000000000000000000", // ETH - "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48", // USDC - "0xdac17f958d2ee523a2206206994597c13d831ec7", // USDT - "0x6b175474e89094c44da98b954eedeac495271d0f", // DAI - ]; + // Set default token when tokens are loaded + useEffect(() => { + if (tokens.length > 0 && !selectedToken && !useCustomToken) { + // Find ETH or use first token as default + const ethToken = tokens.find( + (token) => + token.symbol.toLowerCase() === "eth" || + token.contract_address === + "0x0000000000000000000000000000000000000000" + ); + setSelectedToken(ethToken || tokens[0]); + } + }, [tokens, selectedToken, useCustomToken]); const TESTNET_TOKEN = ["0xB5E9C6e57C9d312937A059089B547d0036c155C7"]; @@ -75,9 +76,9 @@ const GenerateLink = () => { const params = new URLSearchParams({ clientAddress: address || "", - tokenAddress: tokenToUse.address, - customToken: useCustomToken ? true : false, - chain: "1", + tokenAddress: tokenToUse.address || tokenToUse.contract_address, + customToken: useCustomToken ? "true" : "false", + chain: chainId?.toString() || "1", }); if (amount) { @@ -101,7 +102,7 @@ const GenerateLink = () => { }; const verifyToken = async (address) => { - if (!walletClient) return; + if (!walletClient) return; setTokenVerificationState("verifying"); @@ -121,6 +122,7 @@ const GenerateLink = () => { setTokenVerificationState("error"); } }; + return ( <>
@@ -131,9 +133,7 @@ const GenerateLink = () => { />
-
+

@@ -146,6 +146,7 @@ const GenerateLink = () => { details.

+
@@ -159,301 +160,271 @@ const GenerateLink = () => { />
+ {/* Optional Fields */} +
+
+ + setAmount(e.target.value)} + className="border-gray-300" + /> +
+
+ + setDescription(e.target.value)} + className="border-gray-300" + /> +
+
+ {/* Token Selection */} -
+
+

+ + + + + + + Payment Currency +

+
+ {/* Toggle Switch */} + { + setUseCustomToken(enabled); + if (enabled) { + setSelectedToken(null); + } else { + // Reset to first token when switching back + if (tokens.length > 0) { + const ethToken = tokens.find( + (token) => + token.symbol.toLowerCase() === "eth" || + token.contract_address === + "0x0000000000000000000000000000000000000000" + ); + setSelectedToken(ethToken || tokens[0]); + } + } + }} + leftLabel="Select Token" + rightLabel="Input Custom Token" + /> +
- - setSearchTerm(e.target.value)} - /> -
- {!searchTerm && ( - <> -
-
- Popular -
- {TOKEN_PRESETS.filter((token) => - POPULAR_TOKENS.includes(token.address) - ).map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))} + {!useCustomToken ? ( + <> + + { + setSelectedToken({ + address: token.contract_address, + symbol: token.symbol, + name: token.name, + logo: token.image, + decimals: 18, + }); + }} + chainId={chainId || 1} + disabled={loading} + className="w-full" + allowCustom={false} + /> + + ) : ( + <> + + + {/* Custom Token Instructions */} +
+
+
+
-
-
- Testnet Token -
- {TOKEN_PRESETS.filter((token) => - TESTNET_TOKEN.includes(token.address) - ).map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))} +
+

+ Custom Token Setup +

+

+ Enter the contract address of the ERC-20 token + you want to use for payments. +

+
    +
  • + • Make sure the token contract is deployed and + verified +
  • +
  • + • Address should start with "0x" followed by + 40 characters +
  • +
  • + • Token will be verified automatically after + entering +
  • +
- - )} -
-
- {searchTerm ? "Search Results" : "All Tokens"}
-
- {filteredTokens - .filter( - (token) => - !POPULAR_TOKENS.includes(token.address) && - !TESTNET_TOKEN.includes(token.address) - ) - .map((token) => ( - -
-
- {token.name} { - e.currentTarget.src = - "/tokenImages/generic.png"; - }} - /> -
-
- - {token.name} - - - {token.symbol} - -
-
-
- ))} -
- {filteredTokens.length === 0 && ( -
- No tokens found -
- )}
- -
-
- -
- - Custom Token - -
-
- - -
+ { + const address = e.target.value; + setCustomTokenAddress(address); + if (!address || !ethers.isAddress(address)) { + setTokenVerificationState("idle"); + setVerifiedToken(null); + } else if (ethers.isAddress(address)) { + verifyToken(address); + } + }} + className="h-12 bg-gray-50 text-gray-700 border-gray-200" + disabled={loading} + /> - {!useCustomToken && selectedToken && ( -
- -
-
-
- {selectedToken.name} -
-
- - {selectedToken.name} - - - {selectedToken.symbol} + {tokenVerificationState === "verifying" && ( +
+ + + Verifying token contract... -
- {selectedToken.address} -
-
-
-
- )} -
+ )} - {useCustomToken && ( -
-
- - { - const address = e.target.value; - setCustomTokenAddress(address); - if (!address || !ethers.isAddress(address)) { - setTokenVerificationState("idle"); - setVerifiedToken(null); - } else if (ethers.isAddress(address)) { - verifyToken(address); - } - }} - className="h-10 bg-gray-50 text-gray-700 border-gray-200 focus:ring-2 focus:ring-blue-100" - disabled={loading} - /> -

- Enter a valid ERC-20 token contract address -

-
- - {tokenVerificationState === "verifying" && ( -
- - Verifying token... -
- )} + {tokenVerificationState === "success" && + verifiedToken && ( +
+
+
+ +
+
+

+ {verifiedToken.name} ( + {verifiedToken.symbol}) +

+ + Verified ✓ + +
+
+ + {verifiedToken.address} + + +
+

+ Decimals: {String(verifiedToken.decimals)}{" "} + • Contract verified and ready to use +

+
+
+
+ +
+ )} - {tokenVerificationState === "success" && verifiedToken && ( -
-
-
- -
-

- {verifiedToken.name} ({verifiedToken.symbol}) -

-

- {verifiedToken.address} -

-

-

- Decimals: {String(verifiedToken.decimals)} + {tokenVerificationState === "error" && ( +

+
+ +
+

+ Token verification failed

-

+

+ Please check the contract address and try + again. Make sure it's a valid ERC-20 token. +

+
-
- -
+ )} + )} +
+
- {tokenVerificationState === "error" && ( -
-
- -

- Failed to verify token. Please check the address. -

-
-
+
+

+ {useCustomToken ? ( + verifiedToken ? ( + <> + + Note: + {" "} + Your client will need to have sufficient balance of{" "} + {verifiedToken.symbol} to be able to + pay your invoice. + + ) : customTokenAddress ? ( + <> + + Note: + {" "} + Please wait for token verification to complete before + proceeding. + + ) : ( + <> + + Note: + {" "} + Enter a valid ERC-20 token contract address above to + proceed. + + ) + ) : selectedToken ? ( + <> + Note:{" "} + Your client will need to have sufficient balance of{" "} + {selectedToken.symbol} to be able to + pay your invoice. + + ) : ( + <> + Note:{" "} + Please select a payment token to continue with invoice + creation. + )} -

- )} +

+
diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index b81af6e4..655854a8 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -40,7 +40,7 @@ import PaidIcon from "@mui/icons-material/CheckCircle"; import UnpaidIcon from "@mui/icons-material/Pending"; import DownloadIcon from "@mui/icons-material/Download"; import CurrencyExchangeIcon from "@mui/icons-material/CurrencyExchange"; -import { TOKEN_PRESETS } from "@/utils/erc20_token"; +import { useTokenList } from "@/hooks/useTokenList"; import WalletConnectionAlert from "@/components/WalletConnectionAlert"; const columns = [ @@ -56,7 +56,7 @@ function ReceivedInvoice() { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); const { data: walletClient } = useWalletClient(); - const { address, isConnected } = useAccount(); + const { address, isConnected, chainId } = useAccount(); const [loading, setLoading] = useState(true); const [receivedInvoices, setReceivedInvoice] = useState([]); const [fee, setFee] = useState(0); @@ -66,6 +66,10 @@ function ReceivedInvoice() { const [paymentLoading, setPaymentLoading] = useState({}); const [networkLoading, setNetworkLoading] = useState(false); const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); + + // Get tokens from the hook + const { tokens } = useTokenList(chainId || 1); + const handleChangePage = (event, newPage) => { setPage(newPage); }; @@ -75,6 +79,40 @@ function ReceivedInvoice() { setPage(0); }; + // Helper function to get token info + const getTokenInfo = (tokenAddress) => { + if (!tokens || tokens.length === 0) return null; + + return tokens.find( + (token) => + token.contract_address?.toLowerCase() === tokenAddress?.toLowerCase() || + token.address?.toLowerCase() === tokenAddress?.toLowerCase() + ); + }; + + // Helper function to get token logo + const getTokenLogo = (tokenAddress, fallbackLogo) => { + const tokenInfo = getTokenInfo(tokenAddress); + return ( + tokenInfo?.image || + tokenInfo?.logo || + fallbackLogo || + "/tokenImages/generic.png" + ); + }; + + // Helper function to get token decimals + const getTokenDecimals = (tokenAddress, fallbackDecimals = 18) => { + const tokenInfo = getTokenInfo(tokenAddress); + return tokenInfo?.decimals || fallbackDecimals; + }; + + // Helper function to get token symbol + const getTokenSymbol = (tokenAddress, fallbackSymbol = "TOKEN") => { + const tokenInfo = getTokenInfo(tokenAddress); + return tokenInfo?.symbol || fallbackSymbol; + }; + useEffect(() => { const initLit = async () => { try { @@ -96,6 +134,7 @@ function ReceivedInvoice() { }; initLit(); }, []); + useEffect(() => { setShowWalletAlert(!isConnected); }, [isConnected]); @@ -118,6 +157,7 @@ function ReceivedInvoice() { setLoading(false); return; } + const litNodeClient = litClientRef.current; if (!litNodeClient) { alert("Lit client not initialized"); @@ -140,15 +180,6 @@ function ReceivedInvoice() { return; } - // First check if user has any invoices -// if (!res || !Array.isArray(res) || res.length === 0) { -// setReceivedInvoice([]); -// const fee = await contract.fee(); -// setFee(fee); -// return; -// } - - const decryptedInvoices = []; for (const invoice of res) { @@ -168,6 +199,7 @@ function ReceivedInvoice() { console.warn(`Unauthorized access attempt for invoice ${id}`); continue; } + const ciphertext = atob(encryptedStringBase64); const accessControlConditions = [ { @@ -194,6 +226,7 @@ function ReceivedInvoice() { }, }, ]; + const sessionSigs = await litNodeClient.getSessionSigs({ chain: "ethereum", resourceAbilityRequests: [ @@ -219,6 +252,7 @@ function ReceivedInvoice() { return await generateAuthSig({ signer, toSign }); }, }); + const decryptedString = await decryptToString( { accessControlConditions, @@ -229,24 +263,60 @@ function ReceivedInvoice() { }, litNodeClient ); + const parsed = JSON.parse(decryptedString); parsed["id"] = id; parsed["isPaid"] = isPaid; parsed["isCancelled"] = isCancelled; - // Enhance with token details + // Enhance with token details using the new token fetching system if (parsed.paymentToken?.address) { - const tokenInfo = TOKEN_PRESETS.find( - (t) => - t.address.toLowerCase() === - parsed.paymentToken.address.toLowerCase() - ); + const tokenInfo = getTokenInfo(parsed.paymentToken.address); if (tokenInfo) { parsed.paymentToken = { ...parsed.paymentToken, - logo: tokenInfo.logo, - decimals: tokenInfo.decimals, + logo: tokenInfo.image || tokenInfo.logo, + decimals: tokenInfo.decimals || parsed.paymentToken.decimals, + name: tokenInfo.name || parsed.paymentToken.name, + symbol: tokenInfo.symbol || parsed.paymentToken.symbol, }; + } else { + // Fallback: try to fetch token info from blockchain if not in our list + try { + const tokenContract = new ethers.Contract( + parsed.paymentToken.address, + ERC20_ABI, + provider + ); + + const [symbol, name, decimals] = await Promise.all([ + tokenContract + .symbol() + .catch(() => parsed.paymentToken.symbol || "UNKNOWN"), + tokenContract + .name() + .catch(() => parsed.paymentToken.name || "Unknown Token"), + tokenContract + .decimals() + .catch(() => parsed.paymentToken.decimals || 18), + ]); + + parsed.paymentToken = { + ...parsed.paymentToken, + symbol, + name, + decimals: Number(decimals), + logo: "/tokenImages/generic.png", // Generic fallback + }; + } catch (error) { + console.error( + "Failed to fetch token info from blockchain:", + error + ); + // Keep existing data or set defaults + parsed.paymentToken.logo = + parsed.paymentToken.logo || "/tokenImages/generic.png"; + } } } @@ -261,13 +331,14 @@ function ReceivedInvoice() { setFee(fee); } catch (error) { console.error("Fetch error:", error); + setError("Failed to fetch invoices. Please try again."); } finally { setLoading(false); } }; fetchReceivedInvoices(); - }, [walletClient, litReady, address]); + }, [walletClient, litReady, address, tokens]); const payInvoice = async (invoiceId, amountDue, tokenAddress) => { if (!walletClient) { @@ -296,10 +367,8 @@ function ReceivedInvoice() { throw new Error(`Invalid token address: ${tokenAddress}`); } - const tokenInfo = TOKEN_PRESETS.find( - (t) => t.address.toLowerCase() === tokenAddress.toLowerCase() - ); - const tokenSymbol = tokenInfo?.symbol || "Token"; + // Use the helper function instead of TOKEN_PRESETS + const tokenSymbol = getTokenSymbol(tokenAddress, "Token"); if (!isNativeToken) { const tokenContract = new Contract(tokenAddress, ERC20_ABI, signer); @@ -589,6 +658,9 @@ function ReceivedInvoice() { src={invoice.paymentToken.logo} alt={invoice.paymentToken.symbol} className="w-5 h-5 mr-2" + onError={(e) => { + e.target.src = "/tokenImages/generic.png"; + }} /> ) : ( {/* Date Column */} - {formatDate(invoice.issueDate)} @@ -758,7 +829,6 @@ function ReceivedInvoice() { Powered by Chainvoice

-

INVOICE @@ -880,6 +950,9 @@ function ReceivedInvoice() { src={drawerState.selectedInvoice.paymentToken.logo} alt={drawerState.selectedInvoice.paymentToken.symbol} className="w-6 h-6 mr-2" + onError={(e) => { + e.target.src = "/tokenImages/generic.png"; + }} /> ) : (
@@ -973,7 +1046,6 @@ function ReceivedInvoice() {
-
Subtotal: @@ -1003,7 +1075,6 @@ function ReceivedInvoice() {
-
-

- - )} diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index 46ed070b..3ba10d76 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -23,6 +23,7 @@ import { LitAccessControlConditionResource, } from "@lit-protocol/auth-helpers"; import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; +import { toast } from "react-toastify"; import { CircularProgress, Skeleton, @@ -44,7 +45,7 @@ import UnpaidIcon from "@mui/icons-material/Pending"; import DownloadIcon from "@mui/icons-material/Download"; import CancelIcon from "@mui/icons-material/Cancel"; import CurrencyExchangeIcon from "@mui/icons-material/CurrencyExchange"; -import { TOKEN_PRESETS } from "@/utils/erc20_token"; +import { useTokenList } from "@/hooks/useTokenList"; // Import the hook instead of TOKEN_PRESETS import WalletConnectionAlert from "@/components/WalletConnectionAlert"; const columns = [ @@ -60,7 +61,7 @@ function SentInvoice() { const [page, setPage] = useState(0); const [rowsPerPage, setRowsPerPage] = useState(10); const { data: walletClient } = useWalletClient(); - const { address, isConnected } = useAccount(); + const { address, isConnected, chainId } = useAccount(); const [loading, setLoading] = useState(true); const [sentInvoices, setSentInvoices] = useState([]); @@ -73,14 +74,47 @@ function SentInvoice() { const [cancelConfirmOpen, setCancelConfirmOpen] = useState(false); const [invoiceToCancel, setInvoiceToCancel] = useState(null); const [showWalletAlert, setShowWalletAlert] = useState(!isConnected); - + + // Get tokens from the hook + const { tokens } = useTokenList(chainId || 1); + + // Helper function to get token info + const getTokenInfo = (tokenAddress) => { + if (!tokens || tokens.length === 0) return null; + + return tokens.find( + (token) => + token.contract_address?.toLowerCase() === tokenAddress?.toLowerCase() || + token.address?.toLowerCase() === tokenAddress?.toLowerCase() + ); + }; + + // Helper function to get token logo + const getTokenLogo = (tokenAddress, fallbackLogo) => { + const tokenInfo = getTokenInfo(tokenAddress); + return ( + tokenInfo?.image || + tokenInfo?.logo || + fallbackLogo || + "/tokenImages/generic.png" + ); + }; + + // Helper function to get token decimals + const getTokenDecimals = (tokenAddress, fallbackDecimals = 18) => { + const tokenInfo = getTokenInfo(tokenAddress); + return tokenInfo?.decimals || fallbackDecimals; + }; + const handleChangePage = (event, newPage) => { setPage(newPage); }; + const handleChangeRowsPerPage = (event) => { setRowsPerPage(+event.target.value); setPage(0); }; + useEffect(() => { const initLit = async () => { try { @@ -102,6 +136,7 @@ function SentInvoice() { }; initLit(); }, []); + useEffect(() => { setShowWalletAlert(!isConnected); }, [isConnected]); @@ -124,7 +159,6 @@ function SentInvoice() { setLoading(false); return; } - // 2. Connect to Lit Node const litNodeClient = litClientRef.current; if (!litNodeClient) { @@ -132,7 +166,6 @@ function SentInvoice() { return; } - // 3. Contract call to get encrypted invoice const contract = new Contract( import.meta.env.VITE_CONTRACT_ADDRESS, ChainvoiceABI, @@ -168,6 +201,7 @@ function SentInvoice() { console.warn(`Unauthorized access attempt for invoice ${id}`); continue; } + const ciphertext = atob(encryptedStringBase64); const accessControlConditions = [ { @@ -236,20 +270,58 @@ function SentInvoice() { parsed["id"] = id; parsed["isPaid"] = isPaid; parsed["isCancelled"] = isCancelled; + + // Enhance with token details using the new token fetching system if (parsed.paymentToken?.address) { - const tokenInfo = TOKEN_PRESETS.find( - (t) => - t.address.toLowerCase() === - parsed.paymentToken.address.toLowerCase() - ); + const tokenInfo = getTokenInfo(parsed.paymentToken.address); if (tokenInfo) { parsed.paymentToken = { ...parsed.paymentToken, - logo: tokenInfo.logo, - decimals: tokenInfo.decimals, + logo: tokenInfo.image || tokenInfo.logo, + decimals: tokenInfo.decimals || parsed.paymentToken.decimals, + name: tokenInfo.name || parsed.paymentToken.name, + symbol: tokenInfo.symbol || parsed.paymentToken.symbol, }; + } else { + // Fallback: try to fetch token info from blockchain if not in our list + try { + const tokenContract = new ethers.Contract( + parsed.paymentToken.address, + ERC20_ABI, + provider + ); + + const [symbol, name, decimals] = await Promise.all([ + tokenContract + .symbol() + .catch(() => parsed.paymentToken.symbol || "UNKNOWN"), + tokenContract + .name() + .catch(() => parsed.paymentToken.name || "Unknown Token"), + tokenContract + .decimals() + .catch(() => parsed.paymentToken.decimals || 18), + ]); + + parsed.paymentToken = { + ...parsed.paymentToken, + symbol, + name, + decimals: Number(decimals), + logo: "/tokenImages/generic.png", // Generic fallback + }; + } catch (error) { + console.error( + "Failed to fetch token info from blockchain:", + error + ); + // Keep existing data or set defaults + parsed.paymentToken.logo = + parsed.paymentToken.logo || "/tokenImages/generic.png"; + } } } + decryptedInvoices.push(parsed); } catch (err) { console.error(`Error processing invoice ${invoice[0]}:`, err); @@ -261,6 +333,7 @@ function SentInvoice() { setFee(fee); } catch (error) { console.error("Decryption error:", error); + setError("Failed to fetch invoices. Please try again."); } finally { console.log(sentInvoices); setLoading(false); @@ -268,7 +341,7 @@ function SentInvoice() { }; fetchSentInvoices(); - }, [walletClient, litReady, address]); + }, [walletClient, litReady, address, tokens]); // Added tokens to dependency array const [drawerState, setDrawerState] = useState({ open: false, @@ -301,6 +374,7 @@ function SentInvoice() { link.href = data; link.click(); }; + const handleCancelInvoice = async (invoiceId) => { try { setPaymentLoading((prev) => ({ ...prev, [invoiceId]: true })); @@ -328,6 +402,7 @@ function SentInvoice() { setPaymentLoading((prev) => ({ ...prev, [invoiceId]: false })); } }; + const switchNetwork = async () => { try { setNetworkLoading(true); @@ -517,6 +592,9 @@ function SentInvoice() { src={invoice.paymentToken.logo} alt={invoice.paymentToken.symbol} className="w-5 h-5 mr-2 rounded-full" + onError={(e) => { + e.target.src = "/tokenImages/generic.png"; + }} /> ) : ( { + e.target.src = "/tokenImages/generic.png"; + }} /> ) : (
From 10ef8784557b1e8ec12c0a0c1a6a0864bab1f7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Tue, 26 Aug 2025 20:11:12 +0530 Subject: [PATCH 37/39] token selector improvement --- frontend/src/page/GenerateLink.jsx | 36 ++++----------------------- frontend/src/page/ReceivedInvoice.jsx | 1 - frontend/src/page/SentInvoice.jsx | 2 +- 3 files changed, 6 insertions(+), 33 deletions(-) diff --git a/frontend/src/page/GenerateLink.jsx b/frontend/src/page/GenerateLink.jsx index 89d8fc12..22b8ff1f 100644 --- a/frontend/src/page/GenerateLink.jsx +++ b/frontend/src/page/GenerateLink.jsx @@ -23,7 +23,7 @@ import { CopyButton } from "@/components/ui/copyButton"; import { Badge } from "@/components/ui/badge"; import { BrowserProvider, ethers } from "ethers"; import { ERC20_ABI } from "@/contractsABI/ERC20_ABI"; -import { useTokenList } from "../hooks/useTokenList"; // Import the hook instead of TOKEN_PRESETS +import { useTokenList } from "../hooks/useTokenList"; const GenerateLink = () => { @@ -160,32 +160,6 @@ const GenerateLink = () => { />
- {/* Optional Fields */} -
-
- - setAmount(e.target.value)} - className="border-gray-300" - /> -
-
- - setDescription(e.target.value)} - className="border-gray-300" - /> -
-
- {/* Token Selection */}

@@ -388,9 +362,9 @@ const GenerateLink = () => { Note: {" "} - Your client will need to have sufficient balance of{" "} + You need to have sufficient balance of{" "} {verifiedToken.symbol} to be able to - pay your invoice. + pay invoice. ) : customTokenAddress ? ( <> @@ -412,9 +386,9 @@ const GenerateLink = () => { ) : selectedToken ? ( <> Note:{" "} - Your client will need to have sufficient balance of{" "} + You need to have sufficient balance of{" "} {selectedToken.symbol} to be able to - pay your invoice. + pay invoice. ) : ( <> diff --git a/frontend/src/page/ReceivedInvoice.jsx b/frontend/src/page/ReceivedInvoice.jsx index 655854a8..1d61b217 100644 --- a/frontend/src/page/ReceivedInvoice.jsx +++ b/frontend/src/page/ReceivedInvoice.jsx @@ -367,7 +367,6 @@ function ReceivedInvoice() { throw new Error(`Invalid token address: ${tokenAddress}`); } - // Use the helper function instead of TOKEN_PRESETS const tokenSymbol = getTokenSymbol(tokenAddress, "Token"); if (!isNativeToken) { diff --git a/frontend/src/page/SentInvoice.jsx b/frontend/src/page/SentInvoice.jsx index 3ba10d76..bbe2351d 100644 --- a/frontend/src/page/SentInvoice.jsx +++ b/frontend/src/page/SentInvoice.jsx @@ -45,7 +45,7 @@ import UnpaidIcon from "@mui/icons-material/Pending"; import DownloadIcon from "@mui/icons-material/Download"; import CancelIcon from "@mui/icons-material/Cancel"; import CurrencyExchangeIcon from "@mui/icons-material/CurrencyExchange"; -import { useTokenList } from "@/hooks/useTokenList"; // Import the hook instead of TOKEN_PRESETS +import { useTokenList } from "@/hooks/useTokenList"; import WalletConnectionAlert from "@/components/WalletConnectionAlert"; const columns = [ From 86109c058b0faccc7fcff175cdabc3a4d00bdb36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Mon, 1 Sep 2025 18:20:41 +0530 Subject: [PATCH 38/39] Update README with stability standard --- README.md | 233 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 160 insertions(+), 73 deletions(-) diff --git a/README.md b/README.md index c0df07d3..eacdd1b2 100644 --- a/README.md +++ b/README.md @@ -1,90 +1,177 @@ + +
+ + +

+ Stability Nexus +      + Chainvoice +

+ +

+ Stability Nexus · Chainvoice +

+ +

+ Telegram +   + X (Twitter) +   + Discord +   + Medium +   + LinkedIn +   + YouTube +

+ +--- + # Chainvoice -Chainvoice is a decentralized invoice management system built on blockchain technology. It offers a secure, transparent, and tamper-proof platform for creating, managing, and paying invoices. Leveraging Ethereum smart contracts, Chainvoice ensures trust, automation, and eliminates the need for intermediaries in financial transactions. +Chainvoice is a decentralized invoicing platform that enables secure, transparent, and tamper‑proof invoice creation, management, and payments on blockchain. Powered by Ethereum-compatible smart contracts, Chainvoice automates payment flows and reduces reliance on intermediaries. + +## Table of Contents + +- [Overview](#overview) +- [Features](#features) +- [Project Structure](#project-structure) +- [Getting Started](#getting-started) +- [Frontend Setup](#frontend-setup) +- [Smart Contract Testing](#smart-contract-testing) +- [Deploy to Ethereum Classic](#deploy-to-ethereum-classic) +- [Environment Variables](#environment-variables) +- [Community and Support](#community-and-support) +- [License](#license) + +## Overview + +Chainvoice transforms traditional invoicing by leveraging blockchain technology to create a trustless, automated payment system. Users can create invoices, manage payments, and track transaction history with complete transparency and security. + +## Features + +- **Decentralized Invoice Creation** - Create and manage invoices on-chain +- **Multi-Token Support** - Pay using native currency or ERC-20 tokens +- **Immutable Records** - Verifiable transaction history and status tracking +- **Treasury Management** - Built-in fee management for platform sustainability +- **Privacy Protection** - Encrypted invoice data with access control +- **User-Friendly Interface** - Intuitive web application with wallet integration + +## Project Structure + +Chainvoice/ +├── frontend/ # Web application (UI/UX, wallet integration) +├── contracts/ # Solidity smart contracts (core invoicing logic) +├── docs/ # Documentation and guides +└── README.md # This file ## Getting Started -1. Fork the Repository -2. Clone the forked repository to your local machine: -```bash -git clone https://github.com/yourusername/Chainvoice.git -``` -3. Project Structure -The repository contains two main folders: +1. **Fork the repository** +2. **Clone your fork** -- frontend: The user interface of the application. -- contracts: The smart contracts powering the backend logic. +git clone https://github.com/yourusername/Chainvoice.git +cd Chainvoice ## Frontend Setup -1. Navigate to the frontend folder: -```bash +1. **Navigate to frontend directory** cd frontend -``` -2. Install the dependencies:: -```bash + +2. **Install dependencies** npm install -``` -3. Start the development server:: -```bash + + +3. **Start development server** npm run dev -``` -4. Open the app in your browser at http://localhost:5173. + + +4. **Open application** +Navigate to `http://localhost:5173` in your browser ## Smart Contract Testing -1. Navigate to the contracts folder: -```bash +> **Prerequisites:** [Foundry](https://getfoundry.sh/) must be installed + +1. **Navigate to contracts directory** cd contracts -``` -2. Install dependencies using Foundry: -```bash -forge install -``` -3. Run tests -```bash + +3. **Run test suite** 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. + +4. **Run tests with verbosity (optional)** +forge test -vvv + + +## Deploy to Ethereum Classic + +### Prerequisites +- [Foundry](https://getfoundry.sh/) installed +- Wallet funded with ETC +- ETC RPC URL (e.g., Rivet, Ankr, Chainstack) + +### Deployment Steps + +1. **Configure environment variables** +cp contracts/.env.example contracts/.env +Edit contracts/.env with your actual values + +2. **Compile contracts** +cd contracts +forge build + +3. **Load environment variables** +source .env + +4. **Deploy to Ethereum Classic** +forge create contracts/src/Chainvoice.sol:Chainvoice +--rpc-url $ETC_RPC_URL +--private-key $PRIVATE_KEY +--broadcast + + +5. **Configure frontend** +cp frontend/.env.example frontend/.env +Edit frontend/.env and set: +VITE_CONTRACT_ADDRESS=your_deployed_contract_address_here + +6. **Restart frontend development server** +cd frontend +npm run dev + +## Environment Variables + +### Frontend Configuration (`frontend/.env`) +VITE_CONTRACT_ADDRESS=your_deployed_contract_address_here +VITE_CHAIN_ID=61 +VITE_RPC_URL=your_etc_rpc_url_here + +### Contracts Configuration (`contracts/.env`) +PRIVATE_KEY=your_private_key_here +ETC_RPC_URL=your_etc_rpc_endpoint_here +ETHERSCAN_API_KEY=your_etherscan_api_key_here + +> ⚠️ **Security Note:** Never commit `.env` files to version control. Keep your private keys secure. + +## Community and Support + +Join our community for support, updates, and discussions: + +- **Telegram** - [t.me/StabilityNexus](https://t.me/StabilityNexus) +- **Discord** - [discord.gg/YzDKeEfWtS](https://discord.gg/YzDKeEfWtS) +- **X (Twitter)** - [@StabilityNexus](https://x.com/StabilityNexus) +- **Medium** - [news.stability.nexus](https://news.stability.nexus) +- **LinkedIn** - [linkedin.com/company/stability-nexus](https://linkedin.com/company/stability-nexus) +- **YouTube** - [youtube.com/@StabilityNexus](https://www.youtube.com/@StabilityNexus) + +## Contributing + +We welcome contributions! Please read our contributing guidelines and submit pull requests for any improvements. + + +

+ Built with ❤️ by Stability Nexus +

+ +

(back to top)

From 7754f2b4fe17a3acc5b80a9578044d850ed6a527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=F0=9F=85=9A=F0=9F=85=90=F0=9F=85=A1=F0=9F=85=90?= =?UTF-8?q?=F0=9F=85=9D?= Date: Mon, 1 Sep 2025 18:21:46 +0530 Subject: [PATCH 39/39] Update README with stability standard --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index eacdd1b2..d0a84829 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,6 @@ Chainvoice is a decentralized invoicing platform that enables secure, transparen - [Deploy to Ethereum Classic](#deploy-to-ethereum-classic) - [Environment Variables](#environment-variables) - [Community and Support](#community-and-support) -- [License](#license) ## Overview