mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-26 14:45:50 +00:00
Migrate content syntax via script
The changes in this commit were auto-generated by running tool/migrate.sh Following this commit, the Dactyl build no longer works but the Redocly build (mostly) should.
This commit is contained in:
@@ -17,24 +17,24 @@ To complete this tutorial, you should meet the following guidelines:
|
||||
|
||||
1. You have [Node.js](https://nodejs.org/en/download/) v14 or higher installed.
|
||||
2. You have [Yarn](https://yarnpkg.com/en/docs/install) (v1.17.3 or higher) installed.
|
||||
3. You are somewhat familiar with coding with JavaScript and have completed the [Get Started Using JavaScript](get-started-using-javascript.html) tutorial.
|
||||
3. You are somewhat familiar with coding with JavaScript and have completed the [Get Started Using JavaScript](../get-started/get-started-using-javascript.md) tutorial.
|
||||
|
||||
## Source Code
|
||||
|
||||
You can find the complete source code for all of this tutorial's examples in the [code samples section of this website's repository]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-browser-wallet/js/).
|
||||
You can find the complete source code for all of this tutorial's examples in the {% repo-link path="content/_code-samples/build-a-browser-wallet/js/" %}code samples section of this website's repository{% /repo-link %}.
|
||||
|
||||
## Goals
|
||||
|
||||
At the end of this tutorial, you should be able to build a simple XRP wallet displayed below.
|
||||
|
||||

|
||||

