Skip to content

Commit 7e3a41c

Browse files
authored
Merge pull request #352 from somgoli/Contest
Contest
2 parents 57c22c9 + f02bbba commit 7e3a41c

56 files changed

Lines changed: 13129 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Ethereum RPC URLs
2+
SEPOLIA_RPC_URL=https://sepolia.infura.io/v3/YOUR_INFURA_KEY
3+
GOERLI_RPC_URL=https://goerli.infura.io/v3/YOUR_INFURA_KEY
4+
5+
# Private Key (DO NOT commit actual private key to git)
6+
PRIVATE_KEY=your_private_key_here
7+
8+
# Etherscan API Key for contract verification
9+
ETHERSCAN_API_KEY=your_etherscan_api_key_here
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
node_modules/
2+
.env
3+
coverage/
4+
coverage.json
5+
typechain-types
6+
typechain
7+
cache
8+
artifacts
9+
.DS_Store
Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.20;
3+
4+
/**
5+
* @title CoCreation
6+
* @dev Allows multiple creators to collaboratively create puzzles with royalty sharing
7+
*/
8+
contract CoCreation {
9+
// Types
10+
enum Status {
11+
Draft,
12+
PendingSignatures,
13+
Published
14+
}
15+
16+
struct Creator {
17+
address creatorAddress;
18+
uint256 basisPoints; // Share in basis points (10000 = 100%)
19+
bool hasSigned;
20+
}
21+
22+
struct CoCreationData {
23+
uint256 puzzleId;
24+
Creator[] creators;
25+
Status status;
26+
uint256 signatureCount;
27+
mapping(address => bool) isCreator;
28+
}
29+
30+
// State
31+
uint256 public coCreationCounter;
32+
mapping(uint256 => CoCreationData) public coCreations;
33+
mapping(uint256 => mapping(address => bool)) public hasSigned;
34+
35+
// Events
36+
event CoCreationInitiated(
37+
uint256 indexed coCreationId,
38+
uint256 indexed puzzleId,
39+
address[] creators,
40+
uint256[] shares
41+
);
42+
event SignatureAdded(
43+
uint256 indexed coCreationId,
44+
address indexed creator
45+
);
46+
event SignatureWithdrawn(
47+
uint256 indexed coCreationId,
48+
address indexed creator
49+
);
50+
event PuzzlePublished(uint256 indexed coCreationId, uint256 indexed puzzleId);
51+
event RoyaltySplit(
52+
uint256 indexed coCreationId,
53+
address indexed creator,
54+
uint256 amount
55+
);
56+
57+
// Errors
58+
error InvalidShareTotal();
59+
error NotCreator();
60+
error AlreadySigned();
61+
error NotAllCreatorsSigned();
62+
error AlreadyPublished();
63+
error NotDraftOrPending();
64+
error InvalidCoCreationId();
65+
error UnauthorizedRoyaltyDistribution();
66+
67+
// Modifiers
68+
modifier onlyCreator(uint256 coCreationId) {
69+
if (!coCreations[coCreationId].isCreator[msg.sender]) {
70+
revert NotCreator();
71+
}
72+
_;
73+
}
74+
75+
modifier validCoCreation(uint256 coCreationId) {
76+
if (coCreationId == 0 || coCreationId > coCreationCounter) {
77+
revert InvalidCoCreationId();
78+
}
79+
_;
80+
}
81+
82+
/**
83+
* @dev Initialize a new co-creation collaboration
84+
* @param puzzleId The ID of the puzzle being co-created
85+
* @param creatorAddresses Array of creator addresses
86+
* @param basisPoints Array of basis points for each creator (must sum to 10000)
87+
*/
88+
function initiate(
89+
uint256 puzzleId,
90+
address[] calldata creatorAddresses,
91+
uint256[] calldata basisPoints
92+
) external returns (uint256) {
93+
require(
94+
creatorAddresses.length == basisPoints.length,
95+
"Arrays length mismatch"
96+
);
97+
require(creatorAddresses.length > 0, "No creators provided");
98+
99+
// Validate basis points sum to 10000
100+
uint256 totalBasisPoints = 0;
101+
for (uint256 i = 0; i < basisPoints.length; i++) {
102+
totalBasisPoints += basisPoints[i];
103+
}
104+
if (totalBasisPoints != 10000) {
105+
revert InvalidShareTotal();
106+
}
107+
108+
// Create new co-creation
109+
uint256 coCreationId = ++coCreationCounter;
110+
CoCreationData storage coCreation = coCreations[coCreationId];
111+
coCreation.puzzleId = puzzleId;
112+
coCreation.status = Status.PendingSignatures;
113+
coCreation.signatureCount = 0;
114+
115+
// Add creators
116+
for (uint256 i = 0; i < creatorAddresses.length; i++) {
117+
address creatorAddr = creatorAddresses[i];
118+
coCreation.creators.push(
119+
Creator({
120+
creatorAddress: creatorAddr,
121+
basisPoints: basisPoints[i],
122+
hasSigned: false
123+
})
124+
);
125+
coCreation.isCreator[creatorAddr] = true;
126+
}
127+
128+
// Lead creator (msg.sender) signs automatically
129+
_addSignature(coCreationId);
130+
131+
emit CoCreationInitiated(
132+
coCreationId,
133+
puzzleId,
134+
creatorAddresses,
135+
basisPoints
136+
);
137+
138+
return coCreationId;
139+
}
140+
141+
/**
142+
* @dev Sign off on the co-creation
143+
* @param coCreationId The ID of the co-creation
144+
*/
145+
function sign(uint256 coCreationId)
146+
external
147+
validCoCreation(coCreationId)
148+
onlyCreator(coCreationId)
149+
{
150+
CoCreationData storage coCreation = coCreations[coCreationId];
151+
require(coCreation.status != Status.Published, "Already published");
152+
153+
if (hasSigned[coCreationId][msg.sender]) {
154+
revert AlreadySigned();
155+
}
156+
157+
_addSignature(coCreationId);
158+
159+
emit SignatureAdded(coCreationId, msg.sender);
160+
}
161+
162+
/**
163+
* @dev Withdraw signature before all creators have signed
164+
* @param coCreationId The ID of the co-creation
165+
*/
166+
function withdrawSignature(uint256 coCreationId)
167+
external
168+
validCoCreation(coCreationId)
169+
onlyCreator(coCreationId)
170+
{
171+
CoCreationData storage coCreation = coCreations[coCreationId];
172+
require(coCreation.status == Status.PendingSignatures, "Not in pending state");
173+
require(hasSigned[coCreationId][msg.sender], "Not signed");
174+
175+
// Remove signature
176+
hasSigned[coCreationId][msg.sender] = false;
177+
coCreation.signatureCount--;
178+
179+
// Update creator's signed status
180+
for (uint256 i = 0; i < coCreation.creators.length; i++) {
181+
if (coCreation.creators[i].creatorAddress == msg.sender) {
182+
coCreation.creators[i].hasSigned = false;
183+
break;
184+
}
185+
}
186+
187+
// If signature count drops below threshold, revert to draft
188+
if (coCreation.signatureCount == 0) {
189+
coCreation.status = Status.Draft;
190+
}
191+
192+
emit SignatureWithdrawn(coCreationId, msg.sender);
193+
}
194+
195+
/**
196+
* @dev Publish the puzzle once all creators have signed
197+
* @param coCreationId The ID of the co-creation
198+
*/
199+
function publish(uint256 coCreationId)
200+
external
201+
validCoCreation(coCreationId)
202+
onlyCreator(coCreationId)
203+
{
204+
CoCreationData storage coCreation = coCreations[coCreationId];
205+
206+
if (coCreation.status == Status.Published) {
207+
revert AlreadyPublished();
208+
}
209+
210+
if (coCreation.signatureCount != coCreation.creators.length) {
211+
revert NotAllCreatorsSigned();
212+
}
213+
214+
coCreation.status = Status.Published;
215+
216+
emit PuzzlePublished(coCreationId, coCreation.puzzleId);
217+
}
218+
219+
/**
220+
* @dev Distribute royalties to creators based on their shares
221+
* @param coCreationId The ID of the co-creation
222+
* @param totalAmount Total royalty amount to distribute
223+
*/
224+
function distributeRoyalty(uint256 coCreationId, uint256 totalAmount)
225+
external
226+
validCoCreation(coCreationId)
227+
{
228+
CoCreationData storage coCreation = coCreations[coCreationId];
229+
230+
// Only published co-creations can receive royalties
231+
if (coCreation.status != Status.Published) {
232+
revert UnauthorizedRoyaltyDistribution();
233+
}
234+
235+
require(totalAmount > 0, "Amount must be greater than 0");
236+
237+
// Distribute to each creator
238+
for (uint256 i = 0; i < coCreation.creators.length; i++) {
239+
Creator memory creator = coCreation.creators[i];
240+
uint256 share = (totalAmount * creator.basisPoints) / 10000;
241+
242+
if (share > 0) {
243+
payable(creator.creatorAddress).transfer(share);
244+
emit RoyaltySplit(coCreationId, creator.creatorAddress, share);
245+
}
246+
}
247+
}
248+
249+
/**
250+
* @dev Get co-creation details
251+
* @param coCreationId The ID of the co-creation
252+
*/
253+
function getCoCreation(uint256 coCreationId)
254+
external
255+
view
256+
validCoCreation(coCreationId)
257+
returns (
258+
uint256 puzzleId,
259+
address[] memory creators,
260+
uint256[] memory shares,
261+
bool[] memory signedStatuses,
262+
Status status,
263+
uint256 signatureCount
264+
)
265+
{
266+
CoCreationData storage coCreation = coCreations[coCreationId];
267+
268+
creators = new address[](coCreation.creators.length);
269+
shares = new uint256[](coCreation.creators.length);
270+
signedStatuses = new bool[](coCreation.creators.length);
271+
272+
for (uint256 i = 0; i < coCreation.creators.length; i++) {
273+
creators[i] = coCreation.creators[i].creatorAddress;
274+
shares[i] = coCreation.creators[i].basisPoints;
275+
signedStatuses[i] = coCreation.creators[i].hasSigned;
276+
}
277+
278+
return (
279+
coCreation.puzzleId,
280+
creators,
281+
shares,
282+
signedStatuses,
283+
coCreation.status,
284+
coCreation.signatureCount
285+
);
286+
}
287+
288+
/**
289+
* @dev Internal function to add signature
290+
* @param coCreationId The ID of the co-creation
291+
*/
292+
function _addSignature(uint256 coCreationId) private {
293+
CoCreationData storage coCreation = coCreations[coCreationId];
294+
295+
hasSigned[coCreationId][msg.sender] = true;
296+
coCreation.signatureCount++;
297+
298+
// Update creator's signed status
299+
for (uint256 i = 0; i < coCreation.creators.length; i++) {
300+
if (coCreation.creators[i].creatorAddress == msg.sender) {
301+
coCreation.creators[i].hasSigned = true;
302+
break;
303+
}
304+
}
305+
}
306+
307+
/**
308+
* @dev Receive function to accept ETH
309+
*/
310+
receive() external payable {}
311+
}

0 commit comments

Comments
 (0)