From 39ed3c1060e8f19b2fbd79950660b7a938971c4c Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Thu, 22 Jun 2023 14:34:12 -0700 Subject: [PATCH 01/17] minor fmt --- cli/src/command/nft/collection/init.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/command/nft/collection/init.rs b/cli/src/command/nft/collection/init.rs index a4dd123..b5b3727 100644 --- a/cli/src/command/nft/collection/init.rs +++ b/cli/src/command/nft/collection/init.rs @@ -52,7 +52,7 @@ pub(super) async fn process(signer: &impl Signer, rpc_url: &str) -> anyhow::Resu let collection = Collection::get_pda(creator_group, &name); // Construct the instruction to create a minter - let for_minter = Confirm::new("Is this collection for a shadowy super minter? (no for 1/1s). You cannot change this later").prompt()?; + let for_minter = Confirm::new("Is this collection for a shadowy super minter? (no for 1/1s)? You cannot change this later.").prompt()?; let args = CreateCollectionArgs { name, symbol, From 3a941b5e7716b398a370346361d152f122f5bd4b Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Thu, 22 Jun 2023 14:34:45 -0700 Subject: [PATCH 02/17] update confirm msg --- cli/src/command/nft/collection/init.rs | 10 ++++++++++ cli/src/command/nft/creator_group/init.rs | 7 ++++++- cli/src/command/nft/minter/init.rs | 7 ++++++- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/cli/src/command/nft/collection/init.rs b/cli/src/command/nft/collection/init.rs index b5b3727..2c5502b 100644 --- a/cli/src/command/nft/collection/init.rs +++ b/cli/src/command/nft/collection/init.rs @@ -84,6 +84,16 @@ pub(super) async fn process(signer: &impl Signer, rpc_url: &str) -> anyhow::Resu client.get_latest_blockhash().await?, ); + match Confirm::new(&format!( + "Send and confirm transaction (signing with {})?", + signer.pubkey() + )) + .prompt() + { + Ok(true) => {} + _ => return Err(anyhow::Error::msg("Discarded Request")), + } + if let Err(e) = client.send_and_confirm_transaction(&create_group_tx).await { return Err(anyhow::Error::msg(e)); }; diff --git a/cli/src/command/nft/creator_group/init.rs b/cli/src/command/nft/creator_group/init.rs index b36fc17..9c0816b 100644 --- a/cli/src/command/nft/creator_group/init.rs +++ b/cli/src/command/nft/creator_group/init.rs @@ -115,7 +115,12 @@ pub(crate) async fn process( // TODO: add name here when we change contract // Confirm input with user - match Confirm::new(&format!("Confirm Input (signing with {})", signer.pubkey())).prompt() { + match Confirm::new(&format!( + "Send and confirm transaction (signing with {})?", + signer.pubkey() + )) + .prompt() + { Ok(true) => {} _ => return Err(anyhow::Error::msg("Discarded Request")), } diff --git a/cli/src/command/nft/minter/init.rs b/cli/src/command/nft/minter/init.rs index 8619efd..ac20e63 100644 --- a/cli/src/command/nft/minter/init.rs +++ b/cli/src/command/nft/minter/init.rs @@ -376,7 +376,12 @@ pub(super) async fn process( client.get_latest_blockhash().await?, ); - match Confirm::new(&format!("Confirm Input (signing with {})", signer.pubkey())).prompt() { + match Confirm::new(&format!( + "Send and confirm transaction (signing with {})?", + signer.pubkey() + )) + .prompt() + { Ok(true) => {} _ => return Err(anyhow::Error::msg("Discarded Request")), } From 2cc86f23f9d88b44b40f3c5fcabc1f16369743fd Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Thu, 22 Jun 2023 14:34:59 -0700 Subject: [PATCH 03/17] remove todo (done) --- cli/src/command/nft/creator_group/init.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/cli/src/command/nft/creator_group/init.rs b/cli/src/command/nft/creator_group/init.rs index 9c0816b..c02773f 100644 --- a/cli/src/command/nft/creator_group/init.rs +++ b/cli/src/command/nft/creator_group/init.rs @@ -112,8 +112,6 @@ pub(crate) async fn process( ) }; - // TODO: add name here when we change contract - // Confirm input with user match Confirm::new(&format!( "Send and confirm transaction (signing with {})?", From 6a0192fb3ff312fadcf0ff84128f9758fa9b094d Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Thu, 22 Jun 2023 14:35:04 -0700 Subject: [PATCH 04/17] minor fmt --- cli/src/command/nft/creator_group/init.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/command/nft/creator_group/init.rs b/cli/src/command/nft/creator_group/init.rs index c02773f..979b31f 100644 --- a/cli/src/command/nft/creator_group/init.rs +++ b/cli/src/command/nft/creator_group/init.rs @@ -34,7 +34,7 @@ pub(crate) async fn process( }; // Ask user what they would like to name their group - let name = Text::new("What would you like to n ame your group").prompt()?; + let name = Text::new("What would you like to name your group").prompt()?; // Ask for other members if not single_member let other_members = Rc::new(RefCell::new(vec![])); From da480ed3b65a223179218b6ee73804b3a9c4c73c Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Thu, 22 Jun 2023 14:35:24 -0700 Subject: [PATCH 05/17] cargo fmt --- cli/src/command/nft/minter/init.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cli/src/command/nft/minter/init.rs b/cli/src/command/nft/minter/init.rs index ac20e63..ed79efd 100644 --- a/cli/src/command/nft/minter/init.rs +++ b/cli/src/command/nft/minter/init.rs @@ -28,8 +28,7 @@ use solana_sdk::system_program; use solana_sdk::transaction::Transaction; use crate::command::nft::utils::{ - swap_sol_for_shdw_tx, - validate_json_compliance, SHDW_MINT_PUBKEY, + swap_sol_for_shdw_tx, validate_json_compliance, SHDW_MINT_PUBKEY, }; use crate::utils::shadow_client_factory; @@ -110,7 +109,7 @@ pub(super) async fn process( }; let Ok( MinterInitArgs { creator_group, collection, reveal_hash_all_ones_if_none, items_available, mint_price_lamports, start_time_solana_cluster_time, end_time_solana_cluster_time, sdrive_account, name_prefix, metadata_dir } - ) + ) = serde_json::from_str(&config_file_contents) else { return Err(anyhow::Error::msg("Failed to deserialize json. Do you have all fields filled in and is it formatted properly?")) }; @@ -170,8 +169,8 @@ pub(super) async fn process( .list_objects(&account) .await .map_err(|_| anyhow::Error::msg("Failed to get files in storage account"))?; - let all_files_exist = (0..items_available) - .all(|i| existing_files.contains(&format!("{i}.json"))); + let all_files_exist = + (0..items_available).all(|i| existing_files.contains(&format!("{i}.json"))); if !all_files_exist { // Check if there is enough storage From f85cfbe95860bd765aa975c9faba2a16839e65d3 Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Thu, 22 Jun 2023 15:57:42 -0700 Subject: [PATCH 06/17] update standard --- cli/src/command/nft/utils.rs | 30 +++++++++++------------------- 1 file changed, 11 insertions(+), 19 deletions(-) diff --git a/cli/src/command/nft/utils.rs b/cli/src/command/nft/utils.rs index 47fa0ed..fd798b1 100644 --- a/cli/src/command/nft/utils.rs +++ b/cli/src/command/nft/utils.rs @@ -4,38 +4,30 @@ use serde_json::Value; use solana_sdk::{pubkey::Pubkey, transaction::VersionedTransaction}; use std::str::FromStr; -/// This function ensures the contents of a JSON file are compliant with the Metaplex Standard +/// This function ensures the contents of a JSON file are compliant with the Off-Chain Shadow Standard /// which we define as a JSON with the non-null values for the following fields: /// /// 1) `name`: Name of the asset. /// 2) `symbol`: Symbol of the asset. /// 3) `description`: Description of the asset. /// 4) `image`: URI pointing to the asset's logo. -/// 5) `animation_url`: URI pointing to the asset's animation. -/// 6) `external_url`: URI pointing to an external URL defining the asset — e.g. the game's main site. -/// 7) `attributes`: Array of attributes defining the characteristics of the asset. -/// a) `trait_type`: The type of attribute. -/// b) `value`: The value for that attribute. +/// 5) `external_url`: URI pointing to an external URL defining the asset — e.g. the game's main site. /// -/// This is taken from https://docs.metaplex.com/programs/token-metadata/token-standard and reformatted. +/// The function simply checks whether these fields are non-null. Although we do not check for it, +/// we recommend the following fields are included if relevant: /// -/// The function simply checks whether the fields are non-null -pub(crate) fn validate_json_compliance(json: &Value) -> bool { +/// 6) `animation_url` (optional): URI pointing to the asset's animation. +/// 7) `attributes` (optional): Array of attributes defining the characteristics of the asset. +/// a) `trait_type`: The type of attribute. +/// b) `value`: The value for that attribute. +pub fn validate_json_compliance(json: &Value) -> bool { let has_name = json.get("name").is_some(); let has_symbol = json.get("symbol").is_some(); let has_description = json.get("description").is_some(); let has_image = json.get("image").is_some(); - let has_animation_url = json.get("animation_url").is_some(); let has_external_url = json.get("external_url").is_some(); - let has_attributes = json.get("attributes").is_some(); - - has_name - & has_symbol - & has_description - & has_image - & has_animation_url - & has_external_url - & has_attributes + + has_name & has_symbol & has_description & has_image & has_external_url } pub(crate) async fn swap_sol_for_shdw_tx( From 643ca772f54dd139120eeafa6bd5bcafdd1a82f0 Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Thu, 22 Jun 2023 15:57:57 -0700 Subject: [PATCH 07/17] add validate pb, todo --- cli/src/command/nft/minter/init.rs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/cli/src/command/nft/minter/init.rs b/cli/src/command/nft/minter/init.rs index ed79efd..c7216a2 100644 --- a/cli/src/command/nft/minter/init.rs +++ b/cli/src/command/nft/minter/init.rs @@ -3,7 +3,7 @@ use std::path::{Path, PathBuf}; use std::str::FromStr; use futures::StreamExt; -use indicatif::ProgressBar; +use indicatif::{ProgressBar, ProgressStyle}; use inquire::validator::Validation; use inquire::{Confirm, Text}; use itertools::Itertools; @@ -413,6 +413,14 @@ fn validate_metadata_dir( return Err(anyhow::Error::msg("failed to read directory entries")) }; + let pb = ProgressBar::new(items_available as u64) + .with_style( + ProgressStyle::with_template( + "[{elapsed_precise}] {prefix} {bar:30.cyan/blue} {pos:>7}/{len:7}", + ) + .unwrap(), + ) + .with_prefix("Validating JSON"); for i in 0..items_available as usize { // Expected filename let expected_filename = Path::new(&format!("{i}")).with_extension("json"); @@ -432,17 +440,19 @@ fn validate_metadata_dir( ))); } - // Check for companion files + // Check for companion files (minor TODO: This makes loop O(N^2)...) counts[i] += all_files_in_dir .iter() .filter(|f| f.file_stem() == Some(OsStr::new(&format!("{i}")))) .count(); + + pb.inc(1); } // Ensure every json file has a companion media file for (i, count) in counts.into_iter().enumerate() { if count == 1 { - println!("Warning: File {i}.json does not have a companion media file, e.g. {i}.png") + println!("Warning: {i}.json does not have expected companion file, e.g. {i}.png. Ignore if using some other convention.") } } From f0c9a1700fb9d5e951befecb620377ad9c2c9a51 Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Thu, 22 Jun 2023 16:35:13 -0700 Subject: [PATCH 08/17] add autocomplete from inquire example --- cli/src/command/nft/minter/init.rs | 117 +++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) diff --git a/cli/src/command/nft/minter/init.rs b/cli/src/command/nft/minter/init.rs index c7216a2..ca59304 100644 --- a/cli/src/command/nft/minter/init.rs +++ b/cli/src/command/nft/minter/init.rs @@ -102,6 +102,7 @@ pub(super) async fn process( Ok(Validation::Invalid("Path does not exist".into())) } }) + .with_autocomplete(FilePathCompleter::default()) .prompt()?; let Ok(config_file_contents) = std::fs::read_to_string(config_file_path) else { @@ -465,3 +466,119 @@ fn safe_amount(additional_storage: u64, rate_per_gib: u64) -> u64 { ((additional_storage as u128) * (rate_per_gib as u128) / (BYTES_PER_GIB)) as u64 } const BYTES_PER_GIB: u128 = 1 << 30; + +#[derive(Clone, Default)] +pub struct FilePathCompleter { + input: String, + paths: Vec, + lcp: String, +} + +impl FilePathCompleter { + fn update_input(&mut self, input: &str) -> Result<(), inquire::CustomUserError> { + if input == self.input { + return Ok(()); + } + + self.input = input.to_owned(); + self.paths.clear(); + + let input_path = std::path::PathBuf::from(input); + + let fallback_parent = input_path + .parent() + .map(|p| { + if p.to_string_lossy() == "" { + std::path::PathBuf::from(".") + } else { + p.to_owned() + } + }) + .unwrap_or_else(|| std::path::PathBuf::from(".")); + + let scan_dir = if input.ends_with('/') { + input_path + } else { + fallback_parent.clone() + }; + + let entries = match std::fs::read_dir(scan_dir) { + Ok(read_dir) => Ok(read_dir), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + std::fs::read_dir(fallback_parent) + } + Err(err) => Err(err), + }? + .collect::, _>>()?; + + let mut idx = 0; + let limit = 15; + + while idx < entries.len() && self.paths.len() < limit { + let entry = entries.get(idx).unwrap(); + + let path = entry.path(); + let path_str = if path.is_dir() { + format!("{}/", path.to_string_lossy()) + } else { + path.to_string_lossy().to_string() + }; + + if path_str.starts_with(&self.input) && path_str.len() != self.input.len() { + self.paths.push(path_str); + } + + idx = idx.saturating_add(1); + } + + self.lcp = self.longest_common_prefix(); + + Ok(()) + } + + fn longest_common_prefix(&self) -> String { + let mut ret: String = String::new(); + + let mut sorted = self.paths.clone(); + sorted.sort(); + if sorted.is_empty() { + return ret; + } + + let mut first_word = sorted.first().unwrap().chars(); + let mut last_word = sorted.last().unwrap().chars(); + + loop { + match (first_word.next(), last_word.next()) { + (Some(c1), Some(c2)) if c1 == c2 => { + ret.push(c1); + } + _ => return ret, + } + } + } +} + +impl inquire::Autocomplete for FilePathCompleter { + fn get_suggestions(&mut self, input: &str) -> Result, inquire::CustomUserError> { + self.update_input(input)?; + + Ok(self.paths.clone()) + } + + fn get_completion( + &mut self, + input: &str, + highlighted_suggestion: Option, + ) -> Result { + self.update_input(input)?; + + Ok(match highlighted_suggestion { + Some(suggestion) => inquire::autocompletion::Replacement::Some(suggestion), + None => match self.lcp.is_empty() { + true => inquire::autocompletion::Replacement::None, + false => inquire::autocompletion::Replacement::Some(self.lcp.clone()), + }, + }) + } +} From f7249aa993ea924374d006e149c973a335c0c53f Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Fri, 23 Jun 2023 12:22:15 -0700 Subject: [PATCH 09/17] add mint command --- cli/Cargo.toml | 1 + cli/src/command/nft/minter/mint.rs | 88 ++++++++++++++++++++++++++++++ cli/src/command/nft/minter/mod.rs | 9 +++ 3 files changed, 98 insertions(+) create mode 100644 cli/src/command/nft/minter/mint.rs diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 033be8d..3aded02 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -12,6 +12,7 @@ repository = { workspace = true } [dependencies] shadow-drive-sdk = { path = "../sdk", version = "0.7.1" } shadow-rpc-auth = { path = "../auth", version = "0.7.1" } +shadow-nft-common = { git = "https://github.com/genesysgo/shadow-nft-standard", branch = "main"} shadow-nft-standard = { git = "https://github.com/genesysgo/shadow-nft-standard", branch = "main"} shadowy-super-minter = { git = "https://github.com/genesysgo/shadow-nft-standard", branch = "main"} tokio = { version = "^1", features = ["full"] } diff --git a/cli/src/command/nft/minter/mint.rs b/cli/src/command/nft/minter/mint.rs new file mode 100644 index 0000000..7c740dd --- /dev/null +++ b/cli/src/command/nft/minter/mint.rs @@ -0,0 +1,88 @@ +use inquire::Confirm; +use shadow_drive_sdk::{Keypair, Pubkey, Signer}; +use shadow_nft_common::get_payer_pda; +use shadow_nft_standard::common::collection::Collection; +use shadow_nft_standard::common::creator_group::CreatorGroup; +use shadow_nft_standard::common::{token_2022, Metadata}; +use shadowy_super_minter::accounts::Mint as MintAccounts; +use shadowy_super_minter::instruction::Mint as MintInstruction; +use shadowy_super_minter::state::file_type::{AccountDeserialize, InstructionData}; +use shadowy_super_minter::state::file_type::{Id, ToAccountMetas}; +use shadowy_super_minter::state::ShadowySuperMinter; +use solana_client::rpc_client::RpcClient; +use solana_sdk::instruction::Instruction; +use solana_sdk::transaction::Transaction; +use solana_sdk::{system_program, sysvar}; + +pub(super) async fn process( + signer: &impl Signer, + shadowy_super_minter: Pubkey, + rpc_url: &str, +) -> anyhow::Result<()> { + // Get onchain data + let client = RpcClient::new(rpc_url); + let onchain_shadowy_super_minter = ShadowySuperMinter::try_deserialize( + &mut client.get_account_data(&shadowy_super_minter)?.as_slice(), + )?; + let collection = onchain_shadowy_super_minter.collection; + let onchain_collection = + Collection::try_deserialize(&mut client.get_account_data(&collection)?.as_slice())?; + let creator_group = onchain_shadowy_super_minter.creator_group; + let onchain_creator_group = + CreatorGroup::try_deserialize(&mut client.get_account_data(&creator_group)?.as_slice())?; + + // Build mint tx + let mint_keypair = Keypair::new(); // never used after this + let mint_tx = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + shadowy_super_minter::ID, + MintInstruction {}.data().as_ref(), + MintAccounts { + shadowy_super_minter, + minter: signer.pubkey(), + minter_ata: spl_associated_token_account::get_associated_token_address( + &signer.pubkey(), + &mint_keypair.pubkey(), + ), + payer_pda: get_payer_pda(&mint_keypair.pubkey()), + mint: mint_keypair.pubkey(), + collection, + metadata: Metadata::derive_pda(&mint_keypair.pubkey()), + creator_group, + shadow_nft_standard: shadow_nft_standard::ID, + token_program: token_2022::Token2022::id(), + associated_token_program: spl_associated_token_account::ID, + system_program: system_program::ID, + recent_slothashes: sysvar::slot_hashes::ID, + } + .to_account_metas(None), + )], + Some(&signer.pubkey()), + &[signer], + client.get_latest_blockhash()?, + ); + + // Confirm with user + println!("Minting an NFT from:)"); + println!(" minter {shadowy_super_minter}"); + #[rustfmt::skip] + println!(" collection {} ({collection})", &onchain_collection.name); + #[rustfmt::skip] + println!(" creator_group {} ({})", &onchain_creator_group.name, creator_group); + match Confirm::new(&format!( + "Send and confirm transaction (signing with {})?", + signer.pubkey() + )) + .prompt() + { + Ok(true) => {} + _ => return Err(anyhow::Error::msg("Discarded Request")), + } + + // Sign and send + if let Err(e) = client.send_and_confirm_transaction(&mint_tx) { + return Err(anyhow::Error::msg(e)); + }; + + Ok(()) +} diff --git a/cli/src/command/nft/minter/mod.rs b/cli/src/command/nft/minter/mod.rs index 1a0d6cd..ec86ea8 100644 --- a/cli/src/command/nft/minter/mod.rs +++ b/cli/src/command/nft/minter/mod.rs @@ -4,11 +4,18 @@ use shadow_drive_sdk::{Pubkey, Signer}; mod get; mod init; +mod mint; #[derive(Debug, Parser)] pub enum MinterCommand { + /// Initializes a minter given a creator_group and collection Init, + + /// Gets a minter from the chain and prints its state Get { minter: Pubkey }, + + /// Mints an nft from the provided minter + Mint { minter: Pubkey }, } impl MinterCommand { @@ -22,6 +29,8 @@ impl MinterCommand { MinterCommand::Init => init::process(signer, client_signer, rpc_url).await, MinterCommand::Get { minter } => get::process(minter, rpc_url).await, + + MinterCommand::Mint { minter } => mint::process(signer, *minter, rpc_url).await, } } } From 7412f844f82bfc075c958fff3970bb042a2798d7 Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Fri, 23 Jun 2023 12:22:23 -0700 Subject: [PATCH 10/17] add withdraw command --- cli/src/command/nft/collection/mod.rs | 5 ++ cli/src/command/nft/collection/withdraw.rs | 77 ++++++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 cli/src/command/nft/collection/withdraw.rs diff --git a/cli/src/command/nft/collection/mod.rs b/cli/src/command/nft/collection/mod.rs index 88e2a47..35f928b 100644 --- a/cli/src/command/nft/collection/mod.rs +++ b/cli/src/command/nft/collection/mod.rs @@ -3,11 +3,13 @@ use shadow_drive_sdk::{Pubkey, Signer}; mod get; mod init; +mod withdraw; #[derive(Debug, Parser)] pub enum CollectionCommand { Init, Get { collection: Pubkey }, + Withdraw { collection: Pubkey }, } impl CollectionCommand { pub async fn process(&self, signer: &impl Signer, rpc_url: &str) -> anyhow::Result<()> { @@ -15,6 +17,9 @@ impl CollectionCommand { CollectionCommand::Init => init::process(signer, rpc_url).await, CollectionCommand::Get { collection } => get::process(collection, rpc_url).await, + CollectionCommand::Withdraw { collection } => { + withdraw::process(signer, *collection, rpc_url).await + } } } } diff --git a/cli/src/command/nft/collection/withdraw.rs b/cli/src/command/nft/collection/withdraw.rs new file mode 100644 index 0000000..ffb4785 --- /dev/null +++ b/cli/src/command/nft/collection/withdraw.rs @@ -0,0 +1,77 @@ +use inquire::Confirm; +use shadow_drive_sdk::{Pubkey, Signer}; +use shadow_nft_standard::{ + accounts::Withdraw as WithdrawAccounts, + common::{collection::Collection, creator_group::CreatorGroup}, + instruction::Withdraw as WithdrawInstruction, +}; +use shadowy_super_minter::state::file_type::{AccountDeserialize, InstructionData, ToAccountMetas}; +use solana_client::rpc_client::RpcClient; +use solana_sdk::{ + instruction::{AccountMeta, Instruction}, + transaction::Transaction, +}; + +pub(crate) async fn process( + signer: &impl Signer, + collection: Pubkey, + rpc_url: &str, +) -> Result<(), anyhow::Error> { + let client = RpcClient::new(rpc_url); + + // Get onchain data + let onchain_collection = + Collection::try_deserialize(&mut client.get_account_data(&collection)?.as_slice())?; + let onchain_creator_group = CreatorGroup::try_deserialize( + &mut client + .get_account_data(&onchain_collection.creator_group_key)? + .as_slice(), + )?; + let creator_group = onchain_collection.creator_group_key; + + // Build tx + let mut accounts = WithdrawAccounts { + payer_creator: signer.pubkey(), + collection, + creator_group, + } + .to_account_metas(None); + for creator in onchain_creator_group.creators { + if creator != signer.pubkey() { + accounts.push(AccountMeta::new(creator, false)) + } + } + let withdraw_tx = Transaction::new_signed_with_payer( + &[Instruction::new_with_bytes( + shadow_nft_standard::ID, + WithdrawInstruction {}.data().as_ref(), + WithdrawAccounts { + payer_creator: signer.pubkey(), + collection, + creator_group, + } + .to_account_metas(None), + )], + Some(&signer.pubkey()), + &[signer], + client.get_latest_blockhash()?, + ); + + // Confirm with user + match Confirm::new(&format!( + "Send and confirm transaction (signing with {})?", + signer.pubkey() + )) + .prompt() + { + Ok(true) => {} + _ => return Err(anyhow::Error::msg("Discarded Request")), + } + + // Sign and send + if let Err(e) = client.send_and_confirm_transaction(&withdraw_tx) { + return Err(anyhow::Error::msg(e)); + }; + + Ok(()) +} From 7b8b675a403ffb75670f902f52c60692e6213c3e Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Fri, 23 Jun 2023 12:26:46 -0700 Subject: [PATCH 11/17] add minter to tx --- cli/src/command/nft/minter/mint.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/src/command/nft/minter/mint.rs b/cli/src/command/nft/minter/mint.rs index 7c740dd..be96bb0 100644 --- a/cli/src/command/nft/minter/mint.rs +++ b/cli/src/command/nft/minter/mint.rs @@ -58,7 +58,7 @@ pub(super) async fn process( .to_account_metas(None), )], Some(&signer.pubkey()), - &[signer], + &[signer as &dyn Signer, &mint_keypair as &dyn Signer], client.get_latest_blockhash()?, ); From 259806d952a633ffa255c46a0bcd15c660b7deea Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Fri, 23 Jun 2023 12:26:54 -0700 Subject: [PATCH 12/17] update success msg --- cli/src/command/nft/collection/init.rs | 7 +++++-- cli/src/command/nft/collection/withdraw.rs | 7 +++++-- cli/src/command/nft/creator_group/init.rs | 7 +++++-- cli/src/command/nft/minter/init.rs | 8 +++++--- cli/src/command/nft/minter/init_old.rs | 8 +++++--- cli/src/command/nft/minter/mint.rs | 9 +++++++-- 6 files changed, 32 insertions(+), 14 deletions(-) diff --git a/cli/src/command/nft/collection/init.rs b/cli/src/command/nft/collection/init.rs index 2c5502b..cee93e9 100644 --- a/cli/src/command/nft/collection/init.rs +++ b/cli/src/command/nft/collection/init.rs @@ -94,8 +94,11 @@ pub(super) async fn process(signer: &impl Signer, rpc_url: &str) -> anyhow::Resu _ => return Err(anyhow::Error::msg("Discarded Request")), } - if let Err(e) = client.send_and_confirm_transaction(&create_group_tx).await { - return Err(anyhow::Error::msg(e)); + match client.send_and_confirm_transaction(&create_group_tx).await { + Ok(sig) => { + println!("Successful: https://explorer.solana.com/tx/{sig}") + } + Err(e) => return Err(anyhow::Error::msg(e)), }; println!(""); diff --git a/cli/src/command/nft/collection/withdraw.rs b/cli/src/command/nft/collection/withdraw.rs index ffb4785..8bb514b 100644 --- a/cli/src/command/nft/collection/withdraw.rs +++ b/cli/src/command/nft/collection/withdraw.rs @@ -69,8 +69,11 @@ pub(crate) async fn process( } // Sign and send - if let Err(e) = client.send_and_confirm_transaction(&withdraw_tx) { - return Err(anyhow::Error::msg(e)); + match client.send_and_confirm_transaction(&withdraw_tx) { + Ok(sig) => { + println!("Successful: https://explorer.solana.com/tx/{sig}") + } + Err(e) => return Err(anyhow::Error::msg(e)), }; Ok(()) diff --git a/cli/src/command/nft/creator_group/init.rs b/cli/src/command/nft/creator_group/init.rs index 979b31f..20b6d38 100644 --- a/cli/src/command/nft/creator_group/init.rs +++ b/cli/src/command/nft/creator_group/init.rs @@ -150,8 +150,11 @@ pub(crate) async fn process( ); println!("Sending create group tx. May take a while to confirm."); - if let Err(e) = client.send_and_confirm_transaction(&create_group_tx).await { - return Err(anyhow::Error::msg(e)); + match client.send_and_confirm_transaction(&create_group_tx).await { + Ok(sig) => { + println!("Successful: https://explorer.solana.com/tx/{sig}") + } + Err(e) => return Err(anyhow::Error::msg(e)), }; println!("Initialized {creator_group}"); diff --git a/cli/src/command/nft/minter/init.rs b/cli/src/command/nft/minter/init.rs index ca59304..f722d38 100644 --- a/cli/src/command/nft/minter/init.rs +++ b/cli/src/command/nft/minter/init.rs @@ -386,9 +386,11 @@ pub(super) async fn process( _ => return Err(anyhow::Error::msg("Discarded Request")), } - if let Err(e) = client.send_and_confirm_transaction(&create_minter_tx).await { - println!("{e:#?}"); - return Err(anyhow::Error::msg(e)); + match client.send_and_confirm_transaction(&create_minter_tx).await { + Ok(sig) => { + println!("Successful: https://explorer.solana.com/tx/{sig}") + } + Err(e) => return Err(anyhow::Error::msg(e)), }; println!("Initialized Minter for {collection_name}"); diff --git a/cli/src/command/nft/minter/init_old.rs b/cli/src/command/nft/minter/init_old.rs index a66a1c4..df58c35 100644 --- a/cli/src/command/nft/minter/init_old.rs +++ b/cli/src/command/nft/minter/init_old.rs @@ -644,9 +644,11 @@ pub(super) async fn process( client.get_latest_blockhash().await?, ); - if let Err(e) = client.send_and_confirm_transaction(&create_minter_tx).await { - println!("{e:#?}"); - return Err(anyhow::Error::msg(e)); + match client.send_and_confirm_transaction(&create_minter_tx).await { + Ok(sig) => { + println!("Successful: https://explorer.solana.com/tx/{sig}") + } + Err(e) => return Err(anyhow::Error::msg(e)), }; println!("Initialized Minter for {collection_name}"); diff --git a/cli/src/command/nft/minter/mint.rs b/cli/src/command/nft/minter/mint.rs index be96bb0..73283fc 100644 --- a/cli/src/command/nft/minter/mint.rs +++ b/cli/src/command/nft/minter/mint.rs @@ -80,9 +80,14 @@ pub(super) async fn process( } // Sign and send - if let Err(e) = client.send_and_confirm_transaction(&mint_tx) { - return Err(anyhow::Error::msg(e)); + match client.send_and_confirm_transaction(&mint_tx) { + Ok(sig) => { + println!("Successful: https://explorer.solana.com/tx/{sig}") + } + Err(e) => return Err(anyhow::Error::msg(e)), }; + println!("minted"); + Ok(()) } From 4b64dd7f6096a2fbea91f6fa50f37281fbbdf1ea Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Fri, 23 Jun 2023 12:31:27 -0700 Subject: [PATCH 13/17] add compute budget expansion --- cli/src/command/nft/minter/mint.rs | 60 ++++++++++++++++++------------ 1 file changed, 37 insertions(+), 23 deletions(-) diff --git a/cli/src/command/nft/minter/mint.rs b/cli/src/command/nft/minter/mint.rs index 73283fc..8705a65 100644 --- a/cli/src/command/nft/minter/mint.rs +++ b/cli/src/command/nft/minter/mint.rs @@ -14,6 +14,11 @@ use solana_sdk::instruction::Instruction; use solana_sdk::transaction::Transaction; use solana_sdk::{system_program, sysvar}; +const COMPUTE: Pubkey = Pubkey::new_from_array([ + 3, 6, 70, 111, 229, 33, 23, 50, 255, 236, 173, 186, 114, 195, 155, 231, 188, 140, 229, 187, + 197, 247, 18, 107, 44, 67, 155, 58, 64, 0, 0, 0, +]); + pub(super) async fn process( signer: &impl Signer, shadowy_super_minter: Pubkey, @@ -34,29 +39,38 @@ pub(super) async fn process( // Build mint tx let mint_keypair = Keypair::new(); // never used after this let mint_tx = Transaction::new_signed_with_payer( - &[Instruction::new_with_bytes( - shadowy_super_minter::ID, - MintInstruction {}.data().as_ref(), - MintAccounts { - shadowy_super_minter, - minter: signer.pubkey(), - minter_ata: spl_associated_token_account::get_associated_token_address( - &signer.pubkey(), - &mint_keypair.pubkey(), - ), - payer_pda: get_payer_pda(&mint_keypair.pubkey()), - mint: mint_keypair.pubkey(), - collection, - metadata: Metadata::derive_pda(&mint_keypair.pubkey()), - creator_group, - shadow_nft_standard: shadow_nft_standard::ID, - token_program: token_2022::Token2022::id(), - associated_token_program: spl_associated_token_account::ID, - system_program: system_program::ID, - recent_slothashes: sysvar::slot_hashes::ID, - } - .to_account_metas(None), - )], + &[ + Instruction::new_with_borsh::<[u8; 5]>( + COMPUTE, + &[0x02, 0x00, 0x06, 0x1A, 0x80], + vec![], + ), + Instruction::new_with_bytes( + shadowy_super_minter::ID, + MintInstruction {}.data().as_ref(), + MintAccounts { + shadowy_super_minter, + minter: signer.pubkey(), + minter_ata: + spl_associated_token_account::get_associated_token_address_with_program_id( + &signer.pubkey(), + &mint_keypair.pubkey(), + &token_2022::Token2022::id(), + ), + payer_pda: get_payer_pda(&mint_keypair.pubkey()), + mint: mint_keypair.pubkey(), + collection, + metadata: Metadata::derive_pda(&mint_keypair.pubkey()), + creator_group, + shadow_nft_standard: shadow_nft_standard::ID, + token_program: token_2022::Token2022::id(), + associated_token_program: spl_associated_token_account::ID, + system_program: system_program::ID, + recent_slothashes: sysvar::slot_hashes::ID, + } + .to_account_metas(None), + ), + ], Some(&signer.pubkey()), &[signer as &dyn Signer, &mint_keypair as &dyn Signer], client.get_latest_blockhash()?, From ca4852b128baa921c642f79ddf23d2154c413a03 Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Fri, 23 Jun 2023 12:32:11 -0700 Subject: [PATCH 14/17] update to pretty printed verbose errors --- cli/src/command/nft/collection/init.rs | 2 +- cli/src/command/nft/collection/withdraw.rs | 2 +- cli/src/command/nft/creator_group/init.rs | 2 +- cli/src/command/nft/minter/init.rs | 2 +- cli/src/command/nft/minter/init_old.rs | 2 +- cli/src/command/nft/minter/mint.rs | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/cli/src/command/nft/collection/init.rs b/cli/src/command/nft/collection/init.rs index cee93e9..83d78ad 100644 --- a/cli/src/command/nft/collection/init.rs +++ b/cli/src/command/nft/collection/init.rs @@ -98,7 +98,7 @@ pub(super) async fn process(signer: &impl Signer, rpc_url: &str) -> anyhow::Resu Ok(sig) => { println!("Successful: https://explorer.solana.com/tx/{sig}") } - Err(e) => return Err(anyhow::Error::msg(e)), + Err(e) => return Err(anyhow::Error::msg(format!("{e:#?}"))), }; println!(""); diff --git a/cli/src/command/nft/collection/withdraw.rs b/cli/src/command/nft/collection/withdraw.rs index 8bb514b..4c04a4e 100644 --- a/cli/src/command/nft/collection/withdraw.rs +++ b/cli/src/command/nft/collection/withdraw.rs @@ -73,7 +73,7 @@ pub(crate) async fn process( Ok(sig) => { println!("Successful: https://explorer.solana.com/tx/{sig}") } - Err(e) => return Err(anyhow::Error::msg(e)), + Err(e) => return Err(anyhow::Error::msg(format!("{e:#?}"))), }; Ok(()) diff --git a/cli/src/command/nft/creator_group/init.rs b/cli/src/command/nft/creator_group/init.rs index 20b6d38..a33cd89 100644 --- a/cli/src/command/nft/creator_group/init.rs +++ b/cli/src/command/nft/creator_group/init.rs @@ -154,7 +154,7 @@ pub(crate) async fn process( Ok(sig) => { println!("Successful: https://explorer.solana.com/tx/{sig}") } - Err(e) => return Err(anyhow::Error::msg(e)), + Err(e) => return Err(anyhow::Error::msg(format!("{e:#?}"))), }; println!("Initialized {creator_group}"); diff --git a/cli/src/command/nft/minter/init.rs b/cli/src/command/nft/minter/init.rs index f722d38..0c493c6 100644 --- a/cli/src/command/nft/minter/init.rs +++ b/cli/src/command/nft/minter/init.rs @@ -390,7 +390,7 @@ pub(super) async fn process( Ok(sig) => { println!("Successful: https://explorer.solana.com/tx/{sig}") } - Err(e) => return Err(anyhow::Error::msg(e)), + Err(e) => return Err(anyhow::Error::msg(format!("{e:#?}"))), }; println!("Initialized Minter for {collection_name}"); diff --git a/cli/src/command/nft/minter/init_old.rs b/cli/src/command/nft/minter/init_old.rs index df58c35..5a15de1 100644 --- a/cli/src/command/nft/minter/init_old.rs +++ b/cli/src/command/nft/minter/init_old.rs @@ -648,7 +648,7 @@ pub(super) async fn process( Ok(sig) => { println!("Successful: https://explorer.solana.com/tx/{sig}") } - Err(e) => return Err(anyhow::Error::msg(e)), + Err(e) => return Err(anyhow::Error::msg(format!("{e:#?}"))), }; println!("Initialized Minter for {collection_name}"); diff --git a/cli/src/command/nft/minter/mint.rs b/cli/src/command/nft/minter/mint.rs index 8705a65..19022fd 100644 --- a/cli/src/command/nft/minter/mint.rs +++ b/cli/src/command/nft/minter/mint.rs @@ -98,7 +98,7 @@ pub(super) async fn process( Ok(sig) => { println!("Successful: https://explorer.solana.com/tx/{sig}") } - Err(e) => return Err(anyhow::Error::msg(e)), + Err(e) => return Err(anyhow::Error::msg(format!("{e:#?}"))), }; println!("minted"); From 78c31404510cd22f9f232c36fb2af68731001340 Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Fri, 23 Jun 2023 13:13:48 -0700 Subject: [PATCH 15/17] update cli name --- Cargo.toml | 6 +++++- cli/Cargo.toml | 14 +++++++------- cli/src/main.rs | 2 +- py/Cargo.toml | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 046fdca..0aa6024 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ members = [ ] [workspace.package] -version = "0.7.1" +version = "0.8.0" authors = [ "Cavey Cool ", "Tracy " @@ -18,3 +18,7 @@ authors = [ edition = "2021" license = "MIT OR Apache-2.0" repository = "https://github.com/GenesysGo/shadow-drive-rust" + +[workspace.dependencies] +shadow-drive-sdk = { path = "sdk", version = "0.8.0"} +shadow-rpc-auth = { path = "auth", version = "0.8.0"} \ No newline at end of file diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 3aded02..c13f305 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,6 +1,6 @@ [package] -name = "shadow-drive-cli" -description = "The Rust CLI for GenesysGo's Shadow Drive" +name = "shdw" +description = "The Rust CLI for GenesysGo's Shadow Drive, NFT Standard Program, and Minter Program" version = { workspace = true } authors = { workspace = true } edition = { workspace = true } @@ -10,11 +10,11 @@ repository = { workspace = true } # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -shadow-drive-sdk = { path = "../sdk", version = "0.7.1" } -shadow-rpc-auth = { path = "../auth", version = "0.7.1" } -shadow-nft-common = { git = "https://github.com/genesysgo/shadow-nft-standard", branch = "main"} -shadow-nft-standard = { git = "https://github.com/genesysgo/shadow-nft-standard", branch = "main"} -shadowy-super-minter = { git = "https://github.com/genesysgo/shadow-nft-standard", branch = "main"} +shadow-drive-sdk = { workspace = true } +shadow-rpc-auth = { workspace = true } +shadow-nft-common = "0.1.1" +shadow-nft-standard = "0.1.1" +shadowy-super-minter = "0.1.1" tokio = { version = "^1", features = ["full"] } anyhow = "1.0.65" byte-unit = "4.0.14" diff --git a/cli/src/main.rs b/cli/src/main.rs index c746873..f6f0707 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,7 +1,7 @@ use anyhow::anyhow; use clap::{IntoApp, Parser}; -use shadow_drive_cli::Opts; use shadow_rpc_auth::{authenticate, parse_account_id_from_url}; +use shdw::Opts; use solana_clap_v3_utils::keypair::keypair_from_path; pub const GENESYSGO_AUTH_KEYWORD: &str = "genesysgo"; diff --git a/py/Cargo.toml b/py/Cargo.toml index 0b5b44c..04809f1 100644 --- a/py/Cargo.toml +++ b/py/Cargo.toml @@ -17,6 +17,6 @@ concat-arrays = "0.1.2" ed25519-dalek = "1.0.1" pyo3 = { version = "0.17.3", features = ["extension-module"] } reqwest = "0.11.14" -shadow-drive-sdk = { path = "../sdk/", version = "0.7.1" } +shadow-drive-sdk = { workspace = true } tokio = { version = "1.14.1", features = ["full"] } tokio-scoped = "0.2.0" From 12e879902616ecf7f178c6c7cffc48204ace5b73 Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Fri, 23 Jun 2023 13:27:17 -0700 Subject: [PATCH 16/17] revert name and remove extra parenthesis --- cli/Cargo.toml | 10 ++++++++-- cli/src/command/nft/minter/mint.rs | 2 +- cli/src/main.rs | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/cli/Cargo.toml b/cli/Cargo.toml index c13f305..7b76d1d 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "shdw" +name = "shadow-drive-cli" description = "The Rust CLI for GenesysGo's Shadow Drive, NFT Standard Program, and Minter Program" version = { workspace = true } authors = { workspace = true } @@ -7,11 +7,17 @@ edition = { workspace = true } license = { workspace = true } repository = { workspace = true } +[[bin]] +name = "shdw" +path = "src/main.rs" +test = false +bench = false + # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] shadow-drive-sdk = { workspace = true } -shadow-rpc-auth = { workspace = true } +shadow-rpc-auth = { version = "0.7.1" } shadow-nft-common = "0.1.1" shadow-nft-standard = "0.1.1" shadowy-super-minter = "0.1.1" diff --git a/cli/src/command/nft/minter/mint.rs b/cli/src/command/nft/minter/mint.rs index 19022fd..0b5813e 100644 --- a/cli/src/command/nft/minter/mint.rs +++ b/cli/src/command/nft/minter/mint.rs @@ -77,7 +77,7 @@ pub(super) async fn process( ); // Confirm with user - println!("Minting an NFT from:)"); + println!("Minting an NFT from:"); println!(" minter {shadowy_super_minter}"); #[rustfmt::skip] println!(" collection {} ({collection})", &onchain_collection.name); diff --git a/cli/src/main.rs b/cli/src/main.rs index f6f0707..c746873 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,7 +1,7 @@ use anyhow::anyhow; use clap::{IntoApp, Parser}; +use shadow_drive_cli::Opts; use shadow_rpc_auth::{authenticate, parse_account_id_from_url}; -use shdw::Opts; use solana_clap_v3_utils::keypair::keypair_from_path; pub const GENESYSGO_AUTH_KEYWORD: &str = "genesysgo"; From 2226a7d501fe2d226c257475c82a5406612e0593 Mon Sep 17 00:00:00 2001 From: cavemanloverboy Date: Fri, 23 Jun 2023 13:29:39 -0700 Subject: [PATCH 17/17] add subcommand descriptions --- cli/src/command/nft/collection/mod.rs | 5 +++++ cli/src/command/nft/creator_group/mod.rs | 3 +++ 2 files changed, 8 insertions(+) diff --git a/cli/src/command/nft/collection/mod.rs b/cli/src/command/nft/collection/mod.rs index 35f928b..73919d1 100644 --- a/cli/src/command/nft/collection/mod.rs +++ b/cli/src/command/nft/collection/mod.rs @@ -7,8 +7,13 @@ mod withdraw; #[derive(Debug, Parser)] pub enum CollectionCommand { + /// Initialize a collection Init, + + /// Retrieve and print an onchain Collection account Get { collection: Pubkey }, + + /// Withdraw mint fees from an onchain Collection account Withdraw { collection: Pubkey }, } impl CollectionCommand { diff --git a/cli/src/command/nft/creator_group/mod.rs b/cli/src/command/nft/creator_group/mod.rs index c218155..85eef78 100644 --- a/cli/src/command/nft/creator_group/mod.rs +++ b/cli/src/command/nft/creator_group/mod.rs @@ -6,7 +6,10 @@ pub(crate) mod init; #[derive(Debug, Parser)] pub enum CreatorGroupCommand { + /// Initialize a creator group Init, + + /// Retrieve and print an onchain CreatorGroup account Get { creator_group: Pubkey }, }