|
||||
|
||||
This application can:
|
||||
|
||||
- Show updates to the XRP Ledger in real-time.
|
||||
- View any XRP Ledger account's activity, including showing how much XRP was delivered by each transaction.
|
||||
- Show how much XRP is set aside for the account's [reserve requirement](reserves.html).
|
||||
- Send [direct XRP payments](direct-xrp-payments.html), and provide feedback about the intended destination address, including:
|
||||
- Show how much XRP is set aside for the account's [reserve requirement](../../concepts/accounts/reserves.md).
|
||||
- Send [direct XRP payments](../../concepts/payment-types/direct-xrp-payments.md), and provide feedback about the intended destination address, including:
|
||||
- Displaying your account's available balance
|
||||
- Verifying that the destination address is valid
|
||||
- Validating the account has enough XRP to send
|
||||
@@ -57,7 +57,7 @@ yarn create vite simple-xrpl-wallet --template vanilla
|
||||
|
||||
3. Create or modify the file `package.json` to have the following contents:
|
||||
|
||||
{{ include_code("_code-samples/build-a-browser-wallet/js/package.json", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-browser-wallet/js/package.json" language="js" /%}
|
||||
|
||||
- Alternatively you can also do `yarn add <package-name>` for each individual package to add them to your `package.json` file.
|
||||
|
||||
@@ -75,11 +75,11 @@ EXPLORER_NETWORK="testnet"
|
||||
SEED="s████████████████████████████"
|
||||
```
|
||||
|
||||
6. Change the seed to your own seed. You can get credentials from [the Testnet faucet](xrp-test-net-faucet.html).
|
||||
6. Change the seed to your own seed. You can get credentials from [the Testnet faucet](/resources/dev-tools/xrp-faucets).
|
||||
|
||||
7. Set up a Vite bundler. Create a file named `vite.config.js` in the root directory of the project and fill it with the following code:
|
||||
|
||||
{{ include_code("_code-samples/build-a-browser-wallet/js/vite.config.js", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-browser-wallet/js/vite.config.js" language="js" /%}
|
||||
|
||||
This example includes the necessary configuration to make [xrpl.js work with Vite](https://github.com/XRPLF/xrpl.js/blob/main/UNIQUE_SETUPS.md#using-xrpljs-with-vite-react).
|
||||
|
||||
@@ -97,15 +97,15 @@ In your `package.json` file, add the following section if it's not there already
|
||||
|
||||
In this step, we create a home page that displays account and ledger details.
|
||||
|
||||

|
||||

|
||||
|
||||
1. If not already present, create new files in the root folder named `index.html`, `index.js` and `index.css`.
|
||||
|
||||
2. Make a new folder named `src` in the root directory of the project.
|
||||
|
||||
3. Copy the contents of [index.html]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-browser-wallet/js/index.html) in your code.
|
||||
3. Copy the contents of {% repo-link path="content/_code-samples/build-a-browser-wallet/js/index.html" %}index.html{% /repo-link %} in your code.
|
||||
|
||||
4. Add styling to your [index.css]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-browser-wallet/js/index.css) file by following the link.
|
||||
4. Add styling to your {% repo-link path="content/_code-samples/build-a-browser-wallet/js/index.css" %}index.css{% /repo-link %} file by following the link.
|
||||
|
||||
This basic setup creates a homepage and applies some visual styles. The goal is for the homepage to:
|
||||
|
||||
@@ -115,17 +115,17 @@ This basic setup creates a homepage and applies some visual styles. The goal is
|
||||
|
||||
To make that happen, we need to connect to the XRP Ledger and look up the account and the latest validated ledger.
|
||||
|
||||
5. In the `src/` directory, make a new folder named `helpers`. Create a new file there named `get-wallet-details.js` and define a function named `getWalletDetails` there. This function uses the [account_info method](account_info.html) to fetch account details and the [server_info method](server_info.html) to calculate the current [reserves](reserves.html). The code to do all this is as follows:
|
||||
5. In the `src/` directory, make a new folder named `helpers`. Create a new file there named `get-wallet-details.js` and define a function named `getWalletDetails` there. This function uses the [account_info method](../../references/http-websocket-apis/public-api-methods/account-methods/account_info.md) to fetch account details and the [server_info method](../../references/http-websocket-apis/public-api-methods/server-info-methods/server_info.md) to calculate the current [reserves](../../concepts/accounts/reserves.md). The code to do all this is as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-browser-wallet/js/src/helpers/get-wallet-details.js", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-browser-wallet/js/src/helpers/get-wallet-details.js" language="js" /%}
|
||||
|
||||
6. Now, let's add the code to `index.js` file to fetch the account and ledger details and display them on the home page. Copy the code written below to the `index.js` file. Here we render the wallet details using the function we defined in `get-wallet-details.js`. In order to make sure we have up to date ledger data, we are using the [ledger stream](subscribe.html#ledger-stream) to listen for ledger close events.
|
||||
6. Now, let's add the code to `index.js` file to fetch the account and ledger details and display them on the home page. Copy the code written below to the `index.js` file. Here we render the wallet details using the function we defined in `get-wallet-details.js`. In order to make sure we have up to date ledger data, we are using the [ledger stream](../../references/http-websocket-apis/public-api-methods/subscription-methods/subscribe.md#ledger-stream) to listen for ledger close events.
|
||||
|
||||
{{ include_code("_code-samples/build-a-browser-wallet/js/index.js", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-browser-wallet/js/index.js" language="js" /%}
|
||||
|
||||
7. In the `helpers` folder, add [render-xrpl-logo.js]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-browser-wallet/js/src/helpers/render-xrpl-logo.js) to handle displaying a logo.
|
||||
7. In the `helpers` folder, add {% repo-link path="content/_code-samples/build-a-browser-wallet/js/src/helpers/render-xrpl-logo.js" %}render-xrpl-logo.js{% /repo-link %} to handle displaying a logo.
|
||||
|
||||
8. Finally create a new folder named `assets` in the `src/` directory and add the file [`xrpl.svg`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-browser-wallet/js/src/assets/xrpl.svg) there.
|
||||
8. Finally create a new folder named `assets` in the `src/` directory and add the file {% repo-link path="content/_code-samples/build-a-browser-wallet/js/src/assets/xrpl.svg" %}`xrpl.svg`{% /repo-link %} there.
|
||||
|
||||
These files are used to render the XRPL logo for aesthetic purposes.
|
||||
|
||||
@@ -143,21 +143,21 @@ Your terminal should output a URL which you can use to open your app in a browse
|
||||
|
||||
Now that we've created the home page, we can move on to the "Send XRP" page. This is what allows this wallet to manage your account's funds.
|
||||
|
||||

|
||||

|
||||
|
||||
1. Create a folder named `send-xrp` in the `src` directory.
|
||||
|
||||
2. Inside the `send-xrp` folder, create two files named `send-xrp.js` and `send-xrp.html`.
|
||||
|
||||
3. Copy the contents of the [send-xrp.html]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-browser-wallet/js/src/send-xrp/send-xrp.html) file to your `send-xrp.html` file. The provided HTML code includes three input fields for the destination address, amount, and destination tag, each with their corresponding labels.
|
||||
3. Copy the contents of the {% repo-link path="content/_code-samples/build-a-browser-wallet/js/src/send-xrp/send-xrp.html" %}send-xrp.html{% /repo-link %} file to your `send-xrp.html` file. The provided HTML code includes three input fields for the destination address, amount, and destination tag, each with their corresponding labels.
|
||||
|
||||
4. Now that we have the HTML code, let's add the JavaScript code. In the `helpers` folder, create a new file named `submit-transaction.js` and copy the code written below to the file. In this file, we are using the [submit](submit.html) method to submit the transaction to the XRPL. Before submitting every transaction needs to be signed by a wallet, learn more about [signing](sign.html) a transaction.
|
||||
4. Now that we have the HTML code, let's add the JavaScript code. In the `helpers` folder, create a new file named `submit-transaction.js` and copy the code written below to the file. In this file, we are using the [submit](../../references/http-websocket-apis/public-api-methods/transaction-methods/submit.md) method to submit the transaction to the XRPL. Before submitting every transaction needs to be signed by a wallet, learn more about [signing](../../references/http-websocket-apis/admin-api-methods/signing-methods/sign.md) a transaction.
|
||||
|
||||
{{ include_code("_code-samples/build-a-browser-wallet/js/src/helpers/submit-transaction.js", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-browser-wallet/js/src/helpers/submit-transaction.js" language="js" /%}
|
||||
|
||||
5. Now back to the `send-xrp.js` file, copy the code written below to the file. In this piece of code we are first getting all the DOM elements from HTML and adding event listners to update & validate the fields based on the user input. Using `renderAvailableBalance` method we display the current available balance of the wallet. `validateAddress` function validates the user address, and the amount is validated using a regular expression. When all the fields are filled with correct inputs, we call the `submitTransaction` function to submit the transaction to the ledger.
|
||||
|
||||
{{ include_code("_code-samples/build-a-browser-wallet/js/src/send-xrp/send-xrp.js", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-browser-wallet/js/src/send-xrp/send-xrp.js" language="js" /%}
|
||||
|
||||
You can now click 'Send XRP' to try creating your own transaction! You can use this example to send XRP to the testnet faucet to try it out.
|
||||
|
||||
@@ -167,7 +167,7 @@ Amount: 9
|
||||
|
||||
Destination Tag: (Not usually necessary unless you're paying an account tied to an exchange)
|
||||
|
||||

|
||||

|
||||
|
||||
### 4. Create the Transactions Page
|
||||
|
||||
@@ -180,35 +180,32 @@ Now that we have created the home page and the send XRP page, let's create the t
|
||||
- Delivered amount: The amount of XRP or tokens delivered by the transaction, if applicable.
|
||||
- Link: A link to the transaction on the XRP Ledger Explorer.
|
||||
|
||||
**Caution:** When displaying how much money a transaction delivered, always use the `delivered_amount` field from the metadata, not the `Amount` field from the transaction instructions. [Partial Payments](partial-payments.html) can deliver much less than the stated `Amount` and still be successful.
|
||||
**Caution:** When displaying how much money a transaction delivered, always use the `delivered_amount` field from the metadata, not the `Amount` field from the transaction instructions. [Partial Payments](../../concepts/payment-types/partial-payments.md) can deliver much less than the stated `Amount` and still be successful.
|
||||
|
||||

|
||||

|
||||
|
||||
1. Create a folder named `transaction-history` in the src directory.
|
||||
2. Create a file named `transaction-history.js` and copy the code written below.
|
||||
|
||||
{{ include_code("_code-samples/build-a-browser-wallet/js/src/transaction-history/transaction-history.js", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-browser-wallet/js/src/transaction-history/transaction-history.js" language="js" /%}
|
||||
|
||||
This code uses [account_tx](account_tx.html) to fetch transactions we've sent to and from this account. In order to get all the results, we're using the `marker` parameter to paginate through the incomplete list of transactions until we reach the end.
|
||||
This code uses [account_tx](../../references/http-websocket-apis/public-api-methods/account-methods/account_tx.md) to fetch transactions we've sent to and from this account. In order to get all the results, we're using the `marker` parameter to paginate through the incomplete list of transactions until we reach the end.
|
||||
|
||||
3. Create a file named `transaction-history.html` and copy the code from [transaction-history.html]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-browser-wallet/js/src/transaction-history/transaction-history.html) into it.
|
||||
3. Create a file named `transaction-history.html` and copy the code from {% repo-link path="content/_code-samples/build-a-browser-wallet/js/src/transaction-history/transaction-history.html" %}transaction-history.html{% /repo-link %} into it.
|
||||
|
||||
`transaction-history.html` defines a table which displays the fields mentioned above.
|
||||
|
||||
You can use this code as a starting point for displaying your account's transaction history. If you want an additional challenge, try expanding it to support different transaction types (e.g. [TrustSet](trustset.html)). If you want inspiration for how to handle this, you can check out the [XRP Ledger Explorer](https://livenet.xrpl.org/) to see how the transaction details are displayed.
|
||||
You can use this code as a starting point for displaying your account's transaction history. If you want an additional challenge, try expanding it to support different transaction types (e.g. [TrustSet](../../references/protocol/transactions/types/trustset.md)). If you want inspiration for how to handle this, you can check out the [XRP Ledger Explorer](https://livenet.xrpl.org/) to see how the transaction details are displayed.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Now that you have a functional wallet, you can take it in several new directions. The following are a few ideas:
|
||||
|
||||
- You could support more of the XRP Ledger's [transaction types](transaction-types.html) including [tokens](issued-currencies.html) and [cross-currency payments](cross-currency-payments.html)
|
||||
- You could support more of the XRP Ledger's [transaction types](../../references/protocol/transactions/types/index.md) including [tokens](../../concepts/tokens/index.md) and [cross-currency payments](../../concepts/payment-types/cross-currency-payments.md)
|
||||
- You could add support for displaying multiple tokens, beyond just XRP
|
||||
- You could support creating [offers](offers.html) in the [decentralized exchange](decentralized-exchange.html)
|
||||
- You could support creating [offers](../../concepts/tokens/decentralized-exchange/offers.md) in the [decentralized exchange](../../concepts/tokens/decentralized-exchange/index.md)
|
||||
- You could add new ways to request payments, such as with QR codes or URIs that open in your wallet.
|
||||
- You could support better account security including allowing users to set [regular key pairs](cryptographic-keys.html#regular-key-pair) or handle [multi-signing](multi-signing.html).
|
||||
- You could support better account security including allowing users to set [regular key pairs](../../concepts/accounts/cryptographic-keys.md#regular-key-pair) or handle [multi-signing](../../concepts/accounts/multi-signing.md).
|
||||
- Or you could take your code to production by following the [Building for Production with Vite](https://vitejs.dev/guide/build.html#public-base-path) guide.
|
||||
|
||||
<!--{## common link defs #}-->
|
||||
{% include '_snippets/rippled-api-links.md' %}
|
||||
{% include '_snippets/tx-type-links.md' %}
|
||||
{% include '_snippets/rippled_versions.md' %}
|
||||
{% raw-partial file="/_snippets/common-links.md" /%}
|
||||
|
||||
@@ -13,12 +13,12 @@ This tutorial demonstrates how to build a desktop wallet for the XRP Ledger usin
|
||||
To complete this tutorial, you should meet the following requirements:
|
||||
|
||||
- You have [Node.js](https://nodejs.org/) 14+ installed.
|
||||
- You are somewhat familiar with modern JavaScript programming and have completed the [Get Started Using JavaScript tutorial](get-started-using-javascript.html).
|
||||
- You are somewhat familiar with modern JavaScript programming and have completed the [Get Started Using JavaScript tutorial](../get-started/get-started-using-javascript.md).
|
||||
- You have some understanding of the XRP Ledger, its capabilities, and of cryptocurrency in general. Ideally you have completed the [Basic XRPL guide](https://learn.xrpl.org/).
|
||||
|
||||
### Source Code
|
||||
|
||||
You can find the complete source code for all of this tutorial's examples in the [code samples section of this website's repository]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/). After a `npm install` in this directory you can run the application for each step as described in the `scripts` section of `package.json`, e.g, `npm run ledger-index`.
|
||||
You can find the complete source code for all of this tutorial's examples in the {% repo-link path="content/_code-samples/build-a-desktop-wallet/js/" %}code samples section of this website's repository{% /repo-link %}. After a `npm install` in this directory you can run the application for each step as described in the `scripts` section of `package.json`, e.g, `npm run ledger-index`.
|
||||
|
||||
**Caution:** Be careful if you copy-and-paste the source code from these directly from these files. The sample code is split up into different files per step, so some shared imports and files are in different directories in the examples. This especially applies to the `library`, `bootstrap`, and `WALLET_DIR` contents.
|
||||
|
||||
@@ -33,7 +33,7 @@ We will use the well-established [Electron Framework](https://www.electronjs.org
|
||||
|
||||
At the end of this tutorial, you will have built a JavaScript Wallet application that looks something like this:
|
||||
|
||||

|
||||

|
||||
|
||||
The look and feel of the user interface should be roughly the same regardless of operating system, as the Electron Framework allows us to write cross-platform applications that are styled with HTML and CSS just like a web-based application.
|
||||
|
||||
@@ -41,13 +41,13 @@ The application we are going to build here will be capable of the following:
|
||||
|
||||
- Showing updates to the XRP Ledger in real-time.
|
||||
- Viewing any XRP Ledger account's activity "read-only" including showing how much XRP was delivered by each transaction.
|
||||
- Sending [direct XRP payments](direct-xrp-payments.html), and providing feedback about the intended destination address, including:
|
||||
- Sending [direct XRP payments](../../concepts/payment-types/direct-xrp-payments.md), and providing feedback about the intended destination address, including:
|
||||
- Whether the intended destination already exists in the XRP Ledger, or the payment would have to fund its creation.
|
||||
- If the address doesn't want to receive XRP (**Disallow XRP** flag enabled).
|
||||
- If the address has a [verified domain name](xrp-ledger-toml.html#account-verification) associated with it.
|
||||
- If the address has a [verified domain name](../../references/xrp-ledger-toml.md#account-verification) associated with it.
|
||||
|
||||
The application in this tutorial _doesn't_ have the ability to send or trade [tokens](issued-currencies.html) or
|
||||
use other [payment types](payment-types.html) like [Escrow](https://xrpl.org/escrow.html) or [Payment Channels](https://xrpl.org/payment-channels.html). However, it provides a foundation
|
||||
The application in this tutorial _doesn't_ have the ability to send or trade [tokens](../../concepts/tokens/index.md) or
|
||||
use other [payment types](../../concepts/payment-types/index.md) like [Escrow](https://xrpl.org/escrow.html) or [Payment Channels](https://xrpl.org/payment-channels.html). However, it provides a foundation
|
||||
that you can implement those and other features on top of.
|
||||
|
||||
In addition to the above features, you'll also learn a bit about Events, IPC (inter-process-communication) and asynchronous (async) code in JavaScript.
|
||||
@@ -162,23 +162,23 @@ To run the reference application found in `content/_code-samples/build-a-desktop
|
||||
npm run hello
|
||||
```
|
||||
|
||||
In the next steps we will continually expand on this very basic setup. To better keep track of all the changes that will be made, the files in the [reference section]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/) are numbered/prefixed with the respective step number:
|
||||
In the next steps we will continually expand on this very basic setup. To better keep track of all the changes that will be made, the files in the {% repo-link path="content/_code-samples/build-a-desktop-wallet/js/" %}reference section{% /repo-link %} are numbered/prefixed with the respective step number:
|
||||
|
||||
**Full code for this step:**
|
||||
[`0-hello/0_hello.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/0_hello.js),
|
||||
[`0-hello/view/0_hello.html`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/0_hello.html),
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/0_hello.js" %}`0-hello/0_hello.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/0_hello.html" %}`0-hello/view/0_hello.html`{% /repo-link %},
|
||||
|
||||
### 1. Ledger Index
|
||||
|
||||
**Full code for this step:**
|
||||
[`1-ledger-index/index.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/1_ledger-index.js),
|
||||
[`1-ledger-index/view/preload.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/1_preload.js),
|
||||
[`1-ledger-index/view/template.html`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/1_ledger-index.html),
|
||||
[`1-ledger-index/view/renderer.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/1_renderer.js).
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/1_ledger-index.js" %}`1-ledger-index/index.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/1_preload.js" %}`1-ledger-index/view/preload.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/1_ledger-index.html" %}`1-ledger-index/view/template.html`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/1_renderer.js" %}`1-ledger-index/view/renderer.js`{% /repo-link %}.
|
||||
|
||||
Our first step was to have a running "Hello World" application. Now we want to expand on that so that the application can interact on a very basic level with the XRP Ledger and display some information about the current ledger state on the screen. After completing this step, the - for the time being unstyled - application should look like this:
|
||||
|
||||

|
||||

|
||||
|
||||
1. Update `index.js` by adding the following snippet in the import section at the top of the file below the `path` import:
|
||||
|
||||
@@ -224,7 +224,7 @@ const getValidatedLedgerIndex = async () => {
|
||||
const createWindow = () => {
|
||||
```
|
||||
|
||||
This helper function does the following: It establishes a WebSocket connection to the XRP Ledger, calls the XRP Ledger API's [ledger method](ledger.html) and returns the ledger index from the response. We will wire up this function at the end of this step.
|
||||
This helper function does the following: It establishes a WebSocket connection to the XRP Ledger, calls the XRP Ledger API's [ledger method](../../references/http-websocket-apis/public-api-methods/ledger-methods/ledger.md) and returns the ledger index from the response. We will wire up this function at the end of this step.
|
||||
|
||||
2. In order to attach a preloader script, modify the `createWindow` method in `index.js` by adding the following code:
|
||||
|
||||
@@ -340,14 +340,14 @@ npm run ledger-index
|
||||
### 2. Show Ledger Updates by using WebSocket subscriptions
|
||||
|
||||
**Full code for this step:**
|
||||
[`2-async/index.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/2_async-subscribe.js),
|
||||
[`2-async/view/preload.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/2_preload.js),
|
||||
[`2-async/view/renderer.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/2_renderer.js),
|
||||
[`2-async/view/template.html`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/2_async.html).
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/2_async-subscribe.js" %}`2-async/index.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/2_preload.js" %}`2-async/view/preload.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/2_renderer.js" %}`2-async/view/renderer.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/2_async.html" %}`2-async/view/template.html`{% /repo-link %}.
|
||||
|
||||
Our application so far only shows the latest validated ledger sequence at the time when we opened it. Let's take things up a notch and add some dashboard like functionality where our wallet app will keep in sync with the ledger and display the latest specs and stats like a clock that is keeping track of time. The result will look something like this:
|
||||
|
||||

|
||||

|
||||
|
||||
1. In `index.js` remove the `getValidatedLedgerIndex` function.
|
||||
|
||||
@@ -446,22 +446,22 @@ npm run async
|
||||
### 3. Display an Account
|
||||
|
||||
**Full code for this step:**
|
||||
[`library/3_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/3_helpers.js).
|
||||
[`3-account/index.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/3_account.js),
|
||||
[`3-account/view/preload.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/3_preload.js),
|
||||
[`3-account/view/renderer.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/3_renderer.js),
|
||||
[`3-account/view/template.html`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/3_account.html).
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/3_helpers.js" %}`library/3_helpers.js`{% /repo-link %}.
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/3_account.js" %}`3-account/index.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/3_preload.js" %}`3-account/view/preload.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/3_renderer.js" %}`3-account/view/renderer.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/3_account.html" %}`3-account/view/template.html`{% /repo-link %}.
|
||||
|
||||
We now have a permanent connection to the XRPL and some code to bring the delivered data to life on our screen, it's time to add some "wallet" functionality by managing an individual account.
|
||||
|
||||
We will ask the user for address of the account to monitor by using a HTML dialog element. We will furthermore refactor the application by encapsulating some functionality in a library. After finishing this step the application should look like this:
|
||||
|
||||

