DataSpiderSDKによるIPFSサービスの利用

DataSpiderとブロックチェーン　その１０

前回は、DataSpiderSDKによるPDFアダプタの実装例について紹介しました。
今回から、PDFデータをNFTとして扱うために、DataSpiderSDKを使用してNFTアダプタを作成していきますが、手順が多い為、今回はIPFSサービス（Pinataを利用します）の利用と通信するための実装までを紹介したいと思います。

## やること
シナリオの❸と❹ (※[以前の記事で紹介しましたシナリオ](https://qiita.com/dataspider-fan/items/56580cdf3452f4322580)を掲載) で必要となるNFTアダプタのIPFS通信部分を実装していきます。
![nft全体像_pinata.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/7c36e014-fcae-80b2-3ce2-96509a72c907.png)

## PDFデータをNFTとして扱うためには
PDFデータ（PDFに限らずNFTとして扱う電子データ全般）をNFTとして扱うためには、以下の対応が必要です。
+ TRC-721に準拠したスマートコントラクトを使用する。
+ TRC-721に準拠した情報をブロックチェーン上にのせる。

### TRC-721に準拠したスマートコントラクトの使用
非代替性トークンとして保持する情報が定義(ERC-721)されていますので、基本的になその情報を最低保持するようにします。
また、[以前の記事で紹介しましたが、すでにTRC-20ベース（暗号通貨など代替性のあるトークン）のスマートコントラクトをデプロイ](https://qiita.com/dataspider-fan/items/b8c7edf274c7099eb872#%E3%82%B9%E3%83%9E%E3%83%BC%E3%83%88%E3%82%B3%E3%83%B3%E3%83%88%E3%83%A9%E3%82%AF%E3%83%88%E3%81%AE%E3%83%96%E3%83%A9%E3%83%83%E3%82%B7%E3%83%A5%E3%82%A2%E3%83%83%E3%83%97)しました。
暗号通貨など代替性のあるトークンの場合、TRC-20のスマートコントラクトでは、対象のアドレスがどれだけの量のトークンを所有しているかという管理方法でした。
今回は非代替性トークンを扱うということで、対象のアドレスがどのトークンを保持しているか、という管理方法になります。
よって、管理する対象が異なるため暗号通貨とは別のスマートコントラクトを使用します。
TRONネットワークで説明を進めてきましたので、非代替のトークンを扱うためのTRC-721に準拠したスマートコントラクト使用します。
TRC-721については、次回のNFT化するためにトランザクションデータをTRONネットワークにブロードキャストをしますので、その際にまた紹介したいと思います。

今回は、そのブロックチェーン上に載せるための情報として、IPFSからのハッシュ値を扱います。
得られたハッシュ値をJSONデータに埋め込みIPFSサービスに登録しますので、この部分に焦点を当てたいと思います。

:::note info
TRONは、ethereumから発展させたチェーンになりますが、ethereumが定義するEIPと互換性があります。
TRC-721もethereumにて提唱されているERC-721と互換があり、TRC-721はTRONとして標準化された規格名になります。
詳細については、ERC-721のEIP-721を参照ください。
EIP-721：https://eips.ethereum.org/EIPS/eip-721
:::

### TRC-721に準拠した情報の生成
規格からみてみると、EIP-721においては、以下のJSON形式が定義されています。
```json
{
    "title": "Asset Metadata",
    "type": "object",
    "properties": {
        "name": {
            "type": "string",
            "description": "Identifies the asset to which this NFT represents"
        },
        "description": {
            "type": "string",
            "description": "Describes the asset to which this NFT represents"
        },
        "image": {
            "type": "string",
            "description": "A URI pointing to a resource with mime type image/* representing the asset to which this NFT represents. Consider making any images at a width between 320 and 1080 pixels and aspect ratio between 1.91:1 and 4:5 inclusive."
        }
    }
}
```
今回の検証では、Pinataのサービスを使用しますので、Pinataで定義されている情報を付加して、IPFSサービスにアップします。
また、JSONに載せる情報の補足事項として、他のサービスではNFTを管理するうえで必要な情報を加え運用がされています。
対象のNFTデータを管理するための情報は、properties内に記載されていることが多い為、提供サービスにおいて管理するための情報は加えても規格上な問題が無いように思われます。

#### 今回出力するJSONデータ詳細
Pinata向けの情報を加えて、以下の情報を出力する実装していきます。
```json
{
	"pinataMetadata": {
		"name": "修了書_user_publick_address.pdf.json"
	},
	"pinataOptions": {
		"cidVersion": 0
	},
	"pinataContent": {
		"name": "講習会修了証書",
		"description": "DataSpider-Fan test collections."
		"image": "https://gateway.pinata.cloud/ipfs/QmSffjewiFJIEOFJjfdakk67W4TCSLTJ4HjsbQdDV3DrmCWrBy",
	}
}
```

## 開発環境
前回から同様のWindows10上で行います。
DataSpiderもWindows版を用意ください。

## 開発準備
PINATA(IPFS/PIN）サービスの利用を利用するため、Pinataアカウントを用意します。
また、実装を行うにあたり、http通信およびJsonファイルを処理します。
必要なライブラリをダウンロードします。

### PINATA(IPFS/PIN）サービスの利用
まず最初にPINATAサービスはどのようなものか紹介します。
Pinataは、あらゆる種類のファイルをホスト、管理、共有することができるNFTメディア管理サービスです。IPFSピン留め（PIN）サービスも提供されており、エンジニアやコンテンツを配信したいクエリエータ向けに、コンテンツを制限なく共有するための迅速、簡単、かつ信頼性の高い機能と環境を提供してくれています。
PINサービスにおいては、IPFSノードに当該データをPINすると、ノードにデータが重要であり、保存する必要があることを伝えることなります。ノードとは、IPFSに接続し、ファイルを保存するためのサービスです。ピン留めは、重要なデータがノードから削除されるのを防ぎます。データを配置した管理者がデータを制御して固定化できます。
引用元：https://docs.pinata.cloud/what-can-i-learn-here/what-is-pinata

#### PINATAアカウントの発行
Pinataサイトでユーザ登録を行い、APIキーを発行します。
アカウント登録後、DevelopersメニューからAPIキーを生成します。
その際の秘密キーも提示されるため、別途メモをします。
APIキーおよび秘密キーは、プログラム内で使用します。

https://www.pinata.cloud/

1\. PINATAページを開きます。
2\. SignUpボタンを押します。
![pinata_1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/bda27cda-59f3-6550-ac96-00c97403e604.png)
3\. Emailを入力しとBUilderボタンを選択します。選択後Nextボタンを押します。
![pinata_2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/cb66255b-65c3-4edb-08cc-8b17ccb58116.png)
4\. 名前とパスワードを入力し、指示にしたがい会員登録を完了します。
![pinata_3.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/c279eb1c-91b8-c191-342e-8bdc82e82475.png)
会員登録は以上です。

つづいて、API通信に必要なAPIキーを発行します。
API通信に必要なAPIキーをPinata開発者用画面から生成します。
1\. PINATAにログインします。
2\. Developersメニュ（紫色部分）をクリックし、+ NewKeyボタンをクリックします。
![pinata_4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/44c45195-c050-fc3d-7ec8-43219bcf5f96.png)
3\. キーの制限を選択します。今回は制限無しのAdminを選択しました。
![pinata_5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/028118ae-4c50-19ce-a9ad-e05564d6842f.png)
4\. APIとAPI秘密キーが表示されますので、保管します。
![pinata_6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2849535/a7729a16-7b64-f621-b7da-aa5a22a3c959.png)

APIキー発行は以上です。

:::note warn
API秘密キーは非常に重要です。
Pinata API KeyはREST APIの公開鍵として、Pinata Secret API Key はREST APIのパスワードとして使用されます。API秘密キー（Pinata Secret API Key）は漏洩しないように大切に保管しましょう。
:::

Pinataサービス利用の準備は以上です。
続いて、実装に必要となるJavaライブラリを揃えます。
### ライブラリの準備
http通信には、http5を使用します。今回の検証では、http5 ver5.1.5を使用しました。
またJsonファイルを扱いますので、以下の必要なライブラリを揃えます。

https://github.com/apache/httpcomponents-client

https://github.com/apache/httpcomponents-core

https://github.com/stleary/JSON-java

## NFT発行までの処理概要
```plantuml
participant "NFT adapter" as adapter
participant "IPFS/PIN service" as ipfs
participant "TRON service" as tron
database    IPFS_Strage    as ipfsdb
database    TRON_Strage    as trondb

[-> adapter : PDF file path
activate adapter
adapter -> ipfs : POST (PDF file)
activate ipfs
ipfs -> ipfsdb : PDF file
ipfs -> adapter : RESPONSE (PDF hash)
adapter -> adapter : CREATE JSON file (PDF hash)
adapter -> ipfs : POST (JSON file)
ipfs -> ipfsdb : JSON file
ipfs -> adapter : RESPONSE (JSON file hash)
deactivate ipfs
adapter -> tron : POST (URI for JSON hash) ※ ここは次回紹介予定
activate tron
tron -> trondb : blockdata
tron -> adapter : RESPONSE (transaction) ※ ここは次回紹介予定
deactivate tron
[<- adapter : transaction etc...

```

## アダプタの実装
今回は、❸および❹部分を実装します。
NFTアダプタ内で、❸PDF転送、❹JSONファイル生成/転送、❺スマートコントラクト呼び出しを行います。
❸と❹は、DataSpiderが標準で提供しているREST（POSTもしくはPUT処理）アダプタで実現はできますが、処理の流れから機能は分離させず、全てNFTアダプタに内包するかたちで実装します。

## IPFS通信部分
前提として、IPFSサービスに送信するPDFが存在しており、PinataサービスのAPIキーを所有していることです。
IPFSの通信部分のみを抜粋して紹介します。

### Pinataサービス利用のための実装内容
```java

/********************
 * ImageをUpload
 ********************/
/*
* Pinataサービスで指定されているURLを指定します。
*/
String urlFile = "https://api.pinata.cloud/pinning/pinFileToIPFS"; // PDFアップロード用URL
String urlJson = "https://api.pinata.cloud/pinning/pinJSONToIPFS"; // JSONファイルアップロード用URL
/*
* 管理ページから生成されたAPIキーをそれぞれセットします。
*/
String PINATA_KEY="your API key";
String PINATA_SECRET_KEY="your API private key";

String  myFile = "upfile.pdf"; // 前処理でそろえたPDFファイルを指定します。

CloseableHttpClient httpClient = HttpClients.createDefault();

final HttpPost httpPost = new HttpPost(urlFile);

httpPost.addHeader("pinata_api_key", PINATA_KEY);
httpPost.addHeader("pinata_secret_api_key", PINATA_SECRET_KEY);

File file = new File(myFile);

HttpEntity entity = MultipartEntityBuilder.create().addPart("file", new FileBody(file)).build();
httpPost.setEntity(entity);

String jsonText= null;
try (final CloseableHttpResponse response = httpClient.execute(httpPost)) {
    entity = response.getEntity();
    if (entity != null) {
        try {
            jsonText = EntityUtils.toString(entity, StandardCharsets.UTF_8);
        } catch (ParseException | IOException e) {
            e.printStackTrace();
        }
        System.out.println("jsonText : " + jsonText);
    }

}

// jsonデータを分解するための実装。より良い方法がありそうですが参考まで。
JSONArray jsonArr = new JSONArray("[" + jsonText + "]"); 
JSONObject jsonObj = jsonArr.getJSONObject(0);
String ipfsHash = jsonObj.getString("IpfsHash");

/********************
 * Jsonファイルをアップロード
 ********************/
final HttpPost httpJsonPost = new HttpPost(urlJson);
httpJsonPost.addHeader("Content-Type", "application/json; charset=UTF-8");
httpJsonPost.addHeader("pinata_api_key", PINATA_KEY);
httpJsonPost.addHeader("pinata_secret_api_key", PINATA_SECRET_KEY);

/*
* Pinataで定義されている情報をセット
*/
JSONObject pinataOptions = new JSONObject();
pinataOptions.put("cidVersion", 0);
JSONObject pinataMetadata = new JSONObject();
pinataMetadata.put("name", myFile + ".json");
JSONObject pinataContent = new JSONObject();
// ここは、EIP-721に準拠する形で実装
pinataContent.put("name", "〇〇講習会修了証書");
pinataContent.put("description", "DataSpider-Fan test collections.");
pinataContent.put("image", "https://gateway.pinata.cloud/ipfs/" + ipfsHash);

JSONObject uploadData = new JSONObject();
uploadData.put("pinataOptions", pinataOptions);
uploadData.put("pinataMetadata", pinataMetadata);
uploadData.put("pinataContent", pinataContent);

System.out.println("uploadData : " + uploadData.toString());

// JSON形式に変換
httpJsonPost.setEntity(new StringEntity(uploadData.toString(), ContentType.APPLICATION_JSON));

jsonText= null;
try (final CloseableHttpResponse response = httpClient.execute(httpJsonPost)) {
    entity = response.getEntity();
    if (entity != null) {
        try {
            jsonText = EntityUtils.toString(entity, StandardCharsets.UTF_8);
        } catch (ParseException | IOException e) {
            e.printStackTrace();
        }
        System.out.println("jsonText : " + jsonText);
    }

}

jsonArr = new JSONArray("[" + jsonText + "]");
jsonObj = jsonArr.getJSONObject(0);
ipfsHash = jsonObj.getString("IpfsHash"); // 後続のTRONブロードキャストするときに埋め込む文字列のため取得

/*
 * TRON NFTスマートコントラクトへのブロードキャスト処理
 * 次回実装予定
 */

```


## 次回は
[TRC-721スマートコントラクトを利用するための準備と、NFTの登録](https://qiita.com/dataspider-fan/items/176d85cc4e3dd3c381a5)について紹介します。