LINE Blockchainの契約からNFTの発行までの流れ

#はじめに

LINE Developers にBlockchainサービスが登場しました。
このサービスを利用するとLINEと連携させて、tokenの管理を行うことが可能となります。
ここにある基本的な操作は全て、このAPIガイドに沿って進めることで理解することが可能です。

(APIガイド)
https://docs-blockchain.line.biz/api-guide/

全体的な概要の理解としては、
https://docs-blockchain.line.biz/sample-services/Link-cinema
公式ドキュメントの、映画のチケットサンプルなどをみるとわかりやすいかもしれません。
今回は、シンプルなtokenの作成手順をメモとして残しておきます。

チェーン自体はgithubで公開されています。
https://github.com/line/lfb-sdk
Cosmos-SDKをもとに作られているようですが、この辺りについてはまた別記事に記載しようと思います。

LINK Chain概要などの記事
https://logmi.jp/tech/articles/320359


#BlockchainServiceの作成

##1.コンソールにログインする
https://developers.line.biz/console/

##2.プロバイダーを作成する
Create Providersを押下

##3.BlockchainServiceChannnelを作成する
Create a Blockchain Service channnelを押下
![スクリーンショット 2021-06-09 20.01.30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43766/dcd89e37-410d-32c2-400b-d8cb75515251.png)

##4.Create a channel
必要な情報を埋める
##5.Create a service
必要な情報を埋める
##6.Create a Wallet
必要な情報を埋める
##7.Create a Token (後で作成できるのでSkipしても良い)
##8.完了。BlockchainタブからLINE Blockchain Developersに遷移できる。
![スクリーンショット 2021-06-09 20.06.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43766/02e9539d-c364-347f-a21b-93f13199540c.png)


#tokenの構築手順

##1.test用のLINEIDを取得する

LineBlockchainのもっともメリットであるのが
wallet addressを知らなくても直接userにtokenを送信できるという点ですが、
LINEのuser_nameなどで送ることができるわけではなく、必要になってくるのが、useridです。
これはlineのバックグラウンドで使用されているuseridで表には出てこないため、

>GET https://api.line.me/v2/profile

を利用して、レスポインスフィールド内のuserIdを取得する必要があります。

https://developers.line.biz/ja/reference/line-login/#get-user-profile

>{
  "userId":"U4af4980629...",
  "displayName":"Brown",
  "pictureUrl":"https://profile.line-scdn.net/abcdefghijklmn",
  "statusMessage":"Hello, LINE!"
}

test環境ではLineのUserIdをDevelopersのtestUserとして登録しないと
サービスが利用できないため、test環境を作る場合には、合わせて、ログインページを用意して、
あらかじめユーザーに特定のページを踏ませるような仕組みが必要となります。


##2.サービスのウォレットを作成しておく
アセットを作る上で、最初に残高を用意する場所としてWalletを作っておきます。
サービス > Walletから作ることができます。
このウォレットアドレスは、あくまで管理用のアドレスで、ユーザーのBitmaxWalletなどと紐付けはできないものとなります。

##3.Cashew(testnet)用のWalletアドレスを作成する
2で作ったのはあくまでも管理用のWalletであるため、ユーザー側での挙動を再現するためにBitmaxWalletに連携するアドレスを生成します。
LineBlockchainはdaphne(mainnet)とCashew(testnet)が存在しており、注意点としては環境によってアドレスが異なる点です。
(Ethereumなどでは環境が違ってもアドレスは同じ)
testnetで試験をするためにはtest用のユーザーを作成する必要があります。


##3-1. Dashboard > Test usersからユーザーを作成します

name > 任意の名前
email > 任意のアドレス
company > 任意の会社名
UserId > LineのUserIDをセットする

作成するとUserIDに紐づけて、Wallet Addressが発行されます。


##3-2. Bitmax Walletでログインする

bitmax wallet > その他 > 設定 > Realnetモード > オフ

この操作で、Cashew(testnet)用になります。
Walletに戻ると2で作成したWallet Addressが表示されていることが確認できます。
この設定を切り替えることでMainet用のアドレスと、Testnet用のアドレスの切り替えをすることができます。


##4.ServiceToken と ItemTokenの作成

LINE Blockchainで作られるのは、

Service token と Item tokenで、それぞれにFT とNFTを作ることができます。
基本的に、一人が複数保有することが前提で残高で管理するようなものはServiceTokenで、そうでないカードのようなものはItemTokenで良いのかなという理解です。

ServiceTokenはLineDevelopersコンソール側から、mintやtransferが可能ですが、
ItemTokenの方は、APIからのみの制御になっているようですので、ここにapiからのcallの仕方をメモしておきます。
ちなみにsignatureの生成が非常に大変です。。生成方法についてはここに記載されています。
https://docs-blockchain.line.biz/api-guide/Authentication?id=generating-a-signature
https://docs-blockchain.line.biz/ja/api-guide/Authentication
https://github.com/ryukato/developers-sdk/blob/master/developers-sdk-js/src/lib/signature-generator.ts

