Building a Simple NFT Explorer Using Web3 Data APIs

Web3 Data API를 활용하여 간단한 NFT 검색기능을 구현해봅니다.

빌더 여러분 안녕하세요! NFT 튜토리얼에 오신 것을 환영합니다 🎉

이번 튜토리얼에서는 간단한 NFT Explorer를 설계하고 직접 만들어가는 과정을 볼 수 있습니다.

💡

본 튜토리얼은 다음과 같은 순서로 진행됩니다.

Step 1. 요구사항 수립 및 연동할 API 살펴보기

Step 2. 개발 환경 설정하기

Step 3. API 연동하기

Step 4. 예제 코드로 동작 확인하기

그럼 하나씩 진행해볼까요?


Step 1. 요구사항 수립 및 연동할 API 살펴보기

구현할 NFT Explorer는 아래와 같이 간단한 기능을 제공하도록 설계했습니다.

  • 기능 1. 계정 주소를 입력하여 해당 계정이 보유하고 있는 NFT 목록을 조회하여 보여준다.

  • 기능 2. NFT 목록에서 특정 NFT를 클릭하면 해당 NFT에 대한 Metadata와 함께 아래 정보를 보여준다.
    • 기능 2-1. NFT Contract의 Metadata를 확인할 수 있다.
    • 기능 2-2. 해당 NFT가 거래된 이력을 확인할 수 있다.

간단하지만 NFT와 관련된 중요한 기능들을 모두 담고 있네요! 이제 Nodit의 Web3 Data API 문서를 참고하여 각 기능을 구현하기 위해 어떤 API를 사용할 수 있는지 확인해볼까요?


기능 1. 계정 주소를 입력하여 해당 계정이 보유하고 있는 NFT 목록을 조회하여 보여준다.

이 기능을 구현하기 위해서는 사용자가 Input에 주소를 입력하고 [검색] 버튼을 클릭하면 API를 호출하여 응답으로부터 해당 계정이 보유한 NFT의 목록을 조회해야 합니다. 아래 그림과 같이 연동할 수 있습니다.


이러한 인터페이스를 제공하는 API로 Web3 Data API의 NFT API중 getNFTsOwnedByAccount API를 사용할 수 있겠네요! 아래와 같이 curl을 작성하여 API를 호출해 볼 수 있습니다.

curl --request POST \
     --url https://web3.nodit.io/v1/ethereum/mainnet/nft/getNftsOwnedByAccount \
     --header 'X-API-KEY: Input_your_API_Key' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "accountAddress": "Input_your_account_address"
}
'

위 호출에 대한 응답은 다음과 같습니다. 간단한 테이블 구조로 NFT의 목록을 보여주기 위해 name과 symbol 그리고 tokenId, contractAddress를 이용할 예정입니다.

{
  "rpp": 20,
  "cursor": "W3sia2V5IjoidmFsdWUiLCJvcmRlciI6IkRFU0MiLCJlcXVhbCI6dHJ1ZSwicG9pbnQiOiIxIn0seyJrZXkiOiJ0b2tlbklkIiwib3JkZXIiOiJBU0MiLCJlcXVhbCI6ZmFsc2UsInBvaW50IjoiMTkifV0=",
  "items": [
    {
      "tokenId": "0",
      "tokenUri": null,
      "tokenUriSyncedAt": null,
      "ownerAddress": "0x84bb73064300aB1f539310237B892Ca47C58778c",
      "balance": "1",
      "contract": {
        "address": "0xe6313d1776E4043D906D5B7221BE70CF470F5e87",
        "deployedTransactionHash": "0xe015aef229ebe54e6e0b788a0b3eeafac1f4a881dffef74d96e3695a63e0c261",
        "deployedAt": "2023-01-26T02:20:47.000Z",
        "deployerAddress": "0x84bb73064300aB1f539310237B892Ca47C58778c",
        "logoUrl": null,
        "type": "ERC721",
        "name": "OnChainShiba",
        "symbol": "OCS"
      }
    },
  ...
    }
  ]
}

기능 2-1. NFT Contract의 Metadata를 확인할 수 있다.

사용자가 목록의 NFT 중 한 가지를 선택하면 Contract의 Metadata와 거래 이력을 보여주어야 합니다. 아래 다이어그램과 같이 연동할 수 있겠네요.

