Skip to content

Commit f081022

Browse files
initial commit
1 parent 944e9a4 commit f081022

38 files changed

+35284
-434
lines changed

package-lock.json

Lines changed: 29875 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity >=0.8.0 <0.9.0;
3+
4+
interface IListingType {
5+
// Creation lifecycle
6+
function beforeCreate(bytes calldata data) external returns (bool);
7+
function onCreate(address creator, bytes calldata data) external returns (uint256 listingId);
8+
function afterCreate(uint256 listingId, bytes calldata data) external returns (bool);
9+
10+
// Sale lifecycle
11+
function beforeSale(uint256 listingId, address buyer, bytes calldata data) external returns (bool);
12+
function onSale(uint256 listingId, address buyer, bytes calldata data) external payable returns (bool);
13+
function afterSale(uint256 listingId, address buyer, bytes calldata data) external returns (bool);
14+
15+
// Pre-buy lifecycle (e.g., escrow setup)
16+
function beforePreBuy(uint256 listingId, address buyer, bytes calldata data) external returns (bool);
17+
function onPreBuy(uint256 listingId, address buyer, bytes calldata data) external payable returns (bool);
18+
function afterPreBuy(uint256 listingId, address buyer, bytes calldata data) external returns (bool);
19+
20+
// Admin lifecycle
21+
function beforeClose(uint256 listingId, address caller, bytes calldata data) external returns (bool);
22+
function onClose(uint256 listingId, address caller, bytes calldata data) external returns (bool);
23+
function afterClose(uint256 listingId, address caller, bytes calldata data) external returns (bool);
24+
25+
// View helpers
26+
function getListing(uint256 listingId) external view returns (bytes memory data);
27+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity >=0.8.0 <0.9.0;
3+
4+
import { IListingType } from "./IListingType.sol";
5+
6+
contract Marketplace {
7+
// Custom errors
8+
error ListingTypeZeroAddress();
9+
error ListingNotFound();
10+
error NotCreator();
11+
error BeforeCreateFailed();
12+
error InnerIdZero();
13+
error AfterCreateFailed();
14+
error BeforePreBuyFailed();
15+
error OnPreBuyFailed();
16+
error AfterPreBuyFailed();
17+
error BeforeSaleFailed();
18+
error OnSaleFailed();
19+
error AfterSaleFailed();
20+
error BeforeCloseFailed();
21+
error OnCloseFailed();
22+
error AfterCloseFailed();
23+
struct ListingPointer {
24+
address creator;
25+
address listingType; // contract implementing IListingType
26+
uint256 listingId; // ID inside the listing type contract
27+
}
28+
29+
uint256 public listingCount;
30+
mapping(uint256 => ListingPointer) public listings;
31+
32+
event ListingCreated(uint256 indexed id, address indexed creator, address indexed listingType, uint256 listingId);
33+
event ListingSold(uint256 indexed id, address indexed buyer);
34+
event ListingClosed(uint256 indexed id, address indexed caller);
35+
event ListingPreBuy(uint256 indexed id, address indexed buyer);
36+
37+
modifier onlyListing(uint256 id) {
38+
if (listings[id].listingType == address(0)) revert ListingNotFound();
39+
_;
40+
}
41+
42+
function createListing(address listingType, bytes calldata data) external returns (uint256 id) {
43+
if (listingType == address(0)) revert ListingTypeZeroAddress();
44+
45+
// lifecycle: beforeCreate -> onCreate -> afterCreate
46+
if (!IListingType(listingType).beforeCreate(data)) revert BeforeCreateFailed();
47+
uint256 innerId = IListingType(listingType).onCreate(msg.sender, data);
48+
if (innerId == 0) revert InnerIdZero();
49+
50+
id = ++listingCount;
51+
listings[id] = ListingPointer({ creator: msg.sender, listingType: listingType, listingId: innerId });
52+
53+
if (!IListingType(listingType).afterCreate(innerId, data)) revert AfterCreateFailed();
54+
emit ListingCreated(id, msg.sender, listingType, innerId);
55+
}
56+
57+
function preBuyAction(uint256 id, bytes calldata data) external payable onlyListing(id) {
58+
ListingPointer memory ptr = listings[id];
59+
60+
if (!IListingType(ptr.listingType).beforePreBuy(ptr.listingId, msg.sender, data)) revert BeforePreBuyFailed();
61+
if (!IListingType(ptr.listingType).onPreBuy{ value: msg.value }(ptr.listingId, msg.sender, data))
62+
revert OnPreBuyFailed();
63+
if (!IListingType(ptr.listingType).afterPreBuy(ptr.listingId, msg.sender, data)) revert AfterPreBuyFailed();
64+
65+
emit ListingPreBuy(id, msg.sender);
66+
}
67+
68+
function buyListing(uint256 id, bytes calldata data) external payable onlyListing(id) {
69+
ListingPointer memory ptr = listings[id];
70+
71+
// lifecycle: beforeSale -> onSale -> afterSale
72+
if (!IListingType(ptr.listingType).beforeSale(ptr.listingId, msg.sender, data)) revert BeforeSaleFailed();
73+
if (!IListingType(ptr.listingType).onSale{ value: msg.value }(ptr.listingId, msg.sender, data))
74+
revert OnSaleFailed();
75+
if (!IListingType(ptr.listingType).afterSale(ptr.listingId, msg.sender, data)) revert AfterSaleFailed();
76+
77+
emit ListingSold(id, msg.sender);
78+
}
79+
80+
function closeListing(uint256 id, bytes calldata data) external onlyListing(id) {
81+
ListingPointer memory ptr = listings[id];
82+
if (ptr.creator != msg.sender) revert NotCreator();
83+
84+
if (!IListingType(ptr.listingType).beforeClose(ptr.listingId, msg.sender, data)) revert BeforeCloseFailed();
85+
if (!IListingType(ptr.listingType).onClose(ptr.listingId, msg.sender, data)) revert OnCloseFailed();
86+
if (!IListingType(ptr.listingType).afterClose(ptr.listingId, msg.sender, data)) revert AfterCloseFailed();
87+
emit ListingClosed(id, msg.sender);
88+
}
89+
90+
function getListing(
91+
uint256 id
92+
) external view onlyListing(id) returns (ListingPointer memory pointer, bytes memory data) {
93+
pointer = listings[id];
94+
data = IListingType(pointer.listingType).getListing(pointer.listingId);
95+
}
96+
}
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
//SPDX-License-Identifier: MIT
2+
pragma solidity >=0.8.0 <0.9.0;
3+
4+
import { IListingType } from "./IListingType.sol";
5+
6+
interface IERC20 {
7+
function transfer(address to, uint256 value) external returns (bool);
8+
function transferFrom(address from, address to, uint256 value) external returns (bool);
9+
}
10+
11+
contract SimpleListings is IListingType {
12+
// Custom errors
13+
error PriceZero();
14+
error IpfsHashEmpty();
15+
error NotActive();
16+
error IncorrectEth();
17+
error NoEthWithErc20();
18+
error Erc20TransferFailed();
19+
error NotCreator();
20+
error AlreadyClosed();
21+
error NotMarketplace();
22+
error MarketplaceZeroAddress();
23+
error EthSendFailed();
24+
error BeforeCloseFailed();
25+
error OnCloseFailed();
26+
error AfterCloseFailed();
27+
struct SimpleListing {
28+
address creator;
29+
address paymentToken; // address(0) for ETH, ERC20 otherwise
30+
uint256 price;
31+
string ipfsHash;
32+
bool active;
33+
}
34+
35+
address public immutable marketplace;
36+
uint256 public simpleListingCount;
37+
mapping(uint256 => SimpleListing) public listings;
38+
39+
event SimpleListingCreated(
40+
uint256 indexed listingId,
41+
address indexed creator,
42+
address paymentToken,
43+
uint256 price,
44+
string ipfsHash
45+
);
46+
event SimpleListingSold(uint256 indexed listingId, address indexed buyer, uint256 price, address paymentToken);
47+
event SimpleListingClosed(uint256 indexed listingId, address indexed caller);
48+
49+
constructor(address _marketplace) {
50+
if (_marketplace == address(0)) revert MarketplaceZeroAddress();
51+
marketplace = _marketplace;
52+
}
53+
54+
modifier onlyMarketplace() {
55+
if (msg.sender != marketplace) revert NotMarketplace();
56+
_;
57+
}
58+
59+
// View helpers
60+
function getListing(uint256 listingId) external view returns (bytes memory data) {
61+
SimpleListing memory l = listings[listingId];
62+
return abi.encode(l.creator, l.paymentToken, l.price, l.ipfsHash, l.active);
63+
}
64+
65+
// Creation lifecycle
66+
function beforeCreate(bytes calldata data) external view onlyMarketplace returns (bool) {
67+
// data encoding: abi.encode(address paymentToken, uint256 price, string ipfsHash)
68+
(, uint256 price, string memory ipfsHash) = abi.decode(data, (address, uint256, string));
69+
if (price == 0) revert PriceZero();
70+
if (bytes(ipfsHash).length == 0) revert IpfsHashEmpty();
71+
// paymentToken can be zero for ETH or any ERC20 address
72+
return true;
73+
}
74+
75+
function onCreate(address creator, bytes calldata data) external onlyMarketplace returns (uint256 listingId) {
76+
(address paymentToken, uint256 price, string memory ipfsHash) = abi.decode(data, (address, uint256, string));
77+
listingId = ++simpleListingCount;
78+
listings[listingId] = SimpleListing({
79+
creator: creator,
80+
paymentToken: paymentToken,
81+
price: price,
82+
ipfsHash: ipfsHash,
83+
active: true
84+
});
85+
emit SimpleListingCreated(listingId, creator, paymentToken, price, ipfsHash);
86+
}
87+
88+
function afterCreate(uint256 /*listingId*/, bytes calldata /*data*/) external view onlyMarketplace returns (bool) {
89+
return true;
90+
}
91+
92+
// Sale lifecycle
93+
function beforeSale(
94+
uint256 listingId,
95+
address /*buyer*/,
96+
bytes calldata /*data*/
97+
) external view onlyMarketplace returns (bool) {
98+
SimpleListing memory l = listings[listingId];
99+
if (!l.active) revert NotActive();
100+
return true;
101+
}
102+
103+
function onSale(
104+
uint256 listingId,
105+
address buyer,
106+
bytes calldata /*data*/
107+
) external payable onlyMarketplace returns (bool) {
108+
SimpleListing storage l = listings[listingId];
109+
if (!l.active) revert NotActive();
110+
111+
if (l.paymentToken == address(0)) {
112+
if (msg.value != l.price) revert IncorrectEth();
113+
// forward ETH directly to creator
114+
(bool sent, ) = l.creator.call{ value: msg.value }("");
115+
if (!sent) revert EthSendFailed();
116+
} else {
117+
if (msg.value != 0) revert NoEthWithErc20();
118+
// buyer must approve this contract to spend price amount
119+
bool ok = IERC20(l.paymentToken).transferFrom(buyer, l.creator, l.price);
120+
if (!ok) revert Erc20TransferFailed();
121+
}
122+
123+
emit SimpleListingSold(listingId, buyer, l.price, l.paymentToken);
124+
return true;
125+
}
126+
127+
function afterSale(
128+
uint256 listingId,
129+
address /*buyer*/,
130+
bytes calldata /*data*/
131+
) external onlyMarketplace returns (bool) {
132+
SimpleListing storage l = listings[listingId];
133+
if (!l.active) revert NotActive();
134+
l.active = false;
135+
return true;
136+
}
137+
138+
// Pre-buy lifecycle (no-op for simple listings)
139+
function beforePreBuy(
140+
uint256 /*listingId*/,
141+
address /*buyer*/,
142+
bytes calldata /*data*/
143+
) external view onlyMarketplace returns (bool) {
144+
return true;
145+
}
146+
147+
function onPreBuy(
148+
uint256 /*listingId*/,
149+
address /*buyer*/,
150+
bytes calldata /*data*/
151+
) external payable onlyMarketplace returns (bool) {
152+
return true;
153+
}
154+
155+
function afterPreBuy(
156+
uint256 /*listingId*/,
157+
address /*buyer*/,
158+
bytes calldata /*data*/
159+
) external view onlyMarketplace returns (bool) {
160+
return true;
161+
}
162+
163+
// Admin lifecycle
164+
function beforeClose(
165+
uint256 listingId,
166+
address caller,
167+
bytes calldata /*data*/
168+
) external view onlyMarketplace returns (bool) {
169+
SimpleListing memory l = listings[listingId];
170+
if (l.creator != caller) revert NotCreator();
171+
if (!l.active) revert AlreadyClosed();
172+
return true;
173+
}
174+
175+
function onClose(
176+
uint256 listingId,
177+
address caller,
178+
bytes calldata /*data*/
179+
) external onlyMarketplace returns (bool) {
180+
SimpleListing storage l = listings[listingId];
181+
if (l.creator != caller) revert NotCreator();
182+
if (!l.active) revert AlreadyClosed();
183+
l.active = false;
184+
return true;
185+
}
186+
187+
function afterClose(
188+
uint256 listingId,
189+
address caller,
190+
bytes calldata /*data*/
191+
) external onlyMarketplace returns (bool) {
192+
emit SimpleListingClosed(listingId, caller);
193+
return true;
194+
}
195+
196+
// Backward compatibility
197+
function closeListing(uint256 listingId, address caller) external onlyMarketplace returns (bool) {
198+
if (!this.beforeClose(listingId, caller, "")) revert BeforeCloseFailed();
199+
if (!this.onClose(listingId, caller, "")) revert OnCloseFailed();
200+
if (!this.afterClose(listingId, caller, "")) revert AfterCloseFailed();
201+
return true;
202+
}
203+
}

0 commit comments

Comments
 (0)