mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-12-06 17:27:57 +00:00
Reorg tutorials to match nav, and update links
This commit is contained in:
555
docs/tutorials/python/build-a-desktop-wallet-in-python.md
Normal file
555
docs/tutorials/python/build-a-desktop-wallet-in-python.md
Normal file
@@ -0,0 +1,555 @@
|
||||
---
|
||||
html: build-a-desktop-wallet-in-python.html
|
||||
parent: python.html
|
||||
seo:
|
||||
description: Build a graphical desktop wallet for the XRPL in Python.
|
||||
---
|
||||
# Build a Desktop Wallet in Python
|
||||
<!-- STYLE_OVERRIDE: wallet -->
|
||||
|
||||
This tutorial demonstrates how to build a desktop wallet for the XRP Ledger using the Python programming language and various libraries. This application can be used as a starting point for building a more complete and powerful application, as a reference point for building comparable apps, or as a learning experience to better understand how to integrate XRP Ledger functionality into a larger project.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
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](../python/get-started.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 {% repo-link path="_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](../../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](../../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](../../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.
|
||||
|
||||
## Steps
|
||||
|
||||
### Install Dependencies
|
||||
|
||||
This tutorial depends on various programming libraries. Before you get started coding, you should install all of them as follows:
|
||||
|
||||
```sh
|
||||
pip3 install --upgrade xrpl-py wxPython requests toml
|
||||
```
|
||||
|
||||
(On some systems, the command may be `pip` or you may need to use `sudo pip3` instead.)
|
||||
|
||||
This installs and upgrades the following Python libraries:
|
||||
|
||||
- [xrpl-py](https://xrpl-py.readthedocs.io/), a client library for the XRP Ledger. This tutorial requires **version 1.3.0 or higher**.
|
||||
- [wxPython](https://wxpython.org/), a cross-platform graphical toolkit.
|
||||
- [Requests](https://requests.readthedocs.io/), a library for making HTTP requests.
|
||||
- [toml](https://github.com/uiri/toml), a library for parsing TOML-formatted files.
|
||||
|
||||
The `requests` and `toml` libraries are only needed for the [domain verification in step 6](#6-domain-verification-and-polish), but you can install them now while you're installing other dependencies.
|
||||
|
||||
#### Notes for Windows Users
|
||||
|
||||
On Windows, you can build apps using either Windows natively or by using the Windows Subsystem for Linux (WSL). <!-- SPELLING_IGNORE: wsl -->
|
||||
|
||||
On native Windows, the GUI uses native Windows controls and should run without any dependencies beyond those mentioned above.
|
||||
|
||||
On WSL, you may need to install `libnotify-dev` as follows:
|
||||
|
||||
```sh
|
||||
apt-get install libnotify-dev
|
||||
```
|
||||
|
||||
If you have trouble installing wxPython on WSL, you can also try installing it this way:
|
||||
|
||||
```sh
|
||||
python -m pip install -U -f https://extras.wxpython.org/wxPython4/extras/linux/gtk3/ubuntu-18.04 wxPython
|
||||
```
|
||||
|
||||
|
||||
### 1. Hello World
|
||||
|
||||
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:
|
||||
|
||||
{% 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:** {% repo-link path="_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.
|
||||
|
||||
The main challenge with threads is that you have to be careful not to access data from one thread that another thread may be in the middle of changing. A straightforward way to do this is to design your program so that each thread has variables it "owns" and doesn't write to the other thread's variables. In this program, each thread is its own class, so each thread should only write to its own class attributes (anything starting with `self.`). When the threads need to communicate, they use specific, "thread-safe" methods of communication, namely:
|
||||
|
||||
- For GUI to worker thread, use [`asyncio.run_coroutine_threadsafe()`](https://docs.python.org/3/library/asyncio-task.html#asyncio.run_coroutine_threadsafe).
|
||||
- For worker to GUI communications, use [`wx.CallAfter()`](https://docs.wxpython.org/wx.functions.html#wx.CallAfter).
|
||||
|
||||
To make full use of the XRP Ledger's ability to push messages to the client, use [xrpl-py's `AsyncWebsocketClient`](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.asyncio.clients.html#xrpl.asyncio.clients.AsyncWebsocketClient) instead of `JsonRpcClient`. This lets you "subscribe" to updates using asynchronous code, while also performing other request/response actions in response to various events such as user input.
|
||||
|
||||
**Note:** While you can, technically, use the synchronous (that is, non-async) WebSocket client, it gets significantly more complicated to manage these things while also handling input from the GUI. Even if writing async code is unfamiliar to you, it can be worth it to reduce the overall complexity of the code you have to write later.
|
||||
|
||||
Add these imports to the top of the file:
|
||||
|
||||
{% 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):
|
||||
|
||||
{% 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.
|
||||
|
||||
The `watch_xrpl()` function is an example of a such a task (which the GUI thread starts when it's ready): it connects to the XRP Ledger, then calls the [subscribe method][] to be notified whenever a new ledger is validated. It uses the immediate response _and_ all later subscription stream messages to trigger updates of the GUI.
|
||||
|
||||
**Tip:** Define worker jobs like this using `async def` instead of `def` so that you can use the `await` keyword in them; you need to use `await` to get the response to the [`AsyncWebsocketClient.request()` method](https://xrpl-py.readthedocs.io/en/stable/source/xrpl.asyncio.clients.html#xrpl.asyncio.clients.AsyncWebsocketClient.request). Normally, you would also need to use `await` or something similar to get the response from any function you define with `async def`; but, in this app, the `run_bg_job()` helper takes care of that in a different way.
|
||||
|
||||
Update the code for the main thread and GUI frame to look like this:
|
||||
|
||||
{% 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.
|
||||
|
||||
**Tip:** In this tutorial, all the GUI code is written by hand, but you may find it easier to create powerful GUIs using a "builder" tool such as [wxGlade](http://wxglade.sourceforge.net/). Separating the GUI code from the constructor may make it easier to switch to this type of approach later. <!-- SPELLING_IGNORE: wxGlade -->
|
||||
|
||||
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](../../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:
|
||||
|
||||
{% 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](../../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](../public-servers.md) to connect to the Mainnet or other test networks.
|
||||
|
||||
#### Troubleshooting SSL Certificate Errors
|
||||
|
||||
If you get an error like this, you may need to make sure your operating system's certificate authority store is updated:
|
||||
|
||||
```text
|
||||
[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate
|
||||
```
|
||||
|
||||
On macOS, run the [`Install Certificates.command`](https://stackoverflow.com/questions/52805115/certificate-verify-failed-unable-to-get-local-issuer-certificate) for your Python version.
|
||||
|
||||
On Windows, open Edge or Chrome and browse to <https://s1.ripple.com>, then close the page. This should be enough to update your system's certificates. (It doesn't work if you use Firefox or Safari, because those browser's don't use Windows' certificate validation APIs.) <!-- SPELLING_IGNORE: s1 -->
|
||||
|
||||
|
||||
### 3. Display an Account
|
||||
|
||||
**Full code for this step:** {% repo-link path="_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](../../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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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](../../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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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](../../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:
|
||||
|
||||
{% 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](../../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.
|
||||
3. If the user clicked OK, get a value the user input. This example gets the text the user entered in the box.
|
||||
4. Destroy the dialog instance. If you forget to do this, the application can leak memory whenever the user opens a new dialog.
|
||||
|
||||
From there, the `prompt_for_account()` code branches based on whether the input is a classic address, X-address, seed, or not a valid value at all. Assuming the value decodes successfully, it updates the `wx.StaticText` widgets with both the classic and X-address equivalents of the address and returns them. (As noted above, the constructor passes these values to the worker thread.)
|
||||
|
||||
**Tip:** This code exits if the user inputs an invalid value, but you could rewrite it to prompt again or display a different message to the user.
|
||||
|
||||
This code also binds an _event handler_, which is a method that is called whenever a certain type of thing happens to a particular part of the GUI, usually based on the user's actions. In this case, the trigger is `wx.EVT_TEXT` on the dialog, which triggers immediately when the user types or pastes anything into the dialog's text box.
|
||||
|
||||
Add the following method to `TWaXLFrame` class to define the handler:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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](/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:** {% repo-link path="_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:
|
||||
|
||||
{% 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:
|
||||
|
||||
```py
|
||||
wx.CallAfter(self.gui.add_tx_from_sub, message)
|
||||
```
|
||||
|
||||
The complete method should look like this:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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](../../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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/4_tx_history.py", language="py", start_with="Tab 2: \"Transaction History\"", end_before="def run_bg_job") }}
|
||||
|
||||
This adds a second tab containing a [`wx.dataview.DataViewListCtrl`](https://docs.wxpython.org/wx.dataview.DataViewListCtrl.html), which is capable of displaying a bunch of info as a table. It sets up the table columns to show some relevant details of the account's transactions.
|
||||
|
||||
Add the following helper method to the `TWaXLFrame` class:
|
||||
|
||||
{% 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](../../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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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](/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:** {% repo-link path="_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](../../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:
|
||||
|
||||
{% 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**:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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](../../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:
|
||||
|
||||
{% 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](../../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()`:
|
||||
|
||||
{% 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:
|
||||
|
||||
1. It's a validly formatted classic address or X-address.
|
||||
2. It's not the user's own address—you can't send XRP to yourself.
|
||||
|
||||
If either condition is not met, the handler disables the "Send" button for this dialog. If both conditions are met, it enables the "Send" button.
|
||||
|
||||
Next, add the `on_dest_tag_edit()` handler, also as a method of the `SendXRPDialog` class:
|
||||
|
||||
{% 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**:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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.
|
||||
|
||||
Lastly, update the `add_tx_from_sub()` method so that it finds and updates pending transactions with their final results when those transactions are confirmed. Add the following lines **right before the call to** `self.add_tx_row()`:
|
||||
|
||||
{{ include_code("_code-samples/build-a-desktop-wallet/py/5_send_xrp.py", language="py", start_with="if t[\"tx\"][\"hash\"] in", end_before="self.add_tx_row(t, prepend=True)") }}
|
||||
|
||||
You can now use your wallet to send XRP! You can even fund an entirely new account. To do that:
|
||||
|
||||
1. Open the Python interpreter.
|
||||
|
||||
```
|
||||
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()
|
||||
```
|
||||
|
||||
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](/resources/dev-tools/xrp-faucets).
|
||||
|
||||
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.
|
||||
|
||||
6. Open your wallet app and provide the seed value you generated in the Python interpreter.
|
||||
|
||||
7. You should see the balance and transaction history of your newly-funded account, matching the address you generated in the interpreter.
|
||||
|
||||
|
||||
### 6. Domain Verification and Polish
|
||||
|
||||
**Full code for this step:** {% repo-link path="_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](../../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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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](../../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.
|
||||
|
||||
**Caution:** The background check takes the Send XRP dialog (`dlg`) as a parameter, since each dialog is a separate instance, but does not modify the dialog directly since that might not be thread-safe. (It _only_ uses `wx.CallAfter` to pass the results of the check back to the dialog.)
|
||||
|
||||
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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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.
|
||||
|
||||
One of the new checks is to decode X-addresses to pull out the additional data they encode:
|
||||
|
||||
- If the X-address includes a destination tag, show it in the destination tag field.
|
||||
- If the X-address is not intended for a test network and the app is connected to a test network (or the other way around), show an error.
|
||||
|
||||
One tricky bit of writing handlers like this in GUI code is that you have to be ready for the handler to be called many times as the user inputs and erases data. For example, if you disable a field when some input is invalid, you also have to enable it if the user changes their input to be valid.
|
||||
|
||||
The code shows the error icons when it finds errors (and hides them when it doesn't), and adds tooltips with the error message. You could, of course, display errors to the user in another way as well, such as additional pop-up dialogs or a status bar.
|
||||
|
||||
Moving on, you also need a new method in the `SendXRPDialog` class to process the results from the background check. Add the following code:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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:
|
||||
|
||||
{% 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](../../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:
|
||||
|
||||
| Address | Domain | Verified? |
|
||||
|:-------------------------------------|:-------------|:----------|
|
||||
| `rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW` | `mduo13.com` | ✅ Yes |
|
||||
| `rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn` | `xrpl.org` | ❌ No |
|
||||
| `rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe` | (Not set) | ❌ No |
|
||||
|
||||
To test X-addresses, try the following addresses:
|
||||
|
||||
| Address | Destination Tag | Test Net? |
|
||||
|:--------------------------------------------------|:----------------|:----------|
|
||||
| `T7YChPFWifjCAXLEtg5N74c7fSAYsvPKxzQAET8tbZ8q3SC` | 0 | Yes |
|
||||
| `T7YChPFWifjCAXLEtg5N74c7fSAYsvJVm6xKZ14AmjegwRM` | None | Yes |
|
||||
| `X7d3eHCXzwBeWrZec1yT24iZerQjYLjJrFT7A8ZMzzYWCCj` | 0 | No |
|
||||
| `X7d3eHCXzwBeWrZec1yT24iZerQjYLeTFXz1GU9RBnWr7gZ` | None | No |
|
||||
|
||||
|
||||
## 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](../../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="_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](../../concepts/accounts/cryptographic-keys.md#regular-key-pair) or [multi-signing](../../concepts/accounts/multi-signing.md).
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
210
docs/tutorials/python/get-started.md
Normal file
210
docs/tutorials/python/get-started.md
Normal file
@@ -0,0 +1,210 @@
|
||||
---
|
||||
seo:
|
||||
description: Build a Python app that interacts with the XRP Ledger.
|
||||
labels:
|
||||
- Development
|
||||
---
|
||||
# Get Started Using Python
|
||||
|
||||
This tutorial walks you through the basics of building an XRP Ledger-connected application using [`xrpl-py`](https://github.com/XRPLF/xrpl-py), a pure [Python](https://www.python.org) library built to interact with the XRP Ledger using native Python models and methods.
|
||||
|
||||
This tutorial is intended for beginners and should take no longer than 30 minutes to complete.
|
||||
|
||||
## Learning Goals
|
||||
|
||||
In this tutorial, you'll learn:
|
||||
|
||||
* The basic building blocks of XRP Ledger-based applications.
|
||||
* How to connect to the XRP Ledger using `xrpl-py`.
|
||||
* How to get an account on the [Testnet](/resources/dev-tools/xrp-faucets) using `xrpl-py`.
|
||||
* How to use the `xrpl-py` library to look up information about an account on the XRP Ledger.
|
||||
* How to put these steps together to create a Python app.
|
||||
|
||||
## Requirements
|
||||
|
||||
The `xrpl-py` library supports [Python 3.7](https://www.python.org/downloads/) and later.
|
||||
|
||||
|
||||
## Installation
|
||||
|
||||
The [`xrpl-py` library](https://github.com/XRPLF/xrpl-py) is available on [PyPI](https://pypi.org/project/xrpl-py/). Install with `pip`: <!-- SPELLING_IGNORE: pypi -->
|
||||
|
||||
|
||||
```py
|
||||
pip3 install xrpl-py
|
||||
```
|
||||
|
||||
## Start Building
|
||||
|
||||
When you're working with the XRP Ledger, there are a few things you'll need to manage, whether you're adding XRP to your [account](../../concepts/accounts/accounts.md), integrating with the [decentralized exchange](../../concepts/tokens/decentralized-exchange/index.md), or [issuing tokens](../../concepts/tokens/index.md). This tutorial walks you through basic patterns common to getting started with all of these use cases and provides sample code for implementing them.
|
||||
|
||||
Here are the basic steps you'll need to cover for almost any XRP Ledger project:
|
||||
|
||||
1. [Connect to the XRP Ledger.](#1-connect-to-the-xrp-ledger)
|
||||
1. [Get an account.](#2-get-account)
|
||||
1. [Query the XRP Ledger.](#3-query-the-xrp-ledger)
|
||||
|
||||
|
||||
### 1. Connect to the XRP Ledger
|
||||
|
||||
To make queries and submit transactions, you need to connect to the XRP Ledger. To do this with `xrpl-py`, use the [`xrp.clients` module](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.clients.html):
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/get-acct-info.py" from="# Define the network client" before="# Create a wallet using the testnet faucet:" language="py" /%}
|
||||
|
||||
#### Connect to the production XRP Ledger
|
||||
|
||||
The sample code in the previous section shows you how to connect to the Testnet, which is a [parallel network](../../concepts/networks-and-servers/parallel-networks.md) for testing where the money has no real value. When you're ready to integrate with the production XRP Ledger, you'll need to connect to the Mainnet. You can do that in two ways:
|
||||
|
||||
* By [installing the core server](../../infrastructure/installation/index.md) (`rippled`) and running a node yourself. The core server connects to the Mainnet by default, but you can [change the configuration to use Testnet or Devnet](../../infrastructure/configuration/connect-your-rippled-to-the-xrp-test-net.md). [There are good reasons to run your own core server](../../concepts/networks-and-servers/index.md#reasons-to-run-your-own-server). If you run your own server, you can connect to it like so:
|
||||
|
||||
```
|
||||
from xrpl.clients import JsonRpcClient
|
||||
JSON_RPC_URL = "http://localhost:5005/"
|
||||
client = JsonRpcClient(JSON_RPC_URL)
|
||||
```
|
||||
|
||||
See the example [core server config file](https://github.com/XRPLF/rippled/blob/c0a0b79d2d483b318ce1d82e526bd53df83a4a2c/cfg/rippled-example.cfg#L1562) for more information about default values.
|
||||
|
||||
* By using one of the available [public servers][]:
|
||||
|
||||
```
|
||||
from xrpl.clients import JsonRpcClient
|
||||
JSON_RPC_URL = "https://s2.ripple.com:51234/"
|
||||
client = JsonRpcClient(JSON_RPC_URL)
|
||||
```
|
||||
|
||||
|
||||
### 2. Get account
|
||||
|
||||
To store value and execute transactions on the XRP Ledger, you need an account: a [set of keys](../../concepts/accounts/cryptographic-keys.md#key-components) and an [address](../../concepts/accounts/addresses.md) that's been [funded with enough XRP](../../concepts/accounts/accounts.md#creating-accounts) to meet the [account reserve](../../concepts/accounts/reserves.md). The address is the identifier of your account and you use the [private key](../../concepts/accounts/cryptographic-keys.md#private-key) to sign transactions that you submit to the XRP Ledger.
|
||||
|
||||
For testing and development purposes, you can use the [XRP Faucets](/resources/dev-tools/xrp-faucets) to generate keys and fund the account on the Testnet or Devnet. For production purposes, you should take care to store your keys and set up a [secure signing method](../../concepts/transactions/secure-signing.md). Another difference in production is that XRP has real worth, so you can't get it for free from a faucet.
|
||||
|
||||
To create and fund an account on the Testnet, `xrpl-py` provides the [`generate_faucet_wallet`](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.wallet.html#xrpl.wallet.generate_faucet_wallet) method:
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/get-acct-info.py" from="# Create a wallet using the testnet faucet:" before="# Create an account str from the wallet" language="py" /%}
|
||||
|
||||
|
||||
This method returns a [`Wallet` instance](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.wallet.html#xrpl.wallet.Wallet):
|
||||
|
||||
|
||||
```py
|
||||
print(test_wallet)
|
||||
|
||||
# print output
|
||||
|
||||
public_key:: 022FA613294CD13FFEA759D0185007DBE763331910509EF8F1635B4F84FA08AEE3
|
||||
private_key:: -HIDDEN-
|
||||
classic_address: raaFKKmgf6CRZttTVABeTcsqzRQ51bNR6Q
|
||||
```
|
||||
|
||||
#### Using the account
|
||||
|
||||
In this tutorial we only query details about the generated account from the XRP Ledger, but you can use the values in the `Wallet` instance to prepare, sign, and submit transactions with `xrpl-py`.
|
||||
|
||||
##### Prepare
|
||||
|
||||
To prepare the transaction:
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/prepare-payment.py" from="# Prepare payment" before="# print prepared payment" language="py" /%}
|
||||
|
||||
|
||||
|
||||
##### Sign and submit
|
||||
|
||||
To sign and submit the transaction:
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/prepare-payment.py" from="# Sign and submit the transaction" before="# Print tx response" language="py" /%}
|
||||
|
||||
|
||||
##### Derive an X-address
|
||||
|
||||
You can use `xrpl-py`'s [`xrpl.core.addresscodec`](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.core.addresscodec.html) module to derive an [X-address](https://xrpaddress.info/) from the `Wallet.address` field:
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/get-acct-info.py" from="# Derive an x-address from the classic address:" before="# Look up info about your account" language="py" /%}
|
||||
|
||||
The X-address format [packs the address and destination tag](https://github.com/XRPLF/XRPL-Standards/issues/6) into a more user-friendly value.
|
||||
|
||||
|
||||
### 3. Query the XRP Ledger
|
||||
|
||||
You can query the XRP Ledger to get information about [a specific account](../../references/http-websocket-apis/public-api-methods/account-methods/index.md), [a specific transaction](../../references/http-websocket-apis/public-api-methods/transaction-methods/tx.md), the state of a [current or a historical ledger](../../references/http-websocket-apis/public-api-methods/ledger-methods/index.md), and [the XRP Ledger's decentralized exchange](../../references/http-websocket-apis/public-api-methods/path-and-order-book-methods/index.md). You need to make these queries, among other reasons, to look up account info to follow best practices for [reliable transaction submission](../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
Here, we use `xrpl-py`'s [`xrpl.account`](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.account.html) module to look up information about the [account we got](#2-get-account) in the previous step.
|
||||
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/get-acct-info.py" from="# Look up info about your account" language="py" /%}
|
||||
|
||||
|
||||
|
||||
### 4. Putting it all together
|
||||
|
||||
Using these building blocks, we can create a Python app that:
|
||||
|
||||
1. Gets an account on the Testnet.
|
||||
2. Connects to the XRP Ledger.
|
||||
3. Looks up and prints information about the account you created.
|
||||
|
||||
|
||||
{% code-snippet file="/_code-samples/get-started/py/get-acct-info.py" language="python" /%}
|
||||
|
||||
To run the app, you can copy and paste the code into an editor or IDE and run it from there. Or you could download the file from the [XRP Ledger Dev Portal repo](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/get-started/py) and run it locally:
|
||||
|
||||
|
||||
```sh
|
||||
git clone git@github.com:XRPLF/xrpl-dev-portal.git
|
||||
cd xrpl-dev-portal/_code-samples/get-started/py/get-acct-info.py
|
||||
python3 get-acct-info.py
|
||||
```
|
||||
|
||||
You should see output similar to this example:
|
||||
|
||||
```sh
|
||||
Classic address:
|
||||
|
||||
rnQLnSEA1YFMABnCMrkMWFKxnqW6sQ8EWk
|
||||
X-address:
|
||||
|
||||
T7dRN2ktZGYSTyEPWa9SyDevrwS5yDca4m7xfXTGM3bqff8
|
||||
response.status: ResponseStatus.SUCCESS
|
||||
{
|
||||
"account_data": {
|
||||
"Account": "rnQLnSEA1YFMABnCMrkMWFKxnqW6sQ8EWk",
|
||||
"Balance": "1000000000",
|
||||
"Flags": 0,
|
||||
"LedgerEntryType": "AccountRoot",
|
||||
"OwnerCount": 0,
|
||||
"PreviousTxnID": "5A5203AFF41503539D11ADC41BE4185761C5B78B7ED382E6D001ADE83A59B8DC",
|
||||
"PreviousTxnLgrSeq": 16126889,
|
||||
"Sequence": 16126889,
|
||||
"index": "CAD0F7EF3AB91DA7A952E09D4AF62C943FC1EEE41BE926D632DDB34CAA2E0E8F"
|
||||
},
|
||||
"ledger_current_index": 16126890,
|
||||
"queue_data": {
|
||||
"txn_count": 0
|
||||
},
|
||||
"validated": false
|
||||
}
|
||||
```
|
||||
|
||||
#### Interpreting the response
|
||||
|
||||
The response fields that you want to inspect in most cases are:
|
||||
|
||||
* `account_data.Sequence` — This is the sequence number of the next valid transaction for the account. You need to specify the sequence number when you prepare transactions. With `xrpl-py`, you can use the [`get_next_valid_seq_number`](https://xrpl-py.readthedocs.io/en/latest/source/xrpl.account.html#xrpl.account.get_next_valid_seq_number) to get this automatically from the XRP Ledger. See an example of this usage in the project [README](https://github.com/XRPLF/xrpl-py#serialize-and-sign-transactions).
|
||||
|
||||
* `account_data.Balance` — This is the account's balance of [XRP, in drops][]. You can use this to confirm that you have enough XRP to send (if you're making a payment) and to meet the [current transaction cost](../../concepts/transactions/transaction-cost.md#current-transaction-cost) for a given transaction.
|
||||
|
||||
* `validated` — Indicates whether the returned data is from a [validated ledger](../../concepts/ledgers/open-closed-validated-ledgers.md). When inspecting transactions, it's important to confirm that [the results are final](../../concepts/transactions/finality-of-results/index.md) before further processing the transaction. If `validated` is `true` then you know for sure the results won't change. For more information about best practices for transaction processing, see [Reliable Transaction Submission](../../concepts/transactions/reliable-transaction-submission.md).
|
||||
|
||||
For a detailed description of every response field, see [account_info](../../references/http-websocket-apis/public-api-methods/account-methods/account_info.md#response-format).
|
||||
|
||||
|
||||
## Keep on building
|
||||
|
||||
Now that you know how to use `xrpl-py` to connect to the XRP Ledger, get an account, and look up information about it, you can also use `xrpl-py` to:
|
||||
|
||||
* [Send XRP](../tasks/send-xrp.md).
|
||||
* [Set up secure signing](../../concepts/transactions/secure-signing.md) for your account.
|
||||
|
||||
{% raw-partial file="/docs/_snippets/common-links.md" /%}
|
||||
@@ -0,0 +1,896 @@
|
||||
---
|
||||
html: py-authorize-minter.html
|
||||
parent: nfts-using-python.html
|
||||
seo:
|
||||
description: Authorize another account to mint NFTs for you.
|
||||
labels:
|
||||
- Accounts
|
||||
- Quickstart
|
||||
- XRP
|
||||
- NFTs, NFTokens
|
||||
---
|
||||
|
||||
# Assign an Authorized Minter Using Python
|
||||
|
||||
You can assign another account permission to mint NFTs for you.
|
||||
|
||||
This example shows how to:
|
||||
|
||||
1. Authorize an account to create NFTs for your account.
|
||||
2. Mint an NFT for another account, when authorized.
|
||||
|
||||
[](/docs/img/quickstart-py30.png)
|
||||
|
||||
# Usage
|
||||
|
||||
You can download the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> archive to try the sample in your own browser.
|
||||
|
||||
## Get Accounts
|
||||
|
||||
1. Open and run `py-authorize-minter.md`.
|
||||
2. Get accounts.
|
||||
1. If you have existing test account seeds:
|
||||
1. Paste a seed in the **Standby Seed** field.
|
||||
2. Click **Get Standby Account**.
|
||||
3. Click **Get Standby Account Info**.
|
||||
4. Paste a seed in the **Operational Seed** field.
|
||||
5. Click **Get Operational Account**.
|
||||
6. Click **Get Operational Account** info.
|
||||
2. If you do not have existing test accounts:
|
||||
1. Click **Get Standby Account**.
|
||||
2. Click **Get Standby Account Info**.
|
||||
3. Click **Get Operational Account**.
|
||||
4. Click **Get Operational Account Info**.
|
||||
|
||||
## Authorize an Account to Create NFTs
|
||||
|
||||
<div align="center">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/miVhXbph2ls?si=U1mRxlzul3k30R6O" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
To authorize another account to create NFTs for your account (for example, allow the operational account to mint NFTs for the standby account):
|
||||
|
||||
1. Copy the **Operational Account** value.
|
||||
2. Paste the **Operational Account** value in the standby **Authorized Minter** field.
|
||||
3. Click **Set Minter**.
|
||||
|
||||
[](/docs/img/quickstart-py31.png)
|
||||
|
||||
## Mint an NFT for Another Account
|
||||
|
||||
This example uses the Operational account, which was authorized in the previous step, to mint a token on behalf of the Standby account.
|
||||
|
||||
To mint a non-fungible token for another account:
|
||||
|
||||
1. Set the **Flags** field. For testing purposes, we recommend setting the value to _8_.
|
||||
2. Enter the **NFT URI**. This is a URI that points to the data or metadata associated with the NFT. You can use the sample URI provided if you do not have one of your own.
|
||||
3. Enter the **Transfer Fee**, a percentage of the proceeds that the original creator receives from future sales of the NFT. This is a value of 0-50000 inclusive, allowing transfer rates between 0.000% and 50.000% in increments of 0.001%. If you do not set the **Flags** field to allow the NFT to be transferrable, set this field to 0.
|
||||
4. Enter a **Taxon** for the NFT. If you don't have a use for the field, set it to _0_.
|
||||
4. Copy the **Standby Account** value.
|
||||
5. Paste the **Standby Account** value in the Operational account **Issuer** field.
|
||||
6. Click the Operational account **Mint Other** button.
|
||||
|
||||
[](/docs/img/quickstart-py32.png)
|
||||
|
||||
Once the item is minted, the authorized minter can sell the NFT normally. The proceeds go to the authorized minter, less the transfer fee. The minter and the issuer can settle up on a division of the price separately.
|
||||
|
||||
## Create a Sell Offer
|
||||
|
||||
To create a NFT sell offer:
|
||||
|
||||
1. On the Operational account side, enter the **Amount** of the sell offer in drops (millionths of an XRP), for example 100000000 (100 XRP).
|
||||
2. Set the **Flags** field to _1_.
|
||||
3. Enter the **NFT ID** of the minted NFT you want to sell.
|
||||
4. Optionally, enter a number of seconds until **Expiration**.
|
||||
5. Click **Create Sell Offer**.
|
||||
|
||||
The important piece of information in the response is the NFT Offer Index, labeled as `nft_offer_index`, which is used to accept the sell offer.
|
||||
|
||||
[](/docs/img/quickstart-py33.png)
|
||||
|
||||
## Accept Sell Offer
|
||||
|
||||
Once a sell offer is available, you can create a new account to accept the offer and buy the NFT.
|
||||
|
||||
To accept an available sell offer:
|
||||
|
||||
1. Clear the **Standby Seed** field.
|
||||
2. Click **Get Standby Account**.
|
||||
3. Click **Get Standby Account Info**.
|
||||
4. Enter the **NFT Offer Index** (labeled as `nft_offer_index` in the NFT offer results. This is different from the `nft_id`).
|
||||
5. Click **Accept Sell Offer**.
|
||||
|
||||
[](/docs/img/quickstart-py34.png)
|
||||
|
||||
The Buyer account was debited the 100 XRP price plus 10 drops as the transaction cost. The Seller (Authorized Minter) account is credited 90 XRP. the Issuer and the Seller can divide the proceeds per their agreement in a separate transaction. The original Standby account receives a transfer fee of 10 XRP.
|
||||
|
||||
[](/docs/img/quickstart-py35.png)
|
||||
|
||||
# Code Walkthrough
|
||||
|
||||
You can download the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> archive to try each of the samples.
|
||||
|
||||
## mod6.py
|
||||
|
||||
`mod6.py` contains the new business logic for this example, the `set_minter` and `mint_other` methods.
|
||||
|
||||
Import dependencies and define the `testnet_url` global variable.
|
||||
|
||||
```python
|
||||
import xrpl
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.wallet import Wallet
|
||||
testnet_url="https://s.altnet.rippletest.net:51234"
|
||||
```
|
||||
|
||||
### Set Minter
|
||||
|
||||
This function sets the authorized minter for an account. Each account can have 0 or 1 authorized minter that can mint NFTs in its stead.
|
||||
|
||||
Get the wallet of the account granting permission and instantiate a client.
|
||||
|
||||
```python
|
||||
def set_minter(seed, minter):
|
||||
"""set_minter"""
|
||||
granter_wallet=Wallet.from_seed(seed)
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Define the AccountSet transaction that grants permission to another account to mint tokens on behalf of the granter account.
|
||||
|
||||
```python
|
||||
set_minter_tx=xrpl.models.transactions.AccountSet(
|
||||
account=granter_wallet.address,
|
||||
nftoken_minter=minter,
|
||||
set_flag=xrpl.models.transactions.AccountSetAsfFlag.ASF_AUTHORIZED_NFTOKEN_MINTER
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and return the results.
|
||||
|
||||
```python
|
||||
reply=""
|
||||
try:
|
||||
response=xrpl.transaction.submit_and_wait(set_minter_tx,client,
|
||||
granter_wallet)
|
||||
reply=response.result
|
||||
except xrpl.transaction.XRPLReliableSubmissionException as e:
|
||||
reply=f"Submit failed: {e}"
|
||||
return reply
|
||||
```
|
||||
|
||||
### mint_other
|
||||
|
||||
Get the minter wallet and instantiate a client connection to the XRP ledger.
|
||||
|
||||
``` python
|
||||
def mint_other(seed, uri, flags, transfer_fee, taxon, issuer):
|
||||
"""mint_other"""
|
||||
minter_wallet=Wallet.from_seed(seed)
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Define the `NFTokenMint` transaction. The new parameter in this method is the _issuer_ field, identifying the account on whose behalf the token is being minted.
|
||||
|
||||
```python
|
||||
mint_other_tx=xrpl.models.transactions.NFTokenMint(
|
||||
account=minter_wallet.address,
|
||||
uri=xrpl.utils.str_to_hex(uri),
|
||||
flags=int(flags),
|
||||
transfer_fee=int(transfer_fee),
|
||||
nftoken_taxon=int(taxon),
|
||||
issuer=issuer
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and report the results.
|
||||
|
||||
```python
|
||||
reply=""
|
||||
try:
|
||||
response=xrpl.transaction.submit_and_wait(mint_other_tx,client,
|
||||
minter_wallet)
|
||||
reply=response.result
|
||||
except xrpl.transaction.XRPLReliableSubmissionException as e:
|
||||
reply=f"Submit failed: {e}"
|
||||
return reply
|
||||
```
|
||||
|
||||
## lesson6-auth-minter.py
|
||||
|
||||
This form is based on lesson4-transfer-tokens.py. Changes are highlighted below.
|
||||
|
||||
```python
|
||||
import tkinter as tk
|
||||
import xrpl
|
||||
import json
|
||||
|
||||
from mod1 import get_account, get_account_info, send_xrp
|
||||
from mod2 import (
|
||||
create_trust_line,
|
||||
send_currency,
|
||||
get_balance,
|
||||
configure_account,
|
||||
)
|
||||
from mod3 import (
|
||||
mint_token,
|
||||
get_tokens,
|
||||
burn_token,
|
||||
)
|
||||
from mod4 import (
|
||||
create_sell_offer,
|
||||
create_buy_offer,
|
||||
get_offers,
|
||||
cancel_offer,
|
||||
accept_sell_offer,
|
||||
accept_buy_offer,
|
||||
)
|
||||
```
|
||||
|
||||
Import dependencies from `mod6.py`.
|
||||
|
||||
```python
|
||||
from mod6 import set_minter, mint_other
|
||||
```
|
||||
|
||||
Add handlers for new Module 6 methods.
|
||||
|
||||
```python
|
||||
#############################################
|
||||
## Handlers #################################
|
||||
#############################################
|
||||
|
||||
# Module 6 Handlers
|
||||
|
||||
def standby_set_minter():
|
||||
results = set_minter(ent_standby_seed.get(),ent_standby_auth_minter.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def standby_mint_other():
|
||||
results = mint_other(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_uri.get(),
|
||||
ent_standby_flags.get(),
|
||||
ent_standby_transfer_fee.get(),
|
||||
ent_standby_taxon.get(),
|
||||
ent_standby_issuer.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_set_minter():
|
||||
results = set_minter(ent_operational_seed.get(),ent_operational_auth_minter.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_mint_other():
|
||||
results = mint_other(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_uri.get(),
|
||||
ent_operational_flags.get(),
|
||||
ent_operational_transfer_fee.get(),
|
||||
ent_operational_taxon.get(),
|
||||
ent_operational_issuer.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
# Module 4 Handlers
|
||||
|
||||
def standby_create_sell_offer():
|
||||
results = create_sell_offer(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_amount.get(),
|
||||
ent_standby_nft_id.get(),
|
||||
ent_standby_expiration.get(),
|
||||
ent_standby_destination.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_accept_sell_offer():
|
||||
results = accept_sell_offer (
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_nft_offer_index.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_create_buy_offer():
|
||||
results = create_buy_offer(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_amount.get(),
|
||||
ent_standby_nft_id.get(),
|
||||
ent_standby_owner.get(),
|
||||
ent_standby_expiration.get(),
|
||||
ent_standby_destination.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_accept_buy_offer():
|
||||
results = accept_buy_offer (
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_nft_offer_index.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_get_offers():
|
||||
results = get_offers(ent_standby_nft_id.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", results)
|
||||
|
||||
|
||||
def standby_cancel_offer():
|
||||
results = cancel_offer(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_nft_offer_index.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def op_create_sell_offer():
|
||||
results = create_sell_offer(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_amount.get(),
|
||||
ent_operational_nft_id.get(),
|
||||
ent_operational_expiration.get(),
|
||||
ent_operational_destination.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def op_accept_sell_offer():
|
||||
results = accept_sell_offer (
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_nft_offer_index.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def op_create_buy_offer():
|
||||
results = create_buy_offer(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_amount.get(),
|
||||
ent_operational_nft_id.get(),
|
||||
ent_operational_owner.get(),
|
||||
ent_operational_expiration.get(),
|
||||
ent_operational_destination.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def op_accept_buy_offer():
|
||||
results = accept_buy_offer (
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_nft_offer_index.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def op_get_offers():
|
||||
results = get_offers(ent_operational_nft_id.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", results)
|
||||
|
||||
|
||||
def op_cancel_offer():
|
||||
results = cancel_offer(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_nft_offer_index.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
|
||||
# Module 3 Handlers
|
||||
|
||||
def standby_mint_token():
|
||||
results = mint_token(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_uri.get(),
|
||||
ent_standby_flags.get(),
|
||||
ent_standby_transfer_fee.get(),
|
||||
ent_standby_taxon.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_get_tokens():
|
||||
results = get_tokens(ent_standby_account.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_burn_token():
|
||||
results = burn_token(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_nft_id.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_mint_token():
|
||||
results = mint_token(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_uri.get(),
|
||||
ent_operational_flags.get(),
|
||||
ent_operational_transfer_fee.get(),
|
||||
ent_operational_taxon.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_get_tokens():
|
||||
results = get_tokens(ent_operational_account.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_burn_token():
|
||||
results = burn_token(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_nft_id.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
# Module 2 Handlers
|
||||
|
||||
def standby_create_trust_line():
|
||||
results = create_trust_line(ent_standby_seed.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_currency.get(),
|
||||
ent_standby_amount.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_send_currency():
|
||||
results = send_currency(ent_standby_seed.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_currency.get(),
|
||||
ent_standby_amount.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_configure_account():
|
||||
results = configure_account(
|
||||
ent_standby_seed.get(),
|
||||
standbyRippling)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_create_trust_line():
|
||||
results = create_trust_line(ent_operational_seed.get(),
|
||||
ent_operational_destination.get(),
|
||||
ent_operational_currency.get(),
|
||||
ent_operational_amount.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_send_currency():
|
||||
results = send_currency(ent_operational_seed.get(),
|
||||
ent_operational_destination.get(),
|
||||
ent_operational_currency.get(),
|
||||
ent_operational_amount.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_configure_account():
|
||||
results = configure_account(
|
||||
ent_operational_seed.get(),
|
||||
operationalRippling)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def get_balances():
|
||||
results = get_balance(ent_operational_account.get(), ent_standby_account.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
results = get_balance(ent_standby_account.get(), ent_operational_account.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
# Module 1 Handlers
|
||||
def get_standby_account():
|
||||
new_wallet = get_account(ent_standby_seed.get())
|
||||
ent_standby_account.delete(0, tk.END)
|
||||
ent_standby_seed.delete(0, tk.END)
|
||||
ent_standby_account.insert(0, new_wallet.classic_address)
|
||||
ent_standby_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_standby_account_info():
|
||||
accountInfo = get_account_info(ent_standby_account.get())
|
||||
ent_standby_balance.delete(0, tk.END)
|
||||
ent_standby_balance.insert(0,accountInfo['Balance'])
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def standby_send_xrp():
|
||||
response = send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
|
||||
ent_standby_destination.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
def get_operational_account():
|
||||
new_wallet = get_account(ent_operational_seed.get())
|
||||
ent_operational_account.delete(0, tk.END)
|
||||
ent_operational_account.insert(0, new_wallet.classic_address)
|
||||
ent_operational_seed.delete(0, tk.END)
|
||||
ent_operational_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_operational_account_info():
|
||||
accountInfo = get_account_info(ent_operational_account.get())
|
||||
ent_operational_balance.delete(0, tk.END)
|
||||
ent_operational_balance.insert(0,accountInfo['Balance'])
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def operational_send_xrp():
|
||||
response = send_xrp(ent_operational_seed.get(),ent_operational_amount.get(), ent_operational_destination.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
```
|
||||
|
||||
Rename the window for the Authorized Minter examples.
|
||||
|
||||
```python
|
||||
# Create a new window with the title "Quickstart - Authorized Minter"
|
||||
window = tk.Tk()
|
||||
window.title("Quickstart - Authorized Minter")
|
||||
|
||||
myscrollbar=tk.Scrollbar(window,orient="vertical")
|
||||
myscrollbar.pack(side="right",fill="y")
|
||||
|
||||
standbyRippling = tk.BooleanVar()
|
||||
operationalRippling = tk.BooleanVar()
|
||||
|
||||
# Form frame
|
||||
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
|
||||
frm_form.pack()
|
||||
|
||||
# Create the Label and Entry widgets for "Standby Account"
|
||||
lbl_standy_seed = tk.Label(master=frm_form, text="Standby Seed")
|
||||
ent_standby_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_account = tk.Label(master=frm_form, text="Standby Account")
|
||||
ent_standby_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standy_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_standby_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_standby_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_standby_balance = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_currency = tk.Label(master=frm_form, text="Currency")
|
||||
ent_standby_currency = tk.Entry(master=frm_form, width=50)
|
||||
cb_standby_allow_rippling = tk.Checkbutton(master=frm_form, text="Allow Rippling", variable=standbyRippling, onvalue=True, offvalue=False)
|
||||
lbl_standby_uri = tk.Label(master=frm_form, text="NFT URI")
|
||||
ent_standby_uri = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_flags = tk.Label(master=frm_form, text="Flags")
|
||||
ent_standby_flags = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_transfer_fee = tk.Label(master=frm_form, text="Transfer Fee")
|
||||
ent_standby_transfer_fee = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_taxon = tk.Label(master=frm_form, text="Taxon")
|
||||
ent_standby_taxon = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_nft_id = tk.Label(master=frm_form, text="NFT ID")
|
||||
ent_standby_nft_id = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_nft_offer_index = tk.Label(master=frm_form, text="NFT Offer Index")
|
||||
ent_standby_nft_offer_index = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_owner = tk.Label(master=frm_form, text="Owner")
|
||||
ent_standby_owner = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_expiration = tk.Label(master=frm_form, text="Expiration")
|
||||
ent_standby_expiration = tk.Entry(master=frm_form, width="50")
|
||||
```
|
||||
|
||||
Add fields for the **Authorized Minter** and **Issuer**.
|
||||
|
||||
```python
|
||||
lbl_standby_auth_minter = tk.Label(master=frm_form, text="Authorized Minter")
|
||||
ent_standby_auth_minter = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_issuer = tk.Label(master=frm_form, text="Issuer")
|
||||
ent_standby_issuer = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_results = tk.Label(master=frm_form,text='Results')
|
||||
text_standby_results = tk.Text(master=frm_form, height = 50, width = 65)
|
||||
|
||||
|
||||
# Place field in a grid.
|
||||
lbl_standy_seed.grid(row=0, column=0, sticky="w")
|
||||
ent_standby_seed.grid(row=0, column=1)
|
||||
lbl_standby_account.grid(row=2, column=0, sticky="e")
|
||||
ent_standby_account.grid(row=2, column=1)
|
||||
lbl_standy_amount.grid(row=3, column=0, sticky="e")
|
||||
ent_standby_amount.grid(row=3, column=1)
|
||||
lbl_standby_destination.grid(row=4, column=0, sticky="e")
|
||||
ent_standby_destination.grid(row=4, column=1)
|
||||
lbl_standby_balance.grid(row=5, column=0, sticky="e")
|
||||
ent_standby_balance.grid(row=5, column=1)
|
||||
lbl_standby_currency.grid(row=6, column=0, sticky="e")
|
||||
ent_standby_currency.grid(row=6, column=1)
|
||||
cb_standby_allow_rippling.grid(row=7,column=1, sticky="w")
|
||||
lbl_standby_uri.grid(row=8, column=0, sticky="e")
|
||||
ent_standby_uri.grid(row=8, column=1, sticky="w")
|
||||
lbl_standby_flags.grid(row=9, column=0, sticky="e")
|
||||
ent_standby_flags.grid(row=9, column=1, sticky="w")
|
||||
lbl_standby_transfer_fee.grid(row=10, column=0, sticky="e")
|
||||
ent_standby_transfer_fee.grid(row=10, column=1, sticky="w")
|
||||
lbl_standby_taxon.grid(row=11, column=0, sticky="e")
|
||||
ent_standby_taxon.grid(row=11, column=1, sticky="w")
|
||||
lbl_standby_nft_id.grid(row=12, column=0, sticky="e")
|
||||
ent_standby_nft_id.grid(row=12, column=1, sticky="w")
|
||||
lbl_standby_nft_offer_index.grid(row=13, column=0, sticky="ne")
|
||||
ent_standby_nft_offer_index.grid(row=13, column=1, sticky="w")
|
||||
lbl_standby_owner.grid(row=14, column=0, sticky="ne")
|
||||
ent_standby_owner.grid(row=14, column=1, sticky="w")
|
||||
lbl_standby_expiration.grid(row=15, column=0, sticky="ne")
|
||||
ent_standby_expiration.grid(row=15, column=1, sticky="w")
|
||||
```
|
||||
|
||||
Add the new fields to the form grid.
|
||||
|
||||
```python
|
||||
lbl_standby_auth_minter.grid(row=16,column=0, sticky="ne")
|
||||
ent_standby_auth_minter.grid(row=16,column=1, sticky="w")
|
||||
lbl_standby_issuer.grid(row=17,column=0, sticky="ne")
|
||||
ent_standby_issuer.grid(row=17,column=1, sticky="w")
|
||||
lbl_standby_results.grid(row=18, column=0, sticky="ne")
|
||||
text_standby_results.grid(row=18, column=1, sticky="nw")
|
||||
|
||||
cb_standby_allow_rippling.select()
|
||||
|
||||
###############################################
|
||||
## Operational Account ########################
|
||||
###############################################
|
||||
|
||||
# Create the Label and Entry widgets for "Operational Account"
|
||||
lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed")
|
||||
ent_operational_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_account = tk.Label(master=frm_form, text="Operational Account")
|
||||
ent_operational_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_operational_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_operational_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_operational_balance = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_currency = tk.Label(master=frm_form, text="Currency")
|
||||
ent_operational_currency = tk.Entry(master=frm_form, width=50)
|
||||
cb_operational_allow_rippling = tk.Checkbutton(master=frm_form, text="Allow Rippling", variable=operationalRippling, onvalue=True, offvalue=False)
|
||||
lbl_operational_uri = tk.Label(master=frm_form, text="NFT URI")
|
||||
ent_operational_uri = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_flags = tk.Label(master=frm_form, text="Flags")
|
||||
ent_operational_flags = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_transfer_fee = tk.Label(master=frm_form, text="Transfer Fee")
|
||||
ent_operational_transfer_fee = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_taxon = tk.Label(master=frm_form, text="Taxon")
|
||||
ent_operational_taxon = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_nft_id = tk.Label(master=frm_form, text="NFT ID")
|
||||
ent_operational_nft_id = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_nft_offer_index = tk.Label(master=frm_form, text="NFT Offer Index")
|
||||
ent_operational_nft_offer_index = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_owner = tk.Label(master=frm_form, text="Owner")
|
||||
ent_operational_owner = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_expiration = tk.Label(master=frm_form, text="Expiration")
|
||||
ent_operational_expiration = tk.Entry(master=frm_form, width="50")
|
||||
```
|
||||
|
||||
Add *Authorized Minter* and *Issuer* fields to the Operational side of the form.
|
||||
|
||||
```python
|
||||
lbl_operational_auth_minter = tk.Label(master=frm_form, text="Authorized Minter")
|
||||
ent_operational_auth_minter = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_issuer = tk.Label(master=frm_form, text="Issuer")
|
||||
ent_operational_issuer = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_results = tk.Label(master=frm_form,text="Results")
|
||||
text_operational_results = tk.Text(master=frm_form, height = 50, width = 65)
|
||||
|
||||
#Place the widgets in a grid
|
||||
lbl_operational_seed.grid(row=0, column=4, sticky="e")
|
||||
ent_operational_seed.grid(row=0, column=5, sticky="w")
|
||||
lbl_operational_account.grid(row=2,column=4, sticky="e")
|
||||
ent_operational_account.grid(row=2,column=5, sticky="w")
|
||||
lbl_operational_amount.grid(row=3, column=4, sticky="e")
|
||||
ent_operational_amount.grid(row=3, column=5, sticky="w")
|
||||
lbl_operational_destination.grid(row=4, column=4, sticky="e")
|
||||
ent_operational_destination.grid(row=4, column=5, sticky="w")
|
||||
lbl_operational_balance.grid(row=5, column=4, sticky="e")
|
||||
ent_operational_balance.grid(row=5, column=5, sticky="w")
|
||||
lbl_operational_currency.grid(row=6, column=4, sticky="e")
|
||||
ent_operational_currency.grid(row=6, column=5)
|
||||
cb_operational_allow_rippling.grid(row=7,column=5, sticky="w")
|
||||
lbl_operational_uri.grid(row=8, column=4, sticky="e")
|
||||
ent_operational_uri.grid(row=8, column=5, sticky="w")
|
||||
lbl_operational_flags.grid(row=9, column=4, sticky="e")
|
||||
ent_operational_flags.grid(row=9, column=5, sticky="w")
|
||||
lbl_operational_transfer_fee.grid(row=10, column=4, sticky="e")
|
||||
ent_operational_transfer_fee.grid(row=10, column=5, sticky="w")
|
||||
lbl_operational_taxon.grid(row=11, column=4, sticky="e")
|
||||
ent_operational_taxon.grid(row=11, column=5, sticky="w")
|
||||
lbl_operational_nft_id.grid(row=12, column=4, sticky="e")
|
||||
ent_operational_nft_id.grid(row=12, column=5, sticky="w")
|
||||
lbl_operational_nft_offer_index.grid(row=13, column=4, sticky="ne")
|
||||
ent_operational_nft_offer_index.grid(row=13, column=5, sticky="w")
|
||||
lbl_operational_owner.grid(row=14, column=4, sticky="ne")
|
||||
ent_operational_owner.grid(row=14, column=5, sticky="w")
|
||||
lbl_operational_expiration.grid(row=15, column=4, sticky="ne")
|
||||
ent_operational_expiration.grid(row=15, column=5, sticky="w")
|
||||
```
|
||||
|
||||
Add *Authorized Minter* and *Issuer* fields to the Operational grid.
|
||||
|
||||
```python
|
||||
lbl_operational_auth_minter.grid(row=16, column=4, sticky="ne")
|
||||
ent_operational_auth_minter.grid(row=16, column=5, sticky="w")
|
||||
lbl_operational_issuer.grid(row=17, column=4, sticky="ne")
|
||||
ent_operational_issuer.grid(row=17, column=5, sticky="w")
|
||||
lbl_operational_results.grid(row=18, column=4, sticky="ne")
|
||||
text_operational_results.grid(row=18, column=5, sticky="nw")
|
||||
|
||||
cb_operational_allow_rippling.select()
|
||||
|
||||
#############################################
|
||||
## Buttons ##################################
|
||||
#############################################
|
||||
|
||||
# Create the Standby Account Buttons
|
||||
btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account",
|
||||
command = get_standby_account)
|
||||
btn_get_standby_account.grid(row=0, column=2, sticky = "nsew")
|
||||
btn_get_standby_account_info = tk.Button(master=frm_form,
|
||||
text="Get Standby Account Info",
|
||||
command = get_standby_account_info)
|
||||
btn_get_standby_account_info.grid(row=1, column=2, sticky = "nsew")
|
||||
btn_standby_send_xrp = tk.Button(master=frm_form, text="Send XRP >",
|
||||
command = standby_send_xrp)
|
||||
btn_standby_send_xrp.grid(row=2, column = 2, sticky = "nsew")
|
||||
btn_standby_create_trust_line = tk.Button(master=frm_form,
|
||||
text="Create Trust Line",
|
||||
command = standby_create_trust_line)
|
||||
btn_standby_create_trust_line.grid(row=4, column=2, sticky = "nsew")
|
||||
btn_standby_send_currency = tk.Button(master=frm_form, text="Send Currency >",
|
||||
command = standby_send_currency)
|
||||
btn_standby_send_currency.grid(row=5, column=2, sticky = "nsew")
|
||||
btn_standby_send_currency = tk.Button(master=frm_form, text="Get Balances",
|
||||
command = get_balances)
|
||||
btn_standby_send_currency.grid(row=6, column=2, sticky = "nsew")
|
||||
btn_standby_configure_account = tk.Button(master=frm_form,
|
||||
text="Configure Account",
|
||||
command = standby_configure_account)
|
||||
btn_standby_configure_account.grid(row=7,column=0, sticky = "nsew")
|
||||
btn_standby_mint_token = tk.Button(master=frm_form, text="Mint NFT",
|
||||
command = standby_mint_token)
|
||||
btn_standby_mint_token.grid(row=8, column=2, sticky="nsew")
|
||||
btn_standby_get_tokens = tk.Button(master=frm_form, text="Get NFTs",
|
||||
command = standby_get_tokens)
|
||||
btn_standby_get_tokens.grid(row=9, column=2, sticky="nsew")
|
||||
btn_standby_burn_token = tk.Button(master=frm_form, text="Burn NFT",
|
||||
command = standby_burn_token)
|
||||
btn_standby_burn_token.grid(row=10, column=2, sticky="nsew")
|
||||
btn_standby_create_sell_offer = tk.Button(master=frm_form, text="Create Sell Offer",
|
||||
command = standby_create_sell_offer)
|
||||
btn_standby_create_sell_offer.grid(row=11, column=2, sticky="nsew")
|
||||
btn_standby_accept_sell_offer = tk.Button(master=frm_form, text="Accept Sell Offer",
|
||||
command = standby_accept_sell_offer)
|
||||
btn_standby_accept_sell_offer.grid(row=12, column=2, sticky="nsew")
|
||||
btn_standby_create_buy_offer = tk.Button(master=frm_form, text="Create Buy Offer",
|
||||
command = standby_create_buy_offer)
|
||||
btn_standby_create_buy_offer.grid(row=13, column=2, sticky="nsew")
|
||||
btn_standby_accept_buy_offer = tk.Button(master=frm_form, text="Accept Buy Offer",
|
||||
command = standby_accept_buy_offer)
|
||||
btn_standby_accept_buy_offer.grid(row=14, column=2, sticky="nsew")
|
||||
btn_standby_get_offers = tk.Button(master=frm_form, text="Get Offers",
|
||||
command = standby_get_offers)
|
||||
btn_standby_get_offers.grid(row=15, column=2, sticky="nsew")
|
||||
btn_standby_cancel_offer = tk.Button(master=frm_form, text="Cancel Offer",
|
||||
command = standby_cancel_offer)
|
||||
btn_standby_cancel_offer.grid(row=16, column=2, sticky="nsew")
|
||||
```
|
||||
|
||||
Add buttons for *Set Minter* and *Mint Other* to the Standby side of the form.
|
||||
|
||||
```python
|
||||
btn_standby_set_minter = tk.Button(master=frm_form, text="Set Minter",
|
||||
command = standby_set_minter)
|
||||
btn_standby_set_minter.grid(row=17, column=2, sticky="nsew")
|
||||
btn_standby_mint_other = tk.Button(master=frm_form, text="Mint Other",
|
||||
command = standby_mint_other)
|
||||
btn_standby_mint_other.grid(row=18, column=2, sticky="new")
|
||||
|
||||
|
||||
|
||||
# Create the Operational Account Buttons
|
||||
btn_get_operational_account = tk.Button(master=frm_form,
|
||||
text="Get Operational Account",
|
||||
command = get_operational_account)
|
||||
btn_get_operational_account.grid(row=0, column=3, sticky = "nsew")
|
||||
btn_get_op_account_info = tk.Button(master=frm_form, text="Get Op Account Info",
|
||||
command = get_operational_account_info)
|
||||
btn_get_op_account_info.grid(row=1, column=3, sticky = "nsew")
|
||||
btn_op_send_xrp = tk.Button(master=frm_form, text="< Send XRP",
|
||||
command = operational_send_xrp)
|
||||
btn_op_send_xrp.grid(row=2, column = 3, sticky = "nsew")
|
||||
btn_op_create_trust_line = tk.Button(master=frm_form, text="Create Trust Line",
|
||||
command = operational_create_trust_line)
|
||||
btn_op_create_trust_line.grid(row=4, column=3, sticky = "nsew")
|
||||
btn_op_send_currency = tk.Button(master=frm_form, text="< Send Currency",
|
||||
command = operational_send_currency)
|
||||
btn_op_send_currency.grid(row=5, column=3, sticky = "nsew")
|
||||
btn_op_get_balances = tk.Button(master=frm_form, text="Get Balances",
|
||||
command = get_balances)
|
||||
btn_op_get_balances.grid(row=6, column=3, sticky = "nsew")
|
||||
btn_op_configure_account = tk.Button(master=frm_form, text="Configure Account",
|
||||
command = operational_configure_account)
|
||||
btn_op_configure_account.grid(row=7,column=4, sticky = "nsew")
|
||||
btn_op_mint_token = tk.Button(master=frm_form, text="Mint NFT",
|
||||
command = operational_mint_token)
|
||||
btn_op_mint_token.grid(row=8, column=3, sticky="nsew")
|
||||
btn_op_get_tokens = tk.Button(master=frm_form, text="Get NFTs",
|
||||
command = operational_get_tokens)
|
||||
btn_op_get_tokens.grid(row=9, column=3, sticky="nsew")
|
||||
btn_op_burn_token = tk.Button(master=frm_form, text="Burn NFT",
|
||||
command = operational_burn_token)
|
||||
btn_op_burn_token.grid(row=10, column=3, sticky="nsew")
|
||||
btn_op_create_sell_offer = tk.Button(master=frm_form, text="Create Sell Offer",
|
||||
command = op_create_sell_offer)
|
||||
btn_op_create_sell_offer.grid(row=11, column=3, sticky="nsew")
|
||||
btn_op_accept_sell_offer = tk.Button(master=frm_form, text="Accept Sell Offer",
|
||||
command = op_accept_sell_offer)
|
||||
btn_op_accept_sell_offer.grid(row=12, column=3, sticky="nsew")
|
||||
btn_op_create_buy_offer = tk.Button(master=frm_form, text="Create Buy Offer",
|
||||
command = op_create_buy_offer)
|
||||
btn_op_create_buy_offer.grid(row=13, column=3, sticky="nsew")
|
||||
btn_op_accept_buy_offer = tk.Button(master=frm_form, text="Accept Buy Offer",
|
||||
command = op_accept_buy_offer)
|
||||
btn_op_accept_buy_offer.grid(row=14, column=3, sticky="nsew")
|
||||
btn_op_get_offers = tk.Button(master=frm_form, text="Get Offers",
|
||||
command = op_get_offers)
|
||||
btn_op_get_offers.grid(row=15, column=3, sticky="nsew")
|
||||
btn_op_cancel_offer = tk.Button(master=frm_form, text="Cancel Offer",
|
||||
command = op_cancel_offer)
|
||||
btn_op_cancel_offer.grid(row=16, column=3, sticky="nsew")
|
||||
```
|
||||
|
||||
Add *Set Minter* and *Mint Other* buttons to the Operational side of the form.
|
||||
|
||||
```python
|
||||
btn_op_set_minter = tk.Button(master=frm_form, text="Set Minter",
|
||||
command = operational_set_minter)
|
||||
btn_op_set_minter.grid(row=17, column=3, sticky="nsew")
|
||||
btn_op_mint_other = tk.Button(master=frm_form, text="Mint Other",
|
||||
command = operational_mint_other)
|
||||
btn_op_mint_other.grid(row=18, column=3, sticky="new")
|
||||
|
||||
# Start the application
|
||||
window.mainloop()
|
||||
```
|
||||
390
docs/tutorials/python/modular-tutorials/nfts/batch-mint-nfts.md
Normal file
390
docs/tutorials/python/modular-tutorials/nfts/batch-mint-nfts.md
Normal file
@@ -0,0 +1,390 @@
|
||||
---
|
||||
html: py-batch-minting.html
|
||||
parent: nfts-using-python.html
|
||||
seo:
|
||||
description: Mint multiple NFTs with the press of a button.
|
||||
labels:
|
||||
- Accounts
|
||||
- Quickstart
|
||||
- NFTs
|
||||
- XRP
|
||||
---
|
||||
|
||||
# Batch Mint NFTs
|
||||
|
||||
You can create an application that mints multiple NFTs at one time, using a `for` loop to send one transaction after another.
|
||||
|
||||
A best practice is to use `Tickets` to reserve the transaction sequence numbers. If you create an application that creates NFTs without using tickets, if any transaction fails for any reason, the application stops with an error. If you use tickets, the application continues to send transactions, and you can look into the reason for any individual failures afterward.
|
||||
|
||||
[](/docs/img/quickstart-py36.png)
|
||||
|
||||
## Usage
|
||||
|
||||
You can download or clone the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> to try the sample in your own browser.
|
||||
|
||||
## Get an Account
|
||||
|
||||
1. Open and run `lesson7-batch-minting.py`.
|
||||
2. Get a test account.
|
||||
1. If you want to use an existing account seed:
|
||||
1. Paste the account seed in the **Standby Seed** field.
|
||||
2. Click **Get Standby Account**.
|
||||
2. If you do not want to use an existing account seed, just click **Get Standby Account**.
|
||||
3. Click **Get Standby Account Info** to get the current XRP balance.
|
||||
|
||||
|
||||
## Batch Mint NFTs
|
||||
|
||||
<div align="center">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/NjEqEWcqhwc?si=E8ws75gts_7TtOuU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
This example lets you mint multiple NFTs for a single unique item. The NFT might represent "prints" of an original artwork, tickets to an event, or another limited set of unique items.
|
||||
|
||||
To batch mint non-fungible token objects:
|
||||
|
||||
1. Enter the **NFT URI**. This is a URI that points to the data or metadata associated with the NFT object. You can use this sample URI if you do not have one of your own: ipfs://bafybeigdyrzt5sfp7udm7hu76uh7y26nf4dfuylqabf3oclgtqy55fbzdi.
|
||||
2. Set the **Flags** field. For testing purposes, we recommend setting the value to _8_. This sets the _tsTransferable_ flag, meaning that the NFT object can be transferred to another account. Otherwise, the NFT object can only be transferred back to the issuing account. See [NFTokenMint](../../../../references/protocol/transactions/types/nftokenmint.md) for available NFT minting flags.
|
||||
3. Enter the **Transfer Fee**, a percentage of the proceeds that the original creator receives from future sales of the NFT. This is a value of 0-50000 inclusive, allowing transfer fees between 0.000% and 50.000% in increments of 0.001%. If you do not set the **Flags** field to allow the NFT to be transferrable, set this field to 0.
|
||||
4. Enter the **Taxon** for the NFT. If you do not have a need for the Taxon field, set this value to 0.
|
||||
5. Enter an **NFT Count** of up to 200 NFTs to create in one batch.
|
||||
6. Click **Batch Mint NFTs**.
|
||||
|
||||
## Get Batch NFTs
|
||||
|
||||
Click **Get Batch NFTs** to get the current list of NFTs for your account.
|
||||
|
||||
The difference between this function and the `getTokens()` function used earlier is that it allows for larger lists of tokens, sending multiple requests if the tokens exceed the number of objects allowed in a single request.
|
||||
|
||||
# Code Walkthrough
|
||||
|
||||
You can download or clone the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> to try each of the samples locally.
|
||||
|
||||
Import dependencies and define the testnet_url variable.
|
||||
|
||||
```python
|
||||
import xrpl
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.wallet import Wallet
|
||||
from xrpl.models.requests import AccountNFTs
|
||||
|
||||
testnet_url = "https://s.altnet.rippletest.net:51234"
|
||||
```
|
||||
|
||||
## Batch Mint
|
||||
|
||||
Pass the values `seed`, `uri`, `flags`, `transfer_fee`, `taxon`, and `count`.
|
||||
|
||||
```python
|
||||
def batch_mint(seed, uri, flags, transfer_fee, taxon, count):
|
||||
"""batch_mint"""
|
||||
```
|
||||
|
||||
Get the account wallet and a client instance.
|
||||
|
||||
```python
|
||||
wallet=Wallet.from_seed(seed)
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Request the full account info.
|
||||
|
||||
```python
|
||||
acct_info = xrpl.models.requests.account_info.AccountInfo(
|
||||
account=wallet.classic_address,
|
||||
ledger_index='validated'
|
||||
)
|
||||
get_seq_request = client.request(acct_info)
|
||||
|
||||
```
|
||||
|
||||
Parse the current sequence value.
|
||||
|
||||
```python
|
||||
current_sequence=get_seq_request.result['account_data']['Sequence']
|
||||
```
|
||||
|
||||
Create a transaction to create tickets, starting at the current sequence number.
|
||||
|
||||
```python
|
||||
ticket_tx=xrpl.models.transactions.TicketCreate(
|
||||
account=wallet.address,
|
||||
ticket_count=int(count),
|
||||
sequence=current_sequence
|
||||
)
|
||||
```
|
||||
|
||||
Submit the ticket transaction and wait for the result.
|
||||
|
||||
```python
|
||||
response=xrpl.transaction.submit_and_wait(ticket_tx,client,wallet)
|
||||
```
|
||||
|
||||
Create a request to obtain the next ticket number.
|
||||
|
||||
```python
|
||||
ticket_numbers_req=xrpl.models.requests.AccountObjects(
|
||||
account=wallet.address,
|
||||
type='ticket'
|
||||
)
|
||||
ticket_response=client.request(ticket_numbers_req)
|
||||
```
|
||||
|
||||
Create the `tickets` variable to store an array of tickets.
|
||||
|
||||
```python
|
||||
tickets=[int(0)] * int(count)
|
||||
acct_objs= ticket_response.result['account_objects']
|
||||
```
|
||||
|
||||
Create an array of ticket numbers.
|
||||
|
||||
```python
|
||||
for x in range(int(count)):
|
||||
tickets[x] = acct_objs[x]['TicketSequence']
|
||||
```
|
||||
|
||||
Initialize variables to be used in the mint loop.
|
||||
|
||||
```python
|
||||
reply=""
|
||||
create_count=0
|
||||
```
|
||||
|
||||
Use a `for` loop to send repeated mint requests, using the `ticket_sequence` field to uniquely identify the mint transactions.
|
||||
|
||||
```python
|
||||
for x in range(int(count)):
|
||||
mint_tx=xrpl.models.transactions.NFTokenMint(
|
||||
account=wallet.classic_address,
|
||||
uri=xrpl.utils.str_to_hex(uri),
|
||||
flags=int(flags),
|
||||
transfer_fee=int(transfer_fee),
|
||||
ticket_sequence=tickets[x],
|
||||
sequence=0,
|
||||
nftoken_taxon=int(taxon)
|
||||
)
|
||||
```
|
||||
Submit each transaction and report the results.
|
||||
|
||||
```python
|
||||
try:
|
||||
response=xrpl.transaction.submit_and_wait(mint_tx,client,
|
||||
wallet)
|
||||
create_count+=1
|
||||
except xrpl.transaction.XRPLReliableSubmissionException as e:
|
||||
reply+=f"Submit failed: {e}\n"
|
||||
reply+=str(create_count)+' NFTs generated.'
|
||||
return reply
|
||||
```
|
||||
|
||||
### Get Batch
|
||||
|
||||
This version of `getTokens()` allows for a larger set of NFTs by watching for a `marker` at the end of each batch of NFTs. Subsequent requests get the next batch of NFTs starting at the previous marker, until all NFTs are retrieved.
|
||||
|
||||
```python
|
||||
def get_batch(seed, account):
|
||||
"""get_batch"""
|
||||
```
|
||||
|
||||
Get a client instance. Since this is a request for publicly available information, no wallet is required.
|
||||
|
||||
```python
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Define the request for account NFTs. Set a return limit of 400 objects.
|
||||
|
||||
```python
|
||||
acct_nfts=AccountNFTs(
|
||||
account=account,
|
||||
limit=400
|
||||
)
|
||||
```
|
||||
|
||||
Send the request.
|
||||
|
||||
```python
|
||||
response=client.request(acct_nfts)
|
||||
```
|
||||
|
||||
Capture the result in the _responses_ variable.
|
||||
|
||||
```python
|
||||
responses=response.result
|
||||
```
|
||||
|
||||
While there is a _marker_ value in the response, continue to define and send requests for 400 account NFTs at a time. Capture the _result_ from each request in the _responses_ variable.
|
||||
|
||||
```python
|
||||
while(acct_nfts.marker):
|
||||
acct_nfts=AccountNFTs(
|
||||
account=account,
|
||||
limit=400,
|
||||
marker=acct_nfts.marker
|
||||
)
|
||||
response=client.request(acct_nfts)
|
||||
responses+=response.result
|
||||
```
|
||||
|
||||
Return the _responses_ variable.
|
||||
|
||||
```python
|
||||
return responses
|
||||
```
|
||||
|
||||
## lesson7-batch-minting.py
|
||||
|
||||
This form is based on earlier examples, with unused fields, handlers, and buttons removed. Additions are highlighted below.
|
||||
|
||||
```python
|
||||
import tkinter as tk
|
||||
import xrpl
|
||||
import json
|
||||
|
||||
from mod1 import get_account, get_account_info
|
||||
```
|
||||
|
||||
Import dependencies from `mod7.py`.
|
||||
|
||||
```python
|
||||
from mod7 import batch_mint, get_batch
|
||||
|
||||
```
|
||||
|
||||
Add Module 7 handlers.
|
||||
|
||||
```python
|
||||
|
||||
def batch_mint_nfts():
|
||||
results = batch_mint(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_uri.get(),
|
||||
ent_standby_flags.get(),
|
||||
ent_standby_transfer_fee.get(),
|
||||
ent_standby_taxon.get(),
|
||||
ent_standby_nft_count.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def standby_get_batch_nfts():
|
||||
results = get_batch(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_account.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
# Module 1 Handlers
|
||||
def get_standby_account():
|
||||
new_wallet = get_account(ent_standby_seed.get())
|
||||
ent_standby_account.delete(0, tk.END)
|
||||
ent_standby_seed.delete(0, tk.END)
|
||||
ent_standby_account.insert(0, new_wallet.classic_address)
|
||||
ent_standby_seed.insert(0, new_wallet.seed)
|
||||
|
||||
def get_standby_account_info():
|
||||
accountInfo = get_account_info(ent_standby_account.get())
|
||||
ent_standby_balance.delete(0, tk.END)
|
||||
ent_standby_balance.insert(0,accountInfo['Balance'])
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
```
|
||||
|
||||
Rename the window for Module 7.
|
||||
|
||||
```python
|
||||
# Create a new window with the title "Python Module - Batch Minting"
|
||||
window = tk.Tk()
|
||||
window.title("Python Module - Batch Minting")
|
||||
|
||||
|
||||
|
||||
# Form frame
|
||||
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
|
||||
frm_form.pack()
|
||||
|
||||
# Create the Label and Entry widgets for "Standby Account"
|
||||
lbl_standy_seed = tk.Label(master=frm_form, text="Standby Seed")
|
||||
ent_standby_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_account = tk.Label(master=frm_form, text="Standby Account")
|
||||
ent_standby_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_standby_balance = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_uri = tk.Label(master=frm_form, text="NFT URI")
|
||||
ent_standby_uri = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_flags = tk.Label(master=frm_form, text="Flags")
|
||||
ent_standby_flags = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_transfer_fee = tk.Label(master=frm_form, text="Transfer Fee")
|
||||
ent_standby_transfer_fee = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_taxon = tk.Label(master=frm_form, text="Taxon")
|
||||
ent_standby_taxon = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_nft_id = tk.Label(master=frm_form, text="NFT ID")
|
||||
ent_standby_nft_id = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_nft_offer_index = tk.Label(master=frm_form, text="NFT Offer Index")
|
||||
ent_standby_nft_offer_index = tk.Entry(master=frm_form, width="50")
|
||||
```
|
||||
|
||||
Add the **NFT Count** field for batch minting.
|
||||
|
||||
```python
|
||||
lbl_standby_nft_count=tk.Label(master=frm_form, text="NFT Count")
|
||||
ent_standby_nft_count=tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_results = tk.Label(master=frm_form,text="Results")
|
||||
text_standby_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
# Place field in a grid.
|
||||
lbl_standy_seed.grid(row=0, column=0, sticky="w")
|
||||
ent_standby_seed.grid(row=0, column=1)
|
||||
lbl_standby_account.grid(row=2, column=0, sticky="e")
|
||||
ent_standby_account.grid(row=2, column=1)
|
||||
lbl_standby_balance.grid(row=5, column=0, sticky="e")
|
||||
ent_standby_balance.grid(row=5, column=1)
|
||||
lbl_standby_uri.grid(row=8, column=0, sticky="e")
|
||||
ent_standby_uri.grid(row=8, column=1, sticky="w")
|
||||
lbl_standby_flags.grid(row=9, column=0, sticky="e")
|
||||
ent_standby_flags.grid(row=9, column=1, sticky="w")
|
||||
lbl_standby_transfer_fee.grid(row=10, column=0, sticky="e")
|
||||
ent_standby_transfer_fee.grid(row=10, column=1, sticky="w")
|
||||
lbl_standby_taxon.grid(row=11, column=0, sticky="e")
|
||||
ent_standby_taxon.grid(row=11, column=1, sticky="w")
|
||||
```
|
||||
|
||||
Place the **NFT Count** field in the grid.
|
||||
|
||||
```python
|
||||
lbl_standby_nft_count.grid(row=13, column=0, sticky="e")
|
||||
ent_standby_nft_count.grid(row=13, column=1, sticky="w")
|
||||
lbl_standby_results.grid(row=14, column=0, sticky="ne")
|
||||
text_standby_results.grid(row=14, column=1, sticky="nw")
|
||||
|
||||
#############################################
|
||||
## Buttons ##################################
|
||||
#############################################
|
||||
|
||||
# Create the Standby Account Buttons
|
||||
btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account",
|
||||
command = get_standby_account)
|
||||
btn_get_standby_account.grid(row=0, column=2, sticky = "nsew")
|
||||
btn_get_standby_account_info = tk.Button(master=frm_form,
|
||||
text="Get Standby Account Info",
|
||||
command = get_standby_account_info)
|
||||
btn_get_standby_account_info.grid(row=1, column=2, sticky = "nsew")
|
||||
```
|
||||
|
||||
Add the **Batch Mint NFTs** and **Get Batch NFTs** buttons.
|
||||
|
||||
```python
|
||||
btn_standby_batch_mint = tk.Button(master=frm_form,
|
||||
text="Batch Mint NFTs",
|
||||
command = standby_batch_mint)
|
||||
btn_standby_batch_mint.grid(row=5, column=2, sticky = "nsew")
|
||||
btn_standby_get_batch_nfts = tk.Button(master=frm_form,
|
||||
text="Get Batch NFTs",
|
||||
command = standby_get_batch_nfts)
|
||||
btn_standby_get_batch_nfts.grid(row=8, column=2, sticky = "nsew")
|
||||
# Start the application
|
||||
window.mainloop()
|
||||
|
||||
```
|
||||
@@ -0,0 +1,883 @@
|
||||
---
|
||||
html: py-broker-sale.html
|
||||
parent: nfts-using-python.html
|
||||
seo:
|
||||
description: Broker a sale between a sell offer and a buy offer.
|
||||
labels:
|
||||
- Accounts
|
||||
- Quickstart
|
||||
- Broker
|
||||
- XRP
|
||||
---
|
||||
|
||||
# Broker an NFT Sale Using Python
|
||||
|
||||
Earlier examples showed how to make buy and sell offers directly between two accounts. Another option is to use a third account as a broker for the sale. The broker acts on behalf of the NFT owner. The seller creates an offer with the broker account as its destination. The broker gathers and evaluates buy offers and chooses which one to accept, adding an agreed-upon fee for arranging the sale. When the broker account accepts a sell offer with a buy offer, the funds and ownership of the NFT are transferred simultaneously, completing the deal. This allows an account to act as a marketplace or personal agent for NFT creators and traders.
|
||||
|
||||
# Usage
|
||||
|
||||
This example shows how to:
|
||||
|
||||
1. Create a brokered sell offer.
|
||||
2. Get a list of offers for the brokered item.
|
||||
3. Broker a sale between two different accounts.
|
||||
|
||||
[](/docs/img/quickstart-py23.png)
|
||||
|
||||
You can download the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> archive to try each of the samples in your own browser.
|
||||
|
||||
## Get Accounts
|
||||
|
||||
1. Open and run `broker-nfts.py`.
|
||||
2. Get test accounts.
|
||||
1. If you have existing account seeds:
|
||||
1. Paste account seed in the **Broker Seed** field.
|
||||
2. Click **Get Broker Account**.
|
||||
3. Paste account seed in the **Standby Seed** field.
|
||||
4. Click **Get Standby Account**.
|
||||
5. Paste account seed in the **Operational Seed** field.
|
||||
6. Click **Get Operational Account**.
|
||||
2. If you do not have account seeds:
|
||||
1. Click **Get Broker Account**.
|
||||
2. Click **Get Standby Account**.
|
||||
2. Click **Get Operational Account**.
|
||||
3. Click **Get Broker Account Info**.
|
||||
4. Click **Get Standby Account Info**.
|
||||
5. Click **Get Operational Account Info**.
|
||||
|
||||
[](/docs/img/quickstart-py24.png)
|
||||
|
||||
## Prepare a Brokered Transaction
|
||||
|
||||
<div align="center">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/2dhWDnhCBuY?si=qpHSd6Y0ftVOe46E" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
1. Use the Standby account to create an NFT Sell Offer with the Broker account as the destination.
|
||||
1. Enter the **Amount** of the sell offer in drops (millionths of an XRP).
|
||||
2. Enter the **NFT ID** of the NFT you want to sell.
|
||||
3. Optionally, enter a number of seconds until **Expiration**.
|
||||
4. Enter the Broker account number as the **Destination**.
|
||||
5. Click **Create Sell Offer**.
|
||||
6. Click **Get Offers** to see the new offer.
|
||||
|
||||
|
||||
[](/docs/img/quickstart25.png)
|
||||
|
||||
2. Use the Operational account to create a NFT Buy Offer.
|
||||
1. Enter the **Amount** of your offer.
|
||||
2. Enter the **NFT ID**.
|
||||
3. Enter the owner’s account string in the **Owner** field.
|
||||
4. Optionally enter the number of seconds until **Expiration**.
|
||||
5. Click **Create Buy Offer**.
|
||||
|
||||
[](/docs/img/quickstart-py26.png)
|
||||
|
||||
## Get Offers
|
||||
|
||||
1. Enter the **NFT ID**.
|
||||
2. Click **Get Offers**.
|
||||
|
||||
[](/docs/img/quickstart-py27.png)
|
||||
|
||||
## Broker the Sale
|
||||
|
||||
1. Copy the _nft_offer_index_ of the sell offer and paste it in the **Sell NFT Offer Index** field.
|
||||
2. Copy the _nft_offer_index_ of the buy offer and paste it in the **Buy NFT Offer Index** field.
|
||||
3. Enter a **Broker Fee**, in drops.
|
||||
4. Click **Broker Sale**.
|
||||
|
||||
[](/docs/img/quickstart-py28.png)
|
||||
|
||||
|
||||
## Cancel Offer
|
||||
|
||||
After accepting a buy offer, a best practice for the broker is to cancel all other offers, if the broker has permissions to do so. Use **Get Offers** to get the full list of buy offers. To cancel an offer:
|
||||
|
||||
1. Enter the _nft_offer_index_ of the buy offer you want to cancel in the **Buy NFT Offer Index** field.
|
||||
2. Click **Cancel Offer**.
|
||||
|
||||
[](/docs/img/quickstart-py29.png)
|
||||
|
||||
# Code Walkthrough
|
||||
|
||||
You can download the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> archive to examine the code samples.
|
||||
|
||||
## ripplex5-broker-nfts.js
|
||||
<!-- SPELLING_IGNORE: ripplex5 -->
|
||||
|
||||
Four of the five buttons for the Broker are supported by existing methods. The only new method required is the Broker Sale method.
|
||||
|
||||
Import dependencies and create a global variable for `testnet_url`.
|
||||
|
||||
```python
|
||||
import xrpl
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.wallet import Wallet
|
||||
testnet_url = "https://s.altnet.rippletest.net:51234"
|
||||
```
|
||||
|
||||
## Broker Sale
|
||||
|
||||
Pass the _seed_, _sell___offer___index_, _buy___offer___index_, and _broker___fee_.
|
||||
|
||||
```python
|
||||
def broker_sale(seed, sell_offer_index, buy_offer_index, broker_fee):
|
||||
"""broker_sale"""
|
||||
```
|
||||
|
||||
Get the broker wallet and establish a client connection.
|
||||
|
||||
```python
|
||||
broker_wallet=Wallet.from_seed(seed)
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Define the accept offer transaction, matching a sell offer with the selected buy offer.
|
||||
|
||||
```python
|
||||
accept_offer_tx=xrpl.models.transactions.NFTokenAcceptOffer(
|
||||
account=broker_wallet.classic_address,
|
||||
nftoken_sell_offer=sell_offer_index,
|
||||
nftoken_buy_offer=buy_offer_index,
|
||||
nftoken_broker_fee=broker_fee
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and report the results.
|
||||
|
||||
```python
|
||||
reply=""
|
||||
try:
|
||||
response=xrpl.transaction.submit_and_wait(accept_offer_tx,client,broker_wallet)
|
||||
reply=response.result
|
||||
except xrpl.transaction.XRPLReliableSubmissionException as e:
|
||||
reply=f"Submit failed: {e}"
|
||||
return reply
|
||||
```
|
||||
|
||||
## lesson5-broker-nfts.py
|
||||
|
||||
Revise the form from lesson 4 to add a new Broker section at the top. Changes are highlighted below.
|
||||
|
||||
```python
|
||||
import tkinter as tk
|
||||
import xrpl
|
||||
import json
|
||||
```
|
||||
|
||||
Import the `broker_sale` method.
|
||||
|
||||
```python
|
||||
from mod1 import get_account, get_account_info, send_xrp
|
||||
from mod2 import (
|
||||
create_trust_line,
|
||||
send_currency,
|
||||
get_balance,
|
||||
configure_account,
|
||||
)
|
||||
from mod3 import (
|
||||
mint_token,
|
||||
get_tokens,
|
||||
burn_token,
|
||||
)
|
||||
from mod4 import (
|
||||
create_sell_offer,
|
||||
create_buy_offer,
|
||||
get_offers,
|
||||
cancel_offer,
|
||||
accept_sell_offer,
|
||||
accept_buy_offer,
|
||||
)
|
||||
from mod5 import broker_sale
|
||||
|
||||
#############################################
|
||||
## Handlers #################################
|
||||
#############################################
|
||||
```
|
||||
|
||||
Add handlers for the broker account buttons.
|
||||
|
||||
```python
|
||||
# Module 5 Handlers
|
||||
|
||||
def get_broker_account():
|
||||
new_wallet = get_account(ent_broker_seed.get())
|
||||
ent_broker_account.delete(0, tk.END)
|
||||
ent_broker_seed.delete(0, tk.END)
|
||||
ent_broker_account.insert(0, new_wallet.classic_address)
|
||||
ent_broker_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_broker_account_info():
|
||||
accountInfo = get_account_info(ent_broker_account.get())
|
||||
ent_broker_balance.delete(0, tk.END)
|
||||
ent_broker_balance.insert(0,accountInfo['Balance'])
|
||||
text_broker_results.delete("1.0", tk.END)
|
||||
text_broker_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def broker_broker_sale():
|
||||
results = broker_sale(
|
||||
ent_broker_seed.get(),
|
||||
ent_broker_sell_nft_idx.get(),
|
||||
ent_broker_buy_nft_idx.get(),
|
||||
ent_broker_fee.get()
|
||||
)
|
||||
text_broker_results.delete("1.0", tk.END)
|
||||
text_broker_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def broker_get_offers():
|
||||
results = get_offers(ent_broker_nft_id.get())
|
||||
text_broker_results.delete("1.0", tk.END)
|
||||
text_broker_results.insert("1.0", results)
|
||||
|
||||
|
||||
def broker_cancel_offer():
|
||||
results = cancel_offer(
|
||||
ent_broker_seed.get(),
|
||||
ent_broker_buy_nft_idx.get()
|
||||
)
|
||||
text_broker_results.delete("1.0", tk.END)
|
||||
text_broker_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
# Module 4 Handlers
|
||||
|
||||
def standby_create_sell_offer():
|
||||
results = create_sell_offer(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_amount.get(),
|
||||
ent_standby_nft_id.get(),
|
||||
ent_standby_expiration.get(),
|
||||
ent_standby_destination.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_accept_sell_offer():
|
||||
results = accept_sell_offer (
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_nft_offer_index.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_create_buy_offer():
|
||||
results = create_buy_offer(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_amount.get(),
|
||||
ent_standby_nft_id.get(),
|
||||
ent_standby_owner.get(),
|
||||
ent_standby_expiration.get(),
|
||||
ent_standby_destination.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_accept_buy_offer():
|
||||
results = accept_buy_offer (
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_nft_offer_index.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_get_offers():
|
||||
results = get_offers(ent_standby_nft_id.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", results)
|
||||
|
||||
|
||||
def standby_cancel_offer():
|
||||
results = cancel_offer(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_nft_offer_index.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def op_create_sell_offer():
|
||||
results = create_sell_offer(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_amount.get(),
|
||||
ent_operational_nft_id.get(),
|
||||
ent_operational_expiration.get(),
|
||||
ent_operational_destination.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def op_accept_sell_offer():
|
||||
results = accept_sell_offer (
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_nft_offer_index.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def op_create_buy_offer():
|
||||
results = create_buy_offer(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_amount.get(),
|
||||
ent_operational_nft_id.get(),
|
||||
ent_operational_owner.get(),
|
||||
ent_operational_expiration.get(),
|
||||
ent_operational_destination.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def op_accept_buy_offer():
|
||||
results = accept_buy_offer (
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_nft_offer_index.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def op_get_offers():
|
||||
results = get_offers(ent_operational_nft_id.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", results)
|
||||
|
||||
|
||||
def op_cancel_offer():
|
||||
results = cancel_offer(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_nft_offer_index.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
|
||||
# Module 3 Handlers
|
||||
|
||||
def standby_mint_token():
|
||||
results = mint_token(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_uri.get(),
|
||||
ent_standby_flags.get(),
|
||||
ent_standby_transfer_fee.get(),
|
||||
ent_standby_taxon.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_get_tokens():
|
||||
results = get_tokens(ent_standby_account.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_burn_token():
|
||||
results = burn_token(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_nft_id.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_mint_token():
|
||||
results = mint_token(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_uri.get(),
|
||||
ent_operational_flags.get(),
|
||||
ent_operational_transfer_fee.get(),
|
||||
ent_operational_taxon.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_get_tokens():
|
||||
results = get_tokens(ent_operational_account.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_burn_token():
|
||||
results = burn_token(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_nft_id.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
# Module 2 Handlers
|
||||
|
||||
def standby_create_trust_line():
|
||||
results = create_trust_line(ent_standby_seed.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_currency.get(),
|
||||
ent_standby_amount.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_send_currency():
|
||||
results = send_currency(ent_standby_seed.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_currency.get(),
|
||||
ent_standby_amount.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_configure_account():
|
||||
results = configure_account(
|
||||
ent_standby_seed.get(),
|
||||
standbyRippling)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_create_trust_line():
|
||||
results = create_trust_line(ent_operational_seed.get(),
|
||||
ent_operational_destination.get(),
|
||||
ent_operational_currency.get(),
|
||||
ent_operational_amount.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_send_currency():
|
||||
results = send_currency(ent_operational_seed.get(),
|
||||
ent_operational_destination.get(),
|
||||
ent_operational_currency.get(),
|
||||
ent_operational_amount.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_configure_account():
|
||||
results = configure_account(
|
||||
ent_operational_seed.get(),
|
||||
operationalRippling)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def get_balances():
|
||||
results = get_balance(ent_operational_account.get(), ent_standby_account.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
results = get_balance(ent_standby_account.get(), ent_operational_account.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
# Module 1 Handlers
|
||||
def get_standby_account():
|
||||
new_wallet = get_account(ent_standby_seed.get())
|
||||
ent_standby_account.delete(0, tk.END)
|
||||
ent_standby_seed.delete(0, tk.END)
|
||||
ent_standby_account.insert(0, new_wallet.classic_address)
|
||||
ent_standby_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_standby_account_info():
|
||||
accountInfo = get_account_info(ent_standby_account.get())
|
||||
ent_standby_balance.delete(0, tk.END)
|
||||
ent_standby_balance.insert(0,accountInfo['Balance'])
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def standby_send_xrp():
|
||||
response = send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
|
||||
ent_standby_destination.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
def get_operational_account():
|
||||
new_wallet = get_account(ent_operational_seed.get())
|
||||
ent_operational_account.delete(0, tk.END)
|
||||
ent_operational_account.insert(0, new_wallet.classic_address)
|
||||
ent_operational_seed.delete(0, tk.END)
|
||||
ent_operational_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_operational_account_info():
|
||||
accountInfo = get_account_info(ent_operational_account.get())
|
||||
ent_operational_balance.delete(0, tk.END)
|
||||
ent_operational_balance.insert(0,accountInfo['Balance'])
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def operational_send_xrp():
|
||||
response = send_xrp(ent_operational_seed.get(),ent_operational_amount.get(), ent_operational_destination.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
```
|
||||
|
||||
Create a new window with the title _Quickstart - Broker Sale_.
|
||||
|
||||
```python
|
||||
window = tk.Tk()
|
||||
window.title("Quickstart - Broker Sale")
|
||||
|
||||
myscrollbar=tk.Scrollbar(window,orient="vertical")
|
||||
myscrollbar.pack(side="right",fill="y")
|
||||
|
||||
|
||||
standbyRippling = tk.BooleanVar()
|
||||
operationalRippling = tk.BooleanVar()
|
||||
```
|
||||
|
||||
Add a new frame to hold the broker fields and buttons.
|
||||
|
||||
```python
|
||||
# Broker frame
|
||||
|
||||
frm_broker = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
|
||||
frm_broker.pack()
|
||||
```
|
||||
|
||||
Define the broker entry fields.
|
||||
|
||||
```python
|
||||
lbl_broker_seed = tk.Label(master=frm_broker, text="Broker Seed")
|
||||
ent_broker_seed = tk.Entry(master=frm_broker, width=50)
|
||||
lbl_broker_account = tk.Label(master=frm_broker, text="Broker Account")
|
||||
ent_broker_account = tk.Entry(master=frm_broker, width=50)
|
||||
lbl_broker_balance = tk.Label(master=frm_broker, text="XRP Balance")
|
||||
ent_broker_balance = tk.Entry(master=frm_broker, width=50)
|
||||
lbl_broker_amount = tk.Label(master=frm_broker, text="Amount")
|
||||
ent_broker_amount = tk.Entry(master=frm_broker, width=50)
|
||||
lbl_broker_nft_id = tk.Label(master=frm_broker, text="NFT ID")
|
||||
ent_broker_nft_id = tk.Entry(master=frm_broker, width=50)
|
||||
lbl_broker_sell_nft_idx = tk.Label(master=frm_broker, text="Sell NFT Offer Index")
|
||||
ent_broker_sell_nft_idx = tk.Entry(master=frm_broker, width=50)
|
||||
lbl_broker_buy_nft_idx = tk.Label(master=frm_broker, text="Buy NFT Offer Index")
|
||||
ent_broker_buy_nft_idx = tk.Entry(master=frm_broker, width=50)
|
||||
lbl_broker_owner = tk.Label(master=frm_broker, text="Owner")
|
||||
ent_broker_owner = tk.Entry(master=frm_broker, width=50)
|
||||
lbl_broker_fee = tk.Label(master=frm_broker, text="Broker Fee")
|
||||
ent_broker_fee = tk.Entry(master=frm_broker, width=50)
|
||||
lbl_broker_results=tk.Label(master=frm_broker, text="Results")
|
||||
text_broker_results = tk.Text(master=frm_broker, height=10, width=65)
|
||||
```
|
||||
|
||||
Place the fields in a grid.
|
||||
|
||||
```python
|
||||
lbl_broker_seed.grid(row=0, column=0, sticky="w")
|
||||
ent_broker_seed.grid(row=0, column=1)
|
||||
lbl_broker_account.grid(row=1, column=0, sticky="w")
|
||||
ent_broker_account.grid(row=1, column=1)
|
||||
lbl_broker_balance.grid(row=2, column=0, sticky="w")
|
||||
ent_broker_balance.grid(row=2, column=1)
|
||||
lbl_broker_amount.grid(row=3, column=0, sticky="w")
|
||||
ent_broker_amount.grid(row=3, column=1)
|
||||
lbl_broker_nft_id.grid(row=4, column=0, sticky="w")
|
||||
ent_broker_nft_id.grid(row=4, column=1)
|
||||
lbl_broker_sell_nft_idx.grid(row=5, column=0, sticky="w")
|
||||
ent_broker_sell_nft_idx.grid(row=5, column=1)
|
||||
lbl_broker_buy_nft_idx.grid(row=6, column=0, sticky="w")
|
||||
ent_broker_buy_nft_idx.grid(row=6, column=1)
|
||||
lbl_broker_owner.grid(row=7, column=0, sticky="w")
|
||||
ent_broker_owner.grid(row=7, column=1)
|
||||
lbl_broker_fee.grid(row=8, column=0, sticky="w")
|
||||
ent_broker_fee.grid(row=8, column=1)
|
||||
lbl_broker_results.grid(row=9, column=0)
|
||||
text_broker_results.grid(row=9, column=1)
|
||||
```
|
||||
|
||||
Define and place the broker buttons.
|
||||
|
||||
```python
|
||||
btn_broker_get_account = tk.Button(master=frm_broker, text="Get Broker Account",
|
||||
command = get_broker_account)
|
||||
btn_broker_get_account.grid(row=0, column=2, sticky = "nsew")
|
||||
btn_broker_get_account_info = tk.Button(master=frm_broker, text="Get Broker Account Info",
|
||||
command = get_broker_account_info)
|
||||
btn_broker_get_account_info.grid(row=1, column=2, sticky = "nsew")
|
||||
btn_broker_sale = tk.Button(master=frm_broker, text="Broker Sale",
|
||||
command = broker_broker_sale)
|
||||
btn_broker_sale.grid(row=2, column=2, sticky = "nsew")
|
||||
btn_broker_get_offers = tk.Button(master=frm_broker, text="Get Offers",
|
||||
command = broker_get_offers)
|
||||
btn_broker_get_offers.grid(row=3, column=2, sticky = "nsew")
|
||||
btn_broker_cancel_offer = tk.Button(master=frm_broker, text="Cancel Offer",
|
||||
command = broker_cancel_offer)
|
||||
btn_broker_cancel_offer.grid(row=4, column=2, sticky="nsew")
|
||||
|
||||
# Form frame
|
||||
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
|
||||
frm_form.pack()
|
||||
|
||||
# Create the Label and Entry widgets for "Standby Account"
|
||||
lbl_standy_seed = tk.Label(master=frm_form, text="Standby Seed")
|
||||
ent_standby_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_account = tk.Label(master=frm_form, text="Standby Account")
|
||||
ent_standby_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standy_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_standby_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_standby_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_standby_balance = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_currency = tk.Label(master=frm_form, text="Currency")
|
||||
ent_standby_currency = tk.Entry(master=frm_form, width=50)
|
||||
cb_standby_allow_rippling = tk.Checkbutton(master=frm_form, text="Allow Rippling", variable=standbyRippling, onvalue=True, offvalue=False)
|
||||
lbl_standby_uri = tk.Label(master=frm_form, text="NFT URI")
|
||||
ent_standby_uri = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_flags = tk.Label(master=frm_form, text="Flags")
|
||||
ent_standby_flags = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_transfer_fee = tk.Label(master=frm_form, text="Transfer Fee")
|
||||
ent_standby_transfer_fee = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_taxon = tk.Label(master=frm_form, text="Taxon")
|
||||
ent_standby_taxon = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_nft_id = tk.Label(master=frm_form, text="NFT ID")
|
||||
ent_standby_nft_id = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_nft_offer_index = tk.Label(master=frm_form, text="NFT Offer Index")
|
||||
ent_standby_nft_offer_index = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_owner = tk.Label(master=frm_form, text="Owner")
|
||||
ent_standby_owner = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_expiration = tk.Label(master=frm_form, text="Expiration")
|
||||
ent_standby_expiration = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_results = tk.Label(master=frm_form,text='Results')
|
||||
text_standby_results = tk.Text(master=frm_form, height = 10, width = 65)
|
||||
|
||||
# Place field in a grid.
|
||||
lbl_standy_seed.grid(row=0, column=0, sticky="w")
|
||||
ent_standby_seed.grid(row=0, column=1)
|
||||
lbl_standby_account.grid(row=2, column=0, sticky="e")
|
||||
ent_standby_account.grid(row=2, column=1)
|
||||
lbl_standy_amount.grid(row=3, column=0, sticky="e")
|
||||
ent_standby_amount.grid(row=3, column=1)
|
||||
lbl_standby_destination.grid(row=4, column=0, sticky="e")
|
||||
ent_standby_destination.grid(row=4, column=1)
|
||||
lbl_standby_balance.grid(row=5, column=0, sticky="e")
|
||||
ent_standby_balance.grid(row=5, column=1)
|
||||
lbl_standby_currency.grid(row=6, column=0, sticky="e")
|
||||
ent_standby_currency.grid(row=6, column=1)
|
||||
cb_standby_allow_rippling.grid(row=7,column=1, sticky="w")
|
||||
lbl_standby_uri.grid(row=8, column=0, sticky="e")
|
||||
ent_standby_uri.grid(row=8, column=1, sticky="w")
|
||||
lbl_standby_flags.grid(row=9, column=0, sticky="e")
|
||||
ent_standby_flags.grid(row=9, column=1, sticky="w")
|
||||
lbl_standby_transfer_fee.grid(row=10, column=0, sticky="e")
|
||||
ent_standby_transfer_fee.grid(row=10, column=1, sticky="w")
|
||||
lbl_standby_taxon.grid(row=11, column=0, sticky="e")
|
||||
ent_standby_taxon.grid(row=11, column=1, sticky="w")
|
||||
lbl_standby_nft_id.grid(row=12, column=0, sticky="e")
|
||||
ent_standby_nft_id.grid(row=12, column=1, sticky="w")
|
||||
lbl_standby_nft_offer_index.grid(row=13, column=0, sticky="ne")
|
||||
ent_standby_nft_offer_index.grid(row=13, column=1, sticky="w")
|
||||
lbl_standby_owner.grid(row=14, column=0, sticky="ne")
|
||||
ent_standby_owner.grid(row=14, column=1, sticky="w")
|
||||
lbl_standby_expiration.grid(row=15, column=0, sticky="ne")
|
||||
ent_standby_expiration.grid(row=15, column=1, sticky="w")
|
||||
lbl_standby_results.grid(row=17, column=0, sticky="ne")
|
||||
text_standby_results.grid(row=17, column=1, sticky="nw")
|
||||
|
||||
cb_standby_allow_rippling.select()
|
||||
|
||||
###############################################
|
||||
## Operational Account ########################
|
||||
###############################################
|
||||
|
||||
# Create the Label and Entry widgets for "Operational Account"
|
||||
lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed")
|
||||
ent_operational_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_account = tk.Label(master=frm_form, text="Operational Account")
|
||||
ent_operational_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_operational_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_operational_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_operational_balance = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_currency = tk.Label(master=frm_form, text="Currency")
|
||||
ent_operational_currency = tk.Entry(master=frm_form, width=50)
|
||||
cb_operational_allow_rippling = tk.Checkbutton(master=frm_form, text="Allow Rippling", variable=operationalRippling, onvalue=True, offvalue=False)
|
||||
lbl_operational_uri = tk.Label(master=frm_form, text="NFT URI")
|
||||
ent_operational_uri = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_flags = tk.Label(master=frm_form, text="Flags")
|
||||
ent_operational_flags = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_transfer_fee = tk.Label(master=frm_form, text="Transfer Fee")
|
||||
ent_operational_transfer_fee = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_taxon = tk.Label(master=frm_form, text="Taxon")
|
||||
ent_operational_taxon = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_nft_id = tk.Label(master=frm_form, text="NFT ID")
|
||||
ent_operational_nft_id = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_nft_offer_index = tk.Label(master=frm_form, text="NFT Offer Index")
|
||||
ent_operational_nft_offer_index = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_owner = tk.Label(master=frm_form, text="Owner")
|
||||
ent_operational_owner = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_expiration = tk.Label(master=frm_form, text="Expiration")
|
||||
ent_operational_expiration = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_results = tk.Label(master=frm_form,text="Results")
|
||||
text_operational_results = tk.Text(master=frm_form, height = 10, width = 65)
|
||||
|
||||
#Place the widgets in a grid
|
||||
lbl_operational_seed.grid(row=0, column=4, sticky="e")
|
||||
ent_operational_seed.grid(row=0, column=5, sticky="w")
|
||||
lbl_operational_account.grid(row=2,column=4, sticky="e")
|
||||
ent_operational_account.grid(row=2,column=5, sticky="w")
|
||||
lbl_operational_amount.grid(row=3, column=4, sticky="e")
|
||||
ent_operational_amount.grid(row=3, column=5, sticky="w")
|
||||
lbl_operational_destination.grid(row=4, column=4, sticky="e")
|
||||
ent_operational_destination.grid(row=4, column=5, sticky="w")
|
||||
lbl_operational_balance.grid(row=5, column=4, sticky="e")
|
||||
ent_operational_balance.grid(row=5, column=5, sticky="w")
|
||||
lbl_operational_currency.grid(row=6, column=4, sticky="e")
|
||||
ent_operational_currency.grid(row=6, column=5)
|
||||
cb_operational_allow_rippling.grid(row=7,column=5, sticky="w")
|
||||
lbl_operational_uri.grid(row=8, column=4, sticky="e")
|
||||
ent_operational_uri.grid(row=8, column=5, sticky="w")
|
||||
lbl_operational_flags.grid(row=9, column=4, sticky="e")
|
||||
ent_operational_flags.grid(row=9, column=5, sticky="w")
|
||||
lbl_operational_transfer_fee.grid(row=10, column=4, sticky="e")
|
||||
ent_operational_transfer_fee.grid(row=10, column=5, sticky="w")
|
||||
lbl_operational_taxon.grid(row=11, column=4, sticky="e")
|
||||
ent_operational_taxon.grid(row=11, column=5, sticky="w")
|
||||
lbl_operational_nft_id.grid(row=12, column=4, sticky="e")
|
||||
ent_operational_nft_id.grid(row=12, column=5, sticky="w")
|
||||
lbl_operational_nft_offer_index.grid(row=13, column=4, sticky="ne")
|
||||
ent_operational_nft_offer_index.grid(row=13, column=5, sticky="w")
|
||||
lbl_operational_owner.grid(row=14, column=4, sticky="ne")
|
||||
ent_operational_owner.grid(row=14, column=5, sticky="w")
|
||||
lbl_operational_expiration.grid(row=15, column=4, sticky="ne")
|
||||
ent_operational_expiration.grid(row=15, column=5, sticky="w")
|
||||
lbl_operational_results.grid(row=17, column=4, sticky="ne")
|
||||
text_operational_results.grid(row=17, column=5, sticky="nw")
|
||||
|
||||
cb_operational_allow_rippling.select()
|
||||
|
||||
#############################################
|
||||
## Buttons ##################################
|
||||
#############################################
|
||||
|
||||
# Create the Standby Account Buttons
|
||||
btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account",
|
||||
command = get_standby_account)
|
||||
btn_get_standby_account.grid(row=0, column=2, sticky = "nsew")
|
||||
btn_get_standby_account_info = tk.Button(master=frm_form,
|
||||
text="Get Standby Account Info",
|
||||
command = get_standby_account_info)
|
||||
btn_get_standby_account_info.grid(row=1, column=2, sticky = "nsew")
|
||||
btn_standby_send_xrp = tk.Button(master=frm_form, text="Send XRP >",
|
||||
command = standby_send_xrp)
|
||||
btn_standby_send_xrp.grid(row=2, column = 2, sticky = "nsew")
|
||||
btn_standby_create_trust_line = tk.Button(master=frm_form,
|
||||
text="Create Trust Line",
|
||||
command = standby_create_trust_line)
|
||||
btn_standby_create_trust_line.grid(row=4, column=2, sticky = "nsew")
|
||||
btn_standby_send_currency = tk.Button(master=frm_form, text="Send Currency >",
|
||||
command = standby_send_currency)
|
||||
btn_standby_send_currency.grid(row=5, column=2, sticky = "nsew")
|
||||
btn_standby_send_currency = tk.Button(master=frm_form, text="Get Balances",
|
||||
command = get_balances)
|
||||
btn_standby_send_currency.grid(row=6, column=2, sticky = "nsew")
|
||||
btn_standby_configure_account = tk.Button(master=frm_form,
|
||||
text="Configure Account",
|
||||
command = standby_configure_account)
|
||||
btn_standby_configure_account.grid(row=7,column=0, sticky = "nsew")
|
||||
btn_standby_mint_token = tk.Button(master=frm_form, text="Mint NFT",
|
||||
command = standby_mint_token)
|
||||
btn_standby_mint_token.grid(row=8, column=2, sticky="nsew")
|
||||
btn_standby_get_tokens = tk.Button(master=frm_form, text="Get NFTs",
|
||||
command = standby_get_tokens)
|
||||
btn_standby_get_tokens.grid(row=9, column=2, sticky="nsew")
|
||||
btn_standby_burn_token = tk.Button(master=frm_form, text="Burn NFT",
|
||||
command = standby_burn_token)
|
||||
btn_standby_burn_token.grid(row=10, column=2, sticky="nsew")
|
||||
btn_standby_create_sell_offer = tk.Button(master=frm_form, text="Create Sell Offer",
|
||||
command = standby_create_sell_offer)
|
||||
btn_standby_create_sell_offer.grid(row=11, column=2, sticky="nsew")
|
||||
btn_standby_accept_sell_offer = tk.Button(master=frm_form, text="Accept Sell Offer",
|
||||
command = standby_accept_sell_offer)
|
||||
btn_standby_accept_sell_offer.grid(row=12, column=2, sticky="nsew")
|
||||
btn_standby_create_buy_offer = tk.Button(master=frm_form, text="Create Buy Offer",
|
||||
command = standby_create_buy_offer)
|
||||
btn_standby_create_buy_offer.grid(row=13, column=2, sticky="nsew")
|
||||
btn_standby_accept_buy_offer = tk.Button(master=frm_form, text="Accept Buy Offer",
|
||||
command = standby_accept_buy_offer)
|
||||
btn_standby_accept_buy_offer.grid(row=14, column=2, sticky="nsew")
|
||||
btn_standby_get_offers = tk.Button(master=frm_form, text="Get Offers",
|
||||
command = standby_get_offers)
|
||||
btn_standby_get_offers.grid(row=15, column=2, sticky="nsew")
|
||||
btn_standby_cancel_offer = tk.Button(master=frm_form, text="Cancel Offer",
|
||||
command = standby_cancel_offer)
|
||||
btn_standby_cancel_offer.grid(row=16, column=2, sticky="nsew")
|
||||
|
||||
|
||||
|
||||
# Create the Operational Account Buttons
|
||||
btn_get_operational_account = tk.Button(master=frm_form,
|
||||
text="Get Operational Account",
|
||||
command = get_operational_account)
|
||||
btn_get_operational_account.grid(row=0, column=3, sticky = "nsew")
|
||||
btn_get_op_account_info = tk.Button(master=frm_form, text="Get Op Account Info",
|
||||
command = get_operational_account_info)
|
||||
btn_get_op_account_info.grid(row=1, column=3, sticky = "nsew")
|
||||
btn_op_send_xrp = tk.Button(master=frm_form, text="< Send XRP",
|
||||
command = operational_send_xrp)
|
||||
btn_op_send_xrp.grid(row=2, column = 3, sticky = "nsew")
|
||||
btn_op_create_trust_line = tk.Button(master=frm_form, text="Create Trust Line",
|
||||
command = operational_create_trust_line)
|
||||
btn_op_create_trust_line.grid(row=4, column=3, sticky = "nsew")
|
||||
btn_op_send_currency = tk.Button(master=frm_form, text="< Send Currency",
|
||||
command = operational_send_currency)
|
||||
btn_op_send_currency.grid(row=5, column=3, sticky = "nsew")
|
||||
btn_op_get_balances = tk.Button(master=frm_form, text="Get Balances",
|
||||
command = get_balances)
|
||||
btn_op_get_balances.grid(row=6, column=3, sticky = "nsew")
|
||||
btn_op_configure_account = tk.Button(master=frm_form, text="Configure Account",
|
||||
command = operational_configure_account)
|
||||
btn_op_configure_account.grid(row=7,column=4, sticky = "nsew")
|
||||
btn_op_mint_token = tk.Button(master=frm_form, text="Mint NFT",
|
||||
command = operational_mint_token)
|
||||
btn_op_mint_token.grid(row=8, column=3, sticky="nsew")
|
||||
btn_op_get_tokens = tk.Button(master=frm_form, text="Get NFTs",
|
||||
command = operational_get_tokens)
|
||||
btn_op_get_tokens.grid(row=9, column=3, sticky="nsew")
|
||||
btn_op_burn_token = tk.Button(master=frm_form, text="Burn NFT",
|
||||
command = operational_burn_token)
|
||||
btn_op_burn_token.grid(row=10, column=3, sticky="nsew")
|
||||
btn_op_create_sell_offer = tk.Button(master=frm_form, text="Create Sell Offer",
|
||||
command = op_create_sell_offer)
|
||||
btn_op_create_sell_offer.grid(row=11, column=3, sticky="nsew")
|
||||
btn_op_accept_sell_offer = tk.Button(master=frm_form, text="Accept Sell Offer",
|
||||
command = op_accept_sell_offer)
|
||||
btn_op_accept_sell_offer.grid(row=12, column=3, sticky="nsew")
|
||||
btn_op_create_buy_offer = tk.Button(master=frm_form, text="Create Buy Offer",
|
||||
command = op_create_buy_offer)
|
||||
btn_op_create_buy_offer.grid(row=13, column=3, sticky="nsew")
|
||||
btn_op_accept_buy_offer = tk.Button(master=frm_form, text="Accept Buy Offer",
|
||||
command = op_accept_buy_offer)
|
||||
btn_op_accept_buy_offer.grid(row=14, column=3, sticky="nsew")
|
||||
btn_op_get_offers = tk.Button(master=frm_form, text="Get Offers",
|
||||
command = op_get_offers)
|
||||
btn_op_get_offers.grid(row=15, column=3, sticky="nsew")
|
||||
btn_op_cancel_offer = tk.Button(master=frm_form, text="Cancel Offer",
|
||||
command = op_cancel_offer)
|
||||
btn_op_cancel_offer.grid(row=16, column=3, sticky="nsew")
|
||||
|
||||
|
||||
# Start the application
|
||||
window.mainloop()
|
||||
```x
|
||||
@@ -0,0 +1,637 @@
|
||||
---
|
||||
html: py-mint-and-burn-nfts.html
|
||||
parent: nfts-using-python.html
|
||||
seo:
|
||||
description: Mint and burn NFTs.
|
||||
labels:
|
||||
- Quickstart
|
||||
- Tokens
|
||||
- Non-fungible tokens, NFTs
|
||||
---
|
||||
|
||||
# Mint and Burn NFTs Using Python
|
||||
|
||||
This example shows how to:
|
||||
|
||||
1. Mint new Non-fungible Tokens (NFTs).
|
||||
2. Get a list of existing NFTs.
|
||||
3. Delete (Burn) an NFT.
|
||||
|
||||
[](/docs/img/quickstart-py10.png)
|
||||
|
||||
# Usage
|
||||
|
||||
You can download the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> archive to try the sample in your own browser.
|
||||
|
||||
## Get Accounts
|
||||
|
||||
1. Open and run `lesson3-mint-token.py`.
|
||||
2. Get test accounts.
|
||||
1. If you have existing Testnet account seeds:
|
||||
1. Paste the standby account seed in the **Standby Seed** field.
|
||||
2. Click **Get Standby Account**.
|
||||
3. Paste the operational account seed in the **Operational Seed** field.
|
||||
4. Click **Get Operational Account**.
|
||||
2. If you do not have existing Testnet accounts:
|
||||
1. Click **Get New Standby Account**.
|
||||
2. Click **Get New Operational Account**.
|
||||
3. Click **Get Standby Account Info**.
|
||||
4. Click **Get Op Account Info**.
|
||||
|
||||
[](/docs/img/quickstart-py11.png)
|
||||
|
||||
## Mint an NFT
|
||||
|
||||
<div align="center">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/StOLO9Bx9n8?si=IgMtoYRQlheaXzsG" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
To mint a non-fungible token object:
|
||||
|
||||
1. Set the **Flags** field. For testing purposes, we recommend setting the value to _8_. This sets the _tsTransferable_ flag, meaning that the NFT can be transferred to another account. Otherwise, the NFT can only be transferred back to the issuing account. See [NFToken Mint](/docs/references/protocol/transactions/types/nftokenmint/#nftokenmint-flags) for information about all of the available flags for minting NFTs.
|
||||
2. Enter the **NFT URI**. This is a URI that points to the data or metadata associated with the NFT. You can use the sample URI provided if you do not have one of your own.
|
||||
3. Enter the **Transfer Fee**, a percentage of the proceeds from future sales of the NFT that will be returned to the original creator. This is a value of 0-50000 inclusive, allowing transfer rates between 0.000% and 50.000% in increments of 0.001%. If you do not set the **Flags** field to allow the NFT to be transferrable, set this field to 0.
|
||||
4. Optionally a **Taxon** value as an integer. If you choose not to use a taxon, enter _0_.
|
||||
4. Click **Mint NFT**.
|
||||
|
||||
[](/docs/img/quickstart-py12.png)
|
||||
|
||||
|
||||
## Get Tokens
|
||||
|
||||
Click **Get NFTs** to get a list of NFTs owned by the account.
|
||||
|
||||
[](/docs/img/quickstart-py13.png)
|
||||
|
||||
## Burn a Token
|
||||
|
||||
The current owner of an NFT can always destroy (or _burn_) an NFT.
|
||||
|
||||
To permanently destroy an NFT:
|
||||
|
||||
1. Enter the **Token ID**.
|
||||
2. Click **Burn NFT**.
|
||||
|
||||
[](/docs/img/quickstart-py14.png)
|
||||
|
||||
# Code Walkthrough
|
||||
|
||||
You can download the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> archive to examine the code samples.
|
||||
|
||||
## mod3.py
|
||||
|
||||
This module contains the new methods `mint_token`, `get_tokens`, and `burn_token`.
|
||||
|
||||
Import dependencies and set global variable.
|
||||
|
||||
```python
|
||||
import xrpl
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.wallet import Wallet
|
||||
from xrpl.models.requests import AccountNFTs
|
||||
|
||||
testnet_url = "https://s.altnet.rippletest.net:51234"
|
||||
```
|
||||
|
||||
## mintToken
|
||||
|
||||
Pass the arguments account seed, NFT URI, transaction flags, the transfer fee, and optional taxon.
|
||||
|
||||
```python
|
||||
def mint_token(seed, uri, flags, transfer_fee, taxon):
|
||||
"""mint_token"""
|
||||
```
|
||||
|
||||
Get the account wallet and a client instance.
|
||||
|
||||
```python
|
||||
minter_wallet=Wallet.from_seed(seed)
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Define the mint transaction. Note that the NFT URI must be converted to a hex string.
|
||||
|
||||
```python
|
||||
mint_tx=xrpl.models.transactions.NFTokenMint(
|
||||
account=minter_wallet.address,
|
||||
uri=xrpl.utils.str_to_hex(uri),
|
||||
flags=int(flags),
|
||||
transfer_fee=int(transfer_fee),
|
||||
nftoken_taxon=int(taxon)
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and return results.
|
||||
|
||||
```python
|
||||
reply=""
|
||||
try:
|
||||
response=xrpl.transaction.submit_and_wait(mint_tx,client,minter_wallet)
|
||||
reply=response.result
|
||||
except xrpl.transaction.XRPLReliableSubmissionException as e:
|
||||
reply=f"Submit failed: {e}"
|
||||
return reply
|
||||
```
|
||||
|
||||
## getTokens
|
||||
|
||||
```python
|
||||
def get_tokens(account):
|
||||
"""get_tokens"""
|
||||
```
|
||||
|
||||
Instantiate a client.
|
||||
|
||||
```python
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Prepare the `AccountNFTs` request.
|
||||
|
||||
```python
|
||||
acct_nfts=AccountNFTs(
|
||||
account=account
|
||||
)
|
||||
```
|
||||
|
||||
Send the request and return the result.
|
||||
|
||||
```python
|
||||
response=client.request(acct_nfts)
|
||||
return response.result
|
||||
```
|
||||
|
||||
## burn_token
|
||||
|
||||
Pass the owner's seed value and the NFT ID.
|
||||
|
||||
```python
|
||||
def burn_token(seed, nftoken_id):
|
||||
"""burn_token"""
|
||||
```
|
||||
|
||||
Get the owner wallet and client instance.
|
||||
|
||||
```python
|
||||
owner_wallet=Wallet.from_seed(seed)
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Define the NFTokenBurn transaction.
|
||||
|
||||
```python
|
||||
burn_tx=xrpl.models.transactions.NFTokenBurn(
|
||||
account=owner_wallet.address,
|
||||
nftoken_id=nftoken_id
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and return results.
|
||||
|
||||
```python
|
||||
reply=""
|
||||
try:
|
||||
response=xrpl.transaction.submit_and_wait(burn_tx,client,owner_wallet)
|
||||
reply=response.result
|
||||
except xrpl.transaction.XRPLReliableSubmissionException as e:
|
||||
reply=f"Submit failed: {e}"
|
||||
return reply
|
||||
```
|
||||
|
||||
## lesson3-mint-token.py
|
||||
<!-- SPELLING_IGNORE: lesson3 -->
|
||||
|
||||
This module builds on `lesson2-create-trustline-send-currency.py`. Changes are noted below.
|
||||
|
||||
```python
|
||||
import tkinter as tk
|
||||
import xrpl
|
||||
import json
|
||||
import tkinter as tk
|
||||
import xrpl
|
||||
import json
|
||||
|
||||
from mod1 import get_account, get_account_info, send_xrp
|
||||
from mod2 import (
|
||||
create_trust_line,
|
||||
send_currency,
|
||||
get_balance,
|
||||
configure_account,
|
||||
)
|
||||
```
|
||||
|
||||
Import methods from `mod3.py`.
|
||||
|
||||
```python
|
||||
from mod3 import (
|
||||
mint_token,
|
||||
get_tokens,
|
||||
burn_token,
|
||||
)
|
||||
|
||||
#############################################
|
||||
## Handlers #################################
|
||||
#############################################
|
||||
```
|
||||
|
||||
Module 3 Handlers
|
||||
|
||||
```python
|
||||
def standby_mint_token():
|
||||
results = mint_token(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_uri.get(),
|
||||
ent_standby_flags.get(),
|
||||
ent_standby_transfer_fee.get(),
|
||||
ent_standby_taxon.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_get_tokens():
|
||||
results = get_tokens(ent_standby_account.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_burn_token():
|
||||
results = burn_token(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_nft_id.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_mint_token():
|
||||
results = mint_token(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_uri.get(),
|
||||
ent_operational_flags.get(),
|
||||
ent_operational_transfer_fee.get(),
|
||||
ent_operational_taxon.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_get_tokens():
|
||||
results = get_tokens(ent_operational_account.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_burn_token():
|
||||
results = burn_token(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_nft_id.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
# Module 2 Handlers
|
||||
|
||||
def standby_create_trust_line():
|
||||
results = create_trust_line(ent_standby_seed.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_currency.get(),
|
||||
ent_standby_amount.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_send_currency():
|
||||
results = send_currency(ent_standby_seed.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_currency.get(),
|
||||
ent_standby_amount.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_configure_account():
|
||||
results = configure_account(
|
||||
ent_standby_seed.get(),
|
||||
standbyRippling)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_create_trust_line():
|
||||
results = create_trust_line(ent_operational_seed.get(),
|
||||
ent_operational_destination.get(),
|
||||
ent_operational_currency.get(),
|
||||
ent_operational_amount.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_send_currency():
|
||||
results = send_currency(ent_operational_seed.get(),
|
||||
ent_operational_destination.get(),
|
||||
ent_operational_currency.get(),
|
||||
ent_operational_amount.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_configure_account():
|
||||
results = configure_account(
|
||||
ent_operational_seed.get(),
|
||||
operationalRippling)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def get_balances():
|
||||
results = get_balance(ent_operational_account.get(), ent_standby_account.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
results = get_balance(ent_standby_account.get(), ent_operational_account.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
# Module 1 Handlers
|
||||
def get_standby_account():
|
||||
new_wallet = get_account(ent_standby_seed.get())
|
||||
ent_standby_account.delete(0, tk.END)
|
||||
ent_standby_seed.delete(0, tk.END)
|
||||
ent_standby_account.insert(0, new_wallet.classic_address)
|
||||
ent_standby_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_standby_account_info():
|
||||
accountInfo = get_account_info(ent_standby_account.get())
|
||||
ent_standby_balance.delete(0, tk.END)
|
||||
ent_standby_balance.insert(0,accountInfo['Balance'])
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def standby_send_xrp():
|
||||
response = send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
|
||||
ent_standby_destination.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
def get_operational_account():
|
||||
new_wallet = get_account(ent_operational_seed.get())
|
||||
ent_operational_account.delete(0, tk.END)
|
||||
ent_operational_account.insert(0, new_wallet.classic_address)
|
||||
ent_operational_seed.delete(0, tk.END)
|
||||
ent_operational_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_operational_account_info():
|
||||
accountInfo = get_account_info(ent_operational_account.get())
|
||||
ent_operational_balance.delete(0, tk.END)
|
||||
ent_operational_balance.insert(0,accountInfo['Balance'])
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def operational_send_xrp():
|
||||
response = send_xrp(ent_operational_seed.get(),ent_operational_amount.get(), ent_operational_destination.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
```
|
||||
|
||||
Create a new window with the title "Quickstart Module 3."
|
||||
|
||||
```python
|
||||
window = tk.Tk()
|
||||
window.title("Quickstart Module 3")
|
||||
|
||||
standbyRippling = tk.BooleanVar()
|
||||
operationalRippling = tk.BooleanVar()
|
||||
|
||||
# Form frame
|
||||
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
|
||||
frm_form.pack()
|
||||
|
||||
# Create the Label and Entry widgets for "Standby Account"
|
||||
lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed")
|
||||
ent_operational_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_account = tk.Label(master=frm_form, text="Operational Account")
|
||||
ent_operational_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_operational_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_operational_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_operational_balance = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_currency = tk.Label(master=frm_form, text="Currency")
|
||||
ent_operational_currency = tk.Entry(master=frm_form, width=50)
|
||||
cb_standby_allow_rippling = tk.Checkbutton(master=frm_form, text="Allow Rippling", variable=standbyRippling, onvalue=True, offvalue=False)
|
||||
```
|
||||
|
||||
Add **NFT URI**, **Flags**, **Transfer Fee**, **Taxon**, and **NFT ID** fields.
|
||||
|
||||
```python
|
||||
lbl_standby_uri = tk.Label(master=frm_form, text="NFT URI")
|
||||
ent_standby_uri = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_flags = tk.Label(master=frm_form, text="Flags")
|
||||
ent_standby_flags = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_transfer_fee = tk.Label(master=frm_form, text="Transfer Fee")
|
||||
ent_standby_transfer_fee = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_taxon = tk.Label(master=frm_form, text="Taxon")
|
||||
ent_standby_taxon = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_nft_id = tk.Label(master=frm_form, text="NFT ID")
|
||||
ent_standby_nft_id = tk.Entry(master=frm_form, width="50")
|
||||
lbl_standby_results = tk.Label(master=frm_form,text='Results')
|
||||
text_standby_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
# Place field in a grid.
|
||||
lbl_standy_seed.grid(row=0, column=0, sticky="w")
|
||||
ent_standby_seed.grid(row=0, column=1)
|
||||
lbl_standby_account.grid(row=2, column=0, sticky="e")
|
||||
ent_standby_account.grid(row=2, column=1)
|
||||
lbl_standy_amount.grid(row=3, column=0, sticky="e")
|
||||
ent_standby_amount.grid(row=3, column=1)
|
||||
lbl_standby_destination.grid(row=4, column=0, sticky="e")
|
||||
ent_standby_destination.grid(row=4, column=1)
|
||||
lbl_standby_balance.grid(row=5, column=0, sticky="e")
|
||||
ent_standby_balance.grid(row=5, column=1)
|
||||
lbl_standby_currency.grid(row=6, column=0, sticky="e")
|
||||
ent_standby_currency.grid(row=6, column=1)
|
||||
cb_standby_allow_rippling.grid(row=7,column=1, sticky="w")
|
||||
```
|
||||
|
||||
Place new UI elements in the grid.
|
||||
|
||||
```python
|
||||
lbl_standby_uri.grid(row=8, column=0, sticky="e")
|
||||
ent_standby_uri.grid(row=8, column=1, sticky="w")
|
||||
lbl_standby_flags.grid(row=9, column=0, sticky="e")
|
||||
ent_standby_flags.grid(row=9, column=1, sticky="w")
|
||||
lbl_standby_transfer_fee.grid(row=10, column=0, sticky="e")
|
||||
ent_standby_transfer_fee.grid(row=10, column=1, sticky="w")
|
||||
lbl_standby_taxon.grid(row=11, column=0, sticky="e")
|
||||
ent_standby_taxon.grid(row=11, column=1, sticky="w")
|
||||
lbl_standby_nft_id.grid(row=12, column=0, sticky="e")
|
||||
ent_standby_nft_id.grid(row=12, column=1, sticky="w")
|
||||
lbl_standby_results.grid(row=13, column=0, sticky="ne")
|
||||
text_standby_results.grid(row=13, column=1, sticky="nw")
|
||||
cb_standby_allow_rippling.select()
|
||||
|
||||
###############################################
|
||||
## Operational Account ########################
|
||||
###############################################
|
||||
|
||||
# Create the Label and Entry widgets for "Operational Account"
|
||||
lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed")
|
||||
ent_operational_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_account = tk.Label(master=frm_form, text="Operational Account")
|
||||
ent_operational_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_operational_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_operational_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_operational_balance = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_currency = tk.Label(master=frm_form, text="Currency")
|
||||
ent_operational_currency = tk.Entry(master=frm_form, width=50)
|
||||
cb_operational_allow_rippling = tk.Checkbutton(master=frm_form, text="Allow Rippling", variable=operationalRippling, onvalue=True, offvalue=False)
|
||||
```
|
||||
|
||||
Add fields for **NFT URI**, **Flags**, **Transfer Fee**, **Taxon**, and **NFT ID**.
|
||||
|
||||
```python
|
||||
lbl_operational_uri = tk.Label(master=frm_form, text="NFT URI")
|
||||
ent_operational_uri = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_flags = tk.Label(master=frm_form, text="Flags")
|
||||
ent_operational_flags = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_transfer_fee = tk.Label(master=frm_form, text="Transfer Fee")
|
||||
ent_operational_transfer_fee = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_taxon = tk.Label(master=frm_form, text="Taxon")
|
||||
ent_operational_taxon = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_nft_id = tk.Label(master=frm_form, text="NFT ID")
|
||||
ent_operational_nft_id = tk.Entry(master=frm_form, width="50")
|
||||
lbl_operational_results = tk.Label(master=frm_form,text='Results')
|
||||
text_operational_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
#Place the widgets in a grid
|
||||
lbl_operational_seed.grid(row=0, column=4, sticky="e")
|
||||
ent_operational_seed.grid(row=0, column=5, sticky="w")
|
||||
lbl_operational_account.grid(row=2,column=4, sticky="e")
|
||||
ent_operational_account.grid(row=2,column=5, sticky="w")
|
||||
lbl_operational_amount.grid(row=3, column=4, sticky="e")
|
||||
ent_operational_amount.grid(row=3, column=5, sticky="w")
|
||||
lbl_operational_destination.grid(row=4, column=4, sticky="e")
|
||||
ent_operational_destination.grid(row=4, column=5, sticky="w")
|
||||
lbl_operational_balance.grid(row=5, column=4, sticky="e")
|
||||
ent_operational_balance.grid(row=5, column=5, sticky="w")
|
||||
lbl_operational_currency.grid(row=6, column=4, sticky="e")
|
||||
ent_operational_currency.grid(row=6, column=5)
|
||||
cb_operational_allow_rippling.grid(row=7,column=5, sticky="w")
|
||||
```
|
||||
|
||||
Place new UI elements in the grid.
|
||||
|
||||
```python
|
||||
lbl_operational_uri.grid(row=8, column=4, sticky="e")
|
||||
ent_operational_uri.grid(row=8, column=5, sticky="w")
|
||||
lbl_operational_flags.grid(row=9, column=4, sticky="e")
|
||||
ent_operational_flags.grid(row=9, column=5, sticky="w")
|
||||
lbl_operational_transfer_fee.grid(row=10, column=4, sticky="e")
|
||||
ent_operational_transfer_fee.grid(row=10, column=5, sticky="w")
|
||||
lbl_operational_taxon.grid(row=11, column=4, sticky="e")
|
||||
ent_operational_taxon.grid(row=11, column=5, sticky="w")
|
||||
lbl_operational_nft_id.grid(row=12, column=4, sticky="e")
|
||||
ent_operational_nft_id.grid(row=12, column=5, sticky="w")
|
||||
lbl_operational_results.grid(row=13, column=4, sticky="ne")
|
||||
text_operational_results.grid(row=13, column=5, sticky="nw")
|
||||
|
||||
cb_operational_allow_rippling.select()
|
||||
|
||||
#############################################
|
||||
## Buttons ##################################
|
||||
#############################################
|
||||
|
||||
# Create the Standby Account Buttons
|
||||
btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account",
|
||||
command = get_standby_account)
|
||||
btn_get_standby_account.grid(row=0, column=2, sticky = "nsew")
|
||||
btn_get_standby_account_info = tk.Button(master=frm_form,
|
||||
text="Get Standby Account Info",
|
||||
command = get_standby_account_info)
|
||||
btn_get_standby_account_info.grid(row=1, column=2, sticky = "nsew")
|
||||
btn_standby_send_xrp = tk.Button(master=frm_form, text="Send XRP >",
|
||||
command = standby_send_xrp)
|
||||
btn_standby_send_xrp.grid(row=2, column = 2, sticky = "nsew")
|
||||
btn_standby_create_trust_line = tk.Button(master=frm_form,
|
||||
text="Create Trust Line",
|
||||
command = standby_create_trust_line)
|
||||
btn_standby_create_trust_line.grid(row=3, column=2, sticky = "nsew")
|
||||
btn_standby_send_currency = tk.Button(master=frm_form, text="Send Currency >",
|
||||
command = standby_send_currency)
|
||||
btn_standby_send_currency.grid(row=4, column=2, sticky = "nsew")
|
||||
btn_standby_send_currency = tk.Button(master=frm_form, text="Get Balances",
|
||||
command = get_balances)
|
||||
btn_standby_send_currency.grid(row=5, column=2, sticky = "nsew")
|
||||
btn_standby_configure_account = tk.Button(master=frm_form,
|
||||
text="Configure Account",
|
||||
command = standby_configure_account)
|
||||
```
|
||||
|
||||
Add buttons for **Mint NFT**, **Get NFTs**, and **Burn NFT**.
|
||||
|
||||
```python
|
||||
btn_standby_mint_token = tk.Button(master=frm_form, text="Mint NFT",
|
||||
command = standby_mint_token)
|
||||
btn_standby_mint_token.grid(row=8, column=2, sticky="nsew")
|
||||
btn_standby_get_tokens = tk.Button(master=frm_form, text="Get NFTs",
|
||||
command = standby_get_tokens)
|
||||
btn_standby_get_tokens.grid(row=9, column=2, sticky="nsew")
|
||||
btn_standby_burn_token = tk.Button(master=frm_form, text="Burn NFT",
|
||||
command = standby_burn_token)
|
||||
btn_standby_burn_token.grid(row=10, column=2, sticky="nsew")
|
||||
|
||||
|
||||
# Create the Operational Account Buttons
|
||||
btn_get_operational_account = tk.Button(master=frm_form,
|
||||
text="Get Operational Account",
|
||||
command = get_operational_account)
|
||||
btn_get_operational_account.grid(row=0, column=3, sticky = "nsew")
|
||||
btn_get_op_account_info = tk.Button(master=frm_form, text="Get Op Account Info",
|
||||
command = get_operational_account_info)
|
||||
btn_get_op_account_info.grid(row=1, column=3, sticky = "nsew")
|
||||
btn_op_send_xrp = tk.Button(master=frm_form, text="< Send XRP",
|
||||
command = operational_send_xrp)
|
||||
btn_op_send_xrp.grid(row=2, column = 3, sticky = "nsew")
|
||||
btn_op_create_trust_line = tk.Button(master=frm_form, text="Create Trust Line",
|
||||
command = operational_create_trust_line)
|
||||
btn_op_create_trust_line.grid(row=3, column=3, sticky = "nsew")
|
||||
btn_op_send_currency = tk.Button(master=frm_form, text="< Send Currency",
|
||||
command = operational_send_currency)
|
||||
btn_op_send_currency.grid(row=4, column=3, sticky = "nsew")
|
||||
btn_op_get_balances = tk.Button(master=frm_form, text="Get Balances",
|
||||
command = get_balances)
|
||||
btn_op_get_balances.grid(row=5, column=3, sticky = "nsew")
|
||||
btn_op_configure_account = tk.Button(master=frm_form, text="Configure Account",
|
||||
command = operational_configure_account)
|
||||
btn_op_configure_account.grid(row=7,column=4, sticky = "nsew")
|
||||
```
|
||||
Add buttons for **Mint NFT**, **Get NFTs**, and **Burn NFT**.
|
||||
|
||||
```python
|
||||
btn_op_mint_token = tk.Button(master=frm_form, text="Mint NFT",
|
||||
command = operational_mint_token)
|
||||
btn_op_mint_token.grid(row=8, column=3, sticky="nsew")
|
||||
btn_op_get_tokens = tk.Button(master=frm_form, text="Get NFTs",
|
||||
command = operational_get_tokens)
|
||||
btn_op_get_tokens.grid(row=9, column=3, sticky="nsew")
|
||||
btn_op_burn_token = tk.Button(master=frm_form, text="Burn NFT",
|
||||
command = operational_burn_token)
|
||||
btn_op_burn_token.grid(row=10, column=3, sticky="nsew")
|
||||
|
||||
# Start the application
|
||||
window.mainloop()
|
||||
```
|
||||
1024
docs/tutorials/python/modular-tutorials/nfts/transfer-nfts.md
Normal file
1024
docs/tutorials/python/modular-tutorials/nfts/transfer-nfts.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,428 @@
|
||||
---
|
||||
html: py-create-accounts-send-xrp.html
|
||||
parent: send-payments-using-python.html
|
||||
seo:
|
||||
description: Create two accounts and transfer XRP between them using Python.
|
||||
labels:
|
||||
- Accounts
|
||||
- Quickstart
|
||||
- Transaction Sending
|
||||
- XRP
|
||||
---
|
||||
# Create Accounts and Send XRP Using Python
|
||||
|
||||
This example shows how to:
|
||||
|
||||
1. Create accounts on the Testnet, funded with 10000 test XRP with no actual value.
|
||||
2. Retrieve the accounts from seed values.
|
||||
3. Transfer XRP between accounts.
|
||||
|
||||
When you create an account, you receive a public/private key pair offline. Your account does not appear on the ledger until it is funded with XRP. This example shows how to create accounts for Testnet, but not how to create an account that you can use on Mainnet.
|
||||
|
||||
[](/docs/img/quickstart-py2.png)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To get started, create a new folder on your local disk and install the Python library using `pip`.
|
||||
|
||||
```
|
||||
pip3 install xrpl-py
|
||||
```
|
||||
|
||||
Download and expand the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> archive.
|
||||
|
||||
**Note:** Without the Quickstart Samples, you will not be able to try the examples that follow.
|
||||
|
||||
## Usage
|
||||
|
||||
<div align="center">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/Uu36ga0iMv0?si=jUoxQDcmqXpg1c_5" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
To get test accounts:
|
||||
|
||||
1. Open and launch `lesson1-send-xrp.py`.
|
||||
2. Click **Get Standby Account**.
|
||||
3. Click **Get Operational Account**.
|
||||
4. Click **Get Standby Account Info**.
|
||||
5. Click **Get Operational Account Info**.
|
||||
5. Copy and paste the **Standby Seed** and **Operational Seed** fields to a persistent location, such as a Notepad, so that you can reuse the accounts after reloading the form.
|
||||
|
||||
[](/docs/img/quickstart-py3.png)
|
||||
|
||||
You can transfer XRP between your new accounts. Each account has its own fields and buttons.
|
||||
|
||||
<div align="center">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/qUd-CTFdiks?si=chUPgcHZssL54x6U" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
To transfer XRP from the Standby account to the Operational account:
|
||||
|
||||
1. On the Standby (left) side of the form, enter the **Amount** of XRP to send.
|
||||
2. Copy and paste the **Operational Account** field to the Standby **Destination** field.
|
||||
3. Click **Send XRP>** to transfer XRP from the standby account to the operational account
|
||||
|
||||
[](/docs/img/quickstart-py4.png)
|
||||
|
||||
To transfer XRP from the Operational account to the Standby account:
|
||||
|
||||
1. On the Operational (right) side of the form, enter the **Amount** of XRP to send.
|
||||
2. Copy and paste the **Standby Account** field to the Operational **Destination** field.
|
||||
3. Click **<Send XRP** to transfer XRP from the Operational account to the Standby account.
|
||||
|
||||
# Code Walkthrough
|
||||
|
||||
You can download the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> in the source repository for this website.
|
||||
|
||||
## mod1.py
|
||||
|
||||
The mod1.py module contains the business logic for interacting with the XRP Ledger.
|
||||
|
||||
Import the XRPL library.
|
||||
|
||||
```python
|
||||
import xrpl
|
||||
|
||||
```
|
||||
|
||||
Create a variable for the server URI. This example uses the _Testnet_ ledger. You can update the URI to choose a different XRP Ledger instance.
|
||||
|
||||
|
||||
```python
|
||||
testnet_url = "https://s.altnet.rippletest.net:51234/"
|
||||
```
|
||||
|
||||
### get_account
|
||||
|
||||
This method lets you get an existing account by providing a seed value. If you provide no seed value, the method creates a new account for you.
|
||||
|
||||
Import required methods.
|
||||
|
||||
```python
|
||||
def get_account(seed):
|
||||
"""get_account"""
|
||||
```
|
||||
|
||||
Request a new client from the XRP Ledger.
|
||||
|
||||
```python
|
||||
client = xrpl.clients.JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
If you do not enter a seed, generate and return a new wallet. If you provide a seed value, return the wallet for that seed.
|
||||
|
||||
```python
|
||||
if (seed == ''):
|
||||
new_wallet = xrpl.wallet.generate_faucet_wallet(client)
|
||||
else:
|
||||
new_wallet = xrpl.wallet.Wallet.from_seed(seed)
|
||||
return new_wallet
|
||||
```
|
||||
|
||||
### get_account_info
|
||||
|
||||
Pass the account ID to the `get_account_info` method.
|
||||
|
||||
```python
|
||||
def get_account_info(accountId):
|
||||
"""get_account_info"""
|
||||
```
|
||||
|
||||
Get a client instance from Testnet.
|
||||
|
||||
```python
|
||||
client = xrpl.clients.JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Create the account info request, passing the account ID and the ledger index (in this case, the latest validated ledger).
|
||||
|
||||
```python
|
||||
acct_info = xrpl.models.requests.account_info.AccountInfo(
|
||||
account=accountId,
|
||||
ledger_index="validated"
|
||||
)
|
||||
```
|
||||
|
||||
Send the request to the XRP Ledger instance.
|
||||
|
||||
```python
|
||||
response=client.request(acct_info)
|
||||
```
|
||||
|
||||
Return the account data.
|
||||
|
||||
```python
|
||||
return response.result['account_data']
|
||||
```
|
||||
|
||||
### send_xrp
|
||||
|
||||
Transfer XRP to another account by passing the client seed, amount to transfer, and the destination account.
|
||||
|
||||
```python
|
||||
def send_xrp(seed, amount, destination):
|
||||
```
|
||||
|
||||
Get the sending wallet.
|
||||
|
||||
```python
|
||||
sending_wallet = xrpl.wallet.Wallet.from_seed(seed)
|
||||
client = xrpl.clients.JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Create a transaction request, passing the sending account, amount, and destination account.
|
||||
|
||||
```python
|
||||
payment = xrpl.models.transactions.Payment(
|
||||
account=sending_wallet.address,
|
||||
amount=xrpl.utils.xrp_to_drops(int(amount)),
|
||||
destination=destination,
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and return the response. If the transaction fails, return the error message.
|
||||
|
||||
```python
|
||||
try:
|
||||
response = xrpl.transaction.submit_and_wait(payment, client, sending_wallet)
|
||||
except xrpl.transaction.XRPLReliableSubmissionException as e:
|
||||
response = f"Submit failed: {e}"
|
||||
return response
|
||||
```
|
||||
|
||||
## lesson1-send-xrp.py
|
||||
|
||||
This module handles the UI for the application, providing fields for entering and reporting results of transactions and requests.
|
||||
|
||||
Import the tkinter, xrpl, and json modules.
|
||||
|
||||
```python
|
||||
import tkinter as tk
|
||||
import xrpl
|
||||
import json
|
||||
```
|
||||
|
||||
Import the methods from mod1.py.
|
||||
|
||||
```python
|
||||
from .mod1 import get_account, get_account_info, send_xrp
|
||||
```
|
||||
|
||||
### getStandbyAccount
|
||||
|
||||
```python
|
||||
def get_standby_account():
|
||||
```
|
||||
|
||||
Use the value in the standby Seed field (or an empty value) to request a new account.
|
||||
|
||||
```python
|
||||
new_wallet = get_account(ent_standby_seed.get())
|
||||
```
|
||||
|
||||
Clear the **Standby Seed** and **Standby Account** fields.
|
||||
|
||||
```python
|
||||
ent_standby_account.delete(0, tk.END)
|
||||
ent_standby_seed.delete(0, tk.END)
|
||||
```
|
||||
|
||||
Insert the account ID and seed values in the standby fields.
|
||||
|
||||
```python
|
||||
ent_standby_account.insert(0, new_wallet.classic_address)
|
||||
ent_standby_seed.insert(0, new_wallet.seed)
|
||||
```
|
||||
|
||||
### get_standby_account_info
|
||||
|
||||
With an account ID, anyone can request information about the account. Get the standby account value and use it to populate a `get_account_info` request.
|
||||
|
||||
```python
|
||||
def get_standby_account_info():
|
||||
accountInfo = get_account_info(ent_standby_account.get())
|
||||
```
|
||||
|
||||
Clear the Standby **Balance** field and insert the value from the account info response.
|
||||
|
||||
```python
|
||||
ent_standby_balance.delete(0, tk.END)
|
||||
ent_standby_balance.insert(0,accountInfo['Balance'])
|
||||
```
|
||||
|
||||
Clear the Standby **Results** text area and fill it with the full JSON response.
|
||||
|
||||
```python
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
```
|
||||
|
||||
### standby_send_xrp
|
||||
|
||||
```python
|
||||
def standby_send_xrp():
|
||||
```
|
||||
|
||||
Call the `send_xrp` method, passing the standby seed, the amount, and the destination value.
|
||||
|
||||
```python
|
||||
response = send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
|
||||
ent_standby_destination.get())
|
||||
```
|
||||
|
||||
Clear the standby **Results** field and insert the JSON response.
|
||||
|
||||
```python
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
|
||||
```
|
||||
|
||||
Use `get_standby_account_info()` and `get_operational_account_info()` to update the balance field for both accounts.
|
||||
|
||||
```python
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
```
|
||||
|
||||
### Reciprocal Transactions and Requests
|
||||
|
||||
The following four methods are the same as the previous standby transactions, but for the operational account.
|
||||
|
||||
```python
|
||||
def get_operational_account():
|
||||
new_wallet = get_account(ent_operational_seed.get())
|
||||
ent_operational_account.delete(0, tk.END)
|
||||
ent_operational_account.insert(0, new_wallet.classic_address)
|
||||
ent_operational_seed.delete(0, tk.END)
|
||||
ent_operational_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_operational_account_info():
|
||||
account_info = get_account_info(ent_operational_account.get())
|
||||
ent_operational_balance.delete(0, tk.END)
|
||||
ent_operational_balance.insert(0,accountInfo['Balance'])
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def operational_send_xrp():
|
||||
response = send_xrp(ent_operational_seed.get(),ent_operational_amount.get(),
|
||||
ent_operational_destination.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
```
|
||||
|
||||
Create UI elements, starting with the main window.
|
||||
|
||||
```python
|
||||
window = tk.Tk()
|
||||
window.title("Quickstart Module 1")
|
||||
```
|
||||
|
||||
Add a frame for the form.
|
||||
|
||||
```python
|
||||
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
|
||||
frm_form.pack()
|
||||
```
|
||||
|
||||
Create the `Label` and `Entry` widgets for the standby account.
|
||||
|
||||
```python
|
||||
lbl_standy_seed = tk.Label(master=frm_form, text="Standby Seed")
|
||||
ent_standby_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_account = tk.Label(master=frm_form, text="Standby Account")
|
||||
ent_standby_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standy_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_standby_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_standby_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_standby_balance = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_results = tk.Label(master=frm_form,text='Results')
|
||||
text_standby_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
```
|
||||
|
||||
Place the fields in a grid.
|
||||
|
||||
```python
|
||||
lbl_standy_seed.grid(row=0, column=0, sticky="w")
|
||||
ent_standby_seed.grid(row=0, column=1)
|
||||
lbl_standby_account.grid(row=2, column=0, sticky="e")
|
||||
ent_standby_account.grid(row=2, column=1)
|
||||
lbl_standy_amount.grid(row=3, column=0, sticky="e")
|
||||
ent_standby_amount.grid(row=3, column=1)
|
||||
lbl_standby_destination.grid(row=4, column=0, sticky="e")
|
||||
ent_standby_destination.grid(row=4, column=1)
|
||||
lbl_standby_balance.grid(row=5, column=0, sticky="e")
|
||||
ent_standby_balance.grid(row=5, column=1)
|
||||
lbl_standby_results.grid(row=6, column=0, sticky="ne")
|
||||
text_standby_results.grid(row=6, column=1, sticky="nw")
|
||||
```
|
||||
|
||||
Create the `Label` and `Entry` widgets for the operational account
|
||||
|
||||
```python
|
||||
lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed")
|
||||
ent_operational_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_account = tk.Label(master=frm_form, text="Operational Account")
|
||||
ent_operational_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_operational_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_operational_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_operational_balance = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_results = tk.Label(master=frm_form,text='Results')
|
||||
text_operational_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
```
|
||||
|
||||
Place the operational widgets in a grid.
|
||||
|
||||
```python
|
||||
lbl_operational_seed.grid(row=0, column=4, sticky="e")
|
||||
ent_operational_seed.grid(row=0, column=5, sticky="w")
|
||||
lbl_operational_account.grid(row=2,column=4, sticky="e")
|
||||
ent_operational_account.grid(row=2,column=5, sticky="w")
|
||||
lbl_operational_amount.grid(row=3, column=4, sticky="e")
|
||||
ent_operational_amount.grid(row=3, column=5, sticky="w")
|
||||
lbl_operational_destination.grid(row=4, column=4, sticky="e")
|
||||
ent_operational_destination.grid(row=4, column=5, sticky="w")
|
||||
lbl_operational_balance.grid(row=5, column=4, sticky="e")
|
||||
ent_operational_balance.grid(row=5, column=5, sticky="w")
|
||||
lbl_operational_results.grid(row=6, column=4, sticky="ne")
|
||||
text_operational_results.grid(row=6, column=5, sticky="nw")
|
||||
```
|
||||
|
||||
Create the standby account buttons and add them to the grid.
|
||||
|
||||
```python
|
||||
btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account", command = get_standby_account)
|
||||
btn_get_standby_account.grid(row=0, column=2, sticky = "nsew")
|
||||
btn_get_standby_account_info = tk.Button(master=frm_form, text="Get Standby Account Info", command = get_standby_account_info)
|
||||
btn_get_standby_account_info.grid(row=1, column=2, sticky = "nsew")
|
||||
btn_standby_send_xrp = tk.Button(master=frm_form, text="Send XRP >", command = standby_send_xrp)
|
||||
btn_standby_send_xrp.grid(row=2, column = 2, sticky = "nsew")
|
||||
```
|
||||
|
||||
Create the operational account buttons and add them to the grid.
|
||||
|
||||
```python
|
||||
btn_get_operational_account = tk.Button(master=frm_form, text="Get Operational Account",
|
||||
command = get_operational_account)
|
||||
btn_get_operational_account.grid(row=0, column=3, sticky = "nsew")
|
||||
btn_get_op_account_info = tk.Button(master=frm_form, text="Get Op Account Info",
|
||||
command = get_operational_account_info)
|
||||
btn_get_op_account_info.grid(row=1, column=3, sticky = "nsew")
|
||||
btn_op_send_xrp = tk.Button(master=frm_form, text="< Send XRP",
|
||||
command = operational_send_xrp)
|
||||
btn_op_send_xrp.grid(row=2, column = 3, sticky = "nsew")
|
||||
```
|
||||
|
||||
Start the application.
|
||||
|
||||
```python
|
||||
window.mainloop()
|
||||
```
|
||||
@@ -0,0 +1,608 @@
|
||||
---
|
||||
html: py-create-conditional-escrows.html
|
||||
parent: send-payments-using-python.html
|
||||
seo:
|
||||
description: Create, finish, or cancel condition-based escrow transactions.
|
||||
labels:
|
||||
- Accounts
|
||||
- Quickstart
|
||||
- Transaction Sending
|
||||
- XRP
|
||||
---
|
||||
# Create Conditional Escrows Using Python
|
||||
|
||||
This example shows how to:
|
||||
|
||||
1. Create escrow payments that become available when an account enters a fulfillment code.
|
||||
|
||||
2. Complete a conditional escrow transaction.
|
||||
|
||||
3. Cancel a conditional escrow transaction.
|
||||
|
||||
[](/docs/img/quickstart-py-conditional-escrow-1.png)
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Download and expand the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> archive.
|
||||
|
||||
You need the `cryptoconditions` module to generate your condition/fulfillment pair. You can install the module using [pip](https://pip.pypa.io/en/stable/).
|
||||
|
||||
In a terminal window, install the `cryptoconditions` module with this command:
|
||||
|
||||
```bash
|
||||
pip install cryptoconditions
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
### Get Test Accounts
|
||||
|
||||
To get test accounts:
|
||||
|
||||
1. Open and run `lesson9-conditional-escrow.py`.
|
||||
2. Get test accounts.
|
||||
1. If you have existing account seeds
|
||||
1. Paste Standby account seed in the **Standby Seed** field.
|
||||
2. Click **Get Standby Account**.
|
||||
3. Click **Get Standby Account Info**.
|
||||
4. Paste Operational account seed in the **Operational Seed** field.
|
||||
5. Click **Get Operational Account**.
|
||||
6. Click **Get Op Account Info**.
|
||||
2. If you do not have account seeds:
|
||||
1. Click **Get Standby Account**.
|
||||
2. Click **Get Standby Account Info**.
|
||||
3. Click **Get Operational Account**.
|
||||
4. Click **Get Op Account Info**.
|
||||
|
||||
[](/docs/img/quickstart-py-conditional-escrow-2.png)
|
||||
|
||||
#### Get a Condition and Fulfillment
|
||||
|
||||
Click **Get Condition** to generate a condition/fulfillment pair and populate the fields on the form. You can copy the values and store them in a text file for safe keeping.
|
||||
|
||||
[](/docs/img/quickstart-py-conditional-escrow-3.png)
|
||||
|
||||
### Create Conditional Escrow
|
||||
|
||||
<div align="center">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/IUfSX5RKahs?si=7kV0T2NTtqsZfpvX" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
When you create a conditional escrow, you need to specify the `Condition` value you generated above. You must also set a cancel date and time, after which the escrow is no longer available.
|
||||
|
||||
To create a conditional escrow:
|
||||
|
||||
1. Enter an **Amount** to transfer.
|
||||
2. Copy the **Operational Account** value.
|
||||
3. Paste it in the **Destination Account** field.
|
||||
4. Enter the **Escrow Cancel (seconds)** value.
|
||||
5. Click **Create Escrow**.
|
||||
6. Copy and save the _Sequence Number_ of the escrow called out in the **Standby Result** field.
|
||||
|
||||
The escrow is created on the XRP Ledger instance, reserving your requested XRP amount plus the transaction cost.
|
||||
|
||||
When you create an escrow, capture and save the _Sequence Number_ so that you can use it to finish the escrow transaction.
|
||||
|
||||
[](/docs/img/quickstart-py-conditional-escrow-4.png)
|
||||
|
||||
## Finish Conditional Escrow
|
||||
|
||||
Any account can finish the conditional escrow any time before the _Escrow Cancel_ time. Following on the example above, you can use the _Sequence Number_ to finish the transaction once the Escrow Cancel time has passed.
|
||||
|
||||
To finish a conditional escrow:
|
||||
|
||||
1. Paste the sequence number in the Operational account **Sequence Number** field.
|
||||
2. Enter the **Escrow Condition** value.
|
||||
3. Enter the **Escrow Fulfillment** code for the `Condition`.
|
||||
4. Copy the **Standby Account** value.
|
||||
5. Paste it into the **Escrow Owner** field.
|
||||
4. Click **Finish Conditional Escrow**.
|
||||
|
||||
The transaction completes and balances are updated for both the Standby and Operational accounts.
|
||||
|
||||
[](/docs/img/quickstart-py-conditional-escrow-5.png)
|
||||
|
||||
## Get Escrows
|
||||
|
||||
Click **Get Escrows** for either the Standby account or the Operational account to see their current list of escrows.
|
||||
|
||||
## Cancel Escrow
|
||||
|
||||
When the Escrow Cancel time passes, the escrow is no longer available to the recipient. The initiator of the escrow can reclaim the XRP, less the transaction fees. Any account can cancel an escrow once the cancel time has elapsed. Accounts that try to cancel the transaction prior to the **Escrow Cancel** time are charged the nominal transaction cost (about 10-15 drops), but the actual escrow cannot be cancelled until after the Escrow Cancel time.
|
||||
|
||||
## Oh No! I Forgot to Save the Sequence Number!
|
||||
|
||||
If you forget to save the sequence number, you can find it in the escrow transaction record.
|
||||
|
||||
1. Create a new escrow as described in [Create Conditional Escrow](#create-conditional-escrow), above.
|
||||
2. Click **Get Escrows** to get the escrow information.
|
||||
3. Copy the _PreviousTxnLgrSeq_ value from the results.
|
||||

|
||||
4. Paste the _PreviousTxnLgrSeq_ in the **Transaction to Look Up** field.
|
||||

|
||||
5. Click **Get Transaction**.
|
||||
6. Locate the _Sequence_ value in the results.
|
||||
|
||||

|
||||
|
||||
# Code Walkthrough
|
||||
|
||||
You can download the [Modular Tutorials](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> in the source repository for this website.
|
||||
|
||||
## mod9.py
|
||||
|
||||
Import dependencies.
|
||||
|
||||
```python
|
||||
import xrpl
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.wallet import Wallet
|
||||
from datetime import datetime
|
||||
from xrpl.models.transactions import EscrowCreate, EscrowFinish
|
||||
from os import urandom
|
||||
from cryptoconditions import PreimageSha256
|
||||
```
|
||||
|
||||
Create a global variable pointing to Testnet.
|
||||
|
||||
```python
|
||||
testnet_url = "https://s.altnet.rippletest.net:51234"
|
||||
```
|
||||
|
||||
### generate_condition
|
||||
|
||||
Generate the _condition_ and _fulfillment_ values for the escrow.
|
||||
|
||||
```python
|
||||
def generate_condition():
|
||||
```
|
||||
|
||||
Set a variable to 32 random bytes.
|
||||
|
||||
```python
|
||||
randy = urandom(32)
|
||||
```
|
||||
|
||||
Use the 32-byte random variable as the argument for the `PreimageSha256` functino.
|
||||
|
||||
```python
|
||||
fulfillment = PreimageSha256(preimage=randy)
|
||||
```
|
||||
|
||||
Return the binary condition and the binary serialized (fulfillment) value.
|
||||
|
||||
```python
|
||||
return (fulfillment.condition_binary.hex().upper(),
|
||||
fulfillment.serialize_binary().hex().upper())
|
||||
```
|
||||
|
||||
### add_seconds
|
||||
|
||||
Create a date in the Ripple epoch, adding the specified number of seconds.
|
||||
|
||||
```python
|
||||
def add_seconds(numOfSeconds):
|
||||
```
|
||||
|
||||
Create a new_date variable.
|
||||
|
||||
```python
|
||||
new_date = datetime.now()
|
||||
```
|
||||
|
||||
Convert the date to a Ripple time object.
|
||||
|
||||
```python
|
||||
if new_date != '':
|
||||
new_date = xrpl.utils.datetime_to_ripple_time(new_date)
|
||||
```
|
||||
|
||||
Add the requested seconds to the Ripple formatted date variable.
|
||||
|
||||
```python
|
||||
new_date = new_date + int(numOfSeconds)
|
||||
```
|
||||
|
||||
Return the result.
|
||||
|
||||
```python
|
||||
return new_date
|
||||
```
|
||||
|
||||
### create_conditional_escrow
|
||||
|
||||
You create conditional escrows using the same **EscrowCreate** model you used for a time-based escrow, but instead of a finish time, you provide a condition that must be met to complete the transaction.
|
||||
|
||||
Pass the _seed_ for the sending account, the _amount_ to hold in escrow, the _destination_ account to receive the escrow funds, the number of seconds until the escrow will _cancel_, and a _condition_ value that will be matched with a _fulfillment_ value to complete the escrow.
|
||||
|
||||
```python
|
||||
def create_conditional_escrow(seed, amount, destination, cancel, condition):
|
||||
```
|
||||
|
||||
Instantiate a wallet and connect to Testnet.
|
||||
|
||||
```python
|
||||
wallet=Wallet.from_seed(seed)
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Create a *cancel_date* variable, adding your specified number of seconds to the current Ripple epoch date.
|
||||
|
||||
```python
|
||||
cancel_date = add_seconds(cancel)
|
||||
```
|
||||
|
||||
Define the transaction with your provided values.
|
||||
|
||||
```python
|
||||
escrow_tx=xrpl.models.transactions.EscrowCreate(
|
||||
account=wallet.address,
|
||||
amount=amount,
|
||||
destination=destination,
|
||||
cancel_after=cancel_date,
|
||||
condition=condition
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and return the results.
|
||||
|
||||
```python
|
||||
reply=""
|
||||
try:
|
||||
response=xrpl.transaction.submit_and_wait(escrow_tx,client,wallet)
|
||||
reply=response.result
|
||||
except xrpl.transaction.XRPLReliableSubmissionException as e:
|
||||
reply=f"Submit failed: {e}"
|
||||
return reply
|
||||
```
|
||||
|
||||
### finish_conditional_escrow
|
||||
|
||||
At any time prior to the cancel date, the destination account can fulfill the escrow.
|
||||
|
||||
Pass the _seed_ for the receiving account, the _owner_ (sending account), the _sequence_ number for the escrow, the _condition_ value, and the matching _fulfillment_ value.
|
||||
|
||||
```python
|
||||
def finish_conditional_escrow(seed, owner, sequence, condition, fulfillment):
|
||||
```
|
||||
|
||||
Instantiate the account wallet and connect to Testnet.
|
||||
|
||||
```python
|
||||
wallet=Wallet.from_seed(seed)
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Define the **EscrowFinish** transaction, including both the condition and the fulfillment values.
|
||||
|
||||
```python
|
||||
finish_tx=xrpl.models.transactions.EscrowFinish(
|
||||
account=wallet.address,
|
||||
owner=owner,
|
||||
offer_sequence=int(sequence),
|
||||
condition=condition,
|
||||
fulfillment=fulfillment
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and report the results.
|
||||
|
||||
```python
|
||||
reply=""
|
||||
try:
|
||||
response=xrpl.transaction.submit_and_wait(finish_tx,client,wallet)
|
||||
reply=response.result
|
||||
except xrpl.transaction.XRPLReliableSubmissionException as e:
|
||||
reply=f"Submit failed: {e}"
|
||||
return reply
|
||||
```
|
||||
|
||||
## lesson9-conditional-escrow.py
|
||||
|
||||
This example builds on `lesson8-time-escrow.py` to reuse fields, buttons, and functions that apply to both time-based and conditional escrows. Updates are highlighted below.
|
||||
|
||||
```python
|
||||
import tkinter as tk
|
||||
import xrpl
|
||||
import json
|
||||
|
||||
from mod1 import get_account, get_account_info, send_xrp
|
||||
from mod8 import get_escrows, cancel_time_escrow, get_transaction
|
||||
```
|
||||
|
||||
Import new functions for conditional escrows from module 9.
|
||||
|
||||
```python
|
||||
from mod9 import create_conditional_escrow, finish_conditional_escrow, generate_condition
|
||||
|
||||
```
|
||||
|
||||
Add handlers for creating and finishing conditional escrows.
|
||||
|
||||
```python
|
||||
def get_condition():
|
||||
results = generate_condition()
|
||||
ent_standby_escrow_condition.delete(0, tk.END)
|
||||
ent_standby_escrow_condition.insert(0, results[0])
|
||||
ent_operational_escrow_fulfillment.delete(0, tk.END)
|
||||
ent_operational_escrow_fulfillment.insert(0, results[1])
|
||||
|
||||
def standby_create_conditional_escrow():
|
||||
results = create_conditional_escrow(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_amount.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_escrow_cancel.get(),
|
||||
ent_standby_escrow_condition.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_finish_conditional_escrow():
|
||||
results = finish_conditional_escrow(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_escrow_owner.get(),
|
||||
ent_operational_sequence_number.get(),
|
||||
ent_standby_escrow_condition.get(),
|
||||
ent_operational_escrow_fulfillment.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
## Mod 8 Handlers
|
||||
|
||||
def operational_get_escrows():
|
||||
results = get_escrows(ent_operational_account.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def standby_cancel_time_escrow():
|
||||
results = cancel_time_escrow(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_escrow_owner.get(),
|
||||
ent_standby_escrow_sequence_number.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_get_transaction():
|
||||
results = get_transaction(ent_operational_account.get(),
|
||||
ent_operational_look_up.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
## Mod 1 Handlers
|
||||
|
||||
def get_standby_account():
|
||||
new_wallet = get_account(ent_standby_seed.get())
|
||||
ent_standby_account.delete(0, tk.END)
|
||||
ent_standby_seed.delete(0, tk.END)
|
||||
ent_standby_account.insert(0, new_wallet.classic_address)
|
||||
ent_standby_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_standby_account_info():
|
||||
accountInfo = get_account_info(ent_standby_account.get())
|
||||
ent_standby_balance.delete(0, tk.END)
|
||||
ent_standby_balance.insert(0,accountInfo['Balance'])
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def standby_send_xrp():
|
||||
response = send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
|
||||
ent_standby_destination.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
def get_operational_account():
|
||||
new_wallet = get_account(ent_operational_seed.get())
|
||||
ent_operational_account.delete(0, tk.END)
|
||||
ent_operational_account.insert(0, new_wallet.classic_address)
|
||||
ent_operational_seed.delete(0, tk.END)
|
||||
ent_operational_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_operational_account_info():
|
||||
accountInfo = get_account_info(ent_operational_account.get())
|
||||
ent_operational_balance.delete(0, tk.END)
|
||||
ent_operational_balance.insert(0,accountInfo['Balance'])
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def operational_send_xrp():
|
||||
response = send_xrp(ent_operational_seed.get(),ent_operational_amount.get(),
|
||||
ent_operational_destination.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
```
|
||||
|
||||
Rename the window.
|
||||
|
||||
```python
|
||||
window = tk.Tk()
|
||||
window.title("Conditional Escrow Example")
|
||||
|
||||
# Form frame
|
||||
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
|
||||
frm_form.pack()
|
||||
|
||||
# Create the Label and Entry widgets for "Standby Account"
|
||||
lbl_standy_seed = tk.Label(master=frm_form, text="Standby Seed")
|
||||
ent_standby_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_account = tk.Label(master=frm_form, text="Standby Account")
|
||||
ent_standby_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standy_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_standby_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_standby_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_standby_balance = tk.Entry(master=frm_form, width=50)
|
||||
```
|
||||
|
||||
Add a field for the escrow condition.
|
||||
|
||||
```python
|
||||
lbl_standby_escrow_condition = tk.Label(master=frm_form, text="Escrow Condition")
|
||||
ent_standby_escrow_condition = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_cancel = tk.Label(master=frm_form, text="Escrow Cancel (seconds)")
|
||||
ent_standby_escrow_cancel = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_sequence_number = tk.Label(master=frm_form, text="Sequence Number")
|
||||
ent_standby_escrow_sequence_number = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_owner = tk.Label(master=frm_form, text="Escrow Owner")
|
||||
ent_standby_escrow_owner = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_results = tk.Label(master=frm_form, text="Results")
|
||||
text_standby_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
# Place fields in a grid.
|
||||
lbl_standy_seed.grid(row=0, column=0, sticky="e")
|
||||
ent_standby_seed.grid(row=0, column=1)
|
||||
lbl_standby_account.grid(row=2, column=0, sticky="e")
|
||||
ent_standby_account.grid(row=2, column=1)
|
||||
lbl_standy_amount.grid(row=3, column=0, sticky="e")
|
||||
ent_standby_amount.grid(row=3, column=1)
|
||||
lbl_standby_destination.grid(row=4, column=0, sticky="e")
|
||||
ent_standby_destination.grid(row=4, column=1)
|
||||
lbl_standby_balance.grid(row=5, column=0, sticky="e")
|
||||
ent_standby_balance.grid(row=5, column=1)
|
||||
```
|
||||
|
||||
Insert the condition field in the standby grid.
|
||||
|
||||
```python
|
||||
lbl_standby_escrow_condition.grid(row=6, column=0, sticky="e")
|
||||
ent_standby_escrow_condition.grid(row=6, column=1)
|
||||
lbl_standby_escrow_cancel.grid(row=7, column=0, sticky="e")
|
||||
ent_standby_escrow_cancel.grid(row=7, column=1)
|
||||
lbl_standby_escrow_sequence_number.grid(row=8, column=0, sticky="e")
|
||||
ent_standby_escrow_sequence_number.grid(row=8, column=1)
|
||||
lbl_standby_escrow_owner.grid(row=9, column=0, sticky="e")
|
||||
ent_standby_escrow_owner.grid(row=9, column=1)
|
||||
lbl_standby_results.grid(row=10, column=0, sticky="ne")
|
||||
text_standby_results.grid(row=10, column=1, sticky="nw")
|
||||
|
||||
###############################################
|
||||
## Operational Account ########################
|
||||
###############################################
|
||||
|
||||
# Create the Label and Entry widgets for "Operational Account"
|
||||
lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed")
|
||||
ent_operational_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_account = tk.Label(master=frm_form, text="Operational Account")
|
||||
ent_operational_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_operational_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_operational_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_operational_balance = tk.Entry(master=frm_form, width=50)
|
||||
```
|
||||
|
||||
Add a field for the escrow fulfillment value.
|
||||
|
||||
```python
|
||||
lbl_operational_escrow_fulfillment = tk.Label(master=frm_form, text="Escrow Fulfillment")
|
||||
ent_operational_escrow_fulfillment = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_sequence_number = tk.Label(master=frm_form, text="Sequence Number")
|
||||
ent_operational_sequence_number = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_escrow_owner=tk.Label(master=frm_form, text="Escrow Owner")
|
||||
ent_operational_escrow_owner=tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_look_up = tk.Label(master=frm_form, text="Transaction to Look Up")
|
||||
ent_operational_look_up = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_results = tk.Label(master=frm_form,text='Results')
|
||||
text_operational_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
|
||||
#Place the widgets in a grid
|
||||
lbl_operational_seed.grid(row=0, column=4, sticky="e")
|
||||
ent_operational_seed.grid(row=0, column=5, sticky="w")
|
||||
lbl_operational_account.grid(row=2,column=4, sticky="e")
|
||||
ent_operational_account.grid(row=2,column=5, sticky="w")
|
||||
lbl_operational_amount.grid(row=3, column=4, sticky="e")
|
||||
ent_operational_amount.grid(row=3, column=5, sticky="w")
|
||||
lbl_operational_destination.grid(row=4, column=4, sticky="e")
|
||||
ent_operational_destination.grid(row=4, column=5, sticky="w")
|
||||
lbl_operational_balance.grid(row=5, column=4, sticky="e")
|
||||
ent_operational_balance.grid(row=5, column=5, sticky="w")
|
||||
```
|
||||
|
||||
Insert the **Fulfillment** field in the operational grid, moving the other fields down so as to align the **Condition** and **Fulfillment** fields horizontally.
|
||||
|
||||
```python
|
||||
lbl_operational_escrow_fulfillment.grid(row=6, column=4, sticky="e")
|
||||
ent_operational_escrow_fulfillment.grid(row=6, column=5, sticky="w")
|
||||
lbl_operational_sequence_number.grid(row=7, column=4, sticky="e")
|
||||
ent_operational_sequence_number.grid(row=7, column=5, sticky="w")
|
||||
lbl_operational_escrow_owner.grid(row=8, column=4, sticky="e")
|
||||
ent_operational_escrow_owner.grid(row=8, column=5, sticky="w")
|
||||
lbl_operational_look_up.grid(row=9, column=4, sticky="e")
|
||||
ent_operational_look_up.grid(row=9, column=5, sticky="w")
|
||||
lbl_operational_results.grid(row=10, column=4, sticky="ne")
|
||||
text_operational_results.grid(row=10, column=5, sticky="nw")
|
||||
|
||||
#############################################
|
||||
## Buttons ##################################
|
||||
#############################################
|
||||
|
||||
# Create the Get Standby Account Buttons
|
||||
btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account",
|
||||
command = get_standby_account)
|
||||
btn_get_standby_account.grid(row = 0, column = 2, sticky = "nsew")
|
||||
btn_get_standby_account_info = tk.Button(master=frm_form,
|
||||
text="Get Standby Account Info",
|
||||
command = get_standby_account_info)
|
||||
btn_get_standby_account_info.grid(row = 1, column = 2, sticky = "nsew")
|
||||
btn_standby_send_xrp = tk.Button(master=frm_form, text="Send XRP >",
|
||||
command = standby_send_xrp)
|
||||
btn_standby_send_xrp.grid(row = 2, column = 2, sticky = "nsew")
|
||||
```
|
||||
|
||||
Add a **Create Conditional Escrow** button to the Standby grid.
|
||||
|
||||
```python
|
||||
btn_standby_get_condition = tk.Button(master=frm_form, text="Get Condition",
|
||||
command = get_condition)
|
||||
btn_standby_get_condition.grid(row=4, column=2, sticky="nsew")
|
||||
btn_standby_create_escrow = tk.Button(master=frm_form, text="Create Conditional Escrow",
|
||||
command = standby_create_conditional_escrow)
|
||||
btn_standby_create_escrow.grid(row=5, column = 2, sticky="nsew")
|
||||
btn_standby_cancel_escrow = tk.Button(master=frm_form, text="Cancel Escrow",
|
||||
command = standby_cancel_time_escrow)
|
||||
btn_standby_cancel_escrow.grid(row=6,column = 2, sticky="nsew")
|
||||
|
||||
# Create the Operational Account Buttons
|
||||
btn_get_operational_account = tk.Button(master=frm_form,
|
||||
text="Get Operational Account",
|
||||
command = get_operational_account)
|
||||
btn_get_operational_account.grid(row=0, column=3, sticky = "nsew")
|
||||
btn_get_op_account_info = tk.Button(master=frm_form, text="Get Op Account Info",
|
||||
command = get_operational_account_info)
|
||||
btn_get_op_account_info.grid(row=1, column=3, sticky = "nsew")
|
||||
btn_op_send_xrp = tk.Button(master=frm_form, text="< Send XRP",
|
||||
command = operational_send_xrp)
|
||||
btn_op_send_xrp.grid(row=2, column = 3, sticky = "nsew")
|
||||
```
|
||||
|
||||
Add a **Finish Escrow** button to the operational grid.
|
||||
|
||||
```python
|
||||
btn_op_finish_escrow = tk.Button(master=frm_form, text="Finish Escrow",
|
||||
command = operational_finish_conditional_escrow)
|
||||
btn_op_finish_escrow.grid(row = 4, column = 3, sticky="nsew")
|
||||
btn_op_get_escrows = tk.Button(master=frm_form, text="Get Escrows",
|
||||
command = operational_get_escrows)
|
||||
btn_op_get_escrows.grid(row = 5, column = 3, sticky="nsew")
|
||||
btn_op_get_transaction = tk.Button(master=frm_form, text="Get Transaction",
|
||||
command = operational_get_transaction)
|
||||
btn_op_get_transaction.grid(row = 6, column = 3, sticky = "nsew")
|
||||
|
||||
# Start the application
|
||||
window.mainloop()
|
||||
```
|
||||
@@ -0,0 +1,638 @@
|
||||
---
|
||||
html: py-create-time-based-escrows.html
|
||||
parent: send-payments-using-python.html
|
||||
seo:
|
||||
description: Create, finish, or cancel time-based escrow transactions.
|
||||
labels:
|
||||
- Accounts
|
||||
- Quickstart
|
||||
- Transaction Sending
|
||||
- XRP
|
||||
---
|
||||
# Create Time-based Escrows Using Python
|
||||
|
||||
This example shows how to:
|
||||
|
||||
|
||||
1. Create escrow payments that become available at a specified time and expire at a specified time.
|
||||
2. Finish an escrow payment.
|
||||
3. Retrieve information on escrows attached to an account.
|
||||
3. Cancel an escrow payment and return the XRP to the sending account.
|
||||
|
||||
|
||||
[](/docs/img/quickstart-py-escrow1.png)
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Download the [Python Modular Code Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} -->.
|
||||
|
||||
## Usage
|
||||
|
||||
To get test accounts:
|
||||
|
||||
1. Open `lesson8-time-escrow.py.`
|
||||
2. Get test accounts.
|
||||
1. If you have existing account seeds
|
||||
1. Paste Standby account seed in the **Standby Seed** field.
|
||||
2. Click **Get Standby Account**.
|
||||
3. Click **Get Standby Account Info**.
|
||||
4. Paste Operational account seed in the **Operational Seed** field.
|
||||
5. Click **Get Operational Account**.
|
||||
6. Click **Get Op Account Info**.
|
||||
2. If you do not have account seeds:
|
||||
1. Click **Get Standby Account**.
|
||||
2. Click **Get Standby Account Info**.
|
||||
3. Click **Get Operational Account**.
|
||||
4. Click **Get Op Account Info**.
|
||||
|
||||
[](/docs/img/quickstart-py-escrow2.png)
|
||||
|
||||
## Create Escrow
|
||||
|
||||
<div align="center">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/L_2sKokW5E4?si=6r9vn4ojr6b42H2E" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
You can create a time-based escrow with a minimum time to finish the escrow and a cancel time after which the funds in escrow are no longer available to the recipient. This is a test harness: while a practical scenario might express time in days or weeks, this form lets you set the finish and cancel times in seconds so that you can quickly run through a variety of scenarios. (There are 86,400 seconds in a day, if you want to play with longer term escrows.)
|
||||
|
||||
To create a time-based escrow:
|
||||
|
||||
1. Enter an **Amount** to transfer. For example, _100000000_.
|
||||
2. Copy the **Operational Account** value.
|
||||
3. Paste it in the **Destination Account** field.
|
||||
4. Set the **Escrow Finish (seconds)** value. For example, enter _10_.
|
||||
5. Set the **Escrow Cancel (seconds)** value. For example, enter _120_.
|
||||
6. Click **Create Time-based Escrow**.
|
||||
7. Copy the _Sequence Number_ of the escrow called out in the **Standby Result** field.
|
||||
|
||||
The escrow is created on the XRP Ledger instance, reserving 100 XRP plus the transaction cost and reserve requirements. When you create an escrow, capture and save the **Sequence Number** so that you can use it to finish the escrow transaction.
|
||||
|
||||
[](/docs/img/quickstart-py-escrow3.png)
|
||||
|
||||
## Finish Escrow
|
||||
|
||||
The recipient of the XRP held in escrow can finish the transaction any time within the time window after the Escrow Finish date and time but before the Escrow Cancel date and time. Following on the example above, you can use the _Sequence Number_ to finish the transaction once 10 seconds have passed.
|
||||
|
||||
To finish a time-based escrow:
|
||||
|
||||
1. Paste the sequence number in the Operational account **Sequence Number** field.
|
||||
2. Click **Finish Escrow**.
|
||||
3. Click **Get Op Account Info** and **Get Standby Account Info** to update their **XRP Balance**.
|
||||
|
||||
The transaction completes and balances are updated for both the Standby and Operational accounts.
|
||||
|
||||
[](/docs/img/quickstart-py-escrow4.png)
|
||||
|
||||
## Get Escrows
|
||||
|
||||
Click **Get Escrows** to see the current list of escrows for the Operational account. If you click the button now, there are no escrows at the moment.
|
||||
|
||||
For the purposes of this tutorial, you can follow the steps in [Create Escrow](#create-escrow), above, to create a new escrow transaction that you can then look up. Remember to capture the _Sequence Number_ from the transaction results.
|
||||
|
||||
[](/docs/img/quickstart-py-escrow5.png)
|
||||
|
||||
|
||||
## Cancel Escrow
|
||||
|
||||
When the Escrow Cancel time passes, the escrow is no longer available to the recipient. The initiator of the escrow can reclaim the XRP. If you try to cancel the transaction prior to the **Escrow Cancel** time, you are charged for the transaction, but the actual escrow cannot be cancelled until the time limit is reached.
|
||||
|
||||
You can wait the allotted time for the escrow you created in the previous step, then use it to try out the **Cancel Escrow** button
|
||||
|
||||
To cancel an expired escrow:
|
||||
|
||||
1. Enter the sequence number in the Standby **Sequence Number** field.
|
||||
2. Copy the **Standby Account** value and paste it in the **Escrow Owner** field.
|
||||
2. Click **Cancel Time-based Escrow**.
|
||||
|
||||
The funds are returned to the Standby account, less the initial transaction fee.
|
||||
|
||||
[](/docs/img/quickstart-py-escrow6.png)
|
||||
|
||||
## Oh No! I Forgot to Save the Sequence Number!
|
||||
|
||||
If you forget to save the sequence number, you can find it in the escrow transaction record.
|
||||
|
||||
1. Create a new escrow as described in [Create Escrow](#create-escrow), above.
|
||||
2. Click **Get Escrows** to get the escrow information.
|
||||
3. Copy the _PreviousTxnLgrSeq_ value from the results.
|
||||

|
||||
4. Paste the _PreviousTxnLgrSeq_ in the **Transaction to Look Up** field.
|
||||
5. Click **Get Transaction**.
|
||||
6. Locate the _Sequence_ value in the results.
|
||||

|
||||
|
||||
# Code Walkthrough
|
||||
|
||||
You can download the [Python Modular Code Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> in the source repository for this website.
|
||||
|
||||
## mod8.py
|
||||
|
||||
This example can be used with the XRP Ledger network, _Testnet_. You can update the code to choose different or additional XRP Ledger networks.
|
||||
|
||||
### add_seconds
|
||||
|
||||
This function accomplishes two things. It creates a new date object and adds the number of seconds taken from a form field. Then, it adjusts the date from the Python format to the XRP Ledger format.
|
||||
|
||||
Provide the _numOfSeconds_ argument.
|
||||
|
||||
```python
|
||||
def add_seconds(numOfSeconds):
|
||||
```
|
||||
|
||||
Create a new Python date object.
|
||||
|
||||
```python
|
||||
new_date = datetime.now()
|
||||
```
|
||||
|
||||
Convert the date variable for the Ripple epoch.
|
||||
|
||||
```python
|
||||
if new_date != '':
|
||||
new_date = xrpl.utils.datetime_to_ripple_time(new_date)
|
||||
```
|
||||
|
||||
Add your seconds to the date.
|
||||
|
||||
```python
|
||||
new_date = new_date + int(numOfSeconds)
|
||||
```
|
||||
|
||||
Return the resulting date value.
|
||||
|
||||
```python
|
||||
return new_date
|
||||
```
|
||||
|
||||
### create_time_escrow
|
||||
|
||||
Call the create_time_escrow function, passing the _seed_, _amount_, _destination_, _finish_ interval, and _cancel_ interval.
|
||||
|
||||
```python
|
||||
def create_time_escrow(seed, amount, destination, finish, cancel):
|
||||
```
|
||||
|
||||
Get the client wallet.
|
||||
|
||||
```python
|
||||
wallet=Wallet.from_seed(seed)
|
||||
```
|
||||
|
||||
Connect to Testnet.
|
||||
|
||||
```python
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Create variables for the finish and cancel dates using the add_seconds function.
|
||||
|
||||
```python
|
||||
finish_date = add_seconds(finish)
|
||||
cancel_date = add_seconds(cancel)
|
||||
```
|
||||
|
||||
Define the **EscrowCreate** transaction.
|
||||
|
||||
```python
|
||||
escrow_tx=xrpl.models.transactions.EscrowCreate(
|
||||
account=wallet.address,
|
||||
amount=amount,
|
||||
destination=destination,
|
||||
finish_after=finish_date,
|
||||
cancel_after=cancel_date
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and report the results.
|
||||
|
||||
```python
|
||||
reply=""
|
||||
try:
|
||||
response=xrpl.transaction.submit_and_wait(escrow_tx,client,wallet)
|
||||
reply=response.result
|
||||
except xrpl.transaction.XRPLReliableSubmissionException as e:
|
||||
reply=f"Submit failed: {e}"
|
||||
return reply
|
||||
```
|
||||
|
||||
### finish_time_escrow
|
||||
|
||||
Pass the operational account _seed_, escrow _owner_ (in these examples, the standby address), and the escrow _sequence_ number.
|
||||
|
||||
```python
|
||||
def finish_time_escrow(seed, owner, sequence):
|
||||
```
|
||||
|
||||
Instantiate the wallet and client.
|
||||
|
||||
```python
|
||||
wallet=Wallet.from_seed(seed)
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Define the **EscrowFinish** transaction.
|
||||
|
||||
```python
|
||||
finish_tx=xrpl.models.transactions.EscrowFinish(
|
||||
account=wallet.address,
|
||||
owner=owner,
|
||||
offer_sequence=int(sequence)
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and report the results.
|
||||
|
||||
```python
|
||||
reply=""
|
||||
try:
|
||||
response=xrpl.transaction.submit_and_wait(finish_tx,client,wallet)
|
||||
reply=response.result
|
||||
except xrpl.transaction.XRPLReliableSubmissionException as e:
|
||||
reply=f"Submit failed: {e}"
|
||||
return reply
|
||||
```
|
||||
|
||||
### get_escrows
|
||||
|
||||
This request only requires the _account_ argument.
|
||||
|
||||
```python
|
||||
def get_escrows(account):
|
||||
```
|
||||
|
||||
Since this is a request, there's no need to sign in with an account to perform the query. You can just instantiate a client on Testnet.
|
||||
|
||||
```python
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Define the **AccountObjects** request, specifying objects of type _escrow_.
|
||||
|
||||
```python
|
||||
acct_escrows=AccountObjects(
|
||||
account=account,
|
||||
ledger_index="validated",
|
||||
type="escrow"
|
||||
)
|
||||
```
|
||||
|
||||
Submit the request and return the results.
|
||||
|
||||
```python
|
||||
response=client.request(acct_escrows)
|
||||
return response.result
|
||||
```
|
||||
|
||||
### cancel_time_escrows
|
||||
|
||||
Pass the issuer account _seed_, the _owner_ account, and the escrow _sequence_ number.
|
||||
|
||||
```python
|
||||
def cancel_time_escrow(seed, owner, sequence):
|
||||
```
|
||||
|
||||
Get the wallet and instantiate a client on _Testnet_.
|
||||
|
||||
```python
|
||||
wallet=Wallet.from_seed(seed)
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Define the cancel transaction
|
||||
|
||||
```python
|
||||
cancel_tx=xrpl.models.transactions.EscrowCancel(
|
||||
account=wallet.address,
|
||||
owner=owner,
|
||||
offer_sequence=int(sequence)
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and report the results
|
||||
|
||||
```python
|
||||
reply=""
|
||||
try:
|
||||
response=xrpl.transaction.submit_and_wait(cancel_tx,client,wallet)
|
||||
reply=response.result
|
||||
except xrpl.transaction.XRPLReliableSubmissionException as e:
|
||||
reply=f"Submit failed: {e}"
|
||||
return reply
|
||||
```
|
||||
|
||||
### get_transaction
|
||||
|
||||
Pass the requesting account number and the previous transaction ledger sequence number.
|
||||
|
||||
```python
|
||||
def get_transaction(account, ledger_index):
|
||||
```
|
||||
|
||||
Create a client instance.
|
||||
|
||||
```python
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Create the **AccountTx** request.
|
||||
|
||||
```python
|
||||
tx_info=AccountTx(
|
||||
account=account,
|
||||
ledger_index=int(ledger_index)
|
||||
)
|
||||
```
|
||||
|
||||
Send the request and report the results.
|
||||
|
||||
```python
|
||||
response=client.request(tx_info)
|
||||
return response.result
|
||||
```
|
||||
|
||||
## lesson8-time-escrow.py
|
||||
|
||||
This module builds on `lesson1-send-xrp.py`. Changes are noted below.
|
||||
|
||||
```python
|
||||
import tkinter as tk
|
||||
import xrpl
|
||||
import json
|
||||
|
||||
from mod1 import get_account, get_account_info, send_xrp
|
||||
```
|
||||
|
||||
Import new functions from mod8.py.
|
||||
|
||||
```python
|
||||
from mod8 import create_time_escrow, finish_time_escrow, get_escrows, cancel_time_escrow, get_transaction
|
||||
```
|
||||
|
||||
Module 8 Handlers
|
||||
|
||||
```python
|
||||
def standby_create_time_escrow():
|
||||
results = create_time_escrow(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_amount.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_escrow_finish.get(),
|
||||
ent_standby_escrow_cancel.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_finish_time_escrow():
|
||||
results = finish_time_escrow(
|
||||
ent_operational_seed.get(),
|
||||
ent_operational_escrow_owner.get(),
|
||||
ent_operational_sequence_number.get()
|
||||
)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_get_escrows():
|
||||
results = get_escrows(ent_operational_account.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def standby_cancel_time_escrow():
|
||||
results = cancel_time_escrow(
|
||||
ent_standby_seed.get(),
|
||||
ent_standby_escrow_owner.get(),
|
||||
ent_standby_escrow_sequence_number.get()
|
||||
)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
def operational_get_transaction():
|
||||
results = get_transaction(ent_operational_account.get(),
|
||||
ent_operational_look_up.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
## Mod 1 Handlers
|
||||
|
||||
def get_standby_account():
|
||||
new_wallet = get_account(ent_standby_seed.get())
|
||||
ent_standby_account.delete(0, tk.END)
|
||||
ent_standby_seed.delete(0, tk.END)
|
||||
ent_standby_account.insert(0, new_wallet.classic_address)
|
||||
ent_standby_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_standby_account_info():
|
||||
accountInfo = get_account_info(ent_standby_account.get())
|
||||
ent_standby_balance.delete(0, tk.END)
|
||||
ent_standby_balance.insert(0,accountInfo['Balance'])
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def standby_send_xrp():
|
||||
response = send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
|
||||
ent_standby_destination.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
def get_operational_account():
|
||||
new_wallet = get_account(ent_operational_seed.get())
|
||||
ent_operational_account.delete(0, tk.END)
|
||||
ent_operational_account.insert(0, new_wallet.classic_address)
|
||||
ent_operational_seed.delete(0, tk.END)
|
||||
ent_operational_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_operational_account_info():
|
||||
accountInfo = get_account_info(ent_operational_account.get())
|
||||
ent_operational_balance.delete(0, tk.END)
|
||||
ent_operational_balance.insert(0,accountInfo['Balance'])
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def operational_send_xrp():
|
||||
response = send_xrp(ent_operational_seed.get(),ent_operational_amount.get(),
|
||||
ent_operational_destination.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
# Create a new window with the title "Quickstart Module 1"
|
||||
window = tk.Tk()
|
||||
window.title("Time-based Escrow Example")
|
||||
|
||||
# Form frame
|
||||
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
|
||||
frm_form.pack()
|
||||
|
||||
# Create the Label and Entry widgets for "Standby Account"
|
||||
lbl_standy_seed = tk.Label(master=frm_form, text="Standby Seed")
|
||||
ent_standby_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_account = tk.Label(master=frm_form, text="Standby Account")
|
||||
ent_standby_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standy_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_standby_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_standby_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_standby_balance = tk.Entry(master=frm_form, width=50)
|
||||
```
|
||||
|
||||
Add supporting fields for escrow commands.
|
||||
|
||||
```python
|
||||
lbl_standby_escrow_finish = tk.Label(master=frm_form, text="Escrow Finish (seconds)")
|
||||
ent_standby_escrow_finish = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_cancel = tk.Label(master=frm_form, text="Escrow Cancel (seconds)")
|
||||
ent_standby_escrow_cancel = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_sequence_number = tk.Label(master=frm_form, text="Sequence Number")
|
||||
ent_standby_escrow_sequence_number = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_escrow_owner = tk.Label(master=frm_form, text="Escrow Owner")
|
||||
ent_standby_escrow_owner = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_results = tk.Label(master=frm_form, text="Results")
|
||||
text_standby_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
# Place fields in a grid.
|
||||
lbl_standy_seed.grid(row=0, column=0, sticky="e")
|
||||
ent_standby_seed.grid(row=0, column=1)
|
||||
lbl_standby_account.grid(row=2, column=0, sticky="e")
|
||||
ent_standby_account.grid(row=2, column=1)
|
||||
lbl_standy_amount.grid(row=3, column=0, sticky="e")
|
||||
ent_standby_amount.grid(row=3, column=1)
|
||||
lbl_standby_destination.grid(row=4, column=0, sticky="e")
|
||||
ent_standby_destination.grid(row=4, column=1)
|
||||
lbl_standby_balance.grid(row=5, column=0, sticky="e")
|
||||
ent_standby_balance.grid(row=5, column=1)
|
||||
```
|
||||
|
||||
Add supporting fields for escrow to the standby side of the form.
|
||||
|
||||
```python
|
||||
lbl_standby_escrow_finish.grid(row=6, column=0, sticky="e")
|
||||
ent_standby_escrow_finish.grid(row=6, column=1)
|
||||
lbl_standby_escrow_cancel.grid(row=7, column=0, sticky="e")
|
||||
ent_standby_escrow_cancel.grid(row=7, column=1)
|
||||
lbl_standby_escrow_sequence_number.grid(row=8, column=0, sticky="e")
|
||||
ent_standby_escrow_sequence_number.grid(row=8, column=1)
|
||||
lbl_standby_escrow_owner.grid(row=9, column=0, sticky="e")
|
||||
ent_standby_escrow_owner.grid(row=9, column=1)
|
||||
lbl_standby_results.grid(row=10, column=0, sticky="ne")
|
||||
text_standby_results.grid(row=10, column=1, sticky="nw")
|
||||
|
||||
###############################################
|
||||
## Operational Account ########################
|
||||
###############################################
|
||||
|
||||
# Create the Label and Entry widgets for "Operational Account"
|
||||
lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed")
|
||||
ent_operational_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_account = tk.Label(master=frm_form, text="Operational Account")
|
||||
ent_operational_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_operational_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_operational_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_operational_balance = tk.Entry(master=frm_form, width=50)
|
||||
```
|
||||
|
||||
Define escrow supporting fields for the operational side of the form.
|
||||
|
||||
```python
|
||||
lbl_operational_sequence_number = tk.Label(master=frm_form, text="Sequence Number")
|
||||
ent_operational_sequence_number = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_escrow_owner=tk.Label(master=frm_form, text="Escrow Owner")
|
||||
ent_operational_escrow_owner=tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_look_up = tk.Label(master=frm_form, text="Transaction to Look Up")
|
||||
ent_operational_look_up = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_results = tk.Label(master=frm_form,text='Results')
|
||||
text_operational_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
#Place the widgets in a grid
|
||||
lbl_operational_seed.grid(row=0, column=4, sticky="e")
|
||||
ent_operational_seed.grid(row=0, column=5, sticky="w")
|
||||
lbl_operational_account.grid(row=2,column=4, sticky="e")
|
||||
ent_operational_account.grid(row=2,column=5, sticky="w")
|
||||
lbl_operational_amount.grid(row=3, column=4, sticky="e")
|
||||
ent_operational_amount.grid(row=3, column=5, sticky="w")
|
||||
lbl_operational_destination.grid(row=4, column=4, sticky="e")
|
||||
ent_operational_destination.grid(row=4, column=5, sticky="w")
|
||||
lbl_operational_balance.grid(row=5, column=4, sticky="e")
|
||||
ent_operational_balance.grid(row=5, column=5, sticky="w")
|
||||
```
|
||||
|
||||
Add supporting fields for escrow to the operational side of the form.
|
||||
|
||||
```python
|
||||
lbl_operational_sequence_number.grid(row=6, column=4, sticky="e")
|
||||
ent_operational_sequence_number.grid(row=6, column=5, sticky="w")
|
||||
lbl_operational_escrow_owner.grid(row=7, column=4, sticky="e")
|
||||
ent_operational_escrow_owner.grid(row=7, column=5, sticky="w")
|
||||
lbl_operational_look_up.grid(row=8, column=4, sticky="e")
|
||||
ent_operational_look_up.grid(row=8, column=5, sticky="w")
|
||||
lbl_operational_results.grid(row=10, column=4, sticky="ne")
|
||||
text_operational_results.grid(row=10, column=5, sticky="nw")
|
||||
|
||||
#############################################
|
||||
## Buttons ##################################
|
||||
#############################################
|
||||
|
||||
# Create the Get Standby Account Buttons
|
||||
btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account",
|
||||
command = get_standby_account)
|
||||
btn_get_standby_account.grid(row = 0, column = 2, sticky = "nsew")
|
||||
btn_get_standby_account_info = tk.Button(master=frm_form,
|
||||
text="Get Standby Account Info",
|
||||
command = get_standby_account_info)
|
||||
btn_get_standby_account_info.grid(row = 1, column = 2, sticky = "nsew")
|
||||
btn_standby_send_xrp = tk.Button(master=frm_form, text="Send XRP >",
|
||||
command = standby_send_xrp)
|
||||
btn_standby_send_xrp.grid(row = 2, column = 2, sticky = "nsew")
|
||||
```
|
||||
|
||||
Add buttons for escrow activity on the standby side of the form.
|
||||
|
||||
```python
|
||||
btn_standby_create_escrow = tk.Button(master=frm_form, text="Create Time-based Escrow",
|
||||
command = standby_create_time_escrow)
|
||||
btn_standby_create_escrow.grid(row = 4, column = 2, sticky="nsew")
|
||||
btn_standby_cancel_escrow = tk.Button(master=frm_form, text="Cancel Time-based Escrow",
|
||||
command = standby_cancel_time_escrow)
|
||||
btn_standby_cancel_escrow.grid(row=5,column = 2, sticky="nsew")
|
||||
|
||||
# Create the Operational Account Buttons
|
||||
btn_get_operational_account = tk.Button(master=frm_form,
|
||||
text="Get Operational Account",
|
||||
command = get_operational_account)
|
||||
btn_get_operational_account.grid(row=0, column=3, sticky = "nsew")
|
||||
btn_get_op_account_info = tk.Button(master=frm_form, text="Get Op Account Info",
|
||||
command = get_operational_account_info)
|
||||
btn_get_op_account_info.grid(row=1, column=3, sticky = "nsew")
|
||||
btn_op_send_xrp = tk.Button(master=frm_form, text="< Send XRP",
|
||||
command = operational_send_xrp)
|
||||
btn_op_send_xrp.grid(row=2, column = 3, sticky = "nsew")
|
||||
```
|
||||
|
||||
Add buttons to support escrow activity on the operational side of the form.
|
||||
|
||||
```python
|
||||
btn_op_finish_escrow = tk.Button(master=frm_form, text="Finish Escrow",
|
||||
command = operational_finish_time_escrow)
|
||||
btn_op_finish_escrow.grid(row = 4, column = 3, sticky="nsew")
|
||||
btn_op_finish_escrow = tk.Button(master=frm_form, text="Get Escrows",
|
||||
command = operational_get_escrows)
|
||||
btn_op_finish_escrow.grid(row = 5, column = 3, sticky="nsew")
|
||||
btn_op_get_transaction = tk.Button(master=frm_form, text="Get Transaction",
|
||||
command = operational_get_transaction)
|
||||
btn_op_get_transaction.grid(row = 6, column = 3, sticky = "nsew")
|
||||
|
||||
|
||||
# Start the application
|
||||
window.mainloop()
|
||||
```
|
||||
@@ -0,0 +1,580 @@
|
||||
---
|
||||
html: py-create-trustline-send-currency.html
|
||||
parent: send-payments-using-python.html
|
||||
seo:
|
||||
description: Create trust lines and send currency.
|
||||
labels:
|
||||
- Cross-Currency
|
||||
- Payments
|
||||
- Quickstart
|
||||
- Tokens
|
||||
---
|
||||
# Create Trust Line and Send Currency Using Python
|
||||
|
||||
This example shows how to:
|
||||
|
||||
1. Configure accounts to allow transfer of funds to third party accounts.
|
||||
2. Set a currency type for transactions.
|
||||
3. Create a trust line between the standby account and the operational account.
|
||||
4. Send issued currency between accounts.
|
||||
5. Display account balances for all currencies.
|
||||
|
||||
[](/docs/img/quickstart-py5.png)
|
||||
|
||||
You can download the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> archive to try each of the samples in your own browser.
|
||||
|
||||
**Note:** Without the Quickstart Samples, you will not be able to try the examples that follow.
|
||||
|
||||
## Usage
|
||||
|
||||
Open the Quickstart window and get accounts:
|
||||
|
||||
1. Open and run `lesson2-send-currency.py`.
|
||||
2. Get test accounts.
|
||||
1. If you have existing account seeds
|
||||
1. Paste account seeds in the **Seeds** field.
|
||||
2. Click **Get Accounts from Seeds**.
|
||||
2. If you do not have account seeds:
|
||||
1. Click **Get New Standby Account**.
|
||||
2. Click **Get New Operational Account**.
|
||||
|
||||
## Create Trust Line
|
||||
|
||||
<div align="center">
|
||||
<iframe width="560" height="315" src="https://www.youtube.com/embed/6KWP0PV6J8Y?si=SSxFGrvfTo6pOPLD" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
|
||||
</div>
|
||||
|
||||
|
||||
To create a trust line between accounts:
|
||||
|
||||
1. Enter a [currency code](https://www.iban.com/currency-codes) in the **Currency** field.
|
||||
2. Enter the maximum transfer limit in the **Amount** field.
|
||||
3. Enter the destination account value in the **Destination** field.
|
||||
4. Click **Create Trust Line**.
|
||||
|
||||
[](/docs/img/quickstart-py6.png)
|
||||
|
||||
## Send an Issued Currency Token
|
||||
|
||||
To transfer an issued currency token, once you have created a trust line:
|
||||
|
||||
1. Enter the **Amount**.
|
||||
2. Enter the **Destination**.
|
||||
3. Enter the **Currency** type.
|
||||
4. Click **Send Currency**.
|
||||
|
||||
[](/docs/img/quickstart-py7.png)
|
||||
|
||||
### Configure Account
|
||||
|
||||
When transferring fiat currency, the actual transfer of funds is not simultaneous, as it is with XRP. If currency is transferred to a third party for a different currency, there can be a devaluation of the currency that impacts the originating account. To avoid this situation, this up and down valuation of currency, known as _rippling_, is not allowed by default. Currency transferred from one account can only be transferred back to the same account. To enable currency transfer to third parties, you need to set the `rippleDefault` value to true. The Token Test Harness provides a checkbox to enable or disable rippling.
|
||||
|
||||
To enable rippling:
|
||||
|
||||
1. Select the **Allow Rippling** checkbox.
|
||||
2. Click **Configure Account**.
|
||||
|
||||
Verify the setting by looking for the _Set Flag_ value in the response, which should show a flag setting of _8_.
|
||||
|
||||
[](/docs/img/quickstart-py8.png)
|
||||
|
||||
To disable rippling:
|
||||
|
||||
1. Deselect the **Allow Rippling** checkbox.
|
||||
2. Click **Configure Account**.
|
||||
|
||||
Verify the setting by looking for the _Clear Flag_ value in the response, which shold show a flag setting of _8_.
|
||||
|
||||
[](/docs/img/quickstart-py9.png)
|
||||
|
||||
# Code Walkthrough
|
||||
|
||||
You can download the [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} --> archive to try each of the samples.
|
||||
|
||||
## mod2.py
|
||||
|
||||
Module 2 provides the logic for creating trust lines and sending issued currency tokens between accounts.
|
||||
|
||||
Import dependencies and set the `testnet_url`.
|
||||
|
||||
```python
|
||||
import xrpl
|
||||
from xrpl.clients import JsonRpcClient
|
||||
from xrpl.wallet import Wallet
|
||||
|
||||
testnet_url = "https://s.altnet.rippletest.net:51234"
|
||||
```
|
||||
|
||||
### Create Trust Line
|
||||
|
||||
Pass the wallet seed, the issuer account, the currency code, and the maximum amount of currency to send.
|
||||
|
||||
```python
|
||||
def create_trust_line(seed, issuer, currency, amount):
|
||||
"""create_trust_line"""
|
||||
```
|
||||
|
||||
Get the wallet and a new client instance.
|
||||
|
||||
```python
|
||||
receiving_wallet = Wallet.from_seed(seed)
|
||||
client = JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Define the `TrustSet` transaction.
|
||||
|
||||
```python
|
||||
trustline_tx=xrpl.models.transactions.TrustSet(
|
||||
account=receiving_wallet.address,
|
||||
limit_amount=xrpl.models.amounts.IssuedCurrencyAmount(
|
||||
currency=currency,
|
||||
issuer=issuer,
|
||||
value=int(amount)
|
||||
)
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction to the XRP Ledger.
|
||||
|
||||
```python
|
||||
response = xrpl.transaction.submit_and_wait(trustline_tx,
|
||||
client, receiving_wallet)
|
||||
```
|
||||
|
||||
Return the results.
|
||||
|
||||
```python
|
||||
return response.result
|
||||
```
|
||||
## send_currency
|
||||
|
||||
Send currency to another account based on the sender wallet, destination account, the currency type, and the amount of the currency.
|
||||
|
||||
```python
|
||||
def send_currency(seed, destination, currency, amount):
|
||||
"""send_currency"""
|
||||
```
|
||||
|
||||
Get the sending wallet and a client instance on Testnet.
|
||||
|
||||
```python
|
||||
sending_wallet=Wallet.from_seed(seed)
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Define the payment transaction. The amount requires further description to identify the type of currency and issuer.
|
||||
|
||||
```python
|
||||
send_currency_tx=xrpl.models.transactions.Payment(
|
||||
account=sending_wallet.address,
|
||||
amount=xrpl.models.amounts.IssuedCurrencyAmount(
|
||||
currency=currency,
|
||||
value=int(amount),
|
||||
issuer=sending_wallet.address
|
||||
),
|
||||
destination=destination
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and get the response.
|
||||
|
||||
```python
|
||||
response=xrpl.transaction.submit_and_wait(send_currency_tx, client, sending_wallet)```
|
||||
|
||||
Return the results.
|
||||
|
||||
```python
|
||||
return response.result
|
||||
```
|
||||
|
||||
### get_balance
|
||||
|
||||
Update the **XRP Balance** fields and list the balance information for issued currencies in the **Results** text areas.
|
||||
|
||||
```python
|
||||
def get_balance(sb_account_id, op_account_id):
|
||||
"""get_balance"""
|
||||
```
|
||||
|
||||
Connect to the XRP Ledger and instantiate a client.
|
||||
|
||||
```python
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
Create the `GatewayBalances` request.
|
||||
|
||||
```python
|
||||
balance=xrpl.models.requests.GatewayBalances(
|
||||
account=sb_account_id,
|
||||
ledger_index="validated",
|
||||
hotwallet=[op_account_id]
|
||||
)
|
||||
```
|
||||
|
||||
Return the result.
|
||||
|
||||
```python
|
||||
response = client.request(balance)
|
||||
return response.result
|
||||
```
|
||||
|
||||
### configure_account
|
||||
|
||||
This example shows how to set and clear configuration flags using the `AccountSet` method. The `ASF_DEFAULT_RIPPLE` flag is pertinent to experimentation with transfer of issued currencies to third-party accounts, so it is demonstrated here. You can set any of the configuration flags using the same structure, substituting the particular flags you want to set. See [AccountSet Flags](../../../../references/protocol/transactions/types/accountset.md#accountset-flags).
|
||||
|
||||
Send the account seed and a Boolean value for whether to enable or disable rippling.
|
||||
```python
|
||||
def configure_account(seed, default_setting):
|
||||
"""configure_account"
|
||||
```
|
||||
|
||||
Get the account wallet and instantiate a client.
|
||||
|
||||
```python
|
||||
wallet=Wallet.from_seed(seed)
|
||||
client=JsonRpcClient(testnet_url)
|
||||
```
|
||||
|
||||
If `default_setting` is true, create a `set_flag` transaction to enable rippling. If false, create a `clear_flag` transaction to disable rippling.
|
||||
|
||||
```python
|
||||
if (default_setting):
|
||||
setting_tx=xrpl.models.transactions.AccountSet(
|
||||
account=wallet.classic_address,
|
||||
set_flag=xrpl.models.transactions.AccountSetAsfFlag.ASF_DEFAULT_RIPPLE
|
||||
)
|
||||
else:
|
||||
setting_tx=xrpl.models.transactions.AccountSet(
|
||||
account=wallet.classic_address,
|
||||
set_flag=xrpl.models.transactions.AccountSetAsfFlag.ASF_DEFAULT_RIPPLE
|
||||
)
|
||||
```
|
||||
|
||||
Submit the transaction and get results.
|
||||
|
||||
```python
|
||||
response=xrpl.transaction.submit_and_wait(setting_tx,client,wallet)
|
||||
return response.result
|
||||
```
|
||||
|
||||
## lesson2-send-currency.py
|
||||
|
||||
This module builds on `lesson1-send-xrp.py`. Changes are noted below.
|
||||
|
||||
```python
|
||||
import tkinter as tk
|
||||
import xrpl
|
||||
import json
|
||||
```
|
||||
|
||||
Import methods from `mod2.py`.
|
||||
|
||||
```python
|
||||
from mod1 import get_account, get_account_info, send_xrp
|
||||
from mod2 import (
|
||||
create_trust_line,
|
||||
send_currency,
|
||||
get_balance,
|
||||
configure_account,
|
||||
)
|
||||
```
|
||||
|
||||
Module 2 Handlers.
|
||||
|
||||
```python
|
||||
def standby_create_trust_line():
|
||||
results = create_trust_line(ent_standby_seed.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_currency.get(),
|
||||
ent_standby_amount.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_send_currency():
|
||||
results = send_currency(ent_standby_seed.get(),
|
||||
ent_standby_destination.get(),
|
||||
ent_standby_currency.get(),
|
||||
ent_standby_amount.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def standby_configure_account():
|
||||
results = configure_account(
|
||||
ent_standby_seed.get(),
|
||||
standbyRippling)
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_create_trust_line():
|
||||
results = create_trust_line(ent_operational_seed.get(),
|
||||
ent_operational_destination.get(),
|
||||
ent_operational_currency.get(),
|
||||
ent_operational_amount.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_send_currency():
|
||||
results = send_currency(ent_operational_seed.get(),
|
||||
ent_operational_destination.get(),
|
||||
ent_operational_currency.get(),
|
||||
ent_operational_amount.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def operational_configure_account():
|
||||
results = configure_account(
|
||||
ent_operational_seed.get(),
|
||||
operationalRippling)
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
def get_balances():
|
||||
results = get_balance(ent_operational_account.get(), ent_standby_account.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0", json.dumps(results, indent=4))
|
||||
results = get_balance(ent_standby_account.get(), ent_operational_account.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0", json.dumps(results, indent=4))
|
||||
|
||||
|
||||
# Module 1 Handlers
|
||||
def get_standby_account():
|
||||
new_wallet = get_account(ent_standby_seed.get())
|
||||
ent_standby_account.delete(0, tk.END)
|
||||
ent_standby_seed.delete(0, tk.END)
|
||||
ent_standby_account.insert(0, new_wallet.classic_address)
|
||||
ent_standby_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_standby_account_info():
|
||||
accountInfo = get_account_info(ent_standby_account.get())
|
||||
ent_standby_balance.delete(0, tk.END)
|
||||
ent_standby_balance.insert(0,accountInfo['Balance'])
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def standby_send_xrp():
|
||||
response = send_xrp(ent_standby_seed.get(),ent_standby_amount.get(),
|
||||
ent_standby_destination.get())
|
||||
text_standby_results.delete("1.0", tk.END)
|
||||
text_standby_results.insert("1.0",json.dumps(response.result, indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
|
||||
def get_operational_account():
|
||||
new_wallet = get_account(ent_operational_seed.get())
|
||||
ent_operational_account.delete(0, tk.END)
|
||||
ent_operational_account.insert(0, new_wallet.classic_address)
|
||||
ent_operational_seed.delete(0, tk.END)
|
||||
ent_operational_seed.insert(0, new_wallet.seed)
|
||||
|
||||
|
||||
def get_operational_account_info():
|
||||
accountInfo = get_account_info(ent_operational_account.get())
|
||||
ent_operational_balance.delete(0, tk.END)
|
||||
ent_operational_balance.insert(0,accountInfo['Balance'])
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(accountInfo, indent=4))
|
||||
|
||||
|
||||
def operational_send_xrp():
|
||||
response = send_xrp(ent_operational_seed.get(),ent_operational_amount.get(), ent_operational_destination.get())
|
||||
text_operational_results.delete("1.0", tk.END)
|
||||
text_operational_results.insert("1.0",json.dumps(response.result,indent=4))
|
||||
get_standby_account_info()
|
||||
get_operational_account_info()
|
||||
|
||||
# Create a new window with the title "Quickstart Module 2"
|
||||
|
||||
window = tk.Tk()
|
||||
window.title("Quickstart Module 2")
|
||||
|
||||
|
||||
standbyRippling = tk.BooleanVar()
|
||||
operationalRippling = tk.BooleanVar()
|
||||
|
||||
|
||||
# Form frame
|
||||
frm_form = tk.Frame(relief=tk.SUNKEN, borderwidth=3)
|
||||
frm_form.pack()
|
||||
|
||||
# Create the Label and Entry widgets for "Standby Account"
|
||||
lbl_standy_seed = tk.Label(master=frm_form, text="Standby Seed")
|
||||
ent_standby_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_account = tk.Label(master=frm_form, text="Standby Account")
|
||||
ent_standby_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standy_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_standby_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_standby_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_standby_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_standby_balance = tk.Entry(master=frm_form, width=50)
|
||||
```
|
||||
|
||||
Add **Currency** field.
|
||||
|
||||
```python
|
||||
lbl_standby_currency = tk.Label(master=frm_form, text="Currency")
|
||||
ent_standby_currency = tk.Entry(master=frm_form, width=50)
|
||||
```
|
||||
|
||||
Add checkbox to **Allow Rippling**.
|
||||
|
||||
```python
|
||||
cb_standby_allow_rippling = tk.Checkbutton(master=frm_form, text="Allow Rippling", variable=standbyRippling, onvalue=True, offvalue=False)
|
||||
lbl_standby_results = tk.Label(master=frm_form,text='Results')
|
||||
text_standby_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
# Place field in a grid.
|
||||
lbl_standy_seed.grid(row=0, column=0, sticky="w")
|
||||
ent_standby_seed.grid(row=0, column=1)
|
||||
lbl_standby_account.grid(row=2, column=0, sticky="e")
|
||||
ent_standby_account.grid(row=2, column=1)
|
||||
lbl_standy_amount.grid(row=3, column=0, sticky="e")
|
||||
ent_standby_amount.grid(row=3, column=1)
|
||||
lbl_standby_destination.grid(row=4, column=0, sticky="e")
|
||||
ent_standby_destination.grid(row=4, column=1)
|
||||
lbl_standby_balance.grid(row=5, column=0, sticky="e")
|
||||
ent_standby_balance.grid(row=5, column=1)
|
||||
```
|
||||
|
||||
Place new UI elements.
|
||||
|
||||
```python
|
||||
lbl_standby_currency.grid(row=6, column=0, sticky="e")
|
||||
ent_standby_currency.grid(row=6, column=1)
|
||||
cb_standby_allow_rippling.grid(row=7,column=1, sticky="w")
|
||||
lbl_standby_results.grid(row=8, column=0, sticky="ne")
|
||||
text_standby_results.grid(row=8, column=1, sticky="nw")
|
||||
cb_standby_allow_rippling.select()
|
||||
|
||||
###############################################
|
||||
## Operational Account ########################
|
||||
###############################################
|
||||
|
||||
# Create the Label and Entry widgets for "Operational Account"
|
||||
lbl_operational_seed = tk.Label(master=frm_form, text="Operational Seed")
|
||||
ent_operational_seed = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_account = tk.Label(master=frm_form, text="Operational Account")
|
||||
ent_operational_account = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_amount = tk.Label(master=frm_form, text="Amount")
|
||||
ent_operational_amount = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_destination = tk.Label(master=frm_form, text="Destination")
|
||||
ent_operational_destination = tk.Entry(master=frm_form, width=50)
|
||||
lbl_operational_balance = tk.Label(master=frm_form, text="XRP Balance")
|
||||
ent_operational_balance = tk.Entry(master=frm_form, width=50)
|
||||
```
|
||||
|
||||
Add field for **Currency** and checkbox to **Allow Rippling**.
|
||||
|
||||
```python
|
||||
lbl_operational_currency = tk.Label(master=frm_form, text="Currency")
|
||||
ent_operational_currency = tk.Entry(master=frm_form, width=50)
|
||||
cb_operational_allow_rippling = tk.Checkbutton(master=frm_form, text="Allow Rippling", variable=operationalRippling, onvalue=True, offvalue=False)
|
||||
lbl_operational_results = tk.Label(master=frm_form,text='Results')
|
||||
text_operational_results = tk.Text(master=frm_form, height = 20, width = 65)
|
||||
|
||||
#Place the widgets in a grid
|
||||
lbl_operational_seed.grid(row=0, column=4, sticky="e")
|
||||
ent_operational_seed.grid(row=0, column=5, sticky="w")
|
||||
lbl_operational_account.grid(row=2,column=4, sticky="e")
|
||||
ent_operational_account.grid(row=2,column=5, sticky="w")
|
||||
lbl_operational_amount.grid(row=3, column=4, sticky="e")
|
||||
ent_operational_amount.grid(row=3, column=5, sticky="w")
|
||||
lbl_operational_destination.grid(row=4, column=4, sticky="e")
|
||||
ent_operational_destination.grid(row=4, column=5, sticky="w")
|
||||
lbl_operational_balance.grid(row=5, column=4, sticky="e")
|
||||
ent_operational_balance.grid(row=5, column=5, sticky="w")
|
||||
```
|
||||
|
||||
Add elements to the UI.
|
||||
|
||||
```python
|
||||
lbl_operational_currency.grid(row=6, column=4, sticky="e")
|
||||
ent_operational_currency.grid(row=6, column=5)
|
||||
cb_operational_allow_rippling.grid(row=7,column=5, sticky="w")
|
||||
lbl_operational_results.grid(row=8, column=4, sticky="ne")
|
||||
text_operational_results.grid(row=8, column=5, sticky="nw")
|
||||
cb_operational_allow_rippling.select()
|
||||
```
|
||||
|
||||
Create the Standby Account Buttons.
|
||||
|
||||
```python
|
||||
btn_get_standby_account = tk.Button(master=frm_form, text="Get Standby Account",
|
||||
command = get_standby_account)
|
||||
btn_get_standby_account.grid(row=0, column=2, sticky = "nsew")
|
||||
btn_get_standby_account_info = tk.Button(master=frm_form,
|
||||
text="Get Standby Account Info",
|
||||
command = get_standby_account_info)
|
||||
btn_get_standby_account_info.grid(row=1, column=2, sticky = "nsew")
|
||||
btn_standby_send_xrp = tk.Button(master=frm_form, text="Send XRP >",
|
||||
command = standby_send_xrp)
|
||||
btn_standby_send_xrp.grid(row=2, column = 2, sticky = "nsew")
|
||||
```
|
||||
|
||||
Add buttons **Create Trust Line**, **Send Currency**, **Get Balances**, and **Configure Account**.
|
||||
|
||||
```python
|
||||
btn_standby_create_trust_line = tk.Button(master=frm_form,
|
||||
text="Create Trust Line",
|
||||
command = standby_create_trust_line)
|
||||
btn_standby_create_trust_line.grid(row=3, column=2, sticky = "nsew")
|
||||
btn_standby_send_currency = tk.Button(master=frm_form, text="Send Currency >",
|
||||
command = standby_send_currency)
|
||||
btn_standby_send_currency.grid(row=4, column=2, sticky = "nsew")
|
||||
btn_standby_send_currency = tk.Button(master=frm_form, text="Get Balances",
|
||||
command = get_balances)
|
||||
btn_standby_send_currency.grid(row=5, column=2, sticky = "nsew")
|
||||
btn_standby_configure_account = tk.Button(master=frm_form,
|
||||
text="Configure Account",
|
||||
command = standby_configure_account)
|
||||
btn_standby_configure_account.grid(row=7,column=0, sticky = "nsew")
|
||||
```
|
||||
|
||||
Create the Operational Account buttons.
|
||||
|
||||
```python
|
||||
btn_get_operational_account = tk.Button(master=frm_form,
|
||||
text="Get Operational Account",
|
||||
command = get_operational_account)
|
||||
btn_get_operational_account.grid(row=0, column=3, sticky = "nsew")
|
||||
btn_get_op_account_info = tk.Button(master=frm_form, text="Get Op Account Info",
|
||||
command = get_operational_account_info)
|
||||
btn_get_op_account_info.grid(row=1, column=3, sticky = "nsew")
|
||||
btn_op_send_xrp = tk.Button(master=frm_form, text="< Send XRP",
|
||||
command = operational_send_xrp)
|
||||
btn_op_send_xrp.grid(row=2, column = 3, sticky = "nsew")
|
||||
```
|
||||
|
||||
Add operational buttons **Create Trust Line**, **Send Currency**, **Get Balances**, and **Configure Account**.
|
||||
|
||||
```python
|
||||
btn_op_create_trust_line = tk.Button(master=frm_form, text="Create Trust Line",
|
||||
command = operational_create_trust_line)
|
||||
btn_op_create_trust_line.grid(row=3, column=3, sticky = "nsew")
|
||||
btn_op_send_currency = tk.Button(master=frm_form, text="< Send Currency",
|
||||
command = operational_send_currency)
|
||||
btn_op_send_currency.grid(row=4, column=3, sticky = "nsew")
|
||||
btn_op_get_balances = tk.Button(master=frm_form, text="Get Balances",
|
||||
command = get_balances)
|
||||
btn_op_get_balances.grid(row=5, column=3, sticky = "nsew")
|
||||
btn_op_configure_account = tk.Button(master=frm_form, text="Configure Account",
|
||||
command = operational_configure_account)
|
||||
btn_op_configure_account.grid(row=7,column=4, sticky = "nsew")
|
||||
```
|
||||
|
||||
# Start the application
|
||||
|
||||
```python
|
||||
window.mainloop()
|
||||
```
|
||||
@@ -0,0 +1,56 @@
|
||||
---
|
||||
html: send-payments-using-python.html
|
||||
parent: modular-tutorials-in-python.html
|
||||
seo:
|
||||
description: Use a Python test harness to send XRP,trade currencies, and more.
|
||||
labels:
|
||||
- Accounts
|
||||
- Cross-Currency
|
||||
- Non-fungible Tokens, NFTs
|
||||
- Payments
|
||||
- Quickstart
|
||||
- Tokens
|
||||
- XRP
|
||||
---
|
||||
# Send Payments Using Python
|
||||
|
||||
The XRP Ledger (XRPL) is a robust, secure, customizable service. You can create your own interface to try out the capabilities and support your specific business needs.
|
||||
|
||||
This quickstart describes a test harness interface you can build to try out the XRP Ledger. The test harness displays multiple accounts, so that you can transfer tokens from one account to the other and see the results in real time. The image below shows the Token Test Harness at the completion of step 4.
|
||||
|
||||
[](/docs/img/quickstart-py15.png)
|
||||
|
||||
That is a lot of fields and buttons, all working together to perform some significant practical tasks. But getting _started_ with the XRP Ledger is not that complicated. When you eat the elephant a bite at a time, none of the tasks are difficult to consume.
|
||||
|
||||
Typically, the example functions for interacting with the XRP Ledger involve four steps.
|
||||
|
||||
1. Connect to the XRP Ledger and instantiate your wallet.
|
||||
2. Make changes to the XRP Ledger using transactions.
|
||||
3. Get the state of accounts and tokens on the XRP Ledger using requests.
|
||||
4. Disconnect from the XRP Ledger.
|
||||
|
||||
Each lesson shows you how to build the Token Test Harness one section at a time. Each module lets you try out meaningful interactions with the test ledger, with complete Python code samples and a code walkthrough. There is also a link to the complete source code for each section that can be modified with a text editor and run in a Python environment. You can try out the examples in any order.
|
||||
|
||||
This quickstart tutorial introduces you to the API used to implement features and explore the capabilities of XRP Ledger. It does not represent _all_ of the capabilities of the API and this example is not intended for production or secure payment use.
|
||||
|
||||
Much of this is “brute force” code that sacrifices conciseness for readability. Each example builds on the previous step, adding a new Python UI file and a module to support the new behavior in the lesson. We expect the applications you build to greatly improve upon these examples. Your feedback and contributions are most welcome.
|
||||
|
||||
In this quickstart, you can:
|
||||
|
||||
1. [Create Accounts and Send XRP](create-accounts-send-xrp.md)
|
||||
2. [Create Trust Line and Send Currency](create-trust-line-send-currency.md).
|
||||
3. [Create Time-Based Escrows](create-time-based-escrows.md)
|
||||
4. [Create Conditional Escrows](create-conditional-escrows.md)
|
||||
|
||||
|
||||
## Prerequisites
|
||||
|
||||
To get started, create a new folder on your local disk and install the Python library (xrpl-py) using `pip`.
|
||||
|
||||
```
|
||||
pip3 install xrpl-py
|
||||
```
|
||||
|
||||
Download the python [Quickstart Samples](https://github.com/XRPLF/xrpl-dev-portal/tree/master/_code-samples/quickstart/py/)<!-- {.github-code-download} -->.
|
||||
|
||||
**Note:** Without the Quickstart Samples, you will not be able to try the examples that follow.
|
||||
Reference in New Issue
Block a user