Skip to content

Commit

Permalink
Add Pow Signal Monitoring and list of dictionaries for addresses and …
Browse files Browse the repository at this point in the history
…methods (#8)

* Add back mention and OK status discord message

* Add POW Signal monitoring plus list of dictionaries
  • Loading branch information
Rodebrechtd authored Oct 2, 2024
1 parent 1eb10be commit 2eba381
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 57 deletions.
129 changes: 74 additions & 55 deletions pow_monitoring.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@
LILYTEAM_DISCORD_ROLE,
LILYPAD_ADVOCATE_DISCORD_ROLE,
POW_CONTRACT_ADDRESS,
POW_CONTRACT_ADDRESS_URL,
POW_CONTRACT_ADDRESS_METHOD,
POW_SIGNAL_CRON_ADDRESS,
POW_SIGNAL_CRON_ADDRESS_METHOD,
ARBISCAN_API_KEY,
ARBISCAN_API_ENDPOINT
ARBISCAN_API_ENDPOINT,
ARBISCAN_ADDRESS_URL
)

# Constants to set the warning and error thresholds for time since the last transaction
Expand All @@ -18,40 +21,41 @@
# Discord roles that will be tagged in the message
discord_roles = f"{LILYTEAM_DISCORD_ROLE}, {LILYPAD_ADVOCATE_DISCORD_ROLE}"

def get_latest_transaction_time():
def get_latest_transaction_time(contract_address: str, method_signature: str):
"""
Fetch the latest transaction timestamp from the Etherscan API for the given contract address.
Returns the timestamp as a datetime object if successful, or None if an error occurs.
Fetch the latest transaction timestamp and method from the Etherscan API for the given contract address and method.
Returns the timestamp as a datetime object and method name if successful, or None if an error occurs.
"""
# Parameters for querying the Etherscan API to get the list of transactions for a specific address
params = {
'module': 'account',
'action': 'txlist',
'address': POW_CONTRACT_ADDRESS, # Contract address for the Lilypad POW
'address': contract_address, # Dynamic contract address
'startblock': 0, # Start from the first block
'endblock': 99999999, # End at the latest block
'sort': 'desc', # Sort transactions by newest first
'apikey': ARBISCAN_API_KEY # API key for authenticating the request
'apikey': ARBISCAN_API_KEY
}

# Make the request to Etherscan API
response = requests.get(ARBISCAN_API_ENDPOINT, params=params)

if response.status_code == 200: # Check if the API call was successful
data = response.json() # Parse the JSON response
if response.status_code == 200:
data = response.json()

# If status is '1' (success) and transactions are available
if data['status'] == '1' and len(data['result']) > 0:
latest_tx = data['result'][0] # Get the latest transaction (first one in the sorted list)
timestamp = int(latest_tx['timeStamp']) # Extract the timestamp from the transaction data
return datetime.fromtimestamp(timestamp, tz=timezone.utc) # Convert timestamp to datetime object
for tx in data['result']:
method = tx['input'][:10] # First 10 characters (0x + 4-byte selector)

if method == method_signature: # Match the method signature
timestamp = int(tx['timeStamp'])
return datetime.fromtimestamp(timestamp, tz=timezone.utc), method # Return timestamp and method
print(f"No matching transactions found for method {method_signature} on contract {contract_address}.")
return None, None
else:
print("No transactions found for this address.")
return None
print(f"No transactions found for contract {contract_address}.")
return None, None
else:
# Print an error message if the API request fails
print("Error fetching data from Etherscan.")
return None
return None, None

def format_time_difference(seconds):
"""
Expand All @@ -61,48 +65,63 @@ def format_time_difference(seconds):
remaining_seconds = seconds % 60 # Get the remaining seconds after the minutes
return f"{int(minutes)} minutes and {int(remaining_seconds)} seconds" # Return formatted string

def monitor_transactions():
def monitor_transactions(contract_methods):
"""
Monitors the latest transaction time of the Lilypad POW contract.
Sends a Discord message if the time since the last transaction exceeds specified thresholds.
"""
# Get the timestamp of the latest transaction
latest_transaction_time = get_latest_transaction_time()

if latest_transaction_time is None: # Exit if there was an issue fetching the transaction
return
Monitors the latest transactions for multiple contract addresses and methods.
Sends a unified Discord message with the results of all checks.
# Get the current time in UTC
current_time = datetime.now(timezone.utc)
:param contract_methods: A list of dictionaries, each containing 'contract_address' and 'method_signature'.
"""
# Initialize message parts
info_messages = []

# Calculate the difference between the current time and the time of the last transaction
time_diff = current_time - latest_transaction_time
total_seconds_since_last_tx = time_diff.total_seconds() # Get the difference in total seconds
for contract in contract_methods:
contract_address = contract['contract_address']
method_signature = contract['method_signature']
method_name = contract.get('method_name', method_signature)

