diff --git a/.gitignore b/.gitignore index 2d0653a..2cf8fe0 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,9 @@ __pycache__/ # C extensions *.so +# macOS system files +.DS_Store + # Distribution / packaging .Python build/ diff --git a/README.md b/README.md index 0e55fee..4720622 100644 --- a/README.md +++ b/README.md @@ -41,6 +41,12 @@ searcher = Searcher(block_engine_url) tip_accounts = searcher.get_tip_accounts() print("Tip Accounts:", tip_accounts) +# Get what tip amount to use +# Directly access which Percentile Jito Fee to use for your transaction in lamports +tip_floors = searcher.get_tip_floors() +print(f"Avg Jito Tip in Lamports for Top 99 Percentile Landed Txns: " + f"{tip_floors.landed_tips_lamports_99th_percentile}") + # Get bundle statuses bundle_ids = ["your_bundle_id_here"] bundle_statuses = searcher.get_bundle_statuses(bundle_ids) @@ -59,4 +65,4 @@ print("Sent Transaction ID:", transaction_id) ## License -This project is licensed under the MIT License. See the[LICENSE](LICENSE)file for more details. \ No newline at end of file +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details. diff --git a/jito_py/block_engine.py b/jito_py/block_engine.py index 28bad5a..fa33051 100644 --- a/jito_py/block_engine.py +++ b/jito_py/block_engine.py @@ -27,8 +27,12 @@ class BlockEngine: "block_engine_url": "https://tokyo.mainnet.block-engine.jito.wtf", "shred_receiver_addr": "202.8.9.160:1002", "relayer_url": "http://tokyo.mainnet.relayer.jito.wtf:8100", - } - + }, + "Utah": { + "block_engine_url": "https://slc.mainnet.block-engine.jito.wtf", + "shred_receiver_addr": "64.130.53.8:1002", + "relayer_url": "http://slc.mainnet.relayer.jito.wtf:8100", + }, } TESTNET_ADDRESS = { "Dallas": { diff --git a/jito_py/searcher.py b/jito_py/searcher.py index 5996a6b..c325fff 100644 --- a/jito_py/searcher.py +++ b/jito_py/searcher.py @@ -1,8 +1,10 @@ from dataclasses import dataclass, field +from datetime import datetime, timezone from typing import List, Optional, Dict, Any - import requests +JITO_TIPS_ENDPOINT: str = "https://bundles.jito.wtf" + @dataclass class BundleStatus: @@ -19,6 +21,38 @@ class BundleStatusesResponse: statuses: List[BundleStatus] = field(default_factory=list) +@dataclass +class BundlesTipsFloorResponse: + time: datetime + landed_tips_lamports_25th_percentile: int + landed_tips_lamports_50th_percentile: int + landed_tips_lamports_75th_percentile: int + landed_tips_lamports_95th_percentile: int + landed_tips_lamports_99th_percentile: int + ema_landed_tips_lamports_50th_percentile: int + + @staticmethod + def from_dict(data: Dict[str, Any]) -> "BundlesTipsFloorResponse": + """ + Parses a dictionary into a BundlesTipsFloorResponse object, converting the + time string into a datetime object in UTC. + """ + # Parse the ISO8601 time string (e.g., "2025-03-12T15:38:27Z") to a datetime object in UTC + + time_str = data.get("time") + parsed_time = datetime.strptime(time_str, "%Y-%m-%dT%H:%M:%SZ").replace(tzinfo=timezone.utc) + + return BundlesTipsFloorResponse( + time=parsed_time, + landed_tips_lamports_25th_percentile=int(float(data.get("landed_tips_25th_percentile")) * (10 ** 9)), + landed_tips_lamports_50th_percentile=int(float(data.get("landed_tips_50th_percentile")) * (10 ** 9)), + landed_tips_lamports_75th_percentile=int(float(data.get("landed_tips_75th_percentile")) * (10 ** 9)), + landed_tips_lamports_95th_percentile=int(float(data.get("landed_tips_95th_percentile")) * (10 ** 9)), + landed_tips_lamports_99th_percentile=int(float(data.get("landed_tips_99th_percentile")) * (10 ** 9)), + ema_landed_tips_lamports_50th_percentile=int(float(data.get("ema_landed_tips_50th_percentile")) * (10 ** 9)) + ) + + class Searcher: def __init__(self, block_engine_url: str): self.block_engine_url = block_engine_url @@ -80,6 +114,33 @@ def get_tip_accounts(self) -> List[str]: response = self._send_rpc_request("/api/v1/bundles", "getTipAccounts") return self._extract_result(response, "getTipAccounts") + def get_tip_floors(self) -> BundlesTipsFloorResponse: + """ + Retrieves the tips floor data for landed transactions. This data reflects the average SOL tip amounts + based on various percentiles, which can be useful for understanding tip distributions for bundles and + determining the optimal tip amount to land your transaction on Jito. + + The API response is expected to be a list of dictionaries with the following keys: + - time + - landed_tips_25th_percentile + - landed_tips_50th_percentile + - landed_tips_75th_percentile + - landed_tips_95th_percentile + - landed_tips_99th_percentile + - ema_landed_tips_50th_percentile + + :return: A BundlesTipsFloorResponse object parsed from the API response. + :raises Exception: If the API request fails or the response is invalid. + """ + response = requests.get(f"{JITO_TIPS_ENDPOINT}/api/v1/bundles/tip_floor") + response.raise_for_status() # Raises an HTTPError for bad responses. + data = response.json() + if not data: + raise Exception("No tip floor data available") + + # Extract the first dictionary from the list and parse it. + return BundlesTipsFloorResponse.from_dict(data[0]) + def send_bundle(self, transactions: List[str]) -> str: """ Submits a bundled list of signed transactions (base-58 encoded strings) to the cluster for processing.