티스토리 뷰
Github
준비물
- The Graph : 계정 생성
- nvm, node, yarn (또는 npm) : 서브그래프 설정을 위한 환경
- foundry : 이더리움 스마트 컨트랙트 개발환경
The Graph
The Graph는 Ethereum 및 다른 블록체인과 호환되는 인덱싱 프로토콜로, 블록체인 데이터를 효율적으로 쿼리 할 수 있게 도와줍니다. 기본적으로 The Graph는 데이터를 읽기 쉽게 만드는 역할을 하며, 이를 통해 개발자들이 블록체인 데이터를 이용한 응용 프로그램을 쉽게 구축할 수 있습니다.
Subgraph는 The Graph 내에서 블록체인 데이터를 정의하고 구조화하는 방법을 제공합니다. Subgraph를 사용하면 특정 블록체인 이벤트에 대한 데이터를 추출하고 인덱스화하여, GraphQL을 사용하여 쉽게 쿼리 할 수 있습니다. 각 Subgraph는 특정 블록체인 프로젝트나 스마트 컨트랙트의 데이터를 처리하도록 설정될 수 있으며, 이 데이터는 The Graph 노드에 의해 자동으로 처리되고 업데이트됩니다.
예를 들어, DApp이 Ethereum에서 발생하는 특정 거래를 추적하고자 할 때, 개발자는 이 거래를 포함하는 Subgraph를 만들고 The Graph 프로토콜을 통해 이를 쿼리 할 수 있습니다. 이런 방식으로, Subgraph는 블록체인 데이터에 대한 강력하고 유연한 쿼리 기능을 제공하며, 개발자가 필요로 하는 특정 정보에 대한 접근을 용이하게 만듭니다.
Foundry 프로젝트
프로젝트 생성
$ forge init nft-indexer
OpenZeppelin 라이브러리 설치
$ forge install OpenZeppelin/openzeppelin-contracts --no-commit
MyNFT 컨트랙트 작성
src 디렉터리 안에 MyNFT.sol 파일을 생성하고 붙여 넣기
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {ERC721} from "@openzeppelin/contracts/token/ERC721/ERC721.sol";
contract MyNFT is ERC721 {
uint256 _nextTokenId;
constructor() ERC721("MyNFT", "MNFT") {}
function testMint() public {
for (uint256 i = 0; i < 10; i++) {
_mint(msg.sender);
}
}
function _mint(address to) internal {
uint256 tokenId = _nextTokenId++;
_safeMint(to, tokenId);
}
}
MyNFT 컨트랙트 배포 및 testMint 함수 호출 스크립트 작성
script 디렉터리 안에 MyNFT .s.sol 파일을 생성하고 붙여 넣기
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;
import {Script, console} from "forge-std/Script.sol";
import {MyNFT} from "../src/MyNFT.sol";
contract MyNFTScript is Script {
function run() public {
vm.startBroadcast();
MyNFT nft = new MyNFT();
console.log("Deployed MyNFT at address: ", address(nft));
nft.testMint();
}
}
foundry.toml 파일에 rpc endpoint 추가
sepolia 테스트 네트워크의 rpc url을 사용
[rpc_endpoints]
sepolia = "https://ethereum-sepolia-rpc.publicnode.com"
컨트랙트 배포
- --account 플래그를 사용하는 방법 참고: https://piatoss3612.tistory.com/139
- 또는 --private-key 플래그를 사용하여 프라이빗 키를 직접 사용하는 방법도 있다
$ forge script script/MyNFT.s.sol --rpc-url sepolia --account piatoss --sender 0x965b0e63e00e7805569ee3b428cf96330dfc57ef --broadcast -vvvv
...
== Logs ==
Deployed MyNFT at address: 0x8E36a21AFeee005EA1A9e565c620f9A3BB3EfEE2
## Setting up 1 EVM.
==========================
Simulated On-chain Traces:
[864194] → new MyNFT@0x8E36a21AFeee005EA1A9e565c620f9A3BB3EfEE2
└─ ← [Return] 4091 bytes of code
[299606] MyNFT::testMint()
├─ emit Transfer(_from: 0x0000000000000000000000000000000000000000, _to: 0x965B0E63e00E7805569ee3B428Cf96330DFc57EF, _tokenId: 0)
├─ emit Transfer(_from: 0x0000000000000000000000000000000000000000, _to: 0x965B0E63e00E7805569ee3B428Cf96330DFc57EF, _tokenId: 1)
├─ emit Transfer(_from: 0x0000000000000000000000000000000000000000, _to: 0x965B0E63e00E7805569ee3B428Cf96330DFc57EF, _tokenId: 2)
├─ emit Transfer(_from: 0x0000000000000000000000000000000000000000, _to: 0x965B0E63e00E7805569ee3B428Cf96330DFc57EF, _tokenId: 3)
├─ emit Transfer(_from: 0x0000000000000000000000000000000000000000, _to: 0x965B0E63e00E7805569ee3B428Cf96330DFc57EF, _tokenId: 4)
├─ emit Transfer(_from: 0x0000000000000000000000000000000000000000, _to: 0x965B0E63e00E7805569ee3B428Cf96330DFc57EF, _tokenId: 5)
├─ emit Transfer(_from: 0x0000000000000000000000000000000000000000, _to: 0x965B0E63e00E7805569ee3B428Cf96330DFc57EF, _tokenId: 6)
├─ emit Transfer(_from: 0x0000000000000000000000000000000000000000, _to: 0x965B0E63e00E7805569ee3B428Cf96330DFc57EF, _tokenId: 7)
├─ emit Transfer(_from: 0x0000000000000000000000000000000000000000, _to: 0x965B0E63e00E7805569ee3B428Cf96330DFc57EF, _tokenId: 8)
├─ emit Transfer(_from: 0x0000000000000000000000000000000000000000, _to: 0x965B0E63e00E7805569ee3B428Cf96330DFc57EF, _tokenId: 9)
└─ ← [Stop]
Subgraph 생성
대시보드에서 Create a subgraph 선택
subgraph 이름을 정하고 Create Subgraph 클릭
@graphprotocol/graph-cli 설치하기
Foundry 프로젝트 안에 subgraph 초기화하기
$ graph init --studio mynft
- Protocol : ethereum 선택
- Subgraph slug : mynft
- Directory to create the subgraph in : subgraph
- Ethereum network : sepolia
- Contract address : 방금 배포한 MyNFT의 주소
- Failed to fetch ABI -> Do you want to retry가 뜨면 n 선택 (이더스캔을 통해서 컨트랙트를 verify 한 경우 자동으로 넘어감)
- ABI file (path) : ./out/MyNFT.sol/MyNFT.json
- Start Block : 컨트랙트가 배포된 블록 번호
- Contract Name : MyNFT
- Index contract events as entities: true
subgraph를 초기화하고 나면 Foundry 프로젝트 안에 subgraph 디렉터리가 생성되어 있음.
인증 및 subgraph 배포
$graph auth --studio <YOUR_AUTHENTICATION_CODE>
$ cd subgraph
$ graph codegen && graph build
$ graph deploy --studio mynft
Which version label to use? (e.g. "v0.0.1"): v0.0.1
Skip migration: Bump mapping apiVersion from 0.0.1 to 0.0.2
Skip migration: Bump mapping apiVersion from 0.0.2 to 0.0.3
Skip migration: Bump mapping apiVersion from 0.0.3 to 0.0.4
Skip migration: Bump mapping apiVersion from 0.0.4 to 0.0.5
Skip migration: Bump mapping apiVersion from 0.0.5 to 0.0.6
Skip migration: Bump manifest specVersion from 0.0.1 to 0.0.2
Skip migration: Bump manifest specVersion from 0.0.2 to 0.0.4
✔ Apply migrations
✔ Load subgraph from subgraph.yaml
Compile data source: MyNFT => build/MyNFT/MyNFT.wasm
✔ Compile subgraph
Copy schema file build/schema.graphql
Write subgraph file build/MyNFT/abis/MyNFT.json
Write subgraph manifest build/subgraph.yaml
✔ Write compiled subgraph to build/
Add file to IPFS build/schema.graphql
.. QmQuQmn2XZkLdp1fUbtwK243u2a9tXeECq7ApL5AFnPgVZ
Add file to IPFS build/MyNFT/abis/MyNFT.json
.. QmYSi7MXvTp3WXFfCkNrut4xG89XdybGnXgxrtCTDkmEqx
Add file to IPFS build/MyNFT/MyNFT.wasm
.. Qmca3KqTveeDTNFhefXsowHCUsisBNcoj5JnkpkAk6ScWW
✔ Upload subgraph to IPFS
Build completed: QmUqCdVQTPu548HVux8Tp3J2DXGNSn7N5Gk3H9Nmmiemk4
Deployed to https://thegraph.com/studio/subgraph/mynft
Subgraph endpoints:
Queries (HTTP): https://api.studio.thegraph.com/query/71401/mynft/v0.0.1
배포에 성공하면 SYNCED라고 초록색 줄이 뜸
이벤트 인덱싱
node.js 프로젝트 생성
$ cd ..
$ mkdir indexer
$ cd indexer
$ yarn init -y
$ touch index.js
package.json 수정
{
"name": "indexer",
"version": "1.0.0",
"main": "index.js",
"license": "MIT",
"scripts": {
"start": "node index.js"
}
}
라이브러리 설치
$ yarn add graphql graphql-request
index.js 작성
- 스키마는 subgraph 디렉터리의 schema.graphql을 참고.
- 쿼리문은 GraphQL 쿼리문으로 작성.
const { gql, request } = require("graphql-request");
const MY_NFT_QUERY_URL =
"https://api.studio.thegraph.com/query/71401/mynft/version/latest";
const main = async () => {
const query = gql`
{
transfers(first: 5) {
from
to
tokenId
}
}
`;
const response = await request(MY_NFT_QUERY_URL, query);
console.log(response);
};
main();
인덱싱
$ yarn start
yarn run v1.22.21
$ node index.js
{
transfers: [
{
from: '0x0000000000000000000000000000000000000000',
to: '0x965b0e63e00e7805569ee3b428cf96330dfc57ef',
tokenId: '0'
},
{
from: '0x0000000000000000000000000000000000000000',
to: '0x965b0e63e00e7805569ee3b428cf96330dfc57ef',
tokenId: '1'
},
{
from: '0x0000000000000000000000000000000000000000',
to: '0x965b0e63e00e7805569ee3b428cf96330dfc57ef',
tokenId: '2'
},
{
from: '0x0000000000000000000000000000000000000000',
to: '0x965b0e63e00e7805569ee3b428cf96330dfc57ef',
tokenId: '3'
},
{
from: '0x0000000000000000000000000000000000000000',
to: '0x965b0e63e00e7805569ee3b428cf96330dfc57ef',
tokenId: '4'
}
]
}
Done in 0.76s.
결론
요금이 어떤 식으로 차징 되는지는 모르겠으나 일단 graphql 쿼리문을 사용해 이벤트를 인덱싱하는 경우는 카운팅이 되지 않는 것 같습니다. 따라서 비용 부담이 크지 않을 것으로 보이네요.
그러나 영리적인 목적으로 무언가를 사용할 때 따라오는 제약들이 있듯이 어딘가에 제약이 걸려있을 것으로 예상됩니다. 해커톤이나 포트폴리오용 프로젝트에서는 마음껏 사용해도 될 듯합니다.
무엇보다 특정 컨트랙트에 대한 데이터를 온체인 상에서 쿼리 할 때 보다 빠르고 쉽게 인덱싱할 수 있다는 것이 굉장히 큰 메리트로 보입니다. 저는 자주 애용할 것 같습니다.
'블록체인' 카테고리의 다른 글
분산 데이터베이스와 분산 원장 (0) | 2023.09.04 |
---|