EVM と比較しながら XRP Ledger を理解する #4
本記事の目的
Ethereum Virtual Machine(EVM) の開発経験や知識は多少あるが XRP のことは全く知らない人を主な対象とし、EVM と比較しながら XRP Ledger を理解するシリーズです。コンセンサスアルゴリズムなどの理論にはあまり焦点を当てず、アプリ開発の中で必要になりそうな部分を中心に EVM と比較しながらコンパクトにまとめていこうと思います。
- #1 XRP Ledger の基本知識
- #2 ウォレットの残高取得、XRP の送金
- #3 NFT(SBT) の発行
- #4 NFT の送信、売買 👈今ここ
- #5 マーケットプレイスで NFT を購入
- #6 マスターキーとレギュラーキー
- #7 エスクロー決済
- #8 小切手での決済
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: '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を設定します。
実行すると以下の内容がコンソールに表示されます。
{
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)
コンソールに以下の内容が表示されます。
{
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);
{
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