상세 컨텐츠

본문 제목

Build an NFT Collection with a whitelist using Foundry and Solidity - LearnWeb3

Blockchain

by 0xRobert 2025. 10. 5. 23:20

본문

Build an NFT Collection with a whitelist using Foundry and Solidity

Foundry와 Solidity를 사용해 화이트리스트 기반 NFT 컬렉션 구축하기
'Crypto Devs'라는 NFT 컬렉션을 출시하려 하지만, 초기 지지자들이 NFT 발행 시 보장된 접근 권한을 얻길 원합니다. 이를 위한 간단한 해결책은 먼저 화이트리스트 dApp을 구축하고 초기 지지자들이 화이트리스트에 가입하도록 허용하는 것입니다. 화이트리스트에 등록되면 컬렉션 출시 시 NFT를 발행할 수 있는 보장된 접근 권한을 얻게 됩니다!

본 강의에서는 선착순 10명의 사용자가 무료로 참여할 수 있는 화이트리스트를 운영합니다. 화이트리스트에 등록된 사용자는 'Crypto Devs' 컬렉션의 NFT를 무료로 민팅할 수 있으며, 그 외 모든 사용자는 유료로 민팅해야 합니다!

💡
중요: 이전 주제를 따라 이미 메인넷 자금이 없는 새로운 개발용 지갑을 생성하셨을 것으로 가정합니다. 개발 과정에서 메인넷에 실제 자금이 없는 개인 키를 사용하세요. 실수로 실제 자금이 있는 개인 키를 유출하여 자금을 도난당하지 않도록 주의하십시오. 또한 컴퓨터에 Foundry를 이미 설치하셨을 것으로 가정합니다.

설정

스마트 계약을 구축하기 위해 Foundry를 사용할 것입니다. 먼저 컴퓨터에 새 폴더를 생성하세요. 저희는 whitelist-dapp이라는 이름으로 생성하고 터미널을 통해 해당 폴더로 이동합니다.

터미널이 whitelist-dapp 폴더 내부에 있으면 다음 명령어로 Foundry 프로젝트를 설정하세요:

forge init foundry-app
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;


contract Whitelist {

    // Max number of whitelisted addresses allowed
    uint8 public maxWhitelistedAddresses;

    // Create a mapping of whitelistedAddresses
    // if an address is whitelisted, we would set it to true, it is false by default for all other addresses.
    mapping(address => bool) public whitelistedAddresses;

    // numAddressesWhitelisted would be used to keep track of how many addresses have been whitelisted
    uint8 public numAddressesWhitelisted;

    // Setting the Max number of whitelisted addresses
    // User will put the value at the time of deployment
    constructor(uint8 _maxWhitelistedAddresses) {
        maxWhitelistedAddresses =  _maxWhitelistedAddresses;
    }

    /**
        addAddressToWhitelist - This function adds the address of the sender to the
        whitelist
     */
    function addAddressToWhitelist() public {
        // check if the user has already been whitelisted
        require(!whitelistedAddresses[msg.sender], "Sender has already been whitelisted");
        // check if the numAddressesWhitelisted < maxWhitelistedAddresses, if not then throw an error.
        require(numAddressesWhitelisted < maxWhitelistedAddresses, "More addresses cant be added, limit reached");
        // Add the address which called the function to the whitelistedAddress array
        whitelistedAddresses[msg.sender] = true;
        // Increase the number of whitelisted addresses
        numAddressesWhitelisted += 1;
    }

}

코드는 주석에서 설명되어 있지만, 기본적으로 주소와 부울 값 간의 매핑을 유지하여 어떤 주소가 화이트리스트에 포함되는지 표시합니다. 사용자는 화이트리스트에 등록된 주소 수가 최대값에 도달하기 전에 addAddressToWhitelist 함수를 호출하여 자신을 화이트리스트에 추가할 수 있습니다.

환경 변수

먼저 foundry-app 폴더 내부의 .env 파일로 이동하여 다음의 자리 표시자 줄을 추가하세요

PRIVATE_KEY="..."
QUICKNODE_RPC_URL="..."
ETHERSCAN_API_KEY="..."