Nodit의 getNFTMetadataByTokenIds 라는 API를 이용해 해당 기능을 구현할 수 있으며 contractAddress와 tokenId 값을 이용해 API를 호출해보겠습니다.

curl --request POST \
     --url https://web3.nodit.io/v1/ethereum/mainnet/nft/getNftMetadataByTokenIds \
     --header 'X-API-KEY: Input_your_API_Key' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "tokens": [
    {
      "contractAddress": "Input_your_contract_account_address",
      "tokenId": "1"
    }
  ]
}
'

위 호출에 대한 응답으로 다음과 같은 값을 받을 수 있습니다. 이 중에서 필요한 값을 선택해 화면을 구현할 수 있습니다.

[
  {
    "tokenId": "1",
    "tokenUri": "ipfs://QmeSjSinHpPnmXmspMjwiXyN6zS4E9zccariGR3jxcaWtq/1",
    "tokenUriSyncedAt": "2024-04-17T14:38:49.279Z",
    "rawMetadata": "{\"image\":\"ipfs://QmPbxeGcXhYQQNgsC6a36dDyYUcHgMLnGKnF8pVFmGsvqi\",\"attributes\":[{\"trait_type\":\"Mouth\",\"value\":\"Grin\"},{\"trait_type\":\"Clothes\",\"value\":\"Vietnam Jacket\"},{\"trait_type\":\"Background\",\"value\":\"Orange\"},{\"trait_type\":\"Eyes\",\"value\":\"Blue Beams\"},{\"trait_type\":\"Fur\",\"value\":\"Robot\"}]}\n",
    "metadataSyncedAt": "2024-04-17T14:38:53.819Z",
    "media": {
      "originUrl": "ipfs://QmPbxeGcXhYQQNgsC6a36dDyYUcHgMLnGKnF8pVFmGsvqi",
      "cachedUrl": "https://cdn.luniverse.io/media/ethereum/mainnet/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/1/raw.png",
      "thumbnailUrl": "https://cdn.luniverse.io/media/ethereum/mainnet/0xbc4ca0eda7647a8ab7c2061c2e118a18a936f13d/1/medium.png",
      "updatedAt": "2023-08-29T09:23:45.000Z"
    },
    "contract": {
      "address": "0xBC4CA0EdA7647A8aB7C2061c2E118A18a936f13D",
      "deployedTransactionHash": "0x22199329b0aa1aa68902a78e3b32ca327c872fab166c7a2838273de6ad383eba",
      "deployedAt": "2021-04-22T03:03:16.000Z",
      "deployerAddress": "0xaBA7161A7fb69c88e16ED9f455CE62B791EE4D03",
      "logoUrl": null,
      "type": "ERC721",
      "name": "BoredApeYachtClub",
      "symbol": "BAYC"
    }
  }
]

기능 2-2. 해당 NFT가 거래된 이력을 확인할 수 있다.

특정 NFT의 거래 이력에 대한 데이터를 불러올 차례 입니다. 2-a와 동일한 호출 파라미터를 사용하지만 거래 이력에 대한 정보 목록을 조회할 수 있어야 합니다. Nodit에서는 getNftTransfersByTokenId API를 제공하고 있으며 해당 API를 이용해 특정 NFT의 거래 이력을 조회해보겠습니다.

스크린샷 2024-08-30 오후 4.10.56.png
 curl --request POST \
     --url https://web3.nodit.io/v1/ethereum/mainnet/nft/getNftTransfersByTokenId \
     --header 'X-API-KEY: Input_your_API_Key' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "contractAddress": "Input_your_contract_account_address",
  "tokenId": "Input_your_token_id",
}
'

호출에 성공하면 아래와 같은 응답을 받을 수 있습니다.

