11 KiB
Monitor Incoming Payments with WebSocket
This tutorial shows how to monitor for incoming payments using the WebSocket rippled API. Since all XRP Ledger transactions are public, anyone can monitor incoming payments to any address.
WebSocket follows a model where the client and server establish one connection, then send messages both ways through the same connection, which remains 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¹.
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 RippleAPI library for JavaScript to simplify some tasks. This tutorial shows how to monitor for transactions without using RippleAPI so that you can translate the steps to other programming languages that don't have RippleAPI.
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 a
rippledserver. The embedded examples connect to Ripple's pool of public servers. If you run your ownrippledserver, you can also connect to that server locally.
{% 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 requesting using the [ping method][] and sets up a handler to log to the console again when it receives any message from the server side.
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)
})
The above example opens a secure connection (wss://) to one of Ripple's public Test Net API servers. 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:
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 and admin-only data in some responses such as [server_info][server_info method], in addition to the public methods that are available when you connect to a public servers over the internet.
Example:
{{ start_step("Connect") }} Connect Connection status: Not connected
Console:
{{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
typeis the stringresponse. In this case, the server also provides the following:-
An
idfield that matches theidprovided in the request this is a response for. (This is important because responses may arrive out of order.) -
A
statusfield that indicates whether the API successfully processed your request. The string valuesuccessindicates a successful response. The string valueerrorindicates an error.Warning: When submitting transactions, a
statusofsuccessat 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.
-
-
For follow-up messages from subscriptions, the
typeindicates the type of follow-up message it is, such as the notification of an new transaction, ledger, or validation; or a follow-up to an ongoing pathfinding request. Your client only receives these messages if it subscribes to them.
Tip: The RippleAPI library for JavaScript 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.
The following JavaScript code defines a helper function to make API requests into convenient asynchronous Promises, and sets up an interface to map other types of messages to global handlers:
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
this.resolve = resolve
try {
// Use the socket opened in the previous example...
socket.send(JSON.stringify(options))
} catch {
reject()
}
})
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()
{{ start_step("Dispatch Messages") }} Enable Dispatcher Ping!
Responses
Footnotes
1. 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. 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.
{% include '_snippets/rippled-api-links.md' %} {% include '_snippets/tx-type-links.md' %} {% include '_snippets/rippled_versions.md' %}
