新着:HeliusがLight Protocolを買収
Solana DAS NFT API
ブログ/更新情報

Solanaの新しいDAS APIについて知っておくべきこと

デベロッパーリレーションズ責任者LinkedInのHunter Davis
読了時間:15分

概要

Digital Asset Standard(DAS)APIの登場により、Solana上のNFTやトークンを簡単に取得できるようになりました。Solana開発者向けツールキットに新たに加わったDAS APIは、Solana上のデジタルアセットを取得するための統一インターフェースを提供します。アセットタイプごとに複数のエンドポイントを扱う代わりに、単一のAPIからアプリケーションに必要なデータを取得できます。

このインタラクティブガイドで扱う内容:

  1. Solanaで利用できるアセットタイプの理解。
  2. DAS APIが提供するメソッドの包括的な解説。
  3. 各エンドポイントについて、簡単にカスタマイズできる実際のユースケースのデモ。

このガイドでは各ユースケースを実際に試せます。最後まで進めると、DASを自在に活用できるようになります。

前提条件

  • Node.jsがインストールされていること(組み込みのfetchを使用するにはv18.0が必要)
  • Helius RPC
  • JavaScriptの基礎知識

環境のセットアップ

  1. functionsという名前のプロジェクトフォルダーを作成します。
  2. 各例について、このフォルダー内に新しいファイルを作成します。

アセットタイプ

Solanaエコシステムにおける「アセット」とは、ブロックチェーン上に存在するトークンや非代替性トークン(NFT)など、価値を持つあらゆるデジタルアイテムを指します。Solanaは幅広いアセットをサポートしています。これらのアセットを操作する際にDAS APIが返すデータのタイプを理解することが重要です。

各アセットタイプを詳しく見ていきます。

非代替性

非代替性アセットは標準的なNFTモデルに従い、メタデータをトークンアカウントに保存します。このデータはProgram Derived Address(PDA)に格納されます。PDAは特定のユーザーではなく、プログラムが所有するアドレスです。Solanaブロックチェーン上にMetadata PDAとMaster Edition PDAを持ちます。

代替性

代替性アセットは、メタデータが限定されたSPLトークンです。USDCのようなトークンや、コミュニティ/プロジェクトのトークンを表現できます。作成時の小数点以下の桁数が0より大きいトークンは、Fungible標準に準拠します。

代替性アセット

Fungible Assetは、個々の単位ではなくアイテムを表します。標準的なFungibleアセットより多くのメタデータを保持できます。Fungible標準のアイテムを作成する際、小数点以下の桁数を0に設定するとFungible Asset標準になります。

プログラマブル非代替性

Programmable Non-FungibleアセットはNon-Fungible標準と似ていますが、凍結されたトークンアカウントに保持されます。この状態では、Token Metadataプログラムを介さずにプログラマブルアセットをバーン、ロック、転送することはできません。この標準は、Solanaにおけるロイヤリティを巡る議論を受けて作られました。

利用可能なメソッド

DAS APIは、さまざまなユースケースに合わせた以下のメソッドを提供します。

  1. getAsset:IDでアセットを取得します。
  2. searchAssets:各種パラメーターを使ってアセットを検索します。
  3. getAssetProof:IDを使って圧縮アセットのマークルプルーフを取得します。
  4. getAssetsByGroup:グループキーと値を使ってアセットのリストを取得します。
  5. getAssetsByOwner:特定のアドレスが所有するアセットのリストを取得します。
  6. getAssetsByCreator:特定のアドレスが作成したアセットのリストを取得します。
  7. getAssetsByAuthority:特定の権限を持つアセットのリストを検索します。

各メソッドの詳細については、ドキュメントをご覧ください。

1. アセットの取得

getAssetエンドポイントでは、IDを使って特定のアセットを取得できます。このIDには、オンチェーンのトークンアドレス、または圧縮アセットのマークルツリー上のIDを指定できます。

詳細については、getAssetのドキュメントをご覧ください。

例

ランク1のClaynosaurzアセットのメタデータを取得するとします。この場合、アセットのIDを特定し、DASを呼び出す関数を設定したうえで、結果から必要なオブジェクトだけを解析できるようにレスポンスを整えます。

以下の手順に従ってください。

  1. まず、「getAsset.js」という新しいファイルに関数を設定します。
コード
const url = `https://rpc.helius.xyz/?api-key=`;

const getAsset = async () => {
// Code goes here
};
getAsset();

     2. 次に、fetchとawaitの処理を設定します。リクエストボディに必須のIDパラメーターを含めます。

コード
const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 'my-id',
      method: 'getAsset',
      params: {
	// Rank 1 Claynosaurz #7392
        id: 'B1rzqj4cEM6pWsrm3rLPCu8QwcXMn6H6bd7xAnk941dU',
      },
    }),
  });

この手順では、リクエストボディにアセット固有のIDを含め、DAS APIへPOSTリクエストを送信しています。

    3. 結果を解析し、メタデータ情報を表示します。

コード
const { result } = await response.json();
console.log("asset: ", result);

このコードスニペットは、APIレスポンスをJSON形式に解析し、結果をコンソールに出力します。

その後、コマンドnode getAsset.jsを使ってスクリプトを実行できます。

結果

このスクリプトを実行すると、指定したアセットのメタデータが表示されます。

