feat: Add pagination for HSD v7 and return errors when node not connected
All checks were successful
Build Docker / Build Image (push) Successful in 52s

This commit is contained in:
Nathan Woodburn 2025-01-30 12:32:20 +11:00
parent 1962c9345e
commit 42e8f642b4
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
7 changed files with 171 additions and 28 deletions

3
.gitignore vendored
View File

@ -12,4 +12,5 @@ plugins/signatures.json
.venv/
user_data/
customPlugins/
customPlugins/
cache/

Binary file not shown.

View File

@ -6,7 +6,7 @@ import requests
import re
import domainLookup
import json
import time
dotenv.load_dotenv()
@ -22,6 +22,7 @@ if show_expired is None:
hsd = api.hsd(APIKEY,ip)
hsw = api.hsw(APIKEY,ip)
cacheTime = 3600
# Verify the connection
response = hsd.getInfo()
@ -30,6 +31,17 @@ exclude = ["primary"]
if os.getenv("exclude") is not None:
exclude = os.getenv("exclude").split(",")
def hsdConnected():
if hsdVersion() == -1:
return False
return True
def hsdVersion():
info = hsd.getInfo()
if 'error' in info:
return -1
return float('.'.join(info['version'].split(".")[:2]))
def check_account(cookie: str):
if cookie is None:
return False
@ -61,12 +73,15 @@ def check_password(cookie: str, password: str):
return True
def createWallet(account: str, password: str):
if not hsdConnected():
return {
"error": {
"message": "Node not connected"
}
}
# Create the account
# Python wrapper doesn't support this yet
response = requests.put(f"http://x:{APIKEY}@{ip}:12039/wallet/{account}")
print(response)
print(response.json())
if response.status_code != 200:
return {
"error": {
@ -82,7 +97,6 @@ def createWallet(account: str, password: str):
# Encrypt the wallet (python wrapper doesn't support this yet)
response = requests.post(f"http://x:{APIKEY}@{ip}:12039/wallet/{account}/passphrase",
json={"passphrase": password})
print(response)
return {
"seed": seed,
@ -91,6 +105,13 @@ def createWallet(account: str, password: str):
}
def importWallet(account: str, password: str,seed: str):
if not hsdConnected():
return {
"error": {
"message": "Node not connected"
}
}
# Import the wallet
data = {
"passphrase": password,
@ -98,9 +119,6 @@ def importWallet(account: str, password: str,seed: str):
}
response = requests.put(f"http://x:{APIKEY}@{ip}:12039/wallet/{account}",json=data)
print(response)
print(response.json())
if response.status_code != 200:
return {
"error": {
@ -127,6 +145,16 @@ def listWallets():
return response
return ['Wallet not connected']
def selectWallet(account: str):
# Select wallet
response = hsw.rpc_selectWallet(account)
if response['error'] is not None:
return {
"error": {
"message": response['error']['message']
}
}
def getBalance(account: str):
# Get the total balance
info = hsw.getBalance('default',account)
@ -204,13 +232,97 @@ def getDomains(account,own=True):
return domains
def getTransactions(account):
# Get the transactions
info = hsw.getWalletTxHistory(account)
if 'error' in info:
return []
return info
def getPageTXCache(account,page):
page = str(page)
if not os.path.exists(f'cache'):
os.mkdir(f'cache')
if not os.path.exists(f'cache/{account}_page.json'):
with open(f'cache/{account}_page.json', 'w') as f:
f.write('{}')
with open(f'cache/{account}_page.json') as f:
pageCache = json.load(f)
if page in pageCache and pageCache[page]['time'] > int(time.time()) - cacheTime:
return pageCache[page]['txid']
return None
def pushPageTXCache(account,page,txid):
page = str(page)
if not os.path.exists(f'cache/{account}_page.json'):
with open(f'cache/{account}_page.json', 'w') as f:
f.write('{}')
with open(f'cache/{account}_page.json') as f:
pageCache = json.load(f)
pageCache[page] = {
'time': int(time.time()),
'txid': txid
}
with open(f'cache/{account}_page.json', 'w') as f:
json.dump(pageCache, f,indent=4)
return pageCache[page]['txid']
def getTXFromPage(account,page):
if page == 1:
return getTransactions(account)[-1]['hash']
cached = getPageTXCache(account,page)
if cached:
return getPageTXCache(account,page)
previous = getTransactions(account,page)
if len(previous) == 0:
return None
hash = previous[-1]['hash']
pushPageTXCache(account,page,hash)
return hash
def getTransactions(account,page=1,limit=100):
# Get the transactions
if hsdVersion() < 7:
info = hsw.getWalletTxHistory(account)
if 'error' in info:
return []
return info[::-1]
lastTX = None
if page < 1:
return []
if page > 1:
lastTX = getTXFromPage(account,page-1)
if lastTX:
response = requests.get(f'http://x:{APIKEY}@{ip}:12039/wallet/{account}/tx/history?reverse=true&limit={limit}&after={lastTX}')
elif page == 1:
response = requests.get(f'http://x:{APIKEY}@{ip}:12039/wallet/{account}/tx/history?reverse=true&limit={limit}')
else:
return []
if response.status_code != 200:
print(response.text)
return []
data = response.json()
# Refresh the cache if the next page is different
nextPage = getPageTXCache(account,page)
if nextPage is not None and nextPage != data[-1]['hash']:
print(f'Refreshing page {page}')
pushPageTXCache(account,page,data[-1]['hash'])
return data
def getAllTransactions(account):
# Get the transactions
page = 0
txs = []
while True:
txs += getTransactions(account,page)
if len(txs) == 0:
break
page += 1
return txs
def check_address(address: str, allow_name: bool = True, return_address: bool = False):
# Check if the address is valid

25
main.py
View File

@ -125,14 +125,23 @@ def transactions():
return redirect("/login")
account = account_module.check_account(request.cookies.get("account"))
# Get the transactions
transactions = account_module.getTransactions(account)
page = request.args.get('page')
try:
page = int(page)
except:
page = 1
if page < 1:
page = 1
transactions = account_module.getTransactions(account,page)
txCount = len(transactions)
transactions = render.transactions(transactions)
return render_template("tx.html", account=account, sync=account_module.getNodeSync(),
wallet_status=account_module.getWalletStatus(),tx=transactions)
wallet_status=account_module.getWalletStatus(),tx=transactions,
page=page,txCount=txCount)
@app.route('/send')
def send_page():
@ -1435,6 +1444,12 @@ def send_assets(path):
# Try path
@app.route('/<path:path>')
def try_path(path):
# Check if node connected
if not account_module.hsdConnected():
return redirect("/login?message=Node not connected")
if os.path.isfile("templates/" + path + ".html"):
return render_template(path + ".html")
else:

View File

@ -29,7 +29,14 @@ functions = {
def main(params, authentication):
wallet = authentication.split(":")[0]
txs = account.getTransactions(wallet)
txCount = 0
page = 1
while True:
txs = account.getTransactions(wallet,page)
if len(txs) == 0:
break
txCount += len(txs)
page += 1
return {"txs": f'Total TXs: {len(txs)}'}
return {"txs": f'Total TXs: {txCount}'}

View File

@ -29,11 +29,10 @@ def domains(domains, mobile=False):
return html
def transactions(txs):
if len(txs) == 0:
return '<tr><td colspan="5">No transactions found</td></tr>'
html = ''
# Reverse the list
txs = txs[::-1]
for tx in txs:
action = "HNS Transfer"
address = tx["outputs"][0]["address"]

View File

@ -65,8 +65,17 @@
<div class="container-fluid">
<h3 class="text-dark mb-4">Transactions</h3>
<div class="card shadow">
<div class="card-header py-3">
<p class="text-primary m-0 fw-bold">Transactions</p>
<div class="card-header py-3" style="padding: 16px;">
<div style="height: 38px;">
<p class="text-primary m-0 fw-bold" style="display: inline-block;">Transactions</p><div class="btn-group" role="group" style="right: 16px;top: 16px;position: absolute;height: 38px;">
{% if page != 1 %}
<a class="btn btn-primary" role="button" href="/tx?page={{page-1}}">Prev</a>
{% endif %}
{% if txCount == 100 %}
<a class="btn btn-primary" role="button" href="/tx?page={{page+1}}">Next</a>
{% endif %}
</div>
</div>
</div>
<div class="card-body"><div id="dataTable" class="table-responsive table mt-2" role="grid" aria-describedby="dataTable_info">
<table id="dataTable" class="table my-0">