はじめに
前回の続きです。LINE Blockchain Developers SDK for JavaScriptでアイテムトークンを取り扱ってみます。流行りに乗ってNon-fungibleアイテムトークンを発行してみましょう。
アイテムトークンに画像や動画を登録するルールについてはアイテムトークンのアイコンを提供するに詳しく解説されています。LINE BITMAX Walletなどでアイテムトークンを使用する場合は参考にしてください。
アイテムトークンコントラクトを作成する
アイテムトークンを鋳造するには、アイテムトークンコントラクトをが必要です。アイテムトークンコントラクトIDはLINE Blockchainサービス登録時に作成されていますが、コントラクトの情報が未登録なので登録していきます。
まず、LINE Blockchain Developersのコンソールから該当するBlockchainサービスのSettings
を選択してBase URI of token icon
にアイテムトークンコントラクトの画像/動画を格納するURIを設定します。今回はAWS S3で適当な公開用バケットを作成して、そのURLを設定しました
欄下の✓マークをクリックしWallet Secretを入力してOKをクリックします
登録が完了すると以下のようになります
アイテムトークンコントラクトを取得する
アイテムトークンのコントラクトを取得して確認します
import * as dotenv from 'dotenv';
import * as devSdk from '@line/lbd-sdk-js';
import {
WalletResponse,
ItemToken,
} from '@line/lbd-sdk-js';
dotenv.config();
const httpClient = new devSdk.HttpClient(
process.env.BASE_URL as string,
process.env.SERVICE_API_KEY as string,
process.env.SERVICE_API_SECRET as string
);
const getWallets = async (): Promise<WalletResponse[]> => {
const wallets = await httpClient.wallets()
if (!wallets.responseData || wallets.responseData?.length < 1) throw new Error('No response data');
const result = wallets.responseData;
if (!result) throw new Error('No wallet');
return result;
};
const getWalletByName = (name: string, wallets: WalletResponse[]): WalletResponse|undefined => {
return wallets.find((w) => { return w.name === name });
};
const getItemTokenContract = async (contractId: string): Promise<ItemToken|undefined> => {
const res = await httpClient.itemToken(contractId);
return res.responseData;
}
const main = async () => {
const wallets = await getWallets();
const btfWallet = getWalletByName('bathtimefish', wallets);
if (!btfWallet) throw new Error('Wallet not found');
const contract = await getItemTokenContract(process.env.ITEM_TOKEN_CONTRACT_ID as string);
console.log(contract);
};
main();
実行すると以下のようにアイテムトークンコントラクトの情報を得ることができます
yarn run ts-node src/index.ts
{
contractId: '[ITEM TOKEN CONTRACT ID]',
baseImgUri: 'https://btf-lineblockchainapi.s3.ap-northeast-3.amazonaws.com/',
ownerAddress: '[OWNER WALLET ADDRESS]',
createdAt: 1660481980316,
serviceId: '[SERVICE ID]'
}
✨ Done in 1.95s.
Non-fungibleアイテムトークンを発行する
LINE Blockchain Developersのコンソールから該当するBlockchainサービスのAsset
を選択します
Item token(0)
のIssue New
をクリックし、下図のようにNon-fungible
を選択、Token name
欄にアイテムトークン名を入力します
Issue
をクリックし、Wallet Secretを入力してOKをクリックします
確認画面が表示されるのでConfirm
をクリックします
完了すると以下のようにNFTが発行されます
Non-Fungibleアイテムトークンを取得する
登録されているNon-Fungibleアイテムトークンの一覧を取得します
import * as dotenv from 'dotenv';
import * as devSdk from '@line/lbd-sdk-js';
import {
WalletResponse,
PageRequest,
OrderBy,
ItemTokenType,
} from '@line/lbd-sdk-js';
dotenv.config();
const httpClient = new devSdk.HttpClient(
process.env.BASE_URL as string,
process.env.SERVICE_API_KEY as string,
process.env.SERVICE_API_SECRET as string
);
const getWallets = async (): Promise<WalletResponse[]> => {
const wallets = await httpClient.wallets()
if (!wallets.responseData || wallets.responseData?.length < 1) throw new Error('No response data');
const result = wallets.responseData;
if (!result) throw new Error('No wallet');
return result;
};
const getWalletByName = (name: string, wallets: WalletResponse[]): WalletResponse|undefined => {
return wallets.find((w) => { return w.name === name });
};
const getNFTInfo = async (contractId: string, tokenType: string): Promise<ItemTokenType[]|undefined> => {
const pageRequest: PageRequest = {
page: 1,
limit: 10,
orderBy: OrderBy.ASC
};
const res = await httpClient.nonFungibleTokens(contractId, pageRequest);
return res.responseData;
}
const main = async () => {
const wallets = await getWallets();
const btfWallet = getWalletByName('bathtimefish', wallets);
if (!btfWallet) throw new Error('Wallet not found');
const nft = await getNFTInfo(
process.env.ITEM_TOKEN_CONTRACT_ID as string,
process.env.ITEM_TOKEN_TYPE as string,
);
console.log(nft);
};
main();
実行すると以下のように登録されたNFTのリストが取得できます
yarn run ts-node src/index.ts
[
{
tokenType: '[NFT TOKEN TYPE]',
name: 'BTFHOGEHOGE',
meta: '',
createdAt: 1664690394589,
totalSupply: '0',
totalMint: '0',
totalBurn: '0'
}
]
✨ Done in 1.93s.
Non-fungibleアイテムトークンを鋳造する
Non-fungibleアイテムトークンを鋳造を新しく鋳造します。ここでは新規鋳造したNon-fungibleアイテムトークンをオーナーのウォレットに送ります
import * as dotenv from 'dotenv';
import * as devSdk from '@line/lbd-sdk-js';
import {
WalletResponse,
PageRequest,
OrderBy,
ItemTokenType,
NonFungibleTokenType,
NonFungibleTokenMintRequest,
TxHashResponse,
} from '@line/lbd-sdk-js';
dotenv.config();
const httpClient = new devSdk.HttpClient(
process.env.BASE_URL as string,
process.env.SERVICE_API_KEY as string,
process.env.SERVICE_API_SECRET as string
);
const getWallets = async (): Promise<WalletResponse[]> => {
const wallets = await httpClient.wallets()
if (!wallets.responseData || wallets.responseData?.length < 1) throw new Error('No response data');
const result = wallets.responseData;
if (!result) throw new Error('No wallet');
return result;
};
const getWalletByName = (name: string, wallets: WalletResponse[]): WalletResponse|undefined => {
return wallets.find((w) => { return w.name === name });
};
const getNFTInfo = async (contractId: string, tokenType: string): Promise<ItemTokenType[]|undefined> => {
const pageRequest: PageRequest = {
page: 1,
limit: 10,
orderBy: OrderBy.ASC
};
const res = await httpClient.nonFungibleTokens(contractId, pageRequest);
return res.responseData;
}
const mintNFT = async (contractId: string, tokenType: string, walletAddress: string, name: string, meta: string): Promise<TxHashResponse|undefined> => {
const mintRequest: NonFungibleTokenMintRequest = {
ownerAddress: process.env.OWNER_WALLET_ADDRESS as string,
ownerSecret: process.env.OWNER_WALLET_SECRET as string,
toAddress: walletAddress,
name,
meta
}
const res = await httpClient.mintNonFungibleToken(contractId, tokenType, mintRequest);
return res.responseData;
}
const main = async () => {
const wallets = await getWallets();
const btfWallet = getWalletByName('bathtimefish', wallets);
if (!btfWallet) throw new Error('Wallet not found');
const nft = await getNFTInfo(
process.env.ITEM_TOKEN_CONTRACT_ID as string,
process.env.ITEM_TOKEN_TYPE as string,
);
if (!nft || nft.length < 1) throw new Error('NFT not found');
const minted = await mintNFT(
process.env.ITEM_TOKEN_CONTRACT_ID as string,
nft[0].tokenType,
wallets[0].walletAddress,
'BTFHOGEHOGE0101',
'BathTimeFish presents awesome HOGEHOGE image',
);
console.log(minted);
};
main();
正常に処理されるとTxHashが返ります
yarn run ts-node src/index.ts
{
txHash: '7B6978210A1A8CDB624DDE7FC01EF55A949AA5BD8E12AFD260043623BEC5CA64'
}
✨ Done in 2.40s.
Non-fungibleアイテムトークンのtypeを取得する
指定されたcontract ID、Token Typeで鋳造されたnon-fungibleアイテムトークンを取得します。Non-fungibleアイテムトークンのtypeと鋳造されたtokenの一覧が取得できます
import * as dotenv from 'dotenv';
import * as devSdk from '@line/lbd-sdk-js';
import {
WalletResponse,
PageRequest,
OrderBy,
ItemTokenType,
NonFungibleTokenType,
} from '@line/lbd-sdk-js';
dotenv.config();
const httpClient = new devSdk.HttpClient(
process.env.BASE_URL as string,
process.env.SERVICE_API_KEY as string,
process.env.SERVICE_API_SECRET as string
);
const getWallets = async (): Promise<WalletResponse[]> => {
const wallets = await httpClient.wallets()
if (!wallets.responseData || wallets.responseData?.length < 1) throw new Error('No response data');
const result = wallets.responseData;
if (!result) throw new Error('No wallet');
return result;
};
const getWalletByName = (name: string, wallets: WalletResponse[]): WalletResponse|undefined => {
return wallets.find((w) => { return w.name === name });
};
const getNFTInfo = async (contractId: string, tokenType: string): Promise<ItemTokenType[]|undefined> => {
const pageRequest: PageRequest = {
page: 1,
limit: 10,
orderBy: OrderBy.ASC
};
const res = await httpClient.nonFungibleTokens(contractId, pageRequest);
return res.responseData;
};
const getNFTType = async (contractId: string, tokenType: string): Promise<NonFungibleTokenType|undefined> => {
const pageRequest: PageRequest = {
page: 1,
limit: 10,
orderBy: OrderBy.ASC
};
const res = await httpClient.nonFungibleTokenType(contractId, tokenType, pageRequest);
return res.responseData;
};
const main = async () => {
const wallets = await getWallets();
const btfWallet = getWalletByName('bathtimefish', wallets);
if (!btfWallet) throw new Error('Wallet not found');
const nft = await getNFTInfo(
process.env.ITEM_TOKEN_CONTRACT_ID as string,
process.env.ITEM_TOKEN_TYPE as string,
);
if (!nft || nft.length < 1) throw new Error('NFT not found');
const nftType = await getNFTType(
process.env.ITEM_TOKEN_CONTRACT_ID as string,
nft[0].tokenType,
);
console.log(nftType);
};
main();
先ほど鋳造してウォレットに送ったNon-fungibleアイテムトークンも取得できています
{
tokenType: '[TOKEN TYPE]',
name: 'BTFHOGEHOGE',
meta: '',
createdAt: 1664690394589,
totalSupply: '1',
totalMint: '1',
totalBurn: '0',
token: [
{
tokenIndex: '[TOKEN INDEX]',
name: 'BTFHOGEHOGE0101',
meta: 'BathTimeFish presents awesome HOGEHOGE image',
createdAt: 1664693560571,
burnedAt: null
}
]
}
✨ Done in 2.11s.
Non-fungibleアイテムトークンを転送する
鋳造したアイテムトークンをAlice
に転送します
ここでSDKにバグを発見しました。response.d.tsの321行目NonFungibleTokenType
型のtokens
をtoken
に修正する必要があります。プルリク送っておきました
export declare class NonFungibleTokenType {
readonly name: string;
readonly meta: string;
readonly tokenType: string;
readonly totalSupply: number;
readonly totalMint: number;
readonly totalBurn: number;
readonly createdAt: number;
// readonly tokens: Array<NonFungibleIndex>;
readonly token: Array<NonFungibleIndex>;
constructor(name: string, meta: string, tokenType: string, totalSupply: number, totalMint: number, totalBurn: number, createdAt: number, tokens: Array<NonFungibleIndex>);
}
修正した上で、転送処理は以下のコードになります
import * as dotenv from 'dotenv';
import * as devSdk from '@line/lbd-sdk-js';
import {
WalletResponse,
PageRequest,
OrderBy,
ItemTokenType,
NonFungibleTokenType,
TransferNonFungibleTokenRequest,
TxHashResponse,
} from '@line/lbd-sdk-js';
dotenv.config();
const httpClient = new devSdk.HttpClient(
process.env.BASE_URL as string,
process.env.SERVICE_API_KEY as string,
process.env.SERVICE_API_SECRET as string
);
const getWallets = async (): Promise<WalletResponse[]> => {
const wallets = await httpClient.wallets()
if (!wallets.responseData || wallets.responseData?.length < 1) throw new Error('No response data');
const result = wallets.responseData;
if (!result) throw new Error('No wallet');
return result;
};
const getWalletByName = (name: string, wallets: WalletResponse[]): WalletResponse|undefined => {
return wallets.find((w) => { return w.name === name });
};
const getNFTInfo = async (contractId: string, tokenType: string): Promise<ItemTokenType[]|undefined> => {
const pageRequest: PageRequest = {
page: 1,
limit: 10,
orderBy: OrderBy.ASC
};
const res = await httpClient.nonFungibleTokens(contractId, pageRequest);
return res.responseData;
};
const getNFTType = async (contractId: string, tokenType: string): Promise<NonFungibleTokenType|undefined> => {
const pageRequest: PageRequest = {
page: 1,
limit: 10,
orderBy: OrderBy.ASC
};
const res = await httpClient.nonFungibleTokenType(contractId, tokenType, pageRequest);
return res.responseData;
};
const transferNFT = async (walletAddress: string, contractId: string, tokenType: string, tokenIndex: string, request: TransferNonFungibleTokenRequest): Promise<TxHashResponse|undefined> => {
const res = await httpClient.transferNonFungibleTokenOfWallet(walletAddress, contractId, tokenType, tokenIndex, request);
return res.responseData;
};
const main = async () => {
const wallets = await getWallets();
const btfWallet = getWalletByName('bathtimefish', wallets);
if (!btfWallet) throw new Error('Wallet not found');
const nft = await getNFTInfo(
process.env.ITEM_TOKEN_CONTRACT_ID as string,
process.env.ITEM_TOKEN_TYPE as string,
);
if (!nft || nft.length < 1) throw new Error('NFT not found');
const nftType = await getNFTType(
process.env.ITEM_TOKEN_CONTRACT_ID as string,
nft[0].tokenType,
);
if (!nftType || nftType.token.length < 1) throw new Error('Token Index was not found');
const aliceWallet = getWalletByName('Alice', wallets);
if (!aliceWallet) throw new Error('Wallet not found');
const transferRequest: TransferNonFungibleTokenRequest = {
walletSecret: process.env.OWNER_WALLET_SECRET as string,
toAddress: aliceWallet.walletAddress,
};
const transferd = await transferNFT(
wallets[0].walletAddress,
process.env.ITEM_TOKEN_CONTRACT_ID as string,
nftType.tokenType,
nftType.token[0].tokenIndex,
transferRequest,
);
console.log(transferd);
};
main();
正常に処理されるとTxHashが返ります
yarn run ts-node src/index.ts
{
txHash: '6EE0E2D774E215AA615093B0F18446F8E3C3A7A4F8BAF4EB92D73A4F7802BA0F'
}
✨ Done in 2.15s.
Non-fungibleアイテムトークンの所有者を取得する
特定のnon-fungibleアイテムトークンの保有者を取得します
* as dotenv from 'dotenv';
import * as devSdk from '@line/lbd-sdk-js';
import {
WalletResponse,
PageRequest,
OrderBy,
ItemTokenType,
NonFungibleTokenType,
NonFungibleTokenHolder,
} from '@line/lbd-sdk-js';
dotenv.config();
const httpClient = new devSdk.HttpClient(
process.env.BASE_URL as string,
process.env.SERVICE_API_KEY as string,
process.env.SERVICE_API_SECRET as string
);
const getWallets = async (): Promise<WalletResponse[]> => {
const wallets = await httpClient.wallets()
if (!wallets.responseData || wallets.responseData?.length < 1) throw new Error('No response data');
const result = wallets.responseData;
if (!result) throw new Error('No wallet');
return result;
};
const getWalletByName = (name: string, wallets: WalletResponse[]): WalletResponse|undefined => {
return wallets.find((w) => { return w.name === name });
};
const getWalletByAddress = (address: string, wallets: WalletResponse[]): WalletResponse|undefined => {
return wallets.find((w) => { return w.walletAddress === address });
};
const getNFTInfo = async (contractId: string, tokenType: string): Promise<ItemTokenType[]|undefined> => {
const pageRequest: PageRequest = {
page: 1,
limit: 10,
orderBy: OrderBy.ASC
};
const res = await httpClient.nonFungibleTokens(contractId, pageRequest);
return res.responseData;
};
const getNFTType = async (contractId: string, tokenType: string): Promise<NonFungibleTokenType|undefined> => {
const pageRequest: PageRequest = {
page: 1,
limit: 10,
orderBy: OrderBy.ASC
};
const res = await httpClient.nonFungibleTokenType(contractId, tokenType, pageRequest);
return res.responseData;
};
const getNFTHolder = async (contractId: string, tokenType: string, tokenIndex: string): Promise<NonFungibleTokenHolder|undefined> => {
const res = await httpClient.nonFungibleTokenHolder(contractId, tokenType, tokenIndex);
return res.responseData;
};
const main = async () => {
const wallets = await getWallets();
const btfWallet = getWalletByName('bathtimefish', wallets);
if (!btfWallet) throw new Error('Wallet not found');
const nft = await getNFTInfo(
process.env.ITEM_TOKEN_CONTRACT_ID as string,
process.env.ITEM_TOKEN_TYPE as string,
);
if (!nft || nft.length < 1) throw new Error('NFT not found');
const nftType = await getNFTType(
process.env.ITEM_TOKEN_CONTRACT_ID as string,
nft[0].tokenType,
);
if (!nftType || nftType.token.length < 1) throw new Error('Token Index was not found');
const aliceWallet = getWalletByName('Alice', wallets);
if (!aliceWallet) throw new Error('Wallet not found');
const holder = await getNFTHolder(
process.env.ITEM_TOKEN_CONTRACT_ID as string,
nftType.tokenType,
nftType.token[0].tokenIndex,
);
if (!holder) throw new Error('Holder was not found');
console.log(holder);
const holdersWallet = getWalletByAddress(holder.walletAddress, wallets);
console.log(`Holder's name: ${holdersWallet?.name}`);
};
main();
Non-fungibleアイテムトークンが所有者が無事Alice
に転送されていることが確認できました
yarn run ts-node src/index.ts
{
tokenId: '[TOKEN ID]',
walletAddress: '[WALLET ADDRESS]',
userId: null,
amount: '1'
}
Holder's name: Alice
✨ Done in 2.09s.
おわりに
JavaScript SDKを利用してNon-Fungibleアイテムトークンを生成したり転送したりしてみました。最後にアイテムトークンを焼却しようと思いましたが、ちょっと手順がややこしいので次回に回そうと思います。