Skip to content

Commit 3f434c4

Browse files
committed
Added implementation for Ultra and Swap API
1 parent 06e0635 commit 3f434c4

19 files changed

Lines changed: 468 additions & 48 deletions

jup_ag_sdk/client.py

Lines changed: 0 additions & 17 deletions
This file was deleted.

jup_ag_sdk/clients/__init__.py

Whitespace-only changes.
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import base64
2+
import os
3+
4+
import base58
5+
import httpx
6+
from solana.rpc.api import Client
7+
from solders.solders import Keypair, VersionedTransaction
8+
9+
10+
class JupiterClient:
11+
"""
12+
Base client for interacting with Jupiter API. Also acts as a parent class for all sub-clients.
13+
"""
14+
15+
def __init__(self, api_key, rpc_url, private_key_env_var, timeout):
16+
self.api_key = api_key
17+
self.rpc = Client(rpc_url) if rpc_url else None
18+
self.base_url = "https://api.jup.ag" if api_key else "https://lite-api.jup.ag"
19+
self.private_key_env_var = private_key_env_var
20+
self.timeout = timeout
21+
self.client = httpx.Client(timeout=self.timeout)
22+
23+
def close(self):
24+
self.client.close()
25+
26+
def _get_headers(self):
27+
headers = {
28+
"Accept": "application/json",
29+
}
30+
if self.api_key:
31+
headers["x-api-key"] = self.api_key
32+
return headers
33+
34+
def _post_headers(self):
35+
headers = {
36+
"Accept": "application/json",
37+
"Content-Type": "application/json",
38+
}
39+
if self.api_key:
40+
headers["x-api-key"] = self.api_key
41+
return headers
42+
43+
def _get_public_key(self):
44+
wallet = Keypair.from_bytes(
45+
base58.b58decode(os.getenv(self.private_key_env_var))
46+
)
47+
return str(wallet.pubkey())
48+
49+
def _send_transaction(self, transaction):
50+
return self.rpc.send_transaction(transaction)
51+
52+
def _sign_base64_transaction(self, transaction_base64: str):
53+
transaction_bytes = base64.b64decode(transaction_base64)
54+
versioned_transaction = VersionedTransaction.from_bytes(transaction_bytes)
55+
return self._sign_versioned_transaction(versioned_transaction)
56+
57+
def _sign_versioned_transaction(self, versioned_transaction: VersionedTransaction):
58+
wallet = Keypair.from_bytes(
59+
base58.b58decode(os.getenv(self.private_key_env_var))
60+
)
61+
account_keys = versioned_transaction.message.account_keys
62+
wallet_index = account_keys.index(wallet.pubkey())
63+
64+
signers = list(versioned_transaction.signatures)
65+
signers[wallet_index] = wallet
66+
67+
return VersionedTransaction(versioned_transaction.message, signers)
68+
69+
def _serialize_versioned_transaction(self, versioned_transaction: VersionedTransaction):
70+
return base64.b64encode(bytes(versioned_transaction)).decode("utf-8")
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
from jup_ag_sdk.clients.jupiter_client import JupiterClient
2+
from jup_ag_sdk.models.swap_api.quote_request_model import QuoteRequest
3+
from jup_ag_sdk.models.swap_api.swap_request_model import SwapRequest
4+
5+
6+
class SwapApiClient(JupiterClient):
7+
"""
8+
A client for interacting with the Jupiter Swap API. Inherits from JupiterClient.
9+
"""
10+
11+
def __init__(
12+
self, api_key=None, rpc_url="https://api.mainnet-beta.solana.com", private_key_env_var="PRIVATE_KEY", timeout=10
13+
):
14+
super().__init__(
15+
api_key=api_key,
16+
rpc_url=rpc_url,
17+
private_key_env_var=private_key_env_var,
18+
timeout=timeout,
19+
)
20+
21+
def quote(self, request: QuoteRequest) -> dict:
22+
"""
23+
Get a swap quote from the Jupiter API.
24+
25+
Args:
26+
request (QuoteRequest): The request parameters for the quote.
27+
28+
Returns:
29+
dict: The dict api response.
30+
"""
31+
params = request.to_dict()
32+
33+
url = f"{self.base_url}/swap/v1/quote"
34+
response = self.client.get(url, params=params, headers=self._get_headers())
35+
response.raise_for_status()
36+
37+
return response.json()
38+
39+
def swap(self, request: SwapRequest) -> dict:
40+
"""
41+
Submit a swap request to the Jupiter API.
42+
43+
Args:
44+
request (SwapRequest): The swap request parameters.
45+
46+
Returns:
47+
dict: The parsed response containing transaction details and metadata.
48+
"""
49+
payload = request.to_dict()
50+
51+
url = f"{self.base_url}/swap/v1/swap"
52+
response = self.client.post(url, json=payload, headers=self._get_headers())
53+
response.raise_for_status()
54+
55+
return response.json()
56+
57+
def swap_and_execute(self, request: SwapRequest) -> dict:
58+
"""
59+
Submit a swap request to the Jupiter API and execute it immediately.
60+
61+
Args:
62+
request (SwapRequest): The swap request parameters.
63+
64+
Returns:
65+
dict: The raw RPC response containing the result of the transaction execution.
66+
"""
67+
swap_response = self.swap(request)
68+
signed_transaction = self._sign_base64_transaction(
69+
swap_response["swapTransaction"]
70+
)
71+
return self._send_transaction(signed_transaction)
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
from jup_ag_sdk.clients.jupiter_client import JupiterClient
2+
from jup_ag_sdk.models.ultra_api.ultra_execute_request_model import UltraExecuteRequest
3+
from jup_ag_sdk.models.ultra_api.ultra_order_request_model import UltraOrderRequest
4+
5+
6+
class UltraApiClient(JupiterClient):
7+
"""
8+
A client for interacting with the Jupiter Swap API. Inherits from JupiterClient.
9+
"""
10+
11+
def __init__(
12+
self, api_key=None, private_key_env_var="PRIVATE_KEY", timeout=10
13+
):
14+
super().__init__(
15+
api_key=api_key,
16+
rpc_url=None,
17+
private_key_env_var=private_key_env_var,
18+
timeout=timeout,
19+
)
20+
21+
def order(self, request: UltraOrderRequest) -> dict:
22+
"""
23+
Get an order from the Jupiter Ultra API.
24+
25+
Args:
26+
request (UltraOrderRequest): The request parameters for the order.
27+
28+
Returns:
29+
dict: The dict api response.
30+
"""
31+
params = request.to_dict()
32+
33+
url = f"{self.base_url}/ultra/v1/order"
34+
response = self.client.get(url, params=params, headers=self._get_headers())
35+
response.raise_for_status()
36+
37+
return response.json()
38+
39+
def execute(self, request: UltraExecuteRequest) -> dict:
40+
"""
41+
Execute the order with the Jupiter Ultra API.
42+
43+
Args:
44+
request (UltraExecuteRequest): The execute request parameters.
45+
46+
Returns:
47+
dict: The dict api response.
48+
"""
49+
payload = request.to_dict()
50+
51+
url = f"{self.base_url}/ultra/v1/execute"
52+
response = self.client.post(url, json=payload, headers=self._get_headers())
53+
response.raise_for_status()
54+
55+
return response.json()
56+
57+
def order_and_execute(self, request: UltraOrderRequest) -> dict:
58+
"""
59+
Get an order from the Jupiter Ultra API and execute it immediately.
60+
61+
Args:
62+
request (UltraOrderRequest): The request parameters for the order.
63+
64+
Returns:
65+
dict: The dict api response.
66+
"""
67+
order_response = self.order(request)
68+
69+
request_id = order_response["requestId"]
70+
signed_transaction = self._sign_base64_transaction(
71+
order_response["transaction"]
72+
)
73+
74+
execute_request = UltraExecuteRequest(
75+
request_id=request_id,
76+
signed_transaction=self._serialize_versioned_transaction(signed_transaction),
77+
)
78+
79+
return self.execute(execute_request)

