repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
public Clawd ADK gateway launch mirror
stars
latest
clone command
git clone gitlawb://did:key:z6Mkq5mY...iFZ5/my-project-publ...git clone gitlawb://did:key:z6Mkq5mY.../my-project-publ...2fa351d6docs: add automaton and perps launch sources16d ago| #1 | """Jupiter Ultra API Client - Python wrapper for Jupiter Ultra Swap API""" |
| #2 | |
| #3 | import httpx |
| #4 | import base64 |
| #5 | from typing import Optional, Any |
| #6 | from dataclasses import dataclass |
| #7 | from solders.keypair import Keypair |
| #8 | from solders.pubkey import Pubkey |
| #9 | from solders.transaction import VersionedTransaction |
| #10 | from solders.message import to_bytes_versioned |
| #11 | |
| #12 | |
| #13 | JUPITER_ULTRA_API = "https://api.jup.ag/ultra/v1" |
| #14 | JUPITER_TOKENS_API = "https://api.jup.ag/tokens/v2" |
| #15 | JUPITER_CONTENT_API = "https://api.jup.ag/content/v1" |
| #16 | JUPITER_PRICE_API = "https://api.jup.ag" |
| #17 | JUPITER_PORTFOLIO_API = "https://api.jup.ag/portfolio/v1" |
| #18 | WRAPPED_SOL_MINT = "So11111111111111111111111111111111111111112" |
| #19 | |
| #20 | |
| #21 | @dataclass |
| #22 | class JupiterQuote: |
| #23 | """Jupiter Ultra order response""" |
| #24 | mode: str |
| #25 | input_mint: str |
| #26 | output_mint: str |
| #27 | in_amount: str |
| #28 | out_amount: str |
| #29 | in_usd_value: float |
| #30 | out_usd_value: float |
| #31 | price_impact: float |
| #32 | swap_usd_value: float |
| #33 | other_amount_threshold: str |
| #34 | swap_mode: str |
| #35 | slippage_bps: int |
| #36 | route_plan: list |
| #37 | fee_bps: int |
| #38 | platform_fee: dict |
| #39 | signature_fee_lamports: int |
| #40 | signature_fee_payer: Optional[str] |
| #41 | prioritization_fee_lamports: int |
| #42 | prioritization_fee_payer: Optional[str] |
| #43 | rent_fee_lamports: int |
| #44 | rent_fee_payer: Optional[str] |
| #45 | router: str |
| #46 | transaction: Optional[str] |
| #47 | gasless: bool |
| #48 | request_id: str |
| #49 | total_time: float |
| #50 | taker: Optional[str] |
| #51 | quote_id: str |
| #52 | maker: str |
| #53 | expire_at: str |
| #54 | error_code: Optional[int] = None |
| #55 | error_message: Optional[str] = None |
| #56 | raw_response: dict = None |
| #57 | |
| #58 | |
| #59 | @dataclass |
| #60 | class JupiterSwapResult: |
| #61 | """Jupiter Ultra swap result""" |
| #62 | transaction: VersionedTransaction |
| #63 | quote: JupiterQuote |
| #64 | |
| #65 | |
| #66 | class JupiterClient: |
| #67 | """Client for Jupiter Ultra Swap API""" |
| #68 | |
| #69 | def __init__( |
| #70 | self, |
| #71 | api_key: Optional[str] = None, |
| #72 | wallet_pubkey: Optional[str] = None, |
| #73 | keypair: Optional[Keypair] = None, |
| #74 | referral_account: Optional[str] = None, |
| #75 | ): |
| #76 | """ |
| #77 | Initialize Jupiter Ultra client. |
| #78 | |
| #79 | Args: |
| #80 | api_key: Jupiter API key (optional but recommended) |
| #81 | wallet_pubkey: User's public key |
| #82 | keypair: Keypair for signing transactions |
| #83 | referral_account: Referral account for fee sharing |
| #84 | """ |
| #85 | headers = {} |
| #86 | if api_key: |
| #87 | headers["x-api-key"] = api_key |
| #88 | |
| #89 | self._ultra_client = httpx.AsyncClient( |
| #90 | base_url=JUPITER_ULTRA_API, |
| #91 | headers=headers, |
| #92 | timeout=httpx.Timeout(30.0) |
| #93 | ) |
| #94 | self._tokens_client = httpx.AsyncClient( |
| #95 | base_url=JUPITER_TOKENS_API, |
| #96 | headers=headers, |
| #97 | timeout=httpx.Timeout(30.0) |
| #98 | ) |
| #99 | self._content_client = httpx.AsyncClient( |
| #100 | base_url=JUPITER_CONTENT_API, |
| #101 | headers=headers, |
| #102 | timeout=httpx.Timeout(30.0) |
| #103 | ) |
| #104 | self._price_client = httpx.AsyncClient( |
| #105 | base_url=JUPITER_PRICE_API, |
| #106 | headers=headers, |
| #107 | timeout=httpx.Timeout(30.0) |
| #108 | ) |
| #109 | self._portfolio_client = httpx.AsyncClient( |
| #110 | base_url=JUPITER_PORTFOLIO_API, |
| #111 | headers=headers, |
| #112 | timeout=httpx.Timeout(30.0) |
| #113 | ) |
| #114 | self.wallet_pubkey = wallet_pubkey |
| #115 | self.keypair = keypair |
| #116 | self.referral_account = referral_account |
| #117 | |
| #118 | async def close(self): |
| #119 | """Close all HTTP clients""" |
| #120 | await self._ultra_client.aclose() |
| #121 | await self._tokens_client.aclose() |
| #122 | await self._content_client.aclose() |
| #123 | await self._price_client.aclose() |
| #124 | await self._portfolio_client.aclose() |
| #125 | |
| #126 | async def get_quote( |
| #127 | self, |
| #128 | input_mint: str, |
| #129 | output_mint: str, |
| #130 | amount: int, |
| #131 | taker: Optional[str] = None, |
| #132 | slippage_bps: int = 300, |
| #133 | receiver: Optional[str] = None, |
| #134 | payer: Optional[str] = None, |
| #135 | referral_account: Optional[str] = None, |
| #136 | referral_fee: Optional[int] = None, |
| #137 | exclude_routers: Optional[list[str]] = None, |
| #138 | exclude_dexes: Optional[list[str]] = None, |
| #139 | ) -> JupiterQuote: |
| #140 | """ |
| #141 | Get a swap quote from Jupiter Ultra. |
| #142 | |
| #143 | Args: |
| #144 | input_mint: Input token mint address |
| #145 | output_mint: Output token mint address |
| #146 | amount: Amount in smallest unit (lamports) |
| #147 | taker: Taker public key (defaults to wallet_pubkey) |
| #148 | slippage_bps: Slippage tolerance in basis points (default: 300 = 3%) |
| #149 | receiver: Receiver public key for output tokens |
| #150 | payer: Payer public key for gas fees |
| #151 | referral_account: Referral account for fees |
| #152 | referral_fee: Referral fee in basis points (50-255) |
| #153 | exclude_routers: Routers to exclude (iris, jupiterz, dflow, okx) |
| #154 | exclude_dexes: DEXes to exclude (e.g., "Raydium,Orca V2") |
| #155 | |
| #156 | Returns: |
| #157 | JupiterQuote with order details and unsigned transaction |
| #158 | """ |
| #159 | if taker is None: |
| #160 | if self.wallet_pubkey is None: |
| #161 | raise ValueError("No wallet configured and no taker provided") |
| #162 | taker = self.wallet_pubkey |
| #163 | |
| #164 | params = { |
| #165 | "inputMint": input_mint, |
| #166 | "outputMint": output_mint, |
| #167 | "amount": str(amount), |
| #168 | "taker": taker, |
| #169 | } |
| #170 | |
| #171 | if receiver: |
| #172 | params["receiver"] = receiver |
| #173 | if payer: |
| #174 | params["payer"] = payer |
| #175 | if referral_account: |
| #176 | params["referralAccount"] = referral_account |
| #177 | if referral_fee: |
| #178 | params["referralFee"] = referral_fee |
| #179 | if exclude_routers: |
| #180 | params["excludeRouters"] = ",".join(exclude_routers) |
| #181 | if exclude_dexes: |
| #182 | params["excludeDexes"] = exclude_dexes |
| #183 | |
| #184 | response = await self._ultra_client.get("/order", params=params) |
| #185 | response.raise_for_status() |
| #186 | data = response.json() |
| #187 | |
| #188 | return JupiterQuote( |
| #189 | mode=data["mode"], |
| #190 | input_mint=data["inputMint"], |
| #191 | output_mint=data["outputMint"], |
| #192 | in_amount=data["inAmount"], |
| #193 | out_amount=data["outAmount"], |
| #194 | in_usd_value=data.get("inUsdValue", 0), |
| #195 | out_usd_value=data.get("outUsdValue", 0), |
| #196 | price_impact=data.get("priceImpact", 0), |
| #197 | swap_usd_value=data.get("swapUsdValue", 0), |
| #198 | other_amount_threshold=data["otherAmountThreshold"], |
| #199 | swap_mode=data["swapMode"], |
| #200 | slippage_bps=data["slippageBps"], |
| #201 | route_plan=data["routePlan"], |
| #202 | fee_bps=data["feeBps"], |
| #203 | platform_fee=data["platformFee"], |
| #204 | signature_fee_lamports=data["signatureFeeLamports"], |
| #205 | signature_fee_payer=data.get("signatureFeePayer"), |
| #206 | prioritization_fee_lamports=data["prioritizationFeeLamports"], |
| #207 | prioritization_fee_payer=data.get("prioritizationFeePayer"), |
| #208 | rent_fee_lamports=data["rentFeeLamports"], |
| #209 | rent_fee_payer=data.get("rentFeePayer"), |
| #210 | router=data["router"], |
| #211 | transaction=data.get("transaction"), |
| #212 | gasless=data["gasless"], |
| #213 | request_id=data["requestId"], |
| #214 | total_time=data["totalTime"], |
| #215 | taker=data.get("taker"), |
| #216 | quote_id=data["quoteId"], |
| #217 | maker=data["maker"], |
| #218 | expire_at=data["expireAt"], |
| #219 | error_code=data.get("errorCode"), |
| #220 | error_message=data.get("errorMessage"), |
| #221 | raw_response=data, |
| #222 | ) |
| #223 | |
| #224 | async def create_swap_transaction(self, quote: JupiterQuote) -> JupiterSwapResult: |
| #225 | """ |
| #226 | Create a swap transaction from a quote. |
| #227 | |
| #228 | Args: |
| #229 | quote: JupiterQuote from get_quote() |
| #230 | |
| #231 | Returns: |
| #232 | JupiterSwapResult with versioned transaction ready for signing |
| #233 | """ |
| #234 | if quote.transaction is None: |
| #235 | error_msg = quote.error_message or "No transaction returned" |
| #236 | raise Exception(f"Cannot create swap: {error_msg}") |
| #237 | |
| #238 | # Decode the base64 transaction |
| #239 | tx_bytes = base64.b64decode(quote.transaction) |
| #240 | transaction = VersionedTransaction.from_bytes(tx_bytes) |
| #241 | |
| #242 | return JupiterSwapResult( |
| #243 | transaction=transaction, |
| #244 | quote=quote, |
| #245 | ) |
| #246 | |
| #247 | async def sign_and_execute(self, swap_result: JupiterSwapResult) -> str: |
| #248 | """ |
| #249 | Sign and execute a swap transaction. |
| #250 | |
| #251 | Args: |
| #252 | swap_result: JupiterSwapResult from create_swap_transaction() |
| #253 | |
| #254 | Returns: |
| #255 | Transaction signature |
| #256 | """ |
| #257 | if self.keypair is None: |
| #258 | raise ValueError("No keypair configured for signing") |
| #259 | |
| #260 | # Sign the versioned transaction correctly |
| #261 | tx = swap_result.transaction |
| #262 | |
| #263 | # Get message bytes and sign |
| #264 | message_bytes = to_bytes_versioned(tx.message) |
| #265 | signature = self.keypair.sign_message(message_bytes) |
| #266 | |
| #267 | # Create signed transaction |
| #268 | signed_tx = VersionedTransaction.populate(tx.message, [signature]) |
| #269 | |
| #270 | # Encode to base64 |
| #271 | tx_bytes = bytes(signed_tx) |
| #272 | tx_base64 = base64.b64encode(tx_bytes).decode() |
| #273 | |
| #274 | # Execute via Jupiter |
| #275 | response = await self._ultra_client.post( |
| #276 | "/execute", |
| #277 | json={ |
| #278 | "requestId": swap_result.quote.request_id, |
| #279 | "transaction": tx_base64, |
| #280 | } |
| #281 | ) |
| #282 | response.raise_for_status() |
| #283 | data = response.json() |
| #284 | |
| #285 | # Return signature |
| #286 | return data.get("signature", str(signature)) |
| #287 | |
| #288 | async def swap( |
| #289 | self, |
| #290 | input_mint: str, |
| #291 | output_mint: str, |
| #292 | amount: int, |
| #293 | slippage_bps: int = 300, |
| #294 | ) -> str: |
| #295 | """ |
| #296 | Convenience method to get quote, sign, and execute in one call. |
| #297 | |
| #298 | Args: |
| #299 | input_mint: Input token mint address |
| #300 | output_mint: Output token mint address |
| #301 | amount: Amount in smallest unit (lamports) |
| #302 | slippage_bps: Slippage tolerance in basis points (default: 300 = 3%) |
| #303 | |
| #304 | Returns: |
| #305 | Transaction signature |
| #306 | """ |
| #307 | # Get quote |
| #308 | quote = await self.get_quote( |
| #309 | input_mint=input_mint, |
| #310 | output_mint=output_mint, |
| #311 | amount=amount, |
| #312 | slippage_bps=slippage_bps, |
| #313 | ) |
| #314 | |
| #315 | # Create transaction |
| #316 | swap_result = await self.create_swap_transaction(quote) |
| #317 | |
| #318 | # Sign and execute |
| #319 | signature = await self.sign_and_execute(swap_result) |
| #320 | |
| #321 | return signature |
| #322 | |
| #323 | async def buy_token( |
| #324 | self, |
| #325 | token_mint: str, |
| #326 | sol_amount: float, |
| #327 | slippage_bps: int = 300, |
| #328 | ) -> str: |
| #329 | """ |
| #330 | Buy a token with SOL. |
| #331 | |
| #332 | Args: |
| #333 | token_mint: Token mint address to buy |
| #334 | sol_amount: Amount of SOL to spend |
| #335 | slippage_bps: Slippage tolerance in basis points |
| #336 | |
| #337 | Returns: |
| #338 | Transaction signature |
| #339 | """ |
| #340 | # Convert SOL to lamports |
| #341 | lamports = int(sol_amount * 1_000_000_000) |
| #342 | |
| #343 | return await self.swap( |
| #344 | input_mint=WRAPPED_SOL_MINT, |
| #345 | output_mint=token_mint, |
| #346 | amount=lamports, |
| #347 | slippage_bps=slippage_bps, |
| #348 | ) |
| #349 | |
| #350 | async def sell_token( |
| #351 | self, |
| #352 | token_mint: str, |
| #353 | token_amount: int, |
| #354 | slippage_bps: int = 300, |
| #355 | ) -> str: |
| #356 | """ |
| #357 | Sell a token for SOL. |
| #358 | |
| #359 | Args: |
| #360 | token_mint: Token mint address to sell |
| #361 | token_amount: Amount of tokens in smallest unit |
| #362 | slippage_bps: Slippage tolerance in basis points |
| #363 | |
| #364 | Returns: |
| #365 | Transaction signature |
| #366 | """ |
| #367 | return await self.swap( |
| #368 | input_mint=token_mint, |
| #369 | output_mint=WRAPPED_SOL_MINT, |
| #370 | amount=token_amount, |
| #371 | slippage_bps=slippage_bps, |
| #372 | ) |
| #373 | |
| #374 | async def get_holdings(self, wallet: Optional[str] = None) -> dict: |
| #375 | """ |
| #376 | Get wallet holdings from Jupiter Ultra. |
| #377 | |
| #378 | Args: |
| #379 | wallet: Wallet address (defaults to wallet_pubkey) |
| #380 | |
| #381 | Returns: |
| #382 | Holdings data with balances and values |
| #383 | """ |
| #384 | if wallet is None: |
| #385 | if self.wallet_pubkey is None: |
| #386 | raise ValueError("No wallet configured and no wallet provided") |
| #387 | wallet = self.wallet_pubkey |
| #388 | |
| #389 | response = await self._ultra_client.get(f"/holdings/{wallet}") |
| #390 | response.raise_for_status() |
| #391 | return response.json() |
| #392 | |
| #393 | async def get_shield_warnings(self, token_mint: str) -> dict: |
| #394 | """ |
| #395 | Get token warnings from Jupiter Shield. |
| #396 | |
| #397 | Args: |
| #398 | token_mint: Token mint address to check |
| #399 | |
| #400 | Returns: |
| #401 | Shield warnings and safety information |
| #402 | """ |
| #403 | response = await self._ultra_client.get(f"/shield/{token_mint}") |
| #404 | response.raise_for_status() |
| #405 | return response.json() |
| #406 | |
| #407 | async def search_tokens( |
| #408 | self, |
| #409 | query: str, |
| #410 | limit: int = 20, |
| #411 | verified_only: bool = False, |
| #412 | ) -> dict: |
| #413 | """ |
| #414 | Search for tokens using Jupiter Tokens V2 API. |
| #415 | |
| #416 | Args: |
| #417 | query: Search query (token name, symbol, or mint) |
| #418 | limit: Maximum results (default: 20) |
| #419 | verified_only: Only return verified tokens |
| #420 | |
| #421 | Returns: |
| #422 | Search results with token information |
| #423 | """ |
| #424 | params = { |
| #425 | "query": query, |
| #426 | "limit": limit, |
| #427 | } |
| #428 | if verified_only: |
| #429 | params["verified"] = "true" |
| #430 | |
| #431 | response = await self._tokens_client.get("/search", params=params) |
| #432 | response.raise_for_status() |
| #433 | return response.json() |
| #434 | |
| #435 | async def get_tokens_by_tag(self, tag: str) -> dict: |
| #436 | """ |
| #437 | Get tokens by tag (e.g., verified, strict, community). |
| #438 | |
| #439 | Args: |
| #440 | tag: Tag name (verified, strict, community, etc.) |
| #441 | |
| #442 | Returns: |
| #443 | List of tokens with the specified tag |
| #444 | """ |
| #445 | response = await self._tokens_client.get(f"/tag/{tag}") |
| #446 | response.raise_for_status() |
| #447 | return response.json() |
| #448 | |
| #449 | async def get_trending_tokens( |
| #450 | self, |
| #451 | category: str = "top", |
| #452 | interval: str = "24h", |
| #453 | ) -> dict: |
| #454 | """ |
| #455 | Get trending tokens by category and time interval. |
| #456 | |
| #457 | Args: |
| #458 | category: Category (top, gainers, losers, new, volume) |
| #459 | interval: Time interval (1h, 6h, 24h, 7d, 30d) |
| #460 | |
| #461 | Returns: |
| #462 | List of trending tokens with metrics |
| #463 | """ |
| #464 | response = await self._tokens_client.get(f"/{category}/{interval}") |
| #465 | response.raise_for_status() |
| #466 | return response.json() |
| #467 | |
| #468 | async def get_token_content(self, token_mint: str) -> dict: |
| #469 | """ |
| #470 | Get content and metadata for a token. |
| #471 | |
| #472 | Args: |
| #473 | token_mint: Token mint address |
| #474 | |
| #475 | Returns: |
| #476 | Token content including description, social links, etc. |
| #477 | """ |
| #478 | response = await self._content_client.get(f"/content/{token_mint}") |
| #479 | response.raise_for_status() |
| #480 | return response.json() |
| #481 | |
| #482 | async def get_content_feed( |
| #483 | self, |
| #484 | limit: int = 20, |
| #485 | offset: int = 0, |
| #486 | ) -> dict: |
| #487 | """ |
| #488 | Get content feed with latest token updates. |
| #489 | |
| #490 | Args: |
| #491 | limit: Maximum results (default: 20) |
| #492 | offset: Pagination offset (default: 0) |
| #493 | |
| #494 | Returns: |
| #495 | Content feed with token updates |
| #496 | """ |
| #497 | params = { |
| #498 | "limit": limit, |
| #499 | "offset": offset, |
| #500 | } |
| #501 | response = await self._content_client.get("/content/feed", params=params) |
| #502 | response.raise_for_status() |
| #503 | return response.json() |
| #504 | |
| #505 | async def get_token_prices( |
| #506 | self, |
| #507 | token_mints: list[str], |
| #508 | show_extra_info: bool = False, |
| #509 | ) -> dict: |
| #510 | """ |
| #511 | Get token prices from Jupiter Price V3 API. |
| #512 | |
| #513 | Args: |
| #514 | token_mints: List of token mint addresses |
| #515 | show_extra_info: Include extra price information |
| #516 | |
| #517 | Returns: |
| #518 | Price data for requested tokens |
| #519 | """ |
| #520 | params = { |
| #521 | "ids": ",".join(token_mints), |
| #522 | } |
| #523 | if show_extra_info: |
| #524 | params["showExtraInfo"] = "true" |
| #525 | |
| #526 | response = await self._price_client.get("/price/v3", params=params) |
| #527 | response.raise_for_status() |
| #528 | return response.json() |
| #529 | |
| #530 | async def get_wallet_positions(self, wallet: Optional[str] = None) -> dict: |
| #531 | """ |
| #532 | Get wallet positions from Jupiter Portfolio API. |
| #533 | |
| #534 | Args: |
| #535 | wallet: Wallet address (defaults to wallet_pubkey) |
| #536 | |
| #537 | Returns: |
| #538 | Portfolio positions with P&L and metrics |
| #539 | """ |
| #540 | if wallet is None: |
| #541 | if self.wallet_pubkey is None: |
| #542 | raise ValueError("No wallet configured and no wallet provided") |
| #543 | wallet = self.wallet_pubkey |
| #544 | |
| #545 | response = await self._portfolio_client.get(f"/positions/{wallet}") |
| #546 | response.raise_for_status() |
| #547 | return response.json() |
| #548 |