31 KiB
		
	
	
	
	
	
	
	
			
		
		
	
	html, parent, seo, filters, labels
| html | parent | seo | filters | labels | ||||
|---|---|---|---|---|---|---|---|---|
| monitor-incoming-payments-with-websocket.html | http-websocket-apis-tutorials.html | 
  | 
  | 
  | 
WebSocketを使用した着信ペイメントの監視
このチュートリアルでは、WebSocket rippled APIを使用して、着信ペイメントを監視する方法を説明します。すべてのXRP Ledgerトランザクションは公開されているため、誰もが任意のアドレスへの着信ペイメントを監視できます。
WebSocketは、クライアントとサーバーが1つの接続を確立し、その接続を経由して両方向にメッセージを送信するモデルに従います。この接続は、明示的に閉じる(または接続に障害が発生する)まで続きます。これは、リクエストごとにクライアントが新しい接続を開いて閉じるHTTPベースのAPIモデル(JSON-RPCやRESTful APIなど)とは対照的です¹。
ヒント: このページの例はJavaScriptを使用しているため、Webブラウザーでネイティブに実行できます。JavaScriptで開発している場合は、JavaScript向けxrpl.jsライブラリも利用すると、一部の作業を簡素化できます。このチュートリアルでは、xrpl.jsを使用できないその他のプログラミング言語にステップを適合できるよう、xrpl.jsを使用 しない でトランザクションを監視する方法を説明します。
前提条件
- このページの例では、すべての主要な最新ブラウザーで使用できるJavaScriptおよびWebSocketプロトコルを使用しています。JavaScriptにある程度習熟し、WebSocketクライアントを使用する他のプログラミング言語の専門知識があれば、選択する言語に手順を適合させながら進めていくことができます。
 - 安定したインターネット接続と
rippledサーバーへアクセスが必要です。埋め込まれている例では、Rippleの公開サーバーのプールに接続します。独自のrippledサーバーを運用する場合は、ローカルでそのサーバーに接続することもできます。 - 丸め方によるエラーを発生させることなくXRPの価値を適切に処理するには、64ビット符号なし整数で計算できる数値タイプを使用できる必要があります。このチュートリアルの例では、big.jsを使用しています。トークンを使用する場合は、さらに高い精度が求められます。詳細は、通貨の精度を参照してください。
 
