1
1
import customtkinter as ctk
2
2
from tkinter import Toplevel
3
3
from stellar_sdk import Keypair , Server , TransactionBuilder , Network , Asset
4
- from stellar_sdk .exceptions import NotFoundError # for better error descriptions
4
+ from stellar_sdk .exceptions import NotFoundError # for better error descriptions
5
+
5
6
6
7
class StellarWalletApp :
7
8
def __init__ (self , root ):
@@ -10,83 +11,90 @@ def __init__(self, root):
10
11
self .root .geometry ("500x700" )
11
12
self .root .resizable (False , False )
12
13
13
- # Configure grid layout for basic centering
14
14
self .root .grid_columnconfigure (0 , weight = 1 )
15
- self .root .grid_rowconfigure (0 , weight = 1 )
16
- self .root .grid_rowconfigure (10 , weight = 1 )
15
+
16
+ # Style configurations
17
+ self .style_config = {
18
+ "label" : {"font" : ("Arial" , 15 , "bold" )},
19
+ "entry" : {"font" : ("Courier New" , 14 ), "border_width" : 2 , "border_color" : '#2da572' , "height" : 40 ,
20
+ "width" : 350 },
21
+ "button" : {"text_color" : "black" , "font" : ("Courier New" , 14 , "bold" ), "width" : 250 , "height" : 40 },
22
+ }
23
+
24
+ # Grid layout configuration
25
+ self .grid_config = {
26
+ "padx" : 10 ,
27
+ "pady" : (10 , 5 ),
28
+
29
+ }
17
30
18
31
# Create Widgets
19
32
self .create_widgets ()
20
33
21
34
def create_widgets (self ):
22
35
# Title Label
23
- self .title_label = ctk .CTkLabel (self .root , text = "Stellar Wallet 💳" , font = ("Arial" , 20 , "bold" ))
24
- self .title_label .grid (row = 1 , column = 0 , padx = 10 , pady = (10 , 5 ), sticky = "n" )
25
-
26
- # Public Key Label
27
- self .public_key_label = ctk .CTkLabel (self .root , text = "Public Key:" , font = ("Arial" , 15 , "bold" ))
28
- self .public_key_label .grid (row = 2 , column = 0 , padx = 10 , pady = (10 , 5 ), sticky = "w" )
29
-
30
- # Public Key Entry
31
- self .public_key_entry = ctk .CTkEntry (self .root ,
32
- placeholder_text = "Enter Public Key" ,
33
- font = ("Courier New" , 14 ),
34
- border_width = 2 ,
35
- border_color = '#2da572' ,
36
- height = 40 ,
37
- width = 350 )
38
- self .public_key_entry .grid (row = 3 , column = 0 , padx = (10 , 0 ), pady = (5 , 15 ))
39
-
40
- # Secret Key Label
41
- self .secret_key_label = ctk .CTkLabel (self .root , text = "Secret Key:" , font = ("Arial" , 15 , "bold" ))
42
- self .secret_key_label .grid (row = 4 , column = 0 , padx = 10 , pady = (10 , 5 ), sticky = "w" )
43
-
44
- # Secret Key Entry
45
- self .secret_key_entry = ctk .CTkEntry (self .root ,
46
- placeholder_text = "Enter Secret Key" ,
47
- font = ("Courier New" , 14 ),
48
- border_width = 2 ,
49
- border_color = '#2da572' ,
50
- height = 40 ,
51
- show = "*" ,
52
- width = 350 )
53
- self .secret_key_entry .grid (row = 5 , column = 0 , padx = (10 , 0 ), pady = (5 , 15 ))
54
-
55
- # Create Account Button
56
- self .create_account_button = ctk .CTkButton (self .root ,
57
- text = "Create Account" ,
58
- text_color = "black" ,
59
- font = ("Courier New" , 14 ,"bold" ),
60
- width = 250 ,
61
- height = 40 ,
62
- command = self .create_account )
63
- self .create_account_button .grid (row = 6 , column = 0 , padx = (10 , 0 ), pady = (10 , 10 ))
64
-
65
- # Check Balance Button
66
- self .balance_button = ctk .CTkButton (self .root ,
67
- text = "Check Balance" ,
68
- text_color = "black" ,
69
- font = ("Courier New" , 14 ),
70
- width = 250 ,
71
- height = 40 ,
72
- command = self .check_balance )
73
- self .balance_button .grid (row = 7 , column = 0 , padx = (10 , 0 ), pady = (10 , 10 ))
74
-
75
- # Send Payment Button
76
- self .send_payment_button = ctk .CTkButton (self .root ,
77
- text = "Send Payment" ,
78
- text_color = "black" ,
79
- font = ("Courier New" , 14 ),
80
- width = 250 ,
81
- height = 40 ,
82
- command = self .send_payment )
83
- self .send_payment_button .grid (row = 8 , column = 0 , padx = (10 , 0 ), pady = (10 , 10 ))
36
+ title_label = ctk .CTkLabel (self .root , text = "Stellar Wallet 💳" , font = ("Arial" , 20 , "bold" ))
37
+ title_label .grid (row = 0 , column = 0 , pady = (20 , 30 ), sticky = "n" )
38
+
39
+ # Public Key Section
40
+ self .public_key_entry = self ._create_labeled_entry ("Public Key:" , row = 1 )
41
+
42
+ # Secret Key Section
43
+ self .secret_key_entry = self ._create_labeled_entry ("Secret Key:" , row = 3 , show = "*" )
44
+
45
+ # Buttons
46
+ buttons_info = [
47
+ ("Create Account" , self .create_account ),
48
+ ("Check Balance" , self .check_balance ),
49
+ ("Send Payment" , self .send_payment ),
50
+ ]
51
+ for idx , (btn_text , btn_command ) in enumerate (buttons_info ):
52
+ button = ctk .CTkButton (self .root , text = btn_text , command = btn_command , ** self .style_config ["button" ])
53
+ button .grid (row = 5 + idx , column = 0 , pady = (15 , 10 ))
84
54
85
55
# Result Label
86
56
self .result_label = ctk .CTkLabel (self .root , text = "" , font = ("Arial" , 14 ))
87
- self .result_label .grid (row = 9 , column = 0 , padx = (10 , 0 ), pady = (20 , 20 ))
57
+ self .result_label .grid (row = 8 , column = 0 , pady = (20 , 20 ))
58
+
59
+ def _create_labeled_entry (self , label_text , row , show = None ):
60
+ """Helper method to create labeled entry widgets."""
61
+ label = ctk .CTkLabel (self .root , text = label_text , ** self .style_config ["label" ])
62
+ label .grid (row = row , column = 0 , ** self .grid_config )
63
+
64
+ entry = ctk .CTkEntry (self .root , ** self .style_config ["entry" ], show = show )
65
+ entry .grid (row = row + 1 , column = 0 , padx = (10 , 0 ), pady = (5 , 15 ))
66
+
67
+ return entry
68
+
69
+ def _get_input (self , prompt_text , input_type = str ):
70
+ """Unified method to get user input with type conversion."""
71
+ input_window = Toplevel (self .root )
72
+ input_window .title (prompt_text )
73
+ input_window .geometry ("400x200" )
74
+ input_window .resizable (False , False )
75
+
76
+ prompt_label = ctk .CTkLabel (input_window , text = prompt_text , font = ("Arial" , 15 , "bold" ))
77
+ prompt_label .pack (pady = (20 , 10 ))
78
+
79
+ input_entry = ctk .CTkEntry (input_window , font = ("Arial" , 15 , "bold" ), width = 300 )
80
+ input_entry .pack (pady = 10 )
81
+
82
+ input_value = []
83
+
84
+ def submit_input ():
85
+ try :
86
+ value = input_type (input_entry .get ())
87
+ input_value .append (value )
88
+ except ValueError :
89
+ input_value .append (None )
90
+ input_window .destroy ()
88
91
92
+ submit_button = ctk .CTkButton (input_window , text = "Submit" , command = submit_input , font = ("Arial" , 15 , "bold" ),
93
+ width = 150 )
94
+ submit_button .pack (pady = 10 )
89
95
96
+ input_window .wait_window ()
97
+ return input_value [0 ] if input_value else None
90
98
91
99
def create_account (self ):
92
100
"""Generate a new Stellar account and display the keys."""
@@ -99,75 +107,83 @@ def create_account(self):
99
107
100
108
def check_balance (self ):
101
109
"""Check and display the balance of the account."""
110
+ public_key = self .public_key_entry .get ()
111
+ if not public_key :
112
+ self .show_toplevel_message ("Public key is required." )
113
+ return
114
+
102
115
try :
103
- public_key = self .public_key_entry .get ()
104
116
server = Server ("https://horizon-testnet.stellar.org" )
105
117
account = server .accounts ().account_id (public_key ).call ()
106
118
balances = account ['balances' ]
107
119
if not balances or all (float (b ['balance' ]) == 0 for b in balances ):
108
- self .show_toplevel_message ("No balance" )
120
+ self .show_toplevel_message ("No balance found. " )
109
121
else :
110
122
balance_text = "\n " .join ([f"Asset Type: { b ['asset_type' ]} , Balance: { b ['balance' ]} " for b in balances ])
111
123
self .result_label .configure (text = balance_text )
112
- except Exception :
113
- self .show_toplevel_message ("Failed to retrieve balance" )
124
+ except NotFoundError :
125
+ self .show_toplevel_message ("Account not found." )
126
+ except Exception as e :
127
+ self .show_toplevel_message (f"Failed to retrieve balance: { str (e )} " )
114
128
115
129
def send_payment (self ):
116
130
"""Send a payment to another Stellar account."""
117
- try :
118
- source_secret = self .secret_key_entry .get ()
119
- if not source_secret :
120
- self .show_toplevel_message ("Payment unsuccessful: Secret key is required" )
121
- return
131
+ source_secret = self .secret_key_entry .get ()
132
+ if not source_secret :
133
+ self .show_toplevel_message ("Payment unsuccessful: Secret key is required." )
134
+ return
122
135
123
- destination_public = self .prompt_input ("Enter destination public key:" )
124
- print (f"Destination Public Key: { destination_public } " ) # Debugging line
125
- amount = self .prompt_float_input ("Enter amount to send:" )
126
- print (f"Amount to send: { amount } " ) # Debugging line
136
+ destination_public = self ._get_input ("Enter destination public key:" )
137
+ amount = self ._get_input ("Enter amount to send:" , float )
127
138
128
- if not destination_public or not amount :
129
- self .show_toplevel_message ("Payment unsuccessful: All fields are required" )
130
- return
139
+ if not destination_public or not amount :
140
+ self .show_toplevel_message ("Payment unsuccessful: All fields are required. " )
141
+ return
131
142
143
+ try :
132
144
source_keypair = Keypair .from_secret (source_secret )
133
145
server = Server ("https://horizon-testnet.stellar.org" )
134
146
135
147
# Check if source account exists
136
148
try :
137
149
source_account = server .load_account (source_keypair .public_key )
138
150
except NotFoundError :
139
- self .show_toplevel_message ("Payment unsuccessful: Source account not found" )
151
+ self .show_toplevel_message ("Payment unsuccessful: Source account not found. " )
140
152
return
141
153
142
154
# Check if destination account exists
143
155
try :
144
156
server .accounts ().account_id (destination_public ).call ()
145
157
except NotFoundError :
146
- self .show_toplevel_message ("Payment unsuccessful: Destination account not found" )
158
+ self .show_toplevel_message ("Payment unsuccessful: Destination account not found. " )
147
159
return
148
160
149
- transaction = TransactionBuilder (
150
- source_account ,
151
- network_passphrase = Network .TESTNET_NETWORK_PASSPHRASE ,
152
- base_fee = 100
153
- ).add_text_memo ("Stellar Payment" ).append_payment_op (
154
- destination = destination_public ,
155
- amount = str (amount ),
156
- asset = Asset .native () # Specify that the asset is the native XLM
157
- ).build ()
158
-
161
+ transaction = (
162
+ TransactionBuilder (
163
+ source_account ,
164
+ network_passphrase = Network .TESTNET_NETWORK_PASSPHRASE ,
165
+ base_fee = 100
166
+ )
167
+ .add_text_memo ("Stellar Payment" )
168
+ .append_payment_op (
169
+ destination = destination_public ,
170
+ amount = str (amount ),
171
+ asset = Asset .native () # Specify that the asset is the native XLM
172
+ )
173
+ .build ()
174
+ )
159
175
transaction .sign (source_keypair )
160
176
response = server .submit_transaction (transaction )
161
- self .result_label .configure (text = f"Payment sent!\n Transaction Hash:\n { response ['hash' ]} " , font = ( "Courier New" , 10 ))
162
-
177
+ self .result_label .configure (text = f"Payment sent!\n Transaction Hash:\n { response ['hash' ]} " ,
178
+ font = ( "Courier New" , 10 ))
163
179
except Exception as e :
164
- print (f"Exception occurred : { e } " ) # Debugging line
165
- self . show_toplevel_message ( "Payment unsuccessful" )
180
+ self . show_toplevel_message (f"Payment unsuccessful : { str ( e ) } " )
181
+
166
182
def show_toplevel_message (self , message ):
167
183
"""Display a top-level window with a message."""
168
184
top = Toplevel (self .root )
169
185
top .title ("Notification" )
170
- top .geometry ("400x400 " )
186
+ top .geometry ("400x200 " )
171
187
top .resizable (False , False )
172
188
173
189
msg = ctk .CTkLabel (top , text = message , font = ("Arial" , 15 , "bold" ))
@@ -176,68 +192,10 @@ def show_toplevel_message(self, message):
176
192
button = ctk .CTkButton (top , text = "OK" , command = top .destroy , font = ("Arial" , 15 , "bold" ), width = 150 )
177
193
button .pack (pady = 20 )
178
194
179
- def prompt_input (self , prompt_text ):
180
- """Prompt for string input using a custom Toplevel window."""
181
- input_window = Toplevel (self .root )
182
- input_window .title (prompt_text )
183
- input_window .geometry ("400x400" )
184
- input_window .resizable (False , False )
185
-
186
- prompt_label = ctk .CTkLabel (input_window , text = prompt_text , font = ("Arial" , 15 , "bold" ))
187
- prompt_label .pack (pady = (40 , 20 ))
188
-
189
- input_entry = ctk .CTkEntry (input_window , font = ("Arial" , 15 , "bold" ), width = 300 )
190
- input_entry .pack (pady = 20 )
191
-
192
- input_value = []
193
-
194
- def submit_input ():
195
- input_value .append (input_entry .get ())
196
- input_window .destroy ()
197
-
198
- submit_button = ctk .CTkButton (input_window , text = "Submit" , command = submit_input , font = ("Arial" , 15 , "bold" ),
199
- width = 150 )
200
- submit_button .pack (pady = 20 )
201
-
202
- input_window .wait_window ()
203
-
204
- return input_value [0 ] if input_value else None
205
-
206
- def prompt_float_input (self , prompt_text ):
207
- """Prompt for float input using a custom Toplevel window."""
208
- input_window = Toplevel (self .root )
209
- input_window .title (prompt_text )
210
- input_window .geometry ("400x400" )
211
- input_window .resizable (False , False )
212
-
213
- prompt_label = ctk .CTkLabel (input_window , text = prompt_text , font = ("Arial" , 15 , "bold" ))
214
- prompt_label .pack (pady = (40 , 20 ))
215
-
216
- input_entry = ctk .CTkEntry (input_window , font = ("Arial" , 15 , "bold" ), width = 300 )
217
- input_entry .pack (pady = 20 )
218
-
219
- input_value = []
220
-
221
- def submit_input ():
222
- try :
223
- input_value .append (float (input_entry .get ()))
224
- except ValueError :
225
- input_value .append (None )
226
- input_window .destroy ()
227
-
228
- submit_button = ctk .CTkButton (input_window , text = "Submit" , command = submit_input , font = ("Arial" , 15 , "bold" ),
229
- width = 150 )
230
- submit_button .pack (pady = 20 )
231
-
232
- input_window .wait_window ()
233
-
234
- return input_value [0 ] if input_value else None
235
-
236
195
237
196
if __name__ == "__main__" :
238
197
ctk .set_appearance_mode ("dark" ) # Use system theme
239
198
ctk .set_default_color_theme ("green" ) # Default color theme without customization
240
-
241
199
root = ctk .CTk ()
242
200
app = StellarWalletApp (root )
243
201
root .mainloop ()
0 commit comments