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
|
||||
|
||||
source .venv/bin/activate
|
||||
export FLASK_APP=wowstash/run.py
|
||||
export FLASK_APP=wowstash/app.py
|
||||
export FLASK_SECRETS=config.py
|
||||
export FLASK_DEBUG=1
|
||||
flask eviscerate
|
||||
flask $1
|
|
@ -1,38 +1,107 @@
|
|||
from io import BytesIO
|
||||
from base64 import b64encode
|
||||
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 wowstash.blueprints.wallet import wallet_bp
|
||||
from wowstash.library.jsonrpc import wallet, daemon
|
||||
from wowstash.factory import login_manager
|
||||
from wowstash.models import User
|
||||
from wowstash.forms import Send
|
||||
from wowstash.factory import login_manager, db
|
||||
from wowstash.models import User, Transaction
|
||||
|
||||
|
||||
@wallet_bp.route("/wallet/dashboard")
|
||||
@login_required
|
||||
def dashboard():
|
||||
all_transfers = list()
|
||||
send_form = Send()
|
||||
_address_qr = BytesIO()
|
||||
user = User.query.get(current_user.id)
|
||||
wallet_height = wallet.height()['height']
|
||||
daemon_height = daemon.height()['height']
|
||||
subaddress = wallet.get_address(0, user.subaddress_index)
|
||||
balances = wallet.get_balance(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 tx in transfers[type]:
|
||||
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}"'
|
||||
address_qr = qrcode_make(qr_uri).save(_address_qr)
|
||||
qrcode = b64encode(_address_qr.getvalue()).decode()
|
||||
return render_template(
|
||||
"wallet/dashboard.html",
|
||||
wallet_height=wallet_height,
|
||||
daemon_height=daemon_height,
|
||||
subaddress=subaddress,
|
||||
balances=balances,
|
||||
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):
|
||||
return make_response(jsonify({
|
||||
'error': 'Page not found'
|
||||
}), 404)
|
||||
|
||||
@app.cli.command('initdb')
|
||||
def initdb():
|
||||
from wowstash.models import *
|
||||
# @app.cli.command('initdb')
|
||||
def init_db():
|
||||
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
|
||||
|
||||
# Session
|
||||
PERMANENT_SESSION_LIFETIME = 1800 # 30 minute session expiry
|
||||
PERMANENT_SESSION_LIFETIME = 1800 # 60 minute session expiry
|
||||
SESSION_TYPE = 'redis'
|
||||
SESSION_COOKIE_NAME = 'wowstash'
|
||||
SESSION_COOKIE_SECURE = False
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from flask import Flask
|
||||
from flask_sqlalchemy import SQLAlchemy
|
||||
from flask_wtf.csrf import CSRFProtect
|
||||
from flask_session import Session
|
||||
from flask_bcrypt import Bcrypt
|
||||
from flask_login import LoginManager
|
||||
|
@ -14,10 +15,11 @@ bcrypt = None
|
|||
|
||||
def _setup_db(app: Flask):
|
||||
global db
|
||||
uri = 'postgresql+psycopg2://{user}:{pw}@{url}/{db}'.format(
|
||||
uri = 'postgresql+psycopg2://{user}:{pw}@{host}:{port}/{db}'.format(
|
||||
user=config.DB_USER,
|
||||
pw=config.DB_PASS,
|
||||
url=config.DB_HOST,
|
||||
host=config.DB_HOST,
|
||||
port=config.DB_PORT,
|
||||
db=config.DB_NAME
|
||||
)
|
||||
app.config['SQLALCHEMY_DATABASE_URI'] = uri
|
||||
|
@ -27,8 +29,6 @@ def _setup_db(app: Flask):
|
|||
db.create_all()
|
||||
|
||||
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(
|
||||
host=app.config['REDIS_HOST'],
|
||||
port=app.config['REDIS_PORT']
|
||||
|
@ -52,6 +52,7 @@ def create_app():
|
|||
_setup_db(app)
|
||||
_setup_session(app)
|
||||
_setup_bcrypt(app)
|
||||
CSRFProtect(app)
|
||||
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
|
@ -60,7 +61,8 @@ def create_app():
|
|||
@login_manager.user_loader
|
||||
def load_user(user_id):
|
||||
from wowstash.models import User
|
||||
return User.query.get(user_id)
|
||||
user = User.query.get(user_id)
|
||||
return user
|
||||
|
||||
# template filters
|
||||
@app.template_filter('datestamp')
|
||||
|
@ -68,6 +70,27 @@ def create_app():
|
|||
d = datetime.fromtimestamp(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
|
||||
from wowstash.blueprints.auth import auth_bp
|
||||
from wowstash.blueprints.wallet import wallet_bp
|
||||
|
|
|
@ -13,3 +13,7 @@ class Register(FlaskForm):
|
|||
class Login(FlaskForm):
|
||||
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"})
|
||||
|
||||
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)
|
||||
|
||||
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):
|
||||
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.sql import func
|
||||
from wowstash.factory import db
|
||||
|
@ -14,6 +14,7 @@ class User(db.Model):
|
|||
email = db.Column(db.String(50), unique=True, index=True)
|
||||
subaddress_index = db.Column(db.Integer)
|
||||
registered_on = db.Column(db.DateTime, server_default=func.now())
|
||||
funds_locked = db.Column(db.Boolean, default=False)
|
||||
|
||||
@property
|
||||
def is_authenticated(self):
|
||||
|
@ -36,3 +37,17 @@ class User(db.Model):
|
|||
|
||||
def __repr__(self):
|
||||
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 {
|
||||
width: 100%;
|
||||
margin: 2em auto;
|
||||
margin: 3em auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.dashboard-button {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.send-form {
|
||||
width: 60%;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
</form>
|
||||
<hr>
|
||||
<p class="small">Click <a href="{{ url_for('auth.register') }}" class="">here</a> if you need to register.</p>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -21,10 +21,10 @@
|
|||
<p class="inline small">({{ balances.0 }} locked)</p>
|
||||
<span class="dashboard-buttons">
|
||||
<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 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>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -50,7 +50,7 @@
|
|||
<td>{{ tx.timestamp | datestamp }}</td>
|
||||
<td>{{ tx.type }}</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.height }}</td>
|
||||
<td>{{ tx.fee / 100000000000 }} WOW</td>
|
||||
|
@ -61,6 +61,40 @@
|
|||
</div>
|
||||
</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 'scripts.html' %}
|
||||
|
|
Loading…
Reference in a new issue