11use cosmwasm_std:: {
2- BankMsg , Coin , CosmosMsg , CustomMsg , Deps , DepsMut , Env , MessageInfo , Response , StdError
2+ Addr , BankMsg , Coin , CosmosMsg , CustomMsg , Deps , DepsMut , Empty , Env , MessageInfo , Response , StdError
33} ;
44use cw721:: { state:: NftInfo , traits:: Cw721State , Expiration } ;
55
@@ -15,6 +15,8 @@ pub fn list<TNftExtension, TCustomResponseMsg>(
1515 id : String ,
1616 price : Coin ,
1717 reservation : Option < Reserve > ,
18+ marketplace_fee_bps : Option < u16 > ,
19+ marketplace_fee_recipient : Option < String > ,
1820) -> Result < Response < TCustomResponseMsg > , ContractError >
1921where
2022 TNftExtension : Cw721State ,
3133 price : price. amount . u128 ( ) ,
3234 } ) ;
3335 }
36+
37+ // validate market place fee and recipient
38+ if let Some ( market_fee) = marketplace_fee_bps {
39+ if market_fee > 10_000 {
40+ return Err ( ContractError :: InvalidMarketplaceFee {
41+ bps : market_fee,
42+ recipient : marketplace_fee_recipient. clone ( ) . unwrap_or_default ( ) ,
43+ } ) ;
44+ }
45+ }
46+ let ( marketplace_fee_bps_ref, marketplace_fee_recipient_ref) = ( & marketplace_fee_bps, & marketplace_fee_recipient) ;
47+ if marketplace_fee_bps_ref. is_some ( ) && marketplace_fee_recipient_ref. is_none ( ) {
48+ return Err ( ContractError :: InvalidMarketplaceFee {
49+ bps : marketplace_fee_bps_ref. unwrap ( ) ,
50+ recipient : "" . to_string ( ) ,
51+ } ) ;
52+ } else if marketplace_fee_bps_ref. is_some ( ) && marketplace_fee_recipient_ref. is_some ( ) {
53+ deps. api . addr_validate ( marketplace_fee_recipient_ref. as_ref ( ) . unwrap ( ) ) ?;
54+ }
3455 // Ensure the listing does not already exist
3556 let old_listing = asset_config. listings . may_load ( deps. storage , & id) ?;
3657 if old_listing. is_some ( ) {
4364 price : price. clone ( ) ,
4465 reserved : reservation. clone ( ) ,
4566 nft_info,
67+ marketplace_fee_bps,
68+ marketplace_fee_recipient : Some ( deps. api . addr_validate ( & marketplace_fee_recipient. unwrap ( ) ) ?) ,
4669 } ;
4770 asset_config. listings . save ( deps. storage , & id, & listing) ?;
4871 Ok ( Response :: default ( )
@@ -137,6 +160,7 @@ pub fn buy<TNftExtension, TCustomResponseMsg>(
137160 info : & MessageInfo ,
138161 id : String ,
139162 recipient : Option < String > ,
163+ deductions : Vec < ( String , Coin , String ) > ,
140164) -> Result < Response < TCustomResponseMsg > , ContractError >
141165where
142166 TNftExtension : Cw721State ,
@@ -152,11 +176,11 @@ where
152176 let price = listing. price . clone ( ) ;
153177 let seller = listing. seller . clone ( ) ;
154178
155- let payment = info
179+ let mut payment = info
156180 . funds
157181 . iter ( )
158182 . find ( |coin| coin. denom == price. denom )
159- . ok_or_else ( || ContractError :: NoPayment { } ) ?;
183+ . ok_or_else ( || ContractError :: NoPayment { } ) ?. clone ( ) ;
160184
161185 if payment. amount . lt ( & price. amount ) || payment. denom != price. denom {
162186 return Err ( ContractError :: InvalidPayment {
@@ -165,6 +189,30 @@ where
165189 } ) ;
166190 }
167191
192+ let mut response = Response :: < TCustomResponseMsg > :: default ( ) ;
193+
194+ if let Some ( market_fee) = listing. marketplace_fee_bps {
195+ let fee_amount = payment. amount . checked_multiply_ratio ( market_fee, 10_000 as u128 ) . map_err ( |_| ContractError :: InsufficientFunds { } ) ?;
196+ payment. amount = payment. amount . checked_sub ( fee_amount) . map_err ( |_| ContractError :: InsufficientFunds { } ) ?;
197+ if let Some ( recipient) = listing. marketplace_fee_recipient {
198+ if !fee_amount. is_zero ( ) {
199+ response = response. add_attribute ( "marketplace_fee" , fee_amount. to_string ( ) ) ;
200+ response = response. add_message ( BankMsg :: Send {
201+ to_address : recipient. to_string ( ) ,
202+ amount : vec ! [ Coin {
203+ denom: payment. denom. clone( ) ,
204+ amount: fee_amount,
205+ } ] ,
206+ } ) ;
207+ }
208+ }
209+ }
210+
211+ // remove all other deductions e.g. royalties from payment
212+ for ( _, amount, _) in deductions {
213+ payment. amount = payment. amount . checked_sub ( amount. amount ) . map_err ( |_| ContractError :: InsufficientFunds { } ) ?;
214+ }
215+
168216 let buyer = match recipient {
169217 Some ( addr) => deps. api . addr_validate ( & addr) ?,
170218 None => info. sender . clone ( ) ,
@@ -187,17 +235,18 @@ where
187235
188236 asset_config. listings . remove ( deps. storage , & id) ?;
189237
190- Ok ( Response :: default ( )
238+ response = response
191239 . add_message ( BankMsg :: Send {
192240 to_address : seller. to_string ( ) ,
193- amount : vec ! [ payment. clone( ) ] , // we send the entire payment to the seller
241+ amount : vec ! [ payment. clone( ) ] , // we send the remaining payment after deductions to the seller
194242 } )
195243 . add_attribute ( "action" , "buy" )
196244 . add_attribute ( "id" , id)
197245 . add_attribute ( "price" , price. amount . to_string ( ) )
198246 . add_attribute ( "denom" , price. denom )
199247 . add_attribute ( "seller" , seller. to_string ( ) )
200- . add_attribute ( "buyer" , buyer. to_string ( ) ) )
248+ . add_attribute ( "buyer" , buyer. to_string ( ) ) ;
249+ Ok ( response)
201250}
202251
203252/// returns true if the sender can list the token
@@ -275,6 +324,8 @@ fn test_list() {
275324 "token-1" . to_string ( ) ,
276325 price. clone ( ) ,
277326 None ,
327+ None ,
328+ None
278329 )
279330 . unwrap ( ) ;
280331
@@ -312,6 +363,8 @@ fn test_list() {
312363 "token-1" . to_string ( ) ,
313364 Coin :: new ( 200 as u128 , "uxion" ) ,
314365 None ,
366+ None ,
367+ None
315368 )
316369 . unwrap_err ( ) ;
317370 assert_eq ! (
@@ -346,6 +399,8 @@ fn test_list() {
346399 "token-2" . to_string ( ) ,
347400 Coin :: new ( 100 as u128 , "uxion" ) ,
348401 None ,
402+ None ,
403+ None
349404 )
350405 . unwrap_err ( ) ;
351406 assert_eq ! ( err, ContractError :: Unauthorized { } ) ;
@@ -380,6 +435,8 @@ fn test_list() {
380435 "token-3" . to_string ( ) ,
381436 price. clone ( ) ,
382437 None ,
438+ None ,
439+ None
383440 )
384441 . unwrap ( ) ;
385442
@@ -447,6 +504,8 @@ fn test_list() {
447504 "token-4" . to_string ( ) ,
448505 price. clone ( ) ,
449506 None ,
507+ None ,
508+ None
450509 )
451510 . unwrap ( ) ;
452511
@@ -516,6 +575,8 @@ fn test_list() {
516575 "token-5" . to_string ( ) ,
517576 Coin :: new ( 100 as u128 , "uxion" ) ,
518577 None ,
578+ None ,
579+ None
519580 )
520581 . unwrap_err ( ) ;
521582 assert_eq ! ( err, ContractError :: Unauthorized { } ) ;
@@ -545,6 +606,8 @@ fn test_list() {
545606 "token-3" . to_string ( ) ,
546607 Coin :: new ( 0 as u128 , "uxion" ) ,
547608 None ,
609+ None ,
610+ None
548611 )
549612 . unwrap_err ( ) ;
550613 assert_eq ! ( err, ContractError :: InvalidListingPrice { price: 0 } ) ;
@@ -562,6 +625,8 @@ fn test_list() {
562625 "token-999" . to_string ( ) ,
563626 Coin :: new ( 100 as u128 , "uxion" ) ,
564627 None ,
628+ None ,
629+ None
565630 )
566631 . unwrap_err ( ) ;
567632 match err {
@@ -605,6 +670,8 @@ fn test_buy() {
605670 price : price. clone ( ) ,
606671 reserved : None ,
607672 nft_info : nft_info. clone ( ) ,
673+ marketplace_fee_bps : Some ( 100 ) ,
674+ marketplace_fee_recipient : Some ( seller_addr. clone ( ) ) ,
608675 } ,
609676 )
610677 . unwrap ( ) ;
@@ -615,6 +682,7 @@ fn test_buy() {
615682 & message_info ( & buyer_addr, & [ price. clone ( ) ] ) ,
616683 "token-1" . to_string ( ) ,
617684 None ,
685+ vec ! [ ( seller_addr. to_string( ) , coin( 10 as u128 , "uxion" ) , "marketplace_fee" . to_string( ) ) ] ,
618686 )
619687 . unwrap ( ) ;
620688
@@ -626,6 +694,7 @@ fn test_buy() {
626694 assert_eq ! (
627695 attrs,
628696 vec![
697+ ( "marketplace_fee" . to_string( ) , "100" . to_string( ) ) ,
629698 ( "action" . to_string( ) , "buy" . to_string( ) ) ,
630699 ( "id" . to_string( ) , "token-1" . to_string( ) ) ,
631700 ( "price" . to_string( ) , price. amount. to_string( ) ) ,
@@ -643,7 +712,10 @@ fn test_buy() {
643712 msgs,
644713 vec![ CosmosMsg :: Bank ( BankMsg :: Send {
645714 to_address: seller_addr. to_string( ) ,
646- amount: coins( 100 as u128 , "uxion" ) ,
715+ amount: coins( 1 as u128 , "uxion" ) ,
716+ } ) , CosmosMsg :: Bank ( BankMsg :: Send {
717+ to_address: seller_addr. to_string( ) ,
718+ amount: coins( 99 as u128 , "uxion" ) ,
647719 } ) ] ,
648720 ) ;
649721
@@ -691,6 +763,8 @@ fn test_buy() {
691763 price : price. clone ( ) ,
692764 reserved : None ,
693765 nft_info : nft_info. clone ( ) ,
766+ marketplace_fee_bps : None ,
767+ marketplace_fee_recipient : None ,
694768 } ,
695769 )
696770 . unwrap ( ) ;
@@ -701,6 +775,7 @@ fn test_buy() {
701775 & message_info ( & buyer_addr, & [ coin ( 50 as u128 , "uxion" ) ] ) ,
702776 "token-2" . to_string ( ) ,
703777 None ,
778+ vec ! [ ] ,
704779 )
705780 . unwrap_err ( ) ;
706781 assert_eq ! (
@@ -722,6 +797,7 @@ fn test_buy() {
722797 & message_info ( & buyer_addr, & [ coin ( 100 as u128 , "uxion" ) ] ) ,
723798 "token-3" . to_string ( ) ,
724799 None ,
800+ vec ! [ ] ,
725801 )
726802 . unwrap_err ( ) ;
727803 assert_eq ! (
@@ -763,6 +839,8 @@ fn test_buy() {
763839 reserved_until : Expiration :: AtHeight ( env. block . height + 100 ) ,
764840 } ) ,
765841 nft_info : nft_info. clone ( ) ,
842+ marketplace_fee_bps : None ,
843+ marketplace_fee_recipient : None ,
766844 } ,
767845 )
768846 . unwrap ( ) ;
@@ -780,6 +858,8 @@ fn test_buy() {
780858 reserved_until : Expiration :: AtHeight ( env. block . height + 100 ) ,
781859 } ) ,
782860 nft_info : nft_info. clone ( ) ,
861+ marketplace_fee_bps : None ,
862+ marketplace_fee_recipient : None ,
783863 } ,
784864 )
785865 . unwrap ( ) ;
@@ -790,6 +870,7 @@ fn test_buy() {
790870 & message_info ( & seller_addr, & [ price. clone ( ) ] ) ,
791871 "token-4" . to_string ( ) ,
792872 None ,
873+ vec ! [ ] ,
793874 )
794875 . unwrap_err ( ) ;
795876 assert_eq ! ( err, ContractError :: Unauthorized { } ) ;
@@ -801,6 +882,7 @@ fn test_buy() {
801882 & message_info ( & buyer_addr, & [ price. clone ( ) ] ) ,
802883 "token-4" . to_string ( ) ,
803884 None ,
885+ vec ! [ ] ,
804886 )
805887 . unwrap ( ) ;
806888 let attrs: Vec < ( String , String ) > = response
@@ -834,6 +916,8 @@ fn test_buy() {
834916 reserved_until : Expiration :: AtHeight ( env. block . height + 100 ) ,
835917 } ) ,
836918 nft_info : nft_info. clone ( ) ,
919+ marketplace_fee_bps : None ,
920+ marketplace_fee_recipient : None ,
837921 } ,
838922 )
839923 . unwrap ( ) ;
@@ -844,6 +928,7 @@ fn test_buy() {
844928 & message_info ( & seller_addr, & [ price. clone ( ) ] ) ,
845929 "token-4" . to_string ( ) ,
846930 Some ( buyer_addr. to_string ( ) ) , // buyer is the reserver
931+ vec ! [ ] ,
847932 )
848933 . unwrap ( ) ;
849934 let attrs: Vec < ( String , String ) > = response
@@ -898,6 +983,8 @@ fn test_delist() {
898983 price : price. clone ( ) ,
899984 reserved : None ,
900985 nft_info : nft_info. clone ( ) ,
986+ marketplace_fee_bps : None ,
987+ marketplace_fee_recipient : None ,
901988 } ,
902989 )
903990 . unwrap ( ) ;
@@ -963,6 +1050,8 @@ fn test_delist() {
9631050 price : price. clone ( ) ,
9641051 reserved : None ,
9651052 nft_info : nft_info. clone ( ) ,
1053+ marketplace_fee_bps : None ,
1054+ marketplace_fee_recipient : None ,
9661055 } ,
9671056 )
9681057 . unwrap ( ) ;
@@ -1025,6 +1114,8 @@ fn test_delist() {
10251114 reserver : seller_addr. clone ( ) ,
10261115 } ) ,
10271116 nft_info : nft_info. clone ( ) ,
1117+ marketplace_fee_bps : None ,
1118+ marketplace_fee_recipient : None ,
10281119 } ,
10291120 )
10301121 . unwrap ( ) ;
@@ -1100,6 +1191,8 @@ fn test_reserve() {
11001191 price : price. clone ( ) ,
11011192 reserved : None ,
11021193 nft_info : nft_info. clone ( ) ,
1194+ marketplace_fee_bps : None ,
1195+ marketplace_fee_recipient : None ,
11031196 } ,
11041197 )
11051198 . unwrap ( ) ;
0 commit comments