Skip to content

Instantly share code, notes, and snippets.

@OlegJakushkin
Last active April 24, 2024 15:29
Show Gist options
  • Select an option

  • Save OlegJakushkin/87c6054dc838ff802f8c4481122afd5b to your computer and use it in GitHub Desktop.

Select an option

Save OlegJakushkin/87c6054dc838ff802f8c4481122afd5b to your computer and use it in GitHub Desktop.
import base64
import logging
import os
import random
import tempfile
from threading import Thread
from string_templates import base_api
import openai
from diskcache import Cache
from flask import Flask, render_template, jsonify, request
from main_openai_queries import process_complete_task
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[logging.FileHandler('GenerateExamples.log'),
logging.StreamHandler()])
openai.api_key = # TODO: insert YOURS OpenAI API Key! 'sk-...'
app = Flask(__name__)
script_dir = os.path.dirname(os.path.abspath(__file__))
cache_dir = os.path.join(script_dir, 'cache')
os.makedirs(cache_dir, exist_ok=True)
cache = Cache(cache_dir)
app.config["SESSION_PERMANENT"] = False
app.config["SESSION_TYPE"] = "filesystem"
# Thread-safe dictionary to store results
results = {}
# Function to encode images to base64
def encode_image(image_path):
with open(image_path, "rb") as image_file:
return base64.b64encode(image_file.read()).decode('utf-8')
# Home route that renders the index.html template
@app.route('/data', methods=['GET'])
def home():
session_id = request.args.get('session_id', default=str(random.randint(1000, 9999)))
request_text = request.args.get('query', "give me info on account 0xDa12b368a93007Ef2446717765917933cEBC6080")
task = request_text
# Start a new background thread to handle the long-running task
thread = Thread(target=process_task, args=(task, session_id))
thread.start()
# Return an immediate response indicating the task is underway
return jsonify({"message": "Task has started", "session_id": session_id})
@app.route('/status', methods=['GET'])
def check_status():
session_id = request.args.get('session_id')
if session_id in results:
return jsonify(results[session_id])
else:
return jsonify({"message": "Task not found or not completed"})
def process_task(task, session_id):
cache_key = f"{task}"
if cache_key in cache:
results[session_id] = cache[cache_key]
return
# Retrieve the request_text from the query parameters
temp_dir = tempfile.mkdtemp()
# Step 2: Change working directory to the temporary directory
original_dir = os.getcwd() # Store the original directory
os.chdir(temp_dir)
process_complete_task(task)
# Process the request_text to fetch images and response_text as needed
directory = '.'
png_files = [file for file in os.listdir(directory) if file.endswith('.png')]
if len(png_files) <= 0:
result = {
'images': [],
'response_text': "Please try another query",
'request_text': task
}
results[session_id] = result
return
files_to_look_at = min(4, len(png_files))
selected_files = random.sample(png_files, files_to_look_at) # Ensure you have at least 4 .png files
images = []
# Prepare messages list with initial text message
messages = [
{
"role": "user",
"content": [],
}
]
# Add each image to the messages list
for file in selected_files:
base64_image = encode_image(os.path.join(directory, file))
images.append(f"data:image/png;base64,{base64_image}")
messages[0]["content"].append(
{
"type": "image_url",
"image_url": {
"url": f"data:image/jpeg;base64,{base64_image}"
}
}
)
messages[0]["content"].append({
"type": "text",
f"text": base_api + "\n"
f"""Given initial user query '{task}' we have generated attached images. Using them create a summary answer for the user query in the following format:
- 2-3 sentances summarising a response to the initial query based on what can be seen from the images.
- Create a list of 3 queries a user could ask as a followup on this subject using our Forta API
Be polite and try hard to answer initial users query. Do not mention the images/charts explicetly in your reply. Create human readable questions not explicetly mentioning Forta API while knowing they can be answered using it. Always use absolute names and addresses values (do not use stuff like "this address")
Your response shall be in proper insertable HTML <div>. Each of your suggestions shall be faomated as <a href="/?query=suggestion%20as%20text" target="_blank">suggestion as text</a> so that user could click on them.""",
})
response = openai.ChatCompletion.create(
model="gpt-4-turbo",
messages=messages,
max_tokens=4000,
timeout=180
)
response_text = response['choices'][0]['message']['content']
os.chdir(original_dir)
result = {
'images': images,
'response_text': response_text,
'request_text': task
}
cache[cache_key] = result
results[session_id] = result
return
@app.route('/', methods=['GET'])
def index():
# Retrieve the initial request text from query parameters if available
initial_request = request.args.get('query', "give me info on account 0xDa12b368a93007Ef2446717765917933cEBC6080")
return render_template('ui.html', initial_request=initial_request)
if __name__ == '__main__':
app.run(debug=True, use_reloader=False)
  1. Install
pip install -U PyYAML orjson  ujson jsonlines openai==0.28 kaleido plotly forta_agent networkx Cython flask flask_session diskcache
  1. If missing please also install matplotlib

  2. Insert your OpenAI API key into flaskservice.py and in main_openai_queries.py

  3. Insert your Forta API key into main_openai_queries.py

  4. place templates_ui.html into templates/ui.html

  5. Start it up!)

