Skip to content

Commit

Permalink
Add docs for rust sdk
Browse files Browse the repository at this point in the history
  • Loading branch information
danimhr committed Dec 17, 2024
1 parent 6a726d4 commit f30d018
Show file tree
Hide file tree
Showing 4 changed files with 238 additions and 2 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion sdk/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "express-relay-client"
version = "0.2.0"
version = "0.2.1"
edition = "2021"
description = "Pyth Express Relay client"
repository = "https://github.com/pyth-network/per"
Expand Down
67 changes: 67 additions & 0 deletions sdk/rust/src/evm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,20 @@ pub struct Config {
pub chain_id_num: u64,
}


/// Retrieves the EVM configuration for a specific chain.
///
/// # Arguments
///
/// * `chain_id` - A string slice representing the blockchain chain ID.
///
/// # Returns
///
/// * `Result<Config, ClientError>` - A result containing the configuration or an error if the chain is unsupported.
///
/// # Errors
///
/// Returns `ClientError::ChainNotSupported` if the chain ID is unrecognized.
pub fn get_config(chain_id: &str) -> Result<Config, ClientError> {
match chain_id {
"mode" => Ok(Config {
Expand Down Expand Up @@ -99,6 +113,20 @@ pub fn get_config(chain_id: &str) -> Result<Config, ClientError> {
}
}

/// Constructs the Permit2 compatible permitted tokens list for a given opportunity and bid parameters.
///
/// # Arguments
///
/// * `opportunity` - The EVM opportunity structure.
/// * `bid_params` - Bid parameters.
///
/// # Returns
///
/// * `Result<Vec<TokenPermissions>, ClientError>` - A list of token permissions or an error.
///
/// # Errors
///
/// Returns an error if the configuration for the chain cannot be retrieved.
pub fn make_permitted_tokens(
opportunity: OpportunityEvm,
bid_params: BidParamsEvm,
Expand Down Expand Up @@ -130,6 +158,21 @@ pub fn make_permitted_tokens(
Ok(permitted_tokens)
}

/// Creates execution parameters required for executing an opportunity through the ER contract.
///
/// # Arguments
///
/// * `opportunity` - The EVM opportunity structure.
/// * `bid_params` - Bid parameters.
/// * `executor` - The address of the executor.
///
/// # Returns
///
/// * `Result<ExecutionParams, ClientError>` - Execution parameters including permits and witness details.
///
/// # Errors
///
/// Returns an error if permit2 compatible permitted tokens cannot be constructed.
pub fn make_opportunity_execution_params(
opportunity: OpportunityEvm,
bid_params: BidParamsEvm,
Expand Down Expand Up @@ -260,6 +303,21 @@ fn get_signature(
.map_err(|e| ClientError::NewBidError(format!("Failed to sign eip712 data: {:?}", e)))
}

/// Generates adapter calldata for executing an opportunity.
///
/// # Arguments
///
/// * `opportunity` - The EVM opportunity structure.
/// * `bid_params` - Bid parameters.
/// * `wallet` - A `LocalWallet` object for signing transactions.
///
/// # Returns
///
/// * `Result<Bytes, ClientError>` - The calldata bytes for the opportunity adapter.
///
/// # Errors
///
/// Returns an error if signature generation or execution parameter creation fails.
pub fn make_adapter_calldata(
opportunity: OpportunityEvm,
bid_params: BidParamsEvm,
Expand All @@ -278,6 +336,15 @@ pub fn make_adapter_calldata(
Ok(calldata.into())
}

/// Retrieves opportunity parameters from an `OpportunityEvm` object.
///
/// # Arguments
///
/// * `opportunity` - The EVM opportunity structure.
///
/// # Returns
///
/// * `OpportunityCreateV1Evm` - The extracted opportunity parameters.
pub fn get_params(opportunity: OpportunityEvm) -> OpportunityCreateV1Evm {
let OpportunityParamsEvm::V1(OpportunityParamsV1Evm(params)) = opportunity.params;
params
Expand Down
169 changes: 169 additions & 0 deletions sdk/rust/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,21 @@ enum MessageType {
Update(api_types::ws::ServerUpdateResponse),
}

/// A stream of WebSocket updates received from the server.
///
/// # Developer Notes
///
/// - This struct wraps a `BroadcastStream` that delivers updates as `ServerUpdateResponse` objects.
/// - The `PhantomData` ensures that the lifetime of this stream is explicitly tied to the `WsClient` instance.
///
/// ## Why PhantomData?
///
/// - `PhantomData<&'a ()>` acts as a marker to indicate that this struct's lifetime `'a`
/// depends on the `WsClient` that created it.
/// - Without `PhantomData`, the compiler cannot ensure that the `WsClientUpdateStream` does not outlive
/// the `WsClient`. This can lead to dangling references or invalid state.
/// - By including `PhantomData`, the borrow checker guarantees at compile time that the stream
/// remains valid only as long as the `WsClient` exists.
pub struct WsClientUpdateStream<'a> {
stream: BroadcastStream<api_types::ws::ServerUpdateResponse>,
_lifetime: PhantomData<&'a ()>,
Expand All @@ -170,12 +185,40 @@ impl Stream for WsClientUpdateStream<'_> {
}

impl WsClient {
/// Retrieves a stream of WebSocket updates from the server.
///
/// # Returns
///
/// * `WsClientUpdateStream` - A stream of updates that can be polled asynchronously.
///
/// # Lifetime
///
/// The lifetime of the update stream is guaranteed at compile time to be tied to the `WsClient`.
/// If the `WsClient` is dropped, the stream will also become invalid.
pub fn get_update_stream(&self) -> WsClientUpdateStream {
WsClientUpdateStream::new(BroadcastStream::new(
self.inner.update_receiver.resubscribe(),
))
}

/// Runs the WebSocket event loop, managing incoming messages, outgoing requests, and connection health.
///
/// # Developer Notes
///
/// - This function runs continuously and listens for three main events:
/// 1. **Incoming WebSocket messages**: Handles text, binary, ping, and close frames.
/// - WebSocket messages can be of two types:
/// - **Updates**: Broadcasted to all clients via the `update_sender` channel.
/// - **Responses**: Sent as a response to a specific client request and delivered to the
/// corresponding `oneshot` channel for that request (tracked via `requests_map`).
/// 2. **Requests from the client**: Sends messages through the WebSocket when received from the request channel.
/// 3. **Connection health check**: Monitors for pings to ensure the connection is alive.
///
/// - Uses a `HashMap` (`requests_map`) to track pending requests and match responses based on their IDs.
/// - If no ping is received for 32 seconds, the function assumes the connection is broken and terminates.
///
/// This function is spawned as a background task and must be resilient to message errors
/// or other intermittent failures.
async fn run(
mut ws_stream: WebSocketStream<MaybeTlsStream<TcpStream>>,
mut request_receiver: mpsc::UnboundedReceiver<WsRequest>,
Expand Down Expand Up @@ -258,6 +301,18 @@ impl WsClient {
*write_guard
}

/// Sends a WebSocket message and waits for a response.
///
/// # Developer Notes
///
/// - Generates a unique request ID using `fetch_add_request_id` to match requests with responses.
/// - Sends a `ClientRequest` message through the internal `request_sender` channel.
/// - Uses a `oneshot` channel to wait for the response corresponding to the request ID.
/// - Times out after 5 seconds if no response is received, returning a `WsRequestFailed` error.
///
/// **Request Matching**:
/// Responses are matched to their corresponding requests via the `requests_map` in the `run` loop.
/// If the timeout occurs, developers must ensure that orphaned requests are handled appropriately.
async fn send(
&self,
message: api_types::ws::ClientMessage,
Expand Down Expand Up @@ -294,6 +349,19 @@ impl WsClient {
}
}

/// Subscribes to updates for specific blockchain chains.
///
/// # Arguments
///
/// * `chain_ids` - A vector of chain IDs as strings.
///
/// # Returns
///
/// * `Result<(), ClientError>` - Returns `Ok(())` on success or an error.
///
/// # Errors
///
/// Returns an error if the subscription request fails or times out.
pub async fn chain_subscribe(&self, chain_ids: Vec<String>) -> Result<(), ClientError> {
let message = api_types::ws::ClientMessage::Subscribe {
chain_ids: chain_ids
Expand All @@ -308,6 +376,19 @@ impl WsClient {
}
}

/// Unsubscribes from updates for specific blockchain chains.
///
/// # Arguments
///
/// * `chain_ids` - A vector of chain IDs as strings.
///
/// # Returns
///
/// * `Result<(), ClientError>` - Returns `Ok(())` on success or an error.
///
/// # Errors
///
/// Returns an error if the unsubscription request fails or times out.
pub async fn chain_unsubscribe(&self, chain_ids: Vec<String>) -> Result<(), ClientError> {
let message = api_types::ws::ClientMessage::Unsubscribe {
chain_ids: chain_ids
Expand All @@ -322,6 +403,19 @@ impl WsClient {
}
}

/// Submits a bid to the server.
///
/// # Arguments
///
/// * `bid` - The bid object to be submitted, which contains the relevant parameters for the transaction.
///
/// # Returns
///
/// * `Result<api_types::bid::BidResult, ClientError>` - The result of the bid submission.
///
/// # Errors
///
/// Returns an error if the WebSocket request fails or the server responds with an error.
pub async fn submit_bid(
&self,
bid: api_types::bid::BidCreate,
Expand Down Expand Up @@ -350,6 +444,31 @@ impl Client {
}
}

/// Sends an HTTP request to the server and decodes the response.
///
/// # Developer Notes
///
/// - Constructs an HTTP request using the specified route and optional query parameters.
/// - If an `api_key` is set, it adds a `Bearer` authorization header to the request.
/// - This function expects the server response to conform to the following structure:
/// - `DecodedResponse::Ok` for successful responses.
/// - `DecodedResponse::Err` for error bodies returned by the server.
/// - The function uses `reqwest::Client` internally and decodes the response using `serde`.
///
/// # Parameters
///
/// - `route` - Defines the API endpoint and HTTP method via the `Routable` trait.
/// - `query` - Optional query parameters that are serialized into the request URL.
///
/// # Implementation Details
///
/// - If the HTTP response is valid but contains an error body, the function returns a
/// `ClientError::RequestError` with the server's error message.
/// - If the HTTP response fails to decode, it returns `ClientError::DecodeResponseFailed`.
/// - Errors due to request failure (e.g., network issues) are returned as `ClientError::RequestFailed`.
///
/// **Timeouts**:
/// The default `reqwest` client timeout applies here. Ensure proper timeout handling in the caller.
async fn send<T: Serialize, R: DeserializeOwned>(
&self,
route: impl Routable,
Expand All @@ -371,6 +490,19 @@ impl Client {
Client::decode(response).await
}

/// Creates a new HTTP client with the provided configuration.
///
/// # Arguments
///
/// * `config` - The client configuration containing HTTP URL and optional API key.
///
/// # Returns
///
/// * `Result<Self, ClientError>` - A result containing the initialized client or an error.
///
/// # Errors
///
/// Returns an error if the HTTP URL is invalid or has an unsupported scheme.
pub fn try_new(config: ClientConfig) -> Result<Self, ClientError> {
let http_url = Url::parse(config.http_url.as_str())
.map_err(|e| ClientError::InvalidHttpUrl(e.to_string()))?;
Expand All @@ -397,6 +529,19 @@ impl Client {
})
}

/// Establishes a WebSocket connection to the server.
///
/// # Returns
///
/// * `Result<WsClient, ClientError>` - A thread-safe WebSocket client for interacting with the server.
///
/// # Errors
///
/// Returns an error if the connection or WebSocket handshake fails.
///
/// # Thread Safety
///
/// The returned `WsClient` is thread-safe and can be cloned to share across multiple tasks.
pub async fn connect_websocket(&self) -> Result<WsClient, ClientError> {
let url = self
.ws_url
Expand Down Expand Up @@ -432,13 +577,37 @@ impl Client {
})
}

/// Fetches opportunities based on optional query parameters.
///
/// # Arguments
///
/// * `params` - Optional query parameters for filtering opportunities.
///
/// # Returns
///
/// * `Result<Vec<Opportunity>, ClientError>` - A list of opportunities or an error.
pub async fn get_opportunities(
&self,
params: Option<GetOpportunitiesQueryParams>,
) -> Result<Vec<Opportunity>, ClientError> {
self.send(Route::GetOpportunities, params).await
}

/// Creates a new bid for an opportunity.
///
/// # Type Parameters
///
/// * `T` - A type that implements the `Biddable` trait.
///
/// # Arguments
///
/// * `opportunity` - The opportunity to bid on.
/// * `params` - Bid parameters specific to the opportunity type.
/// * `private_key` - The private key for signing the bid.
///
/// # Returns
///
/// * `Result<BidCreate, ClientError>` - A bid creation object or an error.
pub async fn new_bid<T: Biddable>(
opportunity: T,
params: T::Params,
Expand Down

0 comments on commit f30d018

Please sign in to comment.