From 2c96c8172a703ba356f7695ba5d57678d4fa4f1c Mon Sep 17 00:00:00 2001 From: ItsYoosuf Date: Fri, 3 May 2024 17:12:38 +0530 Subject: [PATCH 1/2] Initial commit --- contracts/Escrow.sol | 126 +++++++++++++++++++- contracts/RealEstate.sol | 25 +++- hardhat.config.js | 6 + package-lock.json | 170 ++++++++++++++++++++++++++- package.json | 5 +- public/index.html | 2 +- scripts/deploy.js | 51 +++++++- server/db.js | 18 +++ src/App.js | 61 ++++++++-- src/components/Admin.js | 119 +++++++++++++++++++ src/components/Home.js | 217 ++++++++++++++++++++++++++++++++++- src/components/Listing.js | 38 ++++++ src/components/Navigation.js | 40 ++++++- test/Escrow.js | 156 ++++++++++++++++++++++++- 14 files changed, 1004 insertions(+), 30 deletions(-) create mode 100644 server/db.js create mode 100644 src/components/Admin.js create mode 100644 src/components/Listing.js diff --git a/contracts/Escrow.sol b/contracts/Escrow.sol index d6990b4..88bd8db 100644 --- a/contracts/Escrow.sol +++ b/contracts/Escrow.sol @@ -1,4 +1,4 @@ -//SPDX-License-Identifier: Unlicense +// SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; interface IERC721 { @@ -10,5 +10,129 @@ interface IERC721 { } contract Escrow { + address public nftAddress; + address payable public seller; + address public inspector; + address public lender; + // Define events for logging transactions + event Purchase(uint256 indexed nftID, address indexed buyer, uint256 purchasePrice); + event InspectionStatusUpdate(uint256 indexed nftID, bool passed); + event SaleApproval(uint256 indexed nftID, address indexed approver); + event SaleFinalized(uint256 indexed nftID, address indexed buyer, address indexed seller, uint256 purchasePrice); + event SaleCancelled(uint256 indexed nftID, address indexed canceller); + + modifier onlyBuyer(uint256 _nftID) { + require(msg.sender == buyer[_nftID], "Only buyer can call this method"); + _; + } + + modifier onlySeller() { + require(msg.sender == seller, "Only seller can call this method"); + _; + } + + modifier onlyInspector() { + require(msg.sender == inspector, "Only inspector can call this method"); + _; + } + + mapping(uint256 => bool) public isListed; + mapping(uint256 => uint256) public purchasePrice; + mapping(uint256 => uint256) public escrowAmount; + mapping(uint256 => address) public buyer; + mapping(uint256 => bool) public inspectionPassed; + mapping(uint256 => mapping(address => bool)) public approval; + + constructor( + address _nftAddress, + address payable _seller, + address _inspector, + address _lender + ) { + nftAddress = _nftAddress; + seller = _seller; + inspector = _inspector; + lender = _lender; + } + + function list( + uint256 _nftID, + address _buyer, + uint256 _purchasePrice, + uint256 _escrowAmount + ) public payable onlySeller { + // Transfer NFT from seller to this contract + IERC721(nftAddress).transferFrom(msg.sender, address(this), _nftID); + + isListed[_nftID] = true; + purchasePrice[_nftID] = _purchasePrice; + escrowAmount[_nftID] = _escrowAmount; + buyer[_nftID] = _buyer; + } + + // Put Under Contract (only buyer - payable escrow) + function depositEarnest(uint256 _nftID) public payable onlyBuyer(_nftID) { + require(msg.value >= escrowAmount[_nftID]); + // Emit event for purchase + emit Purchase(_nftID, msg.sender, msg.value); + } + + // Update Inspection Status (only inspector) + function updateInspectionStatus(uint256 _nftID, bool _passed) + public + onlyInspector + { + inspectionPassed[_nftID] = _passed; + // Emit event for inspection status update + emit InspectionStatusUpdate(_nftID, _passed); + } + + // Approve Sale + function approveSale(uint256 _nftID) public { + approval[_nftID][msg.sender] = true; + // Emit event for sale approval + emit SaleApproval(_nftID, msg.sender); + } + + // Finalize Sale + // -> Require inspection status (add more items here, like appraisal) + // -> Require sale to be authorized + // -> Require funds to be correct amount + // -> Transfer NFT to buyer + // -> Transfer Funds to Seller + function finalizeSale(uint256 _nftID) public { + require(inspectionPassed[_nftID]); + require(approval[_nftID][buyer[_nftID]]); + require(approval[_nftID][seller]); + require(approval[_nftID][lender]); + require(address(this).balance >= purchasePrice[_nftID]); + + isListed[_nftID] = false; + + (bool success, ) = payable(seller).call{value: address(this).balance}(""); + require(success); + + IERC721(nftAddress).transferFrom(address(this), buyer[_nftID], _nftID); + // Emit event for sale finalization + emit SaleFinalized(_nftID, buyer[_nftID], seller, purchasePrice[_nftID]); + } + + // Cancel Sale (handle earnest deposit) + // -> if inspection status is not approved, then refund, otherwise send to seller + function cancelSale(uint256 _nftID) public { + if (inspectionPassed[_nftID] == false) { + payable(buyer[_nftID]).transfer(address(this).balance); + } else { + payable(seller).transfer(address(this).balance); + } + // Emit event for sale cancellation + emit SaleCancelled(_nftID, msg.sender); + } + + receive() external payable {} + + function getBalance() public view returns (uint256) { + return address(this).balance; + } } diff --git a/contracts/RealEstate.sol b/contracts/RealEstate.sol index 882434b..ccda268 100644 --- a/contracts/RealEstate.sol +++ b/contracts/RealEstate.sol @@ -1,6 +1,27 @@ //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; -contract RealEstate { +import "@openzeppelin/contracts/utils/Counters.sol"; +import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; +import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; -} +contract RealEstate is ERC721URIStorage { + using Counters for Counters.Counter; + Counters.Counter private _tokenIds; + + constructor() ERC721("Real Estate", "REAL") {} + + function mint(string memory tokenURI) public returns (uint256) { + _tokenIds.increment(); + + uint256 newItemId = _tokenIds.current(); + _mint(msg.sender, newItemId); + _setTokenURI(newItemId, tokenURI); + + return newItemId; + } + + function totalSupply() public view returns (uint256) { + return _tokenIds.current(); + } +} \ No newline at end of file diff --git a/hardhat.config.js b/hardhat.config.js index 86913e7..b04b5c7 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -3,4 +3,10 @@ require("@nomicfoundation/hardhat-toolbox"); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: "0.8.17", + networks: { + // Ganache local network configuration + ganache: { + url: "http://localhost:7545", // Default Ganache RPC server URL + }, + }, }; diff --git a/package-lock.json b/package-lock.json index dbd284d..73ddec7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,11 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.8", + "bootstrap": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -5073,6 +5076,24 @@ "node": ">=8" } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@remix-run/router": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -7374,6 +7395,29 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -7891,6 +7935,24 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, + "node_modules/bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], + "peerDependencies": { + "@popperjs/core": "^2.11.8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -11992,9 +12054,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -20476,6 +20538,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -20824,6 +20891,36 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "dependencies": { + "@remix-run/router": "1.15.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "dependencies": { + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -29285,6 +29382,17 @@ } } }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "peer": true + }, + "@remix-run/router": { + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", + "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==" + }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -31040,6 +31148,28 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz", "integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==" }, + "axios": { + "version": "1.6.8", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", + "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -31451,6 +31581,12 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, + "bootstrap": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", + "requires": {} + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -34585,9 +34721,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "forever-agent": { "version": "0.6.1", @@ -40682,6 +40818,11 @@ } } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -40921,6 +41062,23 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, + "react-router": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", + "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", + "requires": { + "@remix-run/router": "1.15.3" + } + }, + "react-router-dom": { + "version": "6.22.3", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", + "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", + "requires": { + "@remix-run/router": "1.15.3", + "react-router": "6.22.3" + } + }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/package.json b/package.json index 80f2900..c9b6b2f 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,11 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.8", + "bootstrap": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -40,4 +43,4 @@ "@openzeppelin/contracts": "^4.7.3", "hardhat": "^2.12.0" } -} \ No newline at end of file +} diff --git a/public/index.html b/public/index.html index 59922f6..8d00247 100644 --- a/public/index.html +++ b/public/index.html @@ -22,7 +22,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - Millow + dApp diff --git a/scripts/deploy.js b/scripts/deploy.js index a4abac7..75c8dba 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -6,8 +6,57 @@ // global scope, and execute the script. const hre = require("hardhat"); +const tokens = (n) => { + return ethers.utils.parseUnits(n.toString(), 'ether') +} + async function main() { + // Setup accounts + const [buyer, seller, inspector, lender] = await ethers.getSigners() + + // Deploy Real Estate + const RealEstate = await ethers.getContractFactory('RealEstate') + const realEstate = await RealEstate.deploy() + await realEstate.deployed() + + console.log(`Deployed Real Estate Contract at: ${realEstate.address}`) + console.log(`Minting 3 properties...\n`) + + for (let i = 0; i < 3; i++) { + const transaction = await realEstate.connect(seller).mint(`https://ipfs.io/ipfs/QmQVcpsjrA6cr1iJjZAodYwmPekYgbnXGo4DFubJiLc2EB/${i + 1}.json`) + await transaction.wait() + } + + // Deploy Escrow + const Escrow = await ethers.getContractFactory('Escrow') + const escrow = await Escrow.deploy( + realEstate.address, + seller.address, + inspector.address, + lender.address + ) + await escrow.deployed() + + console.log(`Deployed Escrow Contract at: ${escrow.address}`) + console.log(`Listing 3 properties...\n`) + + for (let i = 0; i < 3; i++) { + // Approve properties... + let transaction = await realEstate.connect(seller).approve(escrow.address, i + 1) + await transaction.wait() + } + + // Listing properties... + transaction = await escrow.connect(seller).list(1, buyer.address, tokens(20), tokens(10)) + await transaction.wait() + + transaction = await escrow.connect(seller).list(2, buyer.address, tokens(15), tokens(5)) + await transaction.wait() + + transaction = await escrow.connect(seller).list(3, buyer.address, tokens(10), tokens(5)) + await transaction.wait() + console.log(`Finished.`) } // We recommend this pattern to be able to use async/await everywhere @@ -15,4 +64,4 @@ async function main() { main().catch((error) => { console.error(error); process.exitCode = 1; -}); +}); \ No newline at end of file diff --git a/server/db.js b/server/db.js new file mode 100644 index 0000000..052c7a5 --- /dev/null +++ b/server/db.js @@ -0,0 +1,18 @@ +const mongoose = require('mongoose'); + +const connectDB = async () => { + try { + await mongoose.connect('mongodb://localhost:27017/your_database_name', { + useNewUrlParser: true, + useUnifiedTopology: true, + useCreateIndex: true, + useFindAndModify: false + }); + console.log('MongoDB connected'); + } catch (error) { + console.error('MongoDB connection failed:', error); + process.exit(1); + } +}; + +module.exports = connectDB; diff --git a/src/App.js b/src/App.js index 5f7af4a..c3f169d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,30 +1,67 @@ -import { useEffect, useState } from 'react'; +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import { useState, useEffect } from 'react'; import { ethers } from 'ethers'; // Components import Navigation from './components/Navigation'; import Search from './components/Search'; -import Home from './components/Home'; - +import Listing from './components/Listing'; +import Admin from './components/Admin'; // ABIs -import RealEstate from './abis/RealEstate.json' -import Escrow from './abis/Escrow.json' - +import RealEstate from './abis/RealEstate.json'; +import Escrow from './abis/Escrow.json'; // Config import config from './config.json'; function App() { + const [provider, setProvider] = useState(null); + const [escrow, setEscrow] = useState(null); + const [account, setAccount] = useState(null); + const [homes, setHomes] = useState([]); - return ( -
+ const loadBlockchainData = async () => { + const provider = new ethers.providers.Web3Provider(window.ethereum); + setProvider(provider); + const network = await provider.getNetwork(); -
+ const realEstate = new ethers.Contract(config[network.chainId].realEstate.address, RealEstate, provider); + const totalSupply = await realEstate.totalSupply(); + const homes = []; -

Welcome to Millow

+ for (let i = 1; i <= totalSupply; i++) { + const uri = await realEstate.tokenURI(i); + const response = await fetch(uri); + const metadata = await response.json(); + homes.push(metadata); + } -
+ setHomes(homes); + + const escrow = new ethers.Contract(config[network.chainId].escrow.address, Escrow, provider); + setEscrow(escrow); -
+ window.ethereum.on('accountsChanged', async () => { + const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); + const account = ethers.utils.getAddress(accounts[0]); + setAccount(account); + }); + }; + + useEffect(() => { + loadBlockchainData(); + }, []); + + return ( + +
+ + + + } /> + } /> + +
+
); } diff --git a/src/components/Admin.js b/src/components/Admin.js new file mode 100644 index 0000000..ba8da44 --- /dev/null +++ b/src/components/Admin.js @@ -0,0 +1,119 @@ +import React, { useState } from 'react'; + +// CSS styles as template literals with Facebook-like styling +const styles = ` + .card { + border-radius: 8px; + box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); + margin: 20px; + padding: 20px; + background-color: #fff; + max-width: 400px; + width: 100%; + text-align: center; + } + + .card-header { + background-color: #4267B2; /* Facebook blue color */ + color: #fff; /* White text */ + padding: 20px; + border-top-left-radius: 8px; + border-top-right-radius: 8px; + font-size: 24px; + font-weight: bold; + } + + .card-body { + padding: 20px; + text-align: left; + } + + .form-label { + color: #000; /* Black text */ + font-size: 16px; + margin-bottom: 8px; + } + + .form-control { + border: 1px solid #ddd; /* Light gray border */ + border-radius: 4px; + padding: 10px; + width: 100%; + font-size: 16px; + margin-bottom: 20px; + } + + .btn-primary { + background-color: #4267B2; /* Facebook blue color */ + border-color: #4267B2; /* Facebook blue border color */ + color: #fff; /* White text */ + padding: 12px 20px; + font-size: 16px; + border-radius: 4px; + width: 100%; + cursor: pointer; + } + + .btn-primary:hover { + background-color: #385898; /* Darker blue color on hover */ + border-color: #385898; /* Darker blue border color on hover */ + } + + .alert-danger { + color: #721c24; /* Dark red color */ + background-color: #f8d7da; /* Light red background color */ + border-color: #f5c6cb; /* Light red border color */ + padding: 10px; + border-radius: 4px; + margin-bottom: 20px; + } +`; + +function Admin() { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + + const handleLogin = () => { + // Validate username and password (you can add more complex validation logic here) + if (username === 'admin' && password === 'admin123') { + // Clear any previous error message + setError(''); + + // Perform login action (for example, navigate to dashboard) + console.log('Login successful! Redirecting to dashboard...'); + } else { + // Display error message + setError('Invalid username or password. Please try again.'); + } + }; + + return ( +
+ {/* Embed CSS styles directly */} +
+
+
+
Admin Log in
+
+
+
+ + setUsername(e.target.value)} /> +
+
+ + setPassword(e.target.value)} /> +
+ {error &&
{error}
} + +
+
+
+
+
+
+ ); +} + +export default Admin; diff --git a/src/components/Home.js b/src/components/Home.js index fa82527..432ed24 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -1,14 +1,223 @@ import { ethers } from 'ethers'; import { useEffect, useState } from 'react'; - +import axios from 'axios'; import close from '../assets/close.svg'; -const Home = ({ home, provider, escrow, togglePop }) => { +const Home = ({ home, provider, account, escrow, togglePop }) => { + const [events, setEvents] = useState([]); + const [hasBought, setHasBought] = useState(false) + const [hasLended, setHasLended] = useState(false) + const [hasInspected, setHasInspected] = useState(false) + const [hasSold, setHasSold] = useState(false) + + const [buyer, setBuyer] = useState(null) + const [lender, setLender] = useState(null) + const [inspector, setInspector] = useState(null) + const [seller, setSeller] = useState(null) + + const [owner, setOwner] = useState(null) + + const fetchDetails = async () => { + // -- Buyer + + const buyer = await escrow.buyer(home.id) + setBuyer(buyer) + + const hasBought = await escrow.approval(home.id, buyer) + setHasBought(hasBought) + + // -- Seller + + const seller = await escrow.seller() + setSeller(seller) + + const hasSold = await escrow.approval(home.id, seller) + setHasSold(hasSold) + + // -- Lender + + const lender = await escrow.lender() + setLender(lender) + + const hasLended = await escrow.approval(home.id, lender) + setHasLended(hasLended) + + // -- Inspector + + const inspector = await escrow.inspector() + setInspector(inspector) + + const hasInspected = await escrow.inspectionPassed(home.id) + setHasInspected(hasInspected) + } + + const fetchOwner = async () => { + if (await escrow.isListed(home.id)) return + + const owner = await escrow.buyer(home.id) + setOwner(owner) + } + + const buyHandler = async () => { + const escrowAmount = await escrow.escrowAmount(home.id) + const signer = await provider.getSigner() + + // Buyer deposit earnest + let transaction = await escrow.connect(signer).depositEarnest(home.id, { value: escrowAmount }) + await transaction.wait() + + // Buyer approves... + transaction = await escrow.connect(signer).approveSale(home.id) + await transaction.wait() + + setHasBought(true) + } + + const inspectHandler = async () => { + const signer = await provider.getSigner() + + // Inspector updates status + const transaction = await escrow.connect(signer).updateInspectionStatus(home.id, true) + await transaction.wait() + + setHasInspected(true) + } + + const lendHandler = async () => { + const signer = await provider.getSigner() + + // Lender approves... + const transaction = await escrow.connect(signer).approveSale(home.id) + await transaction.wait() + + // Lender sends funds to contract... + const lendAmount = (await escrow.purchasePrice(home.id) - await escrow.escrowAmount(home.id)) + await signer.sendTransaction({ to: escrow.address, value: lendAmount.toString(), gasLimit: 60000 }) + + setHasLended(true) + } + + const sellHandler = async () => { + const signer = await provider.getSigner() + + // Seller approves... + let transaction = await escrow.connect(signer).approveSale(home.id) + await transaction.wait() + + // Seller finalize... + transaction = await escrow.connect(signer).finalizeSale(home.id) + await transaction.wait() + + setHasSold(true) + } + useEffect(() => { + const fetchEvents = async () => { + // Subscribe to Escrow contract events + const filter = escrow.filters.Purchase(); + escrow.on(filter, async (nftID, buyer, purchasePrice, event) => { + // Send event data to backend server + await sendEventToBackend(event); + }); + + // Cleanup function to unsubscribe from events + return () => { + escrow.off(filter); + }; + }; + + fetchEvents(); + }, []); + + const sendEventToBackend = async (event) => { + try { + // Send event data to backend server + await axios.post('/api/events', event); + } catch (error) { + console.error('Error sending event to backend:', error); + } + }; + + let transaction; // Declare the transaction variable + + useEffect(() => { + fetchDetails() + console.log(transaction); + fetchOwner() + }, [hasSold]) return (
-
+
+
+ Home +
+
+

{home.name}

+

+ {home.attributes[2].value} bds | + {home.attributes[3].value} ba | + {home.attributes[4].value} sqft +

+

{home.address}

+ +

{home.attributes[0].value} ETH

+ + {owner ? ( +
+ Owned by {owner.slice(0, 6) + '...' + owner.slice(38, 42)} +
+ ) : ( +
+ {(account === inspector) ? ( + + ) : (account === lender) ? ( + + ) : (account === seller) ? ( + + ) : ( + + )} + + +
+ )} + +
+ +

Overview

+ +

+ {home.description} +

+ +
+ +

Facts and features

+ +
    + {home.attributes.map((attribute, index) => ( +
  • {attribute.trait_type} : {attribute.value}
  • + ))} +
+
+ + + +
+ ); } -export default Home; +export default Home; \ No newline at end of file diff --git a/src/components/Listing.js b/src/components/Listing.js new file mode 100644 index 0000000..bf4ce28 --- /dev/null +++ b/src/components/Listing.js @@ -0,0 +1,38 @@ +import { useState } from 'react'; +import Home from './Home'; + +function Listing({ homes, provider, account, escrow }) { + const [toggle, setToggle] = useState(false); + const [home, setHome] = useState({}); + + const togglePop = (home) => { + setHome(home); + setToggle((prevToggle) => !prevToggle); + }; + + return ( +
+

Homes For You

+
+
+ {homes.map((home, index) => ( +
togglePop(home)}> +
+ Home +
+
+

{home.attributes[0].value} ETH

+

+ {home.attributes[2].value} bds | {home.attributes[3].value} ba | {home.attributes[4].value} sqft +

+

{home.address}

+
+
+ ))} +
+ {toggle && } +
+ ); +} + +export default Listing; diff --git a/src/components/Navigation.js b/src/components/Navigation.js index 92cd5c8..b315ff0 100644 --- a/src/components/Navigation.js +++ b/src/components/Navigation.js @@ -1,7 +1,45 @@ +import { ethers } from 'ethers'; import logo from '../assets/logo.svg'; const Navigation = ({ account, setAccount }) => { + const connectHandler = async () => { + const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); + const account = ethers.utils.getAddress(accounts[0]) + setAccount(account); + } + return ( + + ); } -export default Navigation; +export default Navigation; \ No newline at end of file diff --git a/test/Escrow.js b/test/Escrow.js index 9b84e1a..013d703 100644 --- a/test/Escrow.js +++ b/test/Escrow.js @@ -6,5 +6,159 @@ const tokens = (n) => { } describe('Escrow', () => { + let buyer, seller, inspector, lender + let realEstate, escrow -}) + beforeEach(async () => { + // Setup accounts + [buyer, seller, inspector, lender] = await ethers.getSigners() + + // Deploy Real Estate + const RealEstate = await ethers.getContractFactory('RealEstate') + realEstate = await RealEstate.deploy() + + // Mint + let transaction = await realEstate.connect(seller).mint("https://ipfs.io/ipfs/QmTudSYeM7mz3PkYEWXWqPjomRPHogcMFSq7XAvsvsgAPS") + await transaction.wait() + + // Deploy Escrow + const Escrow = await ethers.getContractFactory('Escrow') + escrow = await Escrow.deploy( + realEstate.address, + seller.address, + inspector.address, + lender.address + ) + + // Approve Property + transaction = await realEstate.connect(seller).approve(escrow.address, 1) + await transaction.wait() + + // List Property + transaction = await escrow.connect(seller).list(1, buyer.address, tokens(10), tokens(5)) + await transaction.wait() + }) + + describe('Deployment', () => { + it('Returns NFT address', async () => { + const result = await escrow.nftAddress() + expect(result).to.be.equal(realEstate.address) + }) + + it('Returns seller', async () => { + const result = await escrow.seller() + expect(result).to.be.equal(seller.address) + }) + + it('Returns inspector', async () => { + const result = await escrow.inspector() + expect(result).to.be.equal(inspector.address) + }) + + it('Returns lender', async () => { + const result = await escrow.lender() + expect(result).to.be.equal(lender.address) + }) + }) + + describe('Listing', () => { + it('Updates as listed', async () => { + const result = await escrow.isListed(1) + expect(result).to.be.equal(true) + }) + + it('Returns buyer', async () => { + const result = await escrow.buyer(1) + expect(result).to.be.equal(buyer.address) + }) + + it('Returns purchase price', async () => { + const result = await escrow.purchasePrice(1) + expect(result).to.be.equal(tokens(10)) + }) + + it('Returns escrow amount', async () => { + const result = await escrow.escrowAmount(1) + expect(result).to.be.equal(tokens(5)) + }) + + it('Updates ownership', async () => { + expect(await realEstate.ownerOf(1)).to.be.equal(escrow.address) + }) + }) + + describe('Deposits', () => { + beforeEach(async () => { + const transaction = await escrow.connect(buyer).depositEarnest(1, { value: tokens(5) }) + await transaction.wait() + }) + + it('Updates contract balance', async () => { + const result = await escrow.getBalance() + expect(result).to.be.equal(tokens(5)) + }) + }) + + describe('Inspection', () => { + beforeEach(async () => { + const transaction = await escrow.connect(inspector).updateInspectionStatus(1, true) + await transaction.wait() + }) + + it('Updates inspection status', async () => { + const result = await escrow.inspectionPassed(1) + expect(result).to.be.equal(true) + }) + }) + + describe('Approval', () => { + beforeEach(async () => { + let transaction = await escrow.connect(buyer).approveSale(1) + await transaction.wait() + + transaction = await escrow.connect(seller).approveSale(1) + await transaction.wait() + + transaction = await escrow.connect(lender).approveSale(1) + await transaction.wait() + }) + + it('Updates approval status', async () => { + expect(await escrow.approval(1, buyer.address)).to.be.equal(true) + expect(await escrow.approval(1, seller.address)).to.be.equal(true) + expect(await escrow.approval(1, lender.address)).to.be.equal(true) + }) + }) + + describe('Sale', () => { + beforeEach(async () => { + let transaction = await escrow.connect(buyer).depositEarnest(1, { value: tokens(5) }) + await transaction.wait() + + transaction = await escrow.connect(inspector).updateInspectionStatus(1, true) + await transaction.wait() + + transaction = await escrow.connect(buyer).approveSale(1) + await transaction.wait() + + transaction = await escrow.connect(seller).approveSale(1) + await transaction.wait() + + transaction = await escrow.connect(lender).approveSale(1) + await transaction.wait() + + await lender.sendTransaction({ to: escrow.address, value: tokens(5) }) + + transaction = await escrow.connect(seller).finalizeSale(1) + await transaction.wait() + }) + + it('Updates ownership', async () => { + expect(await realEstate.ownerOf(1)).to.be.equal(buyer.address) + }) + + it('Updates balance', async () => { + expect(await escrow.getBalance()).to.be.equal(0) + }) + }) +}) \ No newline at end of file From 1a8fe176b7a42acaa9af529083ce646ce94fce36 Mon Sep 17 00:00:00 2001 From: Yoosuf Mohammed Shaheed <77971881+ItsYoosuf@users.noreply.github.com> Date: Wed, 22 May 2024 18:06:24 +0530 Subject: [PATCH 2/2] Initial commit change --- contracts/Escrow.sol | 126 +------------------- contracts/RealEstate.sol | 25 +--- hardhat.config.js | 6 - package-lock.json | 170 +-------------------------- package.json | 5 +- public/index.html | 2 +- scripts/deploy.js | 51 +------- server/db.js | 18 --- src/App.js | 61 ++-------- src/components/Admin.js | 119 ------------------- src/components/Home.js | 217 +---------------------------------- src/components/Listing.js | 38 ------ src/components/Navigation.js | 40 +------ test/Escrow.js | 156 +------------------------ 14 files changed, 30 insertions(+), 1004 deletions(-) delete mode 100644 server/db.js delete mode 100644 src/components/Admin.js delete mode 100644 src/components/Listing.js diff --git a/contracts/Escrow.sol b/contracts/Escrow.sol index 88bd8db..d6990b4 100644 --- a/contracts/Escrow.sol +++ b/contracts/Escrow.sol @@ -1,4 +1,4 @@ -// SPDX-License-Identifier: Unlicense +//SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; interface IERC721 { @@ -10,129 +10,5 @@ interface IERC721 { } contract Escrow { - address public nftAddress; - address payable public seller; - address public inspector; - address public lender; - // Define events for logging transactions - event Purchase(uint256 indexed nftID, address indexed buyer, uint256 purchasePrice); - event InspectionStatusUpdate(uint256 indexed nftID, bool passed); - event SaleApproval(uint256 indexed nftID, address indexed approver); - event SaleFinalized(uint256 indexed nftID, address indexed buyer, address indexed seller, uint256 purchasePrice); - event SaleCancelled(uint256 indexed nftID, address indexed canceller); - - modifier onlyBuyer(uint256 _nftID) { - require(msg.sender == buyer[_nftID], "Only buyer can call this method"); - _; - } - - modifier onlySeller() { - require(msg.sender == seller, "Only seller can call this method"); - _; - } - - modifier onlyInspector() { - require(msg.sender == inspector, "Only inspector can call this method"); - _; - } - - mapping(uint256 => bool) public isListed; - mapping(uint256 => uint256) public purchasePrice; - mapping(uint256 => uint256) public escrowAmount; - mapping(uint256 => address) public buyer; - mapping(uint256 => bool) public inspectionPassed; - mapping(uint256 => mapping(address => bool)) public approval; - - constructor( - address _nftAddress, - address payable _seller, - address _inspector, - address _lender - ) { - nftAddress = _nftAddress; - seller = _seller; - inspector = _inspector; - lender = _lender; - } - - function list( - uint256 _nftID, - address _buyer, - uint256 _purchasePrice, - uint256 _escrowAmount - ) public payable onlySeller { - // Transfer NFT from seller to this contract - IERC721(nftAddress).transferFrom(msg.sender, address(this), _nftID); - - isListed[_nftID] = true; - purchasePrice[_nftID] = _purchasePrice; - escrowAmount[_nftID] = _escrowAmount; - buyer[_nftID] = _buyer; - } - - // Put Under Contract (only buyer - payable escrow) - function depositEarnest(uint256 _nftID) public payable onlyBuyer(_nftID) { - require(msg.value >= escrowAmount[_nftID]); - // Emit event for purchase - emit Purchase(_nftID, msg.sender, msg.value); - } - - // Update Inspection Status (only inspector) - function updateInspectionStatus(uint256 _nftID, bool _passed) - public - onlyInspector - { - inspectionPassed[_nftID] = _passed; - // Emit event for inspection status update - emit InspectionStatusUpdate(_nftID, _passed); - } - - // Approve Sale - function approveSale(uint256 _nftID) public { - approval[_nftID][msg.sender] = true; - // Emit event for sale approval - emit SaleApproval(_nftID, msg.sender); - } - - // Finalize Sale - // -> Require inspection status (add more items here, like appraisal) - // -> Require sale to be authorized - // -> Require funds to be correct amount - // -> Transfer NFT to buyer - // -> Transfer Funds to Seller - function finalizeSale(uint256 _nftID) public { - require(inspectionPassed[_nftID]); - require(approval[_nftID][buyer[_nftID]]); - require(approval[_nftID][seller]); - require(approval[_nftID][lender]); - require(address(this).balance >= purchasePrice[_nftID]); - - isListed[_nftID] = false; - - (bool success, ) = payable(seller).call{value: address(this).balance}(""); - require(success); - - IERC721(nftAddress).transferFrom(address(this), buyer[_nftID], _nftID); - // Emit event for sale finalization - emit SaleFinalized(_nftID, buyer[_nftID], seller, purchasePrice[_nftID]); - } - - // Cancel Sale (handle earnest deposit) - // -> if inspection status is not approved, then refund, otherwise send to seller - function cancelSale(uint256 _nftID) public { - if (inspectionPassed[_nftID] == false) { - payable(buyer[_nftID]).transfer(address(this).balance); - } else { - payable(seller).transfer(address(this).balance); - } - // Emit event for sale cancellation - emit SaleCancelled(_nftID, msg.sender); - } - - receive() external payable {} - - function getBalance() public view returns (uint256) { - return address(this).balance; - } } diff --git a/contracts/RealEstate.sol b/contracts/RealEstate.sol index ccda268..882434b 100644 --- a/contracts/RealEstate.sol +++ b/contracts/RealEstate.sol @@ -1,27 +1,6 @@ //SPDX-License-Identifier: Unlicense pragma solidity ^0.8.0; -import "@openzeppelin/contracts/utils/Counters.sol"; -import "@openzeppelin/contracts/token/ERC721/ERC721.sol"; -import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol"; +contract RealEstate { -contract RealEstate is ERC721URIStorage { - using Counters for Counters.Counter; - Counters.Counter private _tokenIds; - - constructor() ERC721("Real Estate", "REAL") {} - - function mint(string memory tokenURI) public returns (uint256) { - _tokenIds.increment(); - - uint256 newItemId = _tokenIds.current(); - _mint(msg.sender, newItemId); - _setTokenURI(newItemId, tokenURI); - - return newItemId; - } - - function totalSupply() public view returns (uint256) { - return _tokenIds.current(); - } -} \ No newline at end of file +} diff --git a/hardhat.config.js b/hardhat.config.js index b04b5c7..86913e7 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -3,10 +3,4 @@ require("@nomicfoundation/hardhat-toolbox"); /** @type import('hardhat/config').HardhatUserConfig */ module.exports = { solidity: "0.8.17", - networks: { - // Ganache local network configuration - ganache: { - url: "http://localhost:7545", // Default Ganache RPC server URL - }, - }, }; diff --git a/package-lock.json b/package-lock.json index 73ddec7..dbd284d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,8 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", - "axios": "^1.6.8", - "bootstrap": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -5076,24 +5073,6 @@ "node": ">=8" } }, - "node_modules/@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "peer": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } - }, - "node_modules/@remix-run/router": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", - "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==", - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -7395,29 +7374,6 @@ "node": ">=4" } }, - "node_modules/axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/axios/node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -7935,24 +7891,6 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, - "node_modules/bootstrap": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", - "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/twbs" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - } - ], - "peerDependencies": { - "@popperjs/core": "^2.11.8" - } - }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -12054,9 +11992,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "node_modules/follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", "funding": [ { "type": "individual", @@ -20538,11 +20476,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -20891,36 +20824,6 @@ "node": ">=0.10.0" } }, - "node_modules/react-router": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", - "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", - "dependencies": { - "@remix-run/router": "1.15.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8" - } - }, - "node_modules/react-router-dom": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", - "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", - "dependencies": { - "@remix-run/router": "1.15.3", - "react-router": "6.22.3" - }, - "engines": { - "node": ">=14.0.0" - }, - "peerDependencies": { - "react": ">=16.8", - "react-dom": ">=16.8" - } - }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -29382,17 +29285,6 @@ } } }, - "@popperjs/core": { - "version": "2.11.8", - "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", - "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", - "peer": true - }, - "@remix-run/router": { - "version": "1.15.3", - "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz", - "integrity": "sha512-Oy8rmScVrVxWZVOpEF57ovlnhpZ8CCPlnIIumVcV9nFdiSIrus99+Lw78ekXyGvVDlIsFJbSfmSovJUhCWYV3w==" - }, "@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -31148,28 +31040,6 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.3.tgz", "integrity": "sha512-32+ub6kkdhhWick/UjvEwRchgoetXqTK14INLqbGm5U2TzBkBNF3nQtLYm8ovxSkQWArjEQvftCKryjZaATu3w==" }, - "axios": { - "version": "1.6.8", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.8.tgz", - "integrity": "sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==", - "requires": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - }, - "dependencies": { - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - } - } - } - }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -31581,12 +31451,6 @@ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" }, - "bootstrap": { - "version": "5.3.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", - "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", - "requires": {} - }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -34721,9 +34585,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "follow-redirects": { - "version": "1.15.6", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", - "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" }, "forever-agent": { "version": "0.6.1", @@ -40818,11 +40682,6 @@ } } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" - }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -41062,23 +40921,6 @@ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", "integrity": "sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==" }, - "react-router": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.22.3.tgz", - "integrity": "sha512-dr2eb3Mj5zK2YISHK++foM9w4eBnO23eKnZEDs7c880P6oKbrjz/Svg9+nxqtHQK+oMW4OtjZca0RqPglXxguQ==", - "requires": { - "@remix-run/router": "1.15.3" - } - }, - "react-router-dom": { - "version": "6.22.3", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.3.tgz", - "integrity": "sha512-7ZILI7HjcE+p31oQvwbokjk6OA/bnFxrhJ19n82Ex9Ph8fNAq+Hm/7KchpMGlTgWhUxRHMMCut+vEtNpWpowKw==", - "requires": { - "@remix-run/router": "1.15.3", - "react-router": "6.22.3" - } - }, "react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", diff --git a/package.json b/package.json index c9b6b2f..80f2900 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,8 @@ "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", - "axios": "^1.6.8", - "bootstrap": "^5.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-router-dom": "^6.22.3", "react-scripts": "5.0.1", "web-vitals": "^2.1.4" }, @@ -43,4 +40,4 @@ "@openzeppelin/contracts": "^4.7.3", "hardhat": "^2.12.0" } -} +} \ No newline at end of file diff --git a/public/index.html b/public/index.html index 8d00247..59922f6 100644 --- a/public/index.html +++ b/public/index.html @@ -22,7 +22,7 @@ work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> - dApp + Millow diff --git a/scripts/deploy.js b/scripts/deploy.js index 75c8dba..a4abac7 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -6,57 +6,8 @@ // global scope, and execute the script. const hre = require("hardhat"); -const tokens = (n) => { - return ethers.utils.parseUnits(n.toString(), 'ether') -} - async function main() { - // Setup accounts - const [buyer, seller, inspector, lender] = await ethers.getSigners() - - // Deploy Real Estate - const RealEstate = await ethers.getContractFactory('RealEstate') - const realEstate = await RealEstate.deploy() - await realEstate.deployed() - - console.log(`Deployed Real Estate Contract at: ${realEstate.address}`) - console.log(`Minting 3 properties...\n`) - - for (let i = 0; i < 3; i++) { - const transaction = await realEstate.connect(seller).mint(`https://ipfs.io/ipfs/QmQVcpsjrA6cr1iJjZAodYwmPekYgbnXGo4DFubJiLc2EB/${i + 1}.json`) - await transaction.wait() - } - - // Deploy Escrow - const Escrow = await ethers.getContractFactory('Escrow') - const escrow = await Escrow.deploy( - realEstate.address, - seller.address, - inspector.address, - lender.address - ) - await escrow.deployed() - - console.log(`Deployed Escrow Contract at: ${escrow.address}`) - console.log(`Listing 3 properties...\n`) - - for (let i = 0; i < 3; i++) { - // Approve properties... - let transaction = await realEstate.connect(seller).approve(escrow.address, i + 1) - await transaction.wait() - } - - // Listing properties... - transaction = await escrow.connect(seller).list(1, buyer.address, tokens(20), tokens(10)) - await transaction.wait() - - transaction = await escrow.connect(seller).list(2, buyer.address, tokens(15), tokens(5)) - await transaction.wait() - - transaction = await escrow.connect(seller).list(3, buyer.address, tokens(10), tokens(5)) - await transaction.wait() - console.log(`Finished.`) } // We recommend this pattern to be able to use async/await everywhere @@ -64,4 +15,4 @@ async function main() { main().catch((error) => { console.error(error); process.exitCode = 1; -}); \ No newline at end of file +}); diff --git a/server/db.js b/server/db.js deleted file mode 100644 index 052c7a5..0000000 --- a/server/db.js +++ /dev/null @@ -1,18 +0,0 @@ -const mongoose = require('mongoose'); - -const connectDB = async () => { - try { - await mongoose.connect('mongodb://localhost:27017/your_database_name', { - useNewUrlParser: true, - useUnifiedTopology: true, - useCreateIndex: true, - useFindAndModify: false - }); - console.log('MongoDB connected'); - } catch (error) { - console.error('MongoDB connection failed:', error); - process.exit(1); - } -}; - -module.exports = connectDB; diff --git a/src/App.js b/src/App.js index c3f169d..5f7af4a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,67 +1,30 @@ -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; -import { useState, useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { ethers } from 'ethers'; // Components import Navigation from './components/Navigation'; import Search from './components/Search'; -import Listing from './components/Listing'; -import Admin from './components/Admin'; +import Home from './components/Home'; + // ABIs -import RealEstate from './abis/RealEstate.json'; -import Escrow from './abis/Escrow.json'; +import RealEstate from './abis/RealEstate.json' +import Escrow from './abis/Escrow.json' + // Config import config from './config.json'; function App() { - const [provider, setProvider] = useState(null); - const [escrow, setEscrow] = useState(null); - const [account, setAccount] = useState(null); - const [homes, setHomes] = useState([]); - - const loadBlockchainData = async () => { - const provider = new ethers.providers.Web3Provider(window.ethereum); - setProvider(provider); - const network = await provider.getNetwork(); - - const realEstate = new ethers.Contract(config[network.chainId].realEstate.address, RealEstate, provider); - const totalSupply = await realEstate.totalSupply(); - const homes = []; - - for (let i = 1; i <= totalSupply; i++) { - const uri = await realEstate.tokenURI(i); - const response = await fetch(uri); - const metadata = await response.json(); - homes.push(metadata); - } - setHomes(homes); - - const escrow = new ethers.Contract(config[network.chainId].escrow.address, Escrow, provider); - setEscrow(escrow); + return ( +
- window.ethereum.on('accountsChanged', async () => { - const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); - const account = ethers.utils.getAddress(accounts[0]); - setAccount(account); - }); - }; +
- useEffect(() => { - loadBlockchainData(); - }, []); +

