🎏

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

2024/05/05に公開

本記事の目的

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

XRP Ledger の基本知識

#1 をご覧ください。
https://zenn.dev/taka101/articles/21d1d4cefdc1b7

開発の準備

ネットワークの種類

EVM XRP Ledger
Mainnet, Testnet Mainnet, Testnet, Devnet

EVM と同じくメインネットとテストネットが存在します。また、近くリリース予定の機能が有効になっている Devnet も存在します。
https://xrpl.org/ja/docs/concepts/networks-and-servers/parallel-networks/

テストネット上での XRP は faucet から無償で取得することが可能です。いくつか取得方法はありますが、例えば以下のページにある「Generate Testnet credentials」ボタンをクリックすると、まずウォレットアドレスと秘密鍵が生成され、数秒待つと残高(Balance)も表示され100 XRP が生成したウォレットに付与されたことが確認できます。 ここで生成したアカウントを後ほど使用します。
https://xrpl.org/resources/dev-tools/xrp-faucets/

また、それぞれの公開サーバ(パブリックノード)の URL も公開されています。
https://xrpl.org/ja/docs/tutorials/public-servers/

クライアントライブラリ

EVM XRP Ledger
Ethers, web3.js など xrpl.js, xrpl-client, xrpl-accountlib など

EVM と同じくクライアントライブラリがいくつか存在します。(具体的な使用方法は後ろのセクションで取り上げます。)
本記事では xrpl.js のみを取り扱いますが Python, Java など他の言語のものも用意されています。詳細は以下のドキュメントをご覧ください。
https://xrpl.org/ja/docs/references/client-libraries/

サーバとの通信(API)

EVM XRP Ledger
Ethereum JSON-RPC methods Public API Methods, Admin API Methods, Peer Port Methods

サーバとやりとりするための API が公開されています。いくつか種類があり、それぞれ以下のような棲み分けのようです。アプリ開発では基本的に Public API Methods(パブリック API)を利用することになります。

Public API Methods

XRP Ledger サーバと通信するためのメソッド。開発時は基本的にこれを利用することとなります。
EVM と同じように用途に応じてさまざまなメソッドが用意されています。個人的には EVM よりも整理されているように感じます。こちらのページに一覧が用意されています。
https://xrpl.org/ja/docs/references/http-websocket-apis/public-api-methods/

Admin API Methods

rippledサーバと直接通信するためのメソッド。信頼できるサーバ運用担当者のみが実行可能。サーバの管理、監視、デバッグのためのコマンドが存在します。

Peer Port Methods

ヘルスチェックや、ネットワークトポロジーの取得のための特別なメソッド。

実装例

ここからは実装例を交えながら挙動を見ていこうと思います。ここでは実装に xrpl.js(3.0.0)を利用します。また、nodejs のバージョンは 16 以降としてください。推奨は 16 のようです。

アカウント(ウォレット)の残高の取得

アカウントの残高の取得には、先ほど紹介したパブリック API であるアカウントメソッドの中の一つ、account_infoを利用します。

import { Client, Wallet, xrpToDrops } from 'xrpl'

// 秘密鍵からアカウントを作成
const secret = "sEdVFtbyUknJKfeFULoeDpf2s3SZcav"  // 必要に応じて置き換えてください
const wallet = Wallet.fromSeed(secret)

// 公開サーバーに接続
const client = new Client('wss://testnet.xrpl-labs.com')
await client.connect()

// 残高を取得
const response = await client.request({
	command: 'account_info',
	account: wallet.address
})
console.log(response.result)

Ethers や web3.js とほぼ同じように実装することが可能です。ここでは WebSocket で実装をしています。JSON-RPC でも実装は可能ですが、リクエストフォーマットが若干異なることに注意してください。
上記のコードを実行すると以下の内容がコンソールに表示されます。