{
  "rpp": 20,
  "cursor": null,
  "items": [
    {
      "from": "0x0000000000000000000000000000000000000000",
      "to": "0x84bb73064300aB1f539310237B892Ca47C58778c",
      "value": "1",
      "timestamp": 1674701879,
      "blockNumber": 16488153,
      "transactionHash": "0x8d46fd387f991e7a3bc6c5eecc2c8f3737153ec689a8094f9631253d2544a857",
      "logIndex": 231,
      "contract": {
        "address": "0xe6313d1776E4043D906D5B7221BE70CF470F5e87",
        "deployedTransactionHash": "0xe015aef229ebe54e6e0b788a0b3eeafac1f4a881dffef74d96e3695a63e0c261",
        "deployedAt": "2023-01-26T02:20:47.000Z",
        "deployerAddress": "0x84bb73064300aB1f539310237B892Ca47C58778c",
        "logoUrl": null,
        "type": "ERC721",
        "name": "OnChainShiba",
        "symbol": "OCS"
      },
      "nft": {
        "tokenId": "1",
        "tokenUri": "data:application/json;base64,eyJuYW1lIjoiT25DaGFpblNoaWJhICMxIiwiZGVzY3JpcHRpb24iOiJUaGlzIGNvbGxlY3Rpb24gaXMgdGhlIGZpcnN0IG9mZmljaWFsIGNvbGxlY3Rpb24gb2YgTWV0YXZlcnNlICYgTkZUIFN0dWRpbywgJ0xhYmVsIE0nLiBEZWRpY2F0ZWQgdG8gYWxsIFNoaWJhIGFuZCBwaXhlbCBhcnQgZmFucy4iLCJpbWFnZV9kYXRhIjoiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCM2FXUjBhRDBpTVRBd0pTSWdhR1ZwWjJoMFBTSXhNREFsSWlCMmFXVjNRbTk0UFNJd0lEQWdNVEl3TUNBeE1qQXdJaUIyWlhKemFXOXVQU0l4TGpJaUlIaHRiRzV6UFNKb2RIUndPaTh2ZDNkM0xuY3pMbTl5Wnk4eU1EQXdMM04yWnlJK1BHbHRZV2RsSUhkcFpIUm9QU0l4TWpBd0lpQm9aV2xuYUhROUlqRXlNREFpSUdoeVpXWTlJbVJoZEdFNmFXMWhaMlV2YzNabkszaHRiRHRpWVhObE5qUXNVRWhPTWxwNVFqTmhWMUl3WVVRd2FVMVVTWGROUTBsbllVZFdjRm95YURCUVUwbDRUV3BCZDBscFFqSmhWMVl6VVcwNU5GQlRTWGRKUkVGblRWUkpkMDFEUVhoTmFrRjNTV2xDTWxwWVNucGhWemwxVUZOSmVFeHFTV2xKU0doMFlrYzFlbEJUU205a1NGSjNUMms0ZG1RelpETk1ibU42VEcwNWVWcDVPSGxOUkVGM1RETk9NbHA1U1dkak0xSTFZa2RWT1VsdFNtaFpNblJ1WTIwNU1XSnRVWFJaTWpsellqTkpObVJJU21oaWJrNTNXVmhLYkdKdVVUZFpiVVpxWVRKa2VXSXpWblZhUXpGd1lsZEdibHBVY0RGamJYZHZXa2RHTUZsVWNIQmlWMFp1V2xNNWQySnRZemRaYlVaNldsUlpNRXhIYkZkUmF6bFRaSHBDVEZJeVpIWlJWVVpDVVZVMVZGWlhhRVpXVjJSQ1VWVkdRMW93UmtKUlZVWmFVVEJHV2xGVlJrSlNSMlJyWldwTk1GRlZSa0pSVlVaWlZHeE9VMDFGYkVKamJrMHdXWHBhVWxGVlJrSlNSbHBMVld0V1IxWldUa1prYmxGM1kydFdTMUZWUmtKVVZVWnlVMGRTYlUxdGVFbExNREY1WkZaVmQyUlZWak5MTVc4MFZVZFpjbFZGU2xSYU0wNXlWbFpHZGxvd1JrZFhSWEJEVWxWMFdsUlZSbGRWTVd4U1VUQmtUV0ZZV21oVFNFcEdVVlZLY2xKSVNURmlWVVpDVVZWR1FsRlZWbk5TYkZKeVZUTldVbUpWVGtSTFUzZ3hZMjEzYjFwSFJqQlpWSEJ3WWxkR2JscFRPWGRpYldNM1dXMUdlbHBVV1RCTVIyeFhVV3M1VTJSNlFreFNNbVIyVVZWR1FsRlZOVlJXVjJoR1ZsZGtRbEZWUmtOYU1FWkNVVlZHV2xFd1JscFJWVVpDVWtka2EyVnFUVEJSVlVaQ1VWVkdXVlJzVGxOTlJXeENZMjVOTUZsNldsSlJWVVpDVW14d1MxVnJWa2RXVms1R1pFZHdZVk13UmpSWFZYUlVaVU4wVW1WWGNFZGhSVVpPVGtkU1Jsb3daRzVOUjJ4dVlWVmFRbFpWTVVaa01WSnlWbTVHVTJGSFNYWlphbFYzVGpGR2FWSldRVFJWYlRsQ1l6STVVMW93YkVaVVZsa3dVMFZzVGxaV1RURlNXRkp1WVVjeFJsSkdiRFJaYTJzd1lVaHNibE50YkcxV1JWcDRVVlphY21GRk5WTmlNR2hEVTFVNVQwNVhkRVpTVlVaS1UydFdSbEZ0ZEhkV01GcFZaVlZHUWxGVlJrSlNWM2hIVmtkMFZHUldSblJSTUUxd1RFaFdlV0pEYUd0WldGSm9UMjFzZEZsWFpHeE1NMEoxV25wMGFWbFlUbXhPYWxGellWWmFRMVF4U2pOTlJYUklXakk1UWxGVlJrSlViRTVXWVVWV1Zsb3dSa0pSVlVwdVVWVkdRbEZXYkVSUlZteENVVlZHUlZveVVqWk5lbEpDVVZWR1FsRldhRTlWTVVsM1UxVkdlV042VW1wT2JFWkNVVlZHUjFwRmNGTlNWVnBXVlRCV01HRnNjRXhSV0doYVV6Rk9ORXN4UmpWaGExcHZVVlV3TUZwRlZtNVNNbU4zWVZka2NGSnJSbFpVVlZwMlV6Sm9kbFJyYkhaVFYyaFNVbXRTUTJWVlZYaFNhVGd5VW0xb1VtVnJSa1pSTUU1NVVWVldhbUZwT1V4TlJUQTFVakExZDFkRVpETlJhMjh3Wld4a1JWZEZSakJSVlVwdlZHMTRSRlV4VmxoU1ZXUldVMjVDVTFsVlJrMVpNRlpaVmtWR1JXUXhWa0pPUm5CNFVsZGFhVlpYWkVKUlZVWkNVV3R3VTFaVVZrWmpiWFJMV2pKa2JsQlVNSEJNU0ZaNVlrTm9hMWxZVW1oUGJXeDBXVmRrYkV3elFuVmFlblJwV1ZoT2JFNXFVWE5oVmxwRFZERktNMDFGZEVoYU1qbENVVlZHUWxSc1RsWmhSVlpXV2pCR1FsRlZTbTVSVlVaQ1VWWnNSRkZXYkVKUlZVWkZXakpTTmsxNlVrSlJWVVpDVVZab1QxVXhTWGRUVlVaNVkzcFNhazVzUmtKUlZVWlhaVVZ3VTFKVldsWlZNRll3WVd4d1RGRllhRnBSTVZKSFRERkJOVmRET1cxUFNIQm9ZbFJvY2xVeFJYWlZiV3d5VDBkYVlWVkRkRzFMTTFaV1QxaGplV1ZWV2xSUlZuQlVWRVV4U0dReFJrVlplbGx5VEhsMGMyUkZVbkpVVjNCUlYxTTRNRmRGZDNaVWJuQlJUMFp3TkZKR2FGcFNNRkp1Vm0weGExb3lWazFVTUZKdVZsVm9SbEZVYUZaVk1uUk9USHBCZVdKV1JscFVSMnR5V2tkc2RGSlhWbTlhTUZKS1kwWkdOVnB0V25GVFdHTTBWRVZvUlZSSFduTk1NVTR4VkZNNVRGRXliM3BoVlZvMFlsZE9UV0pyZEhCaFdFRjZUVmRaTkV3ell6VmFWMHBhVkZoV05Fc3hSalpXUlVaM1VXNUpjbHBIWkdGVGJUVkxTekZzZW1GcE9UWmFNbEY1VkVkT2JsRlhkRkJTV0VwNVRIcHNkRkV3Tld0aE1taFhZekJPZW1GR1RucGhiWEI2VkZSa1JWUnRkRmxQUmtKd1VtNUdlbFZHUmtoU1ZUVk9aRmR3ZUdKVVRUUlZWazQwV2pGc1NGTnViRzFrYTJSUlpERlZlVlp0YURGVlJYY3lWRVJvVDJNd2FISlNWV1JEWVhwV1IyRklVbFZYUkZZMFVraEdTRlZHUm1GU01EQjRaRlZXUTA5VlZuaFdXSEJ0VTFWR2FGRnJSa3haTW13MFUydDRhVmR1YUhWTmJrWnZZak53VGxOSWEzWmxibVJDVkVaS2RWSkZWbEpsVkVKeVpWZGtURlZXUm14UmEyc3hWakExU1ZSV1NqVmFWelZHVWpCa00yVllaelZhVmtaT1dUQm9OVkV3UmsxWFIzQkNUa2hTY1dWVlJsUmFNbmMxVmtaYVVWRlZWakZSYldSdVlUQmtVbFJWVmtWUlV6bHFTekprVUZSWFVrVlNhbFl5VTNwb1VGbFhaSFZSYlVadVZGUkdWRmt6VGxoVFdIQjBZVmR3UjJORlJtbFJWMlJWVFZkd2FsbHJTbWhSYTBaVFVrVkdTRkV3VG5KWGJGSkdXVEJXYmxGWFpFTk9NVnAyVkZad2RsbHNhM2RrUlVaQ1VWVkdRbEZyY0ZOV1ZGWkdZMjEwUzFveVpHNVFWREJ3VEVoV2VXSkRhR3RaV0ZKb1QyMXNkRmxYWkd4TU0wSjFXbnAwYVZsWVRteE9hbEZ6WVZaYVExUXhTak5OUlhSSVdqSTVRbEZWUmtKVWJFNVdZVVZXVmxvd1JrSlJWVXB1VVZWR1FsRldiRVJSVm14Q1VWVkdSVm95VWpaTmVsSkNVVlZHUWxGV2FFOVZNVWwzVTFWR2VXTjZVbXBPYkVaQ1VWVkdSbEpyY0ZOU1ZWcFdWVEJXTUdGc2NFeFJXR2hhVXpGT05Fc3hSalZoYTFwdlVWVXhXbGxYT1VkTlIxbHlWa2RhVVU5R2NEWldSR3h2WWxWV2FrMVZUWHBSYWxaaFdrWkdSRXN5ZEZsbFYwWkRaREZHUlZReldsTlRWVXAyVkd0c2VWTnJVbTVUTUZKSVRVZE9TMlJVVWtwVVZXeENVVzB3TkZGclNuUlpWR1JwVkd0S1FsRlZSa0pSVlZaelVteFNjbFV6VmxKaVZVNUVTMU40TVdOdGQyOWFSMFl3V1ZSd2NHSlhSbTVhVXpsM1ltMWpOMWx0Um5wYVZGa3dURWRzVjFGck9WTmtla0pNVWpKa2RsRlZSa0pSVlRWVVZsZG9SbFpYWkVKUlZVWkRXakJHUWxGVlJscFJNRVphVVZWR1FsSkhaR3RsYWswd1VWVkdRbEZWUmxsVWJFNVRUVVZzUW1OdVRUQlplbHBTVVZWR1FsVnVVa3RWYTFaSFZsWk9SbVJyV2xkV1NFNVFaREp3UWxSWFVsaFhXRll6V1RGR1UyRXdOVzlYYWtvMFVqQndkVlV3TkhkWGF6azJVMVZXYTJGV1NraFVibFpYWkd4S1MxTkdiRkJUVms0MlVsVm9VRlpFU25WWlZUbElUbFY0VTFNeGEzbFpNMUYyWlcxVmVrNVlRa05TTVVwc1UxZHdUa3Q1T1VwTlZVWkNZVE5rY0dGcWJHaFZSWFJoVWtoc01Gb3dXalJhYTNSSFRqQm9TbFp0ZEcxWGJFcENZakpHYTFOcldrVmhSbHBJWlZkYVdsZEdSa1JPYld4d1ZFWkpNRk5YY3pKV1YzUkpaREpPVkU5WVpGRmtiV2hXVVd4YWNXUnVRa2xVYld4VlVtMTBZVko2U2t0TmJXUjRVbFZ6TlZGdVFsbFdha3AxV201d1dWUllSa1JTYkVaT1lVaHNlVlJXUmxCTldFNVFVbGQ0Y1ZKWFNsUmFiV041WTFSU1dGUnFUbXRWTURrMFdsZFdhR05yYnpSUFJXeHdWVlZLTW1FelZUVlRSRXAwVWpGbmVtVkZlSGhaTVVwaFVXcHNOVlF4UVhKaFJGWnlUVmRzZDFscldYaFJNV3hJV201bmVsbFZVbmxXTVZaaFYxaGtjazFIVm0xUlZVVjRXa1ZqZGs1VlNrSlViVkp6V1ROdmVGSklUakJsYWtaeFlUQXhVMDFxVGpaT2JWWjZVVEpvUjJGVk9UVldRM1JDWTBoamVrNUZVbEZWTTBKSVpXcEtkMU13VlRSTE1XUkNZWHBvTkUxdGVEQk9NbmhHVFRCd00xUklSbEZoTW5CVlZqQnNhRXN6U20xVVZHUldWRVJhYkZSRmJIQlNNVVpLWVc1Vk5WcHVjRXRpVmxwSVZteFNWRTVGTlVKUlZVWkNVVlZHVkZaV1dsQlZhM014VVRGc1NsTlVNSEJNU0ZaNVlrTm9hMWxZVW1oUGJXeDBXVmRrYkV3elFuVmFlblJwV1ZoT2JFNXFVWE5oVmxwRFZERktNMDFGZEVoYU1qbENVVlZHUWxSc1RsWmhSVlpXV2pCR1FsRlZTbTVSVlVaQ1VWWnNSRkZXYkVKUlZVWkZXakpTTmsxNlVrSlJWVVpDVVZab1QxVXhTWGRUVlVaNVkzcFNhazVzUmtKUlZVWkZWbXR3VTFKVldsWlZNRll3WVdwR1NrMHdPSFpqZW14Q1ZWUm9RazVIUm01U01tZDNVV3BDVDFOWGRFcGhTR2hGVVZkR1UxTXdOVU5WYTFKQ1VsVk9SR0l4YkZWV1ZtUnhWVlpXVW1Rd1NrSmFNMFpJVWxSR1IxTlliRVJUVlVaRFZteENUV015ZUcxaGFrWjBZMVZHUWxGVlJrSlNWM2hIVmtkMFZHUldSblJSTUUxd1R6SkthRmt5ZEc1amJUa3hZbTFSZEdOdFZuZGFWMFl3VDIwMWRreFlTbXhqUjFab1pFUjBhVmxYVG5KYU0wcDJaRmMxYTB4WVRuQmxiVlUyV1RJNWRXUkhSbkJpYW5ScFdWZE9jbG96U25aa1Z6VnJURmhDZG1NeWJEQmhWemwxVDIxT2JHSnVVbXhqYW5Sd1lsZEdibHBUTVhsYVZ6VnJXbGhLY0dKdFl6Wk1XR1JzV1cxMGNHUkRNWFpqU0ZKd1lsZHNObHBUTVdwaU1qVXdZMjFHZW1SRWMzUmlXRTEwWVZjMU1GcFlTbmRpTW5ob1pFZHNkbUpwTVhSaU1sSnNUMjAxYkZsWVNteGpNMUYwWW0xV2NGb3lhR2xpTTBrM1lWY3hhRm95VlhSamJWWjFXa2RXZVdGWE5XNVBhVEYwWWpOdmRGa3pTbkJqTTBGMFdsZFNibHBZVFRkaFZ6Rm9XakpWZEdOdFZuVmFSMVo1WVZjMWJrOXVRbkJsUjFaeldWaFNiRnBFYzJsUWFuZDJZek5hYmxCblBUMGlQand2YVcxaFoyVStQQzl6ZG1jKyIsImF0dHJpYnV0ZXMiOlt7InRyYWl0X3R5cGUiOiJFeWVzIiwidmFsdWUiOiJDYWbDqSBDdXRpZXMifSx7InRyYWl0X3R5cGUiOiJNb3V0aCIsInZhbHVlIjoiVHdpZyJ9LHsidHJhaXRfdHlwZSI6IkhlYWQiLCJ2YWx1ZSI6IkNyb3duIn0seyJ0cmFpdF90eXBlIjoiRWFycmluZyIsInZhbHVlIjoiR29sZCJ9LHsidHJhaXRfdHlwZSI6IkZ1ciIsInZhbHVlIjoiT3JhbmdlIn1dfQ==",
        "tokenUriSyncedAt": "2024-02-21T08:26:18.754Z"
      }
    }
  ]
}

