3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

LINE Blockchain APIでアイテムトークンを操作する その1

Posted at

はじめに

前回の続きです。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をクリックします

スクリーンショット 2022-10-02 14.41.42.png

登録が完了すると以下のようになります

スクリーンショット 2022-10-02 14.41.57.png

アイテムトークンコントラクトを取得する

アイテムトークンのコントラクトを取得して確認します

src/index.ts
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を選択します

Create-item-token-contract.png

Item token(0)Issue Newをクリックし、下図のようにNon-fungibleを選択、Token name欄にアイテムトークン名を入力します

スクリーンショット 2022-10-02 14.56.16.png

Issueをクリックし、Wallet Secretを入力してOKをクリックします

スクリーンショット 2022-10-02 14.58.47.png

確認画面が表示されるのでConfirmをクリックします

スクリーンショット 2022-10-02 15.00.07.png

完了すると以下のようにNFTが発行されます

スクリーンショット 2022-10-02 15.02.10.png

Non-Fungibleアイテムトークンを取得する

登録されているNon-Fungibleアイテムトークンの一覧を取得します

src/index.ts
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アイテムトークンをオーナーのウォレットに送ります

src/index.ts
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の一覧が取得できます

src/index.ts
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型のtokenstokenに修正する必要があります。プルリク送っておきました

response.d.ts
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>);
}

修正した上で、転送処理は以下のコードになります

src/index.ts
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アイテムトークンの保有者を取得します

src/index.ts
 * 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アイテムトークンを生成したり転送したりしてみました。最後にアイテムトークンを焼却しようと思いましたが、ちょっと手順がややこしいので次回に回そうと思います。

3
0
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
3
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?