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