diff --git a/docker-compose.yaml b/docker-compose.yaml
index e436615..756cf62 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -7,9 +7,44 @@ x-log-config: &log-config
max-file: "5"
volumes:
lws:
- lwsadmin:
mymonero:
+ lwsadmin:
services:
+ lwsadmin:
+ container_name: lwsadmin
+ image: lalanza808/lwsadmin:latest
+ restart: unless-stopped
+ depends_on:
+ - monero-lws
+ environment:
+ LWS_URL: http://monero-lws:8080
+ LWS_ADMIN_URL: http://monero-lws:8081
+ QUART_ENV: production
+ HOST: 0.0.0.0
+ DEBUG: 0
+ SERVER_NAME: ${SERVER_NAME:-127.0.0.1:5000}
+ SECRET_KEY: ${SECRET_KEY:-thisisasecret}
+ volumes:
+ - lwsadmin:/srv/lwsadmin/data
+ user: "1000:1000"
+ command:
+ ./.venv/bin/poetry run start
+ ports:
+ - 127.0.0.1:5000:5000
+ <<: *log-config
+ mymonero-web:
+ container_name: mymonero-web
+ image: lalanza808/mymonero-web-js:latest
+ restart: unless-stopped
+ environment:
+ MYMONERO_WEB_NETTYPE: 0
+ MYMONERO_WEB_SERVER_URL: http://localhost:8080/
+ MYMONERO_WEB_APP_NAME: LZAXMR
+ ports:
+ - 127.0.0.1:9110:80
+ volumes:
+ - mymonero:/app
+ <<: *log-config
monero-lws:
container_name: monero-lws
image: lalanza808/monero-lws:develop
diff --git a/lwsadmin/Dockerfile b/lwsadmin/Dockerfile
index 4ed87b0..885e8a2 100644
--- a/lwsadmin/Dockerfile
+++ b/lwsadmin/Dockerfile
@@ -1,6 +1,6 @@
FROM ubuntu:22.04
-RUN apt-get update
+RUN apt-get update -y
RUN apt-get install -y python3 python3-venv
WORKDIR /srv/lwsadmin
RUN adduser \
diff --git a/lwsadmin/lws/__init__.py b/lwsadmin/lws/__init__.py
index e69de29..44b7576 100644
--- a/lwsadmin/lws/__init__.py
+++ b/lwsadmin/lws/__init__.py
@@ -0,0 +1 @@
+from lws.factory import create_app
\ No newline at end of file
diff --git a/lwsadmin/lws/config.py b/lwsadmin/lws/config.py
index c6c837c..ec99c9e 100644
--- a/lwsadmin/lws/config.py
+++ b/lwsadmin/lws/config.py
@@ -18,4 +18,8 @@ QUART_AUTH_DURATION = int(env.get('QUART_AUTH_DURATION', 60 * 60)) # 1 hour
LWS_URL = env.get("LWS_URL", "http://127.0.0.1:8080")
LWS_ADMIN_URL = env.get("LWS_ADMIN_URL", "http://127.0.0.1:8081")
-#
\ No newline at end of file
+# Monerod
+MONEROD_URL = env.get("MONEROD_URL", "http://singapore.node.xmr.pm:18089")
+
+# MyMonero
+MYMONERO_URL = env.get("MYMONERO_URL", "http://localhost:9110")
\ No newline at end of file
diff --git a/lwsadmin/lws/factory.py b/lwsadmin/lws/factory.py
index ee76031..b93790d 100644
--- a/lwsadmin/lws/factory.py
+++ b/lwsadmin/lws/factory.py
@@ -20,16 +20,21 @@ def create_app():
@app.before_serving
async def startup():
- from lws.routes import auth, wallet, meta
+ from lws.routes import auth, wallet, meta, htmx
from lws import filters
app.register_blueprint(filters.bp)
app.register_blueprint(auth.bp)
app.register_blueprint(meta.bp)
app.register_blueprint(wallet.bp)
+ app.register_blueprint(htmx.bp)
@app.errorhandler(Unauthorized)
async def redirect_to_login(*_):
- return redirect(f"/login?next={request.path}")
+ if request.path == "/":
+ return redirect(f"/login?next={request.path}")
+ else:
+ return f"
you need to authenticate first
login"
+
return app
bcrypt = Bcrypt(create_app())
\ No newline at end of file
diff --git a/lwsadmin/lws/filters.py b/lwsadmin/lws/filters.py
index bd70a3a..a840e36 100644
--- a/lwsadmin/lws/filters.py
+++ b/lwsadmin/lws/filters.py
@@ -1,6 +1,8 @@
from monero.numbers import from_atomic
from quart import Blueprint
+from lws.models import Wallet, get_random_words
+
bp = Blueprint('filters', 'filters')
@@ -12,4 +14,12 @@ def atomic(amt):
@bp.app_template_filter('shorten')
def shorten(s):
- return f"{s[:6]}...{s[-6:]}"
\ No newline at end of file
+ return f"{s[:6]}...{s[-6:]}"
+
+@bp.app_template_filter('find_label')
+def find_label(s):
+ w = Wallet.select().where(Wallet.address == s).first()
+ if w:
+ return w.label
+ else:
+ return get_random_words()
\ No newline at end of file
diff --git a/lwsadmin/lws/helpers.py b/lwsadmin/lws/helpers.py
index 7ed8002..a5495a7 100644
--- a/lwsadmin/lws/helpers.py
+++ b/lwsadmin/lws/helpers.py
@@ -18,7 +18,6 @@ from lws import config
# webhook_delete_uuid: {"event_ids": [...]}
# webhook_list: {}
-
class LWS:
def __init__(self):
pass
@@ -26,6 +25,19 @@ class LWS:
def init(self, admin_key):
self.admin_key = admin_key
+ def _init(self):
+ self.admin_key = User.select().first().view_key
+
+ def get_address_info(self, address, view_key):
+ endpoint = f"{config.LWS_URL}/get_address_info"
+ data = {
+ "address": address,
+ "view_key": view_key
+ }
+ r = requests.post(endpoint, json=data, timeout=5)
+ r.raise_for_status()
+ return r.json()
+
def get_wallet(self, address: str) -> dict:
try:
res = self.list_accounts()
@@ -108,9 +120,8 @@ class LWS:
print(f"Failed to add wallet {address}: {e}")
return {}
- def modify_wallet(self, address: str, active: bool) -> dict:
+ def modify_wallet(self, address: str, status: str) -> dict:
endpoint = f"{config.LWS_ADMIN_URL}/modify_account_status"
- status = "active" if active else "inactive"
data = {
"auth": self.admin_key,
"params": {
diff --git a/lwsadmin/lws/models.py b/lwsadmin/lws/models.py
index bb1c0cf..2f75dfe 100644
--- a/lwsadmin/lws/models.py
+++ b/lwsadmin/lws/models.py
@@ -1,9 +1,16 @@
+from random import choice
from datetime import datetime
from peewee import *
+from monero.wordlists import English
-db = SqliteDatabase('data/lws.db')
+db = SqliteDatabase("data/lws.db")
+
+
+def get_random_words():
+ e = English().word_list
+ return f"{choice(e)}-{choice(e)}-{choice(e)}"
class User(Model):
@@ -17,4 +24,13 @@ class User(Model):
database = db
-db.create_tables([User])
+class Wallet(Model):
+ date = DateTimeField(default=datetime.utcnow)
+ address = CharField()
+ label = CharField(default=get_random_words, null=False)
+
+ class Meta:
+ database = db
+
+
+db.create_tables([User, Wallet])
diff --git a/lwsadmin/lws/routes/htmx.py b/lwsadmin/lws/routes/htmx.py
new file mode 100644
index 0000000..fb4f15b
--- /dev/null
+++ b/lwsadmin/lws/routes/htmx.py
@@ -0,0 +1,80 @@
+from quart import Blueprint, render_template, request
+from monero.seed import Seed
+from quart_auth import login_required
+
+from lws.models import User, Wallet
+from lws.helpers import lws
+from lws import config
+
+bp = Blueprint('htmx', 'htmx', url_prefix="/htmx")
+
+
+@bp.route("/create_wallet")
+async def create_wallet():
+ """Creating a new wallet with newly generated seeds"""
+ seed = Seed()
+ return await render_template(
+ "htmx/create_wallet.html",
+ seed=seed.phrase,
+ address=seed.public_address(),
+ psk=seed.public_spend_key(),
+ pvk=seed.public_view_key(),
+ ssk=seed.secret_spend_key(),
+ svk=seed.secret_view_key()
+ )
+
+
+@bp.route("/import_wallet")
+async def import_wallet():
+ """Importing an existing wallet"""
+ return await render_template("htmx/import_wallet.html")
+
+
+@bp.route("/label_wallet")
+async def label_wallet():
+ """Changing the label on a stored wallet"""
+ address = request.args.get("address")
+ label = request.args.get("label")
+ return await render_template(
+ "htmx/label_wallet.html",
+ address=address,
+ label=label
+ )
+
+
+@bp.route("/set_height")
+async def set_height():
+ """Setting a new height to scan from"""
+ address = request.args.get("address")
+ height = request.args.get("height")
+ return await render_template(
+ "htmx/set_height.html",
+ address=address,
+ height=height
+ )
+
+
+@bp.route("/show_wallets")
+@login_required
+async def show_wallets():
+ """Showing all wallets in the database in a table"""
+ admin = User.select().first()
+ lws.init(admin.view_key)
+ accounts = lws.list_accounts()
+ if 'hidden' in accounts:
+ del accounts["hidden"]
+ # save wallets if they don't exist in the db
+ for status in accounts:
+ for account in accounts[status]:
+ w = Wallet.select().where(Wallet.address == account["address"]).first()
+ if not w:
+ w = Wallet(
+ address=account["address"]
+ )
+ w.save()
+ requests = lws.list_requests()
+ return await render_template(
+ "htmx/show_wallets.html",
+ accounts=accounts,
+ requests=requests
+ )
diff --git a/lwsadmin/lws/routes/meta.py b/lwsadmin/lws/routes/meta.py
index 54c6f81..b3d92ee 100644
--- a/lwsadmin/lws/routes/meta.py
+++ b/lwsadmin/lws/routes/meta.py
@@ -9,20 +9,15 @@ from lws import config
bp = Blueprint("meta", "meta")
+
@bp.route("/")
@login_required
async def index():
admin = User.select().first()
lws.init(admin.view_key)
- accounts = lws.list_accounts()
- if 'hidden' in accounts:
- del accounts["hidden"]
- requests = lws.list_requests()
return await render_template(
"index.html",
- config=config,
- accounts=accounts,
- requests=requests
+ config=config
)
diff --git a/lwsadmin/lws/routes/wallet.py b/lwsadmin/lws/routes/wallet.py
index 25e535a..aec9293 100644
--- a/lwsadmin/lws/routes/wallet.py
+++ b/lwsadmin/lws/routes/wallet.py
@@ -1,11 +1,13 @@
import monero.address
-from quart import Blueprint, render_template, request, flash, redirect
-from quart_auth import login_required, current_user
+from monero.seed import Seed
+from quart import Blueprint, request, flash, redirect, url_for
+from quart_auth import login_required
from lws.helpers import lws
+from lws.models import Wallet, get_random_words
-bp = Blueprint('wallet', 'wallet')
+bp = Blueprint("wallet", "wallet")
@bp.route("/wallet/add", methods=["GET", "POST"])
@@ -13,74 +15,61 @@ bp = Blueprint('wallet', 'wallet')
async def add():
form = await request.form
if form:
- address = form.get("address", "")
- view_key = form.get("view_key", "")
- restore_height = form.get("restore_height", 0)
- valid_view_key = False
- if not address:
- await flash("must provide an address")
- return redirect("/wallet/add")
- if not view_key:
- await flash("must provide a view_key")
- return redirect("/wallet/add")
+ label = form.get("label")
+ seed = form.get("seed")
+ restore_height = form.get("restore_height", None)
try:
- _a = monero.address.Address(address)
- valid_view_key = _a.check_private_view_key(view_key)
+ seed = Seed(seed)
except ValueError:
- await flash("Invalid Monero address")
- return redirect("/wallet/add")
- if not valid_view_key:
- await flash("Invalid view key provided for address")
- return redirect("/wallet/add")
- lws.add_wallet(address, view_key)
- lws.rescan(address, int(restore_height))
- await flash("wallet added")
- return redirect(f"/")
- return await render_template("wallet/add.html")
+ await flash("Invalid mnemonic seed")
+ return ""
+ lws._init()
+ address = str(seed.public_address())
+ svk = str(seed.secret_view_key())
+ lws.add_wallet(address, svk)
+ if restore_height != "-1":
+ lws.rescan(address, int(restore_height))
+ w = Wallet(
+ address=seed.public_address(),
+ label=label if label else get_random_words()
+ )
+ w.save()
+ return redirect(url_for("htmx.show_wallets"))
-@bp.route("/wallet/rescan")
+@bp.route("/wallet//rescan/")
@login_required
-async def rescan():
- address = request.args.get('address')
- height = request.args.get('height')
- if not address or not height:
- await flash("you need to provide both address and height")
- return redirect("/")
- if lws.rescan(address, int(height)):
- await flash(f"rescanning {address} from block {height}")
- else:
- await flash("probz")
- return redirect(f"/")
+async def rescan(address, height):
+ lws.rescan(address, int(height))
+ return redirect(url_for("htmx.show_wallets"))
-@bp.route("/wallet//disable")
+@bp.route("/wallet//modify/")
@login_required
-async def disable(address):
- lws.modify_wallet(address, False)
- await flash(f"{address} disabled in LWS")
- return redirect(f"/")
+async def modify(address, status):
+ lws.modify_wallet(address, status)
+ return redirect(url_for("htmx.show_wallets"))
-@bp.route("/wallet//enable")
-@login_required
-async def enable(address):
- lws.modify_wallet(address, True)
- await flash(f"{address} enabled in LWS")
- return redirect(f"/")
-
@bp.route("/wallet//accept")
@login_required
async def accept(address):
lws.accept_request(address)
- await flash(f"{address} accepted")
- return redirect(f"/")
+ return redirect(url_for("htmx.show_wallets"))
@bp.route("/wallet//reject")
@login_required
async def reject(address):
lws.reject_request(address)
- await flash(f"{address} rejected")
- return redirect(f"/")
+ return redirect(url_for("htmx.show_wallets"))
+
+@bp.route("/wallet//label/