>All keys should be sorted in the ascending lexicographical order and connected in the corresponding sequence.

などsignatureの生成時にキーをsortしないといけないのにハマりました。。


例えばmintする例
(事前に、webSDKの方で、item_token_id = "10000001"のtokenを作成してあることが前提です。)

```mint.js
function POST_v1_item_tokens_contractId_non_fungibles_tokenType_mint() {
  var item_token_id = "10000001";
  var item_token_name = "aaa";
  var item_token_meta = "aaa";
  var path = '/v1/item-tokens/'+contract_address+'/non-fungibles/'+item_token_id+'/mint'
  var headers = {
    'service-api-key': service_api_key,
    'nonce': nonce,
    'timestamp': timestamp,
    'Content-Type': 'application/json'
  }
  var request_body = {
    'ownerAddress': wallet_address,
    'ownerSecret': wallet_secret,
    'toAddress': to_wallet_address,
    'name': item_token_name,
    'meta': item_token_meta
  }
  signature = get_signature('POST', path, nonce, timestamp, service_api_secret, request_body);
  headers['signature'] = signature;
  var options = {
    url: server_url + path,
    method: 'POST',
    headers: headers,
    body: JSON.stringify(request_body)
  }
  request(options, function (error, response, body) {
    console.log(response);
    console.log(body);
    console.log(error);
  })
}
```

mintしたtokenをuseridから引くなども簡単にできる

```getTokens.js
function GET_v1_users_userId_item_tokens_contractId_non_fungibles() {
  var path = '/v1/users/'+_userId+'/item-tokens/188f653f/non-fungibles';
  var headers = {
    'service-api-key': service_api_key,
    'nonce': nonce,
    'timestamp': timestamp
  }
  signature = get_signature('GET', path, nonce, timestamp, service_api_secret, '');
  headers['signature'] = signature;
  var options = {
    url: server_url + path,
    method: 'GET',
    headers: headers,
  }
  request(options, function (error, response, body) {
    console.log(response);
    console.log(body);
    console.log(error);
  })
}
```


##5.トークン画像の設定

ServiceTokenの場合は、画像イメージはWebConsoleからアップロードできますが、
ItemTookenの場合、自前で画像サーバーを用意して、ドメインを設定が必要。
イメージの設定方法がよくわからなかったのですが、こちらをみて解決しました。

https://docs-blockchain.line.biz/how-to/provide-item-token-images

ちなみにキャッシュをクリアする仕様がないので、24h以上待つケースもあるとのこと。

テスト環境の場合は下記のような感じでアクセスすると画像proxyがどうなっているのか確認できます
https://lbw-impro.line-apps.com/v1/cashew/token/コントラクトID/10000001

#6.サービス利用での一連の流れ

LineBlockchainには様々なAPIが用意されていますが、
サービスで利用するために必要なAPIはそこまで多くないのではないかと思います。
基本的な流れとしたはカードをmintし、そのmintされたカードを各アカウントごとにgetListするような流れを作るケースが多いのではないかと思います。

1.mint -> 2.getList

下記に最低限、この2本と、mintされたトランザクションが成功したかどうか確認するAPIを含め、
合計3本のAPIのcallを参考に載せています。npmの動作する環境に放り込めば、すぐに動作させることができます。

https://gist.github.com/oggata/a702179c58469532f6861c8284fc0a38


APIには他にも様々なものがあり、例えば、ユーザー間のトレードであるとか、作成したカードのburnなどもありますが、WebConsoleや、BitmaxWallet側での操作で可能なものもあるため、サービスの初期の段階では必要なAPIとしては限られてくるかもしれません。


#7.様々な言語で利用する

Ethereumなど、多くのブロックチェーンでは、NodeJSのモジュールの組み合わせとして作るケースが多いと思いますが、他の言語からのユースケースなども増えています。

Kotlin/Python/PHP
https://github.com/ryukato/developers-sdk
Go
https://github.com/doublejumptokyo/go-lbd

上記でも記載しましたが、signatureの生成を自前で用意するのが面倒なので、例えば、go-lbdであれば、
NewLBDでkeyとsecret渡すだけで利用できるのは非常に便利。

```go-lbd.demo.go
package main
import "fmt"
import lbd "github.com/doublejumptokyo/go-lbd"

func main() {
	var w = lbd.NewWallet(WALLET_ADDRESS,WALLET_SECRET);
	var l,_ = lbd.NewLBD(API_KEY,API_SECRET,w);
	var a,_ = l.ListAllNonFungibles(CONTRACT_ADDRESS);
	var b,_ = l.RetrieveBalanceOfAllNonFungiblesUserWallet(USER_001_ID,CONTRACT_ADDRESS);
	fmt.Printf("%v", a);
	fmt.Printf("%v", b);
}
```


#8. ウォレットの管理権限を委任するためのセッショントークンを発行する

