lunalib 1.6.6__py3-none-any.whl → 1.7.3__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.
lunalib/core/wallet.py CHANGED
@@ -10,15 +10,164 @@ import concurrent.futures
10
10
  from typing import Optional, Callable, Dict, List, Tuple
11
11
  from cryptography.fernet import Fernet
12
12
  from ..core.crypto import KeyManager
13
+ from .wallet_db import WalletDB
13
14
 
14
15
 
15
16
  class LunaWallet:
17
+ def __init__(self, data_dir=None):
18
+ self.data_dir = data_dir or os.path.expanduser("~/.lunawallet")
19
+ self.db = WalletDB(self.data_dir)
20
+ self.wallets = {} # address -> wallet dict (in-memory cache)
21
+ self.current_wallet_address = None
22
+ self.key_manager = KeyManager()
23
+ self.balance_thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=3)
24
+ self.balance_callbacks = []
25
+ self.balance_loading = False
26
+ self.last_balance_update = 0
27
+ self.balance_update_interval = 30 # seconds
28
+ self._reset_current_wallet()
29
+ self._confirmed_tx_cache: Dict[str, List[Dict]] = {}
30
+ self._pending_tx_cache: Dict[str, List[Dict]] = {}
31
+ self._load_wallets_from_db()
32
+
33
+ def _load_wallets_from_db(self):
34
+ """Load all wallets from the database into self.wallets."""
35
+ self.wallets = {}
36
+ for row in self.db.list_wallets():
37
+ addr, label, is_locked, balance, available_balance = row
38
+ w = self.db.load_wallet(addr)
39
+ if w:
40
+ self.wallets[addr] = w
41
+ # Set current_wallet_address to the first wallet if any
42
+ if self.wallets and not self.current_wallet_address:
43
+ self.current_wallet_address = next(iter(self.wallets))
44
+
45
+ def lock_wallet(self, address=None):
46
+ """Lock the wallet (removes private key from memory and updates db)"""
47
+ addr = address or self.current_wallet_address
48
+ if not addr or addr not in self.wallets:
49
+ return {"success": False, "error": "Wallet not found"}
50
+ self.wallets[addr]["is_locked"] = True
51
+ self.wallets[addr]["private_key"] = None
52
+ if addr == self.current_wallet_address:
53
+ self.private_key = None
54
+ self.is_locked = True
55
+ # Update db
56
+ w = self.wallets[addr]
57
+ self.db.save_wallet(
58
+ addr, w.get("label", ""), w.get("public_key", ""), w.get("encrypted_private_key", b""), True, w.get("created", time.time()), w.get("balance", 0.0), w.get("available_balance", 0.0)
59
+ )
60
+ return {"success": True}
61
+
62
+ def unlock_wallet(self, address, password):
63
+ """Unlock wallet with password using SM2. Returns dict with success/error."""
64
+ if address not in self.wallets:
65
+ return {"success": False, "error": f"Wallet {address} not found"}
66
+ wallet_data = self.wallets[address]
67
+ try:
68
+ if wallet_data.get("encrypted_private_key"):
69
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
70
+ fernet = Fernet(key)
71
+ decrypted_key = fernet.decrypt(wallet_data["encrypted_private_key"])
72
+ wallet_data["private_key"] = decrypted_key.decode()
73
+ wallet_data["is_locked"] = False
74
+ if self.current_wallet_address == address:
75
+ self.private_key = wallet_data["private_key"]
76
+ self.is_locked = False
77
+ # Update db
78
+ self.db.save_wallet(
79
+ address, wallet_data.get("label", ""), wallet_data.get("public_key", ""), wallet_data.get("encrypted_private_key", b""), False, wallet_data.get("created", time.time()), wallet_data.get("balance", 0.0), wallet_data.get("available_balance", 0.0)
80
+ )
81
+ if self._verify_wallet_integrity():
82
+ return {"success": True}
83
+ else:
84
+ return {"success": False, "error": "Cryptographic verification failed"}
85
+ except Exception as e:
86
+ return {"success": False, "error": f"Unlock failed: {e}"}
87
+ return {"success": False, "error": "Unlock failed"}
88
+
89
+ def get_wallet_index(self):
90
+ """Return a list of all wallets with address, label, is_locked, balance, available_balance."""
91
+ self._load_wallets_from_db()
92
+ index = []
93
+ for addr, data in self.wallets.items():
94
+ index.append({
95
+ "address": addr,
96
+ "label": data.get("label", ""),
97
+ "is_locked": data.get("is_locked", True),
98
+ "balance": data.get("balance", 0.0),
99
+ "available_balance": data.get("available_balance", 0.0),
100
+ })
101
+ return index
102
+
103
+ def get_wallet_info(self, address=None):
104
+ """Get info for a single wallet (address, label, balances, is_locked, created, public_key)."""
105
+ addr = address or self.current_wallet_address
106
+ if not addr or addr not in self.wallets:
107
+ return {"success": False, "error": "Wallet not found"}
108
+ data = self.wallets[addr]
109
+ return {
110
+ "address": addr,
111
+ "label": data.get("label", ""),
112
+ "is_locked": data.get("is_locked", True),
113
+ "balance": data.get("balance", 0.0),
114
+ "available_balance": data.get("available_balance", 0.0),
115
+ "created": data.get("created", 0),
116
+ "public_key": data.get("public_key", ""),
117
+ }
118
+
119
+ def get_wallet_balances(self, address=None):
120
+ """Get available and pending balances for a wallet."""
121
+ addr = address or self.current_wallet_address
122
+ if not addr or addr not in self.wallets:
123
+ return {"success": False, "error": "Wallet not found"}
124
+ data = self.wallets[addr]
125
+ pending_out = 0.0
126
+ pending_in = 0.0
127
+ if addr in self._pending_tx_cache:
128
+ pending_out, pending_in = self._compute_pending_totals(
129
+ self._pending_tx_cache[addr], addr
130
+ )
131
+ return {
132
+ "address": addr,
133
+ "balance": data.get("balance", 0.0),
134
+ "available_balance": data.get("available_balance", 0.0),
135
+ "pending_out": pending_out,
136
+ "pending_in": pending_in,
137
+ }
138
+
139
+ def get_wallet_transaction_history(self, address=None):
140
+ """Get confirmed and pending transactions for a wallet."""
141
+ addr = address or self.current_wallet_address
142
+ if not addr:
143
+ return {"success": False, "error": "No wallet selected"}
144
+ confirmed = self._confirmed_tx_cache.get(addr, [])
145
+ pending = self._pending_tx_cache.get(addr, [])
146
+ if not confirmed:
147
+ from lunalib.core.blockchain import BlockchainManager
148
+
149
+ blockchain = BlockchainManager()
150
+ confirmed = blockchain.scan_transactions_for_address(addr)
151
+ self._confirmed_tx_cache[addr] = confirmed
152
+ if not pending:
153
+ from lunalib.core.mempool import MempoolManager
154
+
155
+ mempool = MempoolManager()
156
+ pending = mempool.get_pending_transactions(addr, fetch_remote=True)
157
+ self._pending_tx_cache[addr] = pending
158
+ return {
159
+ "address": addr,
160
+ "confirmed": confirmed,
161
+ "pending": pending,
162
+ "total_confirmed": len(confirmed),
163
+ "total_pending": len(pending),
164
+ }
165
+
16
166
  def __init__(self, data_dir=None):
17
167
  self.data_dir = data_dir
18
168
  self.wallets = {}
19
169
  self.current_wallet_address = None
20
170
  self.key_manager = KeyManager()
21
-
22
171
  # Threading for asynchronous balance loading
23
172
  self.balance_thread_pool = concurrent.futures.ThreadPoolExecutor(max_workers=3)
24
173
  self.balance_callbacks = []
@@ -34,11 +183,10 @@ class LunaWallet:
34
183
  # ----------------------
35
184
  def _normalize_address(self, addr: str) -> str:
36
185
  if not addr:
37
- return ''
186
+ return ""
38
187
  addr_str = str(addr).strip("'\" ").lower()
39
- return addr_str[4:] if addr_str.startswith('lun_') else addr_str
40
-
41
-
188
+ return addr_str[4:] if addr_str.startswith("lun_") else addr_str
189
+
42
190
  def _reset_current_wallet(self):
43
191
  """Reset current wallet to empty state"""
44
192
  self.address = None
@@ -46,51 +194,51 @@ class LunaWallet:
46
194
  self.available_balance = 0.0 # Available balance (total - pending outgoing)
47
195
  self.created = time.time()
48
196
  self.private_key = None # Will store REAL SM2 private key
49
- self.public_key = None # Will store REAL SM2 public key
197
+ self.public_key = None # Will store REAL SM2 public key
50
198
  self.encrypted_private_key = None
51
199
  self.label = "New Wallet"
52
200
  self.is_locked = True
53
-
201
+
54
202
  # ============================================================================
55
203
  # REAL CRYPTOGRAPHIC KEY GENERATION (SM2 EAST ASIAN STANDARD)
56
204
  # ============================================================================
57
-
58
-
59
-
205
+
60
206
  def _sign_transaction_data(self, transaction_data):
61
207
  """Sign transaction data with SM2 private key"""
62
208
  if not self.private_key or self.is_locked:
63
209
  print("DEBUG: Cannot sign - wallet locked or no private key")
64
210
  return None
65
-
211
+
66
212
  # Convert transaction data to string for signing
67
213
  tx_string = json.dumps(transaction_data, sort_keys=True)
68
-
214
+
69
215
  # Sign with SM2
70
216
  signature = self.sm2.sign_message(self.private_key, tx_string)
71
-
217
+
72
218
  if signature:
73
- print(f"DEBUG: Transaction signed successfully, signature: {signature[:16]}...")
219
+ print(
220
+ f"DEBUG: Transaction signed successfully, signature: {signature[:16]}..."
221
+ )
74
222
  else:
75
223
  print("DEBUG: Failed to sign transaction")
76
-
224
+
77
225
  return signature
78
-
226
+
79
227
  def _verify_wallet_integrity(self) -> bool:
80
228
  """Basic cryptographic key verification"""
81
229
  # Check if keys exist