# Info message to include with the alerts, contains the contract URL and Discord roles to tag
info_message = f"Lilypad POW Contract Address: {POW_CONTRACT_ADDRESS_URL} \n {discord_roles}"

# Check if the time since the last transaction exceeds the error threshold
if total_seconds_since_last_tx > error_threshold_minutes * 60:
error_message = f"**ERROR**: Last transaction was {format_time_difference(total_seconds_since_last_tx)} ago"
print(error_message) # Print error message
# Send an error alert to the specified Discord webhook
send_discord_message(POW_MONITORING_WEBHOOK, f"🔴 {error_message} \n {info_message}")
# Get the timestamp and method of the latest transaction
latest_transaction_time, latest_method = get_latest_transaction_time(contract_address, method_signature)

if latest_transaction_time is None or latest_method is None: # Skip if no matching transaction
continue

# Get the current time in UTC
current_time = datetime.now(timezone.utc)

# Check if the time since the last transaction exceeds the warning threshold
elif total_seconds_since_last_tx > warning_threshold_minutes * 60:
warning_message = f"**WARNING**: Last transaction was {format_time_difference(total_seconds_since_last_tx)} ago"
print(warning_message) # Print warning message
# Send a warning alert to the specified Discord webhook
send_discord_message(POW_MONITORING_WEBHOOK, f"⚠️ {warning_message} \n {info_message}")
# Calculate the difference between the current time and the time of the last transaction
time_diff = current_time - latest_transaction_time
total_seconds_since_last_tx = time_diff.total_seconds() # Get the difference in total seconds

# Info message specific to this contract-method pair
info_message = f"Contract: [{contract_address}]({ARBISCAN_ADDRESS_URL}{contract_address}) \n Method: {method_signature}"

# Check thresholds and build appropriate messages
if total_seconds_since_last_tx > error_threshold_minutes * 60:
error_message = f"**ERROR**: Last transaction for __**{method_name}**__ was {format_time_difference(total_seconds_since_last_tx)} ago"
print(error_message)
info_messages.append(f"### 🔴 {error_message} \n {info_message} \n {discord_roles}")
elif total_seconds_since_last_tx > warning_threshold_minutes * 60:
warning_message = f"**WARNING**: Last transaction for __**{method_name}**__ was {format_time_difference(total_seconds_since_last_tx)} ago"
print(warning_message)
info_messages.append(f"### ⚠️ {warning_message} \n {info_message} \n {discord_roles}")
else:
ok_message = f"**OK**: Last transaction for __**{method_name}**__ was {format_time_difference(total_seconds_since_last_tx)} ago."
print(ok_message)
info_messages.append(f"### 🟢 {ok_message} \n {info_message}")

else:
# If the time since the last transaction is within acceptable limits
ok_message = f"**OK**: Last transaction was {format_time_difference(total_seconds_since_last_tx)} ago."
print(ok_message) # Print OK message
# Send an OK alert to the specified Discord webhook
send_discord_message(POW_MONITORING_WEBHOOK, f"🟢 {ok_message}")
# Combine all the messages into a single message to send to Discord
final_message = "\n\n".join(info_messages)

if final_message:
send_discord_message(POW_MONITORING_WEBHOOK, final_message)

# If this script is run directly, call the monitor_transactions function
if __name__ == "__main__":
monitor_transactions()
# Define the contract addresses and method signatures to monitor
contract_methods = [
{'contract_address': POW_CONTRACT_ADDRESS, 'method_signature': POW_CONTRACT_ADDRESS_METHOD, 'method_name': 'POW Contract'},
{'contract_address': POW_SIGNAL_CRON_ADDRESS, 'method_signature': POW_SIGNAL_CRON_ADDRESS_METHOD, 'method_name': 'POW Signal/Cron'}
]

monitor_transactions(contract_methods)
8 changes: 6 additions & 2 deletions vars.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@
# Arbiscan
ARBISCAN_API_KEY = os.getenv('ARBISCAN_API_KEY')
ARBISCAN_API_ENDPOINT = f'https://api-sepolia.arbiscan.io/api'
ARBISCAN_ADDRESS_URL = "https://sepolia.arbiscan.io/address/"

#Lilypad Addresses
POW_CONTRACT_ADDRESS = "0x8b852ba45293d6dd51b10c57625c6c5f25adfb40"
POW_CONTRACT_ADDRESS_URL = f"https://sepolia.arbiscan.io/address/{
POW_CONTRACT_ADDRESS}"
POW_CONTRACT_ADDRESS_METHOD = "0xda8accf9"
POW_SIGNAL_CRON_ADDRESS = "0xd10d15cc705f7d2558352b1212a9b3685155d93d"
POW_SIGNAL_CRON_ADDRESS_METHOD = "0xb681f2fd"

0 comments on commit 2eba381

Please sign in to comment.