|
||||

|
||||
|
||||
1. In the project root, create a new directory named `library`. Inside this directory, create a file `3_helpers.js` with the following content:
|
||||
|
||||
`3_helpers.js`
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/js/library/3_helpers.js", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/js/library/3_helpers.js" language="js" /%}
|
||||
|
||||
Here we define three utility functions that will transform data we receive from the ledger into flat value objects for easy digestion in the frontend code. As we progress in this tutorial, we will keep this pattern of adding functionality by adding files that are prefixed by the step number.
|
||||
|
||||
@@ -631,7 +631,7 @@ onEnterAccountAddress: (address) => {
|
||||
|
||||
6. To incorporate the refactored markup, handle the HTML dialog element and well as the new account data section replace the contents of `view/renderer.js` with the following code:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/js/3-account/view/renderer.js", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/js/3-account/view/renderer.js" language="js" /%}
|
||||
|
||||
The parts at the beginning and end are totally new, but the section in the middle is _almost_ the same as before. The difference is that the names of a few fields have changed, since we're now passing them from `prepareLedgerData(...)`. For example `ledger.ledger_time` is now `ledger.ledgerCloseTime` instead.
|
||||
|
||||
@@ -641,7 +641,7 @@ Now you need an XRPL account address to monitor. If you already have one or know
|
||||
npm run start
|
||||
```
|
||||
|
||||
If you need a Testnet address, [you can get one from the faucet](xrp-testnet-faucet.html). Then you can paste that address into the [Transaction Sender](tx-sender.html) to send XRP transactions that your app can see.
|
||||
If you need a Testnet address, [you can get one from the faucet](/resources/dev-tools/xrp-faucets). Then you can paste that address into the [Transaction Sender](/resources/dev-tools/tx-sender) to send XRP transactions that your app can see.
|
||||
|
||||
On, startup, the application should display a simple dialog prompting the user for an XRPL account address. After entering the address the application should display some basic information about that account and about the ledger.
|
||||
|
||||
@@ -654,20 +654,20 @@ npm run account
|
||||
### 4. Show Account's Transactions
|
||||
|
||||
**Full code for this step:**
|
||||
[`library/3_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/3_helpers.js),
|
||||
[`library/4_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/4_helpers.js),
|
||||
[`4-tx-history/index.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/4_tx-history.js),
|
||||
[`4-tx-history/view/preload.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/4_tx-preload.js),
|
||||
[`4-tx-history/view/renderer.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/4_tx-renderer.js),
|
||||
[`4-tx-history/view/index.html`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/4_tx-history.html).
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/3_helpers.js" %}`library/3_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/4_helpers.js" %}`library/4_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/4_tx-history.js" %}`4-tx-history/index.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/4_tx-preload.js" %}`4-tx-history/view/preload.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/4_tx-renderer.js" %}`4-tx-history/view/renderer.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/4_tx-history.html" %}`4-tx-history/view/index.html`{% /repo-link %}.
|
||||
|
||||
At this point, our wallet shows the account's balance getting updated, but doesn't give us any clue about how this state came about, namely the actual transactions that caused the updates. So, our next step is to display the account's up to date transaction history using subscriptions once again:
|
||||
|
||||

|
||||

|
||||
|
||||
1. In the `library` folder, add a new file `4_helpers.js`. Then add the following helper function to that file:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/js/library/4_helpers.js", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/js/library/4_helpers.js" language="js" /%}
|
||||
|
||||
2. Now, in `index.js`, require the new helper function at the bottom of the import section like so:
|
||||
|
||||
@@ -820,17 +820,17 @@ npm run tx-history
|
||||
### 5. Saving the Private Keys with a Password
|
||||
|
||||
**Full code for this step:**
|
||||
[`library/3_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/3_helpers.js).
|
||||
[`library/4_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/4_helpers.js).
|
||||
[`library/5_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/5_helpers.js).
|
||||
[`5-password/index.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/5_password.js).
|
||||
[`5-password/view/preload.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/5_tx-preload.js).
|
||||
[`5-password/view/template.html`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/5_password.html).
|
||||
[`5-password/view/renderer.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/5_tx-renderer.js).
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/3_helpers.js" %}`library/3_helpers.js`{% /repo-link %}.
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/4_helpers.js" %}`library/4_helpers.js`{% /repo-link %}.
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/5_helpers.js" %}`library/5_helpers.js`{% /repo-link %}.
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/5_password.js" %}`5-password/index.js`{% /repo-link %}.
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/5_tx-preload.js" %}`5-password/view/preload.js`{% /repo-link %}.
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/5_password.html" %}`5-password/view/template.html`{% /repo-link %}.
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/5_tx-renderer.js" %}`5-password/view/renderer.js`{% /repo-link %}.
|
||||
|
||||
After finishing this step the application should look like this:
|
||||
|
||||

|
||||

|
||||
|
||||
By now we always query the user for an account address at application startup. We more or less have a monitoring tool for accounts that queries publicly available data. Because we want to have real wallet functionality including sending XRP, we will have to deal with private keys and seeds.
|
||||
|
||||
@@ -838,7 +838,7 @@ In this step we will query the user for a seed and a password they can use to ac
|
||||
|
||||
1. In the `library` folder, add a new file `5_helpers.js` with the following content:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/js/library/5_helpers.js", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/js/library/5_helpers.js" language="js" /%}
|
||||
|
||||
2. Modify the import section at the top of `index.js` to look like this:
|
||||
|
||||
@@ -1069,27 +1069,27 @@ npm run password
|
||||
### 6. Styling
|
||||
|
||||
**Full code for this step:**
|
||||
[`library/3_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/3_helpers.js),
|
||||
[`library/4_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/4_helpers.js),
|
||||
[`library/5_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/5_helpers.js),
|
||||
[`6-styling/index.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/6_styling.js),
|
||||
[`6-styling/view/preload.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/6_tx-preload.js),
|
||||
[`6-styling/view/template.html`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/6_styling.html),
|
||||
[`6-styling/view/renderer.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/6_tx-renderer.js),
|
||||
[`bootstrap/bootstrap.bundle.min.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/bootstrap/bootstrap.bundle.min.js),
|
||||
[`bootstrap/bootstrap.min.css`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/bootstrap/bootstrap.bundle.min.css),
|
||||
[`bootstrap/custom.css`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/bootstrap/custom.css),
|
||||
[`bootstrap/XRPLedger_DevPortal-white.svg`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/bootstrap/XRPLedger_DevPortal-white.svg).
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/3_helpers.js" %}`library/3_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/4_helpers.js" %}`library/4_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/5_helpers.js" %}`library/5_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/6_styling.js" %}`6-styling/index.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/6_tx-preload.js" %}`6-styling/view/preload.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/6_styling.html" %}`6-styling/view/template.html`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/6_tx-renderer.js" %}`6-styling/view/renderer.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/bootstrap/bootstrap.bundle.min.js" %}`bootstrap/bootstrap.bundle.min.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/bootstrap/bootstrap.bundle.min.css" %}`bootstrap/bootstrap.min.css`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/bootstrap/custom.css" %}`bootstrap/custom.css`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/bootstrap/XRPLedger_DevPortal-white.svg" %}`bootstrap/XRPLedger_DevPortal-white.svg`{% /repo-link %}.
|
||||
|
||||
After finishing this step the application should look like this:
|
||||
|
||||

