11import os , re , json , logging , hashlib , asyncio
2- from typing import List , Set , Optional
2+ from typing import List , Set , Optional , Union
33from datetime import datetime
4-
4+ import json
5+ import aiohttp
56import timeago
7+ from quart import jsonify
68from peewee import PostgresqlDatabase , SqliteDatabase , ProgrammingError
79from playhouse .shortcuts import ReconnectMixin
810from aiocryptocurrency .coins import Coin , SUPPORTED_COINS
11+ from aiocryptocurrency import TransactionSet , Transaction
12+
913from aiocryptocurrency .coins .nero import Wownero , Monero
10- from aiocryptocurrency .coins .firo import Firo
1114from quart import Quart , render_template , session , request , g
1215from quart_schema import RequestSchemaValidationError
1316from quart_session import Session
1821from funding .utils .rates import Rates
1922import settings
2023
24+ class Firo (Coin ):
25+ def __init__ (self ):
26+ super (Firo , self ).__init__ ()
27+ self .host = '127.0.0.1'
28+ self .port = 8888
29+ self .basic_auth : Optional [tuple [str ]] = None
30+ self .url = None
31+
32+ async def send (self , address : str , amount : float ) -> str :
33+ """returns txid"""
34+ if amount <= 0 :
35+ raise Exception ("amount cannot be zero or less" )
36+
37+ data = {
38+ "method" : "sendtoaddress" ,
39+ "params" : [address , amount ]
40+ }
41+
42+ blob = await self ._make_request (data = data )
43+ return blob ['result' ]
44+
45+ async def mintspark (self , sparkAddress : str , amount : float , memo : str = "" ) -> str :
46+ """returns txid"""
47+ if amount <= 0 :
48+ raise Exception ("amount cannot be zero or less" )
49+
50+ params = {
51+ sparkAddress : {
52+ "amount" : amount ,
53+ "memo" : memo
54+ }
55+ }
56+
57+ data = {
58+ "method" : "mintspark" ,
59+ "params" : [params ]
60+ }
61+
62+ blob = await self ._make_request (data = data )
63+ return blob ['result' ]
64+
65+
66+ async def create_address (self ) -> dict :
67+ """Returns both a transparent address and a Spark address."""
68+
69+ address_data = {
70+ "method" : "getnewaddress"
71+ }
72+ address_blob = await self ._make_request (data = address_data )
73+ address = address_blob ['result' ]
74+
75+ if address is None or not isinstance (address , str ):
76+ raise Exception ("Invalid standard address result" )
77+
78+ return {
79+ "address" : address ,
80+ }
81+
82+ async def tx_details (self , txid : str ):
83+ if not isinstance (txid , str ) or not txid :
84+ raise Exception ("bad address" )
85+
86+ data = {
87+ "method" : "gettransaction" ,
88+ "params" : [txid ]
89+ }
90+
91+ blob = await self ._make_request (data = data )
92+ return blob ['result' ]
93+
94+ async def list_txs (self , address : str = None , payment_id : str = None , minimum_confirmations : int = 3 ) -> Optional [TransactionSet ]:
95+ txset = TransactionSet ()
96+ if not isinstance (address , str ) or not address :
97+ raise Exception ("bad address" )
98+
99+ results = await self ._make_request (data = {
100+ "method" : "listreceivedbyaddress" ,
101+ "params" : [minimum_confirmations ]
102+ })
103+
104+ if not isinstance (results .get ('result' ), list ):
105+ return txset
106+
107+ try :
108+ result = [r for r in results ['result' ] if r ['address' ] == address ][0 ]
109+ except Exception as ex :
110+ return txset
111+
112+ for txid in result .get ('txids' , []):
113+ # fetch tx details
114+ tx = await self .tx_details (txid )
115+
116+ # fetch blockheight
117+ tx ['blockheight' ] = await self .blockheight (tx ['blockhash' ])
118+ date = datetime .fromtimestamp (tx ['blocktime' ])
119+
120+ txset .add (Transaction (amount = tx ['amount' ],
121+ txid = tx ['txid' ],
122+ date = date ,
123+ blockheight = tx ['blockheight' ],
124+ direction = 'in' ,
125+ confirmations = tx ['confirmations' ]))
126+
127+ return txset
128+
129+ async def blockheight (self , blockhash : str ) -> int :
130+ """blockhash -> blockheight"""
131+ if not isinstance (blockhash , str ) or not blockhash :
132+ raise Exception ("bad address" )
133+
134+ data = {
135+ "method" : "getblock" ,
136+ "params" : [blockhash ]
137+ }
138+ blob = await self ._make_request (data = data )
139+
140+ height = blob ['result' ].get ('height' , 0 )
141+ return height
142+
143+ async def _generate_url (self ) -> None :
144+ self .url = f'http://{ self .host } :{ self .port } /'
145+
146+ async def _make_request (self , data : dict = None ) -> dict :
147+ await self ._generate_url ()
148+
149+ opts = {
150+ "headers" : {
151+ "User-Agent" : self .user_agent
152+ }
153+ }
154+
155+ if self .basic_auth :
156+ opts ['auth' ] = await self ._make_basic_auth ()
157+
158+ async with aiohttp .ClientSession (** opts ) as session :
159+ async with session .post (self .url , json = data ) as resp :
160+ if resp .status == 401 :
161+ raise Exception ("Unauthorized" )
162+ blob = await resp .json ()
163+ if 'result' not in blob :
164+ if blob :
165+ blob = json .dumps (blob , indent = 4 , sort_keys = True )
166+ raise Exception (f"Invalid response: { blob } " )
167+ return blob
168+
21169cache = None
22170peewee = None
23171rates = Rates ()
24172app : Optional [Quart ] = None
25173openid : Optional [OpenID ] = None
26- crypto_provider : Optional [Firo ] = None
174+ crypto_provider : Optional [Firo ] = Firo ()
27175coin : Optional [dict ] = None
28176discourse = Discourse ()
29177proposal_task = None
@@ -47,7 +195,6 @@ def __init__(self, *args, **kwargs):
47195 port = settings .DB_PORT
48196)
49197
50-
51198async def _setup_postgres (app : Quart ):
52199 import peewee
53200 import funding .models .database
@@ -115,7 +262,6 @@ async def _setup_crypto(app: Quart):
115262 if settings .COIN_RPC_AUTH :
116263 crypto_provider .basic_auth = settings .COIN_RPC_AUTH
117264
118-
119265async def _setup_cache (app : Quart ):
120266 global cache
121267 app .config ['SESSION_TYPE' ] = 'redis'
@@ -148,7 +294,6 @@ async def page_not_found(e):
148294def create_app ():
149295 global app
150296 app = Quart (__name__ )
151-
152297 app .logger .setLevel (logging .INFO )
153298 app .secret_key = settings .APP_SECRET
154299
@@ -197,7 +342,7 @@ def template_variables():
197342
198343 @app .errorhandler (RequestSchemaValidationError )
199344 async def handle_request_validation_error (error ):
200- return {"errors" : error . validation_error . json ()} , 400
345+ return jsonify ( {"errors" : str ( error )}) , 400
201346
202347 @app .before_serving
203348 async def startup ():
0 commit comments