좋습니다! API를 통해 NFT 탐색기 구현에 필요한 데이터들을 모두 조회할 수 있음을 확인했습니다. 이제 간단한 개발 환경을 구축하고 실제로 동작하는 어플리케이션을 만들어봅시다.


Step 2. 개발 환경 설정하기

간단한 DApp 구현을 위해 아래 개발 환경 구성이 필요합니다. 설치를 위한 도움이 필요하다면 여기를 클릭해주세요.

VS Code Required

소스 코드 편집기로 코드를 손쉽게 작성하고 편집할 수 있습니다.

Node.js Required

자바스크립트 런타임 환경으로 프로그램 실행을 위해 필요합니다.

git Optional

분산 버전 관리 시스템으로 소스 코드를 로컬 환경에 다운로드 받기 위해 필요합니다. 예제 코드를 다운로드 받을 수 있습니다.

이 외에 사용된 프레임워크, 라이브러리는 Step 4. 예제코드의 package.json 파일을 참고해주세요.


Step 3. API 연동하기

이미 프로젝트 구현에 필요한 API들을 살펴보고 호출 시 응답이 어떻게 오는지 확인하였습니다. 이제 이 API들을 이용해 프론트엔드 코드에 연동해 볼까요?

📘

Nodit API Key를 발급 받으셨나요?

