🎨

XRPLのNFToken開発:総合ガイド

に公開

はじめに

XRP Ledger(XRPL)は、高速で低コストな分散型台帳として知られていますが、2022年にNFToken(Non-Fungible Token)のネイティブサポートが追加され、その機能性がさらに拡張されました。

このガイドでは、XRPLにおけるNFTokenの基本的な仕組みから、実装方法、フラグの設定、そして今後の展望まで詳細に解説します。

NFTokenの仕組み

NFTokenオブジェクト

XRPLのNFTokenは、台帳上のアカウントが所有するオブジェクトとして存在します。各NFTokenは以下の特徴を持ちます。

  • ユニーク性: 各NFTokenは一意のIDを持ちます
  • 所有権: NFTokenはアカウントに直接紐づけられ、台帳上で所有権が明確に記録されます
  • メタデータ: NFTokenにはURIフィールドがあり、外部メタデータへの参照を保存できます
{
  "NFTokenID": "000B0000C124E14550C87D5485C4631D5DC7DF0A0000099B00000000",
  "URI": "697066733A2F2F516D516A447644687332346E6255746359636534685045716E7758746371574C524B3367527375464C767953", // 16進数値で記録(Hex)
  "Flags": 8,
  "Issuer": "r...", // NFToken発行者のウォレットアドレス
  "NFTokenTaxon": 0, // Taxon: 分類に利用(数値)
  "TransferFee": 5000 // 転送手数料(0.5%)- 0〜50000(0%〜50%)いわゆるロイヤリティ
}

メタデータの例

URIの細かい仕様は後述しますが、
先に実際にMint済みのNFTのメタデータを例として紹介します。

{
	"name": "Fuzzybear #168",
	"description": "Original Fuzzybears on the XRP Ledger.",
	"external_url": "",
	"image": "ipfs://bafybeid5gaqiyien2ao2nzlta5p4fdxptty5t7hpbsma4joxzv4s5hvmii/fuzzybear-#168.png",
	"attributes": [
		{
			"trait_type": "Background",
			"value": "XRPL Blue"
		},
		{
			"trait_type": "Fur",
			"value": "Brown"
		},
		{
			"trait_type": "Clothes",
			"value": "Jumpsuit"
		},
		{
			"trait_type": "Mouth",
			"value": "Sharp"
		},
		{
			"trait_type": "Eyes",
			"value": "Thug"
		},
		{
			"trait_type": "Headwear",
			"value": "Christmas"
		}
	],
	"properties": {
		"files": [
			{
				"uri": "fuzzybear-#168.png",
				"type": "image/png"
			}
		],
		"category": "image",
		"creators": []
	},
	"compiler": "NFTexport.io"
}

このように、実際にリリースされるNFTアートでは、中央サーバーに頼らずにデータを保存・取得できる分散型システムであるIPFSに画像アップロードすることが多いです。

また、このNFTは以下のサービスで作成されていることもわかります。

https://nftexport.io/

NFTokenPage

アカウントが多数のNFTokenを所有する場合、それらは「NFTokenPage」と呼ばれる構造に格納されます。

{
  "LedgerEntryType": "NFTokenPage",
  "LedgerIndex": "C66D8CFFC022544F021FB91E598AB6C5D1D45D2CCB81D26EEACE5EE18EE7A548",
  "PreviousTokenID": "000B0000C124E14550C87D5485C4631D5DC7DF0A0000099B00000000",
  "PreviousTxnID": "4C22E4169EA0D96F468E95770314FB45188DB6B39F5177F5A59310BC23ECDDA9",
  "PreviousTxnLgrSeq": 1337,
  "NFTokens": [
    {
      "NFToken": {
        "NFTokenID": "000B0000C124E14550C87D5485C4631D5DC7DF0A0000099B00000000",
        "URI": "697066733A2F2F516D516A447644687332346E6255746359636534685045716E7758746371574C524B3367527375464C767953",
        "Flags": 8, // 後述のフラグ値を指定
        "Issuer": "r...", // ウォレットアドレス
        "NFTokenTaxon": 0, // NFTをコレクションにグループ化するための値
        "TransferFee": 5000 // 0.5%
      }
    }
  ]
}

NFToken関連トランザクションの説明

XRPLでNFTokenを操作するためには、以下の主要なトランザクションタイプを理解する必要があります。

1. NFTokenMint - NFTokenの発行

新しいNFTokenを発行するためのトランザクションのオブジェクト例です。

const mintTransaction = {
  TransactionType: "NFTokenMint",
  Account: "r...", // ウォレットアドレス
  NFTokenTaxon: 0,  // 分類ID(数値)NFTをコレクションにグループ化することが可能
  URI: convertStringToHex(metadataURI),  // メタデータの参照先URIを指定(HEX形式)
  Flags: NFTokenMintFlags.tfTransferable,  // 転送可能フラグ
  TransferFee: 5000,  // 転送手数料(0.5%)- 0〜50000(0%〜50%)
};

