mirrorworld_sdk_rust/
marketplace.rs

1use std::cmp::min;
2// use std::borrow::Borrow;
3use 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
145// #[derive(Debug, Serialize, Deserialize, PartialEq)]
146// pub enum Values {
147//     #[serde(rename = "value")]
148//     Text(String),
149//     #[serde(rename = "value")]
150//     Number(i32),
151// }
152
153fn 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    // only use authorization_token
276    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    // Create Verified Collection
304     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    // Mint NFT into collection
342    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    // List NFT ion Mirror World Marketplace
399    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    // Purchase NFT on Mirror World Marketplace
431    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    // Update NFT Listing on Mirror World Marketplace
463    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    // Cancel listing NFT on Mirror World Marketplace
495    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    // Transfer NFT from holder's wallet to another address
527    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    // Fetch NFTs By Mint Addresses. Returns a detailed payload of all NFTs whose `mintAddresses`
555    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    // Fetch NFTs By Creator Addresses. Returns a detailed payload of all NFTs whose `creatorAddresses`
574    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    // Fetch NFTs By Update Authorities Addresses. Returns a detailed payload of all NFTs whose `updateAuthorities`
593    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    // Fetch NFTs By Owners Addresses. Returns a detailed payload of all NFTs whose `owners`
612    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    // mint_sub_collection、mint_nft
632    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)); // parent collection address
638        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())); // parent collection address
649        data.insert("confirmation".to_string(), Value::String(payload.confirmation));
650        data
651    }
652}
653