yellowstone_shield_cli/command/
policy.rs1use borsh::BorshDeserialize;
2use log::info;
3use solana_sdk::{
4 commitment_config::CommitmentConfig,
5 pubkey::Pubkey,
6 signature::{Keypair, Signer},
7};
8use spl_associated_token_account::get_associated_token_address_with_program_id;
9use spl_pod::optional_keys::OptionalNonZeroPubkey;
10use spl_token_2022::{
11 extension::{BaseStateWithExtensions, ExtensionType, PodStateWithExtensions},
12 pod::PodMint,
13 state::Mint,
14};
15use spl_token_metadata_interface::{
16 borsh::BorshDeserialize as MetadataInterfaceBorshDeserialize, state::TokenMetadata,
17};
18use yellowstone_shield_client::{
19 accounts::{Policy, PolicyV2},
20 instructions::{ClosePolicyBuilder, CreatePolicyBuilder},
21 types::{Kind, PermissionStrategy},
22 CreateAccountBuilder, CreateAsscoiatedTokenAccountBuilder, InitializeMetadataBuilder,
23 InitializeMint2Builder, MetadataPointerInitializeBuilder, PolicyTrait,
24 TokenExtensionsMintToBuilder, TransactionBuilder,
25};
26
27use super::{RunCommand, RunResult};
28use crate::command::{CommandComplete, CommandContext, SolanaAccount};
29
30pub enum PolicyVersion {
31 V1(Policy),
32 V2(PolicyV2),
33}
34
35impl PolicyVersion {
36 pub fn strategy(&self) -> u8 {
37 match self {
38 PolicyVersion::V1(pv1) => pv1.strategy,
39 PolicyVersion::V2(pv2) => pv2.strategy,
40 }
41 }
42}
43
44pub struct CreateCommandBuilder {
46 strategy: Option<PermissionStrategy>,
47 name: Option<String>,
48 symbol: Option<String>,
49 uri: Option<String>,
50}
51
52impl Default for CreateCommandBuilder {
53 fn default() -> Self {
54 Self::new()
55 }
56}
57
58impl CreateCommandBuilder {
59 pub fn new() -> Self {
61 Self {
62 strategy: None,
63 name: None,
64 symbol: None,
65 uri: None,
66 }
67 }
68
69 pub fn strategy(mut self, strategy: PermissionStrategy) -> Self {
71 self.strategy = Some(strategy);
72 self
73 }
74
75 pub fn name(mut self, name: String) -> Self {
77 self.name = Some(name);
78 self
79 }
80
81 pub fn symbol(mut self, symbol: String) -> Self {
83 self.symbol = Some(symbol);
84 self
85 }
86
87 pub fn uri(mut self, uri: String) -> Self {
89 self.uri = Some(uri);
90 self
91 }
92}
93
94#[async_trait::async_trait]
95impl RunCommand for CreateCommandBuilder {
96 async fn run(&mut self, context: CommandContext) -> RunResult {
98 let CommandContext { keypair, client } = context;
99
100 let mint = Keypair::new();
102 let payer_token_account = get_associated_token_address_with_program_id(
104 &keypair.pubkey(),
105 &mint.pubkey(),
106 &spl_token_2022::ID,
107 );
108
109 let mint_size =
111 ExtensionType::try_calculate_account_len::<Mint>(&[ExtensionType::MetadataPointer])
112 .unwrap();
113
114 let token_metadata = TokenMetadata {
115 update_authority: OptionalNonZeroPubkey::try_from(Some(keypair.pubkey())).unwrap(),
116 mint: mint.pubkey(),
117 name: self.name.clone().expect("name must be set"),
118 symbol: self.symbol.clone().expect("symbol must be set"),
119 uri: self.uri.clone().expect("uri must be set"),
120 additional_metadata: Vec::<(String, String)>::new(),
121 };
122
123 let rent = mint_size + token_metadata.tlv_size_of().unwrap();
124
125 let create_mint_ix = CreateAccountBuilder::build()
126 .payer(&keypair.pubkey())
127 .account(&mint.pubkey())
128 .space(mint_size)
129 .rent(rent)
130 .owner(&spl_token_2022::id())
131 .instruction();
132
133 let init_metadata_pointer_ix = MetadataPointerInitializeBuilder::build()
135 .mint(&mint.pubkey())
136 .metadata(mint.pubkey())
137 .authority(keypair.pubkey())
138 .instruction();
139
140 let init_mint_ix = InitializeMint2Builder::build()
141 .mint(&mint.pubkey())
142 .mint_authority(&keypair.pubkey())
143 .instruction();
144
145 let init_metadata_ix = InitializeMetadataBuilder::new()
146 .mint(&mint.pubkey())
147 .owner(&keypair.pubkey())
148 .update_authority(&keypair.pubkey())
149 .mint_authority(&keypair.pubkey())
150 .name(token_metadata.name)
151 .symbol(token_metadata.symbol)
152 .uri(token_metadata.uri)
153 .instruction();
154
155 let (address, _) = Policy::find_pda(&mint.pubkey());
158 let create_policy_ix = CreatePolicyBuilder::new()
159 .policy(address)
160 .mint(mint.pubkey())
161 .payer(keypair.pubkey())
162 .owner(keypair.pubkey())
163 .token_account(payer_token_account)
164 .strategy(self.strategy.expect("strategy must be set"))
165 .instruction();
166
167 let init_payer_token_account_ix = CreateAsscoiatedTokenAccountBuilder::build()
169 .owner(&keypair.pubkey())
170 .mint(&mint.pubkey())
171 .payer(&keypair.pubkey())
172 .instruction();
173
174 let mint_to_payer_ix = TokenExtensionsMintToBuilder::build()
176 .mint(&mint.pubkey())
177 .account(&payer_token_account)
178 .owner(&keypair.pubkey())
179 .amount(1)
180 .instruction();
181
182 let last_blockhash = client.get_latest_blockhash().await?;
183
184 let tx = TransactionBuilder::build()
185 .instruction(create_mint_ix)
186 .instruction(init_metadata_pointer_ix)
187 .instruction(init_mint_ix)
188 .instruction(init_metadata_ix)
189 .instruction(init_payer_token_account_ix)
190 .instruction(mint_to_payer_ix)
191 .instruction(create_policy_ix)
192 .signer(&keypair)
193 .signer(&mint)
194 .payer(&keypair.pubkey())
195 .recent_blockhash(last_blockhash)
196 .transaction();
197
198 client
199 .send_and_confirm_transaction_with_spinner_and_commitment(
200 &tx,
201 CommitmentConfig::confirmed(),
202 )
203 .await?;
204
205 let account_data = client.get_account(&address).await?;
206 let account_data: &[u8] = &account_data.data;
207
208 let policy_version = Kind::try_from_slice(&[account_data[0]])?;
209
210 let policy = match policy_version {
211 Kind::Policy => PolicyVersion::V1(Policy::from_bytes(&account_data[..Policy::LEN])?),
212 Kind::PolicyV2 => {
213 PolicyVersion::V2(PolicyV2::from_bytes(&account_data[..PolicyV2::LEN])?)
214 }
215 };
216
217 let mint_data = client.get_account(&mint.pubkey()).await?;
218 let account_data: &[u8] = &mint_data.data;
219
220 let mint_pod = PodStateWithExtensions::<PodMint>::unpack(account_data).unwrap();
221 let mint_bytes = mint_pod.get_extension_bytes::<TokenMetadata>().unwrap();
222 let token_metadata = TokenMetadata::try_from_slice(mint_bytes).unwrap();
223
224 Ok(CommandComplete(
225 SolanaAccount(mint.pubkey(), Some(token_metadata)),
226 SolanaAccount(address, Some(policy)),
227 ))
228 }
229}
230
231pub struct DeleteCommandBuilder<'a> {
233 mint: Option<&'a Pubkey>,
234}
235
236impl Default for DeleteCommandBuilder<'_> {
237 fn default() -> Self {
238 Self::new()
239 }
240}
241
242impl<'a> DeleteCommandBuilder<'a> {
243 pub fn new() -> Self {
245 Self { mint: None }
246 }
247
248 pub fn mint(mut self, mint: &'a Pubkey) -> Self {
250 self.mint = Some(mint);
251 self
252 }
253}
254
255#[async_trait::async_trait]
256impl RunCommand for DeleteCommandBuilder<'_> {
257 async fn run(&mut self, context: CommandContext) -> RunResult {
259 let CommandContext { keypair, client } = context;
260
261 let mint = self.mint.expect("mint must be set");
262 let (address, _) = Policy::find_pda(mint);
264 let payer_token_account = get_associated_token_address_with_program_id(
265 &keypair.pubkey(),
266 mint,
267 &spl_token_2022::ID,
268 );
269
270 let close_policy = ClosePolicyBuilder::new()
271 .policy(address)
272 .mint(*mint)
273 .payer(keypair.pubkey())
274 .owner(keypair.pubkey())
275 .token_account(payer_token_account)
276 .instruction();
277
278 let last_blockhash = client.get_latest_blockhash().await?;
279
280 let tx = TransactionBuilder::build()
281 .instruction(close_policy)
282 .signer(&keypair)
283 .payer(&keypair.pubkey())
284 .recent_blockhash(last_blockhash)
285 .transaction();
286
287 let signature = client
288 .send_and_confirm_transaction_with_spinner_and_commitment(
289 &tx,
290 CommitmentConfig::confirmed(),
291 )
292 .await?;
293
294 info!("Transaction signature: {}", signature);
295
296 Ok(CommandComplete(
297 SolanaAccount(*mint, None),
298 SolanaAccount(address, None),
299 ))
300 }
301}
302
303pub struct ShowCommandBuilder<'a> {
304 mint: Option<&'a Pubkey>,
305}
306
307impl Default for ShowCommandBuilder<'_> {
308 fn default() -> Self {
309 Self::new()
310 }
311}
312
313impl<'a> ShowCommandBuilder<'a> {
314 pub fn new() -> Self {
315 Self { mint: None }
316 }
317
318 pub fn mint(mut self, mint: &'a Pubkey) -> Self {
319 self.mint = Some(mint);
320 self
321 }
322}
323
324#[async_trait::async_trait]
325impl RunCommand for ShowCommandBuilder<'_> {
326 async fn run(&mut self, context: CommandContext) -> RunResult {
327 let CommandContext { keypair: _, client } = context;
328
329 let mint = self.mint.expect("mint must be set");
330 let (address, _) = Policy::find_pda(mint);
332
333 let account_data = client.get_account(&address).await?;
334 let account_data: &[u8] = &account_data.data;
335
336 let policy_version = Kind::try_from_slice(&[account_data[0]])?;
337
338 let policy = match policy_version {
339 Kind::Policy => PolicyVersion::V1(Policy::from_bytes(account_data)?),
340 Kind::PolicyV2 => PolicyVersion::V2(PolicyV2::from_bytes(account_data)?),
341 };
342
343 let identities = match policy_version {
344 Kind::Policy => Policy::try_deserialize_identities(account_data)?,
345 Kind::PolicyV2 => PolicyV2::try_deserialize_identities(account_data)?,
346 };
347
348 let mint_data = client.get_account(mint).await?;
349 let account_data: &[u8] = &mint_data.data;
350
351 let mint_pod = PodStateWithExtensions::<PodMint>::unpack(account_data).unwrap();
352 let mint_bytes = mint_pod.get_extension_bytes::<TokenMetadata>().unwrap();
353 let token_metadata = TokenMetadata::try_from_slice(mint_bytes).unwrap();
354
355 info!("Identities in policy:");
356 for (i, identity) in identities.iter().enumerate() {
357 info!(" {}. {}", i, identity);
358 }
359
360 Ok(CommandComplete(
361 SolanaAccount(*mint, Some(token_metadata)),
362 SolanaAccount(address, Some(policy)),
363 ))
364 }
365}