Nodit API를 호출하기 위해서는 Nodit API Key가 필요합니다. 아직 Nodit을 시작하지 않으셨다면 아래 링크된 페이지의 안내에 따라 Nodit 콘솔에 가입하고, 개발 연동을 위한 API Key를 발급받아보세요. 시작은 무료입니다!

👉 Nodit Node Quickstart

우선 Nodit의 API Key와 Protocol, Network를 환경 변수로 설정하여 하나의 파일에서 관리하도록 하였습니다.

// .env

VITE_API_KEY=-K5v1arBZA9ZC-tYoG9rYbwTLCuAHo8a
VITE_PROTOCOL=ethereum
VITE_NETWORK=mainnet

환경 변수 파일에 작성한 값을 불러와 HTTP 통신을 이용해 API를 호출할 인스턴스를 생성한 코드입니다.

// instance.ts

import axios, { AxiosInstance } from "axios";

const apiKey = import.meta.env.VITE_API_KEY;
const protocol = import.meta.env.VITE_PROTOCOL;
const network = import.meta.env.VITE_NETWORK;

export function createWeb3ApiInstance(): AxiosInstance {
  const instance = axios.create({
    baseURL: `https://web3.nodit.io/v1/${protocol}/${network}`,
    headers: {
      "X-API-KEY": `${apiKey}`,
      "Content-Type": "application/json",
      Accept: "application/json",
    },
  });
  return instance;
}

