Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ __pycache__/
# C extensions
*.so

# macOS system files
.DS_Store

# Distribution / packaging
.Python
build/
Expand Down
8 changes: 7 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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.
This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for more details.
8 changes: 6 additions & 2 deletions jito_py/block_engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
63 changes: 62 additions & 1 deletion jito_py/searcher.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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
Expand Down Expand Up @@ -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.
Expand Down