tensor_toolbox/token_2022/
wns.rs1use anchor_lang::{
9 solana_program::{
10 account_info::AccountInfo,
11 instruction::{AccountMeta, Instruction},
12 msg,
13 program::invoke_signed,
14 program_error::ProgramError,
15 program_option::COption,
16 pubkey::Pubkey,
17 rent::Rent,
18 sysvar::Sysvar,
19 },
20 Key, Result,
21};
22use anchor_spl::token_interface::spl_token_2022::{
23 extension::{
24 metadata_pointer::MetadataPointer, transfer_hook::TransferHook, BaseStateWithExtensions,
25 StateWithExtensions,
26 },
27 state::Mint,
28};
29use spl_token_metadata_interface::state::TokenMetadata;
30use std::str::FromStr;
31use tensor_vipers::{unwrap_checked, unwrap_int};
32
33use super::extension::{get_extension, get_variable_len_extension};
34
35anchor_lang::declare_id!("wns1gDLt8fgLcGhWi5MqAqgXpwEP1JftKE9eZnXS1HM");
36
37pub const ROYALTY_BASIS_POINTS_FIELD: &str = "royalty_basis_points";
38
39pub const APPROVE_LEN: usize = 8 + 8;
40
41const MANAGER_PUBKEY: Pubkey = Pubkey::new_from_array([
43 125, 100, 129, 23, 165, 236, 2, 226, 233, 63, 107, 17, 242, 89, 72, 105, 75, 145, 77, 172, 118,
44 210, 188, 66, 171, 78, 251, 66, 86, 35, 201, 190,
45]);
46
47pub struct ApproveAccounts<'info> {
49 pub wns_program: AccountInfo<'info>,
50 pub payer: AccountInfo<'info>,
51 pub authority: AccountInfo<'info>,
52 pub mint: AccountInfo<'info>,
53 pub approve_account: AccountInfo<'info>,
54 pub payment_mint: Option<AccountInfo<'info>>,
56 pub distribution_token_account: Option<AccountInfo<'info>>,
58 pub authority_token_account: Option<AccountInfo<'info>>,
60 pub distribution_account: AccountInfo<'info>,
61 pub system_program: AccountInfo<'info>,
62 pub distribution_program: AccountInfo<'info>,
63 pub token_program: AccountInfo<'info>,
64 pub payment_token_program: Option<AccountInfo<'info>>,
65}
66
67impl<'info> ApproveAccounts<'info> {
68 pub fn to_account_infos(self) -> Vec<AccountInfo<'info>> {
69 let mut accounts = vec![
71 self.wns_program,
72 self.payer,
73 self.authority,
74 self.mint,
75 self.approve_account,
76 self.distribution_account,
77 self.system_program,
78 self.distribution_program,
79 self.token_program,
80 ];
81
82 if let Some(distribution_token_account) = self.distribution_token_account {
83 accounts.push(distribution_token_account);
84 };
85
86 if let Some(authority_token_account) = self.authority_token_account {
87 accounts.push(authority_token_account);
88 }
89
90 if let Some(payment_mint) = self.payment_mint {
91 accounts.push(payment_mint);
92 }
93
94 if let Some(payment_token_program) = self.payment_token_program {
95 accounts.push(payment_token_program);
96 }
97
98 accounts
99 }
100
101 pub fn to_account_metas(&self) -> Vec<AccountMeta> {
102 vec![
103 AccountMeta::new(*self.payer.key, true),
104 AccountMeta::new(*self.authority.key, true),
105 AccountMeta::new_readonly(*self.mint.key, false),
106 AccountMeta::new(*self.approve_account.key, false),
107 AccountMeta::new_readonly(
108 self.payment_mint
109 .as_ref()
110 .map_or(*self.system_program.key, |account| *account.key),
111 false,
112 ),
113 if let Some(distribution_token_account) = &self.distribution_token_account {
115 AccountMeta::new(*distribution_token_account.key, false)
116 } else {
117 AccountMeta::new_readonly(*self.wns_program.key, false)
118 },
119 if let Some(authority_token_account) = &self.authority_token_account {
120 AccountMeta::new(*authority_token_account.key, false)
121 } else {
122 AccountMeta::new_readonly(*self.wns_program.key, false)
123 },
124 AccountMeta::new(*self.distribution_account.key, false),
125 AccountMeta::new_readonly(*self.system_program.key, false),
126 AccountMeta::new_readonly(*self.distribution_program.key, false),
127 AccountMeta::new_readonly(*self.token_program.key, false),
128 if let Some(payment_token_program) = &self.payment_token_program {
129 AccountMeta::new_readonly(*payment_token_program.key, false)
130 } else {
131 AccountMeta::new_readonly(*self.wns_program.key, false)
132 },
133 ]
134 }
135}
136
137pub fn validate_mint(mint_info: &AccountInfo) -> Result<u16> {
146 let mint_data = &mint_info.data.borrow();
147 let mint = StateWithExtensions::<Mint>::unpack(mint_data)?;
148
149 if !mint.base.is_initialized {
150 msg!("Mint is not initialized");
151 return Err(ProgramError::UninitializedAccount.into());
152 }
153
154 if mint.base.decimals != 0 {
155 msg!("Mint decimals must be 0");
156 return Err(ProgramError::InvalidAccountData.into());
157 }
158
159 if mint.base.supply != 1 {
160 msg!("Mint supply must be 1");
161 return Err(ProgramError::InvalidAccountData.into());
162 }
163
164 if mint.base.mint_authority.is_some()
165 && mint.base.mint_authority != COption::Some(MANAGER_PUBKEY)
166 {
167 msg!("Mint authority must be none or the WNS manager account");
168 return Err(ProgramError::InvalidAccountData.into());
169 }
170
171 if let Ok(extension) = get_extension::<MetadataPointer>(mint.get_tlv_data()) {
172 let metadata_address: Option<Pubkey> = extension.metadata_address.into();
173 if metadata_address != Some(mint_info.key()) {
174 msg!("Metadata pointer extension: metadata address should be the mint itself");
175 return Err(ProgramError::InvalidAccountData.into());
176 }
177 } else {
178 msg!("Missing metadata pointer extension");
179 return Err(ProgramError::InvalidAccountData.into());
180 }
181
182 if let Ok(extension) = get_extension::<TransferHook>(mint.get_tlv_data()) {
183 let program_id: Option<Pubkey> = extension.program_id.into();
184 if program_id != Some(super::wns::ID) {
185 msg!("Transfer hook extension: program id mismatch");
186 return Err(ProgramError::InvalidAccountData.into());
187 }
188 } else {
189 msg!("Missing transfer hook extension");
190 return Err(ProgramError::InvalidAccountData.into());
191 }
192
193 let metadata = get_variable_len_extension::<TokenMetadata>(mint.get_tlv_data())?;
194 let royalty_basis_points = metadata
195 .additional_metadata
196 .iter()
197 .find(|(key, _)| key == super::wns::ROYALTY_BASIS_POINTS_FIELD)
198 .map(|(_, value)| value)
199 .map(|value| u16::from_str(value).unwrap())
200 .unwrap_or(0);
201
202 Ok(royalty_basis_points)
203}
204
205pub struct ApproveParams<'a> {
207 pub price: u64,
208 pub royalty_fee: u64,
209 pub signer_seeds: &'a [&'a [&'a [u8]]],
210}
211
212impl<'a> ApproveParams<'a> {
213 pub fn no_royalties() -> Self {
215 Self {
216 price: 0,
217 royalty_fee: 0,
218 signer_seeds: &[],
219 }
220 }
221 pub fn no_royalties_with_signer_seeds(signer_seeds: &'a [&'a [&'a [u8]]]) -> Self {
223 Self {
224 price: 0,
225 royalty_fee: 0,
226 signer_seeds,
227 }
228 }
229}
230
231pub fn approve(accounts: super::wns::ApproveAccounts, params: ApproveParams) -> Result<()> {
239 let ApproveParams {
240 price,
241 royalty_fee,
242 signer_seeds,
243 } = params;
244
245 let mut data = vec![198, 217, 247, 150, 208, 60, 169, 244];
247 data.extend(price.to_le_bytes());
248
249 let approve_ix = Instruction {
250 program_id: super::wns::ID,
251 accounts: accounts.to_account_metas(),
252 data,
253 };
254
255 let payer = accounts.payer.clone();
256 let approve = accounts.approve_account.clone();
257
258 let initial_payer_lamports = payer.lamports();
260 let initial_approve_rent = approve.lamports();
261
262 let result = invoke_signed(&approve_ix, &accounts.to_account_infos(), signer_seeds)
264 .map_err(|error| error.into());
265
266 let ending_payer_lamports = payer.lamports();
267
268 let rent_difference = unwrap_int!(std::cmp::max(
272 Rent::get()?.minimum_balance(APPROVE_LEN),
273 initial_approve_rent,
274 )
275 .checked_sub(initial_approve_rent));
276 let dist_realloc_fee = Rent::get()?.minimum_balance(1024);
278
279 let payer_difference = unwrap_int!(initial_payer_lamports.checked_sub(ending_payer_lamports));
280 let expected_fee = unwrap_checked!({
281 royalty_fee
282 .checked_add(rent_difference)?
283 .checked_add(dist_realloc_fee)
284 });
285
286 if payer_difference > expected_fee {
288 msg!(
289 "Unexpected lamports change: expected {} but got {}",
290 expected_fee,
291 payer_difference
292 );
293 return Err(ProgramError::InvalidAccountData.into());
294 }
295
296 result
297}