Open2
LINE BlockChainサービスの作り方
LINEのBlockchainの資料が全て日本語でかつスマートコントラクトをGUIから作れたりして手軽そうなのでまずはこれを触ってみることにした。ドキュメントで用語の解説とかもしてくれるから初心者でも雰囲気を掴めると思う。
LINE BlockChainサービスの作り方
リソース
- LINE Blockchain
- LINE Blockchain Docs
- LINE Blockchain Developers
-
line/blockchain-sample-link-cinema: A sample service of LINE Blockchain, demonstrating how to utilize LINE Blockchain Developers and BITMAX Wallet.
- TutorialのLinkCinemaのバックエンドの実装。Goで書かれてる。
-
line/blockchain-sample-mage-duel: A sample service implementing a card game using the blockchain, which can serve as a guide on tokenizing game money and items.
- LINE BlockChainを利用したゲームの利用例の実装。参考実装なので対戦部分は無い。
- このページの動画とトークンの仕様や利用方法を読むと応用例が理解しやすい
チュートリアル手順
- まずLinkCinemaのチュートリアルをざっとやってみる
準備
- LINE Businessアカウントでプロバイダーを作成する
- チャンネルを作成する
- 審査
- 2日くらいで通過する
- BlockChainを選ぶ
- 審査
- チャンネルにBlockChainサービスを作成する
- API KeyとSecretが手に入る。一度しか表示されないのでメモする。
- サービス用のWalletを作成する
- Secretが手に入る。一度しか表示されないのでメモする。
- ServiceToken(=サービス内通貨)を作成する
- ContractIdが作られる
- BTC,ETH,XRP,NEMとかみたいなやつのサービス内版
- ItemToken(=クーポン券とか)を作成する
- ContractIdが作られる
- TokenId
- ItemTokenを発行する時に作成される一意な識別子
- ContractId+TokenType+TokenIndexを組み合わせて作成される
- (任意)transactionを取得してみる
- このPython3のコードを
tutorial_request.py
として保存して使うと良い -
pip3 install requests
はしておく -
SERVICE_API_KEY
とかSERVICE_API_SECRET
は補完してくれ
- このPython3のコードを
実際のコード
# walletのトランザクション一覧取得
# SERVER_URL=https://test-api.blockchain.line.me SERVICE_API_KEY= SERVICE_API_SECRET= WALLET_ADDRESS= python3 tutorial_request.py
# 特定のトランザクション取得
# SERVER_URL=https://test-api.blockchain.line.me SERVICE_API_KEY= SERVICE_API_SECRET= TRANSACTION_HASH= python3 tutorial_request.py
# NFTの鋳造
# SERVER_URL=https://test-api.blockchain.line.me SERVICE_API_KEY= SERVICE_API_SECRET= OWNER_ADDRESS= OWNER_SECRET= ITEM_CONTRACT_ID= python3 tutorial_request.py
# NFTをユーザーウォレットへ送信
# SERVER_URL=https://test-api.blockchain.line.me SERVICE_API_KEY= SERVICE_API_SECRET= OWNER_ADDRESS= OWNER_SECRET= ITEM_CONTRACT_ID= RECEPIENT_ADDRESS= python3 tutorial_request.py
import hmac
import hashlib
import base64
import os
import requests
import random
import string
import time
import json
class RequestBodyFlattener:
def __flatten_key_value(self, key, value):
if (isinstance(value, str)):
return f"{key}={value}"
if (isinstance(value, list)):
l_key_value = {}
for index, ele in enumerate(value):
for lkey in list(ele.keys() | l_key_value.keys()):
if lkey in ele.keys():
lvalue = ele[lkey]
else:
lvalue = ""
if (lkey in l_key_value.keys()):
l_key_value[lkey] = f"{l_key_value[lkey]},{lvalue}"
else:
l_key_value[lkey] = f"{',' * index}{lvalue}"
return "&".join("%s=%s" % (f"{key}.{lkey}", lvalue) for (lkey, lvalue) in sorted(l_key_value.items()))
def flatten(self, body: dict = {}):
sorted_body = sorted(body.items())
return "&".join(self.__flatten_key_value(key, value) for (key, value) in sorted_body)
class SignatureGenerator:
def __createSignTarget(self, method, path, timestamp, nonce, parameters: dict = {}):
signTarget = f'{nonce}{str(timestamp)}{method}{path}'
if(len(parameters) > 0):
signTarget = signTarget + "?"
return signTarget
def generate(self, secret: str, method: str, path: str, timestamp: int, nonce: str, query_params: dict = {}, body: dict = {}):
body_flattener = RequestBodyFlattener()
all_parameters = {}
all_parameters.update(query_params)
all_parameters.update(body)
signTarget = self.__createSignTarget(method.upper(), path, timestamp, nonce, all_parameters)
if (len(query_params) > 0):
signTarget += '&'.join('%s=%s' % (key, value) for (key, value) in query_params.items())
if (len(body) > 0):
if (len(query_params) > 0):
signTarget += "&" + body_flattener.flatten(body)
else:
signTarget += body_flattener.flatten(body)
raw_hmac = hmac.new(bytes(secret, 'utf-8'), bytes(signTarget, 'utf-8'), hashlib.sha512)
result = base64.b64encode(raw_hmac.digest()).decode('utf-8')
return result
class LinkMovie:
# walletのtransactionを10件取得する
def GET_v1_wallets_walletAddress_transactions(self):
server_url = os.environ['SERVER_URL']
service_api_key = os.environ['SERVICE_API_KEY']
service_api_secret = os.environ['SERVICE_API_SECRET']
wallet_address = os.environ['WALLET_ADDRESS']
nonce = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(8))
timestamp = int(round(time.time() * 1000))
path = '/v1/wallets/' + str(wallet_address) + '/transactions'
query_params = {
'limit': 10,
'orderBy': 'desc',
'page': 1
}
headers = {
'service-api-key': service_api_key,
'nonce': nonce,
'timestamp': str(timestamp)
}
signatureGenerator = SignatureGenerator()
signature = signatureGenerator.generate(service_api_secret, 'GET', path, timestamp, nonce, query_params)
headers['signature'] = signature
res = requests.get(server_url + path, params=query_params, headers=headers)
data = res.json()
return json.dumps(data, indent=2)
# 特定のtransactionを取得する
def GET_v1_wallets_specific_transactions(self):
server_url = os.environ['SERVER_URL']
service_api_key = os.environ['SERVICE_API_KEY']
service_api_secret = os.environ['SERVICE_API_SECRET']
transaction_hash = os.environ['TRANSACTION_HASH']
nonce = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(8))
timestamp = int(round(time.time() * 1000))
path = '/v1/transactions/' + str(transaction_hash)
query_params = {}
headers = {
'service-api-key': service_api_key,
'nonce': nonce,
'timestamp': str(timestamp)
}
signatureGenerator = SignatureGenerator()
signature = signatureGenerator.generate(service_api_secret, 'GET', path, timestamp, nonce, query_params)
headers['signature'] = signature
res = requests.get(server_url + path, params=query_params, headers=headers)
data = res.json()
return json.dumps(data, indent=2)
# 鋳造する
def POST_v1_item_tokens_contractId_non_fungibles_tokenType_mint(self):
owner_address = os.environ['OWNER_ADDRESS']
owner_secret = os.environ['OWNER_SECRET']
item_contract_id = os.environ['ITEM_CONTRACT_ID']
server_url = os.environ['SERVER_URL']
server_url = os.environ['SERVER_URL']
service_api_key = os.environ['SERVICE_API_KEY']
service_api_secret = os.environ['SERVICE_API_SECRET']
nonce = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(8))
timestamp = int(round(time.time() * 1000))
path = '/v1/item-tokens/' + str(item_contract_id) + '/non-fungibles/10000001/mint'
# 自分自身のwalletに送る
request_body = {
'ownerAddress': owner_address,
'ownerSecret': owner_secret,
'toAddress': owner_address,
'name': 'MovieTicket',
'meta': str(round(time.time() * 10000000))
}
headers = {
'service-api-key': service_api_key,
'nonce': nonce,
'timestamp': str(timestamp),
'Content-Type': 'application/json'
}
signatureGenerator = SignatureGenerator()
signature = signatureGenerator.generate(service_api_secret, 'POST', path, timestamp, nonce, body=request_body)
headers['signature'] = signature
res = requests.post(server_url + path, headers=headers, json=request_body)
data = res.json()
return json.dumps(data, indent=2)
# OwnerWalletからUserWalletにアイテムトークンを転送
def POST_v1_wallets_walletAddress_item_tokens_contractId_non_fungibles_tokenType_tokenIndex_transfer(self):
token_id = '00000001'
owner_address = os.environ['OWNER_ADDRESS']
owner_secret = os.environ['OWNER_SECRET']
item_contract_id = os.environ['ITEM_CONTRACT_ID']
recepient_address = os.environ['RECEPIENT_ADDRESS']
server_url = os.environ['SERVER_URL']
service_api_key = os.environ['SERVICE_API_KEY']
service_api_secret = os.environ['SERVICE_API_SECRET']
nonce = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(8))
timestamp = int(round(time.time() * 1000))
path = '/v1/wallets/' + str(owner_address) + '/item-tokens/' + str(item_contract_id) + \
'/non-fungibles/10000001/' + str(token_id) + '/transfer'
request_body = {
'walletSecret': owner_secret,
'toAddress': recepient_address
}
headers = {
'service-api-key': service_api_key,
'nonce': nonce,
'timestamp': str(timestamp),
'Content-Type': 'application/json'
}
signatureGenerator = SignatureGenerator()
signature = signatureGenerator.generate(service_api_secret, 'POST', path, timestamp, nonce, body=request_body)
headers['signature'] = signature
res = requests.post(server_url + path, headers=headers, json=request_body)
data = res.json()
return json.dumps(data, indent=2)
linkMovie = LinkMovie()
# 雑だけど
if os.environ.get('RECEPIENT_ADDRESS') != None:
print(linkMovie.POST_v1_wallets_walletAddress_item_tokens_contractId_non_fungibles_tokenType_tokenIndex_transfer())
elif os.environ.get('OWNER_ADDRESS') != None:
print(linkMovie.POST_v1_item_tokens_contractId_non_fungibles_tokenType_mint())
elif os.environ.get('TRANSACTION_HASH') != None:
print(linkMovie.GET_v1_wallets_specific_transactions())
else:
print(linkMovie.GET_v1_wallets_walletAddress_transactions())
NFTの作成
- ItemTokenから作成する
- 画像の扱い方
- 設定しないとデフォルト画像が使われる
- 鋳造
- APIからのみ可能。POST。
Test Coinの作成
- NFTの購入などにはCachewというTestNetのTest Coinを使う
- GUIからのみ作成できる
LINE BITMAX Walletに登録する
- LINE BITMAX WalletはLINE IDを基盤としたサービスユーザーのためのブロックチェーンウォレット
- Test CoinやアイテムトークンなどLINE BlockChainのすべてのトークンを扱う
- テストユーザーに100人まで登録できる。
- テストユーザーに発行したTest Coinを送信可能
アイテムトークンの購入
- 送金
- ユーザーWallet -> サービスWallet
- アイテムの転送
- サービスWallet -> ユーザーWallet
サービストークンの付与
- アイテムトークンの転送同様、API or GUI(assets -> details -> Sendページ)から特定のアドレスを指定して送信する
FT
- 上記NFTの場合と基本的に操作でできる。性質や利用シーンが異なるだけ。
Composableトークン
-
親子関係を結んだトークンをいいます。親と子は1対多で紐付き、このように紐付けられたトークンはまるで1つのトークンのように取り扱うことができます。代表例が、キャラクターに装備を装着することです。キャラクターといろんな装備がそれぞれのトークンであれば、キャラクターは親、各装備は子になります。 Composableトークンで親子関係になるのようにすることを結合(attach)といい、関係を断つことを分離(detach)といいます。
- アイテムトークンを2つ(A,B)用意する。親になるアイテムトークンAに、子になるアイテムトークンBをAPIで結合する。
- 転送は親のアイテムトークンを送るだけで良い。
Your article is very excellent.