from collections import defaultdict import sqlite3 import json import os import requests import sys import time from datetime import datetime from quart import jsonify, make_response, Quart, render_template, request, flash, redirect, url_for from quart_cors import cors import asyncio from typing import Optional from pybtc import verify_signature from config import * import parsing import subprocess from apscheduler.schedulers.background import BackgroundScheduler import atexit app = Quart(__name__) app.clients = set() app = cors(app, allow_origin="*") # helper functions def retryRequest(tempserverlist, apicall): if len(tempserverlist) != 0: try: response = requests.get( '{}api/{}'.format(tempserverlist[0], apicall)) except: tempserverlist.pop(0) return retryRequest(tempserverlist, apicall) else: if response.status_code == 200: return json.loads(response.content) else: tempserverlist.pop(0) return retryRequest(tempserverlist, apicall) else: print("None of the APIs are responding for the call {}".format(apicall)) sys.exit(0) def multiRequest(apicall, net): testserverlist = ['http://0.0.0.0:9000/', 'https://testnet.flocha.in/', 'https://testnet-flosight.duckdns.org/'] mainserverlist = ['http://0.0.0.0:9001/', 'https://livenet.flocha.in/', 'https://testnet-flosight.duckdns.org/'] if net == 'mainnet': return retryRequest(mainserverlist, apicall) elif net == 'testnet': return retryRequest(testserverlist, apicall) def blockdetailhelper(blockdetail): if blockdetail.isdigit(): blockHash = None blockHeight = int(blockdetail) else: blockHash = str(blockdetail) blockHeight = None # open the latest block database conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) c = conn.cursor() if blockHash: c.execute( f"select jsonData from latestBlocks where blockHash='{blockHash}'") elif blockHeight: c.execute( f"select jsonData from latestBlocks where blockNumber='{blockHeight}'") blockJson = c.fetchall() return blockJson def transactiondetailhelper(transactionHash): # open the latest block database conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) c = conn.cursor() c.execute( f"select jsonData,parsedFloData from latestTransactions where transactionHash='{transactionHash}'") transactionJsonData = c.fetchall() return transactionJsonData # FLO TOKEN APIs @app.route('/api/v1.0/broadcastTx/') async def broadcastTx(raw_transaction_hash): p1 = subprocess.run(['flo-cli',f"-datadir={FLO_DATA_DIR}",'sendrawtransaction',raw_transaction_hash], capture_output=True) return jsonify(args=p1.args,returncode=p1.returncode,stdout=p1.stdout.decode(),stderr=p1.stderr.decode()) @app.route('/api/v1.0/getTokenList', methods=['GET']) async def getTokenList(): filelist = [] for item in os.listdir(os.path.join(dbfolder, 'tokens')): if os.path.isfile(os.path.join(dbfolder, 'tokens', item)): filelist.append(item[:-3]) return jsonify(tokens=filelist, result='ok') @app.route('/api/v1.0/getTokenInfo', methods=['GET']) async def getTokenInfo(): token = request.args.get('token') if token is None: return jsonify(result='error', description='token name hasnt been passed') dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: return jsonify(result='error', description='token doesn\'t exist') c.execute('SELECT * FROM transactionHistory WHERE id=1') incorporationRow = c.fetchall()[0] c.execute('SELECT COUNT (DISTINCT address) FROM activeTable') numberOf_distinctAddresses = c.fetchall()[0][0] c.execute('select max(id) from transactionHistory') numberOf_transactions = c.fetchall()[0][0] c.execute( 'select contractName, contractAddress, blockNumber, blockHash, transactionHash from tokenContractAssociation') associatedContracts = c.fetchall() conn.close() associatedContractList = [] for item in associatedContracts: tempdict = {} item = list(item) tempdict['contractName'] = item[0] tempdict['contractAddress'] = item[1] tempdict['blockNumber'] = item[2] tempdict['blockHash'] = item[3] tempdict['transactionHash'] = item[4] associatedContractList.append(tempdict) return jsonify(result='ok', token=token, incorporationAddress=incorporationRow[1], tokenSupply=incorporationRow[3], time=incorporationRow[6], blockchainReference=incorporationRow[7], activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList) @app.route('/api/v1.0/getTokenTransactions', methods=['GET']) async def getTokenTransactions(): token = request.args.get('token') senderFloAddress = request.args.get('senderFloAddress') destFloAddress = request.args.get('destFloAddress') limit = request.args.get('limit') if token is None: return jsonify(result='error', description='token name hasnt been passed') dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) conn.row_factory = sqlite3.Row c = conn.cursor() else: return jsonify(result='error', description='token doesn\'t exist') if senderFloAddress and not destFloAddress: if limit is None: c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( senderFloAddress)) else: c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT {}'.format( senderFloAddress, limit)) elif not senderFloAddress and destFloAddress: if limit is None: c.execute( 'SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( destFloAddress)) else: c.execute( 'SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format( destFloAddress, limit)) elif senderFloAddress and destFloAddress: if limit is None: c.execute( 'SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( senderFloAddress, destFloAddress)) else: c.execute( 'SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format( senderFloAddress, destFloAddress, limit)) else: if limit is None: c.execute( 'SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT 100') else: c.execute( 'SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT {}'.format(limit)) transactionJsonData = c.fetchall() conn.close() rowarray_list = {} for row in transactionJsonData: temp = {} temp['transactionDetails'] = json.loads(row[0]) temp['parsedFloData'] = json.loads(row[1]) rowarray_list[temp['transactionDetails']['txid']] = temp return jsonify(result='ok', token=token, transactions=rowarray_list) @app.route('/api/v1.0/getTokenBalances', methods=['GET']) async def getTokenBalances(): token = request.args.get('token') if token is None: return jsonify(result='error', description='token name hasnt been passed') dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: return jsonify(result='error', description='token doesn\'t exist') c.execute( 'SELECT address,SUM(transferBalance) FROM activeTable GROUP BY address') addressBalances = c.fetchall() returnList = {} for address in addressBalances: returnList[address[0]] = address[1] return jsonify(result='ok', token=token, balances=returnList) # FLO address APIs @app.route('/api/v1.0/getFloAddressInfo', methods=['GET']) async def getFloAddressInfo(): floAddress = request.args.get('floAddress') if floAddress is None: return jsonify(result='error', description='floAddress hasn\'t been passed') dblocation = dbfolder + '/system.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() c.execute( 'select token from tokenAddressMapping where tokenAddress="{}"'.format(floAddress)) tokenNames = c.fetchall() c.execute( f"select contractName, status, tokenIdentification, contractType, transactionHash, blockNumber, blockHash from activecontracts where contractAddress='{floAddress}'") incorporatedContracts = c.fetchall() if len(tokenNames) != 0: detailList = {} for token in tokenNames: token = token[0] dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): tempdict = {} conn = sqlite3.connect(dblocation) c = conn.cursor() c.execute( 'SELECT SUM(transferBalance) FROM activeTable WHERE address="{}"'.format(floAddress)) balance = c.fetchall()[0][0] tempdict['balance'] = balance tempdict['token'] = token detailList[token] = tempdict else: # Address is not associated with any token return jsonify(result='error', description='FLO address is not associated with any tokens') if len(incorporatedContracts) != 0: incorporatedSmartContracts = [] for contract in incorporatedContracts: tempdict = {} tempdict['contractName'] = contract[0] tempdict['contractAddress'] = floAddress tempdict['status'] = contract[1] tempdict['tokenIdentification'] = contract[2] tempdict['contractType'] = contract[3] tempdict['transactionHash'] = contract[4] tempdict['blockNumber'] = contract[5] tempdict['blockHash'] = contract[6] incorporatedSmartContracts.append(tempdict) return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=incorporatedContracts) else: return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=None) @app.route('/api/v1.0/getFloAddressBalance', methods=['GET']) async def getAddressBalance(): floAddress = request.args.get('floAddress') token = request.args.get('token') if floAddress is None: return jsonify(result='error', description='floAddress hasn\'t been passed') if token is None: dblocation = dbfolder + '/system.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() c.execute( 'select token from tokenAddressMapping where tokenAddress="{}"'.format(floAddress)) tokenNames = c.fetchall() if len(tokenNames) != 0: detailList = {} for token in tokenNames: token = token[0] dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): tempdict = {} conn = sqlite3.connect(dblocation) c = conn.cursor() c.execute( 'SELECT SUM(transferBalance) FROM activeTable WHERE address="{}"'.format(floAddress)) balance = c.fetchall()[0][0] tempdict['balance'] = balance tempdict['token'] = token detailList[token] = tempdict return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList) else: # Address is not associated with any token return jsonify(result='error', description='FLO address is not associated with any tokens') else: dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: return jsonify(result='error', description='token doesn\'t exist') c.execute( 'SELECT SUM(transferBalance) FROM activeTable WHERE address="{}"'.format(floAddress)) balance = c.fetchall()[0][0] conn.close() return jsonify(result='ok', token=token, floAddress=floAddress, balance=balance) @app.route('/api/v1.0/getFloAddressTransactions', methods=['GET']) async def getFloAddressTransactions(): floAddress = request.args.get('floAddress') token = request.args.get('token') limit = request.args.get('limit') if floAddress is None: return jsonify(result='error', description='floAddress has not been passed') if token is None: dblocation = dbfolder + '/system.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() c.execute( 'select token from tokenAddressMapping where tokenAddress="{}"'.format(floAddress)) tokenNames = c.fetchall() else: dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): tokenNames = [[str(token), ]] else: return jsonify(result='error', description='token doesn\'t exist') if len(tokenNames) != 0: allTransactionList = {} for tokenname in tokenNames: tokenname = tokenname[0] dblocation = dbfolder + '/tokens/' + str(tokenname) + '.db' if os.path.exists(dblocation): tempdict = {} conn = sqlite3.connect(dblocation) c = conn.cursor() if limit is None: c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" OR destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( floAddress, floAddress)) else: c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" OR destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format( floAddress, floAddress, limit)) transactionJsonData = c.fetchall() conn.close() for row in transactionJsonData: temp = {} temp['transactionDetails'] = json.loads(row[0]) temp['parsedFloData'] = json.loads(row[1]) allTransactionList[temp['transactionDetails'] ['txid']] = temp if token is None: return jsonify(result='ok', floAddress=floAddress, transactions=allTransactionList) else: return jsonify(result='ok', floAddress=floAddress, transactions=allTransactionList, token=token) else: return jsonify(result='error', description='No token transactions present present on this address') # SMART CONTRACT APIs @app.route('/api/v1.0/getSmartContractList', methods=['GET']) async def getContractList(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() contractList = [] if contractName and contractAddress: c.execute('select * from activecontracts where contractName="{}" and contractAddress="{}"'.format( contractName, contractAddress)) allcontractsDetailList = c.fetchall() for idx, contract in enumerate(allcontractsDetailList): contractDict = {} contractDict['contractName'] = contract[1] contractDict['contractAddress'] = contract[2] contractDict['status'] = contract[3] contractDict['tokenIdentification'] = contract[4] contractDict['contractType'] = contract[5] contractDict['transactionHash'] = contract[6] contractDict['blockNumber'] = contract[7] contractDict['incorporationDate'] = contract[8] if contract[9]: contractDict['expiryDate'] = contract[9] if contract[10]: contractDict['closeDate'] = contract[10] contractList.append(contractDict) elif contractName and not contractAddress: c.execute( 'select * from activecontracts where contractName="{}"'.format(contractName)) allcontractsDetailList = c.fetchall() for idx, contract in enumerate(allcontractsDetailList): contractDict = {} contractDict['contractName'] = contract[1] contractDict['contractAddress'] = contract[2] contractDict['status'] = contract[3] contractDict['tokenIdentification'] = contract[4] contractDict['contractType'] = contract[5] contractDict['transactionHash'] = contract[6] contractDict['blockNumber'] = contract[7] contractDict['incorporationDate'] = contract[8] if contract[9]: contractDict['expiryDate'] = contract[9] if contract[10]: contractDict['closeDate'] = contract[10] contractList.append(contractDict) elif not contractName and contractAddress: c.execute( 'select * from activecontracts where contractAddress="{}"'.format(contractAddress)) allcontractsDetailList = c.fetchall() for idx, contract in enumerate(allcontractsDetailList): contractDict = {} contractDict['contractName'] = contract[1] contractDict['contractAddress'] = contract[2] contractDict['status'] = contract[3] contractDict['tokenIdentification'] = contract[4] contractDict['contractType'] = contract[5] contractDict['transactionHash'] = contract[6] contractDict['blockNumber'] = contract[7] contractDict['incorporationDate'] = contract[8] if contract[9]: contractDict['expiryDate'] = contract[9] if contract[10]: contractDict['closeDate'] = contract[10] contractList.append(contractDict) else: c.execute('select * from activecontracts') allcontractsDetailList = c.fetchall() for idx, contract in enumerate(allcontractsDetailList): contractDict = {} contractDict['contractName'] = contract[1] contractDict['contractAddress'] = contract[2] contractDict['status'] = contract[3] contractDict['tokenIdentification'] = contract[4] contractDict['contractType'] = contract[5] contractDict['transactionHash'] = contract[6] contractDict['blockNumber'] = contract[7] contractDict['incorporationDate'] = contract[8] if contract[9]: contractDict['expiryDate'] = contract[9] if contract[10]: contractDict['closeDate'] = contract[10] contractList.append(contractDict) return jsonify(smartContracts=contractList, result='ok') @app.route('/api/v1.0/getSmartContractInfo', methods=['GET']) async def getContractInfo(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') if contractName is None: return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') if contractAddress is None: return jsonify(result='error', description='Smart Contract\'s address hasn\'t been passed') contractDbName = '{}-{}.db'.format(contractName.strip(), contractAddress.strip()) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): # Make db connection and fetch data conn = sqlite3.connect(filelocation) c = conn.cursor() c.execute( 'SELECT attribute,value FROM contractstructure') result = c.fetchall() contractStructure = {} conditionDict = {} counter = 0 for item in result: if list(item)[0] == 'exitconditions': conditionDict[counter] = list(item)[1] counter = counter + 1 else: contractStructure[list(item)[0]] = list(item)[1] if len(conditionDict) > 0: contractStructure['exitconditions'] = conditionDict del counter, conditionDict returnval = contractStructure returnval['userChoice'] = contractStructure['exitconditions'] returnval.pop('exitconditions') c.execute('select count(participantAddress) from contractparticipants') noOfParticipants = c.fetchall()[0][0] returnval['numberOfParticipants'] = noOfParticipants c.execute('select sum(tokenAmount) from contractparticipants') totalAmount = c.fetchall()[0][0] returnval['tokenAmountDeposited'] = totalAmount conn.close() conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() c.execute('select status, incorporationDate, expiryDate, closeDate from activecontracts where contractName=="{}" and contractAddress=="{}"'.format( contractName.strip(), contractAddress.strip())) results = c.fetchall() if len(results) == 1: for result in results: returnval['status'] = result[0] returnval['incorporationDate'] = result[1] if result[2]: returnval['expiryDate'] = result[2] if result[3]: returnval['closeDate'] = result[3] if returnval['status'] == 'closed': conn = sqlite3.connect(filelocation) c = conn.cursor() if returnval['contractType'] == 'one-time-event': # pull out trigger information # check if the trigger was succesful or failed c.execute( f"select transactionType, transactionSubType from contractTransactionHistory where transactionType='trigger'") triggerntype = c.fetchall() if len(triggerntype) == 1: triggerntype = list(triggerntype[0]) returnval['triggerType'] = triggerntype[1] if 'userChoice' in returnval: # Contract is of the type external trigger if triggerntype[0] == 'trigger' and triggerntype[1] is None: # this is a normal trigger # find the winning condition c.execute( 'select userchoice from contractparticipants where winningAmount is not null limit 1') returnval['winningChoice'] = c.fetchall()[0][0] # find the winning participants c.execute( 'select participantAddress, winningAmount from contractparticipants where winningAmount is not null') winnerparticipants = c.fetchall() returnval['numberOfWinners'] = len( winnerparticipants) else: return jsonify(result='error', description='There is more than 1 trigger in the database for the smart contract. Please check your code, this shouldnt happen') return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, contractInfo=returnval) else: return jsonify(result='error', details='Smart Contract with the given name doesn\'t exist') @app.route('/api/v1.0/getSmartContractParticipants', methods=['GET']) async def getcontractparticipants(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') if contractName is None: return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') if contractAddress is None: return jsonify(result='error', description='Smart Contract\'s address hasn\'t been passed') contractDbName = '{}-{}.db'.format(contractName.strip(), contractAddress.strip()) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): # Make db connection and fetch data conn = sqlite3.connect(filelocation) c = conn.cursor() c.execute( 'SELECT attribute,value FROM contractstructure') result = c.fetchall() contractStructure = {} conditionDict = {} counter = 0 for item in result: if list(item)[0] == 'exitconditions': conditionDict[counter] = list(item)[1] counter = counter + 1 else: contractStructure[list(item)[0]] = list(item)[1] if len(conditionDict) > 0: contractStructure['exitconditions'] = conditionDict del counter, conditionDict if 'exitconditions' in contractStructure: # contract is of the type external trigger # check if the contract has been closed c.execute( 'select * from contractTransactionHistory where transactionType="trigger"') trigger = c.fetchall() if len(trigger) == 1: c.execute( 'select value from contractstructure where attribute="tokenIdentification"') token = c.fetchall() token = token[0][0] c.execute( 'SELECT id,participantAddress, tokenAmount, userChoice, transactionHash, winningAmount FROM contractparticipants') result = c.fetchall() conn.close() returnval = {} for row in result: returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4], 'winningAmount': row[5], 'tokenIdentification': token} return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, participantInfo=returnval) elif len(trigger) == 0: c.execute( 'SELECT id,participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') result = c.fetchall() conn.close() returnval = {} for row in result: returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4]} return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, participantInfo=returnval) else: return jsonify(result='error', description='More than 1 trigger present. This is unusual, please check your code') elif 'payeeAddress' in contractStructure: # contract is of the type internal trigger c.execute( 'SELECT id,participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') result = c.fetchall() conn.close() returnval = {} for row in result: returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4]} return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, participantInfo=returnval) else: return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist') @app.route('/api/v1.0/getParticipantDetails', methods=['GET']) async def getParticipantDetails(): floAddress = request.args.get('floAddress') contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') if floAddress is None: return jsonify(result='error', description='FLO address hasn\'t been passed') dblocation = os.path.join(dbfolder, 'system.db') if (contractName and contractAddress is None) or (contractName is None and contractAddress): return jsonify(result='error', description='pass both, contractName and contractAddress as url parameters') if os.path.isfile(dblocation): # Make db connection and fetch data conn = sqlite3.connect(dblocation) c = conn.cursor() # Check if its a participant address queryString = f"SELECT id, address,contractName, contractAddress, tokenAmount, transactionHash, blockNumber, blockHash FROM contractAddressMapping where address='{floAddress}' and addressType='participant'" c.execute(queryString) result = c.fetchall() if len(result) != 0: participationDetailsList = [] for row in result: detailsDict = {} detailsDict['contractName'] = row[2] detailsDict['contractAddress'] = row[3] detailsDict['tokenAmount'] = row[4] detailsDict['transactionHash'] = row[5] c.execute( f"select status, tokenIdentification, contractType, blockNumber, blockHash, incorporationDate, expiryDate, closeDate from activecontracts where contractName='{detailsDict['contractName']}' and contractAddress='{detailsDict['contractAddress']}'") temp = c.fetchall() detailsDict['status'] = temp[0][0] detailsDict['tokenIdentification'] = temp[0][1] detailsDict['contractType'] = temp[0][2] detailsDict['blockNumber'] = temp[0][3] detailsDict['blockHash'] = temp[0][4] detailsDict['incorporationDate'] = temp[0][5] if temp[0][6]: detailsDict['expiryDate'] = temp[0][6] if temp[0][7]: detailsDict['closeDate'] = temp[0][7] # check if the contract has been closed contractDbName = '{}-{}.db'.format(detailsDict['contractName'].strip(), detailsDict['contractAddress'].strip()) filelocation = os.path.join( dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): # Make db connection and fetch data conn = sqlite3.connect(filelocation) c = conn.cursor() c.execute( 'SELECT attribute,value FROM contractstructure') result = c.fetchall() contractStructure = {} conditionDict = {} counter = 0 for item in result: if list(item)[0] == 'exitconditions': conditionDict[counter] = list(item)[1] counter = counter + 1 else: contractStructure[list(item)[0]] = list(item)[1] if len(conditionDict) > 0: contractStructure['exitconditions'] = conditionDict del counter, conditionDict if 'exitconditions' in contractStructure: # contract is of the type external trigger # check if the contract has been closed c.execute( 'select * from contractTransactionHistory where transactionType="trigger"') trigger = c.fetchall() if detailsDict['status'] == 'closed': c.execute( f"SELECT userChoice, winningAmount FROM contractparticipants where participantAddress='{floAddress}'") result = c.fetchall() conn.close() detailsDict['userChoice'] = result[0][0] detailsDict['winningAmount'] = result[0][1] else: c.execute( f"SELECT userChoice FROM contractparticipants where participantAddress='{floAddress}'") result = c.fetchall() conn.close() detailsDict['userChoice'] = result[0][0] participationDetailsList.append(detailsDict) return jsonify(result='ok', floAddress=floAddress, type='participant', participatedContracts=participationDetailsList) else: return jsonify(result='error', description='Address hasn\'t participanted in any other contract') else: return jsonify(result='error', description='System error. System db is missing') @app.route('/api/v1.0/getSmartContractTransactions', methods=['GET']) async def getsmartcontracttransactions(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') if contractName is None: return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') if contractAddress is None: return jsonify(result='error', description='Smart Contract\'s address hasn\'t been passed') contractDbName = '{}-{}.db'.format(contractName.strip(), contractAddress.strip()) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): # Make db connection and fetch data conn = sqlite3.connect(filelocation) c = conn.cursor() c.execute('select jsonData, parsedFloData from contractTransactionHistory') result = c.fetchall() conn.close() returnval = {} for item in result: temp = {} temp['transactionDetails'] = json.loads(item[0]) temp['parsedFloData'] = json.loads(item[1]) returnval[temp['transactionDetails']['txid']] = temp return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, contractTransactions=returnval) else: return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist') @app.route('/api/v1.0/getBlockDetails/', methods=['GET']) async def getblockdetails(blockdetail): blockJson = blockdetailhelper(blockdetail) if len(blockJson) != 0: blockJson = json.loads(blockJson[0][0]) return jsonify(result='ok', blockDetails=blockJson) else: return jsonify(result='error', description='Block doesn\'t exist in database') @app.route('/api/v1.0/getTransactionDetails/', methods=['GET']) async def gettransactiondetails(transactionHash): transactionJsonData = transactiondetailhelper(transactionHash) if len(transactionJsonData) != 0: transactionJson = json.loads(transactionJsonData[0][0]) parseResult = json.loads(transactionJsonData[0][1]) return jsonify(parsedFloData=parseResult, transactionDetails=transactionJson, transactionHash=transactionHash, result='ok') else: return jsonify(result='error', description='Transaction doesn\'t exist in database') @app.route('/api/v1.0/getLatestTransactionDetails', methods=['GET']) async def getLatestTransactionDetails(): numberOfLatestBlocks = request.args.get('numberOfLatestBlocks') dblocation = dbfolder + '/latestCache.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: return 'Latest transactions db doesn\'t exist. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api' if numberOfLatestBlocks is not None: c.execute('SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(int(numberOfLatestBlocks))) latestTransactions = c.fetchall() c.close() tempdict = {} for idx, item in enumerate(latestTransactions): item = list(item) tx_parsed_details = {} tx_parsed_details['transactionDetails'] = json.loads(item[3]) tx_parsed_details['parsedFloData'] = json.loads(item[5]) tx_parsed_details['parsedFloData']['transactionType'] = item[4] tx_parsed_details['transactionDetails']['blockheight'] = int( item[2]) tempdict[json.loads(item[3])['txid']] = tx_parsed_details else: c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT 100) ORDER BY id ASC;''') latestTransactions = c.fetchall() c.close() tempdict = {} for idx, item in enumerate(latestTransactions): item = list(item) tx_parsed_details = {} tx_parsed_details['transactionDetails'] = json.loads(item[3]) tx_parsed_details['parsedFloData'] = json.loads(item[5]) tx_parsed_details['parsedFloData']['transactionType'] = item[4] tx_parsed_details['transactionDetails']['blockheight'] = int( item[2]) tempdict[json.loads(item[3])['txid']] = tx_parsed_details return jsonify(result='ok', latestTransactions=tempdict) @app.route('/api/v1.0/getLatestBlockDetails', methods=['GET']) async def getLatestBlockDetails(): limit = request.args.get('limit') dblocation = dbfolder + '/latestCache.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: return 'Latest transactions db doesn\'t exist. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api' if limit is None: c.execute( '''SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT 4) ORDER BY id ASC;''') else: int(limit) c.execute( 'SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(limit)) latestBlocks = c.fetchall() c.close() tempdict = {} for idx, item in enumerate(latestBlocks): tempdict[json.loads(item[3])['hash']] = json.loads(item[3]) return jsonify(result='ok', latestBlocks=tempdict) @app.route('/api/v1.0/getBlockTransactions/', methods=['GET']) async def getblocktransactions(blockdetail): blockJson = blockdetailhelper(blockdetail) if len(blockJson) != 0: blockJson = json.loads(blockJson[0][0]) blocktxlist = blockJson['tx'] blocktxs = {} for i in range(len(blocktxlist)): temptx = transactiondetailhelper(blocktxlist[i]) transactionJson = json.loads(temptx[0][0]) parseResult = json.loads(temptx[0][1]) blocktxs[blocktxlist[i]] = { "parsedFloData" : parseResult, "transactionDetails" : transactionJson } return jsonify(result='ok', transactions=blocktxs, blockKeyword=blockdetail) else: return jsonify(result='error', description='Block doesn\'t exist in database') @app.route('/api/v1.0/categoriseString/') async def categoriseString(urlstring): # check if the hash is of a transaction response = requests.get('{}tx/{}'.format(apiUrl, urlstring)) if response.status_code == 200: return jsonify(type='transaction') else: response = requests.get('{}block/{}'.format(apiUrl, urlstring)) if response.status_code == 200: return jsonify(type='block') else: # check urlstring is a token name tokenfolder = os.path.join(dbfolder, 'tokens') onlyfiles = [f[:-3] for f in os.listdir(tokenfolder) if os.path.isfile(os.path.join(tokenfolder, f))] if urlstring.lower() in onlyfiles: return jsonify(type='token') else: contractfolder = os.path.join(dbfolder, 'system.db') conn = sqlite3.connect(contractfolder) conn.row_factory = lambda cursor, row: row[0] c = conn.cursor() contractList = c.execute( 'select contractname from activeContracts').fetchall() if urlstring.lower() in contractList: return jsonify(type='smartContract') else: return jsonify(type='noise') @app.route('/api/v1.0/getTokenSmartContractList', methods=['GET']) async def getTokenSmartContractList(): # list of tokens filelist = [] for item in os.listdir(os.path.join(dbfolder, 'tokens')): if os.path.isfile(os.path.join(dbfolder, 'tokens', item)): filelist.append(item[:-3]) # list of smart contracts conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() contractList = [] c.execute('select * from activecontracts') allcontractsDetailList = c.fetchall() for idx, contract in enumerate(allcontractsDetailList): contractDict = {} contractDict['contractName'] = contract[1] contractDict['contractAddress'] = contract[2] contractDict['status'] = contract[3] contractDict['tokenIdentification'] = contract[4] contractDict['contractType'] = contract[5] contractDict['transactionHash'] = contract[6] contractDict['blockNumber'] = contract[7] contractDict['blockHash'] = contract[8] contractDict['incorporationDate'] = contract[9] if contract[10]: contractDict['expiryDate'] = contract[10] if contract[11]: contractDict['closeDate'] = contract[11] contractList.append(contractDict) return jsonify(tokens=filelist, smartContracts=contractList, result='ok') @app.route('/api/v1.0/getSystemData', methods=['GET']) async def systemData(): # query for the number of flo addresses in tokenAddress mapping conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() tokenAddressCount = c.execute( 'select count(distinct tokenAddress) from tokenAddressMapping').fetchall()[0][0] tokenCount = c.execute( 'select count(distinct token) from tokenAddressMapping').fetchall()[0][0] contractCount = c.execute( 'select count(distinct contractName) from contractAddressMapping').fetchall()[0][0] lastscannedblock = int(c.execute("select value from systemData where attribute=='lastblockscanned'").fetchall()[0][0]) conn.close() # query for total number of validated blocks conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) c = conn.cursor() validatedBlockCount = c.execute( 'select count(distinct blockNumber) from latestBlocks').fetchall()[0][0] validatedTransactionCount = c.execute( 'select count(distinct transactionHash) from latestTransactions').fetchall()[0][0] conn.close() return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock, result='ok') class ServerSentEvent: def __init__( self, data: str, *, event: Optional[str] = None, id: Optional[int] = None, retry: Optional[int] = None, ) -> None: self.data = data self.event = event self.id = id self.retry = retry def encode(self) -> bytes: message = f"data: {self.data}" if self.event is not None: message = f"{message}\nevent: {self.event}" if self.id is not None: message = f"{message}\nid: {self.id}" if self.retry is not None: message = f"{message}\nretry: {self.retry}" message = f"{message}\r\n\r\n" return message.encode('utf-8') @app.route('/', methods=['GET']) async def index(): return await render_template('index.html') @app.route('/', methods=['POST']) async def broadcast(): data = await request.get_json() for queue in app.clients: await queue.put(data['message']) return jsonify(True) @app.route('/sse') async def sse(): queue = asyncio.Queue() app.clients.add(queue) async def send_events(): while True: try: data = await queue.get() event = ServerSentEvent(data) yield event.encode() except asyncio.CancelledError as error: app.clients.remove(queue) response = await make_response( send_events(), { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Transfer-Encoding': 'chunked', }, ) response.timeout = None return response @app.route('/api/v1.0/getPrices', methods=['GET']) async def getPriceData(): # read system.db for price data conn = sqlite3.connect('system.db') c = conn.cursor() ratepairs = c.execute('select ratepair, price from ratepairs') ratepairs = ratepairs.fetchall() prices = {} for ratepair in ratepairs: ratepair = list(ratepair) prices[ratepair[0]] = ratepair[1] return jsonify(prices=prices, result='ok') ''' Stuff required for getPrices endpoint ''' def updatePrices(): prices = {} # USD -> INR response = requests.get(f"https://api.exchangerate-api.com/v4/latest/usd") try: price = response.json() prices['USDINR'] = price['rates']['INR'] except ValueError: response = requests.get( f"https://api.exchangeratesapi.io/latest?base=USD&symbols=INR") try: price = response.json() prices['USDINR'] = price['rates']['INR'] except ValueError: response = requests.get( f"https://api.ratesapi.io/api/latest?base=USD&symbols=INR") try: price = response.json() prices['USDINR'] = price['rates']['INR'] except ValueError: # todo : add logger to the application print('USD to Fiat APIs arent responding') # Blockchain stuff : BTC,FLO -> USD,INR response = requests.get( f"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,flo&vs_currencies=usd,inr") try: price = response.json() prices['BTCUSD'] = price['bitcoin']['usd'] prices['BTCINR'] = price['bitcoin']['inr'] prices['FLOUSD'] = price['flo']['usd'] prices['FLOINR'] = price['flo']['inr'] except ValueError: response1 = requests.get( f"https://api.coinlore.net/api/ticker/?id=90") response2 = requests.get( f"https://api.coinlore.net/api/ticker/?id=67") try: price1 = response1.json() price2 = response2.json() prices['BTCUSD'] = price1[0]['price_usd'] prices['BTCINR'] = price1[0]['price_usd'] * prices['USDINR'] prices['FLOUSD'] = price2[0]['price_usd'] prices['FLOINR'] = price2[0]['price_usd'] * prices['USDINR'] except ValueError: response1 = requests.get( f"https://api.coinpaprika.com/v1/tickers/btc-bitcoin") response2 = requests.get( f"https://api.coinpaprika.com/v1/tickers/flo-flo") try: price1 = response1.json() price2 = response2.json() prices['BTCUSD'] = price1['quotes']['usd']['price'] prices['BTCINR'] = price1['quotes']['usd']['price'] * \ prices['USDINR'] prices['FLOUSD'] = price2['quotes']['usd']['price'] prices['FLOINR'] = price2['quotes']['usd']['price'] * \ prices['USDINR'] except ValueError: # todo : add logger to the application print('Blockchain to Fiat APIs arent responding') # 3. update latest price data print('Prices updated at time: %s' % datetime.now()) print(prices) conn = sqlite3.connect('system.db') c = conn.cursor() for pair in list(prices.items()): pair = list(pair) c.execute( f"UPDATE ratepairs SET price={pair[1]} WHERE ratepair='{pair[0]}'") conn.commit() # if system.db isn't present, initialize it if not os.path.isfile(f"system.db"): # create an empty db conn = sqlite3.connect('system.db') c = conn.cursor() c.execute('''CREATE TABLE ratepairs (id integer primary key, ratepair text, price real)''') c.execute("INSERT INTO ratepairs(ratepair, price) VALUES ('BTCBTC', 1)") c.execute("INSERT INTO ratepairs(ratepair, price) VALUES ('BTCUSD', -1)") c.execute("INSERT INTO ratepairs(ratepair, price) VALUES ('BTCINR', -1)") c.execute("INSERT INTO ratepairs(ratepair, price) VALUES ('FLOUSD', -1)") c.execute("INSERT INTO ratepairs(ratepair, price) VALUES ('FLOINR', -1)") c.execute("INSERT INTO ratepairs(ratepair, price) VALUES ('USDINR', -1)") conn.commit() conn.close() # update the prices once updatePrices() # assign a scheduler for updating prices in the background scheduler = BackgroundScheduler() scheduler.add_job(func=updatePrices, trigger="interval", seconds=600) scheduler.start() # Shut down the scheduler when exiting the app atexit.register(lambda: scheduler.shutdown()) if __name__ == "__main__": app.run(debug=True, host='0.0.0.0', port=5009)