major refactor - start shelling out as a prototype to process per user
This commit is contained in:
parent
549193f0be
commit
8324dc3444
19
README.md
19
README.md
|
@ -1,3 +1,22 @@
|
||||||
# wowstash
|
# wowstash
|
||||||
|
|
||||||
A web wallet for noobs who can't use a CLI.
|
A web wallet for noobs who can't use a CLI.
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
|
||||||
|
```
|
||||||
|
wownero-wallet-cli --generate-from-json e.json --restore-height 247969 --daemon-address node.suchwow.xyz:34568 --command version
|
||||||
|
|
||||||
|
wownero-wallet-rpc --non-interactive --rpc-bind-port 8888 --daemon-address node.suchwow.xyz:34568 --wallet-file wer --rpc-login user1:mypass1 --password pass
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"filename": "<user specific name>",
|
||||||
|
"scan_from_height": <height>,
|
||||||
|
"password": "<password>",
|
||||||
|
"seed": "<seed phrase>"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
@ -4,7 +4,7 @@ services:
|
||||||
image: postgres:9.6.15-alpine
|
image: postgres:9.6.15-alpine
|
||||||
container_name: db
|
container_name: db
|
||||||
ports:
|
ports:
|
||||||
- 5432:5432
|
- 5433:5432
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_PASSWORD: ${DB_PASS}
|
POSTGRES_PASSWORD: ${DB_PASS}
|
||||||
POSTGRES_USER: ${DB_USER}
|
POSTGRES_USER: ${DB_USER}
|
||||||
|
|
|
@ -9,3 +9,4 @@ flask-bcrypt
|
||||||
flask-login
|
flask-login
|
||||||
qrcode
|
qrcode
|
||||||
Pillow
|
Pillow
|
||||||
|
git+https://github.com/lalanza808/MoneroPy
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
|
from os import kill
|
||||||
from flask import request, render_template, session, redirect, url_for, flash
|
from flask import request, render_template, session, redirect, url_for, flash
|
||||||
from flask_login import login_user, logout_user, current_user
|
from flask_login import login_user, logout_user, current_user
|
||||||
|
from secrets import token_urlsafe
|
||||||
from wowstash.blueprints.auth import auth_bp
|
from wowstash.blueprints.auth import auth_bp
|
||||||
from wowstash.forms import Register, Login
|
from wowstash.forms import Register, Login
|
||||||
from wowstash.models import User
|
from wowstash.models import User
|
||||||
from wowstash.library.jsonrpc import wallet
|
|
||||||
from wowstash.factory import db, bcrypt
|
from wowstash.factory import db, bcrypt
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,25 +16,17 @@ def register():
|
||||||
return redirect(url_for('wallet.dashboard'))
|
return redirect(url_for('wallet.dashboard'))
|
||||||
|
|
||||||
if form.validate_on_submit():
|
if form.validate_on_submit():
|
||||||
# Check if Wownero wallet is available
|
|
||||||
if wallet.connected is False:
|
|
||||||
flash('Wallet RPC interface is unavailable at this time. Try again later.')
|
|
||||||
return redirect(url_for('auth.register'))
|
|
||||||
|
|
||||||
# Check if email already exists
|
# Check if email already exists
|
||||||
user = User.query.filter_by(email=form.email.data).first()
|
user = User.query.filter_by(email=form.email.data).first()
|
||||||
if user:
|
if user:
|
||||||
flash('This email is already registered.')
|
flash('This email is already registered.')
|
||||||
return redirect(url_for('auth.login'))
|
return redirect(url_for('auth.login'))
|
||||||
|
|
||||||
# Create new subaddress
|
|
||||||
subaddress = wallet.new_address(label=form.email.data)
|
|
||||||
|
|
||||||
# Save new user
|
# Save new user
|
||||||
user = User(
|
user = User(
|
||||||
email=form.email.data,
|
email=form.email.data,
|
||||||
password=bcrypt.generate_password_hash(form.password.data).decode('utf8'),
|
password=bcrypt.generate_password_hash(form.password.data).decode('utf8'),
|
||||||
subaddress_index=subaddress[0]
|
wallet_password=token_urlsafe(16),
|
||||||
)
|
)
|
||||||
db.session.add(user)
|
db.session.add(user)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
@ -75,5 +68,8 @@ def login():
|
||||||
|
|
||||||
@auth_bp.route("/logout")
|
@auth_bp.route("/logout")
|
||||||
def logout():
|
def logout():
|
||||||
|
if current_user.is_authenticated:
|
||||||
|
current_user.kill_wallet()
|
||||||
|
current_user.clear_wallet_data()
|
||||||
logout_user()
|
logout_user()
|
||||||
return redirect(url_for('meta.index'))
|
return redirect(url_for('meta.index'))
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
from flask import request, render_template, session, redirect, url_for, make_response, jsonify
|
from flask import request, render_template, session, redirect, url_for, make_response, jsonify
|
||||||
from wowstash.blueprints.meta import meta_bp
|
from wowstash.blueprints.meta import meta_bp
|
||||||
from wowstash.library.jsonrpc import daemon
|
from wowstash.library.jsonrpc import daemon
|
||||||
from wowstash.library.info import info
|
from wowstash.library.cache import cache
|
||||||
from wowstash.library.db import Database
|
from wowstash.library.db import Database
|
||||||
|
|
||||||
|
|
||||||
@meta_bp.route('/')
|
@meta_bp.route('/')
|
||||||
def index():
|
def index():
|
||||||
return render_template('meta/index.html', node=daemon.info(), info=info.get_info())
|
return render_template('meta/index.html', node=daemon.info(), info=cache.get_coin_info())
|
||||||
|
|
||||||
@meta_bp.route('/faq')
|
@meta_bp.route('/faq')
|
||||||
def faq():
|
def faq():
|
||||||
|
@ -21,7 +21,6 @@ def terms():
|
||||||
def privacy():
|
def privacy():
|
||||||
return render_template('meta/privacy.html')
|
return render_template('meta/privacy.html')
|
||||||
|
|
||||||
|
|
||||||
@meta_bp.route('/health')
|
@meta_bp.route('/health')
|
||||||
def health():
|
def health():
|
||||||
return make_response(jsonify({
|
return make_response(jsonify({
|
||||||
|
|
|
@ -1,53 +1,115 @@
|
||||||
|
import subprocess
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from base64 import b64encode
|
from base64 import b64encode
|
||||||
from qrcode import make as qrcode_make
|
from qrcode import make as qrcode_make
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from flask import request, render_template, session, redirect, url_for, current_app, flash
|
from flask import request, render_template, session, jsonify
|
||||||
|
from flask import redirect, url_for, current_app, flash
|
||||||
from flask_login import login_required, current_user
|
from flask_login import login_required, current_user
|
||||||
|
from socket import socket
|
||||||
|
from datetime import datetime
|
||||||
from wowstash.blueprints.wallet import wallet_bp
|
from wowstash.blueprints.wallet import wallet_bp
|
||||||
from wowstash.library.jsonrpc import wallet, daemon
|
from wowstash.library.jsonrpc import Wallet, to_atomic
|
||||||
|
from wowstash.library.cache import cache
|
||||||
from wowstash.forms import Send
|
from wowstash.forms import Send
|
||||||
from wowstash.factory import login_manager, db
|
from wowstash.factory import db
|
||||||
from wowstash.models import User, Transaction
|
from wowstash.models import User
|
||||||
|
from wowstash import config
|
||||||
|
|
||||||
|
|
||||||
@wallet_bp.route("/wallet/dashboard")
|
@wallet_bp.route('/wallet/loading')
|
||||||
|
@login_required
|
||||||
|
def loading():
|
||||||
|
if current_user.wallet_connected and current_user.wallet_created:
|
||||||
|
return redirect(url_for('wallet.dashboard'))
|
||||||
|
return render_template('wallet/loading.html')
|
||||||
|
|
||||||
|
@wallet_bp.route('/wallet/dashboard')
|
||||||
@login_required
|
@login_required
|
||||||
def dashboard():
|
def dashboard():
|
||||||
all_transfers = list()
|
|
||||||
send_form = Send()
|
send_form = Send()
|
||||||
_address_qr = BytesIO()
|
_address_qr = BytesIO()
|
||||||
user = User.query.get(current_user.id)
|
all_transfers = list()
|
||||||
wallet_height = wallet.height()['height']
|
wallet = Wallet(
|
||||||
subaddress = wallet.get_address(0, user.subaddress_index)
|
proto='http',
|
||||||
balances = wallet.get_balance(0, user.subaddress_index)
|
host='127.0.0.1',
|
||||||
transfers = wallet.get_transfers(0, user.subaddress_index)
|
port=current_user.wallet_port,
|
||||||
txs_queued = Transaction.query.filter_by(from_user=user.id)
|
username=current_user.id,
|
||||||
|
password=current_user.wallet_password
|
||||||
|
)
|
||||||
|
if not wallet.connected:
|
||||||
|
return redirect(url_for('wallet.loading'))
|
||||||
|
|
||||||
|
address = wallet.get_address()
|
||||||
|
transfers = wallet.get_transfers()
|
||||||
for type in transfers:
|
for type in transfers:
|
||||||
for tx in transfers[type]:
|
for tx in transfers[type]:
|
||||||
all_transfers.append(tx)
|
all_transfers.append(tx)
|
||||||
|
balances = wallet.get_balances()
|
||||||
qr_uri = f'wownero:{subaddress}?tx_description="{current_user.email}"'
|
qr_uri = f'wownero:{address}?tx_description="{current_user.email}"'
|
||||||
address_qr = qrcode_make(qr_uri).save(_address_qr)
|
address_qr = qrcode_make(qr_uri).save(_address_qr)
|
||||||
qrcode = b64encode(_address_qr.getvalue()).decode()
|
qrcode = b64encode(_address_qr.getvalue()).decode()
|
||||||
return render_template(
|
return render_template(
|
||||||
"wallet/dashboard.html",
|
'wallet/dashboard.html',
|
||||||
wallet_height=wallet_height,
|
transfers=all_transfers,
|
||||||
subaddress=subaddress,
|
|
||||||
balances=balances,
|
balances=balances,
|
||||||
all_transfers=all_transfers,
|
address=address,
|
||||||
qrcode=qrcode,
|
qrcode=qrcode,
|
||||||
send_form=send_form,
|
send_form=send_form,
|
||||||
txs_queued=txs_queued
|
user=current_user
|
||||||
)
|
)
|
||||||
|
|
||||||
@wallet_bp.route("/wallet/send", methods=["GET", "POST"])
|
@wallet_bp.route('/wallet/connect')
|
||||||
|
@login_required
|
||||||
|
def connect():
|
||||||
|
if current_user.wallet_connected is False:
|
||||||
|
tcp = socket()
|
||||||
|
tcp.bind(('', 0))
|
||||||
|
_, port = tcp.getsockname()
|
||||||
|
tcp.close()
|
||||||
|
command = f"""wownero-wallet-rpc \
|
||||||
|
--detach \
|
||||||
|
--non-interactive \
|
||||||
|
--rpc-bind-port {port} \
|
||||||
|
--wallet-file {config.WALLET_DIR}/{current_user.id}.wallet \
|
||||||
|
--rpc-login {current_user.id}:{current_user.wallet_password} \
|
||||||
|
--password {current_user.wallet_password} \
|
||||||
|
--daemon-address {config.DAEMON_PROTO}://{config.DAEMON_HOST}:{config.DAEMON_PORT} \
|
||||||
|
--daemon-login {config.DAEMON_USER}:{config.DAEMON_PASS} \
|
||||||
|
--log-file {config.WALLET_DIR}/{current_user.id}.log
|
||||||
|
"""
|
||||||
|
proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
|
||||||
|
outs, errs = proc.communicate()
|
||||||
|
# print(outs)
|
||||||
|
if proc.returncode == 0:
|
||||||
|
print(f'Successfully started RPC for {current_user}!')
|
||||||
|
current_user.wallet_connected = True
|
||||||
|
current_user.wallet_port = port
|
||||||
|
current_user.wallet_pid = proc.pid
|
||||||
|
current_user.wallet_connect_date = datetime.now()
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
return "ok"
|
||||||
|
|
||||||
|
@wallet_bp.route('/wallet/status')
|
||||||
|
@login_required
|
||||||
|
def status():
|
||||||
|
data = {
|
||||||
|
"created": current_user.wallet_created,
|
||||||
|
"connected": current_user.wallet_connected,
|
||||||
|
"port": current_user.wallet_port,
|
||||||
|
"date": current_user.wallet_connect_date
|
||||||
|
}
|
||||||
|
return jsonify(data)
|
||||||
|
|
||||||
|
@wallet_bp.route('/wallet/send', methods=['GET', 'POST'])
|
||||||
@login_required
|
@login_required
|
||||||
def send():
|
def send():
|
||||||
send_form = Send()
|
send_form = Send()
|
||||||
redirect_url = url_for('wallet.dashboard') + "#send"
|
redirect_url = url_for('wallet.dashboard') + '#send'
|
||||||
if send_form.validate_on_submit():
|
if send_form.validate_on_submit():
|
||||||
address = str(send_form.address.data)
|
address = str(send_form.address.data)
|
||||||
|
user = User.query.get(current_user.id)
|
||||||
|
|
||||||
# Check if Wownero wallet is available
|
# Check if Wownero wallet is available
|
||||||
if wallet.connected is False:
|
if wallet.connected is False:
|
||||||
|
@ -66,21 +128,25 @@ def send():
|
||||||
|
|
||||||
# Make sure the amount provided is a number
|
# Make sure the amount provided is a number
|
||||||
try:
|
try:
|
||||||
amount = Decimal(send_form.amount.data)
|
amount = to_atomic(Decimal(send_form.amount.data))
|
||||||
except:
|
except:
|
||||||
flash('Invalid Wownero amount specified.')
|
flash('Invalid Wownero amount specified.')
|
||||||
return redirect(redirect_url)
|
return redirect(redirect_url)
|
||||||
|
|
||||||
|
# Make sure the amount does not exceed the balance
|
||||||
|
if amount >= user.balance:
|
||||||
|
flash('Not enough funds to transfer that much.')
|
||||||
|
return redirect(redirect_url)
|
||||||
|
|
||||||
# Lock user funds
|
# Lock user funds
|
||||||
user = User.query.get(current_user.id)
|
|
||||||
user.funds_locked = True
|
user.funds_locked = True
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
# Queue the transaction
|
# Queue the transaction
|
||||||
tx = Transaction(
|
tx = TransferQueue(
|
||||||
from_user=user.id,
|
user=user.id,
|
||||||
address=address,
|
address=address,
|
||||||
amount=amount,
|
amount=amount
|
||||||
)
|
)
|
||||||
db.session.add(tx)
|
db.session.add(tx)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
|
@ -9,11 +9,7 @@ DAEMON_USER = ''
|
||||||
DAEMON_PASS = ''
|
DAEMON_PASS = ''
|
||||||
|
|
||||||
# Wallet
|
# Wallet
|
||||||
WALLET_PROTO = 'http'
|
WALLET_DIR = './data/wallets'
|
||||||
WALLET_HOST = 'localhost'
|
|
||||||
WALLET_PORT = 9999
|
|
||||||
WALLET_USER = 'yyyyy'
|
|
||||||
WALLET_PASS = 'xxxxx'
|
|
||||||
|
|
||||||
# Security
|
# Security
|
||||||
PASSWORD_SALT = 'salt here' # database salts
|
PASSWORD_SALT = 'salt here' # database salts
|
||||||
|
|
|
@ -70,26 +70,85 @@ def create_app():
|
||||||
d = datetime.fromtimestamp(s)
|
d = datetime.fromtimestamp(s)
|
||||||
return d.strftime('%Y-%m-%d %H:%M:%S')
|
return d.strftime('%Y-%m-%d %H:%M:%S')
|
||||||
|
|
||||||
# commands
|
@app.template_filter('from_atomic')
|
||||||
@app.cli.command('send_transfers')
|
def from_atomic(a):
|
||||||
def send_transfers():
|
from wowstash.library.jsonrpc import from_atomic
|
||||||
from wowstash.models import Transaction, User
|
return from_atomic(a)
|
||||||
from wowstash.library.jsonrpc import wallet
|
|
||||||
from decimal import Decimal
|
|
||||||
|
|
||||||
txes = Transaction.query.filter_by(sent=False)
|
# commands
|
||||||
for tx in txes:
|
@app.cli.command('create_wallets')
|
||||||
user = User.query.get(tx.from_user)
|
def create_wallets():
|
||||||
new_tx = wallet.transfer(
|
import subprocess
|
||||||
0, user.subaddress_index, tx.address, Decimal(tx.amount)
|
from os import makedirs, path
|
||||||
)
|
from moneropy import account
|
||||||
if 'message' in new_tx:
|
from wowstash import config
|
||||||
print(f'Failed to send tx {tx.id}. Reason: {new_tx["message"]}')
|
from wowstash.factory import db
|
||||||
else:
|
from wowstash.models import User
|
||||||
tx.sent = True
|
from wowstash.library.jsonrpc import daemon
|
||||||
user.funds_locked = False
|
|
||||||
|
if not path.isdir(config.WALLET_DIR):
|
||||||
|
makedirs(config.WALLET_DIR)
|
||||||
|
|
||||||
|
wallets_to_create = User.query.filter_by(wallet_created=False)
|
||||||
|
if wallets_to_create:
|
||||||
|
for u in wallets_to_create:
|
||||||
|
print(f'Creating wallet for user {u}')
|
||||||
|
seed, sk, vk, addr = account.gen_new_wallet()
|
||||||
|
command = f"""wownero-wallet-cli \
|
||||||
|
--generate-new-wallet {config.WALLET_DIR}/{u.id}.wallet \
|
||||||
|
--restore-height {daemon.info()['height']} \
|
||||||
|
--password {u.wallet_password} \
|
||||||
|
--mnemonic-language English \
|
||||||
|
--daemon-address {config.DAEMON_PROTO}://{config.DAEMON_HOST}:{config.DAEMON_PORT} \
|
||||||
|
--daemon-login {config.DAEMON_USER}:{config.DAEMON_PASS} \
|
||||||
|
--command version
|
||||||
|
"""
|
||||||
|
proc = subprocess.Popen(command.split(), stdout=subprocess.PIPE)
|
||||||
|
proc.communicate()
|
||||||
|
if proc.returncode == 0:
|
||||||
|
print(f'Successfully created wallet for {u}!')
|
||||||
|
u.wallet_created = True
|
||||||
|
db.session.commit()
|
||||||
|
else:
|
||||||
|
print(f'Failed to create wallet for {u}.')
|
||||||
|
|
||||||
|
@app.cli.command('refresh_wallets')
|
||||||
|
def refresh_wallets():
|
||||||
|
import subprocess
|
||||||
|
from os import kill
|
||||||
|
from moneropy import account
|
||||||
|
from wowstash import config
|
||||||
|
from wowstash.factory import db
|
||||||
|
from wowstash.models import User
|
||||||
|
from wowstash.library.jsonrpc import daemon
|
||||||
|
|
||||||
|
users = User.query.all()
|
||||||
|
for u in users:
|
||||||
|
print(f'Refreshing wallet for {u}')
|
||||||
|
|
||||||
|
if u.wallet_pid is None:
|
||||||
|
break
|
||||||
|
|
||||||
|
# first check if the pid is still there
|
||||||
|
try:
|
||||||
|
kill(u.wallet_pid, 0)
|
||||||
|
except OSError:
|
||||||
|
print('pid does not exist')
|
||||||
|
u.wallet_connected = False
|
||||||
|
u.wallet_pid = None
|
||||||
|
u.wallet_connect_date = None
|
||||||
|
u.wallet_port = None
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
# then check if the user session is still active
|
||||||
|
if u.is_active is False:
|
||||||
|
print('user session inactive')
|
||||||
|
kill(u.wallet_pid, 9)
|
||||||
|
u.wallet_connected = False
|
||||||
|
u.wallet_pid = None
|
||||||
|
u.wallet_connect_date = None
|
||||||
|
u.wallet_port = None
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
print(f'Successfully sent tx! {new_tx}')
|
|
||||||
|
|
||||||
# Routes
|
# Routes
|
||||||
from wowstash.blueprints.auth import auth_bp
|
from wowstash.blueprints.auth import auth_bp
|
||||||
|
|
|
@ -6,19 +6,19 @@ from redis import Redis
|
||||||
from wowstash import config
|
from wowstash import config
|
||||||
|
|
||||||
|
|
||||||
class CoinInfo(object):
|
class Cache(object):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.redis = Redis(host=config.REDIS_HOST, port=config.REDIS_PORT)
|
self.redis = Redis(host=config.REDIS_HOST, port=config.REDIS_PORT)
|
||||||
|
|
||||||
def store_info(self, info):
|
def store_data(self, item_name, expiration_minutes, data):
|
||||||
self.redis.setex(
|
self.redis.setex(
|
||||||
"info",
|
item_name,
|
||||||
timedelta(minutes=15),
|
timedelta(minutes=expiration_minutes),
|
||||||
value=info
|
value=data
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_info(self):
|
def get_coin_info(self):
|
||||||
info = self.redis.get("info")
|
info = self.redis.get("coin_info")
|
||||||
if info:
|
if info:
|
||||||
return json_loads(info)
|
return json_loads(info)
|
||||||
else:
|
else:
|
||||||
|
@ -42,7 +42,7 @@ class CoinInfo(object):
|
||||||
'total_volume': r.json()['market_data']['total_volume']['usd'],
|
'total_volume': r.json()['market_data']['total_volume']['usd'],
|
||||||
'last_updated': r.json()['last_updated']
|
'last_updated': r.json()['last_updated']
|
||||||
}
|
}
|
||||||
self.store_info(json_dumps(info))
|
self.store_data("coin_info", 15, json_dumps(info))
|
||||||
return info
|
return info
|
||||||
|
|
||||||
info = CoinInfo()
|
cache = Cache()
|
|
@ -54,22 +54,19 @@ class Wallet(JSONRPC):
|
||||||
_address = self.make_rpc('create_address', data)
|
_address = self.make_rpc('create_address', data)
|
||||||
return (_address['address_index'], _address['address'])
|
return (_address['address_index'], _address['address'])
|
||||||
|
|
||||||
def get_address(self, account_index, subaddress_index):
|
def get_address(self, account_index=0):
|
||||||
data = {'account_index': account_index, 'address_index': [subaddress_index]}
|
data = {'account_index': account_index}
|
||||||
subaddress = self.make_rpc('get_address', data)['addresses'][0]['address']
|
address = self.make_rpc('get_address', data)['address']
|
||||||
return subaddress
|
return address
|
||||||
|
|
||||||
def get_balance(self, account_index, subaddress_index):
|
def get_balances(self, account_index=0):
|
||||||
data = {'account_index': account_index, 'address_indices': [subaddress_index]}
|
data = {'account_index': account_index}
|
||||||
_balance = self.make_rpc('get_balance', data)
|
balance = self.make_rpc('get_balance', data)
|
||||||
locked = from_atomic(_balance['per_subaddress'][0]['balance'])
|
return (balance['balance'], balance['unlocked_balance'])
|
||||||
unlocked = from_atomic(_balance['per_subaddress'][0]['unlocked_balance'])
|
|
||||||
return (float(locked), float(unlocked))
|
|
||||||
|
|
||||||
def get_transfers(self, account_index, subaddress_index):
|
def get_transfers(self, account_index=0):
|
||||||
data = {
|
data = {
|
||||||
'account_index': account_index,
|
'account_index': account_index,
|
||||||
'subaddr_indices': [subaddress_index],
|
|
||||||
'in': True,
|
'in': True,
|
||||||
'out': True,
|
'out': True,
|
||||||
'pending': True,
|
'pending': True,
|
||||||
|
@ -126,11 +123,3 @@ daemon = Daemon(
|
||||||
username=config.DAEMON_USER,
|
username=config.DAEMON_USER,
|
||||||
password=config.DAEMON_PASS
|
password=config.DAEMON_PASS
|
||||||
)
|
)
|
||||||
|
|
||||||
wallet = Wallet(
|
|
||||||
proto=config.WALLET_PROTO,
|
|
||||||
host=config.WALLET_HOST,
|
|
||||||
port=config.WALLET_PORT,
|
|
||||||
username=config.WALLET_USER,
|
|
||||||
password=config.WALLET_PASS
|
|
||||||
)
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from os import kill
|
||||||
from sqlalchemy import Column, Integer, DateTime, String, ForeignKey
|
from sqlalchemy import Column, Integer, DateTime, String, ForeignKey
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
from sqlalchemy.ext.declarative import declarative_base
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
|
@ -9,12 +10,16 @@ Base = declarative_base()
|
||||||
class User(db.Model):
|
class User(db.Model):
|
||||||
__tablename__ = 'users'
|
__tablename__ = 'users'
|
||||||
|
|
||||||
id = db.Column('user_id', db.Integer, primary_key=True)
|
id = db.Column(db.Integer, primary_key=True)
|
||||||
password = db.Column(db.String(120))
|
|
||||||
email = db.Column(db.String(50), unique=True, index=True)
|
email = db.Column(db.String(50), unique=True, index=True)
|
||||||
subaddress_index = db.Column(db.Integer)
|
password = db.Column(db.String(120))
|
||||||
registered_on = db.Column(db.DateTime, server_default=func.now())
|
register_date = db.Column(db.DateTime, server_default=func.now())
|
||||||
funds_locked = db.Column(db.Boolean, default=False)
|
wallet_password = db.Column(db.String(120))
|
||||||
|
wallet_created = db.Column(db.Boolean, default=False)
|
||||||
|
wallet_connected = db.Column(db.Boolean, default=False)
|
||||||
|
wallet_port = db.Column(db.Integer, nullable=True)
|
||||||
|
wallet_pid = db.Column(db.Integer, nullable=True)
|
||||||
|
wallet_connect_date = db.Column(db.DateTime, nullable=True)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_authenticated(self):
|
def is_authenticated(self):
|
||||||
|
@ -35,19 +40,18 @@ class User(db.Model):
|
||||||
def get_id(self):
|
def get_id(self):
|
||||||
return self.id
|
return self.id
|
||||||
|
|
||||||
def __repr__(self):
|
def kill_wallet(self):
|
||||||
return self.username
|
try:
|
||||||
|
kill(self.wallet_pid, 9)
|
||||||
|
except Exception as e:
|
||||||
|
print('could kill:', e)
|
||||||
|
|
||||||
|
def clear_wallet_data(self):
|
||||||
class Transaction(db.Model):
|
self.wallet_connected = False
|
||||||
__tablename__ = 'transactions'
|
self.wallet_port = None
|
||||||
|
self.wallet_pid = None
|
||||||
id = db.Column('tx_id', db.Integer, primary_key=True)
|
self.wallet_connect_date = None
|
||||||
from_user = db.Column(db.Integer, ForeignKey(User.id))
|
db.session.commit()
|
||||||
sent = db.Column(db.Boolean, default=False)
|
|
||||||
address = db.Column(db.String(120))
|
|
||||||
amount = db.Column(db.String(120))
|
|
||||||
date = db.Column(db.DateTime, server_default=func.now())
|
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return self.id
|
return self.email
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 550 KiB |
|
@ -12,13 +12,12 @@
|
||||||
<div class="section-heading text-center">
|
<div class="section-heading text-center">
|
||||||
<h2>Wallet Info</h2>
|
<h2>Wallet Info</h2>
|
||||||
<h4>Address:</h4>
|
<h4>Address:</h4>
|
||||||
<p class="slim small">{{ subaddress }}</p>
|
<p class="slim small">{{ address }}</p>
|
||||||
<br>
|
<br>
|
||||||
<img src="data:image/png;base64,{{ qrcode }}" width=200 class="center">
|
<img src="data:image/png;base64,{{ qrcode }}" width=200 class="center">
|
||||||
<hr><br>
|
<hr><br>
|
||||||
<h4>Balance</h4>
|
<h4>Balance</h4>
|
||||||
<p class="inline">{{ balances.1 }} WOW </p>
|
<p class="inline">{{ balances[1] | from_atomic }} WOW ({{ balances[0] | from_atomic }} locked)</p>
|
||||||
<p class="inline small">({{ balances.0 }} locked)</p>
|
|
||||||
<span class="dashboard-buttons">
|
<span class="dashboard-buttons">
|
||||||
<div class="col-sm-6 dashboard-button">
|
<div class="col-sm-6 dashboard-button">
|
||||||
<a class="btn btn-lg btn-link btn-outline btn-xl js-scroll-trigger" href="#transfers">List</a>
|
<a class="btn btn-lg btn-link btn-outline btn-xl js-scroll-trigger" href="#transfers">List</a>
|
||||||
|
@ -45,17 +44,19 @@
|
||||||
<th>Height</th>
|
<th>Height</th>
|
||||||
<th>Fee</th>
|
<th>Fee</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for tx in all_transfers | sort(attribute='timestamp', reverse=True) %}
|
{% if transfers %}
|
||||||
|
{% for tx in transfers | sort(attribute='timestamp', reverse=True) %}
|
||||||
{% if tx.type == 'pool' %}<tr class="table-warning">{% else %}<tr>{% endif %}
|
{% if tx.type == 'pool' %}<tr class="table-warning">{% else %}<tr>{% endif %}
|
||||||
<td>{{ tx.timestamp | datestamp }}</td>
|
<td>{{ tx.timestamp | datestamp }}</td>
|
||||||
<td>{{ tx.type }}</td>
|
<td>{{ tx.type }}</td>
|
||||||
<td><a href="https://wownero.club/transaction/{{ tx.txid }}" target="_blank">{{ tx.txid | truncate(12) }}</a></td>
|
<td><a href="https://wownero.club/transaction/{{ tx.txid }}" target="_blank">{{ tx.txid | truncate(12) }}</a></td>
|
||||||
<td>{% if tx.type == 'out' %}{{ tx.destinations.0.amount / 100000000000 }}{% else %}{{ tx.amount / 100000000000 }}{% endif %} WOW</td>
|
<td>{{ tx.amount | from_atomic }} WOW</td>
|
||||||
<td>{{ tx.confirmations }}</td>
|
<td>{{ tx.confirmations }}</td>
|
||||||
<td>{{ tx.height }}</td>
|
<td>{{ tx.height }}</td>
|
||||||
<td>{{ tx.fee / 100000000000 }} WOW</td>
|
<td>{{ tx.fee | from_atomic }} WOW</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -69,7 +70,7 @@
|
||||||
<p>Sending funds is currently locked due to a transfer already in progress. Please try again in a few minutes. Pending transfers:</p>
|
<p>Sending funds is currently locked due to a transfer already in progress. Please try again in a few minutes. Pending transfers:</p>
|
||||||
<ul>
|
<ul>
|
||||||
{% for tx in txs_queued %}
|
{% for tx in txs_queued %}
|
||||||
<li class="slim small">{{ tx.amount }} - {{ tx.address }}</li>
|
<li class="slim small">{{ tx.amount | from_atomic }} - {{ tx.address }}</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% else %}
|
{% else %}
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
{% include 'head.html' %}
|
||||||
|
|
||||||
|
<body id="page-top">
|
||||||
|
|
||||||
|
{% include 'navbar.html' %}
|
||||||
|
|
||||||
|
<section class="section2">
|
||||||
|
<div class="container">
|
||||||
|
<div class="section-heading text-center">
|
||||||
|
{% if current_user.wallet_created == False %}
|
||||||
|
<h2>Your wallet is being created</h2>
|
||||||
|
{% else %}
|
||||||
|
<h2>Your wallet is connecting</h2>
|
||||||
|
{% endif %}
|
||||||
|
<p>Go smoke a fatty. This page should auto-refresh when it's ready...if not, click the button below</p>
|
||||||
|
<img src="/static/img/loading-cat.gif" width=300>
|
||||||
|
<span class="dashboard-buttons">
|
||||||
|
<div class="col-sm-12 dashboard-button">
|
||||||
|
<a class="btn btn-lg btn-link btn-outline btn-xl" href="{{ url_for('wallet.dashboard') }}">Check Again</a>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
function check_wallet_status(attrib) {
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.onreadystatechange = function() {
|
||||||
|
if (xhr.readyState === 4){
|
||||||
|
let res = JSON.parse(xhr.responseText);
|
||||||
|
if (res[attrib] == true) {
|
||||||
|
window.location.href = "{{ url_for('wallet.dashboard') }}"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
xhr.open('GET', '{{ url_for("wallet.status") }}');
|
||||||
|
xhr.send();
|
||||||
|
}
|
||||||
|
|
||||||
|
{% if current_user.wallet_connected == False %}
|
||||||
|
document.addEventListener("DOMContentLoaded", function(){
|
||||||
|
var xhr = new XMLHttpRequest();
|
||||||
|
xhr.open('GET', '{{ url_for("wallet.connect") }}');
|
||||||
|
xhr.send();
|
||||||
|
});
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
window.setInterval(function(){
|
||||||
|
{% if current_user.wallet_connected == False %}
|
||||||
|
check_wallet_status('connected');
|
||||||
|
{% else %}
|
||||||
|
check_wallet_status('created');
|
||||||
|
{% endif %}
|
||||||
|
}, 5000);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% include 'footer.html' %}
|
||||||
|
|
||||||
|
{% include 'scripts.html' %}
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
Loading…
Reference in New Issue