昨年 Tap! で実装した「NFTにメタデータをオンチェーンで持たせる方法」について、そろそろ忘れそうなので今のうちにまとめる。記事内ではNFTについて触れるが、Reissuable Token, Non-Reissuable Tokenでも同様の方法で実装可能なはず。
追記
実際に試せるRailsアプリを作って公開しました。
任意のMetadataファイルをIPFSへアップロードしNFTを発行するまでの一連の流れをローカルで試せます。Docker Composeを使いRails app, tapyrusd, IPFSノードを構築します。
結論
先に結論。
「NFT発行トランザクションのアウトプットに『IPFSへアップロードした任意MetadataファイルのCIDをOP_RETURNの後ろに入れたアウトプット』を追加」しました。
実装
とてもざっくり。ソースコードは Tap! 準拠。
発行自体は以下。
# wallet = 対象のウォレット # cid = 対象ファイルのCID tokens = Glueby::Contract::Token.issue_tap_nft(wallet: wallet, prefix: '', content: cid)
tokens
に発行されたトークンが入る。発行処理は Glueby の issue_nft
メソッドをオープンクラスで改造して作った issue_tap_nft
を利用。
issue_tap_nft
を含むオープンクラス箇所は以下。
module GluebyTap module Contract module Token # issue Tap!-NFT # @param [Glueby::Wallet] wallet issuer's wallet # @param [String] prefix prefix of content # @param [Srting] content raw content's data (cid expected) def issue_tap_nft(wallet:, prefix:, content:) # raw content convert to hex content = content.unpack("H*")[0] # create tx tx = create_tx_for_tap(issuer: wallet, prefix: prefix, data: content) # broadcast tx tx = wallet.internal_wallet.broadcast(tx) # get token_id out_point = tx.inputs.first.out_point color_id = Tapyrus::Color::ColorIdentifier.nft(out_point) # return [Token, Tx] [new(color_id: color_id), tx] end end module TxBuilder # create tx for Tap!-NFT # @param [Glueby::Wallet] issuer issuer's wallet # @param [String] prefix prefix of content # @param [String] data hex of content's data def create_tx_for_tap(issuer:, prefix:, data:) tx = Tapyrus::Tx.new fee = Glueby::Contract::FixedFeeEstimator.new.fee(dummy_issue_tx_from_out_point) sum, outputs = issuer.internal_wallet.collect_uncolored_outputs(fee) fill_input(tx, outputs) # TxOut for NFT out_point = tx.inputs.first.out_point color_id = Tapyrus::Color::ColorIdentifier.nft(out_point) receiver_script = Tapyrus::Script.parse_from_addr(issuer.internal_wallet.receive_address) receiver_colored_script = receiver_script.add_color(color_id) tx.outputs << Tapyrus::TxOut.new(value: 1, script_pubkey: receiver_colored_script) # TxOut for content script = create_script_for_tap(prefix, data) tx.outputs << Tapyrus::TxOut.new(value: 0, script_pubkey: script) fill_change_tpc(tx, issuer, sum - fee) issuer.internal_wallet.sign_tx(tx) end # create payload # @param [String] prefix # @param [String] data def create_payload_for_tap(prefix, data) payload = +'' payload << prefix payload << data payload end # create script # @param [String] prefix # @param [String] data def create_script_for_tap(prefix, data) script = Tapyrus::Script.new script << Tapyrus::Script::OP_RETURN script << create_payload_for_tap(prefix, data) script end end end end Glueby::Contract::Token.singleton_class.prepend(GluebyTap::Contract::Token) Glueby::Contract::TxBuilder.prepend(GluebyTap::Contract::TxBuilder)
issue_tap_nft
に加え、トランザクションの発行メソッド create_tx_for_tap_nft
も作成した。実際にCIDをアウトプットに加える作業はこのメソッド内で処理している。ほとんどは元からあったNFT用トランザクション発行メソッドの内容と同じであり、追加部分は
# TxOut for content script = create_script_for_tap(prefix, data) tx.outputs << Tapyrus::TxOut.new(value: 0, script_pubkey: script)
の部分。入れたいCIDから script
(Tapyrus Script)を用意し、それを新たなアウトプットとしてトランザクションに加えている。
scriptの作成部分について、ここでOP_RETURNの後ろに(ペイロード化した)CIDを入れ込んだTapyrus Scriptを作成している。
# create script # @param [String] prefix # @param [String] data def create_script_for_tap(prefix, data) script = Tapyrus::Script.new script << Tapyrus::Script::OP_RETURN script << create_payload_for_tap(prefix, data) script end
これでトランザクションは
TxIn | 発行に利用するUTXO |
---|---|
TxOut | NFTを含むUTXO |
OP_RETURN以降にCIDが格納されたScriptを含むアウトプット |
のような構造になる。