shadow_drive_sdk/client/
create_storage_account.rs

1use anchor_lang::{system_program, AccountDeserialize, InstructionData, ToAccountMetas};
2use byte_unit::Byte;
3use serde_json::{json, Value};
4use shadow_drive_user_staking::instruction::InitializeAccount;
5use shadow_drive_user_staking::instructions::initialize_account::UserInfo;
6use shadow_drive_user_staking::{accounts as shdw_drive_accounts, instruction::InitializeAccount2};
7use solana_client::{
8    client_error::{ClientError, ClientErrorKind},
9    rpc_request::RpcError,
10};
11use solana_sdk::{
12    instruction::Instruction, pubkey::Pubkey, signer::Signer, sysvar::rent,
13    transaction::Transaction,
14};
15use spl_associated_token_account::get_associated_token_address;
16use spl_token::ID as TokenProgram;
17
18use super::ShadowDriveClient;
19use crate::{
20    constants::{PROGRAM_ADDRESS, SHDW_DRIVE_ENDPOINT, STORAGE_CONFIG_PDA, TOKEN_MINT, UPLOADER},
21    derived_addresses,
22    error::Error,
23    models::*,
24    serialize_and_encode,
25};
26
27pub enum StorageAccountVersion {
28    V1 { owner_2: Option<Pubkey> },
29    V2,
30}
31
32impl StorageAccountVersion {
33    pub fn v1() -> Self {
34        Self::V1 { owner_2: None }
35    }
36
37    pub fn v1_with_owner_2(owner_2: Pubkey) -> Self {
38        Self::V1 {
39            owner_2: Some(owner_2),
40        }
41    }
42
43    pub fn v2() -> Self {
44        Self::V2
45    }
46}
47
48impl<T> ShadowDriveClient<T>
49where
50    T: Signer,
51{
52    /// Creates a [`StorageAccount`](crate::models::StorageAccount) on the Shadow Drive.
53    /// [`StorageAccount`]'s can hold multiple files, and are paid for using the SHDW token.
54    /// * `name` - The name of the [`StorageAccount`](crate::models::StorageAccount). Does not need to be unique.
55    /// * `size` - The amount of storage the [`StorageAccount`](crate::models::StorageAccount) should be initialized with.
56    /// When specifying size, only KB, MB, and GB storage units are currently supported.
57    pub async fn create_storage_account(
58        &self,
59        name: &str,
60        size: Byte,
61        version: StorageAccountVersion,
62    ) -> ShadowDriveResult<CreateStorageAccountResponse> {
63        let wallet = &self.wallet;
64        let wallet_pubkey = wallet.pubkey();
65
66        let rpc_client = &self.rpc_client;
67
68        let (user_info, _) = derived_addresses::user_info(&wallet_pubkey);
69
70        // If userInfo hasn't been initialized, default to 0 for account seed
71        let user_info_acct = rpc_client.get_account(&user_info).await;
72
73        let mut account_seed: u32 = 0;
74        match user_info_acct {
75            Ok(user_info_acct) => {
76                let user_info = UserInfo::try_deserialize(&mut user_info_acct.data.as_slice())
77                    .map_err(Error::AnchorError)?;
78                account_seed = user_info.account_counter;
79            }
80            Err(ClientError {
81                kind: ClientErrorKind::RpcError(RpcError::ForUser(_)),
82                ..
83            }) => {
84                // this is what rpc_client.get_account() returns if the account doesn't exist
85                // assume 0 seed
86            }
87            Err(err) => {
88                //a different rpc error occurred
89                return Err(Error::from(err));
90            }
91        }
92
93        let storage_requested: u64 = size
94            .get_bytes()
95            .try_into()
96            .map_err(|_| Error::InvalidStorage)?;
97
98        let txn_encoded = match version {
99            StorageAccountVersion::V1 { owner_2 } => {
100                self.create_v1(name, account_seed, user_info, storage_requested, owner_2)
101                    .await?
102            }
103            StorageAccountVersion::V2 => {
104                self.create_v2(name, account_seed, user_info, storage_requested)
105                    .await?
106            }
107        };
108
109        let body = serde_json::to_string(&json!({ "transaction": txn_encoded })).unwrap();
110
111        let response = self
112            .http_client
113            .post(format!("{}/storage-account", SHDW_DRIVE_ENDPOINT))
114            .header("Content-Type", "application/json")
115            .body(body)
116            .send()
117            .await?;
118
119        if !response.status().is_success() {
120            return Err(Error::ShadowDriveServerError {
121                status: response.status().as_u16(),
122                message: response.json::<Value>().await?,
123            });
124        }
125
126        let response = response.json::<CreateStorageAccountResponse>().await?;
127
128        Ok(response)
129    }
130
131    async fn create_v1(
132        &self,
133        name: &str,
134        account_seed: u32,
135        user_info: Pubkey,
136        storage_requested: u64,
137        owner_2: Option<Pubkey>,
138    ) -> ShadowDriveResult<String> {
139        let wallet_pubkey = self.wallet.pubkey();
140
141        let (storage_account, _) = derived_addresses::storage_account(&wallet_pubkey, account_seed);
142
143        let (stake_account, _) = derived_addresses::stake_account(&storage_account);
144
145        let owner_ata = get_associated_token_address(&wallet_pubkey, &TOKEN_MINT);
146
147        let accounts = shdw_drive_accounts::InitializeStorageAccountV1 {
148            storage_config: *STORAGE_CONFIG_PDA,
149            user_info,
150            storage_account,
151            stake_account,
152            token_mint: TOKEN_MINT,
153            owner_1: wallet_pubkey,
154            uploader: UPLOADER,
155            owner_1_token_account: owner_ata,
156            system_program: system_program::ID,
157            token_program: TokenProgram,
158            rent: rent::ID,
159        };
160
161        let args = InitializeAccount {
162            identifier: name.to_string(),
163            storage: storage_requested,
164            owner_2,
165        };
166
167        let instruction = Instruction {
168            program_id: PROGRAM_ADDRESS,
169            accounts: accounts.to_account_metas(None),
170            data: args.data(),
171        };
172
173        let mut txn = Transaction::new_with_payer(&[instruction], Some(&wallet_pubkey));
174
175        txn.try_partial_sign(
176            &[&self.wallet],
177            self.rpc_client.get_latest_blockhash().await?,
178        )?;
179
180        let txn_encoded = serialize_and_encode(&txn)?;
181
182        Ok(txn_encoded)
183    }
184
185    async fn create_v2(
186        &self,
187        name: &str,
188        account_seed: u32,
189        user_info: Pubkey,
190        storage_requested: u64,
191    ) -> ShadowDriveResult<String> {
192        let wallet_pubkey = self.wallet.pubkey();
193
194        let (storage_account, _) = derived_addresses::storage_account(&wallet_pubkey, account_seed);
195
196        let (stake_account, _) = derived_addresses::stake_account(&storage_account);
197
198        let owner_ata = get_associated_token_address(&wallet_pubkey, &TOKEN_MINT);
199
200        let accounts = shdw_drive_accounts::InitializeStorageAccountV2 {
201            storage_config: *STORAGE_CONFIG_PDA,
202            user_info,
203            storage_account,
204            stake_account,
205            token_mint: TOKEN_MINT,
206            owner_1: wallet_pubkey,
207            uploader: UPLOADER,
208            owner_1_token_account: owner_ata,
209            system_program: system_program::ID,
210            token_program: TokenProgram,
211            rent: rent::ID,
212        };
213
214        let args = InitializeAccount2 {
215            identifier: name.to_string(),
216            storage: storage_requested,
217        };
218
219        let instruction = Instruction {
220            program_id: PROGRAM_ADDRESS,
221            accounts: accounts.to_account_metas(None),
222            data: args.data(),
223        };
224
225        let mut txn = Transaction::new_with_payer(&[instruction], Some(&wallet_pubkey));
226
227        txn.try_partial_sign(
228            &[&self.wallet],
229            self.rpc_client.get_latest_blockhash().await?,
230        )?;
231
232        let txn_encoded = serialize_and_encode(&txn)?;
233
234        Ok(txn_encoded)
235    }
236}