---
title: "NFT minter 튜토리얼: 풀스택 dApp 만드는 법"
description: "NFT Minter 튜토리얼로 NFT의 세계를 살펴보세요. 풀스택 dApp을 쉽게 만들고 지금 바로 디지털 자산을 민팅해보세요!"
---

# NFT minter 튜토리얼: 풀스택 dApp 만드는 법

이 튜토리얼에서는 NFT minter를 만들고, MetaMask와 Web3 도구를 사용해 스마트 컨트랙트를 React 프론트엔드에 연결하여 풀스택 dApp을 만드는 방법을 배웁니다.

그리고 저희 NFT 튜토리얼 시리즈의 나머지 글들도 꼭 확인해 보세요:

- 🌟  [Web3.js로 NFT를 민팅하는 방법](https://www.alchemy.com/blog/how-to-mint-an-nft-using-web3-js)
- 🪄  [Ether.js로 NFT를 민팅하는 방법](https://www.alchemy.com/blog/how-to-mint-an-nft-with-ethers-js)
- 👛  [모바일 지갑에서 NFT를 확인하는 방법](https://www.alchemy.com/blog/how-to-view-your-nft-in-your-mobile-wallet)
- 💸  [NFT에 가격을 설정하는 방법](https://www.alchemy.com/blog/how-to-set-a-price-on-an-nft)

Web2 배경을 가진 개발자들에게 가장 큰 어려움 중 하나는 스마트 컨트랙트를 프론트엔드 프로젝트에 연결하고 이를 다루는 방법을 파악하는 것입니다.

NFT minter—디지털 자산 링크, 제목, 설명을 입력할 수 있는 간단한 UI—를 만들면서 다음 방법을 배우게 됩니다:

- 프론트엔드 프로젝트를 통해 MetaMask에 연결하기
- 프론트엔드에서 스마트 컨트랙트 메서드 호출하기
- MetaMask로 트랜잭션 서명하기

이 튜토리얼에서는 프론트엔드 프레임워크로 [React](https://reactjs.org/)를 사용합니다. 이 튜토리얼은 주로 [Web3 개발](https://www.alchemy.com/overviews/how-to-learn-web3-development)에 초점을 맞추고 있기 때문에 React의 기본 개념을 자세히 다루지는 않습니다. 대신 프로젝트에 기능을 추가하는 데 집중하겠습니다

<CalloutBlock>

사전 지식으로, 컴포넌트, props, useState/useEffect, 기본 함수 호출 방식이 어떻게 동작하는지 등 React에 대한 초급 수준의 이해가 있어야 합니다. 이러한 용어들을 들어본 적이 없다면 이 [React 입문 튜토리얼](https://reactjs.org/tutorial/tutorial.html)을 참고하시기 바랍니다. 시각적으로 학습하는 것을 선호하신다면, Net Ninja의 훌륭한 [Full Modern React Tutorial](https://www.youtube.com/playlist?list=PL4cUxeGkcC9gZD-Tvwfod2gaISzfRiP9d) 영상 시리즈를 강력히 추천합니다.

</CalloutBlock>

더 이상 지체하지 말고 시작해 봅시다!

## 0단계: NFT 만들기 101

코드를 살펴보기 전에, NFT를 만드는 과정이 어떻게 작동하는지 이해하는 것이 중요합니다. 여기에는 두 단계가 있습니다:

1\) Ethereum 블록체인에 NFT 스마트 컨트랙트를 배포합니다. 보통 [ERC-721](https://eips.ethereum.org/EIPS/eip-721) 또는 [ERC-1155](https://eips.ethereum.org/EIPS/eip-1155) 스마트 컨트랙트를 사용합니다.

<CalloutBlock>

두 NFT 스마트 컨트랙트 표준 간의 가장 큰 차이점은, ERC-1155는 멀티 토큰 표준이며 배치 기능을 포함하는 반면, ERC-721은 단일 토큰 표준이라 한 번에 하나의 토큰만 전송할 수 있다는 점입니다.

</CalloutBlock>

2\) 해당 NFT 스마트 컨트랙트의 민팅 함수를 호출하여 NFT를 민팅합니다. _민팅_이란 단순히 블록체인 상에 여러분의 대체 불가능한 토큰의 고유한 인스턴스를 발행하는 행위를 말합니다.

보통 이 민팅 함수는 파라미터로 두 개의 변수를 전달받습니다. 첫 번째는 receiver로, 새로 민팅된 NFT를 받을 주소를 지정하며, 두 번째는 NFT의 tokenURI로, NFT의 메타데이터를 설명하는 JSON 문서로 연결되는 문자열입니다.

<CalloutBlock>

NFT의 메타데이터는 NFT에 생명을 불어넣는 요소로, 이름, 설명, 이미지(또는 다른 디지털 자산) 및 기타 속성들을 가질 수 있게 해줍니다. 여기 NFT의 메타데이터를 담고 있는 [tokenURI의 예시](https://gateway.pinata.cloud/ipfs/QmSvBcb4tjdFpajGJhbFAWeK3JAxCdNQLQtr6ZdiSi42V2)가 있습니다.

</CalloutBlock>

이 튜토리얼에서는 2단계, 즉 React UI를 사용해 기존 NFT 스마트 컨트랙트의 민팅 함수를 호출하는 것에 초점을 맞춥니다.

이 튜토리얼에서 호출할 ERC-721 NFT 스마트 컨트랙트는 [여기](https://sepolia.etherscan.io/address/0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE)에서 확인하실 수 있습니다. 이를 어떻게 만들었는지 배우고 싶으시다면, 저희의 다른 튜토리얼인 "[NFT를 만드는 방법](https://www.alchemy.com/blog/how-to-create-an-nft)"을 확인해 보시길 강력히 추천합니다.​

좋습니다, 이제 NFT를 만드는 과정을 이해했으니 스타터 파일을 클론해 봅시다!

## 1단계: 스타터 파일 클론하기

먼저 [nft-minter-tutorial](https://github.com/alchemyplatform/nft-minter-tutorial) github 저장소로 가서 이 프로젝트의 스타터 파일을 가져옵니다. 이 저장소를 로컬 환경에 클론하세요.

<CalloutBlock>

저장소를 클론하는 방법을 모르시나요? Github의 [이 가이드](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository)를 확인해 보세요.

</CalloutBlock>

클론한 **nft-minter-tutorial** 저장소를 열어보면 두 개의 폴더가 있는 것을 확인할 수 있습니다: **minter-starter-files**와 **nft-minter**입니다.

- **minter-starter-files**에는 이 프로젝트의 스타터 파일(사실상 React UI)이 들어있습니다. 이 튜토리얼에서 **이 디렉터리에서 작업하게 되며**, 이 UI를 Ethereum 지갑과 NFT 스마트 컨트랙트에 연결해 실제로 동작하게 만드는 방법을 배웁니다.
- **nft-minter**에는 완성된 전체 튜토리얼 코드가 들어있으며, **막히는 부분이 있을 때 참고용**으로 사용하시면 됩니다.

다음으로 복제한 **minter-starter-files**를 원하는 코드 에디터로 열어보세요(Alchemy에서는 [VSCode](https://code.visualstudio.com/download)를 애용합니다). 그런 다음 **src** 폴더로 이동하세요:

우리는 "src" 폴더 안에서 작업할 것입니다

<ImageBlock
  src="https://media.alchemy.com/1704629644-open-minter-starter-files-in-editor.png"
  alt="VSCode에서 minter-starter-files 프로젝트를 열고 src 폴더를 보여주는 화면"
  width={2880}
  height={1798}
  caption="사용하는 에디터에서 minter-starter-files 열기"
/>

앞으로 작성할 모든 코드는 **src** 폴더 아래에 위치합니다. 우리는 **Minter.js** 컴포넌트를 수정하고, 프로젝트에 Web3 기능을 추가하기 위해 추가적인 javascript 파일들을 작성할 것입니다.

## 2단계: 스타터 파일 살펴보기

코딩을 시작하기 전에, 스타터 파일에 이미 제공된 내용을 확인하는 것이 중요합니다.

#### React 프로젝트 실행하기

먼저 브라우저에서 React 프로젝트를 실행해 봅시다. React의 장점은 일단 프로젝트가 브라우저에서 실행되고 나면, 저장하는 모든 변경 사항이 브라우저에 실시간으로 반영된다는 것입니다.

프로젝트를 실행하려면 **minter-starter-files** 폴더의 루트 디렉터리로 이동한 후, 터미널에서 `npm install`을 실행해 프로젝트의 의존성을 설치합니다:

설치가 완료되면 터미널에서 `npm start`을 실행하세요:

이렇게 하면 브라우저에서 [http://localhost:3000/](http://localhost:3000/)이 열리고, 프로젝트의 프론트엔드를 볼 수 있습니다. 여기에는 3개의 필드가 있어야 합니다: NFT 자산 링크를 입력하는 곳, NFT 이름을 입력하는 곳, 설명을 입력하는 곳입니다.

<ImageBlock
  src="https://media.alchemy.com/1704629829-frontend-for-our-project.png"
  alt="자산 링크, 이름, 설명 필드가 있는 NFT 민터 프론트엔드"
  width={2876}
  height={1606}
  caption="프로젝트 UI가 어떻게 보여야 하는지"
/>

"Connect Wallet" 또는 "Mint NFT" 버튼을 클릭해 보면 작동하지 않는 것을 확인할 수 있습니다—아직 해당 기능을 프로그래밍하지 않았기 때문이죠! :\)

#### minter.js 컴포넌트

<CalloutBlock>

**참고:** **nft-minter** 폴더가 아니라 **minter-starter-files** 폴더에 있는지 확인하세요!

</CalloutBlock>

에디터에서 **src** 폴더로 돌아가 **Minter.js** 파일을 열어봅시다. 이 파일은 앞으로 작업할 주요 React 컴포넌트이므로, 안에 있는 모든 내용을 이해하는 것이 매우 중요합니다.

이 파일의 맨 위에는 특정 이벤트 이후에 업데이트할 상태 변수들이 있습니다.

<CalloutBlock>

React 상태 변수나 상태 훅에 대해 들어본 적이 없으신가요? [이 문서](https://reactjs.org/docs/hooks-state.html)를 확인해 보세요.

</CalloutBlock>

각 변수가 나타내는 것은 다음과 같습니다:

- **walletAddress** - 사용자의 지갑 주소를 저장하는 문자열
- **status** - UI 하단에 표시할 메시지를 담고 있는 문자열
- **name** - NFT의 이름을 저장하는 문자열
- **description** - NFT의 설명을 저장하는 문자열
- **url** - NFT의 디지털 자산 링크인 문자열

상태 변수들 다음에는 아직 구현되지 않은 세 개의 함수가 있습니다: `useEffect`, `connectWalletPressed`, `onMintPressed`. 이 함수들은 모두 **async**임을 알 수 있는데, 이는 내부에서 비동기 API 호출을 하기 때문입니다! 함수 이름은 각각의 기능을 그대로 나타냅니다:

- [**useEffect**](https://reactjs.org/docs/hooks-effect.html) - 컴포넌트가 렌더링된 후 호출되는 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 재단의 [이 페이지](https://ethereum.org/en/developers/docs/transactions/)를 확인해 보세요.

[여기](https://metamask.io/download.html)에서 무료로 MetaMask 계정을 다운로드하고 생성할 수 있습니다. 계정을 생성하는 중이거나 이미 계정이 있다면, 오른쪽 위에서 "Ropsten Test Network"로 전환하세요(실제 돈을 다루지 않기 위해서입니다).

<ImageBlock
  src="https://media.alchemy.com/1704630094-sample-metamask-wallet.png"
  alt="Ropsten 테스트 네트워크로 전환된 MetaMask 지갑"
  width={712}
  height={1194}
  caption="MetaMask 지갑 예시"
/>

#### faucet에서 ether 받기

NFT를 민팅하기 위해(또는 Ethereum 블록체인에서 어떤 트랜잭션에든 서명하기 위해), 가짜 Eth가 필요합니다. Eth를 받으려면 [Sepolia faucet](/faucets/ethereum-sepolia)으로 가서 Ropsten 계정 주소를 입력한 후 "Send Ropsten Eth"를 클릭하세요. 잠시 후 MetaMask 계정에 Eth가 표시될 것입니다!

#### 잔액 확인하기

잔액이 제대로 들어왔는지 확인하기 위해 [Alchemy의 composer 도구](https://www.alchemy.com/composer/?composer_state=%7B%22network%22%3A0%2C%22methodName%22%3A%22eth_getBalance%22%2C%22paramValues%22%3A%5B%22%22%2C%22latest%22%5D%7D)를 사용해 `eth_getBalance` 요청을 보내 봅시다. 이렇게 하면 지갑에 있는 Eth의 양이 반환됩니다. MetaMask 계정 주소를 입력하고 "Send Request"를 클릭하면 다음과 같은 응답을 볼 수 있습니다:

<CalloutBlock>

**참고:** 이 결과는 eth가 아니라 wei 단위입니다. wei는 ether의 가장 작은 단위입니다. wei에서 eth로의 변환은 다음과 같습니다: 1 eth = 10¹⁸ wei. 따라서 0xde0b6b3a7640000을 10진수로 변환하면 1\*10¹⁸이 되고, 이는 1 eth와 같습니다.

</CalloutBlock>

휴! 가짜 돈이 모두 제대로 있네요! 🤑

## 4단계: MetaMask를 UI에 연결하기

이제 MetaMask 지갑이 설정되었으니, dApp을 여기에 연결해 봅시다!

[M-V-C](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) 패러다임을 따르고자 하므로, dApp의 로직, 데이터, 규칙을 관리하는 함수들을 담는 별도의 파일을 만든 다음, 그 함수들을 프론트엔드(Minter.js 컴포넌트)에 전달할 것입니다.

#### connectWallet 함수

이를 위해 **src** 디렉터리에 **utils**라는 새 폴더를 만들고, 그 안에 **interact.js**라는 파일을 추가하세요. 이 파일에는 지갑 및 스마트 컨트랙트와 상호작용하는 모든 함수가 들어갑니다.

**interact.js** 파일에서 `connectWallet` 함수를 작성하고, 이를 **Minter.js** 컴포넌트에서 import하여 호출할 것입니다.

**interact.js** 파일에 다음 내용을 추가하세요

이 코드가 무엇을 하는지 살펴봅시다:

먼저, 함수는 브라우저에서 `window.ethereum`가 활성화되어 있는지 확인합니다.

<CalloutBlock>

window.ethereum은 MetaMask와 다른 지갑 프로바이더들이 주입하는 전역 API로, 웹사이트가 사용자의 Ethereum 계정을 요청할 수 있게 해줍니다. 승인되면, 사용자가 연결된 블록체인의 데이터를 읽고, 사용자에게 메시지와 트랜잭션에 서명하도록 제안할 수 있습니다. 더 자세한 내용은 [MetaMask 문서](https://docs.metamask.io/guide/ethereum-provider.html#table-of-contents)를 확인해 보세요!

</CalloutBlock>

`window.ethereum`이 존재하지 _않는다면_, 이는 MetaMask가 설치되어 있지 않다는 것을 의미합니다. 이 경우 JSON 객체가 반환되며, **address**로는 빈 문자열이 반환되고, **status** JSX 객체는 사용자가 MetaMask를 설치해야 한다는 메시지를 전달합니다.

<CalloutBlock>

**우리가 작성할 함수 대부분은 상태 변수와 UI를 업데이트하는 데 사용할 수 있는 JSON 객체를 반환할 것입니다.**

</CalloutBlock>

이제 `window.ethereum `가 _존재한다면_, 그때부터 흥미로워집니다.

try/catch 루프를 사용해 [`window.ethereum.request\(\{ method: "eth\_requestAccounts" \}\);`](https://docs.metamask.io/guide/rpc-api.html#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/](http://localhost:3000/) 페이지를 열고, 페이지 오른쪽 위에 있는 "Connect Wallet" 버튼을 누르세요.

MetaMask가 설치되어 있다면, dApp에 지갑을 연결하라는 프롬프트가 표시될 것입니다. 연결 요청을 수락하세요.

지갑 버튼이 이제 여러분의 주소가 연결되었음을 보여줄 것입니다! Yasssss 🔥

다음으로 페이지를 새로고침해 보세요... 이상하네요. 지갑이 이미 연결되어 있는데도 지갑 버튼이 MetaMask 연결을 요청하고 있습니다...

<ImageBlock
  src="https://media.alchemy.com/1704630515-the-problem-on-page-reload.gif"
  alt="페이지를 새로고침하면 MetaMask 재연결을 잘못 요청하는 지갑 버튼"
  width={946}
  height={480}
  caption="페이지 새로고침 시 발생하는 문제"
/>

하지만 걱정하지 마세요! `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](https://pinata.cloud/)를 사용할 것입니다. 다음 단계에서 이를 정확히 어떻게 하는지 설명하겠습니다!

## 6단계: Pinata를 사용해 메타데이터를 IPFS에 고정하기

Pinata 계정이 없다면 [여기](https://app.pinata.cloud/register)에서 무료 계정에 가입하고 이메일 및 계정 인증 절차를 완료하세요.

#### Pinata API 키 생성하기

[https://app.pinata.cloud/developers/api-keys](https://app.pinata.cloud/developers/api-keys) 페이지로 이동한 다음, 상단의 "New Key" 버튼을 선택하고, Admin 위젯을 활성화한 후 키의 이름을 지정하세요.

<ImageBlock
  src="https://media.alchemy.com/1704630785-create-your-pinata-api-key.png"
  alt="Pinata API 키 페이지: Admin 위젯을 활성화하여 새 키 생성"
  width={2880}
  height={1644}
  caption="Pinata API 키 생성하기"
/>

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

<ImageBlock
  src="https://media.alchemy.com/1704630855-save-your-api-key-and-secret.png"
  alt="API 키와 시크릿을 안전한 곳에 저장해 두세요"
  width={2880}
  height={1650}
  caption="API 키와 시크릿을 안전한 곳에 저장해 두세요"
/>

이제 키가 설정되었으니, 프로젝트에 추가해 사용할 수 있게 해봅시다.

#### .env 파일 생성하기

Pinata 키와 secret을 환경 파일에 안전하게 저장할 수 있습니다. 프로젝트 디렉터리에 [dotenv 패키지](https://www.npmjs.com/package/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](https://docs.pinata.cloud#PinJSONToIPFS)가 있으며, 약간의 수정만 하면 사용할 수 있는 편리한 JavaScript axios 예제도 있습니다.

utils 폴더에 pinata.js라는 파일을 새로 만들고, .env 파일에서 Pinata secret과 key를 다음과 같이 import하세요:

다음으로, 아래의 추가 코드를 pinata.js 파일에 붙여넣으세요. 걱정하지 마세요, 모든 내용이 무엇을 의미하는지 하나씩 설명하겠습니다!

그렇다면 이 코드는 정확히 무엇을 할까요?

먼저, [axios](https://www.npmjs.com/package/axios)를 import합니다. axios는 브라우저와 node.js를 위한 promise 기반 HTTP 클라이언트로, Pinata에 요청을 보내는 데 사용할 것입니다.

그런 다음 비동기 함수 `pinJSONToIPFS`가 있는데, 이 함수는 입력으로 `JSONBody`을, 헤더에는 Pinata api key와 secret을 받아, Pinata의 `pinJSONToIPFS` API에 POST 요청을 보냅니다.

- 이 POST 요청이 성공하면, 함수는 `success` boolean이 true이고, 메타데이터가 고정된 위치인 `pinataUrl`을 담은 JSON 객체를 반환합니다. 이렇게 반환된 `pinataUrl`을 스마트 컨트랙트의 mint 함수에 `tokenURI` 입력으로 사용할 것입니다.
- 이 post 요청이 실패하면, 함수는 `success` boolean이 false이고, 오류를 전달하는 `message` 문자열을 담은 JSON 객체를 반환합니다.

앞서 작성한 `connectWallet` 함수의 반환 타입과 마찬가지로, 여기서도 JSON 객체를 반환해 그 파라미터를 상태 변수와 UI를 업데이트하는 데 사용할 수 있습니다.

## 7단계: 스마트 컨트랙트 불러오기

이제 pinJSONToIPFS 함수를 통해 NFT 메타데이터를 IPFS에 업로드하는 방법이 생겼으니, mintNFT 함수를 호출할 수 있도록 스마트 컨트랙트의 인스턴스를 불러오는 방법이 필요합니다.

<CalloutBlock>

앞서 언급했듯이, 이 튜토리얼에서는 [이 기존 NFT 스마트 컨트랙트](https://sepolia.etherscan.io/address/0x4C4a07F737Bf57F6632B6CAB089B78f62385aCaE)를 사용합니다. 하지만 이를 어떻게 만들었는지, 또는 직접 만들어 보고 싶다면 저희의 다른 튜토리얼인 "[NFT를 만드는 방법](https://www.alchemy.com/blog/how-to-create-an-nft)"을 확인해 보시길 강력히 추천합니다.

</CalloutBlock>

#### 컨트랙트 ABI

파일들을 자세히 살펴보셨다면, **src** 디렉터리에 **contract-abi.json** 파일이 있는 것을 발견하셨을 것입니다. ABI는 컨트랙트가 어떤 함수를 호출할지 지정하고, 그 함수가 예상하는 형식으로 데이터를 반환하도록 보장하는 데 필요합니다.

또한 Ethereum 블록체인에 연결하고 스마트 컨트랙트를 불러오기 위해 Alchemy API 키와 Alchemy Web3 API가 필요합니다.

#### Alchemy API 키 생성하기

아직 Alchemy 계정이 없다면 [여기서 무료로 가입](https://alchemy.com/?r=affiliate:d8895272-924f-412b-b63c-60124deb9357)하세요.

Alchemy 계정을 생성했다면, 앱을 만들어 API 키를 생성할 수 있습니다. 이를 통해 Ropsten 테스트 네트워크에 요청을 보낼 수 있습니다.

Alchemy 대시보드의 네비게이션 바에서 "Apps"에 마우스를 올린 후 "Create App"을 클릭해 "Create App" 페이지로 이동하세요

<ImageBlock
  src="https://media.alchemy.com/1704631152-create-a-new-app.jpg"
  alt="Alchemy 대시보드: Apps 메뉴에서 새 앱 생성"
  width={2880}
  height={1640}
  caption="새 앱 생성하기"
/>

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

<ImageBlock
  src="https://media.alchemy.com/1704631219-configure-your-app-details.png"
  alt="Alchemy 대시보드: 앱 이름, 설명, Ropsten 네트워크 설정"
  width={2880}
  height={1640}
  caption="앱 세부 정보 설정하기"
/>

"Create app"을 클릭하면 끝입니다! 이제 아래 표에 여러분의 앱이 나타날 것입니다.

훌륭합니다, 이제 HTTP Alchemy API URL을 생성했으니, 다음과 같이 클립보드에 복사하세요…

<ImageBlock
  src="https://media.alchemy.com/1704631290-copy-your-alchemy-api-key.gif"
  alt="Alchemy 대시보드: .env 파일에 사용할 앱의 HTTP API URL 복사"
  width={843}
  height={480}
  caption="Alchemy API 키 복사하기"
/>

…그런 다음 이를 **.env** 파일에 추가합시다. 전체적으로 .env 파일은 다음과 같아야 합니다:

이제 컨트랙트 ABI와 Alchemy API 키가 준비되었으니, [Alchemy Web3](https://github.com/alchemyplatform/alchemy-web3)를 사용해 스마트 컨트랙트를 불러올 준비가 되었습니다.

#### Alchemy Web3 엔드포인트와 컨트랙트 설정하기

먼저, 아직 설치하지 않았다면 터미널에서 홈 디렉터리인 **nft-minter-tutorial**로 이동해 [Alchemy Web3](https://github.com/alchemyplatform/alchemy-web3)를 설치해야 합니다:

다음으로 **interact.js** 파일로 돌아갑시다. 파일 맨 위에 다음 코드를 추가해 .env 파일에서 Alchemy 키를 import하고 Alchemy Web3 엔드포인트를 설정하세요:

<CalloutBlock>

[Alchemy Web3](https://github.com/alchemyplatform/alchemy-web3)는 [Web3.js](https://web3js.readthedocs.io/en/v1.2.9/)를 감싼 wrapper로, 향상된 API 메서드와 web3 개발자로서의 삶을 더 편하게 만들어주는 다른 중요한 이점들을 제공합니다. 최소한의 설정만으로 앱에서 바로 사용할 수 있도록 설계되어 있습니다!

</CalloutBlock>

다음으로, 파일에 컨트랙트 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 객체를 반환합니다.
- 트랜잭션이 실패하면, 함수는 `success` boolean이 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를 실제 웹사이트에 배포하는 방법에 대한 이 [튜토리얼](https://app.gitbook.com/@alchemyapi/s/alchemy/tutorials/nft-minter/how-do-i-deploy-nfts-online)을 확인해 보세요:

## 11단계: 블록체인 세계를 정복하세요 🚀

농담이고요, 튜토리얼의 끝에 도달하셨습니다! 정리하자면, NFT minter를 만들면서 다음 방법을 성공적으로 배웠습니다:

- 프론트엔드 프로젝트를 통해 MetaMask에 연결하기
- 프론트엔드에서 스마트 컨트랙트 메서드 호출하기
- MetaMask로 트랜잭션 서명하기

아마도 dApp을 통해 민팅한 NFT를 지갑에서 자랑하고 싶으실 텐데요 — 저희의 짧은 튜토리얼 [지갑에서 NFT를 확인하는 방법](/blog/how-to-view-your-nft-in-your-mobile-wallet)도 꼭 확인해 보세요!

그리고 언제나처럼, 궁금한 점이 있으시면 [Alchemy 지원 센터](https://www.alchemy.com/support)를 방문해 주세요. 이 튜토리얼의 개념들을 여러분의 향후 프로젝트에 어떻게 적용하실지 기대됩니다! 🧙‍♂️
