You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
This repo is archived. You can view files and clone it, but cannot push or open issues/pull-requests.

243 lines
9.6 KiB

import click
import arrow
from os.path import isfile
from time import sleep
from flask import Flask, url_for
from flask_sqlalchemy import SQLAlchemy
from flask_wtf.csrf import CSRFProtect
from redis import Redis
from datetime import datetime, timezone, timedelta
from app import config
from app.library.mattermost import post_webhook
db = SQLAlchemy()
def _setup_db(app: Flask):
uri = 'postgresql+psycopg2://{user}:{pw}@{host}:{port}/{db}'.format(
user=config.DB_USER,
pw=config.DB_PASS,
host=config.DB_HOST,
port=config.DB_PORT,
db=config.DB_NAME
)
app.config['SQLALCHEMY_DATABASE_URI'] = uri
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db = SQLAlchemy(app)
def create_app():
app = Flask(__name__)
app.config.from_envvar('FLASK_SECRETS')
# Setup backends
_setup_db(app)
with app.app_context():
# todo - find a way to move filters into a new file
# Template filters
@app.template_filter('pst')
def pst(s):
d = arrow.get(s).to('US/Pacific').format('YYYY-MM-DD HH:mm:ss')
return d
@app.template_filter('from_atomic_wow')
def from_atomic(v):
from app.library.crypto import wownero
return wownero.as_real(wownero.from_atomic(v))
@app.template_filter('from_atomic_xmr')
def from_atomic(v):
from app.library.crypto import monero
return monero.as_real(monero.from_atomic(v))
@app.template_filter('from_atomic_usd')
def to_ausd(v):
from app.models import Swap
return Swap().from_ausd(v)
@app.template_filter('ts')
def from_ts(v):
from datetime import datetime
return datetime.fromtimestamp(v)
@app.template_filter('wow_block_explorer')
def wow_block_explorer(v):
return f'https://wownero.club/transaction/{v}'
@app.template_filter('xmr_block_explorer')
def xmr_block_explorer(v):
return f'https://www.exploremonero.com/transaction/{v}'
# todo - find a way to move into a new file
# CLI
@app.cli.command('init')
def init():
import app.models
db.create_all()
@app.cli.command('store')
def store():
from app.library.crypto import wow_wallet, xmr_wallet
a, b = wow_wallet.make_wallet_rpc('store'), xmr_wallet.make_wallet_rpc('store')
return (a, b)
@app.cli.command('show')
@click.argument('swap_id')
def show_swap(swap_id):
from pprint import pprint
from app.models import Swap
s = Swap.query.get(swap_id)
if s:
pprint(s.show_details())
else:
print('Swap ID does not exist')
@app.cli.command('delete')
@click.argument('swap_id')
def show_swap(swap_id):
from app.models import Swap
s = Swap.query.get(swap_id)
if s:
print(s.show_details())
db.session.delete(s)
db.session.commit()
print(f'Swap {s.id} was deleted')
post_webhook(f'Deleted swap {s.id} from console')
else:
print('Swap ID does not exist')
@app.cli.command('list')
def list_swaps():
from app.models import Swap
s = Swap.query.all()
for i in s:
print(i.id)
@app.cli.command('wow_transfer')
@click.argument('address')
@click.argument('amount')
def wow_transfer(address, amount):
from app.library.crypto import wow_wallet, wownero
tx = wow_wallet.transfer(address, wownero.to_atomic(wownero.as_real(amount)))
post_webhook(f'Sent {amount} WOW to `{address}` from console. Tx ID: `{tx["tx_hash"]}`')
print(tx)
@app.cli.command('xmr_transfer')
@click.argument('address')
@click.argument('amount')
def xmr_transfer(address, amount):
from app.library.crypto import xmr_wallet, monero
tx = xmr_wallet.transfer(address, monero.to_atomic(monero.as_real(amount)))
post_webhook(f'Sent {amount} XMR to `{address}` from console. Tx ID: `{tx["tx_hash"]}`')
print(tx)
@app.cli.command('process_expired')
def process_expired():
from app.models import Swap
_hours = config.SWAP_EXPIRATION_HOURS
for swap in Swap.query.filter(Swap.funds_received == False):
elapsed = swap.hours_elapsed()
details = swap.show_details()
if elapsed >= _hours:
db.session.delete(swap)
db.session.commit()
print(f'Swap {swap.id} has not received funds in over {_hours} hours ({elapsed} hours) and was deleted: {details}')
post_webhook(f'Deleted swap {swap.id}; {round(elapsed)} hours old with no incoming transfers.')
sleep(5)
@app.cli.command('process_incoming')
def process_incoming():
from app.library.crypto import wow_wallet, xmr_wallet
from app.models import Swap
for swap in Swap.query.filter(Swap.funds_received == False):
swap_tx_amounts = list()
if swap.wow_to_xmr:
incoming_wallet = wow_wallet
else:
incoming_wallet = xmr_wallet
txes = incoming_wallet.incoming_transfers(swap.swap_address_index)
if 'transfers' in txes:
for tx in txes['transfers']:
if tx['unlocked']:
swap_tx_amounts.append(tx['amount'])
amount_expected = swap.receive_amount_atomic
amount_received = sum(swap_tx_amounts)
if amount_received >= amount_expected:
url = url_for('swap.get_swap', swap_id=swap.id, _external=True)
msg = f'Funds received for swap [{swap.id}]({url}) ({swap.show_details()["receive"]})'
swap.funds_received = True
db.session.add(swap)
db.session.commit()
print(msg)
post_webhook(msg)
@app.cli.command('process_outgoing')
def process_outgoing():
from datetime import datetime
from app.library.crypto import wow_wallet, xmr_wallet, wownero, monero
from app.models import Swap
from time import sleep
for swap in Swap.query.filter(Swap.funds_received == True, Swap.completed == False):
print(f'Planning to send {swap.show_details()["send"]} for swap {swap.id}')
sleep(10)
if swap.wow_to_xmr:
outgoing_wallet = xmr_wallet
dest_address = swap.return_xmr_address
crypto = monero
else:
outgoing_wallet = wow_wallet
dest_address = swap.return_wow_address
crypto = wownero
available_funds = outgoing_wallet.balances()[0]
if available_funds > swap.send_amount_atomic:
res = outgoing_wallet.transfer(dest_address, swap.send_amount_atomic)
if not 'message' in res:
url = url_for('swap.get_swap', swap_id=swap.id, _external=True)
res['address'] = dest_address
msg = 'Sent {amount} {coin} to return address for swap [{id}]({url}): `{tx_id}` - `{res}`'.format(
amount=crypto.from_atomic(swap.send_amount_atomic),
coin=swap.send_coin().upper(),
id=swap.id,
tx_id=res['tx_hash'],
url=url,
res=res
)
print(msg)
post_webhook(msg)
swap.completed = True
swap.completed_date = datetime.utcnow()
swap.send_tx_id = res['tx_hash']
swap.funds_sent = True
db.session.add(swap)
db.session.commit()
else:
print('There was an error sending funds for swap {id}: {msg}'.format(
id=swap.id,
msg=res['message']
))
if not isfile(f'/tmp/{swap.id}'):
post_webhook(f'Unable to send funds for swap {swap.id}: {res["message"]}')
with open(f'/tmp/{swap.id}', 'w') as f:
f.write(swap.id)
else:
print('Not enough funds to transfer for swap {id}. Available: {avail}, Required: {req}'.format(
id=swap.id,
avail=crypto.from_atomic(available_funds),
req=crypto.from_atomic(swap.send_amount_atomic)
))
if not isfile(f'/tmp/{swap.id}'):
post_webhook(f'Unable to send funds for swap {swap.id}: Not enough')
with open(f'/tmp/{swap.id}', 'w') as f:
f.write(swap.id)
# Routes/blueprints
from app.routes import meta, swap, api
app.register_blueprint(meta.bp)
app.register_blueprint(swap.bp)
app.register_blueprint(api.bp)
return app