terp721_base/
msg.rs

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    /// Transfer is a base message to move a token to another account without triggering actions
22    TransferNft { recipient: String, token_id: String },
23    /// Send is a base message to transfer a token to a contract and trigger an action
24    /// on the receiving contract.
25    SendNft {
26        contract: String,
27        token_id: String,
28        msg: Binary,
29    },
30    /// Allows operator to transfer / send the token from the owner's account.
31    /// If expiration is set, then this allowance has a time/height limit
32    Approve {
33        spender: String,
34        token_id: String,
35        expires: Option<Expiration>,
36    },
37    /// Remove previously granted Approval
38    Revoke { spender: String, token_id: String },
39    /// Allows operator to transfer / send any token from the owner's account.
40    /// If expiration is set, then this allowance has a time/height limit
41    ApproveAll {
42        operator: String,
43        expires: Option<Expiration>,
44    },
45    /// Remove previously granted ApproveAll permission
46    RevokeAll { operator: String },
47
48    /// Mint a new NFT, can only be called by the contract minter
49    Mint {
50        /// Unique ID of the NFT
51        token_id: String,
52        /// The owner of the newly minter NFT
53        owner: String,
54        /// Universal resource identifier for this NFT
55        /// Should point to a JSON file that conforms to the ERC721
56        /// Metadata JSON Schema
57        token_uri: Option<String>,
58        /// Any custom extension used by this contract
59        extension: T,
60    },
61
62    /// Burn an NFT the sender has access to
63    Burn { token_id: String },
64
65    /// Extension msg
66    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}