82
230
  if not self.private_key or not self.public_key:
83
231
  return False
84
-
232
+
85
233
  # Validate key formats
86
234
  if len(self.private_key) != 64: # 256-bit hex
87
235
  return False
88
-
89
- if not self.public_key.startswith('04') or len(self.public_key) != 130:
236
+
237
+ if not self.public_key.startswith("04") or len(self.public_key) != 130:
90
238
  return False
91
-
239
+
92
240
  return True
93
-
241
+
94
242
  def _generate_key_pair(self):
95
243
  """Generate REAL SM2 key pair"""
96
244
  return self.key_manager.generate_keypair()
@@ -98,181 +246,189 @@ class LunaWallet:
98
246
  def create_wallet(self, name, password):
99
247
  """Create a new wallet and set it as current"""
100
248
  print(f"DEBUG: Creating wallet '{name}'")
101
-
249
+
102
250
  # Generate REAL SM2 keys
103
251
  private_key, public_key, address = self._generate_key_pair()
104
-
252
+
105
253
  # Encrypt private key
106
254
  key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
107
255
  fernet = Fernet(key)
108
256
  encrypted_private_key = fernet.encrypt(private_key.encode())
109
-
257
+
110
258
  # Create wallet data
111
259
  wallet_data = {
112
- 'address': address,
113
- 'balance': 0.0,
114
- 'available_balance': 0.0,
115
- 'created': time.time(),
116
- 'private_key': private_key, # REAL SM2 private key
117
- 'public_key': public_key, # REAL SM2 public key
118
- 'encrypted_private_key': encrypted_private_key,
119
- 'label': name,
120
- 'is_locked': True,
121
- 'crypto_standard': 'SM2_GB/T_32918'
260
+ "address": address,
261
+ "balance": 0.0,
262
+ "available_balance": 0.0,
263
+ "created": time.time(),
264
+ "private_key": private_key, # REAL SM2 private key
265
+ "public_key": public_key, # REAL SM2 public key
266
+ "encrypted_private_key": encrypted_private_key,
267
+ "label": name,
268
+ "is_locked": True,
269
+ "crypto_standard": "SM2_GB/T_32918",
122
270
  }
123
-
271
+
124
272
  # Add to wallets collection
125
273
  self.wallets[address] = wallet_data
126
-
274
+
127
275
  # Set as current wallet
128
276
  self._set_current_wallet(wallet_data)
129
-
277
+
130
278
  print(f"DEBUG: Created and set current wallet {address}")
131
-
279
+
132
280
  return wallet_data
133
281
 
134
-
135
282
  def _generate_address(self, public_key_hex):
136
283
  """Generate address from SM2 public key"""
137
284
  return self.key_manager.derive_address(public_key_hex)
285
+
138
286
  def create_new_wallet(self, name, password):
139
287
  """
140
288
  Create a new wallet without switching to it
141
289
  Returns: wallet_data dict
142
290
  """
143
291
  print(f"DEBUG: Creating additional wallet '{name}'")
144
-
292
+
145
293
  # Generate REAL cryptographic keys
146
294
  private_key_hex, public_key_hex, address = self._generate_key_pair()
147
-
295
+
148
296
  # Encrypt private key
149
297
  key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
150
298
  fernet = Fernet(key)
151
299
  encrypted_private_key = fernet.encrypt(private_key_hex.encode())
152
-
300
+
153
301
  # Create wallet data
154
302
  new_wallet_data = {
155
- 'address': address,
156
- 'balance': 0.0,
157
- 'available_balance': 0.0,
158
- 'created': time.time(),
159
- 'private_key': private_key_hex,
160
- 'public_key': public_key_hex,
161
- 'encrypted_private_key': encrypted_private_key,
162
- 'label': name,
163
- 'is_locked': True,
164
- 'crypto_standard': 'SM2_GB/T_32918'
303
+ "address": address,
304
+ "balance": 0.0,
305
+ "available_balance": 0.0,
306
+ "created": time.time(),
307
+ "private_key": private_key_hex,
308
+ "public_key": public_key_hex,
309
+ "encrypted_private_key": encrypted_private_key,
310
+ "label": name,
311
+ "is_locked": True,
312
+ "crypto_standard": "SM2_GB/T_32918",
165
313
  }
166
-
314
+
167
315
  # Add to wallets collection
168
316
  self.wallets[address] = new_wallet_data
169
-
317
+
170
318
  print(f"DEBUG: Created wallet {address}, total wallets: {len(self.wallets)}")
171
-
319
+
172
320
  return new_wallet_data
173
-
321
+
174
322
  def _set_current_wallet(self, wallet_data):
175
323
  """Set the current wallet from wallet data"""
176
- self.current_wallet_address = wallet_data['address']
177
- self.address = wallet_data['address']
178
- self.balance = wallet_data['balance']
179
- self.available_balance = wallet_data['available_balance']
180
- self.created = wallet_data['created']
181
- self.private_key = wallet_data['private_key']
182
- self.public_key = wallet_data['public_key']
183
- self.encrypted_private_key = wallet_data['encrypted_private_key']
184
- self.label = wallet_data['label']
185
- self.is_locked = wallet_data.get('is_locked', True)
186
-
324
+ self.current_wallet_address = wallet_data["address"]
325
+ self.address = wallet_data["address"]
326
+ self.balance = wallet_data["balance"]
327
+ self.available_balance = wallet_data["available_balance"]
328
+ self.created = wallet_data["created"]
329
+ self.private_key = wallet_data["private_key"]
330
+ self.public_key = wallet_data["public_key"]
331
+ self.encrypted_private_key = wallet_data["encrypted_private_key"]
332
+ self.label = wallet_data["label"]
333
+ self.is_locked = wallet_data.get("is_locked", True)
334
+
187
335
  print(f"DEBUG: Set current wallet to {self.address}")
188
-
336
+
189
337
  def unlock_wallet(self, address, password):
190
338
  """Unlock wallet with password"""
191
339
  if address not in self.wallets:
192
340
  print(f"DEBUG: Wallet {address} not found in collection")
193
341
  return False
194
-
342
+
195
343
  wallet_data = self.wallets[address]
196
-
344
+
197
345
  try:
198
- if wallet_data.get('encrypted_private_key'):
346
+ if wallet_data.get("encrypted_private_key"):
199
347
  # Decrypt private key
200
- key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
348
+ key = base64.urlsafe_b64encode(
349
+ hashlib.sha256(password.encode()).digest()
350
+ )
201
351
  fernet = Fernet(key)
202
- decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
203
-
352
+ decrypted_key = fernet.decrypt(wallet_data["encrypted_private_key"])
353
+
204
354
  # Update wallet data
205
- wallet_data['private_key'] = decrypted_key.decode()
206
- wallet_data['is_locked'] = False
207
-
355
+ wallet_data["private_key"] = decrypted_key.decode()
356
+ wallet_data["is_locked"] = False
357
+
208
358
  # If this is the current wallet, update current state
209
359
  if self.current_wallet_address == address:
210
- self.private_key = wallet_data['private_key']
360
+ self.private_key = wallet_data["private_key"]
211
361
  self.is_locked = False
212
-
362
+
213
363
  print(f"DEBUG: Wallet {address} unlocked successfully")
214
364
  print(f"DEBUG: Private key available: {bool(self.private_key)}")
215
-
365
+
216
366
  # Verify cryptographic integrity after unlock
217
367
  if self._verify_wallet_integrity():
218
368
  print(f"DEBUG: Wallet cryptographic integrity verified")
219
369
  else:
220
- print(f"DEBUG: WARNING: Wallet unlocked but cryptographic verification failed")
221
-
370
+ print(
371
+ f"DEBUG: WARNING: Wallet unlocked but cryptographic verification failed"
372
+ )
373
+
222
374
  return True
223
375
  except Exception as e:
224
376
  print(f"DEBUG: Unlock failed: {e}")
225
-
377
+
226
378
  return False
227
-
379
+
228
380
  def switch_wallet(self, address, password=None):
229
381
  """Switch to a different wallet in the collection"""
230
382
  if address in self.wallets:
231
383
  wallet_data = self.wallets[address]
232
384
  self._set_current_wallet(wallet_data)
233
-
385
+
234
386
  # If password provided, unlock the wallet
235
387
  if password:
236
388
  return self.unlock_wallet(address, password)
237
-
389
+
238
390
  return True
239
-
391
+
240
392
  print(f"DEBUG: Cannot switch to {address} - not in wallet collection")
241
393
  return False
242
-
394
+
243
395
  # ============================================================================
244
396
  # BALANCE AND TRANSACTION METHODS
245
397
  # ============================================================================
246
-
398
+
247
399
  def calculate_available_balance(self) -> float:
248
400
  """Calculate available balance (total balance minus pending outgoing transactions)"""
249
401
  try:
250
402
  # Get total balance from blockchain
251
403
  total_balance = self._get_total_balance_from_blockchain()
252
-
404
+
253
405
  # Get pending outgoing amount
254
406
  pending_outgoing = self._get_pending_balance()
255
-
407
+
256
408
  # Also check for any incoming pending transactions
257
409
  incoming_pending = self._get_pending_incoming_balance()
258
-
410
+
259
411
  available_balance = max(0.0, total_balance - pending_outgoing)
260
-
412
+
261
413
  # Update both current wallet and wallets collection
262
414
  self.available_balance = available_balance
263
415
  self.balance = total_balance # Also update total balance
264
-
416
+
265
417
  if self.current_wallet_address in self.wallets:
266
- self.wallets[self.current_wallet_address]['available_balance'] = available_balance
267
- self.wallets[self.current_wallet_address]['balance'] = total_balance
268
-
269
- print(f"DEBUG: Balance calculated - Total: {total_balance}, Pending Out: {pending_outgoing}, Available: {available_balance}")
270
-
418
+ self.wallets[self.current_wallet_address][
419
+ "available_balance"
420
+ ] = available_balance
421
+ self.wallets[self.current_wallet_address]["balance"] = total_balance
422
+
423
+ print(
424
+ f"DEBUG: Balance calculated - Total: {total_balance}, Pending Out: {pending_outgoing}, Available: {available_balance}"
425
+ )
426
+
271
427
  if incoming_pending > 0:
