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์ ๊ฑฐ๋ ์ด๋ ฅ์ ์กฐํํด๋ณด๊ฒ ์ต๋๋ค.
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=Your_Nodit_API_Key
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๊ฐ ๊ถ๊ธํ์ ๊ฐ์? ์๋ ๋งํฌ๋ฅผ ๋๋ฌ ํ์ธํด ๋ณด์ธ์!
Updated 2 months ago