lunalib 1.5.1__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.

Potentially problematic release.


This version of lunalib might be problematic. Click here for more details.

Files changed (53) hide show
  1. core/__init__.py +0 -0
  2. core/blockchain.py +172 -0
  3. core/crypto.py +32 -0
  4. core/wallet.py +408 -0
  5. gtx/__init__.py +0 -0
  6. gtx/bill_registry.py +122 -0
  7. gtx/digital_bill.py +273 -0
  8. gtx/genesis.py +338 -0
  9. lunalib/__init__.py +21 -0
  10. lunalib/cli.py +18 -0
  11. lunalib/core/__init__.py +0 -0
  12. lunalib/core/blockchain.py +803 -0
  13. lunalib/core/crypto.py +270 -0
  14. lunalib/core/mempool.py +342 -0
  15. lunalib/core/sm2.py +723 -0
  16. lunalib/core/wallet.py +1342 -0
  17. lunalib/core/wallet_manager.py +638 -0
  18. lunalib/core/wallet_sync_helper.py +163 -0
  19. lunalib/gtx/__init__.py +0 -0
  20. lunalib/gtx/bill_registry.py +122 -0
  21. lunalib/gtx/digital_bill.py +273 -0
  22. lunalib/gtx/genesis.py +349 -0
  23. lunalib/luna_lib.py +87 -0
  24. lunalib/mining/__init__.py +0 -0
  25. lunalib/mining/cuda_manager.py +137 -0
  26. lunalib/mining/difficulty.py +106 -0
  27. lunalib/mining/miner.py +617 -0
  28. lunalib/requirements.txt +44 -0
  29. lunalib/storage/__init__.py +0 -0
  30. lunalib/storage/cache.py +148 -0
  31. lunalib/storage/database.py +222 -0
  32. lunalib/storage/encryption.py +105 -0
  33. lunalib/transactions/__init__.py +0 -0
  34. lunalib/transactions/security.py +234 -0
  35. lunalib/transactions/transactions.py +399 -0
  36. lunalib/transactions/validator.py +71 -0
  37. lunalib-1.5.1.dist-info/METADATA +283 -0
  38. lunalib-1.5.1.dist-info/RECORD +53 -0
  39. lunalib-1.5.1.dist-info/WHEEL +5 -0
  40. lunalib-1.5.1.dist-info/entry_points.txt +2 -0
  41. lunalib-1.5.1.dist-info/top_level.txt +6 -0
  42. mining/__init__.py +0 -0
  43. mining/cuda_manager.py +137 -0
  44. mining/difficulty.py +106 -0
  45. mining/miner.py +107 -0
  46. storage/__init__.py +0 -0
  47. storage/cache.py +148 -0
  48. storage/database.py +222 -0
  49. storage/encryption.py +105 -0
  50. transactions/__init__.py +0 -0
  51. transactions/security.py +172 -0
  52. transactions/transactions.py +424 -0
  53. transactions/validator.py +71 -0
