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