Skip to content

Commit 01f55cc

Browse files
committed
First commit
0 parents  commit 01f55cc

27 files changed

+29886
-0
lines changed

.env.schema

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
HARDHAT_KLAYTN_BAOBAB_TESTNET_URL=
2+
HARDHAT_KLAYTN_ACCOUNT_PRIVATE_KEY=

.eslintrc.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "next/core-web-vitals"
3+
}

.gitignore

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Hardhat files
2+
/cache
3+
/artifacts
4+
5+
# TypeChain files
6+
/typechain
7+
/typechain-types
8+
9+
# solidity-coverage files
10+
/coverage
11+
/coverage.json
12+
13+
# dependencies
14+
/node_modules
15+
/.pnp
16+
.pnp.js
17+
18+
# testing
19+
/coverage
20+
21+
# next.js
22+
/.next/
23+
/out/
24+
25+
# production
26+
/build
27+
28+
# misc
29+
.DS_Store
30+
*.pem
31+
32+
# debug
33+
npm-debug.log*
34+
yarn-debug.log*
35+
yarn-error.log*
36+
37+
# local env files
38+
.env
39+
.env.*
40+
!.env.example
41+
!.env.schema
42+
43+
# vercel
44+
.vercel
45+
46+
# typescript
47+
*.tsbuildinfo
48+
next-env.d.ts

.prettierrc

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"tabWidth": 2,
3+
"singleQuote": true,
4+
"useTabs": true,
5+
"trailingComma": "all",
6+
"printWidth": 120
7+
}

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# houseform

contracts/HouseformManager.sol

