XRPレジャーのトランザクション結果をプレビューする
XRP Ledger
XRP Ledgerは、分散型のパブリックブロックチェーンです。XRP Ledgerは、DEX機能やNFT機能をはじめとした様々なネイティブ機能を備えており、日々進化を続けています。
XRPLのトランザクション
XRPLにおいてユーザ/アカウントが送信するトランザクションは次のようなJSON形式で表されます。
{
"TransactionType": "Payment",
"Account": "r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59",
"Destination": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000",
"Flags": 2147483648,
"LastLedgerSequence": 8804620,
"Fee": "12",
"Sequence": 1
}
トランザクションのうち、AccountSetやNFTokenMintなどの一部のTransactionTypeの実行結果はある程度予測可能ですが、特にDEXやAMMなどを利用する一部のトランザクションの実行結果は予測が難しくなっています。
トランザクションのプレビュー
現在XLS-69dとしてSimulate
RPCが提案されており、RPC送信時点のレジャーデータを利用しトランザクションの実行結果をプレビューすることが可能となります。
このRPCではトランザクションの成功/失敗だけでなく、実行によりどのようにレジャーデータが変更されたかを表すメタデータも取得することが可能です。
ただし、RPC実行時と実際のトランザクション送信時の時間差やその他の要因により、このRPCによるプレビュー内容は実際にトランザクションを送信する際の結果と異なる可能性があるため注意が必要です。
試してみる
本記事執筆時点でSimulate
RPCは現在開発中であり、テストネット等でもまだ利用出来ませんが、ローカルでビルドし試してみることが可能です。
ビルド方法については割愛します。
Simulate
RPCはリクエストパラメータにcommand: "simulate"
を指定、そしてtx_json
にトランザクションのJSONを指定します。
tx_json
フィールドに指定するトランザクション情報ではFee
やSequence
などの自動入力可能なフィールドは省略することが可能です。
以下はSimulate
RPCのリクエスト例(WebSocket)です。
{
command: 'simulate',
tx_json: {
TransactionType: 'Payment',
Account: "rQQQrUdN1cLdNmxH4dHfKgmX5P4kf3ZrM",
Destination: "r9fVvKgMMPkBHQ3n28sifxi22zKphwkf8u"
Amount: "1000000"
},
}
AccountSet
トランザクション
最もシンプルなAccountSet
トランザクションをプレビューしてみます。
import { Client, ECDSA, Wallet } from 'xrpl'
const client = new Client('ws://localhost:6006')
const account = Wallet.fromSecret('snoPBrXtMeMyMHUVTgbuqAfg1SUTb', { algorithm: ECDSA.secp256k1 }) // genesis account
const main = async () => {
await client.connect()
const response = await client.request({
command: 'simulate',
tx_json: {
TransactionType: 'AccountSet',
Account: account.address,
},
})
const { tx_json, meta, engine_result } = response.result
console.log(tx_json)
console.log(JSON.stringify(meta, null, 2))
}
main()
処理されたtx_json(幾つかの不足フィールドは自動入力されます。)
{
Account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
Fee: '10',
Sequence: 1,
SigningPubKey: '',
TransactionType: 'AccountSet',
TxnSignature: '',
hash: '2209360D0FBB6D2370F6F576255032B15AAD5688584828C68EC081D12E545CE1'
}
処理されたトランザクションのメタデータ
{
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Balance": "99999999999999990",
"Flags": 0,
"OwnerCount": 0,
"Sequence": 2
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8",
"PreviousFields": {
"Balance": "100000000000000000",
"Sequence": 1
}
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS"
}
OfferCreate
トランザクション
DEX取引を行う複雑な処理を行うトランザクションの1つであり、DEXを使ったトークンの売買を行えるOfferCreate
トランザクションをプレビューしてみます。
今回の例では取引板が1つのみのため計算は可能ですが、メインネットでは取引板には数多くの注文が存在するため、実際にどれほどが約定し、どのような結果になるかは予測が難しいです。
しかし、Simulate
RPCを利用することで簡単にこれらの結果を予測することが可能になるのです。
import { Client, ECDSA, Wallet } from 'xrpl'
const client = new Client('ws://localhost:6006')
const account = Wallet.fromSecret('snoPBrXtMeMyMHUVTgbuqAfg1SUTb', { algorithm: ECDSA.secp256k1 }) // genesis account
const issuer = Wallet.fromSecret('sEdTpvPK4rbWSoQyfApQt4TkeJAWkeC')
const prepare = async () => {
// issuerアカウントの有効化
await client.submit(
{
TransactionType: 'Payment',
Account: account.address,
Destination: issuer.address,
Amount: '100000000',
},
{ wallet: account },
)
await client.request({ command: 'ledger_accept' })
// issuerによるUSD/XRPの注文作成
await client.submit(
{
TransactionType: 'OfferCreate',
Account: issuer.address,
TakerPays: '60000000',
TakerGets: {
currency: 'USD',
issuer: issuer.address,
value: '100',
},
},
{ wallet: issuer },
)
await client.request({ command: 'ledger_accept' })
}
const main = async () => {
await client.connect()
await prepare()
const response = await client.request({
command: 'simulate',
tx_json: {
TransactionType: 'OfferCreate',
Account: account.address,
TakerGets: '600000',
TakerPays: {
currency: 'USD',
issuer: issuer.address,
value: '1',
},
},
})
const { tx_json, meta, engine_result } = response.result
console.log(tx_json)
console.log(JSON.stringify(meta, null, 2))
}
main()
{
Account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
Fee: '10',
Sequence: 2,
SigningPubKey: '',
TakerGets: '600000',
TakerPays: {
currency: 'USD',
issuer: 'rtyqWbUwB3aH25dkvM2m2x22nepD3zvb3',
value: '1'
},
TransactionType: 'OfferCreate',
TxnSignature: '',
hash: '5598FFE6E73299212AE2DFB277174B2B8F9522B77655EEA1149AEFFD2C8F9AF3'
}
{
"AffectedNodes": [
{
"ModifiedNode": {
"FinalFields": {
"Account": "rtyqWbUwB3aH25dkvM2m2x22nepD3zvb3",
"Balance": "100599988",
"Flags": 0,
"OwnerCount": 1,
"Sequence": 4
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "1A3CCA1775ECD91A53B3B8AEB1FE9F3FED70C86BF2406C2FEB65E6E1AA55229F",
"PreviousFields": {
"Balance": "99999988"
},
"PreviousTxnID": "68EEB4DE72AFA91C91A91E0A1FCF7AED37DAC77CE9F7A7FEC8FEEEA879AD65C7",
"PreviousTxnLgrSeq": 4
}
},
{
"ModifiedNode": {
"FinalFields": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Balance": "99999999899399978",
"Flags": 0,
"OwnerCount": 1,
"Sequence": 3
},
"LedgerEntryType": "AccountRoot",
"LedgerIndex": "2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8",
"PreviousFields": {
"Balance": "99999999899999988",
"OwnerCount": 0,
"Sequence": 2
},
"PreviousTxnID": "C8E9E3CA95CCF6491644C59B2255731703AD97317884CB39071350F5D716C601",
"PreviousTxnLgrSeq": 3
}
},
{
"ModifiedNode": {
"FinalFields": {
"Flags": 0,
"Owner": "rtyqWbUwB3aH25dkvM2m2x22nepD3zvb3",
"RootIndex": "848B8D3D67D7FD255708A2D18D039F8E12096061666B2CE28A4AB6B13794067D"
},
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "848B8D3D67D7FD255708A2D18D039F8E12096061666B2CE28A4AB6B13794067D"
}
},
{
"ModifiedNode": {
"FinalFields": {
"Account": "rtyqWbUwB3aH25dkvM2m2x22nepD3zvb3",
"BookDirectory": "E3AA7E0781A80DD7DEB69927BF83488DB977F387D83321CC5A1550F7DCA70000",
"BookNode": "0",
"Flags": 0,
"OwnerNode": "0",
"Sequence": 3,
"TakerGets": {
"currency": "USD",
"issuer": "rtyqWbUwB3aH25dkvM2m2x22nepD3zvb3",
"value": "99"
},
"TakerPays": "59400000"
},
"LedgerEntryType": "Offer",
"LedgerIndex": "A976EA16B7A0F1B067957B2F161E6EC697E92B81782922AA980C62CCA9BC4A1D",
"PreviousFields": {
"TakerGets": {
"currency": "USD",
"issuer": "rtyqWbUwB3aH25dkvM2m2x22nepD3zvb3",
"value": "100"
},
"TakerPays": "60000000"
},
"PreviousTxnID": "68EEB4DE72AFA91C91A91E0A1FCF7AED37DAC77CE9F7A7FEC8FEEEA879AD65C7",
"PreviousTxnLgrSeq": 4
}
},
{
"CreatedNode": {
"LedgerEntryType": "DirectoryNode",
"LedgerIndex": "D8120FC732737A2CF2E9968FDF3797A43B457F2A81AA06D2653171A1EA635204",
"NewFields": {
"Owner": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"RootIndex": "D8120FC732737A2CF2E9968FDF3797A43B457F2A81AA06D2653171A1EA635204"
}
}
},
{
"CreatedNode": {
"LedgerEntryType": "RippleState",
"LedgerIndex": "E63B0DCAFB5380FE24DC472A0FA156D66AF42D5CECA3DA2D21471FCA94F2D75B",
"NewFields": {
"Balance": {
"currency": "USD",
"issuer": "rrrrrrrrrrrrrrrrrrrrBZbvji",
"value": "-1"
},
"Flags": 3276800,
"HighLimit": {
"currency": "USD",
"issuer": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"value": "0"
},
"LowLimit": {
"currency": "USD",
"issuer": "rtyqWbUwB3aH25dkvM2m2x22nepD3zvb3",
"value": "0"
}
}
}
}
],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS"
}
AffectedNodes
オブジェクトを見てみると、新規トラストラインが作成されたり、アカウントの残高が増減したりしていることが確認できます。
まとめ
Simulate
RPCはトランザクションのプレビューを行うための非常に便利なRPCです。本記事で説明したDEX取引だけでなく、AMMプールへの入出金や開発中のBatchトランザクションなどでも利用することができ、複雑化する処理も簡単に予測することが可能です。
また、新機能開発時にも非常に有用であり、同一のレジャーデータを手動テスト実行毎に毎回確認する必要がなくなったり、特定条件下でのトランザクションの挙動を反復的に確認することが可能となります。
興味を持たれた方はXRP Ledger開発者のDiscordチャンネルへ是非お越しください!
日本語チャンネルもありますので、英語ができなくても大丈夫です!
また、XRPL JapanのDiscordサーバもありますので、こちらもぜひご参加ください!
私のX/Twitterアカウントはこちら!
Discussion