NFT minter 튜토리얼: 풀스택 dApp 만드는 법
작성자: Alchemy Team
이 튜토리얼에서는 NFT minter를 만들고, MetaMask와 Web3 도구를 사용해 스마트 컨트랙트를 React 프론트엔드에 연결하여 풀스택 dApp을 만드는 방법을 배웁니다.
그리고 저희 NFT 튜토리얼 시리즈의 나머지 글들도 꼭 확인해 보세요:
Web2 배경을 가진 개발자들에게 가장 큰 어려움 중 하나는 스마트 컨트랙트를 프론트엔드 프로젝트에 연결하고 이를 다루는 방법을 파악하는 것입니다.
NFT minter—디지털 자산 링크, 제목, 설명을 입력할 수 있는 간단한 UI—를 만들면서 다음 방법을 배우게 됩니다:
- 프론트엔드 프로젝트를 통해 MetaMask에 연결하기
- 프론트엔드에서 스마트 컨트랙트 메서드 호출하기
- MetaMask로 트랜잭션 서명하기
이 튜토리얼에서는 프론트엔드 프레임워크로 React를 사용합니다. 이 튜토리얼은 주로 Web3 개발에 초점을 맞추고 있기 때문에 React의 기본 개념을 자세히 다루지는 않습니다. 대신 프로젝트에 기능을 추가하는 데 집중하겠습니다
사전 지식으로, 컴포넌트, props, useState/useEffect, 기본 함수 호출 방식이 어떻게 동작하는지 등 React에 대한 초급 수준의 이해가 있어야 합니다. 이러한 용어들을 들어본 적이 없다면 이 React 입문 튜토리얼을 참고하시기 바랍니다. 시각적으로 학습하는 것을 선호하신다면, Net Ninja의 훌륭한 Full Modern React Tutorial 영상 시리즈를 강력히 추천합니다.
더 이상 지체하지 말고 시작해 봅시다!
0단계: NFT 만들기 101
코드를 살펴보기 전에, NFT를 만드는 과정이 어떻게 작동하는지 이해하는 것이 중요합니다. 여기에는 두 단계가 있습니다:
1) Ethereum 블록체인에 NFT 스마트 컨트랙트를 배포합니다. 보통 ERC-721 또는 ERC-1155 스마트 컨트랙트를 사용합니다.
두 NFT 스마트 컨트랙트 표준 간의 가장 큰 차이점은, ERC-1155는 멀티 토큰 표준이며 배치 기능을 포함하는 반면, ERC-721은 단일 토큰 표준이라 한 번에 하나의 토큰만 전송할 수 있다는 점입니다.
2) 해당 NFT 스마트 컨트랙트의 민팅 함수를 호출하여 NFT를 민팅합니다. _민팅_이란 단순히 블록체인 상에 여러분의 대체 불가능한 토큰의 고유한 인스턴스를 발행하는 행위를 말합니다.
보통 이 민팅 함수는 파라미터로 두 개의 변수를 전달받습니다. 첫 번째는 receiver로, 새로 민팅된 NFT를 받을 주소를 지정하며, 두 번째는 NFT의 tokenURI로, NFT의 메타데이터를 설명하는 JSON 문서로 연결되는 문자열입니다.
NFT의 메타데이터는 NFT에 생명을 불어넣는 요소로, 이름, 설명, 이미지(또는 다른 디지털 자산) 및 기타 속성들을 가질 수 있게 해줍니다. 여기 NFT의 메타데이터를 담고 있는 tokenURI의 예시가 있습니다.
이 튜토리얼에서는 2단계, 즉 React UI를 사용해 기존 NFT 스마트 컨트랙트의 민팅 함수를 호출하는 것에 초점을 맞춥니다.
이 튜토리얼에서 호출할 ERC-721 NFT 스마트 컨트랙트는 여기에서 확인하실 수 있습니다. 이를 어떻게 만들었는지 배우고 싶으시다면, 저희의 다른 튜토리얼인 "NFT를 만드는 방법"을 확인해 보시길 강력히 추천합니다.
좋습니다, 이제 NFT를 만드는 과정을 이해했으니 스타터 파일을 클론해 봅시다!
1단계: 스타터 파일 클론하기
먼저 nft-minter-tutorial github 저장소로 가서 이 프로젝트의 스타터 파일을 가져옵니다. 이 저장소를 로컬 환경에 클론하세요.
저장소를 클론하는 방법을 모르시나요? Github의 이 가이드를 확인해 보세요.
클론한 nft-minter-tutorial 저장소를 열어보면 두 개의 폴더가 있는 것을 확인할 수 있습니다: minter-starter-files와 nft-minter입니다.
- minter-starter-files에는 이 프로젝트의 스타터 파일(사실상 React UI)이 들어있습니다. 이 튜토리얼에서 이 디렉터리에서 작업하게 되며, 이 UI를 Ethereum 지갑과 NFT 스마트 컨트랙트에 연결해 실제로 동작하게 만드는 방법을 배웁니다.
- nft-minter에는 완성된 전체 튜토리얼 코드가 들어있으며, 막히는 부분이 있을 때 참고용으로 사용하시면 됩니다.
다음으로 복제한 minter-starter-files를 원하는 코드 에디터로 열어보세요(Alchemy에서는 VSCode를 애용합니다). 그런 다음 src 폴더로 이동하세요:
우리는 "src" 폴더 안에서 작업할 것입니다

앞으로 작성할 모든 코드는 src 폴더 아래에 위치합니다. 우리는 Minter.js 컴포넌트를 수정하고, 프로젝트에 Web3 기능을 추가하기 위해 추가적인 javascript 파일들을 작성할 것입니다.
2단계: 스타터 파일 살펴보기
코딩을 시작하기 전에, 스타터 파일에 이미 제공된 내용을 확인하는 것이 중요합니다.
React 프로젝트 실행하기
먼저 브라우저에서 React 프로젝트를 실행해 봅시다. React의 장점은 일단 프로젝트가 브라우저에서 실행되고 나면, 저장하는 모든 변경 사항이 브라우저에 실시간으로 반영된다는 것입니다.
프로젝트를 실행하려면 minter-starter-files 폴더의 루트 디렉터리로 이동한 후, 터미널에서 npm install을 실행해 프로젝트의 의존성을 설치합니다:
설치가 완료되면 터미널에서 npm start을 실행하세요:
이렇게 하면 브라우저에서 http://localhost:3000/이 열리고, 프로젝트의 프론트엔드를 볼 수 있습니다. 여기에는 3개의 필드가 있어야 합니다: NFT 자산 링크를 입력하는 곳, NFT 이름을 입력하는 곳, 설명을 입력하는 곳입니다.

"Connect Wallet" 또는 "Mint NFT" 버튼을 클릭해 보면 작동하지 않는 것을 확인할 수 있습니다—아직 해당 기능을 프로그래밍하지 않았기 때문이죠! :)
minter.js 컴포넌트
참고: nft-minter 폴더가 아니라 minter-starter-files 폴더에 있는지 확인하세요!
에디터에서 src 폴더로 돌아가 Minter.js 파일을 열어봅시다. 이 파일은 앞으로 작업할 주요 React 컴포넌트이므로, 안에 있는 모든 내용을 이해하는 것이 매우 중요합니다.
이 파일의 맨 위에는 특정 이벤트 이후에 업데이트할 상태 변수들이 있습니다.
React 상태 변수나 상태 훅에 대해 들어본 적이 없으신가요? 이 문서를 확인해 보세요.
각 변수가 나타내는 것은 다음과 같습니다:
- walletAddress - 사용자의 지갑 주소를 저장하는 문자열
- status - UI 하단에 표시할 메시지를 담고 있는 문자열
- name - NFT의 이름을 저장하는 문자열
- description - NFT의 설명을 저장하는 문자열
- url - NFT의 디지털 자산 링크인 문자열
상태 변수들 다음에는 아직 구현되지 않은 세 개의 함수가 있습니다: useEffect, connectWalletPressed, onMintPressed. 이 함수들은 모두 async임을 알 수 있는데, 이는 내부에서 비동기 API 호출을 하기 때문입니다! 함수 이름은 각각의 기능을 그대로 나타냅니다:
- useEffect - 컴포넌트가 렌더링된 후 호출되는 React 훅입니다. 빈 배열 [] prop이 전달되어 있으므로(3번째 줄 참고), 컴포넌트의 첫 렌더링 시에만 호출됩니다. 여기서는 wallet listener와 지갑 함수를 호출해 이미 지갑이 연결되어 있는지 여부를 UI에 반영합니다.
- connectWalletPressed - 사용자의 MetaMask 지갑을 우리 dApp에 연결하기 위해 호출되는 함수입니다.
- onMintPressed - 사용자의 NFT를 민팅하기 위해 호출되는 함수입니다.
이 파일의 끝부분에는 컴포넌트의 UI가 있습니다. 이 코드를 주의 깊게 살펴보면, 해당 텍스트 필드의 입력값이 변경될 때 url, name, description 상태 변수를 업데이트하는 것을 알 수 있습니다.
또한 mintButton과 walletButton ID를 가진 버튼을 클릭할 때 각각 connectWalletPressed과 onMintPressed가 호출되는 것도 확인할 수 있습니다.
마지막으로 이 Minter 컴포넌트가 어디에 추가되는지 살펴봅시다.
App.js 파일—다른 모든 컴포넌트의 컨테이너 역할을 하는 React의 메인 컴포넌트—로 가보면, 7번째 줄에 우리 Minter 컴포넌트가 삽입되어 있는 것을 확인할 수 있습니다.
이 튜토리얼에서는 Minter.js 파일만 수정하고, src 폴더에 파일을 추가하는 작업만 하게 됩니다.
이제 무엇을 다루고 있는지 이해했으니, Ethereum 지갑을 설정해 봅시다!
3단계: Ethereum 지갑 설정하기
사용자들이 여러분의 스마트 컨트랙트와 상호작용하려면 Ethereum 지갑을 dApp에 연결해야 합니다.
MetaMask 다운로드하기
이 튜토리얼에서는 브라우저용 가상 지갑으로 Ethereum 계정 주소를 관리하는 MetaMask를 사용합니다. Ethereum의 트랜잭션이 어떻게 동작하는지 더 알고 싶다면, Ethereum 재단의 이 페이지를 확인해 보세요.
여기에서 무료로 MetaMask 계정을 다운로드하고 생성할 수 있습니다. 계정을 생성하는 중이거나 이미 계정이 있다면, 오른쪽 위에서 "Ropsten Test Network"로 전환하세요(실제 돈을 다루지 않기 위해서입니다).

faucet에서 ether 받기
NFT를 민팅하기 위해(또는 Ethereum 블록체인에서 어떤 트랜잭션에든 서명하기 위해), 가짜 Eth가 필요합니다. Eth를 받으려면 Sepolia faucet으로 가서 Ropsten 계정 주소를 입력한 후 "Send Ropsten Eth"를 클릭하세요. 잠시 후 MetaMask 계정에 Eth가 표시될 것입니다!
잔액 확인하기
잔액이 제대로 들어왔는지 확인하기 위해 Alchemy의 composer 도구를 사용해 eth_getBalance 요청을 보내 봅시다. 이렇게 하면 지갑에 있는 Eth의 양이 반환됩니다. MetaMask 계정 주소를 입력하고 "Send Request"를 클릭하면 다음과 같은 응답을 볼 수 있습니다:
참고: 이 결과는 eth가 아니라 wei 단위입니다. wei는 ether의 가장 작은 단위입니다. wei에서 eth로의 변환은 다음과 같습니다: 1 eth = 10¹⁸ wei. 따라서 0xde0b6b3a7640000을 10진수로 변환하면 1*10¹⁸이 되고, 이는 1 eth와 같습니다.
휴! 가짜 돈이 모두 제대로 있네요! 🤑
4단계: MetaMask를 UI에 연결하기
이제 MetaMask 지갑이 설정되었으니, dApp을 여기에 연결해 봅시다!
M-V-C 패러다임을 따르고자 하므로, dApp의 로직, 데이터, 규칙을 관리하는 함수들을 담는 별도의 파일을 만든 다음, 그 함수들을 프론트엔드(Minter.js 컴포넌트)에 전달할 것입니다.
connectWallet 함수
이를 위해 src 디렉터리에 utils라는 새 폴더를 만들고, 그 안에 interact.js라는 파일을 추가하세요. 이 파일에는 지갑 및 스마트 컨트랙트와 상호작용하는 모든 함수가 들어갑니다.
interact.js 파일에서 connectWallet 함수를 작성하고, 이를 Minter.js 컴포넌트에서 import하여 호출할 것입니다.
interact.js 파일에 다음 내용을 추가하세요
이 코드가 무엇을 하는지 살펴봅시다:
먼저, 함수는 브라우저에서 window.ethereum가 활성화되어 있는지 확인합니다.
window.ethereum은 MetaMask와 다른 지갑 프로바이더들이 주입하는 전역 API로, 웹사이트가 사용자의 Ethereum 계정을 요청할 수 있게 해줍니다. 승인되면, 사용자가 연결된 블록체인의 데이터를 읽고, 사용자에게 메시지와 트랜잭션에 서명하도록 제안할 수 있습니다. 더 자세한 내용은 MetaMask 문서를 확인해 보세요!
window.ethereum이 존재하지 않는다면, 이는 MetaMask가 설치되어 있지 않다는 것을 의미합니다. 이 경우 JSON 객체가 반환되며, address로는 빈 문자열이 반환되고, status JSX 객체는 사용자가 MetaMask를 설치해야 한다는 메시지를 전달합니다.
우리가 작성할 함수 대부분은 상태 변수와 UI를 업데이트하는 데 사용할 수 있는 JSON 객체를 반환할 것입니다.
이제 window.ethereum 가 존재한다면, 그때부터 흥미로워집니다.
try/catch 루프를 사용해 window.ethereum.request\(\{ method: "eth\_requestAccounts" \}\); 를 호출하여 MetaMask에 연결을 시도합니다. 이 함수를 호출하면 브라우저에서 MetaMask가 열리고, 사용자에게 dApp에 지갑을 연결하라는 프롬프트가 표시됩니다.
- 사용자가 연결을 선택하면,
method: "eth_requestAccounts"은 dApp에 연결된 사용자의 모든 계정 주소를 담은 배열을 반환합니다. 전체적으로connectWallet함수는 이 배열의 첫 번째address(9번째 줄 참고)와, 사용자에게 스마트 컨트랙트에 메시지를 작성하도록 안내하는status메시지를 담은 JSON 객체를 반환합니다. - 사용자가 연결을 거부하면, JSON 객체에는 반환된
address에 빈 문자열이 담기고, 사용자가 연결을 거부했다는 것을 전달하는status메시지가 담깁니다.
connectWallet 함수를 minter.js UI 컴포넌트에 추가하기
이제 connectWallet 함수를 작성했으니, Minter.js 컴포넌트에 연결해 봅시다.
먼저 Minter.js 파일의 맨 위에 다음을 추가해 함수를 import해야 합니다
import \{ connectWallet \} from "./utils/interact.js"**;**를 Minter.js 파일의 맨 위에 추가하세요. 이제 Minter.js의 첫 11줄은 다음과 같아야 합니다:
그런 다음, connectWalletPressed 함수 안에서 import한 connectWallet 함수를 다음과 같이 호출합니다:
우리 기능의 대부분이 Minter.js 컴포넌트로부터 interact.js 파일로 추상화되어 있는 것을 확인할 수 있습니다. 이는 M-V-C 패러다임을 준수하기 위함입니다!
connectWalletPressed에서는 단순히 import한 connectWallet 함수를 await로 호출하고, 그 응답을 사용해 상태 훅을 통해 status와 walletAddress 변수를 업데이트합니다.
이제 두 파일(Minter.js와 interact.js)을 저장하고 지금까지 만든 UI를 테스트해 봅시다.
브라우저에서 http://localhost:3000/ 페이지를 열고, 페이지 오른쪽 위에 있는 "Connect Wallet" 버튼을 누르세요.
MetaMask가 설치되어 있다면, dApp에 지갑을 연결하라는 프롬프트가 표시될 것입니다. 연결 요청을 수락하세요.
지갑 버튼이 이제 여러분의 주소가 연결되었음을 보여줄 것입니다! Yasssss 🔥
다음으로 페이지를 새로고침해 보세요... 이상하네요. 지갑이 이미 연결되어 있는데도 지갑 버튼이 MetaMask 연결을 요청하고 있습니다...

