generated from nathanwoodburn/python-webserver-template
feat: Started on SOL integration
All checks were successful
Build Docker / BuildImage (push) Successful in 35s
All checks were successful
Build Docker / BuildImage (push) Successful in 35s
This commit is contained in:
parent
8c6cad61a2
commit
e612637589
2
.gitignore
vendored
2
.gitignore
vendored
@ -4,3 +4,5 @@ __pycache__/
|
||||
.env
|
||||
.vs/
|
||||
.venv/
|
||||
api_keys/
|
||||
chain_data/
|
89
chain_module.py
Normal file
89
chain_module.py
Normal file
@ -0,0 +1,89 @@
|
||||
import os
|
||||
import json
|
||||
import importlib
|
||||
import sys
|
||||
import hashlib
|
||||
|
||||
if not os.path.exists("chain_data"):
|
||||
os.mkdir("chain_data")
|
||||
|
||||
if not os.path.exists("api_keys"):
|
||||
os.mkdir("api_keys")
|
||||
|
||||
def listChains():
|
||||
chains = []
|
||||
for file in os.listdir("chains"):
|
||||
if file.endswith(".py"):
|
||||
if file != "main.py":
|
||||
chain = importlib.import_module("chains."+file[:-3])
|
||||
if "info" not in dir(chain):
|
||||
continue
|
||||
details = chain.info
|
||||
details["link"] = file[:-3]
|
||||
chains.append(details)
|
||||
|
||||
return chains
|
||||
|
||||
|
||||
def chainExists(chain: str):
|
||||
for file in os.listdir("chains"):
|
||||
if file == chain+".py":
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def validateChainAddress(chain: str, address: str):
|
||||
chain = importlib.import_module("chains."+chain)
|
||||
if "validateAddress" not in dir(chain):
|
||||
return False
|
||||
return chain.validateAddress(address)
|
||||
|
||||
def getChainData(chain: str):
|
||||
chain = importlib.import_module("chains."+chain)
|
||||
return chain.info
|
||||
|
||||
def importAddress(chain: str, address: str):
|
||||
chain = importlib.import_module("chains."+chain)
|
||||
if "importAddress" not in dir(chain):
|
||||
return False
|
||||
return chain.importAddress(address)
|
||||
|
||||
|
||||
def getAllAddresses():
|
||||
addresses = []
|
||||
for file in os.listdir("chains"):
|
||||
if file.endswith(".py"):
|
||||
if file != "main.py":
|
||||
chain = importlib.import_module("chains."+file[:-3])
|
||||
if "listAddresses" in dir(chain):
|
||||
chainAddresses = chain.listAddresses()
|
||||
addresses.append({"chain": file[:-3].capitalize(), "addresses": chainAddresses})
|
||||
|
||||
return addresses
|
||||
|
||||
def deleteAddress(chain: str, address: str):
|
||||
chain = importlib.import_module("chains."+chain)
|
||||
if "deleteAddress" not in dir(chain):
|
||||
return False
|
||||
return chain.deleteAddress(address)
|
||||
|
||||
def syncChains():
|
||||
for file in os.listdir("chains"):
|
||||
if file.endswith(".py"):
|
||||
if file != "main.py":
|
||||
chain = importlib.import_module("chains."+file[:-3])
|
||||
if "sync" in dir(chain):
|
||||
chain.sync()
|
||||
|
||||
def addAPIKey(chain: str, apiKey: str):
|
||||
chain = importlib.import_module("chains."+chain)
|
||||
if "addAPIKey" not in dir(chain):
|
||||
return False
|
||||
return chain.addAPIKey(apiKey)
|
||||
|
||||
|
||||
def getTransactions(chain: str):
|
||||
chain = importlib.import_module("chains."+chain)
|
||||
if "getTransactions" not in dir(chain):
|
||||
return False
|
||||
return chain.getTransactions()
|
35
chains/example.py
Normal file
35
chains/example.py
Normal file
@ -0,0 +1,35 @@
|
||||
import json
|
||||
import requests
|
||||
|
||||
|
||||
# Chain Data
|
||||
info = {
|
||||
"name": "Example Chain",
|
||||
"description": "This is a plugin to be used as an example",
|
||||
"version": "1.0",
|
||||
"author": "Nathan.Woodburn/"
|
||||
}
|
||||
|
||||
|
||||
# Functions
|
||||
functions = {
|
||||
"search":{
|
||||
"name": "Search Owned",
|
||||
"type": "default",
|
||||
"description": "Search for owned domains containing a string",
|
||||
"params": {
|
||||
"search": {
|
||||
"name":"Search string",
|
||||
"type":"text"
|
||||
}
|
||||
},
|
||||
"returns": {
|
||||
"domains":
|
||||
{
|
||||
"name": "List of owned domains",
|
||||
"type": "list"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
105
chains/solana.py
Normal file
105
chains/solana.py
Normal file
@ -0,0 +1,105 @@
|
||||
import json
|
||||
import requests
|
||||
import os
|
||||
import time
|
||||
|
||||
# Chain Data
|
||||
info = {
|
||||
"name": "Solana",
|
||||
"ticker": "SOL",
|
||||
"description": "Solana Chain",
|
||||
"version": "1.0",
|
||||
"author": "Nathan.Woodburn/",
|
||||
"APIInfo": "This chain uses helius RPC service. Please provide a helius API key."
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
def validateAddress(address: str):
|
||||
return True
|
||||
|
||||
def importAddress(address: str):
|
||||
if not os.path.exists("chain_data/solana.json"):
|
||||
addresses = [{"address": address,"txs":[],"lastSynced":0}]
|
||||
with open("chain_data/solana.json", "w") as f:
|
||||
json.dump(addresses, f)
|
||||
return True
|
||||
|
||||
with open("chain_data/solana.json", "r") as f:
|
||||
addresses = json.load(f)
|
||||
print(addresses)
|
||||
for existingAddress in addresses:
|
||||
if existingAddress["address"] == address:
|
||||
return True
|
||||
|
||||
addresses.append({"address": address,"txs":[],"lastSynced":0})
|
||||
with open("chain_data/solana.json", "w") as f:
|
||||
json.dump(addresses, f)
|
||||
return True
|
||||
|
||||
|
||||
def listAddresses():
|
||||
with open("chain_data/solana.json", "r") as f:
|
||||
addresses = json.load(f)
|
||||
addresses = [address["address"] for address in addresses]
|
||||
return addresses
|
||||
|
||||
def deleteAddress(address: str):
|
||||
with open("chain_data/solana.json", "r") as f:
|
||||
addresses = json.load(f)
|
||||
for existingAddress in addresses:
|
||||
if existingAddress["address"] == address:
|
||||
addresses.remove(existingAddress)
|
||||
with open("chain_data/solana.json", "w") as f:
|
||||
json.dump(addresses, f)
|
||||
return True
|
||||
return False
|
||||
|
||||
def addAPIKey(key: str):
|
||||
with open("api_keys/solana.txt", "w") as f:
|
||||
f.write(key)
|
||||
return True
|
||||
|
||||
def sync():
|
||||
with open("chain_data/solana.json", "r") as f:
|
||||
addresses = json.load(f)
|
||||
|
||||
# Get SOLScan API key
|
||||
if os.path.exists("api_keys/solana.txt"):
|
||||
with open("api_keys/solana.txt", "r") as f:
|
||||
apiKey = f.read()
|
||||
else:
|
||||
return False
|
||||
allTxs = []
|
||||
for address in addresses:
|
||||
print("Checking address: " + address["address"])
|
||||
resp = requests.get("https://api.helius.xyz/v0/addresses/" + address["address"] + "/transactions/?api-key=" + apiKey)
|
||||
if resp.status_code != 200:
|
||||
print("Error syncing Solana chain")
|
||||
print(resp.status_code)
|
||||
return False
|
||||
transactions = resp.json()
|
||||
print(transactions)
|
||||
|
||||
|
||||
allTxs.append({
|
||||
"address": address["address"],
|
||||
"txs": transactions,
|
||||
"lastSynced": time.time()
|
||||
})
|
||||
|
||||
with open("chain_data/solana.json", "w") as f:
|
||||
json.dump(allTxs, f)
|
||||
return True
|
||||
|
||||
def getTransactions():
|
||||
with open("chain_data/solana.json", "r") as f:
|
||||
addresses = json.load(f)
|
||||
transactions = []
|
||||
for address in addresses:
|
||||
for tx in address["txs"]:
|
||||
transactions.append(tx)
|
||||
|
||||
#TODO Parse transactions
|
||||
return transactions
|
63
server.py
63
server.py
@ -15,6 +15,7 @@ import json
|
||||
import requests
|
||||
from datetime import datetime
|
||||
import dotenv
|
||||
import chain_module
|
||||
|
||||
dotenv.load_dotenv()
|
||||
|
||||
@ -74,8 +75,68 @@ def wellknown(path):
|
||||
# region Main routes
|
||||
@app.route("/")
|
||||
def index():
|
||||
return render_template("index.html")
|
||||
# List chains
|
||||
chains = chain_module.listChains()
|
||||
addresses = chain_module.getAllAddresses()
|
||||
|
||||
return render_template("index.html",chains=chains,addresses=addresses)
|
||||
|
||||
@app.route("/chains/<path:path>")
|
||||
def chains(path: str):
|
||||
path = path.lower()
|
||||
# Check if the chain exists
|
||||
if not chain_module.chainExists(path):
|
||||
return render_template("404.html"), 404
|
||||
|
||||
# Get chain info
|
||||
chain = chain_module.getChainData(path)
|
||||
transactions = chain_module.getTransactions(path)
|
||||
return render_template("chain.html",chain=chain['name'],transactions=transactions)
|
||||
|
||||
@app.route("/chains/<path:path>", methods=["POST"])
|
||||
def chainsPost(path: str):
|
||||
path = path.lower()
|
||||
# Check if the chain exists
|
||||
if not chain_module.chainExists(path):
|
||||
return jsonify({"error": "Chain not found"}), 404
|
||||
|
||||
# Check if the address is valid
|
||||
address = request.form['address']
|
||||
if not chain_module.validateChainAddress(path, address):
|
||||
return jsonify({"error": "Invalid address"}), 400
|
||||
|
||||
if not chain_module.importAddress(path, address):
|
||||
return jsonify({"error": "Error importing address"}), 400
|
||||
return redirect('/')
|
||||
|
||||
@app.route("/chains/<path:path>/delete/<address>")
|
||||
def chainsDelete(path: str, address: str):
|
||||
path = path.lower()
|
||||
# Check if the chain exists
|
||||
if not chain_module.chainExists(path):
|
||||
return render_template("404.html"), 404
|
||||
|
||||
if not chain_module.deleteAddress(path, address):
|
||||
return jsonify({"error": "Error deleting address"}), 400
|
||||
return redirect('/')
|
||||
|
||||
@app.route("/chains/<path:path>/addAPIKey", methods=["POST"])
|
||||
def chainsAddAPIKey(path: str):
|
||||
path = path.lower()
|
||||
# Check if the chain exists
|
||||
if not chain_module.chainExists(path):
|
||||
return jsonify({"error": "Chain not found"}), 404
|
||||
|
||||
apiKey = request.form['apiKey']
|
||||
if not chain_module.addAPIKey(path, apiKey):
|
||||
return jsonify({"error": "Error adding API key"}), 400
|
||||
return redirect('/')
|
||||
|
||||
|
||||
@app.route("/chains/sync")
|
||||
def chainsSync():
|
||||
chain_module.syncChains()
|
||||
return redirect('/')
|
||||
|
||||
@app.route("/<path:path>")
|
||||
def catch_all(path: str):
|
||||
|
29
templates/assets/css/chain.css
Normal file
29
templates/assets/css/chain.css
Normal file
@ -0,0 +1,29 @@
|
||||
body {
|
||||
background-color: #000000;
|
||||
color: #ffffff;
|
||||
}
|
||||
h1 {
|
||||
font-size: 50px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.centre {
|
||||
margin-top: 10%;
|
||||
text-align: center;
|
||||
}
|
||||
a {
|
||||
color: #ffffff;
|
||||
text-decoration: none;
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.chains-list {
|
||||
width: fit-content;
|
||||
border: 1px solid #ffffff;
|
||||
border-radius: 10px;
|
||||
padding: 10px 20px;
|
||||
margin: auto;
|
||||
margin-top: 10px;
|
||||
|
||||
}
|
@ -17,4 +17,25 @@ a {
|
||||
}
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.chains-list {
|
||||
width: fit-content;
|
||||
border: 1px solid #ffffff;
|
||||
border-radius: 10px;
|
||||
padding: 10px 20px;
|
||||
margin: auto;
|
||||
margin-top: 10px;
|
||||
|
||||
}
|
||||
.address-list {
|
||||
width: fit-content;
|
||||
border: 1px solid #ffffff;
|
||||
border-radius: 10px;
|
||||
padding: 10px 20px;
|
||||
margin: auto;
|
||||
margin-top: 10px;
|
||||
|
||||
}
|
||||
.address-list-item {
|
||||
margin-bottom: 10px;
|
||||
}
|
33
templates/chain.html
Normal file
33
templates/chain.html
Normal file
@ -0,0 +1,33 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<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/chain.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="spacer"></div>
|
||||
<div class="centre">
|
||||
<h1>Nathan.Woodburn/ {{chain}}</h1>
|
||||
|
||||
<form method="post">
|
||||
<h3>Import address</h3>
|
||||
<input type="text" name="address" placeholder="Address">
|
||||
<input type="submit" value="Check">
|
||||
</form>
|
||||
|
||||
<form action="/chains/{{chain}}/addAPIKey" method="post">
|
||||
<h3>Add API Key</h3>
|
||||
<input type="text" name="apiKey" placeholder="API Key">
|
||||
<input type="submit" value="Add">
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
{{transactions|safe}}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
@ -13,6 +13,26 @@
|
||||
<div class="spacer"></div>
|
||||
<div class="centre">
|
||||
<h1>Nathan.Woodburn/</h1>
|
||||
|
||||
<div class="address-list">
|
||||
<h3>Imported Addresses</h3>
|
||||
{% for chain in addresses %}
|
||||
<div class="address-list-item">
|
||||
<h4>{{ chain.chain }}</h4>
|
||||
{% for address in chain.addresses %}
|
||||
{{address}} <a href="/chains/{{ chain.chain }}/delete/{{address}}">Delete</a>
|
||||
<br>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="chains-list">
|
||||
<h3>Add New Address</h3>
|
||||
{% for chain in chains %}
|
||||
<p><a href="/chains/{{ chain.link }}">{{ chain.name }}</a></p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user