PRIVATE_KEY 변수의 경우, MetaMask에서 내보내십시오. 다시 한번, 실전 자금이 있는 계정이 아닌 테스트넷 자금만 있는 계정을 사용해야 합니다. 실전 자금이 있는 계정의 개인 키가 실수로 유출되는 위험을 방지하기 위함입니다. .env 파일 내 PRIVATE_KEY 값을 내보낸 키로 대체하십시오.

QUICKNODE_RPC_URL의 경우, 아직 계정이 없다면 QuickNode에서 계정을 생성하세요. 계정 생성 후 'Create an endpoint'를 클릭하고, Ethereum을 선택한 다음 Sepolia 테스트넷을 선택하세요. 과정을 완료할 때까지 진행한 후, 해당 대시보드에서 HTTP Provider 링크를 복사하세요.

.env 파일 내 QUICKNODE_RPC_URL 변수의 값을 복사한 HTTP 제공자 링크로 대체하세요.

참고: 이전 강의에서 QuickNode에 Sepolia 엔드포인트를 설정해 놓은 경우, 계속해서 해당 엔드포인트를 사용할 수 있습니다. 새로 생성할 필요는 없습니다.

마지막으로, 계약을 Etherscan에서 검증할 수 있도록 Etherscan API 키가 필요합니다. 아직 계정이 없다면 https://etherscan.io에서 계정을 생성하여 Etherscan API 키를 획득하세요. 키를 획득한 후 .env 파일 내 ETHERSCAN_API_KEY 값을 해당 키로 교체하세요.

화이트리스트 계약 배포

이제 터미널 환경에 환경 변수를 로드해야 합니다. 이를 위해 터미널에서 다음 명령어를 실행하세요.

source .env

마지막으로 계약을 배포하려면 다음 명령어를 실행하세요:


forge create --rpc-url $QUICKNODE_RPC_URL --private-key $PRIVATE_KEY --constructor-args 10 --etherscan-api-key $ETHERSCAN_API_KEY --verify src/Whitelist.sol:Whitelist

계약 배포 시 생성자 인자로 10을 전달했습니다. 이는 스마트 계약의 constructor() 호출에서 maxWhitelistedAddresses를 10으로 설정함을 의미합니다.

💡
계약이 이미 검증되었다는 메시지가 표시될 수 있습니다. 실제로 검증되지 않았더라도 발생합니다. 이는 이미 검증된 계약과 동일한 계약을 재검증할 수 없기 때문입니다. 이를 우회하려면 다음 명령을 실행하세요:


forge verify-contract <contract_address> <contract_name> --chain <chain_name>

화이트리스트에 사용자 추가하기

이 계약은 충분히 간단하므로 웹사이트를 구축하지 않을 것입니다. 대신 이더스캔을 통해 화이트리스트에 사용자를 추가할 것입니다. 이더스캔에서 계약을 검증했으므로 이 작업은 매우 쉽습니다.

Sepolia Etherscan으로 이동하여 계약 주소를 검색한 후, 해당 페이지의 'Contract' 탭으로 이동하세요. Etherscan 검증이 정상적으로 완료되었다면 다음과 같은 화면이 표시될 것입니다:

계약 읽기 탭으로 이동하여 numAddressesWhitelisted를 클릭하세요. 아직 화이트리스트에 가입한 사람이 없으므로 현재 값은 0으로 설정되어 있을 것입니다.

이제 계약 쓰기 탭으로 이동하여 먼저 Web3 연결을 클릭하세요. 그러면 지갑을 Etherscan에 연결하라는 메시지가 표시됩니다. 지갑을 연결한 후 addAddressToWhitelist를 클릭하고 쓰기를 클릭하세요.

그러면 거래 확인을 위해 지갑이 열립니다. 거래를 확인하고 완료될 때까지 기다리세요. Etherscan에서 거래 보기 를 클릭하여 상태를 확인할 수 있습니다.

거래가 성공적으로 완료되면 계약 읽기 탭으로 돌아가 새로 고침한 후 numAddressesWhitelisted를 클릭하세요. 이제 1이라고 표시되어야 합니다.

만세! 계약이 정상 작동하며 화이트리스트에 성공적으로 가입되었습니다!

'계약 작성' 탭에서 다른 지갑을 연결해 보고 여러 계정으로 화이트리스트에 가입해 보세요.

