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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
lunalib/core/wallet.py CHANGED
@@ -1,21 +1,43 @@
1
- # wallet.py
2
1
  import time
3
2
  import hashlib
4
3
  import json
5
- from cryptography.fernet import Fernet
6
- import base64
7
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
+
8
14
 
9
15
  class LunaWallet:
10
- """Luna wallet implementation with proper key management and balance tracking"""
11
-
12
16
  def __init__(self, data_dir=None):
13
17
  self.data_dir = data_dir
14
- self.wallets = {} # Main wallet storage: {address: wallet_data}
15
- self.current_wallet_address = None # Track which wallet is active
18
+ self.wallets = {}
19
+ self.current_wallet_address = None
20
+ self.key_manager = KeyManager()
16
21
 
17
- # Initialize with an empty current wallet state
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
18
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
+
19
41
 
20
42
  def _reset_current_wallet(self):
21
43
  """Reset current wallet to empty state"""
@@ -23,110 +45,452 @@ class LunaWallet:
23
45
  self.balance = 0.0 # Total balance (confirmed transactions)
24
46
  self.available_balance = 0.0 # Available balance (total - pending outgoing)
25
47
  self.created = time.time()
26
- self.private_key = None
27
- self.public_key = None
48
+ self.private_key = None # Will store REAL SM2 private key
49
+ self.public_key = None # Will store REAL SM2 public key
28
50
  self.encrypted_private_key = None
29
51
  self.label = "New Wallet"
30
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()
31
97
 
32
- def _generate_address(self):
33
- """Generate unique wallet address"""
34
- import secrets
35
- import time
36
- # Use cryptographically secure random data for uniqueness
37
- random_data = secrets.token_hex(32)
38
- timestamp_ns = time.time_ns() # More precise timestamp
39
- base_data = f"LUN_{timestamp_ns}_{random_data}"
40
- return hashlib.sha256(base_data.encode()).hexdigest()[:32]
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
41
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
+
42
247
  def calculate_available_balance(self) -> float:
43
248
  """Calculate available balance (total balance minus pending outgoing transactions)"""
44
249
  try:
45
- from lunalib.core.mempool import MempoolManager
46
- from lunalib.core.blockchain import BlockchainManager
47
-
48
250
  # Get total balance from blockchain
49
251
  total_balance = self._get_total_balance_from_blockchain()
50
252
 
51
- # Get pending outgoing transactions from mempool
52
- mempool = MempoolManager()
53
- pending_txs = mempool.get_pending_transactions(self.address)
253
+ # Get pending outgoing amount
254
+ pending_outgoing = self._get_pending_balance()
54
255
 
55
- # Sum pending outgoing amounts
56
- pending_outgoing = 0.0
57
- for tx in pending_txs:
58
- if tx.get('from') == self.address:
59
- pending_outgoing += float(tx.get('amount', 0)) + float(tx.get('fee', 0))
256
+ # Also check for any incoming pending transactions
257
+ incoming_pending = self._get_pending_incoming_balance()
60
258
 
61
259
  available_balance = max(0.0, total_balance - pending_outgoing)
62
260
 
63
261
  # Update both current wallet and wallets collection
64
262
  self.available_balance = available_balance
263
+ self.balance = total_balance # Also update total balance
264
+
65
265
  if self.current_wallet_address in self.wallets:
66
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)")
67
273
 
68
- print(f"DEBUG: Available balance calculated - Total: {total_balance}, Pending Out: {pending_outgoing}, Available: {available_balance}")
69
274
  return available_balance
70
275
 
71
276
  except Exception as e:
72
277
  print(f"DEBUG: Error calculating available balance: {e}")
73
278
  return self.balance # Fallback to total balance
74
279
 
75
- def _get_total_balance_from_blockchain(self) -> float:
76
- """Get total balance by scanning blockchain for confirmed transactions"""
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
+
77
353
  try:
78
354
  from lunalib.core.blockchain import BlockchainManager
79
-
355
+ from lunalib.core.mempool import MempoolManager
356
+
80
357
  blockchain = BlockchainManager()