response.result
{
  account_data: {
    Account: 'rNhVg4LxQaVYxRjSRvGsY3HdHQZuoZfbug',
    Balance: '100000000',
    Flags: 0,
    LedgerEntryType: 'AccountRoot',
    OwnerCount: 0,
    PreviousTxnID: '44190B7460EBD8386AC452755198E2FBC90985D29937AF9D5225C9BE3544112A',
    PreviousTxnLgrSeq: 379744,
    Sequence: 379744,
    index: '9C3275ED5A090A4EB45B6FEEB6495B28AA72E75A48EAEF6477ECFB1EC5699873'
  },
  account_flags: {
    allowTrustLineClawback: false,
    defaultRipple: false,
    depositAuth: false,
    disableMasterKey: false,
    disallowIncomingCheck: false,
    disallowIncomingNFTokenOffer: false,
    disallowIncomingPayChan: false,
    disallowIncomingTrustline: false,
    disallowIncomingXRP: false,
    globalFreeze: false,
    noFreeze: false,
    passwordSpent: false,
    requireAuthorization: false,
    requireDestinationTag: false
  },
  ledger_current_index: 379752,
  validated: false
}

詳細はドキュメントに譲ることとしここでは割愛しますが、account_data.Balance に100000000と表示されています。EVM の wei のように、XRP Ledger の世界では単位に "drop" を使用します。1 XRP = 1,000,000 drop のため、このアカウントは100 XRP を持っていることがわかります。

XRP 送金トランザクションの実行

import { Client, Wallet, xrpToDrops } from 'xrpl'

const secret1 = "sEdVFtbyUknJKfeFULoeDpf2s3SZcav"
const secret2 = "sEdTgr5hR6nztyr3wccXcuWpwvcaVis"

const wallet1 = Wallet.fromSeed(secret1)
const wallet2 = Wallet.fromSeed(secret2)

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

// XRP を送金
const prepared = await client.autofill({
	"TransactionType": "Payment",
	"Account": wallet1.address,
	"Amount": xrpToDrops("22"),  // drop 単位に変換
	"Destination": wallet2.address  // 送金先アドレスを指定
})
const signed = wallet1.sign(prepared)
const tx = await client.submitAndWait(signed.tx_blob)
console.log(tx)

wallet1 から wallet2 に22 XRP(22,000,000 drop)を送金するコードです。実行したいトランザクションの内容に対して wallet でサインし、ネットワークにトランザクションを送信をしています。こちらもコーディング内容は EVM とあまり変わりません。
client.autofill() を使用することで、EVM の Gas にあたる Fee フィールドなど一部のフィールドを自動で埋めてくれます。これらの情報はネットワークから取得する必要があるためネットワークに繋がっていない状態では埋めてくれません。(実際は submitAndWait の中で autofill も行ってくれるため明示的に行う必要はありません。詳しくはドキュメントをご覧ください。)
実行すると以下の内容がコンソールに表示されます。

tx
{
  id: 10,
  result: {
    Account: 'rw97tJMpQ5e1Sceq236M7di4wsS8RJZYx7',
    Amount: '22000000',
    DeliverMax: '22000000',
    Destination: 'rM1p399HpVEghUiQQHgkjQKaPHYg3uixB9',
    Fee: '12',
    Flags: 0,
    LastLedgerSequence: 381800,
    Sequence: 336343,
    SigningPubKey: 'ED7D9EE060A38A968CDF5E091288BE014FDEA79742A71C65C4649088F364F3D1F5',
    TransactionType: 'Payment',
    TxnSignature: '3D26E6943A24169228E4294D979BA013B1FFA69E471EB127F739A77504F780533B9A7B94DB41F424E91373A1F29DBAF67CC059E500EEFE83C433EE001C8B9A03',
    ctid: 'C005D35600000001',
    date: 768113583,
    hash: '8BAD6B300CD20B79B4BFA7AADCE58135FA6FD7A6A2828B59C3BB5018F9A1266F',
    inLedger: 381782,
    ledger_index: 381782,
    meta: {
      AffectedNodes: [Array],
      TransactionIndex: 0,
      TransactionResult: 'tesSUCCESS',
      delivered_amount: '22000000'
    },
    validated: true
  },
  type: 'response'
}

