Compare commits
8 Commits
feat/aucti
...
feat/mempo
| Author | SHA1 | Date | |
|---|---|---|---|
|
6911e3663c
|
|||
|
eda690544d
|
|||
|
e67c178ea7
|
|||
|
631d558377
|
|||
|
1d5ed059b3
|
|||
|
747ac575fa
|
|||
|
e574933302
|
|||
|
c0f0dc5010
|
Binary file not shown.
462
account.py
462
account.py
@@ -7,7 +7,6 @@ import re
|
|||||||
import domainLookup
|
import domainLookup
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import concurrent.futures
|
|
||||||
|
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
@@ -461,6 +460,23 @@ def isOwnDomain(account, name: str):
|
|||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def isOwnPrevout(account, prevout: dict):
|
||||||
|
if 'hash' not in prevout or 'index' not in prevout:
|
||||||
|
return False
|
||||||
|
# Get the address from the prevout
|
||||||
|
address = getAddressFromCoin(prevout['hash'], prevout['index'])
|
||||||
|
# Select the account
|
||||||
|
hsw.rpc_selectWallet(account)
|
||||||
|
account = hsw.rpc_getAccount(address)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
def getDomain(domain: str):
|
def getDomain(domain: str):
|
||||||
# Get the domain
|
# Get the domain
|
||||||
@@ -612,202 +628,45 @@ def getWalletStatus():
|
|||||||
return "Error wallet ahead of node"
|
return "Error wallet ahead of node"
|
||||||
|
|
||||||
|
|
||||||
# Add a simple cache for bid data
|
|
||||||
_bid_cache = {}
|
|
||||||
_bid_cache_time = {}
|
|
||||||
_cache_duration = 300 # Increased cache duration to 5 minutes for bids
|
|
||||||
|
|
||||||
# Add domain info cache
|
|
||||||
_domain_info_cache = {}
|
|
||||||
_domain_info_time = {}
|
|
||||||
_domain_info_duration = 600 # Cache domain info for 10 minutes
|
|
||||||
|
|
||||||
# Add wallet authentication cache
|
|
||||||
_wallet_auth_cache = {}
|
|
||||||
_wallet_auth_time = {}
|
|
||||||
_wallet_auth_duration = 300 # Increased to 5 minutes for wallet auth
|
|
||||||
|
|
||||||
def getBids(account, domain="NONE"):
|
def getBids(account, domain="NONE"):
|
||||||
cache_key = f"{account}:{domain}"
|
if domain == "NONE":
|
||||||
current_time = time.time()
|
response = hsw.getWalletBids(account)
|
||||||
|
else:
|
||||||
# Return cached data if available and fresh
|
response = hsw.getWalletBidsByName(domain, account)
|
||||||
if cache_key in _bid_cache and current_time - _bid_cache_time.get(cache_key, 0) < _cache_duration:
|
# Add backup for bids with no value
|
||||||
return _bid_cache[cache_key]
|
bids = []
|
||||||
|
for bid in response:
|
||||||
try:
|
if 'value' not in bid:
|
||||||
if domain == "NONE":
|
bid['value'] = -1000000
|
||||||
response = hsw.getWalletBids(account)
|
|
||||||
else:
|
|
||||||
response = hsw.getWalletBidsByName(domain, account)
|
|
||||||
|
|
||||||
# Add backup for bids with no value
|
|
||||||
bids = []
|
|
||||||
for bid in response:
|
|
||||||
if 'value' not in bid:
|
|
||||||
bid['value'] = -1000000
|
|
||||||
|
|
||||||
# Backup for older HSD versions
|
|
||||||
if 'height' not in bid:
|
|
||||||
bid['height'] = 0
|
|
||||||
bids.append(bid)
|
|
||||||
|
|
||||||
# Cache the results
|
|
||||||
_bid_cache[cache_key] = bids
|
|
||||||
_bid_cache_time[cache_key] = current_time
|
|
||||||
|
|
||||||
return bids
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error fetching bids: {str(e)}")
|
|
||||||
return []
|
|
||||||
|
|
||||||
def _fetch_domain_info(domain):
|
# Backup for older HSD versions
|
||||||
"""Helper function to fetch domain info with caching"""
|
if 'height' not in bid:
|
||||||
current_time = time.time()
|
bid['height'] = 0
|
||||||
|
bids.append(bid)
|
||||||
# Check cache first
|
return bids
|
||||||
if (domain in _domain_info_cache and
|
|
||||||
current_time - _domain_info_time.get(domain, 0) < _domain_info_duration):
|
|
||||||
return _domain_info_cache[domain]
|
|
||||||
|
|
||||||
# Fetch domain info
|
|
||||||
domain_info = getDomain(domain)
|
|
||||||
|
|
||||||
# Store in cache
|
|
||||||
_domain_info_cache[domain] = domain_info
|
|
||||||
_domain_info_time[domain] = current_time
|
|
||||||
|
|
||||||
return domain_info
|
|
||||||
|
|
||||||
def _fetch_domain_batch(domains, max_workers=10):
|
|
||||||
"""Fetch multiple domains in parallel"""
|
|
||||||
results = {}
|
|
||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
|
||||||
# Create a mapping of futures to domains
|
|
||||||
future_to_domain = {executor.submit(_fetch_domain_info, domain): domain for domain in domains}
|
|
||||||
|
|
||||||
# Process as they complete
|
|
||||||
for future in concurrent.futures.as_completed(future_to_domain):
|
|
||||||
domain = future_to_domain[future]
|
|
||||||
try:
|
|
||||||
results[domain] = future.result()
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Error fetching domain {domain}: {str(e)}")
|
|
||||||
results[domain] = {"error": str(e)}
|
|
||||||
|
|
||||||
return results
|
|
||||||
|
|
||||||
def getPossibleOutbids(account):
|
|
||||||
# Get all bids
|
|
||||||
bids = getBids(account)
|
|
||||||
if not bids or 'error' in bids:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Get current height
|
|
||||||
current_height = getBlockHeight()
|
|
||||||
|
|
||||||
# Sort out bids older than 720 blocks and extract domain names
|
|
||||||
filtered_bids = []
|
|
||||||
domains_to_check = set()
|
|
||||||
|
|
||||||
for bid in bids:
|
|
||||||
if (current_height - bid['height']) <= 720:
|
|
||||||
filtered_bids.append(bid)
|
|
||||||
domains_to_check.add(bid['name'])
|
|
||||||
|
|
||||||
if not domains_to_check:
|
|
||||||
return []
|
|
||||||
|
|
||||||
# Fetch all domain info in parallel
|
|
||||||
domain_info_map = _fetch_domain_batch(domains_to_check)
|
|
||||||
|
|
||||||
# Pre-filter domains in bidding state
|
|
||||||
bidding_domains = {
|
|
||||||
domain: info for domain, info in domain_info_map.items()
|
|
||||||
if ('info' in info and 'state' in info['info'] and
|
|
||||||
info['info']['state'] == "BIDDING")
|
|
||||||
}
|
|
||||||
|
|
||||||
# Process the results
|
|
||||||
possible_outbids = []
|
|
||||||
processed_domains = set()
|
|
||||||
|
|
||||||
# Group bids by domain name for efficient processing
|
|
||||||
bids_by_domain = {}
|
|
||||||
for bid in filtered_bids:
|
|
||||||
domain = bid['name']
|
|
||||||
if domain not in bids_by_domain:
|
|
||||||
bids_by_domain[domain] = []
|
|
||||||
bids_by_domain[domain].append(bid)
|
|
||||||
|
|
||||||
# Analyze each domain in bidding state
|
|
||||||
for domain, info in bidding_domains.items():
|
|
||||||
if domain in processed_domains or domain not in bids_by_domain:
|
|
||||||
continue
|
|
||||||
|
|
||||||
processed_domains.add(domain)
|
|
||||||
|
|
||||||
# Get all bids for this domain in one call
|
|
||||||
domain_bids = getBids(account, domain)
|
|
||||||
|
|
||||||
# Find the highest bid we've made
|
|
||||||
our_highest_bid = max(
|
|
||||||
(bid['value'] for bid in domain_bids if bid.get("own", False)),
|
|
||||||
default=0
|
|
||||||
)
|
|
||||||
|
|
||||||
# Quick check if any unrevealed bids could outbid us
|
|
||||||
if any(
|
|
||||||
bid["lockup"] > our_highest_bid
|
|
||||||
for bid in domain_bids
|
|
||||||
if not bid.get("own", False) and bid.get('value', 0) == -1000000
|
|
||||||
):
|
|
||||||
possible_outbids.append(domain)
|
|
||||||
|
|
||||||
return possible_outbids
|
|
||||||
|
|
||||||
def getReveals(account, domain):
|
def getReveals(account, domain):
|
||||||
return hsw.getWalletRevealsByName(domain, account)
|
return hsw.getWalletRevealsByName(domain, account)
|
||||||
|
|
||||||
|
|
||||||
def getPendingReveals(account):
|
def getPendingReveals(account):
|
||||||
bids = getBids(account)
|
bids = getBids(account)
|
||||||
# Only get domains in REVEAL state to reduce API calls
|
domains = getDomains(account, False)
|
||||||
domains = [d for d in getDomains(account, False) if d['state'] == "REVEAL"]
|
|
||||||
|
|
||||||
if not domains: # Early return if no domains in REVEAL state
|
|
||||||
return []
|
|
||||||
|
|
||||||
pending = []
|
pending = []
|
||||||
|
for domain in domains:
|
||||||
# Create a dictionary for O(1) lookups
|
if domain['state'] == "REVEAL":
|
||||||
domain_names = {domain['name']: domain for domain in domains}
|
reveals = getReveals(account, domain['name'])
|
||||||
|
for bid in bids:
|
||||||
# Group bids by name to batch process reveals
|
if bid['name'] == domain['name']:
|
||||||
bids_by_name = {}
|
state_found = False
|
||||||
for bid in bids:
|
for reveal in reveals:
|
||||||
if bid['name'] in domain_names:
|
if reveal['own'] == True:
|
||||||
if bid['name'] not in bids_by_name:
|
if bid['value'] == reveal['value']:
|
||||||
bids_by_name[bid['name']] = []
|
state_found = True
|
||||||
bids_by_name[bid['name']].append(bid)
|
|
||||||
|
if not state_found:
|
||||||
# Fetch reveals for each domain once
|
pending.append(bid)
|
||||||
reveals_by_name = {}
|
|
||||||
for domain_name in bids_by_name:
|
|
||||||
reveals_by_name[domain_name] = getReveals(account, domain_name)
|
|
||||||
|
|
||||||
# Check each bid against the reveals
|
|
||||||
for domain_name, domain_bids in bids_by_name.items():
|
|
||||||
domain_reveals = reveals_by_name[domain_name]
|
|
||||||
for bid in domain_bids:
|
|
||||||
# Check if this bid has been revealed
|
|
||||||
bid_revealed = any(
|
|
||||||
reveal['own'] == True and bid['value'] == reveal['value']
|
|
||||||
for reveal in domain_reveals
|
|
||||||
)
|
|
||||||
|
|
||||||
if not bid_revealed:
|
|
||||||
pending.append(bid)
|
|
||||||
|
|
||||||
return pending
|
return pending
|
||||||
|
|
||||||
|
|
||||||
@@ -820,27 +679,20 @@ def getPendingRedeems(account, password):
|
|||||||
|
|
||||||
pending = []
|
pending = []
|
||||||
try:
|
try:
|
||||||
# Collect all nameHashes first
|
|
||||||
name_hashes = []
|
|
||||||
for output in tx['result']['outputs']:
|
for output in tx['result']['outputs']:
|
||||||
if output['covenant']['type'] != 5:
|
if output['covenant']['type'] != 5:
|
||||||
continue
|
continue
|
||||||
if output['covenant']['action'] != "REDEEM":
|
if output['covenant']['action'] != "REDEEM":
|
||||||
continue
|
continue
|
||||||
name_hashes.append(output['covenant']['items'][0])
|
nameHash = output['covenant']['items'][0]
|
||||||
|
# Try to get the name from hash
|
||||||
# Batch processing name hashes
|
name = hsd.rpc_getNameByHash(nameHash)
|
||||||
name_lookup = {}
|
|
||||||
for name_hash in name_hashes:
|
|
||||||
name = hsd.rpc_getNameByHash(name_hash)
|
|
||||||
if name['error']:
|
if name['error']:
|
||||||
pending.append(name_hash)
|
pending.append(nameHash)
|
||||||
else:
|
else:
|
||||||
pending.append(name['result'])
|
pending.append(name['result'])
|
||||||
name_lookup[name_hash] = name['result']
|
except:
|
||||||
|
print("Failed to parse redeems")
|
||||||
except Exception as e:
|
|
||||||
print(f"Failed to parse redeems: {str(e)}")
|
|
||||||
|
|
||||||
return pending
|
return pending
|
||||||
|
|
||||||
@@ -848,22 +700,13 @@ def getPendingRedeems(account, password):
|
|||||||
def getPendingRegisters(account):
|
def getPendingRegisters(account):
|
||||||
bids = getBids(account)
|
bids = getBids(account)
|
||||||
domains = getDomains(account, False)
|
domains = getDomains(account, False)
|
||||||
|
|
||||||
# Create dictionaries for O(1) lookups
|
|
||||||
bids_by_name = {}
|
|
||||||
for bid in bids:
|
|
||||||
if bid['name'] not in bids_by_name:
|
|
||||||
bids_by_name[bid['name']] = []
|
|
||||||
bids_by_name[bid['name']].append(bid)
|
|
||||||
|
|
||||||
pending = []
|
pending = []
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
if domain['state'] == "CLOSED" and domain['registered'] == False:
|
if domain['state'] == "CLOSED" and domain['registered'] == False:
|
||||||
if domain['name'] in bids_by_name:
|
for bid in bids:
|
||||||
for bid in bids_by_name[domain['name']]:
|
if bid['name'] == domain['name']:
|
||||||
if bid['value'] == domain['highest']:
|
if bid['value'] == domain['highest']:
|
||||||
pending.append(bid)
|
pending.append(bid)
|
||||||
|
|
||||||
return pending
|
return pending
|
||||||
|
|
||||||
|
|
||||||
@@ -874,26 +717,20 @@ def getPendingFinalizes(account, password):
|
|||||||
|
|
||||||
pending = []
|
pending = []
|
||||||
try:
|
try:
|
||||||
# Collect all nameHashes first
|
|
||||||
name_hashes = []
|
|
||||||
for output in tx['outputs']:
|
for output in tx['outputs']:
|
||||||
if output['covenant']['type'] != 10:
|
if output['covenant']['type'] != 10:
|
||||||
continue
|
continue
|
||||||
if output['covenant']['action'] != "FINALIZE":
|
if output['covenant']['action'] != "FINALIZE":
|
||||||
continue
|
continue
|
||||||
name_hashes.append(output['covenant']['items'][0])
|
nameHash = output['covenant']['items'][0]
|
||||||
|
# Try to get the name from hash
|
||||||
# Batch lookup for name hashes
|
name = hsd.rpc_getNameByHash(nameHash)
|
||||||
for name_hash in name_hashes:
|
|
||||||
name = hsd.rpc_getNameByHash(name_hash)
|
|
||||||
if name['error']:
|
if name['error']:
|
||||||
pending.append(name_hash)
|
pending.append(nameHash)
|
||||||
else:
|
else:
|
||||||
pending.append(name['result'])
|
pending.append(name['result'])
|
||||||
|
except:
|
||||||
except Exception as e:
|
print("Failed to parse finalizes")
|
||||||
print(f"Failed to parse finalizes: {str(e)}")
|
|
||||||
|
|
||||||
return pending
|
return pending
|
||||||
|
|
||||||
|
|
||||||
@@ -1047,10 +884,12 @@ def rescan_auction(account, domain):
|
|||||||
return {
|
return {
|
||||||
"error": "Invalid domain"
|
"error": "Invalid domain"
|
||||||
}
|
}
|
||||||
if 'bidPeriodStart' not in response['result']['info']['stats']:
|
if 'height' not in response['result']['info']:
|
||||||
return {
|
return {
|
||||||
"error": "Not in auction"
|
"error": "Can't find start"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
height = response['result']['info']['height']-1
|
height = response['result']['info']['height']-1
|
||||||
response = hsw.rpc_importName(domain, height)
|
response = hsw.rpc_importName(domain, height)
|
||||||
return response
|
return response
|
||||||
@@ -1236,51 +1075,19 @@ def revoke(account, domain):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def _prepare_wallet_for_batch(account_name, password):
|
def sendBatch(account, batch):
|
||||||
"""Helper function to prepare wallet for batch operations with caching"""
|
account_name = check_account(account)
|
||||||
cache_key = f"{account_name}:{password}"
|
password = ":".join(account.split(":")[1:])
|
||||||
current_time = time.time()
|
|
||||||
|
if account_name == False:
|
||||||
# Return cached authentication if available and fresh
|
return {
|
||||||
if (cache_key in _wallet_auth_cache and
|
"error": {
|
||||||
current_time - _wallet_auth_time.get(cache_key, 0) < _wallet_auth_duration):
|
"message": "Invalid account"
|
||||||
return _wallet_auth_cache[cache_key]
|
}
|
||||||
|
}
|
||||||
# Select and unlock wallet
|
|
||||||
result = {'success': False, 'error': None}
|
|
||||||
|
|
||||||
# Try to select the wallet
|
|
||||||
select_response = hsw.rpc_selectWallet(account_name)
|
|
||||||
if select_response['error'] is not None:
|
|
||||||
result['error'] = {"message": select_response['error']['message']}
|
|
||||||
return result
|
|
||||||
|
|
||||||
# Try to unlock the wallet
|
|
||||||
unlock_response = hsw.rpc_walletPassphrase(password, 30) # Increased timeout to reduce future unlocks
|
|
||||||
if (unlock_response['error'] is not None and
|
|
||||||
unlock_response['error']['message'] != "Wallet is not encrypted."):
|
|
||||||
result['error'] = {"message": unlock_response['error']['message']}
|
|
||||||
return result
|
|
||||||
|
|
||||||
# Authentication successful
|
|
||||||
result['success'] = True
|
|
||||||
|
|
||||||
# Cache the authentication result
|
|
||||||
_wallet_auth_cache[cache_key] = result
|
|
||||||
_wallet_auth_time[cache_key] = current_time
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
def _execute_batch_operation(account_name, batch, operation_type="sendbatch"):
|
|
||||||
"""Execute a batch operation with the specified wallet"""
|
|
||||||
# Make the batch request
|
|
||||||
try:
|
try:
|
||||||
response = requests.post(
|
response = hsw.rpc_selectWallet(account_name)
|
||||||
get_wallet_api_url(),
|
|
||||||
json={"method": operation_type, "params": [batch]},
|
|
||||||
timeout=30 # Add timeout to prevent hanging
|
|
||||||
).json()
|
|
||||||
|
|
||||||
if response['error'] is not None:
|
if response['error'] is not None:
|
||||||
return {
|
return {
|
||||||
"error": {
|
"error": {
|
||||||
@@ -1302,42 +1109,31 @@ def _execute_batch_operation(account_name, batch, operation_type="sendbatch"):
|
|||||||
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:
|
||||||
return {"error": {"message": "No result"}}
|
return {
|
||||||
|
"error": {
|
||||||
|
"message": "No result"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return response['result']
|
return response['result']
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return {"error": {"message": str(e)}}
|
return {
|
||||||
|
"error": {
|
||||||
|
"message": str(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
def sendBatch(account, batch):
|
|
||||||
account_name = check_account(account)
|
|
||||||
if account_name == False:
|
|
||||||
return {"error": {"message": "Invalid account"}}
|
|
||||||
|
|
||||||
password = ":".join(account.split(":")[1:])
|
|
||||||
|
|
||||||
# Prepare the wallet (this uses caching)
|
|
||||||
auth_result = _prepare_wallet_for_batch(account_name, password)
|
|
||||||
if not auth_result['success']:
|
|
||||||
return auth_result['error']
|
|
||||||
|
|
||||||
# Execute the batch operation
|
|
||||||
return _execute_batch_operation(account_name, batch, "sendbatch")
|
|
||||||
|
|
||||||
def createBatch(account, batch):
|
def createBatch(account, batch):
|
||||||
account_name = check_account(account)
|
account_name = check_account(account)
|
||||||
if account_name == False:
|
|
||||||
return {"error": {"message": "Invalid account"}}
|
|
||||||
|
|
||||||
password = ":".join(account.split(":")[1:])
|
password = ":".join(account.split(":")[1:])
|
||||||
|
|
||||||
# Prepare the wallet (this uses caching)
|
|
||||||
auth_result = _prepare_wallet_for_batch(account_name, password)
|
|
||||||
if not auth_result['success']:
|
|
||||||
return auth_result['error']
|
|
||||||
|
|
||||||
# Execute the batch operation
|
|
||||||
return _execute_batch_operation(account_name, batch, "createbatch")
|
|
||||||
|
|
||||||
|
if account_name == False:
|
||||||
|
return {
|
||||||
|
"error": {
|
||||||
|
"message": "Invalid account"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
response = hsw.rpc_selectWallet(account_name)
|
response = hsw.rpc_selectWallet(account_name)
|
||||||
@@ -1376,6 +1172,80 @@ def createBatch(account, batch):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# region Mempool
|
||||||
|
def getMempoolTxs():
|
||||||
|
# hsd-cli rpc getrawmempool
|
||||||
|
response = hsd.rpc_getRawMemPool()
|
||||||
|
if 'error' in response and response['error'] is not None:
|
||||||
|
return []
|
||||||
|
|
||||||
|
return response['result'] if 'result' in response else []
|
||||||
|
|
||||||
|
|
||||||
|
def getMempoolBids():
|
||||||
|
mempoolTxs = getMempoolTxs()
|
||||||
|
bids = {}
|
||||||
|
for txid in mempoolTxs:
|
||||||
|
tx = hsd.getTxByHash(txid)
|
||||||
|
if 'error' in tx and tx['error'] is not None:
|
||||||
|
print(f"Error getting tx {txid}: {tx['error']}")
|
||||||
|
continue
|
||||||
|
if 'outputs' not in tx:
|
||||||
|
print(f"Error getting outputs for tx {txid}")
|
||||||
|
continue
|
||||||
|
for output in tx['outputs']:
|
||||||
|
if output['covenant']['action'] not in ["BID", "REVEAL"]:
|
||||||
|
continue
|
||||||
|
if output['covenant']['action'] == "REVEAL":
|
||||||
|
# Try to find bid tx from inputs
|
||||||
|
namehash = output['covenant']['items'][0]
|
||||||
|
for txInput in tx['inputs']:
|
||||||
|
if txInput['coin']['covenant']['action'] != "BID":
|
||||||
|
continue
|
||||||
|
if txInput['coin']['covenant']['items'][0] != namehash:
|
||||||
|
continue
|
||||||
|
name = txInput['coin']['covenant']['items'][2]
|
||||||
|
# Convert name from hex to ascii
|
||||||
|
name = bytes.fromhex(name).decode('ascii')
|
||||||
|
|
||||||
|
bid = {
|
||||||
|
'txid': txid,
|
||||||
|
'lockup': txInput['coin']['value'],
|
||||||
|
'revealed': True,
|
||||||
|
'height': -1,
|
||||||
|
'value': output['value'],
|
||||||
|
'sort_value': txInput['coin']['value'],
|
||||||
|
'owner': "Unknown"
|
||||||
|
}
|
||||||
|
if name not in bids:
|
||||||
|
bids[name] = []
|
||||||
|
bids[name].append(bid)
|
||||||
|
continue
|
||||||
|
|
||||||
|
name = output['covenant']['items'][2]
|
||||||
|
# Convert name from hex to ascii
|
||||||
|
name = bytes.fromhex(name).decode('ascii')
|
||||||
|
if name not in bids:
|
||||||
|
bids[name] = []
|
||||||
|
bid = {
|
||||||
|
'txid': txid,
|
||||||
|
'value': -1000000, # Default value if not found
|
||||||
|
'lockup': output['value'],
|
||||||
|
'revealed': False,
|
||||||
|
'height': -1,
|
||||||
|
'sort_value': output['value'],
|
||||||
|
'owner': "Unknown"
|
||||||
|
}
|
||||||
|
bids[name].append(bid)
|
||||||
|
return bids
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# endregion
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# region settingsAPIs
|
# region settingsAPIs
|
||||||
def rescan():
|
def rescan():
|
||||||
|
|||||||
171
main.py
171
main.py
@@ -46,24 +46,21 @@ def blocks_to_time(blocks: int) -> str:
|
|||||||
elif blocks < 144:
|
elif blocks < 144:
|
||||||
hours = blocks // 6
|
hours = blocks // 6
|
||||||
minutes = (blocks % 6) * 10
|
minutes = (blocks % 6) * 10
|
||||||
|
if minutes == 0:
|
||||||
|
return f"{hours} hrs"
|
||||||
|
|
||||||
return f"{hours} hrs {minutes} mins"
|
return f"{hours} hrs {minutes} mins"
|
||||||
else:
|
else:
|
||||||
days = blocks // 144
|
days = blocks // 144
|
||||||
hours = (blocks % 144) // 6
|
hours = (blocks % 144) // 6
|
||||||
|
if hours == 0:
|
||||||
|
return f"{days} days"
|
||||||
return f"{days} days {hours} hrs"
|
return f"{days} days {hours} hrs"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Add a cache for transactions with a timeout
|
|
||||||
tx_cache = {}
|
|
||||||
TX_CACHE_TIMEOUT = 60*5 # Cache timeout in seconds
|
|
||||||
|
|
||||||
# Add a cache for outbids with a timeout
|
|
||||||
outbids_cache = {}
|
|
||||||
OUTBIDS_CACHE_TIMEOUT = 60*2 # Cache timeout in seconds
|
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
# Check if the user is logged in
|
# Check if the user is logged in
|
||||||
@@ -102,6 +99,10 @@ def reverseDirection(direction: str):
|
|||||||
|
|
||||||
|
|
||||||
#region Transactions
|
#region Transactions
|
||||||
|
# Add a cache for transactions with a timeout
|
||||||
|
tx_cache = {}
|
||||||
|
TX_CACHE_TIMEOUT = 60*5 # Cache timeout in seconds
|
||||||
|
|
||||||
@app.route('/tx')
|
@app.route('/tx')
|
||||||
def transactions():
|
def transactions():
|
||||||
# Check if the user is logged in
|
# Check if the user is logged in
|
||||||
@@ -322,12 +323,8 @@ def auctions():
|
|||||||
sort_time = direction
|
sort_time = direction
|
||||||
sort_time_next = reverseDirection(direction)
|
sort_time_next = reverseDirection(direction)
|
||||||
|
|
||||||
# Check if bids list is empty to avoid IndexError
|
|
||||||
if not bids:
|
|
||||||
domains = sorted(domains, key=lambda k: k['height'],reverse=reverse)
|
|
||||||
sortbyDomain = True
|
|
||||||
# If older HSD version sort by domain height
|
# If older HSD version sort by domain height
|
||||||
elif bids[0]['height'] == 0:
|
if bids[0]['height'] == 0:
|
||||||
domains = sorted(domains, key=lambda k: k['height'],reverse=reverse)
|
domains = sorted(domains, key=lambda k: k['height'],reverse=reverse)
|
||||||
sortbyDomain = True
|
sortbyDomain = True
|
||||||
else:
|
else:
|
||||||
@@ -338,27 +335,7 @@ def auctions():
|
|||||||
sort_domain = direction
|
sort_domain = direction
|
||||||
sort_domain_next = reverseDirection(direction)
|
sort_domain_next = reverseDirection(direction)
|
||||||
|
|
||||||
# Check if outbids set to true
|
bidsHtml = render.bidDomains(bids,domains,sortbyDomain)
|
||||||
outbids = request.args.get("outbids")
|
|
||||||
if outbids is not None and outbids.lower() == "true":
|
|
||||||
# Check cache before making expensive call
|
|
||||||
cache_key = f"outbids_{account}"
|
|
||||||
current_time = time.time()
|
|
||||||
|
|
||||||
if cache_key in outbids_cache and (current_time - outbids_cache[cache_key]['time'] < OUTBIDS_CACHE_TIMEOUT):
|
|
||||||
outbids = outbids_cache[cache_key]['data']
|
|
||||||
else:
|
|
||||||
# Get outbid domains
|
|
||||||
outbids = account_module.getPossibleOutbids(account)
|
|
||||||
# Store in cache
|
|
||||||
outbids_cache[cache_key] = {
|
|
||||||
'data': outbids,
|
|
||||||
'time': current_time
|
|
||||||
}
|
|
||||||
else:
|
|
||||||
outbids = []
|
|
||||||
|
|
||||||
bidsHtml = render.bidDomains(bids,domains,sortbyDomain,outbids)
|
|
||||||
plugins = ""
|
plugins = ""
|
||||||
message = ''
|
message = ''
|
||||||
if 'message' in request.args:
|
if 'message' in request.args:
|
||||||
@@ -386,12 +363,11 @@ def revealAllBids():
|
|||||||
return redirect("/logout")
|
return redirect("/logout")
|
||||||
|
|
||||||
response = account_module.revealAll(request.cookies.get("account"))
|
response = account_module.revealAll(request.cookies.get("account"))
|
||||||
# Simplified error handling
|
if 'error' in response:
|
||||||
if 'error' in response and response['error']:
|
if response['error'] != None:
|
||||||
error_msg = response['error'].get('message', str(response['error']))
|
if response['error']['message'] == "Nothing to do.":
|
||||||
if error_msg == "Nothing to do.":
|
return redirect("/auctions?message=No reveals pending")
|
||||||
return redirect("/auctions?message=No reveals pending")
|
return redirect("/auctions?message=" + response['error']['message'])
|
||||||
return redirect("/auctions?message=" + error_msg)
|
|
||||||
|
|
||||||
return redirect("/success?tx=" + response['result']['hash'])
|
return redirect("/success?tx=" + response['result']['hash'])
|
||||||
|
|
||||||
@@ -530,7 +506,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)
|
||||||
@@ -725,7 +704,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)
|
||||||
|
|
||||||
|
|
||||||
@@ -936,17 +918,18 @@ def auction(domain):
|
|||||||
state = domainInfo['info']['state']
|
state = domainInfo['info']['state']
|
||||||
next_action = ''
|
next_action = ''
|
||||||
|
|
||||||
bids = account_module.getBids(account,search_term)
|
# bids = account_module.getBids(account,search_term)
|
||||||
if bids == []:
|
bids = []
|
||||||
bids = "No bids found"
|
# if bids == []:
|
||||||
next_action = f'<a href="/auction/{domain}/scan">Rescan Auction</a>'
|
# bids = "No bids found"
|
||||||
else:
|
# next_action = f'<a href="/auction/{domain}/scan">Rescan Auction</a>'
|
||||||
reveals = account_module.getReveals(account,search_term)
|
# else:
|
||||||
for reveal in reveals:
|
# reveals = account_module.getReveals(account,search_term)
|
||||||
# Get TX
|
# for reveal in reveals:
|
||||||
revealInfo = account_module.getRevealTX(reveal)
|
# # Get TX
|
||||||
reveal['bid'] = revealInfo
|
# revealInfo = account_module.getRevealTX(reveal)
|
||||||
bids = render.bids(bids,reveals)
|
# reveal['bid'] = revealInfo
|
||||||
|
# bids = render.bids(bids,reveals)
|
||||||
|
|
||||||
stats = domainInfo['info']['stats'] if 'stats' in domainInfo['info'] else {}
|
stats = domainInfo['info']['stats'] if 'stats' in domainInfo['info'] else {}
|
||||||
if state == 'CLOSED':
|
if state == 'CLOSED':
|
||||||
@@ -1562,7 +1545,66 @@ def api_hsd(function):
|
|||||||
return jsonify({"result": account_module.hsdVersion(False)})
|
return jsonify({"result": account_module.hsdVersion(False)})
|
||||||
if function == "height":
|
if function == "height":
|
||||||
return jsonify({"result": account_module.getBlockHeight()})
|
return jsonify({"result": account_module.getBlockHeight()})
|
||||||
|
if function == "mempool":
|
||||||
|
return jsonify({"result": account_module.getMempoolTxs()})
|
||||||
|
if function == "mempoolBids":
|
||||||
|
return jsonify({"result": account_module.getMempoolBids()})
|
||||||
|
if function == "nextAuctionState":
|
||||||
|
# Get the domain from the query parameters
|
||||||
|
domain = request.args.get('domain')
|
||||||
|
if not domain:
|
||||||
|
return jsonify({"error": "No domain specified"}), 400
|
||||||
|
domainInfo = account_module.getDomain(domain)
|
||||||
|
if 'error' in domainInfo and domainInfo['error'] != None:
|
||||||
|
return jsonify({"error": domainInfo['error']}), 400
|
||||||
|
stats = domainInfo['info']['stats'] if 'stats' in domainInfo['info'] else {}
|
||||||
|
state = domainInfo['info']['state']
|
||||||
|
next_action = ""
|
||||||
|
if state == 'CLOSED':
|
||||||
|
if not domainInfo['info']['registered']:
|
||||||
|
if account_module.isOwnDomain(account,domain):
|
||||||
|
print("Waiting to be registered")
|
||||||
|
state = 'PENDING REGISTER'
|
||||||
|
next = "Pending Register"
|
||||||
|
next_action = f'<a href="/auction/{domain}/register">Register Domain</a>'
|
||||||
|
|
||||||
|
else:
|
||||||
|
print("Not registered")
|
||||||
|
state = 'AVAILABLE'
|
||||||
|
next = "Available Now"
|
||||||
|
next_action = f'<a href="/auction/{domain}/open">Open Auction</a>'
|
||||||
|
else:
|
||||||
|
state = 'REGISTERED'
|
||||||
|
expires = domainInfo['info']['stats']['daysUntilExpire']
|
||||||
|
next = f"Expires in ~{expires} days"
|
||||||
|
elif state == "REVOKED":
|
||||||
|
next = "Available Now"
|
||||||
|
next_action = f'<a href="/auction/{domain}/open">Open Auction</a>'
|
||||||
|
elif state == 'OPENING':
|
||||||
|
next = f"Bidding opens in {str(stats['blocksUntilBidding'])} blocks (~{blocks_to_time(stats['blocksUntilBidding'])})"
|
||||||
|
elif state == 'BIDDING':
|
||||||
|
next = f"Reveal in {stats['blocksUntilReveal']} blocks (~{blocks_to_time(stats['blocksUntilReveal'])})"
|
||||||
|
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':
|
||||||
|
next = f"Reveal ends in {str(stats['blocksUntilClose'])} blocks (~{blocks_to_time(stats['blocksUntilClose'])})"
|
||||||
|
next_action = f'<a href="/auction/{domain}/reveal">Reveal All</a>'
|
||||||
|
|
||||||
|
return jsonify({
|
||||||
|
"state": state,
|
||||||
|
"next": next,
|
||||||
|
"next_action": next_action
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
return jsonify({"error": "Invalid function", "result": "Invalid function"}), 400
|
return jsonify({"error": "Invalid function", "result": "Invalid function"}), 400
|
||||||
|
|
||||||
|
|
||||||
@@ -1681,6 +1723,21 @@ def api_wallet(function):
|
|||||||
"page": page
|
"page": page
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if function == "domainBids":
|
||||||
|
domain = request.args.get('domain')
|
||||||
|
if not domain:
|
||||||
|
return jsonify({"error": "No domain specified"}), 400
|
||||||
|
bids = account_module.getBids(account,domain)
|
||||||
|
if bids == []:
|
||||||
|
return jsonify({"result": [], "error": "No bids found"}), 404
|
||||||
|
else:
|
||||||
|
reveals = account_module.getReveals(account,domain)
|
||||||
|
for reveal in reveals:
|
||||||
|
# Get TX
|
||||||
|
revealInfo = account_module.getRevealTX(reveal)
|
||||||
|
reveal['bid'] = revealInfo
|
||||||
|
bids = render.bids(bids,reveals)
|
||||||
|
return jsonify({"result": bids})
|
||||||
|
|
||||||
if function == "icon":
|
if function == "icon":
|
||||||
# Check if there is an icon
|
# Check if there is an icon
|
||||||
@@ -1693,12 +1750,6 @@ def api_wallet(function):
|
|||||||
|
|
||||||
return send_file('templates/assets/img/HNS.png')
|
return send_file('templates/assets/img/HNS.png')
|
||||||
|
|
||||||
|
|
||||||
if function == "possibleOutbids":
|
|
||||||
return jsonify({"result": account_module.getPossibleOutbids(account)})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return jsonify({"error": "Invalid function", "result": "Invalid function"}), 400
|
return jsonify({"error": "Invalid function", "result": "Invalid function"}), 400
|
||||||
|
|
||||||
@app.route('/api/v1/wallet/<function>/mobile', methods=["GET"])
|
@app.route('/api/v1/wallet/<function>/mobile', methods=["GET"])
|
||||||
|
|||||||
38
render.py
38
render.py
@@ -40,6 +40,24 @@ if TX_EXPLORER_URL is None:
|
|||||||
|
|
||||||
|
|
||||||
NAMEHASH_CACHE = 'user_data/namehash_cache.json'
|
NAMEHASH_CACHE = 'user_data/namehash_cache.json'
|
||||||
|
# Validate cache version
|
||||||
|
if os.path.exists(NAMEHASH_CACHE):
|
||||||
|
with open(NAMEHASH_CACHE, 'r') as f:
|
||||||
|
cache = json.load(f)
|
||||||
|
if not isinstance(cache, dict):
|
||||||
|
print("Invalid namehash cache format. Resetting cache.")
|
||||||
|
with open(NAMEHASH_CACHE, 'w') as f:
|
||||||
|
json.dump({}, f)
|
||||||
|
# Check if cache entries are valid
|
||||||
|
for key in cache:
|
||||||
|
if not cache[key].startswith("<a href='/manage/"):
|
||||||
|
print(f"Invalid cache entry for {key}. Resetting cache.")
|
||||||
|
with open(NAMEHASH_CACHE, 'w') as f:
|
||||||
|
json.dump({}, f)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
CACHE_LOCK = threading.Lock()
|
CACHE_LOCK = threading.Lock()
|
||||||
|
|
||||||
|
|
||||||
@@ -302,7 +320,6 @@ def bids(bids,reveals):
|
|||||||
'value': value,
|
'value': value,
|
||||||
'sort_value': value if revealed else lockup # Use value for sorting if revealed, otherwise lockup
|
'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)
|
# Sort by the sort_value in descending order (highest first)
|
||||||
bid_data.sort(key=lambda x: x['sort_value'], reverse=True)
|
bid_data.sort(key=lambda x: x['sort_value'], reverse=True)
|
||||||
|
|
||||||
@@ -330,14 +347,15 @@ def bids(bids,reveals):
|
|||||||
else:
|
else:
|
||||||
html += f"<td>Unknown</td>"
|
html += f"<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 += f"<td><a class='text-decoration-none' style='color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));' target='_blank' href='{TX_EXPLORER_URL}{bid['prevout']['hash']}'>Bid TX 🔗</a></td>"
|
||||||
html += "</tr>"
|
html += "</tr>"
|
||||||
|
|
||||||
return html
|
return html
|
||||||
|
|
||||||
|
|
||||||
def bidDomains(bids,domains, sortbyDomains=False, outbids=[]):
|
def bidDomains(bids,domains, sortbyDomains=False):
|
||||||
html = ''
|
html = ''
|
||||||
|
|
||||||
if not sortbyDomains:
|
if not sortbyDomains:
|
||||||
for bid in bids:
|
for bid in bids:
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
@@ -351,15 +369,13 @@ def bidDomains(bids,domains, sortbyDomains=False, outbids=[]):
|
|||||||
bidDisplay = f'<b>{bidValue:,.2f}</b> (+{blind:,.2f}) HNS'
|
bidDisplay = f'<b>{bidValue:,.2f}</b> (+{blind:,.2f}) HNS'
|
||||||
else:
|
else:
|
||||||
bidDisplay = f'<b>{bidValue:,.2f}</b> HNS'
|
bidDisplay = f'<b>{bidValue:,.2f}</b> HNS'
|
||||||
|
|
||||||
|
|
||||||
html += "<tr>"
|
html += "<tr>"
|
||||||
if domain['name'] in outbids:
|
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='/auction/{domain['name']}'>{renderDomain(domain['name'])}</a></td>"
|
||||||
html += f"<td style='background-color: red;'><a class='text-decoration-none' style='color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));' href='/auction/{domain['name']}'>{renderDomain(domain['name'])}</a></td>"
|
|
||||||
else:
|
|
||||||
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='/auction/{domain['name']}'>{renderDomain(domain['name'])}</a></td>"
|
|
||||||
html += f"<td>{domain['state']}</td>"
|
html += f"<td>{domain['state']}</td>"
|
||||||
html += f"<td style='white-space: nowrap;'>{bidDisplay}</td>"
|
html += f"<td style='white-space: nowrap;'>{bidDisplay}</td>"
|
||||||
html += f"<td class='hide-mobile'>{bid['height']:,}</td>"
|
html += f"<td class='hide-mobile'>{domain['height']:,}</td>"
|
||||||
html += "</tr>"
|
html += "</tr>"
|
||||||
else:
|
else:
|
||||||
for domain in domains:
|
for domain in domains:
|
||||||
@@ -375,7 +391,7 @@ def bidDomains(bids,domains, sortbyDomains=False, outbids=[]):
|
|||||||
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='/auction/{domain['name']}'>{renderDomain(domain['name'])}</a></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='/auction/{domain['name']}'>{renderDomain(domain['name'])}</a></td>"
|
||||||
html += f"<td>{domain['state']}</td>"
|
html += f"<td>{domain['state']}</td>"
|
||||||
html += f"<td>{bidDisplay}</td>"
|
html += f"<td>{bidDisplay}</td>"
|
||||||
html += f"<td class='hide-mobile'>{bid['height']:,}</td>"
|
html += f"<td class='hide-mobile'>{domain['height']:,}</td>"
|
||||||
html += "</tr>"
|
html += "</tr>"
|
||||||
return html
|
return html
|
||||||
|
|
||||||
@@ -548,6 +564,8 @@ def renderDomainAsync(namehash: str) -> None:
|
|||||||
if name["error"] is None:
|
if name["error"] is None:
|
||||||
name = name["result"]
|
name = name["result"]
|
||||||
rendered = renderDomain(name)
|
rendered = renderDomain(name)
|
||||||
|
rendered = f"<a href='/manage/{name}' target='_blank' style='color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));'>{rendered}</a>"
|
||||||
|
|
||||||
|
|
||||||
with CACHE_LOCK:
|
with CACHE_LOCK:
|
||||||
with open(NAMEHASH_CACHE, 'r') as f:
|
with open(NAMEHASH_CACHE, 'r') as f:
|
||||||
|
|||||||
2
templates/assets/js/dashboard.min.js
vendored
2
templates/assets/js/dashboard.min.js
vendored
@@ -1 +1 @@
|
|||||||
function createCard(e,n,t){if(document.getElementById(t)&&document.getElementById(t).remove(),n<=0)return;const s=document.createElement("div");s.classList.add("col-md-6","col-xl-3","mb-4"),s.id=t,html=`\n <div class="card shadow border-start-warning py-2">\n <div class="card-body">\n <div class="row align-items-center no-gutters">\n <div class="col me-2">\n <div class="text-uppercase text-warning fw-bold text-xs mb-1"><span>${e}</span></div>\n <div class="text-dark fw-bold h5 mb-0"><span id="${e}">${n}</span></div>\n </div>\n <div class="col"><a class="btn btn-primary" role="button" href="/all/${t.toLowerCase()}">${t} All</a></div>\n <div class="col-auto"><svg class="fa-2x text-gray-300" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor">\n <g>\n <rect fill="none" height="24" width="24"></rect>\n </g>\n <g>\n <path d="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10C22,6.48,17.52,2,12,2z M7,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C8.5,12.83,7.83,13.5,7,13.5z M12,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C13.5,12.83,12.83,13.5,12,13.5z M17,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C18.5,12.83,17.83,13.5,17,13.5z"></path>\n </g>\n </svg></div>\n </div>\n </div>`,s.innerHTML=html,document.getElementById("actions-row").appendChild(s)}async function updateActions(){const e={Finalize:"Pending Finalizes",Register:"Pending Register",Redeem:"Pending Redeem",Reveal:"Pending Reveal"},n=Object.keys(e).map((e=>request(`wallet/pending${e}`).then((n=>({id:e,result:n}))))),t=await Promise.all(n);for(const{id:n,result:s}of t)"Error"!==s&&createCard(e[n],s.length,n);const s=await request("wallet/possibleOutbids");if("Error"===s)return;const d=document.getElementById("outbids");if(d&&d.remove(),s.length<=0)return;const i=document.createElement("div");i.classList.add("col-md-6","col-xl-3","mb-4"),i.id="outbids",i.innerHTML=`\n <div class="card shadow border-start-warning py-2">\n <div class="card-body">\n <div class="row align-items-center no-gutters">\n <div class="col me-2">\n <div class="text-uppercase text-warning fw-bold text-xs mb-1"><span>Names with possible outbids</span></div>\n <div class="text-dark fw-bold h5 mb-0"><span id="outbids-count">${s.length}</span></div>\n </div>\n <div class="col"><a class="btn btn-primary" role="button" href="/auctions?outbids=true">Show All</a></div>\n <div class="col-auto">\n <svg class="fa-2x text-gray-300" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor">\n <g><rect fill="none" height="24" width="24"></rect></g>\n <g>\n <path d="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10C22,6.48,17.52,2,12,2z M7,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C8.5,12.83,7.83,13.5,7,13.5z M12,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C13.5,12.83,12.83,13.5,12,13.5z M17,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C18.5,12.83,17.83,13.5,17,13.5z"></path>\n </g>\n </svg>\n </div>\n </div>\n </div>\n </div>\n `,document.getElementById("actions-row").appendChild(i)}window.addEventListener("load",(async()=>{updateActions()})),setInterval((async function(){updateActions()}),2e4);
|
function createCard(e,n,t){if(document.getElementById(t)&&document.getElementById(t).remove(),n<=0)return;const a=document.createElement("div");a.classList.add("col-md-6","col-xl-3","mb-4"),a.id=t,html=`\n <div class="card shadow border-start-warning py-2">\n <div class="card-body">\n <div class="row align-items-center no-gutters">\n <div class="col me-2">\n <div class="text-uppercase text-warning fw-bold text-xs mb-1"><span>${e}</span></div>\n <div class="text-dark fw-bold h5 mb-0"><span id="${e}">${n}</span></div>\n </div>\n <div class="col"><a class="btn btn-primary" role="button" href="/all/${t.toLowerCase()}">${t} All</a></div>\n <div class="col-auto"><svg class="fa-2x text-gray-300" xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="1em" viewBox="0 0 24 24" width="1em" fill="currentColor">\n <g>\n <rect fill="none" height="24" width="24"></rect>\n </g>\n <g>\n <path d="M12,2C6.48,2,2,6.48,2,12c0,5.52,4.48,10,10,10s10-4.48,10-10C22,6.48,17.52,2,12,2z M7,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C8.5,12.83,7.83,13.5,7,13.5z M12,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C13.5,12.83,12.83,13.5,12,13.5z M17,13.5c-0.83,0-1.5-0.67-1.5-1.5 c0-0.83,0.67-1.5,1.5-1.5s1.5,0.67,1.5,1.5C18.5,12.83,17.83,13.5,17,13.5z"></path>\n </g>\n </svg></div>\n </div>\n </div>`,a.innerHTML=html,document.getElementById("actions-row").appendChild(a)}async function updateActions(){const e={Finalize:"Pending Finalizes",Register:"Pending Register",Redeem:"Pending Redeem",Reveal:"Pending Reveal"};for(const n in e){const t=await request(`wallet/pending${n}`);"Error"!=t&&createCard(e[n],t.length,n)}}window.addEventListener("load",(async()=>{updateActions()})),setInterval((async function(){updateActions()}),2e4);
|
||||||
@@ -66,9 +66,9 @@
|
|||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="stick-right">{{next_action|safe}}</div>
|
<div id="next-action" 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" id="next">{{next | safe}}</h6>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -96,11 +96,89 @@
|
|||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody id="bids-tbody">
|
||||||
{{bids | safe}}
|
<tr id="loading-row">
|
||||||
|
<td colspan="5" class="text-center">
|
||||||
|
<div class="spinner-border spinner-border-sm me-2" role="status">
|
||||||
|
<span class="visually-hidden">Loading...</span>
|
||||||
|
</div>
|
||||||
|
Loading bids...
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function loadBids(initial = false) {
|
||||||
|
const tbody = document.getElementById('bids-tbody');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch all required data
|
||||||
|
const response = await fetch(`/api/v1/wallet/domainBids?domain={{search_term}}`);
|
||||||
|
const data = await response.json();
|
||||||
|
if (initial) {
|
||||||
|
if (response.ok && data.result) {
|
||||||
|
tbody.innerHTML = data.result;
|
||||||
|
} else {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted">No bids found. <a href="/auction/{{search_term}}/scan">Rescan Auction</a></td></tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const mempoolResponse = await fetch('/api/v1/hsd/mempoolBids');
|
||||||
|
const nextStateResponse = await fetch(`/api/v1/hsd/nextAuctionState?domain={{search_term}}`);
|
||||||
|
|
||||||
|
if (!initial) {
|
||||||
|
if (response.ok && data.result) {
|
||||||
|
tbody.innerHTML = data.result;
|
||||||
|
} else {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="5" class="text-center text-muted">No bids found. <a href="/auction/{{search_term}}/scan">Rescan Auction</a></td></tr>';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const nextStateData = await nextStateResponse.json();
|
||||||
|
|
||||||
|
if (nextStateResponse.ok && nextStateData.state) {
|
||||||
|
document.getElementById('next').innerHTML = nextStateData.next;
|
||||||
|
document.getElementById('next-action').innerHTML = nextStateData.next_action;
|
||||||
|
} else {
|
||||||
|
document.getElementById('next').innerHTML = 'Unknown';
|
||||||
|
document.getElementById('next-action').innerHTML = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
const mempoolData = await mempoolResponse.json();
|
||||||
|
if (mempoolResponse.ok && mempoolData.result) {
|
||||||
|
const domainBids = mempoolData.result['{{search_term}}'];
|
||||||
|
if (domainBids && domainBids.length > 0) {
|
||||||
|
let mempoolRows = '';
|
||||||
|
domainBids.forEach(bid => {
|
||||||
|
const bidValue = bid.revealed ? `${(bid.value / 1000000).toFixed(2)} HNS` : 'Hidden until reveal';
|
||||||
|
const lockupValue = (bid.lockup / 1000000).toFixed(2);
|
||||||
|
const blindValue = bid.revealed ? `${((bid.lockup - bid.value) / 1000000).toFixed(2)} HNS` : 'Hidden until reveal';
|
||||||
|
const type = bid.revealed ? 'Reveal' : 'Bid';
|
||||||
|
mempoolRows += `<tr class="table-warning">
|
||||||
|
<td>${lockupValue} HNS</td>
|
||||||
|
<td>${bidValue}</td>
|
||||||
|
<td>${blindValue}</td>
|
||||||
|
<td>${bid.owner}</td>
|
||||||
|
<td><a class='text-decoration-none' style='color: var(--bs-table-color-state, var(--bs-table-color-type, var(--bs-table-color)));' target='_blank' href='https://shakeshift.com/transaction/${bid.txid}'>Mempool ${type} 🔗</a></td>
|
||||||
|
</tr>`;
|
||||||
|
});
|
||||||
|
tbody.innerHTML += mempoolRows;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading bids:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load bids when page loads
|
||||||
|
document.addEventListener('DOMContentLoaded', () => loadBids(true));
|
||||||
|
|
||||||
|
// Auto-refresh bids every 20 seconds
|
||||||
|
setInterval(() => loadBids(false), 20000);
|
||||||
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user