mirror of
https://git.wownero.com/lza_menace/wowstash.git
synced 2024-08-15 00:33:15 +00:00
major refactor - start shelling out as a prototype to process per user
This commit is contained in:
parent
549193f0be
commit
8324dc3444
14 changed files with 314 additions and 117 deletions
19
README.md
19
README.md
|
@ -1,3 +1,22 @@
|
|||
# wowstash
|
||||
|
||||
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
|
||||
container_name: db
|
||||
ports:
|
||||
- 5432:5432
|
||||
- 5433:5432
|
||||
environment:
|
||||
POSTGRES_PASSWORD: ${DB_PASS}
|
||||
POSTGRES_USER: ${DB_USER}
|
||||
|
|
|
@ -9,3 +9,4 @@ flask-bcrypt
|
|||
flask-login
|
||||
qrcode
|
||||
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_login import login_user, logout_user, current_user
|
||||
from secrets import token_urlsafe
|
||||
from wowstash.blueprints.auth import auth_bp
|
||||
from wowstash.forms import Register, Login
|
||||
from wowstash.models import User
|
||||
from wowstash.library.jsonrpc import wallet
|
||||
from wowstash.factory import db, bcrypt
|
||||
|
||||
|
||||
|
@ -15,25 +16,17 @@ def register():
|
|||
return redirect(url_for('wallet.dashboard'))
|
||||
|
||||
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
|
||||
user = User.query.filter_by(email=form.email.data).first()
|
||||
if user:
|
||||
flash('This email is already registered.')
|
||||
return redirect(url_for('auth.login'))
|
||||
|
||||
# Create new subaddress
|
||||
subaddress = wallet.new_address(label=form.email.data)
|
||||
|
||||
# Save new user
|
||||
user = User(
|
||||
email=form.email.data,
|
||||
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.commit()
|
||||
|
@ -75,5 +68,8 @@ def login():
|
|||
|
||||
@auth_bp.route("/logout")
|
||||
def logout():
|
||||
if current_user.is_authenticated:
|
||||
current_user.kill_wallet()
|
||||
current_user.clear_wallet_data()
|
||||
logout_user()
|
||||
return redirect(url_for('meta.index'))
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
from flask import request, render_template, session, redirect, url_for, make_response, jsonify
|
||||
from wowstash.blueprints.meta import meta_bp
|
||||
from wowstash.library.jsonrpc import daemon
|
||||
from wowstash.library.info import info
|
||||
from wowstash.library.cache import cache
|
||||
from wowstash.library.db import Database
|
||||
|
||||
|
||||
@meta_bp.route('/')
|
||||
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')
|
||||
def faq():
|
||||
|
@ -21,7 +21,6 @@ def terms():
|
|||
def privacy():
|
||||
return render_template('meta/privacy.html')
|
||||
|
||||
|
||||
@meta_bp.route('/health')
|
||||
def health():
|
||||
return make_response(jsonify({
|
||||
|
|
|
@ -1,53 +1,115 @@
|
|||
import subprocess
|
||||
from io import BytesIO
|
||||
from base64 import b64encode
|
||||
from qrcode import make as qrcode_make
|
||||
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 socket import socket
|
||||
from datetime import datetime
|
||||
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.factory import login_manager, db
|
||||
from wowstash.models import User, Transaction
|
||||
from wowstash.factory import db
|
||||
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
|
||||
def dashboard():
|
||||
all_transfers = list()
|
||||
send_form = Send()
|
||||
_address_qr = BytesIO()
|
||||
user = User.query.get(current_user.id)
|
||||
wallet_height = wallet.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)
|
||||
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
|
||||
)
|
||||
if not wallet.connected:
|
||||
return redirect(url_for('wallet.loading'))
|
||||
|
||||
address = wallet.get_address()
|
||||
transfers = wallet.get_transfers()
|
||||
for type in transfers:
|
||||
for tx in transfers[type]:
|
||||
all_transfers.append(tx)
|
||||
|
||||
qr_uri = f'wownero:{subaddress}?tx_description="{current_user.email}"'
|
||||
balances = wallet.get_balances()
|
||||
qr_uri = f'wownero:{address}?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,
|
||||
subaddress=subaddress,
|
||||
'wallet/dashboard.html',
|
||||
transfers=all_transfers,
|
||||
balances=balances,
|
||||
all_transfers=all_transfers,
|
||||
address=address,
|
||||
qrcode=qrcode,
|
||||
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
|
||||
def send():
|
||||
send_form = Send()
|
||||
redirect_url = url_for('wallet.dashboard') + "#send"
|
||||
redirect_url = url_for('wallet.dashboard') + '#send'
|
||||
if send_form.validate_on_submit():
|
||||
address = str(send_form.address.data)
|
||||
user = User.query.get(current_user.id)
|
||||
|
||||
# Check if Wownero wallet is available
|
||||
if wallet.connected is False:
|
||||
|
@ -66,21 +128,25 @@ def send():
|
|||
|
||||
# Make sure the amount provided is a number
|
||||
try:
|
||||
amount = Decimal(send_form.amount.data)
|
||||
amount = to_atomic(Decimal(send_form.amount.data))
|
||||
except:
|
||||
flash('Invalid Wownero amount specified.')
|
||||
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
|
||||
user = User.query.get(current_user.id)
|
||||
user.funds_locked = True
|
||||
db.session.commit()
|
||||
|
||||
# Queue the transaction
|
||||
tx = Transaction(
|
||||
from_user=user.id,
|
||||
tx = TransferQueue(
|
||||
user=user.id,
|
||||
address=address,
|
||||
amount=amount,
|
||||
amount=amount
|
||||
)
|
||||
db.session.add(tx)
|
||||
db.session.commit()
|
||||
|
|
|
@ -9,11 +9,7 @@ DAEMON_USER = ''
|
|||
DAEMON_PASS = ''
|
||||
|
||||
# Wallet
|
||||
WALLET_PROTO = 'http'
|
||||
WALLET_HOST = 'localhost'
|
||||
WALLET_PORT = 9999
|
||||
WALLET_USER = 'yyyyy'
|
||||
WALLET_PASS = 'xxxxx'
|
||||
WALLET_DIR = './data/wallets'
|
||||
|
||||
# Security
|
||||
PASSWORD_SALT = 'salt here' # database salts
|
||||
|
|
|
@ -70,26 +70,85 @@ 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
|
||||
@app.template_filter('from_atomic')
|
||||
def from_atomic(a):
|
||||
from wowstash.library.jsonrpc import from_atomic
|
||||
return from_atomic(a)
|
||||
|
||||
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
|
||||
# commands
|
||||
@app.cli.command('create_wallets')
|
||||
def create_wallets():
|
||||
import subprocess
|
||||
from os import makedirs, path
|
||||
from moneropy import account
|
||||
from wowstash import config
|
||||
from wowstash.factory import db
|
||||
from wowstash.models import User
|
||||
from wowstash.library.jsonrpc import daemon
|
||||
|
||||
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()
|
||||
print(f'Successfully sent tx! {new_tx}')
|
||||
|
||||
# Routes
|
||||
from wowstash.blueprints.auth import auth_bp
|
||||
|
|
|
@ -6,19 +6,19 @@ from redis import Redis
|
|||
from wowstash import config
|
||||
|
||||
|
||||
class CoinInfo(object):
|
||||
class Cache(object):
|
||||
def __init__(self):
|
||||
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(
|
||||
"info",
|
||||
timedelta(minutes=15),
|
||||
value=info
|
||||
item_name,
|
||||
timedelta(minutes=expiration_minutes),
|
||||
value=data
|
||||
)
|
||||
|
||||
def get_info(self):
|
||||
info = self.redis.get("info")
|
||||
def get_coin_info(self):
|
||||
info = self.redis.get("coin_info")
|
||||
if info:
|
||||
return json_loads(info)
|
||||
else:
|
||||
|
@ -42,7 +42,7 @@ class CoinInfo(object):
|
|||
'total_volume': r.json()['market_data']['total_volume']['usd'],
|
||||
'last_updated': r.json()['last_updated']
|
||||
}
|
||||
self.store_info(json_dumps(info))
|
||||
self.store_data("coin_info", 15, json_dumps(info))
|
||||
return info
|
||||
|
||||
info = CoinInfo()
|
||||
cache = Cache()
|
|
@ -54,22 +54,19 @@ class Wallet(JSONRPC):
|
|||
_address = self.make_rpc('create_address', data)
|
||||
return (_address['address_index'], _address['address'])
|
||||
|
||||
def get_address(self, account_index, subaddress_index):
|
||||
data = {'account_index': account_index, 'address_index': [subaddress_index]}
|
||||
subaddress = self.make_rpc('get_address', data)['addresses'][0]['address']
|
||||
return subaddress
|
||||
def get_address(self, account_index=0):
|
||||
data = {'account_index': account_index}
|
||||
address = self.make_rpc('get_address', data)['address']
|
||||
return address
|
||||
|
||||
def get_balance(self, account_index, subaddress_index):
|
||||
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))
|
||||
def get_balances(self, account_index=0):
|
||||
data = {'account_index': account_index}
|
||||
balance = self.make_rpc('get_balance', data)
|
||||
return (balance['balance'], balance['unlocked_balance'])
|
||||
|
||||
def get_transfers(self, account_index, subaddress_index):
|
||||
def get_transfers(self, account_index=0):
|
||||
data = {
|
||||
'account_index': account_index,
|
||||
'subaddr_indices': [subaddress_index],
|
||||
'in': True,
|
||||
'out': True,
|
||||
'pending': True,
|
||||
|
@ -126,11 +123,3 @@ daemon = Daemon(
|
|||
username=config.DAEMON_USER,
|
||||
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.ext.declarative import declarative_base
|
||||
from sqlalchemy.sql import func
|
||||
|
@ -9,12 +10,16 @@ Base = declarative_base()
|
|||
class User(db.Model):
|
||||
__tablename__ = 'users'
|
||||
|
||||
id = db.Column('user_id', db.Integer, primary_key=True)
|
||||
password = db.Column(db.String(120))
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
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)
|
||||
password = db.Column(db.String(120))
|
||||
register_date = db.Column(db.DateTime, server_default=func.now())
|
||||
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
|
||||
def is_authenticated(self):
|
||||
|
@ -35,19 +40,18 @@ class User(db.Model):
|
|||
def get_id(self):
|
||||
return self.id
|
||||
|
||||
def __repr__(self):
|
||||
return self.username
|
||||
def kill_wallet(self):
|
||||
try:
|
||||
kill(self.wallet_pid, 9)
|
||||
except Exception as e:
|
||||
print('could kill:', e)
|
||||
|
||||
|
||||
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 clear_wallet_data(self):
|
||||
self.wallet_connected = False
|
||||
self.wallet_port = None
|
||||
self.wallet_pid = None
|
||||
self.wallet_connect_date = None
|
||||
db.session.commit()
|
||||
|
||||
def __repr__(self):
|
||||
return self.id
|
||||
return self.email
|
||||
|
|
BIN
wowstash/static/img/loading-cat.gif
Normal file
BIN
wowstash/static/img/loading-cat.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 550 KiB |
|
@ -12,13 +12,12 @@
|
|||
<div class="section-heading text-center">
|
||||
<h2>Wallet Info</h2>
|
||||
<h4>Address:</h4>
|
||||
<p class="slim small">{{ subaddress }}</p>
|
||||
<p class="slim small">{{ address }}</p>
|
||||
<br>
|
||||
<img src="data:image/png;base64,{{ qrcode }}" width=200 class="center">
|
||||
<hr><br>
|
||||
<h4>Balance</h4>
|
||||
<p class="inline">{{ balances.1 }} WOW </p>
|
||||
<p class="inline small">({{ balances.0 }} locked)</p>
|
||||
<p class="inline">{{ balances[1] | from_atomic }} WOW ({{ balances[0] | from_atomic }} 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</a>
|
||||
|
@ -45,17 +44,19 @@
|
|||
<th>Height</th>
|
||||
<th>Fee</th>
|
||||
</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 %}
|
||||
<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>{% 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.height }}</td>
|
||||
<td>{{ tx.fee / 100000000000 }} WOW</td>
|
||||
<td>{{ tx.fee | from_atomic }} WOW</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</table>
|
||||
</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>
|
||||
<ul>
|
||||
{% 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 %}
|
||||
</ul>
|
||||
{% else %}
|
||||
|
|
67
wowstash/templates/wallet/loading.html
Normal file
67
wowstash/templates/wallet/loading.html
Normal file
|
@ -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 a new issue