diff --git a/FireWalletBrowser.bsdesign b/FireWalletBrowser.bsdesign index 2c9d800..e4d7098 100644 Binary files a/FireWalletBrowser.bsdesign and b/FireWalletBrowser.bsdesign differ diff --git a/account.py b/account.py index f98db89..4427a61 100644 --- a/account.py +++ b/account.py @@ -109,8 +109,7 @@ def createWallet(account: str, password: str): } # Create the account # Python wrapper doesn't support this yet - response = requests.put( - f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}") + response = requests.put(get_wallet_api_url(f"wallet/{account}")) if response.status_code != 200: return { "error": { @@ -123,7 +122,7 @@ def createWallet(account: str, password: str): seed = seed['mnemonic']['phrase'] # Encrypt the wallet (python wrapper doesn't support this yet) - response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/passphrase", + response = requests.post(get_wallet_api_url(f"/wallet/{account}/passphrase"), json={"passphrase": password}) return { @@ -147,8 +146,7 @@ def importWallet(account: str, password: str, seed: str): "mnemonic": seed, } - response = requests.put( - f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}", json=data) + response = requests.put(get_wallet_api_url(f"/wallet/{account}"), json=data) if response.status_code != 200: return { "error": { @@ -186,7 +184,6 @@ def selectWallet(account: str): } } - def getBalance(account: str): # Get the total balance info = hsw.getBalance('default', account) @@ -251,11 +248,9 @@ def getPendingTX(account: str): def getDomains(account, own=True): if own: - response = requests.get( - f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/name?own=true") + response = requests.get(get_wallet_api_url(f"/wallet/{account}/name?own=true")) else: - response = requests.get( - f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/name") + response = requests.get(get_wallet_api_url(f"/wallet/{account}/name")) info = response.json() if SHOW_EXPIRED: @@ -339,11 +334,9 @@ def getTransactions(account, page=1, limit=100): lastTX = getTXFromPage(account, page-1, limit) if lastTX: - response = requests.get( - f'http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/tx/history?reverse=true&limit={limit}&after={lastTX}') + response = requests.get(get_wallet_api_url(f"/wallet/{account}/tx/history?reverse=true&limit={limit}&after={lastTX}")) elif page == 1: - response = requests.get( - f'http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/tx/history?reverse=true&limit={limit}') + response = requests.get(get_wallet_api_url(f"/wallet/{account}/tx/history?reverse=true&limit={limit}")) else: return [] @@ -383,7 +376,7 @@ def check_address(address: str, allow_name: bool = True, return_address: bool = return check_hip2(address[1:]) # Check if the address is a valid HNS address - response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_NODE_PORT}", json={ + response = requests.post(get_node_api_url(), json={ "method": "validateaddress", "params": [address] }).json() @@ -431,8 +424,6 @@ def send(account, address, amount): response = hsw.rpc_walletPassphrase(password, 10) # Unlock the account - # response = requests.post(f"http://x:{APIKEY}@{ip}:{HSD_WALLET_PORT}/wallet/{account_name}/unlock", - # json={"passphrase": password,"timeout": 10}) if response['error'] is not None: if response['error']['message'] != "Wallet is not encrypted.": return { @@ -454,10 +445,19 @@ def send(account, address, amount): def isOwnDomain(account, name: str): - domains = getDomains(account) - for domain in domains: - if domain['name'] == name: - return True + # Get domain + domain_info = getDomain(name) + owner = getAddressFromCoin(domain_info['info']['owner']['hash'],domain_info['info']['owner']['index']) + # Select the account + hsw.rpc_selectWallet(account) + account = hsw.rpc_getAccount(owner) + + if 'error' in account and account['error'] is not None: + return False + if 'result' not in account: + return False + if account['result'] == 'default': + return True return False @@ -474,20 +474,14 @@ def getDomain(domain: str): def getAddressFromCoin(coinhash: str, coinindex = 0): # Get the address from the hash - response = requests.get(f"http://x:{HSD_API}@{HSD_IP}:{HSD_NODE_PORT}/coin/{coinhash}/{coinindex}") + response = requests.get(get_node_api_url(f"coin/{coinhash}/{coinindex}")) if response.status_code != 200: - return { - "error": { - "message": "Error getting address from coin" - } - } + print(f"Error getting address from coin: {response.text}") + return "No Owner" data = response.json() if 'address' not in data: - return { - "error": { - "message": "Error getting address from coin" - } - } + print(json.dumps(data, indent=4)) + return "No Owner" return data['address'] @@ -783,7 +777,7 @@ def revealAll(account): } } - return requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}", json={"method": "sendbatch", "params": [[["REVEAL"]]]}).json() + return requests.post(get_wallet_api_url(), json={"method": "sendbatch", "params": [[["REVEAL"]]]}).json() except Exception as e: return { "error": { @@ -817,7 +811,7 @@ def redeemAll(account): } } - return requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}", json={"method": "sendbatch", "params": [[["REDEEM"]]]}).json() + return requests.post(get_wallet_api_url(), json={"method": "sendbatch", "params": [[["REDEEM"]]]}).json() except Exception as e: return { "error": { @@ -1089,7 +1083,7 @@ def sendBatch(account, batch): "message": response['error']['message'] } } - response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}", json={ + response = requests.post(get_wallet_api_url(), json={ "method": "sendbatch", "params": [batch] }).json() @@ -1138,7 +1132,7 @@ def createBatch(account, batch): "message": response['error']['message'] } } - response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}", json={ + response = requests.post(get_wallet_api_url(), json={ "method": "createbatch", "params": [batch] }).json() @@ -1198,7 +1192,7 @@ def zapTXs(account): } try: - response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account_name}/zap", + response = requests.post(get_wallet_api_url(f"/wallet/{account_name}/zap"), json={"age": age, "account": "default" }) @@ -1327,3 +1321,25 @@ def generateReport(account, format="{name},{expiry},{value},{maxBid}"): def convertHNS(value: int): return value/1000000 + return value/1000000 + + +def get_node_api_url(path=''): + """Construct a URL for the HSD node API.""" + base_url = f"http://x:{HSD_API}@{HSD_IP}:{HSD_NODE_PORT}" + if path: + # Ensure path starts with a slash if it's not empty + if not path.startswith('/'): + path = f'/{path}' + return f"{base_url}{path}" + return base_url + +def get_wallet_api_url(path=''): + """Construct a URL for the HSD wallet API.""" + base_url = f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}" + if path: + # Ensure path starts with a slash if it's not empty + if not path.startswith('/'): + path = f'/{path}' + return f"{base_url}{path}" + return base_url diff --git a/main.py b/main.py index a3fab1d..e263000 100644 --- a/main.py +++ b/main.py @@ -32,6 +32,30 @@ revokeCheck = random.randint(100000,999999) THEME = os.getenv("THEME") + +def blocks_to_time(blocks: int) -> str: + """ + Convert blocks to time in a human-readable format. + Blocks are mined approximately every 10 minutes. + """ + if blocks < 0: + return "Invalid time" + + if blocks < 6: + return f"{blocks * 10} mins" + elif blocks < 144: + hours = blocks // 6 + minutes = (blocks % 6) * 10 + return f"{hours} hrs {minutes} mins" + else: + days = blocks // 144 + hours = (blocks % 144) // 6 + return f"{days} days {hours} hrs" + + + + + @app.route('/') def index(): # Check if the user is logged in @@ -445,10 +469,11 @@ def search(): state="AVAILABLE", next="Available Now",plugins=plugins) state = domain['info']['state'] + stats = domain['info']['stats'] if state == 'CLOSED': if domain['info']['registered']: state = 'REGISTERED' - expires = domain['info']['stats']['daysUntilExpire'] + expires = stats['daysUntilExpire'] next = f"Expires in ~{expires} days" else: state = 'AVAILABLE' @@ -456,11 +481,11 @@ def search(): elif state == "REVOKED": next = "Available Now" elif state == 'OPENING': - next = "Bidding opens in ~" + str(domain['info']['stats']['blocksUntilBidding']) + " blocks" + next = f"Bidding opens in {str(stats['blocksUntilBidding'])} blocks (~{blocks_to_time(stats['blocksUntilBidding'])})" elif state == 'BIDDING': - next = "Reveal in ~" + str(domain['info']['stats']['blocksUntilReveal']) + " blocks" + next = f"Reveal in {str(stats['blocksUntilReveal'])} blocks (~{blocks_to_time(stats['blocksUntilReveal'])})" elif state == 'REVEAL': - next = "Reveal ends in ~" + str(domain['info']['stats']['blocksUntilClose']) + " blocks" + next = f"Reveal ends in {str(stats['blocksUntilClose'])} blocks (~{blocks_to_time(stats['blocksUntilClose'])})" @@ -501,11 +526,8 @@ def manage(domain: str): return redirect("/logout") domain = domain.lower() - - own_domains = account_module.getDomains(account) - own_domains = [x['name'] for x in own_domains] - own_domains = [x.lower() for x in own_domains] - if domain not in own_domains: + + if not account_module.isOwnDomain(account, domain): return redirect("/search?q=" + domain) domain_info = account_module.getDomain(domain) @@ -514,7 +536,10 @@ def manage(domain: str): rendered=renderDomain(domain), domain=domain, error=domain_info['error']) - expiry = domain_info['info']['stats']['daysUntilExpire'] + if domain_info['info'] is not None and 'stats' in domain_info['info'] and 'daysUntilExpire' in domain_info['info']['stats']: + expiry = domain_info['info']['stats']['daysUntilExpire'] + else: + expiry = "Unknown" dns = account_module.getDNS(domain) raw_dns = str(dns).replace("'",'"') dns = render.dns(dns) @@ -900,7 +925,7 @@ def auction(domain): reveal['bid'] = revealInfo bids = render.bids(bids,reveals) - + stats = domainInfo['info']['stats'] if 'stats' in domainInfo['info'] else {} if state == 'CLOSED': if not domainInfo['info']['registered']: if account_module.isOwnDomain(account,domain): @@ -928,11 +953,21 @@ def auction(domain): next = "Available Now" next_action = f'Open Auction' elif state == 'OPENING': - next = "Bidding opens in ~" + str(domainInfo['info']['stats']['blocksUntilBidding']) + " blocks" + next = f"Bidding opens in {str(stats['blocksUntilBidding'])} blocks (~{blocks_to_time(stats['blocksUntilBidding'])})" elif state == 'BIDDING': - next = "Reveal in ~" + str(domainInfo['info']['stats']['blocksUntilReveal']) + " blocks" + next = f"Reveal in {stats['blocksUntilReveal']} blocks (~{blocks_to_time(stats['blocksUntilReveal'])})" + if stats['blocksUntilReveal'] == 1: + next += "
Bidding no longer possible" + elif stats['blocksUntilReveal'] == 2: + next += "
LAST CHANCE TO BID" + elif stats['blocksUntilReveal'] == 3: + next += f"
Next block is last chance to bid" + elif stats['blocksUntilReveal'] < 6: + next += f"
Last chance to bid in {stats['blocksUntilReveal']-2} blocks" + + elif state == 'REVEAL': - next = "Reveal ends in ~" + str(domainInfo['info']['stats']['blocksUntilClose']) + " blocks" + next = f"Reveal ends in {str(stats['blocksUntilClose'])} blocks (~{blocks_to_time(stats['blocksUntilClose'])})" next_action = f'Reveal All' message = '' @@ -1070,7 +1105,7 @@ def reveal_auction(domain): return redirect("/logout") domain = domain.lower() - response = account_module(request.cookies.get("account"),domain) + response = account_module.revealAuction(request.cookies.get("account"),domain) if 'error' in response: return redirect("/auction/" + domain + "?message=" + response['error']['message']) return redirect("/success?tx=" + response['hash']) diff --git a/render.py b/render.py index c3cdd39..ff79dd9 100644 --- a/render.py +++ b/render.py @@ -210,7 +210,6 @@ def dns(data, edit=False): html_output = "" index = 0 for entry in data: - print(entry, flush=True) html_output += f"{entry['type']}\n" if entry['type'] != 'DS' and not entry['type'].startswith("GLUE") and not entry['type'].startswith("SYNTH"): @@ -279,30 +278,61 @@ def timestamp_to_readable_time(timestamp): return readable_time def bids(bids,reveals): - html = '' + # Create a list to hold bid data for sorting + bid_data = [] + + # Prepare data for sorting for bid in bids: - lockup = bid['lockup'] - lockup = lockup / 1000000 - html += "" - html += f"{lockup:,.2f} HNS" + lockup = bid['lockup'] / 1000000 revealed = False + value = 0 + + # Check if this bid has been revealed for reveal in reveals: if reveal['bid'] == bid['prevout']['hash']: revealed = True - value = reveal['value'] - value = value / 1000000 - html += f"{value:,.2f} HNS" - bidValue = lockup - value - html += f"{bidValue:,.2f} HNS" + value = reveal['value'] / 1000000 break - if not revealed: + + # Store all relevant information for sorting and display + bid_data.append({ + 'bid': bid, + 'lockup': lockup, + 'revealed': revealed, + 'value': value, + 'sort_value': value if revealed else lockup # Use value for sorting if revealed, otherwise lockup + }) + + # Sort by the sort_value in descending order (highest first) + bid_data.sort(key=lambda x: x['sort_value'], reverse=True) + + # Generate HTML from sorted data + html = '' + for data in bid_data: + bid = data['bid'] + lockup = data['lockup'] + revealed = data['revealed'] + value = data['value'] + + html += "" + html += f"{lockup:,.2f} HNS" + + if revealed: + bidValue = lockup - value + html += f"{value:,.2f} HNS" + html += f"{bidValue:,.2f} HNS" + else: html += f"Hidden until reveal" html += f"Hidden until reveal" + if bid['own']: html += "You" else: - html += "Unknown" + html += f"Unknown" + + html += f"Bid TX 🔗" html += "" + return html diff --git a/templates/auction.html b/templates/auction.html index bb41036..cd5b559 100644 --- a/templates/auction.html +++ b/templates/auction.html @@ -68,7 +68,7 @@
{{next_action|safe}}

{{rendered}}

-
{{next}}
+
{{next | safe}}
@@ -93,6 +93,7 @@ Bid Blind Owner +