コード
asset:  {
  interface: 'Custom',
  id: 'B1rzqj4cEM6pWsrm3rLPCu8QwcXMn6H6bd7xAnk941dU',
  content: {
    '$schema': 'https://schema.metaplex.com/nft1.0.json',
    json_uri: 'https://nftstorage.link/ipfs/bafybeig2dp7oyauxdkhwduh274ekbl3cixyvbnky444qfewz3vfcauos6m/7391.json',
    files: [],
    metadata: { name: 'Claynosaurz #7392', symbol: 'DINO' }
  },
  authorities: [
    {
      address: 'B7B2g3WbdZMDV3YcDGRGhEt5KyWqDJZFwRR8zpWVEkUF',
      scopes: [Array]
    }
  ],
  compression: {
    eligible: false,
    compressed: false,
    data_hash: '',
    creator_hash: '',
    asset_hash: '',
    tree: '',
    seq: 0,
    leaf_id: 0
  },
  grouping: [
    {
      group_key: 'collection',
      group_value: '6mszaj17KSfVqADrQj3o4W3zoLMTykgmV37W4QadCczK'
    }
  ],
  royalty: {
    royalty_model: 'creators',
    target: null,
    percent: 0.05,
    basis_points: 500,
    primary_sale_happened: true,
    locked: false
  },
  creators: [
    {
      address: 'AoebZtN5iKpVyUBc82aouWhugVknLzjUmEEUezxviYNo',
      share: 0,
      verified: true
    },
    {
      address: '36tfiBtaDGjAMKd6smPacHQhe4MXycLL6f9ww9CD1naT',
      share: 100,
      verified: false
    }
  ],
  ownership: {
    frozen: false,
    delegated: false,
    delegate: null,
    ownership_model: 'single',
    owner: '4zdNGgAtFsW1cQgHqkiWyRsxaAgxrSRRynnuunxzjxue'
  },
  supply: null,
  mutable: true
}

この出力には、インターフェースタイプ、ID、メタデータ、作成者、権限、所有権など、アセットの詳細情報が含まれます。

完全なコード

IDを使ってアセットを取得する完全なコードは次のとおりです。

コード
const url = `https://rpc.helius.xyz/?api-key=`;

const getAsset = async () => {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 'my-id',
      method: 'getAsset',
      params: {
        id: 'B1rzqj4cEM6pWsrm3rLPCu8QwcXMn6H6bd7xAnk941dU',
      },
    }),
  });

  const { result } = await response.json();
  console.log("asset: ", result);
};

getAsset();

<api-key>を実際のAPIキーに置き換えてください。

このコードでは、DAS APIへPOSTリクエストを送信する非同期関数getAssetを定義しています。リクエストボディにはアセットのIDを渡します。リクエストが完了すると、関数はレスポンスをJSONとして解析し、アセットデータをコンソールに出力します。最後に、getAsset関数を呼び出して、この処理を実行します。

2. アセットプルーフの取得

getAssetProofエンドポイントは、圧縮プログラムの変更に必要なアセットプルーフを取得するために使用します。このような変更には、圧縮アセットの転送、バーン、作成者の更新、コレクションの更新、解凍などがあります。

アセットプルーフを使用する変更の詳細な一覧については、ドキュメントをご覧ください。

例

圧縮アセットの変更に必要なアセットプルーフを取得するには、以下の手順に従います。

  1. まず、「getAssetProof.js」という新しいファイルに関数を設定します。
コード
const url = `https://rpc.helius.xyz/?api-key=`;

const getAsset = async () => {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 'my-id',
      method: 'getAsset',
      params: {
        id: 'B1rzqj4cEM6pWsrm3rLPCu8QwcXMn6H6bd7xAnk941dU',
      },
    }),
  });

  const { result } = await response.json();
  console.log("asset: ", result);
};

getAsset();

      2. 続いて、fetchとawaitを設定します。リクエストボディに必須のIDパラメーターを含めます。

コード
const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
		body: JSON.stringify({
      jsonrpc: '2.0',
      id: 'my-id',
      method: 'getAssetProof',
      params: {
        id: 'JDuAmJjiiNKCfK9SyW1aQCNvhL7krhVWZbeijVupAz4i'
      },
    }),
  });

     3. 結果を解析し、rootを抽出します。

コード
const { proof } = await response.json();
console.log("Asset Proof: ", result);
const root = decode(proof.root);
console.log(root)

‍ここでは、アセットプルーフからrootを抽出しています。このrootを使って、圧縮アセットに追加の変更を加えられます。

ターミナルでnode getAssetProof.jsを実行すると、ここで指定したアセットの結果を取得できます。

結果

出力にはアセットプルーフの情報が含まれます。

コード
Assets Proof:  {
  root: 'FCdCNPGauQp1NqZbs1f4DDAWawHLbBqfD9LYjMy1fqH4',
  proof: [
    'EmJXiXEAhEN3FfNQtBa5hwR8LC5kHvdLsaGCoERosZjK',
    '7NEfhcNPAwbw3L87fjsPqTz2fQdd1CjoLE138SD58FDQ',
    '6dM3VyeQoYkRFZ74G53EwvUPbQC6LsMZge6c7S1Ds4ks',
    '34dQBtcjnCUoPVzZEmVPAMH7b3b8aD6GUB9aYS11AaWJ',
    '2VG5cKeBZdqozwhHGGzs13b9tzy9TXt9kPfN8MzSJ1Sm',
    'r1o8vR5KFHJeER7A1K7kBCjceDHnUbSwiFEqqmeAQSd',
    '88sRtuz1QHWhYEKtx1VamwwrmtDkb8vyDyUuqWCJrxoa',
    '9Y8Xa2qwARx7Mg6deJwP37UEX9BA2tM75N4f6vaGyBDU',
    'CKWwHXAcqoTptkjZkuKqhQ9iuajC8dK6f8eGpknesqWS',
    '4n9Z4eSKNZa1a4oA3sbFGowA7go9BV9WopLpA4KqnYdD',
    '6MJKrpnK1GbYsnEzwMRWStNGkTjAZF23NhzTQSQVXsD3',
    'HjnrJn5vBUUzpCxzjjM9ZnCPuXei2cXKJjX468B9yWD7',
    '4YCF1CSyTXm1Yi9W9JeYevawupkomdgy2dLxEBHL9euq',
    'E3oMtCuPEauftdZLX8EZ8YX7BbFzpBCVRYEiLxwPJLY2'
  ],
  node_index: 16384,
  leaf: '6YdZXw49M97mfFTwgQb6kxM2c6eqZkHSaW9XhhoZXtzv',
  tree_id: '2kuTFCcjbV22wvUmtmgsFR7cas7eZUzAu96jzJUvUcb7'
}

完全なコード

IDを使ってアセットプルーフを取得する完全なコードは次のとおりです。

コード
const url = `https://rpc.helius.xyz/?api-key=`;

const getAssetProof = async () => {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 'my-id',
      method: 'getAssetProof',
      params: {
        id: 'JDuAmJjiiNKCfK9SyW1aQCNvhL7krhVWZbeijVupAz4i'
      },
    }),
  });
  const { result } = await response.json();
  console.log("Asset Proof: ", result);
	const root = proof.root;
	console.log(root)
};
getAssetProof();

<api-key>を実際のAPIキーに置き換えてください。

このスクリプトでは、Helius APIへPOSTリクエストを送信する非同期関数getAssetProofを定義しています。リクエストボディにはアセットのIDを渡します。リクエストが完了すると、関数はレスポンスをJSONに解析し、アセットプルーフとそのrootをコンソールに出力します。最後に、getAssetProof関数を呼び出して、この処理を実行します。

3. アセットの検索

searchAssetsメソッドは、指定した検索パラメーターに基づいてデジタルアセットを取得し、柔軟なデータ取得手段を提供します。検索をカスタマイズできるため、返されるアセットをより細かく制御できます。

パラメーターの詳細は、searchAssetsのドキュメントをご覧ください。

例

ユーザーのウォレットにあるDrip Hausコレクションのアセットについて、画像と名前を表示し、その中から圧縮アイテムのみを表示する例を見てみましょう。このチュートリアルでは簡潔にするため、サンプルウォレット内の各Dripアセットの名前と画像をJSONファイルに出力します。

  1. まず、次の非同期関数を含むsearchAssets.jsという新しいファイルを作成します。
コード
const fs = require('fs');
const url = `https://rpc.helius.xyz/?api-key=`;

const searchAssets = async () => {
	// Code goes here
};
searchAssets();

     2. 指定した検索パラメーターを含むPOSTリクエストを送信するよう関数を実装します。ここでは目的の結果を得るために、圧縮、所有者アドレス、コレクショングループのパラメーターを使用します。

コード
const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: "my-id",
      method: "searchAssets",
      params: {
        // Returning only compressed items.
        compressed: true,
        // Example wallet
        ownerAddress: "2k5AXX4guW9XwRQ1AKCpAuUqgWDpQpwFfpVFh3hnm2Ha",
        // Drip Haus collection ID.
        grouping: [
          "collection",
          "DRiP2Pn2K6fuMLKQmt5rZWyHiUZ6WK3GChEySUpHSS4x"
        ],
        page: 1,
      },
    }),
  });

    3. レスポンスを解析してアセットをID別にグループ化し、各アイテムのレスポンスから必要な項目だけを指定することで、重複の可能性に対処します。

コード
const { result } = await response.json();

  const groupedResults = [];

  for (let i = 0; i < result.items.length; i++) {
    const asset = {
      id: result.items[i].id,
      name: result.items[i].content.metadata.name,
      json_uri: result.items[i].content.json_uri,
    };

    4. 次の関数は重複を検出し、リストから削除します。重複アセットを表示したい場合は、この関数を使わなくても構いません。また、重複でないと判断されたアセットは新しいアセットグループに追加されます。

コード
const existingGroup = groupedResults.find(group => group.id === asset.id);
		if (existingGroup) {
      // Add the asset to the existing group
      existingGroup.assets.push(asset);
    } else {
      // Create a new group for the asset
      const newGroup = {
        id: asset.id,
        assets: [asset],
      };
  // Add the new group to the grouped results
      groupedResults.push(newGroup);
    }
  }

   5. 検索結果をsearchResults.jsonというJSONファイルに保存します。

コード
const json = JSON.stringify(groupedResults, null, 2);

// Write the JSON to a file
fs.writeFileSync('searchResults.json', json);

console.log('Results saved to results.json');

スクリプトを実行するには、node searchAssets.jsを実行します。検索結果がsearchResults.jsonファイルに書き込まれます。

結果

コード
[
  {
    "id": "4XSuZ2JaCPYA76EomCd1mZCtrjkx4F4sdcepBgBF2LKE",
    "assets": [
      {
        "id": "4XSuZ2JaCPYA76EomCd1mZCtrjkx4F4sdcepBgBF2LKE",
        "name": "MOMENT",
        "json_uri": "https://arweave.net/3DniodKpcCTio-GlyB4GMjdA4c5epHzYQecdLZctt5s"
      }
    ]
  },
  {
    "id": "AJw2QNwWMLWTuUTiUWYpEyGjUQSPB5rYEcbn5uiBjQ2g",
    "assets": [
      {
        "id": "AJw2QNwWMLWTuUTiUWYpEyGjUQSPB5rYEcbn5uiBjQ2g",
        "name": "\"Kokoro\" (心)",
        "json_uri": "https://arweave.net/PyPo4Zr4iWH2iRROBDtZTFZv3qWw1dVcrYPq4A_6ttM"
      }
    ]
  }, ...
}

完全なコード

コード
const url = `https://rpc.helius.xyz/?api-key=`;
const fs = require('fs');

const searchAssets = async () => {
  const response = await fetch(url, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      jsonrpc: "2.0",
      id: "my-id",
      method: "searchAssets",
      params: {
        // Returning only compressed items.
        compressed: true,
        // Example wallet
        ownerAddress: "2k5AXX4guW9XwRQ1AKCpAuUqgWDpQpwFfpVFh3hnm2Ha",
        // Drip Haus collection ID.
        grouping: [
          "collection",
          "DRiP2Pn2K6fuMLKQmt5rZWyHiUZ6WK3GChEySUpHSS4x"
        ],
        page: 1,
      },
    }),
  });
  const { result } = await response.json();

  const groupedResults = [];

  for (let i = 0; i < result.items.length; i++) {
    const asset = {
      id: result.items[i].id,
      name: result.items[i].content.metadata.name,
      json_uri: result.items[i].content.json_uri,
    };

    // Find an existing group for the asset if it exists
    const existingGroup = groupedResults.find(group => group.id === asset.id);

    if (existingGroup) {
      // Add the asset to the existing group
      existingGroup.assets.push(asset);
    } else {
      // Create a new group for the asset
      const newGroup = {
        id: asset.id,
        assets: [asset],
      };

      // Add the new group to the grouped results
      groupedResults.push(newGroup);
    }
  }

  // Convert the grouped results to JSON
  const json = JSON.stringify(groupedResults, null, 2);

  // Write the JSON to a file
  fs.writeFileSync('searchResults.json', json);

  console.log('Results saved to results.json');
};

searchAssets();

<api-key>を実際のAPIキーに置き換えてください。

このスクリプトでは、POSTリクエストを送信する非同期関数searchAssetsを定義しています。リクエストボディには、圧縮、所有者アドレス、コレクションIDなどの各種検索パラメーターを使用します。リクエストが完了すると、関数はレスポンスをJSONに解析し、関連するアセット情報(ID、名前、JSON URI)を抽出します。これらのアセットをID別にグループ化し、整理したデータをsearchResults.jsonというJSONファイルに保存します。最後に、searchAssets関数を呼び出して、この処理を開始します。

4. 所有者別のアセット取得

getAssetsByOwnerエンドポイントは、特定のアドレスが所有するデジタルアセットのリストを返します。現在、Heliusを使ってデジタルアセットの特定の所有権情報を取得する最速の方法です。

例

特定のアドレスが所有するアセットを取得するには、以下の手順に従います。

  1. まず、「getAssetsByOwner.js」という新しいファイルに関数を設定します。
コード
const fs = require('fs');
const url = `https://rpc.helius.xyz/?api-key=`;

const getAssetsByOwner = async () => {
  // Code goes here
};
getAssetsByOwner();
  1. fetchとawaitを設定し、リクエストボディにパラメーターを含めます。このエンドポイントではownerAddressを使用します。
コード
const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 'hunter-test',
      method: 'getAssetsByOwner',
      params: {
					// Example wallet
        ownerAddress: '2k5AXX4guW9XwRQ1AKCpAuUqgWDpQpwFfpVFh3hnm2Ha',
        page: 1
      },
    }),
  });
  1. 次にレスポンスを解析し、JSONに出力するために必要なアセット情報を抽出します。アセットID、名前、json URIを使い、検索対象のウォレットごとにグループ化します。
コード
const { result } = await response.json();
 const groupedResults = {};

  for (let i = 0; i < result.items.length; i++) {
    const ownerAddress = result.items[i].owner.address;
    const asset = {
      id: result.items[i].id,
      name: result.items[i].content.metadata.name,
      json_uri: result.items[i].content.json_uri,
    };

    if (groupedResults.hasOwnProperty(ownerAddress)) {
      // Add the asset to the existing group
      groupedResults[ownerAddress].assets.push(asset);
    } else {
      // Create a new group for the owner
      groupedResults[ownerAddress] = {
        assets: [asset],
      };
    }
  }
  1. これで、JSONに出力する関数を設定できます。
コード
// Convert the grouped results to JSON
  const json = JSON.stringify(groupedResults, null, 2);

  // Write the JSON to a file
  fs.writeFileSync('results.json', json);

  console.log('Results saved to results.json');

結果

出力には、指定したアドレスが所有するデジタルアセットが含まれます。

コード
{
  "2k5AXX4guW9XwRQ1AKCpAuUqgWDpQpwFfpVFh3hnm2Ha": {
    "assets": [
      {
        "id": "7Qj3QGCqRChr3uBR4R756usz2eoPSisoWcPfeozY7Bo",
        "name": "",
        "json_uri": "https://nftstorage.link/ipfs/bafybeiewlaxjeredwgsboqqha2ww25g46dgrqs6lwn5cooq3evsvye33iy/3071.json"
      },
      {
        "id": "8W1Dx9vhyQ8fNyi6oVu2EFbZHo2kKpcRxMVbQ3em3ne",
        "name": "Compass Rose #2745",
        "json_uri": "https://shdw-drive.genesysgo.net/HqhFDmVhqqN23g4soMd8UzrfLEXT8GsjWtWaqxfm9A2x/2745.json"
      },
      {
        "id": "Ba5qsjq5LryLPZ1e6AVqwPzB5LBBBzGWvvRQww4KCiG",
        "name": "Foxy Pixel Demon #423",
        "json_uri": "https://cdn.secretsphinx.io/ml/1c22a51697c644a8e45f603ed884ade7.json"
      },
      {
        "id": "Hmid2Dhi3zLV7AxCAibQ8n4nviWdPv2ZvUeZq4N33oe",
        "name": "",
        "json_uri": "https://nftstorage.link/ipfs/bafybeiewlaxjeredwgsboqqha2ww25g46dgrqs6lwn5cooq3evsvye33iy/140.json"
      }, ...
}

完全なコード

特定のアドレスが所有するアセットを取得する完全なコードは次のとおりです。

コード
const url = `https://rpc.helius.xyz/?api-key=`;

const getAssetsByOwner = async () => {
  const response = await fetch(url, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      jsonrpc: '2.0',
      id: 'hunter-test',
      method: 'getAssetsByOwner',
      params: {
					// Example wallet
        ownerAddress: '2k5AXX4guW9XwRQ1AKCpAuUqgWDpQpwFfpVFh3hnm2Ha',
        page: 1,
      },
    }),
  });
 const { result } = await response.json();
 const groupedResults = {};

  for (let i = 0; i < result.items.length; i++) {
    const ownerAddress = result.items[i].owner.address;
    const asset = {
      id: result.items[i].id,
      name: result.items[i].content.metadata.name,
      json_uri: result.items[i].content.json_uri,
    };

    if (groupedResults.hasOwnProperty(ownerAddress)) {
      // Add the asset to the existing group
      groupedResults[ownerAddress].assets.push(asset);
    } else {
      // Create a new group for the owner
      groupedResults[ownerAddress] = {
        assets: [asset],
      };
    }
  }

  // Convert the grouped results to JSON
  const json = JSON.stringify(groupedResults, null, 2);

  // Write the JSON to a file
  fs.writeFileSync('results.json', json);

  console.log('Results saved to results.json');
};

