lunalib 1.1.0__py3-none-any.whl → 1.2.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.
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,347 @@
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
+
32
+ def _generate_address(self):
33
+ """Generate wallet address"""
34
+ base_data = f"LUN_{int(time.time()*1000)}_{hashlib.sha256(str(time.time()).encode()).hexdigest()[:8]}"
35
+ return hashlib.sha256(base_data.encode()).hexdigest()[:32]
36
+
37
+ def _generate_private_key(self):
38
+ """Generate private key"""
39
+ return f"priv_{hashlib.sha256(str(time.time()).encode()).hexdigest()}"
40
+
41
+ def _derive_public_key(self, private_key=None):
42
+ """Derive public key from private key"""
43
+ priv_key = private_key or self.private_key
44
+ if not priv_key:
45
+ return None
46
+ return f"pub_{priv_key[-16:]}"
47
+
48
+ def get_wallet_info(self):
49
+ """Get complete wallet information for current wallet"""
50
+ if not self.address:
51
+ return None
52
+ return {
53
+ 'address': self.address,
54
+ 'balance': self.balance,
55
+ 'available_balance': self.available_balance,
56
+ 'created': self.created,
57
+ 'private_key': self.private_key,
58
+ 'public_key': self.public_key,
59
+ 'encrypted_private_key': self.encrypted_private_key,
60
+ 'label': self.label,
61
+ 'is_locked': self.is_locked
62
+ }
63
+
64
+ def create_new_wallet(self, name, password):
65
+ """Create a new wallet and add to collection without switching"""
66
+ # Generate new wallet data
67
+ address = self._generate_address()
68
+ private_key = self._generate_private_key()
69
+ public_key = f"pub_{private_key[-16:]}"
70
+
71
+ # Encrypt private key
72
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
73
+ fernet = Fernet(key)
74
+ encrypted_private_key = fernet.encrypt(private_key.encode())
75
+
76
+ # Create new wallet data
77
+ new_wallet_data = {
78
+ 'address': address,
79
+ 'balance': 0.0,
80
+ 'available_balance': 0.0,
81
+ 'created': time.time(),
82
+ 'private_key': private_key,
83
+ 'public_key': public_key,
84
+ 'encrypted_private_key': encrypted_private_key,
85
+ 'label': name,
86
+ 'is_locked': True
87
+ }
88
+
89
+ # CRITICAL: Add to wallets collection
90
+ self.wallets[address] = new_wallet_data
91
+
92
+ print(f"DEBUG: Created new wallet {address}, total wallets: {len(self.wallets)}")
93
+
94
+ return new_wallet_data
95
+ def create_wallet(self, name, password):
96
+ """Create a new wallet and set it as current"""
97
+ # Generate new wallet data
98
+ address = self._generate_address()
99
+ private_key = self._generate_private_key()
100
+ public_key = f"pub_{private_key[-16:]}"
101
+
102
+ # Encrypt private key
103
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
104
+ fernet = Fernet(key)
105
+ encrypted_private_key = fernet.encrypt(private_key.encode())
106
+
107
+ # Create wallet data
108
+ wallet_data = {
109
+ 'address': address,
110
+ 'balance': 0.0,
111
+ 'available_balance': 0.0,
112
+ 'created': time.time(),
113
+ 'private_key': private_key,
114
+ 'public_key': public_key,
115
+ 'encrypted_private_key': encrypted_private_key,
116
+ 'label': name,
117
+ 'is_locked': True
118
+ }
119
+
120
+ # CRITICAL: Add to wallets collection
121
+ self.wallets[address] = wallet_data
122
+
123
+ # Set as current wallet
124
+ self._set_current_wallet(wallet_data)
125
+
126
+ print(f"DEBUG: Created and switched to wallet {address}, total wallets: {len(self.wallets)}")
127
+
128
+ return wallet_data
129
+ def _set_current_wallet(self, wallet_data):
130
+ """Set the current wallet from wallet data"""
131
+ self.current_wallet_address = wallet_data['address']
132
+ self.address = wallet_data['address']
133
+ self.balance = wallet_data['balance']
134
+ self.available_balance = wallet_data['available_balance']
135
+ self.created = wallet_data['created']
136
+ self.private_key = wallet_data['private_key']
137
+ self.public_key = wallet_data['public_key']
138
+ self.encrypted_private_key = wallet_data['encrypted_private_key']
139
+ self.label = wallet_data['label']
140
+ self.is_locked = wallet_data.get('is_locked', True)
141
+
142
+ def switch_wallet(self, address, password=None):
143
+ """Switch to a different wallet in the collection"""
144
+ if address in self.wallets:
145
+ wallet_data = self.wallets[address]
146
+ self._set_current_wallet(wallet_data)
147
+
148
+ # If password provided, unlock the wallet
149
+ if password:
150
+ return self.unlock_wallet(address, password)
151
+
152
+ return True
153
+ return False
154
+
155
+ def unlock_wallet(self, address, password):
156
+ """Unlock wallet with password"""
157
+ if address not in self.wallets:
158
+ return False
159
+
160
+ wallet_data = self.wallets[address]
161
+
162
+ try:
163
+ if wallet_data.get('encrypted_private_key'):
164
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
165
+ fernet = Fernet(key)
166
+ decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
167
+ wallet_data['private_key'] = decrypted_key.decode()
168
+ wallet_data['is_locked'] = False
169
+
170
+ # If this is the current wallet, update current state
171
+ if self.current_wallet_address == address:
172
+ self.private_key = wallet_data['private_key']
173
+ self.is_locked = False
174
+
175
+ return True
176
+ except:
177
+ pass
178
+ return False
179
+
180
+ @property
181
+ def is_unlocked(self):
182
+ """Check if current wallet is unlocked"""
183
+ if not self.current_wallet_address:
184
+ return False
185
+ wallet_data = self.wallets.get(self.current_wallet_address, {})
186
+ return not wallet_data.get('is_locked', True)
187
+
188
+ def export_private_key(self, address, password):
189
+ """Export private key with password decryption"""
190
+ if address not in self.wallets:
191
+ return None
192
+
193
+ wallet_data = self.wallets[address]
194
+
195
+ try:
196
+ if wallet_data.get('encrypted_private_key'):
197
+ key = base64.urlsafe_b64encode(hashlib.sha256(password.encode()).digest())
198
+ fernet = Fernet(key)
199
+ decrypted_key = fernet.decrypt(wallet_data['encrypted_private_key'])
200
+ return decrypted_key.decode()
201
+ except:
202
+ pass
203
+ return None
204
+
205
+ def import_wallet(self, wallet_data, password=None):
206
+ """Import wallet from data"""
207
+ if isinstance(wallet_data, dict):
208
+ address = wallet_data.get('address')
209
+ if not address:
210
+ return False
211
+
212
+ # Add to wallets collection
213
+ self.wallets[address] = wallet_data.copy()
214
+
215
+ # Set as current wallet
216
+ self._set_current_wallet(wallet_data)
217
+
218
+ if password and wallet_data.get('encrypted_private_key'):
219
+ return self.unlock_wallet(address, password)
220
+
221
+ return True
222
+ return False
223
+
224
+ def update_balance(self, new_balance):
225
+ """Update current wallet balance"""
226
+ self.balance = float(new_balance)
227
+ self.available_balance = float(new_balance)
228
+
229
+ # Also update in wallets collection
230
+ if self.current_wallet_address and self.current_wallet_address in self.wallets:
231
+ self.wallets[self.current_wallet_address]['balance'] = self.balance
232
+ self.wallets[self.current_wallet_address]['available_balance'] = self.available_balance
233
+
234
+ return True
235
+
236
+ def get_balance(self):
237
+ """Get current wallet balance"""
238
+ return self.balance
239
+
240
+ def get_wallet_by_address(self, address):
241
+ """Get wallet by address from wallets collection"""
242
+ return self.wallets.get(address)
243
+
244
+ def list_wallets(self):
245
+ """List all wallets in collection"""
246
+ return list(self.wallets.keys())
247
+
248
+ def get_current_wallet_info(self):
249
+ """Get current wallet information"""
250
+ if not self.current_wallet_address:
251
+ return None
252
+ return self.wallets.get(self.current_wallet_address)
253
+
254
+ def save_to_file(self, filename=None):
255
+ """Save wallet to file"""
256
+ if not self.data_dir:
257
+ return False
258
+
259
+ if filename is None:
260
+ filename = f"wallet_{self.address}.json"
261
+
262
+ filepath = os.path.join(self.data_dir, filename)
263
+
264
+ try:
265
+ # Ensure directory exists
266
+ os.makedirs(self.data_dir, exist_ok=True)
267
+
268
+ # Prepare encrypted private key for serialization
269
+ encrypted_key_data = None
270
+ if self.encrypted_private_key:
271
+ # Ensure it's bytes before encoding
272
+ if isinstance(self.encrypted_private_key, bytes):
273
+ encrypted_key_data = base64.b64encode(self.encrypted_private_key).decode('utf-8')
274
+ else:
275
+ encrypted_key_data = base64.b64encode(self.encrypted_private_key.encode()).decode('utf-8')
276
+
277
+ # Prepare wallets for serialization (remove any non-serializable data)
278
+ serializable_wallets = {}
279
+ for addr, wallet_info in self.wallets.items():
280
+ serializable_wallet = wallet_info.copy()
281
+ # Ensure encrypted_private_key is serializable
282
+ if serializable_wallet.get('encrypted_private_key') and isinstance(serializable_wallet['encrypted_private_key'], bytes):
283
+ serializable_wallet['encrypted_private_key'] = base64.b64encode(
284
+ serializable_wallet['encrypted_private_key']
285
+ ).decode('utf-8')
286
+ serializable_wallets[addr] = serializable_wallet
287
+
288
+ wallet_data = {
289
+ 'address': self.address,
290
+ 'balance': self.balance,
291
+ 'available_balance': self.available_balance,
292
+ 'created': self.created,
293
+ 'public_key': self.public_key,
294
+ 'encrypted_private_key': encrypted_key_data,
295
+ 'label': self.label,
296
+ 'is_locked': self.is_locked,
297
+ 'wallets': serializable_wallets,
298
+ 'current_wallet_address': self.current_wallet_address
299
+ }
300
+
301
+ with open(filepath, 'w') as f:
302
+ json.dump(wallet_data, f, indent=2)
303
+ return True
304
+ except Exception as e:
305
+ print(f"Error saving wallet: {e}")
306
+ import traceback
307
+ traceback.print_exc()
308
+ return False
309
+
310
+ def load_from_file(self, filename, password=None):
311
+ """Load wallet from file"""
312
+ if not self.data_dir:
313
+ return False
314
+
315
+ filepath = os.path.join(self.data_dir, filename)
316
+
317
+ try:
318
+ with open(filepath, 'r') as f:
319
+ wallet_data = json.load(f)
320
+
321
+ # Load wallets collection
322
+ self.wallets = wallet_data.get('wallets', {})
323
+
324
+ # Load current wallet address
325
+ self.current_wallet_address = wallet_data.get('current_wallet_address')
326
+
327
+ # If we have a current wallet, load its data
328
+ if self.current_wallet_address and self.current_wallet_address in self.wallets:
329
+ current_wallet_data = self.wallets[self.current_wallet_address]
330
+ self._set_current_wallet(current_wallet_data)
331
+
332
+ # Handle encrypted private key
333
+ encrypted_key = wallet_data.get('encrypted_private_key')
334
+ if encrypted_key:
335
+ self.encrypted_private_key = base64.b64decode(encrypted_key.encode())
336
+ # Also update in wallets collection
337
+ if self.current_wallet_address in self.wallets:
338
+ self.wallets[self.current_wallet_address]['encrypted_private_key'] = self.encrypted_private_key
339
+
340
+ # If password provided and we have encrypted key, unlock
341
+ if password and self.encrypted_private_key and self.current_wallet_address:
342
+ return self.unlock_wallet(self.current_wallet_address, password)
343
+
344
+ return True
345
+ except Exception as e:
346
+ print(f"Error loading wallet: {e}")
347
+ return False
gtx/__init__.py ADDED
File without changes
gtx/bill_registry.py ADDED
@@ -0,0 +1,122 @@
1
+ import os
2
+ import json
3
+ import sqlite3
4
+ from typing import Dict, List, Optional
5
+
6
+ class BillRegistry:
7
+ """Manages bill database with verification links and metadata"""
8
+
9
+ def __init__(self, db_path=None):
10
+ self.db_path = db_path or os.path.join(os.path.expanduser("~"), ".luna_wallet", "bills.db")
11
+ os.makedirs(os.path.dirname(self.db_path), exist_ok=True)
12
+ self._init_database()
13
+
14
+ def _init_database(self):
15
+ """Initialize bill database"""
16
+ conn = sqlite3.connect(self.db_path)
17
+ cursor = conn.cursor()
18
+
19
+ cursor.execute('''
20
+ CREATE TABLE IF NOT EXISTS bills (
21
+ bill_serial TEXT PRIMARY KEY,
22
+ denomination INTEGER,
23
+ user_address TEXT,
24
+ hash TEXT,
25
+ mining_time REAL,
26
+ difficulty INTEGER,
27
+ luna_value REAL,
28
+ timestamp REAL,
29
+ verification_url TEXT,
30
+ image_url TEXT,
31
+ metadata TEXT,
32
+ status TEXT DEFAULT 'active'
33
+ )
34
+ ''')
35
+
36
+ conn.commit()
37
+ conn.close()
38
+
39
+ def register_bill(self, bill_info):
40
+ """Register a new bill in the database"""
41
+ conn = sqlite3.connect(self.db_path)
42
+ cursor = conn.cursor()
43
+
44
+ # Generate verification URL
45
+ verification_url = f"https://bank.linglin.art/verify/{bill_info['hash']}"
46
+ image_url = f"https://bank.linglin.art/bills/{bill_info['bill_serial']}.png"
47
+
48
+ cursor.execute('''
49
+ INSERT OR REPLACE INTO bills
50
+ (bill_serial, denomination, user_address, hash, mining_time,
51
+ difficulty, luna_value, timestamp, verification_url, image_url, metadata)
52
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
53
+ ''', (
54
+ bill_info['bill_serial'],
55
+ bill_info['denomination'],
56
+ bill_info['user_address'],
57
+ bill_info['hash'],
58
+ bill_info['mining_time'],
59
+ bill_info['difficulty'],
60
+ bill_info['luna_value'],
61
+ bill_info['timestamp'],
62
+ verification_url,
63
+ image_url,
64
+ json.dumps(bill_info.get('bill_data', {}))
65
+ ))
66
+
67
+ conn.commit()
68
+ conn.close()
69
+
70
+ def get_bill(self, bill_serial):
71
+ """Retrieve bill information"""
72
+ conn = sqlite3.connect(self.db_path)
73
+ cursor = conn.cursor()
74
+
75
+ cursor.execute('SELECT * FROM bills WHERE bill_serial = ?', (bill_serial,))
76
+ result = cursor.fetchone()
77
+ conn.close()
78
+
79
+ if result:
80
+ return {
81
+ 'bill_serial': result[0],
82
+ 'denomination': result[1],
83
+ 'user_address': result[2],
84
+ 'hash': result[3],
85
+ 'mining_time': result[4],
86
+ 'difficulty': result[5],
87
+ 'luna_value': result[6],
88
+ 'timestamp': result[7],
89
+ 'verification_url': result[8],
90
+ 'image_url': result[9],
91
+ 'metadata': json.loads(result[10]) if result[10] else {},
92
+ 'status': result[11]
93
+ }
94
+ return None
95
+
96
+ def get_user_bills(self, user_address):
97
+ """Get all bills for a user"""
98
+ conn = sqlite3.connect(self.db_path)
99
+ cursor = conn.cursor()
100
+
101
+ cursor.execute('SELECT * FROM bills WHERE user_address = ? ORDER BY timestamp DESC', (user_address,))
102
+ results = cursor.fetchall()
103
+ conn.close()
104
+
105
+ bills = []
106
+ for result in results:
107
+ bills.append({
108
+ 'bill_serial': result[0],
109
+ 'denomination': result[1],
110
+ 'user_address': result[2],
111
+ 'hash': result[3],
112
+ 'mining_time': result[4],
113
+ 'difficulty': result[5],
114
+ 'luna_value': result[6],
115
+ 'timestamp': result[7],
116
+ 'verification_url': result[8],
117
+ 'image_url': result[9],
118
+ 'metadata': json.loads(result[10]) if result[10] else {},
119
+ 'status': result[11]
120
+ })
121
+
122
+ return bills