tips table/total, pending txes, verification

master
lza_menace 2 years ago
parent d937a12317
commit e3a3e7aa19

@ -5,3 +5,14 @@ Post your best neroChans. Get tipped.
No middle-men. p2p tips.
No passwords. Wallet signing authentication.
## Dev Stuff
Stagenet txes for testing/validation:
| recipient | tx_id | tx_key |
| --- | --- | --- |
| 78TanhCTvw4V8HkY3vD49A5EiyeGCzCHQUm59sByukTcffZPf3QHoK8PDg8WpMUc6VGwqxTu65HvwCUfB2jZutb6NKpjArk | 077b8654dd95fdfbd6d97808e2a9ad37cf767fb2f9da4cb0e1e6427c8587f6ee | be64bd151bd01cb4f8572a3c9731d0dff726079213e9f7017957799edc46630b |
| 78TanhCTvw4V8HkY3vD49A5EiyeGCzCHQUm59sByukTcffZPf3QHoK8PDg8WpMUc6VGwqxTu65HvwCUfB2jZutb6NKpjArk | 46fd71389ed54f195d359b84897bb89b37bb8da0bbe72ef22b552c8786346805 | b683e96770c76a1a23253873ad8a2ebb1832e14d90a05fb49a9e6e22e73d630a |
| | | |

@ -8,5 +8,4 @@ XMR_WALLET_NETWORK=stagenet
SITE_NAME=nerochan
SECRET_KEY=xxxxxxxxxxxxxxxxxxx
STATS_TOKEN=xxxxxxxxxxxxxxxxxxxx
SERVER_NAME=127.0.0.1:5000

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

