setting up transfers

This commit is contained in:
lza_menace 2020-09-11 13:59:48 -07:00
parent 215b9410c7
commit 5d15852607
11 changed files with 200 additions and 24 deletions

View file

@ -1,7 +1,7 @@
#!/bin/bash #!/bin/bash
source .venv/bin/activate source .venv/bin/activate
export FLASK_APP=wowstash/run.py export FLASK_APP=wowstash/app.py
export FLASK_SECRETS=config.py export FLASK_SECRETS=config.py
export FLASK_DEBUG=1 export FLASK_DEBUG=1
flask eviscerate flask $1

View file

@ -1,38 +1,107 @@
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 flask import request, render_template, session, redirect, url_for, current_app from decimal import Decimal
from flask import request, render_template, session, redirect, url_for, current_app, flash
from flask_login import login_required, current_user from flask_login import login_required, current_user
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, daemon
from wowstash.factory import login_manager from wowstash.forms import Send
from wowstash.models import User from wowstash.factory import login_manager, db
from wowstash.models import User, Transaction
@wallet_bp.route("/wallet/dashboard") @wallet_bp.route("/wallet/dashboard")
@login_required @login_required
def dashboard(): def dashboard():
all_transfers = list() all_transfers = list()
send_form = Send()
_address_qr = BytesIO() _address_qr = BytesIO()
user = User.query.get(current_user.id) user = User.query.get(current_user.id)
wallet_height = wallet.height()['height'] wallet_height = wallet.height()['height']
daemon_height = daemon.height()['height']
subaddress = wallet.get_address(0, user.subaddress_index) subaddress = wallet.get_address(0, user.subaddress_index)
balances = wallet.get_balance(0, user.subaddress_index) balances = wallet.get_balance(0, user.subaddress_index)
transfers = wallet.get_transfers(0, user.subaddress_index) transfers = wallet.get_transfers(0, user.subaddress_index)
txs_queued = Transaction.query.filter_by(from_user=user.id)
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)
# data = {'account_index': account_index, 'address_indices': [subaddress_index]}
# _balance = self.make_rpc('get_balance', data)
# locked = from_atomic(_balance['per_subaddress'][0]['balance'])
# unlocked = from_atomic(_balance['per_subaddress'][0]['unlocked_balance'])
# return (float(locked), float(unlocked))
from wowstash.library.jsonrpc import from_atomic
from pprint import pprint
bal = wallet.make_rpc('get_balance', {'account_index': 0, 'address_indices': [user.subaddress_index]})
# print(from_atomic(bal))
pprint(bal)
# pprint(transfers)
# pprint(balances)
qr_uri = f'wownero:{subaddress}?tx_description="{current_user.email}"' qr_uri = f'wownero:{subaddress}?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, wallet_height=wallet_height,
daemon_height=daemon_height,
subaddress=subaddress, subaddress=subaddress,
balances=balances, balances=balances,
all_transfers=all_transfers, all_transfers=all_transfers,
qrcode=qrcode qrcode=qrcode,
send_form=send_form,
txs_queued=txs_queued
) )
@wallet_bp.route("/wallet/send", methods=["GET", "POST"])
@login_required
def send():
send_form = Send()
redirect_url = url_for('wallet.dashboard') + "#send"
if send_form.validate_on_submit():
address = str(send_form.address.data)
# 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(redirect_url)
# Check if user funds flag is locked
if current_user.funds_locked:
flash('You currently have transactions pending and transfers are locked. Try again later.')
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.')
return redirect(redirect_url)
# Make sure the amount provided is a number
try:
amount = Decimal(send_form.amount.data)
except:
flash('Invalid Wownero amount specified.')
return redirect(redirect_url)
# Lock user funds
user = User.query.get(current_user.id)
user.funds_locked = True
db.session.commit()
# Queue the transaction
tx = Transaction(
from_user=user.id,
address=address,
amount=amount,
)
db.session.add(tx)
db.session.commit()
# Redirect back
flash('Successfully queued transfer.')
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)

View file

@ -1,12 +1,23 @@
from wowstash.library.jsonrpc import wallet
from wowstash.models import Transaction
from wowstash.factory import db
@app.errorhandler(404) # @app.errorhandler(404)
def not_found(error): def not_found(error):
return make_response(jsonify({ return make_response(jsonify({
'error': 'Page not found' 'error': 'Page not found'
}), 404) }), 404)
@app.cli.command('initdb') # @app.cli.command('initdb')
def initdb(): def init_db():
from wowstash.models import *
db.create_all() db.create_all()
# @app.cli.command('send_transfers')
def send_transfers():
txes = Transaction.query.all()
for i in txes:
print(i)
# tx = wallet.transfer(
# 0, current_user.subaddress_index, address, amount
# )

