Skip to content

Commit 78baf7e

Browse files
committed
nft and asset listing added
1 parent d206a79 commit 78baf7e

6 files changed

+299
-0
lines changed

remappings.txt

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
ds-test/=lib/openzeppelin-contracts/lib/forge-std/lib/ds-test/src/
2+
erc4626-tests/=lib/openzeppelin-contracts/lib/erc4626-tests/
3+
forge-std/=lib/forge-std/src/
4+
@openzeppelin-contracts/=lib/openzeppelin-contracts/
5+
@openzeppelin/=lib/openzeppelin-contracts/

src/AssetListing.sol

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "@openzeppelin/contracts/access/Ownable.sol";
5+
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
6+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
7+
import "./PropertyNFT.sol";
8+
9+
contract AssetListing is Initializable, Ownable {
10+
struct Asset {
11+
uint256 assetId;
12+
address owner;
13+
uint256 valuation;
14+
bool isListed;
15+
bool isSingleBuyer;
16+
uint256 collateralAmount;
17+
uint256 listingFee;
18+
uint256 tokenId;
19+
}
20+
21+
mapping(uint256 => Asset) public assets;
22+
uint256 public nextAssetId;
23+
uint256 public collateralPercentage; // Example: 5% collateral
24+
uint256 public listingFeePercentage; // Example: 0.5% listing fee
25+
PropertyNFT public propertyNFT;
26+
IERC20 public usdc;
27+
28+
event AssetRequested(uint256 indexed assetId, address indexed owner, uint256 valuation);
29+
event AssetListed(uint256 indexed assetId, address indexed owner, uint256 valuation);
30+
event CollateralReturned(uint256 indexed assetId, address indexed owner);
31+
event CollateralForfeited(uint256 indexed assetId, address indexed owner);
32+
33+
function initialize(
34+
uint256 _collateralPercentage,
35+
uint256 _listingFeePercentage,
36+
address _usdc,
37+
address _propertyNFTAddress
38+
) public initializer {
39+
collateralPercentage = _collateralPercentage;
40+
listingFeePercentage = _listingFeePercentage;
41+
usdc = IERC20(_usdc);
42+
propertyNFT = PropertyNFT(_propertyNFTAddress);
43+
nextAssetId = 1;
44+
}
45+
46+
// Request to list an asset on the platform
47+
function requestAssetListing(uint256 valuation, bool isSingleBuyer, string memory tokenURI) external {
48+
uint256 collateral = (valuation * collateralPercentage) / 100;
49+
uint256 listingFee = (valuation * listingFeePercentage) / 100;
50+
51+
require(usdc.transferFrom(msg.sender, address(this), collateral + listingFee), "Transfer failed");
52+
53+
uint256 assetId = nextAssetId++;
54+
uint256 tokenId = propertyNFT.mint(address(this), tokenURI);
55+
56+
assets[assetId] = Asset({
57+
assetId: assetId,
58+
owner: msg.sender,
59+
valuation: valuation,
60+
isListed: false,
61+
isSingleBuyer: isSingleBuyer,
62+
collateralAmount: collateral,
63+
listingFee: listingFee,
64+
tokenId: tokenId
65+
});
66+
67+
emit AssetRequested(assetId, msg.sender, valuation);
68+
}
69+
70+
// Approve and list the asset for sale
71+
function listAsset(uint256 assetId) external onlyOwner {
72+
Asset storage asset = assets[assetId];
73+
require(!asset.isListed, "Asset already listed");
74+
75+
if (asset.isSingleBuyer) {
76+
// Logic for single buyer scenario
77+
} else {
78+
// Logic for fractional ownership scenario
79+
// Typically, this would involve creating and selling shares or fractions
80+
}
81+
82+
asset.isListed = true;
83+
84+
emit AssetListed(assetId, asset.owner, asset.valuation);
85+
}
86+
87+
// Return collateral after successful asset sale or full subscription
88+
function returnCollateral(uint256 assetId) external onlyOwner {
89+
Asset storage asset = assets[assetId];
90+
require(asset.isListed, "Asset not listed");
91+
require(asset.isSingleBuyer, "Asset not fully subscribed or sold");
92+
93+
uint256 collateralAmount = asset.collateralAmount;
94+
asset.collateralAmount = 0;
95+
96+
usdc.transfer(asset.owner, collateralAmount);
97+
98+
emit CollateralReturned(assetId, asset.owner);
99+
}
100+
101+
// Forfeit collateral under certain conditions (e.g., listing violation or fraudulent behavior)
102+
function forfeitCollateral(uint256 assetId) external onlyOwner {
103+
Asset storage asset = assets[assetId];
104+
require(asset.collateralAmount > 0, "No collateral to forfeit");
105+
106+
uint256 collateralAmount = asset.collateralAmount;
107+
asset.collateralAmount = 0;
108+
109+
usdc.transfer(owner(), collateralAmount);
110+
111+
emit CollateralForfeited(assetId, asset.owner);
112+
}
113+
}