getAssetsByOwner();

<api-key>を実際のAPIキーに置き換えてください。

このスクリプトでは、Helius APIへPOSTリクエストを送信する非同期関数getAssetsByOwnerを定義しています。リクエストボディには所有者のアドレスを渡します。リクエストが完了すると、関数はレスポンスをJSONに解析し、指定したアドレスが所有するアセットをコンソールに出力します。最後に、getAssetsByOwner関数を呼び出して、この処理を実行します。

5. グループ別のアセット取得

getAssetsByGroupエンドポイントは、特定のコレクションIDに関連付けられたデジタルアセットを取得するために使用します。特定のコレクションのアイテムを取得する場合や、トークンゲート付きdAppを特定のオンチェーンコレクションに関連付ける必要がある場合に重要なエンドポイントです。

例

このシナリオでは、SMBのコレクションスナップショットを作成します。ドキュメントに記載されているとおり、レスポンスを解析し、ownershipオブジェクトからアセットの所有者を抽出することが重要です。複数のSMBを所有する所有者を絞り込み、JSONには一度だけ表示することで、実質的な「スナップショット」を作成します。

  1. まず、「getAssetsByGroup.js」という新しいファイルに関数を設定します。一意の所有者だけを保存するため、Setを使用します。
コード
const url = `https://rpc.helius.xyz/?api-key=`;
const fs = require('fs');
const uniqueOwners = new Set();

const getAssetsByGroup = async () => {
		 // Code goes here
};
getAssetsByGroup();
  1. 続いて、fetchとawaitを設定します。上記のパラメーターをリクエストボディに設定し、リクエスト内でgroupKeyとgroupValueを指定します。ページネーションの準備として、pageを1、hasMoreResultsをtrueに初期化します。その後、ページの結果が1000件(リクエストごとの上限)未満の場合にfalseを返すページネーション関数を設定します。
コード
let page = 1;
  let hasMoreResults = true;

  while (hasMoreResults) {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 'my-id',
        method: 'getAssetsByGroup',
        params: {
          groupKey: 'collection',
          groupValue: 'SMBtHCCC6RYRutFEPb4gZqeBLUZbMNhRKaMKZZLHi7W',
          page,
          limit: 1000,
        },
      }),
    });
  1. 結果を解析し、現在の所有者のスナップショット作成に必要な所有者文字列を抽出します。
コード
const { result } = await response.json();
// Add each owner to the Set, automatically discarding duplicates
result.items.forEach(item => uniqueOwners.add(item.ownership.owner));
  1. 結果が1000件ある場合はpageパラメーターを増やし、ページネーションを設定します。1000件未満の場合はhasMoreResultsをfalseにしてページネーションを停止します。
コード
if (result.items.length < 1000) {
      hasMoreResults = false;
    } else {
      page++;
    }
  }
  1. 次に所有者を配列に設定し、保有者数と一意の各所有者ウォレットを含むJSONへ出力するためのroot値を設定します。
コード
const uniqueOwnersArray = Array.from(uniqueOwners);
  
  const root = {
    count: uniqueOwners.size,
    owners: uniqueOwnersArray
  };
  1. 一意の所有者を格納したSetを配列に変換します。保有者数と一意の各所有者ウォレットをJSONへ出力するroot値を設定します。
コード
fs.writeFile('./ownerResults.json', jsonResult, 'utf8', (err) => {
    if (err) {
      console.error("Error writing JSON file:", err);
    } else {
      console.log("JSON file saved successfully.");
    }
  });
console.log("Total number of unique owners:", uniqueOwners.size);
};

その後、コマンドnode getAssetsByGroup.jsを実行すると、結果が「ownerResults.json」ファイルに書き込まれます。

結果

結果は保有者スナップショットに相当するJSONとなり、一意の所有者数と全所有者のリストが含まれます。

コード
{
  "count": 2785,
  "owners": [
    "6VqzFgtrJb33nhvbug4KZoUx8p65dD2iT1QuAeAQgYiw",
    "5Xeb43ASEa64b9i9owcLB4yrNbUw1oMiTpcWsAdXN8qG",
    "Cb355XH2WGPeQUGTTXWiQZT4nhnyHCPkhScDezUGhXQF",
    "Fuu7xpK3mWqpqPLTHaxF7pU2czkBESKyg3r2Lm2F3AWz",
    "1BWutmTvYPwDtmw9abTkS4Ssr8no61spGAvW1X6NDix",
    "86tCSKzryE5KvbTmMXN9tkxyn8GNr4z54DnNqrcZwYuy",
    "D2DYL5sdxBCpauvKs1oyQkkSm2B9rFzzGowMacv3Q58z",
		// Additional results ...
]
}

