From 09852f19b66af7d46161e8a2ebdbbf30c48fa621 Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Fri, 10 Oct 2025 21:17:23 +1100 Subject: [PATCH] feat: Move solana create transaction to new file --- now.py | 5 -- server.py | 151 ++++++++++++++++++++++++++++++------------------------ sol.py | 45 ++++++++++++++++ 3 files changed, 129 insertions(+), 72 deletions(-) create mode 100644 sol.py diff --git a/now.py b/now.py index 9086a9e..7aef4b8 100644 --- a/now.py +++ b/now.py @@ -2,7 +2,6 @@ import os from flask import render_template from datetime import datetime - def list_now_page_files(): now_pages = os.listdir("templates/now") now_pages = [ @@ -24,7 +23,6 @@ def get_latest_now_date(formatted=False): return date return list_now_dates()[0] -#region Rendering def render_now_page(date,handshake_scripts=None): # If the date is not available, render the latest page 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") return render_template(f"now/{date}.html",DATE=date_formatted,handshake_scripts=handshake_scripts) - def render_latest_now(handshake_scripts=None): now_page = list_now_dates()[0] return render_now_page(now_page,handshake_scripts=handshake_scripts) - -#endregion \ No newline at end of file diff --git a/server.py b/server.py index 1041220..b13f258 100644 --- a/server.py +++ b/server.py @@ -22,19 +22,17 @@ import binascii import base64 from ansi2html import Ansi2HTMLConverter 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 mail import sendEmail -import now -import blog +from now import ( + 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__) CORS(app) @@ -81,6 +79,8 @@ if 'time-zone' not in ncConfig: ncConfig['time-zone'] = 10 # region Helper Functions + + @cache def getAddress(coin: str) -> str: address = "" @@ -95,6 +95,7 @@ def getFilePath(name, path): if name in files: return os.path.join(root, name) + def getClientIP(request): x_forwarded_for = request.headers.get("X-Forwarded-For") if x_forwarded_for: @@ -166,6 +167,7 @@ def asset_get(path): return render_template("404.html"), 404 + @app.route("/sitemap") @app.route("/sitemap.xml") def sitemap_get(): @@ -176,12 +178,14 @@ def sitemap_get(): sitemap = sitemap.replace(".html", "") return make_response(sitemap, 200, {"Content-Type": "application/xml"}) + @app.route("/favicon.") def favicon_get(ext): if ext not in ("png", "svg", "ico"): return render_template("404.html"), 404 return send_from_directory("templates/assets/img/favicon", f"favicon.{ext}") + @app.route("/.js") def javascript_get(name): # Check if file in js directory @@ -192,10 +196,13 @@ def javascript_get(name): # endregion # region Well-known routes + + @app.route("/.well-known/") def wk_index_get(path): return send_from_directory(".well-known", path) + @app.route("/.well-known/wallets/") def wk_wallet_get(path): if path[0] == "." and 'proof' not in path: @@ -214,6 +221,7 @@ def wk_wallet_get(path): return render_template("404.html"), 404 + @app.route("/.well-known/nostr.json") def wk_nostr_get(): # Get name parameter @@ -235,6 +243,7 @@ def wk_nostr_get(): } ) + @app.route("/.well-known/xrp-ledger.toml") def wk_xrp_get(): # Create a response with the xrp-ledger.toml file @@ -248,6 +257,8 @@ def wk_xrp_get(): # endregion # region PWA routes + + @app.route("/manifest.json") def manifest_get(): host = request.host @@ -263,19 +274,29 @@ def manifest_get(): manifest["scope"] = url return jsonify(manifest) + @app.route("/sw.js") def serviceWorker_get(): return send_from_directory("pwa", "sw.js") # endregion + # region Solana Links +SOLANA_HEADERS = { + "Content-Type": "application/json", + "X-Action-Version": "2.4.2", + "X-Blockchain-Ids": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" +} + + @app.route("/actions.json") def sol_actions_get(): return jsonify( {"rules": [{"pathPattern": "/donate**", "apiPath": "/api/donate**"}]} ) + @app.route("/api/donate", methods=["GET", "OPTIONS"]) def sol_donate_get(): data = { @@ -298,12 +319,8 @@ def sol_donate_get(): ] }, } - headers = { - "Content-Type": "application/json", - "X-Action-Version": "2.4.2", - "X-Blockchain-Ids": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp" - } - response = make_response(jsonify(data), 200, headers) + + response = make_response(jsonify(data), 200, SOLANA_HEADERS) if request.method == "OPTIONS": response.headers["Access-Control-Allow-Origin"] = "*" @@ -314,6 +331,7 @@ def sol_donate_get(): return response + @app.route("/api/donate/") def sol_donate_amount_get(amount): data = { @@ -322,68 +340,42 @@ def sol_donate_amount_get(amount): "title": "Donate to Nathan.Woodburn/", "description": f"Donate {amount} SOL to Nathan.Woodburn/", } - return jsonify(data) + return jsonify(data), 200, SOLANA_HEADERS + @app.route("/api/donate/", methods=["POST"]) def sol_donate_post(amount): 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: - return jsonify({"message": "Error: No account provided"}) + return jsonify({"message": "Error: No account provided"}), 400, SOLANA_HEADERS 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 try: amount = float(amount) except: - return jsonify({"message": "Error: Invalid amount"}), 400, headers + return jsonify({"message": "Error: Invalid amount"}), 400, SOLANA_HEADERS 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 - sender = Pubkey.from_string(sender) - 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 + transaction = create_transaction(sender, amount) + return jsonify({"message": "Success", "transaction": transaction}), 200, SOLANA_HEADERS # endregion # region API routes + + @app.route("/api/version") @app.route("/api/v1/version") def api_version_get(): return jsonify({"version": getGitCommit()}) + @app.route("/api") @app.route("/api/") @app.route("/api/v1") @@ -522,6 +514,8 @@ def api_project_get(): # endregion # region Misc routes + + @app.route("/meet") @app.route("/meeting") @app.route("/appointment") @@ -530,10 +524,12 @@ def meetingLink_get(): "https://cloud.woodburn.au/apps/calendar/appointment/PamrmmspWJZr", code=302 ) + @app.route("/links") def links_get(): return render_template("link.html") + @app.route("/generator/") def generator_get(): return render_template(request.path.split("/")[-2] + ".html") @@ -541,6 +537,8 @@ def generator_get(): # endregion # region Main routes + + @app.route("/") def index_get(): global handshake_scripts @@ -751,7 +749,7 @@ def now_index_get(): ): handshake_scripts = "" - return now.render_latest_now(handshake_scripts) + return render_latest_now(handshake_scripts) @app.route("/now/") @@ -766,7 +764,7 @@ def now_path_get(path): ): handshake_scripts = "" - return now.render_now_page(path, handshake_scripts) + return render_now_page(path, handshake_scripts) @app.route("/old") @@ -784,9 +782,9 @@ def now_old_get(): ): handshake_scripts = "" - now_dates = now.list_now_dates()[1:] + now_dates = list_now_dates()[1:] html = '
    ' - html += f'
  • {now.get_latest_now_date(True)}
  • ' + html += f'
  • {get_latest_now_date(True)}
  • ' for date in now_dates: link = date @@ -808,7 +806,7 @@ def now_rss_get(): if ":" in request.host: host = "http://" + request.host # Generate RSS feed - now_pages = now.list_now_page_files() + now_pages = list_now_page_files() rss = f'Nathan.Woodburn/{host}See what I\'ve been up toen-us{datetime.datetime.now(tz=datetime.timezone.utc).strftime("%a, %d %b %Y %H:%M:%S %z")}' for page in now_pages: link = page.strip(".html") @@ -821,7 +819,7 @@ def now_rss_get(): @app.route("/now.json") def now_json_get(): - now_pages = now.list_now_page_files() + now_pages = list_now_page_files() host = "https://" + request.host if ":" in request.host: host = "http://" + request.host @@ -833,6 +831,7 @@ def now_json_get(): # region Blog Pages + @app.route("/blog") @app.route("/blog/") def blog_index_get(): @@ -847,7 +846,7 @@ def blog_index_get(): ): handshake_scripts = "" - return blog.render_blog_home(handshake_scripts) + return render_blog_home(handshake_scripts) @app.route("/blog/") @@ -862,11 +861,13 @@ def blog_path_get(path): ): handshake_scripts = "" - return blog.render_blog_page(path, handshake_scripts) + return render_blog_page(path, handshake_scripts) # endregion # region Donate + + @app.route("/donate") def donate_get(): global handshake_scripts @@ -1007,11 +1008,12 @@ def qraddress_get(address): # Save the QR code image to a temporary file 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 send_file(qr_image_path, mimetype="image/png") + @app.route("/qrcode/") @app.route("/qr/") def qrcode_get(data): @@ -1021,7 +1023,7 @@ def qrcode_get(data): qr.make() 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 logo = Image.open("templates/assets/img/favicon/logo.png") @@ -1038,6 +1040,7 @@ def qrcode_get(data): # endregion + @app.route("/supersecretpath") def supersecretpath_get(): ascii_art = "" @@ -1049,6 +1052,7 @@ def supersecretpath_get(): ascii_art_html = converter.convert(ascii_art) return render_template("ascii.html", ascii_art=ascii_art_html) + @app.route("/download/") def download_get(path): # Check if file exists @@ -1169,6 +1173,7 @@ def hosting_post(): return jsonify({"status": "error", "message": "Failed to send enquiry"}), 500 return jsonify({"status": "success", "message": "Enquiry sent successfully"}), 200 + @app.route("/resume.pdf") def resume_pdf_get(): # Check if file exists @@ -1179,6 +1184,8 @@ def resume_pdf_get(): # endregion # region ACME route + + @app.route("/hnsdoh-acme", methods=["POST"]) def acme_post(): print(f"ACME request from {getClientIP(request)}") @@ -1199,11 +1206,11 @@ def acme_post(): cf = Cloudflare(api_token=os.getenv("CF_TOKEN")) 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( - 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() - 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.create( zone_id=zone_id, @@ -1217,6 +1224,8 @@ def acme_post(): # endregion # region Podcast routes + + @app.route("/ID1") def podcast_index_get(): # Proxy to ID1 url @@ -1225,6 +1234,7 @@ def podcast_index_get(): req.content, 200, {"Content-Type": req.headers["Content-Type"]} ) + @app.route("/ID1/") def podcast_contents_get(): # Proxy to ID1 url @@ -1233,6 +1243,7 @@ def podcast_contents_get(): req.content, 200, {"Content-Type": req.headers["Content-Type"]} ) + @app.route("/ID1/") def podcast_path_get(path): # Proxy to ID1 url @@ -1241,6 +1252,7 @@ def podcast_path_get(path): req.content, 200, {"Content-Type": req.headers["Content-Type"]} ) + @app.route("/ID1.xml") def podcast_xml_get(): # Proxy to ID1 url @@ -1249,6 +1261,7 @@ def podcast_xml_get(): req.content, 200, {"Content-Type": req.headers["Content-Type"]} ) + @app.route("/podsync.opml") def podcast_podsync_get(): req = requests.get("https://podcasts.c.woodburn.au/podsync.opml") @@ -1261,6 +1274,8 @@ def podcast_podsync_get(): # region Error Catching # Catch all for GET requests + + @app.route("/") def catch_all_get(path: str): global handshake_scripts @@ -1314,6 +1329,8 @@ def catch_all_get(path: str): return render_template("404.html"), 404 # 404 catch all + + @app.errorhandler(404) def not_found(e): if request.headers: diff --git a/sol.py b/sol.py new file mode 100644 index 0000000..b099a88 --- /dev/null +++ b/sol.py @@ -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 \ No newline at end of file