Python SDK
The Forta bot Python SDK comes with a set of classes to provide a consistent interface for developers to write their bots. There are also some utility functions available for your convenience to do common operations like searching for an event in the transaction logs. Check out the Python bots in our examples repo to learn more.
NOTE: while you can write bots in Python, you would still use the Node.js forta-agent CLI tool to run the bot.
Handlers
The most relevant functions for bot developers are the handler functions: initialize, handle_block, handle_transaction and handle_alert.
Your agent.py file must declare at least one of the handle_block, handle_transaction or handle_alert functions. You can implement one or all of these depending on your use case, but at least one must be provided. These functions take a BlockEvent, TransactionEvent or AlertEvent as their input, respectively, and return an array of zero or more Finding objects.
You can also optionally declare an initialize function that will be executed on bot startup. This is useful for fetching some data from the network or parsing some file before your bot begins. If you are using the handle_alert handler, then the initialize function is required to return which bot's alerts you want to subscribe to (see the pattern for consuming bot alerts for more information). If you don't want to subscribe to any bot alerts, don't return anything.
BlockEvent
When a block is mined and detected by a Forta scan node, it will generate a BlockEvent containing information such as the block hash and block number. It contains the following fields:
type- specifies whether this was a block reorg or a regular blocknetwork- specifies which network the block was mined on (e.g. mainnet, ropsten, rinkeby, etc)block_hash- alias forblock.hashblock_number- alias forblock.numberblock- data object containing the following fields:difficultyextra_datagas_limitgas_usedhashlogs_bloomminermix_hashnoncenumberparent_hashreceipts_rootsha3_unclessizestate_roottimestamptotal_difficultytransactionstransactions_rootuncles
TransactionEvent
When a transaction is mined and detected by a Forta scan node, it will generate a TransactionEvent containing various information about the transaction. It contains the following fields:
type- specifies whether this was from a block reorg or a regular blocknetwork- specifies which network the transaction was mined on (e.g. mainnet, ropsten, rinkeby, etc)hash- alias fortransaction.hashfrom_- alias fortransaction.from_to- alias fortransaction.togas_price- alias fortransaction.gas_pricetimestamp- alias forblock.timestampblock_number- alias forblock.numberblock_hash- alias forblock.hashaddresses- map of addresses involved in the transaction (generated from transaction to/from address, any event log address and trace data address if available)block- data object containing the following fields:hashnumbertimestamp
transaction- data object containing the following fields:hashfrom_tononcegasgas_pricevaluedatarsv
logs- list of log objects with following fields:addresstopicsdatalog_indexblock_numberblock_hashtransaction_indextransaction_hashremoved
traces- only with tracing enabled; list of trace objects with the following fields:block_hashblock_numbersubtracestrace_addresstransaction_hashtransaction_positiontypeerroraction- object with the following fields:call_typetofrom_inputvalueinitaddressbalancerefund_address
result- object with the following fields:gas_usedaddresscodeoutput
filter_log
filter_log is a convenience function on TransactionEvent to filter and decode transaction logs. For example, you can use it to get all of the Transfer logs in a transaction from a particular ERC-20 token:
erc20_token_address = '0x123abc'
transfer_event_abi = '{"name":"Transfer","type":"event","anonymous":false,"inputs":[{"indexed":true,"name":"from","type":"address"},{"indexed":true,"name":"to","type":"address"},{"indexed":false,"name":"value","type":"uint256"}]}'
transfers = transaction_event.filter_log(transfer_event_abi, erc20_token_address)
print(f'found {transfers.length} transfer events')
The underlying library used for decoding event logs is web3.py. The Python SDK uses the web3.py processLog method and returns an array of Event Log objects. To better understand usage, see the Python filtering example bot.
filter_function
filter_function is a convenience function on TransactionEvent to filter and decode function calls in the transaction or traces. For example, you can use it to get all of the transferFrom function calls on a particular ERC-20 token:
erc20_token_address = '0x123abc'
transferFrom_function_abi = '{"name":"transferFrom","type":"function","constant":false,"inputs":[{"name":"from","type":"address"},{"name":"to","type":"address"},{"name":"value","type":"uint256"}],"outputs":[],"payable":false,"stateMutability":"nonpayable"}'
transfers = transaction_event.filter_function(transferFrom_function_abi, erc20_token_address)
print(f'found {transfers.length} function calls')
The underlying library used for decoding function calls is web3.py. The Python SDK uses the web3.py decode_function_input method and returns an array of (ContractFunction, dict) tuples. To better understand usage, see the Python filtering example bot.
AlertEvent
When an alert is fired from a Forta bot and is detected by the network, any subscribing bots will receive an AlertEvent containing various information about the alert (see the pattern for consuming bot alerts for more information). It contains the following fields:
alert- data object containing an Alertalert_id- alias foralert.alert_idname- alias foralert.namehash- alias foralert.hashbot_id- alias foralert.source.bot.idtransaction_hash- alias foralert.source.transaction_hashblock_hash- alias foralert.source.block.hashblock_number- alias foralert.source.block.numberchain_id- alias foralert.chain_idhas_address- alias function foralert.has_address
Finding
If a bot wants to flag a transaction/block/alert because it meets some condition (e.g. flash loan attack), the handler function would return a Finding object. This object would detail the results of the finding and provide metadata such as the severity of the finding. A Finding object accepts the following properties:
name- required; human-readable name of the finding e.g. "High Gas"description- required; brief description e.g. "High gas used: 1,000,000"alert_id- required; unique string to identify this class of finding, primarily used to group similar findings for the end userprotocol- required; name of the protocol being reported on e.g. "aave", defaults to "ethereum" if left blanktype- required; indicates type of finding:- Exploit
- Suspicious
- Degraded
- Info
severity- required; indicates 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
metadata- optional; dict (both keys and values as strings) for providing extra informationlabels- optional; array ofLabelobjects to attach to this finding
Alert
When an Alert is fired by a Forta bot, it can be consumed using an AlertEvent or manually queried using the get_alerts method. Alert objects have the following properties:
alert_id- unique string to identify this class of findingchain_id- chain ID where this alert was firedaddresses- list of addresses involved in the alert (currently truncated at 50 addresses)labels- list of Labels associated to the alertcontracts- list of contracts related to the alertcreated_at- timestamp when the alert was publisheddescription- text description of the alertname- alert nameprotocol- name of the protocol being reported onscan_node_count- number of scanners that found the alertsource- source where the alert was detectedtransaction_hash- transaction where the alert was detectedblock- block where the alert was detectedtimestampchain_idhashnumber
bot- bot that triggered the alertidreferenceimage
sourceAlert- alert that triggered this alerthashbot_idtimestampchain_id
projects- list of Web3 projects related to the alertcontacts- list of contact infoid- project identifiername- user-friendly name of the projecttokensocialwebsite- main website of the project
finding_type- indicates the type of finding:- Exploit
- Suspicious
- Degraded
- Info
- Unknown
severity- indicates 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
metadata- key-value map (both keys and values as strings) for providing extra information
has_address
has_address is a convenience function on Alert meant for checking the existence of an address involved in the alert. The addresses array is truncated for space efficiency, so this method uses a bloom filter to check for existence. It accepts a single string parameter: the address to check
Label
Labels can be used to add more contextual data to a Finding e.g. "is this address an attacker?". The Label object has the following properties:
id- string identifier of this labelentity_type- enum indicating type of entity:AddressTransactionBlockUrlUnknown
entity- string identifier of the entity being labelled e.g. transaction hashlabel- string label to attach to the entity e.g. "exploit"confidence- confidence level of label between 0 and 1metadata- key-value map (both keys and values as strings) for providing extra informationcreated_at- string containing the timestamp of label creationsource- object with information about where this label came fromalert_hashalert_ididchain_idbotidimageimage_hashmanifest
get_json_rpc_url
A convenience function called get_json_rpc_url can be used to load a JSON-RPC URL for your bot. When running in production, this function will return a URL injected by the scan node that is running the bot. When running locally in development, this function will return the jsonRpcUrl property specified in your forta.config.json file (or https://cloudflare-eth.com/ by default).
get_web3_provider
get_web3_provider is a convenience function that returns a web3.py Provider which can be used to interact with the blockchain. The value from get_json_rpc_url will be used as the JSON-RPC endpoint to connect to.
get_transaction_receipt
A convenience function called get_transaction_receipt can be used to fetch the entire receipt of a transaction and returned in a format matching the SDK Receipt interface.
get_alerts
The get_alerts method can be used to fetch alerts based on input AlertQueryOptions. The get_alerts method accepts the following input filter properties:
bot_idsrequired; list of bot ids to fetch alerts foraddresses- indicates a list of addresses, alerts returned will have those addresses involved.alert_id- filter alerts by alert-idchain_id- EIP155 identifier of the chain alerts returned will only be from the specific chain Id Default is 1 = Ethereum Mainnetcreated_since- indicates number of milliseconds, alerts returned will be alerts created since the number of milliseconds indicated ago (note: if not specified, the query will only search the past 24 hours)first- indicates max number of results.starting_cursor- query results after the specified cursorproject_id- indicates a project id, alerts returned will only be from that project.scan_node_confirmations- filter alerts by number of scan nodes confirming the alertseverities- filter alerts by severity levelstransaction_hash- indicates a transaction hash, alerts returned will only be from that transactionblock_sort_direction- indicates sorting order by block number, 'desc' or 'asc'. The default is 'desc'.block_date_range- alerts returned will be between the specified start and end block timestamp dates when the threats were detectedblock_number_range- alerts for the block number range will be returned
The returned alerts are formatted to match the SDK AlertsResponse class, below is an example using this method:
from forta_agent import get_alerts
response = get_alerts({
'bot_ids': ["0x79af4d8e0ea9bd28ed971f0c54bcfe2e1ba0e6de39c4f3d35726b15843990a51"],
})
has_next = response.page_info.has_next_page
alerts = response.alerts
send_alerts
The send_alerts method enables submitting alerts via the GraphQL API. See the external bots page for more information.
get_labels
The get_labels method can be used to fetch labels based on input LabelQueryOptions. The get_labels method accepts the following input filter properties (at least one of entities, labels or source_ids is required):
entities- string array to filter by label entities (e.g. wallet addresses, block/tx hashes)labels- string array to filter the label value (e.g. "attacker")source_ids- string array to filter the label sources (e.g. bot IDs)entity_type- string to filter labels byEntityType(see label section for possible types)state- boolean, set totrueif only the current state is desiredcreated_since- integer timestamp in milliseconds, labels returns will be created after this timestampcreated_before- integer timestamp in milliseconds, labels returned will be created before this timestampfirst- integer indicating max number of resultsstarting_cursor- query results after the specified cursor object
The returned labels are formatted to match the SDK LabelsResponse class, below is an example using this method:
from forta_agent import get_labels
response = get_labels({
'source_ids': ["0x79af4d8e0ea9bd28ed971f0c54bcfe2e1ba0e6de39c4f3d35726b15843990a51"],
})
has_next = response.page_info.has_next_page
labels = response.labels
fetch_jwt
Scan nodes allow bots to make authorized requests to external APIs by using the scan node's identity, without letting the scan node modify the requests. You can use the fetch_jwt utility function to generate a jwt token from a scan node.
This method will only generate a token if the bot is running on a scan node
If running a bot locally or in a stand-alone environment (ie. outside of a scanner node), this method will throw an error. For local testing, you can run a local scan node and run your bot on it.
The function signature is fetch_jwt(claims, expiresAt):
- claims [required]: a dictionary of any data you would like to include in the data portion of the JWT
- expiresAt: an optional datetime that sets when the JWT will expire
The returned JWT can be decoded using the decode_jwt method.
verify_jwt
A utility method intended to be used on an external server for verifying the claims and signature of a JWT generated by a scan node. This method verifies that the JWT was generated and signed by the same scan node the bot is running on. See an example usage of verifying a JWT
token- required a JWT token generated byfetch_jwt
decode_jwt
A utility method for decoding the header and payload of a JWT returned from a scan node
The function signature is decode_jwt(token):
This method will not verify the signature of a JWT
create_block_event
A utility function for writing tests. You can use create_block_event to easily generate a mock BlockEvent object when writing unit tests for your handle_block handler. To better understand usage, see the Python unit test example.
create_transaction_event
A utility function for writing tests. You can use create_transaction_event to easily generate a mock TransactionEvent object when writing unit tests for your handle_transaction handler. To better understand usage, see the Python unit test example.