mirror of
https://git.wownero.com/lza_menace/wowstash.git
synced 2024-08-15 00:33:15 +00:00
setting up transfers
This commit is contained in:
parent
215b9410c7
commit
5d15852607
11 changed files with 200 additions and 24 deletions
|
@ -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
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
# )
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"})
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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' %}
|
||||||
|
|
Loading…
Reference in a new issue