LoginSignup
8
6

More than 1 year has passed since last update.

COMSAのバンドルNFT(NCFT)情報

Last updated at Posted at 2022-10-19

目的

COMSAにおけるバンドルNFT(NCFTと呼ぶのでなはく、バンドルNFTとする)のオンチェーン保存方法を既存のNFTとは変更しているので、既存のNFT(ユニークNFTと呼ぶ) と比較しながら説明してみる。

ユニークNFTとの比較表

項目 ユニークNFT(1.0) ユニークNFT(1.1) ※ バンドルNFT(NCFT)
トランザクション構成 * Mosaicトランザクション
* ファイルアグリゲートトランザクション
* 画像サムネイルファイルアグリゲートトランザクション
トランザクション内容
* Mosaicトランザクション
* ファイルアグリゲートトランザクション
* 画像サムネイルファイルアグリゲートトランザクション
* Mosaicトランザクション
* ファイルアグリゲートトランザクション
* 画像サムネイルファイルアグリゲートトランザクション
Mosaicのメタキー(nft情報) nft nft ncft
Mosaicのメタキー(version) comsa-nft-1.0 comsa-nft-1.1 comsa-ncft-1.1
Mosaic Transferble true true false
Mosaic amount 1 1 1-10000
ファイル保存分割形式 base64 binary binary

※ 2022/10/31以降に作成したユニークNFTは、ユニークNFT(1.1) となります。

非循環型のバンドルNFTとは?

既存のユニークNFTも、バンドルNFTどちらも1つのMosaicを使用してNFTを表現している。
Mosaicは作成時にTransferbleを無効にすると、必ずMosaic作成者が転送元もしくは転送先にいる必要があり、 不特定多数間でやり取りができない(非循環型)Mosaicを作成することができる。
そのためCOMSAプラットフォームのみでしか使用できない限定的なMosaicだが、シリアルナンバー付きの複数単位で作成できるMosaicとした。

image.png

シリアルナンバー付きのバンドルNFTって?

Mosaicのamountのtokenに対して、個々にシリアルナンバーつける機能がないが、上記に記載した通り、転送する処理はCOMSAプラットフォーム上のみのため、バンドルNFTを転送する際に、必ずメッセージにシリアルナンバーを付与することで、ブロックチェーン上でも確認することができる。バンドルNFTのMosaic転送トランザクションとメッセージを確認することで、シリアル毎に履歴を追跡することができる。

COMSA Explorer上では以下のように表示される。

image.png

以下では、簡単にトランザクション履歴を取得するやり方だが、そこにmessageも合わせれば分かりやすくなる。

const { 
  RepositoryFactoryHttp,
  MosaicId,
  Order,
  TransactionGroup} = require('symbol-sdk');

const url = "SymbolノードのURL"
const mosaic = "MosaicId";

const searchTransaction = async (pageNumber, transactionRepository, mosaicId) => {
  return await transactionRepository.search({
    transferMosaicId: mosaicId,
    pageSize: 100,
    pageNumber: pageNumber,
    order: Order.Desc,
    group: TransactionGroup.Confirmed
  }).toPromise()
}

(async() => {
  const repo = new RepositoryFactoryHttp(url);

  const transactionRepository = repo.createTransactionRepository();
  const mosaicId = new MosaicId(mosaic);

  const histories = [];

  let check = true;
  let count = 1;

  try {
    while (check) {
      const searchtx = await searchTransaction(count, transactionRepository, mosaicId);
      histories.push(searchtx);
      searchtx.isLastPage ? check = false : count++
    }

    histories.map((history) => {
      history.data.map((t) => {
        t.mosaics.map((mosaic) => {
          console.log('Block:', t.transactionInfo.height.compact(), 'Hash: ', t.transactionInfo.hash, 'from:', t.signer.address.plain(), 'To:', t.recipientAddress.plain(), t.message.payload)
        })
      });
    })
  } catch(e) {
    console.error(e)
  }

})()

