renegade
SDK for interacting with the Renegade darkpool.
1"""SDK for interacting with the Renegade darkpool.""" 2 3from .client import ( 4 ExternalMatchClient, ExternalMatchOptions, ExternalMatchClientError, 5 AssembleExternalMatchOptions, RequestQuoteOptions 6) 7from .http import RelayerHttpClient 8from .types import ExternalOrder, OrderSide, AtomicMatchApiBundle, SignedExternalQuote 9 10__all__ = [ 11 "AtomicMatchApiBundle", 12 "SignedExternalQuote", 13 "ExternalMatchClient", 14 "ExternalMatchOptions", 15 "AssembleExternalMatchOptions", 16 "RequestQuoteOptions", 17 "ExternalMatchClientError", 18 "RelayerHttpClient", 19 "ExternalOrder", 20 "OrderSide", 21]
120class AtomicMatchApiBundle(BaseModelWithConfig): 121 match_result: ApiExternalMatchResult 122 fees: FeeTake 123 receive: ApiExternalAssetTransfer 124 send: ApiExternalAssetTransfer 125 settlement_tx: TxParams
Base model with common configuration
115class SignedExternalQuote(BaseModelWithConfig): 116 quote: ApiExternalQuote 117 signature: str 118 gas_sponsorship_info: Optional[SignedGasSponsorshipInfo] = None
Base model with common configuration
191class ExternalMatchClient: 192 """Client for interacting with the Renegade external matching API. 193 194 This client handles authentication and provides methods for requesting quotes, 195 assembling matches, and executing trades. 196 """ 197 198 def __init__(self, api_key: str, api_secret: str, base_url: str): 199 """Initialize a new ExternalMatchClient. 200 201 Args: 202 api_key: The API key for authentication 203 api_secret: The API secret for request signing 204 base_url: The base URL of the Renegade API 205 """ 206 self.api_key = api_key 207 self.http_client = RelayerHttpClient(base_url, api_secret) 208 209 @classmethod 210 def new_arbitrum_sepolia_client(cls, api_key: str, api_secret: str) -> "ExternalMatchClient": 211 """Create a new client configured for the Arbitrum Sepolia testnet. 212 213 Args: 214 api_key: The API key for authentication 215 api_secret: The API secret for request signing 216 217 Returns: 218 A new ExternalMatchClient configured for Arbitrum Sepolia 219 """ 220 return cls(api_key, api_secret, ARBITRUM_SEPOLIA_BASE_URL) 221 222 @classmethod 223 def new_base_sepolia_client(cls, api_key: str, api_secret: str) -> "ExternalMatchClient": 224 """Create a new client configured for Base Sepolia testnet. 225 226 Args: 227 api_key: The API key for authentication 228 api_secret: The API secret for request signing 229 230 Returns: 231 A new ExternalMatchClient configured for Base Sepolia 232 """ 233 return cls(api_key, api_secret, BASE_SEPOLIA_BASE_URL) 234 235 @classmethod 236 @deprecated(version="0.1.8", reason="Use new_arbitrum_sepolia_client instead") 237 def new_sepolia_client(cls, api_key: str, api_secret: str) -> "ExternalMatchClient": 238 """Create a new client configured for the Arbitrum Sepolia testnet. 239 240 Args: 241 api_key: The API key for authentication 242 api_secret: The API secret for request signing 243 244 Returns: 245 A new ExternalMatchClient configured for Arbitrum Sepolia 246 """ 247 return cls.new_arbitrum_sepolia_client(api_key, api_secret) 248 249 @classmethod 250 def new_arbitrum_one_client(cls, api_key: str, api_secret: str) -> "ExternalMatchClient": 251 """Create a new client configured for Arbitrum One. 252 253 Args: 254 api_key: The API key for authentication 255 api_secret: The API secret for request signing 256 257 Returns: 258 A new ExternalMatchClient configured for Arbitrum One 259 """ 260 return cls(api_key, api_secret, ARBITRUM_ONE_BASE_URL) 261 262 @classmethod 263 def new_base_mainnet_client(cls, api_key: str, api_secret: str) -> "ExternalMatchClient": 264 """Create a new client configured for Base mainnet. 265 266 Args: 267 api_key: The API key for authentication 268 api_secret: The API secret for request signing 269 270 Returns: 271 A new ExternalMatchClient configured for Base mainnet 272 """ 273 return cls(api_key, api_secret, BASE_MAINNET_BASE_URL) 274 275 @classmethod 276 @deprecated(version="0.1.8", reason="Use new_arbitrum_one_client instead") 277 def new_mainnet_client(cls, api_key: str, api_secret: str) -> "ExternalMatchClient": 278 """Create a new client configured for mainnet. 279 280 Args: 281 api_key: The API key for authentication 282 api_secret: The API secret for request signing 283 284 Returns: 285 A new ExternalMatchClient configured for mainnet 286 """ 287 return cls.new_arbitrum_one_client(api_key, api_secret) 288 289 async def request_quote(self, order: ExternalOrder) -> Optional[SignedExternalQuote]: 290 """Request a quote for the given order. 291 292 Args: 293 order: The order to request a quote for 294 295 Returns: 296 A signed quote if one is available, None otherwise 297 298 Raises: 299 ExternalMatchClientError: If the request fails 300 """ 301 return await self.request_quote_with_options(order, RequestQuoteOptions()) 302 303 def request_quote_sync(self, order: ExternalOrder) -> Optional[SignedExternalQuote]: 304 """Synchronous version of request_quote method. 305 306 Args: 307 order: The order to request a quote for 308 309 Returns: 310 A signed quote if one is available, None otherwise 311 312 Raises: 313 ExternalMatchClientError: If the request fails 314 """ 315 return self.request_quote_with_options_sync(order, RequestQuoteOptions()) 316 317 async def request_quote_with_options( 318 self, 319 order: ExternalOrder, 320 options: RequestQuoteOptions 321 ) -> Optional[SignedExternalQuote]: 322 """Request a quote for the given order with custom options. 323 324 Args: 325 order: The order to request a quote for 326 options: Custom options for the quote request 327 328 Returns: 329 A signed quote if one is available, None otherwise 330 331 Raises: 332 ExternalMatchClientError: If the request fails 333 """ 334 request = ExternalQuoteRequest(external_order=order) 335 336 path = options.build_request_path() 337 headers = self._get_headers() 338 response = await self.http_client.post_with_headers(path, request.model_dump(), headers) 339 quote_resp = self._handle_optional_response(response) 340 341 if quote_resp == None: 342 return None 343 344 quote_resp = ExternalQuoteResponse(**quote_resp) 345 signed_quote = SignedExternalQuote( 346 quote=quote_resp.signed_quote.quote, 347 signature=quote_resp.signed_quote.signature, 348 gas_sponsorship_info=quote_resp.gas_sponsorship_info 349 ) 350 351 return signed_quote 352 353 def request_quote_with_options_sync( 354 self, 355 order: ExternalOrder, 356 options: RequestQuoteOptions 357 ) -> Optional[ExternalQuoteResponse]: 358 """Synchronous version of request_quote_with_options method. 359 360 Args: 361 order: The order to request a quote for 362 options: Custom options for the quote request 363 364 Returns: 365 A signed quote if one is available, None otherwise 366 367 Raises: 368 ExternalMatchClientError: If the request fails 369 """ 370 request = ExternalQuoteRequest(external_order=order) 371 372 path = options.build_request_path() 373 headers = self._get_headers() 374 response = self.http_client.post_with_headers_sync(path, request.model_dump(), headers) 375 quote_resp = self._handle_optional_response(response) 376 377 if quote_resp == None: 378 return None 379 380 quote_resp = ExternalQuoteResponse(**quote_resp) 381 signed_quote = SignedExternalQuote( 382 quote=quote_resp.signed_quote.quote, 383 signature=quote_resp.signed_quote.signature, 384 gas_sponsorship_info=quote_resp.gas_sponsorship_info 385 ) 386 387 return signed_quote 388 389 async def assemble_quote(self, quote: SignedExternalQuote) -> Optional[ExternalMatchResponse]: 390 """Assemble a quote into a match bundle with default options. 391 392 Args: 393 quote: The signed quote to assemble 394 395 Returns: 396 A match bundle if assembly succeeds, None otherwise 397 398 Raises: 399 ExternalMatchClientError: If the request fails 400 """ 401 return await self.assemble_quote_with_options(quote, AssembleExternalMatchOptions()) 402 403 def assemble_quote_sync(self, quote: SignedExternalQuote) -> Optional[ExternalMatchResponse]: 404 """Synchronous version of assemble_quote method. 405 406 Args: 407 quote: The signed quote to assemble 408 409 Returns: 410 A match bundle if assembly succeeds, None otherwise 411 412 Raises: 413 ExternalMatchClientError: If the request fails 414 """ 415 return self.assemble_quote_with_options_sync(quote, AssembleExternalMatchOptions()) 416 417 async def assemble_quote_with_options( 418 self, 419 quote: SignedExternalQuote, 420 options: AssembleExternalMatchOptions 421 ) -> Optional[ExternalMatchResponse]: 422 """Assemble a quote into a match bundle with custom options. 423 424 Args: 425 quote: The signed quote to assemble 426 options: Custom options for quote assembly 427 428 Returns: 429 A match bundle if assembly succeeds, None otherwise 430 431 Raises: 432 ExternalMatchClientError: If the request fails 433 """ 434 signed_quote = ApiSignedExternalQuote( 435 quote=quote.quote, 436 signature=quote.signature, 437 ) 438 request = AssembleExternalMatchRequest( 439 do_gas_estimation=options.do_gas_estimation, 440 allow_shared=options.allow_shared, 441 receiver_address=options.receiver_address, 442 signed_quote=signed_quote, 443 updated_order=options.updated_order, 444 ) 445 446 path = options.build_request_path() 447 headers = self._get_headers() 448 response = await self.http_client.post_with_headers(path, request.model_dump(), headers) 449 match_resp = self._handle_optional_response(response) 450 if match_resp: 451 return ExternalMatchResponse(**match_resp) 452 453 return None 454 455 def assemble_quote_with_options_sync( 456 self, 457 quote: SignedExternalQuote, 458 options: AssembleExternalMatchOptions 459 ) -> Optional[ExternalMatchResponse]: 460 """Synchronous version of assemble_quote_with_options method. 461 462 Args: 463 quote: The signed quote to assemble 464 options: Custom options for quote assembly 465 466 Returns: 467 A match bundle if assembly succeeds, None otherwise 468 469 Raises: 470 ExternalMatchClientError: If the request fails 471 """ 472 signed_quote = ApiSignedExternalQuote( 473 quote=quote.quote, 474 signature=quote.signature, 475 ) 476 request = AssembleExternalMatchRequest( 477 do_gas_estimation=options.do_gas_estimation, 478 receiver_address=options.receiver_address, 479 signed_quote=signed_quote, 480 updated_order=options.updated_order, 481 ) 482 483 path = options.build_request_path() 484 headers = self._get_headers() 485 response = self.http_client.post_with_headers_sync(path, request.model_dump(), headers) 486 match_resp = self._handle_optional_response(response) 487 if match_resp: 488 return ExternalMatchResponse(**match_resp) 489 490 return None 491 492 def _get_headers(self) -> Headers: 493 """Get the headers required for API requests. 494 495 Returns: 496 Headers containing the API key and SDK version 497 """ 498 headers = Headers() 499 headers[RENEGADE_API_KEY_HEADER] = self.api_key 500 headers[RENEGADE_SDK_VERSION_HEADER] = _get_sdk_version() 501 return headers 502 503 def _handle_optional_response(self, response: Response) -> Optional[dict]: 504 """Handle an API response that may be empty. 505 506 Args: 507 response: The API response to handle 508 509 Returns: 510 The response data if present, None for 204 responses 511 512 Raises: 513 ExternalMatchClientError: If the response indicates an error 514 """ 515 if response.status_code == 204: # NO_CONTENT 516 return None 517 elif response.status_code == 200: # OK 518 return response.json() 519 else: 520 raise ExternalMatchClientError( 521 response.text, 522 status_code=response.status_code 523 )
Client for interacting with the Renegade external matching API.
This client handles authentication and provides methods for requesting quotes, assembling matches, and executing trades.
198 def __init__(self, api_key: str, api_secret: str, base_url: str): 199 """Initialize a new ExternalMatchClient. 200 201 Args: 202 api_key: The API key for authentication 203 api_secret: The API secret for request signing 204 base_url: The base URL of the Renegade API 205 """ 206 self.api_key = api_key 207 self.http_client = RelayerHttpClient(base_url, api_secret)
Initialize a new ExternalMatchClient.
Args: api_key: The API key for authentication api_secret: The API secret for request signing base_url: The base URL of the Renegade API
209 @classmethod 210 def new_arbitrum_sepolia_client(cls, api_key: str, api_secret: str) -> "ExternalMatchClient": 211 """Create a new client configured for the Arbitrum Sepolia testnet. 212 213 Args: 214 api_key: The API key for authentication 215 api_secret: The API secret for request signing 216 217 Returns: 218 A new ExternalMatchClient configured for Arbitrum Sepolia 219 """ 220 return cls(api_key, api_secret, ARBITRUM_SEPOLIA_BASE_URL)
Create a new client configured for the Arbitrum Sepolia testnet.
Args: api_key: The API key for authentication api_secret: The API secret for request signing
Returns: A new ExternalMatchClient configured for Arbitrum Sepolia
222 @classmethod 223 def new_base_sepolia_client(cls, api_key: str, api_secret: str) -> "ExternalMatchClient": 224 """Create a new client configured for Base Sepolia testnet. 225 226 Args: 227 api_key: The API key for authentication 228 api_secret: The API secret for request signing 229 230 Returns: 231 A new ExternalMatchClient configured for Base Sepolia 232 """ 233 return cls(api_key, api_secret, BASE_SEPOLIA_BASE_URL)
Create a new client configured for Base Sepolia testnet.
Args: api_key: The API key for authentication api_secret: The API secret for request signing
Returns: A new ExternalMatchClient configured for Base Sepolia
235 @classmethod 236 @deprecated(version="0.1.8", reason="Use new_arbitrum_sepolia_client instead") 237 def new_sepolia_client(cls, api_key: str, api_secret: str) -> "ExternalMatchClient": 238 """Create a new client configured for the Arbitrum Sepolia testnet. 239 240 Args: 241 api_key: The API key for authentication 242 api_secret: The API secret for request signing 243 244 Returns: 245 A new ExternalMatchClient configured for Arbitrum Sepolia 246 """ 247 return cls.new_arbitrum_sepolia_client(api_key, api_secret)
Create a new client configured for the Arbitrum Sepolia testnet.
Args: api_key: The API key for authentication api_secret: The API secret for request signing
Returns: A new ExternalMatchClient configured for Arbitrum Sepolia
249 @classmethod 250 def new_arbitrum_one_client(cls, api_key: str, api_secret: str) -> "ExternalMatchClient": 251 """Create a new client configured for Arbitrum One. 252 253 Args: 254 api_key: The API key for authentication 255 api_secret: The API secret for request signing 256 257 Returns: 258 A new ExternalMatchClient configured for Arbitrum One 259 """ 260 return cls(api_key, api_secret, ARBITRUM_ONE_BASE_URL)
Create a new client configured for Arbitrum One.
Args: api_key: The API key for authentication api_secret: The API secret for request signing
Returns: A new ExternalMatchClient configured for Arbitrum One
262 @classmethod 263 def new_base_mainnet_client(cls, api_key: str, api_secret: str) -> "ExternalMatchClient": 264 """Create a new client configured for Base mainnet. 265 266 Args: 267 api_key: The API key for authentication 268 api_secret: The API secret for request signing 269 270 Returns: 271 A new ExternalMatchClient configured for Base mainnet 272 """ 273 return cls(api_key, api_secret, BASE_MAINNET_BASE_URL)
Create a new client configured for Base mainnet.
Args: api_key: The API key for authentication api_secret: The API secret for request signing
Returns: A new ExternalMatchClient configured for Base mainnet
275 @classmethod 276 @deprecated(version="0.1.8", reason="Use new_arbitrum_one_client instead") 277 def new_mainnet_client(cls, api_key: str, api_secret: str) -> "ExternalMatchClient": 278 """Create a new client configured for mainnet. 279 280 Args: 281 api_key: The API key for authentication 282 api_secret: The API secret for request signing 283 284 Returns: 285 A new ExternalMatchClient configured for mainnet 286 """ 287 return cls.new_arbitrum_one_client(api_key, api_secret)
Create a new client configured for mainnet.
Args: api_key: The API key for authentication api_secret: The API secret for request signing
Returns: A new ExternalMatchClient configured for mainnet
289 async def request_quote(self, order: ExternalOrder) -> Optional[SignedExternalQuote]: 290 """Request a quote for the given order. 291 292 Args: 293 order: The order to request a quote for 294 295 Returns: 296 A signed quote if one is available, None otherwise 297 298 Raises: 299 ExternalMatchClientError: If the request fails 300 """ 301 return await self.request_quote_with_options(order, RequestQuoteOptions())
Request a quote for the given order.
Args: order: The order to request a quote for
Returns: A signed quote if one is available, None otherwise
Raises: ExternalMatchClientError: If the request fails
303 def request_quote_sync(self, order: ExternalOrder) -> Optional[SignedExternalQuote]: 304 """Synchronous version of request_quote method. 305 306 Args: 307 order: The order to request a quote for 308 309 Returns: 310 A signed quote if one is available, None otherwise 311 312 Raises: 313 ExternalMatchClientError: If the request fails 314 """ 315 return self.request_quote_with_options_sync(order, RequestQuoteOptions())
Synchronous version of request_quote method.
Args: order: The order to request a quote for
Returns: A signed quote if one is available, None otherwise
Raises: ExternalMatchClientError: If the request fails
317 async def request_quote_with_options( 318 self, 319 order: ExternalOrder, 320 options: RequestQuoteOptions 321 ) -> Optional[SignedExternalQuote]: 322 """Request a quote for the given order with custom options. 323 324 Args: 325 order: The order to request a quote for 326 options: Custom options for the quote request 327 328 Returns: 329 A signed quote if one is available, None otherwise 330 331 Raises: 332 ExternalMatchClientError: If the request fails 333 """ 334 request = ExternalQuoteRequest(external_order=order) 335 336 path = options.build_request_path() 337 headers = self._get_headers() 338 response = await self.http_client.post_with_headers(path, request.model_dump(), headers) 339 quote_resp = self._handle_optional_response(response) 340 341 if quote_resp == None: 342 return None 343 344 quote_resp = ExternalQuoteResponse(**quote_resp) 345 signed_quote = SignedExternalQuote( 346 quote=quote_resp.signed_quote.quote, 347 signature=quote_resp.signed_quote.signature, 348 gas_sponsorship_info=quote_resp.gas_sponsorship_info 349 ) 350 351 return signed_quote
Request a quote for the given order with custom options.
Args: order: The order to request a quote for options: Custom options for the quote request
Returns: A signed quote if one is available, None otherwise
Raises: ExternalMatchClientError: If the request fails
353 def request_quote_with_options_sync( 354 self, 355 order: ExternalOrder, 356 options: RequestQuoteOptions 357 ) -> Optional[ExternalQuoteResponse]: 358 """Synchronous version of request_quote_with_options method. 359 360 Args: 361 order: The order to request a quote for 362 options: Custom options for the quote request 363 364 Returns: 365 A signed quote if one is available, None otherwise 366 367 Raises: 368 ExternalMatchClientError: If the request fails 369 """ 370 request = ExternalQuoteRequest(external_order=order) 371 372 path = options.build_request_path() 373 headers = self._get_headers() 374 response = self.http_client.post_with_headers_sync(path, request.model_dump(), headers) 375 quote_resp = self._handle_optional_response(response) 376 377 if quote_resp == None: 378 return None 379 380 quote_resp = ExternalQuoteResponse(**quote_resp) 381 signed_quote = SignedExternalQuote( 382 quote=quote_resp.signed_quote.quote, 383 signature=quote_resp.signed_quote.signature, 384 gas_sponsorship_info=quote_resp.gas_sponsorship_info 385 ) 386 387 return signed_quote
Synchronous version of request_quote_with_options method.
Args: order: The order to request a quote for options: Custom options for the quote request
Returns: A signed quote if one is available, None otherwise
Raises: ExternalMatchClientError: If the request fails
389 async def assemble_quote(self, quote: SignedExternalQuote) -> Optional[ExternalMatchResponse]: 390 """Assemble a quote into a match bundle with default options. 391 392 Args: 393 quote: The signed quote to assemble 394 395 Returns: 396 A match bundle if assembly succeeds, None otherwise 397 398 Raises: 399 ExternalMatchClientError: If the request fails 400 """ 401 return await self.assemble_quote_with_options(quote, AssembleExternalMatchOptions())
Assemble a quote into a match bundle with default options.
Args: quote: The signed quote to assemble
Returns: A match bundle if assembly succeeds, None otherwise
Raises: ExternalMatchClientError: If the request fails
403 def assemble_quote_sync(self, quote: SignedExternalQuote) -> Optional[ExternalMatchResponse]: 404 """Synchronous version of assemble_quote method. 405 406 Args: 407 quote: The signed quote to assemble 408 409 Returns: 410 A match bundle if assembly succeeds, None otherwise 411 412 Raises: 413 ExternalMatchClientError: If the request fails 414 """ 415 return self.assemble_quote_with_options_sync(quote, AssembleExternalMatchOptions())
Synchronous version of assemble_quote method.
Args: quote: The signed quote to assemble
Returns: A match bundle if assembly succeeds, None otherwise
Raises: ExternalMatchClientError: If the request fails
417 async def assemble_quote_with_options( 418 self, 419 quote: SignedExternalQuote, 420 options: AssembleExternalMatchOptions 421 ) -> Optional[ExternalMatchResponse]: 422 """Assemble a quote into a match bundle with custom options. 423 424 Args: 425 quote: The signed quote to assemble 426 options: Custom options for quote assembly 427 428 Returns: 429 A match bundle if assembly succeeds, None otherwise 430 431 Raises: 432 ExternalMatchClientError: If the request fails 433 """ 434 signed_quote = ApiSignedExternalQuote( 435 quote=quote.quote, 436 signature=quote.signature, 437 ) 438 request = AssembleExternalMatchRequest( 439 do_gas_estimation=options.do_gas_estimation, 440 allow_shared=options.allow_shared, 441 receiver_address=options.receiver_address, 442 signed_quote=signed_quote, 443 updated_order=options.updated_order, 444 ) 445 446 path = options.build_request_path() 447 headers = self._get_headers() 448 response = await self.http_client.post_with_headers(path, request.model_dump(), headers) 449 match_resp = self._handle_optional_response(response) 450 if match_resp: 451 return ExternalMatchResponse(**match_resp) 452 453 return None
Assemble a quote into a match bundle with custom options.
Args: quote: The signed quote to assemble options: Custom options for quote assembly
Returns: A match bundle if assembly succeeds, None otherwise
Raises: ExternalMatchClientError: If the request fails
455 def assemble_quote_with_options_sync( 456 self, 457 quote: SignedExternalQuote, 458 options: AssembleExternalMatchOptions 459 ) -> Optional[ExternalMatchResponse]: 460 """Synchronous version of assemble_quote_with_options method. 461 462 Args: 463 quote: The signed quote to assemble 464 options: Custom options for quote assembly 465 466 Returns: 467 A match bundle if assembly succeeds, None otherwise 468 469 Raises: 470 ExternalMatchClientError: If the request fails 471 """ 472 signed_quote = ApiSignedExternalQuote( 473 quote=quote.quote, 474 signature=quote.signature, 475 ) 476 request = AssembleExternalMatchRequest( 477 do_gas_estimation=options.do_gas_estimation, 478 receiver_address=options.receiver_address, 479 signed_quote=signed_quote, 480 updated_order=options.updated_order, 481 ) 482 483 path = options.build_request_path() 484 headers = self._get_headers() 485 response = self.http_client.post_with_headers_sync(path, request.model_dump(), headers) 486 match_resp = self._handle_optional_response(response) 487 if match_resp: 488 return ExternalMatchResponse(**match_resp) 489 490 return None
Synchronous version of assemble_quote_with_options method.
Args: quote: The signed quote to assemble options: Custom options for quote assembly
Returns: A match bundle if assembly succeeds, None otherwise
Raises: ExternalMatchClientError: If the request fails
56@dataclass 57class ExternalMatchOptions: 58 do_gas_estimation: bool = False 59 receiver_address: Optional[str] = None 60 request_gas_sponsorship: bool = False 61 gas_refund_address: Optional[str] = None 62 63 @classmethod 64 def new(cls) -> "ExternalMatchOptions": 65 return cls() 66 67 def with_gas_estimation(self, do_gas_estimation: bool) -> "ExternalMatchOptions": 68 self.do_gas_estimation = do_gas_estimation 69 return self 70 71 def with_receiver_address(self, receiver_address: str) -> "ExternalMatchOptions": 72 self.receiver_address = receiver_address 73 return self 74 75 def with_gas_sponsorship(self, request_gas_sponsorship: bool, gas_refund_address: Optional[str] = None) -> "ExternalMatchOptions": 76 self.request_gas_sponsorship = request_gas_sponsorship 77 self.gas_refund_address = gas_refund_address 78 return self 79 80 def with_updated_order(self, updated_order: ExternalOrder) -> "ExternalMatchOptions": 81 self.updated_order = updated_order 82 return self 83 84 def build_request_path(self) -> str: 85 """ 86 Builds the path at which the request will be sent, with query params 87 """ 88 disable_sponsorship_str = str(not self.request_gas_sponsorship).lower() 89 path = f"{REQUEST_EXTERNAL_MATCH_ROUTE}?{DISABLE_GAS_SPONSORSHIP_QUERY_PARAM}={disable_sponsorship_str}" 90 if self.gas_refund_address: 91 path += f"&{GAS_REFUND_ADDRESS_QUERY_PARAM}={self.gas_refund_address}" 92 93 return path
84 def build_request_path(self) -> str: 85 """ 86 Builds the path at which the request will be sent, with query params 87 """ 88 disable_sponsorship_str = str(not self.request_gas_sponsorship).lower() 89 path = f"{REQUEST_EXTERNAL_MATCH_ROUTE}?{DISABLE_GAS_SPONSORSHIP_QUERY_PARAM}={disable_sponsorship_str}" 90 if self.gas_refund_address: 91 path += f"&{GAS_REFUND_ADDRESS_QUERY_PARAM}={self.gas_refund_address}" 92 93 return path
Builds the path at which the request will be sent, with query params
131@dataclass 132class AssembleExternalMatchOptions: 133 do_gas_estimation: bool = False 134 allow_shared: bool = False 135 receiver_address: Optional[str] = None 136 updated_order: Optional[ExternalOrder] = None 137 request_gas_sponsorship: bool = False 138 gas_refund_address: Optional[str] = None 139 140 @classmethod 141 def new(cls) -> "AssembleExternalMatchOptions": 142 return cls() 143 144 def with_gas_estimation(self, do_gas_estimation: bool) -> "AssembleExternalMatchOptions": 145 self.do_gas_estimation = do_gas_estimation 146 return self 147 148 def with_allow_shared(self, allow_shared: bool) -> "AssembleExternalMatchOptions": 149 self.allow_shared = allow_shared 150 return self 151 152 def with_receiver_address(self, receiver_address: str) -> "AssembleExternalMatchOptions": 153 self.receiver_address = receiver_address 154 return self 155 156 def with_updated_order(self, updated_order: ExternalOrder) -> "AssembleExternalMatchOptions": 157 self.updated_order = updated_order 158 return self 159 160 @deprecated(version="0.1.2", reason="Request gas sponsorship when requesting a quote instead") 161 def with_gas_sponsorship(self, request_gas_sponsorship: bool) -> "AssembleExternalMatchOptions": 162 self.request_gas_sponsorship = request_gas_sponsorship 163 return self 164 165 @deprecated(version="0.1.2", reason="Request gas sponsorship when requesting a quote instead") 166 def with_gas_refund_address(self, gas_refund_address: str) -> "AssembleExternalMatchOptions": 167 self.gas_refund_address = gas_refund_address 168 return self 169 170 def build_request_path(self) -> str: 171 """ 172 Builds the path at which the request will be sent, with query params 173 """ 174 path = ASSEMBLE_EXTERNAL_MATCH_ROUTE 175 if self.request_gas_sponsorship: 176 # We only write this query parameter if it was explicitly set. The 177 # expectation of the auth server is that when gas sponsorship is 178 # requested at the quote stage, there should be no query parameters 179 # at all in the assemble request. 180 disable_sponsorship_str = str(not self.request_gas_sponsorship).lower() 181 path += f"?{DISABLE_GAS_SPONSORSHIP_QUERY_PARAM}={disable_sponsorship_str}" 182 if self.gas_refund_address: 183 path += f"&{GAS_REFUND_ADDRESS_QUERY_PARAM}={self.gas_refund_address}" 184 185 return path
170 def build_request_path(self) -> str: 171 """ 172 Builds the path at which the request will be sent, with query params 173 """ 174 path = ASSEMBLE_EXTERNAL_MATCH_ROUTE 175 if self.request_gas_sponsorship: 176 # We only write this query parameter if it was explicitly set. The 177 # expectation of the auth server is that when gas sponsorship is 178 # requested at the quote stage, there should be no query parameters 179 # at all in the assemble request. 180 disable_sponsorship_str = str(not self.request_gas_sponsorship).lower() 181 path += f"?{DISABLE_GAS_SPONSORSHIP_QUERY_PARAM}={disable_sponsorship_str}" 182 if self.gas_refund_address: 183 path += f"&{GAS_REFUND_ADDRESS_QUERY_PARAM}={self.gas_refund_address}" 184 185 return path
Builds the path at which the request will be sent, with query params
95@dataclass 96class RequestQuoteOptions: 97 disable_gas_sponsorship: bool = False 98 gas_refund_address: Optional[str] = None 99 refund_native_eth: bool = False 100 101 @classmethod 102 def new(cls) -> "RequestQuoteOptions": 103 return cls() 104 105 def with_gas_sponsorship_disabled(self, disable_gas_sponsorship: bool) -> "RequestQuoteOptions": 106 self.disable_gas_sponsorship = disable_gas_sponsorship 107 return self 108 109 def with_gas_refund_address(self, gas_refund_address: str) -> "RequestQuoteOptions": 110 self.gas_refund_address = gas_refund_address 111 return self 112 113 def with_refund_native_eth(self, refund_native_eth: bool) -> "RequestQuoteOptions": 114 self.refund_native_eth = refund_native_eth 115 return self 116 117 def build_request_path(self) -> str: 118 """ 119 Builds the path at which the request will be sent, with query params 120 """ 121 disable_sponsorship_str = str(self.disable_gas_sponsorship).lower() 122 path = f"{REQUEST_EXTERNAL_QUOTE_ROUTE}?{DISABLE_GAS_SPONSORSHIP_QUERY_PARAM}={disable_sponsorship_str}" 123 if self.gas_refund_address: 124 path += f"&{GAS_REFUND_ADDRESS_QUERY_PARAM}={self.gas_refund_address}" 125 if self.refund_native_eth: 126 refund_native_eth_str = str(self.refund_native_eth).lower() 127 path += f"&{REFUND_NATIVE_ETH_QUERY_PARAM}={refund_native_eth_str}" 128 129 return path
117 def build_request_path(self) -> str: 118 """ 119 Builds the path at which the request will be sent, with query params 120 """ 121 disable_sponsorship_str = str(self.disable_gas_sponsorship).lower() 122 path = f"{REQUEST_EXTERNAL_QUOTE_ROUTE}?{DISABLE_GAS_SPONSORSHIP_QUERY_PARAM}={disable_sponsorship_str}" 123 if self.gas_refund_address: 124 path += f"&{GAS_REFUND_ADDRESS_QUERY_PARAM}={self.gas_refund_address}" 125 if self.refund_native_eth: 126 refund_native_eth_str = str(self.refund_native_eth).lower() 127 path += f"&{REFUND_NATIVE_ETH_QUERY_PARAM}={refund_native_eth_str}" 128 129 return path
Builds the path at which the request will be sent, with query params
51class ExternalMatchClientError(Exception): 52 def __init__(self, message: str, status_code: Optional[int] = None): 53 super().__init__(message) 54 self.status_code = status_code
Common base class for all non-exit exceptions.
20class RelayerHttpClient: 21 """HTTP client for making authenticated requests to the Renegade relayer API. 22 23 This client handles request signing and authentication using HMAC-SHA256. 24 """ 25 26 def __init__(self, base_url: str, auth_key: str): 27 """Initialize a new RelayerHttpClient. 28 29 Args: 30 base_url: The base URL of the relayer API 31 auth_key: The base64-encoded authentication key for request signing 32 """ 33 self.async_client = AsyncClient() 34 self.sync_client = Client() 35 self.base_url = base_url 36 # Decode base64 auth key 37 self.auth_key = base64.b64decode(auth_key) 38 39 async def post(self, path: str, body: Any) -> Response: 40 """Make a POST request without custom headers. 41 42 Args: 43 path: The API endpoint path 44 body: The request body to send 45 46 Returns: 47 The API response 48 """ 49 return await self.post_with_headers(path, body, Headers()) 50 51 def post_sync(self, path: str, body: Any) -> Response: 52 """Make a synchronous POST request without custom headers. 53 54 Args: 55 path: The API endpoint path 56 body: The request body to send 57 58 Returns: 59 The API response 60 """ 61 return self.post_with_headers_sync(path, body, Headers()) 62 63 async def get(self, path: str) -> Response: 64 """Make a GET request without custom headers. 65 66 Args: 67 path: The API endpoint path 68 69 Returns: 70 The API response 71 """ 72 return await self.get_with_headers(path, Headers()) 73 74 def get_sync(self, path: str) -> Response: 75 """Make a synchronous GET request without custom headers. 76 77 Args: 78 path: The API endpoint path 79 80 Returns: 81 The API response 82 """ 83 return self.get_with_headers_sync(path, Headers()) 84 85 async def post_with_headers(self, path: str, body: Any, custom_headers: Headers) -> Response: 86 """Make a POST request with custom headers. 87 88 Args: 89 path: The API endpoint path 90 body: The request body to send 91 custom_headers: Additional headers to include 92 93 Returns: 94 The API response 95 """ 96 url = f"{self.base_url}{path}" 97 body_bytes = json.dumps(body).encode() 98 headers = self._add_auth(path, custom_headers, body_bytes) 99 response = await self.async_client.post(url, headers=headers, content=body_bytes) 100 return response 101 102 def post_with_headers_sync(self, path: str, body: Any, custom_headers: Headers) -> Response: 103 """Make a synchronous POST request with custom headers. 104 105 Args: 106 path: The API endpoint path 107 body: The request body to send 108 custom_headers: Additional headers to include 109 110 Returns: 111 The API response 112 """ 113 url = f"{self.base_url}{path}" 114 body_bytes = json.dumps(body).encode() 115 headers = self._add_auth(path, custom_headers, body_bytes) 116 response = self.sync_client.post(url, headers=headers, content=body_bytes) 117 return response 118 119 async def get_with_headers(self, path: str, custom_headers: Headers) -> Response: 120 """Make a GET request with custom headers. 121 122 Args: 123 path: The API endpoint path 124 custom_headers: Additional headers to include 125 126 Returns: 127 The API response 128 """ 129 url = f"{self.base_url}{path}" 130 headers = self._add_auth(path, custom_headers, b"") 131 response = await self.async_client.get(url, headers=headers) 132 return response 133 134 def get_with_headers_sync(self, path: str, custom_headers: Headers) -> Response: 135 """Make a synchronous GET request with custom headers. 136 137 Args: 138 path: The API endpoint path 139 custom_headers: Additional headers to include 140 141 Returns: 142 The API response 143 """ 144 url = f"{self.base_url}{path}" 145 headers = self._add_auth(path, custom_headers, b"") 146 response = self.sync_client.get(url, headers=headers) 147 return response 148 149 def _get_header_bytes(self, headers: Headers) -> bytes: 150 """Get sorted Renegade headers bytes for signature calculation. 151 152 This method extracts all headers with the x-renegade prefix (except auth), 153 sorts them by key, and concatenates them into a single byte string. 154 155 Args: 156 headers: The headers to process 157 158 Returns: 159 A byte string containing the concatenated header data 160 """ 161 renegade_headers = [] 162 for key, value in headers.items(): 163 key_lower = key.lower() 164 if key_lower.startswith(RENEGADE_HEADER_NAMESPACE) and key_lower != RENEGADE_AUTH_HEADER_NAME: 165 renegade_headers.append((key_lower, value)) 166 167 # Sort headers by key 168 renegade_headers.sort(key=lambda x: x[0]) 169 170 # Concatenate headers 171 header_bytes = b"" 172 for key, value in renegade_headers: 173 current = key.encode() + str(value).encode() 174 header_bytes += current 175 176 return header_bytes 177 178 def _add_auth(self, path: str, headers: Headers, body: bytes) -> Headers: 179 """Add authentication headers to a request. 180 181 This method adds the expiration timestamp and HMAC signature headers 182 required for request authentication. 183 184 Args: 185 path: The request path 186 headers: The existing headers 187 body: The request body bytes 188 189 Returns: 190 Headers with authentication information added 191 """ 192 # Add timestamp and expiry 193 timestamp = int(time.time() * 1000) 194 expiry = timestamp + int(REQUEST_SIGNATURE_DURATION.total_seconds() * 1000) 195 headers[RENEGADE_SIG_EXPIRATION_HEADER_NAME] = str(expiry) 196 197 # Calculate signature 198 path_bytes = path.encode() 199 header_bytes = self._get_header_bytes(headers) 200 message = path_bytes + header_bytes + body 201 202 signature = hmac.new(self.auth_key, message, hashlib.sha256).digest() 203 b64_signature = base64.b64encode(signature).decode().rstrip("=") 204 205 headers[RENEGADE_AUTH_HEADER_NAME] = b64_signature 206 return headers
HTTP client for making authenticated requests to the Renegade relayer API.
This client handles request signing and authentication using HMAC-SHA256.
26 def __init__(self, base_url: str, auth_key: str): 27 """Initialize a new RelayerHttpClient. 28 29 Args: 30 base_url: The base URL of the relayer API 31 auth_key: The base64-encoded authentication key for request signing 32 """ 33 self.async_client = AsyncClient() 34 self.sync_client = Client() 35 self.base_url = base_url 36 # Decode base64 auth key 37 self.auth_key = base64.b64decode(auth_key)
Initialize a new RelayerHttpClient.
Args: base_url: The base URL of the relayer API auth_key: The base64-encoded authentication key for request signing
39 async def post(self, path: str, body: Any) -> Response: 40 """Make a POST request without custom headers. 41 42 Args: 43 path: The API endpoint path 44 body: The request body to send 45 46 Returns: 47 The API response 48 """ 49 return await self.post_with_headers(path, body, Headers())
Make a POST request without custom headers.
Args: path: The API endpoint path body: The request body to send
Returns: The API response
51 def post_sync(self, path: str, body: Any) -> Response: 52 """Make a synchronous POST request without custom headers. 53 54 Args: 55 path: The API endpoint path 56 body: The request body to send 57 58 Returns: 59 The API response 60 """ 61 return self.post_with_headers_sync(path, body, Headers())
Make a synchronous POST request without custom headers.
Args: path: The API endpoint path body: The request body to send
Returns: The API response
63 async def get(self, path: str) -> Response: 64 """Make a GET request without custom headers. 65 66 Args: 67 path: The API endpoint path 68 69 Returns: 70 The API response 71 """ 72 return await self.get_with_headers(path, Headers())
Make a GET request without custom headers.
Args: path: The API endpoint path
Returns: The API response
74 def get_sync(self, path: str) -> Response: 75 """Make a synchronous GET request without custom headers. 76 77 Args: 78 path: The API endpoint path 79 80 Returns: 81 The API response 82 """ 83 return self.get_with_headers_sync(path, Headers())
Make a synchronous GET request without custom headers.
Args: path: The API endpoint path
Returns: The API response
85 async def post_with_headers(self, path: str, body: Any, custom_headers: Headers) -> Response: 86 """Make a POST request with custom headers. 87 88 Args: 89 path: The API endpoint path 90 body: The request body to send 91 custom_headers: Additional headers to include 92 93 Returns: 94 The API response 95 """ 96 url = f"{self.base_url}{path}" 97 body_bytes = json.dumps(body).encode() 98 headers = self._add_auth(path, custom_headers, body_bytes) 99 response = await self.async_client.post(url, headers=headers, content=body_bytes) 100 return response
Make a POST request with custom headers.
Args: path: The API endpoint path body: The request body to send custom_headers: Additional headers to include
Returns: The API response
102 def post_with_headers_sync(self, path: str, body: Any, custom_headers: Headers) -> Response: 103 """Make a synchronous POST request with custom headers. 104 105 Args: 106 path: The API endpoint path 107 body: The request body to send 108 custom_headers: Additional headers to include 109 110 Returns: 111 The API response 112 """ 113 url = f"{self.base_url}{path}" 114 body_bytes = json.dumps(body).encode() 115 headers = self._add_auth(path, custom_headers, body_bytes) 116 response = self.sync_client.post(url, headers=headers, content=body_bytes) 117 return response
Make a synchronous POST request with custom headers.
Args: path: The API endpoint path body: The request body to send custom_headers: Additional headers to include
Returns: The API response
119 async def get_with_headers(self, path: str, custom_headers: Headers) -> Response: 120 """Make a GET request with custom headers. 121 122 Args: 123 path: The API endpoint path 124 custom_headers: Additional headers to include 125 126 Returns: 127 The API response 128 """ 129 url = f"{self.base_url}{path}" 130 headers = self._add_auth(path, custom_headers, b"") 131 response = await self.async_client.get(url, headers=headers) 132 return response
Make a GET request with custom headers.
Args: path: The API endpoint path custom_headers: Additional headers to include
Returns: The API response
134 def get_with_headers_sync(self, path: str, custom_headers: Headers) -> Response: 135 """Make a synchronous GET request with custom headers. 136 137 Args: 138 path: The API endpoint path 139 custom_headers: Additional headers to include 140 141 Returns: 142 The API response 143 """ 144 url = f"{self.base_url}{path}" 145 headers = self._add_auth(path, custom_headers, b"") 146 response = self.sync_client.get(url, headers=headers) 147 return response
Make a synchronous GET request with custom headers.
Args: path: The API endpoint path custom_headers: Additional headers to include
Returns: The API response
61class ExternalOrder(BaseModelWithConfig): 62 quote_mint: str 63 base_mint: str 64 side: OrderSide 65 base_amount: Optional[int] = None 66 quote_amount: Optional[int] = None 67 exact_base_output: Optional[int] = None 68 exact_quote_output: Optional[int] = None 69 min_fill_size: int = 0 70 71 def model_post_init(self, __context) -> None: 72 self.quote_mint = Web3.to_checksum_address(self.quote_mint) 73 self.base_mint = Web3.to_checksum_address(self.base_mint) 74 75 @model_validator(mode='after') 76 def validate_amounts(self) -> 'ExternalOrder': 77 # Check if all amount fields are None or 0 78 base_amt_none = self.base_amount is None or self.base_amount == 0 79 quote_amt_none = self.quote_amount is None or self.quote_amount == 0 80 exact_base_amt_none = self.exact_base_output is None or self.exact_base_output == 0 81 exact_quote_amt_none = self.exact_quote_output is None or self.exact_quote_output == 0 82 83 # Count how many amount fields are set 84 amount_fields_set = sum(1 for x in [base_amt_none, quote_amt_none, exact_base_amt_none, exact_quote_amt_none] if not x) 85 86 if amount_fields_set == 0: 87 raise ValueError("One of base_amount, quote_amount, exact_base_amount, or exact_quote_amount must be set") 88 if amount_fields_set > 1: 89 raise ValueError("Only one of base_amount, quote_amount, exact_base_amount, or exact_quote_amount can be set") 90 91 return self
Base model with common configuration
75 @model_validator(mode='after') 76 def validate_amounts(self) -> 'ExternalOrder': 77 # Check if all amount fields are None or 0 78 base_amt_none = self.base_amount is None or self.base_amount == 0 79 quote_amt_none = self.quote_amount is None or self.quote_amount == 0 80 exact_base_amt_none = self.exact_base_output is None or self.exact_base_output == 0 81 exact_quote_amt_none = self.exact_quote_output is None or self.exact_quote_output == 0 82 83 # Count how many amount fields are set 84 amount_fields_set = sum(1 for x in [base_amt_none, quote_amt_none, exact_base_amt_none, exact_quote_amt_none] if not x) 85 86 if amount_fields_set == 0: 87 raise ValueError("One of base_amount, quote_amount, exact_base_amount, or exact_quote_amount must be set") 88 if amount_fields_set > 1: 89 raise ValueError("Only one of base_amount, quote_amount, exact_base_amount, or exact_quote_amount can be set") 90 91 return self
str(object='') -> str str(bytes_or_buffer[, encoding[, errors]]) -> str
Create a new string object from the given object. If encoding or errors is specified, then the object must expose a data buffer that will be decoded using the given encoding and error handler. Otherwise, returns the result of object.__str__() (if defined) or repr(object). encoding defaults to 'utf-8'. errors defaults to 'strict'.