-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: abraham with bonding curve and conviction
- Loading branch information
1 parent
818993b
commit 2d0cf23
Showing
11 changed files
with
996 additions
and
352 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,135 +1,184 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.17; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; | ||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; | ||
|
||
interface IMannaToken { | ||
function burnFrom(address account, uint256 amount) external; | ||
} | ||
|
||
interface IAbrahamNFT { | ||
function mintCreationNFT(address to, uint256 creationId) external; | ||
} | ||
|
||
contract Abraham is ERC1155, Ownable { | ||
address public mannaToken; | ||
address public abrahamNFT; | ||
uint256 public creationCounter; | ||
uint256 public endTimestamp; | ||
|
||
uint256 public minimumMannaSpend = 1e18; | ||
|
||
struct CreationData { | ||
uint256 praises; | ||
uint256 burns; | ||
uint256 blessings; | ||
uint256 totalMannaSpent; | ||
string image; // New field for image URL | ||
} | ||
|
||
struct UserStats { | ||
uint256 praiseCount; | ||
uint256 praiseMannaSpent; | ||
uint256 burnCount; | ||
uint256 burnMannaSpent; | ||
contract Abraham is Ownable { | ||
IERC20 public immutable manna; | ||
|
||
struct Creation { | ||
uint256 id; | ||
string metadataUri; | ||
uint256 totalStaked; | ||
uint256 praisePool; | ||
uint256 conviction; | ||
mapping(address => uint256) praiseBalance; | ||
mapping(address => uint256) stakeTime; | ||
} | ||
|
||
mapping(uint256 => CreationData) public creations; | ||
mapping(uint256 => mapping(address => UserStats)) public userParticipation; | ||
uint256 public creationCount; | ||
mapping(uint256 => Creation) public creations; | ||
uint256[] private _allCreationIds; | ||
|
||
event CreationReleased(uint256 indexed creationId, string image); // include image in event | ||
event Praised(uint256 indexed creationId, address indexed user, uint256 amount); | ||
event Burned(uint256 indexed creationId, address indexed user, uint256 amount); | ||
event Blessed(uint256 indexed creationId, address indexed user, uint256 amount); | ||
uint256 public initPraisePrice = 1e17; // 0.1 Manna | ||
uint256 public initUnpraisePrice = 1e17; // 0.1 Manna | ||
|
||
constructor(address _mannaToken, string memory uri_, address initialOwner) ERC1155(uri_) Ownable(initialOwner) { | ||
mannaToken = _mannaToken; | ||
endTimestamp = block.timestamp + (13 * 365 days); | ||
struct PraiseListing { | ||
uint256 creationId; | ||
address seller; | ||
uint256 amount; | ||
uint256 pricePerPraise; | ||
} | ||
|
||
modifier notEnded() { | ||
require(block.timestamp < endTimestamp, "Contract expired"); | ||
_; | ||
} | ||
PraiseListing[] public praiseListings; | ||
|
||
function setMinimumMannaSpend(uint256 newMin) external onlyOwner { | ||
minimumMannaSpend = newMin; | ||
event CreationAdded(uint256 indexed creationId, string metadataUri); | ||
event Praised(uint256 indexed creationId, address indexed user, uint256 pricePaid, uint256 unitsPraised); | ||
event Unpraised(uint256 indexed creationId, address indexed user, uint256 unitsUnpraised, uint256 mannaRefunded); | ||
event ConvictionUpdated(uint256 indexed creationId, uint256 newConviction); | ||
event PraiseListed(uint256 listingId, uint256 creationId, address indexed seller, uint256 amount, uint256 pricePerPraise); | ||
event PraiseSold(uint256 listingId, uint256 creationId, address indexed buyer, uint256 amount, uint256 totalCost); | ||
|
||
constructor(address _manna, address initialOwner) Ownable(initialOwner) { | ||
require(_manna != address(0), "Invalid token address"); | ||
manna = IERC20(_manna); | ||
} | ||
|
||
function setAbrahamNFT(address _abrahamNFT) external onlyOwner { | ||
abrahamNFT = _abrahamNFT; | ||
function newCreation(string calldata metadataUri) external onlyOwner { | ||
creationCount++; | ||
Creation storage c = creations[creationCount]; | ||
c.id = creationCount; | ||
c.metadataUri = metadataUri; | ||
_allCreationIds.push(creationCount); | ||
emit CreationAdded(creationCount, metadataUri); | ||
} | ||
|
||
function releaseCreation(string memory image) external onlyOwner notEnded { | ||
creationCounter += 1; | ||
function praise(uint256 creationId) external { | ||
Creation storage c = creations[creationId]; | ||
require(c.id > 0, "Creation does not exist"); | ||
|
||
creations[creationCounter] = CreationData({ | ||
praises: 0, | ||
burns: 0, | ||
blessings: 0, | ||
totalMannaSpent: 0, | ||
image: image | ||
}); | ||
uint256 currentStaked = c.totalStaked; | ||
uint256 priceForOne = initPraisePrice + (currentStaked * initPraisePrice); | ||
|
||
emit CreationReleased(creationCounter, image); | ||
bool transferred = manna.transferFrom(msg.sender, address(this), priceForOne); | ||
require(transferred, "Manna transfer failed"); | ||
|
||
require(abrahamNFT != address(0), "AbrahamNFT not set"); | ||
IAbrahamNFT(abrahamNFT).mintCreationNFT(owner(), creationCounter); | ||
} | ||
c.praisePool += priceForOne; | ||
|
||
function _spendManna(uint256 amount) internal { | ||
require(amount >= minimumMannaSpend, "Spend more Manna"); | ||
(bool success, ) = mannaToken.call( | ||
abi.encodeWithSignature("burnFrom(address,uint256)", msg.sender, amount) | ||
); | ||
require(success, "Manna burn failed"); | ||
_updateConvictionOnPraise(c, msg.sender); | ||
|
||
c.totalStaked = currentStaked + 1; | ||
c.praiseBalance[msg.sender]++; | ||
emit Praised(creationId, msg.sender, priceForOne, 1); | ||
} | ||
|
||
function praise(uint256 creationId, uint256 amount) external notEnded { | ||
require(creationId > 0 && creationId <= creationCounter, "Invalid creation"); | ||
_spendManna(amount); | ||
function unpraise(uint256 creationId) external { | ||
Creation storage c = creations[creationId]; | ||
require(c.id > 0, "Creation does not exist"); | ||
require(c.praiseBalance[msg.sender] > 0, "No praise to unpraise"); | ||
|
||
uint256 refundForOne = initUnpraisePrice; | ||
|
||
require(c.praisePool >= refundForOne, "Not enough in praise pool"); | ||
|
||
_updateConvictionOnUnpraise(c, msg.sender); | ||
|
||
creations[creationId].praises += 1; | ||
creations[creationId].totalMannaSpent += amount; | ||
c.praiseBalance[msg.sender]--; | ||
c.totalStaked--; | ||
c.praisePool -= refundForOne; | ||
|
||
userParticipation[creationId][msg.sender].praiseCount += 1; | ||
userParticipation[creationId][msg.sender].praiseMannaSpent += amount; | ||
bool sent = manna.transfer(msg.sender, refundForOne); | ||
require(sent, "Refund transfer failed"); | ||
|
||
_mint(msg.sender, creationId, amount, ""); | ||
emit Praised(creationId, msg.sender, amount); | ||
emit Unpraised(creationId, msg.sender, 1, refundForOne); | ||
} | ||
|
||
function burnCreation(uint256 creationId, uint256 amount) external notEnded { | ||
require(creationId > 0 && creationId <= creationCounter, "Invalid creation"); | ||
_spendManna(amount); | ||
function listPraiseForSale( | ||
uint256 creationId, | ||
uint256 amount, | ||
uint256 pricePerPraise | ||
) external { | ||
Creation storage c = creations[creationId]; | ||
require(c.id > 0, "Creation does not exist"); | ||
require(c.praiseBalance[msg.sender] >= amount, "Insufficient praises to sell"); | ||
|
||
praiseListings.push(PraiseListing({ | ||
creationId: creationId, | ||
seller: msg.sender, | ||
amount: amount, | ||
pricePerPraise: pricePerPraise | ||
})); | ||
|
||
uint256 listingId = praiseListings.length - 1; | ||
emit PraiseListed(listingId, creationId, msg.sender, amount, pricePerPraise); | ||
} | ||
|
||
function buyPraise(uint256 listingId, uint256 amount) external { | ||
PraiseListing storage listing = praiseListings[listingId]; | ||
require(listing.amount >= amount, "Not enough praises available"); | ||
|
||
creations[creationId].burns += 1; | ||
creations[creationId].totalMannaSpent += amount; | ||
uint256 totalCost = amount * listing.pricePerPraise; | ||
bool transferred = manna.transferFrom(msg.sender, listing.seller, totalCost); | ||
require(transferred, "Payment failed"); | ||
|
||
userParticipation[creationId][msg.sender].burnCount += 1; | ||
userParticipation[creationId][msg.sender].burnMannaSpent += amount; | ||
Creation storage c = creations[listing.creationId]; | ||
c.praiseBalance[msg.sender] += amount; | ||
c.praiseBalance[listing.seller] -= amount; | ||
|
||
_mint(msg.sender, creationId, amount, ""); | ||
emit Burned(creationId, msg.sender, amount); | ||
listing.amount -= amount; | ||
|
||
emit PraiseSold(listingId, listing.creationId, msg.sender, amount, totalCost); | ||
} | ||
|
||
function bless(uint256 creationId, uint256 amount) external notEnded { | ||
require(creationId > 0 && creationId <= creationCounter, "Invalid creation"); | ||
_spendManna(amount); | ||
function _updateConvictionOnPraise(Creation storage c, address user) internal { | ||
uint256 currentBalance = c.praiseBalance[user]; | ||
if (currentBalance > 0) { | ||
uint256 timeHeld = block.timestamp - c.stakeTime[user]; | ||
uint256 addedConviction = currentBalance * timeHeld; | ||
c.conviction += addedConviction; | ||
emit ConvictionUpdated(c.id, c.conviction); | ||
} | ||
c.stakeTime[user] = block.timestamp; | ||
} | ||
|
||
creations[creationId].blessings += 1; | ||
creations[creationId].totalMannaSpent += amount; | ||
function _updateConvictionOnUnpraise(Creation storage c, address user) internal { | ||
uint256 currentBalance = c.praiseBalance[user]; | ||
uint256 timeHeld = block.timestamp - c.stakeTime[user]; | ||
uint256 addedConviction = currentBalance * timeHeld; | ||
c.conviction += addedConviction; | ||
emit ConvictionUpdated(c.id, c.conviction); | ||
c.stakeTime[user] = block.timestamp; | ||
} | ||
|
||
function getCreation(uint256 creationId) | ||
external | ||
view | ||
returns ( | ||
uint256 id, | ||
string memory uri, | ||
uint256 totalStaked, | ||
uint256 praisePool, | ||
uint256 conviction | ||
) | ||
{ | ||
Creation storage c = creations[creationId]; | ||
id = c.id; | ||
uri = c.metadataUri; | ||
totalStaked = c.totalStaked; | ||
praisePool = c.praisePool; | ||
conviction = c.conviction; | ||
} | ||
|
||
_mint(msg.sender, creationId, amount, ""); | ||
emit Blessed(creationId, msg.sender, amount); | ||
function getUserPraise(uint256 creationId, address user) external view returns (uint256) { | ||
return creations[creationId].praiseBalance[user]; | ||
} | ||
|
||
function setMannaToken(address _mannaToken) external onlyOwner notEnded { | ||
mannaToken = _mannaToken; | ||
function allCreationIds() external view returns (uint256[] memory) { | ||
return _allCreationIds; | ||
} | ||
|
||
function setURI(string memory newuri) external onlyOwner { | ||
_setURI(newuri); | ||
function getPraiseListings() external view returns (PraiseListing[] memory) { | ||
return praiseListings; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.17; | ||
|
||
import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; | ||
import "@openzeppelin/contracts/access/Ownable.sol"; | ||
|
||
contract Manna is ERC20, Ownable { | ||
uint256 public constant INITIAL_SUPPLY = 1000000 * (10 ** 18); | ||
uint256 public constant MANNA_PRICE = 0.0001 ether; | ||
|
||
event BoughtManna(address indexed buyer, uint256 amount); | ||
event SoldManna(address indexed seller, uint256 mannaAmount, uint256 ethAmount); | ||
|
||
constructor(address initialOwner) ERC20("Manna", "MANNA") Ownable(initialOwner) { | ||
uint256 initialOwnerSupply = INITIAL_SUPPLY / 2; | ||
_mint(initialOwner, initialOwnerSupply); | ||
} | ||
|
||
function buyManna() external payable { | ||
require(msg.value >= MANNA_PRICE, "Insufficient Ether"); | ||
uint256 mannaAmount = (msg.value * (10 ** 18)) / MANNA_PRICE; | ||
require(totalSupply() + mannaAmount <= INITIAL_SUPPLY, "Manna supply cap reached"); | ||
|
||
_mint(msg.sender, mannaAmount); | ||
emit BoughtManna(msg.sender, mannaAmount); | ||
} | ||
|
||
function sellManna(uint256 mannaAmount) external { | ||
require(balanceOf(msg.sender) >= mannaAmount, "Not enough Manna"); | ||
uint256 ethAmount = (mannaAmount * MANNA_PRICE) / (10 ** 18); | ||
require(address(this).balance >= ethAmount, "Contract lacks Ether"); | ||
|
||
_burn(msg.sender, mannaAmount); | ||
|
||
(bool sent, ) = msg.sender.call{value: ethAmount}(""); | ||
require(sent, "Failed to send Ether"); | ||
|
||
emit SoldManna(msg.sender, mannaAmount, ethAmount); | ||
} | ||
|
||
function getContractBalances() external view returns (uint256 mannaBalance, uint256 ethBalance) { | ||
mannaBalance = balanceOf(address(this)); | ||
ethBalance = address(this).balance; | ||
} | ||
|
||
receive() external payable { | ||
this.buyManna{value: msg.value}(); | ||
} | ||
|
||
function burnFrom(address account, uint256 amount) external { | ||
require(balanceOf(account) >= amount, "Not enough Manna to create art"); | ||
_burn(account, amount); | ||
} | ||
} |
Oops, something went wrong.