View file

@ -20,7 +20,7 @@ PASSWORD_SALT = 'salt here' # database salts
SECRET_KEY = 'secret session key here' # encrypts the session token SECRET_KEY = 'secret session key here' # encrypts the session token
# Session # Session
PERMANENT_SESSION_LIFETIME = 1800 # 30 minute session expiry PERMANENT_SESSION_LIFETIME = 1800 # 60 minute session expiry
SESSION_TYPE = 'redis' SESSION_TYPE = 'redis'
SESSION_COOKIE_NAME = 'wowstash' SESSION_COOKIE_NAME = 'wowstash'
SESSION_COOKIE_SECURE = False SESSION_COOKIE_SECURE = False

View file

@ -1,5 +1,6 @@
from flask import Flask from flask import Flask
from flask_sqlalchemy import SQLAlchemy from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect
from flask_session import Session from flask_session import Session
from flask_bcrypt import Bcrypt from flask_bcrypt import Bcrypt
from flask_login import LoginManager from flask_login import LoginManager
@ -14,10 +15,11 @@ bcrypt = None
def _setup_db(app: Flask): def _setup_db(app: Flask):
global db global db
uri = 'postgresql+psycopg2://{user}:{pw}@{url}/{db}'.format( uri = 'postgresql+psycopg2://{user}:{pw}@{host}:{port}/{db}'.format(
user=config.DB_USER, user=config.DB_USER,
pw=config.DB_PASS, pw=config.DB_PASS,
url=config.DB_HOST, host=config.DB_HOST,
port=config.DB_PORT,
db=config.DB_NAME db=config.DB_NAME
) )
app.config['SQLALCHEMY_DATABASE_URI'] = uri app.config['SQLALCHEMY_DATABASE_URI'] = uri
@ -27,8 +29,6 @@ def _setup_db(app: Flask):
db.create_all() db.create_all()
def _setup_session(app: Flask): def _setup_session(app: Flask):
app.config['SESSION_TYPE'] = 'redis'
app.config['SESSION_COOKIE_NAME'] = app.config.get('SESSION_COOKIE_NAME', 'wowstash')
app.config['SESSION_REDIS'] = Redis( app.config['SESSION_REDIS'] = Redis(
host=app.config['REDIS_HOST'], host=app.config['REDIS_HOST'],
port=app.config['REDIS_PORT'] port=app.config['REDIS_PORT']
@ -52,6 +52,7 @@ def create_app():
_setup_db(app) _setup_db(app)
_setup_session(app) _setup_session(app)
_setup_bcrypt(app) _setup_bcrypt(app)
CSRFProtect(app)
login_manager = LoginManager() login_manager = LoginManager()
login_manager.init_app(app) login_manager.init_app(app)
@ -60,7 +61,8 @@ def create_app():
@login_manager.user_loader @login_manager.user_loader
def load_user(user_id): def load_user(user_id):
from wowstash.models import User from wowstash.models import User
return User.query.get(user_id) user = User.query.get(user_id)
return user
# template filters # template filters
@app.template_filter('datestamp') @app.template_filter('datestamp')
@ -68,6 +70,27 @@ 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.cli.command('send_transfers')
def send_transfers():
from wowstash.models import Transaction, User
from wowstash.library.jsonrpc import wallet
from decimal import Decimal
txes = Transaction.query.filter_by(sent=False)
for tx in txes:
user = User.query.get(tx.from_user)
new_tx = wallet.transfer(
0, user.subaddress_index, tx.address, Decimal(tx.amount)
)
if 'message' in new_tx:
print(f'Failed to send tx {tx.id}. Reason: {new_tx["message"]}')
else:
tx.sent = True
user.funds_locked = False
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
from wowstash.blueprints.wallet import wallet_bp from wowstash.blueprints.wallet import wallet_bp

View file

@ -13,3 +13,7 @@ class Register(FlaskForm):
class Login(FlaskForm): class Login(FlaskForm):
email = StringField('Email Address:', validators=[DataRequired()], render_kw={"placeholder": "Email", "class": "form-control", "type": "email"}) email = StringField('Email Address:', validators=[DataRequired()], render_kw={"placeholder": "Email", "class": "form-control", "type": "email"})
password = StringField('Password:', validators=[DataRequired()], render_kw={"placeholder": "Password", "class": "form-control", "type": "password"}) password = StringField('Password:', validators=[DataRequired()], render_kw={"placeholder": "Password", "class": "form-control", "type": "password"})
class Send(FlaskForm):
address = StringField('Destination Address:', validators=[DataRequired()], render_kw={"placeholder": "Wownero address", "class": "form-control"})
amount = StringField('Amount:', validators=[DataRequired()], render_kw={"placeholder": "Amount to send", "class": "form-control"})

View file

@ -78,6 +78,22 @@ class Wallet(JSONRPC):
} }
return self.make_rpc('get_transfers', data) return self.make_rpc('get_transfers', data)
def transfer(self, account_index, subaddress_index, dest_address, amount):
data = {
'account_index': account_index,
'subaddr_indices': [subaddress_index],
'destinations': [{'address': dest_address, 'amount': to_atomic(amount)}],
'priority': 0,
'unlock_time': 0,
'get_tx_key': True,
'get_tx_hex': True,
'new_algorithm': True,
'do_not_relay': False,
'ring_size': 22
}
transfer = self.make_rpc('transfer', data)
return transfer
class Daemon(JSONRPC): class Daemon(JSONRPC):
def __init__(self, **kwargs): def __init__(self, **kwargs):

View file

@ -1,4 +1,4 @@
from sqlalchemy import Column, Integer, DateTime, String 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
from wowstash.factory import db from wowstash.factory import db
@ -14,6 +14,7 @@ class User(db.Model):
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) subaddress_index = db.Column(db.Integer)
registered_on = db.Column(db.DateTime, server_default=func.now()) registered_on = db.Column(db.DateTime, server_default=func.now())
funds_locked = db.Column(db.Boolean, default=False)
@property @property
def is_authenticated(self): def is_authenticated(self):
@ -36,3 +37,17 @@ class User(db.Model):
def __repr__(self): def __repr__(self):
return self.username return self.username
class Transaction(db.Model):
__tablename__ = 'transactions'
id = db.Column('tx_id', db.Integer, primary_key=True)
from_user = db.Column(db.Integer, ForeignKey(User.id))
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):
return self.id

View file

@ -525,10 +525,15 @@ header.masthead .header-content-lg {
.dashboard-buttons { .dashboard-buttons {
width: 100%; width: 100%;
margin: 2em auto; margin: 3em auto;
display: block; display: block;
} }
.dashboard-button { .dashboard-button {
display: inline; display: inline;
} }
.send-form {
width: 60%;
margin: 0 auto;
}

View file

@ -31,7 +31,6 @@
</form> </form>
<hr> <hr>
<p class="small">Click <a href="{{ url_for('auth.register') }}" class="">here</a> if you need to register.</p> <p class="small">Click <a href="{{ url_for('auth.register') }}" class="">here</a> if you need to register.</p>
</div> </div>
</div> </div>
</div> </div>

View file

@ -21,10 +21,10 @@
<p class="inline small">({{ balances.0 }} 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 Tx</a> <a class="btn btn-lg btn-link btn-outline btn-xl js-scroll-trigger" href="#transfers">List</a>
</div> </div>
<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" href="#">Send Tx</a> <a class="btn btn-lg btn-link btn-outline btn-xl js-scroll-trigger" href="#send">Send</a>
</div> </div>
</span> </span>
</div> </div>
@ -50,7 +50,7 @@
<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>{{ tx.amount / 100000000000 }}</td> <td>{% if tx.type == 'out' %}{{ tx.destinations.0.amount / 100000000000 }}{% else %}{{ tx.amount / 100000000000 }}{% endif %} 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 / 100000000000 }} WOW</td>
@ -61,6 +61,40 @@
</div> </div>
</section> </section>
<section class="section2" id="send">
<div class="container-slim">
<div class="section-heading text-center">
<h2>Send</h2>
{% if current_user.funds_locked %}
<p>Sending funds is currently locked due to a transfer already in progress. Please try again in a few minutes. Pending transfers:</p>
<ul>
{% for tx in txs_queued %}
<li class="slim small">{{ tx.amount }} - {{ tx.address }}</li>
{% endfor %}
</ul>
{% else %}
<form method="POST" action="{{ url_for('wallet.send') }}" class="send-form">
{{ send_form.csrf_token }}
{% for f in send_form %}
{% if f.name != 'csrf_token' %}
<div class="form-group">
{{ f.label }}
{{ f }}
</div>
{% endif %}
{% endfor %}
<ul>
{% for field, errors in send_form.errors.items() %}
<li>{{ send_form[field].label }}: {{ ', '.join(errors) }}</li>
{% endfor %}
</ul>
<input type="submit" value="Send" class="btn btn-link btn-outline btn-xl">
</form>
{% endif %}
</div>
</div>
</section>
{% include 'footer.html' %} {% include 'footer.html' %}
{% include 'scripts.html' %} {% include 'scripts.html' %}