トランザクションの内容です。result.validated が true であることからこのトランザクションが検証済みレジャーに取り込まれた(つまり以降結果が変更されることはない)ことがわかり、result.meta.TransactionResult が tesSUCCESS になっていることからこのトランザクションが成功したことがわかります。

ウォレット残高の取得と組み合わせると、正しく送金できていることがよりわかります。

送金前後の残高を表示するコード
import { Client, Wallet, xrpToDrops } from 'xrpl'


const secret1 = "sEdVEHTqHPGLY3c5FtsTrNZd2hPXkfF"
const secret2 = "sEd77at3pqYjXisD37H36zcJ41hEMyk"

const wallet1 = Wallet.fromSeed(secret1)
const wallet2 = Wallet.fromSeed(secret2)

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

// 送金前の残高を取得
const response1b = await client.request({
	command: 'account_info',
	account: wallet1.address
})
console.log("from", response1b.result.account_data.Balance)

const response2b = await client.request({
	command: 'account_info',
	account: wallet2.address
})
console.log("to", response2b.result.account_data.Balance)
console.log("-------------------")

// XRP を送金
const prepared = await client.autofill({
	"TransactionType": "Payment",
	"Account": wallet1.address,
	"Amount": xrpToDrops("22"),  // drop 単位に変換
	"Destination": wallet2.address  // 送金先アドレスを指定
})
const signed = wallet1.sign(prepared)
const tx = await client.submitAndWait(signed.tx_blob)
console.log(tx)

console.log("-------------------")

// 送金後の残高を取得
const response1a = await client.request({
	command: 'account_info',
	account: wallet1.address
})
console.log("from", response1a.result.account_data.Balance)

const response2a = await client.request({
	command: 'account_info',
	account: wallet2.address
})
console.log("to", response2a.result.account_data.Balance)

コンソール
from 100000000
to 100000000
-------------------
{
  id: 12,
  result: {
    Account: 'rfJsDzdaFpQvZpcvQfjPnByAFrYzJ4eGge',
    Amount: '22000000',
    DeliverMax: '22000000',
    Destination: 'rMaiu8sYkyoXXxZ6jQiHNzZrzBEWPoBj6W',
    Fee: '12',
    Flags: 0,
    LastLedgerSequence: 382891,
    Sequence: 382866,
    SigningPubKey: 'ED65B4987E440C89D48A7F0B2B21E63BE986B2C5652B2D6C1A1D30693DB029609D',
    TransactionType: 'Payment',
    TxnSignature: 'D63C62EAE0EF6469EDFE1E06C504C14F864B6EFED84371B215682037DB1E45E1F9F7D5E298DA3E041453632202A29D0AF4E9B5A78BABF2DC9EA7F73538B05906',
    ctid: 'C005D79900000001',
    date: 768116880,
    hash: 'E3533A865DE2DE2B5D2DC71F08B6A796F9FAEB6FABEFC70174313C63E091E611',
    inLedger: 382873,
    ledger_index: 382873,
    meta: {
      AffectedNodes: [Array],
      TransactionIndex: 0,
      TransactionResult: 'tesSUCCESS',
      delivered_amount: '22000000'
    },
    validated: true
  },
  type: 'response'
}
-------------------
from 77999988
to 122000000

ここでは EVM と比較しながら開発に必要なライブラリなどの紹介と、実際にウォレット残高の取得、送金トランザクションの発行を試しました。EVM と同じような感じで簡単に実装できることが確認できたと思います。
今回取り上げていない API レスポンスの項目やその他のパブリック API のメソッドなどは公式ドキュメントtequさんの投稿などをご覧ください。
次回は EVM と比較しながら NFT 関連のトランザクションを取り上げようと思います。#1, #2 の内容は EVM とあまり変わりはありませんでしたが、NFT 関連は EVM とは異なる部分がそれなりに存在します。

Discussion