1#![allow(clippy::result_large_err)]
2
3use anchor_lang::error::ErrorCode;
4use anchor_lang::prelude::*;
5use anchor_spl::{
6 associated_token::AssociatedToken,
7 token_interface::{self, Mint, TokenAccount, TokenInterface, TransferChecked},
8};
9use mpl_token_metadata::{
10 accounts::{Edition, MasterEdition, Metadata},
11 instructions::{DelegateTransferV1CpiBuilder, TransferV1CpiBuilder},
12 types::{AuthorizationData, Key as MplKey, TokenStandard},
13};
14use tensor_vipers::{throw_err, unwrap_opt};
15
16use crate::TensorError;
17
18pub use mpl_token_metadata::ID;
19
20#[inline(never)]
21pub fn assert_decode_metadata(mint: &Pubkey, metadata: &AccountInfo) -> Result<Metadata> {
22 if *metadata.owner != mpl_token_metadata::ID {
23 throw_err!(TensorError::InvalidProgramOwner);
24 }
25
26 let metadata = Metadata::safe_deserialize(&metadata.try_borrow_data()?)
30 .map_err(|_error| TensorError::BadMetadata)?;
31
32 if metadata.mint != *mint {
33 throw_err!(TensorError::BadMetadata);
34 }
35
36 Ok(metadata)
37}
38
39#[inline(never)]
40pub fn assert_decode_master_edition(edition: &AccountInfo) -> Result<MasterEdition> {
41 if *edition.owner != mpl_token_metadata::ID {
42 throw_err!(TensorError::InvalidProgramOwner);
43 }
44
45 let edition = MasterEdition::safe_deserialize(&edition.try_borrow_data()?)
46 .map_err(|_error| TensorError::InvalidEdition)?;
47
48 Ok(edition)
49}
50
51#[inline(never)]
52pub fn assert_decode_edition(edition: &AccountInfo) -> Result<Edition> {
53 if *edition.owner != mpl_token_metadata::ID {
54 throw_err!(TensorError::InvalidProgramOwner);
55 }
56
57 let data = edition.try_borrow_data()?;
58
59 if data.is_empty() || data[0] != MplKey::EditionV1 as u8 {
60 throw_err!(TensorError::InvalidEdition);
61 }
62
63 let edition = Edition::from_bytes(&edition.try_borrow_data()?)
64 .map_err(|_error| TensorError::InvalidEdition)?;
65
66 Ok(edition)
67}
68
69pub struct TransferArgsAi<'a, 'info> {
71 pub payer: &'a AccountInfo<'info>,
73
74 pub source: &'a AccountInfo<'info>,
76
77 pub source_ata: &'a AccountInfo<'info>,
79
80 pub source_token_record: Option<&'a AccountInfo<'info>>,
82
83 pub destination: &'a AccountInfo<'info>,
85
86 pub destination_ata: &'a AccountInfo<'info>,
88
89 pub destination_token_record: Option<&'a AccountInfo<'info>>,
91
92 pub mint: &'a AccountInfo<'info>,
94
95 pub metadata: &'a AccountInfo<'info>,
97
98 pub edition: &'a AccountInfo<'info>,
100
101 pub system_program: &'a AccountInfo<'info>,
103
104 pub spl_token_program: &'a AccountInfo<'info>,
106
107 pub spl_ata_program: &'a AccountInfo<'info>,
109
110 pub sysvar_instructions: Option<&'a AccountInfo<'info>>,
112
113 pub token_metadata_program: Option<&'a AccountInfo<'info>>,
115
116 pub authorization_rules_program: Option<&'a AccountInfo<'info>>,
118
119 pub authorization_rules: Option<&'a AccountInfo<'info>>,
121
122 pub authorization_data: Option<AuthorizationData>,
124
125 pub delegate: Option<&'a AccountInfo<'info>>,
129}
130
131pub struct TransferArgs<'a, 'info> {
133 pub payer: &'a AccountInfo<'info>,
135
136 pub source: &'a AccountInfo<'info>,
138
139 pub source_ata: &'a InterfaceAccount<'info, TokenAccount>,
141
142 pub source_token_record: Option<&'a UncheckedAccount<'info>>,
144
145 pub destination: &'a AccountInfo<'info>,
147
148 pub destination_ata: &'a InterfaceAccount<'info, TokenAccount>,
150
151 pub destination_token_record: Option<&'a UncheckedAccount<'info>>,
153
154 pub mint: &'a InterfaceAccount<'info, Mint>,
156
157 pub metadata: &'a UncheckedAccount<'info>,
159
160 pub edition: &'a UncheckedAccount<'info>,
162
163 pub system_program: &'a Program<'info, System>,
165
166 pub spl_token_program: &'a Interface<'info, TokenInterface>,
168
169 pub spl_ata_program: &'a Program<'info, AssociatedToken>,
171
172 pub sysvar_instructions: Option<&'a UncheckedAccount<'info>>,
174
175 pub token_metadata_program: Option<&'a UncheckedAccount<'info>>,
177
178 pub authorization_rules_program: Option<&'a UncheckedAccount<'info>>,
180
181 pub authorization_rules: Option<&'a UncheckedAccount<'info>>,
183
184 pub authorization_data: Option<AuthorizationData>,
186
187 pub delegate: Option<&'a AccountInfo<'info>>,
191}
192
193fn cpi_transfer_ai(args: TransferArgsAi, signer_seeds: Option<&[&[&[u8]]]>) -> Result<()> {
194 let token_metadata_program =
195 unwrap_opt!(args.token_metadata_program, ErrorCode::AccountNotEnoughKeys);
196 let sysvar_instructions =
197 unwrap_opt!(args.sysvar_instructions, ErrorCode::AccountNotEnoughKeys);
198
199 let mut transfer_cpi = TransferV1CpiBuilder::new(token_metadata_program);
201 transfer_cpi
202 .authority(args.source)
203 .token_owner(args.source)
204 .token(args.source_ata.as_ref())
205 .destination_owner(args.destination)
206 .destination_token(args.destination_ata.as_ref())
207 .mint(args.mint.as_ref())
208 .metadata(args.metadata.as_ref())
209 .edition(Some(args.edition))
210 .payer(args.payer)
211 .spl_ata_program(args.spl_ata_program)
212 .spl_token_program(args.spl_token_program)
213 .system_program(args.system_program)
214 .sysvar_instructions(sysvar_instructions)
215 .token_record(args.source_token_record)
216 .destination_token_record(args.destination_token_record)
217 .authorization_rules_program(args.authorization_rules_program)
218 .authorization_rules(args.authorization_rules)
219 .amount(1);
220
221 args.authorization_data
223 .clone()
224 .map(|data| transfer_cpi.authorization_data(data));
225
226 if let Some(delegate) = args.delegate {
228 transfer_cpi.authority(delegate);
230
231 let mut delegate_cpi = DelegateTransferV1CpiBuilder::new(token_metadata_program);
232 delegate_cpi
233 .authority(args.source)
234 .delegate(delegate)
235 .token(args.source_ata.as_ref())
236 .mint(args.mint.as_ref())
237 .metadata(args.metadata)
238 .master_edition(Some(args.edition))
239 .payer(args.payer)
240 .spl_token_program(Some(args.spl_token_program))
241 .token_record(args.source_token_record)
242 .authorization_rules(args.authorization_rules)
243 .authorization_rules_program(args.authorization_rules_program)
244 .amount(1);
245
246 args.authorization_data
247 .map(|data| delegate_cpi.authorization_data(data));
248
249 delegate_cpi.invoke()?;
250 }
251
252 if let Some(signer_seeds) = signer_seeds {
253 transfer_cpi.invoke_signed(signer_seeds)?;
254 } else {
255 transfer_cpi.invoke()?;
256 }
257
258 Ok(())
259}
260
261fn cpi_transfer(args: TransferArgs, signer_seeds: Option<&[&[&[u8]]]>) -> Result<()> {
262 let token_metadata_program =
263 unwrap_opt!(args.token_metadata_program, ErrorCode::AccountNotEnoughKeys);
264 let sysvar_instructions =
265 unwrap_opt!(args.sysvar_instructions, ErrorCode::AccountNotEnoughKeys);
266
267 let mut transfer_cpi = TransferV1CpiBuilder::new(token_metadata_program);
269 transfer_cpi
270 .authority(args.source)
271 .token_owner(args.source)
272 .token(args.source_ata.as_ref())
273 .destination_owner(args.destination)
274 .destination_token(args.destination_ata.as_ref())
275 .mint(args.mint.as_ref())
276 .metadata(args.metadata.as_ref())
277 .edition(Some(args.edition))
278 .payer(args.payer)
279 .spl_ata_program(args.spl_ata_program)
280 .spl_token_program(args.spl_token_program)
281 .system_program(args.system_program)
282 .sysvar_instructions(sysvar_instructions)
283 .token_record(args.source_token_record.map(|account| account.as_ref()))
284 .destination_token_record(
285 args.destination_token_record
286 .map(|account| account.as_ref()),
287 )
288 .authorization_rules_program(
289 args.authorization_rules_program
290 .map(|account| account.as_ref()),
291 )
292 .authorization_rules(args.authorization_rules.map(|account| account.as_ref()))
293 .amount(1);
294
295 args.authorization_data
297 .clone()
298 .map(|data| transfer_cpi.authorization_data(data));
299
300 if let Some(delegate) = args.delegate {
302 transfer_cpi.authority(delegate);
304
305 let mut delegate_cpi = DelegateTransferV1CpiBuilder::new(token_metadata_program);
306 delegate_cpi
307 .authority(args.source)
308 .delegate(delegate)
309 .token(args.source_ata.as_ref())
310 .mint(args.mint.as_ref())
311 .metadata(args.metadata)
312 .master_edition(Some(args.edition))
313 .payer(args.payer)
314 .spl_token_program(Some(args.spl_token_program))
315 .token_record(args.source_token_record.map(|account| account.as_ref()))
316 .authorization_rules(args.authorization_rules.map(|account| account.as_ref()))
317 .authorization_rules_program(
318 args.authorization_rules_program
319 .map(|account| account.as_ref()),
320 )
321 .amount(1);
322
323 args.authorization_data
324 .map(|data| delegate_cpi.authorization_data(data));
325
326 delegate_cpi.invoke()?;
327 }
328
329 if let Some(signer_seeds) = signer_seeds {
330 transfer_cpi.invoke_signed(signer_seeds)?;
331 } else {
332 transfer_cpi.invoke()?;
333 }
334
335 Ok(())
336}
337
338pub fn transfer_with_ai(
340 args: TransferArgsAi,
341 signer_seeds: Option<&[&[&[u8]]]>,
343) -> Result<()> {
344 let metadata = assert_decode_metadata(&args.mint.key(), args.metadata)?;
345
346 if matches!(
347 metadata.token_standard,
348 Some(TokenStandard::ProgrammableNonFungible)
349 | Some(TokenStandard::ProgrammableNonFungibleEdition)
350 ) {
351 return cpi_transfer_ai(args, signer_seeds);
353 }
354
355 let ctx = CpiContext::new(
358 args.spl_token_program.to_account_info(),
359 TransferChecked {
360 from: args.source_ata.to_account_info(),
361 to: args.destination_ata.to_account_info(),
362 authority: args.source.to_account_info(),
363 mint: args.mint.to_account_info(),
364 },
365 );
366
367 if let Some(signer_seeds) = signer_seeds {
368 token_interface::transfer_checked(ctx.with_signer(signer_seeds), 1, 0)
369 } else {
370 token_interface::transfer_checked(ctx, 1, 0)
371 }
372}
373
374pub fn transfer(
376 args: TransferArgs,
377 signer_seeds: Option<&[&[&[u8]]]>,
379) -> Result<()> {
380 let metadata = assert_decode_metadata(&args.mint.key(), args.metadata)?;
381
382 if matches!(
383 metadata.token_standard,
384 Some(TokenStandard::ProgrammableNonFungible)
385 | Some(TokenStandard::ProgrammableNonFungibleEdition)
386 ) {
387 return cpi_transfer(args, signer_seeds);
389 }
390
391 let ctx = CpiContext::new(
394 args.spl_token_program.to_account_info(),
395 TransferChecked {
396 from: args.source_ata.to_account_info(),
397 to: args.destination_ata.to_account_info(),
398 authority: args.source.to_account_info(),
399 mint: args.mint.to_account_info(),
400 },
401 );
402
403 if let Some(signer_seeds) = signer_seeds {
404 token_interface::transfer_checked(ctx.with_signer(signer_seeds), 1, 0)
405 } else {
406 token_interface::transfer_checked(ctx, 1, 0)
407 }
408}