feat: Move solana create transaction to new file
All checks were successful
Build Docker / BuildImage (push) Successful in 4m18s

This commit is contained in:
2025-10-10 21:17:23 +11:00
parent 8b464cd89d
commit 09852f19b6
3 changed files with 129 additions and 72 deletions

5
now.py
View File

@@ -2,7 +2,6 @@ import os
from flask import render_template from flask import render_template
from datetime import datetime from datetime import datetime
def list_now_page_files(): def list_now_page_files():
now_pages = os.listdir("templates/now") now_pages = os.listdir("templates/now")
now_pages = [ now_pages = [
@@ -24,7 +23,6 @@ def get_latest_now_date(formatted=False):
return date return date
return list_now_dates()[0] return list_now_dates()[0]
#region Rendering
def render_now_page(date,handshake_scripts=None): def render_now_page(date,handshake_scripts=None):
# If the date is not available, render the latest page # If the date is not available, render the latest page
if date is None: if date is None:
@@ -40,9 +38,6 @@ def render_now_page(date,handshake_scripts=None):
date_formatted = date_formatted.strftime("%A, %B %d, %Y") date_formatted = date_formatted.strftime("%A, %B %d, %Y")
return render_template(f"now/{date}.html",DATE=date_formatted,handshake_scripts=handshake_scripts) return render_template(f"now/{date}.html",DATE=date_formatted,handshake_scripts=handshake_scripts)
def render_latest_now(handshake_scripts=None): def render_latest_now(handshake_scripts=None):
now_page = list_now_dates()[0] now_page = list_now_dates()[0]
return render_now_page(now_page,handshake_scripts=handshake_scripts) return render_now_page(now_page,handshake_scripts=handshake_scripts)
#endregion

151
server.py
View File

@@ -22,19 +22,17 @@ import binascii
import base64 import base64
from ansi2html import Ansi2HTMLConverter from ansi2html import Ansi2HTMLConverter
from functools import cache from functools import cache
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solana.rpc.api import Client
from solders.system_program import TransferParams, transfer
from solders.transaction import Transaction
from solders.hash import Hash
from solders.message import MessageV0
from solders.transaction import VersionedTransaction
from solders.null_signer import NullSigner
from PIL import Image from PIL import Image
from mail import sendEmail from mail import sendEmail
import now from now import (
import blog list_now_dates,
get_latest_now_date,
list_now_page_files,
render_latest_now,
render_now_page,
)
from blog import render_blog_home, render_blog_page
from sol import create_transaction
app = Flask(__name__) app = Flask(__name__)
CORS(app) CORS(app)
@@ -81,6 +79,8 @@ if 'time-zone' not in ncConfig:
ncConfig['time-zone'] = 10 ncConfig['time-zone'] = 10
# region Helper Functions # region Helper Functions
@cache @cache
def getAddress(coin: str) -> str: def getAddress(coin: str) -> str:
address = "" address = ""
@@ -95,6 +95,7 @@ def getFilePath(name, path):
if name in files: if name in files:
return os.path.join(root, name) return os.path.join(root, name)
def getClientIP(request): def getClientIP(request):
x_forwarded_for = request.headers.get("X-Forwarded-For") x_forwarded_for = request.headers.get("X-Forwarded-For")
if x_forwarded_for: if x_forwarded_for:
@@ -166,6 +167,7 @@ def asset_get(path):
return render_template("404.html"), 404 return render_template("404.html"), 404
@app.route("/sitemap") @app.route("/sitemap")
@app.route("/sitemap.xml") @app.route("/sitemap.xml")
def sitemap_get(): def sitemap_get():
@@ -176,12 +178,14 @@ def sitemap_get():
sitemap = sitemap.replace(".html", "") sitemap = sitemap.replace(".html", "")
return make_response(sitemap, 200, {"Content-Type": "application/xml"}) return make_response(sitemap, 200, {"Content-Type": "application/xml"})
@app.route("/favicon.<ext>") @app.route("/favicon.<ext>")
def favicon_get(ext): def favicon_get(ext):
if ext not in ("png", "svg", "ico"): if ext not in ("png", "svg", "ico"):
return render_template("404.html"), 404 return render_template("404.html"), 404
return send_from_directory("templates/assets/img/favicon", f"favicon.{ext}") return send_from_directory("templates/assets/img/favicon", f"favicon.{ext}")
@app.route("/<name>.js") @app.route("/<name>.js")
def javascript_get(name): def javascript_get(name):
# Check if file in js directory # Check if file in js directory
@@ -192,10 +196,13 @@ def javascript_get(name):
# endregion # endregion
# region Well-known routes # region Well-known routes
@app.route("/.well-known/<path:path>") @app.route("/.well-known/<path:path>")
def wk_index_get(path): def wk_index_get(path):
return send_from_directory(".well-known", path) return send_from_directory(".well-known", path)
@app.route("/.well-known/wallets/<path:path>") @app.route("/.well-known/wallets/<path:path>")
def wk_wallet_get(path): def wk_wallet_get(path):
if path[0] == "." and 'proof' not in path: if path[0] == "." and 'proof' not in path:
@@ -214,6 +221,7 @@ def wk_wallet_get(path):
return render_template("404.html"), 404 return render_template("404.html"), 404
@app.route("/.well-known/nostr.json") @app.route("/.well-known/nostr.json")
def wk_nostr_get(): def wk_nostr_get():
# Get name parameter # Get name parameter
@@ -235,6 +243,7 @@ def wk_nostr_get():
} }
) )
@app.route("/.well-known/xrp-ledger.toml") @app.route("/.well-known/xrp-ledger.toml")
def wk_xrp_get(): def wk_xrp_get():
# Create a response with the xrp-ledger.toml file # Create a response with the xrp-ledger.toml file
@@ -248,6 +257,8 @@ def wk_xrp_get():
# endregion # endregion
# region PWA routes # region PWA routes
@app.route("/manifest.json") @app.route("/manifest.json")
def manifest_get(): def manifest_get():
host = request.host host = request.host
@@ -263,19 +274,29 @@ def manifest_get():
manifest["scope"] = url manifest["scope"] = url
return jsonify(manifest) return jsonify(manifest)
@app.route("/sw.js") @app.route("/sw.js")
def serviceWorker_get(): def serviceWorker_get():
return send_from_directory("pwa", "sw.js") return send_from_directory("pwa", "sw.js")
# endregion # endregion
# region Solana Links # region Solana Links
SOLANA_HEADERS = {
"Content-Type": "application/json",
"X-Action-Version": "2.4.2",
"X-Blockchain-Ids": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
}
@app.route("/actions.json") @app.route("/actions.json")
def sol_actions_get(): def sol_actions_get():
return jsonify( return jsonify(
{"rules": [{"pathPattern": "/donate**", "apiPath": "/api/donate**"}]} {"rules": [{"pathPattern": "/donate**", "apiPath": "/api/donate**"}]}
) )
@app.route("/api/donate", methods=["GET", "OPTIONS"]) @app.route("/api/donate", methods=["GET", "OPTIONS"])
def sol_donate_get(): def sol_donate_get():
data = { data = {
@@ -298,12 +319,8 @@ def sol_donate_get():
] ]
}, },
} }
headers = {
"Content-Type": "application/json", response = make_response(jsonify(data), 200, SOLANA_HEADERS)
"X-Action-Version": "2.4.2",
"X-Blockchain-Ids": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
}
response = make_response(jsonify(data), 200, headers)
if request.method == "OPTIONS": if request.method == "OPTIONS":
response.headers["Access-Control-Allow-Origin"] = "*" response.headers["Access-Control-Allow-Origin"] = "*"
@@ -314,6 +331,7 @@ def sol_donate_get():
return response return response
@app.route("/api/donate/<amount>") @app.route("/api/donate/<amount>")
def sol_donate_amount_get(amount): def sol_donate_amount_get(amount):
data = { data = {
@@ -322,68 +340,42 @@ def sol_donate_amount_get(amount):
"title": "Donate to Nathan.Woodburn/", "title": "Donate to Nathan.Woodburn/",
"description": f"Donate {amount} SOL to Nathan.Woodburn/", "description": f"Donate {amount} SOL to Nathan.Woodburn/",
} }
return jsonify(data) return jsonify(data), 200, SOLANA_HEADERS
@app.route("/api/donate/<amount>", methods=["POST"]) @app.route("/api/donate/<amount>", methods=["POST"])
def sol_donate_post(amount): def sol_donate_post(amount):
if not request.json: if not request.json:
return jsonify({"message": "Error: No JSON data provided"}) return jsonify({"message": "Error: No JSON data provided"}), 400, SOLANA_HEADERS
if "account" not in request.json: if "account" not in request.json:
return jsonify({"message": "Error: No account provided"}) return jsonify({"message": "Error: No account provided"}), 400, SOLANA_HEADERS
sender = request.json["account"] sender = request.json["account"]
headers = {
"Content-Type": "application/json",
"X-Action-Version": "2.4.2",
"X-Blockchain-Ids": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
}
# Make sure amount is a number # Make sure amount is a number
try: try:
amount = float(amount) amount = float(amount)
except: except:
return jsonify({"message": "Error: Invalid amount"}), 400, headers return jsonify({"message": "Error: Invalid amount"}), 400, SOLANA_HEADERS
if amount < 0.0001: if amount < 0.0001:
return jsonify({"message": "Error: Amount too small"}), 400, headers return jsonify({"message": "Error: Amount too small"}), 400, SOLANA_HEADERS
# Create transaction transaction = create_transaction(sender, amount)
sender = Pubkey.from_string(sender) return jsonify({"message": "Success", "transaction": transaction}), 200, SOLANA_HEADERS
receiver = Pubkey.from_string(
"AJsPEEe6S7XSiVcdZKbeV8GRp1QuhFUsG8mLrqL4XgiU")
transfer_ix = transfer(
TransferParams(
from_pubkey=sender, to_pubkey=receiver, lamports=int(
amount * 1000000000)
)
)
solana_client = Client("https://api.mainnet-beta.solana.com")
blockhashData = solana_client.get_latest_blockhash()
blockhash = blockhashData.value.blockhash
msg = MessageV0.try_compile(
payer=sender,
instructions=[transfer_ix],
address_lookup_table_accounts=[],
recent_blockhash=blockhash,
)
tx = VersionedTransaction(message=msg, keypairs=[NullSigner(sender)])
tx = bytes(tx).hex()
raw_bytes = binascii.unhexlify(tx)
base64_string = base64.b64encode(raw_bytes).decode("utf-8")
return jsonify({"message": "Success", "transaction": base64_string}), 200, headers
# endregion # endregion
# region API routes # region API routes
@app.route("/api/version") @app.route("/api/version")
@app.route("/api/v1/version") @app.route("/api/v1/version")
def api_version_get(): def api_version_get():
return jsonify({"version": getGitCommit()}) return jsonify({"version": getGitCommit()})
@app.route("/api") @app.route("/api")
@app.route("/api/") @app.route("/api/")
@app.route("/api/v1") @app.route("/api/v1")
@@ -522,6 +514,8 @@ def api_project_get():
# endregion # endregion
# region Misc routes # region Misc routes
@app.route("/meet") @app.route("/meet")
@app.route("/meeting") @app.route("/meeting")
@app.route("/appointment") @app.route("/appointment")
@@ -530,10 +524,12 @@ def meetingLink_get():
"https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr", code=302 "https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr", code=302
) )
@app.route("/links") @app.route("/links")
def links_get(): def links_get():
return render_template("link.html") return render_template("link.html")
@app.route("/generator/") @app.route("/generator/")
def generator_get(): def generator_get():
return render_template(request.path.split("/")[-2] + ".html") return render_template(request.path.split("/")[-2] + ".html")
@@ -541,6 +537,8 @@ def generator_get():
# endregion # endregion
# region Main routes # region Main routes
@app.route("/") @app.route("/")
def index_get(): def index_get():
global handshake_scripts global handshake_scripts
@@ -751,7 +749,7 @@ def now_index_get():
): ):
handshake_scripts = "" handshake_scripts = ""
return now.render_latest_now(handshake_scripts) return render_latest_now(handshake_scripts)
@app.route("/now/<path:path>") @app.route("/now/<path:path>")
@@ -766,7 +764,7 @@ def now_path_get(path):
): ):
handshake_scripts = "" handshake_scripts = ""
return now.render_now_page(path, handshake_scripts) return render_now_page(path, handshake_scripts)
@app.route("/old") @app.route("/old")
@@ -784,9 +782,9 @@ def now_old_get():
): ):
handshake_scripts = "" handshake_scripts = ""
now_dates = now.list_now_dates()[1:] now_dates = list_now_dates()[1:]
html = '<ul class="list-group">' html = '<ul class="list-group">'
html += f'<a style="text-decoration:none;" href="/now"><li class="list-group-item" style="background-color:#000000;color:#ffffff;">{now.get_latest_now_date(True)}</li></a>' html += f'<a style="text-decoration:none;" href="/now"><li class="list-group-item" style="background-color:#000000;color:#ffffff;">{get_latest_now_date(True)}</li></a>'
for date in now_dates: for date in now_dates:
link = date link = date
@@ -808,7 +806,7 @@ def now_rss_get():
if ":" in request.host: if ":" in request.host:
host = "http://" + request.host host = "http://" + request.host
# Generate RSS feed # Generate RSS feed
now_pages = now.list_now_page_files() now_pages = list_now_page_files()
rss = f'<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Nathan.Woodburn/</title><link>{host}</link><description>See what I\'ve been up to</description><language>en-us</language><lastBuildDate>{datetime.datetime.now(tz=datetime.timezone.utc).strftime("%a, %d %b %Y %H:%M:%S %z")}</lastBuildDate><atom:link href="{host}/now.rss" rel="self" type="application/rss+xml" />' rss = f'<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Nathan.Woodburn/</title><link>{host}</link><description>See what I\'ve been up to</description><language>en-us</language><lastBuildDate>{datetime.datetime.now(tz=datetime.timezone.utc).strftime("%a, %d %b %Y %H:%M:%S %z")}</lastBuildDate><atom:link href="{host}/now.rss" rel="self" type="application/rss+xml" />'
for page in now_pages: for page in now_pages:
link = page.strip(".html") link = page.strip(".html")
@@ -821,7 +819,7 @@ def now_rss_get():
@app.route("/now.json") @app.route("/now.json")
def now_json_get(): def now_json_get():
now_pages = now.list_now_page_files() now_pages = list_now_page_files()
host = "https://" + request.host host = "https://" + request.host
if ":" in request.host: if ":" in request.host:
host = "http://" + request.host host = "http://" + request.host
@@ -833,6 +831,7 @@ def now_json_get():
# region Blog Pages # region Blog Pages
@app.route("/blog") @app.route("/blog")
@app.route("/blog/") @app.route("/blog/")
def blog_index_get(): def blog_index_get():
@@ -847,7 +846,7 @@ def blog_index_get():
): ):
handshake_scripts = "" handshake_scripts = ""
return blog.render_blog_home(handshake_scripts) return render_blog_home(handshake_scripts)
@app.route("/blog/<path:path>") @app.route("/blog/<path:path>")
@@ -862,11 +861,13 @@ def blog_path_get(path):
): ):
handshake_scripts = "" handshake_scripts = ""
return blog.render_blog_page(path, handshake_scripts) return render_blog_page(path, handshake_scripts)
# endregion # endregion
# region Donate # region Donate
@app.route("/donate") @app.route("/donate")
def donate_get(): def donate_get():
global handshake_scripts global handshake_scripts
@@ -1007,11 +1008,12 @@ def qraddress_get(address):
# Save the QR code image to a temporary file # Save the QR code image to a temporary file
qr_image_path = "/tmp/qr_code.png" qr_image_path = "/tmp/qr_code.png"
qr_image.save(qr_image_path) # type: ignore qr_image.save(qr_image_path) # type: ignore
# Return the QR code image as a response # Return the QR code image as a response
return send_file(qr_image_path, mimetype="image/png") return send_file(qr_image_path, mimetype="image/png")
@app.route("/qrcode/<path:data>") @app.route("/qrcode/<path:data>")
@app.route("/qr/<path:data>") @app.route("/qr/<path:data>")
def qrcode_get(data): def qrcode_get(data):
@@ -1021,7 +1023,7 @@ def qrcode_get(data):
qr.make() qr.make()
qr_image: Image.Image = qr.make_image( qr_image: Image.Image = qr.make_image(
fill_color="black", back_color="white").convert('RGB') # type: ignore fill_color="black", back_color="white").convert('RGB') # type: ignore
# Add logo # Add logo
logo = Image.open("templates/assets/img/favicon/logo.png") logo = Image.open("templates/assets/img/favicon/logo.png")
@@ -1038,6 +1040,7 @@ def qrcode_get(data):
# endregion # endregion
@app.route("/supersecretpath") @app.route("/supersecretpath")
def supersecretpath_get(): def supersecretpath_get():
ascii_art = "" ascii_art = ""
@@ -1049,6 +1052,7 @@ def supersecretpath_get():
ascii_art_html = converter.convert(ascii_art) ascii_art_html = converter.convert(ascii_art)
return render_template("ascii.html", ascii_art=ascii_art_html) return render_template("ascii.html", ascii_art=ascii_art_html)
@app.route("/download/<path:path>") @app.route("/download/<path:path>")
def download_get(path): def download_get(path):
# Check if file exists # Check if file exists
@@ -1169,6 +1173,7 @@ def hosting_post():
return jsonify({"status": "error", "message": "Failed to send enquiry"}), 500 return jsonify({"status": "error", "message": "Failed to send enquiry"}), 500
return jsonify({"status": "success", "message": "Enquiry sent successfully"}), 200 return jsonify({"status": "success", "message": "Enquiry sent successfully"}), 200
@app.route("/resume.pdf") @app.route("/resume.pdf")
def resume_pdf_get(): def resume_pdf_get():
# Check if file exists # Check if file exists
@@ -1179,6 +1184,8 @@ def resume_pdf_get():
# endregion # endregion
# region ACME route # region ACME route
@app.route("/hnsdoh-acme", methods=["POST"]) @app.route("/hnsdoh-acme", methods=["POST"])
def acme_post(): def acme_post():
print(f"ACME request from {getClientIP(request)}") print(f"ACME request from {getClientIP(request)}")
@@ -1199,11 +1206,11 @@ def acme_post():
cf = Cloudflare(api_token=os.getenv("CF_TOKEN")) cf = Cloudflare(api_token=os.getenv("CF_TOKEN"))
zone = cf.zones.list(name="hnsdoh.com").to_dict() zone = cf.zones.list(name="hnsdoh.com").to_dict()
zone_id = zone["result"][0]["id"] # type: ignore zone_id = zone["result"][0]["id"] # type: ignore
existing_records = cf.dns.records.list( existing_records = cf.dns.records.list(
zone_id=zone_id, type="TXT", name="_acme-challenge.hnsdoh.com" # type: ignore zone_id=zone_id, type="TXT", name="_acme-challenge.hnsdoh.com" # type: ignore
).to_dict() ).to_dict()
record_id = existing_records["result"][0]["id"] # type: ignore record_id = existing_records["result"][0]["id"] # type: ignore
cf.dns.records.delete(dns_record_id=record_id, zone_id=zone_id) cf.dns.records.delete(dns_record_id=record_id, zone_id=zone_id)
cf.dns.records.create( cf.dns.records.create(
zone_id=zone_id, zone_id=zone_id,
@@ -1217,6 +1224,8 @@ def acme_post():
# endregion # endregion
# region Podcast routes # region Podcast routes
@app.route("/ID1") @app.route("/ID1")
def podcast_index_get(): def podcast_index_get():
# Proxy to ID1 url # Proxy to ID1 url
@@ -1225,6 +1234,7 @@ def podcast_index_get():
req.content, 200, {"Content-Type": req.headers["Content-Type"]} req.content, 200, {"Content-Type": req.headers["Content-Type"]}
) )
@app.route("/ID1/") @app.route("/ID1/")
def podcast_contents_get(): def podcast_contents_get():
# Proxy to ID1 url # Proxy to ID1 url
@@ -1233,6 +1243,7 @@ def podcast_contents_get():
req.content, 200, {"Content-Type": req.headers["Content-Type"]} req.content, 200, {"Content-Type": req.headers["Content-Type"]}
) )
@app.route("/ID1/<path:path>") @app.route("/ID1/<path:path>")
def podcast_path_get(path): def podcast_path_get(path):
# Proxy to ID1 url # Proxy to ID1 url
@@ -1241,6 +1252,7 @@ def podcast_path_get(path):
req.content, 200, {"Content-Type": req.headers["Content-Type"]} req.content, 200, {"Content-Type": req.headers["Content-Type"]}
) )
@app.route("/ID1.xml") @app.route("/ID1.xml")
def podcast_xml_get(): def podcast_xml_get():
# Proxy to ID1 url # Proxy to ID1 url
@@ -1249,6 +1261,7 @@ def podcast_xml_get():
req.content, 200, {"Content-Type": req.headers["Content-Type"]} req.content, 200, {"Content-Type": req.headers["Content-Type"]}
) )
@app.route("/podsync.opml") @app.route("/podsync.opml")
def podcast_podsync_get(): def podcast_podsync_get():
req = requests.get("https://podcasts.c.woodburn.au/podsync.opml") req = requests.get("https://podcasts.c.woodburn.au/podsync.opml")
@@ -1261,6 +1274,8 @@ def podcast_podsync_get():
# region Error Catching # region Error Catching
# Catch all for GET requests # Catch all for GET requests
@app.route("/<path:path>") @app.route("/<path:path>")
def catch_all_get(path: str): def catch_all_get(path: str):
global handshake_scripts global handshake_scripts
@@ -1314,6 +1329,8 @@ def catch_all_get(path: str):
return render_template("404.html"), 404 return render_template("404.html"), 404
# 404 catch all # 404 catch all
@app.errorhandler(404) @app.errorhandler(404)
def not_found(e): def not_found(e):
if request.headers: if request.headers:

