벌써 4학년... 나이를 먹고 현재 복수전공인 컴공의 졸업프로젝트를 수강하고 있다.
프로젝트 경험이 전무후무하다고 할 수 있는 셋이 모여
스불재(스스로 불러온 재앙) 한번 뛰어들어보자고 무려 주제를 NFT로 잡았다.
그래서 우리 팀의 주제는
자신만의 팬덤 캐릭터 NFT를 발행하며 기부하는 플랫폼이다.
팬덤을 타겟으로 하는 서비스인데 자세한 소개는 노션을 봐주시길 ㅎㅎ
🐣기미덕 서비스 소개 노션🐣
(많관부)
이 서비스에서 빼놓을 수 없는 메인 기능은 단연코
이미지를 토큰으로 발행하여 블록체인 위에 올리는 민팅 기능이다.
오늘은 내가 가지고 있는 이미지 파일을 블록체인에 올리기까지의 과정을 살펴보도록 하자 !
우선 크게 단계를 나눠보자면 다음과 같다.
블록체인 상에서는 업로드하는 데이터의 용량이 클 수록 수수료가 발생한다
그리고 이 수수료가... 생각보다 굉장히 비싸다. (특히 이더리움🙄)
그래서 용량을 최소한으로 차지하기 위해 이미지를 직접 올리는 것이 아닌,
이미지 정보를 담은 JSON 파일을 IPFS(분산저장소)에 올리고,
그 IPFS 저장소의 주소를 블록체인에 올리는 것이다.
간단한 그림으로 나타내보았다.
abc
"image" : "abc"
를 포함하는 JSON 파일을 생성하여 IPFS 분산 저장소에 업로드 -> IPFS주소 : def
def
를 인자로 담아 스마트 컨트랙트를 실행하여 블록체인에 업로드 이렇게 구성된다.
이번엔 블록체인에서 거슬러 올라가보면,
IPFS는 분산 저장소인데, Pinata라는 서비스를 통해 IPFS 분산 저장소에 원하는 파일을 업로드할 수 있다.
우선 나는 Pinata API 중 NodeJS SDK를 설치하여 사용하였다.
🦄Pinata API NodeJS SDK 공식문서 보러가기🦄
공식문서에 파라미터 의미, response, 예시코드까지 정말 너무나도 자세하게 잘 설명되어있기 때문에 Pinata API 설명은 생략하고, 어떤 함수를 사용해야 하는지만 간략히 짚고 넘어가겠다.
이러한 종류의 함수 중 우리가 사용할 것은 딱 두 개
pinFileToIPFS
는 이미지를 IPFS에 업로드하기 위해 사용할 것이고,
pinJSONToIPFS
는 JSON파일을 IPFS에 업로드하기 위해 사용할 것이다.
이미지 IPFS 주소를 담은 JSON 파일을 생성해야 한다.
이때 JSON 파일은 우리가 발행할 NFT의 메타데이터 역할을 한다.
그렇다면 JSON 형식은 어떻게 해야할까?
이 JSON 표준 형식은 블록체인에 민팅만 하면 OpenSea에 자동으로 업로드될 수 있도록 OpenSea가 제공하는 Metadata 표준 형식이다.
진짜 대박적... 세상 편리...
나의 이미지 IPFS 주소를 담아
해당 형태에 맞춰 JSON을 생성해주는 코드를 작성하기만 하면 된다.
여기서 우리는 external_url만 제외하고 네 개의 키값을 지정해줄 것이다.
name
: NFT 이름description
: NFT 상세 설명image
: 업로드하고자 하는 이미지의 IPFS 주소attributes
: 디폴트로 설정하기 위해서는 trait_type
과 value
모두 "Unknown"으로 설정해주면 된다.attributes
키의 값은 object를 요소로 가지는 배열이라는 점 주의!JSON 파일 내부의 형식을 지정해주는 코드이다.
위에서 형식을 지정해준 JSON body는 pinJSONToIPFS
함수의 인자로 들어간다.
JSON을 생성하고, 그 JSON을 IPFS에 업로드하는 코드는
이미지를 IPFS에 올리고 결과값을 반환해주는 pinFileToIPFS
함수의 callback에 실행되도록 구현해야 한다.
🙋🏻♀️ 왜 callback에??
이미지 파일을 업로드 하는 함수 pinFileToIPFS
와
JSON을 업로드하는 함수 pinJSONToIPFS
를 별도로 분리시켜놓을 수도 있지만,
그렇게 될 경우 코드를 실행하였을 때 JSON파일을 업로드 하는 속도가 이미지 파일 업로드 속도보다 빨라서
순서가 바뀌어 JSON이 먼저 업로드 되고 그 후에 이미지가 업로드 된다.
🙋🏻♀️ 순서 바뀌는게 어때서?
이렇게 될 경우 문제점은
이미지 파일 업로드 후 IPFS주소를 반환받아
그것을 JSON의 image 값으로 넣어줘야 하는데,
순서가 바뀌게 되면 image 값에 이미지 주소가 아닌 빈 문자열을 담은 JSON이 IPFS에 먼저 올라가게 된다는 문제가 발생하게 된다.
그렇기 때문에 반드시 이미지 업로드 -> JSON 업로드
순서를 준수하기 위해
이미지 업로드 함수의 callback 내부에 JSON 관련 코드를 구현한 것이다.
pinFileToIPFS
함수 실행 후 반환되는 결과 중 IpfsHash
값을 JSON의 image
에 넣어주면 된다.
💡 이때 주의할 점은!!!!!!!!!!!!!!!!
반드시 hash값 앞에 ipfs://
를 추가해야 한다.
그래야 이미지의 URL이 완성된다.
나는 실제로 이거 하나 추가 안하고
해시값 변수 그대로 image 값으로 넣었다가,
코드와 컨트랙트 다 완성해놓고 맨 마지막에 OpenSea에서 메타데이터가 연동이 안되는 난관에 봉착해 N시간동안 빠져나오지 못하는 경험을 했다... ㅎ
🤦🏻♀️
여러분들은 그런 곳에 시간 낭비하지 마세요...
작성한 코드는 다시말하지만 공식문서에 그대~~로 있으니
전체적인 코드 구성만 간단히 짚어보고 넘어가자.
Pinata API를 사용하여 IPFS에 File 혹은 JSON을 업로드하려면
API가 요구하는 option 데이터를 만들어서 함수호출 시 인자로 담아줘야 한다.
option1
: pinFileToIPFS를 위한 option 데이터pinFileToIPFS
: local 이미지를 IPFS에 업로드하는 함수JSON생성코드
: 위에서 살펴본 것 처럼 OpenSea Metadata 표준에 맞춘 JSON bodyoption2
: pinJSONToIPFS를 위한 option 데이터pinJSONToIPFS
: 생성한 JSON(Metadata)을 IPFS에 업로드하는 함수프로젝트 파일에 기미덕의 탄생 이전 형태, 대왕 알 이미지인 egg.png 파일을 올려놓고, 코드를 실행시켜보자.
isDuplicate
: 이전에 내가 수차례 시도해보면서 같은 이미지 파일을 업로드한 적이 있어서 데이터가 중복되니까 다시 업로드하지 않는다는 의미이다✅ File(이미지)과 JSON(메타데이터) 모두 정상적으로 업로드 되었고,
✅ 순서도 이미지->JSON대로 잘 맞춰진 것을 확인할 수 있다.
이제 Pinata로 가서 실제로 두 파일이 업로드 된 것을 확인해보자
이렇게 IPFS에 정상적으로 두개의 파일이 올라갔음을 확인할 수 있고,
우리가 앞으로 사용할 IPFS 주소의 해시값 CID도 잘 발급되었다.
GIMMEDUCK_TEST0 파일
GIMMEDUCK_TEST_JSON0 파일
원하던 형태로 이미지와 Metadata를 IPFS에 업로드하기 완료!
이제 IPFS에 업로드도 했고, IPFS 주소도 받아왔으니,
블록체인에 올리는 일만 남았다.
이미지를 민팅해주는 서비스를 이용하는 방법은 많지만
우리는 대신 직접 민팅해주는 서비스를 개발하고 있기 때문에
민팅 방식을 정의하는 스마트 컨트랙트를 직접 다룰줄 알아야 한다.
스마트 컨트랙트에서 민팅하는 함수를 알아보고,
필요에 따라 커스텀 함수를 추가 및 수정하며
나만의 스마트 컨트랙트를 배포해보자!😎
우선 스마트 컨트랙트를 작업할 환경은 Klaytn IDE이다.
별도의 설치 없이 웹페이지로 접속하기만 하면 돼서 매우 간편하다
스마트 컨트랙트를 배포하는 데에 truffle 프레임워크가 자주 사용되지만,
커맨드창에서 작업하는 것이 익숙하지 않은 코린이👶🏻들의 모임인 우리는...
더 직관적이고 편리한 GUI를 제공해주는 Klaytn IDE를 이용하기로 했다.
아 빼먹은 중요한 사항이 있는데,
우리 팀은 이번 프로젝트를 위한 블록체인으로 클레이튼 Klaytn을 택했다.
무려 카카오 자회사인 GROUND-X에서 만든 블록체인으로
"K-블록체인" 이라고 할 수 있는 아이다.
왜 이더리움을 선택하지 않았느냐?
이더리움 가스fee를 찾아보세요.....
우리는 가난한 일개 아기 학부생.....
NFT 발행을 위한 스마트 콘트랙트는 KIP-17 표준을 따라 작성돼야 하는데,
KIP-17 표준 코드에 우리에게 필요한 함수만 수정/추가해서 스마트 콘트랙트를 간단하게 커스텀해볼 수 있다.
(KIP-17은 클레이튼의 표준이고, 이더리움 표준은 ERC-721다. 둘 사이의 큰 차이는 없음!)
KIP-17표준에는 NFT를 민팅해주는 함수로 _mint 가 있는데,
코드는 이렇게 생겼다.
to
: 발행한 NFT를 소유할 지갑 주소tokenID
: 토큰의 인덱스 (지금까지 민팅한 NFT의 갯수+1) 부여tokenId
: 토큰의 인덱스를 인자로 입력두 함수는 token의 URI를 내가 등록해줄 수 있는 함수들이다.
(IPFS에서 받은 주소! 그거 써먹을 시간!)
setBaseURI
를 사용해서 내가 업로드한 JSON 파일의 IPFS 주소를 등록하면,
내가 업로드한 파일이 민팅하고자 하는 토큰이 되는 것이다.
가장 중요한 함수다.
특정 지갑 주소(유저) 소유로 하는 NFT를 민팅해주는 함수이다.
말이 조금 복잡한데, 단순히 민팅하는 함수가 아니라 '민팅해주는'이라고 표현되는 이유는 뒤에서 설명된다.
토이프로젝트이기 때문에 최대한 단순하게 필요한 기능만 구현할 수 있도록 짜여진 코드라 매우 간단하다!!
아주 직관적인 코드 ㅎㅎ
코드를 뜯어보자.
onlyMinter
: 솔리디티를 몰라도 글자만 봐도 무슨의미인지 이해가 갈 것이다.여기서 '민팅해주는'의 의미가 나온다.
(프로젝트 서비스에 관심 없고 개발 내용만 보실 분은 아래🔥로 이동!)
우리 프로젝트는 겉으로는 '나만의 NFT를 민팅할 수 있는 서비스'처럼 보이지만,
사실 실제 실행되는 함수는 관리자인 우리가 유저들의 NFT를 대신 민팅해주는 방식으로 구현된다.
🙋🏻♀️ 왜지?!?!?!?!?! 왜 거짓말 치나?!!?!?
거짓말이라고 할 수도 있지만, 그렇다고 NFT 소유자가 우리인 것이 절대 아니니 문제될 건 없다!
이렇게 구현하게 된 이유는 우리 서비스가 기부 플랫폼이기 때문이다.
간단하게 설명하자면
우리는 서비스를 이용하는 첫단계에서 NFT를 민팅하고 기부할 수 있는 권한인 알을 구매한다.
이 알 값에는 기부금+가스fee가 모두 포함되어있다.
그런데 사용자가 직접 자신의 주소로 NFT를 민팅하게 되어버리면
이미 알을 구매할 때 돈을 냈음에도 불구하고 또! 가스비를 개인적으로 지불해야 되는 불상사가 생겨버린다.
그렇기 때문에 사용자 개인의 지갑 주소로 직접 민팅하는 것이 아니라,
1. 알을 구매할 때 기미덕 프로젝트 지갑으로 '기부금+가스비'를 송금하고
2. 기미덕 프로젝트 지갑에서 가스비를 지불하면서 NFT를 대신 민팅해서 사용자 지갑으로 보내주는 것이다.
🔥 다시 Mint 함수로 돌아가자!! 🔥
_mint(user, _mintIndexForSale)
: 앞서 소개되었던 KIP-17 표준 함수인 _mint 함수를 사용하여 NFT를 user 주소로 민팅한다. 여태까지 민팅된 NFT의 갯수가 토큰ID 인자로 들어간다. _mintIndexForSale = _mintIndexForSale.add(1)
: NFT 하나를 민팅 했으니 민팅한 NFT 갯수도 1 증가시켜줘야 한다.결국 Mint 함수를 별도로 구현해준 목적은 _mintIndexForSale
값을 통해 민팅한 NFT 수를 유지하며 tokenID를 관리하기 위함이다.
🙋🏻♀️ 여러개의 NFT를 한번에 발행해야 한다면??
➕ 우리 서비스는 한 번에 한 개의 NFT만을 민팅하기 때문에 위와 같이 코드를 구현하였지만, 만약 N개의 NFT를 한번에 민팅하고 싶다면 방법은 간단하다.
개수 파라미터를 추가하고 반복문을 돌려주면 될 것이다.
function Mint(address user, uint256 count) external onlyMinter {
for (uint256 i = 0; i < count ; i++) {
_mint(user, _mintIndexForSale);
_mintIndexForSale = _mintIndexForSale.add(1);
}
}
count
로 민팅하고자 하는 NFT 수를 파라미터로 추가해주고, Klatn IDE에서 스마트 콘트랙트 작성 후, 이를 배포하는 데까지 필요한 설정값은 다음과 같다. 그대로 설정해주면 된다.
📌 compile 탭
compiler
: 0.5.17 버전 (KIP17의 표준 버전 : 0.5대)language
: solidityevm version
:istanbul
: 바오밥 (테스트넷)constantinople
: 싸이프레스 (메인넷)➡ Compile 클릭
📌 Deploy 탭
environment
: baobab / cypressaccount
: +
버튼 눌러서 나의 카이카스 지갑 연결contract
: 스마트컨트랙트 코드 가장 아랫쪽에 명시되어있는 contract의 이름을 찾아서 선택해준다( 우리 프로젝트의 경우 KIP17GimmeDuckToken이다. )
Deploy
: NFT collection 이름, symbol 설정➡ Deploy - transact 클릭
transact 버튼을 누르고 콘솔창에 이렇게 체크 표시가 뜨면 콘트랙트 정상 배포 완료! 💚
가장 하단의 Deployed contracts
에 내가 배포한 새 컨트랙트가 추가된다.
화살표를 누르면 스마트 컨트랙트 내의 모든 함수를 실행시킬 수 있는 버튼이 나열된다.
( 붉은 색 : 가스비 유료, 파란 색 : 무료 )
이제 본격적인 민팅을 위해 앞서 업로드했던 IPFS의 주소를 찾으러 가보자.
Pinata로 돌아가서 JSON 파일의 CID를 복사해오고,
SetBaseURI
함수의 인자로 넣어주자.
💡 이때!! 주의할점은 그대로 복붙하지 말고 앞에 ipfs://
를 넣어주자.
(아까 봤던 이미지 IPFS URL 주의사항과 똑같다)
즉 인자로 ipfs://+CID
를 넣어주면 된다.
transact 클릭!
ㅎㅎㅎㅎㅎ
반가운 체크 표시
JSON 토큰 URI 설정도 성공적으로 완료되었다.
그럼 이제 정말로 민팅하는 일만 남았다!
우리는 한번에 하나씩만 민팅할 것이기 때문에
Mint함수의 인자로 민팅받을 지갑 주소만 넣어주면 된다.
실제 서비스는 각 사용자의 지갑주소가 인자로 들어가게 되지만,
지금은 테스트이기 때문에 나의 개인 지갑 주소로 민팅 해보겠다!
카이카스 지갑에서 주소를 복사하고,
(숭해는 내 별명이다. 이름이랑 비슷해서.)
Mint함수 인자로 넣어주기!!
긴장되는 순간.........
....
transact 클릭!
초록체크!!!!!!!!!!!!!!!!!!!!!!!!!!
🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳🥳
민팅완료!~!!!!~~!~!~!~!!!~!~!~!~!!~!~!~!!!!~!~!
인생 첫 민팅이다!!!!
기뻐하긴 이르다. (이미 충분히 기뻐하신 것 같은데요...)
콘솔창이 아닌 다른 데에서 직관적으로 우리의 트랜잭션을 확인해보면 좋겠다.
klaytnscope는 클레이튼 네트워크의 블록 탐색기다.
즉 클레이튼 블록체인에 존재하는 블록들과 그와 관련된 트랜잭션들을 찾아볼 수 있는 곳이다.
우리는 방금 민팅을 성공함으로써 클레이튼 블록체인에도 자취를 남긴 것이기 때문에 우리의 목적이 정말 달성된 것이라면 klaytn scope 에서도 확인할 수 있어야 한다.
💡 klaytn scope은 메인넷과 바오밥(테스트넷) 사이트가 분리되어있어서 우리가 민팅한 테스트넷 사이트로 선택해서 들어가야 한다.
방금 전, 민팅 성공 후 초록체크버튼을 받았던 콘솔창의 메시지를 클릭하면 나의 트랜잭션에 대한 상세 정보가 나타난다
여기서 transaction hash를 복사해서 클레이튼 스코프 검색창에 붙여넣기 후 검색하면??
짠~~ 👏🏻👏🏻👏🏻👏🏻👏🏻
Success된 트랜잭션이 조회된다
우리는 성공적으로 클레이튼 블록체인에 자취를 남긴 것이다!
아래에 NFT transfer 내역을 보면 나의 개인 지갑 주소(0xfc01...)로 GIMMEDUCK NFT 중 tokenID 1의 NFT가 보내진 것을 직관적으로 확인할 수 있다.
하지만?
인간의 욕심은 끝이없다.
여기서 끝이 아니다.
조금 더 확실하게 확인해보고 싶다.
🙋🏻♀️ 그나저나 우리가 처음 올리려고 했던 이미지는 어디갔냐!
우리가 지금까지
이미지를 IPFS에 올리고,
그 IPFS주소를 담은 JSON파일을 만들고,
그 JSON 파일을 또 IPFS에 올리고,
그 IPFS주소를 스마트콘트랙트 함수에 담아,
민팅을 하고자 한 최초의 이유가
바로 맨 처음 이미지 PNG를 민팅하기 위해서였다.
그럼 이런 트랜잭션 결과를 설명해주는 정보들이 아닌
우리가 올리고 싶었던 이미지가 정말로 올라간 것인지 두눈으로 확인을 해봐야 하지 않겠는가!👀
이를 위해서
OpenSea를 들어가볼 것이다.
맨 처음, 메타데이터 표준 형식을 설명하면서 이미 언급되었지만
OpenSea는 간단히 말해 NFT 시장이라고 할 수 있다.
민팅한 NFT를 거래 하는 곳!
여기서도 우리는 일반 오픈씨에 접속하면 안되고,
테스트넷에 올라와있는 NFT를 확인할 수 있는
testnet전용 오픈씨에 접속해야 한다.
오른쪽 상단에 프로필 클릭 후 Kaikas 지갑으로 로그인을 진행한다.
지갑 연결!
그러면 나의 NFT 모음집이 보이는데.......
hidden 탭을 누르면?
기절할뻔 했다.
드디어!!!!!!! 나의 대왕 알을 만났다!
오픈씨에 NFT는 올라와있지만 메타데이터가 하나도 연동이 안되는 시행착오를 몇번이나 거치느라 고생했다ㅠㅠ
이런 대문짝만한 알이 이렇게 반가울 일이 있다 진짜
오픈씨 연동에서 애먹은 점은 대수롭지 않은 오류지만
그래도 충분히 다른 분들도 놓칠 수 있는 부분이기에 다시한번 언급하고 넘어가자면
opensea metadata 표준을 확실하게!! 맞춰야 한다.
분명히 민팅도 했고, OpenSea에 NFT 아이템은 올라와있는데
사진도, 이름도, 설명도 아무것도 뜨지 않는다?
싶을 땐 다시한번 JSON 파일의 내부 형태를 꼭꼭 살펴보자.
🔍opensea 표준 보러가기🔍
공식문서 링크 한번 더 첨부!
NFT 상세 페이지를 보면 내가 설정한 JSON 대로 name, description 등 모두 잘 들어간 것들을 확인할 수 있다.
(Details tab에 들어가면 민팅하는데에 사용한 스마트컨트랙트의 주소와 해당 NFT의 토큰 ID도 볼 수 있다)
이렇게 비록 테스트넷이지만
최초의 멀쩡한 NFT 홀더가 되어보았다.
✌🏻