13 Commits

Author SHA1 Message Date
d92cb7c743 Merge branch 'dev'
All checks were successful
Build Docker / Build Image (push) Successful in 1m17s
Update shakeshift explorer and fix unencrypted wallets
2025-05-08 12:09:53 +10:00
fd1ba1d059 fix: Allow wallet acctions from unencrypted wallets
All checks were successful
Build Docker / Build Image (push) Successful in 1m29s
2025-05-08 12:09:04 +10:00
6bbc294116 fix: Correct link for ShakeShift
All checks were successful
Build Docker / Build Image (push) Successful in 40s
2025-03-12 20:28:54 +11:00
80e380b183 feat: Update hns.cymon.de to ShakeShift
All checks were successful
Build Docker / Build Image (push) Successful in 50s
2025-03-12 20:24:48 +11:00
30f61f1505 feat: Use correct a link
All checks were successful
Build Docker / Build Image (push) Successful in 47s
2025-03-07 15:12:33 +11:00
3bee713b9a Merge branch 'dev'
All checks were successful
Build Docker / Build Image (push) Successful in 44s
2025-03-06 10:59:56 +11:00
7bd59a0fd6 feat: Add link to debug tools
All checks were successful
Build Docker / Build Image (push) Successful in 39s
2025-02-28 13:34:43 +11:00
56016b1f6f fix: Update domain sort to stop buggy sorting
All checks were successful
Build Docker / Build Image (push) Successful in 1m15s
2025-02-28 12:57:29 +11:00
3aff724b81 fix: Version check on settings page
All checks were successful
Build Docker / Build Image (push) Successful in 43s
2025-02-05 14:57:51 +11:00
afc227b5b4 fix: Remove some unnecessary logs
All checks were successful
Build Docker / Build Image (push) Successful in 43s
2025-02-05 14:53:33 +11:00
ab7749ef93 fix: Allow desciption to have HTML in plugin output 2025-02-05 14:39:52 +11:00
a568abeb49 fix: Don't import gunicorn unless specified by args
All checks were successful
Build Docker / Build Image (push) Successful in 45s
2025-02-05 13:51:01 +11:00
4652af3a2d feat: Add new server backend to add windows support
All checks were successful
Build Docker / Build Image (push) Successful in 1m18s
2025-02-05 13:20:09 +11:00
15 changed files with 334 additions and 270 deletions

2
.gitignore vendored
View File

@@ -14,3 +14,5 @@ plugins/signatures.json
user_data/ user_data/
customPlugins/ customPlugins/
cache/ cache/
build/
dist/

Binary file not shown.

View File