NFT 계약 작성
이제 NFT 계약에 대해 생각해 보겠습니다. 다음 요구 사항을 제시해 보겠습니다:

  1. 총 20개의 Crypto Devs NFT가 존재할 수 있습니다

  2. 사용자는 거래당 1개의 NFT만 민팅할 수 있어야 합니다

  3. 화이트리스트에 등록된 사용자는 무료로 NFT를 민팅할 수 있어야 합니다

  4. 기타 사용자는 NFT 민팅 시 0.01 ETH를 지불해야 합니다

NFT 계약을 구축하기 위해 OpenZeppelin의 Contracts를 활용하겠습니다. 터미널에서 foundry-appdirectory 디렉터리를 지정하고 OZ Contracts 라이브러리를 설치하세요:

forge install OpenZeppelin/openzeppelin-contracts

프로젝트에서 이를 구성하고 코드 컴파일 시 적용되도록 하려면 다음 명령어를 실행하세요:

forge remappings > remappings.txt

이제 foundry-dapp/src 디렉터리 내에 CryptoDevs.sol이라는 새 파일을 생성하고 다음 코드를 작성하세요:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "./Whitelist.sol";

contract CryptoDevs is ERC721Enumerable, Ownable {
    //  _price is the price of one Crypto Dev NFT
    uint256 constant public _price = 0.01 ether;

    // Max number of CryptoDevs that can ever exist
    uint256 constant public maxTokenIds = 20;

    // Whitelist contract instance
    Whitelist whitelist;

    // Number of tokens reserved for whitelisted members
    uint256 public reservedTokens;
    uint256 public reservedTokensClaimed = 0;

    /**
      * @dev ERC721 constructor takes in a `name` and a `symbol` to the token collection.
      * name in our case is `Crypto Devs` and symbol is `CD`.
      * Constructor for Crypto Devs takes in the baseURI to set _baseTokenURI for the collection.
      * It also initializes an instance of whitelist interface.
      */
    constructor (address whitelistContract) ERC721("Crypto Devs", "CD") Ownable(msg.sender) {
        whitelist = Whitelist(whitelistContract);
        reservedTokens = whitelist.maxWhitelistedAddresses();
    }

    function mint() public payable {
        // Make sure we always leave enough room for whitelist reservations
        require(totalSupply() + reservedTokens - reservedTokensClaimed < maxTokenIds, "EXCEEDED_MAX_SUPPLY");

        // If user is part of the whitelist, make sure there is still reserved tokens left
        if (whitelist.whitelistedAddresses(msg.sender) && msg.value < _price) {
            // Make sure user doesn't already own an NFT
            require(balanceOf(msg.sender) == 0, "ALREADY_OWNED");
            reservedTokensClaimed += 1;
        } else {
            // If user is not part of the whitelist, make sure they have sent enough ETH
            require(msg.value >= _price, "NOT_ENOUGH_ETHER");
        }
        uint256 tokenId = totalSupply();
        _safeMint(msg.sender, tokenId);
    }

    /**
    * @dev withdraw sends all the ether in the contract
    * to the owner of the contract
      */
    function withdraw() public onlyOwner  {
        address _owner = owner();
        uint256 amount = address(this).balance;
        (bool sent, ) =  _owner.call{value: amount}("");
        require(sent, "Failed to send Ether");
    }
}

이 계약을 검토하면서, 우리는 OpenZeppelin의 ERC-721 구현을 활용해 NFT 계약을 구축했습니다. 이후 각 NFT의 가격과 maxTokenIds(즉, 존재할 수 있는 최대 토큰 수)를 위한 상수 몇 가지를 설정했습니다. 생성자에서는 msg.sender(즉, 계약을 배포하는 사람)를 소유자로 처리하도록 명시했습니다. 또한 생성자에서 화이트리스트 계약에 대한 참조를 생성하고, 화이트리스트에 등록될 수 있는 인원 수(즉, 화이트리스트 사용자를 위해 항상 예약되어야 하는 토큰 수)를 명시했습니다.

그런 다음, mint 함수에서 모든 요구 사항이 충족되었는지 확인하는 검사를 수행한 후, 모든 것이 정상으로 보이면 사용자에게 새 NFT를 발행합니다!

