Merge pull request 'feat/mempool-bids' (#2) from feat/mempool-bids into main
All checks were successful
Build Docker / Build Image (push) Successful in 2m19s
All checks were successful
Build Docker / Build Image (push) Successful in 2m19s
Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
Binary file not shown.
91
account.py
91
account.py
@@ -460,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
|
||||||
@@ -1155,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():
|
||||||
|
|||||||
109
main.py
109
main.py
@@ -46,10 +46,15 @@ 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"
|
||||||
|
|
||||||
|
|
||||||
@@ -913,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':
|
||||||
@@ -944,10 +950,7 @@ 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"
|
||||||
|
|
||||||
own_domains = account_module.getDomains(account)
|
if account_module.isOwnDomain(account,domain):
|
||||||
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"
|
||||||
@@ -1542,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
|
||||||
|
|
||||||
|
|
||||||
@@ -1661,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
|
||||||
|
|||||||
@@ -320,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)
|
||||||
|
|
||||||
|
|||||||
@@ -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