81
- transactions = blockchain.scan_transactions_for_address(self.address)
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
82
447
 
83
- total_balance = 0.0
84
- for tx in transactions:
85
- tx_type = tx.get('type', '')
86
-
87
- # Handle incoming transactions
88
- if tx.get('to') == self.address:
89
- if tx_type in ['transfer', 'reward', 'fee_distribution', 'gtx_genesis']:
90
- total_balance += float(tx.get('amount', 0))
91
-
92
- # Handle outgoing transactions
93
- elif tx.get('from') == self.address:
94
- if tx_type in ['transfer', 'stake', 'delegate']:
95
- total_balance -= float(tx.get('amount', 0))
96
- total_balance -= float(tx.get('fee', 0))
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
97
454
 
98
- return max(0.0, total_balance)
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
99
464
 
100
465
  except Exception as e:
101
- print(f"DEBUG: Error getting blockchain balance: {e}")
102
- return self.balance
103
-
104
- def refresh_balance(self) -> bool:
105
- """Refresh both total and available balance from blockchain and mempool"""
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"""
106
471
  try:
107
- total_balance = self._get_total_balance_from_blockchain()
108
- available_balance = self.calculate_available_balance()
472
+ from lunalib.core.mempool import MempoolManager
109
473
 
110
- # Update wallet state
111
- self.balance = total_balance
112
- self.available_balance = available_balance
474
+ mempool = MempoolManager()
113
475
 
114
- # Update in wallets collection
115
- if self.current_wallet_address in self.wallets:
116
- self.wallets[self.current_wallet_address]['balance'] = total_balance
117
- self.wallets[self.current_wallet_address]['available_balance'] = available_balance
476
+ # Get pending transactions for this address
477
+ pending_txs = mempool.get_pending_transactions(self.address, fetch_remote=True)
118
478
 
119
- print(f"DEBUG: Balance refreshed - Total: {total_balance}, Available: {available_balance}")
120
- return True
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
121
489
 
122
490
  except Exception as e:
123
- print(f"DEBUG: Error refreshing balance: {e}")
124
- return False
125
-
126
- def get_available_balance(self) -> float:
127
- """Get current wallet available balance"""
128
- return self.available_balance
129
-
491
+ print(f"DEBUG: Error calculating pending incoming balance: {e}")
492
+ return 0.0
493
+
130
494
  def _get_total_balance_from_blockchain(self) -> float:
131
495
  """Get total balance by scanning blockchain for confirmed transactions"""
132
496
  try:
@@ -134,28 +498,60 @@ class LunaWallet:
134
498
 
135
499
  blockchain = BlockchainManager()
136
500
  transactions = blockchain.scan_transactions_for_address(self.address)
137
-
138
- total_balance = 0.0
139
- for tx in transactions:
140
- tx_type = tx.get('type', '')
141
-
142
- # Handle incoming transactions
143
- if tx.get('to') == self.address:
144
- if tx_type in ['transfer', 'reward', 'fee_distribution', 'gtx_genesis']:
145
- total_balance += float(tx.get('amount', 0))
146
-
147
- # Handle outgoing transactions
148
- elif tx.get('from') == self.address:
149
- if tx_type in ['transfer', 'stake', 'delegate']:
150
- total_balance -= float(tx.get('amount', 0))
151
- total_balance -= float(tx.get('fee', 0))
152
-
153
- return max(0.0, total_balance)
501
+ total_balance = self._compute_confirmed_balance(transactions)
502
+ return total_balance
154
503
 
155
504
  except Exception as e:
156
505
  print(f"DEBUG: Error getting blockchain balance: {e}")
157
506
  return self.balance
158
-
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
+
159
555
  def refresh_balance(self) -> bool:
160
556
  """Refresh both total and available balance from blockchain and mempool"""
161
557
  try:
@@ -177,32 +573,32 @@ class LunaWallet:
177
573
  except Exception as e:
178
574
  print(f"DEBUG: Error refreshing balance: {e}")
179
575
  return False
180
-
576
+
181
577
  def send_transaction(self, to_address: str, amount: float, memo: str = "", password: str = None) -> bool:
182
- """Send transaction using lunalib transactions with proper mempool submission"""
578
+ """Send transaction using REAL SM2 signatures"""
183
579
  try:
184
- print(f"DEBUG: send_transaction called - to: {to_address}, amount: {amount}, memo: {memo}")
580
+ print(f"[SM2] Sending {amount} to {to_address}")
185
581
 
186
- # Refresh balances first to get latest state
187
- self.refresh_balance()
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
188
586
 
189
- # Check available balance before proceeding
190
- if amount > self.available_balance:
191
- print(f"DEBUG: Insufficient available balance: {self.available_balance} < {amount}")
587
+ # 2. Check key integrity
588
+ if not self._verify_wallet_integrity():
589
+ print("[SM2] Invalid cryptographic keys")
192
590
  return False
193
591
 
194
- # Check if wallet is unlocked
195
- if self.is_locked or not self.private_key:
196
- print("DEBUG: Wallet is locked or no private key available")
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}")
197
596
  return False
198
597
 
199
- # Import transaction manager
598
+ # 4. Create and sign transaction
200
599
  from lunalib.transactions.transactions import TransactionManager
201
-
202
- # Create transaction manager
203
600
  tx_manager = TransactionManager()
204
601
 
205
- # Create and sign transaction
206
602
  transaction = tx_manager.create_transaction(
207
603
  from_address=self.address,
208
604
  to_address=to_address,
@@ -212,244 +608,305 @@ class LunaWallet:
212
608
  transaction_type="transfer"
213
609
  )
214
610
 
215
- print(f"DEBUG: Transaction created: {transaction.get('hash')}")
611
+ print(f"[SM2] Transaction created: {transaction.get('hash', 'no_hash')[:16]}")
216
612
 
217
- # Validate transaction
613
+ # 5. Validate
218
614
  is_valid, message = tx_manager.validate_transaction(transaction)
219
615
  if not is_valid:
220
- print(f"DEBUG: Transaction validation failed: {message}")
616
+ print(f"[SM2] Validation failed: {message}")
221
617
  return False
222
618
 
223
- # Send to mempool for broadcasting
619
+ # 6. Broadcast
224
620
  success, message = tx_manager.send_transaction(transaction)
225
621
  if success:
226
- print(f"DEBUG: Transaction sent to mempool: {message}")
622
+ print(f"[SM2] Transaction broadcast: {message}")
227
623
 
228
- # Update available balance immediately (deduct pending transaction)
229
- fee = transaction.get('fee', 0)
230
- self.available_balance -= (amount + fee)
624
+ # Update balance immediately
625
+ total_cost = amount + transaction.get('fee', 0)
626
+ self.available_balance -= total_cost
231
627
  if self.current_wallet_address in self.wallets:
232
628
  self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
233
629
 
234
- print(f"DEBUG: Available balance updated - new available: {self.available_balance}")
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
+
235
636
  return True
236
637
  else:
237
- print(f"DEBUG: Failed to send transaction to mempool: {message}")
638
+ print(f"[SM2] Broadcast failed: {message}")
238
639
  return False
239
640
 
240
641
  except Exception as e:
241
- print(f"DEBUG: Error in send_transaction: {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}")
242
685
  import traceback
243
686
  traceback.print_exc()
244
687
  return False
245
-
246
- def send_transaction_from(self, from_address: str, to_address: str, amount: float, memo: str = "", password: str = None) -> bool:
247
- """Send transaction from specific address"""
688
+
689
+ def load_wallet_data(self, wallet_file=None):
690
+ """Load wallet data from file"""
248
691
  try:
249
- print(f"DEBUG: send_transaction_from called - from: {from_address}, to: {to_address}, amount: {amount}")
250
-
251
- # Switch to the specified wallet if different from current
252
- if from_address != self.current_wallet_address:
253
- if from_address in self.wallets:
254
- # Switch to the wallet first
255
- wallet_data = self.wallets[from_address]
256
- self._set_current_wallet(wallet_data)
257
-
258
- # If password provided, unlock the wallet
259
- if password:
260
- unlock_success = self.unlock_wallet(from_address, password)
261
- if not unlock_success:
262
- print("DEBUG: Failed to unlock wallet for sending")
263
- return False
264
- else:
265
- print(f"DEBUG: Wallet not found: {from_address}")
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")
266
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()
267
732
 
268
- # Now use the regular send_transaction method
269
- return self.send_transaction(to_address, amount, memo, password)
733
+ # Start async balance loading
734
+ self.start_async_balance_loading()
735
+
736
+ return True
270
737
 
271
738
  except Exception as e:
272
- print(f"DEBUG: Error in send_transaction_from: {e}")
273
- import traceback
274
- traceback.print_exc()
739
+ print(f"[WALLET ERROR] Failed to load wallet data: {e}")
275
740
  return False
276
-
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
277
753
  def get_transaction_history(self) -> dict:
278
- """Get complete transaction history (both pending and confirmed)"""
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
+ """
279
758
  try:
