5 Commits

Author SHA1 Message Date
d49327b925 feat: Do some more optimization from AI
All checks were successful
Build Docker / Build Image (push) Successful in 2m48s
Double check this all works
2025-07-12 11:51:24 +10:00
984e193ab7 feat: Try some more optimizations
All checks were successful
Build Docker / Build Image (push) Successful in 2m33s
2025-07-11 13:53:55 +10:00
70f0b2cf0e feat: Optimize some of the auction routes 2025-07-11 13:45:47 +10:00
33fc206c39 feat: Add red warning on auction page for potential outbids 2025-07-11 13:26:14 +10:00
4d4e0bf1e7 feat: Add api route for possible outbidded domains
All checks were successful
Build Docker / Build Image (push) Successful in 2m54s
2025-07-10 18:15:26 +10:00
5 changed files with 78 additions and 207 deletions

Binary file not shown.

View File

@@ -110,7 +110,8 @@ def createWallet(account: str, password: str):
} }
# Create the account # Create the account
# Python wrapper doesn't support this yet # Python wrapper doesn't support this yet
response = requests.put(get_wallet_api_url(f"wallet/{account}")) response = requests.put(
f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}")
if response.status_code != 200: if response.status_code != 200:
return { return {
"error": { "error": {
@@ -123,7 +124,7 @@ def createWallet(account: str, password: str):
seed = seed['mnemonic']['phrase'] seed = seed['mnemonic']['phrase']
# Encrypt the wallet (python wrapper doesn't support this yet) # Encrypt the wallet (python wrapper doesn't support this yet)
response = requests.post(get_wallet_api_url(f"/wallet/{account}/passphrase"), response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/passphrase",
json={"passphrase": password}) json={"passphrase": password})
return { return {
@@ -147,7 +148,8 @@ def importWallet(account: str, password: str, seed: str):
"mnemonic": seed, "mnemonic": seed,
} }
response = requests.put(get_wallet_api_url(f"/wallet/{account}"), json=data) response = requests.put(
f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}", json=data)
if response.status_code != 200: if response.status_code != 200:
return { return {
"error": { "error": {
@@ -185,6 +187,7 @@ def selectWallet(account: str):
} }
} }
def getBalance(account: str): def getBalance(account: str):
# Get the total balance # Get the total balance
info = hsw.getBalance('default', account) info = hsw.getBalance('default', account)
@@ -249,9 +252,11 @@ def getPendingTX(account: str):
def getDomains(account, own=True): def getDomains(account, own=True):
if own: if own:
response = requests.get(get_wallet_api_url(f"/wallet/{account}/name?own=true")) response = requests.get(
f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/name?own=true")
else: else:
response = requests.get(get_wallet_api_url(f"/wallet/{account}/name")) response = requests.get(
f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/name")
info = response.json() info = response.json()
if SHOW_EXPIRED: if SHOW_EXPIRED:
@@ -335,9 +340,11 @@ def getTransactions(account, page=1, limit=100):
lastTX = getTXFromPage(account, page-1, limit) lastTX = getTXFromPage(account, page-1, limit)
if lastTX: if lastTX:
response = requests.get(get_wallet_api_url(f"/wallet/{account}/tx/history?reverse=true&limit={limit}&after={lastTX}")) response = requests.get(
f'http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/tx/history?reverse=true&limit={limit}&after={lastTX}')
elif page == 1: elif page == 1:
response = requests.get(get_wallet_api_url(f"/wallet/{account}/tx/history?reverse=true&limit={limit}")) response = requests.get(
f'http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/tx/history?reverse=true&limit={limit}')
else: else:
return [] return []
@@ -377,7 +384,7 @@ def check_address(address: str, allow_name: bool = True, return_address: bool =
return check_hip2(address[1:]) return check_hip2(address[1:])
# Check if the address is a valid HNS address # Check if the address is a valid HNS address
response = requests.post(get_node_api_url(), json={ response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_NODE_PORT}", json={
"method": "validateaddress", "method": "validateaddress",
"params": [address] "params": [address]
}).json() }).json()
@@ -425,6 +432,8 @@ def send(account, address, amount):
response = hsw.rpc_walletPassphrase(password, 10) response = hsw.rpc_walletPassphrase(password, 10)
# Unlock the account # 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'] is not None:
if response['error']['message'] != "Wallet is not encrypted.": if response['error']['message'] != "Wallet is not encrypted.":
return { return {
@@ -446,18 +455,9 @@ def send(account, address, amount):
def isOwnDomain(account, name: str): def isOwnDomain(account, name: str):
# Get domain domains = getDomains(account)
domain_info = getDomain(name) for domain in domains:
owner = getAddressFromCoin(domain_info['info']['owner']['hash'],domain_info['info']['owner']['index']) if domain['name'] == name:
# 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 True
return False return False
@@ -475,14 +475,20 @@ def getDomain(domain: str):
def getAddressFromCoin(coinhash: str, coinindex = 0): def getAddressFromCoin(coinhash: str, coinindex = 0):
# Get the address from the hash # Get the address from the hash
response = requests.get(get_node_api_url(f"coin/{coinhash}/{coinindex}")) response = requests.get(f"http://x:{HSD_API}@{HSD_IP}:{HSD_NODE_PORT}/coin/{coinhash}/{coinindex}")
if response.status_code != 200: if response.status_code != 200:
print(f"Error getting address from coin: {response.text}") return {
return "No Owner" "error": {
"message": "Error getting address from coin"
}
}
data = response.json() data = response.json()
if 'address' not in data: if 'address' not in data:
print(json.dumps(data, indent=4)) return {
return "No Owner" "error": {
"message": "Error getting address from coin"
}
}
return data['address'] return data['address']
@@ -957,7 +963,7 @@ def revealAll(account):
} }
} }
return requests.post(get_wallet_api_url(), json={"method": "sendbatch", "params": [[["REVEAL"]]]}).json() return requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}", json={"method": "sendbatch", "params": [[["REVEAL"]]]}).json()
except Exception as e: except Exception as e:
return { return {
"error": { "error": {
@@ -991,7 +997,7 @@ def redeemAll(account):
} }
} }
return requests.post(get_wallet_api_url(), json={"method": "sendbatch", "params": [[["REDEEM"]]]}).json() return requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}", json={"method": "sendbatch", "params": [[["REDEEM"]]]}).json()
except Exception as e: except Exception as e:
return { return {
"error": { "error": {
@@ -1276,29 +1282,11 @@ def _execute_batch_operation(account_name, batch, operation_type="sendbatch"):
# Make the batch request # Make the batch request
try: try:
response = requests.post( response = requests.post(
get_wallet_api_url(), f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}",
json={"method": operation_type, "params": [batch]}, json={"method": operation_type, "params": [batch]},
timeout=30 # Add timeout to prevent hanging timeout=30 # Add timeout to prevent hanging
).json() ).json()
if response['error'] is not None:
return {
"error": {
"message": response['error']['message']
}
}
response = hsw.rpc_walletPassphrase(password, 10)
if response['error'] is not None:
if response['error']['message'] != "Wallet is not encrypted.":
return {
"error": {
"message": response['error']['message']
}
}
response = requests.post(get_wallet_api_url(), json={
"method": "sendbatch",
"params": [batch]
}).json()
if response['error'] is not None: if response['error'] is not None:
return response return response
if 'result' not in response: if 'result' not in response:
@@ -1339,44 +1327,6 @@ def createBatch(account, batch):
return _execute_batch_operation(account_name, batch, "createbatch") return _execute_batch_operation(account_name, batch, "createbatch")
try:
response = hsw.rpc_selectWallet(account_name)
if response['error'] is not None:
return {
"error": {
"message": response['error']['message']
}
}
response = hsw.rpc_walletPassphrase(password, 10)
if response['error'] is not None:
if response['error']['message'] != "Wallet is not encrypted.":
return {
"error": {
"message": response['error']['message']
}
}
response = requests.post(get_wallet_api_url(), json={
"method": "createbatch",
"params": [batch]
}).json()
if response['error'] is not None:
return response
if 'result' not in response:
return {
"error": {
"message": "No result"
}
}
return response['result']
except Exception as e:
return {
"error": {
"message": str(e)
}
}
# region settingsAPIs # region settingsAPIs
def rescan(): def rescan():
try: try:
@@ -1415,7 +1365,7 @@ def zapTXs(account):
} }
try: try:
response = requests.post(get_wallet_api_url(f"/wallet/{account_name}/zap"), response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account_name}/zap",
json={"age": age, json={"age": age,
"account": "default" "account": "default"
}) })
@@ -1544,25 +1494,3 @@ def generateReport(account, format="{name},{expiry},{value},{maxBid}"):
def convertHNS(value: int): def convertHNS(value: int):
return value/1000000 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

