mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-21 04:05:49 +00:00
* Update look up escrows to remove redundant info about lookups via sender/destination. Modify cancel expired escrow for brevity. * Cancel escrow: fix notes * Add draft of updated cancel-escrow.js. * Update intro to escrows. * Add Escrow Tutorial * Minor corrections * Fix headings, add HTML * Update escrow docs This commit re-createsf205a92db2with some adjustments: - Omit the accidentally-created dir full of junk - Fix some typos and one mistake in the Escrow limitations section - Add a table to the EscrowCreate ref to clarify valid combos of fields. * Concept info from send-a-time-held-escrow added to escrow.md * IA: Move "Consensus Network" files This re-creates some work from the original commit56fffe0b9f* Rewrite escrows article (re-created) This commit re-creates relevant work from the following commits:9a4a588f2bUpdate escrow.md context infoe1b017dc83Remove references to using escrow for interledger payments. * IA: Move "XRPL servers" files This re-creates some work from original commit7611979abf* IA: move "production readiness" files. Re-creates work from the following commit:692438693aMove tutorials to concepts * New intro articles Original commit:56fffe0b9f* IA: Reorg account concepts Re-creates some work from original commit56fffe0b9f* IA: reorg transaction concepts Original commits:9d4eff9940WIP - reorg accounts7611979abfWIP dir. reorg * IA: reorg consensus concepts Original commit:56fffe0b9f* IA: Reorg ledger docs Original commit:56fffe0b9f- Rephrased some details of the section * IA: rename issuing/operational addresses page Original commit:56fffe0b9f* Moving use cases * Fleshing out Use Cases Note, the dactyl-config.yml file has not been fully updated. * Clean up checks conceptual info. * Remove redundant checks use case section Original commit:3c29e9c05e* IA: move Dex under tokens Original commit:d08b3ba7d7* Touch up stablecoin issuer use case (#1856) * Consolidate stablecoin use case * Stablecoin issuer: cleanup progress through sending * Stablecoin issuer: reorg second half (Note: the dactyl-config.yml is not fully reconciled yet) * Move rippled and clio tutorials into infrastructure * Remove link to checks amendement. * Add note to account_objects.md about commandline interface type field. * Merge expiration case with lifecycle section. * Interoperability Use Cases * Add graphics to intro * Move escrow use cases to dedicated page. * Update use case page intros and corresponding concept info. * Clarify meaning of direct XRP payments. * Intro link updates * Payment use cases * Remove some unnecessary links in transactions section Original commit:e6fcf4a4dc* Link cleanup in Tokens section Original commit:9588dd5e70* Touch up 'Configure Peering' section Original commit:fc8f0990b8* Clean up links in accounts section Original commit:3da5fde7a8* Add NFT mkt use case * p2p payments: edits to Wallets * Clean up payments use cases * Refine history description * IA: use case cleanup * IA: reconcile servers, ledgers sections * IA: reconcile payment types, tx, tokens * IA: reconcile accounts section * IA: reconcile infra * IA: Fix most broken links * Full Docs Index: omit from sidebar * IA: fix up most broken links * fix Absolute path link to internal content * Quick updates to Software Ecosystem * Remove some absolute links to internal resources * Fix remaining broken links in JA target * Contributing: tweak formatting * Tutorials: fix some minor issues * remove interop use cases * remove intro image and personal references to dennis * alphabetize-transaction-nav * Remove unused files * Add QS escrow tutorials * IA: move ledgers, consensus protocol files around * IA: update nav for new page hierarchy * reordering of topics under new networks and servers top-nav * Move "Naming" to "What is XRP?" * Update dactyl-config.yml Remove xrp.md from the TOC. * Update list-xrp-as-an-exchange.md Update link to what-is-xrp * Update list-xrp-as-an-exchange.ja.md Change link to what-is-xrp * Update currency-formats.md Change link to what-is-xrp * Update currency-formats.ja.md Change link to what-is-xrp * Update cancel-an-expired-escrow.md Change link to what-is-xrp * Update paymentchannelfund.md Change link to what-is-xml * Update look-up-escrows.md Change link to what-is-xrp * Update tokens.md change link to what-is-xrp * Update use-payment-channels.md * Update send-a-time-held-escrow.md Update link to what-is-xml * fix broken links * Update parallel-networks.md Change link to what-is-xml * Update parallel-networks.ja.md * Update invariant-checking.md Remove link to xrp.html * Update invariant-checking.ja.md Remove link to xrp.html * Update transaction-cost.md Change link to what-is-xrp * Update transaction-cost.ja.md Change link to what-is-xrp * Update send-a-conditionally-held-escrow.md Change link to what-is-xrp * Update stablecoin-issuer.md Change link to what-is-xrp * Update tokens.ja.md Change link to what-is-xml * Update autobridging.ja.md Change link to what-is-xrp * Update currency-formats.md update text * reorganize infrastructure nav section * Update currency-formats.md Try removing link altogether. * Update currency-formats.ja.md Remove link to what-is-xrp.html * move commandline usage topic to infrastructure * initial intro rewrite * minor update to language * IA.v3: rm Production Readiness * Delete xrp.md * Update xrp link in snippet * Add redirect for old xrp.html URL * Small edits to 'What is XRP?' article * Add missing imgs * XRP - copy edit per @DennisDawson * restructure tutorials nav and pages * fix broken links * more broken link fixes * Algo trading: 1st draft * Algo trading: notes on taxes * Algo trading: edits per review * algo trading: fix broken link * Ledger structure: rewrite for accuracy and clarity * Update links to removed 'tree format' header * Ledger Structure: Update diagrams * Re-gen CSS for ledger structure changes * Ledger structure: edits per review * IA.v3: fix broken NFT links introduced by rebase * Desktop Wallet (py): update little stuff * Update some capacity/storage details * contribute doc nav update * fix image link in create diagram page * IAv3: Fix 'Ledgers' blurb * Update full history requirements with details from community members * add reviewer suggestions * Edits per @trippled review * Apply suggestions from peer review Co-authored-by: oeggert <117319296+oeggert@users.noreply.github.com> * FH: reword file size limit note per review * Update software ecosystem * updates per review * Minor tweaks to graphics * fixTypos * Update content/concepts/introduction/software-ecosystem.md Co-authored-by: Amarantha Kulkarni <amarantha-k@users.noreply.github.com> * Update content/concepts/introduction/software-ecosystem.md Co-authored-by: Amarantha Kulkarni <amarantha-k@users.noreply.github.com> * [JA] update AccountDelete cost * custom transactors doc * add doc to dactyl config * [JA] fix NonFungibleTokensV1_1 amendment status * [JA] update NFTokenOffer page * Remove old, unused XRP article (#2039) * add reviewer suggestions * Add tooling to check for file/nav consistency - From the repo top, run tool/check_file_consistency.py to look for Markdown files that exist in the "content/" directory but aren't used in the documentation. - New "enforce_filenames" filter prints a warning to console when building, if a file's path and filename don't match expectations based on its place in the nav and top heading. * File consistency checker: correctly handle filenames starting in _ * Remove unused old 'get started' and associated code * Create Resources section & reorg some files - Rename some files/folders based on their place in the nav - Move a bunch of non-documentation stuff, and docs on contributing code and/or docs to the new "Resources" section. - Known issue: nav spills into a second row on page widths between 993px-1110px. To be fixed in a later CSS update, maybe along with making the Resources dropdown multi-column. * Fix #2078 code tab bug CSS not built yet, to reduce merge conflicts. Won't have any effect until that happens. * fix Transaction JSON * [JA] translate contributing contents * fix contributing-to-documentation parent * fix contribute-code blurb * Top nav: add cols for Resources, fix broken links * CSS: fix top nav overflows * Fix broken link from redirect not in JA target * Top nav: add Infra to article types * Update contrib info & rename intro file * [ja] Update link to suggested first page to translate * [ja] fix contribute docs organization * Run private network with docker tutorial (#2065) * [NO-ISSUE] Run private network with docker tutorial Adds a tutorial page in the Infrastructure section on how to run a private XRPL network with Docker. Please let me know if you think this is a useful page to include for developers, whether the steps are clear or not, and if you have suggestions on what can be added to it. * Add minor link fixes and Japanese target * Apply suggestions from code review Co-authored-by: Amarantha Kulkarni <amarantha-k@users.noreply.github.com> * Add link to ripple-docker-testnet setup scripts in See Also section * Update repo URL --------- Co-authored-by: Amarantha Kulkarni <amarantha-k@users.noreply.github.com> * add intro gfx (#2036) * add intro gfx * Move graphic up * Update some graphics with their revised versions * Add updated version of the custodial vs non-custodial graphic --------- Co-authored-by: Amarantha Kulkarni <amarantha-k@users.noreply.github.com> Co-authored-by: Amarantha Kulkarni <akulkarni@ripple.com> * Update to reflect current UNL publishers * [ja] update contributing Co-authored-by: tequ <git@tequ.dev> * Incorporate feedback on "What is XRP" page. (#2099) * Add trademark info for XRP * Revert section to previous state * Fix broken link (#2101) --------- Co-authored-by: Oliver Eggert <oeggert@ripple.com> Co-authored-by: ddawson <dennis.s.dawson@gmail.com> Co-authored-by: Maria Shodunke <mshodunke@ripple.com> Co-authored-by: tequ <git@tequ.dev> Co-authored-by: oeggert <117319296+oeggert@users.noreply.github.com> Co-authored-by: Amarantha Kulkarni <amarantha-k@users.noreply.github.com> Co-authored-by: develoQ <develoQ.jp@gmail.com> Co-authored-by: Maria Shodunke <maria-robobug@users.noreply.github.com> Co-authored-by: Amarantha Kulkarni <akulkarni@ripple.com>
517 lines
26 KiB
Markdown
517 lines
26 KiB
Markdown
---
|
||
html: monitor-incoming-payments-with-websocket.html
|
||
parent: http-websocket-apis-tutorials.html
|
||
blurb: Use the WebSocket API to actively monitor for new XRP payments (and more).
|
||
filters:
|
||
- interactive_steps
|
||
labels:
|
||
- Payments
|
||
---
|
||
# Monitor Incoming Payments with WebSocket
|
||
|
||
This tutorial shows how to monitor for incoming [payments](payment-types.html) using the [WebSocket API](http-websocket-apis.html). Since all XRP Ledger transactions are public, anyone can monitor incoming payments to any address.
|
||
|
||
WebSocket follows a model where the client and server open one connection, then send messages both ways through the same connection, which stays open until explicitly closed (or until the connection fails). This is in contrast to the HTTP-based API model (including JSON-RPC and RESTful APIs), where the client opens and closes a new connection for each request.[¹](#footnote-1)<a id="from-footnote-1"></a>
|
||
|
||
**Tip:** The examples in this page use JavaScript so that the examples can run natively in a web browser. If you are developing in JavaScript, you can also use the [xrpl.js library for JavaScript](https://js.xrpl.org/) to simplify some tasks. This tutorial shows how to monitor for transactions _without_ using a xrpl.js so that you can translate the steps to other programming languages that don't have a native XRP Ledger client library.
|
||
|
||
## Prerequisites
|
||
|
||
- The examples in this page use JavaScript and the WebSocket protocol, which are available in all major modern browsers. If you have some JavaScript knowledge and expertise in another programming language with a WebSocket client, you can follow along while adapting the instructions to the language of your choice.
|
||
- You need a stable internet connection and access to an XRP Ledger server. The embedded examples connect to Ripple's pool of public servers. If you [run your own `rippled` or Clio server](install-rippled.html), you can also connect to that server locally.
|
||
- To properly handle XRP values without rounding errors, you need access to a number type that can do math on 64-bit unsigned integers. The examples in this tutorial use [big.js](https://github.com/MikeMcl/big.js/). If you are working with [tokens](tokens.html), you need even more precision. For more information, see [Currency Precision](currency-formats.html#xrp-precision).
|
||
|
||
<!-- Big number support -->
|
||
<script type="application/javascript" src="assets/vendor/big.min.js"></script>
|
||
<script type="application/javascript">
|
||
// Helper stuff for this interactive tutorial specifically
|
||
|
||
function writeToConsole(console_selector, message) {
|
||
let write_msg = "<div class='console-entry'>" + message + "</div>"
|
||
$(console_selector).find(".placeholder").remove()
|
||
$(console_selector).append(write_msg)
|
||
// TODO: JSON pretty-printing, maybe w/ multiple input args?
|
||
}
|
||
</script>
|
||
|
||
{% set n = cycler(* range(1,99)) %}
|
||
|
||
## {{n.next()}}. Connect to the XRP Ledger
|
||
|
||
The first step of monitoring for incoming payments is to connect to the XRP Ledger, specifically a `rippled` server.
|
||
|
||
The following JavaScript code connects to one of Ripple's public server clusters. It then logs a message to the console, sends a request using the [ping method][] and sets up a handler to log to the console again when it receives any message from the server side.
|
||
|
||
```js
|
||
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...')
|
||
})
|
||
```
|
||
|
||
The above example opens a secure connection (`wss://`) to one of Ripple's public API servers on the [Test Net](xrp-test-net-faucet.html). To connect to a locally-running `rippled` server with the default configuration instead, open an _unsecured_ connection (`ws://`) on port **6006** locally, using the following first line:
|
||
|
||
```js
|
||
const socket = new WebSocket('ws://localhost:6006')
|
||
```
|
||
|
||
**Tip:** By default, connecting to a local `rippled` server gives you access to the full set of [admin methods](admin-api-methods.html) and admin-only data in some responses such as [server_info][server_info method], plus the [public methods](public-api-methods.html) that are available when you connect to public servers over the internet.
|
||
|
||
Example:
|
||
|
||
{{ start_step("Connect") }}
|
||
<button id="connect-socket-button" class="btn btn-primary">Connect</button>
|
||
<strong>Connection status:</strong>
|
||
<span id="connection-status">Not connected</span>
|
||
<h5>Console:</h5>
|
||
<div class="ws-console" id="monitor-console-connect"><span class="placeholder">(Log is empty)</span></div>
|
||
{{ end_step() }}
|
||
|
||
<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>
|
||
|
||
|
||
## {{n.next()}}. Dispatch Incoming Messages to Handlers
|
||
|
||
Since WebSocket connections can have several messages going each way and there is not a strict 1:1 correlation between requests and responses, you need to identify what to do with each incoming message. A good model for coding this is to set up a "dispatcher" function that reads incoming messages and relays each message to the correct code path for handling it. To help dispatch messages appropriately, the `rippled` server provides a `type` field on every WebSocket message:
|
||
|
||
- For any message that is a direct response to a request from the client side, the `type` is the string `response`. In this case, the server also provides the following:
|
||
|
||
- An `id` field that matches the `id` provided in the request this is a response for. (This is important because responses may arrive out of order.)
|
||
|
||
- A `status` field that indicates whether the API successfully processed your request. The string value `success` indicates [a successful response](response-formatting.html). The string value `error` indicates [an error](error-formatting.html).
|
||
|
||
**Warning:** When submitting transactions, a `status` of `success` at the top level of the WebSocket message does not mean that the transaction itself succeeded. It only indicates that the server understood your request. For looking up a transaction's actual outcome, see [Look Up Transaction Results](look-up-transaction-results.html).
|
||
|
||
- For follow-up messages from [subscriptions](subscribe.html), the `type` indicates the type of follow-up message it is, such as the notification of a new transaction, ledger, or validation; or a follow-up to an ongoing [pathfinding request](path_find.html). Your client only receives these messages if it subscribes to them.
|
||
|
||
**Tip:** The [xrpl.js library for JavaScript](https://js.xrpl.org/) handles this step by default. All asynchronous API requests use Promises to provide the response, and you can listen to streams using the `.on(event, callback)` method of the `Client`.
|
||
|
||
The following JavaScript code defines a helper function to make API requests into convenient asynchronous [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises), and sets up an interface to map other types of messages to global handlers:
|
||
|
||
```js
|
||
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.warn("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)
|
||
}
|
||
})
|
||
|
||
// Show api_request functionality
|
||
async function pingpong() {
|
||
console.log("Ping...")
|
||
const response = await api_request({command: "ping"})
|
||
console.log("Pong!", response)
|
||
}
|
||
// Add pingpong() to the 'open' listener for socket
|
||
```
|
||
|
||
{{ start_step("Dispatch Messages") }}
|
||
<button id="enable_dispatcher" class="btn btn-primary" disabled="disabled">Enable Dispatcher</button>
|
||
<button id="dispatch_ping" class="btn btn-primary" disabled="disabled">Ping!</button>
|
||
<h5>Responses</h5>
|
||
<div class="ws-console" id="monitor-console-ping"><span class="placeholder">(Log is empty)</span></div>
|
||
{{ end_step() }}
|
||
|
||
<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>
|
||
|
||
## {{n.next()}}. Subscribe to the Account
|
||
|
||
To get a live notification whenever a transaction affects your account, you can subscribe to the account with the [subscribe method][]. In fact, it doesn't have to be your own account: since all transactions are public, you can subscribe to any account or even a combination of accounts.
|
||
|
||
After you subscribe to one or more accounts, the server sends a message with `"type": "transaction"` on each _validated_ transaction that affects any of the specified accounts in some way. To confirm this, look for `"validated": true` in the transaction messages.
|
||
|
||
The following code sample subscribes to the Test Net Faucet's sending address. It logs a message on each such transaction by adding a handler to the dispatcher from the previous step.
|
||
|
||
```js
|
||
async function do_subscribe() {
|
||
const sub_response = await api_request({
|
||
command:"subscribe",
|
||
accounts: ["rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe"]
|
||
})
|
||
if (sub_response.status === "success") {
|
||
console.log("Successfully subscribed!")
|
||
} else {
|
||
console.error("Error subscribing: ", sub_response)
|
||
}
|
||
}
|
||
// Add do_subscribe() to the 'open' listener for socket
|
||
|
||
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
|
||
```
|
||
|
||
For the following example, try opening the [Transaction Sender](tx-sender.html) in a different window or even on a different device and sending transactions to the address you subscribed to:
|
||
|
||
{{ start_step("Subscribe") }}
|
||
<label for="subscribe_address">Test Net Address:</label>
|
||
<input type="text" class="form-control" id="subscribe_address" value="rUCzEr6jrEyMpjhs4wSdQdz4g8Y382NxfM">
|
||
<button id="tx_subscribe" class="btn btn-primary" disabled="disabled">Subscribe</button>
|
||
<h5>Transactions</h5>
|
||
<div class="ws-console" id="monitor-console-subscribe"><span class="placeholder">(Log is empty)</span></div>
|
||
{{ end_step() }}
|
||
|
||
<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 +
|
||
"<br/> Result: " + tx.meta.TransactionResult +
|
||
" in ledger " + tx.ledger_index +
|
||
"<br/> Validated? " + tx.validated)
|
||
}
|
||
WS_HANDLERS["transaction"] = log_tx
|
||
</script>
|
||
|
||
|
||
## {{n.next()}}. Read Incoming Payments
|
||
|
||
When you subscribe to an account, you get messages for _all transactions to or from the account_, as well as _transactions that affect the account indirectly_, such as trading its [tokens](tokens.html). If your goal is to recognize when the account has received incoming payments, you must filter the transactions stream and process the payments based on the amount they actually delivered. Look for the following information:
|
||
|
||
- The **`validated` field** indicates that the transaction's outcome is [final](finality-of-results.html). This should always be the case when you subscribe to `accounts`, but if you _also_ subscribe to `accounts_proposed` or the `transactions_proposed` stream then the server sends similar messages on the same connection for unconfirmed transactions. As a precaution, it's best to always check the `validated` field.
|
||
- The **`meta.TransactionResult` field** is the [transaction result](transaction-results.html). If the result is not `tesSUCCESS`, the transaction failed and cannot have delivered any value.
|
||
- The **`transaction.Account`** field is the sender of the transaction. If you are only looking for transactions sent by others, you can ignore any transactions where this field matches your account's address. (Keep in mind, it _is_ possible to make a cross-currency payment to yourself.)
|
||
- The **`transaction.TransactionType` field** is the type of transaction. The transaction types that can possibly deliver currency to an account are as follows:
|
||
- **[Payment transactions][]** can deliver XRP or [tokens](tokens.html). Filter these by the `transaction.Destination` field, which contains the address of the recipient, and always use the `meta.delivered_amount` to see how much the payment actually delivered. XRP amounts are [formatted as strings](basic-data-types.html#specifying-currency-amounts).
|
||
|
||
**Warning:** If you use the `transaction.Amount` field instead, you may be vulnerable to the [partial payments exploit](partial-payments.html#partial-payments-exploit). Malicious users can use this exploit to trick you into allowing the malicious user to trade or withdraw more money than they paid you.
|
||
|
||
- **[CheckCash transactions][]** allow an account to receive money authorized by a different account's [CheckCreate transaction][]. Look at the metadata of a **CheckCash transaction** to see how much currency the account received.
|
||
|
||
- **[EscrowFinish transactions][]** can deliver XRP by finishing an [Escrow](escrow.html) created by a previous [EscrowCreate transaction][]. Look at the metadata of the **EscrowFinish transaction** to see which account received XRP from the escrow and how much.
|
||
|
||
- **[OfferCreate transactions][]** can deliver XRP or tokens by consuming offers your account has previously placed in the XRP Ledger's [decentralized exchange](decentralized-exchange.html). If you never place offers, you cannot receive money this way. Look at the metadata to see what currency the account received, if any, and how much.
|
||
|
||
- **[PaymentChannelClaim transactions][]** can deliver XRP from a [payment channel](payment-channels.html). Look at the metadata to see which accounts, if any, received XRP from the transaction.
|
||
|
||
- **[PaymentChannelFund transactions][]** can return XRP from a closed (expired) payment channel to the sender.
|
||
|
||
- The **`meta` field** contains [transaction metadata](transaction-metadata.html), including exactly how much of which currency or currencies was delivered where. See [Look Up transaction Results](look-up-transaction-results.html) for more information on how to understand transaction metadata.
|
||
|
||
The following sample code looks at transaction metadata of all the above transaction types to report how much XRP an account received:
|
||
|
||
```js
|
||
{% include '_code-samples/monitor-payments-websocket/js/read-amount-received.js' %}
|
||
```
|
||
|
||
{{ start_step("Read Payments") }}
|
||
<button id="tx_read" class="btn btn-primary" disabled="disabled">Start Reading</button>
|
||
<h5>Transactions</h5>
|
||
<div class="ws-console" id="monitor-console-read"><span class="placeholder">(Log is empty)</span></div>
|
||
{{ end_step() }}
|
||
|
||
<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<affected_nodes.length; i++) {
|
||
if ((affected_nodes[i].hasOwnProperty("ModifiedNode"))) {
|
||
// modifies an existing ledger entry
|
||
let ledger_entry = affected_nodes[i].ModifiedNode
|
||
if (ledger_entry.LedgerEntryType === "AccountRoot" &&
|
||
ledger_entry.FinalFields.Account === address) {
|
||
if (!ledger_entry.PreviousFields.hasOwnProperty("Balance")) {
|
||
writeToConsole("#monitor-console-read", "XRP balance did not change.")
|
||
}
|
||
// Balance is in PreviousFields, so it changed. Time for
|
||
// high-precision math!
|
||
const old_balance = new Big(ledger_entry.PreviousFields.Balance)
|
||
const new_balance = new Big(ledger_entry.FinalFields.Balance)
|
||
const diff_in_drops = new_balance.minus(old_balance)
|
||
const xrp_amount = diff_in_drops.div(1e6)
|
||
if (xrp_amount.gte(0)) {
|
||
writeToConsole("#monitor-console-read", "Received " + xrp_amount.toString()+" XRP.")
|
||
return
|
||
} else {
|
||
writeToConsole("#monitor-console-read", "Spent " + xrp_amount.abs().toString() + " XRP.")
|
||
return
|
||
}
|
||
}
|
||
} else if ((affected_nodes[i].hasOwnProperty("CreatedNode"))) {
|
||
// created a ledger entry. maybe the account got funded?
|
||
let ledger_entry = affected_nodes[i].CreatedNode
|
||
if (ledger_entry.LedgerEntryType === "AccountRoot" &&
|
||
ledger_entry.NewFields.Account === address) {
|
||
const balance_drops = new Big(ledger_entry.NewFields.Balance)
|
||
const xrp_amount = balance_drops.div(1e6)
|
||
writeToConsole("#monitor-console-read", "Received " + xrp_amount.toString() + " XRP (account funded).")
|
||
return
|
||
}
|
||
} // accounts cannot be deleted at this time, so we ignore DeletedNode
|
||
}
|
||
|
||
writeToConsole("#monitor-console-read", "Did not find address in affected nodes.")
|
||
return
|
||
}
|
||
|
||
function CountXRPReceived(tx, address) {
|
||
if (tx.meta.TransactionResult !== "tesSUCCESS") {
|
||
writeToConsole("#monitor-console-read", "Transaction failed.")
|
||
return
|
||
}
|
||
if (tx.transaction.TransactionType === "Payment") {
|
||
if (tx.transaction.Destination !== address) {
|
||
writeToConsole("#monitor-console-read", "Not the destination of this payment. (We're " +
|
||
address + "; they're " + tx.transaction.Destination + ")")
|
||
return
|
||
}
|
||
if (typeof tx.meta.delivered_amount === "string") {
|
||
const amount_in_drops = new Big(tx.meta.delivered_amount)
|
||
const xrp_amount = amount_in_drops.div(1e6)
|
||
writeToConsole("#monitor-console-read", "Received " + xrp_amount.toString() + " XRP.")
|
||
return
|
||
} else {
|
||
writeToConsole("#monitor-console-read", "Received non-XRP currency.")
|
||
return
|
||
}
|
||
} else if (["PaymentChannelClaim", "PaymentChannelFund", "OfferCreate",
|
||
"CheckCash", "EscrowFinish"].includes(
|
||
tx.transaction.TransactionType)) {
|
||
CountXRPDifference(tx.meta.AffectedNodes, address)
|
||
} else {
|
||
writeToConsole("#monitor-console-read", "Not a currency-delivering transaction type (" +
|
||
tx.transaction.TransactionType + ").")
|
||
}
|
||
}
|
||
|
||
$("#tx_read").click((event) => {
|
||
// 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>
|
||
|
||
## Next Steps
|
||
|
||
- [Look Up Transaction Results](look-up-transaction-results.html) to see exactly what a transaction did, and build your software to react appropriately.
|
||
- Try [Sending XRP](send-xrp.html) from your own address.
|
||
- Try monitoring for transactions of advanced types like [Escrows](escrow.html), [Checks](checks.html), or [Payment Channels](payment-channels.html), and responding to incoming notifications.
|
||
<!--{# TODO: uncomment when it's ready. - To more robustly handle internet instability, [Follow a Transaction Chain](follow-a-transaction-chain.html) to detect if you missed a notification. #}-->
|
||
|
||
## Other Programming Languages
|
||
|
||
Many programming languages have libraries for sending and receiving data over a WebSocket connection. If you want a head-start on communicating with `rippled`'s WebSocket API in a language other than JavaScript, the following examples show how:
|
||
|
||
<!-- MULTICODE_BLOCK_START -->
|
||
|
||
_Go_
|
||
|
||
```go
|
||
{% include '_code-samples/monitor-payments-websocket/go/monitor-incoming-payments.go' %}
|
||
```
|
||
|
||
_Python_
|
||
|
||
```py
|
||
{% include '_code-samples/monitor-payments-websocket/py/monitor_incoming.py' %}
|
||
```
|
||
|
||
<!-- MULTICODE_BLOCK_END -->
|
||
|
||
**Tip:** Don't see the programming language of your choice? Click the "Edit on GitHub" link at the top of this page and contribute your own sample code!
|
||
|
||
|
||
## Footnotes
|
||
|
||
[1.](#from-footnote-1) <a id="footnote-1"></a> In practice, when calling an HTTP-based API multiple times, the client and server may reuse the same connection for several requests and responses. This practice is called [HTTP persistent connection, or keep-alive](https://en.wikipedia.org/wiki/HTTP_persistent_connection). From a development standpoint, the code to use an HTTP-based API is the same regardless of whether the underlying connection is new or reused.
|
||
|
||
## See Also
|
||
|
||
- **Concepts:**
|
||
- [Transactions](transactions.html)
|
||
- [Finality of Results](finality-of-results.html) - How to know when a transaction's success or failure is final. (Short version: if a transaction is in a validated ledger, its outcome and metadata are final.)
|
||
- **Tutorials:**
|
||
- [Reliable Transaction Submission](reliable-transaction-submission.html)
|
||
- [Look Up Transaction Results](look-up-transaction-results.html)
|
||
- **References:**
|
||
- [Transaction Types](transaction-types.html)
|
||
- [Transaction Metadata](transaction-metadata.html) - Summary of the metadata format and fields that appear in metadata
|
||
- [Transaction Results](transaction-results.html) - Tables of all possible result codes for transactions.
|
||
|
||
|
||
<!--{# common link defs #}-->
|
||
{% include '_snippets/rippled-api-links.md' %}
|
||
{% include '_snippets/tx-type-links.md' %}
|
||
{% include '_snippets/rippled_versions.md' %}
|