@ -1,10 +1,13 @@
import click
import lorem
from datetime import datetime, timedelta, timezone
from os import path, makedirs
from urllib.request import urlopen
from nerochan.models import User, Artwork
from nerochan.helpers import make_wallet_rpc, daemon
from nerochan.models import User, Artwork, Transaction
def cli(app):
@ -15,6 +18,30 @@ def cli(app):
model = peewee.Model.__subclasses__()
db.create_tables(model)
@app.cli.command('verify_tips')
def verify_tips():
txes = Transaction.select().where(Transaction.verified == False)
for tx in txes:
data = {
'txid': tx.tx_id,
'tx_key': tx.tx_key,
'address': tx.to_address
}
try:
res = make_wallet_rpc('check_tx_key', data)
if res['in_pool'] is False:
txdata = daemon.transactions([tx.tx_id])[0]
d = txdata.timestamp.astimezone(timezone.utc)
tx.atomic_xmr = res['received']
tx.tx_date = d
tx.verified = True
tx.save()
click.echo(f'[+] Found valid tip {tx.tx_id}')
except Exception as e:
# delete if it fails for over 8 hours
if tx.create_date <= datetime.utcnow() - timedelta(hours=8):
pass
@app.cli.command('generate_data')
def generate_data():
data = {
@ -74,7 +101,7 @@ def cli(app):
click.echo(f'[+] Downloaded {art}')
if not Artwork.select().where(Artwork.image == bn).first():
artwork = Artwork(
creator=_user,
user=_user,
image=bn,
approved=True,
title=lorem.sentence(),

@ -17,6 +17,8 @@ XMR_WALLET_RPC_USER = getenv('XMR_WALLET_RPC_USER')
XMR_WALLET_RPC_PASS = getenv('XMR_WALLET_RPC_PASS')
XMR_WALLET_RPC_PORT = getenv('XMR_WALLET_RPC_PORT', 8000)
XMR_WALLET_NETWORK = getenv('XMR_WALLET_NETWORK')
XMR_DAEMON_URI = getenv('XMR_DAEMON_URI')
XMR_DAEMON_HOST, XMR_DAEMON_PORT = XMR_DAEMON_URI.split('://')[1].split(':')
REDIS_HOST = getenv('REDIS_HOST', 'localhost')
REDIS_PORT = getenv('REDIS_PORT', 6379)
DATA_PATH = getenv('DATA_PATH', f'{getcwd()}/data')

@ -1,8 +1,10 @@
from datetime import datetime
import arrow
from monero import numbers
from flask import Blueprint, current_app
from monero.numbers import from_atomic
from flask import Blueprint
from nerochan import config
bp = Blueprint('filters', 'filters')
@ -18,8 +20,15 @@ def from_ts(v):
@bp.app_template_filter('xmr_block_explorer')
def xmr_block_explorer(v):
return f'https://www.exploremonero.com/transaction/{v}'
@bp.app_template_filter('from_atomic')
def from_atomic(amt):
return numbers.from_atomic(amt)
if config.XMR_WALLET_NETWORK == 'stagenet':
return f'https://stagenet.xmrchain.net/tx/{v}'
else:
return f'https://www.exploremonero.com/transaction/{v}'
@bp.app_template_filter('atomic')
def atomic(amt):
return float(from_atomic(amt))
@bp.app_template_filter('shorten')
def shorten(s):
return s[:4] + '...' + s[-5:]

@ -21,27 +21,19 @@ def is_valid_xmr_address(form, field):
class UserRegistration(FlaskForm):
handle = StringField('Handle:', validators=[DataRequired()], render_kw={'placeholder': 'online handle', 'class': 'form-control', 'type': 'text'})
wallet_address = StringField('Wallet Address:', validators=[DataRequired(), is_valid_xmr_address], render_kw={'placeholder': 'monero wallet address', 'class': 'form-control', 'type': 'text'})
handle = StringField('Handle:', validators=[DataRequired()], render_kw={'placeholder': 'online handle', 'class': 'u-full-width', 'type': 'text'})
wallet_address = StringField('Wallet Address:', validators=[DataRequired(), is_valid_xmr_address], render_kw={'placeholder': 'monero wallet address', 'class': 'u-full-width', 'type': 'text'})
class UserLogin(FlaskForm):
handle = StringField('Handle:', validators=[DataRequired()], render_kw={'placeholder': 'online handle', 'class': 'form-control', 'type': 'text'})
handle = StringField('Handle:', validators=[DataRequired()], render_kw={'placeholder': 'online handle', 'class': 'u-full-width', 'type': 'text'})
class UserChallenge(FlaskForm):
signature = StringField('Signature:', validators=[DataRequired()], render_kw={'placeholder': 'signed data', 'class': 'form-control', 'type': 'text'})
signature = StringField('Signature:', validators=[DataRequired()], render_kw={'placeholder': 'signed data', 'class': 'u-full-width', 'type': 'text'})
class ConfirmPlatformSubscription(FlaskForm):
tx_id = StringField('TX ID:', validators=[DataRequired()], render_kw={'placeholder': 'TX ID', 'class': 'form-control', 'type': 'text'})
tx_key = StringField('TX Key:', validators=[DataRequired()], render_kw={'placeholder': 'TX Key', 'class': 'form-control', 'type': 'text'})
class ConfirmTip(FlaskForm):
tx_id = StringField('TX ID:', validators=[DataRequired()], render_kw={'placeholder': 'TX ID', 'class': 'u-full-width', 'type': 'text'})
tx_key = StringField('TX Key:', validators=[DataRequired()], render_kw={'placeholder': 'TX Key', 'class': 'u-full-width', 'type': 'text'})
class ConfirmCreatorSubscription(ConfirmPlatformSubscription):
wallet_address = StringField('Wallet Address:', validators=[DataRequired(), is_valid_xmr_address], render_kw={'placeholder': 'monero wallet address', 'class': 'form-control', 'type': 'text'})
class CreateSubscription(FlaskForm):
price_xmr = FloatField('Price (XMR):', validators=[DataRequired()], render_kw={'placeholder': '.5', 'class': 'form-control', 'type': 'text'})
number_days = FloatField('Length (Days)', validators=[DataRequired()], render_kw={'placeholder': '30', 'class': 'form-control', 'type': 'text'})

@ -1,19 +1,34 @@
import peewee as pw
import playhouse.postgres_ext as pwpg
from monero.wallet import Wallet
from monero.daemon import Daemon
from nerochan import config
daemon = Daemon(
host=config.XMR_DAEMON_HOST,
port=config.XMR_DAEMON_PORT,
timeout=3
)
wallet = Wallet(
port=config.XMR_WALLET_RPC_PORT,
user=config.XMR_WALLET_RPC_USER,
password=config.XMR_WALLET_RPC_PASS,
timeout=3
)
def make_wallet_rpc(method, data={}):
try:
w = Wallet(
port=config.XMR_WALLET_RPC_PORT,
user=config.XMR_WALLET_RPC_USER,
password=config.XMR_WALLET_RPC_PASS,
timeout=3
)
w = wallet
res = w._backend.raw_request(method, data)
return res
except Exception as e:
raise e
def make_daemon_rpc(method, data={}):
try:
d = daemon
res = d._backend.raw_request(method, data)
return res
except Exception as e:
raise e

@ -58,7 +58,7 @@ class User(pw.Model):
class Profile(pw.Model):
"""
Profile model is for users to provide metadata about
themselves; Creators for their fans or even just the general public.
themselves; artists for their fans or even just the general public.
Links to social media, contact info, portfolio sites, etc
should go in here.
"""
@ -77,10 +77,10 @@ class Profile(pw.Model):
class Artwork(pw.Model):
"""
Artwork model is any uploaded content from a creator.
Artwork model is any uploaded content from a user.
"""
id = pw.AutoField()
creator = pw.ForeignKeyField(User)
user = pw.ForeignKeyField(User)
image = pw.CharField()
upload_date = pw.DateTimeField(default=datetime.utcnow)
last_edit_date = pw.DateTimeField(default=datetime.utcnow)
@ -135,9 +135,13 @@ class Transaction(pw.Model):
"""
id = pw.AutoField()
tx_id = pw.CharField(unique=True)
atomic_xmr = pw.BigIntegerField()
tx_key = pw.CharField(unique=True)
atomic_xmr = pw.BigIntegerField(null=True)
to_address = pw.CharField()
content = pw.ForeignKeyField(Artwork)
artwork = pw.ForeignKeyField(Artwork)
verified = pw.BooleanField(default=False)
create_date = pw.DateTimeField(default=datetime.utcnow)
tx_date = pw.DateTimeField(null=True)
class Meta:
database = db

@ -1,7 +1,9 @@
from flask import Blueprint, render_template, flash, redirect, url_for
from flask_login import login_required
from nerochan.models import Artwork, User
from nerochan.helpers import make_wallet_rpc
from nerochan.forms import ConfirmTip
from nerochan.models import Artwork, Transaction
bp = Blueprint('artwork', 'artwork', url_prefix='/artwork')
@ -10,13 +12,43 @@ bp = Blueprint('artwork', 'artwork', url_prefix='/artwork')
def list():
return 'show all artwork'
@bp.route('/<int:id>')
@bp.route('/<int:id>', methods=['GET', 'POST'])
def show(id):
form = ConfirmTip()
artwork = Artwork.get_or_none(id)
if not artwork:
flash('That artwork does not exist.', 'warning')
return redirect(url_for('main.index'))
return render_template('artwork/show.html', artwork=artwork)
if form.validate_on_submit():
# Create a tx object to verify later
try:
tx = Transaction(
tx_id=form.tx_id.data,
tx_key=form.tx_key.data,
to_address=artwork.user.wallet_address,
artwork=artwork.id
)
tx.save()
except Exception as e:
pass
return redirect(url_for('artwork.show', id=artwork.id))
txes_pending = Transaction.select().where(
Transaction.artwork == artwork,
Transaction.verified == False
).count()
txes = Transaction.select().where(
Transaction.artwork == artwork,
Transaction.verified == True
).order_by(Transaction.tx_date.desc())
total = sum([i.atomic_xmr for i in txes])
return render_template(
'artwork/show.html',
artwork=artwork,
txes_pending=txes_pending,
txes=txes,
total=total,
form=form
)
@bp.route('/new', methods=['GET', 'POST'])
@login_required

@ -8,6 +8,12 @@ a, a:visited {
color: white;
}
.container-wide {
max-width: 90%;
margin-left: 3em;
margin-right: 3em;
}
.artworkLink img {
border-radius: 2px;
margin: 1em;
@ -93,7 +99,7 @@ input[type="text"] {
margin-right: 35px;
text-decoration: none;
line-height: 6.5rem;
color: #222;
color: #fff;
transition: all .2s ease;
}

@ -7,7 +7,7 @@
<div class="row">
<h2 class="no-margin"><strong>{{ artwork.title }}</strong></h2>
<h6 class="no-margin">
Posted by <a href="{{ url_for('user.show', handle=artwork.creator.handle) }}">{{ artwork.creator.handle }}</a> - {{ artwork.upload_date | humanize }}
posted by <a href="{{ url_for('user.show', handle=artwork.user.handle) }}">{{ artwork.user.handle }}</a> - {{ artwork.upload_date | humanize }}
</h6>
<p class="artworkDescription">{{ artwork.description }}</p>
</div>
@ -15,46 +15,52 @@
<div class="row">
<div class="column one-half">
<a href="{{ img }}">
<img src="{{ img }}" width="100%">
<img src="{{ img }}" width="100%" style="padding-bottom: 2em;">
</a>
</div>
<div class="column one-half">
<h5>Send a Tip</h5>
<p class="walletAddress">{{ artwork.creator.wallet_address }}</p>
<form method="get">
<p class="walletAddress">{{ artwork.user.wallet_address }}</p>
<form method="post">
{{ form.csrf_token }}
<div class="row">
<div class="six columns">
<label for="txID">TX ID</label>
<input class="u-full-width" type="text" placeholder="..." id="txID" name="txID">
{{ form.tx_id.label }}
{{ form.tx_id }}
</div>
<div class="six columns">
<label for="txKey">TX Key</label>
<input class="u-full-width" type="text" placeholder="..." id="txKey" name="txKey">
{{ form.tx_key.label }}
{{ form.tx_key }}
</div>
</div>
<input class="button-primary" type="submit" value="Submit">
</form>
<table class="u-full-width">
<thead>
<tr>
<th>TXID</th>
<th>XMR</th>
<th>Date</th>
</tr>
</thead>
<tbody>
<tr>
<td>e599...5429</td>
<td>.05</td>
<td>3 days ago</td>
</tr>
<tr>
<td>681a...e264</td>
<td>.25</td>
<td>28 days ago</td>
</tr>
</tbody>
</table>
{% if txes_pending %}
<h6>{{ txes_pending }} tips pending</h6>
{% endif %}
{% if txes %}
<table class="u-full-width">
<thead>
<tr>
<th>TXID</th>
<th>XMR</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{% for tx in txes %}
<tr>
<td><a href="{{ tx.tx_id | xmr_block_explorer }}" target="_blank">{{ tx.tx_id | shorten }}</a></td>
<td>{{ tx.atomic_xmr | atomic }}</td>
<td>{{ tx.tx_date | humanize }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<p>Total Received: <strong>{{ total | atomic }} XMR</strong></p>
{% else %}
<p>No tips confirmed yet...</p>
{% endif %}
</div>
</div>
</div>

@ -4,7 +4,8 @@
<div class="container">
<div class="row">
<h2>Latest Artworks</h2>
<h2>artworks</h2>
{% if feed['artwork'] %}
{%- for _artwork in feed['artwork'] | batch(4) %}
{%- for artwork in _artwork %}
<a class="artworkLink" href="{{ url_for('artwork.show', id=artwork.id) }}">
@ -13,12 +14,19 @@
{%- endfor %}
{%- endfor %}
<p><a href="{{ url_for('artwork.list') }}">...view all</a></p>
{% else %}
<p>There's nothing here yet...</p>
{% endif %}
</div>
<div class="row">
<h2>Latest Artists</h2>
<h2>artists</h2>
{% if feed['users'] %}
{% for user in feed['users'] %}
<p><a href="{{ url_for('user.show', handle=user.handle) }}">{{ user.handle }}</a></p>
{% endfor %}
{% else %}
<p>There's nothing here yet...</p>
{% endif %}
</div>
</div>

Loading…
Cancel
Save