jup_ag_sdk/example.py

Lines changed: 0 additions & 2 deletions
This file was deleted.

jup_ag_sdk/models/__init__.py

Whitespace-only changes.

jup_ag_sdk/models/common/__init__.py

Whitespace-only changes.
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from enum import Enum
2+
from urllib.parse import quote
3+
4+
5+
class DexEnum(str, Enum):
6+
WOOFI = "Woofi"
7+
PUMP_FUN = "Pump.fun"
8+
WHIRLPOOL = "Whirlpool"
9+
VIRTUALS = "Virtuals"
10+
DAOS_FUN = "Daos.fun"
11+
LIFINITY_V2 = "Lifinity V2"
12+
STABBLE_STABLE_SWAP = "Stabble Stable Swap"
13+
TOKEN_MILL = "Token Mill"
14+
METEORA = "Meteora"
15+
OASIS = "Oasis"
16+
ALDRIN = "Aldrin"
17+
GOOSEFX_GAMMA = "GooseFX GAMMA"
18+
PERPS = "Perps"
19+
SOLFI = "SolFi"
20+
DEXLAB = "DexLab"
21+
TOKEN_SWAP = "Token Swap"
22+
ZEROFI = "ZeroFi"
23+
CROPPER = "Cropper"
24+
OBRIC_V2 = "Obric V2"
25+
STABBLE_WEIGHTED_SWAP = "Stabble Weighted Swap"
26+
SANCTUM_INFINITY = "Sanctum Infinity"
27+
MOONIT = "Moonit"
28+
SANCTUM = "Sanctum"
29+
RAYDIUM_CP = "Raydium CP"
30+
PHOENIX = "Phoenix"
31+
PUMP_FUN_AMM = "Pump.fun Amm"
32+
SABER = "Saber"
33+
SABER_DECIMALS = "Saber (Decimals)"
34+
RAYDIUM_CLMM = "Raydium CLMM"
35+
DEX_1 = "1DEX"
36+
PENGUIN = "Penguin"
37+
ORCA_V2 = "Orca V2"
38+
FLUXBEAM = "FluxBeam"
39+
RAYDIUM = "Raydium"
40+
METEORA_DLMM = "Meteora DLMM"
41+
BONKSWAP = "Bonkswap"
42+
SOLAYER = "Solayer"
43+
STEPN = "StepN"
44+
HELIUM_NETWORK = "Helium Network"
45+
MERCURIAL = "Mercurial"
46+
PERENA = "Perena"
47+
ORCA_V1 = "Orca V1"
48+
ALDRIN_V2 = "Aldrin V2"
49+
SAROS = "Saros"
50+
OPENBOOK_V2 = "OpenBook V2"
51+
CREMA = "Crema"
52+
OPENBOOK = "Openbook"
53+
INVARIANT = "Invariant"
54+
GUACSWAP = "Guacswap"
55+
56+
def __str__(self) -> str:
57+
return quote(self.value)

jup_ag_sdk/models/swap_api/__init__.py

Whitespace-only changes.

0 commit comments

Comments
 (0)