lunalib/core/wallet.py ADDED
@@ -0,0 +1,1342 @@
1
+ import time
2
+ import hashlib
3
+ import json
4
+ import os
5
+ import secrets
6
+ import binascii
7
+ import base64
8
+ import threading
9
+ import concurrent.futures
10
+ from typing import Optional, Callable, Dict, List, Tuple
11
+ from cryptography.fernet import Fernet
12
+ from ..core.crypto import KeyManager
13
+
14
+
15
+ class LunaWallet:
16
+ def __init__(self, data_dir=None):
17
+ self.data_dir = data_dir
18
+ self.wallets = {}
19
+ self.current_wallet_address = None
20
+ self.key_manager = KeyManager()
21
+
22
+ # Threading for asynchronous balance loading
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
+
32
+ # ----------------------
33
+ # Address normalization
34
+ # ----------------------
35
+ def _normalize_address(self, addr: str) -> str:
36
+ if not addr:
37
+ return ''
38
+ addr_str = str(addr).strip("'\" ").lower()
39
+ return addr_str[4:] if addr_str.startswith('lun_') else addr_str
40
+
41
+
42
+ def _reset_current_wallet(self):
43
+ """Reset current wallet to empty state"""
44
+ self.address = None
45
+ self.balance = 0.0 # Total balance (confirmed transactions)
46
+ self.available_balance = 0.0 # Available balance (total - pending outgoing)
47
+ self.created = time.time()
48
+ self.private_key = None # Will store REAL SM2 private key
49
+ self.public_key = None # Will store REAL SM2 public key
50
+ self.encrypted_private_key = None
51
+ self.label = "New Wallet"
52
+ self.is_locked = True
53
+
54
+ # ============================================================================
55
+ # REAL CRYPTOGRAPHIC KEY GENERATION (SM2 EAST ASIAN STANDARD)
56
+ # ============================================================================
57
+
58
+
59
+
60
+ def _sign_transaction_data(self, transaction_data):
61
+ """Sign transaction data with SM2 private key"""
62
+ if not self.private_key or self.is_locked:
63
+ print("DEBUG: Cannot sign - wallet locked or no private key")
64
+ return None
65
+
66
+ # Convert transaction data to string for signing
67
+ tx_string = json.dumps(transaction_data, sort_keys=True)
68
+
69
+ # Sign with SM2
70
+ signature = self.sm2.sign_message(self.private_key, tx_string)
71
+
72
+ if signature:
73
+ print(f"DEBUG: Transaction signed successfully, signature: {signature[:16]}...")
74
+ else:
75
+ print("DEBUG: Failed to sign transaction")
76
+
77
+ return signature
78
+
79
+ def _verify_wallet_integrity(self) -> bool:
80
+ """Basic cryptographic key verification"""
81
+ # Check if keys exist
82
+ if not self.private_key or not self.public_key:
83
+ return False
84
+
85
+ # Validate key formats
86
+ if len(self.private_key) != 64: # 256-bit hex
87
+ return False
88
+
89
+ if not self.public_key.startswith('04') or len(self.public_key) != 130:
90
+ return False
91
+
92
+ return True
93
+
94
+ def _generate_key_pair(self):
95
+ """Generate REAL SM2 key pair"""
96
+ return self.key_manager.generate_keypair()
97
+
98
+ def create_wallet(self, name, password):
99
+ """Create a new wallet and set it as current"""
100
+ print(f"DEBUG: Creating wallet '{name}'")
101
+
102
+ # Generate REAL SM2 keys
103
+ private_key, public_key, address = self._generate_key_pair()
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, # 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'
122
+ }
123
+
124
+ # 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 set current wallet {address}")
131
+
132
+ return wallet_data
133
+
134
+
135
+ def _generate_address(self, public_key_hex):
136
+ """Generate address from SM2 public key"""
137
+ return self.key_manager.derive_address(public_key_hex)
138
+ def create_new_wallet(self, name, password):
139
+ """
140
+ Create a new wallet without switching to it
141
+ Returns: wallet_data dict
142
+ """
143
+ print(f"DEBUG: Creating additional wallet '{name}'")
144
+
145
+ # Generate REAL cryptographic keys
146
+ private_key_hex, public_key_hex, address = self._generate_key_pair()
147
+
148
+ # Encrypt private key
149
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
150
+ fernet = Fernet(key)
151
+ encrypted_private_key = fernet.encrypt(private_key_hex.encode())
152
+
153
+ # Create wallet data
154
+ 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'
165
+ }
166
+
167
+ # Add to wallets collection
168
+ self.wallets[address] = new_wallet_data
169
+
170
+ print(f"DEBUG: Created wallet {address}, total wallets: {len(self.wallets)}")
171
+
172
+ return new_wallet_data
173
+
174
+ def _set_current_wallet(self, wallet_data):
175
+ """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
+
187
+ print(f"DEBUG: Set current wallet to {self.address}")
188
+
189
+ def unlock_wallet(self, address, password):
190
+ """Unlock wallet with password"""
191
+ if address not in self.wallets:
192
+ print(f"DEBUG: Wallet {address} not found in collection")
193
+ return False
194
+
195
+ wallet_data = self.wallets[address]
196
+
197
+ try:
198
+ if wallet_data.get('encrypted_private_key'):
199
+ # Decrypt private key
200
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
201
+ fernet = Fernet(key)
202
+ decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
203
+
204
+ # Update wallet data
205
+ wallet_data['private_key'] = decrypted_key.decode()
206
+ wallet_data['is_locked'] = False
207
+
208
+ # If this is the current wallet, update current state
209
+ if self.current_wallet_address == address:
210
+ self.private_key = wallet_data['private_key']
211
+ self.is_locked = False
212
+
213
+ print(f"DEBUG: Wallet {address} unlocked successfully")
214
+ print(f"DEBUG: Private key available: {bool(self.private_key)}")
215
+
216
+ # Verify cryptographic integrity after unlock
217
+ if self._verify_wallet_integrity():
218
+ print(f"DEBUG: Wallet cryptographic integrity verified")
219
+ else:
220
+ print(f"DEBUG: WARNING: Wallet unlocked but cryptographic verification failed")
221
+
222
+ return True
223
+ except Exception as e:
224
+ print(f"DEBUG: Unlock failed: {e}")
225
+
226
+ return False
227
+
228
+ def switch_wallet(self, address, password=None):
229
+ """Switch to a different wallet in the collection"""
230
+ if address in self.wallets:
231
+ wallet_data = self.wallets[address]
232
+ self._set_current_wallet(wallet_data)
233
+
234
+ # If password provided, unlock the wallet
235
+ if password:
236
+ return self.unlock_wallet(address, password)
237
+
238
+ return True
239
+
240
+ print(f"DEBUG: Cannot switch to {address} - not in wallet collection")
241
+ return False
242
+
243
+ # ============================================================================
244
+ # BALANCE AND TRANSACTION METHODS
245
+ # ============================================================================
246
+
247
+ def calculate_available_balance(self) -> float:
248
+ """Calculate available balance (total balance minus pending outgoing transactions)"""
249
+ try:
250
+ # Get total balance from blockchain
251
+ total_balance = self._get_total_balance_from_blockchain()
252
+
253
+ # Get pending outgoing amount
254
+ pending_outgoing = self._get_pending_balance()
255
+
256
+ # Also check for any incoming pending transactions
257
+ incoming_pending = self._get_pending_incoming_balance()
258
+
259
+ available_balance = max(0.0, total_balance - pending_outgoing)
260
+
261
+ # Update both current wallet and wallets collection
262
+ self.available_balance = available_balance
263
+ self.balance = total_balance # Also update total balance
264
+
265
+ 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
+
271
+ if incoming_pending > 0:
272
+ print(f"DEBUG: Also {incoming_pending} LUN incoming (pending)")
273
+
274
+ return available_balance
275
+
276
+ except Exception as e:
277
+ print(f"DEBUG: Error calculating available balance: {e}")
278
+ return self.balance # Fallback to total balance
279
+
280
+ def _compute_confirmed_balance(self, transactions: List[Dict]) -> float:
281
+ """Compute confirmed balance from a list of transactions."""
282
+ total_balance = 0.0
283
+ 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)
287
+
288
+ if tx_type == 'reward' or tx.get('from') == 'network':
289
+ total_balance += amount
290
+ elif direction == 'incoming':
291
+ total_balance += amount
292
+ elif direction == 'outgoing':
293
+ fee = float(tx.get('fee', 0) or 0)
294
+ total_balance -= amount
295
+ total_balance -= fee
296
+
297
+ return max(0.0, total_balance)
298
+
299
+ def _compute_pending_totals(self, pending_txs: List[Dict], address: str) -> Tuple[float, float]:
300
+ """Return (pending_outgoing, pending_incoming) for an address."""
301
+ pending_out = 0.0
302
+ pending_in = 0.0
303
+
304
+ target_norm = self._normalize_address(address)
305
+ 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'))
308
+
309
+ 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)
312
+ pending_out += amount + fee
313
+ if to_norm == target_norm:
314
+ amount = float(tx.get('amount', 0) or 0)
315
+ pending_in += amount
316
+
317
+ return pending_out, pending_in
318
+
319
+ def _recompute_balances_from_cache(self) -> Dict[str, Dict[str, float]]:
320
+ """Recompute balances for all wallets from cached tx sets."""
321
+ updated: Dict[str, Dict[str, float]] = {}
322
+
323
+ for addr, wallet_data in self.wallets.items():
324
+ confirmed = self._confirmed_tx_cache.get(addr, [])
325
+ pending = self._pending_tx_cache.get(addr, [])
326
+
327
+ total_balance = max(0.0, self._compute_confirmed_balance(confirmed))
328
+ pending_out, pending_in = self._compute_pending_totals(pending, addr)
329
+ available_balance = max(0.0, total_balance - pending_out)
330
+
331
+ wallet_data['balance'] = total_balance
332
+ wallet_data['available_balance'] = available_balance
333
+
334
+ if addr == self.current_wallet_address:
335
+ self.balance = total_balance
336
+ self.available_balance = available_balance
337
+
338
+ updated[addr] = {
339
+ 'balance': total_balance,
340
+ 'available_balance': available_balance,
341
+ 'pending_out': pending_out,
342
+ 'pending_in': pending_in
343
+ }
344
+
345
+ return updated
346
+
347
+ def refresh_all_wallet_balances(self) -> Dict[str, Dict[str, float]]:
348
+ """Scan blockchain once for all wallets, then update balances with pending mempool data."""
349
+ addresses = list(self.wallets.keys())
350
+ if not addresses:
351
+ return {}
352
+
353
+ try:
354
+ from lunalib.core.blockchain import BlockchainManager
355
+ from lunalib.core.mempool import MempoolManager
356
+
357
+ blockchain = BlockchainManager()
358
+ mempool = MempoolManager()
359
+
360
+ confirmed_map = blockchain.scan_transactions_for_addresses(addresses)
361
+ pending_map = mempool.get_pending_transactions_for_addresses(addresses, fetch_remote=True)
362
+
363
+ # Reset caches with the latest snapshots
364
+ self._confirmed_tx_cache = confirmed_map
365
+ self._pending_tx_cache = pending_map
366
+
367
+ return self._recompute_balances_from_cache()
368
+
369
+ except Exception as e:
370
+ print(f"DEBUG: Error refreshing all wallet balances: {e}")
371
+ return {}
372
+
373
+ def sync_all_wallets_once(self) -> Dict[str, Dict[str, float]]:
374
+ """Convenience: scan blockchain once and mempool once, update all wallet balances."""
375
+ return self.refresh_all_wallet_balances()
376
+
377
+ def get_all_wallets_overview(self, include_transactions: bool = True) -> Dict[str, Dict]:
378
+ """Return balances and (optionally) cached transactions for all wallets."""
379
+ overview: Dict[str, Dict] = {}
380
+ for addr, wallet_data in self.wallets.items():
381
+ confirmed = self._confirmed_tx_cache.get(addr, [])
382
+ pending = self._pending_tx_cache.get(addr, [])
383
+
384
+ 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],
389
+ }
390
+
391
+ if include_transactions:
392
+ overview[addr]['confirmed_transactions'] = confirmed
393
+ overview[addr]['pending_transactions'] = pending
394
+
395
+ return overview
396
+
397
+ def _apply_transaction_updates(self, confirmed_map: Dict[str, List[Dict]], pending_map: Dict[str, List[Dict]]):
398
+ """Update caches from monitor callbacks and recompute balances."""
399
+ for addr, txs in confirmed_map.items():
400
+ self._confirmed_tx_cache.setdefault(addr, []).extend(txs)
401
+
402
+ for addr, txs in pending_map.items():
403
+ self._pending_tx_cache[addr] = txs
404
+
405
+ return self._recompute_balances_from_cache()
406
+
407
+ def start_wallet_monitoring(self, poll_interval: int = 15):
408
+ """Start monitoring blockchain and mempool for all wallets."""
409
+ addresses = list(self.wallets.keys())
410
+ if not addresses:
411
+ print("DEBUG: No wallets to monitor")
412
+ return None
413
+
414
+ from lunalib.core.blockchain import BlockchainManager
415
+ blockchain = BlockchainManager()
416
+
417
+ # Run an initial refresh to seed caches and balances
418
+ self.refresh_all_wallet_balances()
419
+
420
+ def _on_update(payload: Dict):
421
+ confirmed_map = payload.get('confirmed', {}) or {}
422
+ pending_map = payload.get('pending', {}) or {}
423
+ self._apply_transaction_updates(confirmed_map, pending_map)
424
+
425
+ self._monitor_stop_event = blockchain.monitor_addresses(addresses, _on_update, poll_interval=poll_interval)
426
+ return self._monitor_stop_event
427
+
428
+ def stop_wallet_monitoring(self):
429
+ """Stop the background monitor if running."""
430
+ stop_event = getattr(self, '_monitor_stop_event', None)
431
+ if stop_event:
432
+ stop_event.set()
433
+
434
+ def start_sync_and_monitor(self, poll_interval: int = 15):
435
+ """Run a one-time sync (blockchain + mempool) then start live monitoring."""
436
+ self.sync_all_wallets_once()
437
+ return self.start_wallet_monitoring(poll_interval=poll_interval)
438
+
439
+ def get_all_balances_after_sync(self, include_transactions: bool = True) -> Dict[str, Dict]:
440
+ """One-shot sync then return per-wallet balances (and transactions if requested)."""
441
+ self.sync_all_wallets_once()
442
+ return self.get_all_wallets_overview(include_transactions=include_transactions)
443
+ def _get_pending_balance(self) -> float:
444
+ """Get total pending balance from mempool (outgoing pending transactions)"""
445
+ try:
446
+ from lunalib.core.mempool import MempoolManager
447
+
448
+ mempool = MempoolManager()
449
+
450
+ # Get pending transactions for this address
451
+ pending_txs = mempool.get_pending_transactions(self.address, fetch_remote=True)
452
+
453
+ total_pending_outgoing = 0.0
454
+
455
+ for tx in pending_txs:
456
+ # 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)
460
+ total_pending_outgoing += amount + fee
461
+ print(f"🔍 Found pending outgoing: {amount} + {fee} fee = {amount + fee}")
462
+
463
+ return total_pending_outgoing
464
+
465
+ except Exception as e:
466
+ print(f"DEBUG: Error calculating pending balance: {e}")
467
+ return 0.0
468
+
469
+ def _get_pending_incoming_balance(self) -> float:
470
+ """Get total pending incoming balance from mempool"""
471
+ try:
472
+ from lunalib.core.mempool import MempoolManager
473
+
474
+ mempool = MempoolManager()
475
+
476
+ # Get pending transactions for this address
477
+ pending_txs = mempool.get_pending_transactions(self.address, fetch_remote=True)
478
+
479
+ total_pending_incoming = 0.0
480
+
481
+ for tx in pending_txs:
482
+ # Only count incoming transactions (where we're the receiver)
483
+ if tx.get('to') == self.address:
484
+ amount = float(tx.get('amount', 0))
485
+ total_pending_incoming += amount
486
+ print(f"🔍 Found pending incoming: +{amount}")
487
+
488
+ return total_pending_incoming
489
+
490
+ except Exception as e:
491
+ print(f"DEBUG: Error calculating pending incoming balance: {e}")
492
+ return 0.0
493
+
494
+ def _get_total_balance_from_blockchain(self) -> float:
495
+ """Get total balance by scanning blockchain for confirmed transactions"""
496
+ try:
497
+ from lunalib.core.blockchain import BlockchainManager
498
+
499
+ blockchain = BlockchainManager()
500
+ transactions = blockchain.scan_transactions_for_address(self.address)
501
+ total_balance = self._compute_confirmed_balance(transactions)
502
+ return total_balance
503
+
504
+ except Exception as e:
505
+ print(f"DEBUG: Error getting blockchain balance: {e}")
506
+ return self.balance
507
+
508
+ def register_balance_callback(self, callback: Callable[[float, float], None]):
509
+ """Register a callback to be called when balance is updated asynchronously"""
510
+ self.balance_callbacks.append(callback)
511
+
512
+ def start_async_balance_loading(self):
513
+ """Start asynchronous balance loading in background thread"""
514
+ if self.balance_loading:
515
+ return # Already loading
516
+
517
+ if time.time() - self.last_balance_update < self.balance_update_interval:
518
+ return # Too soon since last update
519
+
520
+ self.balance_loading = True
521
+
522
+ def _async_balance_update():
523
+ try:
524
+ # Load balances asynchronously
525
+ total_balance = self._get_total_balance_from_blockchain()
526
+ available_balance = self.calculate_available_balance()
527
+
528
+ # Update wallet state
529
+ self.balance = total_balance
530
+ self.available_balance = available_balance
531
+ self.last_balance_update = time.time()
532
+
533
+ # Update in wallets collection
534
+ 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
+
538
+ # Notify callbacks
539
+ for callback in self.balance_callbacks:
540
+ try:
541
+ callback(total_balance, available_balance)
542
+ except Exception as e:
543
+ print(f"Error in balance callback: {e}")
544
+
545
+ print(f"DEBUG: Async balance updated - Total: {total_balance}, Available: {available_balance}")
546
+
547
+ except Exception as e:
548
+ print(f"DEBUG: Error in async balance loading: {e}")
549
+ finally:
550
+ self.balance_loading = False
551
+
552
+ # Start in background thread
553
+ self.balance_thread_pool.submit(_async_balance_update)
554
+
555
+ def refresh_balance(self) -> bool:
556
+ """Refresh both total and available balance from blockchain and mempool"""
557
+ try:
558
+ total_balance = self._get_total_balance_from_blockchain()
559
+ available_balance = self.calculate_available_balance()
560
+
561
+ # Update wallet state
562
+ self.balance = total_balance
563
+ self.available_balance = available_balance
564
+
565
+ # Update in wallets collection
566
+ 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}")
571
+ return True
572
+
573
+ except Exception as e:
574
+ print(f"DEBUG: Error refreshing balance: {e}")
575
+ return False
576
+
577
+ def send_transaction(self, to_address: str, amount: float, memo: str = "", password: str = None) -> bool:
578
+ """Send transaction using REAL SM2 signatures"""
579
+ try:
580
+ print(f"[SM2] Sending {amount} to {to_address}")
581
+
582
+ # 1. Basic validation
583
+ if self.is_locked or not self.private_key:
584
+ print("[SM2] Wallet locked or no private key")
585
+ return False
586
+
587
+ # 2. Check key integrity
588
+ if not self._verify_wallet_integrity():
589
+ print("[SM2] Invalid cryptographic keys")
590
+ return False
591
+
592
+ # 3. Balance check
593
+ self.refresh_balance()
594
+ if amount > self.get_available_balance():
595
+ print(f"[SM2] Insufficient balance: {self.get_available_balance()} < {amount}")
596
+ return False
597
+
598
+ # 4. Create and sign transaction
599
+ from lunalib.transactions.transactions import TransactionManager
600
+ tx_manager = TransactionManager()
601
+
602
+ transaction = tx_manager.create_transaction(
603
+ from_address=self.address,
604
+ to_address=to_address,
605
+ amount=amount,
606
+ private_key=self.private_key,
607
+ memo=memo,
608
+ transaction_type="transfer"
609
+ )
610
+
611
+ print(f"[SM2] Transaction created: {transaction.get('hash', 'no_hash')[:16]}")
612
+
613
+ # 5. Validate
614
+ is_valid, message = tx_manager.validate_transaction(transaction)
615
+ if not is_valid:
616
+ print(f"[SM2] Validation failed: {message}")
617
+ return False
618
+
619
+ # 6. Broadcast
620
+ success, message = tx_manager.send_transaction(transaction)
621
+ if success:
622
+ print(f"[SM2] Transaction broadcast: {message}")
623
+
624
+ # Update balance immediately
625
+ total_cost = amount + transaction.get('fee', 0)
626
+ self.available_balance -= total_cost
627
+ if self.current_wallet_address in self.wallets:
628
+ self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
629
+
630
+ # Save state
631
+ self.save_wallet_data()
632
+
633
+ # Trigger async balance refresh to get accurate data
634
+ self.start_async_balance_loading()
635
+
636
+ return True
637
+ else:
638
+ print(f"[SM2] Broadcast failed: {message}")
639
+ return False
640
+
641
+ except Exception as e:
642
+ print(f"[SM2] Error: {e}")
643
+ return False
644
+
645
+ def save_wallet_data(self):
646
+ """Save wallet data to file"""
647
+ try:
648
+ print(f"[WALLET] Saving wallet data...")
649
+
650
+ # Prepare wallet data
651
+ 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
663
+ }
664
+
665
+ # Determine save path
666
+ if hasattr(self, 'wallet_file'):
667
+ save_path = self.wallet_file
668
+ else:
669
+ # Default save location
670
+ import os
671
+ wallet_dir = os.path.expanduser('~/.lunawallet')
672
+ os.makedirs(wallet_dir, exist_ok=True)
673
+ save_path = os.path.join(wallet_dir, f'{self.address}.json')
674
+
675
+ # Save to file
676
+ import json
677
+ with open(save_path, 'w') as f:
678
+ json.dump(wallet_data, f, indent=2)
679
+
680
+ print(f"[WALLET] Wallet data saved to {save_path}")
681
+ return True
682
+
683
+ except Exception as e:
684
+ print(f"[WALLET ERROR] Failed to save wallet data: {e}")
685
+ import traceback
686
+ traceback.print_exc()
687
+ return False
688
+
689
+ def load_wallet_data(self, wallet_file=None):
690
+ """Load wallet data from file"""
691
+ try:
692
+ print(f"[WALLET] Loading wallet data...")
693
+
694
+ if wallet_file:
695
+ self.wallet_file = wallet_file
696
+ load_path = wallet_file
697
+ elif hasattr(self, 'wallet_file'):
698
+ load_path = self.wallet_file
699
+ else:
700
+ # Try to find wallet file
701
+ import os
702
+ wallet_dir = os.path.expanduser('~/.lunawallet')
703
+ # Look for any .json file
704
+ wallet_files = [f for f in os.listdir(wallet_dir) if f.endswith('.json')]
705
+ if not wallet_files:
706
+ print("[WALLET] No wallet file found")
707
+ return False
708
+ load_path = os.path.join(wallet_dir, wallet_files[0])
709
+ self.wallet_file = load_path
710
+
711
+ # Load from file
712
+ import json
713
+ with open(load_path, 'r') as f:
714
+ wallet_data = json.load(f)
715
+
716
+ # 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
+
726
+ print(f"[WALLET] Wallet loaded: {self.address}")
727
+ print(f"[WALLET] Balance: {self.balance}")
728
+
729
+ # Initialize SM2 if we have keys
730
+ if self.public_key and self.private_key:
731
+ self._initialize_sm2()
732
+
733
+ # Start async balance loading
734
+ self.start_async_balance_loading()
735
+
736
+ return True
737
+
738
+ except Exception as e:
739
+ print(f"[WALLET ERROR] Failed to load wallet data: {e}")
740
+ return False
741
+
742
+ def _initialize_sm2(self):
743
+ """Initialize SM2 crypto if keys exist"""
744
+ try:
745
+ if self.public_key and self.private_key:
746
+ from ..core.crypto import KeyManager
747
+ self.key_manager = KeyManager()
748
+ print(f"[WALLET] SM2 initialized with existing keys")
749
+ return True
750
+ except Exception as e:
751
+ print(f"[WALLET ERROR] Failed to initialize SM2: {e}")
752
+ return False
753
+ def get_transaction_history(self) -> dict:
754
+ """Get complete transaction history (both pending and confirmed).
755
+ Uses cached transactions if available, otherwise scans blockchain/mempool.
756
+ Includes ALL reward transactions (mining rewards and explicit reward txs).
757
+ """
758
+ try:
759
+ # Try to use cache first (from monitoring or sync)
760
+ confirmed_txs = self._confirmed_tx_cache.get(self.address, [])
761
+ pending_txs = self._pending_tx_cache.get(self.address, [])
762
+
763
+ # If cache is empty, perform fresh scan
764
+ if not confirmed_txs:
765
+ from lunalib.core.blockchain import BlockchainManager
766
+ from lunalib.core.mempool import MempoolManager
767
+
768
+ blockchain = BlockchainManager()
769
+ mempool = MempoolManager()
770
+
771
+ # Get confirmed transactions from blockchain (includes mining rewards)
772
+ confirmed_txs = blockchain.scan_transactions_for_address(self.address)
773
+ self._confirmed_tx_cache[self.address] = confirmed_txs
774
+
775
+ # Get pending transactions from mempool
776
+ pending_txs = mempool.get_pending_transactions(self.address, fetch_remote=True)
777
+ self._pending_tx_cache[self.address] = pending_txs
778
+
779
+ # 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
+
784
+ 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
790
+ }
791
+ except Exception as e:
792
+ 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]]:
796
+ """Get ALL transactions for a wallet including mining rewards, transfers, and pending.
797
+
798
+ This is the comprehensive transaction getter that ensures ALL reward transactions
799
+ (mining rewards, explicit reward transactions) are included.
800
+
801
+ Args:
802
+ address: wallet address to query (defaults to current wallet address)
803
+ include_pending: whether to include pending mempool transactions (default: True)
804
+
805
+ Returns:
806
+ Dict with:
807
+ - 'confirmed': list of all confirmed transactions (transfers + mining rewards)
808
+ - 'pending': list of pending mempool transactions
809
+ - 'reward_transactions': list of only reward/mining transactions
810
+ - 'transfer_transactions': list of only transfer transactions
811
+ - 'total_rewards': count of reward transactions
812
+ - 'total_transfers': count of transfer transactions
813
+ """
814
+ if address is None:
815
+ address = self.address
816
+
817
+ if not address:
818
+ 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
829
+ }
830
+
831
+ try:
832
+ # Normalize address for comparison
833
+ norm_addr = self._normalize_address(address)
834
+
835
+ # Use cache if available
836
+ confirmed_txs = self._confirmed_tx_cache.get(norm_addr, [])
837
+ pending_txs = self._pending_tx_cache.get(norm_addr, []) if include_pending else []
838
+
839
+ # If cache is empty, perform fresh scan
840
+ if not confirmed_txs:
841
+ from lunalib.core.blockchain import BlockchainManager
842
+ blockchain = BlockchainManager()
843
+ confirmed_txs = blockchain.scan_transactions_for_address(address)
844
+ self._confirmed_tx_cache[norm_addr] = confirmed_txs
845
+
846
+ if include_pending and not pending_txs:
847
+ from lunalib.core.mempool import MempoolManager
848
+ mempool = MempoolManager()
849
+ pending_txs = mempool.get_pending_transactions(address, fetch_remote=True)
850
+ self._pending_tx_cache[norm_addr] = pending_txs
851
+
852
+ # Separate rewards and transfers based on type and source
853
+ # 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
+
858
+ # 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
+
863
+ # 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
+
867
+ 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)
878
+ }
879
+
880
+ print(f"DEBUG: get_wallet_transactions({address}):")
881
+ print(f" - Mining Rewards: {len(reward_txs)}")
882
+ print(f" - Incoming Transfers: {len(incoming_transfers)}")
883
+ print(f" - Outgoing Transfers: {len(outgoing_transfers)}")
884
+ print(f" - Total Transfers: {len(transfer_txs)}")
885
+ print(f" - Pending: {len(pending_txs)}")
886
+ print(f" - Total Confirmed: {len(confirmed_txs)}")
887
+
888
+ return result
889
+
890
+ except Exception as e:
891
+ print(f"DEBUG: Error getting wallet transactions: {e}")
892
+ import traceback
893
+ traceback.print_exc()
894
+ 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
905
+ }
906
+
907
+ # ============================================================================
908
+ # WALLET INFO AND UTILITIES
909
+ # ============================================================================
910
+
911
+ @property
912
+ def is_unlocked(self):
913
+ """Check if current wallet is unlocked"""
914
+ if not self.current_wallet_address:
915
+ return False
916
+ wallet_data = self.wallets.get(self.current_wallet_address, {})
917
+ return not wallet_data.get('is_locked', True)
918
+
919
+ def export_private_key(self, address, password):
920
+ """Export private key with password decryption"""
921
+ if address not in self.wallets:
922
+ return None
923
+
924
+ wallet_data = self.wallets[address]
925
+
926
+ try:
927
+ if wallet_data.get('encrypted_private_key'):
928
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
929
+ fernet = Fernet(key)
930
+ decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
931
+ return decrypted_key.decode()
932
+ except:
933
+ pass
934
+ return None
935
+
936
+ def import_wallet(self, wallet_data, password=None):
937
+ """Import wallet from data"""
938
+ if isinstance(wallet_data, dict):
939
+ address = wallet_data.get('address')
940
+ if not address:
941
+ return False
942
+
943
+ # 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
+
947
+ # Add to wallets collection
948
+ self.wallets[address] = wallet_data.copy()
949
+
950
+ # Set as current wallet
951
+ self._set_current_wallet(wallet_data)
952
+
953
+ if password and wallet_data.get('encrypted_private_key'):
954
+ return self.unlock_wallet(address, password)
955
+
956
+ return True
957
+ return False
958
+
959
+ def update_balance(self, new_balance):
960
+ """Update current wallet balance"""
961
+ self.balance = float(new_balance)
962
+ self.available_balance = float(new_balance)
963
+
964
+ 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
+
968
+ return True
969
+
970
+ def get_balance(self):
971
+ """Get current wallet total balance"""
972
+ return self.balance
973
+
974
+ def get_available_balance(self):
975
+ """Get current wallet available balance"""
976
+ return self.available_balance
977
+
978
+ def get_wallet_by_address(self, address):
979
+ """Get wallet by address from wallets collection"""
980
+ return self.wallets.get(address)
981
+
982
+ def list_wallets(self):
983
+ """List all wallets in collection"""
984
+ return list(self.wallets.keys())
985
+
986
+ def get_current_wallet_info(self):
987
+ """Get current wallet information"""
988
+ if not self.current_wallet_address:
989
+ return None
990
+
991
+ return self.wallets.get(self.current_wallet_address)
992
+
993
+ def get_wallet_info(self):
994
+ """Get complete wallet information for current wallet"""
995
+ if not self.address:
996
+ return None
997
+
998
+ self.refresh_balance()
999
+
1000
+ 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'
1011
+ }
1012
+
1013
+ # ============================================================================
1014
+ # UNIFIED WALLET STATE MANAGER INTEGRATION
1015
+ # ============================================================================
1016
+
1017
+ def sync_with_state_manager(self, blockchain=None, mempool=None) -> Dict:
1018
+ """
1019
+ Sync all registered wallets with the unified WalletStateManager.
1020
+ Scans blockchain once and merges mempool data for all wallets.
1021
+
1022
+ Parameters:
1023
+ blockchain: BlockchainManager instance (required)
1024
+ mempool: MempoolManager instance (required)
1025
+
1026
+ Returns: Dictionary of wallet summaries with balances and transactions
1027
+ """
1028
+ try:
1029
+ if not blockchain or not mempool:
1030
+ print("❌ blockchain and mempool instances required")
1031
+ return {}
1032
+
1033
+ from .wallet_manager import get_wallet_manager
1034
+
1035
+ state_manager = get_wallet_manager()
1036
+
1037
+ # Register all wallets with state manager if not already done
1038
+ addresses = list(self.wallets.keys())
1039
+ state_manager.register_wallets(addresses)
1040
+
1041
+ print(f"🔄 Syncing {len(addresses)} wallets...")
1042
+
1043
+ # Get data from blockchain and mempool (single scan)
1044
+ blockchain_txs = blockchain.scan_transactions_for_addresses(addresses)
1045
+ mempool_txs = mempool.get_pending_transactions_for_addresses(addresses)
1046
+
1047
+ # Sync state manager with the data
1048
+ state_manager.sync_wallets_from_sources(blockchain_txs, mempool_txs)
1049
+
1050
+ # Update LunaWallet balances from state manager
1051
+ balances = state_manager.get_all_balances()
1052
+ for address, balance_data in balances.items():
1053
+ 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
+
1057
+ # Update current wallet
1058
+ if self.current_wallet_address and self.current_wallet_address in balances:
1059
+ balance_data = balances[self.current_wallet_address]
1060
+ self.balance = balance_data['confirmed_balance']
1061
+ self.available_balance = balance_data['available_balance']
1062
+
1063
+ # Return summaries
1064
+ summaries = state_manager.get_all_summaries()
1065
+ print(f"✅ Sync complete - {len(summaries)} wallets updated")
1066
+
1067
+ return summaries
1068
+
1069
+ except Exception as e:
1070
+ print(f"❌ Sync error: {e}")
1071
+ import traceback
1072
+ traceback.print_exc()
1073
+ return {}
1074
+
1075
+ def get_wallet_details(self, address: str = None) -> Optional[Dict]:
1076
+ """
1077
+ Get detailed information for a wallet including balance and transaction summary.
1078
+ If address is None, uses current wallet.
1079
+ """
1080
+ if address is None:
1081
+ address = self.current_wallet_address
1082
+
1083
+ if not address:
1084
+ return None
1085
+
1086
+ try:
1087
+ from .wallet_manager import get_wallet_manager
1088
+ state_manager = get_wallet_manager()
1089
+
1090
+ summary = state_manager.get_wallet_summary(address)
1091
+ if summary:
1092
+ return summary
1093
+
1094
+ # Fallback to basic wallet info
1095
+ if address in self.wallets:
1096
+ wallet_data = self.wallets[address]
1097
+ 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),
1103
+ }
1104
+
1105
+ return None
1106
+
1107
+ except Exception as e:
1108
+ print(f"⚠️ Error getting wallet details: {e}")
1109
+ return None
1110
+
1111
+ def get_wallet_transactions(self, address: str = None, tx_type: str = 'all') -> List[Dict]:
1112
+ """
1113
+ Get transactions for a wallet from the state manager.
1114
+
1115
+ tx_type: 'all', 'confirmed', 'pending', 'transfers', 'rewards', 'genesis'
1116
+ """
1117
+ if address is None:
1118
+ address = self.current_wallet_address
1119
+
1120
+ if not address:
1121
+ return []
1122
+
1123
+ try:
1124
+ from .wallet_manager import get_wallet_manager
1125
+ state_manager = get_wallet_manager()
1126
+
1127
+ return state_manager.get_transactions(address, tx_type)
1128
+
1129
+ except Exception as e:
1130
+ print(f"⚠️ Error getting transactions: {e}")
1131
+ return []
1132
+
1133
+ def register_wallet_ui_callback(self, callback: Callable) -> None:
1134
+ """
1135
+ Register a callback to receive real-time wallet balance updates.
1136
+ Callback will be called with: callback(balance_data_dict)
1137
+ """
1138
+ try:
1139
+ from .wallet_manager import get_wallet_manager
1140
+ state_manager = get_wallet_manager()
1141
+ state_manager.on_balance_update(callback)
1142
+ except Exception as e:
1143
+ print(f"⚠️ Error registering callback: {e}")
1144
+
1145
+ def start_continuous_sync(self, blockchain=None, mempool=None, poll_interval: int = 30) -> None:
1146
+ """
1147
+ Start continuous synchronization in background thread.
1148
+ Syncs every poll_interval seconds.
1149
+ """
1150
+ if not blockchain or not mempool:
1151
+ print("❌ blockchain and mempool instances required")
1152
+ return
1153
+
1154
+ try:
1155
+ from .wallet_manager import get_wallet_manager
1156
+ from .wallet_sync_helper import WalletSyncHelper
1157
+
1158
+ state_manager = get_wallet_manager()
1159
+
1160
+ # Register wallets
1161
+ addresses = list(self.wallets.keys())
1162
+ state_manager.register_wallets(addresses)
1163
+
1164
+ def sync_callback(balance_data):
1165
+ """Update LunaWallet when state manager updates"""
1166
+ for address, balance_info in balance_data.items():
1167
+ 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
+
1171
+ if address == self.current_wallet_address:
1172
+ self.balance = balance_info['confirmed_balance']
1173
+ self.available_balance = balance_info['available_balance']
1174
+
1175
+ # Create sync helper
1176
+ sync_helper = WalletSyncHelper(self, blockchain, mempool)
1177
+
1178
+ # Start continuous sync with callback
1179
+ sync_helper.start_continuous_sync(
1180
+ poll_interval,
1181
+ on_balance_update=sync_callback
1182
+ )
1183
+
1184
+ print(f"🔄 Started continuous sync (interval: {poll_interval}s)")
1185
+
1186
+ except Exception as e:
1187
+ print(f"❌ Error starting continuous sync: {e}")
1188
+
1189
+ def save_to_file(self, filename=None):
1190
+ """Save wallet to file"""
1191
+ if not self.data_dir:
1192
+ return False
1193
+
1194
+ if filename is None:
1195
+ filename = f"wallet_{self.address}.json"
1196
+
1197
+ filepath = os.path.join(self.data_dir, filename)
1198
+
1199
+ try:
1200
+ os.makedirs(self.data_dir, exist_ok=True)
1201
+
1202
+ encrypted_key_data = None
1203
+ if self.encrypted_private_key:
1204
+ if isinstance(self.encrypted_private_key, bytes):
1205
+ encrypted_key_data = base64.b64encode(self.encrypted_private_key).decode('utf-8')
1206
+ else:
1207
+ encrypted_key_data = base64.b64encode(self.encrypted_private_key.encode()).decode('utf-8')
1208
+
1209
+ serializable_wallets = {}
1210
+ for addr, wallet_info in self.wallets.items():
1211
+ 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')
1216
+ serializable_wallets[addr] = serializable_wallet
1217
+
1218
+ 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'
1230
+ }
1231
+
1232
+ with open(filepath, 'w') as f:
1233
+ json.dump(wallet_data, f, indent=2)
1234
+
1235
+ print(f"DEBUG: Wallet saved to {filepath}")
1236
+ return True
1237
+ except Exception as e:
1238
+ print(f"Error saving wallet: {e}")
1239
+ import traceback
1240
+ traceback.print_exc()
1241
+ return False
1242
+
1243
+ def load_from_file(self, filename, password=None):
1244
+ """Load wallet from file"""
1245
+ if not self.data_dir:
1246
+ return False
1247
+
1248
+ filepath = os.path.join(self.data_dir, filename)
1249
+
1250
+ try:
1251
+ with open(filepath, 'r') as f:
1252
+ wallet_data = json.load(f)
1253
+
1254
+ # 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
+
1259
+ # Load wallets collection
1260
+ self.wallets = wallet_data.get('wallets', {})
1261
+
1262
+ # Load current wallet address
1263
+ self.current_wallet_address = wallet_data.get('current_wallet_address')
1264
+
1265
+ # If we have a current wallet, load its data
1266
+ if self.current_wallet_address and self.current_wallet_address in self.wallets:
1267
+ current_wallet_data = self.wallets[self.current_wallet_address]
1268
+ self._set_current_wallet(current_wallet_data)
1269
+
1270
+ encrypted_key = wallet_data.get('encrypted_private_key')
1271
+ if encrypted_key:
1272
+ self.encrypted_private_key = base64.b64decode(encrypted_key.encode())
1273
+ if self.current_wallet_address in self.wallets:
1274
+ self.wallets[self.current_wallet_address]['encrypted_private_key'] = self.encrypted_private_key
1275
+
1276
+ self.refresh_balance()
1277
+
1278
+ if password and self.encrypted_private_key and self.current_wallet_address:
1279
+ return self.unlock_wallet(self.current_wallet_address, password)
1280
+
1281
+ print(f"DEBUG: Wallet loaded from {filepath}")
1282
+ print(f"DEBUG: Total wallets: {len(self.wallets)}")
1283
+ print(f"DEBUG: Current wallet: {self.current_wallet_address}")
1284
+
1285
+ return True
1286
+ except Exception as e:
1287
+ print(f"Error loading wallet: {e}")
1288
+ return False
1289
+
1290
+ def debug_crypto_info(self):
1291
+ """Debug cryptographic information"""
1292
+ print("\n" + "="*60)
1293
+ print("SM2 CRYPTOGRAPHY DEBUG INFO")
1294
+ print("="*60)
1295
+
1296
+ if self.current_wallet_address:
1297
+ print(f"Current Address: {self.address}")
1298
+
1299
+ # Check address format
1300
+ if self.address.startswith('LUN_'):
1301
+ print("✅ Address has correct LUN_ prefix")
1302
+ else:
1303
+ print("❌ Address missing LUN_ prefix")
1304
+
1305
+ # Check private key
1306
+ if self.private_key:
1307
+ print(f"Private Key: {self.private_key[:16]}...")
1308
+ if len(self.private_key) == 64:
1309
+ print(f"✅ Private key is 256-bit (64 hex chars)")
1310
+ else:
1311
+ print(f"❌ Private key has wrong length: {len(self.private_key)} chars")
1312
+ else:
1313
+ print("❌ No private key available")
1314
+
1315
+ # Check public key
1316
+ if self.public_key:
1317
+ print(f"Public Key: {self.public_key[:16]}...")
1318
+ if len(self.public_key) >= 128:
1319
+ print(f"✅ Public key is valid length")
1320
+ else:
1321
+ print(f"❌ Public key too short: {len(self.public_key)} chars")
1322
+ else:
1323
+ print("❌ No public key available")
1324
+
1325
+ # Check cryptographic standard
1326
+ wallet_data = self.wallets.get(self.address, {})
1327
+ if wallet_data.get('crypto_standard') == 'SM2_GB/T_32918':
1328
+ print("✅ Using SM2 GB/T 32918 East Asian cryptography standard")
1329
+ else:
1330
+ print("❌ Not using SM2 cryptography standard")
1331
+
1332
+ # Test signing
1333
+ if self.private_key and not self.is_locked:
1334
+ test_sig = self._sign_transaction_data({"test": "data"})
1335
+ if test_sig:
1336
+ print(f"✅ Can sign transactions, signature: {test_sig[:16]}...")
1337
+ else:
1338
+ print("❌ Cannot sign transactions")
1339
+ else:
1340
+ print("❌ No wallet selected")
1341
+
1342
+ print("="*60)