45
sol.py Normal file
View File

@@ -0,0 +1,45 @@
from solders.keypair import Keypair
from solders.pubkey import Pubkey
from solana.rpc.api import Client
from solders.system_program import TransferParams, transfer
from solders.transaction import Transaction
from solders.hash import Hash
from solders.message import MessageV0
from solders.transaction import VersionedTransaction
from solders.null_signer import NullSigner
import binascii
import base64
import os
SOLANA_ADDRESS = None
if os.path.isfile(".well-known/wallets/SOL"):
with open(".well-known/wallets/SOL") as file:
address = file.read()
SOLANA_ADDRESS = Pubkey.from_string(address.strip())
def create_transaction(sender_address: str, amount: float) -> str:
if SOLANA_ADDRESS is None:
raise ValueError("SOLANA_ADDRESS is not set. Please ensure the .well-known/wallets/SOL file exists and contains a valid address.")
# Create transaction
sender = Pubkey.from_string(sender_address)
transfer_ix = transfer(
TransferParams(
from_pubkey=sender, to_pubkey=SOLANA_ADDRESS, lamports=int(
amount * 1000000000)
)
)
solana_client = Client("https://api.mainnet-beta.solana.com")
blockhashData = solana_client.get_latest_blockhash()
blockhash = blockhashData.value.blockhash
msg = MessageV0.try_compile(
payer=sender,
instructions=[transfer_ix],
address_lookup_table_accounts=[],
recent_blockhash=blockhash,
)
tx = VersionedTransaction(message=msg, keypairs=[NullSigner(sender)])
tx = bytes(tx).hex()
raw_bytes = binascii.unhexlify(tx)
base64_string = base64.b64encode(raw_bytes).decode("utf-8")
return base64_string