1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use std::time::Duration;

use serde_json::{json, Value};
use solana_client::rpc_client::RpcClient;
use solana_sdk::{commitment_config::CommitmentConfig, signer::Signer};

mod add_storage;
mod cancel_delete_file;
mod cancel_delete_storage_account;
mod claim_stake;
mod create_storage_account;
mod delete_file;
mod delete_storage_account;
mod edit_file;
mod get_storage_account;
mod list_objects;
mod make_storage_immutable;
mod reduce_storage;
mod upload_file;
mod upload_multiple_files;

pub use add_storage::*;
pub use cancel_delete_file::*;
pub use cancel_delete_storage_account::*;
pub use claim_stake::*;
pub use create_storage_account::*;
pub use delete_file::*;
pub use delete_storage_account::*;
pub use edit_file::*;
pub use get_storage_account::*;
pub use list_objects::*;
pub use make_storage_immutable::*;
pub use reduce_storage::*;
pub use upload_file::*;
pub use upload_multiple_files::*;

use crate::{
    constants::SHDW_DRIVE_ENDPOINT,
    error::Error,
    models::{FileDataResponse, ShadowDriveResult},
};

/// Client that allows a user to interact with the Shadow Drive.
pub struct ShadowDriveClient<T>
where
    T: Signer,
{
    wallet: T,
    rpc_client: RpcClient,
    http_client: reqwest::Client,
}

impl<T> ShadowDriveClient<T>
where
    T: Signer + Send + Sync,
{
    /// Creates a new [`ShadowDriveClient`] from the given [`Signer`] and URL.
    /// * `wallet` - A [`Signer`] that for signs all transactions generated by the client. Typically this is a user's keypair.
    /// * `rpc_url` - An HTTP URL of a Solana RPC provider.
    ///
    /// The underlying Solana RPC client is configured with 120s timeout and a [commitment level][cl] of [`Finalized`](solana_sdk::commitment_config::CommitmentLevel::Finalized).
    ///
    /// [cl]: https://docs.solana.com/developing/clients/jsonrpc-api#configuring-state-commitment
    ///
    /// To customize [`RpcClient`] settings see [`new_with_rpc`](Self::new_with_rpc).
    ///
    /// # Example
    /// ```
    /// use solana_sdk::signer::keypair::Keypair;    
    ///
    /// let wallet = Keypair::generate();
    /// let shdw_drive = ShadowDriveClient::new(wallet, "https://ssc-dao.genesysgo.net");
    /// ```
    pub fn new<U: ToString>(wallet: T, rpc_url: U) -> Self {
        let rpc_client = RpcClient::new_with_timeout_and_commitment(
            rpc_url,
            Duration::from_secs(120),
            CommitmentConfig::finalized(),
        );
        Self {
            wallet,
            rpc_client,
            http_client: reqwest::Client::new(),
        }
    }

    /// Creates a new [`ShadowDriveClient`] from the given [`Signer`] and [`RpcClient`].
    /// * `wallet` - A [`Signer`] that for signs all transactions generated by the client. Typically this is a user's keypair.
    /// * `rpc_client` - A Solana [`RpcClient`] that handles sending transactions and reading accounts from the blockchain.
    ///
    /// Providng the [`RpcClient`] allows customization of timeout and committment level.
    ///
    /// # Example
    /// ```
    /// use solana_client::rpc_client::RpcClient;
    /// use solana_sdk::signer::keypair::Keypair;    
    /// use solana_sdk::commitment_config::CommitmentConfig;
    ///
    /// let wallet = Keypair::generate();
    /// let solana_rpc = RpcClient::new_with_commitment("https://ssc-dao.genesysgo.net", CommitmentConfig::confirmed());
    /// let shdw_drive = ShadowDriveClient::new_with_rpc(wallet, solana_rpc);
    /// ```
    pub fn new_with_rpc(wallet: T, rpc_client: RpcClient) -> Self {
        Self {
            wallet,
            rpc_client,
            http_client: reqwest::Client::new(),
        }
    }

    async fn get_object_data(&self, location: &str) -> ShadowDriveResult<FileDataResponse> {
        let response = self
            .http_client
            .post(format!("{}/get-object-data", SHDW_DRIVE_ENDPOINT))
            .header("Content-Type", "application/json")
            .json(&json!({ "location": location }))
            .send()
            .await?;

        if !response.status().is_success() {
            return Err(Error::ShadowDriveServerError {
                status: response.status().as_u16(),
                message: response.json::<Value>().await?,
            });
        }

        let response = response.json::<FileDataResponse>().await?;

        Ok(response)
    }
}