diff --git a/contracts/nft/erc721m/ERC721CM.sol b/contracts/nft/erc721m/ERC721CM.sol index e4f315dc..95e5d1a8 100644 --- a/contracts/nft/erc721m/ERC721CM.sol +++ b/contracts/nft/erc721m/ERC721CM.sol @@ -53,8 +53,10 @@ contract ERC721CM is address cosigner, uint256 timestampExpirySeconds, address mintCurrency, - address fundReceiver - ) ERC721ACQueryable(collectionName, collectionSymbol) Ownable() { + address fundReceiver, + address initialOwner + ) ERC721ACQueryable(collectionName, collectionSymbol) { + _transferOwnership(initialOwner); if (globalWalletLimit > maxMintableSupply) { revert GlobalWalletLimitOverflow(); } @@ -76,7 +78,7 @@ contract ERC721CM is /// @notice Returns the contract name and version /// @return The contract name and version as strings function contractNameAndVersion() public pure returns (string memory, string memory) { - return ("ERC721CM", "1.0.0"); + return ("ERC721CM", "1.0.1"); } /// @notice Gets the token URI for a specific token ID @@ -315,6 +317,16 @@ contract ERC721CM is emit SetMintable(mintable); } + /// @notice Sets the default royalty for the contract + /// @param receiver The address to receive royalties + /// @param feeNumerator The royalty fee numerator + function setDefaultRoyalty(address receiver, uint96 feeNumerator) public onlyOwner { + super._setDefaultRoyalty(receiver, feeNumerator); + _royaltyBps = feeNumerator; + _royaltyRecipient = receiver; + emit DefaultRoyaltySet(receiver, feeNumerator); + } + /// @notice Sets the maximum mintable supply /// @param maxMintableSupply The maximum mintable supply to set function setMaxMintableSupply(uint256 maxMintableSupply) external virtual onlyOwner { diff --git a/scripts-foundry/common/100a-create2-magicdrop-impl.sh b/scripts-foundry/common/100a-create2-magicdrop-impl.sh new file mode 100755 index 00000000..b8061d2d --- /dev/null +++ b/scripts-foundry/common/100a-create2-magicdrop-impl.sh @@ -0,0 +1,79 @@ +#!/bin/bash + +if [ -f .env ] +then + export $(grep -v '^#' .env | xargs) +else + echo "Please set your .env file" + exit 1 +fi + +NAME="" +SYMBOL="" +TOKEN_URI_SUFFIX="" +MAX_MINTABLE_SUPPLY="1000" +GLOBAL_WALLET_LIMIT="0" +COSIGNER="0x0000000000000000000000000000000000000000" +TIMESTAMP_EXPIRY_SECONDS="60" +MINT_CURRENCY="0x0000000000000000000000000000000000000000" +FUND_RECEIVER="" +INITIAL_OWNER="" + +# Function to display usage +usage() { + echo "Usage: $0 --impl --name --symbol --fund-receiver --uri --maxMintableSupply --globalWalletLimit --cosigner --timestamp-expiry --mint-currency --initial-owner " + exit 1 +} + +# Process arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --impl) IMPL_PATH=$2; shift ;; + --name) NAME=$2; shift ;; + --symbol) SYMBOL=$2; shift ;; + --uri) TOKEN_URI_SUFFIX=$2; shift ;; + --maxMintableSupply) MAX_MINTABLE_SUPPLY=$2; shift ;; + --globalWalletLimit) GLOBAL_WALLET_LIMIT=$2; shift ;; + --cosigner) COSIGNER=$2; shift ;; + --timestamp-expiry) TIMESTAMP_EXPIRY_SECONDS=$2; shift ;; + --mint-currency) MINT_CURRENCY=$2; shift ;; + --fund-receiver) FUND_RECEIVER=$2; shift ;; + --initial-owner) INITIAL_OWNER=$2; shift ;; + *) usage ;; + esac + shift +done + +echo "IMPL_PATH: $IMPL_PATH" +echo "NAME: $NAME" +echo "SYMBOL: $SYMBOL" +echo "TOKEN_URI_SUFFIX: $TOKEN_URI_SUFFIX" +echo "MAX_MINTABLE_SUPPLY: $MAX_MINTABLE_SUPPLY" +echo "GLOBAL_WALLET_LIMIT: $GLOBAL_WALLET_LIMIT" +echo "COSIGNER: $COSIGNER" +echo "TIMESTAMP_EXPIRY_SECONDS: $TIMESTAMP_EXPIRY_SECONDS" +echo "MINT_CURRENCY: $MINT_CURRENCY" +echo "FUND_RECEIVER: $FUND_RECEIVER" +echo "INITIAL_OWNER: $INITIAL_OWNER" + +# Check if all parameters are set +if [ -z "$IMPL_PATH" ] || [ -z "$NAME" ] || [ -z "$SYMBOL" ] || [ -z "$TOKEN_URI_SUFFIX" ] || [ -z "$MAX_MINTABLE_SUPPLY" ] || [ -z "$GLOBAL_WALLET_LIMIT" ] || [ -z "$COSIGNER" ] || [ -z "$TIMESTAMP_EXPIRY_SECONDS" ] || [ -z "$MINT_CURRENCY" ] || [ -z "$FUND_RECEIVER" ] || [ -z "$INITIAL_OWNER" ]; then + usage +fi + +# NOTE: If you change the number of optimizer runs, you must also change the number in the deploy script, otherwise the CREATE2 address will be different + +echo "create2 MagicDropImpl START" + +implByteCode="$(forge inspect contracts/nft/erc721m/ERC721CM.sol:ERC721CM bytecode --optimizer-runs 777 --via-ir)" +constructorArgs=$(cast abi-encode "constructor(string,string,string,uint256,uint256,address,uint256,address,address,address)" "$NAME" "$SYMBOL" "$TOKEN_URI_SUFFIX" "$MAX_MINTABLE_SUPPLY" "$GLOBAL_WALLET_LIMIT" "$COSIGNER" "$TIMESTAMP_EXPIRY_SECONDS" "$MINT_CURRENCY" "$FUND_RECEIVER" "$INITIAL_OWNER") +constructorArgsNoPrefix=${constructorArgs#0x} +implInitCode=$(cast concat-hex $implByteCode $constructorArgsNoPrefix) + +echo $implByteCode +echo $constructorArgs + +cast create2 --starts-with 88888888 --case-sensitive --init-code $implInitCode +echo "create2 MagicDropImpl END" +echo "-------------------------------------" +echo "" \ No newline at end of file diff --git a/scripts-foundry/common/101a-deploy-magicdrop-impl-direct.sh b/scripts-foundry/common/101a-deploy-magicdrop-impl-direct.sh new file mode 100755 index 00000000..29f2c2b5 --- /dev/null +++ b/scripts-foundry/common/101a-deploy-magicdrop-impl-direct.sh @@ -0,0 +1,135 @@ +#!/usr/bin/env bash + +if [ -f .env ] +then + export $(grep -v '^#' .env | xargs) +else + echo "Please set your .env file" + exit 1 +fi + +# Note: Update the contract in the deploy script if you want to deploy a different version. Default is 1_0_0 + +source ./utils + +# Exit on error +set -e + +# Initialize variables with environment values +CHAIN_ID=${CHAIN_ID:-""} +RPC_URL="" +STANDARD="" +IS_ERC721C=false #optional +IMPL_EXPECTED_ADDRESS="" +IMPL_SALT="" + +NAME="" +SYMBOL="" +TOKEN_URI_SUFFIX="" +MAX_MINTABLE_SUPPLY="1000" +GLOBAL_WALLET_LIMIT="0" +COSIGNER="0x0000000000000000000000000000000000000000" +TIMESTAMP_EXPIRY_SECONDS="60" +MINT_CURRENCY="0x0000000000000000000000000000000000000000" +FUND_RECEIVER="" +ROYALTY_RECIPIENT="0x0000000000000000000000000000000000000000" +ROYALTY_BPS=0 +INITIAL_OWNER="" + +# Function to display usage +usage() { + # Example Usage: ./2a-deploy-magicdrop-impl.sh --chain-id 137 --token-standard ERC721 --is-erc721c true --expected-address 0x0000000000000000000000000000000000000000 --salt 0x0000000000000000000000000000000000000000000000000000000000000000 + echo "Usage: $0 --chain-id --token-standard --is-erc721c --expected-address --salt --name --symbol --fund-receiver --uri --maxMintableSupply --globalWalletLimit --cosigner --timestamp-expiry --mint-currency --initial-owner --royalty-recipient --royalty-bps <250>" + exit 1 +} + +# Process arguments +while [[ "$#" -gt 0 ]]; do + case $1 in + --chain-id) CHAIN_ID=$2; shift ;; + --token-standard) STANDARD=$2; shift ;; + --is-erc721c) IS_ERC721C=$2; shift ;; + --expected-address) IMPL_EXPECTED_ADDRESS=$2; shift ;; + --salt) IMPL_SALT=$2; shift ;; + --name) NAME=$2; shift ;; + --symbol) SYMBOL=$2; shift ;; + --uri) TOKEN_URI_SUFFIX=$2; shift ;; + --maxMintableSupply) MAX_MINTABLE_SUPPLY=$2; shift ;; + --globalWalletLimit) GLOBAL_WALLET_LIMIT=$2; shift ;; + --cosigner) COSIGNER=$2; shift ;; + --timestamp-expiry) TIMESTAMP_EXPIRY_SECONDS=$2; shift ;; + --mint-currency) MINT_CURRENCY=$2; shift ;; + --fund-receiver) FUND_RECEIVER=$2; shift ;; + --royalty-recipient) ROYALTY_RECIPIENT=$2; shift;; + --royalty-bps) ROYALTY_BPS=$2; shift;; + --initial-owner) INITIAL_OWNER=$2; shift ;; + *) usage ;; + esac + shift +done + +# Check if all parameters are set +if [ -z "$CHAIN_ID" ] || [ -z "$STANDARD" ] || [ -z "$IMPL_EXPECTED_ADDRESS" ] || [ -z "$IMPL_SALT" ] || [ -z "$NAME" ] || [ -z "$SYMBOL" ] || [ -z "$TOKEN_URI_SUFFIX" ] || [ -z "$MAX_MINTABLE_SUPPLY" ] || [ -z "$GLOBAL_WALLET_LIMIT" ] || [ -z "$COSIGNER" ] || [ -z "$TIMESTAMP_EXPIRY_SECONDS" ] || [ -z "$MINT_CURRENCY" ] || [ -z "$FUND_RECEIVER" ] || [ -z "$ROYALTY_RECIPIENT" ] || [ -z "$ROYALTY_BPS" ] || [ -z "$INITIAL_OWNER" ]; then + usage +fi + +# Set the RPC URL based on chain ID +set_rpc_url $CHAIN_ID + +# Set the ETHERSCAN API KEY based on chain ID +set_etherscan_api_key $CHAIN_ID + +# Convert STANDARD to lowercase for the path +STANDARD_LOWERCASE=$(echo $STANDARD | tr '[:upper:]' '[:lower:]') + +echo "" +echo "==================== DEPLOYMENT DETAILS ====================" +echo "Chain ID: $CHAIN_ID" +echo "RPC URL: $RPC_URL" +echo "Token Standard: $STANDARD" +echo "Is ERC721C: $IS_ERC721C" +echo "Expected Address: $IMPL_EXPECTED_ADDRESS" +echo "Salt: $IMPL_SALT" +echo "Name: $NAME" +echo "Symbol: $SYMBOL" +echo "Token URI Suffix: $TOKEN_URI_SUFFIX" +echo "Max Mintable Supply: $MAX_MINTABLE_SUPPLY" +echo "Global Wallet Limit: $GLOBAL_WALLET_LIMIT" +echo "Cosigner: $COSIGNER" +echo "Timestamp Expiry: $TIMESTAMP_EXPIRY_SECONDS" +echo "Mint Currency: $MINT_CURRENCY" +echo "Fund Reciever: $FUND_RECEIVER" +echo "Royalty Recipient: $ROYALTY_RECIPIENT" +echo "Royalty BPS: $ROYALTY_BPS" +echo "Initial Owner: $INITIAL_OWNER" +echo "============================================================" +echo "" +read -p "Do you want to proceed? (yes/no) " yn + +case $yn in + yes ) echo ok, we will proceed;; + no ) echo exiting...; + exit;; + * ) echo invalid response; + exit 1;; +esac + +constructorArgs=$(cast abi-encode "constructor(string,string,string,uint256,uint256,address,uint256,address,address,address)" "$NAME" "$SYMBOL" "$TOKEN_URI_SUFFIX" "$MAX_MINTABLE_SUPPLY" "$GLOBAL_WALLET_LIMIT" "$COSIGNER" "$TIMESTAMP_EXPIRY_SECONDS" "$MINT_CURRENCY" "$FUND_RECEIVER" "$INITIAL_OWNER") +echo "constructorArgs: $constructorArgs" + +echo "" +echo "============= DEPLOYING MAGICDROP IMPLEMENTATION =============" +echo "" + +# remove --verify when deploying on Sei Chain. You will need to verify manually. +CHAIN_ID=$CHAIN_ID RPC_URL=$RPC_URL TOKEN_STANDARD=$STANDARD IS_ERC721C=$IS_ERC721C IMPL_EXPECTED_ADDRESS=$IMPL_EXPECTED_ADDRESS IMPL_SALT=$IMPL_SALT NAME=$NAME SYMBOL=$SYMBOL TOKEN_URI_SUFFIX=$TOKEN_URI_SUFFIX MAX_MINTABLE_SUPPLY=$MAX_MINTABLE_SUPPLY GLOBAL_WALLET_LIMIT=$GLOBAL_WALLET_LIMIT COSIGNER=$COSIGNER TIMESTAMP_EXPIRY_SECONDS=$TIMESTAMP_EXPIRY_SECONDS MINT_CURRENCY=$MINT_CURRENCY FUND_RECEIVER=$FUND_RECEIVER ROYALTY_RECIPIENT=$ROYALTY_RECIPIENT ROYALTY_BPS=$ROYALTY_BPS INITIAL_OWNER=$INITIAL_OWNER forge script ./DeployMagicDropImplementationDirect.s.sol:DeployMagicDropImplementationDirect \ + --rpc-url $RPC_URL \ + --broadcast \ + --optimizer-runs 777 \ + --via-ir \ + --verify \ + -v + +echo "" +echo "============= DEPLOYED MAGICDROP IMPLEMENTATION =============" +echo "" \ No newline at end of file diff --git a/scripts-foundry/common/DeployMagicDropImplementationDirect.s.sol b/scripts-foundry/common/DeployMagicDropImplementationDirect.s.sol new file mode 100644 index 00000000..01c77203 --- /dev/null +++ b/scripts-foundry/common/DeployMagicDropImplementationDirect.s.sol @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {Script, console} from "forge-std/Script.sol"; +import {ERC721CM} from "contracts/nft/erc721m/ERC721CM.sol"; +import {TokenStandard} from "contracts/common/Structs.sol"; + +contract DeployMagicDropImplementationDirect is Script { + error AddressMismatch(address expected, address actual); + error InvalidTokenStandard(string standard); + error NotImplementedYet(); + + function run() external { + bytes32 salt = vm.envBytes32("IMPL_SALT"); + address expectedAddress = address(uint160(vm.envUint("IMPL_EXPECTED_ADDRESS"))); + TokenStandard standard = parseTokenStandard(vm.envString("TOKEN_STANDARD")); + bool isERC721C = vm.envBool("IS_ERC721C"); + uint256 privateKey = vm.envUint("PRIVATE_KEY"); + string memory name = vm.envString("NAME"); + string memory symbol = vm.envString("SYMBOL"); + string memory tokenUriSuffix = vm.envString("TOKEN_URI_SUFFIX"); + uint256 maxMintableSupply = vm.envUint("MAX_MINTABLE_SUPPLY"); + uint256 globalWalletLimit = vm.envUint("GLOBAL_WALLET_LIMIT"); + address cosigner = address(uint160(vm.envUint("COSIGNER"))); + uint256 timestampExpirySeconds = vm.envUint("TIMESTAMP_EXPIRY_SECONDS"); + address mintCurrency = address(uint160(vm.envUint("MINT_CURRENCY"))); + address fundReceiver = address(uint160(vm.envUint("FUND_RECEIVER"))); + address royaltyRecipient = address(uint160(vm.envUint("ROYALTY_RECIPIENT"))); + uint96 royaltyBps = uint96(vm.envUint("ROYALTY_BPS")); + address initialOwner = address(uint160(vm.envUint("INITIAL_OWNER"))); + + vm.startBroadcast(privateKey); + + address deployedAddress; + + if (standard == TokenStandard.ERC721) { + if (isERC721C) { + deployedAddress = address(new ERC721CM{salt: salt}( + name, + symbol, + tokenUriSuffix, + maxMintableSupply, + globalWalletLimit, + cosigner, + timestampExpirySeconds, + mintCurrency, + fundReceiver, + initialOwner + )); + + ERC721CM(deployedAddress).setDefaultRoyalty(royaltyRecipient, royaltyBps); + } else { + revert NotImplementedYet(); + } + } else if (standard == TokenStandard.ERC1155) { + revert NotImplementedYet(); + } + + if (address(deployedAddress) != expectedAddress) { + revert AddressMismatch(expectedAddress, deployedAddress); + } + + vm.stopBroadcast(); + } + + function parseTokenStandard(string memory standardString) internal pure returns (TokenStandard) { + if (keccak256(abi.encodePacked(standardString)) == keccak256(abi.encodePacked("ERC721"))) { + return TokenStandard.ERC721; + } else if (keccak256(abi.encodePacked(standardString)) == keccak256(abi.encodePacked("ERC1155"))) { + return TokenStandard.ERC1155; + } else { + revert InvalidTokenStandard(standardString); + } + } +} \ No newline at end of file