Lab3. Query NFTs
루니버스 Multichain API는 NFT를 위한 API가 있다는 것을 아셨나요? 이번 Lab에서는 NFT API를 이용해서 소유하고 있는 NFT 목록과 각 NFT의 Metadata를 조회할 수 있습니다.
Lab Overview
Lab3에서는 다음과 같은 작업을 수행합니다:
- API 이해도 향상: NFT API의 기능과 사용법을 자세히 학습하며, 어떤 데이터를 어떻게 가져올 수 있는지 이해합니다.
- NFT 잔고 조회: NFT 잔고 조회 API를 활용하여 코드를 완성합니다. 사용자의 계정에 소유한 NFT 잔고를 확인하는 코드를 작성합니다.
- NFT 메타데이터 조회: NFT 메타데이터 조회 API를 활용하여 코드를 완성합니다. 각 NFT에 대한 메타데이터를 가져와 출력하는 코드를 작성합니다.
- 프로젝트에 반영 확인: 작성한 코드가 실제 프로젝트에 반영되어 동작하는지 확인합니다. 자산 잔고와 NFT 정보가 정확하게 출력되는지 확인합니다.
이번 Lab을 완료하면 NFT 자산을 조회하고 Metadata를 조회하는 방법을 익힐 수 있습니다.
Prerequisites
이번 랩을 진행하기 전에 아래의 작업을 완료해야 합니다:
- [Lab2. Retrieve Account Asset Balances]를 완료하셨어야 합니다. Lab3에서는 Lab2의 코드와 설정을 그대로 활용합니다.
- Luniverse Multichain API의 AUTH_TOKEN을 발급받았어야 합니다. 발급받은 인증 토큰은 Lab3 코드에서 API 호출 시 사용됩니다.
- instances.ts 파일에 기입된 프로토콜과 네트워크가 발급받은 인증 토큰과 대응되는지 확인해주세요. 다른 환경에서 생성된 인증토큰은 API 호출 시 에러가 발생합니다.
- 발급 받은 인증 토큰의 유효기간이 유효한지 확인해주세요. 모든 인증토큰은 발급 시점으로 부터 7일 동안만 유효합니다.
위의 사항들이 준비되었다면 이제 Lab3에서 사용할 API에 대해서 알아보겠습니다.
사용할 API
루니버스에서 제공하는 NFT API들은 JSON-RPCful 형태로 제공됩니다. 따라서 HTTP 요청의 메서드는 POST, 모든 파라미터는 바디 파라미터에 담아 요청을 보냅니다.
지원하는 NFT 유형
NFT API는 ERC-721과 ERC-1155를 모두 지원합니다. NFT 유형에 따라 응답이 다를 수 있으니 API 문서를 꼭 확인하시기 바랍니다.
예를 들어, listNftByOwner API에 대한 요청은 ERC-1155 표준에 따라 balance 필드가 추가된 응답을 제공합니다. 이는 ERC-721 표준의 응답과는 다릅니다.
listNftByOwner
지난 랩에서는 listAccountBalance API를 이용하여 세 가지 유형의 자산 모두 조회할 수 있다면, 이번 랩에서 사용할 listNftByOwner API는 NFT 자산만 조회가 가능합니다. 즉, listNftByOwner API는 특정 계정 주소가 소유한 NFT 목록을 조회할 수 있습니다. 조회하고 싶은 계정의 주소를 owenrAddress 파라미터에 담아 호출할 수 있습니다.
아래는 ERC-1155 잔고를 조회했을 때의 응답 예시입니다. ERC-1155의 경우, 응답 데이터에 balance 필드가 포함된다는 것을 확인 할 수 있습니다.
// Response example (ERC-1155)
{
"code": "SUCCESS",
"data": {
"count": "31",
"cursor": "eyJzb3J0IjpbImJsb2NrX3RpbWVzdGFtcDpkZXNjIiwidHJhbnNhY3Rpb25faGFzaDphc2MiLCJ0b2tlbl9pZDphc2MiXSwic2VhcmNoQWZ0ZXIiOlsxNjc5OTQxNTQ3MDAwLCIweGM3NzYzMWVlMzc5ZDgwNDA3YTEwZDU2M2IyMmQyNGFkMDMwMGUzZWY2Nzk0OTNlZGEzMTdlNGNhNDQ1YzU3YTgiLCIxNTYiXX0=",
"rpp": 2,
"items": [
{
"contractAddress": "0xde5Ec2A89Eec3F8b63988DE7241118f53fE9d5AA",
"tokenId": "160",
"ownerAddress": "0x0002b4e0e1b2167927E741ffB53dBBA7C77974C0",
"blockTimestamp": "2023-03-27T18:51:47.000Z",
"transactionHash": "0xd6fa5de0d8008cace264f2933178b916822a1afd00e8aeb1c451338795c8b4ef",
**"balance": "1"**
},
{
"contractAddress": "0xde5Ec2A89Eec3F8b63988DE7241118f53fE9d5AA",
"tokenId": "156",
"ownerAddress": "0x0002b4e0e1b2167927E741ffB53dBBA7C77974C0",
"blockTimestamp": "2023-03-27T18:25:47.000Z",
"transactionHash": "0xc77631ee379d80407a10d563b22d24ad0300e3ef679493eda317e4ca445c57a8",
**"balance": "1"**
}
]
}
}
cursor가 뭔가요?
pagination을 지원하는 파라미터로 특정 지점을 기준으로 데이터를 조회할 수 있습니다. 최초 호출시 cursor 값을 응답으로 받을 수 있는데, 이 값이 페이지의 시작점입니다. 이후 호출에서 cursor 값을 입력하면 다음 순서의 데이터 페이지가 조회됩니다.
getNftMetadataByTokenId
NFT 컨트랙트 주소와 토큰 ID을 사용하여 해당 NFT의 메타데이터를 조회합니다. 이때 resync를 이용하여 tokenURI를 온체인에서 직접 조회할 것인지 혹은 캐싱된 데이터를 조회할 것인지 선택할 수 있습니다. 빠른 데이터 조회를 원하다면 해당 파라미터는 false로 설정하는 것이 좋습니다.
메타데이터 조회 시 timeoutSeconds를 통해 시간 초과를 설정할 수 있으며, 설정한 시간 내에 응답이 오지 않을 경우 408 코드를 반환합니다. 408 에러를 반환하더라도 내부적으로 데이터 캐싱은 계속 진행 됩니다.
아래 예제와 같이 응답의 data 안에는 rawMetadata 라는 필드가 존재합니다.
// Response Example
{
"code": "SUCCESS",
"data": {
"name": "Dreadfulz",
"symbol": "Dreadfulz",
"contractType": "ERC-721",
"contractDeployerAddress": "0x4c02Fb7356C7e6Ffc8f5C371098cE6b0d9AE5903",
"deployedTransactionHash": "0x718cd8d504410f3c888409addd3811e71cfd4540e089b56a93ecdca4ac399328",
"contractAddress": "0x81Ae0bE3A8044772D04F32398bac1E1B4B215aa8",
"tokenUriSyncedAt": "2023-07-17T18:01:14.791Z",
"metadataSyncedAt": "2023-07-17T18:01:15.062Z",
"tokenUri": "https://ipfs.io/ipfs/QmPqwtY6tj3Bf9D4efV4fTS7c93nUmbtsUEgDTgpvYVWmB/1.json",
**"rawMetadata": "{\"description\":\"7,777 Dreadfulz have inked their name in fealty to the Grand Inquisitor. Bound by avarice, lustful for $DREAD.\",\"external_url\":\"\",\"image\":\"https://ipfs.io/ipfs/QmZbxmneJTMFfa8SXpv8QweHi34CUMU4LpuPH5UVNvrAbG/1.jpg\",\"name\":\"Dreadfulz #1\",\"attributes\":[{\"trait_type\":\"DIVISION\",\"value\":\"Legendary\"}],\"properties\":{\"category\":\"image\",\"files\":[{\"uri\":\"1.png\",\"type\":\"image/png\"}],\"creators\":[]}}"**
}
}
rawMetadata가 뭔가요?
rawMetadata에는 tokenURI에서 제공하는 JSON 형식의 데이터를 string으로 변환한 값을 반환합니다. 이 필드를 활용하면 tokenURI로 추가 요청할 필요 없이 바로 메타데이터를 얻을 수 있으며, 이를 통해 메타데이터를 가져오는데 소요되는 시간을 줄일 수 있습니다.
더 자세한 내용이 궁금하시다면 listNftByOwner API 가이드와 getNftMetadataByTokenId API 가이드를 참고하세요!
적용해보기
Luniverse Instance
- 파일 경로: [ server > src > apis > instances.ts ]
server 폴더 안의 instances.ts 파일을 열어주세요. 이 파일에는 루니버스 Multichain API에 호출할 때 필요한 인스턴스인 luniverseInstance가 정의되어 있습니다. luniverseInstance 에는 우리가 호출하고자 하는 API의 base URL과 인증 토큰(AUTH_TOKEN)에 대한 정보를 담고 있습니다.
조회하려는 네트워크를 바꾸고 싶다면 어떻게 하나요?
instance.ts 파일의 PROTOCOL과 NETWORK에 원하는 프로토콜과 네트워크로 변경하면 됩니다. 단, 이때 변경한 프로토콜과 네트워크과 대응하는 노드가 필요합니다. (해당 노드가 없다면 가이드 문서를 참조해 노드를 생성해 주시기 바랍니다.) 추가적으로, 변경한 노드에서 발급받은 인증토큰을 .env 파일의 AUTH_TOKEN에 입력해야 합니다.
listNftByOwner
- 파일 경로: [ server > src > apis > lab3.ts ]
이 파일에는 listNftByOwner API를 호출하는 함수가 정의되어 있습니다. listNftByOwner API 가이드를 참고하여 함수 내 미완성된 부분을 수정해주세요.
미완성 코드
// server/src/apis/lab3.ts
import { luniverseInstance } from "./instances";
export async function listNftByOwner(
ownerAddress: string,
rpp?: number,
cursor?: string,
disableCount?: boolean
) {
return luniverseInstance.request({
method: "post",
url: "",
data: {},
});
}
// -- snip --
method
: HTTP 요청의 메서드(method)를 입력하는 필드 입니다. 이 API는 “POST” 요청을 받습니다.url
: 이 필드는 API의 엔드포인트를 입력하는 곳입니다. luniverseInstance에서 baseURL이 설정되어 있기 때문에, 나머지 엔드포인트 부분을 입력해야 합니다. 따라서 url에 "/nft/listNftByOwner"를 입력합니다.data
: 이 필드는 바디 파라미터를 입력하는 필드입니다. 이 API는 총 네 개의 바디 파라미터(ownerAddress, rpp, cursor, disableCount)를 받습니다. 이 중 ownerAddress는 필수 파라미터이므로 반드시 포함되어야 합니다.
완성 코드
비어있는 HTTP 요청의 메서드(method), API의 엔드포인트, 그리고 바디 파라미터(body parameter) 부분을 가이드 문서에 따라 작성하면 아래와 같이 코드를 완성할 수 있습니다.
// server/src/apis/lab3.ts
import { luniverseInstance } from "./instances";
export async function listNftByOwner(
ownerAddress: string,
rpp?: number,
cursor?: string,
disableCount?: boolean
) {
return luniverseInstance.request({
method: "post",
url: "/nft/listNftByOwner",
data: {
ownerAddress,
rpp,
cursor,
disableCount,
},
});
}
// -- snip --
코드를 완성하면, 브라우저로 돌아가서 새로고침을 누르세요. 계정이 소유하고 있는 NFT 목록들이 아래 그림과 같이 조회되면 성공입니다!
하지만 여전히 각 NFT 카드에는 이미지를 비롯한 메타데이터가 조회되지 않습니다. 계속 과정을 진행하여 NFT의 메타데이터도 불러와 보겠습니다.
getNftMetadataByTokenId
- 파일 경로: [ server > src > apis > lab3.ts ]
이 파일에는 getNftMetadataByTokenId API를 호출하는 함수가 정의되어 있습니다. getNftMetadataByTokenId API 가이드를 참고하여 함수 내 미완성된 부분을 수정해주세요.
미완성 코드
// server/src/apis/lab3.ts
// -- snip --
export async function getNftMetadataByTokenId(
contractAddress: string,
tokenId: string,
resync?: boolean,
timeoutSeconds?: number
) {
return luniverseInstance.request({
method: "",
url: "",
data: {},
});
}
method
: HTTP 요청의 메서드(method)를 입력하는 필드입니다. 이 API는 “POST” 요청을 사용합니다.url
: 이 필드는 API의 엔드포인트를 입력하는 곳입니다. baseURL이 이미 luniverseInstance에서 설정되어 있기 때문에, 남은 부분인 엔드포인트만 입력해야 합니다. 따라서 url에 "/nft/getNftMetadataByTokenId"를 입력합니다.data
: 이 필드는 바디 파라미터를 입력하는 곳입니다. 이 API는 총 네 개의 바디 파라미터(contractAddress, tokenId, resync, timeoutSeconds)를 받습니다. contractAddress와 tokenId는 필수 파라미터이므로 반드시 포함되어야 합니다.
완성된 코드
비어있는 HTTP 요청의 메서드(method), API의 엔드포인트, 그리고 바디 파라미터(body parameter) 부분을 가이드 문서에 따라 작성하면 아래와 같이 코드를 완성할 수 있습니다.
// server/src/apis/lab3.ts
// -- snip --
export async function getNftMetadataByTokenId(
contractAddress: string,
tokenId: string,
resync?: boolean,
timeoutSeconds?: number
) {
return luniverseInstance.request({
method: "post",
url: "/nft/getNftMetadataByTokenId",
data: {
contractAddress,
tokenId,
resync,
timeoutSeconds,
},
});
}
코드를 완성하면, 브라우저로 돌아가서 새로고침을 누르세요. 드디어 NFT 카드에 이미지가 표시가 되는 것을 확인할 수 있습니다!
그리고 NFT 카드 중 하나를 클릭하면 Asset 페이지로 이동하여 해당 NFT의 정보를 확인할 수 있습니다.
NFT 이미지가 표시되지 않는 경우가 있는데, 그 이유는 무엇인가요?
일반적으로 NFT의 메타데이터는 JSON 형식으로 제공됩니다. NFT Metadata JSON Schema는 이러한 정보를 어떻게 구성할 것인지를 정의합니다. ERC-721 또는 ERC-1155에서 권장하는 NFT Metdata JSON Schema 표준이 있으며, NFT 마켓플레이스에서는 이 표준들을 기반으로 확장된 표준을 제시하기도 합니다. 예를 들어, Opensea Metadata Standards는 이러한 표준들을 기반으로 한 예시입니다. 자세한 내용은 이 링크를 클릭하여 확인 가능합니다.
이러한 표준을 따르지 않는 NFT의 경우, 튜토리얼에서 NFT의 정보가 제대로 표시되지 않을 수 있습니다.
여기까지 잘 따라오셨나요? 다음 랩에서는 검색한 계정의 트랜잭션을 조회 해볼 예정입니다. 다음 버튼을 눌러서 튜토리얼을 계속 진행해주세요!
Updated about 1 year ago