하지만 걱정하지 마세요! getCurrentWalletConnected이라는 함수를 구현함으로써 쉽게 해결할 수 있습니다. 이 함수는 주소가 이미 dApp에 연결되어 있는지 확인하고 그에 따라 UI를 업데이트합니다!
getCurrentWalletConnected 함수
interact.js 파일에 다음 getCurrentWalletConnected 함수를 추가하세요:
이 코드는 방금 작성한 connectWallet 함수와 매우 유사합니다.
주요 차이점은, 사용자가 지갑을 연결할 수 있도록 MetaMask를 여는 eth_requestAccounts 메서드를 호출하는 대신, 여기서는 dApp에 현재 연결된 MetaMask 주소들을 담은 배열을 단순히 반환하는 eth_accounts 메서드를 호출한다는 점입니다.
이 함수가 실제로 동작하는 것을 보기 위해, Minter.js 컴포넌트의 useEffect 함수에서 이를 호출해 봅시다.
connectWallet에서 했던 것처럼, interact.js 파일에서 이 함수를 Minter.js 파일로 import해야 합니다:
이제 useEffect 함수에서 이를 단순히 호출합니다:
getCurrentWalletConnected 호출의 응답을 사용해 walletAddress와 status 상태 변수를 업데이트하는 것을 확인하세요.
이 코드를 추가한 후, 브라우저 창을 새로고침해 보세요. 버튼에 연결되어 있다는 문구와 함께 연결된 지갑 주소의 미리보기가 표시될 것입니다—새로고침 후에도요! 😅
addWalletListener 구현하기
dApp 지갑 설정의 마지막 단계는 사용자가 연결을 해제하거나 계정을 전환하는 등 지갑 상태가 변경될 때 UI가 업데이트되도록 wallet listener를 구현하는 것입니다.
Minter.js 파일에 다음과 같은 addWalletListener 함수를 추가하세요:
여기서 일어나는 일을 간단히 살펴봅시다:
먼저, 함수는 window.ethereum이 활성화되어 있는지(즉, MetaMask가 설치되어 있는지) 확인합니다.
- 활성화되어 있지 않다면, 단순히
status상태 변수를 사용자에게 MetaMask 설치를 안내하는 JSX 문자열로 설정합니다. - 활성화되어 있다면, 3번째 줄에서
window.ethereum.on("accountsChanged")listener를 설정하는데, 이는 사용자가 dApp에 추가 계정을 연결하거나, 계정을 전환하거나, 계정 연결을 해제하는 등 MetaMask 지갑의 상태 변화를 감지합니다. 연결된 계정이 하나 이상 있다면,walletAddress상태 변수는 listener가 반환한accounts배열의 첫 번째 계정으로 업데이트됩니다. 그렇지 않다면walletAddress은 빈 문자열로 설정됩니다.
마지막으로, useEffect 함수에서 이를 호출해야 합니다:
짜잔! 이제 지갑 관련 모든 기능 프로그래밍을 완료했습니다! 지갑 설정이 끝났으니, 이제 NFT를 민팅하는 방법을 알아봅시다!
5단계: NFT 메타데이터 101
0단계에서 이야기했던 NFT 메타데이터를 기억하시나요—이는 NFT에 생명을 불어넣어, 디지털 자산, 이름, 설명 및 기타 속성들을 가질 수 있게 해줍니다.
이 메타데이터를 JSON 객체로 구성하고 저장해야, 스마트 컨트랙트의 mintNFT 함수를 호출할 때 tokenURI 파라미터로 전달할 수 있습니다.
"Link to Asset", "Name", "Description" 필드에 입력된 텍스트는 NFT 메타데이터의 여러 속성을 구성합니다. 이 메타데이터를 JSON 객체 형태로 만들 것인데, 이 JSON 객체를 저장할 몇 가지 옵션이 있습니다:
- Ethereum 블록체인에 저장할 수도 있습니다. 하지만 Ethereum의 특성상 이렇게 하면 엄청나게 비쌉니다(수백 달러를 훌쩍 넘는 수준). ❌
- AWS나 Firebase 같은 중앙화된 서버에 저장할 수도 있습니다. 하지만 이는 우리의 탈중앙화 철학에 어긋납니다. ❌
- IPFS를 사용할 수 있습니다. IPFS는 분산 파일 시스템에서 데이터를 저장하고 공유하기 위한 탈중앙화 프로토콜이자 피어투피어 네트워크입니다. 이 프로토콜은 탈중앙화되어 있고 무료이므로, 가장 좋은 선택입니다! ✅
메타데이터를 IPFS에 저장하기 위해, 편리한 IPFS API와 도구 모음인 Pinata를 사용할 것입니다. 다음 단계에서 이를 정확히 어떻게 하는지 설명하겠습니다!
6단계: Pinata를 사용해 메타데이터를 IPFS에 고정하기
Pinata 계정이 없다면 여기에서 무료 계정에 가입하고 이메일 및 계정 인증 절차를 완료하세요.
Pinata API 키 생성하기
https://app.pinata.cloud/developers/api-keys 페이지로 이동한 다음, 상단의 "New Key" 버튼을 선택하고, Admin 위젯을 활성화한 후 키의 이름을 지정하세요.

그러면 API 정보가 담긴 팝업이 표시됩니다. 이 정보를 안전한 곳에 보관해 두세요.

이제 키가 설정되었으니, 프로젝트에 추가해 사용할 수 있게 해봅시다.
.env 파일 생성하기
Pinata 키와 secret을 환경 파일에 안전하게 저장할 수 있습니다. 프로젝트 디렉터리에 dotenv 패키지를 설치해 봅시다.
터미널에서 새 탭을 열고(로컬 호스트를 실행 중인 탭과는 별도로) minter-starter-files 폴더에 있는지 확인한 다음, 터미널에서 다음 명령어를 실행하세요:
다음으로, 커맨드라인에서 다음을 입력해 minter-starter-files의 루트 디렉터리에 .env 파일을 생성하세요:
이렇게 하면 vim(텍스트 에디터)에서 .env 파일이 열립니다. 저장하려면 키보드에서 "esc" + ":" + "q"를 순서대로 누르세요.
다음으로, VSCode에서 .env 파일로 이동해 Pinata API 키와 API secret을 다음과 같이 추가하세요:
파일을 저장하면, JSON 메타데이터를 IPFS에 업로드하는 함수를 작성할 준비가 된 것입니다!
pinJSONToIPFS 구현하기
다행히도 Pinata에는 JSON 데이터를 IPFS에 업로드하기 위한 API가 있으며, 약간의 수정만 하면 사용할 수 있는 편리한 JavaScript axios 예제도 있습니다.
utils 폴더에 pinata.js라는 파일을 새로 만들고, .env 파일에서 Pinata secret과 key를 다음과 같이 import하세요:
다음으로, 아래의 추가 코드를 pinata.js 파일에 붙여넣으세요. 걱정하지 마세요, 모든 내용이 무엇을 의미하는지 하나씩 설명하겠습니다!
그렇다면 이 코드는 정확히 무엇을 할까요?
먼저, axios를 import합니다. axios는 브라우저와 node.js를 위한 promise 기반 HTTP 클라이언트로, Pinata에 요청을 보내는 데 사용할 것입니다.
그런 다음 비동기 함수 pinJSONToIPFS가 있는데, 이 함수는 입력으로 JSONBody을, 헤더에는 Pinata api key와 secret을 받아, Pinata의 pinJSONToIPFS API에 POST 요청을 보냅니다.
- 이 POST 요청이 성공하면, 함수는
successboolean이 true이고, 메타데이터가 고정된 위치인pinataUrl을 담은 JSON 객체를 반환합니다. 이렇게 반환된pinataUrl을 스마트 컨트랙트의 mint 함수에tokenURI입력으로 사용할 것입니다. - 이 post 요청이 실패하면, 함수는
successboolean이 false이고, 오류를 전달하는message문자열을 담은 JSON 객체를 반환합니다.
앞서 작성한 connectWallet 함수의 반환 타입과 마찬가지로, 여기서도 JSON 객체를 반환해 그 파라미터를 상태 변수와 UI를 업데이트하는 데 사용할 수 있습니다.
7단계: 스마트 컨트랙트 불러오기
이제 pinJSONToIPFS 함수를 통해 NFT 메타데이터를 IPFS에 업로드하는 방법이 생겼으니, mintNFT 함수를 호출할 수 있도록 스마트 컨트랙트의 인스턴스를 불러오는 방법이 필요합니다.
앞서 언급했듯이, 이 튜토리얼에서는 이 기존 NFT 스마트 컨트랙트를 사용합니다. 하지만 이를 어떻게 만들었는지, 또는 직접 만들어 보고 싶다면 저희의 다른 튜토리얼인 "NFT를 만드는 방법"을 확인해 보시길 강력히 추천합니다.
컨트랙트 ABI
파일들을 자세히 살펴보셨다면, src 디렉터리에 contract-abi.json 파일이 있는 것을 발견하셨을 것입니다. ABI는 컨트랙트가 어떤 함수를 호출할지 지정하고, 그 함수가 예상하는 형식으로 데이터를 반환하도록 보장하는 데 필요합니다.
또한 Ethereum 블록체인에 연결하고 스마트 컨트랙트를 불러오기 위해 Alchemy API 키와 Alchemy Web3 API가 필요합니다.
Alchemy API 키 생성하기
아직 Alchemy 계정이 없다면 여기서 무료로 가입하세요.
Alchemy 계정을 생성했다면, 앱을 만들어 API 키를 생성할 수 있습니다. 이를 통해 Ropsten 테스트 네트워크에 요청을 보낼 수 있습니다.
Alchemy 대시보드의 네비게이션 바에서 "Apps"에 마우스를 올린 후 "Create App"을 클릭해 "Create App" 페이지로 이동하세요

