文件 1 的 11:Album.sol
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./AlbumBuyoutManager.sol";
import "./AlbumNftManager.sol";
import "./AlbumTokenSaleManager.sol";
contract Album is
Ownable,
ERC721Holder,
AlbumBuyoutManager,
AlbumNftManager,
AlbumTokenSaleManager
{
IERC20 public token;
constructor(
address governance,
address _token,
address creator,
TokenSaleParams memory tokenSaleParams,
address[] memory _nftAddrs,
uint256[] memory _nftIds,
uint256 _minReservePrice
) AlbumTokenSaleManager(creator, tokenSaleParams) {
transferOwnership(governance);
token = IERC20(_token);
_addNfts(_nftAddrs, _nftIds);
_setMinReservePrice(_minReservePrice);
}
function addNfts(address[] memory _nfts, uint256[] memory _ids)
public
onlyOwner
{
_addNfts(_nfts, _ids);
}
function sendNfts(address to, uint256[] memory idxs) public onlyOwner {
_sendNfts(to, idxs);
}
function setTimeout(uint256 _timeout) public onlyOwner {
_setTimeout(_timeout);
}
function sendAllToSender() internal override {
address[] memory nfts = getNfts();
uint256[] memory ids = getIds();
bool[] memory sent = getSent();
for (uint256 i = 0; i < nfts.length; i++) {
if (!sent[i]) {
IERC721(nfts[i]).safeTransferFrom(
address(this),
msg.sender,
ids[i]
);
}
}
}
function setMinReservePrice(uint256 _minReservePrice) public onlyOwner {
_setMinReservePrice(_minReservePrice);
}
function setBuyout(address _buyer, uint256 _cost) public onlyOwner {
_setBuyout(_buyer, _cost);
}
function checkOwedAmount(uint256 _amount, uint256 buyoutCost)
internal
override
returns (uint256 owed)
{
token.transferFrom(msg.sender, address(this), _amount);
owed = (_amount * buyoutCost) / token.totalSupply();
}
function getToken() public view override returns (IERC20) {
return token;
}
}
文件 2 的 11:AlbumBuyoutManager.sol
pragma solidity ^0.8.0;
abstract contract AlbumBuyoutManager {
event BuyoutSet(address buyer, uint256 cost, uint256 end);
event Buyout(address buyer, uint256 cost);
event BuyoutPortionClaimed(address claimer, uint256 amount, uint256 owed);
event MinReservePriceSet(uint256 minReservePrice);
address private buyer;
uint256 private buyoutCost;
uint256 private buyoutEnd;
bool private bought;
uint256 private minReservePrice;
uint256 private timeout = 60 * 60 * 24 * 7;
function sendAllToSender() internal virtual;
function checkOwedAmount(uint256 _amount, uint256 _buyoutCost)
internal
virtual
returns (uint256 owed);
modifier noBuyout() {
require(!bought, "A buyout was already completed");
require(
block.timestamp >= buyoutEnd || buyer == address(0),
"A buyout is in progress"
);
_;
}
function getBuyoutData()
public
view
returns (
address _buyer,
uint256 _buyoutCost,
uint256 _buyoutEnd,
bool _bought,
uint256 _timeout,
uint256 _minReservePrice
)
{
return (buyer, buyoutCost, buyoutEnd, bought, timeout, minReservePrice);
}
function _setTimeout(uint256 _timeout) internal {
timeout = _timeout;
}
function _setMinReservePrice(uint256 _minReservePrice) internal {
minReservePrice = _minReservePrice;
emit MinReservePriceSet(_minReservePrice);
}
function _setBuyout(address _buyer, uint256 _cost) internal noBuyout {
require(
_cost >= minReservePrice,
"Album can't be bought out for amount less than minReservePrice!"
);
buyer = _buyer;
buyoutCost = _cost;
buyoutEnd = block.timestamp + timeout;
emit BuyoutSet(buyer, buyoutCost, buyoutEnd);
}
function buyout() public payable {
require(!bought, "Album has already been bought out");
require(msg.sender == buyer, "Caller is not the buyer.");
require(msg.value == buyoutCost, "Not enough ETH.");
require(block.timestamp < buyoutEnd, "Buyout timeout already passed.");
sendAllToSender();
bought = true;
emit Buyout(buyer, buyoutCost);
}
function claim(uint256 _amount) public {
require(bought, "No buyout yet.");
uint256 owed = checkOwedAmount(_amount, buyoutCost);
payable(msg.sender).transfer(owed);
emit BuyoutPortionClaimed(msg.sender, _amount, owed);
}
}
文件 3 的 11:AlbumNftManager.sol
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/IERC721.sol";
abstract contract AlbumNftManager {
event AddNfts(address[] nfts, uint256[] ids);
event SendNfts(address to, uint256[] idxs);
address[] private nfts;
uint256[] private ids;
bool[] private sent;
function getNfts() public view returns (address[] memory) {
return nfts;
}
function getIds() public view returns (uint256[] memory) {
return ids;
}
function getSent() public view returns (bool[] memory) {
return sent;
}
function _addNfts(address[] memory _nfts, uint256[] memory _ids) internal {
require(
_nfts.length == _ids.length,
"Input array lenghts don't match."
);
for (uint256 i = 0; i < _nfts.length; i++) {
address nftAddr = _nfts[i];
IERC721 nft = IERC721(nftAddr);
uint256 id = _ids[i];
address owner = nft.ownerOf(id);
if (owner != address(this)) {
nft.safeTransferFrom(owner, address(this), id);
}
nfts.push(nftAddr);
ids.push(id);
sent.push(false);
}
emit AddNfts(_nfts, _ids);
}
function _sendNfts(address to, uint256[] memory idxs) internal {
uint256 idx;
for (uint256 i = 0; i < idxs.length; i++) {
idx = idxs[i];
IERC721(nfts[idx]).safeTransferFrom(address(this), to, ids[idx]);
sent[idx] = true;
}
emit SendNfts(to, idxs);
}
}
文件 4 的 11:AlbumTokenSaleManager.sol
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
abstract contract AlbumTokenSaleManager {
event SaleInitialized(
address creator,
uint256 tokensPerMilliEth,
uint256 saleStart,
uint256 saleEnd,
uint256 numTokens
);
event TokenSale(address buyer, uint256 amount, uint256 paid);
event SaleSwept(uint256 saleProceeds, uint256 amountUnsold);
uint256 public constant MILLIETH = 1e15;
address public immutable CREATOR;
uint256 public immutable TOKENS_PER_WEI;
uint256 public immutable SALE_START;
uint256 public immutable SALE_END;
uint256 private saleProceeds;
uint256 private amountUnsold;
bool private swept;
modifier saleOver(bool want) {
bool isOver = block.timestamp >= SALE_END;
require(want == isOver, "Sale state is invalid for this method");
_;
}
struct TokenSaleParams {
uint256 price;
uint256 saleStart;
uint256 saleEnd;
uint256 numTokens;
}
constructor(address creator, TokenSaleParams memory params) {
require(params.saleStart < params.saleEnd);
CREATOR = creator;
TOKENS_PER_WEI = params.price;
SALE_START = params.saleStart;
SALE_END = params.saleEnd;
amountUnsold = params.numTokens;
emit SaleInitialized(
creator,
params.price,
params.saleStart,
params.saleEnd,
params.numTokens
);
}
function getToken() public view virtual returns (IERC20 token);
function buyTokens() public payable saleOver(false) {
require(block.timestamp >= SALE_START, "Sale has not started yet");
uint256 amount = msg.value * TOKENS_PER_WEI;
require(amountUnsold >= amount, "Attempted to purchase too many tokens!");
amountUnsold -= amount;
getToken().transfer(msg.sender, amount);
saleProceeds += msg.value;
emit TokenSale(msg.sender, amount, msg.value);
}
function sweepProceeds() public saleOver(true) {
require(!swept, "Already swept");
swept = true;
payable(CREATOR).transfer(saleProceeds);
getToken().transfer(CREATOR, amountUnsold);
emit SaleSwept(saleProceeds, amountUnsold);
}
function getTokenSaleData()
public
view
returns (
address creator,
uint256 tokensPerWei,
uint256 saleStart,
uint256 saleEnd,
uint256 _saleProceeds,
uint256 _amountUnsold,
bool _swept
)
{
return (
CREATOR,
TOKENS_PER_WEI,
SALE_START,
SALE_END,
saleProceeds,
amountUnsold,
swept
);
}
}
文件 5 的 11:Context.sol
pragma solidity ^0.8.0;
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
}
文件 6 的 11:ERC721Holder.sol
pragma solidity ^0.8.0;
import "../IERC721Receiver.sol";
contract ERC721Holder is IERC721Receiver {
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
}
文件 7 的 11:IERC165.sol
pragma solidity ^0.8.0;
interface IERC165 {
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
文件 8 的 11:IERC20.sol
pragma solidity ^0.8.0;
interface IERC20 {
function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address recipient, uint256 amount) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 amount) external returns (bool);
function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
}
文件 9 的 11:IERC721.sol
pragma solidity ^0.8.0;
import "../../utils/introspection/IERC165.sol";
interface IERC721 is IERC165 {
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
function safeTransferFrom(
address from,
address to,
uint256 tokenId
) external;
function transferFrom(
address from,
address to,
uint256 tokenId
) external;
function approve(address to, uint256 tokenId) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function setApprovalForAll(address operator, bool _approved) external;
function isApprovedForAll(address owner, address operator) external view returns (bool);
function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes calldata data
) external;
}
文件 10 的 11:IERC721Receiver.sol
pragma solidity ^0.8.0;
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
文件 11 的 11:Ownable.sol
pragma solidity ^0.8.0;
import "../utils/Context.sol";
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
constructor() {
_setOwner(_msgSender());
}
function owner() public view virtual returns (address) {
return _owner;
}
modifier onlyOwner() {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
_;
}
function renounceOwnership() public virtual onlyOwner {
_setOwner(address(0));
}
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_setOwner(newOwner);
}
function _setOwner(address newOwner) private {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
{
"compilationTarget": {
"contracts/Album.sol": "Album"
},
"evmVersion": "istanbul",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"governance","type":"address"},{"internalType":"address","name":"_token","type":"address"},{"internalType":"address","name":"creator","type":"address"},{"components":[{"internalType":"uint256","name":"price","type":"uint256"},{"internalType":"uint256","name":"saleStart","type":"uint256"},{"internalType":"uint256","name":"saleEnd","type":"uint256"},{"internalType":"uint256","name":"numTokens","type":"uint256"}],"internalType":"struct AlbumTokenSaleManager.TokenSaleParams","name":"tokenSaleParams","type":"tuple"},{"internalType":"address[]","name":"_nftAddrs","type":"address[]"},{"internalType":"uint256[]","name":"_nftIds","type":"uint256[]"},{"internalType":"uint256","name":"_minReservePrice","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address[]","name":"nfts","type":"address[]"},{"indexed":false,"internalType":"uint256[]","name":"ids","type":"uint256[]"}],"name":"AddNfts","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"cost","type":"uint256"}],"name":"Buyout","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"claimer","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"owed","type":"uint256"}],"name":"BuyoutPortionClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"cost","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"end","type":"uint256"}],"name":"BuyoutSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"minReservePrice","type":"uint256"}],"name":"MinReservePriceSet","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"creator","type":"address"},{"indexed":false,"internalType":"uint256","name":"tokensPerMilliEth","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"saleStart","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"saleEnd","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"numTokens","type":"uint256"}],"name":"SaleInitialized","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"saleProceeds","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountUnsold","type":"uint256"}],"name":"SaleSwept","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256[]","name":"idxs","type":"uint256[]"}],"name":"SendNfts","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"buyer","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"paid","type":"uint256"}],"name":"TokenSale","type":"event"},{"inputs":[],"name":"CREATOR","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MILLIETH","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SALE_END","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"SALE_START","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TOKENS_PER_WEI","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_nfts","type":"address[]"},{"internalType":"uint256[]","name":"_ids","type":"uint256[]"}],"name":"addNfts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"buyTokens","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"buyout","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"claim","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getBuyoutData","outputs":[{"internalType":"address","name":"_buyer","type":"address"},{"internalType":"uint256","name":"_buyoutCost","type":"uint256"},{"internalType":"uint256","name":"_buyoutEnd","type":"uint256"},{"internalType":"bool","name":"_bought","type":"bool"},{"internalType":"uint256","name":"_timeout","type":"uint256"},{"internalType":"uint256","name":"_minReservePrice","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getIds","outputs":[{"internalType":"uint256[]","name":"","type":"uint256[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getNfts","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSent","outputs":[{"internalType":"bool[]","name":"","type":"bool[]"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getTokenSaleData","outputs":[{"internalType":"address","name":"creator","type":"address"},{"internalType":"uint256","name":"tokensPerWei","type":"uint256"},{"internalType":"uint256","name":"saleStart","type":"uint256"},{"internalType":"uint256","name":"saleEnd","type":"uint256"},{"internalType":"uint256","name":"_saleProceeds","type":"uint256"},{"internalType":"uint256","name":"_amountUnsold","type":"uint256"},{"internalType":"bool","name":"_swept","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"},{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"bytes","name":"","type":"bytes"}],"name":"onERC721Received","outputs":[{"internalType":"bytes4","name":"","type":"bytes4"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"renounceOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256[]","name":"idxs","type":"uint256[]"}],"name":"sendNfts","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_buyer","type":"address"},{"internalType":"uint256","name":"_cost","type":"uint256"}],"name":"setBuyout","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_minReservePrice","type":"uint256"}],"name":"setMinReservePrice","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_timeout","type":"uint256"}],"name":"setTimeout","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"sweepProceeds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"transferOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"}]