76
main.py
View File

@@ -32,30 +32,6 @@ revokeCheck = random.randint(100000,999999)
THEME = os.getenv("THEME") 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"
# Add a cache for transactions with a timeout # Add a cache for transactions with a timeout
tx_cache = {} tx_cache = {}
TX_CACHE_TIMEOUT = 60*5 # Cache timeout in seconds TX_CACHE_TIMEOUT = 60*5 # Cache timeout in seconds
@@ -498,11 +474,10 @@ def search():
state="AVAILABLE", next="Available Now",plugins=plugins) state="AVAILABLE", next="Available Now",plugins=plugins)
state = domain['info']['state'] state = domain['info']['state']
stats = domain['info']['stats']
if state == 'CLOSED': if state == 'CLOSED':
if domain['info']['registered']: if domain['info']['registered']:
state = 'REGISTERED' state = 'REGISTERED'
expires = stats['daysUntilExpire'] expires = domain['info']['stats']['daysUntilExpire']
next = f"Expires in ~{expires} days" next = f"Expires in ~{expires} days"
else: else:
state = 'AVAILABLE' state = 'AVAILABLE'
@@ -510,11 +485,11 @@ def search():
elif state == "REVOKED": elif state == "REVOKED":
next = "Available Now" next = "Available Now"
elif state == 'OPENING': elif state == 'OPENING':
next = f"Bidding opens in {str(stats['blocksUntilBidding'])} blocks (~{blocks_to_time(stats['blocksUntilBidding'])})" next = "Bidding opens in ~" + str(domain['info']['stats']['blocksUntilBidding']) + " blocks"
elif state == 'BIDDING': elif state == 'BIDDING':
next = f"Reveal in {str(stats['blocksUntilReveal'])} blocks (~{blocks_to_time(stats['blocksUntilReveal'])})" next = "Reveal in ~" + str(domain['info']['stats']['blocksUntilReveal']) + " blocks"
elif state == 'REVEAL': elif state == 'REVEAL':
next = f"Reveal ends in {str(stats['blocksUntilClose'])} blocks (~{blocks_to_time(stats['blocksUntilClose'])})" next = "Reveal ends in ~" + str(domain['info']['stats']['blocksUntilClose']) + " blocks"
@@ -530,7 +505,10 @@ def search():
dns = account_module.getDNS(search_term) dns = account_module.getDNS(search_term)
if account_module.isOwnDomain(account, search_term): 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 search_term in own_domains:
owner = "You" owner = "You"
dns = render.dns(dns) dns = render.dns(dns)
@@ -553,7 +531,10 @@ def manage(domain: str):
domain = domain.lower() domain = domain.lower()
if not account_module.isOwnDomain(account, domain): 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:
return redirect("/search?q=" + domain) return redirect("/search?q=" + domain)
domain_info = account_module.getDomain(domain) domain_info = account_module.getDomain(domain)
@@ -562,10 +543,7 @@ def manage(domain: str):
rendered=renderDomain(domain), rendered=renderDomain(domain),
domain=domain, error=domain_info['error']) domain=domain, error=domain_info['error'])
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'] expiry = domain_info['info']['stats']['daysUntilExpire']
else:
expiry = "Unknown"
dns = account_module.getDNS(domain) dns = account_module.getDNS(domain)
raw_dns = str(dns).replace("'",'"') raw_dns = str(dns).replace("'",'"')
dns = render.dns(dns) dns = render.dns(dns)
@@ -725,7 +703,10 @@ def editPage(domain: str):
domain = domain.lower() domain = domain.lower()
if not account_module.isOwnDomain(account, domain): 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:
return redirect("/search?q=" + domain) return redirect("/search?q=" + domain)
@@ -948,7 +929,7 @@ def auction(domain):
reveal['bid'] = revealInfo reveal['bid'] = revealInfo
bids = render.bids(bids,reveals) bids = render.bids(bids,reveals)
stats = domainInfo['info']['stats'] if 'stats' in domainInfo['info'] else {}
if state == 'CLOSED': if state == 'CLOSED':
if not domainInfo['info']['registered']: if not domainInfo['info']['registered']:
if account_module.isOwnDomain(account,domain): if account_module.isOwnDomain(account,domain):
@@ -967,27 +948,20 @@ def auction(domain):
expires = domainInfo['info']['stats']['daysUntilExpire'] expires = domainInfo['info']['stats']['daysUntilExpire']
next = f"Expires in ~{expires} days" next = f"Expires in ~{expires} days"
if account_module.isOwnDomain(account,domain): 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 search_term in own_domains:
next_action = f'<a href="/manage/{domain}">Manage</a>' next_action = f'<a href="/manage/{domain}">Manage</a>'
elif state == "REVOKED": elif state == "REVOKED":
next = "Available Now" next = "Available Now"
next_action = f'<a href="/auction/{domain}/open">Open Auction</a>' next_action = f'<a href="/auction/{domain}/open">Open Auction</a>'
elif state == 'OPENING': elif state == 'OPENING':
next = f"Bidding opens in {str(stats['blocksUntilBidding'])} blocks (~{blocks_to_time(stats['blocksUntilBidding'])})" next = "Bidding opens in ~" + str(domainInfo['info']['stats']['blocksUntilBidding']) + " blocks"
elif state == 'BIDDING': elif state == 'BIDDING':
next = f"Reveal in {stats['blocksUntilReveal']} blocks (~{blocks_to_time(stats['blocksUntilReveal'])})" next = "Reveal in ~" + str(domainInfo['info']['stats']['blocksUntilReveal']) + " blocks"
if stats['blocksUntilReveal'] == 1:
next += "<br>Bidding no longer possible"
elif stats['blocksUntilReveal'] == 2:
next += "<br>LAST CHANCE TO BID"
elif stats['blocksUntilReveal'] == 3:
next += f"<br>Next block is last chance to bid"
elif stats['blocksUntilReveal'] < 6:
next += f"<br>Last chance to bid in {stats['blocksUntilReveal']-2} blocks"
elif state == 'REVEAL': elif state == 'REVEAL':
next = f"Reveal ends in {str(stats['blocksUntilClose'])} blocks (~{blocks_to_time(stats['blocksUntilClose'])})" next = "Reveal ends in ~" + str(domainInfo['info']['stats']['blocksUntilClose']) + " blocks"
next_action = f'<a href="/auction/{domain}/reveal">Reveal All</a>' next_action = f'<a href="/auction/{domain}/reveal">Reveal All</a>'
message = '' message = ''
@@ -1125,7 +1099,7 @@ def reveal_auction(domain):
return redirect("/logout") return redirect("/logout")
domain = domain.lower() domain = domain.lower()
response = account_module.revealAuction(request.cookies.get("account"),domain) response = account_module(request.cookies.get("account"),domain)
if 'error' in response: if 'error' in response:
return redirect("/auction/" + domain + "?message=" + response['error']['message']) return redirect("/auction/" + domain + "?message=" + response['error']['message'])
return redirect("/success?tx=" + response['hash']) return redirect("/success?tx=" + response['hash'])

