metaboss_lib/transfer/
mod.rs

1use anyhow::Result;
2use mpl_token_metadata::{
3    instructions::TransferV1Builder,
4    types::{AuthorizationData, ProgrammableConfig, TokenStandard},
5};
6use solana_client::rpc_client::RpcClient;
7use solana_sdk::{
8    signature::{Keypair, Signature},
9    signer::Signer,
10};
11
12use crate::{data::Asset, decode::ToPubkey, transaction::send_and_confirm_tx};
13
14pub enum TransferAssetArgs<'a, P: ToPubkey> {
15    V1 {
16        payer: Option<&'a Keypair>,
17        authority: &'a Keypair,
18        mint: P,
19        source_owner: P,
20        source_token: P,
21        destination_owner: P,
22        destination_token: P,
23        amount: u64,
24        authorization_data: Option<AuthorizationData>,
25    },
26}
27
28pub fn transfer_asset<P: ToPubkey>(
29    client: &RpcClient,
30    args: TransferAssetArgs<P>,
31) -> Result<Signature> {
32    match args {
33        TransferAssetArgs::V1 { .. } => transfer_asset_v1(client, args),
34    }
35}
36
37fn transfer_asset_v1<P: ToPubkey>(
38    client: &RpcClient,
39    args: TransferAssetArgs<P>,
40) -> Result<Signature> {
41    let TransferAssetArgs::V1 {
42        payer,
43        authority,
44        mint,
45        source_owner,
46        source_token,
47        destination_owner,
48        destination_token,
49        amount,
50        authorization_data,
51    } = args;
52
53    let mint = mint.to_pubkey()?;
54    let source_owner = source_owner.to_pubkey()?;
55    let source_token = source_token.to_pubkey()?;
56    let destination_owner = destination_owner.to_pubkey()?;
57    let destination_token = destination_token.to_pubkey()?;
58
59    let mut asset = Asset::new(mint);
60    let payer = payer.unwrap_or(authority);
61
62    let mut transfer_builder = TransferV1Builder::new();
63    transfer_builder
64        .payer(payer.pubkey())
65        .authority(authority.pubkey())
66        .token(source_token)
67        .token_owner(source_owner)
68        .destination_token(destination_token)
69        .destination_owner(destination_owner)
70        .mint(asset.mint)
71        .metadata(asset.metadata)
72        .amount(amount);
73
74    if let Some(data) = authorization_data {
75        transfer_builder.authorization_data(data);
76    }
77
78    let md = asset.get_metadata(client)?;
79
80    if matches!(
81        md.token_standard,
82        Some(TokenStandard::ProgrammableNonFungible)
83    ) {
84        // Always need the token records for pNFTs.
85        let source_token_record = asset.get_token_record(&source_token);
86        let destination_token_record = asset.get_token_record(&destination_token);
87        transfer_builder
88            .token_record(Some(source_token_record))
89            .destination_token_record(Some(destination_token_record));
90
91        // If the asset's metadata account has auth rules set, we need to pass the
92        // account in.
93        if let Some(ProgrammableConfig::V1 {
94            rule_set: Some(auth_rules),
95        }) = md.programmable_config
96        {
97            transfer_builder.authorization_rules_program(Some(mpl_token_auth_rules::ID));
98            transfer_builder.authorization_rules(Some(auth_rules));
99        }
100    }
101
102    if matches!(
103        md.token_standard,
104        Some(
105            TokenStandard::NonFungible
106                | TokenStandard::NonFungibleEdition
107                | TokenStandard::ProgrammableNonFungible
108        ) | None
109    ) {
110        asset.add_edition();
111        transfer_builder.edition(asset.edition);
112    }
113
114    let transfer_ix = transfer_builder.instruction();
115
116    send_and_confirm_tx(client, &[payer, authority], &[transfer_ix])
117}