272
428
  print(f"DEBUG: Also {incoming_pending} LUN incoming (pending)")
273
-
429
+
274
430
  return available_balance
275
-
431
+
276
432
  except Exception as e:
277
433
  print(f"DEBUG: Error calculating available balance: {e}")
278
434
  return self.balance # Fallback to total balance
@@ -281,37 +437,39 @@ class LunaWallet:
281
437
  """Compute confirmed balance from a list of transactions."""
282
438
  total_balance = 0.0
283
439
  for tx in transactions:
284
- tx_type = (tx.get('type') or '').lower()
285
- direction = tx.get('direction', '')
286
- amount = float(tx.get('amount', 0) or 0)
440
+ tx_type = (tx.get("type") or "").lower()
441
+ direction = tx.get("direction", "")
442
+ amount = float(tx.get("amount", 0) or 0)
287
443
 
288
- if tx_type == 'reward' or tx.get('from') == 'network':
444
+ if tx_type == "reward" or tx.get("from") == "network":
289
445
  total_balance += amount
290
- elif direction == 'incoming':
446
+ elif direction == "incoming":
291
447
  total_balance += amount
292
- elif direction == 'outgoing':
293
- fee = float(tx.get('fee', 0) or 0)
448
+ elif direction == "outgoing":
449
+ fee = float(tx.get("fee", 0) or 0)
294
450
  total_balance -= amount
295
451
  total_balance -= fee
296
452
 
297
453
  return max(0.0, total_balance)
298
454
 
299
- def _compute_pending_totals(self, pending_txs: List[Dict], address: str) -> Tuple[float, float]:
455
+ def _compute_pending_totals(
456
+ self, pending_txs: List[Dict], address: str
457
+ ) -> Tuple[float, float]:
300
458
  """Return (pending_outgoing, pending_incoming) for an address."""
301
459
  pending_out = 0.0
302
460
  pending_in = 0.0
303
461
 
304
462
  target_norm = self._normalize_address(address)
305
463
  for tx in pending_txs:
306
- from_norm = self._normalize_address(tx.get('from') or tx.get('sender'))
307
- to_norm = self._normalize_address(tx.get('to') or tx.get('receiver'))
464
+ from_norm = self._normalize_address(tx.get("from") or tx.get("sender"))
465
+ to_norm = self._normalize_address(tx.get("to") or tx.get("receiver"))
308
466
 
309
467
  if from_norm == target_norm:
310
- amount = float(tx.get('amount', 0) or 0)
311
- fee = float(tx.get('fee', 0) or tx.get('gas', 0) or 0)
468
+ amount = float(tx.get("amount", 0) or 0)
469
+ fee = float(tx.get("fee", 0) or tx.get("gas", 0) or 0)
312
470
  pending_out += amount + fee
313
471
  if to_norm == target_norm:
314
- amount = float(tx.get('amount', 0) or 0)
472
+ amount = float(tx.get("amount", 0) or 0)
315
473
  pending_in += amount
316
474
 
317
475
  return pending_out, pending_in
@@ -328,18 +486,18 @@ class LunaWallet:
328
486
  pending_out, pending_in = self._compute_pending_totals(pending, addr)
329
487
  available_balance = max(0.0, total_balance - pending_out)
330
488
 
331
- wallet_data['balance'] = total_balance
332
- wallet_data['available_balance'] = available_balance
489
+ wallet_data["balance"] = total_balance
490
+ wallet_data["available_balance"] = available_balance
333
491
 
334
492
  if addr == self.current_wallet_address:
335
493
  self.balance = total_balance
336
494
  self.available_balance = available_balance
337
495
 
338
496
  updated[addr] = {
339
- 'balance': total_balance,
340
- 'available_balance': available_balance,
341
- 'pending_out': pending_out,
342
- 'pending_in': pending_in
497
+ "balance": total_balance,
498
+ "available_balance": available_balance,
499
+ "pending_out": pending_out,
500
+ "pending_in": pending_in,
343
501
  }
344
502
 
345
503
  return updated
@@ -358,7 +516,9 @@ class LunaWallet:
358
516
  mempool = MempoolManager()
359
517
 
360
518
  confirmed_map = blockchain.scan_transactions_for_addresses(addresses)
361
- pending_map = mempool.get_pending_transactions_for_addresses(addresses, fetch_remote=True)
519
+ pending_map = mempool.get_pending_transactions_for_addresses(
520
+ addresses, fetch_remote=True
521
+ )
362
522
 
363
523
  # Reset caches with the latest snapshots
364
524
  self._confirmed_tx_cache = confirmed_map
@@ -374,7 +534,9 @@ class LunaWallet:
374
534
  """Convenience: scan blockchain once and mempool once, update all wallet balances."""
375
535
  return self.refresh_all_wallet_balances()
376
536
 
377
- def get_all_wallets_overview(self, include_transactions: bool = True) -> Dict[str, Dict]:
537
+ def get_all_wallets_overview(
538
+ self, include_transactions: bool = True
539
+ ) -> Dict[str, Dict]:
378
540
  """Return balances and (optionally) cached transactions for all wallets."""
379
541
  overview: Dict[str, Dict] = {}
380
542
  for addr, wallet_data in self.wallets.items():
@@ -382,19 +544,21 @@ class LunaWallet:
382
544
  pending = self._pending_tx_cache.get(addr, [])
383
545
 
384
546
  overview[addr] = {
385
- 'balance': wallet_data.get('balance', 0.0),
386
- 'available_balance': wallet_data.get('available_balance', 0.0),
387
- 'pending_out': self._compute_pending_totals(pending, addr)[0],
388
- 'pending_in': self._compute_pending_totals(pending, addr)[1],
547
+ "balance": wallet_data.get("balance", 0.0),
548
+ "available_balance": wallet_data.get("available_balance", 0.0),
549
+ "pending_out": self._compute_pending_totals(pending, addr)[0],
550
+ "pending_in": self._compute_pending_totals(pending, addr)[1],
389
551
  }
390
552
 
391
553
  if include_transactions:
392
- overview[addr]['confirmed_transactions'] = confirmed
393
- overview[addr]['pending_transactions'] = pending
554
+ overview[addr]["confirmed_transactions"] = confirmed
555
+ overview[addr]["pending_transactions"] = pending
394
556
 
395
557
  return overview
396
558
 
397
- def _apply_transaction_updates(self, confirmed_map: Dict[str, List[Dict]], pending_map: Dict[str, List[Dict]]):
559
+ def _apply_transaction_updates(
560
+ self, confirmed_map: Dict[str, List[Dict]], pending_map: Dict[str, List[Dict]]
561
+ ):
398
562
  """Update caches from monitor callbacks and recompute balances."""
399
563
  for addr, txs in confirmed_map.items():
400
564
  self._confirmed_tx_cache.setdefault(addr, []).extend(txs)
@@ -412,22 +576,25 @@ class LunaWallet:
412
576
  return None
413
577
 
414
578
  from lunalib.core.blockchain import BlockchainManager
579
+
415
580
  blockchain = BlockchainManager()
416
581
 
417
582
  # Run an initial refresh to seed caches and balances
418
583
  self.refresh_all_wallet_balances()
419
584
 
420
585
  def _on_update(payload: Dict):
421
- confirmed_map = payload.get('confirmed', {}) or {}
422
- pending_map = payload.get('pending', {}) or {}
586
+ confirmed_map = payload.get("confirmed", {}) or {}
587
+ pending_map = payload.get("pending", {}) or {}
423
588
  self._apply_transaction_updates(confirmed_map, pending_map)
424
589
 
425
- self._monitor_stop_event = blockchain.monitor_addresses(addresses, _on_update, poll_interval=poll_interval)
590
+ self._monitor_stop_event = blockchain.monitor_addresses(
591
+ addresses, _on_update, poll_interval=poll_interval
592
+ )
426
593
  return self._monitor_stop_event
427
594
 
428
595
  def stop_wallet_monitoring(self):
429
596
  """Stop the background monitor if running."""
430
- stop_event = getattr(self, '_monitor_stop_event', None)
597
+ stop_event = getattr(self, "_monitor_stop_event", None)
431
598
  if stop_event:
432
599
  stop_event.set()
433
600
 
@@ -436,320 +603,355 @@ class LunaWallet:
436
603
  self.sync_all_wallets_once()
437
604
  return self.start_wallet_monitoring(poll_interval=poll_interval)
438
605
 
439
- def get_all_balances_after_sync(self, include_transactions: bool = True) -> Dict[str, Dict]:
606
+ def get_all_balances_after_sync(
607
+ self, include_transactions: bool = True
608
+ ) -> Dict[str, Dict]:
440
609
  """One-shot sync then return per-wallet balances (and transactions if requested)."""
441
610
  self.sync_all_wallets_once()
442
611
  return self.get_all_wallets_overview(include_transactions=include_transactions)
612
+
443
613
  def _get_pending_balance(self) -> float:
444
614
  """Get total pending balance from mempool (outgoing pending transactions)"""
445
615
  try:
446
616
  from lunalib.core.mempool import MempoolManager
447
-
617
+
448
618
  mempool = MempoolManager()
449
-
619
+
450
620
  # Get pending transactions for this address
451
- pending_txs = mempool.get_pending_transactions(self.address, fetch_remote=True)
452
-
621
+ pending_txs = mempool.get_pending_transactions(
622
+ self.address, fetch_remote=True
623
+ )
624
+
453
625
  total_pending_outgoing = 0.0
454
-
626
+
455
627
  for tx in pending_txs:
456
628
  # Only count outgoing transactions (where we're the sender)
457
- if tx.get('from') == self.address:
458
- amount = float(tx.get('amount', 0))
459
- fee = float(tx.get('fee', 0) or tx.get('gas', 0) or 0)
629
+ if tx.get("from") == self.address:
630
+ amount = float(tx.get("amount", 0))
631
+ fee = float(tx.get("fee", 0) or tx.get("gas", 0) or 0)
460
632
  total_pending_outgoing += amount + fee
