feat: Started on SOL integration
All checks were successful
Build Docker / BuildImage (push) Successful in 35s

This commit is contained in:
Nathan Woodburn 2024-11-28 14:05:27 +11:00
parent 8c6cad61a2
commit e612637589
Signed by: nathanwoodburn
GPG Key ID: 203B000478AD0EF1
9 changed files with 396 additions and 1 deletions

2
.gitignore vendored
View File

@ -4,3 +4,5 @@ __pycache__/
.env
.vs/
.venv/
api_keys/
chain_data/

89
chain_module.py Normal file
View 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
View 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
View 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

View File

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

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

View File

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

View File

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