shadow_drive_sdk/
client.rs

1use serde::de::DeserializeOwned;
2use std::{collections::HashMap, time::Duration};
3
4use serde_json::{json, Value};
5use solana_client::nonblocking::rpc_client::RpcClient;
6use solana_sdk::{commitment_config::CommitmentConfig, signer::Signer, transaction::Transaction};
7
8mod add_immutable_storage;
9mod add_storage;
10mod cancel_delete_storage_account;
11mod claim_stake;
12mod create_storage_account;
13mod delete_file;
14mod delete_storage_account;
15mod edit_file;
16mod get_storage_account;
17mod list_objects;
18mod make_storage_immutable;
19mod migrate;
20mod redeem_rent;
21mod reduce_storage;
22mod refresh_stake;
23mod store_files;
24mod top_up;
25// mod upload_multiple_files;
26
27use crate::{
28    constants::SHDW_DRIVE_ENDPOINT,
29    error::Error,
30    models::{FileDataResponse, GetBucketSizeResponse, ShadowDriveResult},
31};
32pub use add_immutable_storage::*;
33pub use add_storage::*;
34pub use cancel_delete_storage_account::*;
35pub use claim_stake::*;
36pub use create_storage_account::*;
37pub use delete_file::*;
38pub use delete_storage_account::*;
39pub use edit_file::*;
40pub use get_storage_account::*;
41pub use list_objects::*;
42pub use make_storage_immutable::*;
43pub use migrate::*;
44pub use redeem_rent::*;
45pub use reduce_storage::*;
46pub use refresh_stake::*;
47pub use store_files::*;
48pub use top_up::*;
49
50/// Client that allows a user to interact with the Shadow Drive.
51pub struct ShadowDriveClient<T>
52where
53    T: Signer,
54{
55    wallet: T,
56    rpc_client: RpcClient,
57    http_client: reqwest::Client,
58}
59
60impl<T> ShadowDriveClient<T>
61where
62    T: Signer,
63{
64    /// Creates a new [`ShadowDriveClient`] from the given [`Signer`] and URL.
65    /// * `wallet` - A [`Signer`] that for signs all transactions generated by the client. Typically this is a user's keypair.
66    /// * `rpc_url` - An HTTP URL of a Solana RPC provider.
67    ///
68    /// The underlying Solana RPC client is configured with 120s timeout and a [commitment level][cl] of [`confirmed`](solana_sdk::commitment_config::CommitmentLevel::Confirmed).
69    ///
70    /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment
71    ///
72    /// To customize [`RpcClient`] settings see [`new_with_rpc`](Self::new_with_rpc).
73    ///
74    /// # Example
75    /// ```
76    /// use solana_sdk::signer::keypair::Keypair;    
77    ///
78    /// let wallet = Keypair::generate();
79    /// let shdw_drive = ShadowDriveClient::new(wallet, "https://ssc-dao.genesysgo.net");
80    /// ```
81    pub fn new<U: ToString>(wallet: T, rpc_url: U) -> Self {
82        let rpc_client = RpcClient::new_with_timeout_and_commitment(
83            rpc_url.to_string(),
84            Duration::from_secs(120),
85            CommitmentConfig::confirmed(),
86        );
87        Self {
88            wallet,
89            rpc_client,
90            http_client: reqwest::Client::new(),
91        }
92    }
93
94    /// Creates a new [`ShadowDriveClient`] from the given [`Signer`] and [`RpcClient`].
95    /// * `wallet` - A [`Signer`] that for signs all transactions generated by the client. Typically this is a user's keypair.
96    /// * `rpc_client` - A Solana [`RpcClient`] that handles sending transactions and reading accounts from the blockchain.
97    ///
98    /// Providng the [`RpcClient`] allows customization of timeout and committment level.
99    ///
100    /// # Example
101    /// ```
102    /// use solana_client::rpc_client::RpcClient;
103    /// use solana_sdk::signer::keypair::Keypair;    
104    /// use solana_sdk::commitment_config::CommitmentConfig;
105    ///
106    /// let wallet = Keypair::generate();
107    /// let solana_rpc = RpcClient::new_with_commitment("https://ssc-dao.genesysgo.net", CommitmentConfig::confirmed());
108    /// let shdw_drive = ShadowDriveClient::new_with_rpc(wallet, solana_rpc);
109    /// ```
110    pub fn new_with_rpc(wallet: T, rpc_client: RpcClient) -> Self {
111        Self {
112            wallet,
113            rpc_client,
114            http_client: reqwest::Client::new(),
115        }
116    }
117
118    pub async fn get_object_data(&self, location: &str) -> ShadowDriveResult<FileDataResponse> {
119        let response = self
120            .http_client
121            .post(format!("{}/get-object-data", SHDW_DRIVE_ENDPOINT))
122            .header("Content-Type", "application/json")
123            .json(&json!({ "location": location }))
124            .send()
125            .await?;
126
127        if !response.status().is_success() {
128            return Err(Error::ShadowDriveServerError {
129                status: response.status().as_u16(),
130                message: response.json::<Value>().await?,
131            });
132        }
133
134        let response = response.json::<FileDataResponse>().await?;
135
136        Ok(response)
137    }
138    pub async fn get_storage_account_size(
139        &self,
140        storage_account_key: &str,
141    ) -> ShadowDriveResult<GetBucketSizeResponse> {
142        let mut bucket_query = HashMap::new();
143        bucket_query.insert("storageAccount", storage_account_key.to_string());
144        let response = self
145            .http_client
146            .get(format!("{}/storage-account-size", SHDW_DRIVE_ENDPOINT))
147            .query(&bucket_query)
148            .header("Content-Type", "application/json")
149            .send()
150            .await?;
151
152        if !response.status().is_success() {
153            return Err(Error::ShadowDriveServerError {
154                status: response.status().as_u16(),
155                message: response.json::<Value>().await?,
156            });
157        }
158
159        let response = response.json::<GetBucketSizeResponse>().await?;
160
161        Ok(response)
162    }
163
164    async fn send_shdw_txn<K: DeserializeOwned>(
165        &self,
166        uri: &str,
167        txn_encoded: String,
168        storage_used: Option<u64>,
169    ) -> ShadowDriveResult<K> {
170        let body = serde_json::to_string(&json!({
171           "transaction": txn_encoded,
172           "commitment": "finalized",
173           "storageUsed": Some(storage_used)
174        }))
175        .map_err(Error::InvalidJson)?;
176
177        let response = self
178            .http_client
179            .post(format!("{}/{}", SHDW_DRIVE_ENDPOINT, uri))
180            .header("Content-Type", "application/json")
181            .body(body)
182            .send()
183            .await?;
184
185        if !response.status().is_success() {
186            return Err(Error::ShadowDriveServerError {
187                status: response.status().as_u16(),
188                message: response.json::<Value>().await?,
189            });
190        }
191
192        let response = response.json::<K>().await?;
193
194        Ok(response)
195    }
196}
197
198pub(crate) fn serialize_and_encode(txn: &Transaction) -> ShadowDriveResult<String> {
199    let serialized = bincode::serialize(txn)
200        .map_err(|error| Error::TransactionSerializationFailed(format!("{:?}", error)))?;
201    Ok(base64::encode(serialized))
202}