完全なコード

コード
const url = `https://rpc.helius.xyz/?api-key=`;
const fs = require('fs');
const uniqueOwners = new Set();

const getAssetsByGroup = async () => {
  let page = 1;
  let hasMoreResults = true;

  while (hasMoreResults) {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 'my-id',
        method: 'getAssetsByGroup',
        params: {
          groupKey: 'collection',
          groupValue: 'SMBtHCCC6RYRutFEPb4gZqeBLUZbMNhRKaMKZZLHi7W',
          page,
          limit: 1000,
        },
      }),
    });

    const { result } = await response.json();
    // Add each owner to the Set, automatically discarding duplicates
    result.items.forEach(item => uniqueOwners.add(item.ownership.owner));

    if (result.items.length < 1000) {
      hasMoreResults = false;
    } else {
      page++;
    }
  }

  // Convert Set to Array for stringification
  const uniqueOwnersArray = Array.from(uniqueOwners);
  
  const root = {
    count: uniqueOwners.size,
    owners: uniqueOwnersArray
  };
  
  const jsonResult = JSON.stringify(root, null, 2);

  fs.writeFile('./ownerResults.json', jsonResult, 'utf8', (err) => {
    if (err) {
      console.error("Error writing JSON file:", err);
    } else {
      console.log("JSON file saved successfully.");
    }
  });
};
console.log("Total number of unique owners:", uniqueOwners.size);

getAssetsByGroup();

<api-key>を実際のAPIキーに置き換えてください。

これで、getAssetsByGroupエンドポイントを使い、特定のコレクションIDに関連付けられたデジタルアセットを取得する方法を習得できました。ページネーションとSetデータ構造を効果的に使って所有者の重複を防ぐことで、オンチェーンIDを持つ任意のコレクションについて、現在の保有者のスナップショットを作成できます。

6. 作成者別のアセット取得

getAssetsByCreatorエンドポイントは、特定の公開鍵アドレスが作成したアセットを取得するために使用します。Solana上の特定のアーティストやプロジェクトに関連するアセットを探す場合に便利です。

例

この例では、Zen0が作成したアセットを返します。作成者のアドレスを使用し、onlyVerifiedパラメーターをtrueに設定することで、検証済みウォレットが作成したアセットだけを取得できます。結果を解析し、各アセットのIDと所有者を表示します。

  1. まず、「getAssetsByCreator.js」という新しいファイルに関数を設定します。結果をJSONファイルに出力するために、fsモジュールを使用します。
コード
const url = `https://rpc.helius.xyz/?api-key=`;
const fs = require('fs');

const getAssetsByCreator = async () => {
  // Code goes here
};
getAssetsByCreator();
  1. 次に、fetchリクエストを設定し、レスポンスをawaitできます。creatorAddressやonlyVerifiedなど、必要なパラメーターをリクエストボディに設定します。
コード
let page = 1;
  let allResults = [];
  let hasMoreResults = true;

  while (hasMoreResults) {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 'my-id',
        method: 'getAssetsByCreator',
        params: {
          creatorAddress: 'zvrsoq2LbNxekPqLK1v8DsLgeC4LHxMQL52beX8Ktn8',
          onlyVerified: true,
          page,
          limit: 1000,
        },
      }),
    });

<creator-address>を、アセットを取得したい作成者の実際のアドレスに置き換えてください。

  1. レスポンスを解析して結果を抽出し、配列に保存します。
コード
const { result } = await response.json();
allResults = allResults.concat(result.items);

if (result.items.length < 1000) {
      hasMoreResults = false;
    } else {
      page++;
    }
  }
  1. 所有権IDに基づいてアセットをグループ化し(1人が複数のアセットを所有できるため)、各IDが所有するアセットを表示します。新しい所有者が検出されると、JSONファイルに新しいグループが作成されます。
コード
const groupedResults = {};

  for (let i = 0; i < allResults.length; i++) {
    const ownershipId = allResults[i].ownership.owner;
    const asset = {
      id: allResults[i].id,
      ownershipId: ownershipId,
    };

    if (groupedResults.hasOwnProperty(ownershipId)) {
      // Add the asset to the existing group
      groupedResults[ownershipId].assets.push(asset);
    } else {
      // Create a new group for the ownership ID
      groupedResults[ownershipId] = {
        assets: [asset],
      };
    }
  }
  1. 次に結果を構造化します。ここではNFTのアドレスと、返された各アセットの所有者を取得します。rootを使って返される結果の件数を指定し、加工した結果を出力用に保存します。
コード
// Create new array to hold the desired properties
 const modifiedResults = totalResults.map(item => ({
    id: item.id,
    owner: item.ownership.owner
  }));
  // Create a root object to hold the count and the results
  const root = {
    count: modifiedResults.length,
    results: modifiedResults
  };
  1. 最後に、fsモジュールを使い、結果を「creatorResults.json」というファイルに保存します。
コード
// Convert the grouped results to JSON
  const json = JSON.stringify(groupedResults, null, 2);

  // Write the JSON to a file
  fs.writeFileSync('creatorResults.json', json);

  console.log('Results saved to results.json');

最後に、コマンドnode getAssetsByCreator.jsを実行してスクリプトを起動し、取得した結果を「creatorResults.json」ファイルに書き込みます。

結果

生成されるJSONファイルには、各所有者の公開鍵アドレスと、それぞれが所有するアセットが含まれます。各アセットはIDで表されるため、特定の作成者が作成したアセットの所有状況を明確に把握できます。

