1use std::cmp::min;
2use reqwest::header::{HeaderMap};
4use reqwest::{self, Client};
5use std::error::Error;
6use std::collections::HashMap;
7use std::fmt;
8use std::fmt::format;
9
10use serde::Deserialize;
11use serde::Serialize;
12use serde_json::value::Value;
13use serde_json::{json, Map, Number};
14use crate::{ActionType, get_basic_url, get_env_name, get_request_header, get_sso_basic_url, NetEnv};
15
16pub struct Marketplace {
17 api_key: String,
18 net: NetEnv,
19 token: String,
20 secret_key: String
21}
22
23#[derive(Debug, Serialize, Deserialize)]
24pub struct Response<T> {
25 status: Option<String>,
26 data: Option<T>,
27 code: u32,
28 message: Option<String>,
29}
30
31#[derive(Debug, Serialize, Deserialize)]
32pub struct CreateCollectionData {
33 pub mint_address: String,
34 pub url: String,
35 pub update_authority: String,
36 pub creator_address: String,
37 pub name: String,
38 pub symbol: String,
39 pub collection: Option<String>,
40 pub signature: String,
41 pub status: Option<String>,
42}
43#[derive(Debug, Serialize, Deserialize)]
44pub struct MintNftPayload {
45 pub name: String,
46 pub symbol: String,
47 pub url: String,
48 pub collection_mint: String,
49}
50
51#[derive(Debug, Serialize, Deserialize)]
52pub struct SolanaMintNftResult {
53 pub mint_address: String,
54 pub url: String,
55 pub update_authority: String,
56 pub creator_address: String,
57 pub name: String,
58 pub symbol: String,
59 pub collection: String,
60 pub signature: String,
61 pub status: String,
62}
63
64pub enum SolanaCommitment {
65 confirmed,
66 finalized,
67}
68
69impl fmt::Display for SolanaCommitment {
70 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71 match self {
72 SolanaCommitment::confirmed => write!(f, "confirmed"),
73 SolanaCommitment::finalized => write!(f, "finalized"),
74 }
75 }
76}
77
78#[derive(Debug, Serialize, Deserialize)]
79pub struct UpdateNftPayload {
80 pub mint_address: String,
81 pub name: String,
82 pub update_authority: String,
83 pub symbol: String,
84 pub url: String,
85 pub seller_fee_basis_points: usize,
86 pub confirmation: String,
87}
88
89#[derive(Debug, Serialize, Deserialize)]
90pub struct NftDetail {
91 pub id: usize,
92 #[serde(rename = "type")]
93 pub type_name: String,
94 pub wallet_address: String,
95 pub mint_address: String,
96 pub price: String,
97 pub seller_address: String,
98 pub to_wallet_address: Option<String>,
99 pub signature: String,
100 pub status: String,
101 #[serde(rename = "updatedAt")]
102 pub updated_at: String,
103 #[serde(rename = "createdAt")]
104 pub created_at: String,
105}
106
107#[derive(Debug, Serialize, Deserialize)]
108pub struct SolanaNFTMintResult {
109 pub mint_address: String,
110 pub url: String,
111 pub update_authority: String,
112 pub creator_address: String,
113 pub name: String,
114 pub symbol: String,
115 pub collection: String,
116 pub signature: String,
117 pub status: String,
118}
119
120#[derive(Debug, Serialize, Deserialize)]
121pub struct SolanaUpdateNftResult {
122 pub mint_address: String,
123 pub url: String,
124 pub update_authority: String,
125 pub creator_address: String,
126 pub name: String,
127 pub symbol: String,
128 pub seller_fee_basis_points: usize,
129 pub signature: String,
130 pub status: String,
131}
132
133#[derive(Debug, Serialize, Deserialize)]
134pub struct Creator {
135 pub address: String,
136 pub verified: bool,
137 pub share: f32,
138}
139
140#[derive(Debug, Serialize, Deserialize)]
141pub struct Owner {
142 pub address: String,
143}
144
145fn string_or_number<'de, D>(de: D) -> Result<Value, D::Error>
154where
155 D: serde::Deserializer<'de>,
156{
157 let helper: Value = Deserialize::deserialize(de)?;
158
159 match helper {
160 Value::Number(n) => {
161 println!("{:#?}", n.as_f64().unwrap().to_string());
162 Ok(Value::Number(n))
163 }
164 Value::String(s) => Ok(json!(s)),
165 _ => Ok(json!(null)),
166 }}
167
168#[derive(Debug, Serialize, Deserialize)]
169pub struct MetadataAttribute {
170 pub trait_type: String,
171 #[serde(deserialize_with = "string_or_number")]
172 pub value: Value,
173}
174
175#[derive(Debug, Serialize, Deserialize)]
176pub struct SolanaNFTListing {
177 pub id: usize,
178 #[serde(rename = "tradeState")]
179 pub trade_state: String,
180 pub seller: String,
181 pub metadata: String,
182 #[serde(rename = "purchaseId")]
183 pub purchase_id: Option<String>,
184 pub price: f32,
185 #[serde(rename = "tokenSize")]
186 pub token_size: usize,
187 #[serde(rename = "createdAt")]
188 pub created_at: String,
189 #[serde(rename = "canceledAt")]
190 pub canceled_at: Option<String>,
191}
192
193#[derive(Debug, Serialize, Deserialize)]
194pub struct SolanaNFTs {
195 pub nfts: Vec<SolanaNFTExtended>
196}
197
198#[derive(Debug, Serialize, Deserialize)]
199pub struct SolanaNFTExtended {
200 pub name: String,
201 #[serde(rename = "sellerFeeBasisPoints")]
202 pub seller_fee_basic_points: usize,
203 #[serde(rename = "updateAuthorityAddress")]
204 pub update_authority_address: String,
205 pub description: Option<String>,
206 pub image: Option<String>,
207 #[serde(rename = "externalUrl")]
208 pub external_url: Option<String>,
209 pub creators: Vec<Creator>,
210 pub owner: Option<Owner>,
211 pub attributes: Option<Vec<MetadataAttribute>>,
212 pub listings: Option<Vec<SolanaNFTListing>>,
213}
214
215#[derive(Debug, Serialize, Deserialize)]
216pub struct SolanaNFTAuctionActivity {
217 pub id: usize,
218 #[serde(rename = "mintAddress")]
219 pub mint_address: String,
220 #[serde(rename = "txSignature")]
221 pub tx_signature: String,
222 pub amount: f32,
223 #[serde(rename = "receiptType")]
224 pub receipt_type: String,
225 #[serde(rename = "tokenPrice")]
226 pub token_price: String,
227 #[serde(rename = "blockTimeCreated")]
228 pub block_time_created: String,
229 #[serde(rename = "blockTimeCanceled")]
230 pub block_time_canceled: Option<String>,
231 #[serde(rename = "tradeState")]
232 pub trade_state: String,
233 #[serde(rename = "auctionHouseAddress")]
234 pub auction_house_address: String,
235 #[serde(rename = "sellerAddress")]
236 pub seller_address: String,
237 #[serde(rename = "buyerAddress")]
238 pub buyer_address: Option<String>,
239 pub metadata: String,
240 #[serde(rename = "blockTime")]
241 pub block_time: String,
242}
243
244#[derive(Debug, Serialize, Deserialize)]
245pub struct SolanaNFTTransfersEntity {
246 pub id: usize,
247 #[serde(rename = "mintAddress")]
248 pub mint_address: String,
249 #[serde(rename = "txSignature")]
250 pub tx_signature: String,
251 #[serde(rename = "fromWalletAddress")]
252 pub from_wallet_address: Option<String>,
253 #[serde(rename = "toWalletAddress")]
254 pub to_wallet_address: String,
255 pub amount: f32,
256 #[serde(rename = "blockTime")]
257 pub block_time: String,
258 pub slot: usize,
259}
260
261#[derive(Debug, Serialize, Deserialize)]
262pub struct SolanaNFTAuctionActivities {
263 #[serde(rename = "mintAddress")]
264 pub mint_address: String,
265
266 #[serde(rename = "auctionActivities")]
267 pub auction_activities: Vec<SolanaNFTAuctionActivity>,
268
269 #[serde(rename = "tokenTransfers")]
270 pub token_transfers: Vec<SolanaNFTTransfersEntity>,
271}
272
273#[derive(Debug, Serialize, Deserialize)]
274pub struct ActionToken {
275 pub authorization_token: String
277}
278
279pub async fn approve_token(action_type: ActionType, headers: HeaderMap, env: NetEnv, payload_data: Map<String, Value>) -> Result<Option<ActionToken>, Box<dyn Error>> {
280 let client = Client::new();
281
282 let url = get_sso_basic_url(env) + "/v1/auth/actions/developer/request";
283 let mut data = Map::new();
284 data.insert("type".to_string(), Value::String(action_type.to_string()));
285 data.insert("data".to_string(), Value::Object(payload_data));
286 let response = client
287 .post(url)
288 .headers(headers)
289 .json(&data)
290 .send()
291 .await?;
292
293 let response_date = response.json::<Response<ActionToken>>().await?;
294 Ok(response_date.data)
295}
296
297
298impl Marketplace {
299 pub fn new(api_key: String, env: NetEnv, token: String, accessSecretKey: String) -> Marketplace {
300 Marketplace { api_key, net: env, token, secret_key: accessSecretKey }
301 }
302
303 pub async fn create_collection(&self, name: String, symbol: String, metadata_uri: String) -> Result<Option<CreateCollectionData>, Box<dyn Error>> {
305 let mut headers = get_request_header(self.api_key.to_string(), self.token.to_string());
306 let mut approve_headers = get_request_header(self.api_key.to_string(), self.secret_key.to_string());
307
308 let client = Client::new();
309 let url_ = format!("/v1/{}/solana/mint/collection", get_env_name(self.net));
310 let url = get_basic_url(self.net) + &url_;
311
312 let mut data = Map::new();
313 data.insert("name".to_string(), Value::String(name));
314 data.insert("symbol".to_string(), Value::String(symbol));
315 data.insert("url".to_string(), Value::String(metadata_uri));
316
317 let action_token = approve_token(
318 ActionType::CREATE_COLLECTION,
319 approve_headers.clone(),
320 self.net,
321 data.clone()
322 ).await.unwrap();
323 if action_token.is_none() {
324 panic!("action is none")
325 }
326 let x_token = action_token.unwrap().authorization_token.to_string();
327 headers.insert("x-authorization-token", x_token.parse().unwrap());
328
329 let response = client
330 .post(url)
331 .headers(headers)
332 .json(&data)
333 .send()
334 .await?;
335
336 let response_data = response.json::<Response<CreateCollectionData>>().await?;
337 println!("{:#?}", response_data);
338 Ok(response_data.data)
339 }
340
341 pub async fn mint_nft(&self, payload: MintNftPayload) -> Result<Option<SolanaMintNftResult>, Box<dyn Error>> {
343 let mut headers = get_request_header(self.api_key.to_string(), self.token.to_string());
344 let mut approve_headers = get_request_header(self.api_key.to_string(), self.secret_key.to_string());
345
346 let client = Client::new();
347 let url_ = format!("/v1/{}/solana/mint/nft", get_env_name(self.net));
348 let url = get_basic_url(self.net) + &url_;
349
350 let data = self.general_payload_to_map(payload);
351 let action_token = approve_token(
352 ActionType::MINT_NFT,
353 approve_headers.clone(),
354 self.net,
355 data.clone()
356 ).await.unwrap();
357 if action_token.is_none() {
358 panic!("action is none")
359 }
360 let x_token = action_token.unwrap().authorization_token.to_string();
361 headers.insert("x-authorization-token", x_token.parse().unwrap());
362
363 let response = client.post(url).headers(headers).json(&data).send().await?;
364 let response_data = response.json::<Response<SolanaMintNftResult>>().await?;
365
366 Ok(response_data.data)
367 }
368
369 pub async fn update_nft(&self, payload: UpdateNftPayload) -> Result<Option<SolanaUpdateNftResult>, Box<dyn Error>> {
370 let mut headers = get_request_header(self.api_key.to_string(), self.token.to_string());
371 let mut approve_headers = get_request_header(self.api_key.to_string(), self.secret_key.to_string());
372
373 let client = Client::new();
374 let url_ = format!("/v1/{}/solana/mint/update", get_env_name(self.net));
375 let url = get_basic_url(self.net) + &url_;
376
377 let data = self.update_nft_payload_to_map(payload);
378 let action_token = approve_token(
379 ActionType::UPDATE_NFT,
380 approve_headers.clone(),
381 self.net,
382 data.clone()
383 ).await.unwrap();
384 if action_token.is_none() {
385 panic!("action is none")
386 }
387 let x_token = action_token.unwrap().authorization_token.to_string();
388 headers.insert("x-authorization-token", x_token.parse().unwrap());
389
390 let response = client.post(url).headers(headers).json(&data).send().await?;
391
392 let response_data = response.json::<Response<SolanaUpdateNftResult>>().await?;
393 println!("{:?}", response_data);
394 Ok(response_data.data)
395
396 }
397
398 pub async fn list_nft(&self, mint_address: String, price: f64, auction_house: String) -> Result<Option<NftDetail>, Box<dyn Error>> {
400 let mut headers = get_request_header(self.api_key.to_string(), self.token.to_string());
401 let mut approve_headers = get_request_header(self.api_key.to_string(), self.secret_key.to_string());
402
403 let client = Client::new();
404 let url_ = format!("/v1/{}/solana/marketplace/list", get_env_name(self.net));
405 let url = get_basic_url(self.net) + &url_;
406
407 let mut data = Map::new();
408 data.insert("mint_address".to_string(), Value::String(mint_address));
409 data.insert("price".to_string(), Value::from(price));
410 if !auction_house.is_empty() {
411 data.insert("auction_house".to_string(), Value::String(auction_house));
412 }
413
414 let action_token = approve_token(
415 ActionType::LIST_NFT,
416 approve_headers.clone(),
417 self.net,
418 data.clone()
419 ).await.unwrap();
420 let x_token = action_token.unwrap().authorization_token.to_string();
421 headers.insert("x-authorization-token", x_token.parse().unwrap());
422
423 let response = client.post(url).headers(headers).json(&data).send().await?;
424
425 let response_data = response.json::<Response<NftDetail>>().await?;
426 println!("{:?}", response_data);
427 Ok(response_data.data)
428 }
429
430 pub async fn buy_nft(&self, mint_address: String, price: f64, auction_house: String) -> Result<Option<NftDetail>, Box<dyn Error>> {
432 let mut headers = get_request_header(self.api_key.to_string(), self.token.to_string());
433 let mut approve_headers = get_request_header(self.api_key.to_string(), self.secret_key.to_string());
434
435 let client = Client::new();
436 let url_ = format!("/v1/{}/solana/marketplace/buy", get_env_name(self.net));
437 let url = get_basic_url(self.net) + &url_;
438
439 let mut data = Map::new();
440 data.insert("mint_address".to_string(), Value::String(mint_address));
441 data.insert("price".to_string(), Value::from(price));
442 if !auction_house.is_empty() {
443 data.insert("auction_house".to_string(), Value::String(auction_house));
444 }
445
446 let action_token = approve_token(
447 ActionType::BUY_NFT,
448 approve_headers.clone(),
449 self.net,
450 data.clone()
451 ).await.unwrap();
452 let x_token = action_token.unwrap().authorization_token.to_string();
453 headers.insert("x-authorization-token", x_token.parse().unwrap());
454
455 let response = client.post(url).headers(headers).json(&data).send().await?;
456
457 let response_data = response.json::<Response<NftDetail>>().await?;
458 println!("{:?}", response_data);
459 Ok(response_data.data)
460 }
461
462 pub async fn update_nft_listing(&self, mint_address: String, price: f64, auction_house: String) -> Result<Option<NftDetail>, Box<dyn Error>> {
464 let mut headers = get_request_header(self.api_key.to_string(), self.token.to_string());
465 let mut approve_headers = get_request_header(self.api_key.to_string(), self.secret_key.to_string());
466
467 let client = Client::new();
468 let url_ = format!("/v1/{}/solana/marketplace/update", get_env_name(self.net));
469 let url = get_basic_url(self.net) + &url_;
470
471 let mut data = Map::new();
472 data.insert("mint_address".to_string(), Value::String(mint_address));
473 data.insert("price".to_string(), Value::from(price));
474 if !auction_house.is_empty() {
475 data.insert("auction_house".to_string(), Value::String(auction_house));
476 }
477
478 let action_token = approve_token(
479 ActionType::UPDATE_LISTING,
480 approve_headers.clone(),
481 self.net,
482 data.clone()
483 ).await.unwrap();
484 let x_token = action_token.unwrap().authorization_token.to_string();
485 headers.insert("x-authorization-token", x_token.parse().unwrap());
486 println!("headres: {:?}", headers.clone());
487
488 let response = client.post(url).headers(headers).json(&data).send().await?;
489
490 let response_data = response.json::<Response<NftDetail>>().await?;
491 Ok(response_data.data)
492 }
493
494 pub async fn cancel_nft_listing(&self, mint_address: String, price: f64, auction_house: String) -> Result<Option<NftDetail>, Box<dyn Error>> {
496 let mut headers = get_request_header(self.api_key.to_string(), self.token.to_string());
497 let mut approve_headers = get_request_header(self.api_key.to_string(), self.secret_key.to_string());
498
499 let client = Client::new();
500 let url_ = format!("/v1/{}/solana/marketplace/cancel", get_env_name(self.net));
501 let url = get_basic_url(self.net) + &url_;
502
503 let mut data = Map::new();
504 data.insert("mint_address".to_string(), Value::String(mint_address));
505 data.insert("price".to_string(), Value::from(price));
506 if !auction_house.is_empty() {
507 data.insert("auction_house".to_string(), Value::String(auction_house));
508 }
509
510 let action_token = approve_token(
511 ActionType::CANCEL_LISTING,
512 approve_headers.clone(),
513 self.net,
514 data.clone()
515 ).await.unwrap();
516 let x_token = action_token.unwrap().authorization_token.to_string();
517 headers.insert("x-authorization-token", x_token.parse().unwrap());
518
519 let response = client.post(url).headers(headers).json(&data).send().await?;
520
521 let response_data = response.json::<Response<NftDetail>>().await?;
522 println!("{:?}", response_data);
523 Ok(response_data.data)
524 }
525
526 pub async fn transfer_nft(&self, mint_address: String, to_wallet_address: String) -> Result<Option<NftDetail>, Box<dyn Error>> {
528 let mut headers:HeaderMap = get_request_header(self.api_key.to_string(), self.token.to_string());
529 let mut approve_headers = get_request_header(self.api_key.to_string(), self.secret_key.to_string());
530
531 let client = Client::new();
532 let url_ = format!("/v1/{}/solana/marketplace/transfer", get_env_name(self.net));
533 let url = get_basic_url(self.net) + &url_;
534
535 let mut data = Map::new();
536 data.insert("mint_address".to_string(), Value::String(mint_address));
537 data.insert("to_wallet_address".to_string(), Value::String(to_wallet_address));
538 let action_token = approve_token(
539 ActionType::TRANSFER_NFT,
540 approve_headers.clone(),
541 self.net,
542 data.clone()
543 ).await.unwrap();
544 let x_token = action_token.unwrap().authorization_token.to_string();
545 headers.insert("x-authorization-token", x_token.parse().unwrap());
546
547 let response = client.post(url).headers(headers).json(&data).send().await?;
548
549 let response_data = response.json::<Response<NftDetail>>().await?;
550 println!("response: {:?}", response_data);
551 Ok(response_data.data)
552 }
553
554 pub async fn fetch_nfts_by_mint_address(&self, mint_address: Vec<String>, limit: usize, offset: usize) -> Result<Option<SolanaNFTs>, Box<dyn Error>> {
556 let headers:HeaderMap = get_request_header(self.api_key.to_string(), self.token.to_string());
557
558 let client = Client::new();
559 let url_ = format!("/v1/{}/solana/nft/mints", get_env_name(self.net));
560 let url = get_basic_url(self.net) + &url_;
561
562 let response = client.post(url).headers(headers).json(&serde_json::json!({
563 "mint_addresses": mint_address,
564 "limit": limit,
565 "offset": offset,
566 })).send().await?;
567
568 let response_data = response.json::<Response<SolanaNFTs>>().await?;
569 println!("{:#?}", response_data);
570 Ok(response_data.data)
571 }
572
573 pub async fn fetch_nfts_by_creator_address(&self, creators: Vec<String>, limit: usize, offset: usize) -> Result<Option<SolanaNFTs>, Box<dyn Error>> {
575 let headers:HeaderMap = get_request_header(self.api_key.to_string(), self.token.to_string());
576
577 let client = Client::new();
578 let url_ = format!("/v1/{}/solana/nft/creators", get_env_name(self.net));
579 let url = get_basic_url(self.net) + &url_;
580
581 let response = client.post(url).headers(headers).json(&serde_json::json!({
582 "creators": creators,
583 "limit": limit,
584 "offset": offset,
585 })).send().await?;
586
587 let response_data = response.json::<Response<SolanaNFTs>>().await?;
588 println!("{:#?}", response_data);
589 Ok(response_data.data)
590 }
591
592 pub async fn fetch_nfts_by_update_authorities(&self, update_authorities: Vec<String>, limit: usize, offset: usize) -> Result<Option<SolanaNFTs>, Box<dyn Error>> {
594 let headers:HeaderMap = get_request_header(self.api_key.to_string(), self.token.to_string());
595
596 let client = Client::new();
597 let url_ = format!("/v1/{}/solana/nft/udpate-authorities", get_env_name(self.net));
598 let url = get_basic_url(self.net) + &url_;
599
600 let response = client.post(url).headers(headers).json(&serde_json::json!({
601 "update_authorities": update_authorities,
602 "limit": limit,
603 "offset": offset,
604 })).send().await?;
605
606 let response_data = response.json::<Response<SolanaNFTs>>().await?;
607 println!("{:#?}", response_data);
608 Ok(response_data.data)
609 }
610
611 pub async fn fetch_nfts_by_owner_addresses(&self, addresses: Vec<String>, limit: usize, offset: usize) -> Result<Option<SolanaNFTs>, Box<dyn Error>> {
613 let headers:HeaderMap = get_request_header(self.api_key.to_string(), self.token.to_string());
614
615 let client = Client::new();
616 let url_ = format!("/v1/{}/solana/nft/owners", get_env_name(self.net));
617 let url = get_basic_url(self.net) + &url_;
618 println!("{}", url);
619
620 let response = client.post(url).headers(headers).json(&serde_json::json!({
621 "owners": addresses,
622 "limit": limit,
623 "offset": offset,
624 })).send().await?;
625
626 let response_data = response.json::<Response<SolanaNFTs>>().await?;
627 println!("{:#?}", response_data.data);
628 Ok(response_data.data)
629 }
630
631 fn general_payload_to_map(&self, payload: MintNftPayload) -> Map<String, Value> {
633 let mut data = Map::new();
634 data.insert("name".to_string(), Value::String(payload.name));
635 data.insert("symbol".to_string(), Value::String(payload.symbol));
636 data.insert("url".to_string(), Value::String(payload.url));
637 data.insert("collection_mint".to_string(), Value::String(payload.collection_mint)); data
639 }
640
641 fn update_nft_payload_to_map(&self, payload: UpdateNftPayload) -> Map<String, Value> {
642 let mut data = Map::new();
643 data.insert("mint_address".to_string(), Value::String(payload.mint_address));
644 data.insert("name".to_string(), Value::String(payload.name));
645 data.insert("update_authority".to_string(), Value::String(payload.update_authority));
646 data.insert("symbol".to_string(), Value::String(payload.symbol));
647 data.insert("url".to_string(), Value::String(payload.url));
648 data.insert("seller_fee_basis_points".to_string(), Value::Number(payload.seller_fee_basis_points.into())); data.insert("confirmation".to_string(), Value::String(payload.confirmation));
650 data
651 }
652}
653