1. XRP Ledgerへの接続
着信ペイメントを監視する最初のステップとして、XRP Ledger、つまりrippledサーバーに接続します。
以下のJavaScriptコードでは、Rippleの公開サーバーのクラスターの1つに接続します。その後、コンソールにメッセージを記録し、[pingメソッド][]を使用してリクエストを送信します。次に、サーバー側からのメッセージを受信するときに、ハンドラーを設定してコンソールに再度メッセージを記録します。
const socket = new WebSocket('wss://s.altnet.rippletest.net:51233')
socket.addEventListener('open', (event) => {
  // This callback runs when the connection is open
  console.log("Connected!")
  const command = {
    "id": "on_open_ping_1",
    "command": "ping"
  }
  socket.send(JSON.stringify(command))
})
socket.addEventListener('message', (event) => {
  console.log('Got message from server:', event.data)
})
socket.addEventListener('close', (event) => {
  // Use this event to detect when you have become disconnected
  // and respond appropriately.
  console.log('Disconnected...')
})
上記の例では、Test Net上にあるRippleの公開APIサーバーの1つに対して、安全な接続(wss://)を開きます。代わりにデフォルトの構成を使用してローカルで運用しているrippledサーバーに接続するには、最初の行に以下を使用して、ローカルのポート6006で 安全ではない 接続(ws://)を開きます。
const socket = new WebSocket('ws://localhost:6006')
ヒント: デフォルトでは、ローカルrippledサーバーに接続することで、インターネット上の公開サーバーに接続する際に使用できるパブリックメソッド以外に、すべての管理メソッドと、[server_info][server_infoメソッド]などの一部のレスポンスに含まれる管理者専用データを利用できます。
例:
{% interactive-block label="Connect" steps=$frontmatter.steps %}
Connect Connection status: Not connected
Console:
{% /interactive-block %}
<script type="application/javascript"> let socket; $("#connect-socket-button").click((event) => { socket = new WebSocket('wss://s.altnet.rippletest.net:51233') socket.addEventListener('open', (event) => { // This callback runs when the connection is open writeToConsole("#monitor-console-connect", "Connected!") $("#connection-status").text("Connected") const command = { "id": "on_open_ping_1", "command": "ping" } socket.send(JSON.stringify(command)) complete_step("Connect") $("#connect-button").prop("disabled", "disabled") $("#enable_dispatcher").prop("disabled",false) }) socket.addEventListener('close', (event) => { $("#connection-status").text("Disconnected") $("#connect-button").prop("disabled", false) }) socket.addEventListener('message', (event) => { writeToConsole("#monitor-console-connect", "Got message from server: " + JSON.stringify(event.data)) }) }) </script>2. ハンドラーへの着信メッセージのディスパッチ
WebSocket接続では、複数のメッセージをどちらの方向にも送信することが可能で、リクエストとレスポンスの間に厳密な1:1の相互関係がないため、各着信メッセージに対応する処理を識別する必要があります。この処理をコーディングする際の優れたモデルとして、「ディスパッチャー」関数の設定が挙げられます。この関数は着信メッセージを読み取り、各メッセージを正しいコードのパスに中継して処理します。メッセージを適切にディスパッチできるように、rippledサーバーでは、すべてのWebSocketメッセージでtypeフィールドを使用できます。
- 
クライアント側からのリクエストへの直接のレスポンスとなるメッセージの場合、
typeは文字列のresponseです。この場合、サーバーは以下も提供します。- 
このレスポンスに対するリクエストで指定された
idに一致するidフィールド(レスポンスが順序どおりに到着しない可能性があるため、これは重要です)。 - 
APIがリクエストの処理に成功したかどうかを示す
statusフィールド。文字列値successは、成功したレスポンスを示します。文字列値errorは、エラーを示します。警告: トランザクションを送信する際、WebSocketメッセージの先頭にある
successのstatusは、必ずしもトランザクション自体が成功したことを意味しません。これは、サーバーによってリクエストが理解されたということのみを示します。トランザクションの実際の結果を確認するには、トランザクションの結果の確認を参照してください。 
 - 
 - 
サブスクリプションからのフォローアップメッセージの場合、
typeは、新しいトランザクション、レジャーまたは検証の通知など、フォローアップメッセージのタイプを示します。または継続しているpathfindingリクエストのフォローアップを示します。クライアントがこれらのメッセージを受信するのは、それらをサブスクライブしている場合のみです。 
ヒント: JavaScript向けxrpl.jsは、デフォルトでこのステップに対応しています。すべての非同期APIリクエストはPromiseを使用してレスポンスを提供します。また.on(event, callback)メソッドを使用して、ストリームをリッスンできます。
以下のJavaScriptコードでは、APIリクエストを便利な非同期Promiseに変換するヘルパー関数を定義し、他のタイプのメッセージをグローバルハンドラーにマップするインターフェイスを設定します。
const AWAITING = {}
const handleResponse = function(data) {
  if (!data.hasOwnProperty("id")) {
    console.error("Got response event without ID:", data)
    return
  }
  if (AWAITING.hasOwnProperty(data.id)) {
    AWAITING[data.id].resolve(data)
  } else {
    console.error("Response to un-awaited request w/ ID " + data.id)
  }
}
let autoid_n = 0
function api_request(options) {
  if (!options.hasOwnProperty("id")) {
    options.id = "autoid_" + (autoid_n++)
  }
  let resolveHolder;
  AWAITING[options.id] = new Promise((resolve, reject) => {
    // Save the resolve func to be called by the handleResponse function later
    resolveHolder = resolve
    try {
      // Use the socket opened in the previous example...
      socket.send(JSON.stringify(options))
    } catch(error) {
      reject(error)
    }
  })
  AWAITING[options.id].resolve = resolveHolder;
  return AWAITING[options.id]
}
const WS_HANDLERS = {
  "response": handleResponse
  // Fill this out with your handlers in the following format:
  // "type": function(event) { /* handle event of this type */ }
}
socket.addEventListener('message', (event) => {
  const parsed_data = JSON.parse(event.data)
  if (WS_HANDLERS.hasOwnProperty(parsed_data.type)) {
    // Call the mapped handler
    WS_HANDLERS[parsed_data.type](parsed_data)
  } else {
    console.log("Unhandled message from server", event)
  }
})
// Demonstrate api_request functionality
async function pingpong() {
  console.log("Ping...")
  const response = await api_request({command: "ping"})
  console.log("Pong!", response)
}
pingpong()
{% interactive-block label="Dispatch Messages" steps=$frontmatter.steps %}
Enable Dispatcher Ping!
Responses
{% /interactive-block %}
<script type="application/javascript"> const AWAITING = {} const handleResponse = function(data) { if (!data.hasOwnProperty("id")) { writeToConsole("#monitor-console-ping", "Got response event without ID:", data) return } if (AWAITING.hasOwnProperty(data.id)) { AWAITING[data.id].resolve(data) } else { writeToConsole("#monitor-console-ping", "Response to un-awaited request w/ ID " + data.id) } } let autoid_n = 0 function api_request(options) { if (!options.hasOwnProperty("id")) { options.id = "autoid_" + (autoid_n++) } let resolveFunc; AWAITING[options.id] = new Promise((resolve, reject) => { // Save the resolve func to be called by the handleResponse function later resolveFunc = resolve try { // Use the socket opened in the previous example... socket.send(JSON.stringify(options)) } catch(error) { reject(error) } }) AWAITING[options.id].resolve = resolveFunc return AWAITING[options.id] } const WS_HANDLERS = { "response": handleResponse } $("#enable_dispatcher").click((clickEvent) => { socket.addEventListener('message', (event) => { const parsed_data = JSON.parse(event.data) if (WS_HANDLERS.hasOwnProperty(parsed_data.type)) { // Call the mapped handler WS_HANDLERS[parsed_data.type](parsed_data) } else { writeToConsole("#monitor-console-ping", "Unhandled message from server: " + event) } }) complete_step("Dispatch Messages") $("#dispatch_ping").prop("disabled", false) $("#tx_subscribe").prop("disabled", false) }) async function pingpong() { const response = await api_request({command: "ping"}) writeToConsole("#monitor-console-ping", "Pong! " + JSON.stringify(response)) } $("#dispatch_ping").click((event) => { pingpong() }) </script>3. アカウントのサブスクライブ
トランザクションがアカウントに影響を及ぼすたびに即座に通知を取得するには、[subscribeメソッド][]を使用してアカウントをサブスクライブします。実際には、このアカウントはあなた自身のアカウントでなくてもかまいません。すべてのトランザクションは公開されているため、任意のアカウントで、または複数のアカウントでもサブスクライブできます。
1つ以上のアカウントをサブスクライブした場合、指定したアカウントのいずれかに何らかの影響を及ぼす各検証済みトランザクションについて、"type": "transaction"が含まれるメッセージがサーバーから送信されます。これを確認するには、トランザクションメッセージで"validated": trueを探します。
以下のコードサンプルは、Test Net Faucetの送信側アドレスをサブスクライブします。このコードサンプルでは、前のステップのディスパッチャーにハンドラーを追加することで、該当する各トランザクションのメッセージを記録します。
async function do_subscribe() {
  const sub_response = await api_request({
    command:"subscribe",
    accounts: ["rUCzEr6jrEyMpjhs4wSdQdz4g8Y382NxfM"]
  })
  if (sub_response.status === "success") {
    console.log("Successfully subscribed!")
  } else {
    console.error("Error subscribing: ", sub_response)
  }
}
do_subscribe()
const log_tx = function(tx) {
  console.log(tx.transaction.TransactionType + " transaction sent by " +
              tx.transaction.Account +
              "\n  Result: " + tx.meta.TransactionResult +
              " in ledger " + tx.ledger_index +
              "\n  Validated? " + tx.validated)
}
WS_HANDLERS["transaction"] = log_tx
以下の例では、別のウィンドウまたは別のデバイスでTransaction Senderを開くことと、サブスクライブしているアドレスへのトランザクションの送信を試みます。
{% interactive-block label="Subscribe" steps=$frontmatter.steps %}
Subscribe
Transactions
{% /interactive-block %}
<script type="application/javascript"> async function do_subscribe() { const sub_address = $("#subscribe_address").val() const sub_response = await api_request({ command:"subscribe", accounts: [sub_address] }) if (sub_response.status === "success") { writeToConsole("#monitor-console-subscribe", "Successfully subscribed!") } else { writeToConsole("#monitor-console-subscribe", "Error subscribing: " + JSON.stringify(sub_response)) } } $("#tx_subscribe").click((event) => { do_subscribe() complete_step("Subscribe") $("#tx_read").prop("disabled", false) }) const log_tx = function(tx) { writeToConsole("#monitor-console-subscribe", tx.transaction.TransactionType + " transaction sent by " + tx.transaction.Account + "Result: " + tx.meta.TransactionResult + " in ledger " + tx.ledger_index + "
Validated? " + tx.validated) } WS_HANDLERS["transaction"] = log_tx </script>
4. 着信ペイメントの読み取り
アカウントをサブスクライブすると、 アカウントへのすべてのトランザクションとアカウントからのすべてのトランザクション 、および アカウントに間接的に影響を及ぼすトランザクション に関するメッセージが表示されます。この例として、トークンの取引があります。アカウントが着信ペイメントを受け取った日時を認識することを目的とする場合、トランザクションストリームを絞り込んで、実際に支払われた額に基づいて支払いを処理する必要があります。以下の情報を探します。
- 
validatedフィールドは、トランザクションの結果が最終的であることを示します。これは、accountsをサブスクライブする場合に常に当てはまりますが、accounts_proposedまたはtransactions_proposedストリーム も サブスクライブしている場合は、サーバーは未確認のトランザクションに関して同様のメッセージを同じ接続で送信します。予防策として、validatedフィールドを常に確認することをお勧めします。 - 
meta.TransactionResultフィールドは、トランザクションの結果です。結果がtesSUCCESSでない場合は、トランザクションは失敗したため、値を送信できません。 - 
transaction.Accountフィールドはトランザクションの送信元です。他の人が送信したトランザクションのみを探している場合は、このフィールドがあなたのアドレスと一致するトランザクションを無視できます(自身に対するクロスカレンシー支払いが 可能である 点に注意してください)。 - 
transaction.TransactionTypeフィールドはトランザクションのタイプです。アカウントに通貨を送金できる可能性があるトランザクションのタイプは以下のとおりです。- 
[Paymentトランザクション][] はXRPまたはトークンを送金できます。受取人のアドレスを含んでいる
transaction.Destinationフィールドによってこれらを絞り込み、必ずmeta.delivered_amountを使用して実際に支払われた額を確認します。XRPの額は、文字列のフォーマットで記述されます。警告: 代わりに
transaction.Amountフィールドを使用すると、Partial Paymentの悪用に対して脆弱になる可能性があります。不正使用者はこの悪用を行ってあなたをだまし、あなたが支払ったよりも多くの金額を交換または引き出すことができます。 - 
**[CheckCashトランザクション][]**では、アカウントは別のアカウントの[CheckCreateトランザクション][]によって承認された金額を受け取ることができます。CheckCashトランザクションのメタデータを確認すると、アカウントが受け取った通貨の額を確認できます。
 - 
[EscrowFinishトランザクション][] は、以前の[EscrowCreateトランザクション][]によって作成されたEscrowを終了することでXRPを送金できます。EscrowFinishトランザクションのメタデータを確認すると、escrowからXRPを受け取ったアカウントと、その額を確認できます。
 - 
[OfferCreateトランザクション][] はアカウントがXRP Ledgerの分散型取引所で以前発行したオファーを消費することで、XRPまたはトークンを送金できます。オファーを発行しないと、この方法で金額を受け取ることはできません。メタデータを確認して、アカウントが受け取った通貨(この情報がある場合)と、金額を確認します。
 - 
[PaymentChannelClaimトランザクション][] では、Payment ChannelからXRPを送金できます。メタデータを確認して、トランザクションからXRPを受け取ったアカウント(この情報がある場合)を確認します。
 - 
[PaymentChannelFundトランザクション][] は、閉鎖された(期限切れの)Payment Channelから送金元にXRPを返金することができます。
 
 - 
 - 
metaフィールドには、1つまたは複数の通貨の種類とその正確な金額、その送金先などを示すトランザクションメタデータが示されています。トランザクションメタデータを理解する方法の詳細は、トランザクションの結果の確認を参照してください。 
以下のサンプルコードは、上に示したすべてのトランザクションのタイプのトランザクションメタデータを確認し、アカウントが受け取ったXRPの金額をレポートします。
{% code-snippet file="/_code-samples/monitor-payments-websocket/js/read-amount-received.js" language="js" /%}
{% interactive-block label="Read Payments" steps=$frontmatter.steps %}
Start Reading
Transactions
{% /interactive-block %}
<script type="application/javascript"> function CountXRPDifference(affected_nodes, address) { // Helper to find an account in an AffectedNodes array and see how much // its balance changed, if at all. Fortunately, each account appears at most // once in the AffectedNodes array, so we can return as soon as we find it. // Note: this reports the net balance change. If the address is the sender, // any XRP balance changes combined with the transaction cost. for (let i=0; i { // Wrap the existing "transaction" handler to do the old thing and also // do the CountXRPReceived thing const sub_address = $("#subscribe_address").val() const old_handler = WS_HANDLERS["transaction"] const new_handler = function(data) { old_handler(data) CountXRPReceived(data, sub_address) } WS_HANDLERS["transaction"] = new_handler // Disable the button so you can't stack up multiple levels of the new handler $("#tx_read").prop("disabled", "disabled") complete_step("Read Payments") }) </script>次のステップ
- トランザクションの結果の確認で、トランザクションの実行内容を確認し、適切に対応するソフトウェアを構築します。
 - あなた自身のアドレスからXRPの送金を試します。
 - Escrow、ChecksまたはPayment Channelのような高度なタイプのトランザクションの監視と着信通知へのレスポンスを試します。
 
その他のプログラミング言語
多くのプログラミング言語には、WebSocket接続を使用して、データの送受信を行うためのライブラリが用意されています。JavaScript以外の言語でrippledのWebSocket APIとの通信を効率良く始めるには、同様な機能を利用している以下の例を参考にしてください。
{% tabs %}
{% tab label="Go" %} {% code-snippet file="/_code-samples/monitor-payments-websocket/go/monitor-incoming-payments.go" language="go" /%} {% /tab %}
{% /tabs %}
ヒント: 目的のプログラミング言語の例がない場合があります。このページの最上部にある「GitHubで編集する」リンクをクリックして、作成したサンプルコードを提供してください。
脚注
1.実際には、HTTPベースのAPIを何度も呼び出す場合、クライアントとサーバーは複数のリクエストとレスポンスを処理する際に同じ接続を再利用できます。この方法は、HTTP永続接続、またはキープアライブと呼ばれます。開発の観点から見ると、基本となる接続が新しい場合でも、再利用される場合でも、HTTPベースのAPIを使用するコードは同じです。
関連項目
- コンセプト:
- トランザクション
 - 結果のファイナリティー - トランザクションの成功また失敗が最終的なものとなるタイミングを判断する方法(簡単な説明: トランザクションが検証済みレジャーにある場合は、その結果とメタデータは最終的なものです)。
 
 - チュートリアル:
 - リファレンス:
- トランザクションのタイプ
 - トランザクションのメタデータ - メタデータフォーマットとメタデータに表示されるフィールドの概要
 - トランザクションの結果 - トランザクションのすべての結果コードを掲載した表一覧
 
 
{% raw-partial file="/_snippets/common-links.md" /%}