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 #[serde(default)]
33 pub token_standard: TokenStandard,
34
35 pub number: u64,
37
38 pub symbol: String,
40
41 pub seller_fee_basis_points: u16,
43
44 pub is_mutable: bool,
46
47 pub is_sequential: bool,
49
50 pub creators: Vec<Creator>,
52
53 pub upload_method: UploadMethod,
55
56 #[serde(deserialize_with = "to_option_pubkey")]
58 #[serde(serialize_with = "to_option_string")]
59 pub rule_set: Option<Pubkey>,
60
61 pub aws_config: Option<AwsConfig>,
63
64 #[serde(serialize_with = "to_option_string")]
66 pub nft_storage_auth_token: Option<String>,
67
68 #[serde(serialize_with = "to_option_string")]
70 pub shdw_storage_account: Option<String>,
71
72 pub pinata_config: Option<PinataConfig>,
74
75 pub hidden_settings: Option<HiddenSettings>,
77
78 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}