280
- from lunalib.core.blockchain import BlockchainManager
281
- from lunalib.core.mempool import MempoolManager
282
-
283
- blockchain = BlockchainManager()
284
- mempool = MempoolManager()
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, [])
285
762
 
286
- # Get confirmed transactions from blockchain
287
- confirmed_txs = blockchain.scan_transactions_for_address(self.address)
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
288
778
 
289
- # Get pending transactions from mempool
290
- pending_txs = mempool.get_pending_transactions(self.address)
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')
291
783
 
292
784
  return {
293
785
  'confirmed': confirmed_txs,
294
786
  'pending': pending_txs,
295
787
  'total_confirmed': len(confirmed_txs),
296
- 'total_pending': len(pending_txs)
788
+ 'total_pending': len(pending_txs),
789
+ 'reward_count': reward_count
297
790
  }
298
791
  except Exception as e:
299
792
  print(f"DEBUG: Error getting transaction history: {e}")
300
- return {'confirmed': [], 'pending': [], 'total_confirmed': 0, 'total_pending': 0}
301
-
302
- def _generate_private_key(self):
303
- """Generate private key"""
304
- return f"priv_{hashlib.sha256(str(time.time()).encode()).hexdigest()}"
793
+ return {'confirmed': [], 'pending': [], 'total_confirmed': 0, 'total_pending': 0, 'reward_count': 0}
305
794
 
