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