NFT 계약 배포하기

CryptoDevs 계약을 배포하려면 foundry-dapp 디렉터리에서 다음 명령을 실행하세요.

forge create --rpc-url $QUICKNODE_RPC_URL --private-key $PRIVATE_KEY --constructor-args <your Whitelist Contract's address> --etherscan-api-key $ETHERSCAN_API_KEY --verify src/CryptoDevs.sol:CryptoDevs

<귀하의 화이트리스트 계약 주소>를 귀하의 화이트리스트 계약 주소로 대체하십시오.

💡
계약이 실제로 검증되지 않았음에도 이미 검증되었다는 메시지가 표시될 수 있습니다. 이는 이미 검증된 계약과 동일한 계약을 재검증할 수 없기 때문입니다. 이를 우회하려면 다음 명령어를 실행하세요:

forge verify-contract --chain

나머지 명령어는 화이트리스트 계약 배포 스크립트와 거의 동일합니다. 유일한 차이는 생성자 인수입니다. 이전에는 화이트리스트 사용자 최대 수를 지정해야 했지만, 이번에는 화이트리스트 계약 주소를 제공해야 합니다.

배포가 완료되고 Etherscan에서 계약이 검증될 때까지 기다리세요. 계약 검증이 완료되면 이제 테스트해 보겠습니다!

화이트리스트 등록된 민트 테스트

이제, 이전에 여러분의 주소로 화이트리스트에 가입했다고 가정하고 테스트해 보겠습니다.

Sepolia Etherscan에서 여러분의 NFT 계약을 열고 계약 탭으로 이동한 다음 계약 쓰기로 이동하세요. 지갑을 Etherscan에 연결한 후 민트 함수를 호출해 봅시다.

Etherscan이 이더로 payableAmount를 작성할 것을 요구한다는 것을 알 수 있을 것입니다. 민트 함수를 payable로 선언했으므로 거래와 함께 ETH를 첨부할 수 있습니다. 하지만 현재는 화이트리스트 주소로 테스트 중이므로 NFT 민트 비용을 지불할 필요가 없습니다!

payableAmount를 0으로 입력한 후 'Write'를 클릭하세요. 거래를 확인하고 진행 과정을 지켜보면, Sepolia에 새로운 NFT가 생성됩니다!

화이트리스트에 등록되지 않은 민트 테스트

이제 지갑에서 이전에 화이트리스트에 등록되지 않은 다른 계정으로 전환하세요. Etherscan을 새로고침하고 새 지갑으로 연결하세요. payableAmount를 변경하지 않은 상태에서 민트를 다시 시도하면 거래가 실제로 실패하는 것을 확인할 수 있습니다!

하지만 payableAmount를 0.01로 변경하고 다시 Write를 누르면 - 이제 NFT 가격을 지불하는 것이므로 거래가 성공합니다!

만세!

결론

여기까지 오셨다면 축하합니다! 이번 강의에서는 다양한 새로운 주제를 다루었으며, 좋은 학습 경험이 되었기를 바랍니다.

Foundry를 사용해 계약을 작성하고 배포하는 방법, Etherscan에서 계약을 검증한 후 Etherscan을 통해 계약과 상호작용하는 방법, 그리고 하나의 스마트 계약이 다른 스마트 계약의 함수를 호출하는 방법을 배웠습니다!

의문점이나 질문이 있거나, 단순히 성공을 축하하고 싶다면 디스코드 서버에서 저희에게 연락해 주세요. 기꺼이 도와드리겠습니다!

-- 리믹스로 진행하기

  1. 리믹스 접속 후 Whitelist.sol 파일 생성 후 컴파일
  2. max white list 숫자 설정 후 배포
  3. addAddressToWhitelist 함수를 호출해서 화이트리스트에 추가

  1. CryptoDevs.sol 배포시 화이트리스트 컨트랙트 설정 후 디플로이
  2. CryptoDevs.sol 컨트랙트에서 화이트리스트에 속한 address는 mint 함수 호출

원본 : https://learnweb3.io/degrees/ethereum-developer-degree/sophomore/build-an-nft-collection-with-a-whitelist-using-foundry-and-solidity/

블록체인 트위터 : https://x.com/yonggal05739034

관련글 더보기