306
- def _derive_public_key(self, private_key=None):
307
- """Derive public key from private key"""
308
- priv_key = private_key or self.private_key
309
- if not priv_key:
310
- return None
311
- return f"pub_{priv_key[-16:]}"
312
-
313
- def get_wallet_info(self):
314
- """Get complete wallet information for current wallet"""
315
- if not self.address:
316
- return None
317
-
318
- # Refresh balances to ensure they're current
319
- self.refresh_balance()
320
-
321
- return {
322
- 'address': self.address,
323
- 'balance': self.balance,
324
- 'available_balance': self.available_balance,
325
- 'created': self.created,
326
- 'private_key': self.private_key,
327
- 'public_key': self.public_key,
328
- 'encrypted_private_key': self.encrypted_private_key,
329
- 'label': self.label,
330
- 'is_locked': self.is_locked
331
- }
332
-
333
- def create_new_wallet(self, name, password):
334
- """Create a new wallet and add to collection without switching"""
335
- # Generate new wallet data
336
- address = self._generate_address()
337
- private_key = self._generate_private_key()
338
- public_key = f"pub_{private_key[-16:]}"
339
-
340
- # Encrypt private key
341
- key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
342
- fernet = Fernet(key)
343
- encrypted_private_key = fernet.encrypt(private_key.encode())
344
-
345
- # Create new wallet data
346
- new_wallet_data = {
347
- 'address': address,
348
- 'balance': 0.0,
349
- 'available_balance': 0.0,
350
- 'created': time.time(),
351
- 'private_key': private_key,
352
- 'public_key': public_key,
353
- 'encrypted_private_key': encrypted_private_key,
354
- 'label': name,
355
- 'is_locked': True
356
- }
357
-
358
- # CRITICAL: Add to wallets collection
359
- self.wallets[address] = new_wallet_data
360
-
361
- print(f"DEBUG: Created new wallet {address}, total wallets: {len(self.wallets)}")
362
-
363
- return new_wallet_data
364
-
365
- def create_wallet(self, name, password):
366
- """Create a new wallet and set it as current"""
367
- # Generate new wallet data
368
- address = self._generate_address()
369
- private_key = self._generate_private_key()
370
- public_key = f"pub_{private_key[-16:]}"
371
-
372
- # Encrypt private key
373
- key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
374
- fernet = Fernet(key)
375
- encrypted_private_key = fernet.encrypt(private_key.encode())
376
-
377
- # Create wallet data
378
- wallet_data = {
379
- 'address': address,
380
- 'balance': 0.0,
381
- 'available_balance': 0.0,
382
- 'created': time.time(),
383
- 'private_key': private_key,
384
- 'public_key': public_key,
385
- 'encrypted_private_key': encrypted_private_key,
386
- 'label': name,
387
- 'is_locked': True
388
- }
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.
389
797
 
