lunalib 1.5.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lunalib might be problematic. Click here for more details.

Files changed (53) hide show
  1. core/__init__.py +0 -0
  2. core/blockchain.py +172 -0
  3. core/crypto.py +32 -0
  4. core/wallet.py +408 -0
  5. gtx/__init__.py +0 -0
  6. gtx/bill_registry.py +122 -0
  7. gtx/digital_bill.py +273 -0
  8. gtx/genesis.py +338 -0
  9. lunalib/__init__.py +21 -0
  10. lunalib/cli.py +18 -0
  11. lunalib/core/__init__.py +0 -0
  12. lunalib/core/blockchain.py +803 -0
  13. lunalib/core/crypto.py +270 -0
  14. lunalib/core/mempool.py +342 -0
  15. lunalib/core/sm2.py +723 -0
  16. lunalib/core/wallet.py +1342 -0
  17. lunalib/core/wallet_manager.py +638 -0
  18. lunalib/core/wallet_sync_helper.py +163 -0
  19. lunalib/gtx/__init__.py +0 -0
  20. lunalib/gtx/bill_registry.py +122 -0
  21. lunalib/gtx/digital_bill.py +273 -0
  22. lunalib/gtx/genesis.py +349 -0
  23. lunalib/luna_lib.py +87 -0
  24. lunalib/mining/__init__.py +0 -0
  25. lunalib/mining/cuda_manager.py +137 -0
  26. lunalib/mining/difficulty.py +106 -0
  27. lunalib/mining/miner.py +617 -0
  28. lunalib/requirements.txt +44 -0
  29. lunalib/storage/__init__.py +0 -0
  30. lunalib/storage/cache.py +148 -0
  31. lunalib/storage/database.py +222 -0
  32. lunalib/storage/encryption.py +105 -0
  33. lunalib/transactions/__init__.py +0 -0
  34. lunalib/transactions/security.py +234 -0
  35. lunalib/transactions/transactions.py +399 -0
  36. lunalib/transactions/validator.py +71 -0
  37. lunalib-1.5.1.dist-info/METADATA +283 -0
  38. lunalib-1.5.1.dist-info/RECORD +53 -0
  39. lunalib-1.5.1.dist-info/WHEEL +5 -0
  40. lunalib-1.5.1.dist-info/entry_points.txt +2 -0
  41. lunalib-1.5.1.dist-info/top_level.txt +6 -0
  42. mining/__init__.py +0 -0
  43. mining/cuda_manager.py +137 -0
  44. mining/difficulty.py +106 -0
  45. mining/miner.py +107 -0
  46. storage/__init__.py +0 -0
  47. storage/cache.py +148 -0
  48. storage/database.py +222 -0
  49. storage/encryption.py +105 -0
  50. transactions/__init__.py +0 -0
  51. transactions/security.py +172 -0
  52. transactions/transactions.py +424 -0
  53. transactions/validator.py +71 -0
