Skip to content

Commit

Permalink
token-2022: add new offchain extra metas helper
Browse files Browse the repository at this point in the history
  • Loading branch information
buffalojoec committed Jan 9, 2024
1 parent d3f899c commit 42aeb0d
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 0 deletions.
1 change: 1 addition & 0 deletions token/client/src/token.rs
Original file line number Diff line number Diff line change
Expand Up @@ -938,6 +938,7 @@ where
if let Some(transfer_hook_accounts) = &self.transfer_hook_accounts {
instruction.accounts.extend(transfer_hook_accounts.clone());
} else {
#[allow(deprecated)]
offchain::resolve_extra_transfer_account_metas(
&mut instruction,
|address| {
Expand Down
97 changes: 97 additions & 0 deletions token/program-2022/src/offchain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ use {
/// (source, mint, destination, authority, and validation state), wheras the
/// transfer instruction only requires four (source, mint, destination, and
/// authority) in addition to `n` number of multisig authorities.
#[deprecated(
since = "1.1.0",
note = "Please use `create_transfer_instruction_with_extra_metas` instead"
)]
pub async fn resolve_extra_transfer_account_metas<F, Fut>(
instruction: &mut Instruction,
fetch_account_data_fn: F,
Expand Down Expand Up @@ -92,3 +96,96 @@ where
}
Ok(())
}

/// Offchain helper to create a `TransferChecked` instruction with all
/// additional required account metas.
///
/// To be client-agnostic and to avoid pulling in the full solana-sdk, this
/// simply takes a function that will return its data as `Future<Vec<u8>>` for
/// the given address. Can be called in the following way:
///
/// ```rust,ignore
/// use futures_util::TryFutureExt;
/// use solana_client::nonblocking::rpc_client::RpcClient;
/// use solana_program::pubkey::Pubkey;
///
/// let mint = Pubkey::new_unique();
/// let client = RpcClient::new_mock("succeeds".to_string());
/// let mut account_metas = vec![];
///
/// get_extra_transfer_account_metas(
/// &mut account_metas,
/// |address| self.client.get_account(&address).map_ok(|opt| opt.map(|acc| acc.data)),
/// &mint,
/// ).await?;
/// ```
/// Note that this offchain helper will build a new `Execute` instruction,
/// resolve the extra account metas, and then add them to the transfer
/// instruction. This is because the extra account metas are configured
/// specifically for the `Execute` instruction, which requires five accounts
/// (source, mint, destination, authority, and validation state), wheras the
/// transfer instruction only requires four (source, mint, destination, and
/// authority) in addition to `n` number of multisig authorities.
pub async fn create_transfer_instruction_with_extra_metas<F, Fut>(
token_program_id: &Pubkey,
source_pubkey: &Pubkey,
mint_pubkey: &Pubkey,
destination_pubkey: &Pubkey,
authority_pubkey: &Pubkey,
signer_pubkeys: &[&Pubkey],
amount: u64,
decimals: u8,
fetch_account_data_fn: F,
) -> Result<Instruction, AccountFetchError>
where
F: Fn(Pubkey) -> Fut,
Fut: Future<Output = AccountDataResult>,
{
let mut transfer_ix = crate::instruction::transfer_checked(
token_program_id,
source_pubkey,
mint_pubkey,
destination_pubkey,
authority_pubkey,
signer_pubkeys,
amount,
decimals,
)?;

if token_program_id == &crate::id() {
let mint_data = fetch_account_data_fn(*mint_pubkey)
.await?
.ok_or(ProgramError::InvalidAccountData)?;
let mint = StateWithExtensions::<Mint>::unpack(&mint_data)?;

if let Some(program_id) = transfer_hook::get_program_id(&mint) {
// Convert the transfer instruction into an `Execute` instruction,
// then resolve the extra account metas as configured in the validation
// account data, then finally add the extra account metas to the original
// transfer instruction.
let mut execute_ix = spl_transfer_hook_interface::instruction::execute(
&program_id,
source_pubkey,
mint_pubkey,
destination_pubkey,
authority_pubkey,
&get_extra_account_metas_address(mint_pubkey, &program_id),
amount,
);

resolve_extra_account_metas(
&mut execute_ix,
fetch_account_data_fn,
mint_pubkey,
&program_id,
)
.await?;

transfer_ix
.accounts
.extend_from_slice(&execute_ix.accounts[5..]);
}
}

Ok(transfer_ix)
}

0 comments on commit 42aeb0d

Please sign in to comment.