390
- # CRITICAL: Add to wallets collection
391
- self.wallets[address] = wallet_data
798
+ This is the comprehensive transaction getter that ensures ALL reward transactions
799
+ (mining rewards, explicit reward transactions) are included.
392
800
 
393
- # Set as current wallet
394
- self._set_current_wallet(wallet_data)
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
395
816
 
396
- print(f"DEBUG: Created and switched to wallet {address}, total wallets: {len(self.wallets)}")
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
+ }
397
830
 
398
- return wallet_data
399
-
400
- def _set_current_wallet(self, wallet_data):
401
- """Set the current wallet from wallet data"""
402
- self.current_wallet_address = wallet_data['address']
403
- self.address = wallet_data['address']
404
- self.balance = wallet_data['balance']
405
- self.available_balance = wallet_data['available_balance']
406
- self.created = wallet_data['created']
407
- self.private_key = wallet_data['private_key']
408
- self.public_key = wallet_data['public_key']
409
- self.encrypted_private_key = wallet_data['encrypted_private_key']
410
- self.label = wallet_data['label']
411
- self.is_locked = wallet_data.get('is_locked', True)
412
-
413
- def switch_wallet(self, address, password=None):
414
- """Switch to a different wallet in the collection"""
415
- if address in self.wallets:
416
- wallet_data = self.wallets[address]
417
- self._set_current_wallet(wallet_data)
831
+ try:
832
+ # Normalize address for comparison
833
+ norm_addr = self._normalize_address(address)
418
834
 
419
- # Refresh balances for the new wallet
420
- self.refresh_balance()
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 []
421
838
 
422
- # If password provided, unlock the wallet
423
- if password:
424
- return self.unlock_wallet(address, password)
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
425
845
 
426
- return True
427
- return False
428
-
429
- def unlock_wallet(self, address, password):
430
- """Unlock wallet with password"""
431
- if address not in self.wallets:
432
- return False
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
433
851
 
434
- wallet_data = self.wallets[address]
435
-
436
- try:
437
- if wallet_data.get('encrypted_private_key'):
438
- key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
439
- fernet = Fernet(key)
440
- decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
441
- wallet_data['private_key'] = decrypted_key.decode()
442
- wallet_data['is_locked'] = False
443
-
444
- # If this is the current wallet, update current state
445
- if self.current_wallet_address == address:
446
- self.private_key = wallet_data['private_key']
447
- self.is_locked = False
448
-
449
- return True
450
- except:
451
- pass
452
- return False
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
+ # ============================================================================
453
910
 
454
911
  @property
455
912
  def is_unlocked(self):
@@ -463,7 +920,7 @@ class LunaWallet:
463
920
  """Export private key with password decryption"""
464
921
  if address not in self.wallets:
465
922
  return None
466
-
923
+
467
924
  wallet_data = self.wallets[address]
468
925
 
469
926
  try:
@@ -482,16 +939,17 @@ class LunaWallet:
482
939
  address = wallet_data.get('address')
483
940
  if not address:
484
941
  return False
485
-
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
+
486
947
  # Add to wallets collection
