신규: Helius가 Light Protocol을 인수했습니다
Solana에서 토큰 보유자를 조회하는 방법
블로그/개발

Solana에서 토큰 보유자를 조회하는 방법

개발자 경험 엔지니어X의 Owen VenterLinkedIn의 Owen Venter
읽는 데 5분

이 가이드에서는 USDC 같은 대체 가능 토큰의 모든 보유자를 조회하는 방법을 살펴봅니다. 토큰 보유자를 추적하거나 에어드롭으로 보상하려는 경우 유용합니다.

개요

먼저 Solana에서 토큰, 특히 대체 불가능 토큰이 작동하는 방식을 살펴보겠습니다. 개발자는 토큰을 만들 때 토큰 프로그램을 사용해 민트 계정을 생성합니다. 민트 계정에는 이름, 토큰 주소, 이미지 등 특정 토큰에 관한 정보가 저장됩니다. 민트 계정을 생성하면 토큰을 민팅해 토큰 계정에 저장할 수 있습니다. 토큰 계정은 특정 주소가 소유한 특정 토큰의 정보를 보관하는 계정입니다. 민트 주소, 소유자 주소, 계정에 있는 해당 토큰의 수량 같은 정보가 포함됩니다. 예를 들어 USDC(SPL 토큰)를 보유한 주소에는 USDC용 토큰 계정이 있습니다.

이제 토큰과 토큰 계정의 작동 방식을 이해했으므로 특정 토큰의 모든 보유자를 조회해 보겠습니다. 특정 토큰을 보유한 모든 지갑에는 해당 토큰의 토큰 계정이 있습니다. 즉, 이 토큰은 해당 토큰을 보유한 모든 지갑의 토큰 계정과 연결됩니다. 이를 통해 전체 보유자를 파악할 수 있습니다. 토큰과 연결된 모든 토큰 계정을 찾은 다음 각 계정의 소유자를 조회하면 전체 보유자 목록을 얻을 수 있습니다.

getTokenAccounts 메서드

다행히 Helius getTokenAccounts API 메서드를 사용하면 정확히 이 작업을 수행할 수 있습니다. API 호출 매개변수에 원하는 토큰의 민트 주소를 포함하면 해당 토큰에 대해 생성된 모든 토큰 계정의 목록이 반환됩니다. 또한 API는 각 토큰 계정의 소유자도 반환합니다. 이 소유자가 일반적으로 말하는 토큰 보유자입니다. 한 계정이 동일한 토큰에 대해 여러 토큰 계정을 가질 수 있다는 점에 유의하세요. 큰 문제는 아닙니다. 소유자가 같은 토큰 계정을 처리하는 로직만 추가하면 됩니다.

구현

이제 실제 구현 방법을 코드로 살펴보겠습니다. 이 가이드를 따라 하려면 Helius API 키가 필요합니다. Helius 대시보드에서 계정을 만들면 무료로 받을 수 있습니다. 먼저 getTokenHolders.js라는 JavaScript 파일을 생성해야 합니다. Helius URL을 추가하고 결과를 JSON 파일로 저장하기 위해 fs 라이브러리를 가져오는 것부터 시작합니다.

코드
const url = `https://mainnet.helius-rpc.com/?api-key=`;
const fs = require("fs");

다음으로 특정 토큰과 연결된 모든 토큰 계정을 가져오는 메서드를 생성합니다. 필요한 데이터를 가져오기 위해 getTokenAccounts 메서드를 사용하는 findHolders라는 메서드부터 만들겠습니다. getTokenAccounts 메서드에 관한 자세한 내용은 여기에서 확인할 수 있습니다.

API를 한 번 호출할 때 반환할 수 있는 토큰 계정은 최대 1,000개라는 점에 유의해야 합니다. Solana의 주요 토큰은 대부분 100,000개가 넘는 토큰 계정을 보유하고 있습니다. 이를 해결하기 위해 페이지네이션으로 모든 토큰 계정을 순회하며 기존의 모든 토큰 계정과 관련된 데이터를 가져올 때까지 API를 계속 호출합니다.

메서드에서는 getTokenAccounts 호출의 매개변수에 토큰 민트를 포함합니다. 모든 토큰 계정을 순회하면서 고유한 각 토큰 계정 소유자를 목록에 추가합니다. 메서드 실행이 끝나면 모든 토큰 보유자가 담긴 JSON 파일로 이 목록을 저장합니다.