@@ -122,7 +122,7 @@ HSD_IP: HSD IP address
THEME: Theme to use (dark-purple, black) THEME: Theme to use (dark-purple, black)
SHOW_EXPIRED: Show expired domains (true/false) SHOW_EXPIRED: Show expired domains (true/false)
EXCLUDE: Comma separated list of wallets to exclude from the wallet list (default primary) EXCLUDE: Comma separated list of wallets to exclude from the wallet list (default primary)
EXPLORER_TX: URL for exploring transactions (default https://niami.io/tx/) EXPLORER_TX: URL for exploring transactions (default https://shakeshift.com/transaction/)
HSD_NETWORK: Network to connect to (main, regtest, simnet) HSD_NETWORK: Network to connect to (main, regtest, simnet)
``` ```

View File

@@ -39,8 +39,8 @@ SHOW_EXPIRED = os.getenv("SHOW_EXPIRED")
if SHOW_EXPIRED is None: if SHOW_EXPIRED is None:
SHOW_EXPIRED = False SHOW_EXPIRED = False
hsd = api.hsd(HSD_API,HSD_IP,HSD_NODE_PORT) hsd = api.hsd(HSD_API, HSD_IP, HSD_NODE_PORT)
hsw = api.hsw(HSD_API,HSD_IP,HSD_WALLET_PORT) hsw = api.hsw(HSD_API, HSD_IP, HSD_WALLET_PORT)
cacheTime = 3600 cacheTime = 3600
@@ -51,11 +51,13 @@ EXCLUDE = ["primary"]
if os.getenv("EXCLUDE") is not None: if os.getenv("EXCLUDE") is not None:
EXCLUDE = os.getenv("EXCLUDE").split(",") EXCLUDE = os.getenv("EXCLUDE").split(",")
def hsdConnected(): def hsdConnected():
if hsdVersion() == -1: if hsdVersion() == -1:
return False return False
return True return True
def hsdVersion(format=True): def hsdVersion(format=True):
info = hsd.getInfo() info = hsd.getInfo()
if 'error' in info: if 'error' in info:
@@ -65,6 +67,7 @@ def hsdVersion(format=True):
else: else:
return info['version'] return info['version']
def check_account(cookie: str): def check_account(cookie: str):
if cookie is None: if cookie is None:
return False return False
@@ -80,6 +83,7 @@ def check_account(cookie: str):
return False return False
return account return account
def check_password(cookie: str, password: str): def check_password(cookie: str, password: str):
account = check_account(cookie) account = check_account(cookie)
if account == False: if account == False:
@@ -89,12 +93,13 @@ def check_password(cookie: str, password: str):
info = hsw.rpc_selectWallet(account) info = hsw.rpc_selectWallet(account)
if info['error'] is not None: if info['error'] is not None:
return False return False
info = hsw.rpc_walletPassphrase(password,1) info = hsw.rpc_walletPassphrase(password, 1)
if info['error'] is not None: if info['error'] is not None:
if info['error']['message'] != "Wallet is not encrypted.": if info['error']['message'] != "Wallet is not encrypted.":
return False return False
return True return True
def createWallet(account: str, password: str): def createWallet(account: str, password: str):
if not hsdConnected(): if not hsdConnected():
return { return {
@@ -104,7 +109,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(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/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": {
@@ -116,10 +122,9 @@ def createWallet(account: str, password: str):
seed = hsw.getMasterHDKey(account) seed = hsw.getMasterHDKey(account)
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(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/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 {
"seed": seed, "seed": seed,
@@ -127,7 +132,8 @@ def createWallet(account: str, password: str):
"password": password "password": password
} }
def importWallet(account: str, password: str,seed: str):
def importWallet(account: str, password: str, seed: str):
if not hsdConnected(): if not hsdConnected():
return { return {
"error": { "error": {
@@ -141,7 +147,8 @@ def importWallet(account: str, password: str,seed: str):
"mnemonic": seed, "mnemonic": seed,
} }
response = requests.put(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/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": {
@@ -168,6 +175,7 @@ def listWallets():
return response return response
return ['Wallet not connected'] return ['Wallet not connected']
def selectWallet(account: str): def selectWallet(account: str):
# Select wallet # Select wallet
response = hsw.rpc_selectWallet(account) response = hsw.rpc_selectWallet(account)
@@ -178,9 +186,10 @@ 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)
if 'error' in info: if 'error' in info:
return {'available': 0, 'total': 0} return {'available': 0, 'total': 0}
@@ -200,13 +209,13 @@ def getBalance(account: str):
total = total - (domainValue/1000000) total = total - (domainValue/1000000)
locked = locked - (domainValue/1000000) locked = locked - (domainValue/1000000)
# Only keep 2 decimal places # Only keep 2 decimal places
total = round(total, 2) total = round(total, 2)
available = round(available, 2) available = round(available, 2)
return {'available': available, 'total': total, 'locked': locked} return {'available': available, 'total': total, 'locked': locked}
def getBlockHeight(): def getBlockHeight():
# Get the block height # Get the block height
info = hsd.getInfo() info = hsd.getInfo()
@@ -214,6 +223,7 @@ def getBlockHeight():
return 0 return 0
return info['chain']['height'] return info['chain']['height']
def getAddress(account: str): def getAddress(account: str):
# Get the address # Get the address
info = hsw.getAccountInfo(account, 'default') info = hsw.getAccountInfo(account, 'default')
@@ -221,27 +231,31 @@ def getAddress(account: str):
return '' return ''
return info['receiveAddress'] return info['receiveAddress']
def getPendingTX(account: str): def getPendingTX(account: str):
pending = 0 pending = 0
page = 1 page = 1
pageSize = 10 pageSize = 10
while True: while True:
txs = getTransactions(account,page,pageSize) txs = getTransactions(account, page, pageSize)
page+=1 page += 1
pendingPage = 0 pendingPage = 0
for tx in txs: for tx in txs:
if tx['confirmations'] < 1: if tx['confirmations'] < 1:
pending+=1 pending += 1
pendingPage+=1 pendingPage += 1
if pendingPage < pageSize: if pendingPage < pageSize:
break break
return pending return pending
def getDomains(account,own=True):
def getDomains(account, own=True):
if own: if own:
response = requests.get(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/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(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/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:
@@ -256,10 +270,10 @@ def getDomains(account,own=True):
continue continue
domains.append(domain) domains.append(domain)
return domains return domains
def getPageTXCache(account,page,size=100):
def getPageTXCache(account, page, size=100):
page = f"{page}-{size}" page = f"{page}-{size}"
if not os.path.exists(f'cache'): if not os.path.exists(f'cache'):
os.mkdir(f'cache') os.mkdir(f'cache')
@@ -274,7 +288,8 @@ def getPageTXCache(account,page,size=100):
return pageCache[page]['txid'] return pageCache[page]['txid']
return None return None
def pushPageTXCache(account,page,txid,size=100):
def pushPageTXCache(account, page, txid, size=100):
page = f"{page}-{size}" page = f"{page}-{size}"
if not os.path.exists(f'cache/{account}_page.json'): if not os.path.exists(f'cache/{account}_page.json'):
with open(f'cache/{account}_page.json', 'w') as f: with open(f'cache/{account}_page.json', 'w') as f:
@@ -287,27 +302,27 @@ def pushPageTXCache(account,page,txid,size=100):
'txid': txid 'txid': txid
} }
with open(f'cache/{account}_page.json', 'w') as f: with open(f'cache/{account}_page.json', 'w') as f:
json.dump(pageCache, f,indent=4) json.dump(pageCache, f, indent=4)
return pageCache[page]['txid'] return pageCache[page]['txid']
def getTXFromPage(account,page,size=100):
if page == 1:
return getTransactions(account,1,size)[-1]['hash']
cached = getPageTXCache(account,page,size) def getTXFromPage(account, page, size=100):
if page == 1:
return getTransactions(account, 1, size)[-1]['hash']
cached = getPageTXCache(account, page, size)
if cached: if cached:
return getPageTXCache(account,page,size) return getPageTXCache(account, page, size)
previous = getTransactions(account,page,size) previous = getTransactions(account, page, size)
if len(previous) == 0: if len(previous) == 0:
return None return None
hash = previous[-1]['hash'] hash = previous[-1]['hash']
pushPageTXCache(account,page,hash,size) pushPageTXCache(account, page, hash, size)
return hash return hash
def getTransactions(account, page=1, limit=100):
def getTransactions(account,page=1,limit=100):
# Get the transactions # Get the transactions
if hsdVersion() < 7: if hsdVersion() < 7:
if page != 1: if page != 1:
@@ -321,12 +336,14 @@ def getTransactions(account,page=1,limit=100):
if page < 1: if page < 1:
return [] return []
if page > 1: if page > 1:
lastTX = getTXFromPage(account,page-1,limit) lastTX = getTXFromPage(account, page-1, limit)
if lastTX: if lastTX:
response = requests.get(f'http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/tx/history?reverse=true&limit={limit}&after={lastTX}') response = requests.get(
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(f'http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/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 []
@@ -336,23 +353,25 @@ def getTransactions(account,page=1,limit=100):
data = response.json() data = response.json()
# Refresh the cache if the next page is different # Refresh the cache if the next page is different
nextPage = getPageTXCache(account,page,limit) nextPage = getPageTXCache(account, page, limit)
if nextPage is not None and nextPage != data[-1]['hash']: if nextPage is not None and nextPage != data[-1]['hash']:
print(f'Refreshing page {page}') print(f'Refreshing page {page}')
pushPageTXCache(account,page,data[-1]['hash'],limit) pushPageTXCache(account, page, data[-1]['hash'], limit)
return data return data
def getAllTransactions(account): def getAllTransactions(account):
# Get the transactions # Get the transactions
page = 0 page = 0
txs = [] txs = []
while True: while True:
txs += getTransactions(account,page,1000) txs += getTransactions(account, page, 1000)
if len(txs) == 0: if len(txs) == 0:
break break
page += 1 page += 1
return txs return txs
def check_address(address: str, allow_name: bool = True, return_address: bool = False): def check_address(address: str, allow_name: bool = True, return_address: bool = False):
# Check if the address is valid # Check if the address is valid
if address.startswith('@'): if address.startswith('@'):
@@ -364,7 +383,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(f"http://x:{HSD_API}@{HSD_IP}:{HSD_NODE_PORT}",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()
@@ -394,13 +413,12 @@ def check_hip2(domain: str):
if address.startswith("Hip2: "): if address.startswith("Hip2: "):
return address return address
if not check_address(address, False,True): if not check_address(address, False, True):
return 'Hip2: Lookup succeeded but address is invalid' return 'Hip2: Lookup succeeded but address is invalid'
return address return address
def send(account, address, amount):
def send(account,address,amount):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
response = hsw.rpc_selectWallet(account_name) response = hsw.rpc_selectWallet(account_name)
@@ -411,18 +429,19 @@ 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", # response = requests.post(f"http://x:{APIKEY}@{ip}:{HSD_WALLET_PORT}/wallet/{account_name}/unlock",
# json={"passphrase": password,"timeout": 10}) # json={"passphrase": password,"timeout": 10})
if response['error'] is not None: if response['error'] is not None:
return { if response['error']['message'] != "Wallet is not encrypted.":
"error": { return {
"message": response['error']['message'] "error": {
"message": response['error']['message']
}
} }
}
response = hsw.rpc_sendToAddress(address,amount) response = hsw.rpc_sendToAddress(address, amount)
if response['error'] is not None: if response['error'] is not None:
return { return {
"error": { "error": {
@@ -433,10 +452,10 @@ def send(account,address,amount):
"tx": response['result'] "tx": response['result']
} }
def isOwnDomain(account,name: str):
def isOwnDomain(account, name: str):
domains = getDomains(account) domains = getDomains(account)
for domain in domains: for domain in domains:
print(domain)
if domain['name'] == name: if domain['name'] == name:
return True return True
return False return False
@@ -453,7 +472,8 @@ def getDomain(domain: str):
} }
return response['result'] return response['result']
def renewDomain(account,domain):
def renewDomain(account, domain):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -464,9 +484,10 @@ def renewDomain(account,domain):
} }
} }
response = hsw.sendRENEW(account_name,password,domain) response = hsw.sendRENEW(account_name, password, domain)
return response return response
def getDNS(domain: str): def getDNS(domain: str):
# Get the DNS # Get the DNS
response = hsd.rpc_getNameResource(domain) response = hsd.rpc_getNameResource(domain)
@@ -485,7 +506,8 @@ def getDNS(domain: str):
return [] return []
return response['result']['records'] return response['result']['records']
def setDNS(account,domain,records):
def setDNS(account, domain, records):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -511,7 +533,7 @@ def setDNS(account,domain,records):
'type': 'NS', 'type': 'NS',
'ns': record['value'] 'ns': record['value']
}) })
elif record['type'] in ['GLUE4','GLUE6',"SYNTH4","SYNTH6"]: elif record['type'] in ['GLUE4', 'GLUE6', "SYNTH4", "SYNTH6"]:
newRecords.append({ newRecords.append({
'type': record['type'], 'type': record['type'],
'ns': str(record['value']).split(' ')[0], 'ns': str(record['value']).split(' ')[0],
@@ -525,13 +547,15 @@ def setDNS(account,domain,records):
'type': 'TXT', 'type': 'TXT',
'txt': TXTRecords 'txt': TXTRecords
}) })
data = '{"records":'+str(newRecords).replace("'","\"")+'}' data = '{"records":'+str(newRecords).replace("'", "\"")+'}'
response = hsw.sendUPDATE(account_name,password,domain,data) response = hsw.sendUPDATE(account_name, password, domain, data)
return response return response
def register(account,domain):
def register(account, domain):
# Maybe add default dns records? # Maybe add default dns records?
return setDNS(account,domain,'[]') return setDNS(account, domain, '[]')
def getNodeSync(): def getNodeSync():
response = hsd.getInfo() response = hsd.getInfo()
@@ -542,6 +566,7 @@ def getNodeSync():
sync = round(sync, 2) sync = round(sync, 2)
return sync return sync
def getWalletStatus(): def getWalletStatus():
response = hsw.rpc_getWalletInfo() response = hsw.rpc_getWalletInfo()
if 'error' in response and response['error'] != None: if 'error' in response and response['error'] != None:
@@ -560,12 +585,11 @@ def getWalletStatus():
return "Error wallet ahead of node" return "Error wallet ahead of node"
def getBids(account, domain="NONE"): def getBids(account, domain="NONE"):
if domain == "NONE": if domain == "NONE":
response = hsw.getWalletBids(account) response = hsw.getWalletBids(account)
else: else:
response = hsw.getWalletBidsByName(domain,account) response = hsw.getWalletBidsByName(domain, account)
# Add backup for bids with no value # Add backup for bids with no value
bids = [] bids = []
for bid in response: for bid in response:
@@ -578,16 +602,18 @@ def getBids(account, domain="NONE"):
bids.append(bid) bids.append(bid)
return bids return bids
def getReveals(account,domain):
return hsw.getWalletRevealsByName(domain,account) def getReveals(account, domain):
return hsw.getWalletRevealsByName(domain, account)
def getPendingReveals(account): def getPendingReveals(account):
bids = getBids(account) bids = getBids(account)
domains = getDomains(account,False) domains = getDomains(account, False)
pending = [] pending = []
for domain in domains: for domain in domains:
if domain['state'] == "REVEAL": if domain['state'] == "REVEAL":
reveals = getReveals(account,domain['name']) reveals = getReveals(account, domain['name'])
for bid in bids: for bid in bids:
if bid['name'] == domain['name']: if bid['name'] == domain['name']:
state_found = False state_found = False
@@ -601,10 +627,10 @@ def getPendingReveals(account):
return pending return pending
def getPendingRedeems(account,password): def getPendingRedeems(account, password):
hsw.rpc_selectWallet(account) hsw.rpc_selectWallet(account)
hsw.rpc_walletPassphrase(password,10) hsw.rpc_walletPassphrase(password, 10)
tx = hsw.rpc_createREDEEM('','default') tx = hsw.rpc_createREDEEM('', 'default')
if tx['error']: if tx['error']:
return [] return []
@@ -627,9 +653,10 @@ def getPendingRedeems(account,password):
return pending return pending
def getPendingRegisters(account): def getPendingRegisters(account):
bids = getBids(account) bids = getBids(account)
domains = getDomains(account,False) domains = getDomains(account, False)
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:
@@ -639,8 +666,9 @@ def getPendingRegisters(account):
pending.append(bid) pending.append(bid)
return pending return pending
def getPendingFinalizes(account,password):
tx = createBatch(f'{account}:{password}',[["FINALIZE"]]) def getPendingFinalizes(account, password):
tx = createBatch(f'{account}:{password}', [["FINALIZE"]])
if 'error' in tx: if 'error' in tx:
return [] return []
@@ -678,7 +706,7 @@ def getRevealTX(reveal):
return tx['inputs'][index]['prevout']['hash'] return tx['inputs'][index]['prevout']['hash']
def revealAuction(account,domain): def revealAuction(account, domain):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -690,13 +718,14 @@ def revealAuction(account,domain):
} }
try: try:
response = hsw.sendREVEAL(account_name,password,domain) response = hsw.sendREVEAL(account_name, password, domain)
return response return response
except Exception as e: except Exception as e:
return { return {
"error": str(e) "error": str(e)
} }
def revealAll(account): def revealAll(account):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -713,11 +742,16 @@ def revealAll(account):
response = hsw.rpc_selectWallet(account_name) response = hsw.rpc_selectWallet(account_name)
if response['error'] is not None: if response['error'] is not None:
return return
response = hsw.rpc_walletPassphrase(password,10) response = hsw.rpc_walletPassphrase(password, 10)
if response['error'] is not None: if response['error'] is not None:
return if response['error']['message'] != "Wallet is not encrypted.":
return {
"error": {
"message": response['error']['message']
}
}
return requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}",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": {
@@ -725,6 +759,7 @@ def revealAll(account):
} }
} }
def redeemAll(account): def redeemAll(account):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -741,11 +776,16 @@ def redeemAll(account):
response = hsw.rpc_selectWallet(account_name) response = hsw.rpc_selectWallet(account_name)
if response['error'] is not None: if response['error'] is not None:
return return
response = hsw.rpc_walletPassphrase(password,10) response = hsw.rpc_walletPassphrase(password, 10)
if response['error'] is not None: if response['error'] is not None:
return if response['error']['message'] != "Wallet is not encrypted.":
return {
"error": {
"message": response['error']['message']
}
}
return requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}",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": {
@@ -753,6 +793,7 @@ def redeemAll(account):
} }
} }
def registerAll(account): def registerAll(account):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -774,8 +815,9 @@ def registerAll(account):
} }
batch = [] batch = []
for domain in domains: for domain in domains:
batch.append(["UPDATE",domain['name'],{"records":[]}]) batch.append(["UPDATE", domain['name'], {"records": []}])
return sendBatch(account,batch) return sendBatch(account, batch)
def finalizeAll(account): def finalizeAll(account):
account_name = check_account(account) account_name = check_account(account)
@@ -788,9 +830,10 @@ def finalizeAll(account):
} }
} }
return sendBatch(account,[["FINALIZE"]]) return sendBatch(account, [["FINALIZE"]])
def rescan_auction(account,domain):
def rescan_auction(account, domain):
# Get height of the start of the auction # Get height of the start of the auction
response = hsw.rpc_selectWallet(account) response = hsw.rpc_selectWallet(account)
response = hsd.rpc_getNameInfo(domain) response = hsd.rpc_getNameInfo(domain)
@@ -803,11 +846,11 @@ def rescan_auction(account,domain):
"error": "Not in auction" "error": "Not in auction"
} }
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
def bid(account,domain,bid,blind): def bid(account, domain, bid, blind):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -822,7 +865,7 @@ def bid(account,domain,bid,blind):
lockup = int(blind)*1000000 + bid lockup = int(blind)*1000000 + bid
try: try:
response = hsw.sendBID(account_name,password,domain,bid,lockup) response = hsw.sendBID(account_name, password, domain, bid, lockup)
return response return response
except Exception as e: except Exception as e:
return { return {
@@ -832,7 +875,7 @@ def bid(account,domain,bid,blind):
} }
def openAuction(account,domain): def openAuction(account, domain):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -844,7 +887,7 @@ def openAuction(account,domain):
} }
try: try:
response = hsw.sendOPEN(account_name,password,domain) response = hsw.sendOPEN(account_name, password, domain)
return response return response
except Exception as e: except Exception as e:
return { return {
@@ -854,8 +897,7 @@ def openAuction(account,domain):
} }
def transfer(account, domain, address):
def transfer(account,domain,address):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -867,7 +909,7 @@ def transfer(account,domain,address):
} }
try: try:
response = hsw.sendTRANSFER(account_name,password,domain,address) response = hsw.sendTRANSFER(account_name, password, domain, address)
return response return response
except Exception as e: except Exception as e:
return { return {
@@ -876,7 +918,8 @@ def transfer(account,domain,address):
} }
} }
def finalize(account,domain):
def finalize(account, domain):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -891,17 +934,18 @@ def finalize(account,domain):
response = hsw.rpc_selectWallet(account_name) response = hsw.rpc_selectWallet(account_name)
if response['error'] is not None: if response['error'] is not None:
return { return {
"error": { "error": {
"message": response['error']['message'] "message": response['error']['message']
}
} }
} response = hsw.rpc_walletPassphrase(password, 10)
response = hsw.rpc_walletPassphrase(password,10)
if response['error'] is not None: if response['error'] is not None:
return { if response['error']['message'] != "Wallet is not encrypted.":
"error": { return {
"message": response['error']['message'] "error": {
} "message": response['error']['message']
} }
}
response = hsw.rpc_sendFINALIZE(domain) response = hsw.rpc_sendFINALIZE(domain)
return response return response
except Exception as e: except Exception as e:
@@ -911,7 +955,8 @@ def finalize(account,domain):
} }
} }
def cancelTransfer(account,domain):
def cancelTransfer(account, domain):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -926,17 +971,18 @@ def cancelTransfer(account,domain):
response = hsw.rpc_selectWallet(account_name) response = hsw.rpc_selectWallet(account_name)
if response['error'] is not None: if response['error'] is not None:
return { return {
"error": { "error": {
"message": response['error']['message'] "message": response['error']['message']
}
} }
} response = hsw.rpc_walletPassphrase(password, 10)
response = hsw.rpc_walletPassphrase(password,10)
if response['error'] is not None: if response['error'] is not None:
return { if response['error']['message'] != "Wallet is not encrypted.":
"error": { return {
"message": response['error']['message'] "error": {
} "message": response['error']['message']
} }
}
response = hsw.rpc_sendCANCEL(domain) response = hsw.rpc_sendCANCEL(domain)
return response return response
except Exception as e: except Exception as e:
@@ -946,7 +992,8 @@ def cancelTransfer(account,domain):
} }
} }
def revoke(account,domain):
def revoke(account, domain):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -961,17 +1008,18 @@ def revoke(account,domain):
response = hsw.rpc_selectWallet(account_name) response = hsw.rpc_selectWallet(account_name)
if response['error'] is not None: if response['error'] is not None:
return { return {
"error": { "error": {
"message": response['error']['message'] "message": response['error']['message']
}
} }
} response = hsw.rpc_walletPassphrase(password, 10)
response = hsw.rpc_walletPassphrase(password,10)
if response['error'] is not None: if response['error'] is not None:
return { if response['error']['message'] != "Wallet is not encrypted.":
"error": { return {
"message": response['error']['message'] "error": {
} "message": response['error']['message']
} }
}
response = hsw.rpc_sendREVOKE(domain) response = hsw.rpc_sendREVOKE(domain)
return response return response
except Exception as e: except Exception as e:
@@ -981,6 +1029,7 @@ def revoke(account,domain):
} }
} }
def sendBatch(account, batch): def sendBatch(account, batch):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -996,18 +1045,19 @@ def sendBatch(account, batch):
response = hsw.rpc_selectWallet(account_name) response = hsw.rpc_selectWallet(account_name)
if response['error'] is not None: if response['error'] is not None:
return { return {
"error": { "error": {
"message": response['error']['message'] "message": response['error']['message']
}
} }
} response = hsw.rpc_walletPassphrase(password, 10)
response = hsw.rpc_walletPassphrase(password,10)
if response['error'] is not None: if response['error'] is not None:
return { if response['error']['message'] != "Wallet is not encrypted.":
"error": { return {
"message": response['error']['message'] "error": {
} "message": response['error']['message']
} }
response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}",json={ }
response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}", json={
"method": "sendbatch", "method": "sendbatch",
"params": [batch] "params": [batch]
}).json() }).json()
@@ -1015,10 +1065,10 @@ def sendBatch(account, batch):
return response return response
if 'result' not in response: if 'result' not in response:
return { return {
"error": { "error": {
"message": "No result" "message": "No result"
}
} }
}
return response['result'] return response['result']
except Exception as e: except Exception as e:
@@ -1028,6 +1078,7 @@ def sendBatch(account, batch):
} }
} }
def createBatch(account, batch): def createBatch(account, batch):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -1043,18 +1094,19 @@ def createBatch(account, batch):
response = hsw.rpc_selectWallet(account_name) response = hsw.rpc_selectWallet(account_name)
if response['error'] is not None: if response['error'] is not None:
return { return {
"error": { "error": {
"message": response['error']['message'] "message": response['error']['message']
}
} }
} response = hsw.rpc_walletPassphrase(password, 10)
response = hsw.rpc_walletPassphrase(password,10)
if response['error'] is not None: if response['error'] is not None:
return { if response['error']['message'] != "Wallet is not encrypted.":
"error": { return {
"message": response['error']['message'] "error": {
} "message": response['error']['message']
} }
response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}",json={ }
response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}", json={
"method": "createbatch", "method": "createbatch",
"params": [batch] "params": [batch]
}).json() }).json()
@@ -1062,10 +1114,10 @@ def createBatch(account, batch):
return response return response
if 'result' not in response: if 'result' not in response:
return { return {
"error": { "error": {
"message": "No result" "message": "No result"
}
} }
}
return response['result'] return response['result']
except Exception as e: except Exception as e:
@@ -1076,7 +1128,7 @@ def createBatch(account, batch):
} }
#region settingsAPIs # region settingsAPIs
def rescan(): def rescan():
try: try:
response = hsw.walletRescan(0) response = hsw.walletRescan(0)
@@ -1088,6 +1140,7 @@ def rescan():
} }
} }
def resendTXs(): def resendTXs():
try: try:
response = hsw.walletResend() response = hsw.walletResend()
@@ -1100,9 +1153,8 @@ def resendTXs():
} }
def zapTXs(account): def zapTXs(account):
age = 60 * 20 # 20 minutes age = 60 * 20 # 20 minutes
account_name = check_account(account) account_name = check_account(account)
@@ -1115,9 +1167,9 @@ def zapTXs(account):
try: try:
response = requests.post(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/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"
}) })
return response return response
except Exception as e: except Exception as e:
return { return {
@@ -1137,10 +1189,8 @@ def getxPub(account):
} }
} }
try: try:
print(account_name) response = hsw.getAccountInfo(account_name, "default")
response = hsw.getAccountInfo(account_name,"default")
if 'error' in response: if 'error' in response:
return { return {
"error": { "error": {
@@ -1158,7 +1208,7 @@ def getxPub(account):
} }
def signMessage(account,domain,message): def signMessage(account, domain, message):
account_name = check_account(account) account_name = check_account(account)
password = ":".join(account.split(":")[1:]) password = ":".join(account.split(":")[1:])
@@ -1169,23 +1219,23 @@ def signMessage(account,domain,message):
} }
} }
try: try:
response = hsw.rpc_selectWallet(account_name) response = hsw.rpc_selectWallet(account_name)
if response['error'] is not None: if response['error'] is not None:
return { return {
"error": { "error": {
"message": response['error']['message'] "message": response['error']['message']
}
} }
} response = hsw.rpc_walletPassphrase(password, 10)
response = hsw.rpc_walletPassphrase(password,10)
if response['error'] is not None: if response['error'] is not None:
return { if response['error']['message'] != "Wallet is not encrypted.":
"error": { return {
"message": response['error']['message'] "error": {
} "message": response['error']['message']
} }
response = hsw.rpc_signMessageWithName(domain,message) }
response = hsw.rpc_signMessageWithName(domain, message)
return response return response
except Exception as e: except Exception as e:
return { return {
@@ -1194,9 +1244,10 @@ def signMessage(account,domain,message):
} }
} }
def verifyMessageWithName(domain,signature,message):
def verifyMessageWithName(domain, signature, message):
try: try:
response = hsd.rpc_verifyMessageWithName(domain,signature,message) response = hsd.rpc_verifyMessageWithName(domain, signature, message)
if 'result' in response: if 'result' in response:
return response['result'] return response['result']
return False return False
@@ -1204,23 +1255,24 @@ def verifyMessageWithName(domain,signature,message):
return False return False
def verifyMessage(address,signature,message): def verifyMessage(address, signature, message):
try: try:
response = hsd.rpc_verifyMessage(address,signature,message) response = hsd.rpc_verifyMessage(address, signature, message)
if 'result' in response: if 'result' in response:
return response['result'] return response['result']
return False return False
except Exception as e: except Exception as e:
return False return False
#endregion # endregion
def generateReport(account,format="{name},{expiry},{value},{maxBid}"):
def generateReport(account, format="{name},{expiry},{value},{maxBid}"):
domains = getDomains(account) domains = getDomains(account)
lines = [format.replace("{","").replace("}","")] lines = [format.replace("{", "").replace("}", "")]
for domain in domains: for domain in domains:
line = format.replace("{name}",domain['name']) line = format.replace("{name}", domain['name'])
expiry = "N/A" expiry = "N/A"
expiryBlock = "N/A" expiryBlock = "N/A"
if 'daysUntilExpire' in domain['stats']: if 'daysUntilExpire' in domain['stats']:
@@ -1230,15 +1282,16 @@ def generateReport(account,format="{name},{expiry},{value},{maxBid}"):
expiry = expiry.strftime("%d/%m/%Y %H:%M:%S") expiry = expiry.strftime("%d/%m/%Y %H:%M:%S")
expiryBlock = str(domain['stats']['renewalPeriodEnd']) expiryBlock = str(domain['stats']['renewalPeriodEnd'])
line = line.replace("{expiry}",expiry) line = line.replace("{expiry}", expiry)
line = line.replace("{state}",domain['state']) line = line.replace("{state}", domain['state'])
line = line.replace("{expiryBlock}",expiryBlock) line = line.replace("{expiryBlock}", expiryBlock)
line = line.replace("{value}",str(domain['value']/1000000)) line = line.replace("{value}", str(domain['value']/1000000))
line = line.replace("{maxBid}",str(domain['highest']/1000000)) line = line.replace("{maxBid}", str(domain['highest']/1000000))
line = line.replace("{openHeight}",str(domain['height'])) line = line.replace("{openHeight}", str(domain['height']))
lines.append(line) lines.append(line)
return lines return lines
def convertHNS(value: int): def convertHNS(value: int):
return value/1000000 return value/1000000

View File

@@ -2,4 +2,4 @@ HSD_API=123480615465636893475aCwyaae6s45
HSD_IP=localhost HSD_IP=localhost
THEME=black THEME=black
SHOW_EXPIRED=false SHOW_EXPIRED=false
EXPLORER_TX=https://niami.io/tx/ EXPLORER_TX=https://shakeshift.com/transaction/

34
main.py
View File

@@ -178,7 +178,15 @@ def sendConfirmed():
address = request.args.get("address") address = request.args.get("address")
amount = float(request.args.get("amount")) amount = float(request.args.get("amount"))
response = account_module.send(request.cookies.get("account"),address,amount) response = account_module.send(request.cookies.get("account"),address,amount)
if 'error' in response: if 'error' in response and response['error'] != None:
# If error is a dict get the message
if isinstance(response['error'], dict):
if 'message' in response['error']:
return redirect("/send?message=" + response['error']['message'] + "&address=" + address + "&amount=" + str(amount))
else:
return redirect("/send?message=" + str(response['error']) + "&address=" + address + "&amount=" + str(amount))
# If error is a string
return redirect("/send?message=" + response['error'] + "&address=" + address + "&amount=" + str(amount)) return redirect("/send?message=" + response['error'] + "&address=" + address + "&amount=" + str(amount))
return redirect("/success?tx=" + response['tx']) return redirect("/success?tx=" + response['tx'])
@@ -551,7 +559,6 @@ def finalize(domain: str):
return redirect("/logout") return redirect("/logout")
domain = domain.lower() domain = domain.lower()
print(domain)
response = account_module.finalize(request.cookies.get("account"),domain) response = account_module.finalize(request.cookies.get("account"),domain)
if response['error'] != None: if response['error'] != None:
print(response) print(response)
@@ -570,7 +577,6 @@ def cancelTransfer(domain: str):
return redirect("/logout") return redirect("/logout")
domain = domain.lower() domain = domain.lower()
print(domain)
response = account_module.cancelTransfer(request.cookies.get("account"),domain) response = account_module.cancelTransfer(request.cookies.get("account"),domain)
if 'error' in response: if 'error' in response:
if response['error'] != None: if response['error'] != None:
@@ -885,7 +891,6 @@ def auction(domain):
# Get TX # Get TX
revealInfo = account_module.getRevealTX(reveal) revealInfo = account_module.getRevealTX(reveal)
reveal['bid'] = revealInfo reveal['bid'] = revealInfo
print(revealInfo)
bids = render.bids(bids,reveals) bids = render.bids(bids,reveals)
@@ -947,7 +952,6 @@ def rescan_auction(domain):
domain = domain.lower() domain = domain.lower()
response = account_module.rescan_auction(account,domain) response = account_module.rescan_auction(account,domain)
print(response)
return redirect("/auction/" + domain) return redirect("/auction/" + domain)
@app.route('/auction/<domain>/bid') @app.route('/auction/<domain>/bid')
@@ -1023,7 +1027,7 @@ def bid_confirm(domain):
response = account_module.bid(request.cookies.get("account"),domain, response = account_module.bid(request.cookies.get("account"),domain,
float(bid), float(bid),
float(blind)) float(blind))
print(response)
if 'error' in response: if 'error' in response:
return redirect("/auction/" + domain + "?error=" + response['error']['message']) return redirect("/auction/" + domain + "?error=" + response['error']['message'])
@@ -1045,7 +1049,7 @@ def open_auction(domain):
if 'error' in response: if 'error' in response:
if response['error'] != None: if response['error'] != None:
return redirect("/auction/" + domain + "?error=" + response['error']['message']) return redirect("/auction/" + domain + "?error=" + response['error']['message'])
print(response)
return redirect("/success?tx=" + response['hash']) return redirect("/success?tx=" + response['hash'])
@app.route('/auction/<domain>/reveal') @app.route('/auction/<domain>/reveal')
@@ -1112,8 +1116,7 @@ def settings():
# import to time from format "2024-02-13 11:24:03" # import to time from format "2024-02-13 11:24:03"
last_commit = datetime.datetime.strptime(last_commit, "%Y-%m-%d %H:%M:%S") last_commit = datetime.datetime.strptime(last_commit, "%Y-%m-%d %H:%M:%S")
version = f'{last_commit.strftime("%y-%m-%d")} {branch}' version = f'{last_commit.strftime("%y-%m-%d")} {branch}'
if info['commit'] != latestVersion(info['refs']):
if info['commit'] != latestVersion(branch):
version += ' (New version available)' version += ' (New version available)'
return render_template("settings.html", account=account, return render_template("settings.html", account=account,
hsd_version=account_module.hsdVersion(False), hsd_version=account_module.hsdVersion(False),
@@ -1221,7 +1224,6 @@ def login_post():
wallets = account_module.listWallets() wallets = account_module.listWallets()
wallets = render.wallets(wallets) wallets = render.wallets(wallets)
return render_template("login.html", return render_template("login.html",
error="Invalid account",wallets=wallets) error="Invalid account",wallets=wallets)
account = account + ":" + password account = account + ":" + password
@@ -1594,8 +1596,6 @@ def try_path(path):
if not account_module.hsdConnected(): if not account_module.hsdConnected():
return redirect("/login?message=Node not connected") return redirect("/login?message=Node not connected")
if os.path.isfile("templates/" + path + ".html"): if os.path.isfile("templates/" + path + ".html"):
return render_template(path + ".html") return render_template(path + ".html")
else: else:
@@ -1610,7 +1610,9 @@ def page_not_found(e):
if __name__ == '__main__': if __name__ == '__main__':
# Check to see if --debug is in the command line arguments # Check to see if --debug is in the command line arguments
if "--debug" in sys.argv: debug = "--debug" in sys.argv
app.run(debug=True,host='0.0.0.0') port = 5000
else: if "--port" in sys.argv:
app.run(host='0.0.0.0') port = int(sys.argv[sys.argv.index("--port")+1])
app.run(debug=True,host='0.0.0.0',port=port)

View File

@@ -9,7 +9,7 @@ import os
info = { info = {
"name": "Batching Functions", "name": "Batching Functions",
"description": "This is a plugin that provides multiple functions to batch transactions", "description": "This is a plugin that provides multiple functions to batch transactions",
"version": "1.0", "version": "1.1",
"author": "Nathan.Woodburn/" "author": "Nathan.Woodburn/"
} }
# https://hsd-dev.org/api-docs/?shell--cli#sendbatch # https://hsd-dev.org/api-docs/?shell--cli#sendbatch
@@ -394,7 +394,6 @@ def bid(params, authentication):
for domain in domains: for domain in domains:
batch.append(['BID', domain, bid, blind]) batch.append(['BID', domain, bid, blind])
print(batch)
response = sendBatch(batch, authentication) response = sendBatch(batch, authentication)
if 'error' in response: if 'error' in response:
return { return {

View File

@@ -8,7 +8,7 @@ import os
# Get Explorer URL # Get Explorer URL
TX_EXPLORER_URL = os.getenv("EXPLORER_TX") TX_EXPLORER_URL = os.getenv("EXPLORER_TX")
if TX_EXPLORER_URL is None: if TX_EXPLORER_URL is None:
TX_EXPLORER_URL = "https://niami.io/tx/" TX_EXPLORER_URL = "https://shakeshift.com/transaction/"

View File

@@ -9,3 +9,4 @@ requests-doh
Flask-QRcode Flask-QRcode
PySocks PySocks
python-git-info python-git-info
waitress

View File

@@ -1,38 +1,44 @@
from flask import Flask
from main import app
import main
from gunicorn.app.base import BaseApplication
import os import os
import sys
import platform
from main import app
from waitress import serve
class GunicornApp(BaseApplication): threads = 4
def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super().__init__()
def load_config(self): def gunicornServer():
for key, value in self.options.items(): from gunicorn.app.base import BaseApplication
if key in self.cfg.settings and value is not None: class GunicornApp(BaseApplication):
self.cfg.set(key.lower(), value) def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super().__init__()
def load(self): def load_config(self):
return self.application for key, value in self.options.items():
if key in self.cfg.settings and value is not None:
self.cfg.set(key.lower(), value)
if __name__ == '__main__': def load(self):
workers = 1 return self.application
threads = 2
if workers is None:
workers = 1
if threads is None:
threads = 2
workers = int(workers)
threads = int(threads)
options = { options = {
'bind': '0.0.0.0:5000', 'bind': '0.0.0.0:5000',
'workers': workers, 'workers': 2,
'threads': threads, 'threads': threads,
} }
gunicorn_app = GunicornApp(app, options) gunicorn_app = GunicornApp(app, options)
print('Starting server with ' + str(workers) + ' workers and ' + str(threads) + ' threads', flush=True) print(f'Starting server with Gunicorn on {platform.system()} with {threads} threads...', flush=True)
gunicorn_app.run() gunicorn_app.run()
if __name__ == '__main__':
# Check if --gunicorn is in the command line arguments
if "--gunicorn" in sys.argv:
gunicornServer()
sys.exit()
print(f'Starting server with Waitress on {platform.system()} with {threads} threads...', flush=True)
print(f'Press Ctrl+C to stop the server', flush=True)
print(f'Serving on http://0.0.0.0:5000/', flush=True)
serve(app, host="0.0.0.0", port=5000, threads=threads)

View File

@@ -1 +1 @@
async function request(e){try{const t=await fetch(`/api/v1/${e}`);if(!t.ok)throw new Error(`HTTP error! Status: ${t.status}`);const n=await t.json();return void 0!==n.error?`Error: ${n.error}`:n.result}catch(e){return console.error("Request failed:",e),"Error"}}function sortTable(e,t=!1){const n=document.getElementById("data-table"),a=n.querySelector("tbody"),l=Array.from(a.querySelectorAll("tr")),r=n.querySelectorAll("th");let o=n.getAttribute("data-sort-order")||"asc",d=n.getAttribute("data-sort-column")||"-1";o=t||d!=e?"asc":"asc"===o?"desc":"asc",n.setAttribute("data-sort-order",o),n.setAttribute("data-sort-column",e),l.sort(((t,n)=>{let a=t.cells[e].innerText.trim(),l=n.cells[e].innerText.trim(),r=parseFloat(a.replace(/[^0-9.,]/g,"").replace(/,/g,"")),d=parseFloat(l.replace(/[^0-9.,]/g,"").replace(/,/g,""));return isNaN(r)||isNaN(d)?"asc"===o?a.localeCompare(l):l.localeCompare(a):"asc"===o?r-d:d-r})),a.innerHTML="",l.forEach((e=>a.appendChild(e))),updateSortIndicators(r,e,o)}function updateSortIndicators(e,t,n){e.forEach(((e,a)=>{let l=e.querySelector(".sort-indicator");l.innerHTML=a===t?"asc"===n?" ▲":" ▼":""}))}window.addEventListener("load",(async()=>{const e=["hsd-sync","hsd-version","hsd-height","wallet-sync","wallet-available","wallet-total","wallet-locked","wallet-pending","wallet-domainCount","wallet-bidCount","wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"],t=["wallet-available","wallet-total","wallet-locked"],n=["wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"];for(const a of e){const e=document.getElementById(a);if(e){const l=a.replace(/-/g,"/");let r=await request(l);n.includes(a)&&"Error"!=r&&(r=r.length),t.includes(a)&&(r=Number(r).toFixed(2)),r=r.toString().replace(/\B(?=(\d{3})+(?!\d))/g,","),e.innerHTML=r}}})),document.addEventListener("DOMContentLoaded",(function(){fetch("/api/v1/wallet/domains").then((e=>e.json())).then((e=>{const t=document.querySelector("#data-table tbody");t&&(t.innerHTML="",e.result.forEach((e=>{const n=document.createElement("tr"),a=document.createElement("td");a.textContent=e.name,n.appendChild(a);var l="Unknown";"stats"in e&&"daysUntilExpire"in e.stats&&(l=e.stats.daysUntilExpire);const r=document.createElement("td");r.textContent=`${l} days`,n.appendChild(r);const o=document.createElement("td");o.textContent=`${(e.value/1e6).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,",")} HNS`,n.appendChild(o);const d=document.createElement("td");d.innerHTML=e.registered?"<a href='/manage/"+e.name+"'>Manage</a>":"<a href='/auction/"+e.name+"/register'>Register</a>",n.appendChild(d),t.appendChild(n)})),sortTable(0,!0))})).catch((e=>console.error("Error fetching data:",e)))})),setInterval((async function(){const e=["hsd-sync","hsd-height","wallet-sync","wallet-pending","wallet-available","wallet-total"];for(const t of e){const e=document.getElementById(t);if(e){const n=t.replace(/-/g,"/");let a=await request(n);["wallet-available","wallet-total"].includes(t)&&(a=Number(a).toFixed(2)),a=a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,","),e.innerHTML=a}}}),2e4),function(){"use strict";var e=document.querySelector(".sidebar"),t=document.querySelectorAll("#sidebarToggle, #sidebarToggleTop");if(e){e.querySelector(".collapse");var n=[].slice.call(document.querySelectorAll(".sidebar .collapse")).map((function(e){return new bootstrap.Collapse(e,{toggle:!1})}));for(var a of t)a.addEventListener("click",(function(t){if(document.body.classList.toggle("sidebar-toggled"),e.classList.toggle("toggled"),e.classList.contains("toggled"))for(var a of n)a.hide()}));window.addEventListener("resize",(function(){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)<768)for(var e of n)e.hide()}))}var l=document.querySelector("body.fixed-nav .sidebar");l&&l.on("mousewheel DOMMouseScroll wheel",(function(e){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)>768){var t=e.originalEvent,n=t.wheelDelta||-t.detail;this.scrollTop+=30*(n<0?1:-1),e.preventDefault()}}));var r=document.querySelector(".scroll-to-top");r&&window.addEventListener("scroll",(function(){var e=window.pageYOffset;r.style.display=e>100?"block":"none"}))}(); async function request(e){try{const t=await fetch(`/api/v1/${e}`);if(!t.ok)throw new Error(`HTTP error! Status: ${t.status}`);const n=await t.json();return void 0!==n.error?`Error: ${n.error}`:n.result}catch(e){return console.error("Request failed:",e),"Error"}}function sortTable(e,t=!1){const n=document.getElementById("data-table"),a=n.querySelector("tbody"),l=Array.from(a.querySelectorAll("tr")),r=n.querySelectorAll("th");let o=n.getAttribute("data-sort-order")||"asc",i=n.getAttribute("data-sort-column")||"-1";o=t||i!=e?"asc":"asc"===o?"desc":"asc",n.setAttribute("data-sort-order",o),n.setAttribute("data-sort-column",e);const c=determineColumnDataType(l,e);l.sort(((t,n)=>{let a=t.cells[e].innerText.trim(),l=n.cells[e].innerText.trim();if("number"===c){let e=parseFloat(a.replace(/[^0-9.,]/g,"").replace(/,/g,"")),t=parseFloat(l.replace(/[^0-9.,]/g,"").replace(/,/g,""));return"asc"===o?e-t:t-e}if("date"===c){let e=new Date(a),t=new Date(l);return"asc"===o?e-t:t-e}return"asc"===o?a.localeCompare(l,void 0,{sensitivity:"base"}):l.localeCompare(a,void 0,{sensitivity:"base"})})),a.innerHTML="",l.forEach((e=>a.appendChild(e))),updateSortIndicators(r,e,o)}function determineColumnDataType(e,t){const n=Math.min(5,e.length);let a=0,l=0;for(let r=0;r<n&&!(r>=e.length);r++){const n=e[r].cells[t].innerText.trim(),o=parseFloat(n.replace(/[^0-9.,]/g,"").replace(/,/g,""));if(!isNaN(o)&&n.replace(/[^0-9.,\s$%]/g,"").length===n.length){a++;continue}const i=new Date(n);isNaN(i)||"Invalid Date"===i.toString()||l++}return a>=n/2?"number":l>=n/2?"date":"text"}function updateSortIndicators(e,t,n){e.forEach(((e,a)=>{let l=e.querySelector(".sort-indicator");l.innerHTML=a===t?"asc"===n?" ▲":" ▼":""}))}window.addEventListener("load",(async()=>{const e=["hsd-sync","hsd-version","hsd-height","wallet-sync","wallet-available","wallet-total","wallet-locked","wallet-pending","wallet-domainCount","wallet-bidCount","wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"],t=["wallet-available","wallet-total","wallet-locked"],n=["wallet-pendingReveal","wallet-pendingRegister","wallet-pendingRedeem"];for(const a of e){const e=document.getElementById(a);if(e){const l=a.replace(/-/g,"/");let r=await request(l);n.includes(a)&&"Error"!=r&&(r=r.length),t.includes(a)&&(r=Number(r).toFixed(2)),r=r.toString().replace(/\B(?=(\d{3})+(?!\d))/g,","),e.innerHTML=r}}})),document.addEventListener("DOMContentLoaded",(function(){fetch("/api/v1/wallet/domains").then((e=>e.json())).then((e=>{const t=document.querySelector("#data-table tbody");t&&(t.innerHTML="",e.result.forEach((e=>{const n=document.createElement("tr"),a=document.createElement("td");a.textContent=e.name,n.appendChild(a);var l="Unknown";"stats"in e&&"daysUntilExpire"in e.stats&&(l=e.stats.daysUntilExpire);const r=document.createElement("td");r.textContent=`${l} days`,n.appendChild(r);const o=document.createElement("td");o.textContent=`${(e.value/1e6).toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g,",")} HNS`,n.appendChild(o);const i=document.createElement("td");i.innerHTML=e.registered?"<a href='/manage/"+e.name+"'>Manage</a>":"<a href='/auction/"+e.name+"/register'>Register</a>",n.appendChild(i),t.appendChild(n)})),sortTable(0,!0))})).catch((e=>console.error("Error fetching data:",e)))})),setInterval((async function(){const e=["hsd-sync","hsd-height","wallet-sync","wallet-pending","wallet-available","wallet-total"];for(const t of e){const e=document.getElementById(t);if(e){const n=t.replace(/-/g,"/");let a=await request(n);["wallet-available","wallet-total"].includes(t)&&(a=Number(a).toFixed(2)),a=a.toString().replace(/\B(?=(\d{3})+(?!\d))/g,","),e.innerHTML=a}}}),2e4),function(){"use strict";var e=document.querySelector(".sidebar"),t=document.querySelectorAll("#sidebarToggle, #sidebarToggleTop");if(e){e.querySelector(".collapse");var n=[].slice.call(document.querySelectorAll(".sidebar .collapse")).map((function(e){return new bootstrap.Collapse(e,{toggle:!1})}));for(var a of t)a.addEventListener("click",(function(t){if(document.body.classList.toggle("sidebar-toggled"),e.classList.toggle("toggled"),e.classList.contains("toggled"))for(var a of n)a.hide()}));window.addEventListener("resize",(function(){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)<768)for(var e of n)e.hide()}))}var l=document.querySelector("body.fixed-nav .sidebar");l&&l.on("mousewheel DOMMouseScroll wheel",(function(e){if(Math.max(document.documentElement.clientWidth||0,window.innerWidth||0)>768){var t=e.originalEvent,n=t.wheelDelta||-t.detail;this.scrollTop+=30*(n<0?1:-1),e.preventDefault()}}));var r=document.querySelector(".scroll-to-top");r&&window.addEventListener("scroll",(function(){var e=window.pageYOffset;r.style.display=e>100?"block":"none"}))}();

View File

@@ -2,4 +2,4 @@
<span style="display: block;">Check your transaction on a block explorer</span> <span style="display: block;">Check your transaction on a block explorer</span>
<a class="card-link" href="https://niami.io/tx/{{tx}}" target="_blank">Niami</a> <a class="card-link" href="https://niami.io/tx/{{tx}}" target="_blank">Niami</a>
<a class="card-link" href="https://3xpl.com/handshake/transaction/{{tx}}" target="_blank">3xpl</a> <a class="card-link" href="https://3xpl.com/handshake/transaction/{{tx}}" target="_blank">3xpl</a>
<a class="card-link" href="https://hns.cymon.de/tx/{{tx}}" target="_blank">Cymon.de</a> <a class="card-link" href="https://shakeshift.com/transaction/{{tx}}" target="_blank">ShakeShift</a>

View File

@@ -74,7 +74,8 @@
<div class="container-fluid" style="margin-top: 50px;"> <div class="container-fluid" style="margin-top: 50px;">
<div class="card"> <div class="card">
<div class="card-body"> <div class="card-body">
<h4 class="card-title" style="display: inline-block;">DNS</h4><a class="btn btn-primary" role="button" style="position: absolute; right:16px;" href="/manage/{{domain}}/edit?dns={{raw_dns}}">Edit</a><div class="table-responsive"> <h4 class="card-title" style="display: inline-block;">DNS</h4>
<div style="width: fit-content;position: absolute;right: 0px;top: 16px;"><a class="btn btn-primary" role="button" href="https://tools.c.woodburn.au/?domain={{domain}}&amp;url=https://{{domain}}" style="margin: 0px 16px;" target="_blank">Debug</a><a class="btn btn-primary" role="button" href="/manage/{{domain}}/edit?dns={{raw_dns}}" style="margin: 0px 16px;">Edit</a></div><div class="table-responsive">
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>

View File

@@ -64,7 +64,7 @@
</nav> </nav>
<div class="container-fluid" style="margin-bottom: 20px;"> <div class="container-fluid" style="margin-bottom: 20px;">
<h3 class="text-dark mb-1">{{name}}</h3> <h3 class="text-dark mb-1">{{name}}</h3>
<h4 class="text-dark mb-1">{{description}}</h4>{{output|safe}} <h4 class="text-dark mb-1">{{description|safe}}</h4>{{output|safe}}
</div> </div>
</div> </div>
<footer class="sticky-footer" style="background: var(--bs-primary-text-emphasis);"> <footer class="sticky-footer" style="background: var(--bs-primary-text-emphasis);">

View File

@@ -67,7 +67,7 @@
</div> </div>
<div class="card" style="max-width: 500px;margin: auto;margin-top: 50px;"> <div class="card" style="max-width: 500px;margin: auto;margin-top: 50px;">
<div class="card-body"> <div class="card-body">
<h4 class="card-title">Your transaction has been sent and will be mined soon.</h4><span style="display: block;font-size: 12px;">TX: {{tx}}</span><span style="display: block;">Check your transaction on a block explorer</span><a class="card-link" href="https://niami.io/tx/{{tx}}" target="_blank">Niami</a><a class="card-link" href="https://3xpl.com/handshake/transaction/{{tx}}" target="_blank">3xpl</a><a class="card-link" href="https://hns.cymon.de/tx/{{tx}}" target="_blank">HNS.Cymon.de</a> <h4 class="card-title">Your transaction has been sent and will be mined soon.</h4><span style="display: block;font-size: 12px;">TX: {{tx}}</span><span style="display: block;">Check your transaction on a block explorer</span><a class="card-link" href="https://niami.io/tx/{{tx}}" target="_blank">Niami</a><a class="card-link" href="https://3xpl.com/handshake/transaction/{{tx}}" target="_blank">3xpl</a><a class="card-link" href="https://shakeshift.com/transaction/{{tx}}" target="_blank">ShakeShift</a>
</div> </div>
</div> </div>
</div> </div>