generated from nathanwoodburn/python-webserver-template
Nathan Woodburn
376f79370f
All checks were successful
Build Docker / BuildImage (push) Successful in 32s
263 lines
7.1 KiB
Python
263 lines
7.1 KiB
Python
from functools import lru_cache
|
|
import json
|
|
from flask import (
|
|
Flask,
|
|
make_response,
|
|
redirect,
|
|
request,
|
|
jsonify,
|
|
render_template,
|
|
send_from_directory,
|
|
send_file,
|
|
)
|
|
import os
|
|
import json
|
|
import requests
|
|
from datetime import datetime
|
|
import dotenv
|
|
import solana
|
|
import solana.rpc
|
|
from solana.rpc.api import Client
|
|
import solana.rpc.api
|
|
from solders.pubkey import Pubkey
|
|
from solana.rpc.types import TokenAccountOpts
|
|
from pycoingecko import CoinGeckoAPI
|
|
from cachetools import TTLCache
|
|
from cachetools import cached
|
|
|
|
dotenv.load_dotenv()
|
|
|
|
app = Flask(__name__)
|
|
|
|
solana_client = Client(os.environ["SOLANA_URL"])
|
|
token = Pubkey.from_string("mNT61ixgiLnggJ4qf5hDCNj7vTiCqnqysosjncrBydf")
|
|
vault = Pubkey.from_string("NWywvhcqdkJsm1s9VVviPm9UfyDtyCW9t8kDb24PDPN")
|
|
|
|
fiat = "USD"
|
|
usd_to_fiat = 1
|
|
stablecoins = ["usdc", "usdt", "dai"]
|
|
|
|
coingecko_client = CoinGeckoAPI()
|
|
|
|
|
|
def find(name, path):
|
|
for root, dirs, files in os.walk(path):
|
|
if name in files:
|
|
return os.path.join(root, name)
|
|
|
|
# Assets routes
|
|
@app.route("/assets/<path:path>")
|
|
def send_assets(path):
|
|
if path.endswith(".json"):
|
|
return send_from_directory(
|
|
"templates/assets", path, mimetype="application/json"
|
|
)
|
|
|
|
if os.path.isfile("templates/assets/" + path):
|
|
return send_from_directory("templates/assets", path)
|
|
|
|
# Try looking in one of the directories
|
|
filename: str = path.split("/")[-1]
|
|
if (
|
|
filename.endswith(".png")
|
|
or filename.endswith(".jpg")
|
|
or filename.endswith(".jpeg")
|
|
or filename.endswith(".svg")
|
|
):
|
|
if os.path.isfile("templates/assets/img/" + filename):
|
|
return send_from_directory("templates/assets/img", filename)
|
|
if os.path.isfile("templates/assets/img/favicon/" + filename):
|
|
return send_from_directory("templates/assets/img/favicon", filename)
|
|
|
|
return render_template("404.html"), 404
|
|
|
|
|
|
# region Special routes
|
|
@app.route("/favicon.png")
|
|
def faviconPNG():
|
|
return send_from_directory("templates/assets/img", "favicon.png")
|
|
|
|
|
|
@app.route("/.well-known/<path:path>")
|
|
def wellknown(path):
|
|
# Try to proxy to https://nathan.woodburn.au/.well-known/
|
|
req = requests.get(f"https://nathan.woodburn.au/.well-known/{path}")
|
|
return make_response(
|
|
req.content, 200, {"Content-Type": req.headers["Content-Type"]}
|
|
)
|
|
|
|
|
|
# endregion
|
|
|
|
|
|
# region Main routes
|
|
@app.route("/")
|
|
def index():
|
|
tokenSupply = getTokenSupplyString()
|
|
tokenValue = getTokenPrice()
|
|
tokens = getTokens()
|
|
solValue = getSolValue()
|
|
|
|
pie_chart_data = [(token['symbol'].upper(), token['value'], f"{token['name']}: ${'{:.2f}'.format(token['value'])}") for token in tokens]
|
|
pie_chart_data.append(("SOL", solValue, f"Solana: ${"{:.2f}".format(solValue)}"))
|
|
|
|
return render_template("index.html", value=tokenValue, supply=tokenSupply, pie_chart=pie_chart_data)
|
|
|
|
|
|
@app.route("/<path:path>")
|
|
def catch_all(path: str):
|
|
if os.path.isfile("templates/" + path):
|
|
return render_template(path)
|
|
|
|
# Try with .html
|
|
if os.path.isfile("templates/" + path + ".html"):
|
|
return render_template(path + ".html")
|
|
|
|
if os.path.isfile("templates/" + path.strip("/") + ".html"):
|
|
return render_template(path.strip("/") + ".html")
|
|
|
|
# Try to find a file matching
|
|
if path.count("/") < 1:
|
|
# Try to find a file matching
|
|
filename = find(path, "templates")
|
|
if filename:
|
|
return send_file(filename)
|
|
|
|
return render_template("404.html"), 404
|
|
|
|
|
|
# endregion
|
|
|
|
|
|
# region Solana
|
|
|
|
coin_price_cache = TTLCache(maxsize=100, ttl=3600)
|
|
@cached(coin_price_cache)
|
|
def get_coin_price(coin_id):
|
|
|
|
try:
|
|
if coin_id in stablecoins:
|
|
return 1 * usd_to_fiat
|
|
|
|
price = coingecko_client.get_price(ids=coin_id, vs_currencies=fiat)
|
|
|
|
return price[coin_id][fiat.lower()]
|
|
except:
|
|
return 0
|
|
|
|
token_price_cache = TTLCache(maxsize=100, ttl=3600)
|
|
@cached(token_price_cache)
|
|
def get_token_price(token_address:str):
|
|
try:
|
|
price = coingecko_client.get_token_price(id='solana',contract_addresses=token_address,vs_currencies=fiat)
|
|
return price[token_address][fiat.lower()]
|
|
except:
|
|
return 0
|
|
|
|
|
|
token_supply_str_cache = TTLCache(maxsize=1, ttl=3600)
|
|
@cached(token_supply_str_cache)
|
|
def getTokenSupplyString() -> str:
|
|
supply = solana_client.get_token_supply(token)
|
|
return supply.value.ui_amount_string
|
|
|
|
token_supply_cache = TTLCache(maxsize=1, ttl=3600)
|
|
@cached(token_supply_cache)
|
|
def getTokenSupply() -> int:
|
|
supply = solana_client.get_token_supply(token)
|
|
return supply.value.ui_amount
|
|
|
|
sol_value_cache = TTLCache(maxsize=1, ttl=3600)
|
|
@cached(sol_value_cache)
|
|
def getSolValue() -> int:
|
|
SOLbalance = solana_client.get_balance(vault).value / 1000000000
|
|
SOLPrice = get_coin_price("solana")
|
|
return SOLbalance * SOLPrice
|
|
|
|
vault_balance_cache = TTLCache(maxsize=1, ttl=3600)
|
|
@cached(vault_balance_cache)
|
|
def getVaultBalance() -> int:
|
|
# Get balance of vault
|
|
SOLbalance = getSolValue()
|
|
tokens = getTokens()
|
|
tokenValue = 0
|
|
for token in tokens:
|
|
tokenValue += token["value"]
|
|
|
|
print(tokens)
|
|
return SOLbalance + tokenValue
|
|
|
|
|
|
get_tokens_cache = TTLCache(maxsize=1, ttl=3600)
|
|
@cached(get_tokens_cache)
|
|
def getTokens():
|
|
programID = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
|
|
|
tokenAccounts = solana_client.get_token_accounts_by_owner(
|
|
vault,
|
|
TokenAccountOpts(program_id=programID)
|
|
)
|
|
|
|
tokenAccounts = tokenAccounts.value
|
|
tokens = []
|
|
|
|
for tokenAccount in tokenAccounts:
|
|
pubkey = tokenAccount.pubkey
|
|
account = solana_client.get_token_account_balance(pubkey)
|
|
mint = tokenAccount.account.data[:32]
|
|
mint = Pubkey(mint)
|
|
# Decode the mint
|
|
token = {
|
|
"mint": str(mint),
|
|
"balance": account.value.ui_amount
|
|
}
|
|
|
|
token["price"] = get_token_price(token["mint"])
|
|
token["value"] = token["price"] * token["balance"]
|
|
|
|
data = getTokenData(str(mint))
|
|
token["name"] = data["name"]
|
|
token["symbol"] = data["symbol"]
|
|
tokens.append(token)
|
|
|
|
return tokens
|
|
|
|
def getTokenData(tokenMint):
|
|
if not os.path.exists("tokens"):
|
|
os.makedirs("tokens")
|
|
|
|
if os.path.exists(f"tokens/{tokenMint}.json"):
|
|
with open(f"tokens/{tokenMint}.json") as f:
|
|
data = json.load(f)
|
|
return data
|
|
else:
|
|
data = coingecko_client.get_coin_info_from_contract_address_by_id('solana', tokenMint)
|
|
with open(f"tokens/{tokenMint}.json", "w") as f:
|
|
json.dump(data, f)
|
|
return data
|
|
|
|
|
|
def getTokenPrice():
|
|
# Get number of tokens minted
|
|
supply = getTokenSupply()
|
|
vaultBalance = getVaultBalance()
|
|
value = vaultBalance/supply
|
|
# Round value to 2 decimal places and ensure it shows 2 decimal places
|
|
value = "{:.2f}".format(value)
|
|
|
|
return value
|
|
|
|
|
|
# endregion
|
|
|
|
# region Error Catching
|
|
# 404 catch all
|
|
@app.errorhandler(404)
|
|
def not_found(e):
|
|
return render_template("404.html"), 404
|
|
|
|
|
|
# endregion
|
|
if __name__ == "__main__":
|
|
app.run(debug=True, port=5000, host="0.0.0.0")
|