앱의 이름을 지정하고(저희는 "My First NFT!"를 선택했습니다), 간단한 설명을 작성한 후, Environment에서 "Staging"(앱 관리에 사용됨)을 선택하고, 네트워크에서 "Ropsten"을 선택하세요.

"Create app"을 클릭하면 끝입니다! 이제 아래 표에 여러분의 앱이 나타날 것입니다.
훌륭합니다, 이제 HTTP Alchemy API URL을 생성했으니, 다음과 같이 클립보드에 복사하세요…

…그런 다음 이를 .env 파일에 추가합시다. 전체적으로 .env 파일은 다음과 같아야 합니다:
이제 컨트랙트 ABI와 Alchemy API 키가 준비되었으니, Alchemy Web3를 사용해 스마트 컨트랙트를 불러올 준비가 되었습니다.
Alchemy Web3 엔드포인트와 컨트랙트 설정하기
먼저, 아직 설치하지 않았다면 터미널에서 홈 디렉터리인 nft-minter-tutorial로 이동해 Alchemy Web3를 설치해야 합니다:
다음으로 interact.js 파일로 돌아갑시다. 파일 맨 위에 다음 코드를 추가해 .env 파일에서 Alchemy 키를 import하고 Alchemy Web3 엔드포인트를 설정하세요:
Alchemy Web3는 Web3.js를 감싼 wrapper로, 향상된 API 메서드와 web3 개발자로서의 삶을 더 편하게 만들어주는 다른 중요한 이점들을 제공합니다. 최소한의 설정만으로 앱에서 바로 사용할 수 있도록 설계되어 있습니다!
다음으로, 파일에 컨트랙트 ABI와 컨트랙트 주소를 추가합시다.
이 둘을 모두 준비했다면, mint 함수를 코딩할 준비가 된 것입니다!
8단계: mintNFT 함수 구현하기
interact.js 파일 안에서, 이름 그대로 NFT를 민팅할 mintNFT 함수를 정의해 봅시다.
메타데이터를 IPFS에 고정하기 위해 Pinata에, 스마트 컨트랙트를 불러오기 위해 Alchemy Web3에, 트랜잭션에 서명하기 위해 MetaMask에 여러 비동기 호출을 하게 되므로, 이 함수도 비동기 함수가 됩니다.
이 함수의 세 가지 입력은 디지털 자산의 url, name, description입니다. connectWallet 함수 아래에 다음 함수 시그니처를 추가하세요:
입력 오류 처리
당연히, 입력 파라미터가 올바르지 않으면 함수를 종료할 수 있도록 함수 시작 부분에 일종의 입력 오류 처리를 두는 것이 합리적입니다. 함수 안에 다음 코드를 추가합시다:
기본적으로, 입력 파라미터 중 하나라도 빈 문자열이라면, success boolean이 false이고, status 문자열이 UI의 모든 필드를 채워야 한다는 것을 전달하는 JSON 객체를 반환합니다.
메타데이터를 IPFS에 업로드하기
메타데이터가 올바르게 포맷되었다는 것을 확인했다면, 다음 단계는 이를 JSON 객체로 감싸서 우리가 작성한 pinJSONToIPFS을 통해 IPFS에 업로드하는 것입니다!
이를 위해서는 먼저 pinJSONToIPFS 함수를 interact.js 파일로 import해야 합니다. interact.js 맨 위에 다음을 추가합시다:
pinJSONToIPFS이 JSON body를 받는다는 것을 기억하세요. 그러므로 이를 호출하기 전에, url, name, description 파라미터를 JSON 객체로 포맷해야 합니다.
코드를 업데이트해 metadata라는 JSON 객체를 만들고, 이 metadata 파라미터를 사용해 pinJSONToIPFS를 호출합시다:
pinJSONToIPFS(metadata) 호출의 응답을 pinataResponse 객체에 저장하는 것을 확인하세요. 그런 다음 이 객체를 파싱해 오류가 있는지 확인합니다.
오류가 있다면, success boolean이 false이고, 호출이 실패했다는 것을 전달하는 status 문자열을 담은 JSON 객체를 반환합니다. 그렇지 않다면, pinataResponse에서 pinataURL을 추출해 tokenURIvariable으로 저장합니다.
이제 파일 맨 위에서 초기화한 Alchemy Web3 API를 사용해 스마트 컨트랙트를 불러올 차례입니다. mintNFT 함수 하단에 다음 코드 줄을 추가해 window.contract 전역 변수에 컨트랙트를 설정하세요:
mintNFT 함수에 마지막으로 추가할 것은 Ethereum 트랜잭션입니다:
Ethereum 트랜잭션에 이미 익숙하다면, 구조가 이전에 봤던 것과 상당히 비슷하다는 것을 알 수 있을 것입니다.
먼저, 트랜잭션 파라미터를 설정합니다.
to은 수신자 주소(우리 스마트 컨트랙트)를 지정합니다from은 트랜잭션의 서명자(MetaMask에 연결된 사용자의 주소:window.ethereum.selectedAddress``\))를 지정합니다data은 스마트 컨트랙트의mintNFT메서드 호출을 담고 있으며, 이 메서드는tokenURI과 사용자의 지갑 주소window.ethereum.selectedAddress를 입력으로 받습니다
그런 다음, MetaMask에 트랜잭션 서명을 요청하기 위해 window.ethereum.request를 await로 호출합니다. 이 요청에서 eth 메서드(eth\_SentTransaction)를 지정하고 transactionParameters을 전달하는 것을 확인하세요. 이 시점에서 브라우저에 MetaMask가 열리고, 사용자에게 트랜잭션을 서명하거나 거부하라는 프롬프트가 표시됩니다.
- 트랜잭션이 성공하면, 함수는 boolean
success이 true로 설정되고,status문자열이 트랜잭션에 대한 자세한 정보를 확인하려면 Etherscan을 확인하라는 프롬프트를 담은 JSON 객체를 반환합니다. - 트랜잭션이 실패하면, 함수는
successboolean이 false로 설정되고,status문자열이 오류 메시지를 전달하는 JSON 객체를 반환합니다.
전체적으로, mintNFT 함수는 다음과 같아야 합니다:
정말 거대한 함수네요! 이제 mintNFT 함수를 Minter.js 컴포넌트에 연결하기만 하면 됩니다...
9단계: mintNFT를 minter.js 프론트엔드에 연결하기
Minter.js 파일을 열고 맨 위의 import \{ connectWallet \} from "./utils/interact.js"; 줄을 다음과 같이 업데이트하세요:
마지막으로, import한 mintNFT 함수를 await로 호출하고, 트랜잭션이 성공했는지 실패했는지를 반영하도록 status 상태 변수를 업데이트하는 onMintPressed 함수를 구현하세요:
10단계: NFT를 실제 웹사이트에 배포하기
사용자들이 상호작용할 수 있도록 프로젝트를 실제로 배포할 준비가 되셨나요? Minter를 실제 웹사이트에 배포하는 방법에 대한 이 튜토리얼을 확인해 보세요:
11단계: 블록체인 세계를 정복하세요 🚀
농담이고요, 튜토리얼의 끝에 도달하셨습니다! 정리하자면, NFT minter를 만들면서 다음 방법을 성공적으로 배웠습니다:
- 프론트엔드 프로젝트를 통해 MetaMask에 연결하기
- 프론트엔드에서 스마트 컨트랙트 메서드 호출하기
- MetaMask로 트랜잭션 서명하기
아마도 dApp을 통해 민팅한 NFT를 지갑에서 자랑하고 싶으실 텐데요 — 저희의 짧은 튜토리얼 지갑에서 NFT를 확인하는 방법도 꼭 확인해 보세요!
그리고 언제나처럼, 궁금한 점이 있으시면 Alchemy 지원 센터를 방문해 주세요. 이 튜토리얼의 개념들을 여러분의 향후 프로젝트에 어떻게 적용하실지 기대됩니다! 🧙♂️
Alchemy 뉴스레터
출시 소식을 가장 먼저 받아보세요
뉴스레터 구독하기
Alchemy의 최신 제품 업데이트와 리소스 받기
이메일 주소를 입력하면 마케팅 커뮤니케이션 및 제품 업데이트 수신에 동의하게 됩니다. Alchemy는 저희 개인정보 처리방침에 따라 수집한 정보를 처리합니다. 언제든지 구독을 취소할 수 있습니다.
관련 아티클

Claude Code를 온체인 리서치 에이전트로 전환하기
Alchemy CLI로 Claude Code를 온체인 리서치 에이전트로 전환하세요. 적절한 질문을 통해 토큰 출시, 배포자 지갑, 관리자 키를 추적할 수 있습니다.

Cortex 라우터: 모든 요청에 가장 빠른 정상 노드를 선택하는 방법
요청을 가까운 리전으로 보내는 것은 기본 요건일 뿐입니다. 그 다음, 즉 어떤 노드가 응답할지 선택하는 과정에서 Cortex가 작동합니다.

AI 에이전트를 위한 최고의 블록체인 인프라 (2026)
2026년 AI 에이전트를 위한 최고의 블록체인 인프라 비교: 에이전트 프레임워크, 에이전트에 최적화된 RPC 및 데이터 제공자, 그리고 이들을 연결하는 표준.