DEV Community

Cover image for Stellar + Python: The Ultimate Duo for a Payment App (Absolute Beginner's Guide)
Buzzpy πŸ’‘
Buzzpy πŸ’‘

Posted on • Updated on

Stellar + Python: The Ultimate Duo for a Payment App (Absolute Beginner's Guide)

This tutorial is a submission for the Stellar Challenge on the DEV Community.

Image description

Stellar is a popular tool for JavaScript developers. The exciting news? Stellar also supports SDKs for backend languages like Python and Go, although not many developers have built a practical application using them.

Worry not, in this tutorial, we are going to build a Stellar Wallet application using Python + Stellar SDK. This tutorial is a follow-along guide, with every step explained from Backend logic to GUI!

Image description

A look at the app

Before starting to build the application, it's better to know what we are building.

Image description

This Stellar Wallet application is a simple tool that lets users create a new Stellar account, check the balance of an account, and send payments to other Stellar accounts. The application is built using the Stellar SDK (a Python library to interact with the Stellar network) and CustomTkinter (a modern GUI library for Python).

Here's what the application finally looks like:
Image description

Please note that this is a tutorial and our main focus is building a "functional" application using Stellar, we will have to focus less on GUI and more on integrating Stellar with Python.

You can find the full script used on GitHub: Python Projects / Stellar Wallet.py

Image description

But wait, what's Stellar?

Stellar is a decentralized network designed to facilitate fast and inexpensive financial transactions. Stellar allows users to send and receive various forms of currency (like dollars, yens, or even cryptocurrencies) quickly, with lower fees.

In simplest words, imagine that you want to send money to a foreign country. If you send money via banks, this could be slow and expensive, but with Stellar, you can send money almost instantly, with no currency trouble.

The Stellar network uses a cryptocurrency called Lumens (XLM) to help with these transactions, but you don’t necessarily need to use or own Lumens to benefit from the network.

Prerequisites for the tutorial

  • Basic Python Knowledge (do you know how to print "hello world" 5 times in a loop? You're good to go!)
  • Python 3.8+ installed and set up in the system.

Building the Stellar Wallet

Glossary

Here's the glossary of the terms that would be used here, and they are further explained in the tutorial.

From here continue the step-by-step tutorial on building your Payment app using Python and Stellar.

Doodle

1. Setting Up the Gear

First, start with installing the necessary libraries using pip.

pip install customtkinter stellar-sdk
Enter fullscreen mode Exit fullscreen mode

Tkinter comes pre-installed with Python, so you don't have to import it. You can use the following code to import the libraries:

import customtkinter as ctk
from tkinter import Toplevel
from stellar_sdk import Keypair, Server, TransactionBuilder, Network, Asset
from stellar_sdk.exceptions import NotFoundError # for better error descriptions
Enter fullscreen mode Exit fullscreen mode
  • Toplevel: This is a special type of window in Tkinter (the base of customtkinter) that we use for pop-up windows.
  • stellar_sdk: This is the Python SDK for Stellar, which lets us interact with the Stellar network. It includes tools like Keypair (for creating accounts), Server (for connecting to the Stellar network), TransactionBuilder (for creating transactions), and Network (which provides network details like the testnet).

And that's it, the environment is set up.

2. Creating the main class

A class in Python can be considered as a blueprint of the object. They are used in code to make things organized and reusable. They also come in handy when you want to make your code more modular (divided into independent parts) and easier to understand.

In this specific case, the StellarWalletApp class defines the structure and behavior of the wallet, and when we create an instance of this class, we get a fully functional wallet window.

class StellarWalletApp:
    def __init__(self, root):
        self.root = root
        self.root.title("Stellar Wallet")
        self.root.geometry("500x700")
        self.root.resizable(False, False)
Enter fullscreen mode Exit fullscreen mode

Let me break this down:

  • class StellarWalletApp: This defines a new class called StellarWalletApp. The name is just a label we choose, and it represents the overall structure of our wallet app. Inside this class, we’ll define all the attributes (like window size) and methods (like creating buttons) that make our app work.
  • def __init__(self, root): This is a special method called a constructor. Every time we create an instance of StellarWalletApp, this method runs automatically to set up the object. It’s like the starting point for everything that happens in the app.
  • self.root = root: This line assigns the main window (passed as root) to an attribute of the class, self.root. This allows us to use self.root later to refer to the main window and modify its properties.
  • self.root.title("Stellar Wallet"): This sets the title of the window to "Stellar Wallet". This is what the user sees at the top of the app window.
  • self.root.geometry("500x700"): This sets the size of the window to 500 pixels wide and 700 pixels tall.
  • self.root.resizable(False, False): This prevents the user from resizing the window, keeping the app at a fixed size.

Image description

3. Configuring a Layout

Before inserting widgets into the windows, we have to configure the grids and columns using the following code:

        self.root.grid_columnconfigure(0, weight=1)
        self.root.grid_rowconfigure(0, weight=1)
        self.root.grid_rowconfigure(10, weight=1)
        self.create_widgets()
Enter fullscreen mode Exit fullscreen mode

Here, we have set only 1 column (represented by 0 in the code) and 10 vertical rows. Here's an image that shows how it's configured:

Image description

The final line, create_widgets() calls a method named create_widgets, which we define next. That method will create all the buttons, text boxes, and labels that the user interacts with.

4. The create_widgets() method

As mentioned before, this method will be used to create GUI components for our application.

IMPORTANT: Indentation matters, and therefore make sure all the functions and classes are properly indented. Please refer to the code on GitHub if any error occurs. Make sure all the methods (including __init__ method and others created later in the tutorial) are inside the class StellarWalletApp.

How code should be indented:
Image description

Application Title Label

Image description

Here's the Label you can use as the title of the application:

def create_widgets(self): # declaring the method
        # Title Label
        self.title_label = ctk.CTkLabel(self.root, text="Stellar Wallet πŸ’³", font=("Arial", 20, "bold"))
        self.title_label.grid(row=1, column=0, padx=10, pady=(10, 5), sticky="n")

Enter fullscreen mode Exit fullscreen mode

Image description

Image description

Public Key Label and Entry box

Image description

Here's how to make a label and an entry box for user input:

 # Public Key Label
        self.public_key_label = ctk.CTkLabel(self.root, text="Public Key:", font=("Arial", 15, "bold"))
        self.public_key_label.grid(row=2, column=0, padx=10, pady=(10, 5), sticky="w")

        # Public Key Entry
        self.public_key_entry = ctk.CTkEntry(self.root,
                                             placeholder_text="Enter Public Key",
                                             font=("Courier New", 14),
                                             border_width=2,
                                             border_color='#2da572',
                                             height=40,
                                             width=350)
        self.public_key_entry.grid(row=3, column=0, padx=(10, 0), pady=(5, 15))

Enter fullscreen mode Exit fullscreen mode

Secret Key Label and Entry box

Since secret keys should be treated like a password, we should ensure that the user input is not visible. For that, you can use the Customtkinter's show="*" argument.

 # Secret Key Label
        self.secret_key_label = ctk.CTkLabel(self.root, text="Secret Key:", font=("Arial", 15, "bold"))
        self.secret_key_label.grid(row=4, column=0, padx=10, pady=(10, 5), sticky="w")

        # Secret Key Entry
        self.secret_key_entry = ctk.CTkEntry(self.root,
                                             placeholder_text="Enter Secret Key",
                                             font=("Courier New", 14),
                                             border_width=2,
                                             border_color='#2da572',
                                             height=40,
                                             show="*",
                                             width=350)
        self.secret_key_entry.grid(row=5, column=0, padx=(10, 0), pady=(5, 15))

Enter fullscreen mode Exit fullscreen mode

Image description

5. Creating the buttons

For this application, we will need to create 3 buttons for 3 main purposes: To create an account, check your balance, and send XLM to someone.

I. Create Account Button

This button, when clicked, will generate a new Stellar account.

# Create Account Button
        self.create_account_button = ctk.CTkButton(self.root,
                                                   text="Create Account",
                                                   text_color="black",
                                                   font=("Courier New", 14,"bold"),
                                                   width=250,
                                                   height=40,
                                                   command=self.create_account)
        self.create_account_button.grid(row=6, column=0, padx=(10, 0), pady=(10, 10))

Enter fullscreen mode Exit fullscreen mode

II. Check Balance Button

This button, when clicked, will check and display the balance of the entered or created public key.

        # Check Balance Button
        self.balance_button = ctk.CTkButton(self.root,
                                            text="Check Balance",
                                            text_color="black",
                                            font=("Courier New", 14),
                                            width=250,
                                            height=40,
                                            command=self.check_balance)
        self.balance_button.grid(row=7, column=0, padx=(10, 0), pady=(10, 10))

Enter fullscreen mode Exit fullscreen mode

III. Send Payment Button

When clicked, this will send a payment to another Stellar account using the Public key.

# Send Payment Button
        self.send_payment_button = ctk.CTkButton(self.root,
                                                 text="Send Payment",
                                                 text_color="black",
                                                 font=("Courier New", 14),
                                                 width=250,
                                                 height=40,
                                                 command=self.send_payment)
        self.send_payment_button.grid(row=8, column=0, padx=(10, 0), pady=(10, 10))

Enter fullscreen mode Exit fullscreen mode

And finally, we'll need a...

Result label

This label will display messages to the user, such as the result of creating an account or checking a balance. The methods we develop will handle the content later, so we will keep it blank.

        # Result Label
        self.result_label = ctk.CTkLabel(self.root, text="", font=("Arial", 14))
        self.result_label.grid(row=9, column=0, padx=(10, 0), pady=(20, 20))
Enter fullscreen mode Exit fullscreen mode

That's it for the GUI, time to build the methods for making those buttons work. πŸš€

Feel free to play with GUI, by changing button colors, text colors, and whatnot.

Image description

6. create_account Method

The following method can be used to create an account on Stellar.

def create_account(self):
        """Generate a new Stellar account and display the keys."""
        pair = Keypair.random()
        self.public_key_entry.delete(0, ctk.END)
        self.public_key_entry.insert(0, pair.public_key)
        self.secret_key_entry.delete(0, ctk.END)
        self.secret_key_entry.insert(0, pair.secret)
        self.result_label.configure(text="New account created! Use Testnet Faucet to fund it.")
Enter fullscreen mode Exit fullscreen mode

In addition to GUI changes, what happens behind the scenes?

Keypair Creation___

The Keypair.random() method generates a public and secret key using a cryptographic algorithm. Stellar uses elliptic curve cryptography, specifically the Ed25519 curve, to generate these keys.

The public key is derived from the secret key in a way that ensures it can be shared publicly without compromising the secret key.

Account Existence___

On the Stellar network, accounts are represented by their public key. However, just generating a keypair doesn’t immediately create an active account on the network.

For the account to be recognized by the network and become active, it must hold at least 1 Lumen (XLM). This is why we created a suggestion label on using a "Testnet Faucet" to fund the account.

What's Testnet, by the way?
Testent is a sandbox environment where developers can test their applications without using real money. The Testnet Faucet is a service that provides free Lumens for testing purposes.

7. check_balance Method

The following method can be used to check the balance of a certain account, using the input public and secret keys.

def check_balance(self):
        """Check and display the balance of the account."""
        try:
            public_key = self.public_key_entry.get()
            server = Server("https://horizon-testnet.stellar.org")
            account = server.accounts().account_id(public_key).call()
            balances = account['balances']
            if not balances or all(float(b['balance']) == 0 for b in balances):
                self.show_toplevel_message("No balance")
            else:
                balance_text = "\n".join([f"Asset Type: {b['asset_type']}, Balance: {b['balance']}" for b in balances])
                self.result_label.configure(text=balance_text)
        except Exception:
            self.show_toplevel_message("Failed to retrieve balance")

Enter fullscreen mode Exit fullscreen mode

As you may see, we are connecting to a Stellar test network, and using the public key the balance of the account is retrieved. If the balance is 0, a top-level with the message "No balance" is retrieved, and if an error occurs, a top-level with the message "Failed to retrieve balance" is shown to the user.

The show_toplevel_message() function will be created later in the tutorial, so just keep the code as it is without executing.

Image description

8. send_payment Method

The following method can be used to send payments to other Stellar accounts using their public keys,

 def send_payment(self):
        """Send a payment to another Stellar account."""
        try:
            source_secret = self.secret_key_entry.get()
            if not source_secret:
                self.show_toplevel_message("Payment unsuccessful: Secret key is required")
                return

            destination_public = self.prompt_input("Enter destination public key:")
            print(f"Destination Public Key: {destination_public}")  # Debugging line
            amount = self.prompt_float_input("Enter amount to send:")
            print(f"Amount to send: {amount}")  # Debugging line

            if not destination_public or not amount:
                self.show_toplevel_message("Payment unsuccessful: All fields are required")
                return

            source_keypair = Keypair.from_secret(source_secret)
            server = Server("https://horizon-testnet.stellar.org")

            # Check if source account exists
            try:
                source_account = server.load_account(source_keypair.public_key)
            except NotFoundError:
                self.show_toplevel_message("Payment unsuccessful: Source account not found")
                return

            # Check if destination account exists
            try:
                server.accounts().account_id(destination_public).call()
            except NotFoundError:
                self.show_toplevel_message("Payment unsuccessful: Destination account not found")
                return

            transaction = TransactionBuilder(
                source_account,
                network_passphrase=Network.TESTNET_NETWORK_PASSPHRASE,
                base_fee=100
            ).add_text_memo("Stellar Payment").append_payment_op(
                destination=destination_public,
                amount=str(amount),
                asset=Asset.native()  # Specify that the asset is the native XLM
            ).build()

            transaction.sign(source_keypair)
            response = server.submit_transaction(transaction)
            self.result_label.configure(text=f"Payment sent!\nTransaction Hash:\n{response['hash']}", font=("Courier New", 10))

        except Exception as e:
            print(f"Exception occurred: {e}")  # Debugging line
            self.show_toplevel_message("Payment unsuccessful")
Enter fullscreen mode Exit fullscreen mode

The process here is a bit more complex than the others.

This function starts by retrieving the user's secret key and checking if it's empty; if so, an error is shown, and the process halts. Next, the user is prompted to input the recipient's public key and the amount to send. If either input is missing, an error is displayed, and the method stops.

The method then creates a keypair from the secret key to access the user's Stellar account, connects to the Stellar test network, and loads the user's account details.

A transaction is built, signed with the secret key for authorization, and then submitted to the Stellar network. If successful, a confirmation message with the transaction hash is shown; otherwise, an error message is displayed.

Image description

9. Messages in Pop-up windows

Remember the function used in the previous three functions for showing messages? Here's how it can be created.

def show_toplevel_message(self, message):
        """Display a top-level window with a message."""
        top = Toplevel(self.root)
        top.title("Notification")
        top.geometry("400x400")
        top.resizable(False, False)

        msg = ctk.CTkLabel(top, text=message, font=("Arial", 15, "bold"))
        msg.pack(pady=20)

        button = ctk.CTkButton(top, text="OK", command=top.destroy, font=("Arial", 15, "bold"), width=150)
        button.pack(pady=20)
Enter fullscreen mode Exit fullscreen mode

As simple as it looks, this method creates a pop-up window with an OK button for displaying messages.

Image description

User Input Prompts

The following is a function of acquiring user input using a top-level window.

def prompt_input(self, prompt_text):
        """Prompt for string input using a custom Toplevel window."""
        input_window = Toplevel(self.root)
        input_window.title(prompt_text)
        input_window.geometry("400x400")
        input_window.resizable(False, False)

        prompt_label = ctk.CTkLabel(input_window, text=prompt_text, font=("Arial", 15, "bold"))
        prompt_label.pack(pady=(40, 20))

        input_entry = ctk.CTkEntry(input_window, font=("Arial", 15, "bold"), width=300)
        input_entry.pack(pady=20)

        input_value = []

        def submit_input():
            input_value.append(input_entry.get())
            input_window.destroy()

        submit_button = ctk.CTkButton(input_window, text="Submit", command=submit_input, font=("Arial", 15, "bold"),
                                      width=150)
        submit_button.pack(pady=20)

        input_window.wait_window()

        return input_value[0] if input_value else None

Enter fullscreen mode Exit fullscreen mode

Inside this window, a label displays the prompt text, and a text entry box is provided for the user to type their input. The method includes a nested function, submit_input, which handles the submission by retrieving the input, closing the window, and returning the entered text. A "Submit" button triggers this function, and the method waits until the window is closed before proceeding.

Finally, the entered text is returned for further use in the application.

User Input Prompts - Numerical Only

In some inputs, the user is supposed to enter numerical values only. For example, when sending a payment, the amount of the transaction should only contain numbers. The following method can be used to achieve this.

    def prompt_float_input(self, prompt_text):
        """Prompt for float input using a custom Toplevel window."""
        input_window = Toplevel(self.root)
        input_window.title(prompt_text)
        input_window.geometry("400x400")
        input_window.resizable(False, False)

        prompt_label = ctk.CTkLabel(input_window, text=prompt_text, font=("Arial", 15, "bold"))
        prompt_label.pack(pady=(40, 20))

        input_entry = ctk.CTkEntry(input_window, font=("Arial", 15, "bold"), width=300)
        input_entry.pack(pady=20)

        input_value = []

        def submit_input():
            try:
                input_value.append(float(input_entry.get()))
            except ValueError:
                input_value.append(None)
            input_window.destroy()

        submit_button = ctk.CTkButton(input_window, text="Submit", command=submit_input, font=("Arial", 15, "bold"),
                                      width=150)
        submit_button.pack(pady=20)

        input_window.wait_window()

        return input_value[0] if input_value else None
Enter fullscreen mode Exit fullscreen mode

This method is very similar to the prompt_input method, but it specifically prompts for numeric input.

input_value = float(input_entry.get()) - Here, the input is converted to a float. If the user enters something that isn’t a valid number, a ValueError is caught, and input_value is set to None.

And that's all about it! You have successfully built an application with Stellar! That was easy, right?

Oh wait, we didn't run the application yet.

Running the application

if __name__ == "__main__":
    ctk.set_appearance_mode("dark")  # Use system theme
    ctk.set_default_color_theme("green")  # Default color theme without customization

    root = ctk.CTk()
    app = StellarWalletApp(root)
    root.mainloop()

Enter fullscreen mode Exit fullscreen mode

The following image is a sample of how the methods and classes should be indented in the script.

Image description

Now it's ready to be executed. Did it go well? Please let me know if you are facing any bugs, have some errors, or if things don't work out.

Image description

Find the full script on GitHub

Testing...


It's time to make sure it works.

Create an account:

As far as my research, there's no way to check the validity of the account with no funds, so you'll have to be satisfied with the program creating a public and secret key. To fund an account, you can use Stellar Friendbot. All you have to do is run the following URL on a browser, with YOUR_PUBLIC_KEY replaced by your actual public key.

https://friendbot.stellar.org/?addr=YOUR_PUBLIC_KEY
Enter fullscreen mode Exit fullscreen mode

Check Balance

If your Public key is correct and has been funded, the balance and asset type of the account will be displayed in our results_label. (View the demo video)

Send payment

This is where things may get tricky.

To send and retrieve the payment, you'd need 2 accounts. For these accounts to be identified by Stellar Network, they must have a balance exceeding 1 Lumen.

The easiest way to create 2 already funded accounts would be to use "GAIH's XLM Emporium" from Superluminal - ftl.ai. Other than creating accounts, it also keeps track of the accounts which helps a lot in testing these transactions.

Image description

However, if you're feeling too lazy to do all that, you can just copy/paste the following source and destination accounts.

-> Source Account

Public Key - 
GA5WU7PTF47VWOSSYBDMEVSLMENEFAGUY3LN6YJJCJ2LRIHQ2R2O5OUE

Secret Key - 
SCIY762XT7PP4AB2L35X7XTCTXZMMEQNVITE4WJWXH4RWIQWXLCCRYE7

-> Destination Account (for Payment)

Public Key - 
GDJ6VRIAZHGWN4ZTKRU3ADDXSMZUGIQQ54AGBBZOWMJ27VXAIEFB3GPH
Enter fullscreen mode Exit fullscreen mode

Image description

Next Steps 🐾

Now that you have a functional stellar wallet application, the future possibilities are endless!

  • You can make this application look good with a GUI.
  • You can deploy this somewhere and make it available for the users.
  • Or better yet, you can put this project in your portfolio.

Whatever the next step is, you have already developed your experience and knowledge in working with Stellar.

Image description

Happy pythoneering!

Top comments (6)

Collapse
 
buzzpy profile image
Buzzpy πŸ’‘

The beautiful Python logo redesign in the cover letter is by @stokry

View it here

Thank you for the best logo redesign @stokry !

Collapse
 
stokry profile image
Stokry

Thank you :-)

Collapse
 
danieljancar profile image
Daniel Jancar • Edited

Hey @buzzpy
I really like your tutorial, it gives a great intro to Smart Contracts. Also the uniqueness using banners is great, never really seen that anywhere else. πŸ”₯
Have you drawn these stickman's yourself? They look great! πŸ€“

Collapse
 
buzzpy profile image
Buzzpy πŸ’‘ • Edited

Thanks a bunch for the feedback @danieljancar ! That means a lot. πŸ₯°

And yes I did draw them by myself. Have to admit it, I feel proud of myself too! πŸ˜‡
Oh, by the way, I gave them some names here -

Image description

Collapse
 
danieljancar profile image
Daniel Jancar

Haha cool!

Collapse
 
stokry profile image
Stokry

This is a really nice tutorial!