Skip to content

Instantly share code, notes, and snippets.

@jadeden1999
Created December 1, 2023 14:02
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jadeden1999/33f37a85473a21483b8124151fed700b to your computer and use it in GitHub Desktop.
Save jadeden1999/33f37a85473a21483b8124151fed700b 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.22+commit.4fc1097e.js&optimize=false&runs=200&gist=
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
function transfer(address to, uint256 amount) external returns (bool);
function allowance(
address owner,
address spender
) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
}
interface IUniswapV2Router01 {
function factory() external pure returns (address);
function WETH() external pure returns (address);
function addLiquidity(
address tokenA,
address tokenB,
uint256 amountADesired,
uint256 amountBDesired,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB, uint256 liquidity);
function addLiquidityETH(
address token,
uint256 amountTokenDesired,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
)
external
payable
returns (uint256 amountToken, uint256 amountETH, uint256 liquidity);
function removeLiquidity(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline
) external returns (uint256 amountA, uint256 amountB);
function removeLiquidityETH(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountToken, uint256 amountETH);
function removeLiquidityWithPermit(
address tokenA,
address tokenB,
uint256 liquidity,
uint256 amountAMin,
uint256 amountBMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountA, uint256 amountB);
function removeLiquidityETHWithPermit(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountToken, uint256 amountETH);
function swapExactTokensForTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapTokensForExactTokens(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapExactETHForTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
function swapTokensForExactETH(
uint256 amountOut,
uint256 amountInMax,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapExactTokensForETH(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external returns (uint256[] memory amounts);
function swapETHForExactTokens(
uint256 amountOut,
address[] calldata path,
address to,
uint256 deadline
) external payable returns (uint256[] memory amounts);
function quote(
uint256 amountA,
uint256 reserveA,
uint256 reserveB
) external pure returns (uint256 amountB);
function getAmountOut(
uint256 amountIn,
uint256 reserveIn,
uint256 reserveOut
) external pure returns (uint256 amountOut);
function getAmountIn(
uint256 amountOut,
uint256 reserveIn,
uint256 reserveOut
) external pure returns (uint256 amountIn);
function getAmountsOut(
uint256 amountIn,
address[] calldata path
) external view returns (uint256[] memory amounts);
function getAmountsIn(
uint256 amountOut,
address[] calldata path
) external view returns (uint256[] memory amounts);
}
interface IUniswapV2Router02 is IUniswapV2Router01 {
function removeLiquidityETHSupportingFeeOnTransferTokens(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline
) external returns (uint256 amountETH);
function removeLiquidityETHWithPermitSupportingFeeOnTransferTokens(
address token,
uint256 liquidity,
uint256 amountTokenMin,
uint256 amountETHMin,
address to,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) external returns (uint256 amountETH);
function swapExactTokensForTokensSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
function swapExactETHForTokensSupportingFeeOnTransferTokens(
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external payable;
function swapExactTokensForETHSupportingFeeOnTransferTokens(
uint256 amountIn,
uint256 amountOutMin,
address[] calldata path,
address to,
uint256 deadline
) external;
}
contract Market is Ownable(msg.sender) {
struct OfferDetails {
uint256 _id; // index of offer
address _tokenA; // base token address
uint256 _amountA; // base token amount
address _tokenB; // quote token address
uint256 _amountB; // quote token amount
uint256 _endTime; // offer live time
address _creator; // creator address
address _claimer; // claimer address
uint8 _active; // active: 0, claimed: 1, cancelled: 2
bool _type; // 0: buy, 1: sell
bool _public; // 0: private, 1: public
}
mapping(uint256 => mapping(address => bool)) public claimWallets;
mapping(uint256 => uint256) public actualBaseTokenAmount;
mapping(uint256 => uint256) public actualQuoteTokenAmount;
// Uniswap router for token swap
address private constant UNISWAP_V2_ROUTER =
0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
// Dead address for burn of PEAR token
address private burnAddress = 0x000000000000000000000000000000000000dEaD;
// Pear token amount of burn for each offer creation
uint256 public pearBurnAmount;
// Pear token address (Goerli)
address public pearTokenContract;
// Flag for whitelist function
bool public whiteListRequired = false;
// Whitelisted token address
mapping(address => bool) public whiteListedToken;
// Total count of created offers
uint256 public totalOffers;
// Created offer list
mapping(uint256 => OfferDetails) public OfferId;
// Fee values
uint256 public txFee;
uint256 public ptFee;
// Reward pool size in ETH
uint256 public rewardCollected;
// Bonus pool size in ETH
uint256 public bonus;
// Platform pool size in ETH
uint256 public platformCollected;
// Reward threshold
uint256 public rewardThreshold;
// Reward distribution
bool public isRewardEnabled;
address[] private platformAdmins;
// Reward list
mapping(address => uint256) public rewardBalance;
// Bonus list
mapping(address => uint256) public bonusBalance;
// Top holder count for reward distribution
uint256 public topHolderForReward;
event PearTokenContractUpdated(address _address);
event PearBurnAmountUpdated(uint256 _amount);
event TotalFeeUpdated(uint256 _amount);
event PlatformFeeUpdated(uint256 _amount);
event RewardThresholdUpdated(uint256 _amount);
event TopHolderForRewardUpdated(uint256 _amount);
event BonusAmountIncreased(uint256 _increasedAmount, uint256 _totalAmount);
event OfferCreated(address _creator, uint256 _offerId);
event OfferClaimed(address _claimer, uint256 _offerId);
event OfferCancelled(address _creator, uint256 _offerId);
event WithdrawCompleted(
address _caller,
address _receiver,
uint256 _amount
);
event DistributionCompleted(
address _caller,
uint256 _count,
uint256 _amount
);
event BonusDistributionCompleted(
address _caller,
uint256 _count,
uint256 _amount
);
constructor(address _pearTokenContract) {
whiteListedToken[address(0x0)] = true;
// pearTokenContract = 0x5dCD6272C3cbb250823F0b7e6C618bce11B21f90; // ETH
// pearTokenContract = 0x8a13336A0ca3a5B2D2EEb2F70A187313e25cD4c4; // Goerli
// pearTokenContract = 0x8f5cCc621AF516a949AB1c57b8A085426481B8F6; // Ganache
pearTokenContract = _pearTokenContract;
pearBurnAmount = 50 * 1000000000000000000;
rewardThreshold = 1000; // $1000
topHolderForReward = 50;
txFee = 1; // 1%
ptFee = 50; // 50% of collected fee
isRewardEnabled = true;
platformAdmins.push(msg.sender);
}
fallback() external payable {}
receive() external payable {}
function whiteListToken(address _token) public onlyOwner {
whiteListedToken[_token] = true;
}
function flipRewardEnabled() public onlyOwner {
isRewardEnabled = !isRewardEnabled;
}
function flipWhiteList() public onlyOwner {
whiteListRequired = !whiteListRequired;
}
function setPearContract(address _address) public onlyOwner {
require(
_address != address(0x0) &&
_address != address(burnAddress) &&
_address != address(this),
"Pearswap token address is not valid"
);
pearTokenContract = _address;
emit PearTokenContractUpdated(_address);
}
function setPearBurnAmount(uint256 _value) public onlyOwner {
require(_value > 0, "value is not valid");
pearBurnAmount = _value;
emit PearBurnAmountUpdated(_value);
}
function setTotalFee(uint256 _value) public onlyOwner {
require(_value > 0 && _value < 100, "value is not valid");
txFee = _value;
emit TotalFeeUpdated(_value);
}
function setptFee(uint256 _value) public onlyOwner {
require(_value > 0 && _value < 100, "value is not valid");
ptFee = _value;
emit PlatformFeeUpdated(_value);
}
function setRewardThreshold(uint256 _value) public onlyOwner {
require(_value > 0, "value is not valid");
rewardThreshold = _value;
emit RewardThresholdUpdated(_value);
}
function setTopHolderForReward(uint256 _value) public onlyOwner {
require(_value > 0, "value is not valid");
topHolderForReward = _value;
emit TopHolderForRewardUpdated(_value);
}
function makeOffer(
address _tokenA,
uint256 _amountA,
address _tokenB,
uint256 _amountB,
uint256 _endTime,
bool _type,
bool _public,
address[] memory _claim
) public payable {
require(msg.sender == tx.origin, "Only EOA");
if (whiteListRequired) {
require(
whiteListedToken[_tokenA] == true,
"Token not allowed for sales yet"
);
}
require(_claim.length > 0, "Claim address must be provided");
for (uint i = 0; i < _claim.length; i++) {
require(
_claim[i] != address(0x0) &&
_claim[i] != burnAddress &&
_claim[i] != address(this),
"Claim address is not valid"
);
}
// check pear token allowance and balance
checkPearTokenBalance(msg.sender);
// burn PEAR token from msg.sender
IERC20(pearTokenContract).transferFrom(
msg.sender,
burnAddress,
pearBurnAmount
);
uint256 amountA;
if (_tokenA == address(0x0)) {
require(msg.value == _amountA, "You must send ETH equal to amount");
require(isContract(_tokenB), "Quote token must be a contract");
} else {
require(
isContract(_tokenA),
"Your offered token must be a contract"
);
checkTokenBalance(msg.sender, _tokenA, _amountA);
uint256 curBalance = IERC20(_tokenA).balanceOf(address(this));
IERC20(_tokenA).transferFrom(msg.sender, address(this), _amountA);
amountA = IERC20(_tokenA).balanceOf(address(this)) - curBalance;
}
OfferId[totalOffers]._id = totalOffers;
OfferId[totalOffers]._tokenA = _tokenA;
OfferId[totalOffers]._amountA = _amountA;
OfferId[totalOffers]._tokenB = _tokenB;
OfferId[totalOffers]._amountB = _amountB;
OfferId[totalOffers]._endTime = _endTime;
OfferId[totalOffers]._creator = msg.sender;
OfferId[totalOffers]._claimer = address(0x0);
OfferId[totalOffers]._active = 0;
OfferId[totalOffers]._type = _type;
OfferId[totalOffers]._public = _public;
if (_tokenA == address(0x0)) {
actualBaseTokenAmount[totalOffers] = _amountA;
} else {
actualBaseTokenAmount[totalOffers] = amountA;
}
if (_claim.length > 0) {
for (uint i = 0; i < _claim.length; i++) {
claimWallets[totalOffers][_claim[i]] = true;
}
}
emit OfferCreated(msg.sender, totalOffers);
totalOffers += 1;
}
function acceptOffer(uint256 _id) public payable {
require(msg.sender == tx.origin, "Only EOA");
require(OfferId[_id]._active == 0, "Offer already closed");
require(block.timestamp <= OfferId[_id]._endTime, "The time is up.");
require(claimWallets[_id][msg.sender] == true, "Permission denied");
uint256 feeCollected = 0;
uint256 curBalance;
address tokenA = OfferId[_id]._tokenA;
uint256 amountA = OfferId[_id]._amountA;
uint256 feeA = (amountA * txFee) / 100;
uint256 rawAmountA = actualBaseTokenAmount[_id] - feeA;
if (tokenA == address(0x0)) {
payable(msg.sender).transfer(rawAmountA);
feeCollected += feeA;
} else {
IERC20(tokenA).transfer(msg.sender, rawAmountA);
curBalance = address(this).balance;
_swapTokensForEth(tokenA, feeA);
feeCollected += (address(this).balance - curBalance);
}
address tokenB = OfferId[_id]._tokenB;
uint256 amountB = OfferId[_id]._amountB;
address creator = OfferId[_id]._creator;
uint256 feeB = (amountB * txFee) / 100;
uint256 rawAmountB = amountB - feeB;
if (tokenB == address(0x0)) {
require(
msg.value == amountB,
"Not enough ETH to proceed this offer"
);
payable(creator).transfer(rawAmountB);
feeCollected += feeB;
} else {
checkTokenBalance(msg.sender, tokenB, amountB);
curBalance = IERC20(tokenB).balanceOf(address(this));
IERC20(tokenB).transferFrom(msg.sender, address(this), amountB);
amountB = IERC20(tokenB).balanceOf(address(this)) - curBalance;
rawAmountB = amountB - feeB;
IERC20(tokenB).transfer(creator, rawAmountB);
curBalance = address(this).balance;
_swapTokensForEth(tokenB, feeB);
feeCollected += (address(this).balance - curBalance);
}
actualQuoteTokenAmount[_id] = amountB;
uint256 ptFeeCollected = (feeCollected * ptFee) / 100;
uint256 reFeeCollected = feeCollected - ptFeeCollected;
platformCollected += ptFeeCollected;
rewardCollected += reFeeCollected;
OfferId[_id]._claimer = msg.sender;
OfferId[_id]._active = 1;
emit OfferClaimed(msg.sender, _id);
}
function cancelOffer(uint256 _id) public payable {
require(msg.sender == tx.origin, "Only EOA");
require(OfferId[_id]._active == 0, "Offer already closed");
address tokenA = OfferId[_id]._tokenA;
uint256 amountA = OfferId[_id]._amountA;
address creator = OfferId[_id]._creator;
require(msg.sender == creator, "You are not the owner of this offer");
if (tokenA == address(0x0)) {
payable(creator).transfer(amountA);
} else {
IERC20(tokenA).transfer(creator, actualBaseTokenAmount[_id]);
}
OfferId[_id]._active = 2;
emit OfferCancelled(creator, _id);
}
function checkPearTokenBalance(address _account) internal view {
uint256 pearApproved = IERC20(pearTokenContract).allowance(
_account,
address(this)
);
require(
pearApproved >= pearBurnAmount,
"Insufficient allowance, Approve PEAR first"
);
uint256 pearBalance = IERC20(pearTokenContract).balanceOf(_account);
require(
pearBalance >= pearBurnAmount,
"You don't have enough PEAR balance"
);
}
function checkTokenBalance(
address _account,
address _token,
uint256 _amount
) internal view {
uint256 approved = IERC20(_token).allowance(_account, address(this));
require(
approved >= _amount,
"Insufficient allowance, Approve token first"
);
uint256 userBalance = IERC20(_token).balanceOf(_account);
require(userBalance >= _amount, "You don't have enough balance");
}
function isContract(address _account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return _account.code.length > 0;
}
function getCounts() external view returns (uint256) {
return totalOffers;
}
function getOfferDetail(
uint256 _id
) external view returns (OfferDetails memory) {
return OfferId[_id];
}
function getUserOffers(
address _account
) external view returns (OfferDetails[] memory) {
uint256 count = 0;
uint256 i = 0;
for (i = 0; i < totalOffers; i++) {
if (OfferId[i]._creator == _account) {
count++;
} else if (
OfferId[i]._active == 1 && OfferId[i]._claimer == _account
) {
count++;
}
}
OfferDetails[] memory result = new OfferDetails[](count);
uint256 index = 0;
for (i = 0; i < totalOffers; i++) {
if (OfferId[i]._creator == _account) {
result[index] = OfferId[i];
index++;
} else if (
OfferId[i]._active == 1 && OfferId[i]._claimer == _account
) {
result[index] = OfferId[i];
index++;
}
}
return result;
}
function getTotalOffers() external view returns (OfferDetails[] memory) {
OfferDetails[] memory result = new OfferDetails[](totalOffers);
for (uint256 i = 0; i < totalOffers; i++) {
result[i] = OfferId[i];
}
return result;
}
function isWhitelistedToken(address _token) external view returns (bool) {
return whiteListedToken[_token];
}
function isOfferCompleted(uint256 _id) external view returns (bool) {
return OfferId[_id]._active != 0;
}
function _swapTokensForEth(address _token, uint256 _amount) internal {
require(_amount > 0, "Swap amount cannot be zero");
address[] memory path = new address[](2);
path[0] = _token;
path[1] = IUniswapV2Router02(UNISWAP_V2_ROUTER).WETH();
IERC20(_token).approve(UNISWAP_V2_ROUTER, _amount);
IUniswapV2Router02(UNISWAP_V2_ROUTER)
.swapExactTokensForETHSupportingFeeOnTransferTokens(
_amount,
0,
path,
address(this),
block.timestamp
);
}
/** Admin functions */
function getPlatformFeeCollected() external view returns (uint256) {
return platformCollected;
}
function withdrawAdmin(address _to, uint256 _amount) public onlyOwner {
require(msg.sender == tx.origin, "Only EOA");
require(platformCollected > 0, "no platform earning");
require(
_to != address(0x0) && _to != burnAddress && _to != address(this),
"withdraw address is not valid"
);
require(_amount <= platformCollected, "amount is not valid");
payable(_to).transfer(_amount);
platformCollected -= _amount;
emit WithdrawCompleted(msg.sender, _to, _amount);
}
/** Reward functions for users */
function distributeReward(
address[] memory _addresses,
uint256[] memory _rate
) public onlyOwner {
require(msg.sender == tx.origin, "Only EOA");
require(isRewardEnabled == true, "reward distribution is disabled");
require(rewardCollected > 0, "reward pool is empty");
require(
_addresses.length == _rate.length &&
_addresses.length <= topHolderForReward,
"invalid parameter"
);
uint256 totalRate = 0;
for (uint i = 0; i < _addresses.length; i++) {
totalRate += _rate[i];
}
uint256 distributeValue = 0;
uint256 totalAmount = 0;
uint256 originRewardSize = rewardCollected;
for (uint i = 0; i < _addresses.length; i++) {
distributeValue = (originRewardSize * _rate[i]) / totalRate;
rewardBalance[_addresses[i]] += distributeValue;
rewardCollected -= distributeValue;
totalAmount += distributeValue;
}
emit DistributionCompleted(msg.sender, _addresses.length, totalAmount);
}
function getRewardDistributed(address _to) external view returns (uint256) {
return rewardBalance[_to];
}
function withdrawReward(address _to, uint256 _amount) public {
require(msg.sender == tx.origin, "Only EOA");
require(rewardBalance[msg.sender] > 0, "no reward");
require(
_to != address(0x0) && _to != burnAddress && _to != address(this),
"withdraw address is not valid"
);
require(_amount <= rewardBalance[msg.sender], "amount is not valid");
payable(_to).transfer(_amount);
rewardBalance[msg.sender] -= _amount;
emit WithdrawCompleted(msg.sender, _to, _amount);
}
/////////////////////// Feature: Admins ///////////////////////
/// @dev add an address as platform admin.
function addAdmin(address _address) external onlyOwner {
require(msg.sender == tx.origin, "Only EOA");
if (!isAdmin(_address)) {
platformAdmins.push(_address);
}
}
/// @dev remove an address from platform admins if any.
/// @return bool returns true if _address was already an admin and false if it wasn't.
function removeAdmin(address _address) external onlyOwner returns (bool) {
require(msg.sender == tx.origin, "Only EOA");
for (uint i = 0; i < platformAdmins.length; i++) {
if (platformAdmins[i] == _address) {
delete platformAdmins[i];
return true;
}
}
return false;
}
/// @dev get all admins of platform.
function getAdmins() external view onlyOwner returns (address[] memory) {
require(msg.sender == tx.origin, "Only EOA");
return platformAdmins;
}
/// @dev checks whether and address is an admin or not.
/// @return bool if is admin and false if isn't.
function isAdmin(address _address) internal view returns (bool) {
for (uint i = 0; i < platformAdmins.length; i++) {
if (platformAdmins[i] == _address) {
return true;
}
}
return false;
}
/// @dev checks whether or not an address is admin of platform
/// @return bool
function isPlatformAdmin(
address _address
) external view onlyOwner returns (bool) {
require(msg.sender == tx.origin, "Only EOA");
return isAdmin(_address);
}
/////////////////////// Feature: Bonus ///////////////////////
/// @dev deposits an amount as bonus.
function depositBonus() external payable {
require(msg.sender == tx.origin, "Only EOA");
require(isAdmin(msg.sender) == true, "access prohibited");
bonus += msg.value;
emit BonusAmountIncreased(msg.value, bonus);
}
/// @dev get total amount of undistributed bonus.
/// @return uint256
function getTotalUndistributedBonus() external view returns (uint256) {
return bonus;
}
/// @dev distribute bonus among an list of addresses.
/// @param _addresses a list of addresses to distribute bonus.
/// @param _rates a list of rates.
function distributeBonus(
address[] memory _addresses,
uint256[] memory _rates
) external onlyOwner {
require(msg.sender == tx.origin, "Only EOA");
require(bonus > 0, "bonus pool is empty");
uint256 totalRate = 0;
for (uint i = 0; i < _addresses.length; i++) {
totalRate += _rates[i];
}
uint256 distributeValue = 0;
uint256 totalAmount = 0;
uint256 originRewardSize = bonus;
for (uint i = 0; i < _addresses.length; i++) {
distributeValue = (originRewardSize * _rates[i]) / totalRate;
bonusBalance[_addresses[i]] += distributeValue;
bonus -= distributeValue;
totalAmount += distributeValue;
}
emit BonusDistributionCompleted(
msg.sender,
_addresses.length,
totalAmount
);
}
/// @dev get distributed bonus to an address
/// @return uint256
function getBonusDistributed(address _to) external view returns (uint256) {
return bonusBalance[_to];
}
/// @dev withdraw bonus balance of an address
function withdrawBonus(address _to, uint256 _amount) public {
require(msg.sender == tx.origin, "Only EOA");
require(bonusBalance[msg.sender] > 0, "no bonus reward");
require(
_to != address(0x0) && _to != burnAddress && _to != address(this),
"withdraw address is not valid"
);
require(_amount <= bonusBalance[msg.sender], "amount is not valid");
payable(_to).transfer(_amount);
bonusBalance[msg.sender] -= _amount;
emit WithdrawCompleted(msg.sender, _to, _amount);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment