Social Challenge Contract (Fun + Accountability)
What it does: Friends commit to challenges (fitness, reading, learning). Failures result in automatic penalties or donations to charity.
Why it matters: Adds accountability while making habits enjoyable.
How it works:
-
Participants stake funds.
-
Proof of completion submitted via oracles.
-
If someone fails, penalties execute automatically.
-
Leaderboards and badges kept on-chain.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
/**
* SOCIAL CHALLENGE CONTRACT
* Gamified accountability:
* Stake → Complete Challenge → Get Stake Back
* Fail → Stake goes to Charity
*/
contract SocialChallenge {
enum Status {
Open,
Active,
Completed,
Settled,
Cancelled
}
enum ProofStatus {
None,
Submitted,
Approved,
Rejected
}
struct Participant {
uint256 stake;
bool joined;
ProofStatus proofStatus;
string proofNote;
bool paidOut;
}
struct Challenge {
string title;
string description;
address organizer;
address charity;
uint256 minStake;
uint256 startTime;
uint256 endTime;
Status status;
address[] participants;
mapping(address => Participant) participantData;
address verifier; // optional third-party verifier
bool autoApprove; // organizer verifies if false, else verifier/organizer
}
uint256 public challengeCount;
mapping(uint256 => Challenge) private challenges;
event ChallengeCreated(
uint256 indexed challengeId,
address indexed organizer,
string title,
uint256 startTime,
uint256 endTime
);
event JoinedChallenge(
uint256 indexed challengeId,
address indexed participant,
uint256 stake
);
event ProofSubmitted(
uint256 indexed challengeId,
address indexed participant,
string note
);
event ProofReviewed(
uint256 indexed challengeId,
address indexed participant,
bool approved
);
event Settled(
uint256 indexed challengeId
);
modifier onlyOrganizer(uint256 challengeId) {
require(msg.sender == challenges[challengeId].organizer, "Not organizer");
_;
}
modifier onlyVerifierOrOrganizer(uint256 challengeId) {
Challenge storage c = challenges[challengeId];
require(
msg.sender == c.verifier || msg.sender == c.organizer,
"Not authorized"
);
_;
}
modifier challengeExists(uint256 challengeId) {
require(challengeId startTime, "Invalid times");
require(minStake > 0, "Min stake must be > 0");
uint256 id = challengeCount;
Challenge storage c = challenges[id];
c.title = title;
c.description = description;
c.organizer = msg.sender;
c.charity = charity;
c.minStake = minStake;
c.startTime = startTime;
c.endTime = endTime;
c.status = Status.Open;
c.verifier = verifier;
c.autoApprove = autoApprove;
challengeCount++;
emit ChallengeCreated(id, msg.sender, title, startTime, endTime);
return id;
}
/**
* Join challenge by staking ETH
*/
function join(uint256 challengeId)
external
payable
challengeExists(challengeId)
{
Challenge storage c = challenges[challengeId];
require(block.timestamp = c.minStake, "Not enough stake");
c.participants.push(msg.sender);
c.participantData[msg.sender] = Participant({
stake: msg.value,
joined: true,
proofStatus: ProofStatus.None,
proofNote: "",
paidOut: false
});
emit JoinedChallenge(challengeId, msg.sender, msg.value);
}
/**
* Organizer locks the challenge when it starts
*/
function activate(uint256 challengeId)
external
onlyOrganizer(challengeId)
challengeExists(challengeId)
{
Challenge storage c = challenges[challengeId];
require(block.timestamp >= c.startTime, "Too early");
require(c.status == Status.Open, "Not open");
c.status = Status.Active;
}
/**
* Submit proof (simple text or link)
* In a production system, proof would come via oracles (photos, APIs, etc.)
*/
function submitProof(uint256 challengeId, string calldata note)
external
challengeExists(challengeId)
{
Challenge storage c = challenges[challengeId];
Participant storage p = c.participantData[msg.sender];
require(c.status == Status.Active, "Challenge not active");
require(p.joined, "Not participant");
require(block.timestamp c.endTime, "Not finished yet");
require(
c.status == Status.Active || c.status == Status.Completed,
"Already settled"
);
c.status = Status.Settled;
uint256 totalToCharity = 0;
for (uint256 i = 0; i 0) {
payable(c.charity).transfer(totalToCharity);
}
emit Settled(challengeId);
}
/**
* View helpers
*/
function getChallenge(
uint256 challengeId
)
external
view
challengeExists(challengeId)
returns (
string memory,
string memory,
address,
address,
uint256,
uint256,
uint256,
Status,
address,
bool,
uint256
)
{
Challenge storage c = challenges[challengeId];
return (
c.title,
c.description,
c.organizer,
c.charity,
c.minStake,
c.startTime,
c.endTime,
c.status,
c.verifier,
c.autoApprove,
c.participants.length
);
}
function getParticipant(
uint256 challengeId,
address user
)
external
view
challengeExists(challengeId)
returns (
uint256,
bool,
ProofStatus,
string memory,
bool
)
{
Participant storage p = challenges[challengeId].participantData[user];
return (p.stake, p.joined, p.proofStatus, p.proofNote, p.paidOut);
}
}