lunalib 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lunalib/__init__.py +19 -0
- lunalib/cli.py +18 -0
- lunalib/core/__init__.py +0 -0
- lunalib/core/blockchain.py +451 -0
- lunalib/core/crypto.py +32 -0
- lunalib/core/mempool.py +340 -0
- lunalib/core/wallet.py +635 -0
- lunalib/gtx/__init__.py +0 -0
- lunalib/gtx/bill_registry.py +122 -0
- lunalib/gtx/digital_bill.py +273 -0
- lunalib/gtx/genesis.py +349 -0
- lunalib/luna_lib.py +77 -0
- lunalib/mining/__init__.py +0 -0
- lunalib/mining/cuda_manager.py +137 -0
- lunalib/mining/difficulty.py +106 -0
- lunalib/mining/miner.py +107 -0
- lunalib/requirements.txt +44 -0
- lunalib/storage/__init__.py +0 -0
- lunalib/storage/cache.py +148 -0
- lunalib/storage/database.py +222 -0
- lunalib/storage/encryption.py +105 -0
- lunalib/transactions/__init__.py +0 -0
- lunalib/transactions/security.py +172 -0
- lunalib/transactions/transactions.py +583 -0
- lunalib/transactions/validator.py +71 -0
- lunalib-1.1.0.dist-info/METADATA +283 -0
- lunalib-1.1.0.dist-info/RECORD +30 -0
- lunalib-1.1.0.dist-info/WHEEL +5 -0
- lunalib-1.1.0.dist-info/entry_points.txt +2 -0
- lunalib-1.1.0.dist-info/top_level.txt +1 -0
lunalib/core/wallet.py
ADDED
|
@@ -0,0 +1,635 @@
|
|
|
1
|
+
# wallet.py
|
|
2
|
+
import time
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
from cryptography.fernet import Fernet
|
|
6
|
+
import base64
|
|
7
|
+
import os
|
|
8
|
+
|
|
9
|
+
class LunaWallet:
|
|
10
|
+
"""Luna wallet implementation with proper key management and balance tracking"""
|
|
11
|
+
|
|
12
|
+
def __init__(self, data_dir=None):
|
|
13
|
+
self.data_dir = data_dir
|
|
14
|
+
self.wallets = {} # Main wallet storage: {address: wallet_data}
|
|
15
|
+
self.current_wallet_address = None # Track which wallet is active
|
|
16
|
+
|
|
17
|
+
# Initialize with an empty current wallet state
|
|
18
|
+
self._reset_current_wallet()
|
|
19
|
+
|
|
20
|
+
def _reset_current_wallet(self):
|
|
21
|
+
"""Reset current wallet to empty state"""
|
|
22
|
+
self.address = None
|
|
23
|
+
self.balance = 0.0 # Total balance (confirmed transactions)
|
|
24
|
+
self.available_balance = 0.0 # Available balance (total - pending outgoing)
|
|
25
|
+
self.created = time.time()
|
|
26
|
+
self.private_key = None
|
|
27
|
+
self.public_key = None
|
|
28
|
+
self.encrypted_private_key = None
|
|
29
|
+
self.label = "New Wallet"
|
|
30
|
+
self.is_locked = True
|
|
31
|
+
|
|
32
|
+
def _generate_address(self):
|
|
33
|
+
"""Generate unique wallet address"""
|
|
34
|
+
import secrets
|
|
35
|
+
import time
|
|
36
|
+
# Use cryptographically secure random data for uniqueness
|
|
37
|
+
random_data = secrets.token_hex(32)
|
|
38
|
+
timestamp_ns = time.time_ns() # More precise timestamp
|
|
39
|
+
base_data = f"LUN_{timestamp_ns}_{random_data}"
|
|
40
|
+
return hashlib.sha256(base_data.encode()).hexdigest()[:32]
|
|
41
|
+
|
|
42
|
+
def calculate_available_balance(self) -> float:
|
|
43
|
+
"""Calculate available balance (total balance minus pending outgoing transactions)"""
|
|
44
|
+
try:
|
|
45
|
+
from lunalib.core.mempool import MempoolManager
|
|
46
|
+
from lunalib.core.blockchain import BlockchainManager
|
|
47
|
+
|
|
48
|
+
# Get total balance from blockchain
|
|
49
|
+
total_balance = self._get_total_balance_from_blockchain()
|
|
50
|
+
|
|
51
|
+
# Get pending outgoing transactions from mempool
|
|
52
|
+
mempool = MempoolManager()
|
|
53
|
+
pending_txs = mempool.get_pending_transactions(self.address)
|
|
54
|
+
|
|
55
|
+
# Sum pending outgoing amounts
|
|
56
|
+
pending_outgoing = 0.0
|
|
57
|
+
for tx in pending_txs:
|
|
58
|
+
if tx.get('from') == self.address:
|
|
59
|
+
pending_outgoing += float(tx.get('amount', 0)) + float(tx.get('fee', 0))
|
|
60
|
+
|
|
61
|
+
available_balance = max(0.0, total_balance - pending_outgoing)
|
|
62
|
+
|
|
63
|
+
# Update both current wallet and wallets collection
|
|
64
|
+
self.available_balance = available_balance
|
|
65
|
+
if self.current_wallet_address in self.wallets:
|
|
66
|
+
self.wallets[self.current_wallet_address]['available_balance'] = available_balance
|
|
67
|
+
|
|
68
|
+
print(f"DEBUG: Available balance calculated - Total: {total_balance}, Pending Out: {pending_outgoing}, Available: {available_balance}")
|
|
69
|
+
return available_balance
|
|
70
|
+
|
|
71
|
+
except Exception as e:
|
|
72
|
+
print(f"DEBUG: Error calculating available balance: {e}")
|
|
73
|
+
return self.balance # Fallback to total balance
|
|
74
|
+
|
|
75
|
+
def _get_total_balance_from_blockchain(self) -> float:
|
|
76
|
+
"""Get total balance by scanning blockchain for confirmed transactions"""
|
|
77
|
+
try:
|
|
78
|
+
from lunalib.core.blockchain import BlockchainManager
|
|
79
|
+
|
|
80
|
+
blockchain = BlockchainManager()
|
|
81
|
+
transactions = blockchain.scan_transactions_for_address(self.address)
|
|
82
|
+
|
|
83
|
+
total_balance = 0.0
|
|
84
|
+
for tx in transactions:
|
|
85
|
+
tx_type = tx.get('type', '')
|
|
86
|
+
|
|
87
|
+
# Handle incoming transactions
|
|
88
|
+
if tx.get('to') == self.address:
|
|
89
|
+
if tx_type in ['transfer', 'reward', 'fee_distribution', 'gtx_genesis']:
|
|
90
|
+
total_balance += float(tx.get('amount', 0))
|
|
91
|
+
|
|
92
|
+
# Handle outgoing transactions
|
|
93
|
+
elif tx.get('from') == self.address:
|
|
94
|
+
if tx_type in ['transfer', 'stake', 'delegate']:
|
|
95
|
+
total_balance -= float(tx.get('amount', 0))
|
|
96
|
+
total_balance -= float(tx.get('fee', 0))
|
|
97
|
+
|
|
98
|
+
return max(0.0, total_balance)
|
|
99
|
+
|
|
100
|
+
except Exception as e:
|
|
101
|
+
print(f"DEBUG: Error getting blockchain balance: {e}")
|
|
102
|
+
return self.balance
|
|
103
|
+
|
|
104
|
+
def refresh_balance(self) -> bool:
|
|
105
|
+
"""Refresh both total and available balance from blockchain and mempool"""
|
|
106
|
+
try:
|
|
107
|
+
total_balance = self._get_total_balance_from_blockchain()
|
|
108
|
+
available_balance = self.calculate_available_balance()
|
|
109
|
+
|
|
110
|
+
# Update wallet state
|
|
111
|
+
self.balance = total_balance
|
|
112
|
+
self.available_balance = available_balance
|
|
113
|
+
|
|
114
|
+
# Update in wallets collection
|
|
115
|
+
if self.current_wallet_address in self.wallets:
|
|
116
|
+
self.wallets[self.current_wallet_address]['balance'] = total_balance
|
|
117
|
+
self.wallets[self.current_wallet_address]['available_balance'] = available_balance
|
|
118
|
+
|
|
119
|
+
print(f"DEBUG: Balance refreshed - Total: {total_balance}, Available: {available_balance}")
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
except Exception as e:
|
|
123
|
+
print(f"DEBUG: Error refreshing balance: {e}")
|
|
124
|
+
return False
|
|
125
|
+
|
|
126
|
+
def get_available_balance(self) -> float:
|
|
127
|
+
"""Get current wallet available balance"""
|
|
128
|
+
return self.available_balance
|
|
129
|
+
|
|
130
|
+
def _get_total_balance_from_blockchain(self) -> float:
|
|
131
|
+
"""Get total balance by scanning blockchain for confirmed transactions"""
|
|
132
|
+
try:
|
|
133
|
+
from lunalib.core.blockchain import BlockchainManager
|
|
134
|
+
|
|
135
|
+
blockchain = BlockchainManager()
|
|
136
|
+
transactions = blockchain.scan_transactions_for_address(self.address)
|
|
137
|
+
|
|
138
|
+
total_balance = 0.0
|
|
139
|
+
for tx in transactions:
|
|
140
|
+
tx_type = tx.get('type', '')
|
|
141
|
+
|
|
142
|
+
# Handle incoming transactions
|
|
143
|
+
if tx.get('to') == self.address:
|
|
144
|
+
if tx_type in ['transfer', 'reward', 'fee_distribution', 'gtx_genesis']:
|
|
145
|
+
total_balance += float(tx.get('amount', 0))
|
|
146
|
+
|
|
147
|
+
# Handle outgoing transactions
|
|
148
|
+
elif tx.get('from') == self.address:
|
|
149
|
+
if tx_type in ['transfer', 'stake', 'delegate']:
|
|
150
|
+
total_balance -= float(tx.get('amount', 0))
|
|
151
|
+
total_balance -= float(tx.get('fee', 0))
|
|
152
|
+
|
|
153
|
+
return max(0.0, total_balance)
|
|
154
|
+
|
|
155
|
+
except Exception as e:
|
|
156
|
+
print(f"DEBUG: Error getting blockchain balance: {e}")
|
|
157
|
+
return self.balance
|
|
158
|
+
|
|
159
|
+
def refresh_balance(self) -> bool:
|
|
160
|
+
"""Refresh both total and available balance from blockchain and mempool"""
|
|
161
|
+
try:
|
|
162
|
+
total_balance = self._get_total_balance_from_blockchain()
|
|
163
|
+
available_balance = self.calculate_available_balance()
|
|
164
|
+
|
|
165
|
+
# Update wallet state
|
|
166
|
+
self.balance = total_balance
|
|
167
|
+
self.available_balance = available_balance
|
|
168
|
+
|
|
169
|
+
# Update in wallets collection
|
|
170
|
+
if self.current_wallet_address in self.wallets:
|
|
171
|
+
self.wallets[self.current_wallet_address]['balance'] = total_balance
|
|
172
|
+
self.wallets[self.current_wallet_address]['available_balance'] = available_balance
|
|
173
|
+
|
|
174
|
+
print(f"DEBUG: Balance refreshed - Total: {total_balance}, Available: {available_balance}")
|
|
175
|
+
return True
|
|
176
|
+
|
|
177
|
+
except Exception as e:
|
|
178
|
+
print(f"DEBUG: Error refreshing balance: {e}")
|
|
179
|
+
return False
|
|
180
|
+
|
|
181
|
+
def send_transaction(self, to_address: str, amount: float, memo: str = "", password: str = None) -> bool:
|
|
182
|
+
"""Send transaction using lunalib transactions with proper mempool submission"""
|
|
183
|
+
try:
|
|
184
|
+
print(f"DEBUG: send_transaction called - to: {to_address}, amount: {amount}, memo: {memo}")
|
|
185
|
+
|
|
186
|
+
# Refresh balances first to get latest state
|
|
187
|
+
self.refresh_balance()
|
|
188
|
+
|
|
189
|
+
# Check available balance before proceeding
|
|
190
|
+
if amount > self.available_balance:
|
|
191
|
+
print(f"DEBUG: Insufficient available balance: {self.available_balance} < {amount}")
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
# Check if wallet is unlocked
|
|
195
|
+
if self.is_locked or not self.private_key:
|
|
196
|
+
print("DEBUG: Wallet is locked or no private key available")
|
|
197
|
+
return False
|
|
198
|
+
|
|
199
|
+
# Import transaction manager
|
|
200
|
+
from lunalib.transactions.transactions import TransactionManager
|
|
201
|
+
|
|
202
|
+
# Create transaction manager
|
|
203
|
+
tx_manager = TransactionManager()
|
|
204
|
+
|
|
205
|
+
# Create and sign transaction
|
|
206
|
+
transaction = tx_manager.create_transaction(
|
|
207
|
+
from_address=self.address,
|
|
208
|
+
to_address=to_address,
|
|
209
|
+
amount=amount,
|
|
210
|
+
private_key=self.private_key,
|
|
211
|
+
memo=memo,
|
|
212
|
+
transaction_type="transfer"
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
print(f"DEBUG: Transaction created: {transaction.get('hash')}")
|
|
216
|
+
|
|
217
|
+
# Validate transaction
|
|
218
|
+
is_valid, message = tx_manager.validate_transaction(transaction)
|
|
219
|
+
if not is_valid:
|
|
220
|
+
print(f"DEBUG: Transaction validation failed: {message}")
|
|
221
|
+
return False
|
|
222
|
+
|
|
223
|
+
# Send to mempool for broadcasting
|
|
224
|
+
success, message = tx_manager.send_transaction(transaction)
|
|
225
|
+
if success:
|
|
226
|
+
print(f"DEBUG: Transaction sent to mempool: {message}")
|
|
227
|
+
|
|
228
|
+
# Update available balance immediately (deduct pending transaction)
|
|
229
|
+
fee = transaction.get('fee', 0)
|
|
230
|
+
self.available_balance -= (amount + fee)
|
|
231
|
+
if self.current_wallet_address in self.wallets:
|
|
232
|
+
self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
|
|
233
|
+
|
|
234
|
+
print(f"DEBUG: Available balance updated - new available: {self.available_balance}")
|
|
235
|
+
return True
|
|
236
|
+
else:
|
|
237
|
+
print(f"DEBUG: Failed to send transaction to mempool: {message}")
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
print(f"DEBUG: Error in send_transaction: {e}")
|
|
242
|
+
import traceback
|
|
243
|
+
traceback.print_exc()
|
|
244
|
+
return False
|
|
245
|
+
|
|
246
|
+
def send_transaction_from(self, from_address: str, to_address: str, amount: float, memo: str = "", password: str = None) -> bool:
|
|
247
|
+
"""Send transaction from specific address"""
|
|
248
|
+
try:
|
|
249
|
+
print(f"DEBUG: send_transaction_from called - from: {from_address}, to: {to_address}, amount: {amount}")
|
|
250
|
+
|
|
251
|
+
# Switch to the specified wallet if different from current
|
|
252
|
+
if from_address != self.current_wallet_address:
|
|
253
|
+
if from_address in self.wallets:
|
|
254
|
+
# Switch to the wallet first
|
|
255
|
+
wallet_data = self.wallets[from_address]
|
|
256
|
+
self._set_current_wallet(wallet_data)
|
|
257
|
+
|
|
258
|
+
# If password provided, unlock the wallet
|
|
259
|
+
if password:
|
|
260
|
+
unlock_success = self.unlock_wallet(from_address, password)
|
|
261
|
+
if not unlock_success:
|
|
262
|
+
print("DEBUG: Failed to unlock wallet for sending")
|
|
263
|
+
return False
|
|
264
|
+
else:
|
|
265
|
+
print(f"DEBUG: Wallet not found: {from_address}")
|
|
266
|
+
return False
|
|
267
|
+
|
|
268
|
+
# Now use the regular send_transaction method
|
|
269
|
+
return self.send_transaction(to_address, amount, memo, password)
|
|
270
|
+
|
|
271
|
+
except Exception as e:
|
|
272
|
+
print(f"DEBUG: Error in send_transaction_from: {e}")
|
|
273
|
+
import traceback
|
|
274
|
+
traceback.print_exc()
|
|
275
|
+
return False
|
|
276
|
+
|
|
277
|
+
def get_transaction_history(self) -> dict:
|
|
278
|
+
"""Get complete transaction history (both pending and confirmed)"""
|
|
279
|
+
try:
|
|
280
|
+
from lunalib.blockchain import BlockchainManager
|
|
281
|
+
from lunalib.core.mempool import MempoolManager
|
|
282
|
+
|
|
283
|
+
blockchain = BlockchainManager()
|
|
284
|
+
mempool = MempoolManager()
|
|
285
|
+
|
|
286
|
+
# Get confirmed transactions from blockchain
|
|
287
|
+
confirmed_txs = blockchain.scan_transactions_for_address(self.address)
|
|
288
|
+
|
|
289
|
+
# Get pending transactions from mempool
|
|
290
|
+
pending_txs = mempool.get_pending_transactions(self.address)
|
|
291
|
+
|
|
292
|
+
return {
|
|
293
|
+
'confirmed': confirmed_txs,
|
|
294
|
+
'pending': pending_txs,
|
|
295
|
+
'total_confirmed': len(confirmed_txs),
|
|
296
|
+
'total_pending': len(pending_txs)
|
|
297
|
+
}
|
|
298
|
+
except Exception as e:
|
|
299
|
+
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()}"
|
|
305
|
+
|
|
306
|
+
def _derive_public_key(self, private_key=None):
|
|
307
|
+
"""Derive public key from private key"""
|
|
308
|
+
priv_key = private_key or self.private_key
|
|
309
|
+
if not priv_key:
|
|
310
|
+
return None
|
|
311
|
+
return f"pub_{priv_key[-16:]}"
|
|
312
|
+
|
|
313
|
+
def get_wallet_info(self):
|
|
314
|
+
"""Get complete wallet information for current wallet"""
|
|
315
|
+
if not self.address:
|
|
316
|
+
return None
|
|
317
|
+
|
|
318
|
+
# Refresh balances to ensure they're current
|
|
319
|
+
self.refresh_balance()
|
|
320
|
+
|
|
321
|
+
return {
|
|
322
|
+
'address': self.address,
|
|
323
|
+
'balance': self.balance,
|
|
324
|
+
'available_balance': self.available_balance,
|
|
325
|
+
'created': self.created,
|
|
326
|
+
'private_key': self.private_key,
|
|
327
|
+
'public_key': self.public_key,
|
|
328
|
+
'encrypted_private_key': self.encrypted_private_key,
|
|
329
|
+
'label': self.label,
|
|
330
|
+
'is_locked': self.is_locked
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
def create_new_wallet(self, name, password):
|
|
334
|
+
"""Create a new wallet and add to collection without switching"""
|
|
335
|
+
# Generate new wallet data
|
|
336
|
+
address = self._generate_address()
|
|
337
|
+
private_key = self._generate_private_key()
|
|
338
|
+
public_key = f"pub_{private_key[-16:]}"
|
|
339
|
+
|
|
340
|
+
# Encrypt private key
|
|
341
|
+
key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
|
|
342
|
+
fernet = Fernet(key)
|
|
343
|
+
encrypted_private_key = fernet.encrypt(private_key.encode())
|
|
344
|
+
|
|
345
|
+
# Create new wallet data
|
|
346
|
+
new_wallet_data = {
|
|
347
|
+
'address': address,
|
|
348
|
+
'balance': 0.0,
|
|
349
|
+
'available_balance': 0.0,
|
|
350
|
+
'created': time.time(),
|
|
351
|
+
'private_key': private_key,
|
|
352
|
+
'public_key': public_key,
|
|
353
|
+
'encrypted_private_key': encrypted_private_key,
|
|
354
|
+
'label': name,
|
|
355
|
+
'is_locked': True
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
# CRITICAL: Add to wallets collection
|
|
359
|
+
self.wallets[address] = new_wallet_data
|
|
360
|
+
|
|
361
|
+
print(f"DEBUG: Created new wallet {address}, total wallets: {len(self.wallets)}")
|
|
362
|
+
|
|
363
|
+
return new_wallet_data
|
|
364
|
+
|
|
365
|
+
def create_wallet(self, name, password):
|
|
366
|
+
"""Create a new wallet and set it as current"""
|
|
367
|
+
# Generate new wallet data
|
|
368
|
+
address = self._generate_address()
|
|
369
|
+
private_key = self._generate_private_key()
|
|
370
|
+
public_key = f"pub_{private_key[-16:]}"
|
|
371
|
+
|
|
372
|
+
# Encrypt private key
|
|
373
|
+
key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
|
|
374
|
+
fernet = Fernet(key)
|
|
375
|
+
encrypted_private_key = fernet.encrypt(private_key.encode())
|
|
376
|
+
|
|
377
|
+
# Create wallet data
|
|
378
|
+
wallet_data = {
|
|
379
|
+
'address': address,
|
|
380
|
+
'balance': 0.0,
|
|
381
|
+
'available_balance': 0.0,
|
|
382
|
+
'created': time.time(),
|
|
383
|
+
'private_key': private_key,
|
|
384
|
+
'public_key': public_key,
|
|
385
|
+
'encrypted_private_key': encrypted_private_key,
|
|
386
|
+
'label': name,
|
|
387
|
+
'is_locked': True
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
# CRITICAL: Add to wallets collection
|
|
391
|
+
self.wallets[address] = wallet_data
|
|
392
|
+
|
|
393
|
+
# Set as current wallet
|
|
394
|
+
self._set_current_wallet(wallet_data)
|
|
395
|
+
|
|
396
|
+
print(f"DEBUG: Created and switched to wallet {address}, total wallets: {len(self.wallets)}")
|
|
397
|
+
|
|
398
|
+
return wallet_data
|
|
399
|
+
|
|
400
|
+
def _set_current_wallet(self, wallet_data):
|
|
401
|
+
"""Set the current wallet from wallet data"""
|
|
402
|
+
self.current_wallet_address = wallet_data['address']
|
|
403
|
+
self.address = wallet_data['address']
|
|
404
|
+
self.balance = wallet_data['balance']
|
|
405
|
+
self.available_balance = wallet_data['available_balance']
|
|
406
|
+
self.created = wallet_data['created']
|
|
407
|
+
self.private_key = wallet_data['private_key']
|
|
408
|
+
self.public_key = wallet_data['public_key']
|
|
409
|
+
self.encrypted_private_key = wallet_data['encrypted_private_key']
|
|
410
|
+
self.label = wallet_data['label']
|
|
411
|
+
self.is_locked = wallet_data.get('is_locked', True)
|
|
412
|
+
|
|
413
|
+
def switch_wallet(self, address, password=None):
|
|
414
|
+
"""Switch to a different wallet in the collection"""
|
|
415
|
+
if address in self.wallets:
|
|
416
|
+
wallet_data = self.wallets[address]
|
|
417
|
+
self._set_current_wallet(wallet_data)
|
|
418
|
+
|
|
419
|
+
# Refresh balances for the new wallet
|
|
420
|
+
self.refresh_balance()
|
|
421
|
+
|
|
422
|
+
# If password provided, unlock the wallet
|
|
423
|
+
if password:
|
|
424
|
+
return self.unlock_wallet(address, password)
|
|
425
|
+
|
|
426
|
+
return True
|
|
427
|
+
return False
|
|
428
|
+
|
|
429
|
+
def unlock_wallet(self, address, password):
|
|
430
|
+
"""Unlock wallet with password"""
|
|
431
|
+
if address not in self.wallets:
|
|
432
|
+
return False
|
|
433
|
+
|
|
434
|
+
wallet_data = self.wallets[address]
|
|
435
|
+
|
|
436
|
+
try:
|
|
437
|
+
if wallet_data.get('encrypted_private_key'):
|
|
438
|
+
key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
|
|
439
|
+
fernet = Fernet(key)
|
|
440
|
+
decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
|
|
441
|
+
wallet_data['private_key'] = decrypted_key.decode()
|
|
442
|
+
wallet_data['is_locked'] = False
|
|
443
|
+
|
|
444
|
+
# If this is the current wallet, update current state
|
|
445
|
+
if self.current_wallet_address == address:
|
|
446
|
+
self.private_key = wallet_data['private_key']
|
|
447
|
+
self.is_locked = False
|
|
448
|
+
|
|
449
|
+
return True
|
|
450
|
+
except:
|
|
451
|
+
pass
|
|
452
|
+
return False
|
|
453
|
+
|
|
454
|
+
@property
|
|
455
|
+
def is_unlocked(self):
|
|
456
|
+
"""Check if current wallet is unlocked"""
|
|
457
|
+
if not self.current_wallet_address:
|
|
458
|
+
return False
|
|
459
|
+
wallet_data = self.wallets.get(self.current_wallet_address, {})
|
|
460
|
+
return not wallet_data.get('is_locked', True)
|
|
461
|
+
|
|
462
|
+
def export_private_key(self, address, password):
|
|
463
|
+
"""Export private key with password decryption"""
|
|
464
|
+
if address not in self.wallets:
|
|
465
|
+
return None
|
|
466
|
+
|
|
467
|
+
wallet_data = self.wallets[address]
|
|
468
|
+
|
|
469
|
+
try:
|
|
470
|
+
if wallet_data.get('encrypted_private_key'):
|
|
471
|
+
key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
|
|
472
|
+
fernet = Fernet(key)
|
|
473
|
+
decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
|
|
474
|
+
return decrypted_key.decode()
|
|
475
|
+
except:
|
|
476
|
+
pass
|
|
477
|
+
return None
|
|
478
|
+
|
|
479
|
+
def import_wallet(self, wallet_data, password=None):
|
|
480
|
+
"""Import wallet from data"""
|
|
481
|
+
if isinstance(wallet_data, dict):
|
|
482
|
+
address = wallet_data.get('address')
|
|
483
|
+
if not address:
|
|
484
|
+
return False
|
|
485
|
+
|
|
486
|
+
# Add to wallets collection
|
|
487
|
+
self.wallets[address] = wallet_data.copy()
|
|
488
|
+
|
|
489
|
+
# Set as current wallet
|
|
490
|
+
self._set_current_wallet(wallet_data)
|
|
491
|
+
|
|
492
|
+
# Refresh balances for imported wallet
|
|
493
|
+
self.refresh_balance()
|
|
494
|
+
|
|
495
|
+
if password and wallet_data.get('encrypted_private_key'):
|
|
496
|
+
return self.unlock_wallet(address, password)
|
|
497
|
+
|
|
498
|
+
return True
|
|
499
|
+
return False
|
|
500
|
+
|
|
501
|
+
def update_balance(self, new_balance):
|
|
502
|
+
"""Update current wallet balance (use refresh_balance instead for accurate tracking)"""
|
|
503
|
+
self.balance = float(new_balance)
|
|
504
|
+
self.available_balance = float(new_balance)
|
|
505
|
+
|
|
506
|
+
# Also update in wallets collection
|
|
507
|
+
if self.current_wallet_address and self.current_wallet_address in self.wallets:
|
|
508
|
+
self.wallets[self.current_wallet_address]['balance'] = self.balance
|
|
509
|
+
self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
|
|
510
|
+
|
|
511
|
+
return True
|
|
512
|
+
|
|
513
|
+
def get_balance(self):
|
|
514
|
+
"""Get current wallet total balance"""
|
|
515
|
+
return self.balance
|
|
516
|
+
|
|
517
|
+
def get_available_balance(self):
|
|
518
|
+
"""Get current wallet available balance"""
|
|
519
|
+
return self.available_balance
|
|
520
|
+
|
|
521
|
+
def get_wallet_by_address(self, address):
|
|
522
|
+
"""Get wallet by address from wallets collection"""
|
|
523
|
+
return self.wallets.get(address)
|
|
524
|
+
|
|
525
|
+
def list_wallets(self):
|
|
526
|
+
"""List all wallets in collection"""
|
|
527
|
+
return list(self.wallets.keys())
|
|
528
|
+
|
|
529
|
+
def get_current_wallet_info(self):
|
|
530
|
+
"""Get current wallet information"""
|
|
531
|
+
if not self.current_wallet_address:
|
|
532
|
+
return None
|
|
533
|
+
|
|
534
|
+
# Refresh balances to ensure they're current
|
|
535
|
+
self.refresh_balance()
|
|
536
|
+
|
|
537
|
+
return self.wallets.get(self.current_wallet_address)
|
|
538
|
+
|
|
539
|
+
def save_to_file(self, filename=None):
|
|
540
|
+
"""Save wallet to file"""
|
|
541
|
+
if not self.data_dir:
|
|
542
|
+
return False
|
|
543
|
+
|
|
544
|
+
if filename is None:
|
|
545
|
+
filename = f"wallet_{self.address}.json"
|
|
546
|
+
|
|
547
|
+
filepath = os.path.join(self.data_dir, filename)
|
|
548
|
+
|
|
549
|
+
try:
|
|
550
|
+
# Ensure directory exists
|
|
551
|
+
os.makedirs(self.data_dir, exist_ok=True)
|
|
552
|
+
|
|
553
|
+
# Prepare encrypted private key for serialization
|
|
554
|
+
encrypted_key_data = None
|
|
555
|
+
if self.encrypted_private_key:
|
|
556
|
+
# Ensure it's bytes before encoding
|
|
557
|
+
if isinstance(self.encrypted_private_key, bytes):
|
|
558
|
+
encrypted_key_data = base64.b64encode(self.encrypted_private_key).decode('utf-8')
|
|
559
|
+
else:
|
|
560
|
+
encrypted_key_data = base64.b64encode(self.encrypted_private_key.encode()).decode('utf-8')
|
|
561
|
+
|
|
562
|
+
# Prepare wallets for serialization (remove any non-serializable data)
|
|
563
|
+
serializable_wallets = {}
|
|
564
|
+
for addr, wallet_info in self.wallets.items():
|
|
565
|
+
serializable_wallet = wallet_info.copy()
|
|
566
|
+
# Ensure encrypted_private_key is serializable
|
|
567
|
+
if serializable_wallet.get('encrypted_private_key') and isinstance(serializable_wallet['encrypted_private_key'], bytes):
|
|
568
|
+
serializable_wallet['encrypted_private_key'] = base64.b64encode(
|
|
569
|
+
serializable_wallet['encrypted_private_key']
|
|
570
|
+
).decode('utf-8')
|
|
571
|
+
serializable_wallets[addr] = serializable_wallet
|
|
572
|
+
|
|
573
|
+
wallet_data = {
|
|
574
|
+
'address': self.address,
|
|
575
|
+
'balance': self.balance,
|
|
576
|
+
'available_balance': self.available_balance,
|
|
577
|
+
'created': self.created,
|
|
578
|
+
'public_key': self.public_key,
|
|
579
|
+
'encrypted_private_key': encrypted_key_data,
|
|
580
|
+
'label': self.label,
|
|
581
|
+
'is_locked': self.is_locked,
|
|
582
|
+
'wallets': serializable_wallets,
|
|
583
|
+
'current_wallet_address': self.current_wallet_address
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
with open(filepath, 'w') as f:
|
|
587
|
+
json.dump(wallet_data, f, indent=2)
|
|
588
|
+
return True
|
|
589
|
+
except Exception as e:
|
|
590
|
+
print(f"Error saving wallet: {e}")
|
|
591
|
+
import traceback
|
|
592
|
+
traceback.print_exc()
|
|
593
|
+
return False
|
|
594
|
+
|
|
595
|
+
def load_from_file(self, filename, password=None):
|
|
596
|
+
"""Load wallet from file"""
|
|
597
|
+
if not self.data_dir:
|
|
598
|
+
return False
|
|
599
|
+
|
|
600
|
+
filepath = os.path.join(self.data_dir, filename)
|
|
601
|
+
|
|
602
|
+
try:
|
|
603
|
+
with open(filepath, 'r') as f:
|
|
604
|
+
wallet_data = json.load(f)
|
|
605
|
+
|
|
606
|
+
# Load wallets collection
|
|
607
|
+
self.wallets = wallet_data.get('wallets', {})
|
|
608
|
+
|
|
609
|
+
# Load current wallet address
|
|
610
|
+
self.current_wallet_address = wallet_data.get('current_wallet_address')
|
|
611
|
+
|
|
612
|
+
# If we have a current wallet, load its data
|
|
613
|
+
if self.current_wallet_address and self.current_wallet_address in self.wallets:
|
|
614
|
+
current_wallet_data = self.wallets[self.current_wallet_address]
|
|
615
|
+
self._set_current_wallet(current_wallet_data)
|
|
616
|
+
|
|
617
|
+
# Handle encrypted private key
|
|
618
|
+
encrypted_key = wallet_data.get('encrypted_private_key')
|
|
619
|
+
if encrypted_key:
|
|
620
|
+
self.encrypted_private_key = base64.b64decode(encrypted_key.encode())
|
|
621
|
+
# Also update in wallets collection
|
|
622
|
+
if self.current_wallet_address in self.wallets:
|
|
623
|
+
self.wallets[self.current_wallet_address]['encrypted_private_key'] = self.encrypted_private_key
|
|
624
|
+
|
|
625
|
+
# Refresh balances after loading
|
|
626
|
+
self.refresh_balance()
|
|
627
|
+
|
|
628
|
+
# If password provided and we have encrypted key, unlock
|
|
629
|
+
if password and self.encrypted_private_key and self.current_wallet_address:
|
|
630
|
+
return self.unlock_wallet(self.current_wallet_address, password)
|
|
631
|
+
|
|
632
|
+
return True
|
|
633
|
+
except Exception as e:
|
|
634
|
+
print(f"Error loading wallet: {e}")
|
|
635
|
+
return False
|
lunalib/gtx/__init__.py
ADDED
|
File without changes
|