Skip to content

Instantly share code, notes, and snippets.

@coderwithsense
Created June 19, 2025 12:56
Show Gist options
  • Save coderwithsense/ca14e2e0cba046962cc27a28eacd954d to your computer and use it in GitHub Desktop.
Save coderwithsense/ca14e2e0cba046962cc27a28eacd954d to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.30+commit.73712a01.js&optimize=false&runs=200&gist=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";
contract PredictionGame {
enum Side { BULL, BEAR }
address public immutable creator; // e.g., 0xYourWallet
string public tokenPair; // e.g., "ETH/USD"
uint256 public immutable targetPrice; // e.g., 2900
uint256 public immutable expiry; // e.g., block.timestamp + 2 days
uint256 public immutable creatorFee; // e.g., 100 (1%)
uint256 public immutable minBetAmount; // e.g., 0.01 ether
AggregatorV3Interface public immutable priceFeed;
uint256 public snapshotPrice;
bool public snapshotTaken;
bool public creatorWithdrawn;
mapping(address => uint256) public bullBets;
mapping(address => uint256) public bearBets;
uint256 public totalBull;
uint256 public totalBear;
event Mint(address indexed user, Side side, uint256 amount);
event Burn(address indexed user, Side side, uint256 amount, uint256 fee);
event SnapshotTaken(uint256 price);
event RewardClaimed(address indexed user, uint256 amount);
event CreatorFeeWithdrawn(address indexed creator, uint256 amount);
constructor(
address _creator, // e.g., 0xCreator
string memory _tokenPair, // e.g., "ETH/USD"
uint256 _targetPrice, // e.g., 2900
uint256 _expiry, // e.g., block.timestamp + 2 days
uint256 _creatorFee, // e.g., 100 = 1%
uint256 _minBetAmount, // e.g., 0.01 ether
address _priceFeed // e.g., Chainlink ETH/USD feed address
) {
require(_creator != address(0), "Invalid creator");
require(_expiry > block.timestamp, "Expiry must be future");
require(_creatorFee <= 1000, "Max 10% creator fee");
require(_minBetAmount > 0, "Minimum bet must be > 0");
creator = _creator;
tokenPair = _tokenPair;
targetPrice = _targetPrice;
expiry = _expiry;
creatorFee = _creatorFee;
minBetAmount = _minBetAmount;
priceFeed = AggregatorV3Interface(_priceFeed);
}
modifier onlyBeforeExpiry() {
require(block.timestamp < expiry, "Market expired");
_;
}
modifier onlyAfterExpiry() {
require(block.timestamp >= expiry, "Market not expired");
_;
}
function getLatestPrice() public view returns (uint256) {
(, int256 price,,,) = priceFeed.latestRoundData();
require(price > 0, "Invalid price");
uint8 decimals = priceFeed.decimals();
return uint256(price) / (10 ** decimals);
}
function mintBull() external payable onlyBeforeExpiry {
require(msg.value >= minBetAmount, "Bet below minimum");
bullBets[msg.sender] += msg.value;
totalBull += msg.value;
emit Mint(msg.sender, Side.BULL, msg.value);
}
function mintBear() external payable onlyBeforeExpiry {
require(msg.value >= minBetAmount, "Bet below minimum");
bearBets[msg.sender] += msg.value;
totalBear += msg.value;
emit Mint(msg.sender, Side.BEAR, msg.value);
}
function burnBull(uint256 amount) external onlyBeforeExpiry {
require(bullBets[msg.sender] >= amount, "Insufficient BULL balance");
uint256 fee = calculateBurnFee();
uint256 refund = amount - ((amount * fee) / 10000);
bullBets[msg.sender] -= amount;
totalBull -= amount;
payable(msg.sender).transfer(refund);
emit Burn(msg.sender, Side.BULL, amount, fee);
}
function burnBear(uint256 amount) external onlyBeforeExpiry {
require(bearBets[msg.sender] >= amount, "Insufficient BEAR balance");
uint256 fee = calculateBurnFee();
uint256 refund = amount - ((amount * fee) / 10000);
bearBets[msg.sender] -= amount;
totalBear -= amount;
payable(msg.sender).transfer(refund);
emit Burn(msg.sender, Side.BEAR, amount, fee);
}
function takeSnapshot() external onlyAfterExpiry {
require(!snapshotTaken, "Snapshot already taken");
snapshotPrice = getLatestPrice();
snapshotTaken = true;
emit SnapshotTaken(snapshotPrice);
}
function claim() external onlyAfterExpiry {
require(snapshotTaken, "Snapshot not taken");
bool bullWins = snapshotPrice > targetPrice;
uint256 userBet;
uint256 payout;
uint256 rewardPool = totalBull + totalBear;
uint256 totalWinning = bullWins ? totalBull : totalBear;
if (bullWins) {
userBet = bullBets[msg.sender];
require(userBet > 0, "Not a winner");
bullBets[msg.sender] = 0;
} else {
userBet = bearBets[msg.sender];
require(userBet > 0, "Not a winner");
bearBets[msg.sender] = 0;
}
uint256 creatorCut = (rewardPool * creatorFee) / 10000;
payout = (rewardPool - creatorCut) * userBet / totalWinning;
payable(msg.sender).transfer(payout);
emit RewardClaimed(msg.sender, payout);
}
function withdrawCreatorFee() external onlyAfterExpiry {
require(msg.sender == creator, "Only creator");
require(!creatorWithdrawn, "Already withdrawn");
uint256 rewardPool = totalBull + totalBear;
uint256 feeAmount = (rewardPool * creatorFee) / 10000;
creatorWithdrawn = true;
payable(creator).transfer(feeAmount);
emit CreatorFeeWithdrawn(creator, feeAmount);
}
function calculateBurnFee() public view returns (uint256) {
uint256 timeLeft = expiry > block.timestamp ? expiry - block.timestamp : 0;
uint256 maxFee = 300; // 3%
uint256 minFee = 50; // 0.5%
if (timeLeft > 1 days) return minFee;
if (timeLeft < 1 hours) return maxFee;
return minFee + ((maxFee - minFee) * (1 days - timeLeft)) / (1 days - 1 hours);
}
receive() external payable {
revert("Use mint functions");
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment