2020-09-21 08:03:04 +01:00
|
|
|
from time import sleep
|
2020-08-31 05:10:28 +01:00
|
|
|
from io import BytesIO
|
|
|
|
from base64 import b64encode
|
|
|
|
from qrcode import make as qrcode_make
|
2020-09-11 21:59:48 +01:00
|
|
|
from decimal import Decimal
|
2020-09-20 09:53:30 +01:00
|
|
|
from flask import request, render_template, session, jsonify
|
|
|
|
from flask import redirect, url_for, current_app, flash
|
2020-08-27 06:50:08 +01:00
|
|
|
from flask_login import login_required, current_user
|
2020-09-20 09:53:30 +01:00
|
|
|
from socket import socket
|
|
|
|
from datetime import datetime
|
2020-08-27 06:50:08 +01:00
|
|
|
from wowstash.blueprints.wallet import wallet_bp
|
2020-09-21 08:03:04 +01:00
|
|
|
from wowstash.library.docker import docker
|
2020-12-30 22:49:32 +00:00
|
|
|
from wowstash.library.helpers import capture_event
|
2021-07-07 23:24:43 +01:00
|
|
|
from wowstash.library.jsonrpc import Wallet, daemon, to_atomic
|
2020-09-20 09:53:30 +01:00
|
|
|
from wowstash.library.cache import cache
|
2020-12-30 08:43:23 +00:00
|
|
|
from wowstash.forms import Send, Delete, Restore
|
2020-09-20 09:53:30 +01:00
|
|
|
from wowstash.factory import db
|
2021-11-16 07:01:58 +00:00
|
|
|
from wowstash.models import User, DonatePrompt
|
2020-09-20 09:53:30 +01:00
|
|
|
from wowstash import config
|
2020-08-27 06:50:08 +01:00
|
|
|
|
|
|
|
|
2020-12-30 08:43:23 +00:00
|
|
|
@wallet_bp.route('/wallet/setup', methods=['GET', 'POST'])
|
|
|
|
@login_required
|
|
|
|
def setup():
|
|
|
|
if current_user.wallet_created:
|
|
|
|
return redirect(url_for('wallet.dashboard'))
|
|
|
|
else:
|
|
|
|
restore_form = Restore()
|
|
|
|
if restore_form.validate_on_submit():
|
|
|
|
c = docker.create_wallet(current_user.id, restore_form.seed.data)
|
|
|
|
cache.store_data(f'init_wallet_{current_user.id}', 30, c)
|
2020-12-30 22:49:32 +00:00
|
|
|
capture_event(current_user.id, 'restore_wallet')
|
2020-12-30 08:43:23 +00:00
|
|
|
current_user.wallet_created = True
|
|
|
|
db.session.commit()
|
|
|
|
return redirect(url_for('wallet.loading'))
|
|
|
|
else:
|
|
|
|
return render_template(
|
|
|
|
'wallet/setup.html',
|
|
|
|
restore_form=restore_form
|
|
|
|
)
|
|
|
|
|
2020-09-20 09:53:30 +01:00
|
|
|
@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'))
|
2020-12-30 08:43:23 +00:00
|
|
|
if current_user.wallet_created is False:
|
|
|
|
return redirect(url_for('wallet.setup'))
|
2020-09-20 09:53:30 +01:00
|
|
|
return render_template('wallet/loading.html')
|
|
|
|
|
|
|
|
@wallet_bp.route('/wallet/dashboard')
|
2020-08-27 06:50:08 +01:00
|
|
|
@login_required
|
|
|
|
def dashboard():
|
2020-09-11 21:59:48 +01:00
|
|
|
send_form = Send()
|
2020-09-25 08:10:48 +01:00
|
|
|
delete_form = Delete()
|
2020-08-31 05:10:28 +01:00
|
|
|
_address_qr = BytesIO()
|
2020-09-20 09:53:30 +01:00
|
|
|
all_transfers = list()
|
|
|
|
wallet = Wallet(
|
|
|
|
proto='http',
|
|
|
|
host='127.0.0.1',
|
|
|
|
port=current_user.wallet_port,
|
|
|
|
username=current_user.id,
|
|
|
|
password=current_user.wallet_password
|
|
|
|
)
|
2020-09-21 17:05:02 +01:00
|
|
|
if not docker.container_exists(current_user.wallet_container):
|
|
|
|
current_user.clear_wallet_data()
|
|
|
|
return redirect(url_for('wallet.loading'))
|
|
|
|
|
2020-09-20 09:53:30 +01:00
|
|
|
if not wallet.connected:
|
2020-12-30 08:43:23 +00:00
|
|
|
sleep(1.5)
|
2020-09-20 09:53:30 +01:00
|
|
|
return redirect(url_for('wallet.loading'))
|
|
|
|
|
2021-11-16 07:01:58 +00:00
|
|
|
# Redirect to donate page if user isnt prompted for a bit
|
|
|
|
dp = DonatePrompt.query.filter(
|
|
|
|
DonatePrompt.user == current_user.id
|
|
|
|
).order_by(
|
|
|
|
DonatePrompt.date.desc()
|
|
|
|
).first()
|
|
|
|
if not dp:
|
|
|
|
d = DonatePrompt(user=current_user.id)
|
|
|
|
db.session.add(d)
|
|
|
|
db.session.commit()
|
|
|
|
return redirect(url_for('meta.donate'))
|
|
|
|
|
|
|
|
# If havent seen donate page in some time, show again
|
|
|
|
if dp.hours_elapsed() > 168:
|
|
|
|
d = DonatePrompt(user=current_user.id)
|
|
|
|
db.session.add(d)
|
|
|
|
db.session.commit()
|
|
|
|
return redirect(url_for('meta.donate'))
|
|
|
|
|
2020-09-20 09:53:30 +01:00
|
|
|
address = wallet.get_address()
|
|
|
|
transfers = wallet.get_transfers()
|
2020-08-30 20:27:35 +01:00
|
|
|
for type in transfers:
|
|
|
|
for tx in transfers[type]:
|
|
|
|
all_transfers.append(tx)
|
2020-09-20 09:53:30 +01:00
|
|
|
balances = wallet.get_balances()
|
2020-09-21 08:03:04 +01:00
|
|
|
qr_uri = f'wownero:{address}?tx_description={current_user.email}'
|
2020-08-31 05:10:28 +01:00
|
|
|
address_qr = qrcode_make(qr_uri).save(_address_qr)
|
|
|
|
qrcode = b64encode(_address_qr.getvalue()).decode()
|
2020-09-21 17:05:02 +01:00
|
|
|
seed = wallet.seed()
|
|
|
|
spend_key = wallet.spend_key()
|
|
|
|
view_key = wallet.view_key()
|
2020-12-30 22:49:32 +00:00
|
|
|
capture_event(current_user.id, 'load_dashboard')
|
2020-08-27 06:50:08 +01:00
|
|
|
return render_template(
|
2020-09-20 09:53:30 +01:00
|
|
|
'wallet/dashboard.html',
|
|
|
|
transfers=all_transfers,
|
2021-07-07 23:24:43 +01:00
|
|
|
wallet_height=wallet.height(),
|
|
|
|
node_height=daemon.height(),
|
|
|
|
node_addr=config.DAEMON_HOST,
|
2020-11-29 08:25:13 +00:00
|
|
|
sorted_txes=get_sorted_txes(transfers),
|
2020-08-30 20:27:35 +01:00
|
|
|
balances=balances,
|
2020-09-20 09:53:30 +01:00
|
|
|
address=address,
|
2020-09-11 21:59:48 +01:00
|
|
|
qrcode=qrcode,
|
|
|
|
send_form=send_form,
|
2020-09-25 08:10:48 +01:00
|
|
|
delete_form=delete_form,
|
2020-09-21 17:05:02 +01:00
|
|
|
user=current_user,
|
|
|
|
seed=seed,
|
|
|
|
spend_key=spend_key,
|
|
|
|
view_key=view_key,
|
2020-08-27 06:50:08 +01:00
|
|
|
)
|
2020-09-11 21:59:48 +01:00
|
|
|
|
2020-09-20 09:53:30 +01:00
|
|
|
@wallet_bp.route('/wallet/connect')
|
|
|
|
@login_required
|
|
|
|
def connect():
|
2020-12-30 08:43:23 +00:00
|
|
|
if current_user.wallet_created is False:
|
|
|
|
data = {
|
|
|
|
'result': 'fail',
|
|
|
|
'message': 'Wallet not yet created'
|
|
|
|
}
|
|
|
|
return jsonify(data)
|
|
|
|
|
2020-09-20 09:53:30 +01:00
|
|
|
if current_user.wallet_connected is False:
|
2020-09-21 08:03:04 +01:00
|
|
|
wallet = docker.start_wallet(current_user.id)
|
|
|
|
port = docker.get_port(wallet)
|
|
|
|
current_user.wallet_connected = docker.container_exists(wallet)
|
|
|
|
current_user.wallet_port = port
|
|
|
|
current_user.wallet_container = wallet
|
2020-09-28 04:52:58 +01:00
|
|
|
current_user.wallet_start = datetime.utcnow()
|
2020-09-21 08:03:04 +01:00
|
|
|
db.session.commit()
|
2020-12-30 22:49:32 +00:00
|
|
|
capture_event(current_user.id, 'start_wallet')
|
2020-12-30 08:43:23 +00:00
|
|
|
data = {
|
|
|
|
'result': 'success',
|
|
|
|
'message': 'Wallet has been connected'
|
|
|
|
}
|
|
|
|
else:
|
|
|
|
data = {
|
|
|
|
'result': 'fail',
|
|
|
|
'message': 'Wallet is already connected'
|
|
|
|
}
|
2020-09-20 09:53:30 +01:00
|
|
|
|
2020-12-30 08:43:23 +00:00
|
|
|
return jsonify(data)
|
2020-09-21 08:03:04 +01:00
|
|
|
|
|
|
|
@wallet_bp.route('/wallet/create')
|
|
|
|
@login_required
|
|
|
|
def create():
|
|
|
|
if current_user.wallet_created is False:
|
2020-12-30 08:43:23 +00:00
|
|
|
c = docker.create_wallet(current_user.id)
|
|
|
|
cache.store_data(f'init_wallet_{current_user.id}', 30, c)
|
2020-12-30 22:49:32 +00:00
|
|
|
capture_event(current_user.id, 'create_wallet')
|
2020-09-21 08:03:04 +01:00
|
|
|
current_user.wallet_created = True
|
|
|
|
db.session.commit()
|
2020-12-30 08:43:23 +00:00
|
|
|
return redirect(url_for('wallet.loading'))
|
|
|
|
else:
|
|
|
|
return redirect(url_for('wallet.dashboard'))
|
2020-09-20 09:53:30 +01:00
|
|
|
|
|
|
|
@wallet_bp.route('/wallet/status')
|
|
|
|
@login_required
|
|
|
|
def status():
|
2020-12-30 08:43:23 +00:00
|
|
|
user_vol = docker.get_user_volume(current_user.id)
|
|
|
|
create_container = cache.get_data(f'init_wallet_{current_user.id}')
|
2020-09-20 09:53:30 +01:00
|
|
|
data = {
|
2020-09-21 08:03:04 +01:00
|
|
|
'created': current_user.wallet_created,
|
|
|
|
'connected': current_user.wallet_connected,
|
|
|
|
'port': current_user.wallet_port,
|
2020-12-30 08:43:23 +00:00
|
|
|
'container': current_user.wallet_container,
|
|
|
|
'volume': docker.volume_exists(user_vol),
|
|
|
|
'initializing': docker.container_exists(create_container)
|
2020-09-20 09:53:30 +01:00
|
|
|
}
|
|
|
|
return jsonify(data)
|
|
|
|
|
|
|
|
@wallet_bp.route('/wallet/send', methods=['GET', 'POST'])
|
2020-09-11 21:59:48 +01:00
|
|
|
@login_required
|
|
|
|
def send():
|
|
|
|
send_form = Send()
|
2020-09-20 09:53:30 +01:00
|
|
|
redirect_url = url_for('wallet.dashboard') + '#send'
|
2020-09-21 19:42:17 +01:00
|
|
|
wallet = Wallet(
|
|
|
|
proto='http',
|
|
|
|
host='127.0.0.1',
|
|
|
|
port=current_user.wallet_port,
|
|
|
|
username=current_user.id,
|
|
|
|
password=current_user.wallet_password
|
|
|
|
)
|
2020-09-11 21:59:48 +01:00
|
|
|
if send_form.validate_on_submit():
|
|
|
|
address = str(send_form.address.data)
|
2020-09-20 09:53:30 +01:00
|
|
|
user = User.query.get(current_user.id)
|
2020-09-11 21:59:48 +01:00
|
|
|
|
|
|
|
# Check if Wownero wallet is available
|
|
|
|
if wallet.connected is False:
|
|
|
|
flash('Wallet RPC interface is unavailable at this time. Try again later.')
|
2020-12-30 22:49:32 +00:00
|
|
|
capture_event(user.id, 'tx_fail_rpc_unavailable')
|
2020-09-11 21:59:48 +01:00
|
|
|
return redirect(redirect_url)
|
|
|
|
|
|
|
|
# Quick n dirty check to see if address is WOW
|
|
|
|
if len(address) not in [97, 108]:
|
|
|
|
flash('Invalid Wownero address provided.')
|
2020-12-30 22:49:32 +00:00
|
|
|
capture_event(user.id, 'tx_fail_address_invalid')
|
2020-09-11 21:59:48 +01:00
|
|
|
return redirect(redirect_url)
|
|
|
|
|
2020-09-21 19:42:17 +01:00
|
|
|
# Check if we're sweeping or not
|
|
|
|
if send_form.amount.data == 'all':
|
|
|
|
tx = wallet.transfer(address, None, 'sweep_all')
|
|
|
|
else:
|
|
|
|
# Make sure the amount provided is a number
|
|
|
|
try:
|
|
|
|
amount = to_atomic(Decimal(send_form.amount.data))
|
|
|
|
except:
|
|
|
|
flash('Invalid Wownero amount specified.')
|
2020-12-30 22:49:32 +00:00
|
|
|
capture_event(user.id, 'tx_fail_amount_invalid')
|
2020-09-21 19:42:17 +01:00
|
|
|
return redirect(redirect_url)
|
|
|
|
|
|
|
|
# Send transfer
|
|
|
|
tx = wallet.transfer(address, amount)
|
|
|
|
|
|
|
|
# Inform user of result and redirect
|
|
|
|
if 'message' in tx:
|
|
|
|
msg = tx['message'].capitalize()
|
2020-09-24 22:18:51 +01:00
|
|
|
msg_lower = tx['message'].replace(' ', '_').lower()
|
2020-09-21 19:42:17 +01:00
|
|
|
flash(f'There was a problem sending the transaction: {msg}')
|
2021-11-16 06:12:40 +00:00
|
|
|
capture_event(user.id, f'tx_fail_{msg_lower[0:50]}')
|
2020-09-21 19:42:17 +01:00
|
|
|
else:
|
|
|
|
flash('Successfully sent transfer.')
|
2020-12-30 22:49:32 +00:00
|
|
|
capture_event(user.id, 'tx_success')
|
2020-09-11 21:59:48 +01:00
|
|
|
|
|
|
|
return redirect(redirect_url)
|
|
|
|
else:
|
|
|
|
for field, errors in send_form.errors.items():
|
|
|
|
flash(f'{send_form[field].label}: {", ".join(errors)}')
|
|
|
|
return redirect(redirect_url)
|
2020-11-29 08:25:13 +00:00
|
|
|
|
|
|
|
def get_sorted_txes(_txes):
|
|
|
|
total = 0
|
|
|
|
txes = {}
|
|
|
|
sorted_txes = {}
|
|
|
|
for tx_type in _txes:
|
|
|
|
for t in _txes[tx_type]:
|
|
|
|
txes[t['txid']] = {
|
|
|
|
'type': tx_type,
|
|
|
|
'amount': t['amount'],
|
|
|
|
'timestamp': t['timestamp'],
|
|
|
|
'fee': t['fee']
|
|
|
|
}
|
|
|
|
|
|
|
|
for i in sorted(txes.items(), key=lambda x: x[1]['timestamp']):
|
|
|
|
if i[1]['type'] == 'in':
|
|
|
|
total += i[1]['amount']
|
|
|
|
elif i[1]['type'] == 'out':
|
|
|
|
total -= i[1]['amount']
|
|
|
|
total -= i[1]['fee']
|
|
|
|
sorted_txes[i[0]] = {
|
|
|
|
'type': i[1]['type'],
|
|
|
|
'amount': i[1]['amount'],
|
|
|
|
'timestamp': i[1]['timestamp'],
|
|
|
|
'total': total
|
|
|
|
}
|
|
|
|
return sorted_txes
|