앞서 작성한 인스턴스를 이용해 API를 호출하는 함수를 만든 코드입니다.

// useQueries.ts

import { useQuery } from "@tanstack/react-query";
import { createWeb3ApiInstance } from "../apis/instance";
const instance = createWeb3ApiInstance();

export const useGetNFTOwnedByAccount = (
  accountAddress: string,
  page: number
) => {
  return useQuery({
    queryKey: ["getNFTOwnedByAccount", accountAddress, page],
    queryFn: async () => {
      try {
        const result = await instance.post("nft/getNftsOwnedByAccount", {
          accountAddress,
          withCount: true,
          withMetadata: true,
          rpp: 20,
          page: page,
        });
        return result;
      } catch (error) {
        console.error(error);
      }
    },
    retry: false,
    staleTime: 1000 * 60 * 5,
  });
};

화면에 노출될 테이블 컴포넌트를 구성합니다. 부모 컴포넌트에서 처리한 데이터인 ownedNftsByAccountData를 받아 테이블 요소에 바인딩 합니다.

// NftTable.tsx

import {
  NftListTableProps,
  NftsOwnedByAccountResponse,
} from "../types/interface";

const NftTable = ({ ownedNftsByAccountData }: NftListTableProps) =>

  return (
    <div className="flex flex-col items-center justify-center">
      <div className="mt-10 text-2xl font-bold">NFT List</div>
      {ownedNftsByAccountData && ownedNftsByAccountData.items.length > 0 ? (
          <table className="w-full max-w-7xl table-fixed border-collapse mt-5 mb-10 shadow-xl shadow-black">
            <thead>
              <tr className="border-2 border-noditGreen bg-noditGreen text-white">
                <th className="pl-5">Number</th>
                <th className="p-5">Name</th>
                <th className="p-5">Symbol</th>
                <th className="pr-5">Token Id</th>
              </tr>
            </thead>
            <tbody>
              {ownedNftsByAccountData.items.map(
                (item: NftsOwnedByAccountResponse, index: number) => (
                  <tr
                    key={index + item.contract.deployedTransactionHash}
                    className="border border-noditGreen hover:scale-105 duration-100 cursor-pointer"
                  >
                    <th className="font-bold p-5">
                      {ownedNftsByAccountData.page === 1
                        ? index + 1
                        : ownedNftsByAccountData.page * 20 + index - 19}
                    </th>
                    <th className="font-light p-5">{item.contract.name}</th>
                    <th className="font-light p-5">{item.contract.symbol}</th>
                    <th className="font-light p-5">
                      {item.tokenId.length < 10
                        ? item.tokenId
                        : item.tokenId.slice(0, 10) + "..."}
                    </th>
                  </tr>
                )
              )}
            </tbody>
          </table>
      ) : (
        <div>This Account doesn't have any NFTs</div>
      )}
    </div>
  );
};