461
- print(f"🔍 Found pending outgoing: {amount} + {fee} fee = {amount + fee}")
462
-
633
+ print(
634
+ f"🔍 Found pending outgoing: {amount} + {fee} fee = {amount + fee}"
635
+ )
636
+
463
637
  return total_pending_outgoing
464
-
638
+
465
639
  except Exception as e:
466
640
  print(f"DEBUG: Error calculating pending balance: {e}")
467
641
  return 0.0
468
-
642
+
469
643
  def _get_pending_incoming_balance(self) -> float:
470
644
  """Get total pending incoming balance from mempool"""
471
645
  try:
472
646
  from lunalib.core.mempool import MempoolManager
473
-
647
+
474
648
  mempool = MempoolManager()
475
-
649
+
476
650
  # Get pending transactions for this address
477
- pending_txs = mempool.get_pending_transactions(self.address, fetch_remote=True)
478
-
651
+ pending_txs = mempool.get_pending_transactions(
652
+ self.address, fetch_remote=True
653
+ )
654
+
479
655
  total_pending_incoming = 0.0
480
-
656
+
481
657
  for tx in pending_txs:
482
658
  # Only count incoming transactions (where we're the receiver)
483
- if tx.get('to') == self.address:
484
- amount = float(tx.get('amount', 0))
659
+ if tx.get("to") == self.address:
660
+ amount = float(tx.get("amount", 0))
485
661
  total_pending_incoming += amount
486
662
  print(f"🔍 Found pending incoming: +{amount}")
487
-
663
+
488
664
  return total_pending_incoming
489
-
665
+
490
666
  except Exception as e:
491
667
  print(f"DEBUG: Error calculating pending incoming balance: {e}")
492
668
  return 0.0
493
-
669
+
494
670
  def _get_total_balance_from_blockchain(self) -> float:
495
671
  """Get total balance by scanning blockchain for confirmed transactions"""
496
672
  try:
497
673
  from lunalib.core.blockchain import BlockchainManager
498
-
674
+
499
675
  blockchain = BlockchainManager()
500
676
  transactions = blockchain.scan_transactions_for_address(self.address)
501
677
  total_balance = self._compute_confirmed_balance(transactions)
502
678
  return total_balance
503
-
679
+
504
680
  except Exception as e:
505
681
  print(f"DEBUG: Error getting blockchain balance: {e}")
506
682
  return self.balance
507
-
683
+
508
684
  def register_balance_callback(self, callback: Callable[[float, float], None]):
509
685
  """Register a callback to be called when balance is updated asynchronously"""
510
686
  self.balance_callbacks.append(callback)
511
-
687
+
512
688
  def start_async_balance_loading(self):
513
689
  """Start asynchronous balance loading in background thread"""
514
690
  if self.balance_loading:
515
691
  return # Already loading
516
-
692
+
517
693
  if time.time() - self.last_balance_update < self.balance_update_interval:
518
694
  return # Too soon since last update
519
-
695
+
520
696
  self.balance_loading = True
521
-
697
+
522
698
  def _async_balance_update():
523
699
  try:
524
700
  # Load balances asynchronously
525
701
  total_balance = self._get_total_balance_from_blockchain()
526
702
  available_balance = self.calculate_available_balance()
527
-
703
+
528
704
  # Update wallet state
529
705
  self.balance = total_balance
530
706
  self.available_balance = available_balance
531
707
  self.last_balance_update = time.time()
532
-
708
+
533
709
  # Update in wallets collection
534
710
  if self.current_wallet_address in self.wallets:
535
- self.wallets[self.current_wallet_address]['balance'] = total_balance
536
- self.wallets[self.current_wallet_address]['available_balance'] = available_balance
537
-
711
+ self.wallets[self.current_wallet_address]["balance"] = total_balance
712
+ self.wallets[self.current_wallet_address][
713
+ "available_balance"
714
+ ] = available_balance
715
+
538
716
  # Notify callbacks
539
717
  for callback in self.balance_callbacks:
540
718
  try:
541
719
  callback(total_balance, available_balance)
542
720
  except Exception as e:
543
721
  print(f"Error in balance callback: {e}")
544
-
545
- print(f"DEBUG: Async balance updated - Total: {total_balance}, Available: {available_balance}")
546
-
722
+
723
+ print(
724
+ f"DEBUG: Async balance updated - Total: {total_balance}, Available: {available_balance}"
725
+ )
726
+
547
727
  except Exception as e:
548
728
  print(f"DEBUG: Error in async balance loading: {e}")
549
729
  finally:
550
730
  self.balance_loading = False
551
-
731
+
552
732
  # Start in background thread
553
733
  self.balance_thread_pool.submit(_async_balance_update)
554
-
734
+
555
735
  def refresh_balance(self) -> bool:
556
736
  """Refresh both total and available balance from blockchain and mempool"""
557
737
  try:
558
738
  total_balance = self._get_total_balance_from_blockchain()
559
739
  available_balance = self.calculate_available_balance()
560
-
740
+
561
741
  # Update wallet state
562
742
  self.balance = total_balance
563
743
  self.available_balance = available_balance
564
-
744
+
565
745
  # Update in wallets collection
566
746
  if self.current_wallet_address in self.wallets:
567
- self.wallets[self.current_wallet_address]['balance'] = total_balance
568
- self.wallets[self.current_wallet_address]['available_balance'] = available_balance
569
-
570
- print(f"DEBUG: Balance refreshed - Total: {total_balance}, Available: {available_balance}")
747
+ self.wallets[self.current_wallet_address]["balance"] = total_balance
748
+ self.wallets[self.current_wallet_address][
749
+ "available_balance"
750
+ ] = available_balance
751
+
752
+ print(
753
+ f"DEBUG: Balance refreshed - Total: {total_balance}, Available: {available_balance}"
754
+ )
571
755
  return True
572
-
756
+
573
757
  except Exception as e:
574
758
  print(f"DEBUG: Error refreshing balance: {e}")
575
759
  return False
576
-
577
- def send_transaction(self, to_address: str, amount: float, memo: str = "", password: str = None) -> bool:
760
+
761
+ def send_transaction(
762
+ self, to_address: str, amount: float, memo: str = "", password: str = None
763
+ ) -> bool:
578
764
  """Send transaction using REAL SM2 signatures"""
579
765
  try:
580
766
  print(f"[SM2] Sending {amount} to {to_address}")
581
-
767
+
582
768
  # 1. Basic validation
583
769
  if self.is_locked or not self.private_key:
584
770
  print("[SM2] Wallet locked or no private key")
585
771
  return False
586
-
772
+
587
773
  # 2. Check key integrity
588
774
  if not self._verify_wallet_integrity():
589
775
  print("[SM2] Invalid cryptographic keys")
590
776
  return False
591
-
777
+
592
778
  # 3. Balance check
593
779
  self.refresh_balance()
594
780
  if amount > self.get_available_balance():
595
- print(f"[SM2] Insufficient balance: {self.get_available_balance()} < {amount}")
781
+ print(
782
+ f"[SM2] Insufficient balance: {self.get_available_balance()} < {amount}"
783
+ )
596
784
  return False
597
-
785
+
598
786
  # 4. Create and sign transaction
599
787
  from lunalib.transactions.transactions import TransactionManager
788
+
600
789
  tx_manager = TransactionManager()
601
-
790
+
602
791
  transaction = tx_manager.create_transaction(
603
792
  from_address=self.address,
604
793
  to_address=to_address,
605
794
  amount=amount,
606
795
  private_key=self.private_key,
607
796
  memo=memo,
608
- transaction_type="transfer"
797
+ transaction_type="transfer",
798
+ )
799
+
800
+ print(
801
+ f"[SM2] Transaction created: {transaction.get('hash', 'no_hash')[:16]}"
609
802
  )
610
-
611
- print(f"[SM2] Transaction created: {transaction.get('hash', 'no_hash')[:16]}")
612
-
803
+
613
804
  # 5. Validate
614
805
  is_valid, message = tx_manager.validate_transaction(transaction)
615
806
  if not is_valid:
616
807
  print(f"[SM2] Validation failed: {message}")
617
808
  return False
618
-
809
+
619
810
  # 6. Broadcast
620
811
  success, message = tx_manager.send_transaction(transaction)
621
812
  if success:
622
813
  print(f"[SM2] Transaction broadcast: {message}")
623
-
814
+
624
815
  # Update balance immediately
625
- total_cost = amount + transaction.get('fee', 0)
816
+ total_cost = amount + transaction.get("fee", 0)
626
817
  self.available_balance -= total_cost
627
818
  if self.current_wallet_address in self.wallets:
628
- self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
629
-
819
+ self.wallets[self.current_wallet_address][
820
+ "available_balance"
821
+ ] = self.available_balance
822
+
630
823
  # Save state
631
824
  self.save_wallet_data()
632
-
825
+
633
826
  # Trigger async balance refresh to get accurate data
634
827
  self.start_async_balance_loading()
635
-
828
+
636
829
  return True
637
830
  else:
638
831
  print(f"[SM2] Broadcast failed: {message}")
639
832
  return False
640
-
833
+
641
834
  except Exception as e:
642
835
  print(f"[SM2] Error: {e}")
643
836
  return False
644
-
837
+
645
838
  def save_wallet_data(self):
646
839
  """Save wallet data to file"""
647
840
  try:
648
841
  print(f"[WALLET] Saving wallet data...")
649
-
842
+
650
843
  # Prepare wallet data
651
844
  wallet_data = {
652
- 'version': '2.0',
653
- 'address': self.address,
654
- 'public_key': self.public_key,
655
- 'private_key': self.private_key, # WARNING: In production, encrypt this!
656
- 'balance': self.balance,
657
- 'available_balance': self.available_balance,
658
- 'transactions': self.transactions,
659
- 'created_at': self.created_at,
660
- 'last_sync': time.time(),
661
- 'network': self.network,
662
- 'key_type': 'SM2' # Indicate SM2 key type
845
+ "version": "2.0",
846
+ "address": self.address,
847
+ "public_key": self.public_key,
848
+ "private_key": self.private_key, # WARNING: In production, encrypt this!
849
+ "balance": self.balance,
850
+ "available_balance": self.available_balance,
851
+ "transactions": self.transactions,
852
+ "created_at": self.created_at,
853
+ "last_sync": time.time(),
854
+ "network": self.network,
855
+ "key_type": "SM2", # Indicate SM2 key type
663
856
  }
