🐦

EVM と比較しながら XRP Ledger を理解する #4

2024/05/22に公開

本記事の目的

Ethereum Virtual Machine(EVM) の開発経験や知識は多少あるが XRP のことは全く知らない人を主な対象とし、EVM と比較しながら XRP Ledger を理解するシリーズです。コンセンサスアルゴリズムなどの理論にはあまり焦点を当てず、アプリ開発の中で必要になりそうな部分を中心に EVM と比較しながらコンパクトにまとめていこうと思います。

NFT を送る方法

EVM XRP Ledger
transferFrom メソッドを実行 送信者が NFTokenCreateOffer トランザクションを実行
+ 受領者が NFTokenAcceptOffer トランザクションを実行

ERC-721 準拠の NFT であれば transferFrom メソッドを実行すると NFT を送ることができますが、XRP Ledger の場合は送信者が送信(売却)オファーを作成する NFTokenAcceptOffer トランザクションを実行後、受領者そのオファーを受け入れる NFTokenCreateOffer トランザクションを実行することで、NFT の送信が行われます。ERC-721 と比較すると一手間多いですが、トークンが勝手に送られてくることを防ぐことができます。見知らぬアカウントから送られてきたトークンがトリガーになって資産を盗まれてしまうこともあるため、XRP Ledger はよりセキュアな仕組みになっていると言えます。

オファーの作成

事前にアカウントに NFT を1つ用意しておきます。今回は以下のように wallet1(rfJsDzdaFpQvZpcvQfjPnByAFrYzJ4eGge)に NFT を1つ用意しました。

account_nfts
{
  account: 'rfJsDzdaFpQvZpcvQfjPnByAFrYzJ4eGge',
  account_nfts: [
    {
      Flags: 11,
      Issuer: 'rfJsDzdaFpQvZpcvQfjPnByAFrYzJ4eGge',
      NFTokenID: '000B27104509AEBD50EBDBD96645A1E9D9F3B757CE2FA0E3991B28320005D797',
      NFTokenTaxon: 0,
      TransferFee: 10000,
      URI: '68747470733A2F2F7872706C2E6F72672F6A612F',
      nft_serial: 382871
    }
  ],
  ledger_hash: 'DDC5EDA942DDC34AB7230F47F5ACE7D6A06C462F01F61CA56F6330437478EF13',
  ledger_index: 800696,
  validated: true
}
アカウントが所有する NFT を表示
import { Client, Wallet, xrpToDrops } from "xrpl";

const secret = "sEdVEHTqHPGLY3c5FtsTrNZd2hPXkfF";
const wallet = Wallet.fromSeed(secret);

const client = new Client("wss://testnet.xrpl-labs.com");
await client.connect();

// 所有する NFT を取得
const response = await client.request({
  command: "account_nfts",
  account: wallet.address,
  ledger_index: "validated",
});
console.log(response.result);

それでは早速、NFTokenCreateOffer を利用して売り( sell )オファーを作成します。

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

const secret1 = "sEdVEHTqHPGLY3c5FtsTrNZd2hPXkfF";

const wallet1 = Wallet.fromSeed(secret1);

const client = new Client("wss://testnet.xrpl-labs.com");
await client.connect();

const NFTokenId =
  "000B27104509AEBD50EBDBD96645A1E9D9F3B757CE2FA0E3991B28320005D797";

const prepared = await client.autofill(
  {
    TransactionType: "NFTokenCreateOffer",
    Account: wallet1.address,
    NFTokenID: NFTokenId,
    Amount: "3",
    Flags: 1,
  }
);
const signed = wallet1.sign(prepared)
const tx = await client.submitAndWait(signed.tx_blob)
console.log(tx)

Flags フィールドで買いオファーか売りオファーかを指定できます。0の場合は買い、1の場合は売りです。Amount フィールドで価格を設定できます。今回は3 XRP としています。単純に送りたいだけの場合は0を設定します。
実行すると以下の内容がコンソールに表示されます。

