lunalib 1.2.3__py3-none-any.whl → 1.5.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
core/wallet.py CHANGED
@@ -1,347 +1,408 @@
1
- # wallet.py
2
- import time
3
- import hashlib
4
- import json
5
- from cryptography.fernet import Fernet
6
- import base64
7
- import os
8
-
9
- class LunaWallet:
10
- """Luna wallet implementation with proper key management"""
11
-
12
- def __init__(self, data_dir=None):
13
- self.data_dir = data_dir
14
- self.wallets = {} # Main wallet storage: {address: wallet_data}
15
- self.current_wallet_address = None # Track which wallet is active
16
-
17
- # Initialize with an empty current wallet state
18
- self._reset_current_wallet()
19
-
20
- def _reset_current_wallet(self):
21
- """Reset current wallet to empty state"""
22
- self.address = None
23
- self.balance = 0.0
24
- self.available_balance = 0.0
25
- self.created = time.time()
26
- self.private_key = None
27
- self.public_key = None
28
- self.encrypted_private_key = None
29
- self.label = "New Wallet"
30
- self.is_locked = True
31
-
32
- def _generate_address(self):
33
- """Generate wallet address"""
34
- base_data = f"LUN_{int(time.time()*1000)}_{hashlib.sha256(str(time.time()).encode()).hexdigest()[:8]}"
35
- return hashlib.sha256(base_data.encode()).hexdigest()[:32]
36
-
37
- def _generate_private_key(self):
38
- """Generate private key"""
39
- return f"priv_{hashlib.sha256(str(time.time()).encode()).hexdigest()}"
40
-
41
- def _derive_public_key(self, private_key=None):
42
- """Derive public key from private key"""
43
- priv_key = private_key or self.private_key
44
- if not priv_key:
45
- return None
46
- return f"pub_{priv_key[-16:]}"
47
-
48
- def get_wallet_info(self):
49
- """Get complete wallet information for current wallet"""
50
- if not self.address:
51
- return None
52
- return {
53
- 'address': self.address,
54
- 'balance': self.balance,
55
- 'available_balance': self.available_balance,
56
- 'created': self.created,
57
- 'private_key': self.private_key,
58
- 'public_key': self.public_key,
59
- 'encrypted_private_key': self.encrypted_private_key,
60
- 'label': self.label,
61
- 'is_locked': self.is_locked
62
- }
63
-
64
- def create_new_wallet(self, name, password):
65
- """Create a new wallet and add to collection without switching"""
66
- # Generate new wallet data
67
- address = self._generate_address()
68
- private_key = self._generate_private_key()
69
- public_key = f"pub_{private_key[-16:]}"
70
-
71
- # Encrypt private key
72
- key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
73
- fernet = Fernet(key)
74
- encrypted_private_key = fernet.encrypt(private_key.encode())
75
-
76
- # Create new wallet data
77
- new_wallet_data = {
78
- 'address': address,
79
- 'balance': 0.0,
80
- 'available_balance': 0.0,
81
- 'created': time.time(),
82
- 'private_key': private_key,
83
- 'public_key': public_key,
84
- 'encrypted_private_key': encrypted_private_key,
85
- 'label': name,
86
- 'is_locked': True
87
- }
88
-
89
- # CRITICAL: Add to wallets collection
90
- self.wallets[address] = new_wallet_data
91
-
92
- print(f"DEBUG: Created new wallet {address}, total wallets: {len(self.wallets)}")
93
-
94
- return new_wallet_data
95
- def create_wallet(self, name, password):
96
- """Create a new wallet and set it as current"""
97
- # Generate new wallet data
98
- address = self._generate_address()
99
- private_key = self._generate_private_key()
100
- public_key = f"pub_{private_key[-16:]}"
101
-
102
- # Encrypt private key
103
- key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
104
- fernet = Fernet(key)
105
- encrypted_private_key = fernet.encrypt(private_key.encode())
106
-
107
- # Create wallet data
108
- wallet_data = {
109
- 'address': address,
110
- 'balance': 0.0,
111
- 'available_balance': 0.0,
112
- 'created': time.time(),
113
- 'private_key': private_key,
114
- 'public_key': public_key,
115
- 'encrypted_private_key': encrypted_private_key,
116
- 'label': name,
117
- 'is_locked': True
118
- }
119
-
120
- # CRITICAL: Add to wallets collection
121
- self.wallets[address] = wallet_data
122
-
123
- # Set as current wallet
124
- self._set_current_wallet(wallet_data)
125
-
126
- print(f"DEBUG: Created and switched to wallet {address}, total wallets: {len(self.wallets)}")
127
-
128
- return wallet_data
129
- def _set_current_wallet(self, wallet_data):
130
- """Set the current wallet from wallet data"""
131
- self.current_wallet_address = wallet_data['address']
132
- self.address = wallet_data['address']
133
- self.balance = wallet_data['balance']
134
- self.available_balance = wallet_data['available_balance']
135
- self.created = wallet_data['created']
136
- self.private_key = wallet_data['private_key']
137
- self.public_key = wallet_data['public_key']
138
- self.encrypted_private_key = wallet_data['encrypted_private_key']
139
- self.label = wallet_data['label']
140
- self.is_locked = wallet_data.get('is_locked', True)
141
-
142
- def switch_wallet(self, address, password=None):
143
- """Switch to a different wallet in the collection"""
144
- if address in self.wallets:
145
- wallet_data = self.wallets[address]
146
- self._set_current_wallet(wallet_data)
147
-
148
- # If password provided, unlock the wallet
149
- if password:
150
- return self.unlock_wallet(address, password)
151
-
152
- return True
153
- return False
154
-
155
- def unlock_wallet(self, address, password):
156
- """Unlock wallet with password"""
157
- if address not in self.wallets:
158
- return False
159
-
160
- wallet_data = self.wallets[address]
161
-
162
- try:
163
- if wallet_data.get('encrypted_private_key'):
164
- key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
165
- fernet = Fernet(key)
166
- decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
167
- wallet_data['private_key'] = decrypted_key.decode()
168
- wallet_data['is_locked'] = False
169
-
170
- # If this is the current wallet, update current state
171
- if self.current_wallet_address == address:
172
- self.private_key = wallet_data['private_key']
173
- self.is_locked = False
174
-
175
- return True
176
- except:
177
- pass
178
- return False
179
-
180
- @property
181
- def is_unlocked(self):
182
- """Check if current wallet is unlocked"""
183
- if not self.current_wallet_address:
184
- return False
185
- wallet_data = self.wallets.get(self.current_wallet_address, {})
186
- return not wallet_data.get('is_locked', True)
187
-
188
- def export_private_key(self, address, password):
189
- """Export private key with password decryption"""
190
- if address not in self.wallets:
191
- return None
192
-
193
- wallet_data = self.wallets[address]
194
-
195
- try:
196
- if wallet_data.get('encrypted_private_key'):
197
- key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
198
- fernet = Fernet(key)
199
- decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
200
- return decrypted_key.decode()
201
- except:
202
- pass
203
- return None
204
-
205
- def import_wallet(self, wallet_data, password=None):
206
- """Import wallet from data"""
207
- if isinstance(wallet_data, dict):
208
- address = wallet_data.get('address')
209
- if not address:
210
- return False
211
-
212
- # Add to wallets collection
213
- self.wallets[address] = wallet_data.copy()
214
-
215
- # Set as current wallet
216
- self._set_current_wallet(wallet_data)
217
-
218
- if password and wallet_data.get('encrypted_private_key'):
219
- return self.unlock_wallet(address, password)
220
-
221
- return True
222
- return False
223
-
224
- def update_balance(self, new_balance):
225
- """Update current wallet balance"""
226
- self.balance = float(new_balance)
227
- self.available_balance = float(new_balance)
228
-
229
- # Also update in wallets collection
230
- if self.current_wallet_address and self.current_wallet_address in self.wallets:
231
- self.wallets[self.current_wallet_address]['balance'] = self.balance
232
- self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
233
-
234
- return True
235
-
236
- def get_balance(self):
237
- """Get current wallet balance"""
238
- return self.balance
239
-
240
- def get_wallet_by_address(self, address):
241
- """Get wallet by address from wallets collection"""
242
- return self.wallets.get(address)
243
-
244
- def list_wallets(self):
245
- """List all wallets in collection"""
246
- return list(self.wallets.keys())
247
-
248
- def get_current_wallet_info(self):
249
- """Get current wallet information"""
250
- if not self.current_wallet_address:
251
- return None
252
- return self.wallets.get(self.current_wallet_address)
253
-
254
- def save_to_file(self, filename=None):
255
- """Save wallet to file"""
256
- if not self.data_dir:
257
- return False
258
-
259
- if filename is None:
260
- filename = f"wallet_{self.address}.json"
261
-
262
- filepath = os.path.join(self.data_dir, filename)
263
-
264
- try:
265
- # Ensure directory exists
266
- os.makedirs(self.data_dir, exist_ok=True)
267
-
268
- # Prepare encrypted private key for serialization
269
- encrypted_key_data = None
270
- if self.encrypted_private_key:
271
- # Ensure it's bytes before encoding
272
- if isinstance(self.encrypted_private_key, bytes):
273
- encrypted_key_data = base64.b64encode(self.encrypted_private_key).decode('utf-8')
274
- else:
275
- encrypted_key_data = base64.b64encode(self.encrypted_private_key.encode()).decode('utf-8')
276
-
277
- # Prepare wallets for serialization (remove any non-serializable data)
278
- serializable_wallets = {}
279
- for addr, wallet_info in self.wallets.items():
280
- serializable_wallet = wallet_info.copy()
281
- # Ensure encrypted_private_key is serializable
282
- if serializable_wallet.get('encrypted_private_key') and isinstance(serializable_wallet['encrypted_private_key'], bytes):
283
- serializable_wallet['encrypted_private_key'] = base64.b64encode(
284
- serializable_wallet['encrypted_private_key']
285
- ).decode('utf-8')
286
- serializable_wallets[addr] = serializable_wallet
287
-
288
- wallet_data = {
289
- 'address': self.address,
290
- 'balance': self.balance,
291
- 'available_balance': self.available_balance,
292
- 'created': self.created,
293
- 'public_key': self.public_key,
294
- 'encrypted_private_key': encrypted_key_data,
295
- 'label': self.label,
296
- 'is_locked': self.is_locked,
297
- 'wallets': serializable_wallets,
298
- 'current_wallet_address': self.current_wallet_address
299
- }
300
-
301
- with open(filepath, 'w') as f:
302
- json.dump(wallet_data, f, indent=2)
303
- return True
304
- except Exception as e:
305
- print(f"Error saving wallet: {e}")
306
- import traceback
307
- traceback.print_exc()
308
- return False
309
-
310
- def load_from_file(self, filename, password=None):
311
- """Load wallet from file"""
312
- if not self.data_dir:
313
- return False
314
-
315
- filepath = os.path.join(self.data_dir, filename)
316
-
317
- try:
318
- with open(filepath, 'r') as f:
319
- wallet_data = json.load(f)
320
-
321
- # Load wallets collection
322
- self.wallets = wallet_data.get('wallets', {})
323
-
324
- # Load current wallet address
325
- self.current_wallet_address = wallet_data.get('current_wallet_address')
326
-
327
- # If we have a current wallet, load its data
328
- if self.current_wallet_address and self.current_wallet_address in self.wallets:
329
- current_wallet_data = self.wallets[self.current_wallet_address]
330
- self._set_current_wallet(current_wallet_data)
331
-
332
- # Handle encrypted private key
333
- encrypted_key = wallet_data.get('encrypted_private_key')
334
- if encrypted_key:
335
- self.encrypted_private_key = base64.b64decode(encrypted_key.encode())
336
- # Also update in wallets collection
337
- if self.current_wallet_address in self.wallets:
338
- self.wallets[self.current_wallet_address]['encrypted_private_key'] = self.encrypted_private_key
339
-
340
- # If password provided and we have encrypted key, unlock
341
- if password and self.encrypted_private_key and self.current_wallet_address:
342
- return self.unlock_wallet(self.current_wallet_address, password)
343
-
344
- return True
345
- except Exception as e:
346
- print(f"Error loading wallet: {e}")
1
+ # wallet.py
2
+ import time
3
+ import hashlib
4
+ import json
5
+ from cryptography.fernet import Fernet
6
+ import base64
7
+ import os
8
+
9
+ class LunaWallet:
10
+ """Luna wallet implementation with proper key management"""
11
+
12
+ def __init__(self, data_dir=None):
13
+ self.data_dir = data_dir
14
+ self.wallets = {} # Main wallet storage: {address: wallet_data}
15
+ self.current_wallet_address = None # Track which wallet is active
16
+
17
+ # Initialize with an empty current wallet state
18
+ self._reset_current_wallet()
19
+
20
+ def _reset_current_wallet(self):
21
+ """Reset current wallet to empty state"""
22
+ self.address = None
23
+ self.balance = 0.0
24
+ self.available_balance = 0.0
25
+ self.created = time.time()
26
+ self.private_key = None
27
+ self.public_key = None
28
+ self.encrypted_private_key = None
29
+ self.label = "New Wallet"
30
+ self.is_locked = True
31
+ self.pending_transactions = {} # {tx_hash: amount}
32
+
33
+ def _generate_address(self):
34
+ """Generate wallet address"""
35
+ base_data = f"LUN_{int(time.time()*1000)}_{hashlib.sha256(str(time.time()).encode()).hexdigest()[:8]}"
36
+ return hashlib.sha256(base_data.encode()).hexdigest()[:32]
37
+
38
+ def _generate_private_key(self):
39
+ """Generate private key"""
40
+ return f"priv_{hashlib.sha256(str(time.time()).encode()).hexdigest()}"
41
+
42
+ def _derive_public_key(self, private_key=None):
43
+ """Derive public key from private key"""
44
+ priv_key = private_key or self.private_key
45
+ if not priv_key:
46
+ return None
47
+ return f"pub_{priv_key[-16:]}"
48
+
49
+ def get_wallet_info(self):
50
+ """Get complete wallet information for current wallet"""
51
+ if not self.address:
52
+ return None
53
+ return {
54
+ 'address': self.address,
55
+ 'balance': self.balance,
56
+ 'available_balance': self.available_balance,
57
+ 'pending_amount': self.get_pending_amount(),
58
+ 'created': self.created,
59
+ 'private_key': self.private_key,
60
+ 'public_key': self.public_key,
61
+ 'encrypted_private_key': self.encrypted_private_key,
62
+ 'label': self.label,
63
+ 'is_locked': self.is_locked
64
+ }
65
+
66
+ def create_new_wallet(self, name, password):
67
+ """Create a new wallet and add to collection without switching"""
68
+ # Generate new wallet data
69
+ address = self._generate_address()
70
+ private_key = self._generate_private_key()
71
+ public_key = f"pub_{private_key[-16:]}"
72
+
73
+ # Encrypt private key
74
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
75
+ fernet = Fernet(key)
76
+ encrypted_private_key = fernet.encrypt(private_key.encode())
77
+
78
+ # Create new wallet data
79
+ new_wallet_data = {
80
+ 'address': address,
81
+ 'balance': 0.0,
82
+ 'available_balance': 0.0,
83
+ 'created': time.time(),
84
+ 'private_key': private_key,
85
+ 'public_key': public_key,
86
+ 'encrypted_private_key': encrypted_private_key,
87
+ 'label': name,
88
+ 'is_locked': True,
89
+ 'pending_transactions': {}
90
+ }
91
+
92
+ # CRITICAL: Add to wallets collection
93
+ self.wallets[address] = new_wallet_data
94
+
95
+ print(f"DEBUG: Created new wallet {address}, total wallets: {len(self.wallets)}")
96
+
97
+ return new_wallet_data
98
+ def create_wallet(self, name, password):
99
+ """Create a new wallet and set it as current"""
100
+ # Generate new wallet data
101
+ address = self._generate_address()
102
+ private_key = self._generate_private_key()
103
+ public_key = f"pub_{private_key[-16:]}"
104
+
105
+ # Encrypt private key
106
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
107
+ fernet = Fernet(key)
108
+ encrypted_private_key = fernet.encrypt(private_key.encode())
109
+
110
+ # Create wallet data
111
+ wallet_data = {
112
+ 'address': address,
113
+ 'balance': 0.0,
114
+ 'available_balance': 0.0,
115
+ 'created': time.time(),
116
+ 'private_key': private_key,
117
+ 'public_key': public_key,
118
+ 'encrypted_private_key': encrypted_private_key,
119
+ 'label': name,
120
+ 'is_locked': True,
121
+ 'pending_transactions': {}
122
+ }
123
+
124
+ # CRITICAL: Add to wallets collection
125
+ self.wallets[address] = wallet_data
126
+
127
+ # Set as current wallet
128
+ self._set_current_wallet(wallet_data)
129
+
130
+ print(f"DEBUG: Created and switched to wallet {address}, total wallets: {len(self.wallets)}")
131
+
132
+ return wallet_data
133
+ def _set_current_wallet(self, wallet_data):
134
+ """Set the current wallet from wallet data"""
135
+ self.current_wallet_address = wallet_data['address']
136
+ self.address = wallet_data['address']
137
+ self.balance = wallet_data['balance']
138
+ self.pending_transactions = wallet_data.get('pending_transactions', {})
139
+ self._recalculate_available_balance()
140
+ self.created = wallet_data['created']
141
+ self.private_key = wallet_data['private_key']
142
+ self.public_key = wallet_data['public_key']
143
+ self.encrypted_private_key = wallet_data['encrypted_private_key']
144
+ self.label = wallet_data['label']
145
+ self.is_locked = wallet_data.get('is_locked', True)
146
+
147
+ def switch_wallet(self, address, password=None):
148
+ """Switch to a different wallet in the collection"""
149
+ if address in self.wallets:
150
+ wallet_data = self.wallets[address]
151
+ self._set_current_wallet(wallet_data)
152
+
153
+ # If password provided, unlock the wallet
154
+ if password:
155
+ return self.unlock_wallet(address, password)
156
+
157
+ return True
158
+ return False
159
+
160
+ def unlock_wallet(self, address, password):
161
+ """Unlock wallet with password"""
162
+ if address not in self.wallets:
163
+ return False
164
+
165
+ wallet_data = self.wallets[address]
166
+
167
+ try:
168
+ if wallet_data.get('encrypted_private_key'):
169
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
170
+ fernet = Fernet(key)
171
+ decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
172
+ wallet_data['private_key'] = decrypted_key.decode()
173
+ wallet_data['is_locked'] = False
174
+
175
+ # If this is the current wallet, update current state
176
+ if self.current_wallet_address == address:
177
+ self.private_key = wallet_data['private_key']
178
+ self.is_locked = False
179
+
180
+ return True
181
+ except:
182
+ pass
183
+ return False
184
+
185
+ @property
186
+ def is_unlocked(self):
187
+ """Check if current wallet is unlocked"""
188
+ if not self.current_wallet_address:
189
+ return False
190
+ wallet_data = self.wallets.get(self.current_wallet_address, {})
191
+ return not wallet_data.get('is_locked', True)
192
+
193
+ def export_private_key(self, address, password):
194
+ """Export private key with password decryption"""
195
+ if address not in self.wallets:
196
+ return None
197
+
198
+ wallet_data = self.wallets[address]
199
+
200
+ try:
201
+ if wallet_data.get('encrypted_private_key'):
202
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
203
+ fernet = Fernet(key)
204
+ decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
205
+ return decrypted_key.decode()
206
+ except:
207
+ pass
208
+ return None
209
+
210
+ def import_wallet(self, wallet_data, password=None):
211
+ """Import wallet from data"""
212
+ if isinstance(wallet_data, dict):
213
+ address = wallet_data.get('address')
214
+ if not address:
215
+ return False
216
+
217
+ # Ensure pending_transactions exists
218
+ if 'pending_transactions' not in wallet_data:
219
+ wallet_data['pending_transactions'] = {}
220
+
221
+ # Add to wallets collection
222
+ self.wallets[address] = wallet_data.copy()
223
+
224
+ # Set as current wallet
225
+ self._set_current_wallet(wallet_data)
226
+
227
+ if password and wallet_data.get('encrypted_private_key'):
228
+ return self.unlock_wallet(address, password)
229
+
230
+ return True
231
+ return False
232
+
233
+ def update_balance(self, new_balance):
234
+ """Update current wallet balance"""
235
+ self.balance = float(new_balance)
236
+ self._recalculate_available_balance()
237
+
238
+ # Also update in wallets collection
239
+ if self.current_wallet_address and self.current_wallet_address in self.wallets:
240
+ self.wallets[self.current_wallet_address]['balance'] = self.balance
241
+ self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
242
+
243
+ return True
244
+
245
+ def _recalculate_available_balance(self):
246
+ """Recalculate available balance based on pending transactions"""
247
+ pending_amount = sum(self.pending_transactions.values())
248
+ self.available_balance = max(0.0, self.balance - pending_amount)
249
+
250
+ def add_pending_transaction(self, tx_hash: str, amount: float) -> bool:
251
+ """Add a transaction to pending list (e.g., when sent to mempool)"""
252
+ if amount > self.available_balance:
253
+ return False
254
+ self.pending_transactions[tx_hash] = float(amount)
255
+ self._recalculate_available_balance()
256
+
257
+ # Update in wallets collection
258
+ if self.current_wallet_address and self.current_wallet_address in self.wallets:
259
+ self.wallets[self.current_wallet_address]['pending_transactions'] = self.pending_transactions
260
+ self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
261
+
262
+ return True
263
+
264
+ def confirm_pending_transaction(self, tx_hash: str) -> bool:
265
+ """Confirm a pending transaction (e.g., added to blockchain)"""
266
+ if tx_hash in self.pending_transactions:
267
+ del self.pending_transactions[tx_hash]
268
+ self._recalculate_available_balance()
269
+
270
+ # Update in wallets collection
271
+ if self.current_wallet_address and self.current_wallet_address in self.wallets:
272
+ self.wallets[self.current_wallet_address]['pending_transactions'] = self.pending_transactions
273
+ self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
274
+
275
+ return True
276
+ return False
277
+
278
+ def get_pending_amount(self) -> float:
279
+ """Get total amount in pending transactions"""
280
+ return sum(self.pending_transactions.values())
281
+
282
+ def get_pending_transactions(self) -> dict:
283
+ """Get all pending transactions"""
284
+ return self.pending_transactions.copy()
285
+
286
+ def clear_pending_transaction(self, tx_hash: str) -> bool:
287
+ """Remove a pending transaction (e.g., if it failed)"""
288
+ return self.confirm_pending_transaction(tx_hash)
289
+
290
+ def get_balance(self):
291
+ """Get current wallet balance"""
292
+ return self.balance
293
+
294
+ def get_wallet_by_address(self, address):
295
+ """Get wallet by address from wallets collection"""
296
+ return self.wallets.get(address)
297
+
298
+ def list_wallets(self):
299
+ """List all wallets in collection"""
300
+ return list(self.wallets.keys())
301
+
302
+ def get_current_wallet_info(self):
303
+ """Get current wallet information"""
304
+ if not self.current_wallet_address:
305
+ return None
306
+ return self.wallets.get(self.current_wallet_address)
307
+
308
+ def save_to_file(self, filename=None):
309
+ """Save wallet to file"""
310
+ if not self.data_dir:
311
+ return False
312
+
313
+ if filename is None:
314
+ filename = f"wallet_{self.address}.json"
315
+
316
+ filepath = os.path.join(self.data_dir, filename)
317
+
318
+ try:
319
+ # Ensure directory exists
320
+ os.makedirs(self.data_dir, exist_ok=True)
321
+
322
+ # Prepare encrypted private key for serialization
323
+ encrypted_key_data = None
324
+ if self.encrypted_private_key:
325
+ # Ensure it's bytes before encoding
326
+ if isinstance(self.encrypted_private_key, bytes):
327
+ encrypted_key_data = base64.b64encode(self.encrypted_private_key).decode('utf-8')
328
+ else:
329
+ encrypted_key_data = base64.b64encode(self.encrypted_private_key.encode()).decode('utf-8')
330
+
331
+ # Prepare wallets for serialization (remove any non-serializable data)
332
+ serializable_wallets = {}
333
+ for addr, wallet_info in self.wallets.items():
334
+ serializable_wallet = wallet_info.copy()
335
+ # Ensure encrypted_private_key is serializable
336
+ if serializable_wallet.get('encrypted_private_key') and isinstance(serializable_wallet['encrypted_private_key'], bytes):
337
+ serializable_wallet['encrypted_private_key'] = base64.b64encode(
338
+ serializable_wallet['encrypted_private_key']
339
+ ).decode('utf-8')
340
+ serializable_wallets[addr] = serializable_wallet
341
+
342
+ wallet_data = {
343
+ 'address': self.address,
344
+ 'balance': self.balance,
345
+ 'available_balance': self.available_balance,
346
+ 'pending_amount': self.get_pending_amount(),
347
+ 'pending_transactions': self.pending_transactions,
348
+ 'created': self.created,
349
+ 'public_key': self.public_key,
350
+ 'encrypted_private_key': encrypted_key_data,
351
+ 'label': self.label,
352
+ 'is_locked': self.is_locked,
353
+ 'wallets': serializable_wallets,
354
+ 'current_wallet_address': self.current_wallet_address
355
+ }
356
+
357
+ with open(filepath, 'w') as f:
358
+ json.dump(wallet_data, f, indent=2)
359
+ return True
360
+ except Exception as e:
361
+ print(f"Error saving wallet: {e}")
362
+ import traceback
363
+ traceback.print_exc()
364
+ return False
365
+
366
+ def load_from_file(self, filename, password=None):
367
+ """Load wallet from file"""
368
+ if not self.data_dir:
369
+ return False
370
+
371
+ filepath = os.path.join(self.data_dir, filename)
372
+
373
+ try:
374
+ with open(filepath, 'r') as f:
375
+ wallet_data = json.load(f)
376
+
377
+ # Load wallets collection
378
+ self.wallets = wallet_data.get('wallets', {})
379
+
380
+ # Ensure all wallets have pending_transactions field
381
+ for addr, w_data in self.wallets.items():
382
+ if 'pending_transactions' not in w_data:
383
+ w_data['pending_transactions'] = {}
384
+
385
+ # Load current wallet address
386
+ self.current_wallet_address = wallet_data.get('current_wallet_address')
387
+
388
+ # If we have a current wallet, load its data
389
+ if self.current_wallet_address and self.current_wallet_address in self.wallets:
390
+ current_wallet_data = self.wallets[self.current_wallet_address]
391
+ self._set_current_wallet(current_wallet_data)
392
+
393
+ # Handle encrypted private key
394
+ encrypted_key = wallet_data.get('encrypted_private_key')
395
+ if encrypted_key:
396
+ self.encrypted_private_key = base64.b64decode(encrypted_key.encode())
397
+ # Also update in wallets collection
398
+ if self.current_wallet_address in self.wallets:
399
+ self.wallets[self.current_wallet_address]['encrypted_private_key'] = self.encrypted_private_key
400
+
401
+ # If password provided and we have encrypted key, unlock
402
+ if password and self.encrypted_private_key and self.current_wallet_address:
403
+ return self.unlock_wallet(self.current_wallet_address, password)
404
+
405
+ return True
406
+ except Exception as e:
407
+ print(f"Error loading wallet: {e}")
347
408
  return False