코드
const findHolders = async () => {
  // Pagination logic
  let page = 1;
 	// allOwners will store all the addresses that hold the token
  let allOwners = new Set();

  while (true) {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        jsonrpc: "2.0",
        method: "getTokenAccounts",
        id: "helius-test",
        params: {
          page: page,
          limit: 1000,
          displayOptions: {},
					//mint address for the token we are interested in
          mint: "CKfatsPMUf8SkiURsDXs7eK6GWb4Jsd6UDbs7twMCWxo",
        },
      }),
    });

		// Check if any error in the response
      if (!response.ok) {
        console.log(
          `Error: ${response.status}, ${response.statusText}`
        );
        break;
      }

    const data = await response.json();
  	// Pagination logic.
    if (!data.result || data.result.token_accounts.length === 0) {
      console.log(`No more results. Total pages: ${page - 1}`);
      break;
    }
    console.log(`Processing results from page ${page}`);
 		// Adding unique owners to a list of token owners.
    data.result.token_accounts.forEach((account) =>
      allOwners.add(account.owner)
    );
    page++;
  }

  fs.writeFileSync(
    "output.json",
    JSON.stringify(Array.from(allOwners), null, 2)
  );
};

‍위 예시에서는 페이지네이션으로 모든 토큰 계정을 순회하는 동안 getTokenAccounts 메서드를 여러 번 호출합니다. API 응답은 각 토큰 계정에 대해 다음 데이터를 제공합니다:

코드
{
"address": "CVMR1nbxTcQ7Jpa1p137t5TyKFii3Y7Vazt9fFct3tk9",
"mint": "SHDWyBxihqiCj6YekG2GUr7wqKLeLAMK1gHZck9pL6y",
"owner": "CckxW6C1CjsxYcXSiDbk7NYfPLhfqAm3kSB5LEZunnSE",
"amount": 100000000,
"delegated_amount": 0,
"frozen": false
},

이러한 토큰 계정에서 소유자를 추출해 목록에 추가했습니다. 원하는 경우 각 토큰 계정이 보유한 토큰 수량도 저장하여 최대 보유자를 파악할 수 있습니다.

이 작업이 완료되면 메서드를 호출하기만 하면 됩니다:

코드
findHolders();

getTokenHolders.js 파일의 전체 코드는 다음과 같습니다:

코드
const url = `https://mainnet.helius-rpc.com/?api-key=`;
const fs = require("fs");

const findHolders = async () => {
  let page = 1;
  let allOwners = new Set();

  while (true) {
    const response = await fetch(url, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        jsonrpc: "2.0",
        method: "getTokenAccounts",
        id: "helius-test",
        params: {
          page: page,
          limit: 1000,
          displayOptions: {},
          mint: "DezXAZ8z7PnrnRJjz3wXBoRgixCa6xjnB7YaB1pPB263",
        },
      }),
    });

		// Check if any error in the response
      if (!response.ok) {
        console.log(
          `Error: ${response.status}, ${response.statusText}`
        );
        break;
      }

    const data = await response.json();

    if (!data.result || data.result.token_accounts.length === 0) {
      console.log(`No more results. Total pages: ${page - 1}`);

      break;
    }
    console.log(`Processing results from page ${page}`);
    data.result.token_accounts.forEach((account) =>
      allOwners.add(account.owner)
    );
    page++;
  }

  fs.writeFileSync(
    "output.json",
    JSON.stringify(Array.from(allOwners), null, 2)
  );
};

findHolders();

출력

코드의 출력은 모든 보유자의 목록이며 다음과 유사합니다:

코드
[
  "111An9SVxuPpgjnuXW9Ub7hcVmZpYNrYZF4edsGwJEW",
  "11Mmng3DoMsq2Roq8LBcqdz6d4kw9oSD8oka9Pwfbj",
  "112uNfcC8iwX9P2TkRdJKyPatg6a4GNcr9NC5mTc2z3",
  "113uswn5HNgEfBUKfK4gVBmd2GpZYbxd1N6h1uUWReg",
  "11CyvpdYTqFmCVWbJJeKFNX8F8RSjNSYW5VVUi8eX4P",
  "11MANeaiHEy9S9pRQNu3nqKa2gpajzX2wrRJqWrf8dQ",
…
]

replit 예제에서 직접 테스트할 수 있습니다.

마무리

이 가이드에서는 Helius getTokenAccounts API를 사용해 Solana 토큰 보유자를 식별하는 과정을 살펴봤습니다. 이제 에어드롭, 인사이트 확보 또는 추가 상호작용 등 다양한 목적으로 토큰 커뮤니티와 직접 소통하는 데 필요한 기반을 갖추셨습니다. 궁금한 점이 있다면 Twitter 또는 Discord로 언제든 문의해 주세요.‍

Helius 구독하기

최신 Solana 개발 소식을 확인하고 새 게시물 알림을 받아보세요

확대 이미지