Welcome to Millow

- return ( - -
- - - - } /> - } /> -
-
+ +
); } diff --git a/src/components/Admin.js b/src/components/Admin.js deleted file mode 100644 index ba8da44..0000000 --- a/src/components/Admin.js +++ /dev/null @@ -1,119 +0,0 @@ -import React, { useState } from 'react'; - -// CSS styles as template literals with Facebook-like styling -const styles = ` - .card { - border-radius: 8px; - box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1); - margin: 20px; - padding: 20px; - background-color: #fff; - max-width: 400px; - width: 100%; - text-align: center; - } - - .card-header { - background-color: #4267B2; /* Facebook blue color */ - color: #fff; /* White text */ - padding: 20px; - border-top-left-radius: 8px; - border-top-right-radius: 8px; - font-size: 24px; - font-weight: bold; - } - - .card-body { - padding: 20px; - text-align: left; - } - - .form-label { - color: #000; /* Black text */ - font-size: 16px; - margin-bottom: 8px; - } - - .form-control { - border: 1px solid #ddd; /* Light gray border */ - border-radius: 4px; - padding: 10px; - width: 100%; - font-size: 16px; - margin-bottom: 20px; - } - - .btn-primary { - background-color: #4267B2; /* Facebook blue color */ - border-color: #4267B2; /* Facebook blue border color */ - color: #fff; /* White text */ - padding: 12px 20px; - font-size: 16px; - border-radius: 4px; - width: 100%; - cursor: pointer; - } - - .btn-primary:hover { - background-color: #385898; /* Darker blue color on hover */ - border-color: #385898; /* Darker blue border color on hover */ - } - - .alert-danger { - color: #721c24; /* Dark red color */ - background-color: #f8d7da; /* Light red background color */ - border-color: #f5c6cb; /* Light red border color */ - padding: 10px; - border-radius: 4px; - margin-bottom: 20px; - } -`; - -function Admin() { - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); - const [error, setError] = useState(''); - - const handleLogin = () => { - // Validate username and password (you can add more complex validation logic here) - if (username === 'admin' && password === 'admin123') { - // Clear any previous error message - setError(''); - - // Perform login action (for example, navigate to dashboard) - console.log('Login successful! Redirecting to dashboard...'); - } else { - // Display error message - setError('Invalid username or password. Please try again.'); - } - }; - - return ( -
- {/* Embed CSS styles directly */} -
-
-
-
Admin Log in
-
-
-
- - setUsername(e.target.value)} /> -
-
- - setPassword(e.target.value)} /> -
- {error &&
{error}
} - -
-
-
-
-
-
- ); -} - -export default Admin; diff --git a/src/components/Home.js b/src/components/Home.js index 432ed24..fa82527 100644 --- a/src/components/Home.js +++ b/src/components/Home.js @@ -1,223 +1,14 @@ import { ethers } from 'ethers'; import { useEffect, useState } from 'react'; -import axios from 'axios'; -import close from '../assets/close.svg'; - -const Home = ({ home, provider, account, escrow, togglePop }) => { - const [events, setEvents] = useState([]); - const [hasBought, setHasBought] = useState(false) - const [hasLended, setHasLended] = useState(false) - const [hasInspected, setHasInspected] = useState(false) - const [hasSold, setHasSold] = useState(false) - - const [buyer, setBuyer] = useState(null) - const [lender, setLender] = useState(null) - const [inspector, setInspector] = useState(null) - const [seller, setSeller] = useState(null) - - const [owner, setOwner] = useState(null) - - const fetchDetails = async () => { - // -- Buyer - - const buyer = await escrow.buyer(home.id) - setBuyer(buyer) - - const hasBought = await escrow.approval(home.id, buyer) - setHasBought(hasBought) - - // -- Seller - - const seller = await escrow.seller() - setSeller(seller) - - const hasSold = await escrow.approval(home.id, seller) - setHasSold(hasSold) - - // -- Lender - - const lender = await escrow.lender() - setLender(lender) - - const hasLended = await escrow.approval(home.id, lender) - setHasLended(hasLended) - - // -- Inspector - - const inspector = await escrow.inspector() - setInspector(inspector) - - const hasInspected = await escrow.inspectionPassed(home.id) - setHasInspected(hasInspected) - } - - const fetchOwner = async () => { - if (await escrow.isListed(home.id)) return - - const owner = await escrow.buyer(home.id) - setOwner(owner) - } - - const buyHandler = async () => { - const escrowAmount = await escrow.escrowAmount(home.id) - const signer = await provider.getSigner() - - // Buyer deposit earnest - let transaction = await escrow.connect(signer).depositEarnest(home.id, { value: escrowAmount }) - await transaction.wait() - - // Buyer approves... - transaction = await escrow.connect(signer).approveSale(home.id) - await transaction.wait() - - setHasBought(true) - } - - const inspectHandler = async () => { - const signer = await provider.getSigner() - - // Inspector updates status - const transaction = await escrow.connect(signer).updateInspectionStatus(home.id, true) - await transaction.wait() - setHasInspected(true) - } - - const lendHandler = async () => { - const signer = await provider.getSigner() - - // Lender approves... - const transaction = await escrow.connect(signer).approveSale(home.id) - await transaction.wait() - - // Lender sends funds to contract... - const lendAmount = (await escrow.purchasePrice(home.id) - await escrow.escrowAmount(home.id)) - await signer.sendTransaction({ to: escrow.address, value: lendAmount.toString(), gasLimit: 60000 }) - - setHasLended(true) - } - - const sellHandler = async () => { - const signer = await provider.getSigner() - - // Seller approves... - let transaction = await escrow.connect(signer).approveSale(home.id) - await transaction.wait() - - // Seller finalize... - transaction = await escrow.connect(signer).finalizeSale(home.id) - await transaction.wait() - - setHasSold(true) - } - useEffect(() => { - const fetchEvents = async () => { - // Subscribe to Escrow contract events - const filter = escrow.filters.Purchase(); - escrow.on(filter, async (nftID, buyer, purchasePrice, event) => { - // Send event data to backend server - await sendEventToBackend(event); - }); - - // Cleanup function to unsubscribe from events - return () => { - escrow.off(filter); - }; - }; - - fetchEvents(); - }, []); - - const sendEventToBackend = async (event) => { - try { - // Send event data to backend server - await axios.post('/api/events', event); - } catch (error) { - console.error('Error sending event to backend:', error); - } - }; - - let transaction; // Declare the transaction variable +import close from '../assets/close.svg'; - useEffect(() => { - fetchDetails() - console.log(transaction); - fetchOwner() - }, [hasSold]) +const Home = ({ home, provider, escrow, togglePop }) => { return (
-
-
- Home -
-
-

{home.name}

-

- {home.attributes[2].value} bds | - {home.attributes[3].value} ba | - {home.attributes[4].value} sqft -

-

{home.address}

- -

{home.attributes[0].value} ETH

- - {owner ? ( -
- Owned by {owner.slice(0, 6) + '...' + owner.slice(38, 42)} -
- ) : ( -
- {(account === inspector) ? ( - - ) : (account === lender) ? ( - - ) : (account === seller) ? ( - - ) : ( - - )} - - -
- )} - -
- -

Overview

- -

- {home.description} -

- -
- -

Facts and features

- -
    - {home.attributes.map((attribute, index) => ( -
  • {attribute.trait_type} : {attribute.value}
  • - ))} -
-
- - - -
-
+
); } -export default Home; \ No newline at end of file +export default Home; diff --git a/src/components/Listing.js b/src/components/Listing.js deleted file mode 100644 index bf4ce28..0000000 --- a/src/components/Listing.js +++ /dev/null @@ -1,38 +0,0 @@ -import { useState } from 'react'; -import Home from './Home'; - -function Listing({ homes, provider, account, escrow }) { - const [toggle, setToggle] = useState(false); - const [home, setHome] = useState({}); - - const togglePop = (home) => { - setHome(home); - setToggle((prevToggle) => !prevToggle); - }; - - return ( -
-

Homes For You

-
-
- {homes.map((home, index) => ( -
togglePop(home)}> -
- Home -
-
-

{home.attributes[0].value} ETH

-

- {home.attributes[2].value} bds | {home.attributes[3].value} ba | {home.attributes[4].value} sqft -

-

{home.address}

-
-
- ))} -
- {toggle && } -
- ); -} - -export default Listing; diff --git a/src/components/Navigation.js b/src/components/Navigation.js index b315ff0..92cd5c8 100644 --- a/src/components/Navigation.js +++ b/src/components/Navigation.js @@ -1,45 +1,7 @@ -import { ethers } from 'ethers'; import logo from '../assets/logo.svg'; const Navigation = ({ account, setAccount }) => { - const connectHandler = async () => { - const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' }); - const account = ethers.utils.getAddress(accounts[0]) - setAccount(account); - } - return ( - - ); } -export default Navigation; \ No newline at end of file +export default Navigation; diff --git a/test/Escrow.js b/test/Escrow.js index 013d703..9b84e1a 100644 --- a/test/Escrow.js +++ b/test/Escrow.js @@ -6,159 +6,5 @@ const tokens = (n) => { } describe('Escrow', () => { - let buyer, seller, inspector, lender - let realEstate, escrow - beforeEach(async () => { - // Setup accounts - [buyer, seller, inspector, lender] = await ethers.getSigners() - - // Deploy Real Estate - const RealEstate = await ethers.getContractFactory('RealEstate') - realEstate = await RealEstate.deploy() - - // Mint - let transaction = await realEstate.connect(seller).mint("https://ipfs.io/ipfs/QmTudSYeM7mz3PkYEWXWqPjomRPHogcMFSq7XAvsvsgAPS") - await transaction.wait() - - // Deploy Escrow - const Escrow = await ethers.getContractFactory('Escrow') - escrow = await Escrow.deploy( - realEstate.address, - seller.address, - inspector.address, - lender.address - ) - - // Approve Property - transaction = await realEstate.connect(seller).approve(escrow.address, 1) - await transaction.wait() - - // List Property - transaction = await escrow.connect(seller).list(1, buyer.address, tokens(10), tokens(5)) - await transaction.wait() - }) - - describe('Deployment', () => { - it('Returns NFT address', async () => { - const result = await escrow.nftAddress() - expect(result).to.be.equal(realEstate.address) - }) - - it('Returns seller', async () => { - const result = await escrow.seller() - expect(result).to.be.equal(seller.address) - }) - - it('Returns inspector', async () => { - const result = await escrow.inspector() - expect(result).to.be.equal(inspector.address) - }) - - it('Returns lender', async () => { - const result = await escrow.lender() - expect(result).to.be.equal(lender.address) - }) - }) - - describe('Listing', () => { - it('Updates as listed', async () => { - const result = await escrow.isListed(1) - expect(result).to.be.equal(true) - }) - - it('Returns buyer', async () => { - const result = await escrow.buyer(1) - expect(result).to.be.equal(buyer.address) - }) - - it('Returns purchase price', async () => { - const result = await escrow.purchasePrice(1) - expect(result).to.be.equal(tokens(10)) - }) - - it('Returns escrow amount', async () => { - const result = await escrow.escrowAmount(1) - expect(result).to.be.equal(tokens(5)) - }) - - it('Updates ownership', async () => { - expect(await realEstate.ownerOf(1)).to.be.equal(escrow.address) - }) - }) - - describe('Deposits', () => { - beforeEach(async () => { - const transaction = await escrow.connect(buyer).depositEarnest(1, { value: tokens(5) }) - await transaction.wait() - }) - - it('Updates contract balance', async () => { - const result = await escrow.getBalance() - expect(result).to.be.equal(tokens(5)) - }) - }) - - describe('Inspection', () => { - beforeEach(async () => { - const transaction = await escrow.connect(inspector).updateInspectionStatus(1, true) - await transaction.wait() - }) - - it('Updates inspection status', async () => { - const result = await escrow.inspectionPassed(1) - expect(result).to.be.equal(true) - }) - }) - - describe('Approval', () => { - beforeEach(async () => { - let transaction = await escrow.connect(buyer).approveSale(1) - await transaction.wait() - - transaction = await escrow.connect(seller).approveSale(1) - await transaction.wait() - - transaction = await escrow.connect(lender).approveSale(1) - await transaction.wait() - }) - - it('Updates approval status', async () => { - expect(await escrow.approval(1, buyer.address)).to.be.equal(true) - expect(await escrow.approval(1, seller.address)).to.be.equal(true) - expect(await escrow.approval(1, lender.address)).to.be.equal(true) - }) - }) - - describe('Sale', () => { - beforeEach(async () => { - let transaction = await escrow.connect(buyer).depositEarnest(1, { value: tokens(5) }) - await transaction.wait() - - transaction = await escrow.connect(inspector).updateInspectionStatus(1, true) - await transaction.wait() - - transaction = await escrow.connect(buyer).approveSale(1) - await transaction.wait() - - transaction = await escrow.connect(seller).approveSale(1) - await transaction.wait() - - transaction = await escrow.connect(lender).approveSale(1) - await transaction.wait() - - await lender.sendTransaction({ to: escrow.address, value: tokens(5) }) - - transaction = await escrow.connect(seller).finalizeSale(1) - await transaction.wait() - }) - - it('Updates ownership', async () => { - expect(await realEstate.ownerOf(1)).to.be.equal(buyer.address) - }) - - it('Updates balance', async () => { - expect(await escrow.getBalance()).to.be.equal(0) - }) - }) -}) \ No newline at end of file +})