この記事はnem Advent Calendar 2019 9日目の記事です。
NFT(Non Fungible Token)とは
最近よく聞くNFTですが、要は、代替不可能で固有の価値や独自性を持っているトークンです。
EthereumのERC721が最も有名で、標準的な実装だと思います。
https://eips.ethereum.org/EIPS/eip-721
しかしEthereumでやるのはやっぱり色々気にしないといけないことが多いです。
(コントラクトの脆弱性や乱高下するGas etc...)
APIで簡単に様々なことができるNEMでNFTを扱えるようにすることは、魅力的だと思います。
以前、NEMでNFTについて書かれている方もいました。
https://qiita.com/tkskysk/items/adfb779bd64202a5d546
https://yu-kimura.jp/2018/12/19/nem-nft/
サブネームスペースを使用したり、アドレスがモザイク名となるようにしたりと工夫されています。
CatapultではMetadataがあるので、よりシンプルにNFTを実現できるのではと思いました。
というよりNFT(的なもの)を意識してMetadataの機能を入れたのではないかという気も勝手にしてます。
メタデータとは
Catapultで追加された機能です。
ドキュメントから引用します。
識別子にリンクされた value は 1024 文字までの文字列です。クライアントアプリケーションはメッセージを暗号化するか、またはすべてのブロックチェーン参加者が見える必要があります。 https://nemtech.github.io/ja/concepts/metadata.html
1024文字までの任意の文字列をアカウントやネームスペース、モザイクに割り当てることができます。
NEM Foundationのnem2-nonfungible-asset
NFTについてはNEM Foundationがすでに参考実装としてGitHubにあげていました。
安定して動くようになればNIPとして提出すると書かれています。
https://github.com/nemfoundation/nem2-nonfungible-asset/
このサンプル実装の内容をみて、実際にNFTをNEM Catapultで作ってみようというのが今回の記事の主旨です。
気になったのが、メタデータを更新することはできるのか?ということです。
例えばNFTを作成するのに、メタデータにトークンの名前や画像のURIなどトークン固有のパラメータを格納する場合、更新が可能ならば、分散的ではありませんし、権利の証明などに使うのは難しい気がします。
しかし、メタデータは更新可能と書かれているのでこのあたりにも注目しつつ実装がどうなっているのか見てみたいと思います。
https://nemtech.github.io/ja/guides/metadata/updating-metadata-entries.html
開発環境
Ubuntu 19.10
Node js v10.16.3
npm 6.13.1
nem2-sdk 0.10.0
nem2-nonfungible-assetについてはまだnpmなどでパッケージ化されてないので、gitで直接ソースを取得してnpm installして使用します。
https://github.com/nemfoundation/nem2-nonfungible-asset/
$ git clone https://github.com/nemfoundation/nem2-nonfungible-asset.git
$ cd nem2-nonfungible-asset
$ npm install
Catapultサーバー起動
Catapultはbootstrapの最新のmasterのものを利用しました。
dockerがあればすぐ起動できます。
$ git clone https://github.com/tech-bureau/catapult-service-bootstrap.git
$ ./cmds/start-all
サンプルコードを実行してみる
ライブラリとしてまだ提供はされていないので、雑ですがindex.tsに直接書いてbuildして実行します。
buildは以下のようにしてできます。
distディレクトリにコンパイルされたファイルができます。
$ npm run build
index.tsは以下のようにします。
基本はサンプルコードと同じです。
export { Asset } from './src/Asset';
export { AssetCommand } from './src/AssetCommand';
export { AssetRepository } from './src/AssetRepository';
export { AssetTransferOwnershipCommand } from './src/AssetTransferOwnershipCommand';
export { Evidence } from './src/Evidence';
import { Asset } from './src/Asset';
import { AssetRepository } from './src/AssetRepository';
import { Account, NetworkHttp, NetworkType, PublicAccount, TransactionHttp, AccountHttp, BlockchainHttp } from 'nem2-sdk';
const node = 'http://localhost:3000';
const network = NetworkType.MIJIN_TEST;
// このsourceとidentifierから新しいアカウントが作成される
// サンプルがotherchainというのは他のblockchainと紐付けるようなことを意識している?
const source = 'otherchain';
const identifier = '26198278f6e862fd82d26c7388a9ed19ed16282c2a4d562463b8b4336929c5d6';
// catapult-service-bootstrap/build/generated-addresses/addresses.yamlのkeyを利用
const publicKey = 'D915F56C016BA83B527F471BB411C2CB1ADA85AF80173E9AE46C40C8EB57FD4C';
const privateKey = '6D2165DB6417BFA23EABC065E146B5D87819E3947E7496726C79E249515E4C6C';
// NFTの所有者
const owner = PublicAccount.createFromPublicKey(
publicKey,
network,
);
// よくあるERC721のtokenURIのような感じにしてみるがURI入らない...
const metadata = {
"name": "NFT",
"description": "NFT test",
"image": "imageURI"
}
// asset作成
const asset = Asset.create(
owner,
source,
identifier,
metadata,
);
console.log(asset)
// トランザクションを作成
const transactionHttp = new TransactionHttp(node);
const account = Account.createFromPrivateKey(privateKey, network);
const publishableAsset = AssetRepository.publish(asset);
// 署名
const signed_tx = account.sign(publishableAsset);
console.log(signed_tx);
// ブロードキャスト
transactionHttp
.announce(signed_tx)
.subscribe(result => console.log(result));
実行してみる
$ node dist/index.js
Assetオブジェクトはこのようになりました。
Asset {
publicKey:
'1485030412335ACAE6A59E8F5826AA7B7EAA831EAC73FE60E6A00E893A306F71',
address:
Address {
address: 'SAG3VKH4XRCVYTMDMHUN62AH353TJC74BFDKKNOA',
networkType: 144 },
owner:
PublicAccount {
publicKey:
'D915F56C016BA83B527F471BB411C2CB1ADA85AF80173E9AE46C40C8EB57FD4C',
address:
Address {
address: 'SDZOUGIKOGPPBQPRN4IWHRFG3GNA6U2NMGHA6KMG',
networkType: 144 } },
source: 'otherchain',
identifier:
'26198278f6e862fd82d26c7388a9ed19ed16282c2a4d562463b8b4336929c5d6',
metadata: { name: 'NFT', description: 'NFT test', image: 'imageURI' },
commands: [],
networkType: 144 }
新しいpublicKeyとaddressが作成されています。
ソースを見ると、sourceとidentifierから新しいアカウントを作成するようになっています。
自分はモザイクを新しく作成すると思っていたので、これは意外でした。
metadataには定義したnameなどが入っています。
ちょっと色々試しているうちに以下のエラーに引っかかったのですが、このAssetのmetadataにはドット(".")とカンマ(",")を入れることができず、validationで弾くようにしています。
なぜこうしてるのかわからないのですが、これではURIとか入れれないような...
throw Error(`${key} contains special characters (, or .)`)
そしてcommandsが何かよくわからなかったのですが、ソースを見たところAssetCommandというclassがあり、想像ですが特定の関数を呼び出して実行するようなことをできるようにしているのかもしれません。スマートコントラクトとまでは言いませんが、そういうこともできるように意識してるのかな...
今回のようにcreateメソッドからAssetクラスを生成した場合はcommandsは空のlistを渡していて空になっています。
署名したトランザクションも載せておきます。
SignedTransaction {
payload:
'9201000055E6AE992FBE603AA12D3CEC58A388D0FBC9C8AA2D2DD26E3516CA151BE16AFEA73E06DE3C22D467FEA821D7DE4D0A78EC5C27C8158CFEC31D45D79102694908D915F56C016BA83B527F471BB411C2CB1ADA85AF80173E9AE46C40C8EB57FD4C0290414100000000000000002622D2161B0000001601000099000000D915F56C016BA83B527F471BB411C2CB1ADA85AF80173E9AE46C40C8EB57FD4C03905441900DBAA8FCBC455C4D8361E8DF6807DF77348BFC0946A535C05500000061737365742831293A6F74686572636861696E2C323631393832373866366538363266643832643236633733383861396564313965643136323832633261346435363234363362386234333336393239633564367D000000D915F56C016BA83B527F471BB411C2CB1ADA85AF80173E9AE46C40C8EB57FD4C03905441900DBAA8FCBC455C4D8361E8DF6807DF77348BFC0946A535C0390000006D657461646174612831293A6E616D653B4E46547C6465736372697074696F6E3B4E465420746573747C696D6167653B696D616765555249',
hash:
'1FFE540D06EADD286C0BA7A3B032FAE4EA17E0B868EA8E4DC11C40C8C23345F1',
signer:
'D915F56C016BA83B527F471BB411C2CB1ADA85AF80173E9AE46C40C8EB57FD4C',
type: 16705,
networkType: 144 }
TransactionAnnounceResponse {
message: 'packet 9 was pushed to the network via /transaction' }
で、ここでAnnounceに成功したと思いきや、確認したらどうも有効にならないので調べたところトランザクションが弾かれてるような感じでした。
api-node-0のlogに以下のように出てました。
api-node-0_1 | 2019-12-08 07:45:48.890019 0x00007f69d99bd700: <warning> (model::Transaction.cpp@31) rejected transaction with type: EntityType<0x0000>
api-node-0_1 | 2019-12-08 07:45:48.890070 0x00007f69d99bd700: <warning> (ionet::PacketPayloadParser.h@34) entity (header size = 128) has failed validity check with size 399
api-node-0_1 | 2019-12-08 07:45:48.890080 0x00007f69d99bd700: <warning> (handlers::HandlerUtils.h@39) rejecting empty range: packet Push_Transactions with size 407
トランザクションの形式がおかしいのかな、ちょっとよくわかりません...
(教えて詳しい人)
EntityType<0x0000>がおかしい感じなので、調べてみると予約済ってどういうこと...?
https://nemtech.github.io/ja/concepts/transaction.html#entitytype
よくわからない感じなので、時間もないしトランザクションの確認は諦めましたw
まとめ
ブロードキャストもできなかったので私の理解が合ってるのかも怪しいところですが、今のところNEM Foundationのサンプル実装では、Metadataを持つNFTアカウントを作成して、NFTを表現しようとしているようです。
transferOwnership関数があり、オーナーがNFTアカウントを譲渡することは可能となっています。
(これ旧オーナーのチェック入ってないような気がする)
ただこのアカウントを利用して作る場合って、サービスとして提供しづらい気がするのだけどそんなことないのでしょうか...?ゲームとか作りたい場合どうやってやるんでしょう...
自分としてはモザイクで発行するものだと思ってたので、アカウントでやるのがあまりイメージできないところはあります。
そして最初の方にも書きましたが、Metadataって更新できそうな気もするのですが、そのあたりはどうなんでしょう...
実際の実装を見て、自分の疑問が増えるような感じになってしまいました。(NEMでNFTええやんってなりたかった)
私もよくわかってないところも多いですし、まだまだこれから色々検討していく段階なのかもしれません。
いずれNIPとして議論されるとのことなので、期待したいと思います。
何が言いたいのかもよくわからない記事になってしまいましたが、気になるところではあるのでこれからも情報は追っていきたいと思います。間違ってるところとか、こうなってるんだよっていうのがあれば教えていただけると嬉しいです。