feat: Initial version
All checks were successful
Build Docker / BuildImage (push) Successful in 1m5s

This commit is contained in:
Nathan Woodburn 2024-12-04 18:59:04 +11:00
parent 8e00541b26
commit db0b194635
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
11 changed files with 314 additions and 13 deletions

View File

@ -1,4 +1,8 @@
flask
gunicorn
requests
python-dotenv
python-dotenv
solders
solana
pycoingecko
cachetools

118
server.py
View File

@ -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

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

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

View 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);

View File

@ -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&amp;display=swap">
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Lato:100,100i,300,300i,400,400i,700,700i,900,900i&amp;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&nbsp; 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&nbsp;© Vault | Woodburn 2024</p>
</div>
</footer>
<script src="assets/bootstrap/js/bootstrap.min.js"></script>
</body>
</html>