#!pip install -U PyYAML orjson ujson jsonlines openai==0.28 kaleido plotly forta_agent networkx Cython flask flask_session diskcache
# %%time
import jsonlines
import random
import json
import os
import openai
import concurrent.futures
import traceback
import logging
import itertools
import time
import yaml
import ujson
import orjson
import queue
import threading
from string_templates import *
logging.basicConfig(level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s',
handlers=[logging.FileHandler('GenerateExamples.log'),
logging.StreamHandler()])
openai.api_key = # TODO: insert YOURS OpenAI API Key! 'sk-...'
import os
api_key = # TODO: insert YOURS FORTA_API_KEY! '8...:...'
os.environ["FORTA_API_KEY"] = api_key
def parse_json_string(s):
try:
return yaml.safe_load(s)
except:
pass
try:
return json.loads(s)
except:
pass
try:
return ujson.loads(s)
except:
pass
return orjson.loads(s)
def process_task_query(task, i=0):
i = i + 1
task_text = generate_tasks(task)
response = None
record = {}
try:
messages = [
{"role": "user",
"content": task_text
}]
functions = [
{
"name": "print",
"description": "print tasks",
"parameters": {
"type": "object",
"properties": {
"tasks": {
"type": "array",
"items": {
"type": "object",
"properties": {
"Name": {
"type": "string",
"description": "Task Name, Number"
},
"Task": {
"type": "string",
"description": "what shall be done using graphql forta api"
},
"Visualization": {
"type": "string",
"description": "how tasks results shall be depicted "
}
}
},
"description": "create tasks"
}
}
}
}
]
response = openai.ChatCompletion.create(
model="gpt-4-turbo",
messages=messages,
functions=functions,
max_tokens=4000,
function_call="auto",
timeout=180
)
# print( response)
response_message = response["choices"][0]["message"]
if response_message.get("function_call"):
function_name = response_message["function_call"]["name"]
function_args = parse_json_string(response_message["function_call"]["arguments"])
if function_args.get("tasks",None) is None:
raise "no subtasks retry!"
record = function_args
#record["src"] = f"{task}"
#logging.info(f"Processed record {task}.")
except Exception as e:
logging.error(f"Failed processing record {task}.")
logging.error(f"Failed processing record {response}.")
trace = traceback.format_exc()
logging.error("Caught an error: {}\nTraceback:\n{}".format(e, trace))
logging.info("Retrying...")
prefixes = ['Balance length and complexity', "Add questions"]
if "openai" in str(e).lower():
retry_wait_time = 0
if "Please try again in" in str(e):
try:
retry_wait_time = int(str(e).split('Please try again in')[1].split('ms')[0].strip())
except (ValueError, IndexError):
retry_wait_time = 30000 * i # Default to 1 second if parsing fails
else:
retry_wait_time = 30000 * i
# Convert milliseconds to seconds, add a small buffer (e.g., 100ms)
wait_time_seconds = (retry_wait_time + 100) / 1000.0
try:
if i < 2:
record = process_task_query("Please try hard to decompose into an array of subtasks and implement this: " +task, i)
except Exception as e:
logging.error(f"Failed retrying record {task}. Error: {str(e)}")
return record
def process_code_query(task, i=0):
i = i + 1
response = None
record = {}
try:
messages = [
{"role": "user",
"content": task
}]
functions = [
{
"name": "print",
"description": "print code",
"parameters": {
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "complete exacutable code of plot_task function"
}
}
}
}
]
response = openai.ChatCompletion.create(
model="gpt-4-turbo",
messages=messages,
functions=functions,
max_tokens=4000,
function_call="auto",
timeout=180
)
# print( response)
response_message = response["choices"][0]["message"]
if response_message.get("function_call"):
function_name = response_message["function_call"]["name"]
function_args = parse_json_string(response_message["function_call"]["arguments"])
record = function_args
#record["src"] = f"{task}"
#logging.info(f"Processed record {task}.")
except Exception as e:
logging.error(f"Failed processing record {task}.")
logging.error(f"Failed processing record {response}.")
trace = traceback.format_exc()
logging.error("Caught an error: {}\nTraceback:\n{}".format(e, trace))
logging.info("Retrying...")
prefixes = ['Balance length and complexity', "Add questions"]
if "openai" in str(e).lower():
retry_wait_time = 0
if "Please try again in" in str(e):
try:
retry_wait_time = int(str(e).split('Please try again in')[1].split('ms')[0].strip())
except (ValueError, IndexError):
retry_wait_time = 30000 * i # Default to 1 second if parsing fails
else:
retry_wait_time = 30000 * i
# Convert milliseconds to seconds, add a small buffer (e.g., 100ms)
wait_time_seconds = (retry_wait_time + 100) / 1000.0
try:
if i < 2:
record = process_task_query(task, i)
except Exception as e:
logging.error(f"Failed retrying record {task}. Error: {str(e)}")
return record
import time
import pprint
def run_code(code_to_run):
prefix = """
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import networkx as nx
import requests
import os
forta_api = "https://api.forta.network/graphql"
headers = {"content-type": "application/json"}
headers["Authorization"] = f'Bearer {os.environ["FORTA_API_KEY"]}'
"""
suffix = """
plot_task()
"""
programm = prefix + code_to_run["code"] + suffix
exec(programm)
import concurrent.futures
def process_single_task(task, i, t):
try:
time.sleep(i)
print(f"sending code gen task {i}")
code_task = generate_code(task, t, i)
code = process_code_query(code_task)
run_code(code)
except Exception as e:
# Retry
try:
time.sleep(1)
print(f"sending code gen task {i}")
code_task = generate_code(task, t, i)
code = process_code_query(code_task)
run_code(code)
except Exception as e:
print(f"task {i} failed: {str(e)}")
pprint.pprint(task)
print(code["code"])
def process_complete_task(task = "give me info on account 0xDa12b368a93007Ef2446717765917933cEBC6080"):
print("sending initial task")
tasks = process_task_query(task)
if tasks.get("tasks", None) is None:
print("retry!")
tasks = process_task_query(task)
# Use ThreadPoolExecutor to parallelize tasks
with concurrent.futures.ThreadPoolExecutor() as executor:
futures = {executor.submit(process_single_task, task, i, t): t for i, t in enumerate(tasks["tasks"])}
for future in concurrent.futures.as_completed(futures):
t = futures[future]
try:
future.result()
except Exception as e:
print(f"Task {t} resulted in an error: {str(e)}")
base_api = """use forta graphql API
Forta API GraphQL API Reference
Free GraphQL API access to alerts and blockchain projects data
Terms of Service
https://forta.org/terms-of-use/
API ENDPOINTS
https://api.forta.network/graphql
Forta API Support
The API is in beta mode. If you see any bugs or issues, please let us know at github:forta-network/forta-api or discord.
Queries
alerts
Description
Fetches alerts
Response
Returns an AlertsResponse
Arguments
Name Description
input - AlertsInput
Example
QUERY
query alerts($input: AlertsInput) {
alerts(input: $input) {
alerts {
...AlertFragment
}
pageInfo {
...AlertPageInfoFragment
}
}
}
VARIABLES
{"input": AlertsInput}
RESPONSE
{
"data": {
"alerts": {
"alerts": [Alert],
"pageInfo": AlertPageInfo
}
}
}
Queries
batch
Description
Fetches a batch given its batch ID
Response
Returns a Batch
Arguments
Name Description
id - String!
Example
QUERY
query batch($id: String!) {
batch(id: $id) {
id
parentId
createdAt
chainId
scanNodeId
receiptId
}
}
VARIABLES
{"id": "xyz789"}
RESPONSE
{
"data": {
"batch": {
"id": "abc123",
"parentId": "xyz789",
"createdAt": "xyz789",
"chainId": 123,
"scanNodeId": "xyz789",
"receiptId": "abc123"
}
}
}
Queries
batches
Description
Fetches batches
Response
Returns a BatchesResponse
Arguments
Name Description
input - BatchesInput
Example
QUERY
query batches($input: BatchesInput) {
batches(input: $input) {
batches {
...BatchFragment
}
pageInfo {
...BatchPageInfoFragment
}
}
}
VARIABLES
{"input": BatchesInput}
RESPONSE
{
"data": {
"batches": {
"batches": [Batch],
"pageInfo": BatchPageInfo
}
}
}
Queries
bots
Description
Fetches bots
Response
Returns a BotsResponse
Arguments
Name Description
input - BotsInput
Example
QUERY
query bots($input: BotsInput) {
bots(input: $input) {
bots {
...BotFragment
}
pageInfo {
...BotPageInfoFragment
}
}
}
VARIABLES
{"input": BotsInput}
RESPONSE
{
"data": {
"bots": {
"bots": [Bot],
"pageInfo": BotPageInfo
}
}
}
Queries
labels
Description
Fetches labels
Response
Returns a LabelsResponse
Arguments
Name Description
input - LabelsInput
Example
QUERY
query labels($input: LabelsInput) {
labels(input: $input) {
labels {
...LabelEventFragment
}
pageInfo {
...LabelPageInfoFragment
}
}
}
VARIABLES
{"input": LabelsInput}
RESPONSE
{
"data": {
"labels": {
"labels": [LabelEvent],
"pageInfo": LabelPageInfo
}
}
}
Queries
project
Description
Fetches a web3 project given its project ID
Response
Returns a Project
Arguments
Name Description
id - String!
Example
QUERY
query project($id: String!) {
project(id: $id) {
contacts {
...ContactsFragment
}
name
id
social {
...SocialFragment
}
token {
...TokenFragment
}
website
}
}
VARIABLES
{"id": "xyz789"}
RESPONSE
{
"data": {
"project": {
"contacts": Contacts,
"name": "abc123",
"id": "xyz789",
"social": Social,
"token": Token,
"website": "abc123"
}
}
}
Queries
projects
Description
Fetches all existing web3 projects recorded in github repo: https://github.com/ethereum-lists/contracts/tree/main/projects
Response
Returns a ProjectsResponse
Arguments
Name Description
input - ProjectsInput
Example
QUERY
query projects($input: ProjectsInput) {
projects(input: $input) {
edges {
...ProjectEdgeFragment
}
pageInfo {
...ProjectPageInfoFragment
}
}
}
VARIABLES
{"input": ProjectsInput}
RESPONSE
{
"data": {
"projects": {
"edges": [ProjectEdge],
"pageInfo": ProjectPageInfo
}
}
}
Types
Alert
Description
Alert information
Fields
Field Name Description
alertId - String Unique string to identify this class of finding, primarily used to group similar findings for the end user
addresses - [String] List of addresses involved in the alert
contracts - [Contract] List of contracts related to the alert
createdAt - String Timestamp when the alert was published
description - String Text description of the alert
hash - String Alert hash identifier
metadata - JSONObject Extra alert information
name - String Alert name
projects - [Project] List of Web3 projects related to the alert
protocol - String Name of protocol being reported on
scanNodeCount - Int Number of scanners that found the alert
severity - String
Impact level of finding
CRITICAL - Exploitable vulnerabilities, massive impact on users/funds
HIGH - Exploitable under more specific conditions, significant impact on users/funds
MEDIUM - Notable unexpected behaviours, moderate to low impact on users/funds
LOW - Minor oversights, negligible impact on users/funds
INFO - Miscellaneous behaviours worth describing
source - AlertSource Source where the alert was detected
findingType - String
Type of finding
UNKNOWN_TYPE
EXPLOIT
SUSPICIOUS
DEGRADED
INFO
alertDocumentType - String Type of alert UNKNOWN PRIVATE BLOCK TRANSACTION COMBINATION
relatedAlerts - [String] List of alerts involved in the alert
chainId - NonNegativeInt Chain id
labels - [Label] List of labels related to the alert
addressBloomFilter - BloomFilter Bloom filter for addresses
truncated - Boolean Flags alert as truncated for size purposes
EXAMPLE
{
"alertId": "abc123",
"addresses": ["abc123"],
"contracts": [Contract],
"createdAt": "abc123",
"description": "abc123",
"hash": "abc123",
"metadata": {},
"name": "xyz789",
"projects": [Project],
"protocol": "xyz789",
"scanNodeCount": 987,
"severity": "xyz789",
"source": AlertSource,
"findingType": "xyz789",
"alertDocumentType": "abc123",
"relatedAlerts": ["abc123"],
"chainId": 123,
"labels": [Label],
"addressBloomFilter": BloomFilter,
"truncated": true
}
Types
AlertEndCursor
Fields
Field Name Description
blockNumber - NonNegativeInt!
alertId - String!
EXAMPLE
{"blockNumber": 123, "alertId": "xyz789"}
Types
AlertEndCursorInput
Description
Search after specified block number and alertId
Fields
Input Field Description
blockNumber - NonNegativeInt!
alertId - String!
EXAMPLE
{"blockNumber": 123, "alertId": "xyz789"}
Types
AlertPageInfo
Fields
Field Name Description
endCursor - AlertEndCursor!
hasNextPage - Boolean!
totalAlerts - TotalMetadata!
EXAMPLE
{
"endCursor": AlertEndCursor,
"hasNextPage": true,
"totalAlerts": TotalMetadata
}
Types
AlertSource
Description
Source where the threat was detected
Fields
Field Name Description
transactionHash - String Transaction where the threat was detected
bot - Bot Bot that triggered the alert
block - Block Block where the threat was detected
sourceAlert - SourceAlertEvent Source alert event the threat was detected
EXAMPLE
{
"transactionHash": "abc123",
"bot": Bot,
"block": Block,
"sourceAlert": SourceAlertEvent
}
Types
AlertsInput
Description
Alert list input
Fields
Input Field Description
addresses - [String] Indicate a list of addresses. Alerts returned will have those addresses involved.
after - AlertEndCursorInput Search results after the specified cursor
alertId - String Filter alerts by alert-id.
alertIds - [String] Filter alerts by multiple alert-ids.
alertHash - String Get a specific alert by alert hash.
alertName - String Filter alerts by alert name.
blockDateRange - DateRange Block Date range Alerts returned will be between the specified start and end block timestamp dates when the threats were detected. By default, start and end date will be set to local query execution date.
blockNumberRange - BlockRange Block number range Alerts for the block number range will be returned.
blockTimestampRange - TimestampRange Block Timestamp range Alerts returned will be between the specified start and end block timestamp when the threats were detected.
blockSortDirection - Sort Indicate sorting order by block number, 'desc' or 'asc'. Default is 'desc'.
bots - [String] Indicate a list of bot hashes. Alerts returned will only be from any of those bots.
chainId - NonNegativeInt Indicate a chain Id: EIP155 identifier of the chain Alerts returned will only be from the specific chain Id Default is 1 = Ethereum Mainnet.
createdSince - NonNegativeInt Indicate number of milliseconds Alerts returned will be alerts created since the number of milliseconds indicated ago.
createdBefore - NonNegativeInt Indicate number of milliseconds Alerts returned will be alerts created before the number of milliseconds indicated ago.
first - NonNegativeInt Indicate max number of results.
projectId - String Indicate a project Id. Alerts returned will only be from that project.
scanNodeConfirmations - scanNodeFilters Filter alerts by number of scan nodes confirming the alert.
severities - [String] Filter alerts by severity levels.
transactionHash - String Indicate a transaction hash Alerts returned will only be from that transaction.
labelMetadata - String Filter alerts by label metadata
afterCreatedAtDate - String Filter alerts by created at date after the specified date in RFC 3399 format.
beforeCreatedAtDate - String Filter alerts by created at date before the specified date in RFC 3399 format.
EXAMPLE
{
"addresses": ["abc123"],
"after": AlertEndCursorInput,
"alertId": "xyz789",
"alertIds": ["abc123"],
"alertHash": "xyz789",
"alertName": "xyz789",
"blockDateRange": DateRange,
"blockNumberRange": BlockRange,
"blockTimestampRange": TimestampRange,
"blockSortDirection": "asc",
"bots": ["abc123"],
"chainId": 123,
"createdSince": 123,
"createdBefore": 123,
"first": 123,
"projectId": "xyz789",
"scanNodeConfirmations": scanNodeFilters,
"severities": ["xyz789"],
"transactionHash": "xyz789",
"labelMetadata": "abc123",
"afterCreatedAtDate": "xyz789",
"beforeCreatedAtDate": "abc123"
}
Types
AlertsResponse
Fields
Field Name Description
alerts - [Alert]
pageInfo - AlertPageInfo
EXAMPLE
{
"alerts": [Alert],
"pageInfo": AlertPageInfo
}
Types
Batch
Description
Batch information
Fields
Field Name Description
id - String Batch id
parentId - String Id of preceding batch to form chain
createdAt - String Timestamp when the batch was published
chainId - NonNegativeInt chain id on batch was created from
scanNodeId - String Reference to scanner that sent this batch request
receiptId - String Receipt id for the batch
EXAMPLE
{
"id": "abc123",
"parentId": "xyz789",
"createdAt": "abc123",
"chainId": 123,
"scanNodeId": "xyz789",
"receiptId": "xyz789"
}
Types
BatchEndCursor
Fields
Field Name Description
createdAt - String
EXAMPLE
{"createdAt": "xyz789"}
Types
BatchEndCursorInput
Description
Search after specified batch after id
Fields
Input Field Description
createdAt - DateTime
EXAMPLE
{"createdAt": "2007-12-03T10:15:30Z"}
Types
BatchPageInfo
Fields
Field Name Description
endCursor - BatchEndCursor
hasNextPage - Boolean
totalBatches - TotalMetadata!
EXAMPLE
{
"endCursor": BatchEndCursor,
"hasNextPage": true,
"totalBatches": TotalMetadata
}
Types
BatchesInput
Description
Batch list input
Fields
Input Field Description
after - BatchEndCursorInput Search results after the specified cursor
parentId - String Filter batch by parent's Id.
scanNodeId - String Filter batches by scanner.
receiptId - String Filter batches by receiptId.
chainId - NonNegativeInt Indicate a chain Id: EIP155 identifier of the chain Alerts returned will only be from the specific chain Id Default is 1 = Ethereum Mainnet.
first - NonNegativeInt Indicate max number of results.
createdRange - TimestampRange Batches returned will only be those created in the time range
EXAMPLE
{
"after": BatchEndCursorInput,
"parentId": "xyz789",
"scanNodeId": "abc123",
"receiptId": "xyz789",
"chainId": 123,
"first": 123,
"createdRange": TimestampRange
}
Types
BatchesResponse
Fields
Field Name Description
batches - [Batch]
pageInfo - BatchPageInfo
EXAMPLE
{
"batches": [Batch],
"pageInfo": BatchPageInfo
}
Types
Block
Description
Block information
Fields
Field Name Description
number - NonNegativeInt Block number
hash - String Block hash
timestamp - String Block's timestamp
chainId - NonNegativeInt Block's chain id
EXAMPLE
{
"number": 123,
"hash": "abc123",
"timestamp": "abc123",
"chainId": 123
}
Types
BlockRange
Description
Block range
Fields
Input Field Description
startBlockNumber - NonNegativeInt!
endBlockNumber - NonNegativeInt!
EXAMPLE
{"startBlockNumber": 123, "endBlockNumber": 123}
Types
BloomFilter
Description
Bloom filter information
Fields
Field Name Description
k - String Number of hash functions used in filter
m - String Capacity of bloom filter
bitset - String Bloom filter as bitset, encoded in base64
itemCount - NonNegativeInt Total number of addresses in filter
EXAMPLE
{
"k": "xyz789",
"m": "abc123",
"bitset": "abc123",
"itemCount": 123
}
Types
Boolean
Description
The Boolean scalar type represents true or false.
Types
Bot
Description
Bot information
Fields
Field Name Description
chainIds - [String]
createdAt - String
description - String
developer - String Bot Developer address
docReference - String Bot Documentation IPFS reference
enabled - Boolean
id - String Bot id
image - String Bot docker image hash
name - String
reference - String Bot manifest IPFS reference
repository - String Bot code repository url
projects - [String] Projects the bot monitors
scanNodes - [String] List of scanNodes running the bot
version - String
EXAMPLE
{
"chainIds": ["abc123"],
"createdAt": "xyz789",
"description": "xyz789",
"developer": "abc123",
"docReference": "abc123",
"enabled": false,
"id": "xyz789",
"image": "abc123",
"name": "abc123",
"reference": "abc123",
"repository": "abc123",
"projects": ["xyz789"],
"scanNodes": ["xyz789"],
"version": "abc123"
}
Types
BotEndCursor
Fields
Field Name Description
createdAt - String
EXAMPLE
{"createdAt": "xyz789"}
Types
BotEndCursorInput
Description
Search after specified bot createdAt
Fields
Input Field Description
createdAt - Timestamp
EXAMPLE
{"createdAt": 1592577642}
Types
BotInfo
Fields
Field Name Description
image - String
imageHash - String
id - String
manifest - String
EXAMPLE
{
"image": "abc123",
"imageHash": "abc123",
"id": "xyz789",
"manifest": "xyz789"
}
Types
BotPageInfo
Fields
Field Name Description
endCursor - BotEndCursor
hasNextPage - Boolean
totalBots - TotalMetadata!
EXAMPLE
{
"endCursor": BotEndCursor,
"hasNextPage": false,
"totalBots": TotalMetadata
}
Types
BotsInput
Description
Bot list input
Fields
Input Field Description
after - BotEndCursorInput Search results after the specified cursor
first - NonNegativeInt Indicate max number of results.
ids - [String] Filter bots by bot Ids.
name - String Filter bots by bot name. Case insensitive partial match.
developer - String Filter bots by developer checksum address.
createdAtSortDirection - Sort Indicate sorting order by bot createdAt 'desc' or 'asc'. Default is 'desc'.
enabled - Boolean Filter bots by enabled flag.
EXAMPLE
{
"after": BotEndCursorInput,
"first": 123,
"ids": ["xyz789"],
"name": "abc123",
"developer": "abc123",
"createdAtSortDirection": "asc",
"enabled": true
}
Types
BotsResponse
Fields
Field Name Description
bots - [Bot]
pageInfo - BotPageInfo
EXAMPLE
{
"bots": [Bot],
"pageInfo": BotPageInfo
}
Types
Contacts
Description
Project Contact Information
Fields
Field Name Description
generalEmailAddress - EmailAddress General contact email
securityEmailAddress - EmailAddress Security contact email
EXAMPLE
{
"generalEmailAddress": "test@test.com",
"securityEmailAddress": "test@test.com"
}
Types
Contract
Description
Smart Contract Information
Fields
Field Name Description
address - String! EIP55 checksummed deployment address
name - String! User-friendly name of the contract
projectId - String! Related web3 project
EXAMPLE
{
"address": "abc123",
"name": "abc123",
"projectId": "xyz789"
}
Types
DateRange
Description
Date range Date format: YYYY-MM-DD
Fields
Input Field Description
startDate - LocalDate!
endDate - LocalDate!
EXAMPLE
{
"startDate": "2020-07-19",
"endDate": "2020-07-19"
}
Types
DateTime
Description
A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the date-time format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar.
EXAMPLE
"2007-12-03T10:15:30Z"
Types
EmailAddress
Description
A field whose value conforms to the standard internet email address format as specified in RFC822: https://www.w3.org/Protocols/rfc822/.
EXAMPLE
"test@test.com"
Types
Float
Description
The Float scalar type represents signed double-precision fractional values as specified by IEEE 754.
EXAMPLE
123.45
Types
Int
Description
The Int scalar type represents non-fractional signed whole numeric values. Int can represent values between -(2^31) and 2^31 - 1.
EXAMPLE
987
Types
JSONObject
Description
The JSONObject scalar type represents JSON objects as specified by ECMA-404.
EXAMPLE
{}
Types
Label
Description
Label information
Fields
Field Name Description
label - String Label name
confidence - Float Label confidence
entity - String Label's entity
entityType - String Label's entity type
remove - Boolean Label's status
metadata - [String] Extra label information
EXAMPLE
{
"label": "abc123",
"confidence": 123.45,
"entity": "xyz789",
"entityType": "abc123",
"remove": false,
"metadata": ["xyz789"]
}
Types
LabelEndCursor
Fields
Field Name Description
pageToken - String
EXAMPLE
{"pageToken": "abc123"}
Types
LabelEndCursorInput
Description
Search after specified pageToken
Fields
Input Field Description
pageToken - String!
EXAMPLE
{"pageToken": "abc123"}
Types
LabelEvent
Description
Label event information
Fields
Field Name Description
id - String Unique string to identify this specific label event
label - Label The label payload
source - LabelSource Properties of the source information for this label
createdAt - String Timestamp when this label was added to Forta
EXAMPLE
{
"id": "abc123",
"label": Label,
"source": LabelSource,
"createdAt": "abc123"
}
Types
LabelPageInfo
Fields
Field Name Description
endCursor - LabelEndCursor!
hasNextPage - Boolean!
EXAMPLE
{"endCursor": LabelEndCursor, "hasNextPage": true}
Types
LabelSource
Fields
Field Name Description
bot - BotInfo
alertHash - String
alertId - String
id - String
chainId - NonNegativeInt
EXAMPLE
{
"bot": BotInfo,
"alertHash": "abc123",
"alertId": "xyz789",
"id": "xyz789",
"chainId": 123
}
Types
LabelsInput
Description
Labels input
Fields
Input Field Description
entities - [String] Indicate a list of entities. Labels returned will only includes these entities
labels - [String] Indicate a list of labels. Labels returned will only includes these labels
sourceIds - [String] Indicate a list of sourceIds. Labels returned will only includes these sourceIds
chainIds - [NonNegativeInt] Indicate a list of chainIds. Labels returned will only includes these chainIds
entityType - String Indicate an entityType. Labels returned will only includes this entityType
metadata - JSONObject Indicate a map of key-value pairs Labels returned will have a metadata entry matching ALL of these pairs The map should be a basic map with only keys and string/numeric values (no objects)
excludedMetadata - JSONObject Indicate a map of key-value pairs Labels returned will have a metadata entry NOT matching ANY of these pairs The map should be a basic map with only keys and string/numeric values (no objects)
createdSince - NonNegativeInt Indicate earliest timestamp (in milliseconds). Labels returned will be created since this date.
createdBefore - NonNegativeInt Indicate latest timestamp (in milliseconds). Labels returned will be created before this date.
afterCreatedAtDate - String Indicate earliest timestamp (in RFC3339 format). Labels returned will be created after this date.
beforeCreatedAtDate - String Indicate latest timestamp (in RFC3339 format). Labels returned will be created before this date.
after - LabelEndCursorInput Search results after the specified cursor
first - NonNegativeInt Indicate max number of results.
state - Boolean Indicate true if only current state is desired
EXAMPLE
{
"entities": ["abc123"],
"labels": ["abc123"],
"sourceIds": ["abc123"],
"chainIds": [123],
"entityType": "xyz789",
"metadata": {},
"excludedMetadata": {},
"createdSince": 123,
"createdBefore": 123,
"afterCreatedAtDate": "abc123",
"beforeCreatedAtDate": "xyz789",
"after": LabelEndCursorInput,
"first": 123,
"state": false
}
Types
LabelsResponse
Fields
Field Name Description
labels - [LabelEvent]
pageInfo - LabelPageInfo
EXAMPLE
{
"labels": [LabelEvent],
"pageInfo": LabelPageInfo
}
Types
LocalDate
Description
A local date string (i.e., with no associated timezone) in YYYY-MM-DD format, e.g. 2020-01-01.
EXAMPLE
"2020-07-19"
Types
NonNegativeInt
Description
Integers that will have a value of 0 or more.
EXAMPLE
123
Types
Project
Description
Web3 Project Information
Fields
Field Name Description
contacts - Contacts List of contact info
name - String! User-friendly name of the project
id - String! Project identifier
social - Social
token - Token
website - String Main website of the project
EXAMPLE
{
"contacts": Contacts,
"name": "xyz789",
"id": "xyz789",
"social": Social,
"token": Token,
"website": "xyz789"
}
Types
ProjectEdge
Fields
Field Name Description
cursor - String
node - Project
EXAMPLE
{
"cursor": "xyz789",
"node": Project
}
Types
ProjectPageInfo
Fields
Field Name Description
endCursor - String
hasNextPage - Boolean
totalProjects - TotalMetadata!
EXAMPLE
{
"endCursor": "xyz789",
"hasNextPage": true,
"totalProjects": TotalMetadata
}
Types
ProjectsInput
Fields
Input Field Description
first - Int
after - String
EXAMPLE
{"first": 987, "after": "xyz789"}
Types
ProjectsResponse
Fields
Field Name Description
edges - [ProjectEdge]
pageInfo - ProjectPageInfo
EXAMPLE
{
"edges": [ProjectEdge],
"pageInfo": ProjectPageInfo
}
Types
Relation
Values
Enum Value Description
eq
gte
EXAMPLE
"eq"
Types
Social
Description
Social Media Links
Fields
Field Name Description
coingecko - String Path to the coingecko identifier
everest - String Identifier in the everest.link registry
github - String Github organization slug
twitter - String Twitter account name
EXAMPLE
{
"coingecko": "xyz789",
"everest": "abc123",
"github": "abc123",
"twitter": "xyz789"
}
Types
Sort
Values
Enum Value Description
asc
desc
EXAMPLE
"asc"
Types
SourceAlertEvent
Description
Source alert information
Fields
Field Name Description
hash - String Alert hash
botId - String Bot id
timestamp - String Alert timestamp
chainId - NonNegativeInt Chain id
EXAMPLE
{
"hash": "abc123",
"botId": "abc123",
"timestamp": "abc123",
"chainId": 123
}
Types
String
Description
The String scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.
EXAMPLE
"abc123"
Types
Timestamp
Description
The javascript Date as integer. Type represents date and time as number of milliseconds from start of UNIX epoch.
EXAMPLE
1592577642
Types
TimestampRange
Description
Timestamp range Timestamp format: number of milliseconds from start of UNIX epoch
Fields
Input Field Description
startTimestamp - Timestamp!
endTimestamp - Timestamp!
EXAMPLE
{"startTimestamp": 1592577642, "endTimestamp": 1592577642}
Types
Token
Description
Project Token Information
Fields
Field Name Description
address - String! Address where the project token is deployed
chainId - Int! EIP155 identifier of the chain where the project token is deployed
decimals - Int Decimals of the token contract
name - String Name of the token
symbol - String Symbol of the token
EXAMPLE
{
"address": "xyz789",
"chainId": 987,
"decimals": 123,
"name": "abc123",
"symbol": "abc123"
}
Types
TotalMetadata
Description
Metadata about the number of matching items
Fields
Field Name Description
value - NonNegativeInt! Total number of matching documents.
relation - Relation! Indicates whether the number of matching items in the value parameter is accurate or a lower bound.
EXAMPLE
{"value": 123, "relation": "eq"}
Types
scanNodeFilters
Description
Filter by number of scan nodes confirming the alert.
Fields
Input Field Description
gte - NonNegativeInt
lte - NonNegativeInt
EXAMPLE
{"gte": 123, "lte": 123}
And python request example like this
import pandas as pd
import requests
import os
api_key = '81be5ee58f631902:1d6211e4f5888baef52c91720a3984c002d03eb66c95213856a41b523221e2c2'
os.environ["FORTA_API_KEY"] = api_key
forta_api = "https://api.forta.network/graphql"
headers = {"content-type": "application/json"}
headers["Authorization"] = f'Bearer {os.environ["FORTA_API_KEY"]}'
# start and end date needs to be in the format: YYYY-MM-DD
START_DATE = "2024-04-23"
END_DATE = "2024-04-24"
ALERT_COUNT_LIMIT = 100000
query = \"\"\"
query exampleQuery($input: AlertsInput) {
alerts(input: $input) {
alerts {
name
protocol
findingType
source {
transactionHash
block {
number
chainId
timestamp
hash
}
bot {
id
}
}
severity
metadata
alertId
addresses
description
hash
}
pageInfo {
hasNextPage
endCursor {
blockNumber
alertId
}
}
}
}
\"\"\"
address="0xDa12b368a93007Ef2446717765917933cEBC6080"
query_variables = {
"input": {
"first": 100,
"addresses": [address],
"blockDateRange": {
"startDate": START_DATE,
"endDate": END_DATE
}
}
}
all_alerts = []
next_page_exists = True
while next_page_exists and len(all_alerts) < ALERT_COUNT_LIMIT:
# query Forta API
payload = dict(query=query, variables=query_variables)
response = requests.request("POST", forta_api, json=payload, headers=headers)
# collect alerts
data = response.json()['data']['alerts']
alerts = data['alerts']
all_alerts += alerts
# get next page of alerts if it exists
next_page_exists = data['pageInfo']['hasNextPage']
# endCursor contains alert Id and block number.
# This is needed to get the next page of alerts.
end_cursor = data['pageInfo']['endCursor']
query_variables['input']['after'] = end_cursor
df = pd.DataFrame.from_dict(all_alerts)
len(df) # size: ALERT_COUNT_LIMIT = 100000
print(df)
"""
def generate_tasks(task, cnt=3):
return base_api+ "To create a least of {} tasks with plottable results that can reveal more information given the users query:".format(cnt*2) + "{}".format(task) +"""
tasks shall implement initial users desire, help him better understand the surrounding context, and be helpfull in gaining insites into what is going on
Make sure that all tasks you create
1) are completely implementable within and using given Forta graphql api (not using external data sources)
2) are plottable using somenthing like above and matplotlib or plotly for data visualisation.
3) are described in a complete and informative way
If query is not implementable within forta graphql api print a polite response asking use to reformulate his query giving three diverse alternative query examples
"""
generate_tasks("give me info on account 0xDa12b368a93007Ef2446717765917933cEBC6080")
def generate_code(q, t, n=0):
return base_api + "\n while keeping in mind the original user query {}".format(q) + f"""
To implement this task:
Task - {t['Name']}
Use GraphQL api to: {t['Task']}
Then show it following this guidelines: {t['Visualization']}
"""+"""
Implement the task as a single function taking 0 arguments in python make sure that all extra variables you might need are encompused in that function
given this is already set:
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import networkx as nx
import requests
import os
forta_api = "https://api.forta.network/graphql"
headers = {"content-type": "application/json"}
headers["Authorization"] = f'Bearer {os.environ["FORTA_API_KEY"]}'
notes:
1. always use blockDateRange to set data range time,
Today is "2024-04-24"
if users query had no specific time interval specified use such range as START_DATE = "2024-04-23" END_DATE = "2024-04-24"
2. if returned data was empty do not try to plot it
3. make sure that your visualisation contains labels easely understandable for human-readabilety without explicit hovering as if it was for printing, make sure that all markers are always visible
4. assume that any requested string such as for example bot name can returned from graphql as None or empty
5. when in need of parsing dates use something like this `dates = [datetime.datetime.strptime(alert['createdAt'][:23], "%Y-%m-%dT%H:%M:%S.%f").date() for alert in all_alerts]`
6. prefer using raw alert data such as alert name to general bot information
7. always set MAX ALERT_COUNT_LIMIT you look into to 10000
use matplotlib to generate an image approximatly 700x700px and save it as """+ str(n) +""".png
print only function body, name it "plot_task", stat from "def plot_task():"""
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Forta Smart Search</title>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<style>
body {
background-color: #121212;
color: #fff;
}
.message-panel, .image-panel {
margin-bottom: 20px;
padding: 10px;
background-color: #333;
border-radius: 5px;
}
img {
width: 100%;
max-width: 300px;
border-radius: 8px;
cursor: pointer;
}
.loading-icon {
border: 6px solid #f3f3f3; /* Light grey */
border-top: 6px solid #3498db; /* Blue */
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 2s linear infinite;
clip-path: polygon(50% 0%, 100% 0, 100% 100%, 50% 100%);
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.modal-dialog {
max-width: 95%; /* Adjust the max-width to fit the screen size */
margin: 1.75rem auto;
}
.modal-content {
background-color: #121212; /* Dark theme for the modal content */
height: calc(100vh - 3.5rem); /* Make the modal's content as high as the viewport */
overflow-y: auto; /* Enable vertical scroll on the content if it's too long */
}
.modal-body {
position: relative; /* Positioning context for the image */
padding: 0; /* Remove padding for full-width image display */
height: auto; /* Let the height grow as much as it needs */
}
.modal-body img {
max-width: 100%; /* Maximum width is the width of the modal body */
max-height: calc(100vh - 3.5rem); /* Maximum height is 100% of the viewport height minus modal's top and bottom space */
height: auto; /* Height is auto so the image's aspect ratio is maintained */
width: auto; /* Width is auto so the image's aspect ratio is maintained */
object-fit: contain; /* The image will be scaled to maintain its aspect ratio while fitting within the element's content box */
display: block; /* Display block to remove bottom space under the image */
margin: auto; /* Center the image */
}
</style>
</head>
<body>
<div class="container mt-4">
<h1 class="mb-3">Forta Smart Search</h1>
<form id="queryForm" method="get" action="/">
<input type="text" name="query" id="queryInput" class="form-control mb-2" placeholder="Enter your request" value="{{ initial_request }}">
<button type="submit" class="btn btn-primary mb-4">Search</button>
</form>
<div class="text-center mb-4">
<div class="loading-icon"></div>
</div>
<div class="row mb-4">
<div class="col-md-6 message-panel" id="request-panel">
Loading request...
</div>
<div class="col-md-6 message-panel" id="response-panel">
Loading response...
</div>
</div>
<div class="row" id="image-container">
<!-- Images will be loaded here -->
</div>
</div>
<!-- Modal for enlarged image -->
<!-- Modal for enlarged image -->
<div class="modal fade" id="imageModal" tabindex="-1" aria-labelledby="imageModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="imageModalLabel">Enlarged Image</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<img src="" id="modalImage" alt="Enlarged Image" class="modal-image">
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
// Function to update the content based on the data received
function updateContent(data) {
$('.loading-icon').hide(); // Hide the loading icon once the data is ready
$('#request-panel').html('<h5>Request</h5><p>' + data.request_text + '</p>');
$('#response-panel').html('<h5>Response</h5>' + data.response_text + '');
var imagesHtml = '';
data.images.forEach(function (image) {
imagesHtml += '<div class="col-md-4"><div class="image-panel"><img src="' + image + '" alt="Image result" class="clickable-image"></div></div>';
});
$('#image-container').html(imagesHtml);
// Handle image click to show modal
$('.clickable-image').on('click', function () {
var src = $(this).attr('src');
$('#modalImage').attr('src', src).removeClass('img-fluid'); // Remove img-fluid which can conflict with our styles
$('#imageModal').modal('show');
});
}
// Function to load data with an optional query
function loadData(query) {
var url = '/data';
if(query) {
url += '?query=' + encodeURIComponent(query);
}
$('.loading-icon').show(); // Show loading icon when loading data
$.getJSON(url, function(data) {
// Poll for status if task has started
if (data.message === 'Task has started') {
checkTaskStatus(data.session_id); // Start polling
} else {
updateContent(data);
}
});
}
// Function to check task status
function checkTaskStatus(sessionId) {
$.getJSON(`/status?session_id=${sessionId}`, function(data) {
if (data.message === 'Task not found or not completed') {
// Continue polling
setTimeout(function() {
checkTaskStatus(sessionId);
}, 5000); // Check every 5 seconds
} else {
updateContent(data); // Update UI with the response
}
});
}
// Event handler for the search form submission
$('#queryForm').on('submit', function(e) {
e.preventDefault();
var query = $('#queryInput').val().trim();
// Update the browser's URL with the search query without reloading the page
window.history.pushState({}, '', '?query=' + encodeURIComponent(query));
// Load the data related to the search query
loadData(query);
});
// Load data on page load based on the URL query parameter
var initialQuery = new URLSearchParams(window.location.search).get('query');
if (initialQuery) {
$('#queryInput').val(initialQuery); // Set the input field to the current query
loadData(initialQuery); // Load the data for the initial query
} else {
loadData(); // Load default data if no query is present
}
});
</script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>
@OlegJakushkin
Copy link
Author

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment