1 Commits

Author SHA1 Message Date
0fc95d0eb1 feat: Start on packaging for windows
All checks were successful
Build Docker / Build Image (push) Successful in 44s
2025-02-05 13:03:57 +11:00
16 changed files with 333 additions and 332 deletions

2
.gitignore vendored
View File

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

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://shakeshift.com/transaction/) EXPLORER_TX: URL for exploring transactions (default https://niami.io/tx/)
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,13 +51,11 @@ 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:
@@ -67,7 +65,6 @@ 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
@@ -83,7 +80,6 @@ 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:
@@ -93,13 +89,12 @@ 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 {
@@ -109,8 +104,7 @@ 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( response = requests.put(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}")
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": {
@@ -122,9 +116,10 @@ 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,
@@ -132,8 +127,7 @@ 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": {
@@ -147,8 +141,7 @@ def importWallet(account: str, password: str, seed: str):
"mnemonic": seed, "mnemonic": seed,
} }
response = requests.put( response = requests.put(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}",json=data)
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": {
@@ -175,7 +168,6 @@ 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)
@@ -186,10 +178,9 @@ 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}
@@ -209,13 +200,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()
@@ -223,7 +214,6 @@ 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')
@@ -231,31 +221,27 @@ 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( response = requests.get(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/name?own=true")
f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/name?own=true")
else: else:
response = requests.get( response = requests.get(f"http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/name")
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:
@@ -270,10 +256,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')
@@ -288,8 +274,7 @@ 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:
@@ -302,27 +287,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):
def getTXFromPage(account, page, size=100):
if page == 1: if page == 1:
return getTransactions(account, 1, size)[-1]['hash'] return getTransactions(account,1,size)[-1]['hash']
cached = getPageTXCache(account, page, size) 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:
@@ -336,14 +321,12 @@ 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( response = requests.get(f'http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/tx/history?reverse=true&limit={limit}&after={lastTX}')
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( response = requests.get(f'http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/tx/history?reverse=true&limit={limit}')
f'http://x:{HSD_API}@{HSD_IP}:{HSD_WALLET_PORT}/wallet/{account}/tx/history?reverse=true&limit={limit}')
else: else:
return [] return []
@@ -353,25 +336,23 @@ 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('@'):
@@ -383,7 +364,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()
@@ -413,12 +394,13 @@ 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)
@@ -429,19 +411,18 @@ 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:
if response['error']['message'] != "Wallet is not encrypted.": return {
return { "error": {
"error": { "message": response['error']['message']
"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": {
@@ -452,10 +433,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
@@ -472,8 +453,7 @@ 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:])
@@ -484,10 +464,9 @@ 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)
@@ -506,8 +485,7 @@ 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:])
@@ -533,7 +511,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],
@@ -547,15 +525,13 @@ 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()
@@ -566,7 +542,6 @@ 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:
@@ -585,11 +560,12 @@ 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:
@@ -602,18 +578,16 @@ def getBids(account, domain="NONE"):
bids.append(bid) bids.append(bid)
return bids return bids
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)
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
@@ -627,10 +601,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 []
@@ -653,10 +627,9 @@ 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:
@@ -666,9 +639,8 @@ def getPendingRegisters(account):
pending.append(bid) pending.append(bid)
return pending return pending
def getPendingFinalizes(account,password):
def getPendingFinalizes(account, password): tx = createBatch(f'{account}:{password}',[["FINALIZE"]])
tx = createBatch(f'{account}:{password}', [["FINALIZE"]])
if 'error' in tx: if 'error' in tx:
return [] return []
@@ -706,7 +678,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:])
@@ -718,14 +690,13 @@ 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:])
@@ -742,16 +713,11 @@ 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:
if response['error']['message'] != "Wallet is not encrypted.": return
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": {
@@ -759,7 +725,6 @@ 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:])
@@ -776,16 +741,11 @@ 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:
if response['error']['message'] != "Wallet is not encrypted.": return
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": {
@@ -793,7 +753,6 @@ 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:])
@@ -815,9 +774,8 @@ 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)
@@ -830,10 +788,9 @@ 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)
@@ -846,11 +803,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:])
@@ -865,7 +822,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 {
@@ -875,7 +832,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:])
@@ -887,7 +844,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 {
@@ -897,7 +854,8 @@ 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:])
@@ -909,7 +867,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 {
@@ -918,8 +876,7 @@ 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:])
@@ -934,18 +891,17 @@ 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:
if response['error']['message'] != "Wallet is not encrypted.": return {
return { "error": {
"error": { "message": response['error']['message']
"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:
@@ -955,8 +911,7 @@ 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:])
@@ -971,18 +926,17 @@ 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:
if response['error']['message'] != "Wallet is not encrypted.": return {
return { "error": {
"error": { "message": response['error']['message']
"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:
@@ -992,8 +946,7 @@ 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:])
@@ -1008,18 +961,17 @@ 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:
if response['error']['message'] != "Wallet is not encrypted.": return {
return { "error": {
"error": { "message": response['error']['message']
"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:
@@ -1029,7 +981,6 @@ 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:])
@@ -1045,19 +996,18 @@ 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:
if response['error']['message'] != "Wallet is not encrypted.": return {
return { "error": {
"error": { "message": response['error']['message']
"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()
@@ -1065,10 +1015,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:
@@ -1078,7 +1028,6 @@ 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:])
@@ -1094,19 +1043,18 @@ 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:
if response['error']['message'] != "Wallet is not encrypted.": return {
return { "error": {
"error": { "message": response['error']['message']
"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()
@@ -1114,10 +1062,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:
@@ -1128,7 +1076,7 @@ def createBatch(account, batch):
} }
# region settingsAPIs #region settingsAPIs
def rescan(): def rescan():
try: try:
response = hsw.walletRescan(0) response = hsw.walletRescan(0)
@@ -1140,7 +1088,6 @@ def rescan():
} }
} }
def resendTXs(): def resendTXs():
try: try:
response = hsw.walletResend() response = hsw.walletResend()
@@ -1153,8 +1100,9 @@ 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)
@@ -1167,9 +1115,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 {
@@ -1189,8 +1137,10 @@ def getxPub(account):
} }
} }
try: try:
response = hsw.getAccountInfo(account_name, "default") print(account_name)
response = hsw.getAccountInfo(account_name,"default")
if 'error' in response: if 'error' in response:
return { return {
"error": { "error": {
@@ -1208,7 +1158,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:])
@@ -1219,23 +1169,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:
if response['error']['message'] != "Wallet is not encrypted.": return {
return { "error": {
"error": { "message": response['error']['message']
"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 {
@@ -1244,10 +1194,9 @@ 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
@@ -1255,24 +1204,23 @@ 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']:
@@ -1282,16 +1230,15 @@ 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://shakeshift.com/transaction/ EXPLORER_TX=https://niami.io/tx/

34
main.py
View File

@@ -178,15 +178,7 @@ 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 and response['error'] != None: if 'error' in response:
# 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'])
@@ -559,6 +551,7 @@ 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)
@@ -577,6 +570,7 @@ 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:
@@ -891,6 +885,7 @@ 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)
@@ -952,6 +947,7 @@ 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')
@@ -1027,7 +1023,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'])
@@ -1049,7 +1045,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')
@@ -1116,7 +1112,8 @@ 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),
@@ -1224,6 +1221,7 @@ 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
@@ -1596,6 +1594,8 @@ 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,9 +1610,7 @@ 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
debug = "--debug" in sys.argv if "--debug" in sys.argv:
port = 5000 app.run(debug=True,host='0.0.0.0')
if "--port" in sys.argv: else:
port = int(sys.argv[sys.argv.index("--port")+1]) app.run(host='0.0.0.0',threaded=True)
app.run(debug=True,host='0.0.0.0',port=port)

63
main.spec Normal file
View File

@@ -0,0 +1,63 @@
# -*- mode: python ; coding: utf-8 -*-
import compileall
from PyInstaller.utils.hooks import collect_data_files
import os
compileall.compile_dir('.', force=True)
datas = [
('templates', 'templates'),
('assets', 'assets'),
('themes', 'themes'),
('plugins', 'plugins')
]
hiddenimports = [
'plugins.automations',
'plugins.batching',
'plugins.customPlugins',
'plugins.renewal',
'plugins.varo'
]
# Copy the plugins folder to the dist folder
os.system(f'cp -r plugins dist/')
a = Analysis(
['main.py'],
pathex=[],
binaries=[],
datas=datas,
hiddenimports=hiddenimports,
hookspath=[],
hooksconfig={},
runtime_hooks=[],
excludes=[],
noarchive=False,
optimize=0,
)
pyz = PYZ(a.pure)
exe = EXE(
pyz,
a.scripts,
a.binaries,
a.datas,
[],
name='FireWallet',
debug=False,
bootloader_ignore_signals=False,
strip=False,
upx=True,
upx_exclude=[],
runtime_tmpdir=None,
console=False,
disable_windowed_traceback=False,
argv_emulation=False,
target_arch=None,
codesign_identity=None,
entitlements_file=None,
onefile=False,
)

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.1", "version": "1.0",
"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,6 +394,7 @@ 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://shakeshift.com/transaction/" TX_EXPLORER_URL = "https://niami.io/tx/"

View File

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

View File

@@ -1,44 +1,38 @@
import os from flask import Flask
import sys
import platform
from main import app from main import app
from waitress import serve import main
from gunicorn.app.base import BaseApplication
import os
threads = 4 class GunicornApp(BaseApplication):
def __init__(self, app, options=None):
self.options = options or {}
self.application = app
super().__init__()
def gunicornServer(): def load_config(self):
from gunicorn.app.base import BaseApplication for key, value in self.options.items():
class GunicornApp(BaseApplication): if key in self.cfg.settings and value is not None:
def __init__(self, app, options=None): self.cfg.set(key.lower(), value)
self.options = options or {}
self.application = app
super().__init__()
def load_config(self): def load(self):
for key, value in self.options.items(): return self.application
if key in self.cfg.settings and value is not None:
self.cfg.set(key.lower(), value)
def load(self): if __name__ == '__main__':
return self.application workers = 1
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': 2, 'workers': workers,
'threads': threads, 'threads': threads,
} }
gunicorn_app = GunicornApp(app, options) gunicorn_app = GunicornApp(app, options)
print(f'Starting server with Gunicorn on {platform.system()} with {threads} threads...', flush=True) print('Starting server with ' + str(workers) + ' workers and ' + str(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",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"}))}(); 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"}))}();

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://shakeshift.com/transaction/{{tx}}" target="_blank">ShakeShift</a> <a class="card-link" href="https://hns.cymon.de/tx/{{tx}}" target="_blank">Cymon.de</a>

View File

@@ -74,8 +74,7 @@
<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> <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">
<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|safe}}</h4>{{output|safe}} <h4 class="text-dark mb-1">{{description}}</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://shakeshift.com/transaction/{{tx}}" target="_blank">ShakeShift</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://hns.cymon.de/tx/{{tx}}" target="_blank">HNS.Cymon.de</a>
</div> </div>
</div> </div>
</div> </div>