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 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 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 }
87 Err(err) => {
88 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}