+209
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import '@openzeppelin/contracts/token/ERC721/IERC721.sol';
5+
import '@openzeppelin/contracts/token/ERC721/extensions/IERC721Enumerable.sol';
6+
import '@openzeppelin/contracts/access/Ownable.sol';
7+
8+
contract HouseformManager is Ownable {
9+
struct ConstructionProject {
10+
address payable developer;
11+
uint256 goalAmount;
12+
uint256 currentAmount;
13+
uint256 totalInvestors;
14+
bool projectCompleted;
15+
uint256 fundraisingDeadline; // Deadline timestamp for reaching the fundraising goal
16+
uint256 completionDeadline; // Deadline timestamp for completing the project goal
17+
uint256 saleAmount; // Amount at which the project is sold
18+
uint256 developerShare; // Percentage of the sale amount that goes to the developer
19+
}
20+
21+
mapping(uint256 => ConstructionProject) public constructionProjects;
22+
mapping(address => uint256[]) public developerProjects;
23+
mapping(uint256 => uint256) public nftIdToProjectId;
24+
25+
address public houseformShareNFTAddress;
26+
27+
event InvestmentMade(address investor, uint256 projectId, uint256 amount, uint256 sharesBought);
28+
event ProjectCompleted(uint256 projectId, uint256 totalReturns);
29+
event ShareNFTMinted(address indexed investor, uint256 indexed projectId, uint256 tokenId, uint256 sharesBought);
30+
event ShareRedeemed(address indexed investor, uint256 indexed projectId, uint256 amount);
31+
event ShareBurned(address indexed investor, uint256 indexed projectId, uint256 tokenId);
32+
event ProjectCancelled(uint256 projectId);
33+
event FundraisingFailed(uint256 projectId);
34+
35+
modifier onlyProjectOwner(uint256 _projectId) {
36+
require(constructionProjects[_projectId].developer == msg.sender, 'Caller is not the project owner');
37+
_;
38+
}
39+
40+
modifier onlyDeveloper(uint256 _projectId) {
41+
require(constructionProjects[_projectId].developer == msg.sender, 'Caller is not the project developer');
42+
_;
43+
}
44+
45+
modifier onlyCustomer(uint256 _projectId) {
46+
require(_isCustomer(_projectId, msg.sender), 'Caller is not a customer for this project');
47+
_;
48+
}
49+
50+
modifier projectNotCompleted(uint256 _projectId) {
51+
require(!constructionProjects[_projectId].projectCompleted, 'Project is already completed');
52+
_;
53+
}
54+
55+
modifier projectNotCancelled(uint256 _projectId) {
56+
require(constructionProjects[_projectId].completionDeadline > 0, 'Project is cancelled');
57+
_;
58+
}
59+
60+
modifier projectNotExpired(uint256 _projectId) {
61+
require(
62+
constructionProjects[_projectId].completionDeadline == 0 ||
63+
block.timestamp <= constructionProjects[_projectId].completionDeadline,
64+
'Project deadline expired'
65+
);
66+
_;
67+
}
68+
69+
modifier fundraisingDeadlineNotExpired(uint256 _projectId) {
70+
require(
71+
constructionProjects[_projectId].fundraisingDeadline == 0 ||
72+
block.timestamp <= constructionProjects[_projectId].fundraisingDeadline,
73+
'Fundraising deadline expired'
74+
);
75+
_;
76+
}
77+
78+
constructor() {}
79+
80+
receive() external payable {
81+
// Handle received Ether, if necessary
82+
}
83+
84+
function sethouseformShareNFT(address _houseformShareNFTAddress) external onlyOwner {
85+
houseformShareNFTAddress = _houseformShareNFTAddress;
86+
}
87+
88+
function createConstructionProject(
89+
uint256 _projectId,
90+
uint256 _goalAmount,
91+
uint256 _fundraisingDeadline,
92+
uint256 _completionDeadline,
93+
uint256 _saleAmount,
94+
uint256 _developerShare
95+
) external {
96+
require(constructionProjects[_projectId].developer == address(0), 'Project already exists');
97+
require(_fundraisingDeadline >= block.timestamp, 'Fundraising deadline must be in the future');
98+
99+
constructionProjects[_projectId] = ConstructionProject({
100+
developer: payable(msg.sender),
101+
goalAmount: _goalAmount,
102+
currentAmount: 0,
103+
totalInvestors: 0,
104+
projectCompleted: false,
105+
fundraisingDeadline: _fundraisingDeadline,
106+
completionDeadline: _completionDeadline,
107+
saleAmount: _saleAmount,
108+
developerShare: _developerShare
109+
});
110+
111+
developerProjects[msg.sender].push(_projectId);
112+
}
113+
114+
function invest(
115+
uint256 _projectId,
116+
uint256 _investmentAmount,
117+
uint256 _sharesToBuy
118+
)
119+
external
120+
payable
121+
onlyCustomer(_projectId)
122+
projectNotCompleted(_projectId)
123+
projectNotExpired(_projectId)
124+
fundraisingDeadlineNotExpired(_projectId)
125+
{
126+
ConstructionProject storage project = constructionProjects[_projectId];
127+
128+
require(project.developer != address(0), 'Project does not exist');
129+
require(msg.value == _investmentAmount, 'Incorrect investment amount');
130+
require(
131+
_sharesToBuy > 0 && _sharesToBuy <= project.goalAmount.sub(project.currentAmount),
132+
'Invalid number of shares'
133+
);
134+
135+
project.currentAmount = project.currentAmount.add(_investmentAmount);
136+
project.totalInvestors = project.totalInvestors.add(1);
137+
138+
_mintShareNFT(msg.sender, _projectId, _sharesToBuy);
139+
140+
emit InvestmentMade(msg.sender, _projectId, _investmentAmount, _sharesToBuy);
141+
142+
if (project.currentAmount >= project.goalAmount) {
143+
_completeFundraising(_projectId);
144+
}
145+
}
146+
147+
function _completeFundraising(uint256 _projectId) internal onlyDeveloper(_projectId) {
148+
ConstructionProject storage project = constructionProjects[_projectId];
149+
150+
// If the goal is reached, cancel the fundraisingDeadline and allow completion of the project
151+
project.fundraisingDeadline = 0;
152+
153+
// Emit event indicating successful fundraising
154+
emit FundraisingFailed(_projectId);
155+
}
156+
157+
function redeemShare(
158+
uint256 _projectId
159+
)
160+
external
161+
onlyCustomer(_projectId)
162+
projectNotCompleted(_projectId)
163+
projectNotExpired(_projectId)
164+
projectNotCancelled(_projectId)
165+
{
166+
ConstructionProject storage project = constructionProjects[_projectId];
167+
168+
// Check if the project completion deadline has passed
169+
require(block.timestamp > project.completionDeadline, 'Project completion deadline not reached');
170+
171+
uint256 tokenId = _getInvestorTokenId(msg.sender, _projectId);
172+
173+
_burnShareNFT(msg.sender, tokenId);
174+
175+
uint256 amountToRedeem = (project.currentAmount * project.developerShare) / 100;
176+
177+
payable(msg.sender).transfer(amountToRedeem);
178+
179+
emit ShareRedeemed(msg.sender, _projectId, amountToRedeem);
180+
}
181+
182+
function getProjectByNFTId(uint256 _nftId) external view returns (ConstructionProject memory) {
183+
uint256 projectId = nftIdToProjectId[_nftId];
184+
return constructionProjects[projectId];
185+
}
186+
187+
function getAllProjects() external view returns (uint256[] memory) {
188+
uint256[] memory projectIds = developerProjects[msg.sender];
189+
return projectIds;
190+
}
191+
192+
function getProjectById(uint256 _projectId) external view returns (ConstructionProject memory) {
193+
return constructionProjects[_projectId];
194+
}
195+
196+
function getProjectsByDeveloper(address _developer) external view returns (uint256[] memory) {
197+
return developerProjects[_developer];
198+
}
199+
200+
function _isCustomer(uint256 _projectId, address _customer) internal view returns (bool) {
201+
uint256[] storage customerProjects = developerProjects[_customer];
202+
for (uint256 i = 0; i < customerProjects.length; i++) {
203+
if (customerProjects[i] == _projectId) {
204+
return true;
205+
}
206+
}
207+
return false;
208+
}
209+
}