664
-
857
+
665
858
  # Determine save path
666
- if hasattr(self, 'wallet_file'):
859
+ if hasattr(self, "wallet_file"):
667
860
  save_path = self.wallet_file
668
861
  else:
669
862
  # Default save location
670
863
  import os
671
- wallet_dir = os.path.expanduser('~/.lunawallet')
864
+
865
+ wallet_dir = os.path.expanduser("~/.lunawallet")
672
866
  os.makedirs(wallet_dir, exist_ok=True)
673
- save_path = os.path.join(wallet_dir, f'{self.address}.json')
674
-
867
+ save_path = os.path.join(wallet_dir, f"{self.address}.json")
868
+
675
869
  # Save to file
676
870
  import json
677
- with open(save_path, 'w') as f:
871
+
872
+ with open(save_path, "w") as f:
678
873
  json.dump(wallet_data, f, indent=2)
679
-
874
+
680
875
  print(f"[WALLET] Wallet data saved to {save_path}")
681
876
  return True
682
-
877
+
683
878
  except Exception as e:
684
879
  print(f"[WALLET ERROR] Failed to save wallet data: {e}")
685
880
  import traceback
881
+
686
882
  traceback.print_exc()
687
883
  return False
688
-
884
+
689
885
  def load_wallet_data(self, wallet_file=None):
690
886
  """Load wallet data from file"""
691
887
  try:
692
888
  print(f"[WALLET] Loading wallet data...")
693
-
889
+
694
890
  if wallet_file:
695
891
  self.wallet_file = wallet_file
696
892
  load_path = wallet_file
697
- elif hasattr(self, 'wallet_file'):
893
+ elif hasattr(self, "wallet_file"):
698
894
  load_path = self.wallet_file
699
895
  else:
700
896
  # Try to find wallet file
701
897
  import os
702
- wallet_dir = os.path.expanduser('~/.lunawallet')
898
+
899
+ wallet_dir = os.path.expanduser("~/.lunawallet")
703
900
  # Look for any .json file
704
- wallet_files = [f for f in os.listdir(wallet_dir) if f.endswith('.json')]
901
+ wallet_files = [
902
+ f for f in os.listdir(wallet_dir) if f.endswith(".json")
903
+ ]
705
904
  if not wallet_files:
706
905
  print("[WALLET] No wallet file found")
707
906
  return False
708
907
  load_path = os.path.join(wallet_dir, wallet_files[0])
709
908
  self.wallet_file = load_path
710
-
909
+
711
910
  # Load from file
712
911
  import json
713
- with open(load_path, 'r') as f:
912
+
913
+ with open(load_path, "r") as f:
714
914
  wallet_data = json.load(f)
715
-
915
+
716
916
  # Restore wallet data
717
- self.address = wallet_data.get('address', '')
718
- self.public_key = wallet_data.get('public_key', '')
719
- self.private_key = wallet_data.get('private_key', '')
720
- self.balance = wallet_data.get('balance', 0.0)
721
- self.available_balance = wallet_data.get('available_balance', 0.0)
722
- self.transactions = wallet_data.get('transactions', [])
723
- self.created_at = wallet_data.get('created_at', time.time())
724
- self.network = wallet_data.get('network', 'mainnet')
725
-
917
+ self.address = wallet_data.get("address", "")
918
+ self.public_key = wallet_data.get("public_key", "")
919
+ self.private_key = wallet_data.get("private_key", "")
920
+ self.balance = wallet_data.get("balance", 0.0)
921
+ self.available_balance = wallet_data.get("available_balance", 0.0)
922
+ self.transactions = wallet_data.get("transactions", [])
923
+ self.created_at = wallet_data.get("created_at", time.time())
924
+ self.network = wallet_data.get("network", "mainnet")
925
+
726
926
  print(f"[WALLET] Wallet loaded: {self.address}")
727
927
  print(f"[WALLET] Balance: {self.balance}")
728
-
928
+
729
929
  # Initialize SM2 if we have keys
730
930
  if self.public_key and self.private_key:
731
931
  self._initialize_sm2()
732
-
932
+
733
933
  # Start async balance loading
734
934
  self.start_async_balance_loading()
735
-
935
+
736
936
  return True
737
-
937
+
738
938
  except Exception as e:
739
939
  print(f"[WALLET ERROR] Failed to load wallet data: {e}")
740
940
  return False
741
-
941
+
742
942
  def _initialize_sm2(self):
743
943
  """Initialize SM2 crypto if keys exist"""
744
944
  try:
745
945
  if self.public_key and self.private_key:
746
946
  from ..core.crypto import KeyManager
947
+
747
948
  self.key_manager = KeyManager()
748
949
  print(f"[WALLET] SM2 initialized with existing keys")
749
950
  return True
750
951
  except Exception as e:
751
952
  print(f"[WALLET ERROR] Failed to initialize SM2: {e}")
752
953
  return False
954
+
753
955
  def get_transaction_history(self) -> dict:
754
956
  """Get complete transaction history (both pending and confirmed).
755
957
  Uses cached transactions if available, otherwise scans blockchain/mempool.
@@ -759,49 +961,62 @@ class LunaWallet:
759
961
  # Try to use cache first (from monitoring or sync)
760
962
  confirmed_txs = self._confirmed_tx_cache.get(self.address, [])
761
963
  pending_txs = self._pending_tx_cache.get(self.address, [])
762
-
964
+
763
965
  # If cache is empty, perform fresh scan
764
966
  if not confirmed_txs:
765
967
  from lunalib.core.blockchain import BlockchainManager
766
968
  from lunalib.core.mempool import MempoolManager
767
-
969
+
768
970
  blockchain = BlockchainManager()
769
971
  mempool = MempoolManager()
770
-
972
+
771
973
  # Get confirmed transactions from blockchain (includes mining rewards)
772
974
  confirmed_txs = blockchain.scan_transactions_for_address(self.address)
773
975
  self._confirmed_tx_cache[self.address] = confirmed_txs
774
-
976
+
775
977
  # Get pending transactions from mempool
776
- pending_txs = mempool.get_pending_transactions(self.address, fetch_remote=True)
978
+ pending_txs = mempool.get_pending_transactions(
979
+ self.address, fetch_remote=True
980
+ )
777
981
  self._pending_tx_cache[self.address] = pending_txs
778
-
982
+
779
983
  # Count by type for debugging
780
- reward_count = sum(1 for tx in confirmed_txs
781
- if tx.get('type', '').lower() in ['reward', 'mining']
782
- or tx.get('from') == 'network')
783
-
984
+ reward_count = sum(
985
+ 1
986
+ for tx in confirmed_txs
987
+ if tx.get("type", "").lower() in ["reward", "mining"]
988
+ or tx.get("from") == "network"
989
+ )
990
+
784
991
  return {
785
- 'confirmed': confirmed_txs,
786
- 'pending': pending_txs,
787
- 'total_confirmed': len(confirmed_txs),
788
- 'total_pending': len(pending_txs),
789
- 'reward_count': reward_count
992
+ "confirmed": confirmed_txs,
993
+ "pending": pending_txs,
994
+ "total_confirmed": len(confirmed_txs),
995
+ "total_pending": len(pending_txs),
996
+ "reward_count": reward_count,
790
997
  }
791
998
  except Exception as e:
792
999
  print(f"DEBUG: Error getting transaction history: {e}")
793
- return {'confirmed': [], 'pending': [], 'total_confirmed': 0, 'total_pending': 0, 'reward_count': 0}
794
-
795
- def get_wallet_transactions(self, address: str = None, include_pending: bool = True) -> Dict[str, List[Dict]]:
1000
+ return {
1001
+ "confirmed": [],
1002
+ "pending": [],
1003
+ "total_confirmed": 0,
1004
+ "total_pending": 0,
1005
+ "reward_count": 0,
1006
+ }
1007
+
1008
+ def get_wallet_transactions(
1009
+ self, address: str = None, include_pending: bool = True
1010
+ ) -> Dict[str, List[Dict]]:
796
1011
  """Get ALL transactions for a wallet including mining rewards, transfers, and pending.
797
-
1012
+
798
1013
  This is the comprehensive transaction getter that ensures ALL reward transactions
799
1014
  (mining rewards, explicit reward transactions) are included.
800
-
1015
+
801
1016
  Args:
802
1017
  address: wallet address to query (defaults to current wallet address)
803
1018
  include_pending: whether to include pending mempool transactions (default: True)
804
-
1019
+
805
1020
  Returns:
806
1021
  Dict with:
807
1022
  - 'confirmed': list of all confirmed transactions (transfers + mining rewards)