src/FractionalOwnershipProxy.sol

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
5+
6+
contract FractionalOwnershipProxy is TransparentUpgradeableProxy {
7+
constructor(address _logic, address admin_, bytes memory _data)
8+
TransparentUpgradeableProxy(_logic, admin_, _data) {}
9+
}

src/FractionalPropertyToken.sol

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5+
import "@openzeppelin/contracts/access/Ownable.sol";
6+
import "./PropertyToken.sol";
7+
8+
contract FractionalOwnership is ERC20, Ownable {
9+
PropertyToken public propertyTokenContract;
10+
11+
// Mapping from property ID to the total fractional tokens issued
12+
mapping(uint256 => uint256) public totalFractionsIssued;
13+
14+
// Constructor to set the token name, symbol, and property token contract
15+
constructor(address _propertyTokenContract) ERC20("FractionalPropertyToken", "FPT") {
16+
propertyTokenContract = PropertyToken(_propertyTokenContract);
17+
}
18+
19+
// Function to issue fractional tokens for a specific property
20+
function issueFractionalTokens(uint256 propertyId, uint256 amount) public onlyOwner {
21+
require(propertyTokenContract.ownerOf(propertyId) == msg.sender, "Caller is not the property owner");
22+
require(totalFractionsIssued[propertyId] == 0, "Fractional tokens already issued for this property");
23+
24+
_mint(msg.sender, amount);
25+
totalFractionsIssued[propertyId] = amount;
26+
}
27+
28+
// Function to allow buying fractional tokens (could be expanded to include pricing, etc.)
29+
function buyFractionalTokens(uint256 propertyId, uint256 amount) public payable {
30+
require(totalFractionsIssued[propertyId] > 0, "No fractional tokens issued for this property");
31+
32+
// Implement logic to handle the payment and transfer of tokens
33+
_transfer(owner(), msg.sender, amount);
34+
}
35+
36+
// Function to sell fractional tokens back
37+
function sellFractionalTokens(uint256 propertyId, uint256 amount) public {
38+
require(balanceOf(msg.sender) >= amount, "Insufficient balance to sell");
39+
40+
// Implement logic to handle the return of tokens and payment
41+
_transfer(msg.sender, owner(), amount);
42+
}
43+
44+
// Function to distribute income (like rent) to fractional token holders
45+
function distributeIncome(uint256 propertyId, uint256 totalIncome) public onlyOwner {
46+
require(totalFractionsIssued[propertyId] > 0, "No fractional tokens issued for this property");
47+
48+
uint256 incomePerToken = totalIncome / totalFractionsIssued[propertyId];
49+
50+
for (uint256 i = 0; i < totalSupply(); i++) {
51+
address tokenHolder = address(uint160(i));
52+
uint256 holderBalance = balanceOf(tokenHolder);
53+
if (holderBalance > 0) {
54+
payable(tokenHolder).transfer(holderBalance * incomePerToken);
55+
}
56+
}
57+
}
58+
}