contracts/HouseformShareNFT.sol

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
import '@openzeppelin/contracts/token/ERC721/ERC721.sol';
5+
import '@openzeppelin/contracts/access/Ownable.sol';
6+
7+
contract HouseformShareNFT is ERC721, Ownable {
8+
constructor() ERC721('HouseformShareNFT', 'HS') {}
9+
10+
function mintShares(address _to, uint256 _projectId, uint256 _amount) external onlyOwner {
11+
uint256 tokenId = totalSupply() + 1;
12+
_mint(_to, tokenId);
13+
_setTokenURI(tokenId, string(abi.encodePacked(_projectId, '-', _amount)));
14+
}
15+
16+
function burnShares(uint256 _tokenId) external onlyOwner {
17+
_burn(_tokenId);
18+
}
19+
20+
function getTokenProjectId(uint256 _tokenId) external view returns (uint256) {
21+
require(_exists(_tokenId), 'Token does not exist');
22+
string memory tokenURI = tokenURI(_tokenId);
23+
bytes memory tokenIdBytes = bytes(tokenURI);
24+
uint256 projectId = 0;
25+
26+
for (uint256 i = 0; i < tokenIdBytes.length; i++) {
27+
if (tokenIdBytes[i] == '-') {
28+
projectId = parseInt(tokenURI, i + 1);
29+
break;
30+
}
31+
}
32+
33+
return projectId;
34+
}
35+
36+
function getTokenAmount(uint256 _tokenId) external view returns (uint256) {
37+
require(_exists(_tokenId), 'Token does not exist');
38+
string memory tokenURI = tokenURI(_tokenId);
39+
bytes memory tokenIdBytes = bytes(tokenURI);
40+
uint256 amount = 0;
41+
42+
for (uint256 i = 0; i < tokenIdBytes.length; i++) {
43+
if (tokenIdBytes[i] == '-') {
44+
amount = parseInt(tokenURI, i + 1);
45+
break;
46+
}
47+
}
48+
49+
return amount;
50+
}
51+
52+
function parseInt(string memory _value, uint256 _startIndex) internal pure returns (uint256) {
53+
uint256 result = 0;
54+
for (uint256 i = _startIndex; i < bytes(_value).length; i++) {
55+
if ((uint8(bytes(_value)[i]) >= 48) && (uint8(bytes(_value)[i]) <= 57)) {
56+
result = result * 10 + (uint8(bytes(_value)[i]) - 48);
57+
}
58+
}
59+
return result;
60+
}
61+
}

hardhat.config.js

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
require('@nomicfoundation/hardhat-toolbox');
2+
require('dotenv').config();
3+
4+
module.exports = {
5+
solidity: '0.8.20',
6+
networks: {
7+
baobab: {
8+
url: process.env.HARDHAT_KLAYTN_BAOBAB_TESTNET_URL || '',
9+
gasPrice: 250000000000,
10+
accounts:
11+
process.env.HARDHAT_KLAYTN_ACCOUNT_PRIVATE_KEY !== undefined
12+
? [process.env.HARDHAT_KLAYTN_ACCOUNT_PRIVATE_KEY]
13+
: [],
14+
},
15+
},
16+
};

next.config.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module.exports = {
2+
webpack: (config, options) => {
3+
config.module.rules.push({
4+
test: /\.svg$/,
5+
use: ['@svgr/webpack'],
6+
});
7+
return config;
8+
},
9+
};

0 commit comments

Comments
 (0)