487
948
  self.wallets[address] = wallet_data.copy()
488
949
 
489
950
  # Set as current wallet
490
951
  self._set_current_wallet(wallet_data)
491
952
 
492
- # Refresh balances for imported wallet
493
- self.refresh_balance()
494
-
495
953
  if password and wallet_data.get('encrypted_private_key'):
496
954
  return self.unlock_wallet(address, password)
497
955
 
@@ -499,11 +957,10 @@ class LunaWallet:
499
957
  return False
500
958
 
501
959
  def update_balance(self, new_balance):
502
- """Update current wallet balance (use refresh_balance instead for accurate tracking)"""
960
+ """Update current wallet balance"""
503
961
  self.balance = float(new_balance)
504
962
  self.available_balance = float(new_balance)
505
963
 
506
- # Also update in wallets collection
507
964
  if self.current_wallet_address and self.current_wallet_address in self.wallets:
508
965
  self.wallets[self.current_wallet_address]['balance'] = self.balance
509
966
  self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
@@ -531,10 +988,27 @@ class LunaWallet:
531
988
  if not self.current_wallet_address:
532
989
  return None
533
990
 
534
- # Refresh balances to ensure they're current
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
+
535
998
  self.refresh_balance()
536
999
 
537
- return self.wallets.get(self.current_wallet_address)
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
+ }
538
1012
 
539
1013
  def save_to_file(self, filename=None):
540
1014
  """Save wallet to file"""
@@ -547,23 +1021,18 @@ class LunaWallet:
547
1021
  filepath = os.path.join(self.data_dir, filename)
548
1022
 
549
1023
  try:
550
- # Ensure directory exists
551
1024
  os.makedirs(self.data_dir, exist_ok=True)
552
1025
 
553
- # Prepare encrypted private key for serialization
554
1026
  encrypted_key_data = None
555
1027
  if self.encrypted_private_key:
556
- # Ensure it's bytes before encoding
557
1028
  if isinstance(self.encrypted_private_key, bytes):
558
1029
  encrypted_key_data = base64.b64encode(self.encrypted_private_key).decode('utf-8')
559
1030
  else:
560
1031
  encrypted_key_data = base64.b64encode(self.encrypted_private_key.encode()).decode('utf-8')
561
1032
 
562
- # Prepare wallets for serialization (remove any non-serializable data)
563
1033
  serializable_wallets = {}
564
1034
  for addr, wallet_info in self.wallets.items():
565
1035
  serializable_wallet = wallet_info.copy()
566
- # Ensure encrypted_private_key is serializable
567
1036
  if serializable_wallet.get('encrypted_private_key') and isinstance(serializable_wallet['encrypted_private_key'], bytes):
568
1037
  serializable_wallet['encrypted_private_key'] = base64.b64encode(
569
1038
  serializable_wallet['encrypted_private_key']
@@ -580,18 +1049,21 @@ class LunaWallet:
580
1049
  'label': self.label,
581
1050
  'is_locked': self.is_locked,
582
1051
  'wallets': serializable_wallets,
583
- 'current_wallet_address': self.current_wallet_address
1052
+ 'current_wallet_address': self.current_wallet_address,
1053
+ 'crypto_standard': 'SM2_GB/T_32918'
584
1054
  }
585
1055
 
586
1056
  with open(filepath, 'w') as f:
587
1057
  json.dump(wallet_data, f, indent=2)
1058
+
1059
+ print(f"DEBUG: Wallet saved to {filepath}")
588
1060
  return True
589
1061
  except Exception as e:
590
1062
  print(f"Error saving wallet: {e}")
591
1063
  import traceback
592
1064
  traceback.print_exc()
593
1065
  return False
594
-
1066
+
595
1067
  def load_from_file(self, filename, password=None):
596
1068
  """Load wallet from file"""
597
1069
  if not self.data_dir:
@@ -603,6 +1075,11 @@ class LunaWallet:
603
1075
  with open(filepath, 'r') as f:
604
1076
  wallet_data = json.load(f)
605
1077
 
