Skip to content

Commit

Permalink
Spark address support
Browse files Browse the repository at this point in the history
  • Loading branch information
levoncrypto committed Oct 12, 2024
1 parent 579a7ec commit ec7e2ab
Show file tree
Hide file tree
Showing 5 changed files with 168 additions and 13 deletions.
161 changes: 153 additions & 8 deletions funding/factory.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import os, re, json, logging, hashlib, asyncio
from typing import List, Set, Optional
from typing import List, Set, Optional, Union
from datetime import datetime

import json
import aiohttp
import timeago
from quart import jsonify
from peewee import PostgresqlDatabase, SqliteDatabase, ProgrammingError
from playhouse.shortcuts import ReconnectMixin
from aiocryptocurrency.coins import Coin, SUPPORTED_COINS
from aiocryptocurrency import TransactionSet, Transaction

from aiocryptocurrency.coins.nero import Wownero, Monero
from aiocryptocurrency.coins.firo import Firo
from quart import Quart, render_template, session, request, g
from quart_schema import RequestSchemaValidationError
from quart_session import Session
Expand All @@ -18,12 +21,157 @@
from funding.utils.rates import Rates
import settings

class Firo(Coin):
def __init__(self):
super(Firo, self).__init__()
self.host = '127.0.0.1'
self.port = 8888
self.basic_auth: Optional[tuple[str]] = None
self.url = None

async def send(self, address: str, amount: float) -> str:
"""returns txid"""
if amount <= 0:
raise Exception("amount cannot be zero or less")

data = {
"method": "sendtoaddress",
"params": [address, amount]
}

blob = await self._make_request(data=data)
return blob['result']

async def mintspark(self, sparkAddress: str, amount: float, memo: str = "") -> str:
"""returns txid"""
if amount <= 0:
raise Exception("amount cannot be zero or less")

params = {
sparkAddress: {
"amount": amount,
"memo": memo
}
}

data = {
"method": "mintspark",
"params": [params]
}

blob = await self._make_request(data=data)
return blob['result']


async def create_address(self) -> dict:
"""Returns both a transparent address and a Spark address."""

address_data = {
"method": "getnewaddress"
}
address_blob = await self._make_request(data=address_data)
address = address_blob['result']

if address is None or not isinstance(address, str):
raise Exception("Invalid standard address result")

return {
"address": address,
}

async def tx_details(self, txid: str):
if not isinstance(txid, str) or not txid:
raise Exception("bad address")

data = {
"method": "gettransaction",
"params": [txid]
}

blob = await self._make_request(data=data)
return blob['result']

async def list_txs(self, address: str = None, payment_id: str = None, minimum_confirmations: int = 3) -> Optional[TransactionSet]:
txset = TransactionSet()
if not isinstance(address, str) or not address:
raise Exception("bad address")

results = await self._make_request(data={
"method": "listreceivedbyaddress",
"params": [minimum_confirmations]
})

if not isinstance(results.get('result'), list):
return txset

try:
result = [r for r in results['result'] if r['address'] == address][0]
except Exception as ex:
return txset

for txid in result.get('txids', []):
# fetch tx details
tx = await self.tx_details(txid)

# fetch blockheight
tx['blockheight'] = await self.blockheight(tx['blockhash'])
date = datetime.fromtimestamp(tx['blocktime'])

txset.add(Transaction(amount=tx['amount'],
txid=tx['txid'],
date=date,
blockheight=tx['blockheight'],
direction='in',
confirmations=tx['confirmations']))

return txset

async def blockheight(self, blockhash: str) -> int:
"""blockhash -> blockheight"""
if not isinstance(blockhash, str) or not blockhash:
raise Exception("bad address")

data = {
"method": "getblock",
"params": [blockhash]
}
blob = await self._make_request(data=data)

height = blob['result'].get('height', 0)
return height

async def _generate_url(self) -> None:
self.url = f'http://{self.host}:{self.port}/'

async def _make_request(self, data: dict = None) -> dict:
await self._generate_url()

opts = {
"headers": {
"User-Agent": self.user_agent
}
}

