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