1078
+ # Check crypto standard
1079
+ crypto_standard = wallet_data.get('crypto_standard')
1080
+ if crypto_standard != 'SM2_GB/T_32918':
1081
+ print(f"DEBUG: WARNING: Loading wallet with different crypto standard: {crypto_standard}")
1082
+
606
1083
  # Load wallets collection
607
1084
  self.wallets = wallet_data.get('wallets', {})
608
1085
 
@@ -614,22 +1091,76 @@ class LunaWallet:
614
1091
  current_wallet_data = self.wallets[self.current_wallet_address]
615
1092
  self._set_current_wallet(current_wallet_data)
616
1093
 
617
- # Handle encrypted private key
618
1094
  encrypted_key = wallet_data.get('encrypted_private_key')
619
1095
  if encrypted_key:
620
1096
  self.encrypted_private_key = base64.b64decode(encrypted_key.encode())
621
- # Also update in wallets collection
622
1097
  if self.current_wallet_address in self.wallets:
623
1098
  self.wallets[self.current_wallet_address]['encrypted_private_key'] = self.encrypted_private_key
624
1099
 
625
- # Refresh balances after loading
626
1100
  self.refresh_balance()
627
1101
 
628
- # If password provided and we have encrypted key, unlock
629
1102
  if password and self.encrypted_private_key and self.current_wallet_address:
630
1103
  return self.unlock_wallet(self.current_wallet_address, password)
631
1104
 
1105
+ print(f"DEBUG: Wallet loaded from {filepath}")
1106
+ print(f"DEBUG: Total wallets: {len(self.wallets)}")
1107
+ print(f"DEBUG: Current wallet: {self.current_wallet_address}")
1108
+
632
1109
  return True
633
1110
  except Exception as e:
634
1111
  print(f"Error loading wallet: {e}")
635
- return False
1112
+ return False
1113
+
1114
+ def debug_crypto_info(self):
1115
+ """Debug cryptographic information"""
1116
+ print("\n" + "="*60)
1117
+ print("SM2 CRYPTOGRAPHY DEBUG INFO")
1118
+ print("="*60)
1119
+
1120
+ if self.current_wallet_address:
1121
+ print(f"Current Address: {self.address}")
1122
+
1123
+ # Check address format
1124
+ if self.address.startswith('LUN_'):
1125
+ print("✅ Address has correct LUN_ prefix")
1126
+ else:
1127
+ print("❌ Address missing LUN_ prefix")
1128
+
1129
+ # Check private key
1130
+ if self.private_key:
1131
+ print(f"Private Key: {self.private_key[:16]}...")
1132
+ if len(self.private_key) == 64:
1133
+ print(f"✅ Private key is 256-bit (64 hex chars)")
1134
+ else:
1135
+ print(f"❌ Private key has wrong length: {len(self.private_key)} chars")
1136
+ else:
1137
+ print("❌ No private key available")
1138
+
1139
+ # Check public key
1140
+ if self.public_key:
1141
+ print(f"Public Key: {self.public_key[:16]}...")
1142
+ if len(self.public_key) >= 128:
1143
+ print(f"✅ Public key is valid length")
1144
+ else:
1145
+ print(f"❌ Public key too short: {len(self.public_key)} chars")
1146
+ else:
1147
+ print("❌ No public key available")
1148
+
1149
+ # Check cryptographic standard
1150
+ wallet_data = self.wallets.get(self.address, {})
1151
+ if wallet_data.get('crypto_standard') == 'SM2_GB/T_32918':
1152
+ print("✅ Using SM2 GB/T 32918 East Asian cryptography standard")
1153
+ else:
1154
+ print("❌ Not using SM2 cryptography standard")
1155
+
1156
+ # Test signing
1157
+ if self.private_key and not self.is_locked:
1158
+ test_sig = self._sign_transaction_data({"test": "data"})
1159
+ if test_sig:
1160
+ print(f"✅ Can sign transactions, signature: {test_sig[:16]}...")
1161
+ else:
1162
+ print("❌ Cannot sign transactions")
1163
+ else:
1164
+ print("❌ No wallet selected")
1165
+
1166
+ print("="*60)