src/PropertyNFT.sol

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
2+
3+
/* {
4+
"name": "Property #123",
5+
"description": "A lovely 2-bedroom apartment in New York.",
6+
"image": "https://example.com/images/property123.png",
7+
"attributes": {
8+
"location": "New York, NY",
9+
"size": "1000 sq ft",
10+
"valuation": "$500,000"
11+
}
12+
}
13+
*/
14+
15+
16+
17+
// SPDX-License-Identifier: MIT
18+
pragma solidity ^0.8.0;
19+
20+
21+
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
22+
23+
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
24+
import "@openzeppelin/contracts/access/Ownable.sol";
25+
26+
contract PropertyNFT is ERC721, Ownable {
27+
uint256 public nextTokenId;
28+
mapping(uint256 => string) private _tokenURIs;
29+
IERC20 public usdc;
30+
31+
// Mapping of token ID to user address to their ownership share
32+
mapping(uint256 => mapping(address => uint256)) public ownershipShares;
33+
// Mapping of token ID to the total ownership sold
34+
mapping(uint256 => uint256) public totalOwnershipSold;
35+
36+
event OwnershipPurchased(uint256 indexed tokenId, address indexed buyer, uint256 amount);
37+
event OwnershipTransferred(uint256 indexed tokenId, address indexed from, address indexed to, uint256 amount);
38+
39+
constructor(address _usdc) ERC721("PropertyToken", "PROP") {
40+
nextTokenId = 1;
41+
usdc = IERC20(_usdc);
42+
43+
}
44+
45+
// Function to mint a new property NFT
46+
function mint(address to, string memory _tokenURI) external onlyOwner returns (uint256) {
47+
uint256 tokenId = nextTokenId;
48+
_safeMint(to, tokenId);
49+
_setTokenURI(tokenId, _tokenURI);
50+
nextTokenId++;
51+
return tokenId;
52+
}
53+
54+
// Function to set the token URI
55+
function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
56+
require(_exists(tokenId), "ERC721Metadata: URI set of nonexistent token");
57+
_tokenURIs[tokenId] = _tokenURI;
58+
}
59+
// Purchase fractional ownership in a property
60+
function purchaseOwnership(uint256 tokenId, uint256 usdcAmount) external {
61+
require(ownerOf(tokenId) == address(this), "Property not available for sale");
62+
63+
uint256 ownershipPercentage = (usdcAmount * 10000) / totalAssetValue(tokenId);
64+
ownershipShares[tokenId][msg.sender] += ownershipPercentage;
65+
totalOwnershipSold[tokenId] += ownershipPercentage;
66+
67+
require(totalOwnershipSold[tokenId] <= 10000, "Cannot exceed 100% ownership");
68+
69+
//> instead of addressthis, send it to some vault, msig.
70+
usdc.transferFrom(msg.sender, address(this), usdcAmount);
71+
72+
emit OwnershipPurchased(tokenId, msg.sender, ownershipPercentage);
73+
}
74+
75+
// Transfer fractional ownership from one user to another
76+
function transferOwnership(uint256 tokenId, address to, uint256 amount) external {
77+
require(ownershipShares[tokenId][msg.sender] >= amount, "Insufficient ownership");
78+
79+
ownershipShares[tokenId][msg.sender] -= amount;
80+
ownershipShares[tokenId][to] += amount;
81+
82+
emit OwnershipTransferred(tokenId, msg.sender, to, amount);
83+
}
84+
85+
// Return the total value of the asset (placeholder)
86+
function totalAssetValue(uint256 tokenId) public view returns (uint256) {
87+
// Placeholder for actual value, should be set or calculated
88+
return 1000000; // Example: $1,000,000
89+
}
90+
91+
// Retrieve the ownership percentage of a specific user for a token
92+
function getOwnershipPercentage(uint256 tokenId, address owner) external view returns (uint256) {
93+
return ownershipShares[tokenId][owner];
94+
}
95+
96+
// Withdraw USDC funds (e.g., when the asset is sold)
97+
function withdrawFunds() external onlyOwner {
98+
uint256 balance = usdc.balanceOf(address(this));
99+
usdc.transfer(owner(), balance);
100+
}
101+
102+
103+
}
104+
105+

src/PropertyTokenProxy.sol

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.0;
3+
4+
import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol";
5+
6+
contract PropertyTokenProxy is TransparentUpgradeableProxy {
7+
constructor(address _logic, address admin_, bytes memory _data)
8+
TransparentUpgradeableProxy(_logic, admin_, _data) {}
9+
}

0 commit comments

Comments
 (0)