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