NFTokenTaxonはNFTokenを分類できるフィールドで、NFTアートコレクションのグループ分けにも利用可能です。このフィールドにより、発行者が独自にIDを指定・管理することで、一つのアートコレクションとして管理することが可能になっています。

https://xrpl.org/ja/docs/references/protocol/transactions/types/nftokenmint

また最近では、NFTokenMintOfferというAmendmentにより、NFTokenMintと同時にOfferを出すことも可能になりました。


// 例: 3ヶ月後の日付のUNIXタイムスタンプをリップルタイムに変換
const threeMonthsLaterDate = new Date();
threeMonthsLaterDate.setMonth(threeMonthsLaterDate.getMonth() + 3);

// 3ヶ月後のUNIXタイムスタンプ(秒)
const threeMonthsLaterUnixTimestamp = Math.floor(threeMonthsLaterDate.getTime() / 1000);

const mintTransaction = {
  TransactionType: "NFTokenMint",
  Account: "r...", // ウォレットアドレス
  NFTokenTaxon: 0,  // 分類ID(数値で指定)
  URI: convertStringToHex(metadataURI),  // メタデータURI(HEX形式)
  Flags: NFTokenMintFlags.tfTransferable,  // 転送可能フラグ
  TransferFee: 5000,  // 転送手数料(0.5%)- 0〜50000(0%〜50%)
  Amount: xrpToDrops('10'), // 販売金額 10 XRP
  Destination: 'r...', // 送付(販売)先アドレス
  Expiration: unixTimeToRippleTime(threeMonthsLaterUnixTimestamp) // 期限
};

DestinationExpirationAmountフィールドが未指定の場合はエラーとなります。

https://zenn.dev/tequ/articles/nftokenmintoffer

2. NFTokenCreateOffer - オファーの作成

NFTokenの売買オファーを作成するトランザクションのオブジェクト例です。

// 売りオファーの例
const sellOfferTransaction = {
  TransactionType: "NFTokenCreateOffer",
  Account: "r...", // ウォレットアドレス
  NFTokenID: "000B0000C124E14550C87D5485C4631D5DC7DF0A0000099B00000000",
  Amount: "10000000",  // 10 XRP(Drops単位)
  Flags: 1  // 1 = 売りオファー
};

// 買いオファーの例
const buyOfferTransaction = {
  TransactionType: "NFTokenCreateOffer",
  Account: "r...", // ウォレットアドレス
  NFTokenID: "000B0000C124E14550C87D5485C4631D5DC7DF0A0000099B00000000",
  Owner: "r...",  // NFTの所有者アドレス(買いオファーの場合は必須)
  Amount: "5000000",  // 5 XRP(Drops単位)
  Flags: 0  // 0 = 買いオファー
};

3. NFTokenAcceptOffer - オファーの承認

NFTokenの売買オファーを承認するトランザクションのオブジェクト例です。

// 売りオファーを承認する例(購入)
const acceptSellOfferTransaction = {
  TransactionType: "NFTokenAcceptOffer",
  Account: "r...", // ウォレットアドレス
  NFTokenSellOffer: "9C92E061381C1EF42390A32CE9F9CF222B21C58AFBE8C57D753AA3F35675A242"
};

// 買いオファーを承認する例(売却)
const acceptBuyOfferTransaction = {
  TransactionType: "NFTokenAcceptOffer",
  Account: "r...", // ウォレットアドレス
  NFTokenBuyOffer: "65023C0F68CA6DE72E3FA4CF95B50697753F7980CB8F8E489E9EC975AD293EF2"
};

注意点としては、NFTokenSellOfferNFTokenBuyOfferNFTokenIDではありません。

以下のような関数とコマンドで、NFTokenIDに対するオファー情報がレジャー存在するか取得した上で、指定する必要があります。

// NFTのオファーを取得する関数
export async function getNFTOffers(
  client: Client,
  nftokenID: string
): Promise<any[]> {
  try {
    // sell_offersを取得
    const sellResponse = await client.request({
      command: "nft_sell_offers",
      nft_id: nftokenID,
    });
    
    return sellResponse.result.offers || [];
  } catch (error: unknown) {
    // objectNotFoundエラーの場合は空配列を返す(NFTは存在するがオファーがない場合)
    const errorStr = String(error);
    if (errorStr.includes("objectNotFound") || errorStr.includes("object was not found")) {
      console.log(`No sell offers found for NFT: ${nftokenID}`);
      return [];
    }
    
    // その他のエラーは通常通り処理
    console.error(`Failed to get NFT offers: ${error}`);
    throw error;
  }
}