@@ -813,70 +1028,86 @@ class LunaWallet:
813
1028
  """
814
1029
  if address is None:
815
1030
  address = self.address
816
-
1031
+
817
1032
  if not address:
818
1033
  return {
819
- 'confirmed': [],
820
- 'pending': [],
821
- 'reward_transactions': [],
822
- 'transfer_transactions': [],
823
- 'incoming_transfers': [],
824
- 'outgoing_transfers': [],
825
- 'total_rewards': 0,
826
- 'total_transfers': 0,
827
- 'total_incoming': 0,
828
- 'total_outgoing': 0
1034
+ "confirmed": [],
1035
+ "pending": [],
1036
+ "reward_transactions": [],
1037
+ "transfer_transactions": [],
1038
+ "incoming_transfers": [],
1039
+ "outgoing_transfers": [],
1040
+ "total_rewards": 0,
1041
+ "total_transfers": 0,
1042
+ "total_incoming": 0,
1043
+ "total_outgoing": 0,
829
1044
  }
830
-
1045
+
831
1046
  try:
832
1047
  # Normalize address for comparison
833
1048
  norm_addr = self._normalize_address(address)
834
-
1049
+
835
1050
  # Use cache if available
836
1051
  confirmed_txs = self._confirmed_tx_cache.get(norm_addr, [])
837
- pending_txs = self._pending_tx_cache.get(norm_addr, []) if include_pending else []
838
-
1052
+ pending_txs = (
1053
+ self._pending_tx_cache.get(norm_addr, []) if include_pending else []
1054
+ )
1055
+
839
1056
  # If cache is empty, perform fresh scan
840
1057
  if not confirmed_txs:
841
1058
  from lunalib.core.blockchain import BlockchainManager
1059
+
842
1060
  blockchain = BlockchainManager()
843
1061
  confirmed_txs = blockchain.scan_transactions_for_address(address)
844
1062
  self._confirmed_tx_cache[norm_addr] = confirmed_txs
845
-
1063
+
846
1064
  if include_pending and not pending_txs:
847
1065
  from lunalib.core.mempool import MempoolManager
1066
+
848
1067
  mempool = MempoolManager()
849
- pending_txs = mempool.get_pending_transactions(address, fetch_remote=True)
1068
+ pending_txs = mempool.get_pending_transactions(
1069
+ address, fetch_remote=True
1070
+ )
850
1071
  self._pending_tx_cache[norm_addr] = pending_txs
851
-
1072
+
852
1073
  # Separate rewards and transfers based on type and source
853
1074
  # Rewards: explicitly marked as reward/mining type, or from network
854
- reward_txs = [tx for tx in confirmed_txs
855
- if tx.get('type', '').lower() in ['reward', 'mining', 'gtx_genesis']
856
- or tx.get('from') == 'network']
857
-
1075
+ reward_txs = [
1076
+ tx
1077
+ for tx in confirmed_txs
1078
+ if tx.get("type", "").lower() in ["reward", "mining", "gtx_genesis"]
1079
+ or tx.get("from") == "network"
1080
+ ]
1081
+
858
1082
  # Transfers: anything that's NOT a reward (includes both incoming and outgoing)
859
- transfer_txs = [tx for tx in confirmed_txs
860
- if tx.get('type', '').lower() not in ['reward', 'mining', 'gtx_genesis']
861
- and tx.get('from') != 'network']
862
-
1083
+ transfer_txs = [
1084
+ tx
1085
+ for tx in confirmed_txs
1086
+ if tx.get("type", "").lower() not in ["reward", "mining", "gtx_genesis"]
1087
+ and tx.get("from") != "network"
1088
+ ]
1089
+
863
1090
  # Separate incoming vs outgoing transfers
864
- incoming_transfers = [tx for tx in transfer_txs if tx.get('direction') == 'incoming']
865
- outgoing_transfers = [tx for tx in transfer_txs if tx.get('direction') == 'outgoing']
866
-
1091
+ incoming_transfers = [
1092
+ tx for tx in transfer_txs if tx.get("direction") == "incoming"
1093
+ ]
1094
+ outgoing_transfers = [
1095
+ tx for tx in transfer_txs if tx.get("direction") == "outgoing"
1096
+ ]
1097
+
867
1098
  result = {
868
- 'confirmed': confirmed_txs,
869
- 'pending': pending_txs,
870
- 'reward_transactions': reward_txs,
871
- 'transfer_transactions': transfer_txs,
872
- 'incoming_transfers': incoming_transfers,
873
- 'outgoing_transfers': outgoing_transfers,
874
- 'total_rewards': len(reward_txs),
875
- 'total_transfers': len(transfer_txs),
876
- 'total_incoming': len(incoming_transfers),
877
- 'total_outgoing': len(outgoing_transfers)
1099
+ "confirmed": confirmed_txs,
1100
+ "pending": pending_txs,
1101
+ "reward_transactions": reward_txs,
1102
+ "transfer_transactions": transfer_txs,
1103
+ "incoming_transfers": incoming_transfers,
1104
+ "outgoing_transfers": outgoing_transfers,
1105
+ "total_rewards": len(reward_txs),
1106
+ "total_transfers": len(transfer_txs),
1107
+ "total_incoming": len(incoming_transfers),
1108
+ "total_outgoing": len(outgoing_transfers),
878
1109
  }
879
-
1110
+
880
1111
  print(f"DEBUG: get_wallet_transactions({address}):")
881
1112
  print(f" - Mining Rewards: {len(reward_txs)}")
882
1113
  print(f" - Incoming Transfers: {len(incoming_transfers)}")
@@ -884,194 +1115,204 @@ class LunaWallet:
884
1115
  print(f" - Total Transfers: {len(transfer_txs)}")
885
1116
  print(f" - Pending: {len(pending_txs)}")
886
1117
  print(f" - Total Confirmed: {len(confirmed_txs)}")
887
-
1118
+
888
1119
  return result
889
-
1120
+
890
1121
  except Exception as e:
891
1122
  print(f"DEBUG: Error getting wallet transactions: {e}")
892
1123
  import traceback
1124
+
893
1125
  traceback.print_exc()
894
1126
  return {
895
- 'confirmed': [],
896
- 'pending': [],
897
- 'reward_transactions': [],
898
- 'transfer_transactions': [],
899
- 'incoming_transfers': [],
900
- 'outgoing_transfers': [],
901
- 'total_rewards': 0,
902
- 'total_transfers': 0,
903
- 'total_incoming': 0,
904
- 'total_outgoing': 0
1127
+ "confirmed": [],
1128
+ "pending": [],
1129
+ "reward_transactions": [],
1130
+ "transfer_transactions": [],
1131
+ "incoming_transfers": [],
1132
+ "outgoing_transfers": [],
1133
+ "total_rewards": 0,
1134
+ "total_transfers": 0,
1135
+ "total_incoming": 0,
1136
+ "total_outgoing": 0,
905
1137
  }
906
-
1138
+
907
1139
  # ============================================================================
908
1140
  # WALLET INFO AND UTILITIES
909
1141
  # ============================================================================
910
-
1142
+
911
1143
  @property
912
1144
  def is_unlocked(self):
913
1145
  """Check if current wallet is unlocked"""
914
1146
  if not self.current_wallet_address:
915
1147
  return False
916
1148
  wallet_data = self.wallets.get(self.current_wallet_address, {})
917
- return not wallet_data.get('is_locked', True)
918
-
1149
+ return not wallet_data.get("is_locked", True)
1150
+
919
1151
  def export_private_key(self, address, password):
920
1152
  """Export private key with password decryption"""
921
1153
  if address not in self.wallets:
922
1154
  return None
923
-
1155
+
924
1156
  wallet_data = self.wallets[address]
925
-
1157
+
926
1158
  try:
927
- if wallet_data.get('encrypted_private_key'):
928
- key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
1159
+ if wallet_data.get("encrypted_private_key"):
1160
+ key = base64.urlsafe_b64encode(
1161
+ hashlib.sha256(password.encode()).digest()
1162
+ )
929
1163
  fernet = Fernet(key)
930
- decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
1164
+ decrypted_key = fernet.decrypt(wallet_data["encrypted_private_key"])
931
1165
  return decrypted_key.decode()
932
1166
  except:
933
1167
  pass
934
1168
  return None
935
-
1169
+
936
1170
  def import_wallet(self, wallet_data, password=None):
937
1171
  """Import wallet from data"""
938
1172
  if isinstance(wallet_data, dict):
939
- address = wallet_data.get('address')
1173
+ address = wallet_data.get("address")
940
1174
  if not address:
941
1175
  return False
942
-
1176
+
943
1177
  # Check if wallet uses SM2 cryptography
944
- if wallet_data.get('crypto_standard') != 'SM2_GB/T_32918':
945
- print(f"DEBUG: WARNING: Importing wallet without SM2 cryptography standard")
946
-
1178
+ if wallet_data.get("crypto_standard") != "SM2_GB/T_32918":
1179
+ print(
1180
+ f"DEBUG: WARNING: Importing wallet without SM2 cryptography standard"
1181
+ )
1182
+
947
1183
  # Add to wallets collection
948
1184
  self.wallets[address] = wallet_data.copy()
949
-
1185
+
950
1186
  # Set as current wallet
951
1187
  self._set_current_wallet(wallet_data)
952
-
953
- if password and wallet_data.get('encrypted_private_key'):
1188
+
1189
+ if password and wallet_data.get("encrypted_private_key"):
954
1190
  return self.unlock_wallet(address, password)
955
-
1191
+
956
1192
  return True
957
1193
  return False
958
-
1194
+
959
1195
  def update_balance(self, new_balance):
960
1196
  """Update current wallet balance"""
961
1197
  self.balance = float(new_balance)
962
1198
  self.available_balance = float(new_balance)
963
-
1199
+
964
1200
  if self.current_wallet_address and self.current_wallet_address in self.wallets:
965
- self.wallets[self.current_wallet_address]['balance'] = self.balance
966
- self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
967
-
1201
+ self.wallets[self.current_wallet_address]["balance"] = self.balance
1202
+ self.wallets[self.current_wallet_address][
1203
+ "available_balance"
1204
+ ] = self.available_balance
1205
+
968
1206
  return True
969
-
1207
+
970
1208
  def get_balance(self):
971
1209
  """Get current wallet total balance"""
972
1210
  return self.balance
973
-
1211
+
974
1212
  def get_available_balance(self):
975
1213
  """Get current wallet available balance"""
976
1214
  return self.available_balance
977
-
1215
+
978
1216
  def get_wallet_by_address(self, address):
979
1217
  """Get wallet by address from wallets collection"""
980
1218
  return self.wallets.get(address)
981
-
1219
+
982
1220
  def list_wallets(self):
983
1221
  """List all wallets in collection"""
984
1222
  return list(self.wallets.keys())
985
-
1223
+
986
1224
  def get_current_wallet_info(self):
987
1225
  """Get current wallet information"""
988
1226
  if not self.current_wallet_address:
989
1227
  return None
990
-
1228
+
991
1229
  return self.wallets.get(self.current_wallet_address)
992
-
1230
+
993
1231
  def get_wallet_info(self):
994
1232
  """Get complete wallet information for current wallet"""
995
1233
  if not self.address:
996
1234
  return None
997
-
1235
+
998
1236
  self.refresh_balance()
999
-
1237
+
1000
1238
  return {
1001
- 'address': self.address,
1002
- 'balance': self.balance,
1003
- 'available_balance': self.available_balance,
1004
- 'created': self.created,
1005
- 'private_key': self.private_key,
1006
- 'public_key': self.public_key,
1007
- 'encrypted_private_key': self.encrypted_private_key,
1008
- 'label': self.label,
1009
- 'is_locked': self.is_locked,
1010
- 'crypto_standard': 'SM2_GB/T_32918'
1239
+ "address": self.address,
1240
+ "balance": self.balance,
1241
+ "available_balance": self.available_balance,
1242
+ "created": self.created,
1243
+ "private_key": self.private_key,
1244
+ "public_key": self.public_key,
1245
+ "encrypted_private_key": self.encrypted_private_key,
1246
+ "label": self.label,
1247
+ "is_locked": self.is_locked,
1248
+ "crypto_standard": "SM2_GB/T_32918",
1011
1249
  }
1012
-
1250
+
1013
1251
  # ============================================================================
1014
1252
  # UNIFIED WALLET STATE MANAGER INTEGRATION
1015
1253
  # ============================================================================
1016
-
1254
+
1017
1255
  def sync_with_state_manager(self, blockchain=None, mempool=None) -> Dict:
1018
1256
  """
