mirror of
https://git.wownero.com/wownero/wownero-funding-system.git
synced 2024-08-15 00:53:45 +00:00
aeonfunding update for wownero
a lot more generic; a lot more configurable; UI goodness with accounting; wallet account support for ezmode
This commit is contained in:
parent
f473a4234e
commit
955de2544e
19 changed files with 361 additions and 173 deletions
|
@ -2,11 +2,9 @@ from datetime import datetime
|
|||
from flask import request, redirect, Response, abort, render_template, url_for, flash, make_response, send_from_directory, jsonify
|
||||
from flask.ext.login import login_user , logout_user , current_user , login_required, current_user
|
||||
from flask_yoloapi import endpoint, parameter
|
||||
|
||||
import settings
|
||||
from wowfunding.factory import app, db_session
|
||||
from wowfunding.orm.orm import Proposal, User
|
||||
|
||||
from funding.factory import app, db_session
|
||||
from funding.orm.orm import Proposal, User
|
||||
|
||||
@app.route('/api/1/proposals')
|
||||
@endpoint.api(
|
||||
|
@ -21,15 +19,13 @@ def api_proposals_get(status, cat, limit, offset):
|
|||
except Exception as ex:
|
||||
print(ex)
|
||||
return 'error', 500
|
||||
|
||||
return [p.json for p in proposals]
|
||||
|
||||
|
||||
@app.route('/api/1/convert/wow-usd')
|
||||
@endpoint.api(
|
||||
parameter('wow', type=int, location='args', required=True)
|
||||
parameter('amount', type=int, location='args', required=True)
|
||||
)
|
||||
def api_wow_usd(wow):
|
||||
from wowfunding.bin.utils import Summary, wow_to_usd
|
||||
def api_coin_usd(amount):
|
||||
from funding.bin.utils import Summary, coin_to_usd
|
||||
prices = Summary.fetch_prices()
|
||||
return jsonify(usd=wow_to_usd(wows=wow, btc_per_wow=prices['wow-btc'], usd_per_btc=prices['btc-usd']))
|
||||
return jsonify(usd=coin_to_usd(amt=amount, btc_per_coin=prices['coin-btc'], usd_per_btc=prices['btc-usd']))
|
|
@ -207,4 +207,4 @@ def such_xss(inp):
|
|||
line = '>%s' % line
|
||||
_lines.append(line)
|
||||
|
||||
return "\n".join(_lines)
|
||||
return "\n".join(_lines)
|
|
@ -1,10 +1,12 @@
|
|||
import settings
|
||||
import requests
|
||||
from requests.auth import HTTPDigestAuth
|
||||
|
||||
|
||||
class WowneroDaemon:
|
||||
class Daemon:
|
||||
def __init__(self):
|
||||
self.url = settings.RPC_LOCATION
|
||||
self.username = settings.RPC_USERNAME
|
||||
self.password = settings.RPC_PASSWORD
|
||||
self.headers = {"User-Agent": "Mozilla"}
|
||||
|
||||
def create_address(self, label_name):
|
||||
|
@ -16,37 +18,65 @@ class WowneroDaemon:
|
|||
}
|
||||
return self._make_request(data)
|
||||
|
||||
def get_address(self, index):
|
||||
def create_account(self, pid):
|
||||
data = {
|
||||
'method': 'get_address',
|
||||
'params': {'address_index': [index], 'account_index': 0},
|
||||
'method': 'create_account',
|
||||
'params': {'label': '%s' % pid},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
return self._make_request(data)
|
||||
|
||||
def get_address(self, index, proposal_id):
|
||||
data = {
|
||||
'method': 'getaddress',
|
||||
'params': {'account_index': proposal_id, 'address_index': '[0]'},
|
||||
'jsonrpc': '2.0',
|
||||
'id': '0'
|
||||
}
|
||||
try:
|
||||
result = self._make_request(data)
|
||||
return next(z for z in result['result']['addresses'] if z['address_index'] == index)
|
||||
return result['result']
|
||||
except:
|
||||
return
|
||||
|
||||
def get_transfers_in(self, index):
|
||||
def get_transfers_in(self, index, proposal_id):
|
||||
data = {
|
||||
"method":"get_transfers",
|
||||
"params": {"pool": True, "in": True, "account_index": 0, "subaddr_indices": [index]},
|
||||
"params": {"pool": True, "in": True, "account_index": proposal_id},
|
||||
"jsonrpc": "2.0",
|
||||
"id": "0",
|
||||
}
|
||||
data = self._make_request(data)
|
||||
data = data['result'].get('in', [])
|
||||
for d in data:
|
||||
d['amount_human'] = float(d['amount'])/1e11
|
||||
|
||||
d['amount_human'] = float(d['amount'])/1e12
|
||||
return {
|
||||
'sum': sum([float(z['amount'])/1e11 for z in data]),
|
||||
'sum': sum([float(z['amount'])/1e12 for z in data]),
|
||||
'txs': data
|
||||
}
|
||||
|
||||
def get_transfers_out(self, index, proposal_id):
|
||||
data = {
|
||||
"method":"get_transfers",
|
||||
"params": {"pool": True, "out": True, "account_index": proposal_id},
|
||||
"jsonrpc": "2.0",
|
||||
"id": "0",
|
||||
}
|
||||
data = self._make_request(data)
|
||||
data = data['result'].get('out', [])
|
||||
for d in data:
|
||||
d['amount_human'] = float(d['amount'])/1e12
|
||||
return {
|
||||
'sum': sum([float(z['amount'])/1e12 for z in data]),
|
||||
'txs': data
|
||||
}
|
||||
|
||||
def _make_request(self, data):
|
||||
r = requests.post(self.url, json=data, headers=self.headers)
|
||||
if self.username:
|
||||
if self.password:
|
||||
r = requests.post(self.url, auth=HTTPDigestAuth(settings.RPC_USERNAME, settings.RPC_PASSWORD), json=data, headers=self.headers)
|
||||
else:
|
||||
r = requests.post(self.url, json=data, headers=self.headers)
|
||||
r.raise_for_status()
|
||||
return r.json()
|
||||
return r.json()
|
|
@ -1,44 +1,39 @@
|
|||
from datetime import datetime, date
|
||||
|
||||
import requests
|
||||
from flask import g
|
||||
from flask.json import JSONEncoder
|
||||
|
||||
import json
|
||||
import settings
|
||||
|
||||
|
||||
def json_encoder(obj):
|
||||
if isinstance(obj, (datetime, date)):
|
||||
return obj.isoformat()
|
||||
raise TypeError ("Type %s not serializable" % type(obj))
|
||||
|
||||
|
||||
class Summary:
|
||||
@staticmethod
|
||||
def fetch_prices():
|
||||
if hasattr(g, 'wowfunding_prices') and g.wow_prices:
|
||||
return g.wow_prices
|
||||
|
||||
from wowfunding.factory import cache
|
||||
cache_key = 'wowfunding_prices'
|
||||
if hasattr(g, 'funding_prices') and g.coin_prices:
|
||||
return g.coin_prices
|
||||
from funding.factory import cache
|
||||
cache_key = 'funding_prices'
|
||||
data = cache.get(cache_key)
|
||||
if data:
|
||||
return data
|
||||
data = {
|
||||
'wow-btc': price_tradeogre_wow_btc(),
|
||||
'coin-btc': coin_btc_value(),
|
||||
'btc-usd': price_cmc_btc_usd()
|
||||
}
|
||||
|
||||
cache.set(cache_key, data=data, expiry=7200)
|
||||
g.wow_prices = data
|
||||
g.coin_prices = data
|
||||
return data
|
||||
|
||||
@staticmethod
|
||||
def fetch_stats(purge=False):
|
||||
from wowfunding.factory import db_session
|
||||
from wowfunding.orm.orm import Proposal, User, Comment
|
||||
from wowfunding.factory import cache
|
||||
cache_key = 'wowfunding_stats'
|
||||
from funding.factory import db_session
|
||||
from funding.orm.orm import Proposal, User, Comment
|
||||
from funding.factory import cache
|
||||
cache_key = 'funding_stats'
|
||||
data = cache.get(cache_key)
|
||||
if data and not purge:
|
||||
return data
|
||||
|
@ -65,7 +60,6 @@ class Summary:
|
|||
cache.set(cache_key, data=data, expiry=300)
|
||||
return data
|
||||
|
||||
|
||||
def price_cmc_btc_usd():
|
||||
headers = {'User-Agent': 'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0'}
|
||||
try:
|
||||
|
@ -75,8 +69,7 @@ def price_cmc_btc_usd():
|
|||
except:
|
||||
return
|
||||
|
||||
|
||||
def price_tradeogre_wow_btc():
|
||||
def coin_btc_value():
|
||||
headers = {'User-Agent': 'Mozilla/5.0 (Android 4.4; Mobile; rv:41.0) Gecko/41.0 Firefox/41.0'}
|
||||
try:
|
||||
r = requests.get('https://tradeogre.com/api/v1/ticker/BTC-WOW', headers=headers)
|
||||
|
@ -85,9 +78,8 @@ def price_tradeogre_wow_btc():
|
|||
except:
|
||||
return
|
||||
|
||||
|
||||
def wow_to_usd(wows: float, usd_per_btc: float, btc_per_wow: float):
|
||||
def coin_to_usd(amt: float, usd_per_btc: float, btc_per_coin: float):
|
||||
try:
|
||||
return round(usd_per_btc / (1.0 / (wows * btc_per_wow)), 2)
|
||||
return round(usd_per_btc / (1.0 / (amt * btc_per_coin)), 2)
|
||||
except:
|
||||
pass
|
|
@ -1,34 +1,32 @@
|
|||
from datetime import datetime
|
||||
from flask import session, g
|
||||
|
||||
import settings
|
||||
from wowfunding.bin.utils import Summary
|
||||
from wowfunding.factory import app, db_session
|
||||
from wowfunding.orm.orm import Proposal, User, Comment
|
||||
|
||||
from funding.bin.utils import Summary
|
||||
from funding.factory import app, db_session
|
||||
from funding.orm.orm import Proposal, User, Comment
|
||||
|
||||
@app.context_processor
|
||||
def templating():
|
||||
from flask.ext.login import current_user
|
||||
recent_comments = db_session.query(Comment).filter(Comment.automated == False).order_by(Comment.date_added.desc()).limit(10).all()
|
||||
recent_comments = db_session.query(Comment).filter(Comment.automated == False).order_by(Comment.date_added.desc()).limit(8).all()
|
||||
summary_data = Summary.fetch_stats()
|
||||
newest_users = db_session.query(User).filter(User.admin == False).order_by(User.registered_on.desc()).limit(5).all()
|
||||
return dict(logged_in=current_user.is_authenticated,
|
||||
current_user=current_user,
|
||||
funding_categories=settings.FUNDING_CATEGORIES,
|
||||
funding_statuses=settings.FUNDING_STATUSES,
|
||||
summary_data=summary_data,
|
||||
recent_comments=recent_comments)
|
||||
|
||||
recent_comments=recent_comments,
|
||||
newest_users=newest_users)
|
||||
|
||||
@app.before_request
|
||||
def before_request():
|
||||
pass
|
||||
|
||||
|
||||
@app.after_request
|
||||
def after_request(res):
|
||||
if hasattr(g, 'wowfunding_prices'):
|
||||
delattr(g, 'wowfunding_prices')
|
||||
if hasattr(g, 'funding_prices'):
|
||||
delattr(g, 'funding_prices')
|
||||
res.headers.add('Accept-Ranges', 'bytes')
|
||||
if settings.DEBUG:
|
||||
res.headers['Cache-Control'] = 'no-cache, no-store, must-revalidate'
|
||||
|
@ -37,12 +35,10 @@ def after_request(res):
|
|||
res.headers['Cache-Control'] = 'public, max-age=0'
|
||||
return res
|
||||
|
||||
|
||||
@app.teardown_appcontext
|
||||
def shutdown_session(**kwargs):
|
||||
db_session.remove()
|
||||
|
||||
|
||||
@app.errorhandler(404)
|
||||
def error(err):
|
||||
return 'Error', 404
|
||||
return 'Error', 404
|
|
@ -4,7 +4,7 @@ import redis
|
|||
from flask_session import RedisSessionInterface
|
||||
|
||||
import settings
|
||||
from wowfunding.bin.utils import json_encoder
|
||||
from funding.bin.utils import json_encoder
|
||||
|
||||
|
||||
def redis_args():
|
||||
|
@ -58,4 +58,4 @@ class WowCache:
|
|||
return {}
|
||||
|
||||
def set(self, key: str, data: dict, expiry = 300):
|
||||
self._cache.set(key, json.dumps(data, default=json_encoder), ex=expiry)
|
||||
self._cache.set(key, json.dumps(data, default=json_encoder), ex=expiry)
|
|
@ -17,7 +17,7 @@ def create_app():
|
|||
global cache
|
||||
global bcrypt
|
||||
|
||||
from wowfunding.orm.connect import create_session
|
||||
from funding.orm.connect import create_session
|
||||
db_session = create_session()
|
||||
|
||||
app = Flask(__name__)
|
||||
|
@ -39,18 +39,18 @@ def create_app():
|
|||
|
||||
@login_manager.user_loader
|
||||
def load_user(_id):
|
||||
from wowfunding.orm.orm import User
|
||||
from funding.orm.orm import User
|
||||
return User.query.get(int(_id))
|
||||
|
||||
# session init
|
||||
from wowfunding.cache import JsonRedis, WowCache
|
||||
from funding.cache import JsonRedis, WowCache
|
||||
app.session_interface = JsonRedis(key_prefix=app.config['SESSION_PREFIX'], use_signer=False)
|
||||
cache = WowCache()
|
||||
|
||||
# import routes
|
||||
from wowfunding import routes
|
||||
from wowfunding import api
|
||||
from wowfunding.bin import utils_request
|
||||
from funding import routes
|
||||
from funding import api
|
||||
from funding.bin import utils_request
|
||||
|
||||
app.app_context().push()
|
||||
return app
|
||||
return app
|
|
@ -8,11 +8,11 @@ import settings
|
|||
|
||||
|
||||
def create_session():
|
||||
from wowfunding.orm.orm import base
|
||||
from funding.orm.orm import base
|
||||
engine = sa.create_engine(settings.SQLALCHEMY_DATABASE_URI, echo=False, encoding="latin")
|
||||
session = scoped_session(sessionmaker(autocommit=False,
|
||||
autoflush=False,
|
||||
bind=engine))
|
||||
base.query = session.query_property()
|
||||
base.metadata.create_all(bind=engine)
|
||||
return session
|
||||
return session
|
|
@ -1,5 +1,4 @@
|
|||
from datetime import datetime
|
||||
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm import scoped_session, sessionmaker, relationship
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
|
@ -7,7 +6,6 @@ import settings
|
|||
|
||||
base = declarative_base(name="Model")
|
||||
|
||||
|
||||
class User(base):
|
||||
__tablename__ = "users"
|
||||
id = sa.Column('user_id', sa.Integer, primary_key=True)
|
||||
|
@ -17,11 +15,10 @@ class User(base):
|
|||
registered_on = sa.Column(sa.DateTime)
|
||||
admin = sa.Column(sa.Boolean, default=False)
|
||||
proposals = relationship('Proposal', back_populates="user")
|
||||
|
||||
comments = relationship("Comment", back_populates="user")
|
||||
|
||||
def __init__(self, username, password, email):
|
||||
from wowfunding.factory import bcrypt
|
||||
from funding.factory import bcrypt
|
||||
self.username = username
|
||||
self.password = bcrypt.generate_password_hash(password).decode('utf8')
|
||||
self.email = email
|
||||
|
@ -47,12 +44,12 @@ class User(base):
|
|||
return self.id
|
||||
|
||||
def __repr__(self):
|
||||
return '<User %r>' % self.username
|
||||
return self.username
|
||||
|
||||
@classmethod
|
||||
def add(cls, username, password, email):
|
||||
from wowfunding.factory import db_session
|
||||
from wowfunding.validation import val_username, val_email
|
||||
from funding.factory import db_session
|
||||
from funding.validation import val_username, val_email
|
||||
|
||||
try:
|
||||
# validate incoming username/email
|
||||
|
@ -134,7 +131,7 @@ class Proposal(base):
|
|||
|
||||
@classmethod
|
||||
def find_by_id(cls, pid: int):
|
||||
from wowfunding.factory import db_session
|
||||
from funding.factory import db_session
|
||||
q = cls.query
|
||||
q = q.filter(Proposal.id == pid)
|
||||
result = q.first()
|
||||
|
@ -149,21 +146,21 @@ class Proposal(base):
|
|||
|
||||
@property
|
||||
def funds_target_usd(self):
|
||||
from wowfunding.bin.utils import Summary, wow_to_usd
|
||||
from funding.bin.utils import Summary, coin_to_usd
|
||||
prices = Summary.fetch_prices()
|
||||
if not prices:
|
||||
return
|
||||
return wow_to_usd(wows=self.funds_target, btc_per_wow=prices['wow-btc'], usd_per_btc=prices['btc-usd'])
|
||||
return coin_to_usd(amt=self.funds_target, btc_per_coin=prices['coin-btc'], usd_per_btc=prices['btc-usd'])
|
||||
|
||||
@property
|
||||
def comment_count(self):
|
||||
from wowfunding.factory import db_session
|
||||
from funding.factory import db_session
|
||||
q = db_session.query(sa.func.count(Comment.id))
|
||||
q = q.filter(Comment.proposal_id == self.id)
|
||||
return q.scalar()
|
||||
|
||||
def get_comments(self):
|
||||
from wowfunding.factory import db_session
|
||||
from funding.factory import db_session
|
||||
q = db_session.query(Comment)
|
||||
q = q.filter(Comment.proposal_id == self.id)
|
||||
q = q.filter(Comment.replied_to == None)
|
||||
|
@ -184,46 +181,86 @@ class Proposal(base):
|
|||
def balance(self):
|
||||
"""This property retrieves the current funding status
|
||||
of this proposal. It uses Redis cache to not spam the
|
||||
wownerod too much. Returns a nice dictionary containing
|
||||
daemon too much. Returns a nice dictionary containing
|
||||
all relevant proposal funding info"""
|
||||
from wowfunding.bin.utils import Summary, wow_to_usd
|
||||
from wowfunding.factory import cache, db_session
|
||||
from funding.bin.utils import Summary, coin_to_usd
|
||||
from funding.factory import cache, db_session
|
||||
rtn = {'sum': 0.0, 'txs': [], 'pct': 0.0}
|
||||
|
||||
cache_key = 'wow_balance_pid_%d' % self.id
|
||||
cache_key = 'coin_balance_pid_%d' % self.id
|
||||
data = cache.get(cache_key)
|
||||
if not data:
|
||||
from wowfunding.bin.daemon import WowneroDaemon
|
||||
from funding.bin.daemon import Daemon
|
||||
try:
|
||||
data = WowneroDaemon().get_transfers_in(index=self.id)
|
||||
data = Daemon().get_transfers_in(index=self.id, proposal_id=self.id)
|
||||
if not isinstance(data, dict):
|
||||
print('error; get_transfers; %d' % self.id)
|
||||
print('error; get_transfers_in; %d' % self.id)
|
||||
return rtn
|
||||
cache.set(cache_key, data=data, expiry=300)
|
||||
except:
|
||||
print('error; get_transfers; %d' % self.id)
|
||||
print('error; get_transfers_in; %d' % self.id)
|
||||
return rtn
|
||||
|
||||
prices = Summary.fetch_prices()
|
||||
for tx in data['txs']:
|
||||
if prices:
|
||||
tx['amount_usd'] = wow_to_usd(wows=tx['amount_human'], btc_per_wow=prices['wow-btc'], usd_per_btc=prices['btc-usd'])
|
||||
tx['amount_usd'] = coin_to_usd(amt=tx['amount_human'], btc_per_coin=prices['coin-btc'], usd_per_btc=prices['btc-usd'])
|
||||
tx['datetime'] = datetime.fromtimestamp(tx['timestamp'])
|
||||
|
||||
if data.get('sum', 0.0):
|
||||
data['pct'] = 100 / float(self.funds_target / data.get('sum', 0.0))
|
||||
data['remaining'] = data['sum'] - self.funds_withdrew
|
||||
data['available'] = data['sum']
|
||||
else:
|
||||
data['pct'] = 0.0
|
||||
data['remaining'] = 0.0
|
||||
data['available'] = 0.0
|
||||
|
||||
if data['pct'] != self.funds_progress:
|
||||
self.funds_progress = data['pct']
|
||||
db_session.commit()
|
||||
db_session.flush()
|
||||
|
||||
if data['remaining']:
|
||||
data['remaining_pct'] = 100 / float(data['sum'] / data['remaining'])
|
||||
if data['available']:
|
||||
data['remaining_pct'] = 100 / float(data['sum'] / data['available'])
|
||||
else:
|
||||
data['remaining_pct'] = 0.0
|
||||
|
||||
return data
|
||||
|
||||
@property
|
||||
def spends(self):
|
||||
from funding.bin.utils import Summary, coin_to_usd
|
||||
from funding.factory import cache, db_session
|
||||
rtn = {'sum': 0.0, 'txs': [], 'pct': 0.0}
|
||||
|
||||
cache_key = 'coin_spends_pid_%d' % self.id
|
||||
data = cache.get(cache_key)
|
||||
if not data:
|
||||
from funding.bin.daemon import Daemon
|
||||
try:
|
||||
data = Daemon().get_transfers_out(index=self.id, proposal_id=self.id)
|
||||
if not isinstance(data, dict):
|
||||
print('error; get_transfers_out; %d' % self.id)
|
||||
return rtn
|
||||
cache.set(cache_key, data=data, expiry=300)
|
||||
except:
|
||||
print('error; get_transfers_out; %d' % self.id)
|
||||
return rtn
|
||||
|
||||
prices = Summary.fetch_prices()
|
||||
for tx in data['txs']:
|
||||
if prices:
|
||||
tx['amount_usd'] = coin_to_usd(amt=tx['amount_human'], btc_per_coin=prices['coin-btc'], usd_per_btc=prices['btc-usd'])
|
||||
tx['datetime'] = datetime.fromtimestamp(tx['timestamp'])
|
||||
|
||||
if data.get('sum', 0.0):
|
||||
data['pct'] = 100 / float(self.funds_target / data.get('sum', 0.0))
|
||||
data['spent'] = data['sum']
|
||||
else:
|
||||
data['pct'] = 0.0
|
||||
data['spent'] = 0.0
|
||||
|
||||
if data['spent']:
|
||||
data['remaining_pct'] = 100 / float(data['sum'] / data['spent'])
|
||||
else:
|
||||
data['remaining_pct'] = 0.0
|
||||
|
||||
|
@ -231,13 +268,13 @@ class Proposal(base):
|
|||
|
||||
@staticmethod
|
||||
def generate_donation_addr(cls):
|
||||
from wowfunding.factory import db_session
|
||||
from wowfunding.bin.daemon import WowneroDaemon
|
||||
from funding.factory import db_session
|
||||
from funding.bin.daemon import Daemon
|
||||
if cls.addr_donation:
|
||||
return cls.addr_donation
|
||||
|
||||
try:
|
||||
addr_donation = WowneroDaemon().get_address(index=cls.id)
|
||||
addr_donation = Daemon().get_address(index=cls.id, proposal_id=cls.id)
|
||||
if not isinstance(addr_donation, dict):
|
||||
raise Exception('get_address, needs dict; %d' % cls.id)
|
||||
except Exception as ex:
|
||||
|
@ -250,9 +287,19 @@ class Proposal(base):
|
|||
db_session.flush()
|
||||
return addr_donation['address']
|
||||
|
||||
@staticmethod
|
||||
def generate_proposal_subaccount(pid):
|
||||
from funding.bin.daemon import Daemon
|
||||
|
||||
try:
|
||||
Daemon().create_account(pid)
|
||||
except Exception as ex:
|
||||
print('error: %s' % str(ex))
|
||||
return
|
||||
|
||||
@classmethod
|
||||
def find_by_args(cls, status: int = None, cat: str = None, limit: int = 20, offset=0):
|
||||
from wowfunding.factory import db_session
|
||||
from funding.factory import db_session
|
||||
if isinstance(status, int) and status not in settings.FUNDING_STATUSES.keys():
|
||||
raise NotImplementedError('invalid status')
|
||||
if isinstance(cat, str) and cat not in settings.FUNDING_CATEGORIES:
|
||||
|
@ -298,7 +345,7 @@ class Payout(base):
|
|||
from flask.ext.login import current_user
|
||||
if not current_user.admin:
|
||||
raise Exception("user must be admin to add a payout")
|
||||
from wowfunding.factory import db_session
|
||||
from funding.factory import db_session
|
||||
|
||||
try:
|
||||
payout = Payout(propsal_id=proposal_id, amount=amount, to_address=to_address)
|
||||
|
@ -339,17 +386,17 @@ class Comment(base):
|
|||
|
||||
@property
|
||||
def ago(self):
|
||||
from wowfunding.bin.utils_time import TimeMagic
|
||||
from funding.bin.utils_time import TimeMagic
|
||||
return TimeMagic().ago(self.date_added)
|
||||
|
||||
@staticmethod
|
||||
def find_by_id(cid: int):
|
||||
from wowfunding.factory import db_session
|
||||
from funding.factory import db_session
|
||||
return db_session.query(Comment).filter(Comment.id == cid).first()
|
||||
|
||||
@staticmethod
|
||||
def remove(cid: int):
|
||||
from wowfunding.factory import db_session
|
||||
from funding.factory import db_session
|
||||
from flask.ext.login import current_user
|
||||
if current_user.id != user_id and not current_user.admin:
|
||||
raise Exception("no rights to remove this comment")
|
||||
|
@ -364,7 +411,7 @@ class Comment(base):
|
|||
|
||||
@staticmethod
|
||||
def lock(cid: int):
|
||||
from wowfunding.factory import db_session
|
||||
from funding.factory import db_session
|
||||
from flask.ext.login import current_user
|
||||
if not current_user.admin:
|
||||
raise Exception("admin required")
|
||||
|
@ -383,7 +430,7 @@ class Comment(base):
|
|||
@classmethod
|
||||
def add_comment(cls, pid: int, user_id: int, message: str, cid: int = None, message_id: int = None, automated=False):
|
||||
from flask.ext.login import current_user
|
||||
from wowfunding.factory import db_session
|
||||
from funding.factory import db_session
|
||||
if not message:
|
||||
raise Exception("empty message")
|
||||
|
||||
|
@ -420,4 +467,4 @@ class Comment(base):
|
|||
except Exception as ex:
|
||||
db_session.rollback()
|
||||
raise Exception(str(ex))
|
||||
return comment
|
||||
return comment
|
|
@ -4,8 +4,8 @@ from flask.ext.login import login_user , logout_user , current_user, login_requi
|
|||
from flask_yoloapi import endpoint, parameter
|
||||
|
||||
import settings
|
||||
from wowfunding.factory import app, db_session
|
||||
from wowfunding.orm.orm import Proposal, User, Comment
|
||||
from funding.factory import app, db_session
|
||||
from funding.orm.orm import Proposal, User, Comment
|
||||
|
||||
|
||||
@app.route('/')
|
||||
|
@ -61,7 +61,7 @@ def proposal_comment(pid, text, cid):
|
|||
|
||||
@app.route('/proposal/<int:pid>/comment/<int:cid>')
|
||||
def propsal_comment_reply(cid, pid):
|
||||
from wowfunding.orm.orm import Comment
|
||||
from funding.orm.orm import Comment
|
||||
c = Comment.find_by_id(cid)
|
||||
if not c or c.replied_to:
|
||||
return redirect(url_for('proposal', pid=pid))
|
||||
|
@ -88,7 +88,7 @@ def proposal(pid):
|
|||
parameter('title', type=str, required=True, location='json'),
|
||||
parameter('content', type=str, required=True, location='json'),
|
||||
parameter('pid', type=int, required=False, location='json'),
|
||||
parameter('funds_target', type=float, required=True, location='json'),
|
||||
parameter('funds_target', type=str, required=True, location='json'),
|
||||
parameter('addr_receiving', type=str, required=True, location='json'),
|
||||
parameter('category', type=str, required=True, location='json'),
|
||||
parameter('status', type=int, required=True, location='json', default=1)
|
||||
|
@ -99,8 +99,9 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category
|
|||
if current_user.is_anonymous:
|
||||
return make_response(jsonify('err'), 500)
|
||||
|
||||
if len(title) <= 10:
|
||||
if len(title) <= 8:
|
||||
return make_response(jsonify('title too short'), 500)
|
||||
|
||||
if len(content) <= 20:
|
||||
return make_response(jsonify('content too short'), 500)
|
||||
|
||||
|
@ -114,12 +115,14 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category
|
|||
return make_response(jsonify('no rights to change status'), 500)
|
||||
|
||||
try:
|
||||
from wowfunding.bin.anti_xss import such_xss
|
||||
from funding.bin.anti_xss import such_xss
|
||||
content_escaped = such_xss(content)
|
||||
html = markdown2.markdown(content_escaped, safe_mode=True)
|
||||
except Exception as ex:
|
||||
return make_response(jsonify('markdown error'), 500)
|
||||
|
||||
|
||||
|
||||
if pid:
|
||||
p = Proposal.find_by_id(pid=pid)
|
||||
if not p:
|
||||
|
@ -146,13 +149,22 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category
|
|||
|
||||
p.status = status
|
||||
p.last_edited = datetime.now()
|
||||
|
||||
|
||||
else:
|
||||
if funds_target <= 1:
|
||||
return make_response(jsonify('proposal asking less than 1 error :)'), 500)
|
||||
try:
|
||||
funds_target = float(funds_target)
|
||||
except Exception as ex:
|
||||
return make_response(jsonify('letters detected'),500)
|
||||
if funds_target < 1:
|
||||
return make_response(jsonify('Proposal asking less than 1 error :)'), 500)
|
||||
if len(addr_receiving) != 97:
|
||||
return make_response(jsonify('faulty addr_receiving address, should be of length 72'), 500)
|
||||
return make_response(jsonify('Faulty address, should be of length 72'), 500)
|
||||
|
||||
p = Proposal(headline=title, content=content, category='misc', user=current_user)
|
||||
proposalID = current_user
|
||||
addr_donation = Proposal.generate_proposal_subaccount(proposalID)
|
||||
p.addr_donation = addr_donation
|
||||
p.html = html
|
||||
p.last_edited = datetime.now()
|
||||
p.funds_target = funds_target
|
||||
|
@ -160,12 +172,14 @@ def proposal_api_add(title, content, pid, funds_target, addr_receiving, category
|
|||
p.category = category
|
||||
p.status = status
|
||||
db_session.add(p)
|
||||
|
||||
|
||||
|
||||
db_session.commit()
|
||||
db_session.flush()
|
||||
|
||||
# reset cached statistics
|
||||
from wowfunding.bin.utils import Summary
|
||||
from funding.bin.utils import Summary
|
||||
Summary.fetch_stats(purge=True)
|
||||
|
||||
return make_response(jsonify({'url': url_for('proposal', pid=p.id)}))
|
||||
|
@ -254,7 +268,7 @@ def login(username, password):
|
|||
if request.method == 'GET':
|
||||
return make_response(render_template('login.html'))
|
||||
|
||||
from wowfunding.factory import bcrypt
|
||||
from funding.factory import bcrypt
|
||||
user = User.query.filter_by(username=username).first()
|
||||
if user is None or not bcrypt.check_password_hash(user.password, password):
|
||||
flash('Username or Password is invalid', 'error')
|
||||
|
@ -277,4 +291,4 @@ def logout():
|
|||
|
||||
@app.route('/static/<path:path>')
|
||||
def static_route(path):
|
||||
return send_from_directory('static', path)
|
||||
return send_from_directory('static', path)
|
|
@ -7,23 +7,27 @@ body {
|
|||
padding-top: 56px;
|
||||
}
|
||||
}
|
||||
|
||||
.center {
|
||||
text-align: center;
|
||||
margin: 0 auto 0 auto;
|
||||
}
|
||||
.navbar {
|
||||
padding: .1rem 1rem;
|
||||
}
|
||||
|
||||
.navbar .navbar-brand img#logo{
|
||||
width:28px;
|
||||
height:28px;
|
||||
float:left;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.navbar .navbar-brand img#text{
|
||||
width:235px;
|
||||
height:13px;
|
||||
float:left;
|
||||
margin-top: 8px;
|
||||
margin-left:8px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.navbar-nav .nav-link{
|
||||
|
@ -55,7 +59,7 @@ body {
|
|||
margin-top: 6px;
|
||||
font-weight: bold;
|
||||
color: #ff0000;
|
||||
font-size: 22px;
|
||||
font-size: 18px;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
|
@ -268,7 +272,12 @@ a {
|
|||
background-clip: padding-box;
|
||||
border: 1px solid #008926;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.form-group:last-of-type {
|
||||
margin-bottom:2.5rem;
|
||||
}
|
||||
/*fuku chrome*/
|
||||
input {
|
||||
outline:none;
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
</p>
|
||||
|
||||
<b>Example:</b>
|
||||
<pre>curl -vvX GET 'https://funding.wownero.com/api/1/convert/wow-usd?wow=1000'</pre>
|
||||
<pre>curl -vvX GET 'https://funding.wownero.com/api/1/convert/wow-usd?amount=1000'</pre>
|
||||
|
||||
<b>Response:</b>
|
||||
<pre>{"usd": 6.7}</pre>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
padding-left:4px;
|
||||
}
|
||||
</style>
|
||||
<div class="col-lg-12" id="xox">
|
||||
<div class="col-lg-8" id="xox">
|
||||
<h3>Secure login </h3>
|
||||
<img src="/static/nasa.png"/>
|
||||
<img src="/static/cyber.png"/>
|
||||
|
@ -46,9 +46,10 @@
|
|||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-lg-12">
|
||||
<div class="col-lg-5 center">
|
||||
<a href="/register">Or register here</a>
|
||||
</div>
|
||||
{% include 'sidebar.html' %}
|
||||
</div>
|
||||
</div>
|
||||
<br>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<th>Username</th>
|
||||
<th id="date">Date</th>
|
||||
{% if _proposals and _proposals[0].status >= 2 %}
|
||||
<th style="text-align: right;">Funding</th>
|
||||
<th style="text-align: right;">Funded</th>
|
||||
{% else %}
|
||||
<th></th>
|
||||
{% endif %}
|
||||
|
@ -41,4 +41,4 @@
|
|||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endmacro %}
|
||||
{% endmacro %}
|
|
@ -11,11 +11,6 @@
|
|||
<!-- Title -->
|
||||
<h1 class="mt-4" style="margin-bottom: 0.1rem;">
|
||||
{{ proposal.headline }}
|
||||
|
||||
<div id="point-wow-left">
|
||||
<img src="/static/point-left.png" style="margin-left: 10px;width: 60px;">
|
||||
<span style="color: #fc4dff;font-size: 16px;font-style: italic;font-weight: bold;margin-left: 6px;">wow</span>
|
||||
</div>
|
||||
</h1>
|
||||
|
||||
<p>
|
||||
|
@ -78,8 +73,8 @@
|
|||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Progress</td>
|
||||
<td><span class="badge">{{proposal.balance['pct'] |round}} %</span></td>
|
||||
<td>Funded</td>
|
||||
<td><span class="badge">{{proposal.balance['pct'] | round or 0}} %</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@ -100,17 +95,43 @@
|
|||
<hr>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
{{proposal.balance['remaining'] or 0}} WOW available
|
||||
|
||||
{{proposal.balance['available']|round(3) or 0 }} WOW Raised
|
||||
{% if (proposal.funds_target-proposal.balance['available']|float|round(3)) > 0 %}
|
||||
({{ (proposal.funds_target-proposal.balance['available']|float|round(3)|int) }} WOW until goal)
|
||||
{% else %}
|
||||
({{ (proposal.balance['available']-proposal.funds_target|float|round(3)|int) }} WOW past goal!)
|
||||
{% endif %}
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-warning progress-bar" style="width: {{proposal.balance['pct']}}%;">
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
<br/>
|
||||
|
||||
<br/>
|
||||
<div class="col-lg-8">
|
||||
{{proposal.spends['spent']|round(3) or 0}} WOW Paid out
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-warning progress-bar" style="width: {{proposal.spends['spent_remaining_pct']}}%;">
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
<div class="col-lg-8">
|
||||
{{(proposal.balance['available']-proposal.spends['spent']) |round(3) or 0}} WOW Available to Payout
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-warning progress-bar" style="width: {{proposal.balance['remaining_pct']}}%;">
|
||||
</div>
|
||||
</div>
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
</div>
|
||||
<div class="row" style="margin-top:16px;">
|
||||
<div class="col-lg-12">
|
||||
Donatation address:
|
||||
Donation address:
|
||||
<pre class="proposal_address">{% if proposal.addr_donation %}{{ proposal.addr_donation }}{% else %}<small>None generated yet</small>{% endif %}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -122,7 +143,6 @@
|
|||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="alert alert-danger">
|
||||
<img src="/static/doge_head.png" style="width: 64px;margin-right: 8px;">
|
||||
This proposal is disabled.
|
||||
</div>
|
||||
</div>
|
||||
|
@ -159,7 +179,7 @@
|
|||
<br>
|
||||
<a target="_blank" href="https://explore.wownero.com/tx/{{tx['txid']}}">{{tx['txid'][:32]}}...</a>
|
||||
<span style="float:right;color:#008926;font-weight:bold;">
|
||||
+ {{tx['amount_human']|round(2)}} WOW
|
||||
+ {{tx['amount_human']|round(3)}} WOW
|
||||
{% if 'amount_usd' in tx %}
|
||||
<small style="color: black">
|
||||
➞ $ {{tx['amount_usd']}}
|
||||
|
@ -176,6 +196,37 @@
|
|||
<!-- /.row -->
|
||||
{% endif %}
|
||||
|
||||
|
||||
{% if proposal.spends['txs'] %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="card my-6" id="incoming_txs">
|
||||
<h5 class="card-header">Outgoing transactions <small>({{proposal.spends['txs']|length}})</small></h5>
|
||||
<div class="card-body">
|
||||
<ul class="list-group">
|
||||
{% for tx in proposal.spends['txs'] %}
|
||||
<li class="list-group-item">
|
||||
{{tx['datetime'].strftime('%Y-%m-%d %H:%M')}}
|
||||
<span style="float:right"><b>Blockheight</b>: {{tx['height']}}</span>
|
||||
<br>
|
||||
<a target="_blank" href="https://explore.wownero.com/tx/{{tx['txid']}}">{{tx['txid'][:32]}}...</a>
|
||||
<span style="float:right;color:#890000;font-weight:bold;">
|
||||
- {{tx['amount_human']|round(3)}} WOW
|
||||
{% if 'amount_usd' in tx %}
|
||||
<small style="color: black">
|
||||
➞ $ {{tx['amount_usd']}}
|
||||
</small>
|
||||
{% endif %}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
@ -242,4 +293,4 @@
|
|||
</script>
|
||||
|
||||
<!-- /.container -->
|
||||
{% endblock %}
|
||||
{% endblock %}
|
|
@ -71,6 +71,21 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Side Widget -->
|
||||
<div class="card my-4">
|
||||
<h5 class="card-header">Newest Users</h5>
|
||||
<div class="card-body">
|
||||
<ul class="b">
|
||||
|
||||
{% for user in newest_users %}
|
||||
<li>
|
||||
<a href="/user/{{ user.username }}"> {{ user.username }} </a>
|
||||
</li>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</li>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
var search_input = document.getElementById("search_input");
|
||||
|
||||
|
|
|
@ -4,35 +4,62 @@
|
|||
{% if user %}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="col-lg-12">
|
||||
<span class="form-text text-muted" style="margin-top: -2px;">
|
||||
Details for '{{user.username}}'
|
||||
Proposals made by '{{user.username}}'
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-8">
|
||||
<div class="col-lg-12">
|
||||
{% if user.proposals %}
|
||||
<table class="table table-proposal table-hover" style="margin-bottom:6px;">
|
||||
<tbody>
|
||||
{% for p in user.proposals %}
|
||||
<th>Proposal</th>
|
||||
<th>Category</th>
|
||||
<th>Target Amount</th>
|
||||
<th>Amount Raised</th>
|
||||
<th>Date</th>
|
||||
{% for p in user.proposals | sort(attribute='date_added', reverse=True) %}
|
||||
<tr>
|
||||
<td><b><a href="/proposal/{{ p.id }}">{{ p.headline }}</a></b></td>
|
||||
<td><a href="/user/{{ p.user.username }}">{{ p.user.username }}</a></td>
|
||||
<td><b><a href="/proposal/{{ p.id }}">{{ p.headline | truncate(42)}}</a></b></td>
|
||||
<td><a href="/proposals?cat={{ p.category }}">{{ p.category |capitalize}}</a></td>
|
||||
<td>{{p.funds_target}}</td>
|
||||
<td>{{p.funds_progress | round(3, 'floor')}}%</td>
|
||||
<td>{{ p.date_added.strftime('%Y-%m-%d') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
This user did not submit any proposals yet.
|
||||
This user has not submitted any proposals yet.
|
||||
{% endif %}
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
Comments made by {{user.username}}
|
||||
{% if user.comments %}
|
||||
<table class="table table-proposal table-hover" style="margin-bottom:6px;">
|
||||
<tbody>
|
||||
<th>Comment</th>
|
||||
<th>Proposal</th>
|
||||
<th>Date</th>
|
||||
{% for y in user.comments | sort(attribute='date_added', reverse=True) %}
|
||||
<tr>
|
||||
<td><b><a href="/proposal/{{y.proposal.id}}#comment-{{ y.id }}">{{ y.message | truncate(32)}}</a></b></td>
|
||||
<td><a href="/proposal/{{y.proposal.id}}">#{{y.proposal.id }} {{ y.proposal.headline | truncate(32) }}</a></td>
|
||||
<td>{{ y.date_added.strftime('%Y-%m-%d') }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
This user has not submitted any proposals yet.
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% include 'sidebar.html' %}
|
||||
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
</div>
|
||||
|
@ -40,4 +67,4 @@
|
|||
No user found by that name.
|
||||
{% endif %}
|
||||
<!-- /.container -->
|
||||
{% endblock %}
|
||||
{% endblock %}
|
|
@ -1,4 +1,4 @@
|
|||
from wowfunding.factory import create_app
|
||||
from funding.factory import create_app
|
||||
import settings
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
|
|
@ -3,30 +3,40 @@ import socket
|
|||
import collections
|
||||
import os
|
||||
|
||||
|
||||
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
SECRET = 'changeme'
|
||||
SECRET = ''
|
||||
DEBUG = True
|
||||
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI', 'postgresql://postgres:@localhost/ffs')
|
||||
COINCODE = ''
|
||||
PSQL_USER = ''
|
||||
PSQL_PASS = ''
|
||||
PSQL_DB = ''
|
||||
|
||||
SESSION_COOKIE_NAME = os.environ.get('WOW_SESSION_COOKIE_NAME', 'wow_id')
|
||||
SESSION_PREFIX = os.environ.get('WOW_SESSION_PREFIX', 'session:')
|
||||
SQLALCHEMY_DATABASE_URI = os.environ.get('SQLALCHEMY_DATABASE_URI', 'postgresql://{user}:{pw}@localhost/{db}').format(user=PSQL_USER, pw=PSQL_PASS, db=PSQL_DB)
|
||||
|
||||
REDIS_HOST = os.environ.get('WOW_REDIS_HOST', '127.0.0.1')
|
||||
REDIS_PORT = int(os.environ.get('WOW_REDIS_PORT', 6379))
|
||||
REDIS_PASSWD = os.environ.get('WOW_REDIS_PASSWD', None)
|
||||
SESSION_COOKIE_NAME = os.environ.get('{coincode}_SESSION_COOKIE_NAME', '{coincode}_id').format(coincode=COINCODE)
|
||||
SESSION_PREFIX = os.environ.get('{coincode}_SESSION_PREFIX', 'session:').format(coincode=COINCODE)
|
||||
|
||||
BIND_HOST = os.environ.get("WOW_BIND_HOST", "127.0.0.1")
|
||||
REDIS_HOST = os.environ.get('REDIS_HOST', '127.0.0.1')
|
||||
REDIS_PORT = int(os.environ.get('REDIS_PORT', 6379))
|
||||
REDIS_PASSWD = os.environ.get('REDIS_PASSWD', None)
|
||||
|
||||
BIND_HOST = os.environ.get("BIND_HOST", "0.0.0.0")
|
||||
if not BIND_HOST:
|
||||
raise Exception("WOW_BIND_HOST missing")
|
||||
BIND_PORT = os.environ.get("WOW_BIND_PORT", 5004)
|
||||
raise Exception("BIND_HOST missing")
|
||||
BIND_PORT = os.environ.get("BIND_PORT", 5004)
|
||||
if not BIND_PORT:
|
||||
raise Exception("WOW_BIND_PORT missing")
|
||||
raise Exception("BIND_PORT missing")
|
||||
|
||||
HOSTNAME = os.environ.get("WOW_HOSTNAME", socket.gethostname())
|
||||
HOSTNAME = os.environ.get("{coincode}_HOSTNAME", socket.gethostname()).format(coincode=COINCODE)
|
||||
|
||||
RPC_LOCATION = "http://127.0.0.1:45678/json_rpc"
|
||||
# If using a local RPC, no need for --rpc-login credentials unless you're binding wallet-rpc to 0.0.0.0. If you are, you're bad.
|
||||
# elif, remote wallet-rpc, enable --rpc-login and enter credentials below.
|
||||
RPC_HOST = '127.0.0.1'
|
||||
RPC_PORT = '11182'
|
||||
RPC_LOCATION = "http://{host}:{rpc_port}/json_rpc".format(host=RPC_HOST, rpc_port=RPC_PORT)
|
||||
RPC_USERNAME = ""
|
||||
RPC_PASSWORD = ""
|
||||
|
||||
FUNDING_CATEGORIES = [
|
||||
'wallets',
|
||||
|
@ -52,7 +62,7 @@ What problem(s) are you trying to solve?
|
|||
|
||||
#### How much?
|
||||
|
||||
What is the total cost in WOW? List expenses per item. Total hours of work and per hour rate. What exchange rates are you using?
|
||||
What is the total cost in {coincode}? List expenses per item. Total hours of work and per hour rate. What exchange rates are you using?
|
||||
|
||||
#### What?
|
||||
|
||||
|
@ -69,4 +79,4 @@ What will be delivered? What goals will be reached?
|
|||
#### Why you?
|
||||
|
||||
What skills and experience do you have?
|
||||
""".strip()
|
||||
""".strip().format(coincode=COINCODE)
|
Loading…
Reference in a new issue