// NFTのbuyオファーを取得する関数(追加)
export async function getNFTBuyOffers(
  client: Client,
  nftokenID: string
): Promise<any[]> {
  try {
    // buy_offersを取得
    const buyResponse = await client.request({
      command: "nft_buy_offers",
      nft_id: nftokenID,
    });
    
    return buyResponse.result.offers || [];
  } catch (error: unknown) {
    // objectNotFoundエラーの場合は空配列を返す
    const errorStr = String(error);
    if (errorStr.includes("objectNotFound") || errorStr.includes("object was not found")) {
      console.log(`No buy offers found for NFT: ${nftokenID}`);
      return [];
    }
    
    console.error(`Failed to get NFT buy offers: ${error}`);
    throw error;
  }
}

4. NFTokenCancelOffer - オファーのキャンセル

NFTokenのオファーをキャンセルするトランザクションのオブジェクト例です。

const cancelOfferTransaction = {
  TransactionType: "NFTokenCancelOffer",
  Account: "r...", // ウォレットアドレス
  NFTokenOffers: [ // 配列
    "9C92E061381C1EF42390A32CE9F9CF222B21C58AFBE8C57D753AA3F35675A242",
    "65023C0F68CA6DE72E3FA4CF95B50697753F7980CB8F8E489E9EC975AD293EF2"
  ]
};

5. NFTokenBurn - NFTの焼却

NFTokenを永久に焼却(削除)するトランザクションのオブジェクト例です。

const burnTransaction = {
  TransactionType: "NFTokenBurn",
  Account: "r...", // ウォレットアドレス
  NFTokenID: "000B0000C124E14550C87D5485C4631D5DC7DF0A0000099B00000000"
};

NFTokenフラグの説明

NFTokenMintトランザクションで使用できるフラグについて詳しく見ていきましょう。

// フラグの定義(ビット値)
const NFTokenMintFlags = {
  tfBurnable: 1,       // 発行者以外も焼却可能
  tfOnlyXRP: 2,        // XRPでのみ取引可能
  tfTrustLine: 4,      // 発行者の信頼線を要求(非推奨: fixRemoveNFTokenAutoTrustLine Amendmentにより、このフラグの設定は無効となりました。)
  tfTransferable: 8    // 転送可能(このフラグがないとNFTは転送不可)
};

// フラグの組み合わせ
const combinedFlags = NFTokenMintFlags.tfBurnable | NFTokenMintFlags.tfTransferable; // 9

NFTokenの場合、フラグは足し算で指定します。

上記例だと、tfBurnable + tfTransferable = 9という指定です。
xrpl.jsを用いた開発では、NFTokenMintFlagsといったフラグの定数をまとめたオブジェクトが提供されているため、importしてオブジェクトを利用することを推奨します。

フラグの詳細

  1. tfBurnable (1): このフラグがあると、NFTの所有者(発行者でなくても)がNFTを焼却できます。設定しない場合、発行者のみが焼却できます。

  2. tfOnlyXRP (2): このフラグがあると、NFTはXRPでのみ取引可能となります。IOUトークンでの取引はできません。

  3. tfTrustLine (4): このフラグがあると、NFTを受け取るには発行者のトラストラインが必要になります。(非推奨: fixRemoveNFTokenAutoTrustLine Amendmentにより、このフラグの設定は無効となりました。)

  4. tfTransferable (8): このフラグがあると、NFTは転送可能になります。設定しない場合、NFTは転送できなくなるので注意が必要です。

URIフィールドとメタデータ

NFTokenのURIフィールドは、メタデータを参照するための重要な要素です。
メタデータを16進数に変換し、指定する必要があります。

// URIフィールドの例
const metadata = JSON.stringify({
  name: "My XRPL NFT",
  description: "This is a test NFT on the XRP Ledger",
  image: "ipfs://example/nft-image.png",
  attributes: [
    { trait_type: "Background", value: "Blue" },
    { trait_type: "Character", value: "Robot" }
  ]
});

// Task: jsonをIPFSかサーバーにアップロードして保存

const jsonURI = "json_url_here";

// 16進数に変換
const hexMetadata = convertStringToHex(jsonURI); // convertStringToHex のインポートが必要です

URIフィールドには一般的にはIPFSがよく利用されますが、通常のサーバーに保管させることも技術的には可能です。

  1. IPFS URI: ipfs://Qm...
  2. HTTP/HTTPS URI: https://example.com/metadata/123.json

実装例:xrpl.jsを使用したNFT発行の基本

まずはパッケージマネージャーでxrpl.jsをインストールします。

npm i xrpl