1019
1257
  Sync all registered wallets with the unified WalletStateManager.
1020
1258
  Scans blockchain once and merges mempool data for all wallets.
1021
-
1259
+
1022
1260
  Parameters:
1023
1261
  blockchain: BlockchainManager instance (required)
1024
1262
  mempool: MempoolManager instance (required)
1025
-
1263
+
1026
1264
  Returns: Dictionary of wallet summaries with balances and transactions
1027
1265
  """
1028
1266
  try:
1029
1267
  if not blockchain or not mempool:
1030
1268
  print("❌ blockchain and mempool instances required")
1031
1269
  return {}
1032
-
1270
+
1033
1271
  from .wallet_manager import get_wallet_manager
1034
-
1272
+
1035
1273
  state_manager = get_wallet_manager()
1036
-
1274
+
1037
1275
  # Register all wallets with state manager if not already done
1038
1276
  addresses = list(self.wallets.keys())
1039
1277
  state_manager.register_wallets(addresses)
1040
-
1278
+
1041
1279
  print(f"🔄 Syncing {len(addresses)} wallets...")
1042
-
1280
+
1043
1281
  # Get data from blockchain and mempool (single scan)
1044
1282
  blockchain_txs = blockchain.scan_transactions_for_addresses(addresses)
1045
1283
  mempool_txs = mempool.get_pending_transactions_for_addresses(addresses)
1046
-
1284
+
1047
1285
  # Sync state manager with the data
1048
1286
  state_manager.sync_wallets_from_sources(blockchain_txs, mempool_txs)
1049
-
1287
+
1050
1288
  # Update LunaWallet balances from state manager
1051
1289
  balances = state_manager.get_all_balances()
1052
1290
  for address, balance_data in balances.items():
1053
1291
  if address in self.wallets:
1054
- self.wallets[address]['balance'] = balance_data['confirmed_balance']
1055
- self.wallets[address]['available_balance'] = balance_data['available_balance']
1056
-
1292
+ self.wallets[address]["balance"] = balance_data["confirmed_balance"]
1293
+ self.wallets[address]["available_balance"] = balance_data[
1294
+ "available_balance"
1295
+ ]
1296
+
1057
1297
  # Update current wallet
1058
1298
  if self.current_wallet_address and self.current_wallet_address in balances:
1059
1299
  balance_data = balances[self.current_wallet_address]
1060
- self.balance = balance_data['confirmed_balance']
1061
- self.available_balance = balance_data['available_balance']
1062
-
1300
+ self.balance = balance_data["confirmed_balance"]
1301
+ self.available_balance = balance_data["available_balance"]
1302
+
1063
1303
  # Return summaries
1064
1304
  summaries = state_manager.get_all_summaries()
1065
1305
  print(f"✅ Sync complete - {len(summaries)} wallets updated")
1066
-
1306
+
1067
1307
  return summaries
1068
-
1308
+
1069
1309
  except Exception as e:
1070
1310
  print(f"❌ Sync error: {e}")
1071
1311
  import traceback
1312
+
1072
1313
  traceback.print_exc()
1073
1314
  return {}
1074
-
1315
+
1075
1316
  def get_wallet_details(self, address: str = None) -> Optional[Dict]:
1076
1317
  """
1077
1318
  Get detailed information for a wallet including balance and transaction summary.
@@ -1079,57 +1320,61 @@ class LunaWallet:
1079
1320
  """
1080
1321
  if address is None:
1081
1322
  address = self.current_wallet_address
1082
-
1323
+
1083
1324
  if not address:
1084
1325
  return None
1085
-
1326
+
1086
1327
  try:
1087
1328
  from .wallet_manager import get_wallet_manager
1329
+
1088
1330
  state_manager = get_wallet_manager()
1089
-
1331
+
1090
1332
  summary = state_manager.get_wallet_summary(address)
1091
1333
  if summary:
1092
1334
  return summary
1093
-
1335
+
1094
1336
  # Fallback to basic wallet info
1095
1337
  if address in self.wallets:
1096
1338
  wallet_data = self.wallets[address]
1097
1339
  return {
1098
- 'address': address,
1099
- 'label': wallet_data.get('label', 'Wallet'),
1100
- 'balance': wallet_data.get('balance', 0.0),
1101
- 'available_balance': wallet_data.get('available_balance', 0.0),
1102
- 'is_locked': wallet_data.get('is_locked', True),
1340
+ "address": address,
1341
+ "label": wallet_data.get("label", "Wallet"),
1342
+ "balance": wallet_data.get("balance", 0.0),
1343
+ "available_balance": wallet_data.get("available_balance", 0.0),
1344
+ "is_locked": wallet_data.get("is_locked", True),
1103
1345
  }
1104
-
1346
+
1105
1347
  return None
1106
-
1348
+
1107
1349
  except Exception as e:
1108
1350
  print(f"⚠️ Error getting wallet details: {e}")
1109
1351
  return None
1110
-
1111
- def get_wallet_transactions(self, address: str = None, tx_type: str = 'all') -> List[Dict]:
1352
+
1353
+ def get_wallet_transactions(
1354
+ self, address: str = None, tx_type: str = "all"
1355
+ ) -> List[Dict]:
1112
1356
  """
1113
1357
  Get transactions for a wallet from the state manager.
1114
-
1358
+
1115
1359
  tx_type: 'all', 'confirmed', 'pending', 'transfers', 'rewards', 'genesis'
1116
1360
  """
1117
1361
  if address is None:
1118
1362
  address = self.current_wallet_address
1119
-
1363
+
1120
1364
  if not address:
1121
1365
  return []
1122
-
1366
+
1123
1367
  try:
1124
1368
  from .wallet_manager import get_wallet_manager
1369
+
1125
1370
  state_manager = get_wallet_manager()
1126
-
1371
+
1127
1372
  return state_manager.get_transactions(address, tx_type)
1128
-
1373
+
1129
1374
  except Exception as e:
1130
1375
  print(f"⚠️ Error getting transactions: {e}")
1131
1376
  return []
1132
-
1377
+
1133
1378
  def register_wallet_ui_callback(self, callback: Callable) -> None:
1134
1379
  """
1135
1380
  Register a callback to receive real-time wallet balance updates.
@@ -1137,12 +1382,15 @@ class LunaWallet:
1137
1382
  """
1138
1383
  try:
1139
1384
  from .wallet_manager import get_wallet_manager
1385
+
1140
1386
  state_manager = get_wallet_manager()
1141
1387
  state_manager.on_balance_update(callback)
1142
1388
  except Exception as e:
1143
1389
  print(f"⚠️ Error registering callback: {e}")
1144
-
1145
- def start_continuous_sync(self, blockchain=None, mempool=None, poll_interval: int = 30) -> None:
1390
+
1391
+ def start_continuous_sync(
1392
+ self, blockchain=None, mempool=None, poll_interval: int = 30
1393
+ ) -> None:
1146
1394
  """
1147
1395
  Start continuous synchronization in background thread.
1148
1396
  Syncs every poll_interval seconds.
@@ -1150,168 +1398,189 @@ class LunaWallet:
1150
1398
  if not blockchain or not mempool:
1151
1399
  print("❌ blockchain and mempool instances required")
1152
1400
  return
1153
-
1401
+
1154
1402
  try:
1155
1403
  from .wallet_manager import get_wallet_manager
1156
1404
  from .wallet_sync_helper import WalletSyncHelper
1157
-
1405
+
1158
1406
  state_manager = get_wallet_manager()
1159
-
1407
+
1160
1408
  # Register wallets
1161
1409
  addresses = list(self.wallets.keys())
1162
1410
  state_manager.register_wallets(addresses)
1163
-
1411
+
1164
1412
  def sync_callback(balance_data):
1165
1413
  """Update LunaWallet when state manager updates"""
1166
1414
  for address, balance_info in balance_data.items():
1167
1415
  if address in self.wallets:
1168
- self.wallets[address]['balance'] = balance_info['confirmed_balance']
1169
- self.wallets[address]['available_balance'] = balance_info['available_balance']
1170
-
1416
+ self.wallets[address]["balance"] = balance_info[
1417
+ "confirmed_balance"
1418
+ ]
1419
+ self.wallets[address]["available_balance"] = balance_info[
1420
+ "available_balance"
1421
+ ]
1422
+
1171
1423
  if address == self.current_wallet_address:
1172
- self.balance = balance_info['confirmed_balance']
1173
- self.available_balance = balance_info['available_balance']
1174
-
1424
+ self.balance = balance_info["confirmed_balance"]
1425
+ self.available_balance = balance_info["available_balance"]
1426
+
1175
1427
  # Create sync helper
1176
1428
  sync_helper = WalletSyncHelper(self, blockchain, mempool)
1177
-
1429
+
1178
1430
  # Start continuous sync with callback
1179
1431
  sync_helper.start_continuous_sync(
1180
- poll_interval,
1181
- on_balance_update=sync_callback
1432
+ poll_interval, on_balance_update=sync_callback
1182
1433
  )