|
||||

|
||||
|
||||
1. In the project root, create a new folder `bootstrap` and add the following files into that directory:
|
||||
[`bootstrap.bundle.min.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/bootstrap/bootstrap.bundle.min.js),
|
||||
[`bootstrap.min.css`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/bootstrap/bootstrap.bundle.min.css),
|
||||
[`custom.css`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/bootstrap/custom.css),
|
||||
[`XRPLedger_DevPortal-white.svg`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/bootstrap/XRPLedger_DevPortal-white.svg)
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/bootstrap/bootstrap.bundle.min.js" %}`bootstrap.bundle.min.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/bootstrap/bootstrap.bundle.min.css" %}`bootstrap.min.css`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/bootstrap/custom.css" %}`custom.css`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/bootstrap/XRPLedger_DevPortal-white.svg" %}`XRPLedger_DevPortal-white.svg`{% /repo-link %}
|
||||
|
||||
2. Change the content of `view/template.html` to be the following code:
|
||||
|
||||
@@ -1232,23 +1232,23 @@ npm run styling
|
||||
### 7. Send XRP
|
||||
|
||||
**Full code for this step:**
|
||||
[`library/3_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/3_helpers.js),
|
||||
[`library/4_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/4_helpers.js),
|
||||
[`library/5_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/5_helpers.js),
|
||||
[`library/6_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/6_helpers.js),
|
||||
[`library/7_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/7_helpers.js),
|
||||
[`7-send-xrp/index.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/7_send-xrp.js),
|
||||
[`7-send-xrp/view/preload.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/7_preload.js),
|
||||
[`7-send-xrp/view/renderer.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/7_renderer.js),
|
||||
[`7-send-xrp/view/template.html`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/7_send-xrp.html).
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/3_helpers.js" %}`library/3_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/4_helpers.js" %}`library/4_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/5_helpers.js" %}`library/5_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/6_helpers.js" %}`library/6_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/7_helpers.js" %}`library/7_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/7_send-xrp.js" %}`7-send-xrp/index.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/7_preload.js" %}`7-send-xrp/view/preload.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/7_renderer.js" %}`7-send-xrp/view/renderer.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/7_send-xrp.html" %}`7-send-xrp/view/template.html`{% /repo-link %}.
|
||||
|
||||
Up until now we have enabled our app to query and display data from the XRPL. Now it's time to actively participate in the ledger by enabling our application to send transactions. For now, we can stick to sending direct XRP payments because there are more complexities involved in sending issued tokens. After finishing this step the application should look like this:
|
||||
|
||||

|
||||

|
||||
|
||||
1. Create the file `library/7_helpers.js` and add the following contents:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/js/library/7_helpers.js", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/js/library/7_helpers.js" language="js" /%}
|
||||
|
||||
|
||||
(There was no `6-helpers.js`, so don't worry!)
|
||||
@@ -1378,32 +1378,32 @@ npm run send-xrp
|
||||
### 8. Domain Verification and Polish
|
||||
|
||||
**Full code for this step:**
|
||||
[`library/3_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/3_helpers.js),
|
||||
[`library/4_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/4_helpers.js),
|
||||
[`library/5_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/5_helpers.js),
|
||||
[`library/6_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/6_helpers.js),
|
||||
[`library/7_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/7_helpers.js),
|
||||
[`library/8_helpers.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/library/8_helpers.js),
|
||||
[`8-domain-verification/index.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/8_domain-verification.js),
|
||||
[`8-domain-verification/view/8_prelaod.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/8_preload.js),
|
||||
[`8-domain-verification/view/8_domain-verification.html`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/8_domain-verification.html),
|
||||
[`8-domain-verification/view/8_renderer.js`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/js/view/8_renderer.js).
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/3_helpers.js" %}`library/3_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/4_helpers.js" %}`library/4_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/5_helpers.js" %}`library/5_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/6_helpers.js" %}`library/6_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/7_helpers.js" %}`library/7_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/library/8_helpers.js" %}`library/8_helpers.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/8_domain-verification.js" %}`8-domain-verification/index.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/8_preload.js" %}`8-domain-verification/view/8_prelaod.js`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/8_domain-verification.html" %}`8-domain-verification/view/8_domain-verification.html`{% /repo-link %},
|
||||
{% repo-link path="content/_code-samples/build-a-desktop-wallet/js/view/8_renderer.js" %}`8-domain-verification/view/8_renderer.js`{% /repo-link %}.
|
||||
|
||||
One of the biggest shortcomings of the wallet app from the previous step is that it doesn't provide a lot of protections or feedback for users to save them from human error and scams. These sorts of protections are extra important when dealing with the cryptocurrency space because decentralized systems like the XRP Ledger don't have an admin or support team one can ask to cancel or refund a payment if you made a mistake such as sending it to the wrong address.
|
||||
|
||||
This step shows how to add some checks on destination addresses to warn the user before sending XRP.
|
||||
|
||||
One type of check we could make is to verify the domain name associated with an XRP Ledger address; this is called [account domain verification](xrp-ledger-toml.html#account-verification). When an account's domain is verified, we can could show it like this:
|
||||
One type of check we could make is to verify the domain name associated with an XRP Ledger address; this is called [account domain verification](../../references/xrp-ledger-toml.md#account-verification). When an account's domain is verified, we can could show it like this:
|
||||
|
||||

|
||||

|
||||
|
||||
1. In the `library` folder, add a new file `8_helpers.js`. Then add the following contents to that file:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/js/library/8_helpers.js", language="js") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/js/library/8_helpers.js" language="js" /%}
|
||||
|
||||
The code in `8_helpers.js` looks up the account on the ledger by sending an [`account_info`](account_info.html) request.
|
||||
The code in `8_helpers.js` looks up the account on the ledger by sending an [`account_info`](../../references/http-websocket-apis/public-api-methods/account-methods/account_info.md) request.
|
||||
|
||||
If the account does exist, the code checks for the [`lsfDisallowXRP` flag](accountroot.html#accountroot-flags).
|
||||
If the account does exist, the code checks for the [`lsfDisallowXRP` flag](../../references/protocol/ledger-data/ledger-entry-types/accountroot.md#accountroot-flags).
|
||||
|
||||
2. Import the new helper function in `index.js`:
|
||||
|
||||
@@ -1520,7 +1520,7 @@ npm run domain-verification
|
||||
|
||||
Congratulations, you now have created your own wallet application! In completing this tutorial, you've not only learned how to interact with the XRP Ledger, but also which challenges this provokes when building a user facing application. So let's do a recap of what you have accomplished:
|
||||
|
||||

|
||||

|
||||
|
||||
0. First you set up the project and created a basic electron application.
|
||||
1. In Step 1 you did your first ledger query and had the application display the latest closed ledger index, using Electron's inter-process-communication.
|
||||
|
||||
@@ -13,30 +13,30 @@ This tutorial demonstrates how to build a desktop wallet for the XRP Ledger usin
|
||||
To complete this tutorial, you should meet the following guidelines:
|
||||
|
||||
- You have Python 3.7 or higher installed.
|
||||
- You are somewhat familiar with object-oriented programming in Python and have completed the [Get Started Using Python tutorial](get-started-using-python.html).
|
||||
- You are somewhat familiar with object-oriented programming in Python and have completed the [Get Started Using Python tutorial](../get-started/get-started-using-python.md).
|
||||
- You have some understanding of what the XRP Ledger can do and of cryptocurrency in general. You don't need to be an expert.
|
||||
|
||||
## Source Code
|
||||
|
||||
You can find the complete source code for all of this tutorial's examples in the [code samples section of this website's repository]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/py/).
|
||||
You can find the complete source code for all of this tutorial's examples in the {% repo-link path="content/_code-samples/build-a-desktop-wallet/py/" %}code samples section of this website's repository{% /repo-link %}.
|
||||
|
||||
## Goals
|
||||
|
||||
At the end of this tutorial, you should have a Python application that looks something like this:
|
||||
|
||||

|
||||

|
||||
|
||||
The exact look and feel of the user interface depend on your computer's operating system. This application is capable of the following:
|
||||
|
||||
- Shows updates to the XRP Ledger in real-time.
|
||||
- Can view any XRP Ledger account's activity "read-only" including showing how much XRP was delivered by each transaction.
|
||||
- Shows how much XRP is set aside for the account's [reserve requirement](reserves.html).
|
||||
- Can send [direct XRP payments](direct-xrp-payments.html), and provides feedback about the intended destination address, including:
|
||||
- Shows how much XRP is set aside for the account's [reserve requirement](../../concepts/accounts/reserves.md).
|
||||
- Can send [direct XRP payments](../../concepts/payment-types/direct-xrp-payments.md), and provides feedback about the intended destination address, including:
|
||||
- Whether the intended destination already exists in the XRP Ledger, or the payment would have to fund its creation.
|
||||
- If the address doesn't want to receive XRP (**Disallow XRP** flag enabled).
|
||||
- If the address has a [verified domain name](xrp-ledger-toml.html#account-verification) associated with it.
|
||||
- If the address has a [verified domain name](../../references/xrp-ledger-toml.md#account-verification) associated with it.
|
||||
|
||||
The application in this tutorial _doesn't_ have the ability to send or trade [tokens](issued-currencies.html) or use other [payment types](payment-types.html) like Escrow or Payment Channels. However, it provides a foundation that you can implement those and other features on top of.
|
||||
The application in this tutorial _doesn't_ have the ability to send or trade [tokens](../../concepts/tokens/index.md) or use other [payment types](../../concepts/payment-types/index.md) like Escrow or Payment Channels. However, it provides a foundation that you can implement those and other features on top of.
|
||||
|
||||
Other topics mentioned in this tutorial include graphical user interface (GUI) programming, threading, and asynchronous (async) code in Python.
|
||||
|
||||
@@ -84,21 +84,21 @@ python -m pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/g
|
||||
|
||||
The first step is to build an app that combines the "hello world" equivalents for the XRP Ledger and wxPython programming. The code is as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/1_hello.py", language="py") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/1_hello.py" language="py" /%}
|
||||
|
||||
When you run this script, it displays a single window that (hopefully) shows the latest validated ledger index on the XRP Ledger Testnet. It looks like this:
|
||||
|
||||

|
||||

|
||||
|
||||
Under the hood, the code makes a JSON-RPC client, connects to a public Testnet server, and uses the [ledger method][] to get this information. Meanwhile, it creates a [`wx.Frame`](https://docs.wxpython.org/wx.Frame.html) subclass as the base of the user interface. This class makes a window the user can see, with a [`wx.StaticText`](https://docs.wxpython.org/wx.StaticText.html) widget to display text to the user, and a [`wx.Panel`](https://docs.wxpython.org/wx.Panel.html) to hold that widget.
|
||||
|
||||
### 2. Show Ledger Updates
|
||||
|
||||
**Full code for this step:** [`2_threaded.py`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/py/2_threaded.py).
|
||||
**Full code for this step:** {% repo-link path="content/_code-samples/build-a-desktop-wallet/py/2_threaded.py" %}`2_threaded.py`{% /repo-link %}.
|
||||
|
||||
You may have noticed that the app in step 1 only shows the latest validated ledger at the time you opened it: the text displayed never changes unless you close the app and reopen it. The actual XRP Ledger is constantly making forward progress, so a more useful app would show it, something like this:
|
||||
|
||||

|
||||

|
||||
|
||||
If you want to continually watch the ledger for updates (for example, waiting to see when new transactions have been confirmed), then you need to change the architecture of your app slightly. For reasons specific to Python, it's best to use two _threads_: a "GUI" thread to handle user input and display, and a "worker" thread for XRP Ledger network connectivity. The operating system can switch quickly between the two threads at any time, so the user interface can remain responsive while the background thread waits on information from the network that may take a while to arrive.
|
||||
|
||||
@@ -113,11 +113,11 @@ To make full use of the XRP Ledger's ability to push messages to the client, use
|
||||
|
||||
Add these imports to the top of the file:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/2_threaded.py", language="py", start_with="import async", end_before="class XRPLMonitorThread") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/2_threaded.py" from="import async" before="class XRPLMonitorThread" language="py" /%}
|
||||
|
||||
Then, the code for the monitor thread is as follows (put this in the same file as the rest of the app):
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/2_threaded.py", language="py", start_with="class XRPLMonitorThread", end_before="class TWaXLFrame") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/2_threaded.py" from="class XRPLMonitorThread" before="class TWaXLFrame" language="py" /%}
|
||||
|
||||
This code defines a `Thread` subclass for the worker. When the thread starts, it sets up an event loop, which waits for async tasks to be created and run. The code uses [`asyncio`'s Debug Mode](https://docs.python.org/3/library/asyncio-dev.html#asyncio-debug-mode) so that the console shows any errors that occur in async tasks.
|
||||
|
||||
@@ -127,7 +127,7 @@ The `watch_xrpl()` function is an example of a such a task (which the GUI thread
|
||||
|
||||
Update the code for the main thread and GUI frame to look like this:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/2_threaded.py", language="py", start_with="class TWaXLFrame", end_before="if __name__") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/2_threaded.py" from="class TWaXLFrame" before="if __name__" language="py" /%}
|
||||
|
||||
The part that builds the GUI has been moved to a separate method, `build_ui(self)`. This helps to divide the code into chunks that are easier to understand, because the `__init__()` constructor has other work to do now, too: it starts the worker thread, and gives it its first job. The GUI setup also now uses a [sizer](https://docs.wxpython.org/sizers_overview.html) to control placement of the text within the frame.
|
||||
|
||||
@@ -135,15 +135,15 @@ The part that builds the GUI has been moved to a separate method, `build_ui(self
|
||||
|
||||
There's a new helper method, `run_bg_job()`, which runs an asynchronous function (defined with `async def`) in the worker thread. Use this method any time you want the worker thread to interact with the XRP Ledger network.
|
||||
|
||||
Instead of a `get_validated_ledger()` method, the GUI class now has an `update_ledger()` method, which takes an object in the format of a [ledger stream message](subscribe.html#ledger-stream) and displays some of that information to the user. The worker thread calls this method using `wx.CallAfter()` whenever it gets a `ledgerClosed` event from the ledger.
|
||||
Instead of a `get_validated_ledger()` method, the GUI class now has an `update_ledger()` method, which takes an object in the format of a [ledger stream message](../../references/http-websocket-apis/public-api-methods/subscription-methods/subscribe.md#ledger-stream) and displays some of that information to the user. The worker thread calls this method using `wx.CallAfter()` whenever it gets a `ledgerClosed` event from the ledger.
|
||||
|
||||
Finally, change the code to start the app (at the end of the file) slightly:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/2_threaded.py", language="py", start_with="if __name__") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/2_threaded.py" from="if __name__" language="py" /%}
|
||||
|
||||
Since the app uses a WebSocket client instead of the JSON-RPC client now, the code has to use a WebSocket URL to connect.
|
||||
|
||||
**Tip:** If you [run your own `rippled` server](networks-and-servers.html#reasons-to-run-your-own-server) you can connect to it using `ws://localhost:6006` as the URL. You can also use the WebSocket URLs of [public servers](public-servers.html) to connect to the Mainnet or other test networks.
|
||||
**Tip:** If you [run your own `rippled` server](../../concepts/networks-and-servers/index.md#reasons-to-run-your-own-server) you can connect to it using `ws://localhost:6006` as the URL. You can also use the WebSocket URLs of [public servers](../get-started/public-servers.md) to connect to the Mainnet or other test networks.
|
||||
|
||||
#### Troubleshooting SSL Certificate Errors
|
||||
|
||||
@@ -160,57 +160,57 @@ On Windows, open Edge or Chrome and browse to <https://s1.ripple.com>, then clos
|
||||
|
||||
### 3. Display an Account
|
||||
|
||||
**Full code for this step:** [`3_account.py`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/py/3_account.py)
|
||||
**Full code for this step:** {% repo-link path="content/_code-samples/build-a-desktop-wallet/py/3_account.py" %}`3_account.py`{% /repo-link %}
|
||||
|
||||
Now that you have a working, ongoing connection to the XRP Ledger, it's time to start adding some "wallet" functionality that lets you manage an individual account. For this step, you should prompt the user to input their address or master seed, then use that to display information about their account including how much XRP is set aside for the [reserve requirement](reserves.html).
|
||||
Now that you have a working, ongoing connection to the XRP Ledger, it's time to start adding some "wallet" functionality that lets you manage an individual account. For this step, you should prompt the user to input their address or master seed, then use that to display information about their account including how much XRP is set aside for the [reserve requirement](../../concepts/accounts/reserves.md).
|
||||
|
||||
The prompt is in a pop-up dialog like this:
|
||||
|
||||

|
||||

|
||||
|
||||
After the user inputs the prompt, the updated GUI looks like this:
|
||||
|
||||

|
||||

|
||||
|
||||
When you do math on XRP amounts, you should use the `Decimal` class so that you don't get rounding errors. Add this to the top of the file, with the other imports:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/3_account.py", language="py", start_with="from decimal", end_before="class XRPLMonitorThread") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/3_account.py" from="from decimal" before="class XRPLMonitorThread" language="py" /%}
|
||||
|
||||
In the `XRPLMonitorThread` class, rename and update the `watch_xrpl()` method as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/3_account.py", language="py", start_with="async def watch_xrpl", end_before="async def on_connected") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/3_account.py" from="async def watch_xrpl" before="async def on_connected" language="py" /%}
|
||||
|
||||
The newly renamed `watch_xrpl_account()` method now takes an address and optional wallet and saves them for later. (The GUI thread provides these based on user input.) This method also adds a new case for [transaction stream messages](subscribe.html#transaction-streams). When it sees a new transaction, the worker does not yet do anything with the transaction itself, but it uses that as a trigger to get the account's latest XRP balance and other info using the [account_info method][]. When _that_ response arrives, the worker passes the account data to the GUI for display.
|
||||
The newly renamed `watch_xrpl_account()` method now takes an address and optional wallet and saves them for later. (The GUI thread provides these based on user input.) This method also adds a new case for [transaction stream messages](../../references/http-websocket-apis/public-api-methods/subscription-methods/subscribe.md#transaction-streams). When it sees a new transaction, the worker does not yet do anything with the transaction itself, but it uses that as a trigger to get the account's latest XRP balance and other info using the [account_info method][]. When _that_ response arrives, the worker passes the account data to the GUI for display.
|
||||
|
||||
Still in the `XRPLMonitorThread` class, update the `on_connected()` method as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/3_account.py", language="py", start_with="async def on_connected", end_before="class AutoGridBagSizer") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/3_account.py" from="async def on_connected" before="class AutoGridBagSizer" language="py" /%}
|
||||
|
||||
The `on_connected()` method now subscribes to transactions for the provided account (and the ledger stream too). Furthermore, it now calls [account_info][account_info method] on startup, and passes the response to the GUI for display.
|
||||
|
||||
The new GUI has a lot more fields that need to be laid out in two dimensions. The following subclass of [`wx.GridBagSizer`](https://docs.wxpython.org/wx.GridBagSizer.html) provides a quick way to do so, setting the appropriate padding and sizing values for a two-dimensional list of widgets. Add this code to the same file:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/3_account.py", language="py", start_with="class AutoGridBagSizer", end_before="class TWaXLFrame") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/3_account.py" from="class AutoGridBagSizer" before="class TWaXLFrame" language="py" /%}
|
||||
|
||||
Update the `TWaXLFrame`'s constructor as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/3_account.py", language="py", start_with="def __init__(self, url, test_network=True):", end_before="def build_ui(self):") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/3_account.py" from="def __init__(self, url, test_network=True):" before="def build_ui(self):" language="py" /%}
|
||||
|
||||
Now the constructor takes a boolean to indicate whether it's connecting to a test network. (If you provide a Mainnet URL, you should also pass `False`.) It uses this to encode and decode X-addresses and warn if they're intended for a different network. It also calls a new method, `prompt_for_account()` to get an address and wallet, and passes those to the renamed `watch_xrpl_account()` background job.
|
||||
|
||||
Update the `build_ui()` method definition as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/3_account.py", language="py", start_with="def build_ui(self):", end_before="def run_bg_job(self, job):") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/3_account.py" from="def build_ui(self):" before="def run_bg_job(self, job):" language="py" /%}
|
||||
|
||||
This adds a [`wx.StaticBox`](https://docs.wxpython.org/wx.StaticBox.html) with several new widgets, then uses the `AutoGridBagSizer` (defined above) to lay them out in 2×4 grid within the box. These new widgets are all static text to display [details of the account](accountroot.html), though some of them start with placeholder text. (Since they require data from the ledger, you have to wait for the worker thread to send that data back.)
|
||||
This adds a [`wx.StaticBox`](https://docs.wxpython.org/wx.StaticBox.html) with several new widgets, then uses the `AutoGridBagSizer` (defined above) to lay them out in 2×4 grid within the box. These new widgets are all static text to display [details of the account](../../references/protocol/ledger-data/ledger-entry-types/accountroot.md), though some of them start with placeholder text. (Since they require data from the ledger, you have to wait for the worker thread to send that data back.)
|
||||
|
||||
**Caution:** You may notice that even though the constructor for this class sees the `wallet` variable, it does not save it as a property of the object. This is because the wallet mostly needs to be managed by the worker thread, not the GUI thread, and updating it in both places might not be thread-safe.
|
||||
|
||||
Add a new `prompt_for_account()` method to the `TWaXLFrame` class:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/3_account.py", language="py", start_with="def prompt_for_account", end_before="def update_ledger") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/3_account.py" from="def prompt_for_account" before="def update_ledger" language="py" /%}
|
||||
|
||||
The constructor calls this method to prompt the user for their [address](addresses.html) or [master seed](cryptographic-keys.html#seed), then processes the user input to decode whatever value the user put in, and use it accordingly. With wxPython, you usually follow this pattern with dialog boxes:
|
||||
The constructor calls this method to prompt the user for their [address](../../concepts/accounts/addresses.md) or [master seed](../../concepts/accounts/cryptographic-keys.md#seed), then processes the user input to decode whatever value the user put in, and use it accordingly. With wxPython, you usually follow this pattern with dialog boxes:
|
||||
|
||||
1. Create a new instance of a dialog class, such as a [`wx.TextEntryDialog`](https://docs.wxpython.org/wx.TextEntryDialog.html).
|
||||
2. Use `showModal()` to display it to the user and get a return code based on which button the user clicked.
|
||||
@@ -225,50 +225,50 @@ This code also binds an _event handler_, which is a method that is called whenev
|
||||
|
||||
Add the following method to `TWaXLFrame` class to define the handler:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/3_account.py", language="py", start_with="def toggle_dialog_style", end_before="def prompt_for_account") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/3_account.py" from="def toggle_dialog_style" before="def prompt_for_account" language="py" /%}
|
||||
|
||||
Event handlers generally take one positional argument, a [`wx.Event` object](https://docs.wxpython.org/wx.Event.html) which describes the exact event that occurred. In this case, the handler uses this object to find out what value the user input. If the input looks like a master seed (it starts with the letter "s"), the handler switches the dialog to a "password" style that masks the user input, so people viewing the user's screen won't see the secret. And, if the user erases it and switches back to inputting an address, the handler toggles the style back.
|
||||
|
||||
Add the following lines **at the end of** the `update_ledger()` method:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/3_account.py", language="py", start_with="# Save reserve settings", end_before="def calculate_reserve_xrp") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/3_account.py" from="# Save reserve settings" before="def calculate_reserve_xrp" language="py" /%}
|
||||
|
||||
This saves the ledger's current reserves settings, so that you can use them to calculate the account's total amount of XRP reserved. Add the following method to the `TWaXLFrame` class, to do exactly that:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/3_account.py", language="py", start_with="def calculate_reserve_xrp", end_before="def update_account") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/3_account.py" from="def calculate_reserve_xrp" before="def update_account" language="py" /%}
|
||||
|
||||
Add an `update_account()` method to the `TWaXLFrame` class:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/3_account.py", language="py", start_with="def update_account", end_before="if __name__") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/3_account.py" from="def update_account" before="if __name__" language="py" /%}
|
||||
|
||||
The worker thread calls this method to pass account details to the GUI for display.
|
||||
|
||||
Lastly, towards the end of the file, in the `if __name__ == "__main__":` block, update the line that instantiates the `TWaXLFrame` class to pass the new `test_net` parameter:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/3_account.py", language="py", start_with="frame = TWaXLFrame", end_before="frame.Show()") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/3_account.py" from="frame = TWaXLFrame" before="frame.Show()" language="py" /%}
|
||||
|
||||
(If you change the code to connect to a Mainnet server URL, also change this value to `False`.)
|
||||
|
||||
To test your wallet app with your own test account, first go to the [Testnet Faucet](xrp-testnet-faucet.html) and **Get Testnet credentials**. Save the address and secret key somewhere, and try your wallet app with either one. Then, to see balance changes, go to the [Transaction Sender](tx-sender.html) and paste your address into the **Destination Address** field. Click **Initialize** and try out some of the transaction types there, and see if the balance displayed by your wallet app updates as you expect.
|
||||
To test your wallet app with your own test account, first go to the [Testnet Faucet](/resources/dev-tools/xrp-faucets) and **Get Testnet credentials**. Save the address and secret key somewhere, and try your wallet app with either one. Then, to see balance changes, go to the [Transaction Sender](/resources/dev-tools/tx-sender) and paste your address into the **Destination Address** field. Click **Initialize** and try out some of the transaction types there, and see if the balance displayed by your wallet app updates as you expect.
|
||||
|
||||
|
||||
### 4. Show Account's Transactions
|
||||
|
||||
**Full code for this step:** [`4_tx_history.py`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/py/4_tx_history.py)
|
||||
**Full code for this step:** {% repo-link path="content/_code-samples/build-a-desktop-wallet/py/4_tx_history.py" %}`4_tx_history.py`{% /repo-link %}
|
||||
|
||||
At this point, your wallet shows the account's balance getting updated, but doesn't show you anything about the actual transactions that caused the updates. So, the next step is to display the account's transaction history (and keep it updated).
|
||||
|
||||
The new transaction history displays in a new tab, like this:
|
||||
|
||||

|
||||

|
||||
|
||||
Additionally, the app can produce desktop notifications (sometimes called "toasts"), which might look like this depending on your operating system:
|
||||
|
||||

|
||||

|
||||
|
||||
First, add the following imports to get GUI classes for the table view and notifications:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/4_tx_history.py", language="py", start_with="import wx.dataview", end_before="import asyncio") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/4_tx_history.py" from="import wx.dataview" before="import asyncio" language="py" /%}
|
||||
|
||||
Next, update the `watch_xrpl_account()` method of the worker class to pass transaction details to the GUI when you receive a transaction subscription message. This requires only one line:
|
||||
|
||||
@@ -278,17 +278,17 @@ wx.CallAfter(self.gui.add_tx_from_sub, message)
|
||||
|
||||
The complete method should look like this:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/4_tx_history.py", language="py", start_with="async def watch_xrpl_account", end_before="async def on_connected") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/4_tx_history.py" from="async def watch_xrpl_account" before="async def on_connected" language="py" /%}
|
||||
|
||||
Have the worker use the [account_tx method][] to look up the account's transaction history and pass it to the GUI. This method gets a list of transactions that affected an account, including transactions from, to, or passing through the account in question, starting with the most recent by default. Add new code **to the end of** the `XRPLMonitorThread`'s `on_connected()` method, as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/4_tx_history.py", language="py", start_with="# Get the first page of the account's transaction history", end_before="class AutoGridBagSizer") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/4_tx_history.py" from="# Get the first page of the account's transaction history" before="class AutoGridBagSizer" language="py" /%}
|
||||
|
||||
**Note:** You may have to [paginate](markers-and-pagination.html) across multiple [account_tx][account_tx method] requests and responses if you want the _complete_ list of transactions that affected an account since its creation. This example does not show pagination, so the app only displays the most recent transactions to affect the account.
|
||||
**Note:** You may have to [paginate](../../references/http-websocket-apis/api-conventions/markers-and-pagination.md) across multiple [account_tx][account_tx method] requests and responses if you want the _complete_ list of transactions that affected an account since its creation. This example does not show pagination, so the app only displays the most recent transactions to affect the account.
|
||||
|
||||
Now, edit the `build_ui()` method of the `TWaXLFrame` class. **Update the beginning of the method** to add a new [`wx.Notebook`](https://docs.wxpython.org/wx.Notebook.html), which makes a "tabs" interface, and make the `main_panel` into the first tab, as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/4_tx_history.py", language="py", start_with="def build_ui", end_before="self.acct_info_area") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/4_tx_history.py" from="def build_ui" before="self.acct_info_area" language="py" /%}
|
||||
|
||||
Additionally, add a new tab for the transaction history to the **end of the** `build_ui()` method, as follows:
|
||||
|
||||
@@ -298,64 +298,64 @@ This adds a second tab containing a [`wx.dataview.DataViewListCtrl`](https://doc
|
||||
|
||||
Add the following helper method to the `TWaXLFrame` class:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/4_tx_history.py", language="py", start_with="def displayable_amount", end_before="def add_tx_row") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/4_tx_history.py" from="def displayable_amount" before="def add_tx_row" language="py" /%}
|
||||
|
||||
This method takes a [currency amount](basic-data-types.html#specifying-currency-amounts) and converts it into a string for display to a human. Since it's used with the [`delivered_amount` field](transaction-metadata.html#delivered_amount) in particular, it also handles the special case for pre-2014 partial payments where the delivered amount is unavailable.
|
||||
This method takes a [currency amount](../../references/protocol/data-types/basic-data-types.md#specifying-currency-amounts) and converts it into a string for display to a human. Since it's used with the [`delivered_amount` field](../../references/protocol/transactions/metadata.md#delivered_amount) in particular, it also handles the special case for pre-2014 partial payments where the delivered amount is unavailable.
|
||||
|
||||
After that, add another helper method to the `TWaXLFrame` class:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/4_tx_history.py", language="py", start_with="def add_tx_row", end_before="def update_account_tx") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/4_tx_history.py" from="def add_tx_row" before="def update_account_tx" language="py" /%}
|
||||
|
||||
This method takes a transaction object, parses some of its fields into formats more suitable for displaying to users, and then adds it to the `DataViewListCtrl` in the transaction history tab.
|
||||
|
||||
Add a method to the `TWaXLFrame` class to update the transaction history based on the [account_tx response][account_tx method] from the worker thread, as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/4_tx_history.py", language="py", start_with="def update_account_tx", end_before="def add_tx_from_sub") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/4_tx_history.py" from="def update_account_tx" before="def add_tx_from_sub" language="py" /%}
|
||||
|
||||
Lastly, add a similar method to the `TWaXLFrame` to add a single transaction to the transaction history table whenever the worker thread passes a transaction subscription message:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/4_tx_history.py", language="py", start_with="def add_tx_from_sub", end_before="if __name__") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/4_tx_history.py" from="def add_tx_from_sub" before="if __name__" language="py" /%}
|
||||
|
||||
As before, you can test your wallet app with your own test account if you use the [Testnet Faucet](xrp-testnet-faucet.html) and the [Transaction Sender](tx-sender.html). On the Faucet page, select **Get Testnet credentials** (or use the same credentials from before). Input either the address or secret when you open your wallet app. On the Transaction Sender page, paste your address into the **Destination Address** field, click **Initialize**, then click various transaction buttons to see how your wallet displays the results.
|
||||
As before, you can test your wallet app with your own test account if you use the [Testnet Faucet](/resources/dev-tools/xrp-faucets) and the [Transaction Sender](/resources/dev-tools/tx-sender). On the Faucet page, select **Get Testnet credentials** (or use the same credentials from before). Input either the address or secret when you open your wallet app. On the Transaction Sender page, paste your address into the **Destination Address** field, click **Initialize**, then click various transaction buttons to see how your wallet displays the results.
|
||||
|
||||
|
||||
### 5. Send XRP
|
||||
|
||||
**Full code for this step:** [`5_send_xrp.py`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py)
|
||||
**Full code for this step:** {% repo-link path="content/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" %}`5_send_xrp.py`{% /repo-link %}
|
||||
|
||||
Until now, you've made the app able to view data from the ledger, and it's capable of showing the transactions an account has received. Now it's finally time to make the app capable of _sending_ transactions. For now, you can stick to sending [direct XRP payments](direct-xrp-payments.html) because there are more complexities involved in sending [issued tokens](issued-currencies.html).
|
||||
Until now, you've made the app able to view data from the ledger, and it's capable of showing the transactions an account has received. Now it's finally time to make the app capable of _sending_ transactions. For now, you can stick to sending [direct XRP payments](../../concepts/payment-types/direct-xrp-payments.md) because there are more complexities involved in sending [issued tokens](../../concepts/tokens/index.md).
|
||||
|
||||
The main window gets a new "Send XRP" button:
|
||||
|
||||

|
||||

|
||||
|
||||
Clicking this button opens a dialog where the user can enter the details of the payment:
|
||||
|
||||

|
||||

|
||||
|
||||
First, add the [regular expressions](https://docs.python.org/3/howto/regex.html) library to the list of imports at the top of the file:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="import re", end_before="from threading") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" from="import re" before="from threading" language="py" /%}
|
||||
|
||||
In the `XRPLMonitorThread` class, add the following lines to the `on_connected()` method, anywhere **after getting a successful [account_info][account_info method] response**:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="if self.wallet:", end_before="# Get the first page") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" from="if self.wallet:" before="# Get the first page" language="py" /%}
|
||||
|
||||
Add a new method to the `XRPLMonitorThread` class to send an XRP payment based on data the user provides, and alert the GUI when it has been sent:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="def send_xrp", end_before="class AutoGridBagSizer") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" from="def send_xrp" before="class AutoGridBagSizer" language="py" /%}
|
||||
|
||||
In this flow, the app sends the transaction without waiting for it to be confirmed by the consensus process. You should be careful to mark any results from the initial submission as "pending" or "tentative" since the actual result of the transaction [isn't final until it's confirmed](finality-of-results.html). Since the app is also subscribed to the account's transactions, it automatically gets notified when the transaction is confirmed.
|
||||
In this flow, the app sends the transaction without waiting for it to be confirmed by the consensus process. You should be careful to mark any results from the initial submission as "pending" or "tentative" since the actual result of the transaction [isn't final until it's confirmed](../../concepts/transactions/finality-of-results/index.md). Since the app is also subscribed to the account's transactions, it automatically gets notified when the transaction is confirmed.
|
||||
|
||||
Now, create a custom dialog for the user to input the necessary details for the payment:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="class SendXRPDialog", end_before="def on_to_edit") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" from="class SendXRPDialog" before="def on_to_edit" language="py" /%}
|
||||
|
||||
This subclass of [`wx.Dialog`](https://docs.wxpython.org/wx.Dialog.html) has several custom widgets, which are laid out using the `GridBagSizer` defined earlier. Notably, it has text boxes for the "To" address, the amount of XRP, and the [destination tag](source-and-destination-tags.html) to use, if any. (A destination tag is kind of like a phone extension for an XRP Ledger address: for addresses owned by individuals, you don't need it, but if the destination address has many users then you need to specify it so that the destination knows which recipient you intended. It's common to need a destination tag to deposit at a cryptocurrency exchange.) The dialog also has **OK** and **Cancel** buttons, which automatically function to cancel or complete the dialog, although the "OK" button is labeled "Send" instead to make it clearer what the app does when the user clicks it.
|
||||
This subclass of [`wx.Dialog`](https://docs.wxpython.org/wx.Dialog.html) has several custom widgets, which are laid out using the `GridBagSizer` defined earlier. Notably, it has text boxes for the "To" address, the amount of XRP, and the [destination tag](../../concepts/transactions/source-and-destination-tags.md) to use, if any. (A destination tag is kind of like a phone extension for an XRP Ledger address: for addresses owned by individuals, you don't need it, but if the destination address has many users then you need to specify it so that the destination knows which recipient you intended. It's common to need a destination tag to deposit at a cryptocurrency exchange.) The dialog also has **OK** and **Cancel** buttons, which automatically function to cancel or complete the dialog, although the "OK" button is labeled "Send" instead to make it clearer what the app does when the user clicks it.
|
||||
|
||||
The `SendXRPDialog` constructor also binds two event handlers for when the user inputs text in the "to" and "destination tag" fields, so you need the definitions for those handlers to the same class. First, add `on_to_edit()`:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="def on_to_edit", end_before="def on_dest_tag_edit") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" from="def on_to_edit" before="def on_dest_tag_edit" language="py" /%}
|
||||
|
||||
This checks the "To" address to ensure that it matches two conditions:
|
||||
|
||||
@@ -366,37 +366,37 @@ If either condition is not met, the handler disables the "Send" button for this
|
||||
|
||||
Next, add the `on_dest_tag_edit()` handler, also as a method of the `SendXRPDialog` class:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="def on_dest_tag_edit", end_before="class TWaXLFrame") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" from="def on_dest_tag_edit" before="class TWaXLFrame" language="py" /%}
|
||||
|
||||
In other GUI toolkits, you might be able to use a dedicated number entry control for the Destination Tag field, but with wxPython there is only a generic text entry field, so the `on_dest_tag_edit()` handler makes it behave more like a number-only control by instantly deleting any non-numeric characters the user tries to enter in the field.
|
||||
|
||||
From here, you need to edit the `TWaXLFrame` class. First, in the `build_ui()` method, you need to add a new "Send XRP" button, and bind it to a new event handler. Add the following lines **before the code to add things to the sizer**:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="# Send XRP button.", end_before="self.ledger_info =") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" from="# Send XRP button." before="self.ledger_info =" language="py" /%}
|
||||
|
||||
Still in the `build_ui()` method, add the new button to the `main_sizer` so it fits nicely in between the account info area and the ledger info area. The sizer code **at the end of the "Tab 1" section** should look like the following, including one new line and the previous (unchanged) lines:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="main_sizer = wx.BoxSizer", end_before="# Tab 2:") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" from="main_sizer = wx.BoxSizer" before="# Tab 2:" language="py" /%}
|
||||
|
||||
Also in the `build_ui()` method, initialize a dictionary to hold rows with pending transaction details, so that you can replace them with the confirmed results when those are available. Add this line **anywhere near the "Tab 2" section** that sets up `self.tx_list` code:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="self.pending_tx_rows = {}", end_before="objs_panel") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" from="self.pending_tx_rows = {}" before="objs_panel" language="py" /%}
|
||||
|
||||
The "Send XRP" button starts out disabled, so you need to add a new method to the `TWaXLFrame` class to enable it when the right conditions are met:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="def enable_readwrite", end_before="def displayable_amount") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" from="def enable_readwrite" before="def displayable_amount" language="py" /%}
|
||||
|
||||
The changes you made to `on_connected()` earlier in this step call this method after successfully receiving account data, but only if the worker class has a `Wallet` instance—meaning the user input the secret key to an account that really exists. If the user input an address, this method never gets called.
|
||||
|
||||
Add the handler for when the user clicks the "Send XRP" button as a method of the `TWaXLFrame` class:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="def click_send_xrp", end_before="if __name__") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" from="def click_send_xrp" before="if __name__" language="py" /%}
|
||||
|
||||
This dialog opens a new "Send XRP" dialog using the custom `SendXRPDialog` class defined earlier in this step. If the user clicks the "Send" button, it passes the details to the worker thread to send the payment, and displays a notification that indicates the transaction is sending. (Note, the transaction can still fail after this point, so the notification does not say what the transaction did.)
|
||||
|
||||
Also add a new method to the `TWaXLFrame` class to display the pending transaction in the Transaction History pane when the worker thread sends it, as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="def add_pending_tx", end_before="def click_send_xrp") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/5_send_xrp.py" from="def add_pending_tx" before="def click_send_xrp" language="py" /%}
|
||||
|
||||
This method is similar to the `add_tx_row()` method in that it processes a transaction for display and adds it to the Transaction History table. The differences are that it takes one of [xrpl-py's Transaction models](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.models.transactions.html) rather than a JSON-like API response; and it handles certain columns differently because the transaction has not yet been confirmed. Importantly, it saves a reference to table row containing this transaction to the `pending_tx_rows` dictionary, so that later on when the transaction is confirmed, you can remove the table row for the pending version and replace it with the final version of the transaction.
|
||||
|
||||
@@ -408,24 +408,28 @@ You can now use your wallet to send XRP! You can even fund an entirely new accou
|
||||
|
||||
1. Open the Python interpreter.
|
||||
|
||||
python3
|
||||
```
|
||||
python3
|
||||
```
|
||||
|
||||
**Caution:** Depending on your OS, the command may be `python` or `python3`. You want to open Python 3, not a Python 2.x version.
|
||||
|
||||
|
||||
2. Run the following commands in the Python interpreter:
|
||||
|
||||
import xrpl
|
||||
w = xrpl.wallet.Wallet.create()
|
||||
print(w.address)
|
||||
print(w.seed)
|
||||
exit()
|
||||
```
|
||||
import xrpl
|
||||
w = xrpl.wallet.Wallet.create()
|
||||
print(w.address)
|
||||
print(w.seed)
|
||||
exit()
|
||||
```
|
||||
|
||||
Save the classic address and seed somewhere.
|
||||
|
||||
3. Open your wallet app and provide a **Secret** (seed) value from an already-funded address, such as one you got from the [Testnet Faucet](xrp-testnet-faucet.html).
|
||||
3. Open your wallet app and provide a **Secret** (seed) value from an already-funded address, such as one you got from the [Testnet Faucet](/resources/dev-tools/xrp-faucets).
|
||||
|
||||
4. Send at least the [base reserve](reserves.html) (currently 10 XRP) to the brand-new classic address you generated in the Python interpreter.
|
||||
4. Send at least the [base reserve](../../concepts/accounts/reserves.md) (currently 10 XRP) to the brand-new classic address you generated in the Python interpreter.
|
||||
|
||||
5. Wait for the transaction to be confirmed, then close your wallet app.
|
||||
|
||||
@@ -436,33 +440,33 @@ You can now use your wallet to send XRP! You can even fund an entirely new accou
|
||||
|
||||
### 6. Domain Verification and Polish
|
||||
|
||||
**Full code for this step:** [`6_verification_and_polish.py`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py)
|
||||
**Full code for this step:** {% repo-link path="content/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py" %}`6_verification_and_polish.py`{% /repo-link %}
|
||||
|
||||
One of the biggest shortcomings of the wallet app from the previous step is that it doesn't provide a lot of protections or feedback for users to save them from human error and scams. These sorts of protections are extra important when dealing with the cryptocurrency space, because decentralized systems like the XRP Ledger don't have an admin or support team you can ask to cancel or refund a payment if you made a mistake such as sending it to the wrong address. This step shows how to add some checks on destination addresses to warn the user before sending.
|
||||
|
||||
One type of check you can make is to verify the domain name associated with an XRP Ledger address; this is called [account domain verification](xrp-ledger-toml.html#account-verification). When an account's domain is verified, you could show it like this:
|
||||
One type of check you can make is to verify the domain name associated with an XRP Ledger address; this is called [account domain verification](../../references/xrp-ledger-toml.md#account-verification). When an account's domain is verified, you could show it like this:
|
||||
|
||||

|
||||

|
||||
|
||||
When there are other errors, you can expose them to the user with an icon and a tooltip, which looks like this:
|
||||
|
||||

|
||||

|
||||
|
||||
The following code implements account domain verification; **save it as a new file** named `verify_domain.py` in the same folder as your app's main file:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/verify_domain.py", language="py") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/verify_domain.py" language="py" /%}
|
||||
|
||||
**In your app's main file,** import the `verify_account_domain` function:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py", language="py", start_with="from verify_domain", end_before="class XRPLMonitorThread") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py" from="from verify_domain" before="class XRPLMonitorThread" language="py" /%}
|
||||
|
||||
In the `XRPLMonitorThread` class, add a new `check_destination()` method to check the destination address, as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py", language="py", start_with="async def check_destination", end_before="async def send_xrp") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py" from="async def check_destination" before="async def send_xrp" language="py" /%}
|
||||
|
||||
This code uses [`xrpl.asyncio.account.get_account_info()`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.asyncio.account.html#xrpl.asyncio.account.get_account_info) to look up the account in the ledger; unlike using the client's `request()` method, `get_account_info()` raises an exception if the account is not found.
|
||||
|
||||
If the account _does_ exist, the code checks for the [`lsfDisallowXRP` flag](accountroot.html#accountroot-flags). Note that this is an `lsf` (ledger state flag) value because this is an object from the ledger state data; these are different than the flag values the [AccountSet transaction][] uses to configure the same settings.
|
||||
If the account _does_ exist, the code checks for the [`lsfDisallowXRP` flag](../../references/protocol/ledger-data/ledger-entry-types/accountroot.md#accountroot-flags). Note that this is an `lsf` (ledger state flag) value because this is an object from the ledger state data; these are different than the flag values the [AccountSet transaction][] uses to configure the same settings.
|
||||
|
||||
Finally, the code decodes the account's `Domain` field, if present, and performs domain verification using the method imported above.
|
||||
|
||||
@@ -470,23 +474,23 @@ Finally, the code decodes the account's `Domain` field, if present, and performs
|
||||
|
||||
After this, it's time to update the `SendXRPDialog` class to make it capable of displaying these errors. You can also set a more specific upper bound for how much XRP the account can actually send. Change the constructor to take a new parameter:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py", language="py", start_with="def __init__(self, parent, max_send=100000000.0)", end_before="wx.Dialog.__init__") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py" from="def __init__(self, parent, max_send=100000000.0)" before="wx.Dialog.__init__" language="py" /%}
|
||||
|
||||
Add some icon widgets to the UI, also in the `SendXRPDialog` constructor:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py", language="py", start_with="# Icons to indicate", end_before="lbl_to =") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py" from="# Icons to indicate" before="lbl_to =" language="py" /%}
|
||||
|
||||
Still in the `SendXRPDialog` constructor, add a maximum value to the line that creates the `self.txt_amt` widget:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py", language="py", start_with="self.txt_amt =", end_before="self.txt_amt.SetDigits(6)") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py" from="self.txt_amt =" before="self.txt_amt.SetDigits(6)" language="py" /%}
|
||||
|
||||
Don't forget to add all the new widgets to the `SendXRPDialog`'s sizer so they fit in the right places. Update the `BulkAdd` call in the constructor as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py", language="py", start_with="sizer.BulkAdd(((lbl_to,", end_before="sizer.Fit(self)") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py" from="sizer.BulkAdd(((lbl_to," before="sizer.Fit(self)" language="py" /%}
|
||||
|
||||
Next, refactor the `on_to_edit()` handler in the `SendXRPDialog` class to perform more checks, including the new background check on the destination address. The updated handler should be as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py", language="py", start_with="def on_to_edit", end_before="def on_dest_tag_edit") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py" from="def on_to_edit" before="def on_dest_tag_edit" language="py" /%}
|
||||
|
||||
In addition to starting the background check, this handler does some checks immediately. Any check that doesn't require getting data from the network is probably fast enough to run directly in the handler; if the check requires network access, you have to run it in the worker thread instead.
|
||||
|
||||
@@ -501,23 +505,23 @@ The code shows the error icons when it finds errors (and hides them when it does
|
||||
|
||||
Moving on, you also need a new method in the `SendXRPDialog` class to process the results from the background check. Add the following code:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py", language="py", start_with="def update_dest_info", end_before="class TWaXLFrame") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py" from="def update_dest_info" before="class TWaXLFrame" language="py" /%}
|
||||
|
||||
This code takes the dictionary passed by the `check_destination()` and uses it to update various widgets in the Send XRP dialog's GUI.
|
||||
|
||||
You need to make a few small updates to configure the maximum send amount in the Send XRP dialog. Start by adding these lines to the `TWaXLFrame` class's constructor:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py", language="py", start_with="# This account's total XRP reserve", end_before="self.build_ui()") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py" from="# This account's total XRP reserve" before="self.build_ui()" language="py" /%}
|
||||
|
||||
Then modify the `update_account()` method of the `TWaXLFrame` to save the latest calculated reserve. Modify the last few lines to look like this:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py", language="py", start_with="# Display account reserve and", end_before="def enable_readwrite") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py" from="# Display account reserve and" before="def enable_readwrite" language="py" /%}
|
||||
|
||||
Finally, calculate the maximum amount the user can send and provide it to the Send XRP dialog. Modify **the beginning of the `click_send_xrp()` handler** as follows:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py", language="py", start_with="xrp_bal = Decimal", end_before="dlg.CenterOnScreen()") }}
|
||||
{% code-snippet file="/_code-samples/build-a-desktop-wallet/py/6_verification_and_polish.py" from="xrp_bal = Decimal" before="dlg.CenterOnScreen()" language="py" /%}
|
||||
|
||||
The formula this code uses to calculate the maximum amount the user can send is the account's XRP balance, minus its [reserve](reserves.html) and minus the [transaction cost](transaction-cost.html). The calculation uses the `Decimal` class to avoid rounding errors, but ultimately it has to be converted down to a `float` because that's what wxPython's [`wx.SpinCtrlDouble`](https://docs.wxpython.org/wx.SpinCtrlDouble.html) accepts for minimum and maximum values. Still there is less opportunity for floating-point rounding errors to occur if the conversion happens _after_ the other calculations.
|
||||
The formula this code uses to calculate the maximum amount the user can send is the account's XRP balance, minus its [reserve](../../concepts/accounts/reserves.md) and minus the [transaction cost](../../concepts/transactions/transaction-cost.md). The calculation uses the `Decimal` class to avoid rounding errors, but ultimately it has to be converted down to a `float` because that's what wxPython's [`wx.SpinCtrlDouble`](https://docs.wxpython.org/wx.SpinCtrlDouble.html) accepts for minimum and maximum values. Still there is less opportunity for floating-point rounding errors to occur if the conversion happens _after_ the other calculations.
|
||||
|
||||
Test your wallet app the same way you did in the previous steps. To test domain verification, try entering the following addresses in the "To" box of the Send XRP dialog:
|
||||
|
||||
@@ -541,13 +545,10 @@ To test X-addresses, try the following addresses:
|
||||
|
||||
Now that you have a functional wallet, you can take it in several new directions. The following are a few ideas:
|
||||
|
||||
- You could support more of the XRP Ledger's [transaction types](transaction-types.html) including [tokens](issued-currencies.html) and [cross-currency payments](cross-currency-payments.html)
|
||||
- Example code for displaying token balances and other objects: [`7_owned_objects.py`]({{target.github_forkurl}}/tree/{{target.github_branch}}/content/_code-samples/build-a-desktop-wallet/py/7_owned_objects.py)
|
||||
- Allow the user to trade in the [decentralized exchange](decentralized-exchange.html)
|
||||
- You could support more of the XRP Ledger's [transaction types](../../references/protocol/transactions/types/index.md) including [tokens](../../concepts/tokens/index.md) and [cross-currency payments](../../concepts/payment-types/cross-currency-payments.md)
|
||||
- Example code for displaying token balances and other objects: {% repo-link path="content/_code-samples/build-a-desktop-wallet/py/7_owned_objects.py" %}`7_owned_objects.py`{% /repo-link %}
|
||||
- Allow the user to trade in the [decentralized exchange](../../concepts/tokens/decentralized-exchange/index.md)
|
||||
- Add a way to request payments, such as with QR codes or URIs that open in your wallet.
|
||||
- Support better account security including [regular key pairs](cryptographic-keys.html#regular-key-pair) or [multi-signing](multi-signing.html).
|
||||
- Support better account security including [regular key pairs](../../concepts/accounts/cryptographic-keys.md#regular-key-pair) or [multi-signing](../../concepts/accounts/multi-signing.md).
|
||||
|
||||
<!--{# common link defs #}-->
|
||||
{% include '_snippets/rippled-api-links.md' %}
|
||||
{% include '_snippets/tx-type-links.md' %}
|
||||
{% include '_snippets/rippled_versions.md' %}
|
||||
{% raw-partial file="/_snippets/common-links.md" /%}
|
||||
|
||||
Reference in New Issue
Block a user