View File

@@ -210,6 +210,7 @@ def dns(data, edit=False):
html_output = "" html_output = ""
index = 0 index = 0
for entry in data: for entry in data:
print(entry, flush=True)
html_output += f"<tr><td>{entry['type']}</td>\n" html_output += f"<tr><td>{entry['type']}</td>\n"
if entry['type'] != 'DS' and not entry['type'].startswith("GLUE") and not entry['type'].startswith("SYNTH"): if entry['type'] != 'DS' and not entry['type'].startswith("GLUE") and not entry['type'].startswith("SYNTH"):
@@ -278,61 +279,30 @@ def timestamp_to_readable_time(timestamp):
return readable_time return readable_time
def bids(bids,reveals): def bids(bids,reveals):
# Create a list to hold bid data for sorting html = ''
bid_data = []
# Prepare data for sorting
for bid in bids: for bid in bids:
lockup = bid['lockup'] / 1000000 lockup = bid['lockup']
lockup = lockup / 1000000
html += "<tr>"
html += f"<td>{lockup:,.2f} HNS</td>"
revealed = False revealed = False
value = 0
# Check if this bid has been revealed
for reveal in reveals: for reveal in reveals:
if reveal['bid'] == bid['prevout']['hash']: if reveal['bid'] == bid['prevout']['hash']:
revealed = True revealed = True
value = reveal['value'] / 1000000 value = reveal['value']
break value = value / 1000000
# 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 += "<tr>"
html += f"<td>{lockup:,.2f} HNS</td>"
if revealed:
bidValue = lockup - value
html += f"<td>{value:,.2f} HNS</td>" html += f"<td>{value:,.2f} HNS</td>"
bidValue = lockup - value
html += f"<td>{bidValue:,.2f} HNS</td>" html += f"<td>{bidValue:,.2f} HNS</td>"
else: break
if not revealed:
html += f"<td>Hidden until reveal</td>" html += f"<td>Hidden until reveal</td>"
html += f"<td>Hidden until reveal</td>" html += f"<td>Hidden until reveal</td>"
if bid['own']: if bid['own']:
html += "<td>You</td>" html += "<td>You</td>"
else: else:
html += f"<td>Unknown</td>" html += "<td>Unknown</td>"
html += f"<td><a class='text-decoration-none' style='color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));' href='{TX_EXPLORER_URL}{bid['prevout']['hash']}'>Bid TX 🔗</a></td>"
html += "</tr>" html += "</tr>"
return html return html

View File

@@ -68,7 +68,7 @@
<div class="card-body"> <div class="card-body">
<div class="stick-right">{{next_action|safe}}</div> <div class="stick-right">{{next_action|safe}}</div>
<h4 class="card-title">{{rendered}}</h4> <h4 class="card-title">{{rendered}}</h4>
<h6 class="text-muted mb-2 card-subtitle">{{next | safe}}</h6> <h6 class="text-muted mb-2 card-subtitle">{{next}}</h6>
</div> </div>
</div> </div>
</div> </div>
@@ -93,7 +93,6 @@
<th>Bid</th> <th>Bid</th>
<th>Blind</th> <th>Blind</th>
<th>Owner</th> <th>Owner</th>
<th></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>