-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathDatasetMarketplace.sol
More file actions
251 lines (219 loc) · 8.25 KB
/
DatasetMarketplace.sol
File metadata and controls
251 lines (219 loc) · 8.25 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import { ReentrancyGuard } from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
/**
* @title DatasetMarketplace
* @notice Marketplace for buying/selling completed labeled datasets stored on Filecoin
* @dev 15% platform fee, 85% to curator
*/
contract DatasetMarketplace is ReentrancyGuard {
using SafeERC20 for IERC20;
// ------------------------------------------------------------
// Constants
// ------------------------------------------------------------
IERC20 public immutable CUSD;
address public immutable owner;
uint256 public constant PLATFORM_FEE_BPS = 1500; // 15% in basis points
uint256 public constant BPS_DENOMINATOR = 10000;
// ------------------------------------------------------------
// State
// ------------------------------------------------------------
struct Listing {
uint256 datasetId; // Off-chain dataset ID (from Supabase)
address curator; // Address that created the dataset
uint256 price; // Price in cUSD (wei)
bool active; // Is listing active?
string filecoinCIDs; // JSON string of Filecoin CIDs (array of {filename, cid, annotations_cid})
}
uint256 public nextListingId;
mapping(uint256 => Listing) public listings;
mapping(uint256 => mapping(address => bool)) public hasPurchased; // listingId => buyer => purchased
uint256 public totalPlatformFees; // Accumulated fees for owner to withdraw
// ------------------------------------------------------------
// Events
// ------------------------------------------------------------
event DatasetListed(
uint256 indexed listingId,
uint256 indexed datasetId,
address indexed curator,
uint256 price,
string filecoinCIDs
);
event DatasetPurchased(
uint256 indexed listingId,
address indexed buyer,
uint256 price,
uint256 platformFee,
uint256 curatorPayout
);
event ListingDeactivated(uint256 indexed listingId);
event PlatformFeesWithdrawn(address indexed owner, uint256 amount);
// ------------------------------------------------------------
// Constructor
// ------------------------------------------------------------
constructor(IERC20 cusdToken) {
CUSD = cusdToken;
owner = msg.sender;
}
// ------------------------------------------------------------
// Modifiers
// ------------------------------------------------------------
modifier onlyOwner() {
require(msg.sender == owner, "Not owner");
_;
}
modifier listingExists(uint256 listingId) {
require(listingId < nextListingId, "Listing does not exist");
_;
}
modifier listingActive(uint256 listingId) {
require(listings[listingId].active, "Listing not active");
_;
}
// ------------------------------------------------------------
// List Dataset for Sale
// ------------------------------------------------------------
/**
* @notice List a completed dataset for sale on the marketplace
* @param datasetId The off-chain dataset ID (from Supabase)
* @param price Price in cUSD (wei)
* @param filecoinCIDs JSON string containing array of Filecoin CIDs
* @return listingId The ID of the created listing
*/
function listDataset(
uint256 datasetId,
uint256 price,
string calldata filecoinCIDs
) external returns (uint256 listingId) {
require(price > 0, "Price must be > 0");
require(bytes(filecoinCIDs).length > 0, "Must provide Filecoin CIDs");
listingId = nextListingId++;
listings[listingId] = Listing({
datasetId: datasetId,
curator: msg.sender,
price: price,
active: true,
filecoinCIDs: filecoinCIDs
});
emit DatasetListed(listingId, datasetId, msg.sender, price, filecoinCIDs);
}
// ------------------------------------------------------------
// Buy Dataset
// ------------------------------------------------------------
/**
* @notice Purchase a listed dataset
* @dev Transfers cUSD: 15% to platform, 85% to curator
* @param listingId The ID of the listing to purchase
*/
function buyDataset(uint256 listingId)
external
nonReentrant
listingExists(listingId)
listingActive(listingId)
{
require(!hasPurchased[listingId][msg.sender], "Already purchased");
Listing storage listing = listings[listingId];
uint256 price = listing.price;
// Calculate fees
uint256 platformFee = (price * PLATFORM_FEE_BPS) / BPS_DENOMINATOR; // 15%
uint256 curatorPayout = price - platformFee; // 85%
// Mark as purchased
hasPurchased[listingId][msg.sender] = true;
// Accumulate platform fees
totalPlatformFees += platformFee;
// Transfer cUSD from buyer
CUSD.safeTransferFrom(msg.sender, address(this), price);
// Pay curator immediately
CUSD.safeTransfer(listing.curator, curatorPayout);
emit DatasetPurchased(listingId, msg.sender, price, platformFee, curatorPayout);
}
// ------------------------------------------------------------
// View Functions
// ------------------------------------------------------------
/**
* @notice Get Filecoin CIDs for a purchased dataset
* @param listingId The listing ID
* @return filecoinCIDs JSON string of Filecoin CIDs
*/
function getDatasetFiles(uint256 listingId)
external
view
listingExists(listingId)
returns (string memory filecoinCIDs)
{
require(
hasPurchased[listingId][msg.sender] || msg.sender == listings[listingId].curator,
"Not authorized - must purchase dataset"
);
return listings[listingId].filecoinCIDs;
}
/**
* @notice Check if an address has purchased a dataset
* @param listingId The listing ID
* @param buyer The buyer address
* @return purchased True if purchased
*/
function hasUserPurchased(uint256 listingId, address buyer)
external
view
listingExists(listingId)
returns (bool purchased)
{
return hasPurchased[listingId][buyer];
}
/**
* @notice Get listing details
* @param listingId The listing ID
*/
function getListing(uint256 listingId)
external
view
listingExists(listingId)
returns (
uint256 datasetId,
address curator,
uint256 price,
bool active
)
{
Listing memory listing = listings[listingId];
return (listing.datasetId, listing.curator, listing.price, listing.active);
}
// ------------------------------------------------------------
// Curator Management
// ------------------------------------------------------------
/**
* @notice Deactivate a listing (curator only)
* @param listingId The listing ID to deactivate
*/
function deactivateListing(uint256 listingId)
external
listingExists(listingId)
listingActive(listingId)
{
require(msg.sender == listings[listingId].curator, "Not curator");
listings[listingId].active = false;
emit ListingDeactivated(listingId);
}
// ------------------------------------------------------------
// Owner Functions (Platform Fee Withdrawal)
// ------------------------------------------------------------
/**
* @notice Withdraw accumulated platform fees (owner only)
*/
function withdrawPlatformFees() external onlyOwner {
uint256 amount = totalPlatformFees;
require(amount > 0, "No fees to withdraw");
totalPlatformFees = 0;
CUSD.safeTransfer(owner, amount);
emit PlatformFeesWithdrawn(owner, amount);
}
/**
* @notice View accumulated platform fees
*/
function getPlatformFees() external view returns (uint256) {
return totalPlatformFees;
}
}