以下はNFTokenMintトランザクションの例です。

import { Client, Wallet, convertStringToHex, NFTokenMintFlags } from "xrpl";

async function mintNFT() {
  const client = new Client("wss://s.altnet.rippletest.net:51233"); // テストネット

  try {
    // クライアント接続
    await client.connect();
    
    // ウォレット準備
    const wallet = Wallet.fromSeed("wallet_seed_here");
    
    // メタデータ作成
    const metadata = JSON.stringify({
      name: "My XRPL NFT",
      description: "This is a test NFT on the XRP Ledger",
      image: "https://example.com/nft-image.png"
    });

    // Task: jsonのアップロード処理が必要です

    const metadataURI = "";
    
    // NFTokenMintトランザクション
    const txBrob = {
      TransactionType: "NFTokenMint",
      Account: wallet.address,
      NFTokenTaxon: 3, // NFTをコレクションにグループ化(例えば3を〇〇コレクションとして管理したい場合は個別で指定してください)
      URI: convertStringToHex(metadataURI), // 16進数に変換(Hex)
      Flags: NFTokenMintFlags.tfTransferable, // 転送可能なNFTとして発行
      TransferFee: 5000  // 0.5% 販売ロイヤリティ
    };
    
    // トランザクション送信
    const tx = await client.submitAndWait(txBrob, { wallet });
    
    // NFToken IDの取得
    const nftokenID = tx.result.meta.nftoken_id;
    console.log("NFToken minted with ID:", nftokenID);
    
    return nftokenID;
  } catch (error) {
    console.error("Error minting NFT:", error);
    throw error; // エラーハンドリング
  } finally {
    // クライアント切断
    await client.disconnect();
  }
}

mintNFT().catch(console.error);

未来の展望

現在、XRPLのNFT関連のAmendmentで注目すべきものを2つ紹介します。

※提案中の機能はDevNetで試すことはできますが、合意を経てアクティブにならない限りは、メインネットで利用することはできません。

https://zenn.dev/tequ/articles/xrpl-amendments

Dynamic NFT Amendment

Dynamic NFTについては、Web3の共通言語ではあるものの、現在のXRPLのNFTokenは、発行後にメタデータの変更ができないという制限があります。Dynamic NFT Amendmentは、NFTokenの動的な更新を可能にする提案です。

これによりインタラクティブなNFT体験(ゲーム、チケット、アートなど)を提供できる可能性があります。

// 将来的に実装される可能性のあるDynamic NFTの例
{
  "TransactionType": "NFTokenModify",
  "Account": "r...", // ウォレットアドレス
  "Owner": "r...", // NFT Ownerのウォレットアドレス
  "NFTokenID": “0008C350C182B4F213B82CCFA4C6F59AD76F0AFCFBDF04D5A048C0A300000007", // 更新したいNFTokenIDを指定
  "URI": "6970667...", // メタデータを更新
  }

https://zenn.dev/tequ/articles/xrpl-dynamic-nft

MPT (Multi-Purpose Token)

MPT(Multi-Purpose Token)は、既存のToken仕様にFungible Tokenの特性を組み合わせた新しいトークンタイプの提案です。

GameFiやDeFiでの新たなユースケースを創出することが期待されています。

  • トラストラインに比べてストレージ使用量が少なく、ネットワークのスケーラビリティとパフォーマンスが向上する
  • 一方向の関係性と単一の残高構造(IOUのような債務関係ではない)により、開発者やユーザーにとって理解しやすく、実装がシンプルになることがメリット
  • 多様なユースケース対応: 金融的な概念以外のユースケースに向いている(ポイントや、マイルのようなロイヤリティポイントなど)

どちらかと言えば資産としてのトークンに適している規格になるかと思いますが、多目的トークンということで紹介しました。

まとめ

XRPLのNFToken機能は、以下のような特徴を持つNFTソリューションを提供しています。

  1. ネイティブサポート: 追加のスマートコントラクトなしでNFTをサポート
  2. 低コスト: 低コストで発行・取引可能(一般的にいうガス代)
  3. 高速性: 3〜5秒でのファイナリティ
  4. 柔軟性: 多様なユースケースに対応するフラグやオプション

XRPLのNFToken機能は、既に多くのプロジェクト(大阪万博やNFTマーケットプレイス、NFTクリエイターのアート作品)で利用されており、今後のAmendmentによってさらに機能追加・改善されていくことで、さらに多様化し、新たなユースケースが開拓されると考えられます。

関連情報

NFTokenの開発を始める際は、まずTestnetで十分にテストを行い、フラグの設定やメタデータの構造に特に注意を払うことをお勧めします。

イベント告知

https://x.com/XRPLJapan/status/1920082204212056169

Discussion