export default NftTable;

부모 컴포넌트에서는 API의 데이터를 호출하고 테이블 컴포넌트에 API 호출 결과 데이터를 전달합니다.

// NftList.tsx

import React from "react";
import { useGetNFTOwnedByAccount } from "./useQueries";
import NftTable from "./NftTable";

const NftList = (): React.ReactElement => {
  const {
    isError,
    data: ownedNftData,
    isLoading,
  } = useGetNFTOwnedByAccount(accountAddress, currentPage);
  return (
    <div>
      <NftTable
        ownedNftByAccountData={ownedNftData?.data}
        accountAddress={accountAddress}
      />
    </div>
  );
};

export default NftList;

위와 같이 작성 후 서버를 실행하면 사진과 같이 데이터를 화면에서 확인할 수 있습니다.


Step 4. 예제 코드로 동작 확인하기

위에서 구현한 내용을 포함한, 미리 만들어놓은 예제 프로젝트를 공유해 드립니다.

  • 소스 코드 다운로드 및 접근
$ git clone https://github.com/Lambda256/Nodit-EVM-Tutorials
$ cd Nodit-EVM-Tutorials
$ cd nft_tutorial
  • 프로젝트 초기화 및 라이브러리 설치
$ npm install

사용자가 이용할 프로토콜과 네트워크 그리고 사용자의 Nodit API Key를 환경 변수로 설정합니다. Nodit API Key와 프로토콜, 네트워크의 경우 Nodit Console에서 확인할 수 있으며 기본 값으로 입력되어 있는 ethereum과 mainnet 외에도 사용자가 연결한 노드가 있다면 이용할 수 있습니다.

// .env

VITE_API_KEY=Your_Nodit_API_Key
VITE_PROTOCOL=ethereum
VITE_NETWORK=mainnet

환경 설정을 완료한 후 터미널에 아래의 명령어를 입력하여 Nodit NFT Tutorial을 실행합니다.

$ npm run dev

아래와 같은 화면이 뜨면 정상적으로 구동된것입니다! 기능을 확인해보세요.


Congratulation! 🎊

Nodit NFT Tutorial을 모두 완료하였습니다!이제 다른 Nodit API를 이용하여 나만의 기능을 추가해 보세요.

더욱 다양한 기능을 가진 Nodit의 API가 궁금하신가요? 아래 링크를 눌러 확인해 보세요!

Nodit API Reference 바로가기