1183
-
1434
+
1184
1435
  print(f"🔄 Started continuous sync (interval: {poll_interval}s)")
1185
-
1436
+
1186
1437
  except Exception as e:
1187
1438
  print(f"❌ Error starting continuous sync: {e}")
1188
-
1439
+
1189
1440
  def save_to_file(self, filename=None):
1190
1441
  """Save wallet to file"""
1191
1442
  if not self.data_dir:
1192
1443
  return False
1193
-
1444
+
1194
1445
  if filename is None:
1195
1446
  filename = f"wallet_{self.address}.json"
1196
-
1447
+
1197
1448
  filepath = os.path.join(self.data_dir, filename)
1198
-
1449
+
1199
1450
  try:
1200
1451
  os.makedirs(self.data_dir, exist_ok=True)
1201
-
1452
+
1202
1453
  encrypted_key_data = None
1203
1454
  if self.encrypted_private_key:
1204
1455
  if isinstance(self.encrypted_private_key, bytes):
1205
- encrypted_key_data = base64.b64encode(self.encrypted_private_key).decode('utf-8')
1456
+ encrypted_key_data = base64.b64encode(
1457
+ self.encrypted_private_key
1458
+ ).decode("utf-8")
1206
1459
  else:
1207
- encrypted_key_data = base64.b64encode(self.encrypted_private_key.encode()).decode('utf-8')
1208
-
1460
+ encrypted_key_data = base64.b64encode(
1461
+ self.encrypted_private_key.encode()
1462
+ ).decode("utf-8")
1463
+
1209
1464
  serializable_wallets = {}
1210
1465
  for addr, wallet_info in self.wallets.items():
1211
1466
  serializable_wallet = wallet_info.copy()
1212
- if serializable_wallet.get('encrypted_private_key') and isinstance(serializable_wallet['encrypted_private_key'], bytes):
1213
- serializable_wallet['encrypted_private_key'] = base64.b64encode(
1214
- serializable_wallet['encrypted_private_key']
1215
- ).decode('utf-8')
1467
+ if serializable_wallet.get("encrypted_private_key") and isinstance(
1468
+ serializable_wallet["encrypted_private_key"], bytes
1469
+ ):
1470
+ serializable_wallet["encrypted_private_key"] = base64.b64encode(
1471
+ serializable_wallet["encrypted_private_key"]
1472
+ ).decode("utf-8")
1216
1473
  serializable_wallets[addr] = serializable_wallet
1217
-
1474
+
1218
1475
  wallet_data = {
1219
- 'address': self.address,
1220
- 'balance': self.balance,
1221
- 'available_balance': self.available_balance,
1222
- 'created': self.created,
1223
- 'public_key': self.public_key,
1224
- 'encrypted_private_key': encrypted_key_data,
1225
- 'label': self.label,
1226
- 'is_locked': self.is_locked,
1227
- 'wallets': serializable_wallets,
1228
- 'current_wallet_address': self.current_wallet_address,
1229
- 'crypto_standard': 'SM2_GB/T_32918'
1476
+ "address": self.address,
1477
+ "balance": self.balance,
1478
+ "available_balance": self.available_balance,
1479
+ "created": self.created,
1480
+ "public_key": self.public_key,
1481
+ "encrypted_private_key": encrypted_key_data,
1482
+ "label": self.label,
1483
+ "is_locked": self.is_locked,
1484
+ "wallets": serializable_wallets,
1485
+ "current_wallet_address": self.current_wallet_address,
1486
+ "crypto_standard": "SM2_GB/T_32918",
1230
1487
  }
1231
-
1232
- with open(filepath, 'w') as f:
1488
+
1489
+ with open(filepath, "w") as f:
1233
1490
  json.dump(wallet_data, f, indent=2)
1234
-
1491
+
1235
1492
  print(f"DEBUG: Wallet saved to {filepath}")
1236
1493
  return True
1237
1494
  except Exception as e:
1238
1495
  print(f"Error saving wallet: {e}")
1239
1496
  import traceback
1497
+
1240
1498
  traceback.print_exc()
1241
1499
  return False
1242
-
1500
+
1243
1501
  def load_from_file(self, filename, password=None):
1244
1502
  """Load wallet from file"""
1245
1503
  if not self.data_dir:
1246
1504
  return False
1247
-
1505
+
1248
1506
  filepath = os.path.join(self.data_dir, filename)
1249
-
1507
+
1250
1508
  try:
1251
- with open(filepath, 'r') as f:
1509
+ with open(filepath, "r") as f:
1252
1510
  wallet_data = json.load(f)
1253
-
1511
+
1254
1512
  # Check crypto standard
1255
- crypto_standard = wallet_data.get('crypto_standard')
1256
- if crypto_standard != 'SM2_GB/T_32918':
1257
- print(f"DEBUG: WARNING: Loading wallet with different crypto standard: {crypto_standard}")
1258
-
1513
+ crypto_standard = wallet_data.get("crypto_standard")
1514
+ if crypto_standard != "SM2_GB/T_32918":
1515
+ print(
1516
+ f"DEBUG: WARNING: Loading wallet with different crypto standard: {crypto_standard}"
1517
+ )
1518
+
1259
1519
  # Load wallets collection
1260
- self.wallets = wallet_data.get('wallets', {})
1261
-
1520
+ self.wallets = wallet_data.get("wallets", {})
1521
+
1262
1522
  # Load current wallet address
1263
- self.current_wallet_address = wallet_data.get('current_wallet_address')
1264
-
1523
+ self.current_wallet_address = wallet_data.get("current_wallet_address")
1524
+
1265
1525
  # If we have a current wallet, load its data
1266
- if self.current_wallet_address and self.current_wallet_address in self.wallets:
1526
+ if (
1527
+ self.current_wallet_address
1528
+ and self.current_wallet_address in self.wallets
1529
+ ):
1267
1530
  current_wallet_data = self.wallets[self.current_wallet_address]
1268
1531
  self._set_current_wallet(current_wallet_data)
1269
-
1270
- encrypted_key = wallet_data.get('encrypted_private_key')
1532
+
1533
+ encrypted_key = wallet_data.get("encrypted_private_key")
1271
1534
  if encrypted_key:
1272
- self.encrypted_private_key = base64.b64decode(encrypted_key.encode())
1535
+ self.encrypted_private_key = base64.b64decode(
1536
+ encrypted_key.encode()
1537
+ )
1273
1538
  if self.current_wallet_address in self.wallets:
1274
- self.wallets[self.current_wallet_address]['encrypted_private_key'] = self.encrypted_private_key
1275
-
1539
+ self.wallets[self.current_wallet_address][
1540
+ "encrypted_private_key"
1541
+ ] = self.encrypted_private_key
1542
+
1276
1543
  self.refresh_balance()
1277
-
1544
+
1278
1545
  if password and self.encrypted_private_key and self.current_wallet_address:
1279
1546
  return self.unlock_wallet(self.current_wallet_address, password)
1280
-
1547
+
1281
1548
  print(f"DEBUG: Wallet loaded from {filepath}")
1282
1549
  print(f"DEBUG: Total wallets: {len(self.wallets)}")
1283
1550
  print(f"DEBUG: Current wallet: {self.current_wallet_address}")
1284
-
1551
+
1285
1552
  return True
1286
1553
  except Exception as e:
1287
1554
  print(f"Error loading wallet: {e}")
1288
1555
  return False
1289
-
1556
+
1290
1557
  def debug_crypto_info(self):
1291
1558
  """Debug cryptographic information"""
1292
- print("\n" + "="*60)
1559
+ print("\n" + "=" * 60)
1293
1560
  print("SM2 CRYPTOGRAPHY DEBUG INFO")
1294
- print("="*60)
1295
-
1561
+ print("=" * 60)
1562
+
1296
1563
  if self.current_wallet_address:
1297
1564
  print(f"Current Address: {self.address}")
1298
-
1565
+
1299
1566
  # Check address format
1300
- if self.address.startswith('LUN_'):
1567
+ if self.address.startswith("LUN_"):
1301
1568
  print("✅ Address has correct LUN_ prefix")
1302
1569
  else:
1303
1570
  print("❌ Address missing LUN_ prefix")
1304
-
1571
+
1305
1572
  # Check private key
1306
1573
  if self.private_key:
1307
1574
  print(f"Private Key: {self.private_key[:16]}...")
1308
1575
  if len(self.private_key) == 64:
1309
1576
  print(f"✅ Private key is 256-bit (64 hex chars)")
1310
1577
  else:
1311
- print(f"❌ Private key has wrong length: {len(self.private_key)} chars")
1578
+ print(
1579
+ f"❌ Private key has wrong length: {len(self.private_key)} chars"
1580
+ )
1312
1581
  else:
1313
1582
  print("❌ No private key available")
1314
-
1583
+
1315
1584
  # Check public key
1316
1585
  if self.public_key:
1317
1586
  print(f"Public Key: {self.public_key[:16]}...")
@@ -1321,14 +1590,14 @@ class LunaWallet:
1321
1590
  print(f"❌ Public key too short: {len(self.public_key)} chars")
1322
1591
  else:
1323
1592
  print("❌ No public key available")
1324
-
1593
+
1325
1594
  # Check cryptographic standard
1326
1595
  wallet_data = self.wallets.get(self.address, {})
1327
- if wallet_data.get('crypto_standard') == 'SM2_GB/T_32918':
1596
+ if wallet_data.get("crypto_standard") == "SM2_GB/T_32918":
1328
1597
  print("✅ Using SM2 GB/T 32918 East Asian cryptography standard")
1329
1598
  else:
1330
1599
  print("❌ Not using SM2 cryptography standard")
1331
-
1600
+
1332
1601
  # Test signing
1333
1602
  if self.private_key and not self.is_locked:
1334
1603
  test_sig = self._sign_transaction_data({"test": "data"})
@@ -1338,5 +1607,5 @@ class LunaWallet:
1338
1607
  print("❌ Cannot sign transactions")
1339
1608
  else:
1340
1609
  print("❌ No wallet selected")
1341
-
1342
- print("="*60)
1610
+
1611
+ print("=" * 60)