if self.basic_auth:
opts['auth'] = await self._make_basic_auth()

async with aiohttp.ClientSession(**opts) as session:
async with session.post(self.url, json=data) as resp:
if resp.status == 401:
raise Exception("Unauthorized")
blob = await resp.json()
if 'result' not in blob:
if blob:
blob = json.dumps(blob, indent=4, sort_keys=True)
raise Exception(f"Invalid response: {blob}")
return blob

cache = None
peewee = None
rates = Rates()
app: Optional[Quart] = None
openid: Optional[OpenID] = None
crypto_provider: Optional[Firo] = None
crypto_provider: Optional[Firo] = Firo()
coin: Optional[dict] = None
discourse = Discourse()
proposal_task = None
Expand All @@ -47,7 +195,6 @@ def __init__(self, *args, **kwargs):
port=settings.DB_PORT
)


async def _setup_postgres(app: Quart):
import peewee
import funding.models.database
Expand Down Expand Up @@ -115,7 +262,6 @@ async def _setup_crypto(app: Quart):
if settings.COIN_RPC_AUTH:
crypto_provider.basic_auth = settings.COIN_RPC_AUTH


async def _setup_cache(app: Quart):
global cache
app.config['SESSION_TYPE'] = 'redis'
Expand Down Expand Up @@ -148,7 +294,6 @@ async def page_not_found(e):
def create_app():
global app
app = Quart(__name__)

app.logger.setLevel(logging.INFO)
app.secret_key = settings.APP_SECRET

Expand Down Expand Up @@ -197,7 +342,7 @@ def template_variables():

@app.errorhandler(RequestSchemaValidationError)
async def handle_request_validation_error(error):
return {"errors": error.validation_error.json()}, 400
return jsonify({"errors": str(error)}), 400

@app.before_serving
async def startup():
Expand Down
7 changes: 7 additions & 0 deletions funding/models/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,13 @@ async def upsert(cls, data: ProposalUpsert):
if proposal._is_new:
proposal.slug = proposal.generate_slug(data.title)

blob = await crypto_provider.check_address(data.addr_receiving)
if data.addr_receiving[0] == 'a' and not blob['isvalid']:
raise Exception("Invalid Address")

elif data.addr_receiving[0] == 's' and not blob['isvalidSpark']:
raise Exception("Invalid Spark Address")

proposal.set_addr_receiving(data.addr_receiving, user)
await proposal.set_category(data.category, user)
await proposal.set_status(data.status, user)
Expand Down
1 change: 0 additions & 1 deletion funding/proposals/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

bp_proposals_api = Blueprint('bp_proposals_api', __name__, url_prefix='/api/proposals')


@bp_proposals_api.post("/upsert")
@validate_request(ProposalUpsert, source=DataSource.JSON)
@login_required
Expand Down
2 changes: 1 addition & 1 deletion funding/proposals/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ class ProposalUpsert(BaseModel):
category: ProposalCategory
status: Optional[ProposalStatus]
discourse_topic_id: Optional[int]
addr_receiving: constr(min_length=8, max_length=128)
addr_receiving: constr(min_length=8, max_length=255)

class Config:
use_enum_values = False
10 changes: 7 additions & 3 deletions funding/proposals/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,6 @@ async def funds(slug: str = None):

return await render_template("proposals/funds.html", proposal=proposal, crumbs=crumbs)


@bp_proposals.post("/<path:slug>/funds/transfer")
@validate_request(ProposalFundsTransfer, source=DataSource.FORM)
@admin_required
Expand Down Expand Up @@ -103,9 +102,14 @@ async def funds_transfer(data: ProposalFundsTransfer, slug: str = None):

destination = data.destination.strip()
try:
txid = await crypto_provider.send(
address=destination,
if (destination[0] == 's'):
txid = await crypto_provider.mintspark(
sparkAddress=destination,
amount=amount)
else:
txid = await crypto_provider.send(
address=destination,
amount=amount)
except Exception as ex:
return f"Error sending to '{destination}': {ex}"

Expand Down

0 comments on commit ec7e2ab

Please sign in to comment.