console.log(tx)
{
  id: 10,
  result: {
    Account: 'rfJsDzdaFpQvZpcvQfjPnByAFrYzJ4eGge',
    Amount: '3',
    Fee: '12',
    Flags: 1,
    LastLedgerSequence: 800837,
    NFTokenID: '000B27104509AEBD50EBDBD96645A1E9D9F3B757CE2FA0E3991B28320005D797',
    Sequence: 382881,
    SigningPubKey: 'ED65B4987E440C89D48A7F0B2B21E63BE986B2C5652B2D6C1A1D30693DB029609D',
    TransactionType: 'NFTokenCreateOffer',
    TxnSignature: 'AE9DFAA17A64A7CA8AC58A6F3F3DA05F182ADFFCB6CB2846CDD17ACE9C354AC9A8A69773EA34BF477C039F7501DC0CD4BD63C3FDDAE88EE2FEA0F11ABFDD7B06',
    ctid: 'C00C383300000001',
    date: 769430501,
    hash: '67276D156C9A3982AE7B05570E5356C27EB33F9B333BA0B163D4C89F6C5E6AC6',
    inLedger: 800819,
    ledger_index: 800819,
    meta: {
      AffectedNodes: [Array],
      TransactionIndex: 0,
      TransactionResult: 'tesSUCCESS',
      offer_id: '735CA229EF0588F7FE68F8ACBE66EE9B1AD129A238E89F27DB2798004DA6E44B'
    },
    validated: true
  },
  type: 'response'
}

result.meta.offer_id がこのオファーの id になります。後ほど使用します。

オファーリストの取得

オファーの作成に成功しているか確認してみましょう。オファーは object の一種のため、アカウントに紐づくオブジェクトを取得する account_objects で確認することができます。

import { Client, Wallet } from 'xrpl'

const secret = "sEdVEHTqHPGLY3c5FtsTrNZd2hPXkfF"
const wallet = Wallet.fromSeed(secret)

const client = new Client('wss://testnet.xrpl-labs.com')
await client.connect()

// オブジェクトを取得
const response = await client.request({
	command: 'account_objects',
	account: wallet.address
})
console.log(response.result)

コンソールに以下の内容が表示されます。

console.log(response.result)
{
  account: 'rfJsDzdaFpQvZpcvQfjPnByAFrYzJ4eGge',
  account_objects: [
    {
      Flags: 0,
      LedgerEntryType: 'NFTokenPage',
      NFTokens: [Array],
      PreviousTxnID: '0569CE01BCDE26C1D29F4D6D6510FB534D67DB5024D0785D40D0B586B97ACABA',
      PreviousTxnLgrSeq: 800689,
      index: '4509AEBD50EBDBD96645A1E9D9F3B757CE2FA0E3FFFFFFFFFFFFFFFFFFFFFFFF'
    },
    {
      Amount: '3',
      Flags: 1,
      LedgerEntryType: 'NFTokenOffer',
      NFTokenID: '000B27104509AEBD50EBDBD96645A1E9D9F3B757CE2FA0E3991B28320005D797',
      NFTokenOfferNode: '0',
      Owner: 'rfJsDzdaFpQvZpcvQfjPnByAFrYzJ4eGge',
      OwnerNode: '0',
      PreviousTxnID: '67276D156C9A3982AE7B05570E5356C27EB33F9B333BA0B163D4C89F6C5E6AC6',
      PreviousTxnLgrSeq: 800819,
      index: '735CA229EF0588F7FE68F8ACBE66EE9B1AD129A238E89F27DB2798004DA6E44B'
    }
  ],
  ledger_current_index: 800955,
  validated: false
}

account_objects[0] にある NFTokenPage は #3 で紹介した NFT を保持するオブジェクトです。そして account_objects[1] に先ほど作成したオファーが表示されています。index と先ほど発行したトランザクションにあった offer_id が一致しています。

オファーの受け入れ

それでは先ほど作成した売りオファーに対して、別のアカウント wallet2(rM1p399HpVEghUiQQHgkjQKaPHYg3uixB9) で NFTokenAcceptOffer を実行してみます。NFTokenSellOffer に offer_id をセットします。

import { Client, Wallet } from "xrpl";


const secret2 = "sEdTgr5hR6nztyr3wccXcuWpwvcaVis";

const wallet2 = Wallet.fromSeed(secret2);

const client = new Client("wss://testnet.xrpl-labs.com");
await client.connect();

