From 1258d2d0c9239f36fd4de74afa4c696acbd8c49f Mon Sep 17 00:00:00 2001 From: freqnik Date: Sat, 25 Oct 2025 21:53:33 -0400 Subject: [PATCH 01/18] Add SubToPlan(). Rename to ShareSubTX(). Update API to handle new type of allocation for users. --- meile_plan_api.py | 73 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 66 insertions(+), 7 deletions(-) diff --git a/meile_plan_api.py b/meile_plan_api.py index 92e7f53..e9c5f98 100644 --- a/meile_plan_api.py +++ b/meile_plan_api.py @@ -19,7 +19,7 @@ from werkzeug.security import generate_password_hash, check_password_hash from sentinel_sdk.sdk import SDKInstance -from sentinel_sdk.types import TxParams +from sentinel_sdk.types import TxParams, RenewalPricePolicy from sentinel_sdk.utils import search_attribute from sentinel_protobuf.cosmos.base.v1beta1.coin_pb2 import Coin from mospy import Transaction @@ -32,7 +32,7 @@ import scrtxxs -VERSION=20250911.1514 +VERSION=20251025.2127 app = Flask(__name__) mysql = MySQL() @@ -184,7 +184,7 @@ def CheckRenewalStatus(subid, wallet): else: return False, None, None -def AllocateTX(sdk, sub_id: int, wallet, size=scrtxxs.BYTES): +def SubToPlan(plan_id: int, wallet: str): # Add logging WalletLogFile = os.path.join(WalletLogDIR, "meile_allocate.log") log_file_descriptor = open(WalletLogFile, "a+") @@ -196,7 +196,50 @@ def AllocateTX(sdk, sub_id: int, wallet, size=scrtxxs.BYTES): denom="udvpn" ) - tx = sdk.subscriptions.Allocate(address=wallet, bytes=str(size), id=sub_id, tx_params=tx_params) + + tx = sdk.subscriptions.StartSubscription(plan_id=plan_id, denom="udvpn", renewal = RenewalPricePolicy.RENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUAL, tx_params=tx_params) + + if tx.get("log", None) is not None: + log_file_descriptor.write(f"\nERROR:\n{tx.get('log')}") + log_file_descriptor.flush() + log_file_descriptor.close() + message = "Error subscribing to plan. Please contact support@mathnodes.com for assistance." + return {"status" : False, + "message" : message, + "hash" : "0x0", + "tx_response" : None, + "sub_id" : None} + + if tx.get("hash", None) is not None: + tx_response = sdk.nodes.wait_transaction(tx["hash"]) + log_file_descriptor.write(f"\nSuccess:\n {tx_response}") + subscription_id = search_attribute( + tx_response, "sentinel.subscription.v3.EventCreate", "subscription_id" + ) + log_file_descriptor.flush() + log_file_descriptor.close() + return {"status" : True, + "message" : "Success.", + "hash" : tx['hash'], + "tx_response" : tx_response, + 'sub_id' : subscription_id} + +def ShareSubTX(sdk, sub_id: int, wallet, size=scrtxxs.BYTES): + # Add logging + WalletLogFile = os.path.join(WalletLogDIR, "meile_allocate.log") + log_file_descriptor = open(WalletLogFile, "a+") + + tx_params = TxParams( + gas=150000, + gas_multiplier=1.2, + fee_amount=31415, + denom="udvpn" + ) + + tx = sdk.subscriptions.ShareSubscription(subscription_id=sub_id, + wallet_address=wallet, + bytes=str(size), + tx_params=tx_params) if tx.get("log", None) is not None: log_file_descriptor.write(f"\nERROR:\n{tx.get('log')}") @@ -268,7 +311,7 @@ def add_wallet_to_plan(): wallet = JSON['data']['wallet'] plan_id = int(JSON['data']['plan_id']) # plan ID, we should have 4 or 5 plans. Will be a UUID. duration = int(JSON['data']['duration']) # duration of plan subscription, in months - sub_id = int(JSON['data']['sub_id']) # subscription ID of plan + #sub_id = int(JSON['data']['sub_id']) # subscription ID of plan uuid = JSON['data']['uuid'] # uuid of subscription amt_paid = float(JSON['data']['amt']) denom = JSON['data']['denom'] @@ -307,13 +350,29 @@ def add_wallet_to_plan(): WalletLogFile = os.path.join(WalletLogDIR, "meile_plan.log") log_file_descriptor = open(WalletLogFile, "a+") - result = AllocateTX(sdk, sub_id, wallet) + sub_result = SubToPlan(plan_id, wallet) + if not sub_result['status']: + PlanTX = {'status' : result["status"], + 'wallet' : wallet, + 'planid' : plan_id, + 'duration' : duration, + 'tx' : result["hash"], + 'message' : result["message"], + 'expires' : None} + print(PlanTX) + log_file_descriptor.write(json.dumps(PlanTX) + '\n') + return jsonify(PlanTX) + + else: + sub_id = sub_result['sub_id'] + + result = ShareSubTX(sdk, sub_id, wallet) if not result['status']: PlanTX = {'status' : result["status"], 'wallet' : wallet, 'planid' : plan_id, - 'id' : sub_id, + 'id' : sub_result['sub_id'], 'duration' : duration, 'tx' : result["hash"], 'message' : result["message"], From 8821ed0e25be96cf3a7457aaec26c938dfcf76cf Mon Sep 17 00:00:00 2001 From: freqnik Date: Wed, 29 Oct 2025 17:07:01 -0400 Subject: [PATCH 02/18] Update PNS module in PMS. --- meile_plan_api.py | 2 +- pms/plan_node_subscriptions.py | 47 +++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/meile_plan_api.py b/meile_plan_api.py index e9c5f98..2b0fdc1 100644 --- a/meile_plan_api.py +++ b/meile_plan_api.py @@ -372,7 +372,7 @@ def add_wallet_to_plan(): PlanTX = {'status' : result["status"], 'wallet' : wallet, 'planid' : plan_id, - 'id' : sub_result['sub_id'], + 'id' : sub_id, 'duration' : duration, 'tx' : result["hash"], 'message' : result["message"], diff --git a/pms/plan_node_subscriptions.py b/pms/plan_node_subscriptions.py index 1ccd2a9..679888f 100755 --- a/pms/plan_node_subscriptions.py +++ b/pms/plan_node_subscriptions.py @@ -12,7 +12,7 @@ from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins from sentinel_sdk.sdk import SDKInstance -from sentinel_sdk.types import TxParams +from sentinel_sdk.types import TxParams, Price, RenewalPricePolicy from sentinel_sdk.utils import search_attribute from keyrings.cryptfile.cryptfile import CryptFileKeyring import ecdsa @@ -28,7 +28,7 @@ MNAPI = "https://api.sentinel.mathnodes.com" -NODEAPI = "/sentinel/nodes/%s" +NODEAPI = "/sentinel/node/v3/nodes/%s" GRPC = scrtxxs.GRPC_DEV SSL = True VERSION = 20250715.1555 @@ -127,23 +127,32 @@ def subscribe_to_nodes_for_plan(self, nodeaddress, duration=0, GB=0): ) try: - tx = self.sdk.nodes.SubscribeToNode( + price = Price( + denom="udvpn" + ) + tx = self.sdk.lease.StartLease( node_address=nodeaddress, - gigabytes=int(GB), # TODO: review this please - hours=int(duration), # TODO: review this please - denom="udvpn", - tx_params=tx_params, + hours=scrtxxs.HOURS, + max_price=price, + renewal=RenewalPricePolicy.RENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUALRENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUAL ) if tx.get("log", None) is not None: return(False, tx["log"]) - + if tx.get("hash", None) is not None: tx_response = self.sdk.nodes.wait_transaction(tx["hash"]) print(tx_response) - subscription_id = search_attribute( - tx_response, "sentinel.node.v2.EventCreateSubscription", "id" + lease_id = search_attribute( + tx_response, "sentinel.lease.v3.EventCreate", "lease_id" ) + now = datetime.now() + inactive_at = now + timedelta(hours=scrtxxs.HOURS) + self.UpdateNodePlanDB(nodeaddress, lease_id, inactive_at) + + return (True, lease_id) + + ''' if subscription_id: sleep(4) try: @@ -154,22 +163,19 @@ def subscribe_to_nodes_for_plan(self, nodeaddress, duration=0, GB=0): now = datetime.now() inactive_at = now + timedelta(hours=scrtxxs.HOURS) inactive_at = inactive_at.strftime('%Y-%m-%d %H:%M:%S') - - self.UpdateNodePlanDB(nodeaddress, inactive_at) - - return (True, subscription_id) + ''' return(False, "Tx error") except grpc.RpcError as e: print(e.details()) - def UpdateNodePlanDB(self, nodeaddress, inactive_at): + def UpdateNodePlanDB(self, nodeaddress, lease_id, inactive_at): c = self._db.cursor() q = ''' - UPDATE plan_node_subscriptions SET inactive_date = '%s' WHERE node_address = '%s'; - ''' % (inactive_at, nodeaddress) + UPDATE plan_node_subscriptions SET inactive_date = '%s', node_subscription_id = '%s' WHERE node_address = '%s'; + ''' % (inactive_at, lease_id, nodeaddress) print(f"[pns]: {q}") c.execute(q) @@ -201,6 +207,7 @@ def add_node_to_plan(self, plan_id, node): return (False,"Tx error") +''' def run_update(uuid): update_cmd = f"{scrtxxs.HELPERS}/update-node-scriptions.py --uuid {uuid}" @@ -208,7 +215,7 @@ def run_update(uuid): proc1.wait(timeout=30) proc_out,proc_err = proc1.communicate() - +''' def run_insert(node_file, uuid): update_cmd = f"{scrtxxs.HELPERS}/insert-nodes.py --uuid {uuid} --file {node_file}" @@ -293,7 +300,7 @@ def run_insert(node_file, uuid): print(f"[pns]: Linking {n} to plan {plan_id}...") for pid in plan_id: ps.add_node_to_plan(pid, n) - + ''' print("[pns]: Waiting....") sleep(10) # Run db updater script with UUIDs @@ -304,4 +311,4 @@ def run_insert(node_file, uuid): run_update(uuid) sleep(2) print("[pns]: Done.") - \ No newline at end of file + ''' \ No newline at end of file From 69c8ea27eee4b8c007d58b76a8cf9d5f50dd0423 Mon Sep 17 00:00:00 2001 From: freqnik Date: Wed, 29 Oct 2025 17:23:29 -0400 Subject: [PATCH 03/18] Fix Typo --- pms/plan_node_subscriptions.py | 4 ++-- requirements.txt | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/pms/plan_node_subscriptions.py b/pms/plan_node_subscriptions.py index 679888f..a2ac2c9 100755 --- a/pms/plan_node_subscriptions.py +++ b/pms/plan_node_subscriptions.py @@ -31,7 +31,7 @@ NODEAPI = "/sentinel/node/v3/nodes/%s" GRPC = scrtxxs.GRPC_DEV SSL = True -VERSION = 20250715.1555 +VERSION = 20251029.1707 class PlanSubscribe(): @@ -134,7 +134,7 @@ def subscribe_to_nodes_for_plan(self, nodeaddress, duration=0, GB=0): node_address=nodeaddress, hours=scrtxxs.HOURS, max_price=price, - renewal=RenewalPricePolicy.RENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUALRENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUAL + renewal=RenewalPricePolicy.RENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUAL ) if tx.get("log", None) is not None: diff --git a/requirements.txt b/requirements.txt index 0427da6..89e2eb8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -84,7 +84,8 @@ requests==2.25.1 requests-toolbelt==0.9.1 safe-pysha3==1.0.4 SecretStorage==3.3.1 -sentinel_protobuf==0.3.3 +sentinel_protobuf==0.4.3 +snetinel_sdk==0.1.0 six==1.16.0 sniffio==1.3.1 SQLAlchemy==1.3.12 From e29145af0ad09c0e2923113e7945cacdf4c8e286 Mon Sep 17 00:00:00 2001 From: freqnik Date: Wed, 29 Oct 2025 21:09:39 -0400 Subject: [PATCH 04/18] Update PNS modules for PMS --- pms/plan_node_subscriptions.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/pms/plan_node_subscriptions.py b/pms/plan_node_subscriptions.py index a2ac2c9..e38b471 100755 --- a/pms/plan_node_subscriptions.py +++ b/pms/plan_node_subscriptions.py @@ -12,8 +12,10 @@ from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins from sentinel_sdk.sdk import SDKInstance -from sentinel_sdk.types import TxParams, Price, RenewalPricePolicy +from sentinel_sdk.types import TxParams from sentinel_sdk.utils import search_attribute +from sentinel_protobuf.sentinel.types.v1.price_pb2 import Price +from sentinel_protobuf.sentinel.types.v1.renewal_pb2 import RenewalPricePolicy from keyrings.cryptfile.cryptfile import CryptFileKeyring import ecdsa import hashlib @@ -127,11 +129,14 @@ def subscribe_to_nodes_for_plan(self, nodeaddress, duration=0, GB=0): ) try: + # temporary price = Price( - denom="udvpn" + denom="udvpn", + base_value="0", + quote_value="0" ) tx = self.sdk.lease.StartLease( - node_address=nodeaddress, + node=nodeaddress, hours=scrtxxs.HOURS, max_price=price, renewal=RenewalPricePolicy.RENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUAL @@ -144,7 +149,7 @@ def subscribe_to_nodes_for_plan(self, nodeaddress, duration=0, GB=0): tx_response = self.sdk.nodes.wait_transaction(tx["hash"]) print(tx_response) lease_id = search_attribute( - tx_response, "sentinel.lease.v3.EventCreate", "lease_id" + tx_response, "sentinel.lease.v1.EventCreate", "lease_id" ) now = datetime.now() inactive_at = now + timedelta(hours=scrtxxs.HOURS) @@ -217,6 +222,7 @@ def run_update(uuid): proc_out,proc_err = proc1.communicate() ''' def run_insert(node_file, uuid): + update_cmd = f"{scrtxxs.HELPERS}/insert-nodes.py --uuid {uuid} --file {node_file}" proc1 = Popen(update_cmd, shell=True) @@ -297,9 +303,13 @@ def run_insert(node_file, uuid): print(f"[pns]: Subscribing to {n} for {scrtxxs.HOURS} hour(s) on plan {plan}...") response = ps.subscribe_to_nodes_for_plan(n, duration=scrtxxs.HOURS) print(f"[pns]: {response}") + plan_id = list(set(plan_id)) print(f"[pns]: Linking {n} to plan {plan_id}...") for pid in plan_id: - ps.add_node_to_plan(pid, n) + try: + ps.add_node_to_plan(pid, n) + except Exception as e: + print(str(e)) ''' print("[pns]: Waiting....") sleep(10) From c0c6a675ad8e773dca193d2b635ea061a0c04f56 Mon Sep 17 00:00:00 2001 From: freqnik Date: Thu, 30 Oct 2025 16:18:12 -0400 Subject: [PATCH 05/18] Add v12 subscriber migration script --- scrtxxs.py | 1 + v12_migration.py | 148 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 v12_migration.py diff --git a/scrtxxs.py b/scrtxxs.py index 35511a8..7e93238 100644 --- a/scrtxxs.py +++ b/scrtxxs.py @@ -14,6 +14,7 @@ MySQLDB = 'db_name' #BYTES = 1073741824000 # 1 TiB BYTES = 10995116277760 # 10 TiB +BYTES_50 = 54975581388800 # 50 TIB ONE_GB = 1073741824 HOURS = 360 # sub amount in hours RPC = "https://rpc.mathnodes.com:443" diff --git a/v12_migration.py b/v12_migration.py new file mode 100644 index 0000000..1aa8905 --- /dev/null +++ b/v12_migration.py @@ -0,0 +1,148 @@ +#!/bin/env python3 + +''' +Run in a crontab: +0 * * * * cmd +''' + + +import argparse +import scrtxxs +from urllib.parse import urlparse +from bip_utils import Bip39SeedGenerator, Bip44, Bip44Coins + +from sentinel_sdk.sdk import SDKInstance +from sentinel_sdk.types import TxParams +from sentinel_sdk.utils import search_attribute +from sentinel_protobuf.sentinel.types.v1.price_pb2 import Price +from sentinel_protobuf.sentinel.types.v1.renewal_pb2 import RenewalPricePolicy +from keyrings.cryptfile.cryptfile import CryptFileKeyring +import ecdsa +import hashlib +import bech32 +from os import path, getcwd +import pymysql +from datetime import datetime,timedelta +from subprocess import Popen +from time import sleep +import requests +import grpc + +MNAPI = "https://api.sentinel.mathnodes.com" +NODEAPI = "/sentinel/node/v3/nodes/%s" +GRPC = scrtxxs.GRPC_DEV +SSL = True +VERSION = 20251029.1707 + +class V12Migration(): + def __init__(self, keyring_passphrase, wallet_name, seed_phrase = None): + self.wallet_name = wallet_name + + if seed_phrase: + seed_bytes = Bip39SeedGenerator(seed_phrase).Generate() + bip44_def_ctx = Bip44.FromSeed(seed_bytes, Bip44Coins.COSMOS).DeriveDefaultPath() + privkey_obj = ecdsa.SigningKey.from_string(bip44_def_ctx.PrivateKey().Raw().ToBytes(), curve=ecdsa.SECP256k1) + pubkey = privkey_obj.get_verifying_key() + s = hashlib.new("sha256", pubkey.to_string("compressed")).digest() + r = hashlib.new("ripemd160", s).digest() + five_bit_r = bech32.convertbits(r, 8, 5) + account_address = bech32.bech32_encode("sent", five_bit_r) + print(account_address) + self.keyring = self.__keyring(keyring_passphrase) + self.keyring.set_password("meile-plan", wallet_name, bip44_def_ctx.PrivateKey().Raw().ToBytes().hex()) + else: + self.keyring = self.__keyring(keyring_passphrase) + + self._db = pymysql.connect(host=scrtxxs.MySQLHost, + port=scrtxxs.MySQLPort, + user=scrtxxs.MySQLUsername, + passwd=scrtxxs.MySQLPassword, + db=scrtxxs.MySQLDB, + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor) + + private_key = self.keyring.get_password("meile-plan", self.wallet_name) + + grpcaddr, grpcport = urlparse(GRPC).netloc.split(":") + + self.sdk = SDKInstance(grpcaddr, int(grpcport), secret=private_key, ssl=SSL) + + + def __keyring(self, keyring_passphrase: str): + kr = CryptFileKeyring() + kr.filename = "keyring.cfg" + kr.file_path = path.join(scrtxxs.PlanKeyringDIR, kr.filename) + kr.keyring_key = keyring_passphrase + return kr + + def GetActiveSubscribers(self): + + c = self._db.cursor() + q = "SELECT * FROM meile_subscriptions WHERE active = 1;" + c.execute(q) + + return c.fetchall() + + def ShareSubscriptionWithActiveSubscribers(self, active_subscribers): + tx_params = TxParams( + # denom="udvpn", # TODO: from ConfParams + # fee_amount=20000, # TODO: from ConfParams + # gas=ConfParams.GAS, + gas_multiplier=1.15 + ) + for subscriber in active_subscribers: + wallet = subscriber['wallet'] + plan_id = subscriber['plan_id'] + + tx = self.sdk.subscriptions.StartSubscription(plan_id=plan_id, + denom="udvpn", + renewal=RenewalPricePolicy.RENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUAL + ) + if tx.get("log", None) is not None: + return(False, tx["log"]) + + if tx.get("hash", None) is not None: + tx_response = self.sdk.subscriptions.wait_transaction(tx["hash"]) + print(tx_response) + sub_id = search_attribute( + tx_response, "sentinel.subscription.v3.EventCreate", "subscription_id" + ) + print(f"Subscription ID: {sub_id}") + + sleep(3) + tx = self.sdk.subscriptions.ShareSubscription(subscription_id=int(sub_id), + wallet_address=wallet, + bytes=str(scrtxxs.BYTES_50)) + + if tx.get("log", None) is not None: + return(False, tx["log"]) + + if tx.get("hash", None) is not None: + tx_response = self.sdk.subscriptions.wait_transaction(tx["hash"]) + print(tx_response) + granted_bytes = search_attribute( + tx_response, "sentinel.subscription.v3.EventAllocate", "granted_bytes" + ) + print(f"Granted Bytes: {granted_bytes}") + + answer = input(f"Update table for {wallet} (Y/n):") + if answer.upper() == "Y": + self.UpdateSubscriberTable(wallet,int(sub_id)) + + + def UpdateSubscriberTable(self, wallet, sub_id: int): + c = self._db.cursor() + + query = 'UPDATE meile_subscriptions SET subscription_id = %d, expires = NOW() + INTERVAL 35 DAY WHERE wallet = "%s";' % (sub_id, wallet) + c.execute(query) + self._db.commit() + +if __name__ == "__main__": + v12 = V12Migration(scrtxxs.HotWalletPW, scrtxxs.WalletName, None) + v12.ShareSubscriptionWithActiveSubscribers(v12.GetActiveSubscribers()) + + + + + + \ No newline at end of file From ea1f87eb4f3fc878bbb27b736bfbd4bb1746b0ab Mon Sep 17 00:00:00 2001 From: freqnik Date: Sat, 1 Nov 2025 01:13:01 -0400 Subject: [PATCH 06/18] Update dependency --- meile_plan_api.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/meile_plan_api.py b/meile_plan_api.py index 2b0fdc1..debffbb 100644 --- a/meile_plan_api.py +++ b/meile_plan_api.py @@ -19,9 +19,10 @@ from werkzeug.security import generate_password_hash, check_password_hash from sentinel_sdk.sdk import SDKInstance -from sentinel_sdk.types import TxParams, RenewalPricePolicy +from sentinel_sdk.types import TxParams from sentinel_sdk.utils import search_attribute from sentinel_protobuf.cosmos.base.v1beta1.coin_pb2 import Coin +from sentinel_protobuf.sentinel.types.v1.renewal_pb2 import RenewalPricePolicy from mospy import Transaction from keyrings.cryptfile.cryptfile import CryptFileKeyring from grpc import RpcError @@ -197,7 +198,10 @@ def SubToPlan(plan_id: int, wallet: str): ) - tx = sdk.subscriptions.StartSubscription(plan_id=plan_id, denom="udvpn", renewal = RenewalPricePolicy.RENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUAL, tx_params=tx_params) + tx = sdk.subscriptions.StartSubscription(plan_id=plan_id, + denom="udvpn", + renewal = RenewalPricePolicy.RENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUAL, + tx_params=tx_params) if tx.get("log", None) is not None: log_file_descriptor.write(f"\nERROR:\n{tx.get('log')}") From 08803ce1f26a5dbe8b59721a3ed480f83d056bab Mon Sep 17 00:00:00 2001 From: freqnik Date: Sat, 1 Nov 2025 01:58:18 -0400 Subject: [PATCH 07/18] update requirements, README --- README.md | 9 +++++---- requirements.txt | 19 +++++-------------- 2 files changed, 10 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 277ca66..83e7479 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,10 @@ Meile Subscription Plan REST API ## Dependencies +```shell +sudo apt install pkg-config cmkae libcairo2-dev libicu-dev libgirepository1.0-dev libdbus-1-dev +``` + * uWSGI, `sudo apt install uwsgi-plugin-python3` * Flask, * FlaskMySQL, @@ -15,12 +19,11 @@ Meile Subscription Plan REST API `pip install -r requirements.txt` + # API Documentation [Swagger OpenAPI](https://petstore.swagger.io/?url=https://raw.githubusercontent.com/MathNodes/meile-plan-rest/main/doc/meile-api.yaml) - - # Database Schema Create a database and name it `meile`: @@ -56,8 +59,6 @@ Then run the following command to start the API server; Filling in the ip address you wish to run the server on. This will spawn the server on port 5000. It will also spawn a stats server on port 9191. - - # Helpers Creators of Sentinel subscription plans need to manage their plans. This is not fully automated as of yet. We are looking to develope a daemon that will handle these helper files automatically. For now here is the list of helpers and what they do. diff --git a/requirements.txt b/requirements.txt index 89e2eb8..aee539b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -15,7 +15,6 @@ cffi==1.16.0 chardet==4.0.0 click==8.1.7 coincurve==18.0.0 -command-not-found==0.3 ConfigArgParse==1.5.3 configobj==5.0.6 cosmospy_protobuf==0.4.0 @@ -23,7 +22,6 @@ crcmod==1.7 cryptography==3.4.8 dbus-python==1.2.18 distro==1.7.0 -distro-info==1.1+ubuntu0.1 ecdsa==0.18.0 ed25519-blake2b==1.4.1 exceptiongroup==1.2.0 @@ -32,13 +30,13 @@ Flask-HTTPAuth==4.8.0 Flask-MySQL==1.5.2 Flask-SQLAlchemy==2.5.1 greenlet==3.0.1 -grpcio==1.62.1 +grpcio==1.71.0 h11==0.14.0 hdwallets==0.1.2 httpcore==1.0.4 httplib2==0.20.2 httpx==0.27.0 -idna==3.3 +idna==2.8 importlib-metadata==4.6.4 itsdangerous==2.1.2 jaraco.classes==3.3.1 @@ -47,7 +45,6 @@ Jinja2==3.1.2 josepy==1.10.0 keyring==23.5.0 keyrings.cryptfile==1.3.9 -language-selector==0.1 launchpadlib==1.10.16 lazr.restfulclient==0.14.4 lazr.uri==1.0.6 @@ -60,7 +57,7 @@ oauthlib==3.2.0 parsedatetime==2.6 passlib==1.7.4 pexpect==4.8.0 -protobuf==5.26.0 +protobuf==6.33.0 ptyprocess==0.7.0 py-sr25519-bindings==0.2.0 pycparser==2.21 @@ -74,26 +71,20 @@ PyNaCl==1.5.0 pyOpenSSL==21.0.0 pyparsing==2.4.7 pyRFC3339==1.1 -python-apt==2.4.0+ubuntu2 python-augeas==0.5.0 python-dateutil==2.8.2 pytz==2022.1 -pywgkey==1.0.0 -PyYAML==5.4.1 requests==2.25.1 requests-toolbelt==0.9.1 safe-pysha3==1.0.4 SecretStorage==3.3.1 -sentinel_protobuf==0.4.3 -snetinel_sdk==0.1.0 +sentinel_protobuf==0.3.3 +sentinel-sdk==0.1.0 six==1.16.0 sniffio==1.3.1 SQLAlchemy==1.3.12 ssh-import-id==5.11 typing_extensions==4.8.0 -ubuntu-advantage-tools==8001 -ufw==0.36.1 -unattended-upgrades==0.1 urllib3==1.26.5 uWSGI==2.0.23 wadllib==1.3.6 From 13ed880ae7dfeb595f57dd2295faeba9a82305c0 Mon Sep 17 00:00:00 2001 From: freqnik Date: Tue, 4 Nov 2025 02:29:04 -0500 Subject: [PATCH 08/18] Update PNS module for PMS --- meile_plan_api.py | 2 +- pms/plan_node_subscriptions.py | 52 ++++++++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/meile_plan_api.py b/meile_plan_api.py index debffbb..18e78ce 100644 --- a/meile_plan_api.py +++ b/meile_plan_api.py @@ -33,7 +33,7 @@ import scrtxxs -VERSION=20251025.2127 +VERSION=20251101.1200 app = Flask(__name__) mysql = MySQL() diff --git a/pms/plan_node_subscriptions.py b/pms/plan_node_subscriptions.py index e38b471..3ccc778 100755 --- a/pms/plan_node_subscriptions.py +++ b/pms/plan_node_subscriptions.py @@ -27,13 +27,15 @@ from time import sleep import requests import grpc +import subprocess +import json MNAPI = "https://api.sentinel.mathnodes.com" NODEAPI = "/sentinel/node/v3/nodes/%s" GRPC = scrtxxs.GRPC_DEV SSL = True -VERSION = 20251029.1707 +VERSION = 20251104.0130 class PlanSubscribe(): @@ -118,7 +120,7 @@ def __remove_duplicates(self, test): return test - def subscribe_to_nodes_for_plan(self, nodeaddress, duration=0, GB=0): + def subscribe_to_nodes_for_plan(self, nodeaddress, base_value: str, quote_value: str, duration=0, GB=0): error_message = "NotNone" tx_params = TxParams( @@ -132,8 +134,8 @@ def subscribe_to_nodes_for_plan(self, nodeaddress, duration=0, GB=0): # temporary price = Price( denom="udvpn", - base_value="0", - quote_value="0" + base_value=base_value, + quote_value=quote_value ) tx = self.sdk.lease.StartLease( node=nodeaddress, @@ -142,6 +144,8 @@ def subscribe_to_nodes_for_plan(self, nodeaddress, duration=0, GB=0): renewal=RenewalPricePolicy.RENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUAL ) + print(tx_params) + if tx.get("log", None) is not None: return(False, tx["log"]) @@ -299,9 +303,46 @@ def run_insert(node_file, uuid): except Exception as e: print(str(e)) pass + + # need to replace by SDK call, when SDK is completed + cmd = [ + "/home/sentinel/go/bin/sentinelhub", + "q", "vpn", "node", n, + "--node", "https://rpc.mathnodes.com:443", + "--output", "json" + ] + try: + result = subprocess.run(cmd, capture_output=True, text=True, check=True) + data = json.loads(result.stdout) + except subprocess.CalledProcessError as e: + print("Error running command:", e.stderr) + except json.JSONDecodeError as e: + print("Error parsing JSON:", e) + + hourly_prices = data.get("node", {}).get("hourly_prices", []) + + base_value = None + quote_value = None + + for price in hourly_prices: + if price.get("denom") == "udvpn": + base_value = price.get("base_value") + quote_value = price.get("quote_value") + break + + if not base_value: + base_value = "0" + + if not quote_value: + quote_value = "0" + + base_value = int(float(base_value) * 10**18) + print(f"base_value = {base_value}") + print(f"quote_value = {quote_value}") + print(f"[pns]: Subscribing to {n} for {scrtxxs.HOURS} hour(s) on plan {plan}...") - response = ps.subscribe_to_nodes_for_plan(n, duration=scrtxxs.HOURS) + response = ps.subscribe_to_nodes_for_plan(n, base_value=str(base_value), quote_value=str(quote_value), duration=scrtxxs.HOURS) print(f"[pns]: {response}") plan_id = list(set(plan_id)) print(f"[pns]: Linking {n} to plan {plan_id}...") @@ -310,6 +351,7 @@ def run_insert(node_file, uuid): ps.add_node_to_plan(pid, n) except Exception as e: print(str(e)) + sleep(2) ''' print("[pns]: Waiting....") sleep(10) From f714351c23e5a2702ea913c13372d202549073ae Mon Sep 17 00:00:00 2001 From: freqnik Date: Wed, 5 Nov 2025 21:23:39 -0500 Subject: [PATCH 09/18] Fix API for subscribers --- meile_plan_api.py | 46 +++++++++++++------------ pms/manual_plan_update.py | 70 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 95 insertions(+), 21 deletions(-) create mode 100644 pms/manual_plan_update.py diff --git a/meile_plan_api.py b/meile_plan_api.py index 18e78ce..42f5c0d 100644 --- a/meile_plan_api.py +++ b/meile_plan_api.py @@ -33,7 +33,7 @@ import scrtxxs -VERSION=20251101.1200 +VERSION=20251105.1639 app = Flask(__name__) mysql = MySQL() @@ -169,14 +169,17 @@ def GetPlanCostDenom(uuid): return plan411[0], plan411[1] -def CheckRenewalStatus(subid, wallet): - - query = f"SELECT subscription_id, subscribe_date, expires FROM meile_subscriptions WHERE wallet = '{wallet}' AND subscription_id = {subid}" - c = GetDBCursor() - c.execute(query) - - results = c.fetchone() +def CheckRenewalStatus(wallet, sub_id): + if sub_id != 0: + query = f"SELECT subscription_id, subscribe_date, expires FROM meile_subscriptions WHERE wallet = '{wallet}' AND subscription_id = {sub_id};" + c = GetDBCursor() + c.execute(query) + + results = c.fetchone() + else: + results = None + if results is not None: if results[0] and results[1]: return True,results[1],results[2] @@ -280,7 +283,7 @@ def FeeGrant(wallet): tx.add_msg( tx_type='transfer', sender=sdk._account, - receipient=wallet, + recipient=wallet, amount=1000000, denom="udvpn", ) @@ -311,14 +314,14 @@ def add_wallet_to_plan(): renewal = False hash = "0x0" try: - JSON = request.json - wallet = JSON['data']['wallet'] - plan_id = int(JSON['data']['plan_id']) # plan ID, we should have 4 or 5 plans. Will be a UUID. - duration = int(JSON['data']['duration']) # duration of plan subscription, in months - #sub_id = int(JSON['data']['sub_id']) # subscription ID of plan - uuid = JSON['data']['uuid'] # uuid of subscription - amt_paid = float(JSON['data']['amt']) - denom = JSON['data']['denom'] + JSON = request.json + wallet = JSON['data']['wallet'] + plan_id = int(JSON['data']['plan_id']) # plan ID, we should have 4 or 5 plans. Will be a UUID. + duration = int(JSON['data']['duration']) # duration of plan subscription, in months + old_sub_id = int(JSON['data']['sub_id']) # subscription ID of plan + uuid = JSON['data']['uuid'] # uuid of subscription + amt_paid = float(JSON['data']['amt']) + denom = JSON['data']['denom'] except Exception as e: print(str(e)) status = False @@ -339,7 +342,7 @@ def add_wallet_to_plan(): print(PlanTX) return jsonify(PlanTX) - renewal,subscription_date, expiration = CheckRenewalStatus(sub_id, wallet) + renewal,subscription_date, expiration = CheckRenewalStatus(wallet, old_sub_id) now = datetime.now() if expiration: @@ -368,7 +371,7 @@ def add_wallet_to_plan(): return jsonify(PlanTX) else: - sub_id = sub_result['sub_id'] + sub_id = int(sub_result['sub_id']) result = ShareSubTX(sdk, sub_id, wallet) @@ -402,8 +405,8 @@ def add_wallet_to_plan(): query = ''' UPDATE meile_subscriptions SET uuid = "%s", wallet = "%s", subscription_id = %d, plan_id = %d, amt_paid = %.8f, amt_denom = "%s", subscribe_date = "%s", subscription_duration = %d, expires = "%s", active = "1" - WHERE wallet = "%s" AND subscription_id = %d - ''' % (uuid, wallet, sub_id, plan_id, amt_paid, denom, subscription_date, duration, str(expires), wallet, sub_id) + WHERE wallet = "%s" AND subscription_id = %d AND plan_id = %d + ''' % (uuid, wallet, sub_id, plan_id, amt_paid, denom, subscription_date, duration, str(expires), wallet, old_sub_id, plan_id) else: query = ''' @@ -413,6 +416,7 @@ def add_wallet_to_plan(): print("Updating Subscription Table...") + print(query) try: UpdateDBTable(query) diff --git a/pms/manual_plan_update.py b/pms/manual_plan_update.py new file mode 100644 index 0000000..de99e16 --- /dev/null +++ b/pms/manual_plan_update.py @@ -0,0 +1,70 @@ +import pymysql +import uuid +from datetime import datetime, timedelta +import scrtxxs + +def get_user_input(): + user_input = {} + user_input['uuid'] = input("Enter UUID: ") + user_input['wallet'] = input("Enter wallet: ") + user_input['subscription_id'] = input("Enter subscription ID: ") + user_input['plan_id'] = input("Enter plan ID: ") + user_input['amt_paid'] = input("Enter amount paid: ") + user_input['amt_denom'] = input("Enter amount denomination: ") + user_input['subscription_duration'] = input("Enter subscription duration (in months): ") + return user_input + +def connect_to_db(): + return pymysql.connect(host=scrtxxs.MySQLHost, + port=scrtxxs.MySQLPort, + user=scrtxxs.MySQLUsername, + passwd=scrtxxs.MySQLPassword, + db=scrtxxs.MySQLDB, + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor) + +def check_and_insert_or_update(db, user_input): + cursor = db.cursor() + + # Check if an entry exists for the wallet + check_query = "SELECT * FROM your_table WHERE wallet = %s" + cursor.execute(check_query, (user_input['wallet'],)) + result = cursor.fetchone() + + if result: + # If entry exists, perform an update + update_query = """ + UPDATE your_table + SET uuid = %s, subscription_id = %s, plan_id = %s, amt_paid = %s, amt_denom = %s, subscription_duration = %s + WHERE wallet = %s + """ + cursor.execute(update_query, ( + user_input['uuid'], user_input['subscription_id'], user_input['plan_id'], + user_input['amt_paid'], user_input['amt_denom'], user_input['subscription_duration'], + user_input['wallet'] + )) + else: + # If entry does not exist, perform an insert + subscribe_date = datetime.now() + expires_date = subscribe_date + timedelta(days=int(user_input['subscription_duration']) * 30) + insert_query = """ + INSERT INTO your_table (uuid, wallet, subscription_id, plan_id, amt_paid, amt_denom, subscribe_date, subscription_duration, expires, active) + VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, 1) + """ + cursor.execute(insert_query, ( + user_input['uuid'], user_input['wallet'], user_input['subscription_id'], + user_input['plan_id'], user_input['amt_paid'], user_input['amt_denom'], + subscribe_date, user_input['subscription_duration'], expires_date + )) + + db.commit() + cursor.close() + +def main(): + user_input = get_user_input() + db = connect_to_db() + check_and_insert_or_update(db, user_input) + db.close() + +if __name__ == "__main__": + main() \ No newline at end of file From 778381316abf94dc46ec3982f49f3048d7a77573 Mon Sep 17 00:00:00 2001 From: freqnik Date: Wed, 10 Dec 2025 16:43:57 -0500 Subject: [PATCH 10/18] Update PES --- pms/plan_node_subscriptions.py | 47 ++++++++++++++++++++++++++++++++-- pms/purge_expired_subs.py | 31 +++++----------------- 2 files changed, 52 insertions(+), 26 deletions(-) diff --git a/pms/plan_node_subscriptions.py b/pms/plan_node_subscriptions.py index 3ccc778..f9231e6 100755 --- a/pms/plan_node_subscriptions.py +++ b/pms/plan_node_subscriptions.py @@ -35,7 +35,7 @@ NODEAPI = "/sentinel/node/v3/nodes/%s" GRPC = scrtxxs.GRPC_DEV SSL = True -VERSION = 20251104.0130 +VERSION = 20251126.1720 class PlanSubscribe(): @@ -215,6 +215,27 @@ def add_node_to_plan(self, plan_id, node): return (True, None) return (False,"Tx error") + + def get_price_of_node(self, node): + result = self.sdk.nodes.QueryNode(address=node) + + k = 0 + for gb_price in result.gigabyte_prices: + if "udvpn" == gb_price.denom: + break + else: + k += 1 + + if k > len(result.gigabyte_prices) - 1: + print(f"No proper denomination found!") + return {"success" : False, "base_value" : None, "quote_value" : None} + + + base_value = result.hourly_prices[k].base_value + quote_value = result.hourly_prices[k].quote_value + + return {"success" : True, "base_value" : base_value, "quote_value" : quote_value} + ''' def run_update(uuid): @@ -258,8 +279,18 @@ def run_insert(node_file, uuid): nodes = nodefile.readlines() for n in nodes: + prices = ps.get_price_of_node(node=n) + + if not prices['success']: + continue + + base_value = int(float(prices['base_value']) * 10**18) + quote_value = int(prices['quote_value']) + + print(f"base_value = {base_value}") + print(f"quote_value = {quote_value}") print(f"[pns]: Subscribing to {n} for {scrtxxs.HOURS} hour(s) on plan {args.uuid}...") - response = ps.subscribe_to_nodes_for_plan(n, duration=scrtxxs.HOURS) + response = ps.subscribe_to_nodes_for_plan(n, base_value=str(base_value), quote_value=str(quote_value), duration=scrtxxs.HOURS) print(response) print("[pns]: Waiting 5s...") sleep(5) @@ -305,6 +336,7 @@ def run_insert(node_file, uuid): pass # need to replace by SDK call, when SDK is completed + ''' cmd = [ "/home/sentinel/go/bin/sentinelhub", "q", "vpn", "node", n, @@ -338,6 +370,17 @@ def run_insert(node_file, uuid): base_value = int(float(base_value) * 10**18) + print(f"base_value = {base_value}") + print(f"quote_value = {quote_value}") + ''' + prices = ps.get_price_of_node(node=n) + + if not prices['success']: + continue + + base_value = int(float(prices['base_value']) * 10**18) + quote_value = int(prices['quote_value']) + print(f"base_value = {base_value}") print(f"quote_value = {quote_value}") diff --git a/pms/purge_expired_subs.py b/pms/purge_expired_subs.py index 9c0f831..9d4f6b8 100644 --- a/pms/purge_expired_subs.py +++ b/pms/purge_expired_subs.py @@ -15,7 +15,7 @@ from keyrings.cryptfile.cryptfile import CryptFileKeyring -VERSION = 20241124.1758 +VERSION = 20251210.1545 class PurgeExpiredSubs(): @@ -57,7 +57,7 @@ def get_subscription_table(self,db): return c.fetchall() def update_sub_table(self,sub,db): - q = f"UPDATE meile_subscriptions SET active = 0 WHERE id = {sub['id']};" + q = f"UPDATE meile_subscriptions SET active = 0, subscription_id = NULL WHERE id = {sub['id']};" c = db.cursor() c.execute(q) db.commit() @@ -66,42 +66,25 @@ def update_sub_table(self,sub,db): def deactivate_expired_subscriptions(self, db, subs_table): NOW = datetime.now() self.LOGFILE.write(f"-----------------------------{NOW}-------------------------------------\n") - self.LOGFILE.write("[pes]: Removing Allocations...\n") + self.LOGFILE.write("[pes]: Cancelling subscription...\n") for sub in subs_table: if sub['expires'] < NOW: - self.LOGFILE.write(f"[pes]: ({sub['wallet']}) Querying allocation...\n") - - try: - allocation = self.sdk.subscriptions.QueryAllocation(address=sub['wallet'], - subscription_id=int(sub['subscription_id']) - ) - - ubytes = int(allocation.utilised_bytes) - self.LOGFILE.write(f"({sub['wallet']}) Utilised bytes: {ubytes}\n") - except Exception as e: - self.LOGFILE.write(f"{str(e)}\n") - self.LOGFILE.write("Could not get allocation amt... skipping\n") - continue - - self.LOGFILE.write(f"[pes]: Unallocating; {sub}\n") - + self.LOGFILE.write(f"[pes]: Wallet: {sub['wallet']}, subid: {sub['subscription_id']}...\n") + try: tx_params = TxParams( gas_multiplier=1.15 ) - tx = self.sdk.subscriptions.Allocate(address=sub['wallet'], - bytes=str(ubytes), - id=sub['subscription_id'], - tx_params=tx_params - ) + tx = self.sdk.subscriptions.Cancel(sub['subscription_id'], tx_params) if tx.get("log", None) is not None: self.LOGFILE.write(f"{tx['log']}\n") continue if tx.get("hash", None) is not None: tx_response = self.sdk.subscriptions.wait_for_tx(tx['hash'], timeout=30) + print(tx_response) self.LOGFILE.write(f"[pes]: {json.dumps(tx_response)}\n") else: self.LOGFILE.write("[pes]: Error getting tx response... Skipping...\n") From 6793f553cd1ff8c86afa06c0ee714fdb24a89bb0 Mon Sep 17 00:00:00 2001 From: freqnik Date: Thu, 11 Dec 2025 15:48:29 -0500 Subject: [PATCH 11/18] Update schema. Update API for itemized storage --- meile_plan_api.py | 16 ++++++++++++++-- schema/itemized_subscriptions.sql | 10 ++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 schema/itemized_subscriptions.sql diff --git a/meile_plan_api.py b/meile_plan_api.py index 42f5c0d..fd88b96 100644 --- a/meile_plan_api.py +++ b/meile_plan_api.py @@ -33,7 +33,7 @@ import scrtxxs -VERSION=20251105.1639 +VERSION=20251211.1544 app = Flask(__name__) mysql = MySQL() @@ -424,7 +424,19 @@ def add_wallet_to_plan(): print(str(e)) log_file_descriptor.write("ERROR ADDING WALLET TO SUBSCRIPTION DATABASE" + '\n') - + query = ''' + INSERT INTO itemized_subscriptions (wallet, plan_id, amt_paid, amt_denom, subscribe_date, subscription_duration) + VALUES(%s", %d, %.8f, "%s", "%s", %d) + ''' % (wallet, plan_id, amt_paid, denom, str(now), duration) + + print("Updating Itemized Subscription Table...") + print(query) + + try: + UpdateDBTable(query) + except Exception as e: + print(str(e)) + log_file_descriptor.write("ERROR ADDING WALLET TO ITEMIZED SUBSCRIPTION DATABASE" + '\n') result = FeeGrant(wallet) diff --git a/schema/itemized_subscriptions.sql b/schema/itemized_subscriptions.sql new file mode 100644 index 0000000..4f52d8a --- /dev/null +++ b/schema/itemized_subscriptions.sql @@ -0,0 +1,10 @@ +CREATE TABLE itemized_subscriptions ( + id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, + wallet VARCHAR(100) NULL, + plan_id BIGINT UNSIGNED NULL, + amt_paid DECIMAL(24, 12) NULL, + amt_denom VARCHAR(10) NULL, + subscribe_date TIMESTAMP NULL, + subscription_duration SMALLINT UNSIGNED NULL, + PRIMARY KEY (id) +); \ No newline at end of file From 8d890fb52d54c29fb1d1b595b16a3d8820a788fc Mon Sep 17 00:00:00 2001 From: freqnik Date: Fri, 12 Dec 2025 14:12:10 -0500 Subject: [PATCH 12/18] Update REST API --- meile_plan_api.py | 38 +++++---- pms/plan_node_subscriptions.py | 146 ++++++++++++++++----------------- 2 files changed, 91 insertions(+), 93 deletions(-) diff --git a/meile_plan_api.py b/meile_plan_api.py index fd88b96..2cc6695 100644 --- a/meile_plan_api.py +++ b/meile_plan_api.py @@ -33,7 +33,7 @@ import scrtxxs -VERSION=20251211.1544 +VERSION=20251212.1411 app = Flask(__name__) mysql = MySQL() @@ -169,19 +169,17 @@ def GetPlanCostDenom(uuid): return plan411[0], plan411[1] -def CheckRenewalStatus(wallet, sub_id): +def CheckRenewalStatus(wallet, plan_id): - if sub_id != 0: - query = f"SELECT subscription_id, subscribe_date, expires FROM meile_subscriptions WHERE wallet = '{wallet}' AND subscription_id = {sub_id};" - c = GetDBCursor() - c.execute(query) - - results = c.fetchone() - else: - results = None - - if results is not None: - if results[0] and results[1]: + + query = f"SELECT subscription_id, subscribe_date, expires FROM meile_subscriptions WHERE wallet = '{wallet}' AND plan_id = {plan_id};" + c = GetDBCursor() + c.execute(query) + + results = c.fetchone() + + if results: + if results[1] and results[2]: return True,results[1],results[2] else: return False, None, None @@ -318,7 +316,10 @@ def add_wallet_to_plan(): wallet = JSON['data']['wallet'] plan_id = int(JSON['data']['plan_id']) # plan ID, we should have 4 or 5 plans. Will be a UUID. duration = int(JSON['data']['duration']) # duration of plan subscription, in months - old_sub_id = int(JSON['data']['sub_id']) # subscription ID of plan + try: + old_sub_id = int(JSON['data']['sub_id']) # subscription ID of plan + except: + old_sub_id = 0 uuid = JSON['data']['uuid'] # uuid of subscription amt_paid = float(JSON['data']['amt']) denom = JSON['data']['denom'] @@ -342,8 +343,9 @@ def add_wallet_to_plan(): print(PlanTX) return jsonify(PlanTX) - renewal,subscription_date, expiration = CheckRenewalStatus(wallet, old_sub_id) + renewal,subscription_date, expiration = CheckRenewalStatus(wallet, plan_id) + print(f"renewal: {renewal}, sub date: {subscription_date}") now = datetime.now() if expiration: if now < expiration: @@ -405,8 +407,8 @@ def add_wallet_to_plan(): query = ''' UPDATE meile_subscriptions SET uuid = "%s", wallet = "%s", subscription_id = %d, plan_id = %d, amt_paid = %.8f, amt_denom = "%s", subscribe_date = "%s", subscription_duration = %d, expires = "%s", active = "1" - WHERE wallet = "%s" AND subscription_id = %d AND plan_id = %d - ''' % (uuid, wallet, sub_id, plan_id, amt_paid, denom, subscription_date, duration, str(expires), wallet, old_sub_id, plan_id) + WHERE wallet = "%s" AND plan_id = %d + ''' % (uuid, wallet, sub_id, plan_id, amt_paid, denom, subscription_date, duration, str(expires), wallet, plan_id) else: query = ''' @@ -426,7 +428,7 @@ def add_wallet_to_plan(): query = ''' INSERT INTO itemized_subscriptions (wallet, plan_id, amt_paid, amt_denom, subscribe_date, subscription_duration) - VALUES(%s", %d, %.8f, "%s", "%s", %d) + VALUES("%s", %d, %.8f, "%s", "%s", %d) ''' % (wallet, plan_id, amt_paid, denom, str(now), duration) print("Updating Itemized Subscription Table...") diff --git a/pms/plan_node_subscriptions.py b/pms/plan_node_subscriptions.py index f9231e6..757494b 100755 --- a/pms/plan_node_subscriptions.py +++ b/pms/plan_node_subscriptions.py @@ -35,7 +35,7 @@ NODEAPI = "/sentinel/node/v3/nodes/%s" GRPC = scrtxxs.GRPC_DEV SSL = True -VERSION = 20251126.1720 +VERSION = 20251211.2234 class PlanSubscribe(): @@ -120,7 +120,14 @@ def __remove_duplicates(self, test): return test - def subscribe_to_nodes_for_plan(self, nodeaddress, base_value: str, quote_value: str, duration=0, GB=0): + def subscribe_to_nodes_for_plan(self, + nodeaddress, + base_value: str, + quote_value: str, + duration=0, + GB=0, + uuids: list() = [], + plans: list() = []): error_message = "NotNone" tx_params = TxParams( @@ -138,7 +145,7 @@ def subscribe_to_nodes_for_plan(self, nodeaddress, base_value: str, quote_value: quote_value=quote_value ) tx = self.sdk.lease.StartLease( - node=nodeaddress, + node=nodeaddress.rstrip(), hours=scrtxxs.HOURS, max_price=price, renewal=RenewalPricePolicy.RENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUAL @@ -157,7 +164,17 @@ def subscribe_to_nodes_for_plan(self, nodeaddress, base_value: str, quote_value: ) now = datetime.now() inactive_at = now + timedelta(hours=scrtxxs.HOURS) - self.UpdateNodePlanDB(nodeaddress, lease_id, inactive_at) + if self.QueryDBSubscriptions(nodeaddress): + self.UpdateNodePlanDB(nodeaddress.rstrip(), lease_id, inactive_at) + + else: + self.InsertNodeInDB(uuids, + plans, + str(scrtxxs.HOURS*int(quote_value)) + "udvpn", + scrtxxs.HOURS, + lease_id, + inactive_at, + nodeaddress.rstrip()) return (True, lease_id) @@ -178,7 +195,34 @@ def subscribe_to_nodes_for_plan(self, nodeaddress, base_value: str, quote_value: except grpc.RpcError as e: print(e.details()) + def InsertNodeInDB(self,uuids,plans,deposit, hours, lease_id, inactive_at, nodeaddress): + c = self._db.cursor() + for uuid, plan in zip(uuids,plans): + q = ''' + INSERT IGNORE INTO plan_nodes (uuid, node_address) + VALUES ("%s", "%s"); + ''' % (uuid, nodeaddress) + + print(q) + c.execute(q) + self._db.commit() + q = ''' + INSERT IGNORE INTO plan_node_subscriptions (node_address,uuid,plan_id,plan_subscription_id,node_subscription_id,deposit,hours,inactive_date) + VALUES ("%s", "%s", %d, %d, %d, "%s", %d, "%s") + ''' % (nodeaddress, uuid,int(plan),0, int(lease_id), deposit, hours, str(inactive_at)) + print(q) + c.execute(q) + self._db.commit() + + def QueryDBSubscriptions(self, nodeaddress): + c = self._db.cursor() + query = "SELECT * from plan_node_subscriptions WHERE node_address = '%s';" % (nodeaddress) + c.execute(query) + result = c.fetchall() + return bool(result) + + def UpdateNodePlanDB(self, nodeaddress, lease_id, inactive_at): c = self._db.cursor() @@ -193,9 +237,6 @@ def UpdateNodePlanDB(self, nodeaddress, lease_id, inactive_at): def add_node_to_plan(self, plan_id, node): tx_params = TxParams( - # denom="udvpn", # TODO: from ConfParams - # fee_amount=20000, # TODO: from ConfParams - # gas=ConfParams.GAS, gas_multiplier=1.15 ) @@ -245,7 +286,7 @@ def run_update(uuid): proc1.wait(timeout=30) proc_out,proc_err = proc1.communicate() -''' + def run_insert(node_file, uuid): update_cmd = f"{scrtxxs.HELPERS}/insert-nodes.py --uuid {uuid} --file {node_file}" @@ -254,7 +295,9 @@ def run_insert(node_file, uuid): proc1.wait(timeout=30) proc_out,proc_err = proc1.communicate() - +''' + + if __name__ == "__main__": @@ -290,26 +333,23 @@ def run_insert(node_file, uuid): print(f"base_value = {base_value}") print(f"quote_value = {quote_value}") print(f"[pns]: Subscribing to {n} for {scrtxxs.HOURS} hour(s) on plan {args.uuid}...") - response = ps.subscribe_to_nodes_for_plan(n, base_value=str(base_value), quote_value=str(quote_value), duration=scrtxxs.HOURS) + response = ps.subscribe_to_nodes_for_plan(n, + base_value=str(base_value), + quote_value=str(quote_value), + duration=scrtxxs.HOURS, + uuids=args.uuid.split(','), + plans=plan_id) print(response) print("[pns]: Waiting 5s...") sleep(5) print(f"[pns]: Adding {n} to plan {plan_id},{args.uuid}...") for pid in plan_id: - ps.add_node_to_plan(pid, n) - - - for uuid in args.uuid.split(','): - print("[pns]: Inserting nodes in plan DB...", end='') - run_insert(args.file, uuid) - sleep(2) - print("[pns]: Done.") - print("[pns]: Wainting...") - sleep(20) - print("[pns]: Updating plan_node_subscriptions...") - run_update(uuid) - print("[pns]: Done.") - + try: + ps.add_node_to_plan(pid, n) + except Exception as e: + print(str(e)) + + else: plan_id = [] print("[pns]: Computing Resubscriptions...") @@ -335,44 +375,6 @@ def run_insert(node_file, uuid): print(str(e)) pass - # need to replace by SDK call, when SDK is completed - ''' - cmd = [ - "/home/sentinel/go/bin/sentinelhub", - "q", "vpn", "node", n, - "--node", "https://rpc.mathnodes.com:443", - "--output", "json" - ] - try: - result = subprocess.run(cmd, capture_output=True, text=True, check=True) - data = json.loads(result.stdout) - except subprocess.CalledProcessError as e: - print("Error running command:", e.stderr) - except json.JSONDecodeError as e: - print("Error parsing JSON:", e) - - hourly_prices = data.get("node", {}).get("hourly_prices", []) - - base_value = None - quote_value = None - - for price in hourly_prices: - if price.get("denom") == "udvpn": - base_value = price.get("base_value") - quote_value = price.get("quote_value") - break - - if not base_value: - base_value = "0" - - if not quote_value: - quote_value = "0" - - base_value = int(float(base_value) * 10**18) - - print(f"base_value = {base_value}") - print(f"quote_value = {quote_value}") - ''' prices = ps.get_price_of_node(node=n) if not prices['success']: @@ -385,7 +387,12 @@ def run_insert(node_file, uuid): print(f"quote_value = {quote_value}") print(f"[pns]: Subscribing to {n} for {scrtxxs.HOURS} hour(s) on plan {plan}...") - response = ps.subscribe_to_nodes_for_plan(n, base_value=str(base_value), quote_value=str(quote_value), duration=scrtxxs.HOURS) + response = ps.subscribe_to_nodes_for_plan(n, + base_value=str(base_value), + quote_value=str(quote_value), + duration=scrtxxs.HOURS, + uuids=uuids.split(',')[1:], + plans=plan_id) print(f"[pns]: {response}") plan_id = list(set(plan_id)) print(f"[pns]: Linking {n} to plan {plan_id}...") @@ -395,15 +402,4 @@ def run_insert(node_file, uuid): except Exception as e: print(str(e)) sleep(2) - ''' - print("[pns]: Waiting....") - sleep(10) - # Run db updater script with UUIDs - uuids = uuids.split(',')[1:] - print(f"[pns]: uuids: {uuids}") - for uuid in uuids: - print(f"[pns]: Updating node subs for plan {uuid}...", end='') - run_update(uuid) - sleep(2) - print("[pns]: Done.") - ''' \ No newline at end of file + \ No newline at end of file From 51980e8a65be0ff454209bd25c00b3af6793b98b Mon Sep 17 00:00:00 2001 From: freqnik Date: Sat, 27 Dec 2025 21:18:21 -0500 Subject: [PATCH 13/18] Add script to manually add subscription for promotions --- pms/manual_subscription.py | 313 +++++++++++++++++++++++++++++++++++++ 1 file changed, 313 insertions(+) create mode 100644 pms/manual_subscription.py diff --git a/pms/manual_subscription.py b/pms/manual_subscription.py new file mode 100644 index 0000000..076cc3d --- /dev/null +++ b/pms/manual_subscription.py @@ -0,0 +1,313 @@ +#!/bin/env python3 + +import os +import time +from os import path +import json +import pymysql +from urllib.parse import urlparse + +from datetime import datetime +from dateutil.relativedelta import relativedelta + +from sentinel_sdk.sdk import SDKInstance +from sentinel_sdk.types import TxParams +from sentinel_sdk.utils import search_attribute +from sentinel_protobuf.cosmos.base.v1beta1.coin_pb2 import Coin +from sentinel_protobuf.sentinel.types.v1.renewal_pb2 import RenewalPricePolicy +from mospy import Transaction +from keyrings.cryptfile.cryptfile import CryptFileKeyring +from grpc import RpcError + +import scrtxxs + +WalletLogDIR = scrtxxs.LogDIR +HotWalletAddress = scrtxxs.WalletAddress +keyring_passphrase = scrtxxs.HotWalletPW +def __keyring(keyring_passphrase: str): + kr = CryptFileKeyring() + kr.filename = "keyring.cfg" + kr.file_path = path.join(scrtxxs.PlanKeyringDIR, kr.filename) + kr.keyring_key = keyring_passphrase + return kr + +keyring = __keyring(scrtxxs.HotWalletPW) +private_key = keyring.get_password("meile-plan", scrtxxs.WalletName) +grpcaddr, grpcport = urlparse(scrtxxs.GRPC_DEV).netloc.split(":") +sdk = SDKInstance(grpcaddr, int(grpcport), secret=private_key, ssl=True) +db = pymysql.connect(host=scrtxxs.MySQLHost, + port=scrtxxs.MySQLPort, + user=scrtxxs.MySQLUsername, + passwd=scrtxxs.MySQLPassword, + db=scrtxxs.MySQLDB, + charset='utf8mb4', + cursorclass=pymysql.cursors.DictCursor) + + +def UpdateDBTable(query): + + cursor = db.cursor() + cursor.execute(query) + db.commit() + db.close() + +def SubToPlan(plan_id: int, wallet: str): + # Add logging + WalletLogFile = os.path.join(WalletLogDIR, "meile_allocate.log") + log_file_descriptor = open(WalletLogFile, "a+") + + tx_params = TxParams( + gas=150000, + gas_multiplier=1.2, + fee_amount=31415, + denom="udvpn" + ) + + + tx = sdk.subscriptions.StartSubscription(plan_id=plan_id, + denom="udvpn", + renewal = RenewalPricePolicy.RENEWAL_PRICE_POLICY_IF_LESSER_OR_EQUAL, + tx_params=tx_params) + + if tx.get("log", None) is not None: + log_file_descriptor.write(f"\nERROR:\n{tx.get('log')}") + log_file_descriptor.flush() + log_file_descriptor.close() + message = "Error subscribing to plan. Please contact support@mathnodes.com for assistance." + return {"status" : False, + "message" : message, + "hash" : "0x0", + "tx_response" : None, + "sub_id" : None} + + if tx.get("hash", None) is not None: + tx_response = sdk.nodes.wait_transaction(tx["hash"]) + log_file_descriptor.write(f"\nSuccess:\n {tx_response}") + subscription_id = search_attribute( + tx_response, "sentinel.subscription.v3.EventCreate", "subscription_id" + ) + log_file_descriptor.flush() + log_file_descriptor.close() + return {"status" : True, + "message" : "Success.", + "hash" : tx['hash'], + "tx_response" : tx_response, + 'sub_id' : subscription_id} + +def ShareSubTX(sub_id: int, wallet, size=scrtxxs.BYTES_50): + # Add logging + WalletLogFile = os.path.join(WalletLogDIR, "meile_allocate.log") + log_file_descriptor = open(WalletLogFile, "a+") + + tx_params = TxParams( + gas=150000, + gas_multiplier=1.2, + fee_amount=31415, + denom="udvpn" + ) + + tx = sdk.subscriptions.ShareSubscription(subscription_id=sub_id, + wallet_address=wallet, + bytes=str(size), + tx_params=tx_params) + + if tx.get("log", None) is not None: + log_file_descriptor.write(f"\nERROR:\n{tx.get('log')}") + log_file_descriptor.flush() + log_file_descriptor.close() + message = "Error adding wallet to plan. Please contact support@mathnodes.com for assistance." + return {"status" : False, "message" : message, "hash" : "0x0", "tx_response" : None} + + if tx.get("hash", None) is not None: + tx_response = sdk.nodes.wait_transaction(tx["hash"]) + log_file_descriptor.write(f"\nSuccess:\n {tx_response}") + log_file_descriptor.flush() + log_file_descriptor.close() + return {"status" : True, "message" : "Success.", "hash" : tx['hash'], "tx_response" : tx_response} + + +def FeeGrant(wallet): + + tx_params = TxParams( + gas=150000, + gas_multiplier=1.2, + fee_amount=31415, + denom="udvpn" + ) + + tx = Transaction( + account=sdk._account, + fee=Coin(denom=tx_params.denom, amount=f"{tx_params.fee_amount}"), + gas=tx_params.gas, + protobuf="sentinel", + chain_id="sentinelhub-2", + memo=f"Meile Gas Favor", + ) + tx.add_msg( + tx_type='transfer', + sender=sdk._account, + recipient=wallet, + amount=1000000, + denom="udvpn", + ) + + sdk._client.load_account_data(account=sdk._account) + + tx_height = 0 + + try: + tx = sdk._client.broadcast_transaction(transaction=tx) + except RpcError as rpc_error: + details = rpc_error.details() + print("details", details) + print("code", rpc_error.code()) + print("debug_error_string", rpc_error.debug_error_string()) + return {"tx_response" : None, "height" : None, "status" : False} + + if tx.get("log", None) is None: + tx_response = sdk.nodes.wait_for_tx(tx["hash"]) + tx_height = tx_response.get("txResponse", {}).get("height", 0) if isinstance(tx_response, dict) else tx_response.tx_response.height + return {"tx_response" : tx_response, "height" : tx_height, "status" : True} + +def CheckRenewalStatus(wallet, plan_id): + + + query = f"SELECT subscription_id, subscribe_date, expires FROM meile_subscriptions WHERE wallet = '{wallet}' AND plan_id = {plan_id};" + c = db.cursor() + c.execute(query) + + results = c.fetchone() + + if results: + if results[1] and results[2]: + return True,results[1],results[2] + else: + return False, None, None + else: + return False, None, None + + +def manual_sub(uuid, plan_id, wallet, amt_paid, denom, duration): + + WalletLogFile = os.path.join(WalletLogDIR, "meile_plan.log") + log_file_descriptor = open(WalletLogFile, "a+") + + renewal,subscription_date, expiration = CheckRenewalStatus(wallet, plan_id) + + print(f"renewal: {renewal}, sub date: {subscription_date}") + now = datetime.now() + if expiration: + if now < expiration: + expires = expiration + relativedelta(months=+duration) + else: + expires = now + relativedelta(months=+duration) + + else: + expires = now + relativedelta(months=+duration) + + sub_result = SubToPlan(plan_id, wallet) + if not sub_result['status']: + PlanTX = {'status' : result["status"], + 'wallet' : wallet, + 'planid' : plan_id, + 'duration' : duration, + 'tx' : result["hash"], + 'message' : result["message"], + 'expires' : None} + print(PlanTX) + log_file_descriptor.write(json.dumps(PlanTX) + '\n') + return jsonify(PlanTX) + + else: + sub_id = int(sub_result['sub_id']) + + result = ShareSubTX(sub_id, wallet) + + if not result['status']: + PlanTX = {'status' : result["status"], + 'wallet' : wallet, + 'planid' : plan_id, + 'id' : sub_id, + 'duration' : duration, + 'tx' : result["hash"], + 'message' : result["message"], + 'expires' : None} + print(PlanTX) + log_file_descriptor.write(json.dumps(PlanTX) + '\n') + return jsonify(PlanTX) + + else: + print(result["tx_response"]) + PlanTX = {'status' : result["status"], + 'wallet' : wallet, + 'planid' : plan_id, + 'id' : sub_id, + 'duration' : duration, + 'tx' : result["hash"], + 'message' : result["message"], + 'expires' : str(expires)} + log_file_descriptor.write(json.dumps(result["tx_response"]) + '\n') + log_file_descriptor.write(json.dumps(PlanTX) + '\n') + + if renewal and subscription_date is not None: + query = ''' + UPDATE meile_subscriptions + SET uuid = "%s", wallet = "%s", subscription_id = %d, plan_id = %d, amt_paid = %.8f, amt_denom = "%s", subscribe_date = "%s", subscription_duration = %d, expires = "%s", active = "1" + WHERE wallet = "%s" AND plan_id = %d + ''' % (uuid, wallet, sub_id, plan_id, amt_paid, denom, subscription_date, duration, str(expires), wallet, plan_id) + + else: + query = ''' + INSERT INTO meile_subscriptions (uuid, wallet, subscription_id, plan_id, amt_paid, amt_denom, subscribe_date, subscription_duration, expires) + VALUES("%s", "%s", %d, %d, %.8f, "%s", "%s", %d, "%s") + ''' % (uuid, wallet, sub_id, plan_id, amt_paid, denom, str(now), duration, str(expires)) + + + print("Updating Subscription Table...") + print(query) + + try: + UpdateDBTable(query) + except Exception as e: + print(str(e)) + log_file_descriptor.write("ERROR ADDING WALLET TO SUBSCRIPTION DATABASE" + '\n') + + query = ''' + INSERT INTO itemized_subscriptions (wallet, plan_id, amt_paid, amt_denom, subscribe_date, subscription_duration) + VALUES("%s", %d, %.8f, "%s", "%s", %d) + ''' % (wallet, plan_id, amt_paid, denom, str(now), duration) + + print("Updating Itemized Subscription Table...") + print(query) + + try: + UpdateDBTable(query) + except Exception as e: + print(str(e)) + log_file_descriptor.write("ERROR ADDING WALLET TO ITEMIZED SUBSCRIPTION DATABASE" + '\n') + + result = FeeGrant(wallet) + + if result['status']: + log_file_descriptor.write(json.dumps(result["tx_response"]) + '\n') + log_file_descriptor.write(result["height"] + '\n') + print(f'Successfully sent 1dvpn to: {wallet}, height: {result["height"]}') + else: + log_message = f'Error sending 1dvpn to: {wallet}, height: {result["height"]}' + print(log_message) + log_file_descriptor.write(log_message + '\n') + + + log_file_descriptor.close() + +if __name__ == "__main__": + uuid = input("Plan UUID: ") + plan_id = input("Plan ID: ") + wallet = input("Wallet address: ") + amt_paid = input("Amount paid: ") + denom = input("Denom paid: ") + duration = input("Duration (months): ") + + manual_sub(uuid, int(plan_id), wallet, int(amt_paid), denom, int(duration)) + + \ No newline at end of file From d15101fc31bac5cfb9b13ceac4b6cd66170f970e Mon Sep 17 00:00:00 2001 From: freqnik Date: Thu, 29 Jan 2026 23:25:46 -0500 Subject: [PATCH 14/18] Add Firo InstantLock endpoint --- meile_plan_api.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/meile_plan_api.py b/meile_plan_api.py index 2cc6695..12f2cac 100644 --- a/meile_plan_api.py +++ b/meile_plan_api.py @@ -697,6 +697,80 @@ def get_spark_balance(): else: return jsonify({'result': 0.0, 'error': response.status_code, 'id': 'meile'}) +@app.route('/v1/firo/getsparktxs', methods=['POST']) +@auth.login_required +def get_spark_txs(): + try: + JSON = request.json + amount = JSON['amount'] + except Exception as e: + print(str(e)) + return jsonify({ + "success": False, + "chainlock": False, + "instantlock": False, + "error": "Invalid request body" + }), 400 + + url = "https://firo-rpc.host.com:8888/" + headers = {'content-type': 'text/plain;'} + data = { + "jsonrpc": "1.0", + "id": "meile", + "method": "listtransactions", + "params": ["*", 10, 0, True] # Note: Python uses True, not true + } + + response = requests.post( + url, + json=data, + headers=headers, + auth=RequestsAuth(scrtxxs.FIROUSER, scrtxxs.FIROPASSWORD) + ) + + print(response.status_code) + if response.status_code == 200: + try: + rpc_response = response.json() + transactions = rpc_response.get("result", []) + + # Search for a transaction with matching amount + for tx in transactions: + tx_amount = tx.get("amount", 0) + + # Compare amounts (using float comparison with tolerance for precision) + if abs(float(tx_amount) - float(amount)) < 0.00000001: + return jsonify({ + "success": True, + "chainlock": tx.get("chainlock", False), + "instantlock": tx.get("instantlock", False) + }) + + # No matching transaction found + return jsonify({ + "success": False, + "chainlock": False, + "instantlock": False, + "error": "No transaction found with matching amount" + }) + + except Exception as e: + print(f"Error parsing response: {str(e)}") + return jsonify({ + "success": False, + "chainlock": False, + "instantlock": False, + "error": "Failed to parse RPC response" + }), 500 + else: + return jsonify({ + "success": False, + "chainlock": False, + "instantlock": False, + "error": f"RPC request failed with status {response.status_code}" + }), 502 + + @app.route('/v1/firo/getsparkwalletbalance', methods=['GET']) def get_spark_wallet_balance(): From 3afa5d2371f602bcfe7cd74cf6b03e058bb3e671 Mon Sep 17 00:00:00 2001 From: freqnik Date: Fri, 30 Jan 2026 18:33:56 -0500 Subject: [PATCH 15/18] Update version number --- meile_plan_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meile_plan_api.py b/meile_plan_api.py index 12f2cac..8aa55f5 100644 --- a/meile_plan_api.py +++ b/meile_plan_api.py @@ -33,7 +33,7 @@ import scrtxxs -VERSION=20251212.1411 +VERSION=20260130.1833 app = Flask(__name__) mysql = MySQL() From 7aeb6264f282e270ce6bf85a43e949de957aef86 Mon Sep 17 00:00:00 2001 From: freqnik Date: Wed, 4 Feb 2026 01:03:13 -0500 Subject: [PATCH 16/18] Add Zephyr API endpoints --- meile_plan_api.py | 134 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/meile_plan_api.py b/meile_plan_api.py index 8aa55f5..056ec88 100644 --- a/meile_plan_api.py +++ b/meile_plan_api.py @@ -987,6 +987,140 @@ def get_zano_balances(): else: return jsonify({'result': 0.0, 'error': response.status_code, 'id': 'meile'}) +@app.route('/v1/zeph/newaddress', methods=['GET']) +@auth.login_required +def get_new_zeph_address(): + url = "https://zephyr.ithurtswhenip.ee/json_rpc" + headers = {'content-type': 'text/plain;'} + data = { + "jsonrpc": "2.0", + "id": "0", + "method": "create_address", + "params": { + "account_index": 0, + "label": "meile payment" + } + } + + response = requests.post( + url, + json=data, + headers=headers, + auth=RequestsAuth(scrtxxs.FIROUSER, scrtxxs.FIROPASSWORD) + ) + + print(response.status_code) + if response.status_code == 200: + print(response.json()) + result = response.json() + return jsonify({ + "success" : True, + "address" : result['result']['address'], + "index" : result['result']['address_index'] + }) + #return jsonify(response.json()) + else: + return jsonify({ + "success" : False, + "address" : None, + "index" : None + }) + +@app.route('/v1/zephyr/getbalance', methods=['POST']) +@auth.login_required +def get_zephyr_balance(): + try: + data = request.json + index = data['index'] + amount = data['amount'] + asset = data['asset'] + except Exception as e: + print(str(e)) + return jsonify({ + 'success': False, + 'confirmations': None, + 'difference': None, + 'error': 'Invalid request parameters' + }), 400 + + url = "https://zephyr.ithurtswhenip.ee/json_rpc" + headers = {'content-type': 'application/json'} + + payload = { + "jsonrpc": "2.0", + "id": "0", + "method": "get_transfers", + "params": { + "in": True, + "pool": True, + "out": False, + "pending": False, + "failed": False, + "account_index": 0, + "subaddr_indices": [index] + } + } + + try: + response = requests.post( + url, + json=payload, + headers=headers, + auth=HTTPDigestAuth(scrtxxs.ZEPHYRUSER, scrtxxs.ZEPHYRPASSWORD) + ) + result = response.json().get('result', {}) + except Exception as e: + print(str(e)) + return jsonify({ + 'success': False, + 'confirmations': None, + 'difference': None, + 'error': 'RPC request failed' + }), 500 + + confirmed_txs = result.get('in', []) + pool_txs = result.get('pool', []) + + all_txs = [] + + for tx in confirmed_txs: + if tx.get('asset_type') == asset: + all_txs.append({ + 'amount': tx.get('amount', 0), + 'confirmations': tx.get('confirmations', 0), + 'in_pool': False + }) + + for tx in pool_txs: + if tx.get('asset_type') == asset: + all_txs.append({ + 'amount': tx.get('amount', 0), + 'confirmations': 0, + 'in_pool': True + }) + + if not all_txs: + return jsonify({ + 'success': False, + 'confirmations': 0, + 'difference': -amount + }) + + total_received = sum(tx['amount'] for tx in all_txs) + total_received_decimal = total_received / 1e12 + difference = amount - total_received_decimal + + min_confirmations = min(tx['confirmations'] for tx in all_txs) + success = total_received_decimal >= amount + + return jsonify({ + 'success': success, + 'confirmations': min_confirmations, + 'difference': difference + }) + + + def UpdateMeileSubscriberDB(): pass From 09a1b0b07bc0494b961dce1aff77b61d60d5cd2d Mon Sep 17 00:00:00 2001 From: freqnik Date: Wed, 4 Feb 2026 01:59:16 -0500 Subject: [PATCH 17/18] Fix host. Fix auth --- meile_plan_api.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/meile_plan_api.py b/meile_plan_api.py index 056ec88..3fb9435 100644 --- a/meile_plan_api.py +++ b/meile_plan_api.py @@ -33,7 +33,7 @@ import scrtxxs -VERSION=20260130.1833 +VERSION=20260204.0138 app = Flask(__name__) mysql = MySQL() @@ -990,7 +990,7 @@ def get_zano_balances(): @app.route('/v1/zeph/newaddress', methods=['GET']) @auth.login_required def get_new_zeph_address(): - url = "https://zephyr.ithurtswhenip.ee/json_rpc" + url = "https://zeph.ithurtswhenip.ee/json_rpc" headers = {'content-type': 'text/plain;'} data = { "jsonrpc": "2.0", @@ -1013,6 +1013,7 @@ def get_new_zeph_address(): if response.status_code == 200: print(response.json()) result = response.json() + print(result) return jsonify({ "success" : True, "address" : result['result']['address'], @@ -1043,7 +1044,7 @@ def get_zephyr_balance(): 'error': 'Invalid request parameters' }), 400 - url = "https://zephyr.ithurtswhenip.ee/json_rpc" + url = "https://zeph.ithurtswhenip.ee/json_rpc" headers = {'content-type': 'application/json'} payload = { @@ -1066,7 +1067,7 @@ def get_zephyr_balance(): url, json=payload, headers=headers, - auth=HTTPDigestAuth(scrtxxs.ZEPHYRUSER, scrtxxs.ZEPHYRPASSWORD) + auth=RequestsAuth(scrtxxs.FIROUSER, scrtxxs.FIROPASSWORD) ) result = response.json().get('result', {}) except Exception as e: From dd92a3c79412904c271e96924fd56ca19af623d8 Mon Sep 17 00:00:00 2001 From: freqnik Date: Tue, 7 Apr 2026 02:42:24 -0400 Subject: [PATCH 18/18] update hosts lists to scrtxxs list --- meile_plan_api.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/meile_plan_api.py b/meile_plan_api.py index 3fb9435..6b44b46 100644 --- a/meile_plan_api.py +++ b/meile_plan_api.py @@ -550,7 +550,7 @@ def allocate(): @app.route('/v1/pirate/newaddress', methods=['GET']) @auth.login_required def get_new_zaddress(): - url = "http://127.0.0.1:45453/" + url = scrtxxs.PIRATEHOST headers = {'content-type': 'text/plain;'} data = { "jsonrpc": "1.0", @@ -585,7 +585,7 @@ def get_pirate_balance(): print(str(e)) return False - url = "http://127.0.0.1:45453/" + url = scrtxxs.PIRATEHOST headers = {'content-type': 'text/plain;'} data = { "jsonrpc": "1.0", @@ -613,7 +613,7 @@ def get_pirate_balance(): @app.route('/v1/pirate/getbalances', methods=['GET']) def get_pirate_balances(): - url = "http://127.0.0.1:45453/" + url = scrtxxs.PIRATEHOST headers = {'content-type': 'text/plain;'} data = { "jsonrpc": "1.0", @@ -640,7 +640,7 @@ def get_pirate_balances(): @app.route('/v1/firo/newsparkaddress', methods=['GET']) @auth.login_required def get_new_saddress(): - url = "https://firo.mathnodes.com:8888/" + url = scrtxxs.FIROHOST headers = {'content-type': 'text/plain;'} data = { "jsonrpc": "1.0", @@ -674,7 +674,7 @@ def get_spark_balance(): print(str(e)) return False - url = "https://firo.mathnodes.com:8888/" + url = scrtxxs.FIROHOST headers = {'content-type': 'text/plain;'} data = { "jsonrpc": "1.0", @@ -712,7 +712,7 @@ def get_spark_txs(): "error": "Invalid request body" }), 400 - url = "https://firo-rpc.host.com:8888/" + url = scrtxxs.FIROHOST headers = {'content-type': 'text/plain;'} data = { "jsonrpc": "1.0", @@ -774,7 +774,7 @@ def get_spark_txs(): @app.route('/v1/firo/getsparkwalletbalance', methods=['GET']) def get_spark_wallet_balance(): - url = "https://firo.mathnodes.com:8888/" + url = scrtxxs.FIROHOST headers = {'content-type': 'text/plain;'} data = { "jsonrpc": "1.0", @@ -801,7 +801,7 @@ def get_spark_wallet_balance(): @app.route('/v1/pivx/newaddress', methods=['GET']) @auth.login_required def get_new_paddress(): - url = "https://pivx.mathnodes.com:9999/" + url = scrtxxs.PIVXHOST headers = {'content-type': 'text/plain;'} data = { "jsonrpc": "1.0", @@ -836,7 +836,7 @@ def get_pivx_balance(): print(str(e)) return False - url = "https://pivx.mathnodes.com:9999/" + url = scrtxxs.PIVXHOST headers = {'content-type': 'text/plain;'} data = { "jsonrpc": "1.0", @@ -862,7 +862,7 @@ def get_pivx_balance(): @app.route('/v1/pivx/getbalances', methods=['GET']) def get_pivx_balances(): - url = "https://pivx.mathnodes.com:9999/" + url = scrtxxs.PIVXHOST headers = {'content-type': 'text/plain;'} data = { "jsonrpc": "1.0", @@ -906,7 +906,7 @@ def get_zano_txs(): asset_id = ASSET_IDS[coin] - url = "https://zano.mindcontrollers.xyz:7778/json_rpc" + url = scrtxxs.ZANOHOST headers = {'content-type': 'text/plain;'} data = { "id": 0, @@ -965,7 +965,7 @@ def get_zano_txs(): @app.route('/v1/zano/getbalances', methods=['GET']) def get_zano_balances(): - url = "https://zano.mindcontrollers.xyz:7778/json_rpc" + url = scrtxxs.ZANOHOST headers = {'content-type': 'text/plain;'} data = { "id": 0, @@ -990,7 +990,7 @@ def get_zano_balances(): @app.route('/v1/zeph/newaddress', methods=['GET']) @auth.login_required def get_new_zeph_address(): - url = "https://zeph.ithurtswhenip.ee/json_rpc" + url = scrtxxs.ZEPHYRHOST headers = {'content-type': 'text/plain;'} data = { "jsonrpc": "2.0", @@ -1044,7 +1044,7 @@ def get_zephyr_balance(): 'error': 'Invalid request parameters' }), 400 - url = "https://zeph.ithurtswhenip.ee/json_rpc" + url = scrtxxs.ZEPHYRHOST headers = {'content-type': 'application/json'} payload = {