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, send_file from quart_cors import cors import asyncio from typing import Optional from config import * import parsing import subprocess from apscheduler.schedulers.background import BackgroundScheduler import atexit import pyflo from operator import itemgetter import pdb import ast import time app = Quart(__name__) app.clients = set() app = cors(app, allow_origin="*") # Global values and configg internalTransactionTypes = [ 'tokenswapDepositSettlement', 'tokenswapParticipationSettlement', 'smartContractDepositReturn'] if net == 'mainnet': is_testnet = False elif net == 'testnet': is_testnet = True # Validation functionss def check_flo_address(floaddress, is_testnet=False): return pyflo.is_address_valid(floaddress, testnet=is_testnet) def check_integer(value): return str.isdigit(value) # 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): # check if legit transaction hash # open the latest block database conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) c = conn.cursor() c.execute(f"SELECT jsonData, parsedFloData, transactionType, db_reference FROM latestTransactions WHERE transactionHash='{transactionHash}'") transactionJsonData = c.fetchall() return transactionJsonData def update_transaction_confirmations(transactionJson): url = f"{apiUrl}api/v1/tx/{transactionJson['txid']}" response = requests.get(url) if response.status_code == 200: response_data = response.json() transactionJson['confirmations'] = response_data['confirmations'] return transactionJson def smartcontract_morph_helper(smart_contracts): contractList = [] for idx, contract in enumerate(smart_contracts): contractDict = {} contractDict['contractName'] = contract[1] contractDict['contractAddress'] = contract[2] contractDict['status'] = contract[3] contractDict['contractType'] = contract[5] if contractDict['contractType'] in ['continuous-event', 'continuos-event']: contractDict['contractSubType'] = 'tokenswap' accepting_selling_tokens = ast.literal_eval(contract[4]) contractDict['acceptingToken'] = accepting_selling_tokens[0] contractDict['sellingToken'] = accepting_selling_tokens[1] contractStructure = fetchContractStructure(contractDict['contractName'], contractDict['contractAddress']) if contractStructure['pricetype'] == 'dynamic': # temp fix if 'oracle_address' in contractStructure.keys(): contractDict['oracle_address'] = contractStructure['oracle_address'] contractDict['price'] = fetch_dynamic_swap_price(contractStructure, {'time': datetime.now().timestamp()}) else: contractDict['price'] = contractStructure['price'] elif contractDict['contractType'] == 'one-time-event': contractDict['tokenIdentification'] = contract[4] # pull the contract structure contractStructure = fetchContractStructure(contractDict['contractName'], contractDict['contractAddress']) # compare if 'payeeAddress' in contractStructure.keys(): contractDict['contractSubType'] = 'time-trigger' else: choice_list = [] for obj_key in contractStructure['exitconditions'].keys(): choice_list.append(contractStructure['exitconditions'][obj_key]) contractDict['userChoices'] = choice_list contractDict['contractSubType'] = 'external-trigger' contractDict['expiryDate'] = contract[9] contractDict['closeDate'] = contract[10] contractDict['transactionHash'] = contract[6] contractDict['blockNumber'] = contract[7] contractDict['incorporationDate'] = contract[8] contractList.append(contractDict) return contractList def smartContractInfo_output(contractName, contractAddress, contractType, subtype): if contractType == 'continuos-event' and contractType == 'tokenswap': pass elif contractType == 'one-time-event' and contractType == 'userchoice': pass elif contractType == 'one-time-event' and contractType == 'timetrigger': pass def return_smart_contracts(connection, contractName=None, contractAddress=None): # find all the contracts details if contractName and contractAddress: connection.execute("SELECT * FROM activecontracts WHERE id IN (SELECT max(id) FROM activecontracts GROUP BY contractName, contractAddress) AND contractName=? AND contractAddress=?", (contractName, contractAddress)) elif contractName and not contractAddress: connection.execute("SELECT * FROM activecontracts WHERE id IN (SELECT max(id) FROM activecontracts GROUP BY contractName, contractAddress) AND contractName=?", (contractName,)) elif not contractName and contractAddress: connection.execute("SELECT * FROM activecontracts WHERE id IN (SELECT max(id) FROM activecontracts GROUP BY contractName, contractAddress) AND contractAddress=?", (contractAddress,)) else: connection.execute("SELECT * FROM activecontracts WHERE id IN (SELECT max(id) FROM activecontracts GROUP BY contractName, contractAddress)") smart_contracts = connection.fetchall() return smart_contracts def create_database_connection(type, parameters=None): if type == 'token': filelocation = os.path.join(dbfolder, 'tokens', parameters['token_name']) elif type == 'smart_contract': contractDbName = '{}-{}.db'.format(parameters['contract_name'].strip(), parameters['contract_address'].strip()) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) elif type == 'system_dbs': filelocation = os.path.join(dbfolder, 'system.db') elif type == 'latest_cache': filelocation = os.path.join(dbfolder, 'latestCache.db') conn = sqlite3.connect(filelocation) c = conn.cursor() return [conn, c] def fetchContractStructure(contractName, contractAddress): # Make connection to contract database contractDbName = '{}-{}.db'.format(contractName.strip(),contractAddress.strip()) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): # fetch from contractStructure 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, c conn.close() if 'contractAmount' in contractStructure: contractStructure['contractAmount'] = float(contractStructure['contractAmount']) if 'payeeAddress' in contractStructure: contractStructure['payeeAddress'] = json.loads(contractStructure['payeeAddress']) if 'maximumsubscriptionamount' in contractStructure: contractStructure['maximumsubscriptionamount'] = float(contractStructure['maximumsubscriptionamount']) if 'minimumsubscriptionamount' in contractStructure: contractStructure['minimumsubscriptionamount'] = float(contractStructure['minimumsubscriptionamount']) if 'price' in contractStructure: contractStructure['price'] = float(contractStructure['price']) return contractStructure else: return 0 def fetchContractStatus(contractName, contractAddress): conn, c = create_database_connection('system_dbs') # select status from the last instance of activecontracts where match contractName and contractAddress c.execute(f'SELECT status FROM activecontracts WHERE contractName="{contractName}" AND contractAddress="{contractAddress}" ORDER BY id DESC LIMIT 1') status = c.fetchall() if len(status)==0: return None else: return status[0][0] def extract_ip_op_addresses(transactionJson): sender_address = transactionJson['vin'][0]['addresses'][0] receiver_address = None for utxo in transactionJson['vout']: if utxo['scriptPubKey']['addresses'][0] == sender_address: continue receiver_address = utxo['scriptPubKey']['addresses'][0] return sender_address, receiver_address def updatePrices(): prices = {} # USD -> INR response = requests.get(f"https://api.exchangerate-api.com/v4/latest/usd") price = response.json() prices['USDINR'] = price['rates']['INR'] # Blockchain stuff : BTC,FLO -> USD,INR # BTC->USD | BTC->INR response = requests.get(f"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,flo&vs_currencies=usd,inr") price = response.json() prices['BTCUSD'] = price['bitcoin']['usd'] prices['BTCINR'] = price['bitcoin']['inr'] # FLO->USD | FLO->INR response = requests.get(f"https://api.coinlore.net/api/ticker/?id=67") price = response.json() prices["FLOUSD"] = float(price[0]['price_usd']) prices["FLOINR"] = float(prices["FLOUSD"]) * float(prices['USDINR']) # 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() def fetch_dynamic_swap_price(contractStructure, blockinfo): oracle_address = contractStructure['oracle_address'] # fetch transactions from the blockchain where from address : oracle-address... to address: contract address # find the first contract transaction which adheres to price change format # {"price-update":{"contract-name": "", "contract-address": "", "price": 3}} print(f'oracle address is : {oracle_address}') response = requests.get(f'{apiUrl}api/v1/address/{oracle_address}?details=txs') if response.status_code == 200: response = response.json() if 'txs' not in response.keys(): # API doesn't return 'transactions' key, if 0 txs present on address return float(contractStructure['price']) else: transactions = response['txs'] for transaction in transactions: #transaction_response = requests.get(f'{apiUrl}api/v1/tx/{transaction_hash}') # if transaction_response.status_code == 200: floData = transaction['floData'] # If the blocktime of the transaction is < than the current block time if transaction['time'] < blockinfo['time']: # Check if flodata is in the format we are looking for # ie. {"price-update":{"contract-name": "", "contract-address": "", "price": 3}} # and receiver address should be contractAddress try: sender_address, receiver_address = find_sender_receiver(transaction) assert receiver_address == contractStructure['contractAddress'] assert sender_address == oracle_address floData = json.loads(floData) # Check if the contract name and address are right assert floData['price-update']['contract-name'] == contractStructure['contractName'] assert floData['price-update']['contract-address'] == contractStructure['contractAddress'] return float(floData['price-update']['price']) except: continue else: continue # else: # print('API error while fetch_dynamic_swap_price') # return None return float(contractStructure['price']) else: print('API error while fetch_dynamic_swap_price') return None def find_sender_receiver(transaction_data): # Create vinlist and outputlist vinlist = [] querylist = [] #totalinputval = 0 #inputadd = '' # todo Rule 40 - For each vin, find the feeding address and the fed value. Make an inputlist containing [inputaddress, n value] for vin in transaction_data["vin"]: vinlist.append([vin["addresses"][0], float(vin["value"])]) totalinputval = float(transaction_data["valueIn"]) # todo Rule 41 - Check if all the addresses in a transaction on the input side are the same for idx, item in enumerate(vinlist): if idx == 0: temp = item[0] continue if item[0] != temp: print(f"System has found more than one address as part of vin. Transaction {transaction_data['txid']} is rejected") return 0 inputlist = [vinlist[0][0], totalinputval] inputadd = vinlist[0][0] # todo Rule 42 - If the number of vout is more than 2, reject the transaction if len(transaction_data["vout"]) > 2: print(f"System has found more than 2 address as part of vout. Transaction {transaction_data['txid']} is rejected") return 0 # todo Rule 43 - A transaction accepted by the system has two vouts, 1. The FLO address of the receiver # 2. Flo address of the sender as change address. If the vout address is change address, then the other adddress # is the recevier address outputlist = [] addresscounter = 0 inputcounter = 0 for obj in transaction_data["vout"]: if obj["scriptPubKey"]["isAddress"] == True: addresscounter = addresscounter + 1 if inputlist[0] == obj["scriptPubKey"]["addresses"][0]: inputcounter = inputcounter + 1 continue outputlist.append([obj["scriptPubKey"]["addresses"][0], obj["value"]]) if addresscounter == inputcounter: outputlist = [inputlist[0]] elif len(outputlist) != 1: print(f"Transaction's change is not coming back to the input address. Transaction {transaction_data['txid']} is rejected") return 0 else: outputlist = outputlist[0] return inputlist[0], outputlist[0] def fetch_contract_status_time_info(contractName, contractAddress): conn, c = create_database_connection('system_dbs') c.execute('SELECT status, incorporationDate, expiryDate, closeDate FROM activecontracts WHERE contractName=="{}" AND contractAddress=="{}" ORDER BY id DESC LIMIT 1'.format(contractName, contractAddress)) contract_status_time_info = c.fetchall() return contract_status_time_info def checkIF_commitee_trigger_tranasaction(transactionDetails): if transactionDetails[3] == 'trigger': pass def transaction_post_processing(transactionJsonData): rowarray_list = [] for row in transactionJsonData: transactions_object = {} parsedFloData = json.loads(row[1]) transactionDetails = json.loads(row[0]) if row[3] in internalTransactionTypes or (row[3]=='trigger' and row[8]!='committee'): internal_info = {} internal_info['senderAddress'] = row[4] internal_info['receiverAddress'] = row[5] internal_info['tokenAmount'] = row[6] internal_info['tokenIdentification'] = row[7] internal_info['contractName'] = parsedFloData['contractName'] internal_info['transactionTrigger'] = transactionDetails['txid'] internal_info['time'] = transactionDetails['time'] internal_info['type'] = row[3] internal_info['onChain'] = False transactions_object = internal_info else: transactions_object = {**parsedFloData, **transactionDetails} transactions_object = update_transaction_confirmations(transactions_object) transactions_object['onChain'] = True rowarray_list.append(transactions_object) return rowarray_list def fetch_token_transactions(token, senderFloAddress=None, destFloAddress=None, limit=None, use_and=False): 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(description="Token doesn't exist"), 404 # Build the base SQL query query = f"SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '{token}' AS token, '' AS transactionSubType FROM transactionHistory" # Build the WHERE clause based on conditions conditions = [] parameters = {} if senderFloAddress and not destFloAddress: conditions.append('sourceFloAddress=:sender_flo_address') parameters['sender_flo_address'] = senderFloAddress elif not senderFloAddress and destFloAddress: conditions.append('destFloAddress=:dest_flo_address') parameters['dest_flo_address'] = destFloAddress elif senderFloAddress and destFloAddress: if use_and: conditions.append('sourceFloAddress=:sender_flo_address AND destFloAddress=:dest_flo_address') else: conditions.append('sourceFloAddress=:sender_flo_address OR destFloAddress=:dest_flo_address') parameters['sender_flo_address'] = senderFloAddress parameters['dest_flo_address'] = destFloAddress # Add the WHERE clause if conditions exist if conditions: query += ' WHERE {}'.format(' AND '.join(conditions)) # Add the LIMIT clause if specified if limit is not None: query += ' LIMIT :limit' parameters['limit'] = limit # Execute the query with parameters c.execute(query, parameters) transactionJsonData = c.fetchall() conn.close() return transaction_post_processing(transactionJsonData) def fetch_contract_transactions(contractName, contractAddress, _from=0, to=100): sc_file = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) conn = sqlite3.connect(sc_file) c = conn.cursor() # Find token db names and attach contractStructure = fetchContractStructure(contractName, contractAddress) if contractStructure['contractType'] == 'continuos-event': token1 = contractStructure['accepting_token'] token2 = contractStructure['selling_token'] token1_file = f"{dbfolder}/tokens/{token1}.db" token2_file = f"{dbfolder}/tokens/{token2}.db" conn.execute(f"ATTACH DATABASE '{token1_file}' AS token1db") conn.execute(f"ATTACH DATABASE '{token2_file}' AS token2db") transaction_query = f''' SELECT t1.jsonData, t1.parsedFloData, t1.time, t1.transactionType, t1.sourceFloAddress, t1.destFloAddress, t1.transferAmount, '{token1}' AS token, s.transactionSubType FROM main.contractTransactionHistory AS s INNER JOIN token1db.transactionHistory AS t1 ON t1.transactionHash = s.transactionHash UNION SELECT t2.jsonData, t2.parsedFloData, t2.time, t2.transactionType, t2.sourceFloAddress, t2.destFloAddress, t2.transferAmount, '{token2}' AS token, s.transactionSubType FROM main.contractTransactionHistory AS s INNER JOIN token2db.transactionHistory AS t2 ON t2.transactionHash = s.transactionHash WHERE s.id BETWEEN {_from} AND {to} ''' creation_tx_query = ''' SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '' AS token, transactionSubType FROM contractTransactionHistory ORDER BY id LIMIT 1; ''' elif contractStructure['contractType'] == 'one-time-event': token1 = contractStructure['tokenIdentification'] token1_file = f"{dbfolder}/tokens/{token1}.db" conn.execute(f"ATTACH DATABASE '{token1_file}' AS token1db") transaction_query = f''' SELECT t1.jsonData, t1.parsedFloData, t1.time, t1.transactionType, t1.sourceFloAddress, t1.destFloAddress, t1.transferAmount, '{token1}' AS token, s.transactionSubType FROM main.contractTransactionHistory AS s INNER JOIN token1db.transactionHistory AS t1 ON t1.transactionHash = s.transactionHash WHERE s.id BETWEEN {_from} AND {to} ''' creation_tx_query = ''' SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '' AS token, transactionSubType FROM contractTransactionHistory ORDER BY id LIMIT 1; ''' c.execute(transaction_query) transactionJsonData = c.fetchall() c.execute(creation_tx_query) creation_tx = c.fetchall() transactionJsonData = creation_tx + transactionJsonData return transaction_post_processing(transactionJsonData) def fetch_swap_contract_transactions(contractName, contractAddress, transactionHash=None): sc_file = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) conn = sqlite3.connect(sc_file) c = conn.cursor() # Find token db names and attach contractStructure = fetchContractStructure(contractName, contractAddress) token1 = contractStructure['accepting_token'] token2 = contractStructure['selling_token'] token1_file = f"{dbfolder}/tokens/{token1}.db" token2_file = f"{dbfolder}/tokens/{token2}.db" conn.execute(f"ATTACH DATABASE '{token1_file}' AS token1db") conn.execute(f"ATTACH DATABASE '{token2_file}' AS token2db") # Get data from db query = f''' SELECT t1.jsonData, t1.parsedFloData, t1.time, t1.transactionType, t1.sourceFloAddress, t1.destFloAddress, t1.transferAmount, '{token1}' AS token, t1.transactionSubType FROM main.contractTransactionHistory AS s INNER JOIN token1db.transactionHistory AS t1 ON t1.transactionHash = s.transactionHash AND s.transactionHash = '{transactionHash}' UNION SELECT t2.jsonData, t2.parsedFloData, t2.time, t2.transactionType, t2.sourceFloAddress, t2.destFloAddress, t2.transferAmount, '{token2}' AS token, t2.transactionSubType FROM main.contractTransactionHistory AS s INNER JOIN token2db.transactionHistory AS t2 ON t2.transactionHash = s.transactionHash AND s.transactionHash = '{transactionHash}' ''' '''if transactionHash: query += f" WHERE s.transactionHash = '{transactionHash}'"''' try: c.execute(query) except: pass transactionJsonData = c.fetchall() return transaction_post_processing(transactionJsonData) def sort_transactions(transactionJsonData): transactionJsonData = sorted(transactionJsonData, key=lambda x: x['time'], reverse=True) return transactionJsonData def process_committee_flodata(flodata): flo_address_list = [] try: contract_committee_actions = flodata['token-tracker']['contract-committee'] except KeyError: print('Flodata related to contract committee') else: # Adding first and removing later to maintain consistency and not to depend on floData for order of execution for action in contract_committee_actions.keys(): if action == 'add': for floid in contract_committee_actions[f'{action}']: flo_address_list.append(floid) for action in contract_committee_actions.keys(): if action == 'remove': for floid in contract_committee_actions[f'{action}']: flo_address_list.remove(floid) finally: return flo_address_list def refresh_committee_list(admin_flo_id, api_url, blocktime): committee_list = [] latest_param = 'true' mempool_param = 'false' init_id = None def process_transaction(transaction_info): if 'isCoinBase' in transaction_info or transaction_info['vin'][0]['addresses'][0] != admin_flo_id or transaction_info['blocktime'] > blocktime: return try: tx_flodata = json.loads(transaction_info['floData']) committee_list.extend(process_committee_flodata(tx_flodata)) except: pass def send_api_request(url): response = requests.get(url, verify=API_VERIFY) if response.status_code == 200: return response.json() else: print('Response from the Flosight API failed') sys.exit(0) url = f'{api_url}api/v1/address/{admin_flo_id}?details=txs' response = send_api_request(url) for transaction_info in response.get('txs', []): process_transaction(transaction_info) while 'incomplete' in response: url = f'{api_url}api/v1/address/{admin_flo_id}/txs?latest={latest_param}&mempool={mempool_param}&before={init_id}' response = send_api_request(url) for transaction_info in response.get('items', []): process_transaction(transaction_info) if 'incomplete' in response: init_id = response['initItem'] return committee_list @app.route('/') async def welcome_msg(): return jsonify('Welcome to RanchiMall FLO Api v2') @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') @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()) # FLO TOKEN APIs @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'.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'.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'.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') 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: transactions_object = {} transactions_object['transactionDetails'] = json.loads(row[0]) transactions_object['transactionDetails'] = update_transaction_confirmations(transactions_object['transactionDetails']) transactions_object['parsedFloData'] = json.loads(row[1]) rowarray_list[transactions_object['transactionDetails']['txid']] = transactions_object 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(description='floAddress hasn\'t been passed'), 400 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'.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: transactions_object = {} transactions_object['transactionDetails'] = json.loads(row[0]) transactions_object['transactionDetails'] = update_transaction_confirmations(transactions_object['transactionDetails']) transactions_object['parsedFloData'] = json.loads(row[1]) allTransactionList[transactions_object['transactionDetails']['txid']] = transactions_object 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') contractName = contractName.strip() contractAddress = contractAddress.strip() filelocation = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) if os.path.isfile(filelocation): # Make db connection and fetch data contractStructure = fetchContractStructure(contractName, contractAddress) conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) 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} 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]} 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]} elif contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': c.execute('SELECT * FROM contractparticipants') contract_participants = c.fetchall() returnval = {} for row in contract_participants: returnval[row[1]] = { 'participantFloAddress': row[1], 'participationAmount': row[2], 'swapPrice': float(row[3]), 'transactionHash': row[4], 'blockNumber': row[5], 'blockHash': row[6], 'swapAmount': row[7] } conn.close() 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) and os.path.isfile(contract_db): if os.path.isfile(dblocation): # Make db connection and fetch data conn = sqlite3.connect(dblocation) c = conn.cursor() if contractName is not None: c.execute(f'SELECT * FROM contractAddressMapping WHERE address="{floAddress}" AND addressType="participant" AND contractName="{contractName}" AND contractAddress="{contractAddress}"') else: c.execute(f'SELECT * FROM contractAddressMapping WHERE address="{floAddress}" AND addressType="participant"') participant_address_contracts = c.fetchall() if len(participant_address_contracts) != 0: participationDetailsList = [] for contract in participant_address_contracts: detailsDict = {} contract_db = os.path.join(dbfolder, 'smartContracts', f"{contract[3]}-{contract[4]}.db") # Make db connection and fetch contract structure conn = sqlite3.connect(contract_db) c = conn.cursor() # Get details of the type of Smart Contract 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 contractStructure['contractType']=='continuos-event' and contractStructure['subtype']=='tokenswap': # normal result + swap details # what is a api detail c.execute('SELECT * FROM contractparticipants WHERE participantAddress=?',(floAddress,)) participant_details = c.fetchall() if len(participant_details) > 0: participationList = [] for participation in participant_details: c.execute("SELECT value FROM contractstructure WHERE attribute='selling_token'") structure = c.fetchall() detailsDict['participationAddress'] = floAddress detailsDict['participationAmount'] = participation[2] detailsDict['receivedAmount'] = float(participation[3]) detailsDict['participationToken'] = contractStructure['accepting_token'] detailsDict['receivedToken'] = contractStructure['selling_token'] detailsDict['swapPrice_received_to_participation'] = float(participation[7]) detailsDict['transactionHash'] = participation[4] detailsDict['blockNumber'] = participation[5] detailsDict['blockHash'] = participation[6] participationList.append(detailsDict) participationDetailsList.append(participationList) elif contractStructure['contractType']=='one-time-event' and 'payeeAddress' in contractStructure.keys(): # normal results conn = sqlite3.connect(dblocation) c = conn.cursor() detailsDict = {} detailsDict['contractName'] = contract[3] detailsDict['contractAddress'] = contract[4] detailsDict['tokenAmount'] = contract[5] detailsDict['transactionHash'] = contract[6] 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 'payeeAddress' in contractStructure: # contract is of the type external trigger # check if the contract has been closed c.execute(f"SELECT tokenAmount FROM contractparticipants where participantAddress='{floAddress}'") result = c.fetchall() conn.close() detailsDict['tokenAmount'] = result[0][0] elif contractStructure['contractType']=='one-time-event' and 'exitconditions' in contractStructure.keys(): # normal results + winning/losing details conn = sqlite3.connect(dblocation) c = conn.cursor() detailsDict = {} detailsDict['contractName'] = contract[3] detailsDict['contractAddress'] = contract[4] detailsDict['tokenAmount'] = contract[5] detailsDict['transactionHash'] = contract[6] 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 participated 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: transactions_object = {} transactions_object['transactionDetails'] = json.loads(item[0]) transactions_object['transactionDetails'] = update_transaction_confirmations(transactions_object['transactionDetails']) transactions_object['parsedFloData'] = json.loads(item[1]) returnval[transactions_object['transactionDetails']['txid']] = transactions_object 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]) transactionJson = update_transaction_confirmations(transactionJson) 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['transactionDetails'] = update_transaction_confirmations(tx_parsed_details['transactionDetails']) 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) 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['transactionDetails'] = update_transaction_confirmations(tx_parsed_details['transactionDetails']) 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]) transactionJson = update_transaction_confirmations(transactionJson) 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('{}api/v1/tx/{}'.format(apiUrl, urlstring)) if response.status_code == 200: return jsonify(type='transaction') else: response = requests.get('{}api/v1/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') ################### ### VERSION 2 ### ################### @app.route('/api/v2/info', methods=['GET']) async def info(): # 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), 200 @app.route('/api/v2/broadcastTx/') async def broadcastTx_v2(raw_transaction_hash): p1 = subprocess.run(['flo-cli','sendrawtransaction',raw_transaction_hash], capture_output=True) return jsonify(args=p1.args,returncode=p1.returncode,stdout=p1.stdout.decode(),stderr=p1.stderr.decode()), 200 # FLO TOKEN APIs @app.route('/api/v2/tokenList', methods=['GET']) async def tokenList(): 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), 200 @app.route('/api/v2/tokenInfo/', methods=['GET']) async def tokenInfo(token): if token is None: return jsonify(description='Token name hasnt been passed'), 400 # todo : input validation dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: return jsonify(description="Token doesn't exist"), 404 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(token=token, incorporationAddress=incorporationRow[1], tokenSupply=incorporationRow[3], time=incorporationRow[6], blockchainReference=incorporationRow[7], activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList), 200 @app.route('/api/v2/tokenTransactions/', methods=['GET']) async def tokenTransactions(token): if token is None: return jsonify(description='Token name hasnt been passed'), 400 # Input validations senderFloAddress = request.args.get('senderFloAddress') if senderFloAddress is not None and not check_flo_address(senderFloAddress, is_testnet): return jsonify(description='senderFloAddress validation failed'), 400 destFloAddress = request.args.get('destFloAddress') if destFloAddress is not None and not check_flo_address(destFloAddress, is_testnet): return jsonify(description='destFloAddress validation failed'), 400 limit = request.args.get('limit') if limit is not None and not check_integer(limit): return jsonify(description='limit validation failed'), 400 use_AND = request.args.get('use_AND') if use_AND is not None and use_AND not in [True, False]: return jsonify(description='use_AND validation failed'), 400 _from = int(request.args.get('_from', 1)) # Get page number, default is 1 to = int(request.args.get('to', 100)) # Get limit, default is 10 if _from<1: return jsonify(description='_from validation failed'), 400 if to<1: return jsonify(description='to validation failed'), 400 filelocation = os.path.join(dbfolder, 'tokens', f'{token}.db') if os.path.isfile(filelocation): transactionJsonData = fetch_token_transactions(token, senderFloAddress, destFloAddress, limit, use_AND) sortedFormattedTransactions = sort_transactions(transactionJsonData) return jsonify(token=token, transactions=sortedFormattedTransactions), 200 else: return jsonify(description='Token with the given name doesn\'t exist'), 404 @app.route('/api/v2/tokenBalances/', methods=['GET']) async def tokenBalances(token): if token is None: return jsonify(description='Token name hasnt been passed'), 400 dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: return jsonify(description="Token doesn't exist"), 404 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(token=token, balances=returnList), 200 # FLO Address APIs @app.route('/api/v2/floAddressInfo/', methods=['GET']) async def floAddressInfo(floAddress): if floAddress is None: return jsonify(description='floAddress hasn\'t been passed'), 400 # input validation if not check_flo_address(floAddress, is_testnet): return jsonify(description='floAddress validation failed'), 400 dblocation = dbfolder + '/system.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() c.execute(f'SELECT token FROM tokenAddressMapping WHERE tokenAddress="{floAddress}"') tokenNames = c.fetchall() c.execute(f"SELECT contractName, status, tokenIdentification, contractType, transactionHash, blockNumber, blockHash FROM activecontracts WHERE contractAddress='{floAddress}'") incorporatedContracts = c.fetchall() detailList = None 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(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{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(description='FLO address is not associated with any tokens'), 404 incorporatedSmartContracts = None 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(floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=incorporatedSmartContracts), 200 @app.route('/api/v2/floAddressBalance/', methods=['GET']) async def floAddressBalance(floAddress): if floAddress is None: return jsonify(description='floAddress hasn\'t been passed'), 400 # input validation if not check_flo_address(floAddress, is_testnet): return jsonify(description='floAddress validation failed'), 400 token = request.args.get('token') if token is None: dblocation = dbfolder + '/system.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() c.execute(f'SELECT token FROM tokenAddressMapping WHERE tokenAddress="{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(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') balance = c.fetchall()[0][0] tempdict['balance'] = balance tempdict['token'] = token detailList[token] = tempdict return jsonify(floAddress=floAddress, floAddressBalances=detailList), 200 else: # Address is not associated with any token return jsonify(floAddress=floAddress, floAddressBalances={}), 200 else: dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: return jsonify(description="Token doesn't exist"), 404 c.execute(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') balance = c.fetchall()[0][0] conn.close() return jsonify(floAddress=floAddress, token=token, balance=balance), 200 @app.route('/api/v2/floAddressTransactions/', methods=['GET']) async def floAddressTransactions(floAddress): if floAddress is None: return jsonify(description='floAddress has not been passed'), 400 if not check_flo_address(floAddress, is_testnet): return jsonify(description='floAddress validation failed'), 400 limit = request.args.get('limit') if limit is not None and not check_integer(limit): return jsonify(description='limit validation failed'), 400 token = request.args.get('token') 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(description="Token doesn't exist"), 404 if len(tokenNames) != 0: allTransactionList = [] for tokenname in tokenNames: tokenname = tokenname[0] transactionJsonData = fetch_token_transactions(tokenname, senderFloAddress=floAddress, destFloAddress=floAddress, limit=limit) allTransactionList = allTransactionList + transactionJsonData sortedFormattedTransactions = sort_transactions(allTransactionList) if token is None: return jsonify(floAddress=floAddress, transactions=sortedFormattedTransactions), 200 else: return jsonify(floAddress=floAddress, transactions=sortedFormattedTransactions, token=token), 200 else: return jsonify(floAddress=floAddress, transactions=[], token=token), 200 # SMART CONTRACT APIs @app.route('/api/v2/smartContractList', methods=['GET']) async def getContractList_v2(): contractName = request.args.get('contractName') if contractName is not None: contractName = contractName.strip().lower() # todo - Add validation for contractAddress and contractName to prevent SQL injection attacks contractAddress = request.args.get('contractAddress') if contractAddress is not None: contractAddress = contractAddress.strip() if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 contractList = [] conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() smart_contracts = return_smart_contracts(c, contractName, contractAddress) smart_contracts_morphed = smartcontract_morph_helper(smart_contracts) conn.close() committeeAddressList = refresh_committee_list(APP_ADMIN, apiUrl, int(time.time())) return jsonify(smartContracts=smart_contracts_morphed, smartContractCommittee=committeeAddressList), 200 @app.route('/api/v2/smartContractInfo', methods=['GET']) async def getContractInfo_v2(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') if contractName is None: return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 contractName = contractName.strip().lower() if contractAddress is None: return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 contractAddress = contractAddress.strip() if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 contractStructure = fetchContractStructure(contractName, contractAddress) if contractStructure: returnval = contractStructure # Categorize into what type of contract it is right now if contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) c.execute('SELECT COUNT(participantAddress), SUM(tokenAmount), SUM(winningAmount) FROM contractparticipants') participation_details = c.fetchall() c.execute('SELECT depositAmount FROM contractdeposits') deposit_details = c.fetchall() returnval['numberOfParticipants'] = participation_details[0][0] returnval['totalParticipationAmount'] = participation_details[0][1] returnval['totalHonorAmount'] = participation_details[0][2] c.execute('SELECT COUNT(DISTINCT transactionHash) FROM contractdeposits') returnval['numberOfDeposits'] = c.fetchall()[0][0] c.execute('SELECT SUM(depositBalance) AS totalDepositBalance FROM contractdeposits c1 WHERE id = ( SELECT MAX(id) FROM contractdeposits c2 WHERE c1.transactionHash = c2.transactionHash);') returnval['currentDepositBalance'] = c.fetchall()[0][0] # todo - add code to token tracker to save continuos event subtype KEY as contractSubtype as part of contractStructure and remove the following line returnval['contractSubtype'] = 'tokenswap' returnval['priceType'] = returnval['pricetype'] if returnval['pricetype'] not in ['predetermined']: returnval['price'] = fetch_dynamic_swap_price(contractStructure, {'time': datetime.now().timestamp()}) returnval['acceptingToken'] = returnval['accepting_token'] returnval['sellingToken'] = returnval['selling_token'] elif contractStructure['contractType'] == 'one-time-event' and 'exitconditions' in contractStructure.keys(): choice_list = [] for obj_key in contractStructure['exitconditions'].keys(): choice_list.append(contractStructure['exitconditions'][obj_key]) returnval['userChoices'] = choice_list returnval.pop('exitconditions') contract_status_time_info = fetch_contract_status_time_info(contractName, contractAddress) if len(contract_status_time_info) == 1: for status_time_info in contract_status_time_info: returnval['status'] = status_time_info[0] returnval['incorporationDate'] = status_time_info[1] if status_time_info[2]: returnval['expiryDate'] = status_time_info[2] if status_time_info[3]: returnval['closeDate'] = status_time_info[3] # todo - add code to token tracker to save one-time-event subtype as part of contractStructure and remove the following line returnval['contractSubtype'] = 'external-trigger' elif contractStructure['contractType'] == 'one-time-event' and 'payeeAddress' in contractStructure.keys(): contract_status_time_info = fetch_contract_status_time_info(contractName, contractAddress) if len(contract_status_time_info) == 1: for status_time_info in contract_status_time_info: returnval['status'] = status_time_info[0] returnval['incorporationDate'] = status_time_info[1] if status_time_info[2]: returnval['expiryDate'] = status_time_info[2] if status_time_info[3]: returnval['closeDate'] = status_time_info[3] returnval['contractSubtype'] = 'time-trigger' return jsonify(contractName=contractName, contractAddress=contractAddress, contractInfo=returnval), 200 else: return jsonify(details="Smart Contract with the given name doesn't exist"), 404 @app.route('/api/v2/smartContractParticipants', methods=['GET']) async def getcontractparticipants_v2(): contractName = request.args.get('contractName') if contractName is None: return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 contractName = contractName.strip().lower() contractAddress = request.args.get('contractAddress') if contractAddress is None: return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 contractAddress = contractAddress.strip() if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 filelocation = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) if os.path.isfile(filelocation): # Make db connection and fetch data contractStructure = fetchContractStructure(contractName, contractAddress) contractStatus = fetchContractStatus(contractName, contractAddress) conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) if 'exitconditions' in contractStructure: # contract is of the type external trigger # check if the contract has been closed if contractStatus == 'closed': token = contractStructure['tokenIdentification'] c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash, winningAmount FROM contractparticipants') result = c.fetchall() returnval = [] for row in result: # Check value of winning amount c.execute(f'SELECT winningAmount FROM contractwinners WHERE referenceTxHash="{row[4]}"') participant_winningAmount = c.fetchall() if participant_winningAmount != []: participant_winningAmount = participant_winningAmount[0][0] else: participant_winningAmount = 0 participation = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4], 'winningAmount': participant_winningAmount, 'tokenIdentification': token} returnval.append(participation) else: c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') result = c.fetchall() conn.close() returnval = [] for row in result: participation = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4]} returnval.append(participation) return jsonify(contractName=contractName, contractAddress=contractAddress, contractType=contractStructure['contractType'], contractSubtype='external-trigger', participantInfo=returnval), 200 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: participation = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'transactionHash': row[4]} returnval.append(participation) return jsonify(contractName=contractName, contractAddress=contractAddress, contractType=contractStructure['contractType'], contractSubtype='time-trigger', participantInfo=returnval), 200 elif contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': c.execute('SELECT * FROM contractparticipants') contract_participants = c.fetchall() returnval = [] for row in contract_participants: participation = { 'participantFloAddress': row[1], 'participationAmount': row[2], 'swapPrice': float(row[3]), 'transactionHash': row[4], 'blockNumber': row[5], 'blockHash': row[6], 'swapAmount': row[7] } returnval.append(participation) conn.close() return jsonify(contractName=contractName, contractAddress=contractAddress, contractType=contractStructure['contractType'], contractSubtype=contractStructure['subtype'], participantInfo=returnval), 200 else: return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 @app.route('/api/v2/participantDetails/', methods=['GET']) async def participantDetails(floAddress): if floAddress is None: return jsonify(description='FLO address hasn\'t been passed'), 400 if not check_flo_address(floAddress, is_testnet): return jsonify(description='floAddress validation failed'), 400 contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') # Url param checking if contractName is None and contractAddress is None: return jsonify(description='Pass both, contractName and contractAddress as url parameters'), 400 elif contractName is None: return jsonify(description='Pass contractName as url parameter'), 400 else: return jsonify(description='Pass contractAddress as url parameter'), 400 if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 contractName = contractName.strip().lower() contractAddress = contractAddress.strip() systemdb_location = os.path.join(dbfolder, 'system.db') if os.path.isfile(systemdb_location): # Make db connection and fetch data systemdb_conn = sqlite3.connect(systemdb_location) c = systemdb_conn.cursor() if contractName is not None: c.execute(f'SELECT * FROM contractAddressMapping WHERE address="{floAddress}" AND addressType="participant" AND contractName="{contractName}" AND contractAddress="{contractAddress}"') else: c.execute(f'SELECT * FROM contractAddressMapping WHERE address="{floAddress}" AND addressType="participant"') participant_address_contracts = c.fetchall() if len(participant_address_contracts) != 0: participationDetailsList = [] for contract in participant_address_contracts: detailsDict = {} contract_db = os.path.join(dbfolder, 'smartContracts', f"{contract[3]}-{contract[4]}.db") # Make db connection and fetch contract structure contractdb_conn = sqlite3.connect(contract_db) contract_c = contractdb_conn.cursor() # Get details of the type of Smart Contract contract_c.execute('SELECT attribute,value FROM contractstructure') result = contract_c.fetchall() contractStructure = fetchContractStructure(contract[3], contract[4]) contractDbName = '{}-{}.db'.format(contract[3], contract[4]) if contractStructure['contractType']=='continuos-event' and contractStructure['subtype']=='tokenswap': # normal result + swap details # what is a api detail contract_c.execute('SELECT * FROM contractparticipants WHERE participantAddress=?',(floAddress,)) participant_details = contract_c.fetchall() if len(participant_details) > 0: participationList = [] for participation in participant_details: detailsDict['participationAddress'] = floAddress detailsDict['participationAmount'] = participation[2] detailsDict['receivedAmount'] = float(participation[3]) detailsDict['participationToken'] = contractStructure['accepting_token'] detailsDict['receivedToken'] = contractStructure['selling_token'] detailsDict['swapPrice_received_to_participation'] = float(participation[7]) detailsDict['transactionHash'] = participation[4] detailsDict['blockNumber'] = participation[5] detailsDict['blockHash'] = participation[6] participationList.append(detailsDict) participationDetailsList.append(participationList) elif contractStructure['contractType']=='one-time-event' and 'payeeAddress' in contractStructure.keys(): # normal results detailsDict = {} detailsDict['contractName'] = contract[3] detailsDict['contractAddress'] = contract[4] detailsDict['tokenAmount'] = contract[5] detailsDict['transactionHash'] = contract[6] 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 filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): if 'payeeAddress' in contractStructure: # contract is of the type external trigger # check if the contract has been closed contract_c.execute(f"SELECT tokenAmount FROM contractparticipants WHERE participantAddress='{floAddress}'") result = contract_c.fetchall() detailsDict['tokenAmount'] = result[0][0] elif contractStructure['contractType']=='one-time-event' and 'exitconditions' in contractStructure.keys(): # normal results + winning/losing details detailsDict = {} detailsDict['contractName'] = contract[3] detailsDict['contractAddress'] = contract[4] detailsDict['tokenAmount'] = contract[5] detailsDict['transactionHash'] = contract[6] 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 filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): # Make db connection and fetch data contract_c.execute('SELECT attribute,value FROM contractstructure') result = contract_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 if detailsDict['status'] == 'closed': contract_c.execute(f"SELECT userChoice, winningAmount FROM contractparticipants WHERE participantAddress='{floAddress}'") result = contract_c.fetchall() detailsDict['userChoice'] = result[0][0] detailsDict['winningAmount'] = result[0][1] else: contract_c.execute(f"SELECT userChoice FROM contractparticipants WHERE participantAddress='{floAddress}'") result = contract_c.fetchall() detailsDict['userChoice'] = result[0][0] participationDetailsList.append(detailsDict) return jsonify(floAddress=floAddress, type='participant', participatedContracts=participationDetailsList), 200 else: return jsonify(description="Address hasn't participated in any other contract"), 404 else: return jsonify(description='System error. System.db is missing. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api'), 500 @app.route('/api/v2/smartContractTransactions', methods=['GET']) async def smartcontracttransactions(): contractName = request.args.get('contractName') if contractName is None: return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 contractName = contractName.strip().lower() contractAddress = request.args.get('contractAddress') if contractAddress is None: return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 contractAddress = contractAddress.strip() if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 _from = int(request.args.get('_from', 1)) # Get page number, default is 1 to = int(request.args.get('to', 100)) # Get limit, default is 10 if _from<1: return jsonify(description='_from validation failed'), 400 if to<1: return jsonify(description='to validation failed'), 400 contractDbName = '{}-{}.db'.format(contractName, contractAddress) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): # Make db connection and fetch data transactionJsonData = fetch_contract_transactions(contractName, contractAddress, _from, to) transactionJsonData = sort_transactions(transactionJsonData) return jsonify(contractName=contractName, contractAddress=contractAddress, contractTransactions=transactionJsonData), 200 else: return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 # todo - add options to only ask for active/consumed/returned deposits @app.route('/api/v2/smartContractDeposits', methods=['GET']) async def smartcontractdeposits(): # todo - put validation for transactionHash contractName = request.args.get('contractName') if contractName is None: return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 contractName = contractName.strip().lower() contractAddress = request.args.get('contractAddress') if contractAddress is None: return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 contractAddress = contractAddress.strip() if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 contractDbName = '{}-{}.db'.format(contractName, contractAddress) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): # active deposits conn = sqlite3.connect(filelocation) c = conn.cursor() c.execute('''SELECT depositorAddress, transactionHash, status, depositBalance FROM contractdeposits WHERE (transactionHash, id) IN (SELECT transactionHash, MAX(id) FROM contractdeposits GROUP BY transactionHash) ORDER BY id DESC; ''') distinct_deposits = c.fetchall() deposit_info = [] for a_deposit in distinct_deposits: #c.execute(f"SELECT depositBalance FROM contractdeposits WHERE (transactionHash, id) IN (SELECT transactionHash, MIN(id) FROM contractdeposits GROUP BY transactionHash );") c.execute(f"SELECT depositBalance, unix_expiryTime FROM contractdeposits WHERE transactionHash=='{a_deposit[1]}' ORDER BY id LIMIT 1") original_deposit_balance = c.fetchall() obj = { 'depositorAddress': a_deposit[0], 'transactionHash': a_deposit[1], 'status': a_deposit[2], 'originalBalance': original_deposit_balance[0][0], 'currentBalance': a_deposit[3], 'time': original_deposit_balance[0][1] } deposit_info.append(obj) c.execute('SELECT SUM(depositBalance) AS totalDepositBalance FROM contractdeposits c1 WHERE id = ( SELECT MAX(id) FROM contractdeposits c2 WHERE c1.transactionHash = c2.transactionHash);') currentDepositBalance = c.fetchall()[0][0] return jsonify(currentDepositBalance=currentDepositBalance, depositInfo=deposit_info), 200 else: return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 @app.route('/api/v2/blockDetails/', methods=['GET']) async def blockdetails(blockHash): # todo - validate blockHash blockJson = blockdetailhelper(blockHash) if len(blockJson) != 0: blockJson = json.loads(blockJson[0][0]) return jsonify(blockDetails=blockJson), 200 else: return jsonify(description='Block doesn\'t exist in database'), 404 @app.route('/api/v2/transactionDetails/', methods=['GET']) async def transactiondetails1(transactionHash): # todo - validate transactionHash transactionJsonData = transactiondetailhelper(transactionHash) if len(transactionJsonData) != 0: transactionJson = json.loads(transactionJsonData[0][0]) transactionJson = update_transaction_confirmations(transactionJson) parseResult = json.loads(transactionJsonData[0][1]) operation = transactionJsonData[0][2] db_reference = transactionJsonData[0][3] sender_address, receiver_address = extract_ip_op_addresses(transactionJson) mergeTx = {**parseResult, **transactionJson} # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions mergeTx['onChain'] = True operationDetails = {} if operation == 'smartContractDeposit': # open the db reference and check if there is a deposit return conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") c = conn.cursor() c.execute("SELECT depositAmount, blockNumber FROM contractdeposits WHERE status='deposit-return' AND transactionHash=?",(transactionJson['txid'],)) returned_deposit_tx = c.fetchall() if len(returned_deposit_tx) == 1: operationDetails['returned_depositAmount'] = returned_deposit_tx[0][0] operationDetails['returned_blockNumber'] = returned_deposit_tx[0][1] c.execute("SELECT depositAmount, blockNumber FROM contractdeposits WHERE status='deposit-honor' AND transactionHash=?",(transactionJson['txid'],)) deposit_honors = c.fetchall() operationDetails['depositHonors'] = {} operationDetails['depositHonors']['list'] = [] operationDetails['depositHonors']['count'] = len(deposit_honors) for deposit_honor in deposit_honors: operationDetails['depositHonors']['list'].append({'honor_amount':deposit_honor[0],'blockNumber':deposit_honor[1]}) c.execute("SELECT depositBalance FROM contractdeposits WHERE id=(SELECT max(id) FROM contractdeposits WHERE transactionHash=?)",(transactionJson['txid'],)) depositBalance = c.fetchall() operationDetails['depositBalance'] = depositBalance[0][0] operationDetails['consumedAmount'] = parseResult['depositAmount'] - operationDetails['depositBalance'] elif operation == 'tokenswap-participation': conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") c = conn.cursor() c.execute('SELECT tokenAmount, winningAmount, userChoice FROM contractparticipants WHERE transactionHash=?',(transactionJson['txid'],)) swap_amounts = c.fetchall() c.execute("SELECT value FROM contractstructure WHERE attribute='selling_token'") structure = c.fetchall() operationDetails['participationAmount'] = swap_amounts[0][0] operationDetails['receivedAmount'] = swap_amounts[0][1] operationDetails['participationToken'] = parseResult['tokenIdentification'] operationDetails['receivedToken'] = structure[0][0] operationDetails['swapPrice_received_to_participation'] = float(swap_amounts[0][2]) elif operation == 'smartContractPays': # Find what happened because of the trigger # Find who conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") c = conn.cursor() c.execute('SELECT participantAddress, tokenAmount, userChoice, winningAmount FROM contractparticipants WHERE winningAmount IS NOT NULL') winner_participants = c.fetchall() if len(winner_participants) != 0: operationDetails['total_winners'] = len(winner_participants) operationDetails['winning_choice'] = winner_participants[0][2] operationDetails['winner_list'] = [] for participant in winner_participants: winner_details = {} winner_details['participantAddress'] = participant[0] winner_details['participationAmount'] = participant[1] winner_details['winningAmount'] = participant[3] operationDetails['winner_list'].append(winner_details) elif operation == 'ote-externaltrigger-participation': # Find if this guy has won conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") c = conn.cursor() c.execute('SELECT winningAmount FROM contractparticipants WHERE transactionHash=?',(transactionHash,)) winningAmount = c.fetchall() if winningAmount[0][0] is not None: operationDetails['winningAmount'] = winningAmount[0][0] elif operation == 'tokenswapParticipation': contractName, contractAddress = db_reference.rsplit('-',1) conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") c = conn.cursor() txhash_txs = fetch_swap_contract_transactions(contractName, contractAddress, transactionHash) mergeTx['subTransactions'] = [] for transaction in txhash_txs: if transaction['onChain'] == False: mergeTx['subTransactions'].append(transaction) mergeTx['operation'] = operation mergeTx['operationDetails'] = operationDetails return jsonify(mergeTx), 200 else: return jsonify(description='Transaction doesn\'t exist in database'), 404 @app.route('/api/v2/latestTransactionDetails', methods=['GET']) async def latestTransactionDetails(): limit = request.args.get('limit') if limit is not None and not check_integer(limit): return jsonify(description='limit validation failed'), 400 dblocation = dbfolder + '/latestCache.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: return jsonify(description='Latest transactions db doesn\'t exist. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api'), 500 if limit is not None: c.execute('SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT {}) ORDER BY id DESC;'.format(int(limit))) else: c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC) ORDER BY id DESC;''') latestTransactions = c.fetchall() c.close() tx_list = [] for idx, item in enumerate(latestTransactions): item = list(item) tx_parsed_details = {} tx_parsed_details['transactionDetails'] = json.loads(item[3]) tx_parsed_details['transactionDetails'] = update_transaction_confirmations(tx_parsed_details['transactionDetails']) tx_parsed_details['parsedFloData'] = json.loads(item[5]) tx_parsed_details['parsedFloData']['transactionType'] = item[4] tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) tx_parsed_details = {**tx_parsed_details['transactionDetails'], **tx_parsed_details['parsedFloData']} # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions tx_parsed_details['onChain'] = True tx_list.append(tx_parsed_details) return jsonify(latestTransactions=tx_list), 200 @app.route('/api/v2/latestBlockDetails', methods=['GET']) async def latestBlockDetails(): limit = request.args.get('limit') if limit is not None and not check_integer(limit): return jsonify(description='limit validation failed'), 400 dblocation = dbfolder + '/latestCache.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: return jsonify(description='Latest transactions db doesn\'t exist. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api'), 404 if limit is None: c.execute('''SELECT jsonData FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT 4) ORDER BY id DESC;''') else: c.execute(f'SELECT jsonData FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT {limit}) ORDER BY id DESC;') latestBlocks = c.fetchall() c.close() templst = [] for idx, item in enumerate(latestBlocks): templst.append(json.loads(item[0])) return jsonify(latestBlocks=templst), 200 @app.route('/api/v2/blockTransactions/', methods=['GET']) async def blocktransactions(blockHash): blockJson = blockdetailhelper(blockHash) if len(blockJson) != 0: blockJson = json.loads(blockJson[0][0]) blocktxlist = blockJson['txs'] blocktxs = [] for i in range(len(blocktxlist)): temptx = transactiondetailhelper(blocktxlist[i]['txid']) transactionJson = json.loads(temptx[0][0]) parseResult = json.loads(temptx[0][1]) blocktxs.append({**parseResult , **transactionJson}) # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions #blocktxs['onChain'] = True return jsonify(transactions=blocktxs, blockKeyword=blockHash), 200 else: return jsonify(description='Block doesn\'t exist in database'), 404 @app.route('/api/v2/categoriseString/') async def categoriseString_v2(urlstring): # check if the hash is of a transaction response = requests.get('{}api/v1/tx/{}'.format(apiUrl, urlstring)) if response.status_code == 200: return jsonify(type='transaction'), 200 else: response = requests.get('{}api/v1/block/{}'.format(apiUrl, urlstring)) if response.status_code == 200: return jsonify(type='block'), 200 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'), 200 else: systemdb = os.path.join(dbfolder, 'system.db') conn = sqlite3.connect(systemdb) 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'), 200 else: return jsonify(type='noise'), 200 @app.route('/api/v2/tokenSmartContractList', methods=['GET']) async def tokenSmartContractList(): # 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 contractName = request.args.get('contractName') if contractName is not None: contractName = contractName.strip().lower() # todo - Add validation for contractAddress and contractName to prevent SQL injection attacks contractAddress = request.args.get('contractAddress') if contractAddress is not None: contractAddress = contractAddress.strip() if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() smart_contracts = return_smart_contracts(c, contractName, contractAddress) smart_contracts_morphed = smartcontract_morph_helper(smart_contracts) conn.close() committeeAddressList = refresh_committee_list(APP_ADMIN, apiUrl, int(time.time())) return jsonify(tokens=filelist, smartContracts=smart_contracts_morphed, smartContractCommittee=committeeAddressList), 200 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('/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/v2/prices', methods=['GET']) async def priceData(): # 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), 200 ####################### ####################### # 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=7200) scheduler.start() # Shut down the scheduler when exiting the app atexit.register(lambda: scheduler.shutdown()) if __name__ == "__main__": app.run(debug=debug_status, host=HOST, port=PORT)