1use cosmwasm_schema::cw_serde;
2use cosmwasm_schema::QueryResponses;
3use cosmwasm_std::{
4 coin, Addr, BankMsg, Binary, Empty, Event, StdError, StdResult, Timestamp, Uint128,
5};
6use cw721::{
7 AllNftInfoResponse, ApprovalResponse, ApprovalsResponse, ContractInfoResponse, NftInfoResponse,
8 NumTokensResponse, OperatorsResponse, OwnerOfResponse, TokensResponse,
9};
10use cw721_base::msg::MinterResponse;
11use cw721_base::msg::QueryMsg as Cw721QueryMsg;
12use cw_ownable::cw_ownable_execute;
13use cw_ownable::cw_ownable_query;
14use cw_utils::Expiration;
15use terp721::ResidualInfoResponse;
16use terp_sdk::{Response, SubMsg, NATIVE_DENOM};
17
18#[cw_ownable_execute]
19#[cw_serde]
20pub enum ExecuteMsg<T, E> {
21 TransferNft { recipient: String, token_id: String },
23 SendNft {
26 contract: String,
27 token_id: String,
28 msg: Binary,
29 },
30 Approve {
33 spender: String,
34 token_id: String,
35 expires: Option<Expiration>,
36 },
37 Revoke { spender: String, token_id: String },
39 ApproveAll {
42 operator: String,
43 expires: Option<Expiration>,
44 },
45 RevokeAll { operator: String },
47
48 Mint {
50 token_id: String,
52 owner: String,
54 token_uri: Option<String>,
58 extension: T,
60 },
61
62 Burn { token_id: String },
64
65 Extension { msg: E },
67}
68
69#[cw_ownable_query]
70#[derive(QueryResponses)]
71#[cw_serde]
72pub enum QueryMsg {
73 #[returns(OwnerOfResponse)]
74 OwnerOf {
75 token_id: String,
76 include_expired: Option<bool>,
77 },
78 #[returns(ApprovalResponse)]
79 Approval {
80 token_id: String,
81 spender: String,
82 include_expired: Option<bool>,
83 },
84 #[returns(ApprovalsResponse)]
85 Approvals {
86 token_id: String,
87 include_expired: Option<bool>,
88 },
89 #[returns(OperatorsResponse)]
90 AllOperators {
91 owner: String,
92 include_expired: Option<bool>,
93 start_after: Option<String>,
94 limit: Option<u32>,
95 },
96 #[returns(NumTokensResponse)]
97 NumTokens {},
98 #[returns(ContractInfoResponse)]
99 ContractInfo {},
100 #[returns(NftInfoResponse<Empty>)]
101 NftInfo { token_id: String },
102 #[returns(AllNftInfoResponse<Empty>)]
103 AllNftInfo {
104 token_id: String,
105 include_expired: Option<bool>,
106 },
107 #[returns(TokensResponse)]
108 Tokens {
109 owner: String,
110 start_after: Option<String>,
111 limit: Option<u32>,
112 },
113 #[returns(TokensResponse)]
114 AllTokens {
115 start_after: Option<String>,
116 limit: Option<u32>,
117 },
118 #[returns(MinterResponse)]
119 Minter {},
120 #[returns(MinterResponse)]
121 CollectionInfo {},
122}
123
124impl From<QueryMsg> for Cw721QueryMsg<Empty> {
125 fn from(msg: QueryMsg) -> Cw721QueryMsg<Empty> {
126 match msg {
127 QueryMsg::OwnerOf {
128 token_id,
129 include_expired,
130 } => Cw721QueryMsg::OwnerOf {
131 token_id,
132 include_expired,
133 },
134 QueryMsg::Approval {
135 token_id,
136 spender,
137 include_expired,
138 } => Cw721QueryMsg::Approval {
139 token_id,
140 spender,
141 include_expired,
142 },
143 QueryMsg::Approvals {
144 token_id,
145 include_expired,
146 } => Cw721QueryMsg::Approvals {
147 token_id,
148 include_expired,
149 },
150 QueryMsg::AllOperators {
151 owner,
152 include_expired,
153 start_after,
154 limit,
155 } => Cw721QueryMsg::AllOperators {
156 owner,
157 include_expired,
158 start_after,
159 limit,
160 },
161 QueryMsg::NumTokens {} => Cw721QueryMsg::NumTokens {},
162 QueryMsg::ContractInfo {} => Cw721QueryMsg::ContractInfo {},
163 QueryMsg::NftInfo { token_id } => Cw721QueryMsg::NftInfo { token_id },
164 QueryMsg::AllNftInfo {
165 token_id,
166 include_expired,
167 } => Cw721QueryMsg::AllNftInfo {
168 token_id,
169 include_expired,
170 },
171 QueryMsg::Tokens {
172 owner,
173 start_after,
174 limit,
175 } => Cw721QueryMsg::Tokens {
176 owner,
177 start_after,
178 limit,
179 },
180 QueryMsg::AllTokens { start_after, limit } => {
181 Cw721QueryMsg::AllTokens { start_after, limit }
182 }
183 QueryMsg::Minter {} => Cw721QueryMsg::Minter {},
184 QueryMsg::Ownership {} => Cw721QueryMsg::Ownership {},
185 _ => unreachable!("cannot convert {:?} to Cw721QueryMsg", msg),
186 }
187 }
188}
189
190#[cw_serde]
191pub struct CollectionInfoResponse {
192 pub creator: String,
193 pub description: String,
194 pub image: String,
195 pub external_link: Option<String>,
196 pub explicit_content: Option<bool>,
197 pub start_trading_time: Option<Timestamp>,
198 pub residual_info: Option<ResidualInfoResponse>,
199}
200
201impl CollectionInfoResponse {
202 pub fn royalty_payout(
203 &self,
204 collection: Addr,
205 payment: Uint128,
206 protocol_fee: Uint128,
207 finders_fee: Option<Uint128>,
208 res: &mut Response,
209 ) -> StdResult<Uint128> {
210 if let Some(residual_info) = self.residual_info.as_ref() {
211 if residual_info.share.is_zero() {
212 return Ok(Uint128::zero());
213 }
214 let royalty = coin((payment * residual_info.share).u128(), NATIVE_DENOM);
215 if payment < (protocol_fee + finders_fee.unwrap_or(Uint128::zero()) + royalty.amount) {
216 return Err(StdError::generic_err("Fees exceed payment"));
217 }
218 res.messages.push(SubMsg::new(BankMsg::Send {
219 to_address: residual_info.payment_address.to_string(),
220 amount: vec![royalty.clone()],
221 }));
222
223 let event = Event::new("royalty-payout")
224 .add_attribute("collection", collection.to_string())
225 .add_attribute("amount", royalty.to_string())
226 .add_attribute("recipient", residual_info.payment_address.to_string());
227 res.events.push(event);
228
229 Ok(royalty.amount)
230 } else {
231 Ok(Uint128::zero())
232 }
233 }
234}
235
236#[cw_serde]
237pub enum NftParams<T> {
238 NftData {
239 token_id: String,
240 owner: String,
241 token_uri: Option<String>,
242 extension: T,
243 },
244}