sugar_cli/config/
data.rs

1use std::{
2    fmt::{self, Display},
3    str::FromStr,
4};
5
6use anchor_client::solana_sdk::{
7    native_token::LAMPORTS_PER_MLN, pubkey::Pubkey, signature::Keypair,
8};
9pub use anyhow::{anyhow, Result};
10use chrono::prelude::*;
11use serde::{Deserialize, Deserializer, Serialize, Serializer};
12
13use super::CandyGuardData;
14use crate::config::errors::*;
15
16pub struct SugarConfig {
17    pub keypair: Keypair,
18    pub rpc_url: String,
19}
20
21#[derive(Debug, Deserialize, Serialize)]
22pub struct SolanaConfig {
23    pub json_rpc_url: String,
24    pub keypair_path: String,
25    pub commitment: String,
26}
27
28#[derive(Serialize, Deserialize, Debug, Clone, Default)]
29#[serde(rename_all = "camelCase")]
30pub struct ConfigData {
31    /// Token standard.
32    #[serde(default)]
33    pub token_standard: TokenStandard,
34
35    /// Number of assets available
36    pub number: u64,
37
38    /// Symbol for the asset
39    pub symbol: String,
40
41    /// Secondary sales royalty basis points (0-10000)
42    pub seller_fee_basis_points: u16,
43
44    /// Indicates if the asset is mutable or not (default yes)
45    pub is_mutable: bool,
46
47    /// Indicates whether the index generation is sequential or not
48    pub is_sequential: bool,
49
50    /// List of creators
51    pub creators: Vec<Creator>,
52
53    /// Upload method to use
54    pub upload_method: UploadMethod,
55
56    // Token auth rules account (for pNFTs).
57    #[serde(deserialize_with = "to_option_pubkey")]
58    #[serde(serialize_with = "to_option_string")]
59    pub rule_set: Option<Pubkey>,
60
61    // AWS specific configuration
62    pub aws_config: Option<AwsConfig>,
63
64    // NFT.Storage specific configuration
65    #[serde(serialize_with = "to_option_string")]
66    pub nft_storage_auth_token: Option<String>,
67
68    // Shadow Drive specific configuration
69    #[serde(serialize_with = "to_option_string")]
70    pub shdw_storage_account: Option<String>,
71
72    // Pinata specific configuration
73    pub pinata_config: Option<PinataConfig>,
74
75    /// Hidden setttings
76    pub hidden_settings: Option<HiddenSettings>,
77
78    /// Guards configuration
79    pub guards: Option<CandyGuardData>,
80}
81
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct AwsConfig {
84    pub bucket: String,
85    pub profile: String,
86    pub directory: String,
87    pub domain: Option<String>,
88}
89
90impl AwsConfig {
91    pub fn new(
92        bucket: String,
93        profile: String,
94        directory: String,
95        domain: Option<String>,
96    ) -> AwsConfig {
97        AwsConfig {
98            bucket,
99            profile,
100            directory,
101            domain,
102        }
103    }
104}
105
106#[derive(Debug, Clone, Serialize, Deserialize)]
107#[serde(rename_all = "camelCase")]
108pub struct PinataConfig {
109    pub jwt: String,
110    pub api_gateway: String,
111    pub content_gateway: String,
112    pub parallel_limit: Option<u16>,
113}
114
115impl PinataConfig {
116    pub fn new(jwt: String, api_gateway: String, content_gateway: String) -> PinataConfig {
117        PinataConfig {
118            jwt,
119            api_gateway,
120            content_gateway,
121            parallel_limit: None,
122        }
123    }
124}
125
126pub fn to_string<T, S>(value: &T, serializer: S) -> Result<S::Ok, S::Error>
127where
128    T: Display,
129    S: Serializer,
130{
131    serializer.collect_str(value)
132}
133
134pub fn to_option_string<T, S>(value: &Option<T>, serializer: S) -> Result<S::Ok, S::Error>
135where
136    T: Display,
137    S: Serializer,
138{
139    match value {
140        Some(v) => serializer.collect_str(&v),
141        None => serializer.serialize_none(),
142    }
143}
144
145fn to_option_pubkey<'de, D>(deserializer: D) -> Result<Option<Pubkey>, D::Error>
146where
147    D: Deserializer<'de>,
148{
149    let s: String = match Deserialize::deserialize(deserializer) {
150        Ok(s) => s,
151        Err(_) => return Ok(None),
152    };
153
154    let pubkey = Pubkey::from_str(&s).map_err(serde::de::Error::custom)?;
155    Ok(Some(pubkey))
156}
157
158pub fn parse_string_as_date(go_live_date: &str) -> Result<String> {
159    let date = dateparser::parse_with(
160        go_live_date,
161        &Local,
162        NaiveTime::from_hms_opt(0, 0, 0).ok_or_else(|| anyhow!("Failed to parse go live date"))?,
163    )?;
164
165    Ok(date.to_rfc3339())
166}
167
168pub fn go_live_date_as_timestamp(go_live_date: &Option<String>) -> Result<Option<i64>> {
169    if let Some(go_live_date) = go_live_date {
170        let date = dateparser::parse(go_live_date)?;
171        Ok(Some(date.timestamp()))
172    } else {
173        Ok(None)
174    }
175}
176
177pub fn price_as_lamports(price: f64) -> u64 {
178    (price * LAMPORTS_PER_MLN as f64) as u64
179}
180
181fn to_pubkey<'de, D>(deserializer: D) -> Result<Pubkey, D::Error>
182where
183    D: Deserializer<'de>,
184{
185    let s: String = Deserialize::deserialize(deserializer)?;
186    Pubkey::from_str(&s).map_err(serde::de::Error::custom)
187}
188
189#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
190pub struct HiddenSettings {
191    name: String,
192    uri: String,
193    hash: String,
194}
195
196impl HiddenSettings {
197    pub fn new(name: String, uri: String, hash: String) -> HiddenSettings {
198        HiddenSettings { name, uri, hash }
199    }
200    pub fn to_candy_format(&self) -> mpl_candy_machine_core::HiddenSettings {
201        mpl_candy_machine_core::HiddenSettings {
202            name: self.name.clone(),
203            uri: self.uri.clone(),
204            hash: self.hash.as_bytes().try_into().unwrap_or([0; 32]),
205        }
206    }
207    pub fn set_hash(&mut self, hash: String) {
208        self.hash = hash;
209    }
210}
211
212#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
213#[serde(rename_all = "snake_case")]
214pub enum UploadMethod {
215    #[default]
216    Bundlr,
217    #[serde(rename = "aws")]
218    AWS,
219    NftStorage,
220    #[serde(rename = "shdw")]
221    SHDW,
222    Pinata,
223}
224
225impl Display for UploadMethod {
226    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
227        write!(f, "{:?}", self)
228    }
229}
230
231#[derive(Debug, Clone, Deserialize, Default, Serialize)]
232pub struct Creator {
233    #[serde(deserialize_with = "to_pubkey")]
234    #[serde(serialize_with = "to_string")]
235    pub address: Pubkey,
236    pub share: u8,
237}
238
239impl Creator {
240    pub fn to_candy_format(&self) -> Result<mpl_candy_machine_core::Creator> {
241        let creator = mpl_candy_machine_core::Creator {
242            address: self.address,
243            percentage_share: self.share,
244            verified: false,
245        };
246
247        Ok(creator)
248    }
249}
250
251#[derive(Debug, Clone, Serialize)]
252pub enum Cluster {
253    Devnet,
254    Mainnet,
255    Localnet,
256    Unknown,
257}
258
259impl FromStr for Cluster {
260    type Err = anyhow::Error;
261
262    fn from_str(s: &str) -> Result<Self> {
263        match s {
264            "devnet" => Ok(Cluster::Devnet),
265            "mainnet" => Ok(Cluster::Mainnet),
266            "localnet" => Ok(Cluster::Localnet),
267            "unknown" => Ok(Cluster::Unknown),
268            _ => Err(ConfigError::InvalidCluster(s.to_string()).into()),
269        }
270    }
271}
272
273impl ToString for Cluster {
274    fn to_string(&self) -> String {
275        match self {
276            Cluster::Devnet => "devnet".to_string(),
277            Cluster::Mainnet => "mainnet".to_string(),
278            Cluster::Localnet => "localnet".to_string(),
279            Cluster::Unknown => "unknown".to_string(),
280        }
281    }
282}
283
284#[derive(Debug, Clone, Copy, Serialize, Deserialize, Default)]
285#[serde(rename_all = "snake_case")]
286pub enum TokenStandard {
287    #[serde(rename = "nft")]
288    #[default]
289    NonFungible,
290    #[serde(rename = "pnft")]
291    ProgrammableNonFungible,
292}
293
294impl Display for TokenStandard {
295    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
296        write!(f, "{:?}", self)
297    }
298}
299
300impl From<TokenStandard> for mpl_token_metadata::state::TokenStandard {
301    fn from(token_standard: TokenStandard) -> Self {
302        match token_standard {
303            TokenStandard::NonFungible => mpl_token_metadata::state::TokenStandard::NonFungible,
304            TokenStandard::ProgrammableNonFungible => {
305                mpl_token_metadata::state::TokenStandard::ProgrammableNonFungible
306            }
307        }
308    }
309}
310
311impl FromStr for TokenStandard {
312    type Err = anyhow::Error;
313
314    fn from_str(s: &str) -> Result<Self> {
315        match s {
316            "nft" => Ok(TokenStandard::NonFungible),
317            "pnft" => Ok(TokenStandard::ProgrammableNonFungible),
318            _ => Err(ConfigError::InvalidTokenStandard(s.to_string()).into()),
319        }
320    }
321}