core/__init__.py ADDED
File without changes
core/blockchain.py ADDED
@@ -0,0 +1,172 @@
1
+ from storage.cache import BlockchainCache
2
+ import requests
3
+ import time
4
+ import json
5
+ from typing import Dict, List, Optional, Tuple
6
+
7
+
8
+ class BlockchainManager:
9
+ """Manages blockchain interactions and scanning"""
10
+
11
+ def __init__(self, endpoint_url="https://bank.linglin.art"):
12
+ self.endpoint_url = endpoint_url.rstrip('/')
13
+ self.cache = BlockchainCache()
14
+ self.network_connected = False
15
+
16
+ def get_blockchain_height(self) -> int:
17
+ """Get current blockchain height"""
18
+ try:
19
+ # Try height endpoint first
20
+ response = requests.get(f'{self.endpoint_url}/blockchain/height', timeout=10)
21
+ if response.status_code == 200:
22
+ data = response.json()
23
+ return data.get('height', 0)
24
+
25
+ # Fallback to blocks endpoint
26
+ response = requests.get(f'{self.endpoint_url}/blockchain/blocks', timeout=10)
27
+ if response.status_code == 200:
28
+ data = response.json()
29
+ blocks = data.get('blocks', [])
30
+ return len(blocks)
31
+
32
+ except Exception as e:
33
+ print(f"Blockchain height error: {e}")
34
+
35
+ return 0
36
+
37
+ def get_block(self, height: int) -> Optional[Dict]:
38
+ """Get block by height"""
39
+ # Check cache first
40
+ cached_block = self.cache.get_block(height)
41
+ if cached_block:
42
+ return cached_block
43
+
44
+ try:
45
+ response = requests.get(f'{self.endpoint_url}/blockchain/block/{height}', timeout=10)
46
+ if response.status_code == 200:
47
+ block = response.json()
48
+ self.cache.save_block(height, block.get('hash', ''), block)
49
+ return block
50
+ except Exception as e:
51
+ print(f"Get block error: {e}")
52
+
53
+ return None
54
+
55
+ def get_blocks_range(self, start_height: int, end_height: int) -> List[Dict]:
56
+ """Get range of blocks"""
57
+ blocks = []
58
+
59
+ # Check cache first
60
+ cached_blocks = self.cache.get_block_range(start_height, end_height)
61
+ if len(cached_blocks) == (end_height - start_height + 1):
62
+ return cached_blocks
63
+
64
+ try:
65
+ response = requests.get(
66
+ f'{self.endpoint_url}/blockchain/range?start={start_height}&end={end_height}',
67
+ timeout=30
68
+ )
69
+ if response.status_code == 200:
70
+ blocks = response.json().get('blocks', [])
71
+ # Cache the blocks
72
+ for block in blocks:
73
+ height = block.get('index', 0)
74
+ self.cache.save_block(height, block.get('hash', ''), block)
75
+ else:
76
+ # Fallback: get blocks individually
77
+ for height in range(start_height, end_height + 1):
78
+ block = self.get_block(height)
79
+ if block:
80
+ blocks.append(block)
81
+ time.sleep(0.01) # Be nice to the API
82
+
83
+ except Exception as e:
84
+ print(f"Get blocks range error: {e}")
85
+
86
+ return blocks
87
+
88
+ def get_mempool(self) -> List[Dict]:
89
+ """Get current mempool transactions"""
90
+ try:
91
+ response = requests.get(f'{self.endpoint_url}/mempool', timeout=10)
92
+ if response.status_code == 200:
93
+ return response.json()
94
+ except Exception as e:
95
+ print(f"Mempool error: {e}")
96
+
97
+ return []
98
+
99
+ def broadcast_transaction(self, transaction: Dict) -> bool:
100
+ """Broadcast transaction to network"""
101
+ try:
102
+ response = requests.post(
103
+ f'{self.endpoint_url}/mempool/add',
104
+ json=transaction,
105
+ timeout=30
106
+ )
107
+ return response.status_code == 201
108
+ except Exception as e:
109
+ print(f"Broadcast error: {e}")
110
+ return False
111
+
112
+ def check_network_connection(self) -> bool:
113
+ """Check if network is accessible"""
114
+ try:
115
+ response = requests.get(f'{self.endpoint_url}/health', timeout=5)
116
+ self.network_connected = response.status_code == 200
117
+ return self.network_connected
118
+ except:
119
+ self.network_connected = False
120
+ return False
121
+
122
+ def scan_transactions_for_address(self, address: str, start_height: int = 0, end_height: int = None) -> List[Dict]:
123
+ """Scan blockchain for transactions involving an address"""
124
+ if end_height is None:
125
+ end_height = self.get_blockchain_height()
126
+
127
+ transactions = []
128
+
129
+ # Scan in batches for efficiency
130
+ batch_size = 100
131
+ for batch_start in range(start_height, end_height + 1, batch_size):
132
+ batch_end = min(batch_start + batch_size - 1, end_height)
133
+ blocks = self.get_blocks_range(batch_start, batch_end)
134
+
135
+ for block in blocks:
136
+ block_transactions = self._find_address_transactions(block, address)
137
+ transactions.extend(block_transactions)
138
+
139
+ return transactions
140
+
141
+ def _find_address_transactions(self, block: Dict, address: str) -> List[Dict]:
142
+ """Find transactions in block that involve the address"""
143
+ transactions = []
144
+ address_lower = address.lower()
145
+
146
+ # Check block reward
147
+ miner = block.get('miner', '').lower()
148
+ if miner == address_lower:
149
+ reward_tx = {
150
+ 'type': 'reward',
151
+ 'from': 'network',
152
+ 'to': address,
153
+ 'amount': block.get('reward', 0),
154
+ 'block_height': block.get('index'),
155
+ 'timestamp': block.get('timestamp'),
156
+ 'hash': f"reward_{block.get('index')}_{address}",
157
+ 'status': 'confirmed'
158
+ }
159
+ transactions.append(reward_tx)
160
+
161
+ # Check regular transactions
162
+ for tx in block.get('transactions', []):
163
+ from_addr = (tx.get('from') or '').lower()
164
+ to_addr = (tx.get('to') or '').lower()
165
+
166
+ if from_addr == address_lower or to_addr == address_lower:
167
+ enhanced_tx = tx.copy()
168
+ enhanced_tx['block_height'] = block.get('index')
169
+ enhanced_tx['status'] = 'confirmed'
170
+ transactions.append(enhanced_tx)
171
+
172
+ return transactions
core/crypto.py ADDED
@@ -0,0 +1,32 @@
1
+ import hashlib
2
+ import secrets
3
+ from typing import Optional
4
+
5
+ class KeyManager:
6
+ """Manages cryptographic keys and signing"""
7
+
8
+ def generate_private_key(self):
9
+ """Generate a new private key"""
10
+ return secrets.token_hex(32)
11
+
12
+ def derive_public_key(self, private_key):
13
+ """Derive public key from private key"""
14
+ return hashlib.sha256(private_key.encode()).hexdigest()
15
+
16
+ def derive_address(self, public_key):
17
+ """Derive address from public key"""
18
+ address_hash = hashlib.sha256(public_key.encode()).hexdigest()
19
+ return f"LUN_{address_hash[:16]}_{secrets.token_hex(4)}"
20
+
21
+ def sign_data(self, data, private_key):
22
+ """Sign data with private key"""
23
+ # In production, use proper ECDSA
24
+ # For now, simplified implementation
25
+ sign_string = data + private_key
26
+ return hashlib.sha256(sign_string.encode()).hexdigest()
27
+
28
+ def verify_signature(self, data, signature, public_key):
29
+ """Verify signature with public key"""
30
+ # Simplified verification
31
+ expected_public = self.derive_public_key(public_key)
32
+ return public_key == expected_public
core/wallet.py ADDED
@@ -0,0 +1,408 @@
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"""
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
24
+ self.available_balance = 0.0
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
+ self.pending_transactions = {} # {tx_hash: amount}
32
+
33
+ def _generate_address(self):
34
+ """Generate wallet address"""
35
+ base_data = f"LUN_{int(time.time()*1000)}_{hashlib.sha256(str(time.time()).encode()).hexdigest()[:8]}"
36
+ return hashlib.sha256(base_data.encode()).hexdigest()[:32]
37
+
38
+ def _generate_private_key(self):
39
+ """Generate private key"""
40
+ return f"priv_{hashlib.sha256(str(time.time()).encode()).hexdigest()}"
41
+
42
+ def _derive_public_key(self, private_key=None):
43
+ """Derive public key from private key"""
44
+ priv_key = private_key or self.private_key
45
+ if not priv_key:
46
+ return None
47
+ return f"pub_{priv_key[-16:]}"
48
+
49
+ def get_wallet_info(self):
50
+ """Get complete wallet information for current wallet"""
51
+ if not self.address:
52
+ return None
53
+ return {
54
+ 'address': self.address,
55
+ 'balance': self.balance,
56
+ 'available_balance': self.available_balance,
57
+ 'pending_amount': self.get_pending_amount(),
58
+ 'created': self.created,
59
+ 'private_key': self.private_key,
60
+ 'public_key': self.public_key,
61
+ 'encrypted_private_key': self.encrypted_private_key,
62
+ 'label': self.label,
63
+ 'is_locked': self.is_locked
64
+ }
65
+
66
+ def create_new_wallet(self, name, password):
67
+ """Create a new wallet and add to collection without switching"""
68
+ # Generate new wallet data
69
+ address = self._generate_address()
70
+ private_key = self._generate_private_key()
71
+ public_key = f"pub_{private_key[-16:]}"
72
+
73
+ # Encrypt private key
74
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
75
+ fernet = Fernet(key)
76
+ encrypted_private_key = fernet.encrypt(private_key.encode())
77
+
78
+ # Create new wallet data
79
+ new_wallet_data = {
80
+ 'address': address,
81
+ 'balance': 0.0,
82
+ 'available_balance': 0.0,
83
+ 'created': time.time(),
84
+ 'private_key': private_key,
85
+ 'public_key': public_key,
86
+ 'encrypted_private_key': encrypted_private_key,
87
+ 'label': name,
88
+ 'is_locked': True,
89
+ 'pending_transactions': {}
90
+ }
91
+
92
+ # CRITICAL: Add to wallets collection
93
+ self.wallets[address] = new_wallet_data
94
+
95
+ print(f"DEBUG: Created new wallet {address}, total wallets: {len(self.wallets)}")
96
+
97
+ return new_wallet_data
98
+ def create_wallet(self, name, password):
99
+ """Create a new wallet and set it as current"""
100
+ # Generate new wallet data
101
+ address = self._generate_address()
102
+ private_key = self._generate_private_key()
103
+ public_key = f"pub_{private_key[-16:]}"
104
+
105
+ # Encrypt private key
106
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
107
+ fernet = Fernet(key)
108
+ encrypted_private_key = fernet.encrypt(private_key.encode())
109
+
110
+ # Create wallet data
111
+ wallet_data = {
112
+ 'address': address,
113
+ 'balance': 0.0,
114
+ 'available_balance': 0.0,
115
+ 'created': time.time(),
116
+ 'private_key': private_key,
117
+ 'public_key': public_key,
118
+ 'encrypted_private_key': encrypted_private_key,
119
+ 'label': name,
120
+ 'is_locked': True,
121
+ 'pending_transactions': {}
122
+ }
123
+
124
+ # CRITICAL: Add to wallets collection
125
+ self.wallets[address] = wallet_data
126
+
127
+ # Set as current wallet
128
+ self._set_current_wallet(wallet_data)
129
+
130
+ print(f"DEBUG: Created and switched to wallet {address}, total wallets: {len(self.wallets)}")
131
+
132
+ return wallet_data
133
+ def _set_current_wallet(self, wallet_data):
134
+ """Set the current wallet from wallet data"""
135
+ self.current_wallet_address = wallet_data['address']
136
+ self.address = wallet_data['address']
137
+ self.balance = wallet_data['balance']
138
+ self.pending_transactions = wallet_data.get('pending_transactions', {})
139
+ self._recalculate_available_balance()
140
+ self.created = wallet_data['created']
141
+ self.private_key = wallet_data['private_key']
142
+ self.public_key = wallet_data['public_key']
143
+ self.encrypted_private_key = wallet_data['encrypted_private_key']
144
+ self.label = wallet_data['label']
145
+ self.is_locked = wallet_data.get('is_locked', True)
146
+
147
+ def switch_wallet(self, address, password=None):
148
+ """Switch to a different wallet in the collection"""
149
+ if address in self.wallets:
150
+ wallet_data = self.wallets[address]
151
+ self._set_current_wallet(wallet_data)
152
+
153
+ # If password provided, unlock the wallet
154
+ if password:
155
+ return self.unlock_wallet(address, password)
156
+
157
+ return True
158
+ return False
159
+
160
+ def unlock_wallet(self, address, password):
161
+ """Unlock wallet with password"""
162
+ if address not in self.wallets:
163
+ return False
164
+
165
+ wallet_data = self.wallets[address]
166
+
167
+ try:
168
+ if wallet_data.get('encrypted_private_key'):
169
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
170
+ fernet = Fernet(key)
171
+ decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
172
+ wallet_data['private_key'] = decrypted_key.decode()
173
+ wallet_data['is_locked'] = False
174
+
175
+ # If this is the current wallet, update current state
176
+ if self.current_wallet_address == address:
177
+ self.private_key = wallet_data['private_key']
178
+ self.is_locked = False
179
+
180
+ return True
181
+ except:
182
+ pass
183
+ return False
184
+
185
+ @property
186
+ def is_unlocked(self):
187
+ """Check if current wallet is unlocked"""
188
+ if not self.current_wallet_address:
189
+ return False
190
+ wallet_data = self.wallets.get(self.current_wallet_address, {})
191
+ return not wallet_data.get('is_locked', True)
192
+
193
+ def export_private_key(self, address, password):
194
+ """Export private key with password decryption"""
195
+ if address not in self.wallets:
196
+ return None
197
+
198
+ wallet_data = self.wallets[address]
199
+
200
+ try:
201
+ if wallet_data.get('encrypted_private_key'):
202
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
203
+ fernet = Fernet(key)
204
+ decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
205
+ return decrypted_key.decode()
206
+ except:
207
+ pass
208
+ return None
209
+
210
+ def import_wallet(self, wallet_data, password=None):
211
+ """Import wallet from data"""
212
+ if isinstance(wallet_data, dict):
213
+ address = wallet_data.get('address')
214
+ if not address:
215
+ return False
216
+
217
+ # Ensure pending_transactions exists
218
+ if 'pending_transactions' not in wallet_data:
219
+ wallet_data['pending_transactions'] = {}
220
+
221
+ # Add to wallets collection
222
+ self.wallets[address] = wallet_data.copy()
223
+
224
+ # Set as current wallet
225
+ self._set_current_wallet(wallet_data)
226
+
227
+ if password and wallet_data.get('encrypted_private_key'):
228
+ return self.unlock_wallet(address, password)
229
+
230
+ return True
231
+ return False
232
+
233
+ def update_balance(self, new_balance):
234
+ """Update current wallet balance"""
235
+ self.balance = float(new_balance)
236
+ self._recalculate_available_balance()
237
+
238
+ # Also update in wallets collection
239
+ if self.current_wallet_address and self.current_wallet_address in self.wallets:
240
+ self.wallets[self.current_wallet_address]['balance'] = self.balance
241
+ self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
242
+
243
+ return True
244
+
245
+ def _recalculate_available_balance(self):
246
+ """Recalculate available balance based on pending transactions"""
247
+ pending_amount = sum(self.pending_transactions.values())
248
+ self.available_balance = max(0.0, self.balance - pending_amount)
249
+
250
+ def add_pending_transaction(self, tx_hash: str, amount: float) -> bool:
251
+ """Add a transaction to pending list (e.g., when sent to mempool)"""
252
+ if amount > self.available_balance:
253
+ return False
254
+ self.pending_transactions[tx_hash] = float(amount)
255
+ self._recalculate_available_balance()
256
+
257
+ # Update in wallets collection
258
+ if self.current_wallet_address and self.current_wallet_address in self.wallets:
259
+ self.wallets[self.current_wallet_address]['pending_transactions'] = self.pending_transactions
260
+ self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
261
+
262
+ return True
263
+
264
+ def confirm_pending_transaction(self, tx_hash: str) -> bool:
265
+ """Confirm a pending transaction (e.g., added to blockchain)"""
266
+ if tx_hash in self.pending_transactions:
267
+ del self.pending_transactions[tx_hash]
268
+ self._recalculate_available_balance()
269
+
270
+ # Update in wallets collection
271
+ if self.current_wallet_address and self.current_wallet_address in self.wallets:
272
+ self.wallets[self.current_wallet_address]['pending_transactions'] = self.pending_transactions
273
+ self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
274
+
275
+ return True
276
+ return False
277
+
278
+ def get_pending_amount(self) -> float:
279
+ """Get total amount in pending transactions"""
280
+ return sum(self.pending_transactions.values())
281
+
282
+ def get_pending_transactions(self) -> dict:
283
+ """Get all pending transactions"""
284
+ return self.pending_transactions.copy()
285
+
286
+ def clear_pending_transaction(self, tx_hash: str) -> bool:
287
+ """Remove a pending transaction (e.g., if it failed)"""
288
+ return self.confirm_pending_transaction(tx_hash)
289
+
290
+ def get_balance(self):
291
+ """Get current wallet balance"""
292
+ return self.balance
293
+
294
+ def get_wallet_by_address(self, address):
295
+ """Get wallet by address from wallets collection"""
296
+ return self.wallets.get(address)
297
+
298
+ def list_wallets(self):
299
+ """List all wallets in collection"""
300
+ return list(self.wallets.keys())
301
+
302
+ def get_current_wallet_info(self):
303
+ """Get current wallet information"""
304
+ if not self.current_wallet_address:
305
+ return None
306
+ return self.wallets.get(self.current_wallet_address)
307
+
308
+ def save_to_file(self, filename=None):
309
+ """Save wallet to file"""
310
+ if not self.data_dir:
311
+ return False
312
+
313
+ if filename is None:
314
+ filename = f"wallet_{self.address}.json"
315
+
316
+ filepath = os.path.join(self.data_dir, filename)
317
+
318
+ try:
319
+ # Ensure directory exists
320
+ os.makedirs(self.data_dir, exist_ok=True)
321
+
322
+ # Prepare encrypted private key for serialization
323
+ encrypted_key_data = None
324
+ if self.encrypted_private_key:
325
+ # Ensure it's bytes before encoding
326
+ if isinstance(self.encrypted_private_key, bytes):
327
+ encrypted_key_data = base64.b64encode(self.encrypted_private_key).decode('utf-8')
328
+ else:
329
+ encrypted_key_data = base64.b64encode(self.encrypted_private_key.encode()).decode('utf-8')
330
+
331
+ # Prepare wallets for serialization (remove any non-serializable data)
332
+ serializable_wallets = {}
333
+ for addr, wallet_info in self.wallets.items():
334
+ serializable_wallet = wallet_info.copy()
335
+ # Ensure encrypted_private_key is serializable
336
+ if serializable_wallet.get('encrypted_private_key') and isinstance(serializable_wallet['encrypted_private_key'], bytes):
337
+ serializable_wallet['encrypted_private_key'] = base64.b64encode(
338
+ serializable_wallet['encrypted_private_key']
339
+ ).decode('utf-8')
340
+ serializable_wallets[addr] = serializable_wallet
341
+
342
+ wallet_data = {
343
+ 'address': self.address,
344
+ 'balance': self.balance,
345
+ 'available_balance': self.available_balance,
346
+ 'pending_amount': self.get_pending_amount(),
347
+ 'pending_transactions': self.pending_transactions,
348
+ 'created': self.created,
349
+ 'public_key': self.public_key,
350
+ 'encrypted_private_key': encrypted_key_data,
351
+ 'label': self.label,
352
+ 'is_locked': self.is_locked,
353
+ 'wallets': serializable_wallets,
354
+ 'current_wallet_address': self.current_wallet_address
355
+ }
356
+
357
+ with open(filepath, 'w') as f:
358
+ json.dump(wallet_data, f, indent=2)
359
+ return True
360
+ except Exception as e:
361
+ print(f"Error saving wallet: {e}")
362
+ import traceback
363
+ traceback.print_exc()
364
+ return False
365
+
366
+ def load_from_file(self, filename, password=None):
367
+ """Load wallet from file"""
368
+ if not self.data_dir:
369
+ return False
370
+
371
+ filepath = os.path.join(self.data_dir, filename)
372
+
373
+ try:
374
+ with open(filepath, 'r') as f:
375
+ wallet_data = json.load(f)
376
+
377
+ # Load wallets collection
378
+ self.wallets = wallet_data.get('wallets', {})
379
+
380
+ # Ensure all wallets have pending_transactions field
381
+ for addr, w_data in self.wallets.items():
382
+ if 'pending_transactions' not in w_data:
383
+ w_data['pending_transactions'] = {}
384
+
385
+ # Load current wallet address
386
+ self.current_wallet_address = wallet_data.get('current_wallet_address')
387
+
388
+ # If we have a current wallet, load its data
389
+ if self.current_wallet_address and self.current_wallet_address in self.wallets:
390
+ current_wallet_data = self.wallets[self.current_wallet_address]
391
+ self._set_current_wallet(current_wallet_data)
392
+
393
+ # Handle encrypted private key
394
+ encrypted_key = wallet_data.get('encrypted_private_key')
395
+ if encrypted_key:
396
+ self.encrypted_private_key = base64.b64decode(encrypted_key.encode())
397
+ # Also update in wallets collection
398
+ if self.current_wallet_address in self.wallets:
399
+ self.wallets[self.current_wallet_address]['encrypted_private_key'] = self.encrypted_private_key
400
+
401
+ # If password provided and we have encrypted key, unlock
402
+ if password and self.encrypted_private_key and self.current_wallet_address:
403
+ return self.unlock_wallet(self.current_wallet_address, password)
404
+
405
+ return True
406
+ except Exception as e:
407
+ print(f"Error loading wallet: {e}")
408
+ return False
gtx/__init__.py ADDED
File without changes