const prepared = await client.autofill({
  Account: wallet2.address,
  NFTokenSellOffer: "735CA229EF0588F7FE68F8ACBE66EE9B1AD129A238E89F27DB2798004DA6E44B",
  TransactionType: "NFTokenAcceptOffer",
});
const signed = wallet2.sign(prepared);
const tx = await client.submitAndWait(signed.tx_blob);
console.log(tx);
console.log(tx)
{
  id: 10,
  result: {
    Account: 'rM1p399HpVEghUiQQHgkjQKaPHYg3uixB9',
    Fee: '12',
    Flags: 0,
    LastLedgerSequence: 843355,
    NFTokenSellOffer: '735CA229EF0588F7FE68F8ACBE66EE9B1AD129A238E89F27DB2798004DA6E44B',
    Sequence: 347878,
    SigningPubKey: 'EDFBF38FE259B4693CC712B1CA99ACB870ACEB0FFD5401BF4310E8A3B43C935E16',
    TransactionType: 'NFTokenAcceptOffer',
    TxnSignature: 'A7A1334A72E56BFEDE34A276044EEC0E08A94808009D315C3E6C4E53D3D28C3599B50C153C70541A04B64643B99C60861D632C90E27CC1C776E36690BF662807',
    ctid: 'C00CDE4900020001',
    date: 769565602,
    hash: 'A9A3B6FA929DF2EE1BD65304849D2432A6037529C81609A473CFBB333106F693',
    inLedger: 843337,
    ledger_index: 843337,
    meta: {
      AffectedNodes: [Array],
      TransactionIndex: 2,
      TransactionResult: 'tesSUCCESS',
      nftoken_id: '000B27104509AEBD50EBDBD96645A1E9D9F3B757CE2FA0E3991B28320005D797'
    },
    validated: true
  },
  type: 'response'
}

これで先ほど作成したオファーが成立し、NFT が wallet2 に送られました。
試しにそれぞれのウォレットの所有する NFT を表示してみます。

wallet1:
{
  account: 'rfJsDzdaFpQvZpcvQfjPnByAFrYzJ4eGge',
  account_nfts: [],
  ledger_hash: '05A80B0D187F49876408F603D715E01F742FC030C3AFF7EEDA31FD83CE699B7E',
  ledger_index: 801771,
  validated: true
}
------
wallet2:
{
  account: 'rM1p399HpVEghUiQQHgkjQKaPHYg3uixB9',
  account_nfts: [
    {
      Flags: 11,
      Issuer: 'rfJsDzdaFpQvZpcvQfjPnByAFrYzJ4eGge',
      NFTokenID: '000B27104509AEBD50EBDBD96645A1E9D9F3B757CE2FA0E3991B28320005D797',
      NFTokenTaxon: 0,
      TransferFee: 10000,
      URI: '68747470733A2F2F7872706C2E6F72672F6A612F',
      nft_serial: 382871
    }
  ],
  ledger_hash: 'F74A0E1044A17DAACE2680BF16D93C44580B1928302686175CC039763B47F9DC',
  ledger_index: 801772,
  validated: true
}

wallet1 から wallet2 に NFT が移動したことがわかります。

オファーのキャンセル

作成したオファーはもちろんキャンセルすることも可能です。NFTokenCancelOffer を利用します。

import { Client, Wallet, parseNFTokenID } from "xrpl";

const secret1 = "sEdVEHTqHPGLY3c5FtsTrNZd2hPXkfF";
const wallet1 = Wallet.fromSeed(secret1);

const client = new Client("wss://testnet.xrpl-labs.com");
await client.connect();

const NFTokenId =
  "000B27104509AEBD50EBDBD96645A1E9D9F3B757CE2FA0E35469B52F0005D794";
const NFToken = parseNFTokenID(NFTokenId)

const prepared = await client.autofill({
  Account: NFToken.Issuer,
  NFTokenOffers: ["7A36A0530EB2D3A832AC52263647BDD827C6E4D1428D0D96D6F95FA0BFE41553"],  // キャンセルしたい offer_id を指定
  TransactionType: "NFTokenCancelOffer",
});
const signed = wallet1.sign(prepared);
const tx = await client.submitAndWait(signed.tx_blob);
console.log(tx);

今回は NFT を送る、売買する方法を取り上げました。XRP Ledger では勝手にトークンが送られてくることがないので初心者でも安心ですね。ここでは取り上げていない機能もありますので、詳しくは公式ドキュメントをご覧ください。

Discussion