ファイル保存形式の変更

ユニークNFTは、ファイルをbase64に分割して転送トランザクションのメッセージに格納していたが、バンドルNFTはファイルをバイナリで分割する方法に変更した。
リリース当初からバイナリ格納は検討していたが、symbol-sdkが正式バージョンでBufferからのRaw Message(message.toBuffer())に対応していなかったため保留していたが、ある程度の期間、テストを行ったため、 grushと同様なバージョン 1.0.3-message-improvement-202111021446 を使用した。

COMSA Explorer以外でも復元することはもちろんオンチェーンに保存している限りは誰でも可能なのでサンプルコードを以下に記載。
ただし、上記に記載した通り、現状では上記のバージョンのみで復元が可能

const {
  RepositoryFactoryHttp,
  TransactionGroup,
  MetadataType,
  KeyGenerator,
  MosaicId,
} = require('symbol-sdk');
const fileType = require('file-type');
const fs = require('fs').promises;
const lodash = require('lodash');

const url = "SymbolノードのURL"
const mosaic = "MosaicId";

// 保存先
const fileWritePath = 'outputs';


const getDataFromTransactions = async (hashList, transactionRepository) => {
  const transactions = await transactionRepository.getTransactionsById(hashList, TransactionGroup.Confirmed).toPromise()
  const transactionMap = new Map();

  transactions.forEach(transaction => {
    if (transaction.transactionInfo.hash != null) {
      transactionMap.set(transaction.transactionInfo.hash, transaction)
    }
  })

  const dataList = []

  hashList.forEach(hash => {
    const transaction = transactionMap.get(hash)
    dataList.push(transaction.innerTransactions.slice(1).map(itrans => {
      return itrans.message.toBuffer()
    }))
  });

  const flatDataList = lodash.flatMap(dataList);

  const totalLength = flatDataList.reduce((acc, value) => acc + value.length, 0);
  console.log(totalLength)
  const result = new Uint8Array(totalLength);
  let length = 0;
  for (const array of flatDataList) {
    result.set(array, length);
    length += array.length;
  }
  return result
}

const metaEntry = async (metadataRepository, id) => {
  const entries = await metadataRepository.search({
    targetId: new MosaicId(id),
    metadataType: MetadataType.Mosaic,
    pageSize: 1000,
  }).toPromise()
  const entryMap = new Map();

  entries.data.forEach((metadata) => {
    entryMap.set(metadata.metadataEntry.scopedMetadataKey.toHex(), metadata.metadataEntry.value)
  })
  return entryMap
}

(async () => {
  try {
    const repo = new RepositoryFactoryHttp(url);
    const transactionRepository = repo.createTransactionRepository();
    const metadataRepository = repo.createMetadataRepository();

    // Mosaicに紐づくメタデータを取得
    const meta = await metaEntry(metadataRepository, mosaicId);
    
    // メタデータのkey dataXからアグリゲートを取得
    let aggregateHash = []
    for (let i=1; ;i++) {
      const data = meta.get(KeyGenerator.generateUInt64Key(`data${i}`).toHex());
      if (data == null) break;
      aggregateHash.push(JSON.parse(data))
    }

    // トランザクションからバイナリを取得
    const binary = await getDataFromTransactions(aggregateHash.flat(), transactionRepository);

    // file Type取得
    const fileExtension = (await fileType.fromBuffer(binary)).ext

    // ファイル名
    const fileName = 'image.' + fileExtension;

    // ファイル作成
    const res = await fs.writeFile(fileWritePath + '/' + fileName, binary);

    console.log("### Save " + fileWritePath + '/' + fileName + " ### ")

  } catch (e) {
    console.error(e)
  }

})()

上記ではメタデータからバンドルNFTを復元したが、ユニークNFT同様にトランザクション履歴から追うことももちろん可能。
どのように復元するかは、ユニークNFTの内容とほぼ同一の考え方なのでここでは割愛する。

8
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
6