| 1 | // SPDX-License-Identifier: MIT |
| 2 | pragma solidity ^0.8.20; |
| 3 | |
| 4 | contract HomeTransaction { |
| 5 | // Constants |
| 6 | uint constant FINALIZATION_WINDOW = 5 minutes; |
| 7 | uint constant MIN_DEPOSIT_PERCENTAGE = 10; |
| 8 | uint constant MAX_DEPOSIT_PERCENTAGE = 100; |
| 9 | |
| 10 | enum ContractState { |
| 11 | WaitingSellerSignature, |
| 12 | WaitingBuyerSignature, |
| 13 | WaitingRealtorReview, |
| 14 | WaitingFinalization, |
| 15 | Finalized, |
| 16 | Rejected |
| 17 | } |
| 18 | ContractState public contractState = ContractState.WaitingSellerSignature; |
| 19 | |
| 20 | // Roles acting on contract |
| 21 | address payable public realtor; |
| 22 | address payable public seller; |
| 23 | address payable public buyer; |
| 24 | |
| 25 | // Contract details |
| 26 | string public homeAddress; |
| 27 | string public zip; |
| 28 | string public city; |
| 29 | uint public realtorFee; |
| 30 | uint public price; |
| 31 | |
| 32 | // Set when buyer signs and pays deposit |
| 33 | uint public deposit; |
| 34 | uint public finalizeDeadline; |
| 35 | |
| 36 | // Set when realtor reviews closing conditions |
| 37 | enum ClosingConditionsReview { Pending, Accepted, Rejected } |
| 38 | ClosingConditionsReview public closingConditionsReview = ClosingConditionsReview.Pending; |
| 39 | |
| 40 | // Events |
| 41 | event SellerSigned(address indexed seller); |
| 42 | event BuyerSignedAndDeposited(address indexed buyer, uint deposit); |
| 43 | event RealtorReviewed(address indexed realtor, bool accepted); |
| 44 | event TransactionFinalized(address indexed buyer, uint totalPrice); |
| 45 | event TransactionWithdrawn(address indexed initiator); |
| 46 | |
| 47 | constructor( |
| 48 | string memory _address, |
| 49 | string memory _zip, |
| 50 | string memory _city, |
| 51 | uint _realtorFee, |
| 52 | uint _price, |
| 53 | address payable _realtor, |
| 54 | address payable _seller, |
| 55 | address payable _buyer |
| 56 | ) { |
| 57 | require(_price >= _realtorFee, "Price must be >= realtor fee"); |
| 58 | require(_realtor != address(0), "Invalid realtor address"); |
| 59 | require(_seller != address(0), "Invalid seller address"); |
| 60 | require(_buyer != address(0), "Invalid buyer address"); |
| 61 | |
| 62 | realtor = _realtor; |
| 63 | seller = _seller; |
| 64 | buyer = _buyer; |
| 65 | homeAddress = _address; |
| 66 | zip = _zip; |
| 67 | city = _city; |
| 68 | price = _price; |
| 69 | realtorFee = _realtorFee; |
| 70 | } |
| 71 | |
| 72 | modifier onlySeller() { |
| 73 | require(msg.sender == seller, "Only seller can call"); |
| 74 | _; |
| 75 | } |
| 76 | |
| 77 | modifier onlyBuyer() { |
| 78 | require(msg.sender == buyer, "Only buyer can call"); |
| 79 | _; |
| 80 | } |
| 81 | |
| 82 | modifier onlyRealtor() { |
| 83 | require(msg.sender == realtor, "Only realtor can call"); |
| 84 | _; |
| 85 | } |
| 86 | |
| 87 | modifier inState(ContractState _state) { |
| 88 | require(contractState == _state, "Invalid contract state"); |
| 89 | _; |
| 90 | } |
| 91 | |
| 92 | function sellerSignContract() |
| 93 | external |
| 94 | onlySeller |
| 95 | inState(ContractState.WaitingSellerSignature) |
| 96 | { |
| 97 | contractState = ContractState.WaitingBuyerSignature; |
| 98 | emit SellerSigned(msg.sender); |
| 99 | } |
| 100 | |
| 101 | function buyerSignContractAndPayDeposit() |
| 102 | external |
| 103 | payable |
| 104 | onlyBuyer |
| 105 | inState(ContractState.WaitingBuyerSignature) |
| 106 | { |
| 107 | uint minDeposit = (price * MIN_DEPOSIT_PERCENTAGE) / 100; |
| 108 | uint maxDeposit = (price * MAX_DEPOSIT_PERCENTAGE) / 100; |
| 109 | |
| 110 | require( |
| 111 | msg.value >= minDeposit && msg.value <= maxDeposit, |
| 112 | "Deposit must be 10-100% of price" |
| 113 | ); |
| 114 | |
| 115 | deposit = msg.value; |
| 116 | finalizeDeadline = block.timestamp + FINALIZATION_WINDOW; |
| 117 | contractState = ContractState.WaitingRealtorReview; |
| 118 | |
| 119 | emit BuyerSignedAndDeposited(msg.sender, msg.value); |
| 120 | } |
| 121 | |
| 122 | function realtorReviewClosingConditions(bool accepted) |
| 123 | external |
| 124 | onlyRealtor |
| 125 | inState(ContractState.WaitingRealtorReview) |
| 126 | { |
| 127 | if (accepted) { |
| 128 | closingConditionsReview = ClosingConditionsReview.Accepted; |
| 129 | contractState = ContractState.WaitingFinalization; |
| 130 | } else { |
| 131 | closingConditionsReview = ClosingConditionsReview.Rejected; |
| 132 | contractState = ContractState.Rejected; |
| 133 | |
| 134 | // Refund buyer's deposit |
| 135 | (bool success, ) = buyer.call{value: deposit}(""); |
| 136 | require(success, "Refund failed"); |
| 137 | } |
| 138 | |
| 139 | emit RealtorReviewed(msg.sender, accepted); |
| 140 | } |
| 141 | |
| 142 | function buyerFinalizeTransaction() |
| 143 | external |
| 144 | payable |
| 145 | onlyBuyer |
| 146 | inState(ContractState.WaitingFinalization) |
| 147 | { |
| 148 | require( |
| 149 | msg.value + deposit == price, |
| 150 | "Must pay remaining balance" |
| 151 | ); |
| 152 | |
| 153 | contractState = ContractState.Finalized; |
| 154 | |
| 155 | // Pay seller (price minus realtor fee) |
| 156 | (bool sellerPaid, ) = seller.call{value: price - realtorFee}(""); |
| 157 | require(sellerPaid, "Seller payment failed"); |
| 158 | |
| 159 | // Pay realtor |
| 160 | (bool realtorPaid, ) = realtor.call{value: realtorFee}(""); |
| 161 | require(realtorPaid, "Realtor payment failed"); |
| 162 | |
| 163 | emit TransactionFinalized(msg.sender, price); |
| 164 | } |
| 165 | |
| 166 | function withdrawFromTransaction() |
| 167 | external |
| 168 | inState(ContractState.WaitingFinalization) |
| 169 | { |
| 170 | require( |
| 171 | msg.sender == buyer || block.timestamp >= finalizeDeadline, |
| 172 | "Only buyer can withdraw before deadline" |
| 173 | ); |
| 174 | |
| 175 | contractState = ContractState.Rejected; |
| 176 | |
| 177 | // Seller gets deposit minus realtor fee |
| 178 | (bool sellerPaid, ) = seller.call{value: deposit - realtorFee}(""); |
| 179 | require(sellerPaid, "Seller payment failed"); |
| 180 | |
| 181 | // Realtor still gets fee |
| 182 | (bool realtorPaid, ) = realtor.call{value: realtorFee}(""); |
| 183 | require(realtorPaid, "Realtor payment failed"); |
| 184 | |
| 185 | emit TransactionWithdrawn(msg.sender); |
| 186 | } |
| 187 | |
| 188 | // View functions |
| 189 | function getContractDetails() external view returns ( |
| 190 | string memory _homeAddress, |
| 191 | string memory _zip, |
| 192 | string memory _city, |
| 193 | uint _price, |
| 194 | uint _realtorFee, |
| 195 | ContractState _state |
| 196 | ) { |
| 197 | return (homeAddress, zip, city, price, realtorFee, contractState); |
| 198 | } |
| 199 | |
| 200 | function getRemainingBalance() external view returns (uint) { |
| 201 | if (contractState != ContractState.WaitingFinalization) { |
| 202 | return 0; |
| 203 | } |
| 204 | return price - deposit; |
| 205 | } |
| 206 | } |