コード
{
  "ZVcBfkk3Be8QMn4rmQL2VtP2WJQp9wpU8udFwrTGA22": {
    "assets": [
      {
        "id": "KvbtDebCi6BGSFuafJWZpwV5mt1XYng13Pt2vh4G2Qa",
        "ownershipId": "ZVcBfkk3Be8QMn4rmQL2VtP2WJQp9wpU8udFwrTGA22"
      },
      {
        "id": "28bXCZaETv6ihBGbtavDkedzRcWZPbmDG5VYXxSdmZBc",
        "ownershipId": "ZVcBfkk3Be8QMn4rmQL2VtP2WJQp9wpU8udFwrTGA22"
      },
      {
        "id": "2H4txSfZwV3nX4fJ1D8dPNHiafn247iQ8vtq3y3UYdpe",
        "ownershipId": "ZVcBfkk3Be8QMn4rmQL2VtP2WJQp9wpU8udFwrTGA22"
      },
      {
        "id": "5oauJRPWbboJVUBms2mqBF2wMQGMSCFScgX18SawyKV3",
        "ownershipId": "ZVcBfkk3Be8QMn4rmQL2VtP2WJQp9wpU8udFwrTGA22"
      },
	"Fb7pzfL6SLDNi79WSWkZm6zKA94LYNsYKDJC7GNvkZaC": {
    "assets": [
      {
        "id": "2jLjf8Vmbisu5eac1NgSiDcqkcJfR8R9LSGjD6SExC99",
        "ownershipId": "Fb7pzfL6SLDNi79WSWkZm6zKA94LYNsYKDJC7GNvkZaC"
      },
      {
        "id": "2rVtLCHWp1hpUc7YVoAkFrSL3iwGQDcCzAvboCvrq5kT",
        "ownershipId": "Fb7pzfL6SLDNi79WSWkZm6zKA94LYNsYKDJC7GNvkZaC"
      },
      {
        "id": "6QXfTMwuaKTMZyFYoUwNWpaT692Z9Zqg94WE9MTi4J2p",
        "ownershipId": "Fb7pzfL6SLDNi79WSWkZm6zKA94LYNsYKDJC7GNvkZaC"
      }
    ]
  }, ...
}

「ownershipId」はSolanaブロックチェーン上の所有者の公開鍵アドレスを表し、assetsはそのアドレスに紐づくNFTです。

完全なコード

コード
const fs = require('fs');

const url = `https://rpc.helius.xyz/?api-key=`;

const getAssetsByCreator = async () => {
  let page = 1;
  let allResults = [];
  let hasMoreResults = true;

  while (hasMoreResults) {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 'my-id',
        method: 'getAssetsByCreator',
        params: {
          creatorAddress: 'zvrsoq2LbNxekPqLK1v8DsLgeC4LHxMQL52beX8Ktn8',
          onlyVerified: true,
          page,
          limit: 1000,
        },
      }),
    });

    const { result } = await response.json();
    allResults = allResults.concat(result.items);

    if (result.items.length < 1000) {
      hasMoreResults = false;
    } else {
      page++;
    }
  }

  // Create an object to store the grouped results
  const groupedResults = {};

  for (let i = 0; i < allResults.length; i++) {
    const ownershipId = allResults[i].ownership.owner;
    const asset = {
      id: allResults[i].id,
      ownershipId: ownershipId,
    };

    if (groupedResults.hasOwnProperty(ownershipId)) {
      // Add the asset to the existing group
      groupedResults[ownershipId].assets.push(asset);
    } else {
      // Create a new group for the ownership ID
      groupedResults[ownershipId] = {
        assets: [asset],
      };
    }
  }

  // Convert the grouped results to JSON
  const json = JSON.stringify(groupedResults, null, 2);

  // Write the JSON to a file
  fs.writeFileSync('creatorResults.json', json);

  console.log('Results saved to results.json');
};

getAssetsByCreator();

<api-key>を実際のAPIキーに置き換えてください。

この例では、「getAssetsByCreator」エンドポイントに対してDAS APIへの非同期リクエストを送信しています。次に作成者のアドレスを渡し、その作成者が検証済みであることを指定します。最後にレスポンスを設定し、作成者アドレスに紐づく各アセットの所有者を解析して、外部のJSONファイルに出力します。

これは、Solanaブロックチェーン上のアセットの動きを追跡または分析したい場合、特に特定の作成者やプロジェクトに関連するアセットを調べる際に役立つツールです。

7. 権限別のアセット取得

関数getAssetsByAuthorityは、特定の更新権限に関連付けられたアセットを取得します。更新権限とは、コレクションを変更する権利を持つアドレスです。この機能は、コレクションIDが存在しない場合に一連のアセットを取得する必要があるとき、特に有用です。また、単一のコレクションIDの範囲を超えて、権限アドレスに関連付けられたより広範なアセット群を取得できる利点もあります。

例

次の例では、Taiyo Robotics、Pilots、Infantsの各コレクションNFTとその所有者を取得します。これらは共通の更新権限を持つため、プロジェクトに関連するすべての結果を返せます。

設定した権限CDgbhX61QFADQAeeYKP5BQ7nnzDyMkkR3NEhYF2ETn1kに紐づくアセットをグループ化できるため、これはgetAssetsByGroup関数を拡張した方法です。

  1. まず、新しく作成した「getAssetsByAuthority.js」というファイルに関数を設定します。
コード
const url = `https://rpc.helius.xyz/?api-key=`;

const getAssetsByAuthority = async () => {
  // Code goes here
};
getAssetsByAuthority();
  1. fetchリクエストを構成し、authorityAddress、page、limitなど、必要なパラメーターをリクエストボディに含めます。
