This tutorial is a submission for the Stellar Challenge on the DEV Community.
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!
A look at the app
Before starting to build the application, it's better to know what we are building.
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:
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
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
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.
1. Setting Up the Gear
First, start with installing the necessary libraries using pip
.
pip install customtkinter stellar-sdk
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
-
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)
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 ofStellarWalletApp
, 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 useself.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.
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()
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:
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
.
Application Title Label
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")
Public Key Label and Entry box
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))
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))
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))
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))
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))
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))
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.
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.")
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")
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.
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")
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.
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)
As simple as it looks, this method creates a pop-up window with an OK button for displaying messages.
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
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
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()
The following image is a sample of how the methods and classes should be indented in the script.
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.
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
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.
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
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.
Happy pythoneering!
Top comments (6)
The beautiful Python logo redesign in the cover letter is by @stokry
View it here
Thank you for the best logo redesign @stokry !
Thank you :-)
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! π€
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 -
Haha cool!
This is a really nice tutorial!