Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add mint fee #102

Merged
merged 8 commits into from
May 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 41 additions & 14 deletions contracts/ERC721CM.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard {
// Address of ERC-20 token used to pay for minting. If 0 address, use native currency.
address private _mintCurrency;

// Total mint fee
uint256 private _totalMintFee;

address public constant MINT_FEE_RECEIVER =
0x0B98151bEdeE73f9Ba5F2C7b72dEa02D38Ce49Fc;

constructor(
string memory collectionName,
string memory collectionSymbol,
Expand Down Expand Up @@ -160,7 +166,7 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard {
function setStages(MintStageInfo[] calldata newStages) external onlyOwner {
delete _mintStages;

for (uint256 i = 0; i < newStages.length;) {
for (uint256 i = 0; i < newStages.length; ) {
channing-magiceden marked this conversation as resolved.
Show resolved Hide resolved
if (i >= 1) {
if (
newStages[i].startTimeUnixSeconds <
Expand All @@ -177,6 +183,7 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard {
_mintStages.push(
MintStageInfo({
price: newStages[i].price,
mintFee: newStages[i].mintFee,
walletLimit: newStages[i].walletLimit,
merkleRoot: newStages[i].merkleRoot,
maxStageSupply: newStages[i].maxStageSupply,
Expand All @@ -187,14 +194,17 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard {
emit UpdateStage(
i,
newStages[i].price,
newStages[i].mintFee,
newStages[i].walletLimit,
newStages[i].merkleRoot,
newStages[i].maxStageSupply,
newStages[i].startTimeUnixSeconds,
channing-magiceden marked this conversation as resolved.
Show resolved Hide resolved
newStages[i].endTimeUnixSeconds
);

unchecked { ++i; }
unchecked {
++i;
}
}
}

Expand Down Expand Up @@ -376,8 +386,10 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard {
stage = _mintStages[activeStage];

// Check value if minting with ETH
if (_mintCurrency == address(0) && msg.value < stage.price * qty)
revert NotEnoughValue();
if (
_mintCurrency == address(0) &&
msg.value < (stage.price + stage.mintFee) * qty
) revert NotEnoughValue();

// Check stage supply if applicable
if (stage.maxStageSupply > 0) {
Expand Down Expand Up @@ -409,7 +421,10 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard {
) revert InvalidProof();

// Verify merkle proof mint limit
if (limit > 0 && _stageMintedCountsPerWallet[activeStage][to] + qty > limit) {
if (
limit > 0 &&
_stageMintedCountsPerWallet[activeStage][to] + qty > limit
) {
revert WalletStageLimitExceeded();
}
}
Expand All @@ -418,10 +433,12 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard {
IERC20(_mintCurrency).safeTransferFrom(
channing-magiceden marked this conversation as resolved.
Show resolved Hide resolved
msg.sender,
address(this),
stage.price * qty
(stage.price + stage.mintFee) * qty
);
}

_totalMintFee += stage.mintFee * qty;

_stageMintedCountsPerWallet[activeStage][to] += qty;
_stageMintedCounts[activeStage] += qty;
_safeMint(to, qty);
Expand All @@ -444,20 +461,28 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard {
* @dev Withdraws funds by owner.
*/
function withdraw() external onlyOwner {
uint256 value = address(this).balance;
(bool success, ) = msg.sender.call{value: value}("");
(bool success, ) = MINT_FEE_RECEIVER.call{value: _totalMintFee}("");
if (!success) revert TransferFailed();

uint256 remainingValue = address(this).balance;
(success, ) = msg.sender.call{value: remainingValue}("");
if (!success) revert WithdrawFailed();
emit Withdraw(value);

emit Withdraw(_totalMintFee + remainingValue);
}

/**
* @dev Withdraws ERC-20 funds by owner.
*/
function withdrawERC20() external onlyOwner {
if (_mintCurrency == address(0)) revert WrongMintCurrency();
uint256 value = IERC20(_mintCurrency).balanceOf(address(this));
IERC20(_mintCurrency).safeTransfer(msg.sender, value);
emit WithdrawERC20(_mintCurrency, value);

IERC20(_mintCurrency).safeTransfer(MINT_FEE_RECEIVER, _totalMintFee);

uint256 remaining = IERC20(_mintCurrency).balanceOf(address(this));
IERC20(_mintCurrency).safeTransfer(msg.sender, remaining);

emit WithdrawERC20(_mintCurrency, _totalMintFee + remaining);
}

/**
Expand Down Expand Up @@ -557,14 +582,16 @@ contract ERC721CM is IERC721M, ERC721ACQueryable, Ownable, ReentrancyGuard {
function getActiveStageFromTimestamp(
uint64 timestamp
) public view returns (uint256) {
for (uint256 i = 0; i < _mintStages.length;) {
for (uint256 i = 0; i < _mintStages.length; ) {
if (
timestamp >= _mintStages[i].startTimeUnixSeconds &&
timestamp < _mintStages[i].endTimeUnixSeconds
) {
return i;
}
unchecked { ++i; }
unchecked {
++i;
}
}
revert InvalidStage();
}
Expand Down
49 changes: 35 additions & 14 deletions contracts/ERC721M.sol
channing-magiceden marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard {
// Address of ERC-20 token used to pay for minting. If 0 address, use native currency.
address private _mintCurrency;

// Total mint fee
uint256 private _totalMintFee;

address public constant MINT_FEE_RECEIVER =
0x0B98151bEdeE73f9Ba5F2C7b72dEa02D38Ce49Fc;

constructor(
string memory collectionName,
string memory collectionSymbol,
Expand Down Expand Up @@ -157,10 +163,7 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard {
* ]
*/
function setStages(MintStageInfo[] calldata newStages) external onlyOwner {
uint256 originalSize = _mintStages.length;
for (uint256 i = 0; i < originalSize; i++) {
_mintStages.pop();
}
delete _mintStages;

for (uint256 i = 0; i < newStages.length; i++) {
if (i >= 1) {
Expand All @@ -179,6 +182,7 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard {
_mintStages.push(
MintStageInfo({
price: newStages[i].price,
mintFee: newStages[i].mintFee,
walletLimit: newStages[i].walletLimit,
merkleRoot: newStages[i].merkleRoot,
maxStageSupply: newStages[i].maxStageSupply,
Expand All @@ -189,6 +193,7 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard {
emit UpdateStage(
i,
newStages[i].price,
newStages[i].mintFee,
newStages[i].walletLimit,
newStages[i].merkleRoot,
newStages[i].maxStageSupply,
Expand Down Expand Up @@ -376,8 +381,10 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard {
stage = _mintStages[activeStage];

// Check value if minting with ETH
if (_mintCurrency == address(0) && msg.value < stage.price * qty)
revert NotEnoughValue();
if (
_mintCurrency == address(0) &&
msg.value < (stage.price + stage.mintFee) * qty
) revert NotEnoughValue();

// Check stage supply if applicable
if (stage.maxStageSupply > 0) {
Expand Down Expand Up @@ -409,19 +416,25 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard {
) revert InvalidProof();

// Verify merkle proof mint limit
if (limit > 0 && _stageMintedCountsPerWallet[activeStage][to] + qty > limit) {
if (
limit > 0 &&
_stageMintedCountsPerWallet[activeStage][to] + qty > limit
) {
revert WalletStageLimitExceeded();
}
}

if (_mintCurrency != address(0)) {
// ERC20 mint payment
channing-magiceden marked this conversation as resolved.
Show resolved Hide resolved
IERC20(_mintCurrency).safeTransferFrom(
channing-magiceden marked this conversation as resolved.
Show resolved Hide resolved
msg.sender,
address(this),
stage.price * qty
(stage.price + stage.mintFee) * qty
);
}

_totalMintFee += stage.mintFee * qty;

_stageMintedCountsPerWallet[activeStage][to] += qty;
_stageMintedCounts[activeStage] += qty;
_safeMint(to, qty);
Expand All @@ -444,20 +457,28 @@ contract ERC721M is IERC721M, ERC721AQueryable, Ownable, ReentrancyGuard {
* @dev Withdraws funds by owner.
*/
function withdraw() external onlyOwner {
uint256 value = address(this).balance;
(bool success, ) = msg.sender.call{value: value}("");
(bool success, ) = MINT_FEE_RECEIVER.call{value: _totalMintFee}("");
if (!success) revert TransferFailed();

uint256 remainingValue = address(this).balance;
(success, ) = msg.sender.call{value: remainingValue}("");
if (!success) revert WithdrawFailed();
emit Withdraw(value);

emit Withdraw(_totalMintFee + remainingValue);
}

/**
* @dev Withdraws ERC-20 funds by owner.
*/
function withdrawERC20() external onlyOwner {
if (_mintCurrency == address(0)) revert WrongMintCurrency();
uint256 value = IERC20(_mintCurrency).balanceOf(address(this));
IERC20(_mintCurrency).safeTransfer(msg.sender, value);
emit WithdrawERC20(_mintCurrency, value);

IERC20(_mintCurrency).safeTransfer(MINT_FEE_RECEIVER, _totalMintFee);

uint256 remaining = IERC20(_mintCurrency).balanceOf(address(this));
IERC20(_mintCurrency).safeTransfer(msg.sender, remaining);

emit WithdrawERC20(_mintCurrency, _totalMintFee + remaining);
}

/**
Expand Down
28 changes: 24 additions & 4 deletions contracts/IERC721M.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface IERC721M is IERC721AQueryable {
error Mintable();
error StageSupplyExceeded();
error TimestampExpired();
error TransferFailed();
error WalletGlobalLimitExceeded();
error WalletStageLimitExceeded();
error WithdrawFailed();
Expand All @@ -29,6 +30,7 @@ interface IERC721M is IERC721AQueryable {

struct MintStageInfo {
uint80 price;
uint80 mintFee;
uint32 walletLimit; // 0 for unlimited
bytes32 merkleRoot; // 0x0 for no presale enforced
uint24 maxStageSupply; // 0 for unlimited
Expand All @@ -39,6 +41,7 @@ interface IERC721M is IERC721AQueryable {
event UpdateStage(
uint256 stage,
uint80 price,
uint80 mintFee,
uint32 walletLimit,
bytes32 merkleRoot,
uint24 maxStageSupply,
Expand Down Expand Up @@ -69,10 +72,27 @@ interface IERC721M is IERC721AQueryable {
function getStageInfo(
uint256 index
) external view returns (MintStageInfo memory, uint32, uint256);

function mint(uint32 qty, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external payable;

function mintWithLimit(uint32 qty, uint32 limit, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external payable;
function mint(
uint32 qty,
bytes32[] calldata proof,
uint64 timestamp,
bytes calldata signature
) external payable;

function crossmint(uint32 qty, address to, bytes32[] calldata proof, uint64 timestamp, bytes calldata signature) external payable;
function mintWithLimit(
uint32 qty,
uint32 limit,
bytes32[] calldata proof,
uint64 timestamp,
bytes calldata signature
) external payable;

function crossmint(
uint32 qty,
address to,
bytes32[] calldata proof,
uint64 timestamp,
bytes calldata signature
) external payable;
}
1 change: 0 additions & 1 deletion contracts/auctions/IBucketAuction.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ interface IBucketAuction {
error NotClaimable();
error PriceHasBeenSet();
error PriceNotSet();
error TransferFailed();
error UserAlreadyClaimed();

struct User {
Expand Down
2 changes: 2 additions & 0 deletions scripts/setStages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export interface ISetStagesParams {

interface StageConfig {
price: string;
mintFee?: string;
startDate: number;
endDate: number;
walletLimit?: number;
Expand Down Expand Up @@ -92,6 +93,7 @@ export const setStages = async (

const stages = stagesConfig.map((s, i) => ({
price: ethers.utils.parseEther(s.price),
mintFee: s.mintFee ? ethers.utils.parseEther(s.mintFee) : 0,
maxStageSupply: s.maxSupply ?? 0,
walletLimit: s.walletLimit ?? 0,
merkleRoot: merkleRoots[i],
Expand Down
2 changes: 2 additions & 0 deletions test/BucketAuction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ describe('BucketAuction', function () {
await ownerConn.setStages([
{
price: ethers.utils.parseEther('0.1'),
mintFee: 0,
walletLimit: 0,
merkleRoot: ethers.utils.hexZeroPad('0x0', 32),
maxStageSupply: 100,
Expand All @@ -46,6 +47,7 @@ describe('BucketAuction', function () {
},
{
price: ethers.utils.parseEther('0.2'),
mintFee: 0,
walletLimit: 0,
merkleRoot: ethers.utils.hexZeroPad('0x0', 32),
maxStageSupply: 100,
Expand Down
Loading
Loading