コード
let page = 1;
let hasMoreResults = true;

while (hasMoreResults) {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 'my-id',
        method: 'getAssetsByAuthority',
        params: {
          authorityAddress: 'CDgbhX61QFADQAeeYKP5BQ7nnzDyMkkR3NEhYF2ETn1k',
          page,
          limit: 1000,
        },
      }),
    });

<authority-address>を、アセットを抽出したい権限の実際のアドレスに置き換えてください。

     3. 結果が1000件ある場合に繰り返し取得できるよう、返り値を構成します。

     結果が1000件未満の場合はフラグがfalseに設定され、ページネーションが終了し、hasMoreResultsはfalseを返します。

コード
const { result } = await response.json();
    totalResults.push(...result.items);

    if (result.items.length < 1000) {
      hasMoreResults = false;
    } else {
      page++;
    }
  }
  1. アセットを処理して必要な情報を取得します。ここでは、アセットのIDと所有者を表示します。また、アセット数と処理済みアセットの配列を格納するrootオブジェクトも作成します。
コード
// Create new array to hold the desired properties
 const modifiedResults = totalResults.map(item => ({
    id: item.id,
    owner: item.ownership.owner
  }));
  // Create a root object to hold the count and the results
  const root = {
    count: modifiedResults.length,
    results: modifiedResults
  };
  1. これで、返されたアイテムをauthorityResults.jsonというJSONに出力する準備が整いました。処理が完了したらログを出力します。
コード
const jsonResult = JSON.stringify(root, null, 2);

  fs.writeFile('./authorityResults.json', jsonResult, 'utf8', (err) => {
    if (err) {
      console.error("Error writing JSON file:", err);
    } else {
      console.log("JSON file saved successfully.");
    }
  });
console.log("Process completed.");
console.timeEnd("getAssetsByAuthority");

最後に、コマンドnode getAssetsByAuthority.jsを実行してスクリプトを起動し、取得した結果を「authorityResults.json」ファイルに書き込みます。

結果

getAssetsByAuthorityエンドポイントの出力には、アセット数とオブジェクトの配列が含まれます。各オブジェクトは1つのアセットを表し、そのIDと所有者を保持します。サンプルは次のとおりです。

コード
{
  "count": 37330,
  "results": [
    {
      "id": "HmwL6uy7gQcXv74Mi8xF5G9mymZNd9rnUCpUFwSx1DX1",
      "owner": "5gt59Q14FEhT8LuETjLyiNHRkqCQf5hQsPP58ucBy5oC"
    },
    {
      "id": "HmztH56n4GD3p3EdgMKdzj8x21LjFQk4Bcp2YAQD8Urx",
      "owner": "H3AkHZHfcqGCcJpBn3FJWe52LcLxFMJQoZvZ6XyApFWf"
    },
    {
      "id": "Hn1NZXCaAxcr1btsaDm3eZRtLNbamy9FyrcVGLbZ2k5w",
      "owner": "AEqhqiQZBBa3dPTY1GwGsZJJnf5vzRKJpdXv6xzakfx8"
    }, ...
}

完全なコード

コード
const fs = require('fs');
const url = `https://rpc.helius.xyz/?api-key=`;

const totalResults = [];

const getAssetsByAuthority = async () => {
  let page = 1;
  let hasMoreResults = true;

  while (hasMoreResults) {
    const response = await fetch(url, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        jsonrpc: '2.0',
        id: 'my-id',
        method: 'getAssetsByAuthority',
        params: {
          authorityAddress: 'CDgbhX61QFADQAeeYKP5BQ7nnzDyMkkR3NEhYF2ETn1k',
          page,
          limit: 1000,
        },
      }),
    });

    const { result } = await response.json();
    totalResults.push(...result.items);

    if (result.items.length < 1000) {
      hasMoreResults = false;
    } else {
      page++;
    }
  }  
  // Create new array to hold the desired properties
  const modifiedResults = totalResults.map(item => ({
    id: item.id,
    owner: item.ownership.owner
  }));
  
  // Create a root object to hold the count and the results
  const root = {
    count: modifiedResults.length,
    results: modifiedResults
  };
  
  // Stringify with 2 spaces indentation for readability
  const jsonResult = JSON.stringify(root, null, 2);

  fs.writeFile('./authorityResults.json', jsonResult, 'utf8', (err) => {
    if (err) {
      console.error("Error writing JSON file:", err);
    } else {
      console.log("JSON file saved successfully.");
    }
  });
console.log("Process completed.");
console.timeEnd("getAssetsByAuthority");
};
getAssetsByAuthority()

<api-key>を実際のAPIキーに置き換えてください。

この例では、「getAssetsByAuthority」メソッドを使い、3つのコレクションにわたって権限アドレスに紐づくすべてのアセットを返します。リクエストの追加パラメーターには、pageとlimitのみを使用します。その後、結果を解析して権限下のアセットを抽出し、外部のJSONファイルに保存します。

まとめ

これで、Digital Asset Standard(DAS)APIの使用方法と、各メソッドの実用的な活用例を包括的に理解できたはずです。特定のアセット情報を取得するために複数のエンドポイントが必要だった従来の方法から、大きく進化しています。

これらのメソッドは圧縮アセットと通常のアセットの両方に対応し、さまざまな方法でリクエストを行うための統一インターフェースを提供します。

詳細については、Digital Asset Standard(DAS)APIに関する充実したドキュメントをご覧ください。リクエストとレスポンスのパラメーターを詳しく確認できるOpen APIウィジェットも利用できます。

いつでもDiscordコミュニティにご参加ください。ご質問があれば、気軽にメンションしてください。

‍

Heliusを購読

Solana開発の最新情報や新しい記事の公開通知を受け取れます