generated from nathanwoodburn/python-webserver-template
feat: Initial version
All checks were successful
Build Docker / BuildImage (push) Successful in 1m5s
All checks were successful
Build Docker / BuildImage (push) Successful in 1m5s
This commit is contained in:
parent
8e00541b26
commit
db0b194635
@ -1,4 +1,8 @@
|
||||
flask
|
||||
gunicorn
|
||||
requests
|
||||
python-dotenv
|
||||
python-dotenv
|
||||
solders
|
||||
solana
|
||||
pycoingecko
|
||||
cachetools
|
118
server.py
118
server.py
@ -1,4 +1,4 @@
|
||||
from functools import cache
|
||||
from functools import lru_cache
|
||||
import json
|
||||
from flask import (
|
||||
Flask,
|
||||
@ -15,11 +15,30 @@ 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):
|
||||
@ -74,7 +93,9 @@ def wellknown(path):
|
||||
# region Main routes
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template("index.html")
|
||||
tokenSupply = getTokenSupplyString()
|
||||
tokenValue = getTokenPrice()
|
||||
return render_template("index.html", value=tokenValue, supply=tokenSupply)
|
||||
|
||||
|
||||
@app.route("/<path:path>")
|
||||
@ -102,6 +123,99 @@ def catch_all(path: str):
|
||||
# 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=100, 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=100, ttl=3600)
|
||||
@cached(token_supply_cache)
|
||||
def getTokenSupply() -> int:
|
||||
supply = solana_client.get_token_supply(token)
|
||||
return supply.value.ui_amount
|
||||
|
||||
vault_balance_cache = TTLCache(maxsize=100, ttl=3600)
|
||||
@cached(vault_balance_cache)
|
||||
def getVaultBalance() -> int:
|
||||
# Get balance of vault
|
||||
# SOLbalance = solana_client.get_balance(vault).value / 1000000000
|
||||
# SOLPrice = get_coin_price("solana")
|
||||
# Ignore SOL for now as the vault will be handling tokens
|
||||
SOLbalance = 0
|
||||
SOLPrice = 0
|
||||
|
||||
programID = Pubkey.from_string("TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA")
|
||||
|
||||
tokenAccounts = solana_client.get_token_accounts_by_owner(
|
||||
vault,
|
||||
TokenAccountOpts(program_id=programID)
|
||||
)
|
||||
|
||||
tokenAccounts = tokenAccounts.value
|
||||
tokens = []
|
||||
|
||||
tokenValue = 0
|
||||
|
||||
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"]
|
||||
tokenValue += token["value"]
|
||||
tokens.append(token)
|
||||
|
||||
print(tokens)
|
||||
return (SOLbalance * SOLPrice) + tokenValue
|
||||
|
||||
|
||||
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)
|
||||
|
BIN
stWDBRN.bsdesign
Normal file
BIN
stWDBRN.bsdesign
Normal file
Binary file not shown.
7
templates/assets/bootstrap/css/bootstrap.min.css
vendored
Normal file
7
templates/assets/bootstrap/css/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
6
templates/assets/bootstrap/js/bootstrap.min.js
vendored
Normal file
6
templates/assets/bootstrap/js/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
templates/assets/img/01.jpg
Normal file
BIN
templates/assets/img/01.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 164 KiB |
BIN
templates/assets/img/02.jpg
Normal file
BIN
templates/assets/img/02.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
BIN
templates/assets/img/03.jpg
Normal file
BIN
templates/assets/img/03.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 141 KiB |
Binary file not shown.
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 25 KiB |
88
templates/assets/js/wallet.js
Normal file
88
templates/assets/js/wallet.js
Normal file
@ -0,0 +1,88 @@
|
||||
// Get a list of all the wallets available
|
||||
// Try to connect to window.solana and window.solflare
|
||||
|
||||
const solflareWallet = window.solflare;
|
||||
const phantomWallet = window.solana;
|
||||
|
||||
var wallets = [];
|
||||
// Check which are valid
|
||||
if (phantomWallet && phantomWallet.isPhantom) {
|
||||
console.log("Phantom wallet found");
|
||||
wallets.push("Phantom");
|
||||
}
|
||||
if (solflareWallet && solflareWallet.isSolflare) {
|
||||
console.log("Solflare wallet found");
|
||||
wallets.push("Solflare");
|
||||
}
|
||||
|
||||
async function connectPhantomWallet() {
|
||||
try {
|
||||
const { publicKey } = await phantomWallet.connect();
|
||||
console.log("Connected to Phantom wallet with public key", publicKey.toBase58());
|
||||
return publicKey.toBase58();
|
||||
}
|
||||
catch (err) {
|
||||
console.error("Wallet connection failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
async function connectSolflareWallet() {
|
||||
try {
|
||||
await solflareWallet.connect();
|
||||
const publicKey = solflareWallet.publicKey.toBase58();
|
||||
console.log("Connected to Solflare wallet with public key", publicKey);
|
||||
return publicKey;
|
||||
}
|
||||
catch (err) {
|
||||
console.error("Wallet connection failed", err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Inject buttons to connect the wallets
|
||||
function injectButtons() {
|
||||
// Get div to hold buttons
|
||||
var div = document.getElementById("wallet-buttons");
|
||||
if (!div) {
|
||||
return;
|
||||
}
|
||||
// Clear the div
|
||||
div.innerHTML = "";
|
||||
|
||||
// If no wallets found, add message
|
||||
if (wallets.length == 0) {
|
||||
var p = document.createElement("p");
|
||||
p.innerText = "No wallets found";
|
||||
div.appendChild(p);
|
||||
return;
|
||||
}
|
||||
|
||||
// Add buttons for each wallet
|
||||
for (var i = 0; i < wallets.length; i++) {
|
||||
var button = document.createElement("button");
|
||||
button.className = "btn btn-primary";
|
||||
button.style.margin = "5px";
|
||||
button.innerText = wallets[i];
|
||||
if (wallets[i] == "Phantom") {
|
||||
button.onclick = async function () {
|
||||
var publicKey = await connectPhantomWallet();
|
||||
if (publicKey) {
|
||||
window.location.href = "/?publicKey=" + publicKey;
|
||||
}
|
||||
};
|
||||
};
|
||||
if (wallets[i] == "Solflare") {
|
||||
button.onclick = async function () {
|
||||
var publicKey = await connectSolflareWallet();
|
||||
if (publicKey) {
|
||||
window.location.href = "/?publicKey=" + publicKey;
|
||||
}
|
||||
};
|
||||
};
|
||||
div.appendChild(button);
|
||||
}
|
||||
console.log("Wallet buttons injected");
|
||||
}
|
||||
|
||||
// Wait for page to load
|
||||
window.addEventListener("load", injectButtons);
|
@ -1,19 +1,101 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<html data-bs-theme="dark" lang="en-au">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Nathan.Woodburn/</title>
|
||||
<link rel="icon" href="/assets/img/favicon.png" type="image/png">
|
||||
<link rel="stylesheet" href="/assets/css/index.css">
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0, shrink-to-fit=no">
|
||||
<title>Home - Vault | Woodburn</title>
|
||||
<meta name="twitter:description" content="Woodburn Vault">
|
||||
<meta name="twitter:image" content="https://vault.woodburn.au/assets/img/favicon.png">
|
||||
<meta property="og:description" content="Woodburn Vault">
|
||||
<meta property="og:image" content="https://vault.woodburn.au/assets/img/favicon.png">
|
||||
<meta name="description" content="Woodburn Vault">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="Vault | Woodburn">
|
||||
<meta name="twitter:card" content="summary">
|
||||
<meta name="twitter:title" content="Vault | Woodburn">
|
||||
<script type="application/ld+json">
|
||||
{
|
||||
"@context": "http://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "Vault | Woodburn",
|
||||
"url": "https://vault.woodburn.au"
|
||||
}
|
||||
</script>
|
||||
<link rel="icon" type="image/png" sizes="512x512" href="assets/img/favicon.png">
|
||||
<link rel="stylesheet" href="assets/bootstrap/css/bootstrap.min.css">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Catamaran:100,200,300,400,500,600,700,800,900&display=swap">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:100,100i,300,300i,400,400i,700,700i,900,900i&display=swap">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="spacer"></div>
|
||||
<div class="centre">
|
||||
<h1>Nathan.Woodburn/</h1>
|
||||
</div>
|
||||
<nav class="navbar navbar-expand-lg fixed-top bg-dark navbar-custom navbar-dark">
|
||||
<div class="container"><a class="navbar-brand" href="#">Vault | Woodburn</a><button class="navbar-toggler" data-bs-toggle="collapse"><span class="visually-hidden">Toggle navigation</span><span class="navbar-toggler-icon"></span></button></div>
|
||||
</nav>
|
||||
<header class="text-center text-white masthead">
|
||||
<div class="masthead-content">
|
||||
<div class="container">
|
||||
<h1 class="masthead-heading mb-0">Woodburn Portfolio Vault</h1>
|
||||
<h2 class="masthead-subheading mb-0">An easy way to start crypto investing</h2>
|
||||
<p>stWDBRN Token Supply: {{supply}}<br>Current Token Value: {{value}} USD</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-circle-1 bg-circle"></div>
|
||||
<div class="bg-circle-2 bg-circle"></div>
|
||||
<div class="bg-circle-3 bg-circle"></div>
|
||||
<div class="bg-circle-4 bg-circle"></div>
|
||||
</header>
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6 order-lg-2">
|
||||
<div class="p-5"><img class="rounded-circle img-fluid" src="assets/img/01.jpg"></div>
|
||||
</div>
|
||||
<div class="col-lg-6 order-lg-1">
|
||||
<div class="p-5">
|
||||
<h2 class="display-4">1. Buy tokens</h2>
|
||||
<p>To get started buy stWDBRN tokens at the current price to buy a percent of the vault value. The buy in value is then invested in various projects in order to increase the token's value.</p><a class="btn btn-primary" role="button" href="mailto:vault@woodburn.au">Buy</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6 order-lg-1">
|
||||
<div class="p-5"><img class="rounded-circle img-fluid" src="assets/img/02.jpg"></div>
|
||||
</div>
|
||||
<div class="col-lg-6 order-lg-2">
|
||||
<div class="p-5">
|
||||
<h2 class="display-4">2. HODL and check price</h2>
|
||||
<p>Hodl your stWDBRN while the value fluctuates. Check this site to see the current value.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-lg-6 order-lg-2">
|
||||
<div class="p-5"><img class="rounded-circle img-fluid" src="assets/img/03.jpg"></div>
|
||||
</div>
|
||||
<div class="col-lg-6 order-lg-1">
|
||||
<div class="p-5">
|
||||
<h2 class="display-4">3. Sell your tokens</h2>
|
||||
<p>When you want to cash out just sell your tokens back at the current token price.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<footer class="py-5 bg-black">
|
||||
<div class="container">
|
||||
<p class="text-center text-white m-0 small">Copyright © Vault | Woodburn 2024</p>
|
||||
</div>
|
||||
</footer>
|
||||
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user