ranchimallflo-api/ranchimallflo_api.py

2660 lines
128 KiB
Python

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):
# 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/addr/{oracle_address}')
if response.status_code == 200:
response = response.json()
if 'transactions' not in response.keys(): # API doesn't return 'transactions' key, if 0 txs present on address
return float(contractStructure['price'])
else:
transactions = response['transactions']
for transaction_hash in transactions:
transaction_response = requests.get(f'{apiUrl}api/v1/tx/{transaction_hash}')
if transaction_response.status_code == 200:
transaction = transaction_response.json()
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["addr"], 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"]["type"] == "pubkeyhash":
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/<raw_transaction_hash>')
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/<blockdetail>', 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/<transactionHash>', 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/<blockdetail>', 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/<urlstring>')
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/<raw_transaction_hash>')
async def broadcastTx_v2(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()), 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/<token>', 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/<token>', 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/<token>', 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/<floAddress>', 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/<floAddress>', 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/<floAddress>', 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/<floAddress>', 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')
if contractAddress is not None and not check_flo_address(contractAddress, is_testnet):
return jsonify(description='contractAddress validation failed'), 400
contractAddress = contractAddress.strip()
if (contractName and contractAddress is None) or (contractName is None and contractAddress):
return jsonify(description='pass both, contractName and contractAddress as url parameters'), 400
contractName = contractName.strip().lower()
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/<blockHash>', 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/<transactionHash>', 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/<blockHash>', 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/<urlstring>')
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=600)
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)