ユーザーのウォレットの中のtokenを、APIを用いて、送受信したい場合は、まず、ユーザーのウォレットの権限を委任させる必要があります。
OpenSeaなどもTokenTransferProxyを踏ませることで、Tokenのオーナーが保有している権限をマーケットに委譲しますが、それと同じような操作となります。

https://recruit.gmo.jp/engineer/jisedai/blog/opensea-research/


流れとしては、

1.APIでセッショントークンを作成

```js
function POST_v1_users_userId_item_tokens_contractId_request_proxy() {
		path = '/v1/users/' + user_id + '/item-tokens/' + contract_address + '/request-proxy';

		var query_params = {
	        'requestType': 'aoa'
	    }
		var headers = {
				'service-api-key': service_api_key,
				'nonce': nonce,
				'timestamp': timestamp,
				'Content-Type': 'application/json'
		}
		var request_body = {
        	'ownerAddress': owner_wallet_address,
        	'landingUri': 'https://my.service.landing/home'
	    }
		var signature = get_signature('POST', path, nonce, timestamp, service_api_secret, query_params, request_body);
		headers['signature'] = signature;
		var rbody = JSON.stringify(request_body);
		axios.post(server_url + path, request_body, {headers : headers, params : query_params}  ).then(response => {
		    console.log('status:', response.status);
		    console.log('body:', response.data);
		});
}
```

2.ユーザーのaoa(LINEのタイムライン)で、確認ボタンを押下
![Image from iOS (16).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43766/9aba4ebe-9f28-8524-2b30-66120f90e0c0.png)
3.APIで、セッションをコミット (tokenは1のapiをcall後取得できる)

```js
function POST_v1_user_requests_requestSessionToken_commit() {
		var token = "...";
		path = '/v1/user-requests/' + token + '/commit'
		var headers = {
				'service-api-key': service_api_key,
				'nonce': nonce,
				'timestamp': timestamp,
				'Content-Type': 'application/json'
		}
		var request_body = {}
		var signature = get_signature('POST', path, nonce, timestamp, service_api_secret, "", request_body);
		headers['signature'] = signature;
		var rbody = JSON.stringify(request_body);
		axios.post(server_url + path, request_body, {headers : headers}  ).then(response => {
		    console.log('status:', response.status);
		    console.log('body:', response.data);
		});
}
```

という流れになります。


#9. Composable Tokenを使う

ComposableTokenはERC998によって作られた規格で、親子関係を作ることができます。
ERC998に、ERC721または、ERC20をぶら下げることができます。
さらに、規格としては2種類トップダウンと、ボトムアップが仕様として提案されています。
トップダウン　: 通常のトークンをComposable なNFT / FTに所有させたときに使う
ボトムアップ　: 何かしらのComposableなトークンを NFT / FTにtransferしたいときに使う
このようなComposableの仕組みもLINEのAPIには用意されていますが、ユーザーのウォレット上で、
Composableのアタッチやデタッチを行いたい場合は、7の手順で示したプロキシの発行とユーザによる承認のフローが必要となるため注意が必要です。


1.APIでアタッチする(この例では、2を親として、1をぶら下げる

```js
function POST_v1_item_tokens_contractId_non_fungibles_tokenType_tokenIndex_parent() {
		var path = '/v1/item-tokens/' + contract_address + '/non-fungibles/10000001/00000001/parent'
		var headers = {
				'service-api-key': service_api_key,
				'nonce': nonce,
				'timestamp': timestamp,
				'Content-Type': 'application/json'
		}
		var request_body = {
	        'serviceWalletAddress': owner_wallet_address,
	        'serviceWalletSecret': owner_wallet_secret,
	        'parentTokenId': '1000000100000002',
	        'tokenHolderAddress': TEST_USER_ADDRESS
	    }
		var signature = get_signature('POST', path, nonce, timestamp, service_api_secret, "", request_body);
		headers['signature'] = signature;
		var rbody = JSON.stringify(request_body);
		axios.post(server_url + path, request_body, {headers : headers}  ).then(response => {
		    console.log('status:', response.status);
		    console.log('body:', response.data);
		});
}
```

LINEのウォレット表示自体は下記のようになります。

![Image from iOS (17).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43766/24339dc3-fc78-aec8-82e8-a21dd132e1b5.png)

![Image from iOS (15).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43766/5f0f7a2f-38e1-5aa7-2f68-70193a77ee33.png)


```json
body: {
  responseTime: 1633693439721,
  statusCode: 1000,
  statusMessage: 'Success',
  responseData: [
    {
      name: 'aaa',
      tokenId: '100000010000000b',
      meta: 'aaa',
      createdAt: 1606722080556,
      burnedAt: null
    },
    {
      name: 'aaa',
      tokenId: '1000000100000008',
      meta: 'aaa',
      createdAt: 1606721721412,
      burnedAt: null
    }
  ]
}
```






#conclusion

LineBlockchainDevelopersを利用することで誰でも簡単にtokenの発行を行うことができました。