mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-26 14:45:50 +00:00
Re-level non-docs content to top of repo and rename content→docs
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
---
|
||||
html: get-started-using-http-websocket-apis.html
|
||||
parent: http-websocket-apis-tutorials.html
|
||||
seo:
|
||||
description: Unleash the full power of the XRP Ledger's native APIs.
|
||||
cta_text: Get Started
|
||||
top_nav_name: HTTP / WebSocket
|
||||
top_nav_grouping: Get Started
|
||||
labels:
|
||||
- Development
|
||||
showcase_icon: assets/img/logos/globe.svg
|
||||
---
|
||||
# Get Started Using HTTP / WebSocket APIs
|
||||
|
||||
If you don't have or don't want to use a [client library](../../references/client-libraries.md) in your preferred programming language, you can access the XRP Ledger directly through the APIs of its core server software, [`rippled`](../../concepts/networks-and-servers/index.md). The server provides APIs over JSON-RPC and WebSocket protocols. If you don't [run your own instance of `rippled`](../../infrastructure/installation/index.md) you can still use a [public server][public servers].
|
||||
|
||||
**Tip:** You can dive right into the API with the [**WebSocket API Tool**](/resources/dev-tools/websocket-api-tool), or use the [XRP Ledger Explorer](https://livenet.xrpl.org/) to watch the progress of the ledger live.
|
||||
|
||||
## Differences Between JSON-RPC and WebSocket
|
||||
|
||||
Both JSON-RPC and WebSocket are HTTP-based protocols, and for the most part the data provided over both protocols is the same. The major differences are as follows:
|
||||
|
||||
- JSON-RPC uses individual HTTP requests and responses for each call, similar to a RESTful API. You can use any common HTTP client such as [curl](https://curl.se/), [Postman](https://www.postman.com/downloads/), or [Requests](https://requests.readthedocs.io/) to access this API.
|
||||
- WebSocket uses a persistent connection that allows the server to push data to the client. Functions that require push messages, like [event subscriptions](../../references/http-websocket-apis/public-api-methods/subscription-methods/subscribe.md), are only available using WebSocket.
|
||||
|
||||
Both APIs can be served unencrypted (`http://` and `ws://`) or encrypted using TLS (`https://` and `wss://`). Unencrypted connections should not be served over open networks, but can be used when the client is on the same machine as the server.
|
||||
|
||||
|
||||
## Admin Access
|
||||
|
||||
The API methods are divided into [Public Methods](../../references/http-websocket-apis/public-api-methods/index.md) and [Admin Methods](../../references/http-websocket-apis/admin-api-methods/index.md) so that organizations can offer public servers for the benefit of the community. To access admin methods, or admin functionality of public methods, you must connect to the API on a **port and IP address marked as admin** in the server's config file.
|
||||
|
||||
The [example config file](https://github.com/XRPLF/rippled/blob/f00f263852c472938bf8e993e26c7f96f435935c/cfg/rippled-example.cfg#L1154-L1179) listens for connections on the local loopback network (127.0.0.1), with JSON-RPC (HTTP) on port 5005 and WebSocket (WS) on port 6006, and treats all connected clients as admin.
|
||||
|
||||
|
||||
## WebSocket API
|
||||
|
||||
If you are looking to try out some methods on the XRP Ledger, you can skip writing your own WebSocket code and go straight to using the API at the [WebSocket API Tool](/resources/dev-tools/websocket-api-tool). Later on, when you want to connect to your own `rippled` server, you can [build your own client](monitor-incoming-payments-with-websocket.md) or use a [client library](../../references/client-libraries.md) with WebSocket support.
|
||||
|
||||
Example WebSocket API request:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "my_first_request",
|
||||
"command": "server_info",
|
||||
"api_version": 1
|
||||
}
|
||||
```
|
||||
|
||||
The response shows you the current status of the server.
|
||||
|
||||
Read more: [Request Formatting >](../../references/http-websocket-apis/api-conventions/request-formatting.md) [Response Formatting >](../../references/http-websocket-apis/api-conventions/response-formatting.md) [About the server_info method >][server_info method]
|
||||
|
||||
## JSON-RPC
|
||||
|
||||
You can use any HTTP client (like [RESTED for Firefox](https://addons.mozilla.org/en-US/firefox/addon/rested/), [Postman for Chrome](https://chrome.google.com/webstore/detail/postman/fhbjgbiflinjbdggehcddcbncdddomop?hl=en) or [Online HTTP client ExtendsClass](https://extendsclass.com/rest-client-online.html)) to make JSON-RPC calls a `rippled` server. Most programming languages have a library for making HTTP requests built in. <!-- SPELLING_IGNORE: extendsclass -->
|
||||
|
||||
Example JSON-RPC request:
|
||||
|
||||
```json
|
||||
POST http://s1.ripple.com:51234/
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"method": "server_info",
|
||||
"params": [
|
||||
{
|
||||
"api_version": 1
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
The response shows you the current status of the server.
|
||||
|
||||
Read more: [Request Formatting >](../../references/http-websocket-apis/api-conventions/request-formatting.md#json-rpc-format) [Response Formatting >](../../references/http-websocket-apis/api-conventions/response-formatting.md) [About the server_info method >][server_info method]
|
||||
|
||||
## Commandline
|
||||
|
||||
The commandline interface connects to the same service as the JSON-RPC one, so the public servers and server configuration are the same. By default, the commandline connects to a `rippled` server running on the same machine.
|
||||
|
||||
Example commandline request:
|
||||
|
||||
```
|
||||
rippled --conf=/etc/opt/ripple/rippled.cfg server_info
|
||||
```
|
||||
|
||||
Read more: [Commandline Usage Reference >](../../infrastructure/commandline-usage.md)
|
||||
|
||||
**Caution:** The commandline interface is intended for administrative purposes only and is _not a supported API_. New versions of `rippled` may introduce breaking changes to the commandline API without warning!
|
||||
|
||||
## Available Methods
|
||||
|
||||
For a full list of API methods, see:
|
||||
|
||||
- [Public `rippled` Methods](../../references/http-websocket-apis/public-api-methods/index.md): Methods available on public servers, including looking up data from the ledger and submitting transactions.
|
||||
- [Admin `rippled` Methods](../../references/http-websocket-apis/admin-api-methods/index.md): Methods for [managing](../../infrastructure/installation/install-rippled-on-ubuntu.md) the `rippled` server.
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [XRP Ledger Overview](/about/)
|
||||
- [Client Libraries](../../references/client-libraries.md)
|
||||
- [Parallel Networks](../../concepts/networks-and-servers/parallel-networks.md)
|
||||
- **Tutorials:**
|
||||
- [Get Started Using JavaScript](get-started-using-javascript.md)
|
||||
- [Reliable Transaction Submission](../../concepts/transactions/reliable-transaction-submission.md)
|
||||
- [Manage the rippled Server](../../infrastructure/installation/install-rippled-on-ubuntu.md)
|
||||
- **References:**
|
||||
- [rippled API Reference](../../references/http-websocket-apis/index.md)
|
||||
|
||||
{% raw-partial file="/_snippets/common-links.md" /%}
|
||||
228
docs/tutorials/get-started/get-started-using-java.md
Normal file
228
docs/tutorials/get-started/get-started-using-java.md
Normal file
@@ -0,0 +1,228 @@
|
||||
---
|
||||
html: get-started-using-java.html
|
||||
parent: java.html
|
||||
funnel: Build
|
||||
doc_type: Tutorials
|
||||
category: Get Started
|
||||
seo:
|
||||
description: Build a Java app that interacts with the XRP Ledger.
|
||||
cta_text: Build an XRP Ledger-connected app
|
||||
top_nav_name: Java
|
||||
top_nav_grouping: Get Started
|
||||
labels:
|
||||
- Development
|
||||
showcase_icon: assets/img/logos/java.svg
|
||||
---
|
||||
# Get Started Using Java
|
||||
|
||||
This tutorial walks you through the basics of building an XRP Ledger-connected application using [`xrpl4j`](https://github.com/XRPLF/xrpl4j), a pure Java library built to interact with the XRP Ledger.
|
||||
|
||||
This tutorial is intended for beginners and should take no longer than 30 minutes to complete.
|
||||
|
||||
## Learning Goals
|
||||
|
||||
In this tutorial, you'll learn:
|
||||
|
||||
* The basic building blocks of XRP Ledger-based applications.
|
||||
* How to connect to the XRP Ledger using `xrpl4j`.
|
||||
* How to get an account on the [Testnet](/resources/dev-tools/xrp-faucets) using `xrpl4j`.
|
||||
* How to use the `xrpl4j` library to look up information about an account on the XRP Ledger.
|
||||
* How to put these steps together to create a Java app.
|
||||
|
||||
## Requirements
|
||||
|
||||
* The `xrpl4j` library supports Java 1.8 and later.
|
||||
* A project management tool such as [Maven](https://maven.apache.org/) or [Gradle](https://gradle.org/). <!-- SPELLING_IGNORE: gradle -->
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
The [`xrpl4j` library](https://github.com/XRPLF/xrpl4j) is available on [Maven Central](https://search.maven.org/artifact/org.xrpl/xrpl4j-parent).
|
||||
`xrpl4j` is split into multiple artifacts, which can be imported as needed.
|
||||
|
||||
In this tutorial, you need the [xrpl4j-client](https://javadoc.io/doc/org.xrpl/xrpl4j-client/latest/index.html), [xrpl4j-address-codec](https://javadoc.io/doc/org.xrpl/xrpl4j-address-codec/latest/index.html), [xrpl4j-keypairs](https://javadoc.io/doc/org.xrpl/xrpl4j-keypairs/latest/index.html), and [xrpl4j-model](https://javadoc.io/doc/org.xrpl/xrpl4j-model/latest/index.html) modules. <!-- SPELLING_IGNORE: keypairs -->
|
||||
|
||||
To install with Maven, add the following to your project's `pom.xml` file and then run `mvn install`:
|
||||
|
||||
```xml
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.xrpl</groupId>
|
||||
<artifactId>xrpl4j-bom</artifactId>
|
||||
<version>3.0.1</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
```
|
||||
|
||||
```xml
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.xrpl</groupId>
|
||||
<artifactId>xrpl4j-core</artifactId>
|
||||
<version>3.0.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.xrpl</groupId>
|
||||
<artifactId>xrpl4j-client</artifactId>
|
||||
<version>3.0.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
```
|
||||
|
||||
Check out the [xrpl4j sample project](https://github.com/XRPLF/xrpl4j-sample) for a full Maven project containing the code from this tutorial.
|
||||
|
||||
## Start Building
|
||||
|
||||
When you're working with the XRP Ledger, there are a few things you'll need to manage, whether you're adding XRP to your [account](../../concepts/accounts/accounts.md), integrating with the [decentralized exchange](../../concepts/tokens/decentralized-exchange/index.md), or [issuing tokens](../../concepts/tokens/index.md). This tutorial walks you through basic patterns common to getting started with all of these use cases and provides sample code for implementing them.
|
||||
|
||||
Here are the basic steps you'll need to cover for almost any XRP Ledger project:
|
||||
|
||||
1. [Connect to the XRP Ledger.](#1-connect-to-the-xrp-ledger)
|
||||
1. [Get an account.](#2-get-account)
|
||||
1. [Query the XRP Ledger.](#3-query-the-xrp-ledger)
|
||||
|
||||
|
||||
### 1. Connect to the XRP Ledger
|
||||
|
||||
To make queries and submit transactions, you need to connect to the XRP Ledger. To do this with `xrpl4j`,
|
||||
you can use an [`XrplClient`](https://javadoc.io/doc/org.xrpl/xrpl4j-client/3.0.1/org/xrpl/xrpl4j/client/XrplClient.html):
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/java/GetAccountInfo.java" from="// Construct a network client" before="// Create a random KeyPair" language="java" /%}
|
||||
|
||||
#### Connect to the production XRP Ledger
|
||||
|
||||
The sample code in the previous section shows you how to connect to the Testnet, which is one of the available [parallel networks](../../concepts/networks-and-servers/parallel-networks.md). When you're ready to integrate with the production XRP Ledger, you'll need to connect to the Mainnet. You can do that in two ways:
|
||||
|
||||
* By [installing the core server](../../infrastructure/installation/index.md) (`rippled`) and running a node yourself. The core server connects to the Mainnet by default, but you can [change the configuration to use Testnet or Devnet](../../infrastructure/configuration/connect-your-rippled-to-the-xrp-test-net.md). [There are good reasons to run your own core server](../../concepts/networks-and-servers/index.md#reasons-to-run-your-own-server). If you run your own server, you can connect to it like so:
|
||||
|
||||
```
|
||||
final HttpUrl rippledUrl = HttpUrl.get("http://localhost:5005/");
|
||||
XrplClient xrplClient = new XrplClient(rippledUrl);
|
||||
```
|
||||
|
||||
See the example [core server config file](https://github.com/XRPLF/rippled/blob/c0a0b79d2d483b318ce1d82e526bd53df83a4a2c/cfg/rippled-example.cfg#L1562) for more information about default values.
|
||||
|
||||
* By using one of the available [public servers][]:
|
||||
|
||||
```
|
||||
final HttpUrl rippledUrl = HttpUrl.get("https://s2.ripple.com:51234/");
|
||||
XrplClient xrplClient = new XrplClient(rippledUrl);
|
||||
```
|
||||
|
||||
### 2. Get account
|
||||
|
||||
To store value and execute transactions on the XRP Ledger, you need to get an account: a [set of keys](../../concepts/accounts/cryptographic-keys.md#key-components) and an [address](../../concepts/accounts/addresses.md) that's been [funded with enough XRP](../../concepts/accounts/accounts.md#creating-accounts) to meet the [account reserve](../../concepts/accounts/reserves.md). The address is the identifier of your account and you use the [private key](../../concepts/accounts/cryptographic-keys.md#private-key) to sign transactions that you submit to the XRP Ledger. For production purposes, you should take care to store your keys and set up a [secure signing method](../../concepts/transactions/secure-signing.md).
|
||||
|
||||
To generate a new account, `xrpl4j` provides the [`DefaultWalletFactory`](https://javadoc.io/doc/org.xrpl/xrpl4j-keypairs/latest/org/xrpl/xrpl4j/wallet/DefaultWalletFactory.html).
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/java/GetAccountInfo.java" from="// Create a random KeyPair" before="// Derive the Classic and X-Addresses from testWallet" language="java" /%}
|
||||
|
||||
|
||||
The result of a call to `walletFactory.randomWallet(true).wallet()` is a [`Wallet` instance](https://javadoc.io/doc/org.xrpl/xrpl4j-keypairs/latest/org/xrpl/xrpl4j/wallet/Wallet.html):
|
||||
|
||||
```java
|
||||
System.out.println(testWallet);
|
||||
|
||||
// print output
|
||||
Wallet {
|
||||
privateKey= -HIDDEN-,
|
||||
publicKey=ED90635A6F2A5905D3D5CD2C14905FFB2D838185993576CA4CEE24A920D0D6BD6B,
|
||||
classicAddress=raj5eirfEpbN9YzG9FzB8ZPNyjpFvH6ycV,
|
||||
xAddress=T76mQFr9zLGi2LCjVDgJ7mEQCk4767SdEL32mZFygpdGcFf,
|
||||
isTest=true
|
||||
}
|
||||
```
|
||||
|
||||
For testing and development purposes, you can use a `FaucetClient` connected to the XRP Ledger [Testnet](../../concepts/networks-and-servers/parallel-networks.md):
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/java/GetAccountInfo.java" from="// Fund the account using the testnet Faucet" before="// Look up your Account Info" language="java" /%}
|
||||
|
||||
### 3. Query the XRP Ledger
|
||||
|
||||
You can query the XRP Ledger to get information about [a specific account](../../references/http-websocket-apis/public-api-methods/account-methods/index.md), [a specific transaction](../../references/http-websocket-apis/public-api-methods/transaction-methods/tx.md), the state of a [current or a historical ledger](../../references/http-websocket-apis/public-api-methods/ledger-methods/index.md), and [the XRP Ledger's decentralized exchange](../../references/http-websocket-apis/public-api-methods/path-and-order-book-methods/index.md). You need to make these queries, among other reasons, to look up account info to follow best practices for [reliable transaction submission](../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
Here, we'll use the [`XrplClient` we constructed](#1-connect-to-the-xrp-ledger) to look up information about the [account we got](#2-get-account) in the previous step.
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/java/GetAccountInfo.java" from="// Look up your Account Info" before="// Print the result" language="java" /%}
|
||||
|
||||
|
||||
### 4. Putting it all together
|
||||
|
||||
Using these building blocks, we can create a Java app that:
|
||||
|
||||
1. Gets an account on the Testnet.
|
||||
2. Connects to the XRP Ledger.
|
||||
3. Looks up and prints information about the account you created.
|
||||
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/java/GetAccountInfo.java" language="java" /%}
|
||||
|
||||
To run the app, you can download the code from [Github](https://github.com/XRPLF/xrpl4j-sample) and run `GetAccountInfo` either
|
||||
from your IDE or from the command line.
|
||||
|
||||
```sh
|
||||
git clone https://github.com/XRPLF/xrpl4j-sample.git
|
||||
cd xrpl4j-sample
|
||||
mvn compile exec:java -Dexec.mainClass="org.xrpl.xrpl4j.samples.GetAccountInfo"
|
||||
```
|
||||
|
||||
You should see output similar to this example:
|
||||
|
||||
```sh
|
||||
Running the GetAccountInfo sample...
|
||||
Constructing an XrplClient connected to https://s.altnet.rippletest.net:51234/
|
||||
Generated KeyPair: KeyPair{
|
||||
privateKey=PrivateKey{value=[redacted], destroyed=false},
|
||||
publicKey=PublicKey{value=UnsignedByteArray{unsignedBytes=List(size=33)},
|
||||
base58Value=aKGgrZL2WTc85HJSkQGuKfinem5oMH1uCJankSWFATGUhqvygxir,
|
||||
base16Value=EDFB1073327CCBDA342AD685AF1C04530294866B9CB10C21126DC004BFDBA287D1,
|
||||
keyType=ED25519
|
||||
}
|
||||
}
|
||||
Classic Address: rBXHGshqXu3Smy9FUsQTmo49bGpQUQEm3X
|
||||
X-Address: T7yMiiJJCmgY2yg5WB2davUedDeBFAG5B8r9KHjKCxDdvv3
|
||||
Funded the account using the Testnet faucet.
|
||||
AccountInfoResult{
|
||||
status=success,
|
||||
accountData=AccountRootObject{
|
||||
ledgerEntryType=ACCOUNT_ROOT,
|
||||
account=rDNwS2t4afhBogKqSFFmsDi1k7gmeGhz4p,
|
||||
balance=10000000000,
|
||||
flags=0,
|
||||
ownerCount=0,
|
||||
previousTransactionId=0000000000000000000000000000000000000000000000000000000000000000,
|
||||
previousTransactionLedgerSequence=0,
|
||||
sequence=37649083,
|
||||
signerLists=[],
|
||||
index=F607809578C2A413774B9A240480B8B7B10C3E296CA609337D2F41813F566B92
|
||||
},
|
||||
ledgerCurrentIndex=37649083,
|
||||
validated=false
|
||||
}
|
||||
```
|
||||
|
||||
#### Interpreting the response
|
||||
|
||||
The response fields contained in `AccountInfoResult` that you want to inspect in most cases are:
|
||||
|
||||
* `accountData.sequence` — This is the sequence number of the next valid transaction for the account. You need to specify the sequence number when you prepare transactions.
|
||||
|
||||
* `accountData.balance` — This is the account's balance of XRP, in drops. You can use this to confirm that you have enough XRP to send (if you're making a payment) and to meet the [current transaction cost](../../concepts/transactions/transaction-cost.md#current-transaction-cost) for a given transaction.
|
||||
|
||||
* `validated` — Indicates whether the returned data is from a [validated ledger](../../concepts/ledgers/open-closed-validated-ledgers.md). When inspecting transactions, it's important to confirm that [the results are final](../../concepts/transactions/finality-of-results/index.md) before further processing the transaction. If `validated` is `true` then you know for sure the results won't change. For more information about best practices for transaction processing, see [Reliable Transaction Submission](../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
For a detailed description of every response field, see [account_info](../../references/http-websocket-apis/public-api-methods/account-methods/account_info.md#response-format).
|
||||
|
||||
|
||||
## Keep on building
|
||||
|
||||
Now that you know how to use `xrpl4j` to connect to the XRP Ledger, get an account, and look up information about it, you can also use `xrpl4j` to:
|
||||
|
||||
* [Send XRP](send-xrp.md).
|
||||
* [Set up secure signing](../../concepts/transactions/secure-signing.md) for your account.
|
||||
|
||||
{% raw-partial file="/_snippets/common-links.md" /%}
|
||||
173
docs/tutorials/get-started/get-started-using-javascript.md
Normal file
173
docs/tutorials/get-started/get-started-using-javascript.md
Normal file
@@ -0,0 +1,173 @@
|
||||
---
|
||||
html: get-started-using-javascript.html
|
||||
parent: javascript.html
|
||||
seo:
|
||||
description: Build an entry-level JavaScript application for querying the XRP Ledger.
|
||||
top_nav_name: JavaScript
|
||||
top_nav_grouping: Get Started
|
||||
labels:
|
||||
- Development
|
||||
showcase_icon: assets/img/logos/javascript.svg
|
||||
---
|
||||
# Get Started Using JavaScript
|
||||
|
||||
This tutorial guides you through the basics of building an XRP Ledger-connected application in JavaScript or TypeScript using the [`xrpl.js`](https://github.com/XRPLF/xrpl.js/) client library in either Node.js or web browsers.
|
||||
|
||||
The scripts and config files used in this guide are {% repo-link path="content/_code-samples/get-started/js/" %}available in this website's GitHub Repository{% /repo-link %}.
|
||||
|
||||
|
||||
## Learning Goals
|
||||
|
||||
In this tutorial, you'll learn:
|
||||
|
||||
* The basic building blocks of XRP Ledger-based applications.
|
||||
* How to connect to the XRP Ledger using `xrpl.js`.
|
||||
* How to get an account on the [Testnet](/resources/dev-tools/xrp-faucets) using `xrpl.js`.
|
||||
* How to use the `xrpl.js` library to look up information about an account on the XRP Ledger.
|
||||
* How to put these steps together to create a JavaScript app or web-app.
|
||||
|
||||
|
||||
## Requirements
|
||||
|
||||
To follow this tutorial, you should have some familiarity with writing code in JavaScript and managing small JavaScript projects. In browsers, any modern web browser with JavaScript support should work fine. In Node.js, **version 14** is recommended. Node.js versions 12 and 16 are also regularly tested.
|
||||
|
||||
|
||||
## Install with npm
|
||||
|
||||
Start a new project by creating an empty folder, then move into that folder and use [NPM](https://www.npmjs.com/) to install the latest version of xrpl.js:
|
||||
|
||||
```sh
|
||||
npm install xrpl
|
||||
```
|
||||
|
||||
|
||||
## Start Building
|
||||
|
||||
When you're working with the XRP Ledger, there are a few things you'll need to manage, whether you're adding XRP to your [account](../../concepts/accounts/accounts.md), integrating with the [decentralized exchange](../../concepts/tokens/decentralized-exchange/index.md), or [issuing tokens](../../concepts/tokens/index.md). This tutorial walks you through basic patterns common to getting started with all of these use cases and provides sample code for implementing them.
|
||||
|
||||
Here are some steps you use in many XRP Ledger projects:
|
||||
|
||||
1. [Import the library.](#1-import-the-library)
|
||||
1. [Connect to the XRP Ledger.](#2-connect-to-the-xrp-ledger)
|
||||
1. [Get an account.](#3-get-account)
|
||||
1. [Query the XRP Ledger.](#4-query-the-xrp-ledger)
|
||||
1. [Listen for Events.](#5-listen-for-events)
|
||||
|
||||
### 1. Import the Library
|
||||
|
||||
How you load `xrpl.js` into your project depends on your development environment:
|
||||
|
||||
#### Web Browsers
|
||||
|
||||
Add a `<script>` tag such as the following to your HTML:
|
||||
|
||||
```html
|
||||
<script src="https://unpkg.com/xrpl/build/xrpl-latest-min.js"></script>
|
||||
```
|
||||
|
||||
You can load the library from a CDN as in the above example, or download a release and host it on your own website.
|
||||
|
||||
This loads the module into the top level as `xrpl`.
|
||||
|
||||
#### Node.js
|
||||
|
||||
Add the library using [npm](https://www.npmjs.com/). This updates your `package.json` file, or creates a new one if it didn't already exist:
|
||||
|
||||
```sh
|
||||
npm install xrpl
|
||||
```
|
||||
|
||||
Then import the library:
|
||||
|
||||
```js
|
||||
const xrpl = require("xrpl")
|
||||
```
|
||||
|
||||
|
||||
### 2. Connect to the XRP Ledger
|
||||
|
||||
To make queries and submit transactions, you need to connect to the XRP Ledger. To do this with `xrpl.js`, you create an instance of the `Client` class and use the `connect()` method.
|
||||
|
||||
**Tip:** Many network functions in `xrpl.js` use [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) to return values asynchronously. The code samples here use the [`async/await` pattern](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous/Async_await) to wait for the actual result of the Promises.
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/js/base.js" language="js" /%}
|
||||
|
||||
#### Connect to the XRP Ledger Mainnet
|
||||
|
||||
The sample code in the previous section shows you how to connect to the Testnet, which is one of the available [parallel networks](../../concepts/networks-and-servers/parallel-networks.md). When you're ready to move to production, you'll need to connect to the XRP Ledger Mainnet. You can do that in two ways:
|
||||
|
||||
* By [installing the core server](../../infrastructure/installation/index.md) (`rippled`) and running a node yourself. The core server connects to the Mainnet by default, but you can [change the configuration to use Testnet or Devnet](../../infrastructure/configuration/connect-your-rippled-to-the-xrp-test-net.md). [There are good reasons to run your own core server](../../concepts/networks-and-servers/index.md#reasons-to-run-your-own-server). If you run your own server, you can connect to it like so:
|
||||
|
||||
```
|
||||
const MY_SERVER = "ws://localhost:6006/"
|
||||
const client = new xrpl.Client(MY_SERVER)
|
||||
await client.connect()
|
||||
```
|
||||
|
||||
See the example [core server config file](https://github.com/XRPLF/rippled/blob/c0a0b79d2d483b318ce1d82e526bd53df83a4a2c/cfg/rippled-example.cfg#L1562) for more information about default values.
|
||||
|
||||
* By using one of the available [public servers][]:
|
||||
|
||||
```
|
||||
const PUBLIC_SERVER = "wss://xrplcluster.com/"
|
||||
const client = new xrpl.Client(PUBLIC_SERVER)
|
||||
await client.connect()
|
||||
```
|
||||
|
||||
|
||||
### 3. Get Account
|
||||
|
||||
The `xrpl.js` library has a `Wallet` class for handling the keys and address of an XRP Ledger account. On Testnet, you can fund a new account like this:
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/js/get-acct-info.js" from="// Create a wallet" before="// Get info" language="js" /%}
|
||||
|
||||
If you only want to generate keys, you can create a new `Wallet` instance like this:
|
||||
|
||||
```js
|
||||
const test_wallet = xrpl.Wallet.generate()
|
||||
```
|
||||
|
||||
Or, if you already have a seed encoded in [base58][], you can make a `Wallet` instance from it like this:
|
||||
|
||||
```js
|
||||
const test_wallet = xrpl.Wallet.fromSeed("sn3nxiW7v8KXzPzAqzyHXbSSKNuN9") // Test secret; don't use for real
|
||||
```
|
||||
|
||||
### 4. Query the XRP Ledger
|
||||
|
||||
Use the Client's `request()` method to access the XRP Ledger's [WebSocket API](../../references/http-websocket-apis/api-conventions/request-formatting.md). For example:
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/js/get-acct-info.js" from="// Get info" before="// Listen to ledger close events" language="js" /%}
|
||||
|
||||
|
||||
### 5. Listen for Events
|
||||
|
||||
You can set up handlers for various types of events in `xrpl.js`, such as whenever the XRP Ledger's [consensus process](../../concepts/consensus-protocol/index.md) produces a new [ledger version](../../concepts/ledgers/index.md). To do that, first call the [subscribe method][] to get the type of events you want, then attach an event handler using the `on(eventType, callback)` method of the client.
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/js/get-acct-info.js" from="// Listen to ledger close events" before="// Disconnect when done" language="js" /%}
|
||||
|
||||
|
||||
## Keep on Building
|
||||
|
||||
Now that you know how to use `xrpl.js` to connect to the XRP Ledger, get an account, and look up information about it, you can also:
|
||||
|
||||
* [Send XRP](send-xrp.md).
|
||||
* [Issue a Fungible Token](../use-tokens/issue-a-fungible-token.md)
|
||||
* [Set up secure signing](../../concepts/transactions/secure-signing.md) for your account.
|
||||
|
||||
|
||||
## See Also
|
||||
|
||||
- **Concepts:**
|
||||
- [XRP Ledger Overview](/about/)
|
||||
- [Client Libraries](../../references/client-libraries.md)
|
||||
- **Tutorials:**
|
||||
- [Send XRP](send-xrp.md)
|
||||
- **References:**
|
||||
- [`xrpl.js` Reference](https://js.xrpl.org/)
|
||||
- [Public API Methods](../../references/http-websocket-apis/public-api-methods/index.md)
|
||||
- [API Conventions](../../references/http-websocket-apis/api-conventions/index.md)
|
||||
- [base58 Encodings](../../references/protocol/data-types/base58-encodings.md)
|
||||
- [Transaction Formats](../../references/protocol/transactions/index.md)
|
||||
|
||||
{% raw-partial file="/_snippets/common-links.md" /%}
|
||||
238
docs/tutorials/get-started/get-started-using-php.md
Normal file
238
docs/tutorials/get-started/get-started-using-php.md
Normal file
@@ -0,0 +1,238 @@
|
||||
---
|
||||
html: get-started-using-php.html
|
||||
parent: php.html
|
||||
funnel: Build
|
||||
doc_type: Tutorials
|
||||
category: Get Started
|
||||
seo:
|
||||
description: Build a PHP app that interacts with the XRP Ledger.
|
||||
cta_text: Build an XRP Ledger-connected app
|
||||
top_nav_name: PHP
|
||||
top_nav_grouping: Get Started
|
||||
labels:
|
||||
- Development
|
||||
showcase_icon: assets/img/logos/java.svg
|
||||
---
|
||||
# Get Started Using PHP
|
||||
|
||||
This tutorial walks you through the basics of building an XRP Ledger-connected application using [`XRPL_PHP`](https://github.com/AlexanderBuzz/xrpl-php), a PHP library built to interact with the XRP Ledger.
|
||||
|
||||
This tutorial is intended for beginners and should take no longer than 30 minutes to complete.
|
||||
|
||||
## Learning Goals
|
||||
|
||||
In this tutorial, you'll learn:
|
||||
|
||||
* The basic building blocks of XRP Ledger-based applications.
|
||||
* How to connect to the XRP Ledger using `XRPL_PHP`.
|
||||
* How to get an account on the [Testnet](/resources/dev-tools/xrp-faucets) using `XRPL_PHP`.
|
||||
* How to use the `XRPL_PHP` library to look up information about an account on the XRP Ledger.
|
||||
* How to put these steps together to create a simple application.
|
||||
|
||||
## Requirements
|
||||
|
||||
* `XRPL_PHP` requires PHP 8.1 and the PHP extension [GMP](http://php.net/manual/en/book.gmp.php).
|
||||
* The PHP extension [BCMATH](https://www.php.net/manual/de/book.bc.php) is recommended for increased performance.
|
||||
|
||||
## Installation
|
||||
|
||||
`XRPL_PHP` can be installed via [Composer](https://getcomposer.org/doc/00-intro.md):
|
||||
|
||||
```console
|
||||
composer require hardcastle/xrpl_php
|
||||
```
|
||||
|
||||
|
||||
## Start Building
|
||||
|
||||
When you're working with the XRP Ledger, there are a few things you'll need to manage, whether you're adding XRP to your [account](../../concepts/accounts/accounts.md), integrating with the [decentralized exchange](../../concepts/tokens/decentralized-exchange/index.md), or [issuing tokens](../../concepts/tokens/index.md). This tutorial walks you through basic patterns common to getting started with all of these use cases and provides sample code for implementing them.
|
||||
|
||||
Here are the basic steps you'll need to cover for almost any XRP Ledger project:
|
||||
|
||||
1. [Connect to the XRP Ledger.](#1-connect-to-the-xrp-ledger)
|
||||
1. [Get an account.](#2-get-account)
|
||||
1. [Query the XRP Ledger.](#3-query-the-xrp-ledger)
|
||||
|
||||
|
||||
### 1. Connect to the XRP Ledger
|
||||
|
||||
To make queries and submit transactions, you need to connect to the XRP Ledger. To do this with `XRPL_PHP`, you can use the [`JsonRpcClient`](https://alexanderbuzz.github.io/xrpl-php-docs/client.html):
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// Use the Composer autoloader
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Imports
|
||||
use XRPL_PHP\Client\JsonRpcClient;
|
||||
|
||||
// Create a Client using the Testnet
|
||||
$client = new JsonRpcClient("https://s.altnet.rippletest.net:51234");
|
||||
```
|
||||
|
||||
Note that PHP has no native support for WebSockets, so the Client does not establish a permanent connection.
|
||||
|
||||
#### Connect to the production XRP Ledger
|
||||
|
||||
The sample code in the previous section shows you how to connect to the Testnet, which is one of the available [parallel networks](../../concepts/networks-and-servers/parallel-networks.md). When you're ready to integrate with the production XRP Ledger, you'll need to connect to the Mainnet. You can do that in two ways:
|
||||
|
||||
* By [installing the core server](../../infrastructure/installation/index.md) (`rippled`) and running a node yourself. The core server connects to the Mainnet by default, but you can [change the configuration to use Testnet or Devnet](../../infrastructure/configuration/connect-your-rippled-to-the-xrp-test-net.md). [There are good reasons to run your own core server](../../concepts/networks-and-servers/index.md#reasons-to-run-your-own-server). If you run your own server, you can connect to it like so:
|
||||
|
||||
```
|
||||
use XRPL_PHP\Client\JsonRpcClient;
|
||||
|
||||
const LOCAL_JSON_RPC_URL = "http://localhost:5005/";
|
||||
$client = new JsonRpcClient("LOCAL_JSON_RPC_URL");
|
||||
```
|
||||
|
||||
See the example [core server config file](https://github.com/XRPLF/rippled/blob/c0a0b79d2d483b318ce1d82e526bd53df83a4a2c/cfg/rippled-example.cfg#L1562) for more information about default values.
|
||||
|
||||
* By using one of the available [public servers][]:
|
||||
|
||||
```
|
||||
use XRPL_PHP\Client\JsonRpcClient;
|
||||
|
||||
const MAINNET_JSON_RPC_URL = "https://s2.ripple.com:51234/";
|
||||
$client = new JsonRpcClient("MAINNET_JSON_RPC_URL");
|
||||
```
|
||||
|
||||
### 2. Get account
|
||||
|
||||
To store value and execute transactions on the XRP Ledger, you need to get an account: a [set of keys](../../concepts/accounts/cryptographic-keys.md#key-components) and an [address](../../concepts/accounts/addresses.md) that's been [funded with enough XRP](../../concepts/accounts/accounts.md#creating-accounts) to meet the [account reserve](../../concepts/accounts/reserves.md). The address is the identifier of your account and you use the [private key](../../concepts/accounts/cryptographic-keys.md#private-key) to sign transactions that you submit to the XRP Ledger. For production purposes, you should take care to store your keys and set up a [secure signing method](../../concepts/transactions/secure-signing.md).
|
||||
|
||||
To generate a new account, `PHP_XRPL` provides the static `generate()` method in the `Wallet` class:
|
||||
|
||||
```php
|
||||
<?php
|
||||
|
||||
// Use the Composer autoloader
|
||||
require __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Imports
|
||||
use XRPL_PHP\Wallet\Wallet;
|
||||
|
||||
// Create a new wallet
|
||||
$wallet = Wallet::generate();
|
||||
```
|
||||
|
||||
The result returns a `Wallet` instance:
|
||||
|
||||
```php
|
||||
// print wallet properties
|
||||
print_r([
|
||||
'publicKey' => $wallet->getPublicKey(),
|
||||
'privateKey' => $wallet->getPrivateKey(),
|
||||
'classicAddress' => $wallet->getAddress(),
|
||||
'seed' => $wallet->getSeed()
|
||||
]);
|
||||
|
||||
// output
|
||||
// Array
|
||||
// (
|
||||
// [publicKey] => ED2C4CE69F663254840905AEF5FB8596FC243EDEBE0295A6ECEE86CE8EB8F76210
|
||||
// [privateKey] => -HIDDEN-
|
||||
// [classicAddress] => rBi9u1P3ofRKTFPFTgrguANz2wRqsdKHvm
|
||||
// [seed] => -HIDDEN-
|
||||
//)
|
||||
|
||||
```
|
||||
|
||||
For testing and development purposes, you can use the `fundWallet()` helper function on the XRP Ledger [Testnet](../../concepts/networks-and-servers/parallel-networks.md):
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/php/get-account-info.php" from="<?php" before="// Create an AccountInfoRequest" language="php" /%}
|
||||
|
||||
### 3. Query the XRP Ledger
|
||||
|
||||
You can query the XRP Ledger to get information about [a specific account](../../references/http-websocket-apis/public-api-methods/account-methods/index.md), [a specific transaction](../../references/http-websocket-apis/public-api-methods/transaction-methods/tx.md), the state of a [current or a historical ledger](../../references/http-websocket-apis/public-api-methods/ledger-methods/index.md), and [the XRP Ledger's decentralized exchange](../../references/http-websocket-apis/public-api-methods/path-and-order-book-methods/index.md). You need to make these queries, among other reasons, to look up account info to follow best practices for [reliable transaction submission](../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
Here, we'll use the [`JsonRpcClient` we constructed](#1-connect-to-the-xrp-ledger) to look up information about the [account we got](#2-get-account) in the previous step.
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/php/get-account-info.php" from="// Create an AccountInfoRequest" language="php" /%}
|
||||
|
||||
### 4. Starting the script
|
||||
|
||||
Now, we have a simple application that:
|
||||
|
||||
1. Creates an account on the Testnet.
|
||||
2. Connects to the XRP Ledger.
|
||||
3. Looks up and prints information about the account you created.
|
||||
|
||||
To run the app, you can copy the code from {% repo-link path="content/_code-samples/get-started/php/" %}this website's GitHub Repository{% /repo-link %} and run it from the command line:
|
||||
|
||||
```console
|
||||
composer require hardcastle/xrpl_php
|
||||
php get-account-info.php
|
||||
```
|
||||
|
||||
You should see output similar to this example:
|
||||
|
||||
```console
|
||||
XRPL_PHP\Models\Account\AccountInfoResponse Object
|
||||
(
|
||||
[id:protected] =>
|
||||
[result:protected] => Array
|
||||
(
|
||||
[account_data] => Array
|
||||
(
|
||||
[Account] => rDTRjR6sWrRmGe18KMVwBuL212gLpArVLy
|
||||
[Balance] => 10000000000
|
||||
[Flags] => 0
|
||||
[LedgerEntryType] => AccountRoot
|
||||
[OwnerCount] => 0
|
||||
[PreviousTxnID] => AE18C0B30DE740490E66E92D9F45162C8860A6D9FCF279CF7A51FAFE05F573FB
|
||||
[PreviousTxnLgrSeq] => 42719256
|
||||
[Sequence] => 42719256
|
||||
[index] => 4DD9F7FAE7365B7A917932D6453DBA9B223AA4FF7193691EF6E5EE230519F4CA
|
||||
)
|
||||
|
||||
[account_flags] => Array
|
||||
(
|
||||
[defaultRipple] =>
|
||||
[depositAuth] =>
|
||||
[disableMasterKey] =>
|
||||
[disallowIncomingCheck] =>
|
||||
[disallowIncomingNFTokenOffer] =>
|
||||
[disallowIncomingPayChan] =>
|
||||
[disallowIncomingTrustline] =>
|
||||
[disallowIncomingXRP] =>
|
||||
[globalFreeze] =>
|
||||
[noFreeze] =>
|
||||
[passwordSpent] =>
|
||||
[requireAuthorization] =>
|
||||
[requireDestinationTag] =>
|
||||
)
|
||||
|
||||
[ledger_hash] => 57F0CB8311CDEAE9AC60854CC482990CE971D1FD36DB1CC215B7A6634E27E739
|
||||
[ledger_index] => 42719256
|
||||
[status] => success
|
||||
[validated] => 1
|
||||
)
|
||||
|
||||
[warnings:protected] =>
|
||||
[status:protected] => success
|
||||
[type:protected] => response
|
||||
)
|
||||
```
|
||||
|
||||
#### Interpreting the response
|
||||
|
||||
The response fields contained in `AccountInfoResponse` that you want to inspect in most cases are:
|
||||
|
||||
* `['account_data']['Sequence']` — This is the sequence number of the next valid transaction for the account. You need to specify the sequence number when you prepare transactions.
|
||||
|
||||
* `['account_data']['Balance']` — This is the account's balance of XRP, in drops. You can use this to confirm that you have enough XRP to send (if you're making a payment) and to meet the [current transaction cost](../../concepts/transactions/transaction-cost.md#current-transaction-cost) for a given transaction.
|
||||
|
||||
* `['validated']` — Indicates whether the returned data is from a [validated ledger](../../concepts/ledgers/open-closed-validated-ledgers.md). When inspecting transactions, it's important to confirm that [the results are final](../../concepts/transactions/finality-of-results/index.md) before further processing the transaction. If `validated` is `true` then you know for sure the results won't change. For more information about best practices for transaction processing, see [Reliable Transaction Submission](../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
For a detailed description of every response field, see [account_info](../../references/http-websocket-apis/public-api-methods/account-methods/account_info.md#response-format).
|
||||
|
||||
|
||||
## Keep on building
|
||||
|
||||
Now that you know how to use `XRPL_PHP` to connect to the XRP Ledger, get an account, and look up information about it, you can also use `XRPL_PHP` to:
|
||||
|
||||
* [Send XRP](send-xrp.md).
|
||||
* [Set up secure signing](../../concepts/transactions/secure-signing.md) for your account.
|
||||
|
||||
{% raw-partial file="/_snippets/common-links.md" /%}
|
||||
216
docs/tutorials/get-started/get-started-using-python.md
Normal file
216
docs/tutorials/get-started/get-started-using-python.md
Normal file
@@ -0,0 +1,216 @@
|
||||
---
|
||||
html: get-started-using-python.html
|
||||
parent: python.html
|
||||
seo:
|
||||
description: Build a Python app that interacts with the XRP Ledger.
|
||||
cta_text: Build an XRP Ledger-connected app
|
||||
top_nav_name: Python
|
||||
top_nav_grouping: Get Started
|
||||
labels:
|
||||
- Development
|
||||
showcase_icon: assets/img/logos/python.svg
|
||||
---
|
||||
# Get Started Using Python
|
||||
|
||||
This tutorial walks you through the basics of building an XRP Ledger-connected application using [`xrpl-py`](https://github.com/XRPLF/xrpl-py), a pure [Python](https://www.python.org) library built to interact with the XRP Ledger using native Python models and methods.
|
||||
|
||||
This tutorial is intended for beginners and should take no longer than 30 minutes to complete.
|
||||
|
||||
## Learning Goals
|
||||
|
||||
In this tutorial, you'll learn:
|
||||
|
||||
* The basic building blocks of XRP Ledger-based applications.
|
||||
* How to connect to the XRP Ledger using `xrpl-py`.
|
||||
* How to get an account on the [Testnet](/resources/dev-tools/xrp-faucets) using `xrpl-py`.
|
||||
* How to use the `xrpl-py` library to look up information about an account on the XRP Ledger.
|
||||
* How to put these steps together to create a Python app.
|
||||
|
||||
## Requirements
|
||||
|
||||
The `xrpl-py` library supports [Python 3.7](https://www.python.org/downloads/) and later.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
The [`xrpl-py` library](https://github.com/XRPLF/xrpl-py) is available on [PyPI](https://pypi.org/project/xrpl-py/). Install with `pip`: <!-- SPELLING_IGNORE: pypi -->
|
||||
|
||||
|
||||
```py
|
||||
pip3 install xrpl-py
|
||||
```
|
||||
|
||||
## Start Building
|
||||
|
||||
When you're working with the XRP Ledger, there are a few things you'll need to manage, whether you're adding XRP to your [account](../../concepts/accounts/accounts.md), integrating with the [decentralized exchange](../../concepts/tokens/decentralized-exchange/index.md), or [issuing tokens](../../concepts/tokens/index.md). This tutorial walks you through basic patterns common to getting started with all of these use cases and provides sample code for implementing them.
|
||||
|
||||
Here are the basic steps you'll need to cover for almost any XRP Ledger project:
|
||||
|
||||
1. [Connect to the XRP Ledger.](#1-connect-to-the-xrp-ledger)
|
||||
1. [Get an account.](#2-get-account)
|
||||
1. [Query the XRP Ledger.](#3-query-the-xrp-ledger)
|
||||
|
||||
|
||||
### 1. Connect to the XRP Ledger
|
||||
|
||||
To make queries and submit transactions, you need to connect to the XRP Ledger. To do this with `xrpl-py`, use the [`xrp.clients` module](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.clients.html):
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/get-acct-info.py" from="# Define the network client" before="# Create a wallet using the testnet faucet:" language="py" /%}
|
||||
|
||||
#### Connect to the production XRP Ledger
|
||||
|
||||
The sample code in the previous section shows you how to connect to the Testnet, which is a [parallel network](../../concepts/networks-and-servers/parallel-networks.md) for testing where the money has no real value. When you're ready to integrate with the production XRP Ledger, you'll need to connect to the Mainnet. You can do that in two ways:
|
||||
|
||||
* By [installing the core server](../../infrastructure/installation/index.md) (`rippled`) and running a node yourself. The core server connects to the Mainnet by default, but you can [change the configuration to use Testnet or Devnet](../../infrastructure/configuration/connect-your-rippled-to-the-xrp-test-net.md). [There are good reasons to run your own core server](../../concepts/networks-and-servers/index.md#reasons-to-run-your-own-server). If you run your own server, you can connect to it like so:
|
||||
|
||||
```
|
||||
from xrpl.clients import JsonRpcClient
|
||||
JSON_RPC_URL = "http://localhost:5005/"
|
||||
client = JsonRpcClient(JSON_RPC_URL)
|
||||
```
|
||||
|
||||
See the example [core server config file](https://github.com/XRPLF/rippled/blob/c0a0b79d2d483b318ce1d82e526bd53df83a4a2c/cfg/rippled-example.cfg#L1562) for more information about default values.
|
||||
|
||||
* By using one of the available [public servers][]:
|
||||
|
||||
```
|
||||
from xrpl.clients import JsonRpcClient
|
||||
JSON_RPC_URL = "https://s2.ripple.com:51234/"
|
||||
client = JsonRpcClient(JSON_RPC_URL)
|
||||
```
|
||||
|
||||
|
||||
### 2. Get account
|
||||
|
||||
To store value and execute transactions on the XRP Ledger, you need an account: a [set of keys](../../concepts/accounts/cryptographic-keys.md#key-components) and an [address](../../concepts/accounts/addresses.md) that's been [funded with enough XRP](../../concepts/accounts/accounts.md#creating-accounts) to meet the [account reserve](../../concepts/accounts/reserves.md). The address is the identifier of your account and you use the [private key](../../concepts/accounts/cryptographic-keys.md#private-key) to sign transactions that you submit to the XRP Ledger.
|
||||
|
||||
For testing and development purposes, you can use the [XRP Faucets](/resources/dev-tools/xrp-faucets) to generate keys and fund the account on the Testnet or Devnet. For production purposes, you should take care to store your keys and set up a [secure signing method](../../concepts/transactions/secure-signing.md). Another difference in production is that XRP has real worth, so you can't get it for free from a faucet.
|
||||
|
||||
To create and fund an account on the Testnet, `xrpl-py` provides the [`generate_faucet_wallet`](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.wallet.html#xrpl.wallet.generate_faucet_wallet) method:
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/get-acct-info.py" from="# Create a wallet using the testnet faucet:" before="# Create an account str from the wallet" language="py" /%}
|
||||
|
||||
|
||||
This method returns a [`Wallet` instance](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.wallet.html#xrpl.wallet.Wallet):
|
||||
|
||||
|
||||
```py
|
||||
print(test_wallet)
|
||||
|
||||
# print output
|
||||
|
||||
public_key:: 022FA613294CD13FFEA759D0185007DBE763331910509EF8F1635B4F84FA08AEE3
|
||||
private_key:: -HIDDEN-
|
||||
classic_address: raaFKKmgf6CRZttTVABeTcsqzRQ51bNR6Q
|
||||
```
|
||||
|
||||
#### Using the account
|
||||
|
||||
In this tutorial we only query details about the generated account from the XRP Ledger, but you can use the values in the `Wallet` instance to prepare, sign, and submit transactions with `xrpl-py`.
|
||||
|
||||
##### Prepare
|
||||
|
||||
To prepare the transaction:
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/prepare-payment.py" from="# Prepare payment" before="# print prepared payment" language="py" /%}
|
||||
|
||||
|
||||
|
||||
##### Sign and submit
|
||||
|
||||
To sign and submit the transaction:
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/prepare-payment.py" from="# Sign and submit the transaction" before="# Print tx response" language="py" /%}
|
||||
|
||||
|
||||
##### Derive an X-address
|
||||
|
||||
You can use `xrpl-py`'s [`xrpl.core.addresscodec`](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.core.addresscodec.html) module to derive an [X-address](https://xrpaddress.info/) from the `Wallet.address` field:
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/get-acct-info.py" from="# Derive an x-address from the classic address:" before="# Look up info about your account" language="py" /%}
|
||||
|
||||
The X-address format [packs the address and destination tag](https://github.com/XRPLF/XRPL-Standards/issues/6) into a more user-friendly value.
|
||||
|
||||
|
||||
### 3. Query the XRP Ledger
|
||||
|
||||
You can query the XRP Ledger to get information about [a specific account](../../references/http-websocket-apis/public-api-methods/account-methods/index.md), [a specific transaction](../../references/http-websocket-apis/public-api-methods/transaction-methods/tx.md), the state of a [current or a historical ledger](../../references/http-websocket-apis/public-api-methods/ledger-methods/index.md), and [the XRP Ledger's decentralized exchange](../../references/http-websocket-apis/public-api-methods/path-and-order-book-methods/index.md). You need to make these queries, among other reasons, to look up account info to follow best practices for [reliable transaction submission](../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
Here, we use `xrpl-py`'s [`xrpl.account`](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.account.html) module to look up information about the [account we got](#2-get-account) in the previous step.
|
||||
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/get-acct-info.py" from="# Look up info about your account" language="py" /%}
|
||||
|
||||
|
||||
|
||||
### 4. Putting it all together
|
||||
|
||||
Using these building blocks, we can create a Python app that:
|
||||
|
||||
1. Gets an account on the Testnet.
|
||||
2. Connects to the XRP Ledger.
|
||||
3. Looks up and prints information about the account you created.
|
||||
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/get-acct-info.py" language="python" /%}
|
||||
|
||||
To run the app, you can copy and paste the code into an editor or IDE and run it from there. Or you could download the file from the [XRP Ledger Dev Portal repo](https://github.com/XRPLF/xrpl-dev-portal/tree/master/content/_code-samples/get-started/py) and run it locally:
|
||||
|
||||
|
||||
```sh
|
||||
git clone git@github.com:XRPLF/xrpl-dev-portal.git
|
||||
cd xrpl-dev-portal/content/_code-samples/get-started/py/get-acct-info.py
|
||||
python3 get-acct-info.py
|
||||
```
|
||||
|
||||
You should see output similar to this example:
|
||||
|
||||
```sh
|
||||
Classic address:
|
||||
|
||||
rnQLnSEA1YFMABnCMrkMWFKxnqW6sQ8EWk
|
||||
X-address:
|
||||
|
||||
T7dRN2ktZGYSTyEPWa9SyDevrwS5yDca4m7xfXTGM3bqff8
|
||||
response.status: ResponseStatus.SUCCESS
|
||||
{
|
||||
"account_data": {
|
||||
"Account": "rnQLnSEA1YFMABnCMrkMWFKxnqW6sQ8EWk",
|
||||
"Balance": "1000000000",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 0,
|
||||
"PreviousTxnID": "5A5203AFF41503539D11ADC41BE4185761C5B78B7ED382E6D001ADE83A59B8DC",
|
||||
"PreviousTxnLgrSeq": 16126889,
|
||||
"Sequence": 16126889,
|
||||
"index": "CAD0F7EF3AB91DA7A952E09D4AF62C943FC1EEE41BE926D632DDB34CAA2E0E8F"
|
||||
},
|
||||
"ledger_current_index": 16126890,
|
||||
"queue_data": {
|
||||
"txn_count": 0
|
||||
},
|
||||
"validated": false
|
||||
}
|
||||
```
|
||||
|
||||
#### Interpreting the response
|
||||
|
||||
The response fields that you want to inspect in most cases are:
|
||||
|
||||
* `account_data.Sequence` — This is the sequence number of the next valid transaction for the account. You need to specify the sequence number when you prepare transactions. With `xrpl-py`, you can use the [`get_next_valid_seq_number`](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.account.html#xrpl.account.get_next_valid_seq_number) to get this automatically from the XRP Ledger. See an example of this usage in the project [README](https://github.com/XRPLF/xrpl-py#serialize-and-sign-transactions).
|
||||
|
||||
* `account_data.Balance` — This is the account's balance of [XRP, in drops][]. You can use this to confirm that you have enough XRP to send (if you're making a payment) and to meet the [current transaction cost](../../concepts/transactions/transaction-cost.md#current-transaction-cost) for a given transaction.
|
||||
|
||||
* `validated` — Indicates whether the returned data is from a [validated ledger](../../concepts/ledgers/open-closed-validated-ledgers.md). When inspecting transactions, it's important to confirm that [the results are final](../../concepts/transactions/finality-of-results/index.md) before further processing the transaction. If `validated` is `true` then you know for sure the results won't change. For more information about best practices for transaction processing, see [Reliable Transaction Submission](../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
For a detailed description of every response field, see [account_info](../../references/http-websocket-apis/public-api-methods/account-methods/account_info.md#response-format).
|
||||
|
||||
|
||||
## Keep on building
|
||||
|
||||
Now that you know how to use `xrpl-py` to connect to the XRP Ledger, get an account, and look up information about it, you can also use `xrpl-py` to:
|
||||
|
||||
* [Send XRP](send-xrp.md).
|
||||
* [Set up secure signing](../../concepts/transactions/secure-signing.md) for your account.
|
||||
|
||||
{% raw-partial file="/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,516 @@
|
||||
---
|
||||
html: monitor-incoming-payments-with-websocket.html
|
||||
parent: http-websocket-apis-tutorials.html
|
||||
seo:
|
||||
description: 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](../../concepts/payment-types/index.md) using the [WebSocket API](../../references/http-websocket-apis/index.md). 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](../../infrastructure/installation/index.md), 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](../../concepts/tokens/index.md), you need even more precision. For more information, see [Currency Precision](../../references/protocol/data-types/currency-formats.md#xrp-precision).
|
||||
|
||||
|
||||
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
|
||||
<!-- Big number support -->
|
||||
<script type="application/javascript" src="/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>
|
||||
|
||||
|
||||
## 1. 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](/resources/dev-tools/xrp-faucets). 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](../../references/http-websocket-apis/admin-api-methods/index.md) and admin-only data in some responses such as [server_info][server_info method], plus the [public methods](../../references/http-websocket-apis/public-api-methods/index.md) that are available when you connect to public servers over the internet.
|
||||
|
||||
Example:
|
||||
|
||||
{% interactive-block label="Connect" steps=$frontmatter.steps %}
|
||||
|
||||
<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>
|
||||
|
||||
{% /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. 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](../../references/http-websocket-apis/api-conventions/response-formatting.md). The string value `error` indicates [an error](../../references/http-websocket-apis/api-conventions/error-formatting.md).
|
||||
|
||||
**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](../../concepts/transactions/finality-of-results/look-up-transaction-results.md).
|
||||
|
||||
- For follow-up messages from [subscriptions](../../references/http-websocket-apis/public-api-methods/subscription-methods/subscribe.md), 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](../../references/http-websocket-apis/public-api-methods/path-and-order-book-methods/path_find.md). 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
|
||||
```
|
||||
|
||||
{% interactive-block label="Dispatch Messages" steps=$frontmatter.steps %}
|
||||
|
||||
<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>
|
||||
|
||||
{% /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 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](/resources/dev-tools/tx-sender) in a different window or even on a different device and sending transactions to the address you subscribed to:
|
||||
|
||||
{% interactive-block label="Subscribe" steps=$frontmatter.steps %}
|
||||
|
||||
<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>
|
||||
|
||||
{% /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 +
|
||||
"<br/> Result: " + tx.meta.TransactionResult +
|
||||
" in ledger " + tx.ledger_index +
|
||||
"<br/> Validated? " + tx.validated)
|
||||
}
|
||||
WS_HANDLERS["transaction"] = log_tx
|
||||
</script>
|
||||
|
||||
|
||||
## 4. 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](../../concepts/tokens/index.md). 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](../../concepts/transactions/finality-of-results/index.md). 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](../../references/protocol/transactions/transaction-results/transaction-results.md). 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](../../concepts/tokens/index.md). 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](../../references/protocol/data-types/basic-data-types.md#specifying-currency-amounts).
|
||||
|
||||
**Warning:** If you use the `transaction.Amount` field instead, you may be vulnerable to the [partial payments exploit](../../concepts/payment-types/partial-payments.md#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](../../concepts/payment-types/escrow.md) 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](../../concepts/tokens/decentralized-exchange/index.md). 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](../../concepts/payment-types/payment-channels.md). 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](../../references/protocol/transactions/metadata.md), including exactly how much of which currency or currencies was delivered where. See [Look Up transaction Results](../../concepts/transactions/finality-of-results/look-up-transaction-results.md) 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:
|
||||
|
||||
{% code-snippet file="/_code-samples/monitor-payments-websocket/js/read-amount-received.js" language="js" /%}
|
||||
|
||||
{% interactive-block label="Read Payments" steps=$frontmatter.steps %}
|
||||
|
||||
<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>
|
||||
|
||||
{% /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<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](../../concepts/transactions/finality-of-results/look-up-transaction-results.md) to see exactly what a transaction did, and build your software to react appropriately.
|
||||
- Try [Sending XRP](send-xrp.md) from your own address.
|
||||
- Try monitoring for transactions of advanced types like [Escrows](../../concepts/payment-types/escrow.md), [Checks](../../concepts/payment-types/checks.md), or [Payment Channels](../../concepts/payment-types/payment-channels.md), 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:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="Go" %}
|
||||
{% code-snippet file="/_code-samples/monitor-payments-websocket/go/monitor-incoming-payments.go" language="go" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/monitor-payments-websocket/py/monitor_incoming.py" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**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](../../concepts/transactions/index.md)
|
||||
- [Finality of Results](../../concepts/transactions/finality-of-results/index.md) - 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](../../concepts/transactions/reliable-transaction-submission.md)
|
||||
- [Look Up Transaction Results](../../concepts/transactions/finality-of-results/look-up-transaction-results.md)
|
||||
- **References:**
|
||||
- [Transaction Types](../../references/protocol/transactions/types/index.md)
|
||||
- [Transaction Metadata](../../references/protocol/transactions/metadata.md) - Summary of the metadata format and fields that appear in metadata
|
||||
- [Transaction Results](../../references/protocol/transactions/transaction-results/transaction-results.md) - Tables of all possible result codes for transactions.
|
||||
|
||||
{% raw-partial file="/_snippets/common-links.md" /%}
|
||||
32
docs/tutorials/get-started/public-servers.md
Normal file
32
docs/tutorials/get-started/public-servers.md
Normal file
@@ -0,0 +1,32 @@
|
||||
---
|
||||
html: public-servers.html
|
||||
parent: tutorials.html
|
||||
seo:
|
||||
description: Use these public servers to access the XRP Ledger without needing your own infrastructure.
|
||||
labels:
|
||||
- Core Server
|
||||
---
|
||||
# Public Servers
|
||||
|
||||
If you don't [run your own `rippled` server](../../infrastructure/installation/index.md), you can use the following public servers to submit transactions or read data from the ledger.
|
||||
|
||||
| Operator | [Network][] | JSON-RPC URL | WebSocket URL | Notes |
|
||||
|:----------|:------------|:-------------|:--------------|:---------------------|
|
||||
| XRP Ledger Foundation | **Mainnet** | `https://xrplcluster.com/` <br> `https://xrpl.ws/` [²][] | `wss://xrplcluster.com/` <br> `wss://xrpl.ws/` [²][] | Full history server cluster with CORS support. |
|
||||
| Ripple[¹][] | **Mainnet** | `https://s1.ripple.com:51234/` | `wss://s1.ripple.com/` | General purpose server cluster |
|
||||
| Ripple[¹][] | **Mainnet** | `https://s2.ripple.com:51234/` | `wss://s2.ripple.com/` | [Full-history server](../../concepts/networks-and-servers/ledger-history.md#full-history) cluster |
|
||||
| Ripple[¹][] | Testnet | `https://s.altnet.rippletest.net:51234/` | `wss://s.altnet.rippletest.net:51233/` | Testnet public server |
|
||||
| XRPL Labs | Testnet | `https://testnet.xrpl-labs.com/` | `wss://testnet.xrpl-labs.com/` | Testnet public server with CORS support |
|
||||
| Ripple[¹][] | Devnet | `https://s.devnet.rippletest.net:51234/` | `wss://s.devnet.rippletest.net:51233/` | Devnet public server |
|
||||
| Ripple[¹][] | Sidechain-Devnet | `https://sidechain-net2.devnet.rippletest.net:51234/` | `wss://sidechain-net2.devnet.rippletest.net:51233/` | Sidechain Devnet to test cross-chain bridge features. Devnet serves as the locking chain while this sidechain serves as the issuing chain. |
|
||||
| XRPL Labs | Xahau Testnet | `https://xahau-test.net/` | `wss://xahau-test.net/` | [Hooks-enabled](https://hooks.xrpl.org/) Xahau Testnet |
|
||||
|
||||
[Network]: ../../concepts/networks-and-servers/parallel-networks.md
|
||||
[¹]: #footnote-1
|
||||
[²]: #footnote-2
|
||||
|
||||
<a id="footnote-1"></a>¹ Ripple's public servers are not for sustained or business use, and they may become unavailable at any time. For regular use, you should run your own `rippled` server or contract someone you trust to do so. Ripple includes [Reporting Mode][] servers in its public clusters.
|
||||
|
||||
<a id="footnote-2"></a>² `xrpl.ws` is an alias for `xrplcluster.com`. However, the `.ws` top-level domain's reliability may be unsuitable for production uses.
|
||||
|
||||
{% raw-partial file="/_snippets/common-links.md" /%}
|
||||
488
docs/tutorials/get-started/send-xrp.md
Normal file
488
docs/tutorials/get-started/send-xrp.md
Normal file
@@ -0,0 +1,488 @@
|
||||
---
|
||||
html: send-xrp.html
|
||||
parent: tasks.html
|
||||
seo:
|
||||
description: Learn how to send test payments right from your browser.
|
||||
cta_text: Send XRP
|
||||
labels:
|
||||
- XRP
|
||||
- Payments
|
||||
top_nav_grouping: Popular Pages
|
||||
steps: ['Generate', 'Connect', 'Prepare', 'Sign', 'Submit', 'Wait', 'Check']
|
||||
---
|
||||
# Send XRP
|
||||
|
||||
This tutorial explains how to send a direct XRP Payment using `xrpl.js` for JavaScript, `xrpl-py` for Python, `xrpl4j` for Java or `XRPL_PHP` for PHP. First, we step through the process with the [XRP Ledger Testnet](../../concepts/networks-and-servers/parallel-networks.md). Then, we compare that to the additional requirements for doing the equivalent in production.
|
||||
|
||||
**Tip:** Check out the [Code Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/content/_code-samples) for a complete version of the code used in this tutorial.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
<!-- Source for this specific tutorial's interactive bits: -->
|
||||
<script type="application/javascript" src="/js/interactive-tutorial.js"></script>
|
||||
<script type="application/javascript" src="/js/tutorials/send-xrp.js"></script>
|
||||
|
||||
To interact with the XRP Ledger, you need to set up a dev environment with the necessary tools. This tutorial provides examples using the following options:
|
||||
|
||||
- **JavaScript** with the [xrpl.js library](https://github.com/XRPLF/xrpl.js/). See [Get Started Using JavaScript](get-started-using-javascript.md) for setup steps.
|
||||
- **Python** with the [`xrpl-py` library](https://xrpl-py.readthedocs.io/). See [Get Started using Python](get-started-using-python.md) for setup steps.
|
||||
- **Java** with the [xrpl4j library](https://github.com/XRPLF/xrpl4j). See [Get Started Using Java](get-started-using-java.md) for setup steps.
|
||||
- **PHP** with the [XRPL_PHP library](https://github.com/AlexanderBuzz/xrpl-php). See [Get Started Using PHP](get-started-using-php.md) for setup steps.
|
||||
|
||||
|
||||
## Send a Payment on the Test Net
|
||||
|
||||
### 1. Get Credentials
|
||||
|
||||
To transact on the XRP Ledger, you need an address and secret key, and some XRP. The address and secret key look like this:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/js/send-xrp.js" from="// Example credentials" before="// Connect" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" before="# Connect" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" before="// Connect" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Example credentials" before="// Create" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
The secret key shown here is for example only. For development purposes, you can get your own credentials, pre-funded with XRP, on the Testnet using the following interface:
|
||||
|
||||
{% partial file="/_snippets/interactive-tutorials/generate-step.md" /%}
|
||||
|
||||
When you're building production-ready software, you should use an existing account, and manage your keys using a [secure signing configuration](../../concepts/transactions/secure-signing.md).
|
||||
|
||||
|
||||
### 2. Connect to a Testnet Server
|
||||
|
||||
First, you must connect to an XRP Ledger server so you can get the current status of your account and the shared ledger. You can use this information to [automatically fill in some required fields of a transaction](../../references/protocol/transactions/common-fields.md#auto-fillable-fields). You also must be connected to the network to submit transactions to it.
|
||||
|
||||
The following code connects to a public Testnet servers:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/get-started/js/base.js" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" from="# Connect" before="# Get credentials" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" from="// Connect" before="// Prepare transaction" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Create a client" before="// Transaction definition" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
For this tutorial, click the following button to connect:
|
||||
|
||||
{% partial file="/_snippets/interactive-tutorials/connect-step.md" /%}
|
||||
|
||||
|
||||
### 3. Prepare Transaction
|
||||
|
||||
Typically, we create XRP Ledger transactions as objects in the JSON [transaction format](../../references/protocol/transactions/index.md). The following example shows a minimal Payment specification:
|
||||
|
||||
```json
|
||||
{
|
||||
"TransactionType": "Payment",
|
||||
"Account": "rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe",
|
||||
"Amount": "2000000",
|
||||
"Destination": "rUCzEr6jrEyMpjhs4wSdQdz4g8Y382NxfM"
|
||||
}
|
||||
```
|
||||
|
||||
The bare minimum set of instructions you must provide for an XRP Payment is:
|
||||
|
||||
- An indicator that this is a payment. (`"TransactionType": "Payment"`)
|
||||
- The sending address. (`"Account"`)
|
||||
- The address that should receive the XRP (`"Destination"`). This can't be the same as the sending address.
|
||||
- The amount of XRP to send (`"Amount"`). Typically, this is specified as an integer in "drops" of XRP, where 1,000,000 drops equals 1 XRP.
|
||||
|
||||
Technically, a transaction must contain some additional fields, and certain optional fields such as `LastLedgerSequence` are strongly recommended. Some other language-specific notes:
|
||||
|
||||
- If you're using `xrpl.js` for JavaScript, you can use the [`Client.autofill()` method](https://js.xrpl.org/classes/Client.html#autofill) to automatically fill in good defaults for the remaining fields of a transaction. In TypeScript, you can also use the transaction models like `xrpl.Payment` to enforce the correct fields.
|
||||
- With `xrpl-py` for Python, you can use the models in `xrpl.models.transactions` to construct transactions as native Python objects.
|
||||
- With xrpl4j for Java, you can use the model objects in the `xrpl4j-model` module to construct transactions as Java objects.
|
||||
- Unlike the other libraries, you must provide the account `sequence` and the `signingPublicKey` of the source
|
||||
account of a `Transaction` at the time of construction, as well as a `fee`.
|
||||
|
||||
Here's an example of preparing the above payment:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/js/send-xrp.js" from="// Prepare" before="// Sign" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" from="# Prepare" before="# Sign" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" from="// Prepare" before="// Sign" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Transaction definition" before="// Sign" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% interactive-block label="Prepare" steps=$frontmatter.steps %}
|
||||
|
||||
<div class="input-group mb-3">
|
||||
<div class="input-group-prepend">
|
||||
<span class="input-group-text">Send: </span>
|
||||
</div>
|
||||
<input type="number" class="form-control" value="22" id="xrp-amount"
|
||||
aria-label="Amount of XRP, as a decimal" aria-describedby="xrp-amount-label"
|
||||
min=".000001" max="100000000000" step="any" />
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text" id="xrp-amount-label"> XRP</span>
|
||||
</div>
|
||||
</div>
|
||||
<button id="prepare-button" class="btn btn-primary previous-steps-required">Prepare
|
||||
example transaction</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 4. Sign the Transaction Instructions
|
||||
|
||||
Signing a transaction uses your credentials to authorize the transaction on your behalf. The input to this step is a completed set of transaction instructions (usually JSON), and the output is a binary blob containing the instructions and a signature from the sender.
|
||||
|
||||
- **JavaScript:** Use the [`sign()` method of a `Wallet` instance](https://js.xrpl.org/classes/Wallet.html#sign) to sign the transaction with `xrpl.js`.
|
||||
- **Python:** Use the [`xrpl.transaction.safe_sign_transaction()` method](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.transaction.html#xrpl.transaction.safe_sign_transaction) with a model and `Wallet` object.
|
||||
- **Java:** Use a [`SignatureService`](https://javadoc.io/doc/org.xrpl/xrpl4j-crypto-core/latest/org/xrpl/xrpl4j/crypto/signing/SignatureService.html) instance to sign the transaction. For this tutorial, use the [`SingleKeySignatureService`](https://javadoc.io/doc/org.xrpl/xrpl4j-crypto-bouncycastle/latest/org/xrpl/xrpl4j/crypto/signing/SingleKeySignatureService.html).
|
||||
- **PHP:** Use a [`sign()` method of a `Wallet` instance](https://alexanderbuzz.github.io/xrpl-php-docs/wallet.html#signing-a-transaction) instance to sign the transaction. The input to this step is a completed array of transaction instructions.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/js/send-xrp.js" from="// Sign" before="// Submit" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" from="# Sign" before="# Submit" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" from="// Sign" before="// Submit" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Sign" before="// Submit" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
The result of the signing operation is a transaction object containing a signature. Typically, XRP Ledger APIs expect a signed transaction to be the hexadecimal representation of the transaction's canonical [binary format](../../references/protocol/binary-format.md), called a "blob".
|
||||
|
||||
- In `xrpl.js`, the signing API also returns the transaction's ID, or identifying hash, which you can use to look up the transaction later. This is a 64-character hexadecimal string that is unique to this transaction.
|
||||
- In `xrpl-py`, you can get the transaction's hash in the response to submitting it in the next step.
|
||||
- In xrpl4j, `SignatureService.sign` returns a `SignedTransaction`, which contains the transaction's hash, which you can use to look up the transaction later.
|
||||
- In `XRPL_PHP`, the signing API also returns the transaction's ID, or identifying hash, which you can use to look up the transaction later. This is a 64-character hexadecimal string that is unique to this transaction.
|
||||
|
||||
{% interactive-block label="Sign" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="sign-button" class="btn btn-primary previous-steps-required">Sign
|
||||
example transaction</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 5. Submit the Signed Blob
|
||||
|
||||
Now that you have a signed transaction, you can submit it to an XRP Ledger server, which relays it through the network. It's also a good idea to take note of the latest validated ledger index before you submit. The earliest ledger version that your transaction could get into as a result of this submission is one higher than the latest validated ledger when you submit it. Of course, if the same transaction was previously submitted, it could already be in a previous ledger. (It can't succeed a second time, but you may not realize it succeeded if you aren't looking in the right ledger versions.)
|
||||
|
||||
- **JavaScript:** Use the [`submitAndWait()` method of the Client](https://js.xrpl.org/classes/Client.html#submitAndWait) to submit a signed transaction to the network and wait for the response, or use [`submitSigned()`](https://js.xrpl.org/classes/Client.html#submitSigned) to submit a transaction and get only the preliminary response.
|
||||
- **Python:** Use the [`xrpl.transaction.submit_and_wait()` method](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.submit_and_wait) to submit a transaction to the network and wait for a response.
|
||||
- **Java:** Use the [`XrplClient.submit(SignedTransaction)` method](https://javadoc.io/doc/org.xrpl/xrpl4j-client/latest/org/xrpl/xrpl4j/client/XrplClient.html#submit(org.xrpl.xrpl4j.crypto.signing.SignedTransaction)) to submit a transaction to the network. Use the [`XrplClient.ledger()`](https://javadoc.io/doc/org.xrpl/xrpl4j-client/latest/org/xrpl/xrpl4j/client/XrplClient.html#ledger(org.xrpl.xrpl4j.model.client.ledger.LedgerRequestParams)) method to get the latest validated ledger index.
|
||||
- **PHP:** Use the [`submitAndWait()` method of the Client](https://alexanderbuzz.github.io/xrpl-php-docs/client.html) to submit a transaction to the network and wait for the response.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/js/send-xrp.js" from="// Submit" before="// Wait" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" from="# Submit" before="# Wait" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" from="// Submit" before="// Wait" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Submit" before="// Wait" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
This method returns the **tentative** result of trying to apply the transaction to the open ledger. This result _can_ change when the transaction is included in a validated ledger: transactions that succeed initially might ultimately fail, and transactions that fail initially might ultimately succeed. Still, the tentative result often matches the final result, so it's OK to get excited if you see `tesSUCCESS` here. 😁
|
||||
|
||||
If you see any other result, you should check the following:
|
||||
|
||||
- Are you using the correct addresses for the sender and destination?
|
||||
- Did you forget any other fields of the transaction, skip any steps, or make any other typos?
|
||||
- Do you have enough Test XRP to send the transaction? The amount of XRP you can send is limited by the [reserve requirement](../../concepts/accounts/reserves.md), which is currently 10 XRP with an additional 2 XRP for each "object" you own in the ledger. (If you generated a new address with the Testnet Faucet, you don't own any objects.)
|
||||
- Are you connected to a server on the test network?
|
||||
|
||||
See the full list of [transaction results](../../references/protocol/transactions/transaction-results/transaction-results.md) for more possibilities.
|
||||
|
||||
{% interactive-block label="Submit" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="submit-button" class="btn btn-primary previous-steps-required" data-tx-blob-from="#signed-tx-blob" data-wait-step-name="Wait">Submit
|
||||
example transaction</button>
|
||||
|
||||
{% loading-icon message="Sending..." /%}
|
||||
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
### 6. Wait for Validation
|
||||
|
||||
Most transactions are accepted into the next ledger version after they're submitted, which means it may take 4-7 seconds for a transaction's outcome to be final. If the XRP Ledger is busy or poor network connectivity delays a transaction from being relayed throughout the network, a transaction may take longer to be confirmed. (For more information on expiration of unconfirmed transactions, see [Reliable Transaction Submission](../../concepts/transactions/reliable-transaction-submission.md).)
|
||||
|
||||
- **JavaScript:** If you used the [`.submitAndWait()` method](https://js.xrpl.org/classes/Client.html#submitAndWait), you can wait until the returned Promise resolves. Other, more asynchronous approaches are also possible.
|
||||
|
||||
- **Python:** If you used the [`xrpl.transaction.submit_and_wait()` method](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.submit_and_wait), you can wait for the function to return. Other approaches, including asynchronous ones using the WebSocket client, are also possible.
|
||||
|
||||
- **Java** Poll the [`XrplClient.transaction()` method](https://javadoc.io/doc/org.xrpl/xrpl4j-client/latest/org/xrpl/xrpl4j/client/XrplClient.html#transaction(org.xrpl.xrpl4j.model.client.transactions.TransactionRequestParams,java.lang.Class)) to see if your transaction has a final result. Periodically check that the latest validated ledger index has not passed the `LastLedgerIndex` of the transaction using the [`XrplClient.ledger()`](https://javadoc.io/doc/org.xrpl/xrpl4j-client/latest/org/xrpl/xrpl4j/client/XrplClient.html#ledger(org.xrpl.xrpl4j.model.client.ledger.LedgerRequestParams)) method.
|
||||
|
||||
- **PHP:** If you used the [`.submitAndWait()` method](https://alexanderbuzz.github.io/xrpl-php-docs/client.html), you can wait until the returned Promise resolves. Other, more asynchronous approaches are also possible.
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/js/send-xrp.js" from="// Wait" before="// Check" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" from="# Wait" before="# Check" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" from="// Wait" before="// Check" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Wait" before="// Check" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
{% partial file="/_snippets/interactive-tutorials/wait-step.md" /%}
|
||||
|
||||
|
||||
### 7. Check Transaction Status
|
||||
|
||||
To know for sure what a transaction did, you must look up the outcome of the transaction when it appears in a validated ledger version.
|
||||
|
||||
- **JavaScript:** Use the response from `submitAndWait()` or call the [tx method][] using [`Client.request()`](https://js.xrpl.org/classes/Client.html#request).
|
||||
|
||||
**Tip:** In **TypeScript** you can pass a [`TxRequest`](https://js.xrpl.org/interfaces/TxRequest.html) to the [`Client.request()`](https://js.xrpl.org/classes/Client.html#request) method.
|
||||
|
||||
- **Python:** Use the response from [`submit_and_wait()`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.transaction.html#xrpl.transaction.submit_and_wait) or call the [`xrpl.transaction.get_transaction_from_hash()` method](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.transaction.html#xrpl.transaction.get_transaction_from_hash). (See the [tx method response format](../../references/http-websocket-apis/public-api-methods/transaction-methods/tx.md#response-format) for a detailed reference of the fields this can contain.)
|
||||
|
||||
- **Java:** Use the [`XrplClient.transaction()`](https://javadoc.io/doc/org.xrpl/xrpl4j-client/latest/org/xrpl/xrpl4j/client/XrplClient.html#transaction(org.xrpl.xrpl4j.model.client.transactions.TransactionRequestParams,java.lang.Class)) method to check the status of a transaction.
|
||||
|
||||
- **PHP:** Use the response from `submitAndWait()` or call the `tx method` using [`$client->syncRequest()`](https://alexanderbuzz.github.io/xrpl-php-docs/client.html).
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/js/send-xrp.js" from="// Check" before="// End of" language="js" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/py/send-xrp.py" from="# Check" language="py" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/java/SendXrp.java" from="// Check" language="java" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
{% code-snippet file="/_code-samples/send-xrp/php/send-xrp.php" from="// Check" language="php" /%}
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**Caution:** XRP Ledger APIs may return tentative results from ledger versions that have not yet been validated. For example, in [tx method][] response, be sure to look for `"validated": true` to confirm that the data comes from a validated ledger version. Transaction results that are not from a validated ledger version are subject to change. For more information, see [Finality of Results](../../concepts/transactions/finality-of-results/index.md).
|
||||
|
||||
{% interactive-block label="Check" steps=$frontmatter.steps %}
|
||||
|
||||
<button id="get-tx-button" class="btn btn-primary previous-steps-required">Check transaction status</button>
|
||||
<div class="output-area"></div>
|
||||
|
||||
{% /interactive-block %}
|
||||
|
||||
|
||||
## Differences for Production
|
||||
|
||||
To send an XRP payment on the production XRP Ledger, the steps you take are largely the same. However, there are some key differences in the necessary setup:
|
||||
|
||||
- [Getting real XRP isn't free.](#getting-a-real-xrp-account)
|
||||
- [You must connect to a server that's synced with the production XRP Ledger network.](#connecting-to-the-production-xrp-ledger)
|
||||
|
||||
### Getting a Real XRP Account
|
||||
|
||||
This tutorial uses a button to get an address that's already funded with Test Net XRP, which only works because Test Net XRP is not worth anything. For actual XRP, you need to get XRP from someone who already has some. (For example, you might buy it on an exchange.) You can generate an address and secret that'll work on either production or the Testnet as follows:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
```js
|
||||
const wallet = new xrpl.Wallet()
|
||||
console.log(wallet.address) // Example: rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f
|
||||
console.log(wallet.seed) // Example: sp6JS7f14BuwFY8Mw6bTtLKWauoUs
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
```py
|
||||
from xrpl.wallet import Wallet
|
||||
my_wallet = Wallet.create()
|
||||
print(my_wallet.address) # Example: rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f
|
||||
print(my_wallet.seed) # Example: sp6JS7f14BuwFY8Mw6bTtLKWauoUs
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
```java
|
||||
WalletFactory walletFactory = DefaultWalletFactory.getInstance();
|
||||
SeedWalletGenerationResult generationResult = walletFactory.randomWallet(false);
|
||||
Wallet wallet = generationResult.wallet();
|
||||
System.out.println(wallet.classicAddress()); // Example: rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f
|
||||
System.out.println(generationResult.seed()); // Example: sp6JS7f14BuwFY8Mw6bTtLKWauoUs
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
```php
|
||||
use XRPL_PHP\Wallet\Wallet;
|
||||
|
||||
$wallet = Wallet::generate();
|
||||
|
||||
print_r("Address: " . $wallet->getAddress()); // Example: rGCkuB7PBr5tNy68tPEABEtcdno4hE6Y7f
|
||||
print_r("Seed: " . $wallet->getSeed()); // Example: sp6JS7f14BuwFY8Mw6bTtLKWauoUs
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**Warning:** You should only use an address and secret that you generated securely, on your local machine. If another computer generated the address and secret and sent it to you over a network, it's possible that someone else on the network may see that information. If they do, they'll have as much control over your XRP as you do. It's also recommended not to use the same address for the Testnet and Mainnet, because transactions that you created for use on one network could also be valid to execute on the other network, depending on the parameters you provided.
|
||||
|
||||
Generating an address and secret doesn't get you XRP directly; you're only choosing a random number. You must also receive XRP at that address to [fund the account](../../concepts/accounts/accounts.md#creating-accounts). A common way to acquire XRP is to buy it from an exchange, then withdraw it to your own address.
|
||||
|
||||
### Connecting to the Production XRP Ledger
|
||||
|
||||
When you instantiate your client's connect to the XRP Ledger, you must specify a server that's synced with the appropriate [network](../../concepts/networks-and-servers/parallel-networks.md). For many cases, you can use [public servers](public-servers.md), such as in the following example:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
```js
|
||||
const xrpl = require('xrpl')
|
||||
const api = new xrpl.Client('wss://xrplcluster.com')
|
||||
api.connect()
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
```py
|
||||
from xrpl.clients import JsonRpcClient
|
||||
client = JsonRpcClient("https://xrplcluster.com")
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
```java
|
||||
final HttpUrl rippledUrl = HttpUrl.get("https://xrplcluster.com");
|
||||
XrplClient xrplClient = new XrplClient(rippledUrl);
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
```
|
||||
use XRPL_PHP\Client\JsonRpcClient;
|
||||
|
||||
$client = new JsonRpcClient("https://xrplcluster.com");
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
If you [install `rippled`](../../infrastructure/installation/index.md) yourself, it connects to the production network by default. (You can also [configure it to connect to the test net](../../infrastructure/configuration/connect-your-rippled-to-the-xrp-test-net.md) instead.) After the server has synced (typically within about 15 minutes of starting it up), you can connect to it locally, which has [various benefits](../../concepts/networks-and-servers/index.md). The following example shows how to connect to a server running the default configuration:
|
||||
|
||||
{% tabs %}
|
||||
|
||||
{% tab label="JavaScript" %}
|
||||
```js
|
||||
const xrpl = require('xrpl')
|
||||
const api = new xrpl.Client('ws://localhost:6006')
|
||||
api.connect()
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Python" %}
|
||||
```py
|
||||
from xrpl.clients import JsonRpcClient
|
||||
client = JsonRpcClient("http://localhost:5005")
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="Java" %}
|
||||
```java
|
||||
final HttpUrl rippledUrl = HttpUrl.get("http://localhost:5005");
|
||||
XrplClient xrplClient = new XrplClient(rippledUrl);
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% tab label="PHP" %}
|
||||
```php
|
||||
use XRPL_PHP\Client\JsonRpcClient;
|
||||
|
||||
$client = new JsonRpcClient("http://localhost:5005");
|
||||
```
|
||||
{% /tab %}
|
||||
|
||||
{% /tabs %}
|
||||
|
||||
**Tip:** The local connection uses an unencrypted protocol (`ws` or `http`) rather than the TLS-encrypted version (`wss` or `https`). This is secure only because the communications never leave the same machine, and is easier to set up because it does not require a TLS certificate. For connections on an outside network, always use `wss` or `https`.
|
||||
|
||||
## Next Steps
|
||||
|
||||
After completing this tutorial, you may want to try the following:
|
||||
|
||||
- [Issue a token](../use-tokens/issue-a-fungible-token.md) on the XRP Ledger Testnet.
|
||||
- [Trade in the Decentralized Exchange](../use-tokens/trade-in-the-decentralized-exchange.md).
|
||||
- Build [Reliable transaction submission](../../concepts/transactions/reliable-transaction-submission.md) for production systems.
|
||||
- Check your [client library](../../references/client-libraries.md)'s API reference for the full range of XRP Ledger functionality.
|
||||
- Customize your [Account Settings](../tasks/manage-account-settings/index.md).
|
||||
- Learn how [Transaction Metadata](../../references/protocol/transactions/metadata.md) describes the outcome of a transaction in detail.
|
||||
- Explore more [Payment Types](../../concepts/payment-types/index.md) such as Escrows and Payment Channels.
|
||||
|
||||
{% raw-partial file="/_snippets/common-links.md" /%}
|
||||
Reference in New Issue
Block a user