use {
crate::client::{ProgramClient, ProgramClientError, SendTransaction, SimulateTransaction},
futures::{future::join_all, try_join},
futures_util::TryFutureExt,
solana_program_test::tokio::time,
solana_sdk::{
account::Account as BaseAccount,
hash::Hash,
instruction::{AccountMeta, Instruction},
message::Message,
program_error::ProgramError,
program_pack::Pack,
pubkey::Pubkey,
signer::{signers::Signers, Signer, SignerError},
system_instruction,
transaction::Transaction,
},
spl_associated_token_account::{
get_associated_token_address_with_program_id, instruction::create_associated_token_account,
instruction::create_associated_token_account_idempotent,
},
spl_token_2022::{
extension::{
confidential_transfer::{
self,
account_info::{
ApplyPendingBalanceAccountInfo, EmptyAccountAccountInfo, TransferAccountInfo,
WithdrawAccountInfo,
},
ciphertext_extraction::SourceDecryptHandles,
instruction::TransferSplitContextStateAccounts,
ConfidentialTransferAccount, DecryptableBalance,
},
confidential_transfer_fee::{
self, account_info::WithheldTokensInfo, ConfidentialTransferFeeAmount,
ConfidentialTransferFeeConfig,
},
cpi_guard, default_account_state, interest_bearing_mint, memo_transfer,
metadata_pointer, transfer_fee, transfer_hook, BaseStateWithExtensions, ExtensionType,
StateWithExtensionsOwned,
},
instruction, offchain,
proof::ProofLocation,
solana_zk_token_sdk::{
encryption::{
auth_encryption::AeKey,
elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey, ElGamalSecretKey},
},
instruction::*,
zk_token_elgamal::pod::ElGamalPubkey as PodElGamalPubkey,
zk_token_proof_instruction::{self, ContextStateInfo, ProofInstruction},
zk_token_proof_program,
zk_token_proof_state::ProofContextState,
},
state::{Account, AccountState, Mint, Multisig},
},
spl_token_metadata_interface::state::{Field, TokenMetadata},
std::{
fmt, io,
mem::size_of,
sync::{Arc, RwLock},
time::{Duration, Instant},
},
thiserror::Error,
};
#[cfg(feature = "proof-program")]
use {
solana_sdk::epoch_info::EpochInfo,
spl_token_2022::solana_zk_token_sdk::{
encryption::{auth_encryption::*, elgamal::*},
instruction::transfer_with_fee::FeeParameters,
},
std::convert::TryInto,
};
#[derive(Error, Debug)]
pub enum TokenError {
#[error("client error: {0}")]
Client(ProgramClientError),
#[error("program error: {0}")]
Program(#[from] ProgramError),
#[error("account not found")]
AccountNotFound,
#[error("invalid account owner")]
AccountInvalidOwner,
#[error("invalid account mint")]
AccountInvalidMint,
#[error("invalid associated account address")]
AccountInvalidAssociatedAddress,
#[error("invalid auxiliary account address")]
AccountInvalidAuxiliaryAddress,
#[error("proof generation")]
ProofGeneration,
#[error("maximum deposit transfer amount exceeded")]
MaximumDepositTransferAmountExceeded,
#[error("encryption key error")]
Key(SignerError),
#[error("account decryption failed")]
AccountDecryption,
#[error("not enough funds in account")]
NotEnoughFunds,
#[error("missing memo signer")]
MissingMemoSigner,
#[error("decimals required, but missing")]
MissingDecimals,
#[error("decimals specified, but incorrect")]
InvalidDecimals,
}
impl PartialEq for TokenError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Client(ref a), Self::Client(ref b)) => a.to_string() == b.to_string(),
(Self::Program(ref a), Self::Program(ref b)) => a == b,
(Self::AccountNotFound, Self::AccountNotFound) => true,
(Self::AccountInvalidOwner, Self::AccountInvalidOwner) => true,
(Self::AccountInvalidMint, Self::AccountInvalidMint) => true,
(Self::AccountInvalidAssociatedAddress, Self::AccountInvalidAssociatedAddress) => true,
(Self::AccountInvalidAuxiliaryAddress, Self::AccountInvalidAuxiliaryAddress) => true,
(Self::ProofGeneration, Self::ProofGeneration) => true,
(
Self::MaximumDepositTransferAmountExceeded,
Self::MaximumDepositTransferAmountExceeded,
) => true,
(Self::AccountDecryption, Self::AccountDecryption) => true,
(Self::NotEnoughFunds, Self::NotEnoughFunds) => true,
(Self::MissingMemoSigner, Self::MissingMemoSigner) => true,
(Self::MissingDecimals, Self::MissingDecimals) => true,
(Self::InvalidDecimals, Self::InvalidDecimals) => true,
_ => false,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum ExtensionInitializationParams {
ConfidentialTransferMint {
authority: Option<Pubkey>,
auto_approve_new_accounts: bool,
auditor_elgamal_pubkey: Option<PodElGamalPubkey>,
},
DefaultAccountState {
state: AccountState,
},
MintCloseAuthority {
close_authority: Option<Pubkey>,
},
TransferFeeConfig {
transfer_fee_config_authority: Option<Pubkey>,
withdraw_withheld_authority: Option<Pubkey>,
transfer_fee_basis_points: u16,
maximum_fee: u64,
},
InterestBearingConfig {
rate_authority: Option<Pubkey>,
rate: i16,
},
NonTransferable,
PermanentDelegate {
delegate: Pubkey,
},
TransferHook {
authority: Option<Pubkey>,
program_id: Option<Pubkey>,
},
MetadataPointer {
authority: Option<Pubkey>,
metadata_address: Option<Pubkey>,
},
ConfidentialTransferFeeConfig {
authority: Option<Pubkey>,
withdraw_withheld_authority_elgamal_pubkey: PodElGamalPubkey,
},
}
impl ExtensionInitializationParams {
pub fn extension(&self) -> ExtensionType {
match self {
Self::ConfidentialTransferMint { .. } => ExtensionType::ConfidentialTransferMint,
Self::DefaultAccountState { .. } => ExtensionType::DefaultAccountState,
Self::MintCloseAuthority { .. } => ExtensionType::MintCloseAuthority,
Self::TransferFeeConfig { .. } => ExtensionType::TransferFeeConfig,
Self::InterestBearingConfig { .. } => ExtensionType::InterestBearingConfig,
Self::NonTransferable => ExtensionType::NonTransferable,
Self::PermanentDelegate { .. } => ExtensionType::PermanentDelegate,
Self::TransferHook { .. } => ExtensionType::TransferHook,
Self::MetadataPointer { .. } => ExtensionType::MetadataPointer,
Self::ConfidentialTransferFeeConfig { .. } => {
ExtensionType::ConfidentialTransferFeeConfig
}
}
}
pub fn instruction(
self,
token_program_id: &Pubkey,
mint: &Pubkey,
) -> Result<Instruction, ProgramError> {
match self {
Self::ConfidentialTransferMint {
authority,
auto_approve_new_accounts,
auditor_elgamal_pubkey,
} => confidential_transfer::instruction::initialize_mint(
token_program_id,
mint,
authority,
auto_approve_new_accounts,
auditor_elgamal_pubkey,
),
Self::DefaultAccountState { state } => {
default_account_state::instruction::initialize_default_account_state(
token_program_id,
mint,
&state,
)
}
Self::MintCloseAuthority { close_authority } => {
instruction::initialize_mint_close_authority(
token_program_id,
mint,
close_authority.as_ref(),
)
}
Self::TransferFeeConfig {
transfer_fee_config_authority,
withdraw_withheld_authority,
transfer_fee_basis_points,
maximum_fee,
} => transfer_fee::instruction::initialize_transfer_fee_config(
token_program_id,
mint,
transfer_fee_config_authority.as_ref(),
withdraw_withheld_authority.as_ref(),
transfer_fee_basis_points,
maximum_fee,
),
Self::InterestBearingConfig {
rate_authority,
rate,
} => interest_bearing_mint::instruction::initialize(
token_program_id,
mint,
rate_authority,
rate,
),
Self::NonTransferable => {
instruction::initialize_non_transferable_mint(token_program_id, mint)
}
Self::PermanentDelegate { delegate } => {
instruction::initialize_permanent_delegate(token_program_id, mint, &delegate)
}
Self::TransferHook {
authority,
program_id,
} => transfer_hook::instruction::initialize(
token_program_id,
mint,
authority,
program_id,
),
Self::MetadataPointer {
authority,
metadata_address,
} => metadata_pointer::instruction::initialize(
token_program_id,
mint,
authority,
metadata_address,
),
Self::ConfidentialTransferFeeConfig {
authority,
withdraw_withheld_authority_elgamal_pubkey,
} => {
confidential_transfer_fee::instruction::initialize_confidential_transfer_fee_config(
token_program_id,
mint,
authority,
withdraw_withheld_authority_elgamal_pubkey,
)
}
}
}
}
pub type TokenResult<T> = Result<T, TokenError>;
#[derive(Debug)]
struct TokenMemo {
text: String,
signers: Vec<Pubkey>,
}
impl TokenMemo {
pub fn to_instruction(&self) -> Instruction {
spl_memo::build_memo(
self.text.as_bytes(),
&self.signers.iter().collect::<Vec<_>>(),
)
}
}
pub struct Token<T> {
client: Arc<dyn ProgramClient<T>>,
pubkey: Pubkey, decimals: Option<u8>,
payer: Arc<dyn Signer>,
program_id: Pubkey,
nonce_account: Option<Pubkey>,
nonce_authority: Option<Arc<dyn Signer>>,
nonce_blockhash: Option<Hash>,
memo: Arc<RwLock<Option<TokenMemo>>>,
transfer_hook_accounts: Option<Vec<AccountMeta>>,
}
impl<T> fmt::Debug for Token<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Token")
.field("pubkey", &self.pubkey)
.field("decimals", &self.decimals)
.field("payer", &self.payer.pubkey())
.field("program_id", &self.program_id)
.field("nonce_account", &self.nonce_account)
.field(
"nonce_authority",
&self.nonce_authority.as_ref().map(|s| s.pubkey()),
)
.field("nonce_blockhash", &self.nonce_blockhash)
.field("memo", &self.memo.read().unwrap())
.field("transfer_hook_accounts", &self.transfer_hook_accounts)
.finish()
}
}
fn native_mint(program_id: &Pubkey) -> Pubkey {
if program_id == &spl_token_2022::id() {
spl_token_2022::native_mint::id()
} else if program_id == &spl_token::id() {
spl_token::native_mint::id()
} else {
panic!("Unrecognized token program id: {}", program_id);
}
}
fn native_mint_decimals(program_id: &Pubkey) -> u8 {
if program_id == &spl_token_2022::id() {
spl_token_2022::native_mint::DECIMALS
} else if program_id == &spl_token::id() {
spl_token::native_mint::DECIMALS
} else {
panic!("Unrecognized token program id: {}", program_id);
}
}
impl<T> Token<T>
where
T: SendTransaction + SimulateTransaction,
{
pub fn new(
client: Arc<dyn ProgramClient<T>>,
program_id: &Pubkey,
address: &Pubkey,
decimals: Option<u8>,
payer: Arc<dyn Signer>,
) -> Self {
Token {
client,
pubkey: *address,
decimals,
payer,
program_id: *program_id,
nonce_account: None,
nonce_authority: None,
nonce_blockhash: None,
memo: Arc::new(RwLock::new(None)),
transfer_hook_accounts: None,
}
}
pub fn new_native(
client: Arc<dyn ProgramClient<T>>,
program_id: &Pubkey,
payer: Arc<dyn Signer>,
) -> Self {
Self::new(
client,
program_id,
&native_mint(program_id),
Some(native_mint_decimals(program_id)),
payer,
)
}
pub fn is_native(&self) -> bool {
self.pubkey == native_mint(&self.program_id)
}
pub fn get_address(&self) -> &Pubkey {
&self.pubkey
}
pub fn with_payer(mut self, payer: Arc<dyn Signer>) -> Self {
self.payer = payer;
self
}
pub fn with_nonce(
mut self,
nonce_account: &Pubkey,
nonce_authority: Arc<dyn Signer>,
nonce_blockhash: &Hash,
) -> Self {
self.nonce_account = Some(*nonce_account);
self.nonce_authority = Some(nonce_authority);
self.nonce_blockhash = Some(*nonce_blockhash);
self.transfer_hook_accounts = Some(vec![]);
self
}
pub fn with_transfer_hook_accounts(mut self, transfer_hook_accounts: Vec<AccountMeta>) -> Self {
self.transfer_hook_accounts = Some(transfer_hook_accounts);
self
}
pub fn with_memo<M: AsRef<str>>(&self, memo: M, signers: Vec<Pubkey>) -> &Self {
let mut w_memo = self.memo.write().unwrap();
*w_memo = Some(TokenMemo {
text: memo.as_ref().to_string(),
signers,
});
self
}
pub async fn get_new_latest_blockhash(&self) -> TokenResult<Hash> {
let blockhash = self
.client
.get_latest_blockhash()
.await
.map_err(TokenError::Client)?;
let start = Instant::now();
let mut num_retries = 0;
while start.elapsed().as_secs() < 5 {
let new_blockhash = self
.client
.get_latest_blockhash()
.await
.map_err(TokenError::Client)?;
if new_blockhash != blockhash {
return Ok(new_blockhash);
}
time::sleep(Duration::from_millis(200)).await;
num_retries += 1;
}
Err(TokenError::Client(Box::new(io::Error::new(
io::ErrorKind::Other,
format!(
"Unable to get new blockhash after {}ms (retried {} times), stuck at {}",
start.elapsed().as_millis(),
num_retries,
blockhash
),
))))
}
fn get_multisig_signers<'a>(
&self,
authority: &Pubkey,
signing_pubkeys: &'a [Pubkey],
) -> Vec<&'a Pubkey> {
if signing_pubkeys == [*authority] {
vec![]
} else {
signing_pubkeys.iter().collect::<Vec<_>>()
}
}
async fn construct_tx<S: Signers>(
&self,
token_instructions: &[Instruction],
signing_keypairs: &S,
) -> TokenResult<Transaction> {
let mut instructions = vec![];
let payer_key = self.payer.pubkey();
let fee_payer = Some(&payer_key);
{
let mut w_memo = self.memo.write().unwrap();
if let Some(memo) = w_memo.take() {
let signing_pubkeys = signing_keypairs.pubkeys();
if !memo
.signers
.iter()
.all(|signer| signing_pubkeys.contains(signer))
{
return Err(TokenError::MissingMemoSigner);
}
instructions.push(memo.to_instruction());
}
}
instructions.extend_from_slice(token_instructions);
let (message, blockhash) =
if let (Some(nonce_account), Some(nonce_authority), Some(nonce_blockhash)) = (
self.nonce_account,
&self.nonce_authority,
self.nonce_blockhash,
) {
let mut message = Message::new_with_nonce(
token_instructions.to_vec(),
fee_payer,
&nonce_account,
&nonce_authority.pubkey(),
);
message.recent_blockhash = nonce_blockhash;
(message, nonce_blockhash)
} else {
let latest_blockhash = self
.client
.get_latest_blockhash()
.await
.map_err(TokenError::Client)?;
(
Message::new_with_blockhash(&instructions, fee_payer, &latest_blockhash),
latest_blockhash,
)
};
let mut transaction = Transaction::new_unsigned(message);
transaction
.try_partial_sign(&vec![self.payer.clone()], blockhash)
.map_err(|error| TokenError::Client(error.into()))?;
if let Some(nonce_authority) = &self.nonce_authority {
transaction
.try_partial_sign(&vec![nonce_authority.clone()], blockhash)
.map_err(|error| TokenError::Client(error.into()))?;
}
transaction
.try_partial_sign(signing_keypairs, blockhash)
.map_err(|error| TokenError::Client(error.into()))?;
Ok(transaction)
}
pub async fn simulate_ixs<S: Signers>(
&self,
token_instructions: &[Instruction],
signing_keypairs: &S,
) -> TokenResult<T::SimulationOutput> {
let transaction = self
.construct_tx(token_instructions, signing_keypairs)
.await?;
self.client
.simulate_transaction(&transaction)
.await
.map_err(TokenError::Client)
}
pub async fn process_ixs<S: Signers>(
&self,
token_instructions: &[Instruction],
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let transaction = self
.construct_tx(token_instructions, signing_keypairs)
.await?;
self.client
.send_transaction(&transaction)
.await
.map_err(TokenError::Client)
}
#[allow(clippy::too_many_arguments)]
pub async fn create_mint<'a, S: Signers>(
&self,
mint_authority: &'a Pubkey,
freeze_authority: Option<&'a Pubkey>,
extension_initialization_params: Vec<ExtensionInitializationParams>,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let decimals = self.decimals.ok_or(TokenError::MissingDecimals)?;
let extension_types = extension_initialization_params
.iter()
.map(|e| e.extension())
.collect::<Vec<_>>();
let space = ExtensionType::try_calculate_account_len::<Mint>(&extension_types)?;
let mut instructions = vec![system_instruction::create_account(
&self.payer.pubkey(),
&self.pubkey,
self.client
.get_minimum_balance_for_rent_exemption(space)
.await
.map_err(TokenError::Client)?,
space as u64,
&self.program_id,
)];
for params in extension_initialization_params {
instructions.push(params.instruction(&self.program_id, &self.pubkey)?);
}
instructions.push(instruction::initialize_mint(
&self.program_id,
&self.pubkey,
mint_authority,
freeze_authority,
decimals,
)?);
self.process_ixs(&instructions, signing_keypairs).await
}
pub async fn create_native_mint(
client: Arc<dyn ProgramClient<T>>,
program_id: &Pubkey,
payer: Arc<dyn Signer>,
) -> TokenResult<Self> {
let token = Self::new_native(client, program_id, payer);
token
.process_ixs::<[&dyn Signer; 0]>(
&[instruction::create_native_mint(
program_id,
&token.payer.pubkey(),
)?],
&[],
)
.await?;
Ok(token)
}
pub async fn create_multisig(
&self,
account: &dyn Signer,
multisig_members: &[&Pubkey],
minimum_signers: u8,
) -> TokenResult<T::Output> {
let instructions = vec![
system_instruction::create_account(
&self.payer.pubkey(),
&account.pubkey(),
self.client
.get_minimum_balance_for_rent_exemption(Multisig::LEN)
.await
.map_err(TokenError::Client)?,
Multisig::LEN as u64,
&self.program_id,
),
instruction::initialize_multisig(
&self.program_id,
&account.pubkey(),
multisig_members,
minimum_signers,
)?,
];
self.process_ixs(&instructions, &[account]).await
}
pub fn get_associated_token_address(&self, owner: &Pubkey) -> Pubkey {
get_associated_token_address_with_program_id(owner, &self.pubkey, &self.program_id)
}
pub async fn create_associated_token_account(&self, owner: &Pubkey) -> TokenResult<T::Output> {
self.process_ixs::<[&dyn Signer; 0]>(
&[create_associated_token_account(
&self.payer.pubkey(),
owner,
&self.pubkey,
&self.program_id,
)],
&[],
)
.await
}
pub async fn create_auxiliary_token_account(
&self,
account: &dyn Signer,
owner: &Pubkey,
) -> TokenResult<T::Output> {
self.create_auxiliary_token_account_with_extension_space(account, owner, vec![])
.await
}
pub async fn create_auxiliary_token_account_with_extension_space(
&self,
account: &dyn Signer,
owner: &Pubkey,
extensions: Vec<ExtensionType>,
) -> TokenResult<T::Output> {
let state = self.get_mint_info().await?;
let mint_extensions: Vec<ExtensionType> = state.get_extension_types()?;
let mut required_extensions =
ExtensionType::get_required_init_account_extensions(&mint_extensions);
for extension_type in extensions.into_iter() {
if !required_extensions.contains(&extension_type) {
required_extensions.push(extension_type);
}
}
let space = ExtensionType::try_calculate_account_len::<Account>(&required_extensions)?;
let mut instructions = vec![system_instruction::create_account(
&self.payer.pubkey(),
&account.pubkey(),
self.client
.get_minimum_balance_for_rent_exemption(space)
.await
.map_err(TokenError::Client)?,
space as u64,
&self.program_id,
)];
if required_extensions.contains(&ExtensionType::ImmutableOwner) {
instructions.push(instruction::initialize_immutable_owner(
&self.program_id,
&account.pubkey(),
)?)
}
instructions.push(instruction::initialize_account(
&self.program_id,
&account.pubkey(),
&self.pubkey,
owner,
)?);
self.process_ixs(&instructions, &[account]).await
}
pub async fn get_account(&self, account: Pubkey) -> TokenResult<BaseAccount> {
self.client
.get_account(account)
.await
.map_err(TokenError::Client)?
.ok_or(TokenError::AccountNotFound)
}
fn unpack_mint_info(
&self,
account: BaseAccount,
) -> TokenResult<StateWithExtensionsOwned<Mint>> {
if account.owner != self.program_id {
return Err(TokenError::AccountInvalidOwner);
}
let mint_result =
StateWithExtensionsOwned::<Mint>::unpack(account.data).map_err(Into::into);
if let (Ok(mint), Some(decimals)) = (&mint_result, self.decimals) {
if decimals != mint.base.decimals {
return Err(TokenError::InvalidDecimals);
}
}
mint_result
}
pub async fn get_mint_info(&self) -> TokenResult<StateWithExtensionsOwned<Mint>> {
let account = self.get_account(self.pubkey).await?;
self.unpack_mint_info(account)
}
pub async fn get_account_info(
&self,
account: &Pubkey,
) -> TokenResult<StateWithExtensionsOwned<Account>> {
let account = self.get_account(*account).await?;
if account.owner != self.program_id {
return Err(TokenError::AccountInvalidOwner);
}
let account = StateWithExtensionsOwned::<Account>::unpack(account.data)?;
if account.base.mint != *self.get_address() {
return Err(TokenError::AccountInvalidMint);
}
Ok(account)
}
pub async fn get_or_create_associated_account_info(
&self,
owner: &Pubkey,
) -> TokenResult<StateWithExtensionsOwned<Account>> {
let account = self.get_associated_token_address(owner);
match self.get_account_info(&account).await {
Ok(account) => Ok(account),
Err(TokenError::AccountNotFound) | Err(TokenError::AccountInvalidOwner) => {
self.create_associated_token_account(owner).await?;
self.get_account_info(&account).await
}
Err(error) => Err(error),
}
}
pub async fn set_authority<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
new_authority: Option<&Pubkey>,
authority_type: instruction::AuthorityType,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[instruction::set_authority(
&self.program_id,
account,
new_authority,
authority_type,
authority,
&multisig_signers,
)?],
signing_keypairs,
)
.await
}
pub async fn mint_to<S: Signers>(
&self,
destination: &Pubkey,
authority: &Pubkey,
amount: u64,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
let instructions = if let Some(decimals) = self.decimals {
[instruction::mint_to_checked(
&self.program_id,
&self.pubkey,
destination,
authority,
&multisig_signers,
amount,
decimals,
)?]
} else {
[instruction::mint_to(
&self.program_id,
&self.pubkey,
destination,
authority,
&multisig_signers,
amount,
)?]
};
self.process_ixs(&instructions, signing_keypairs).await
}
#[allow(clippy::too_many_arguments)]
pub async fn transfer<S: Signers>(
&self,
source: &Pubkey,
destination: &Pubkey,
authority: &Pubkey,
amount: u64,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
let mut instruction = if let Some(decimals) = self.decimals {
instruction::transfer_checked(
&self.program_id,
source,
&self.pubkey,
destination,
authority,
&multisig_signers,
amount,
decimals,
)?
} else {
#[allow(deprecated)]
instruction::transfer(
&self.program_id,
source,
destination,
authority,
&multisig_signers,
amount,
)?
};
if let Some(transfer_hook_accounts) = &self.transfer_hook_accounts {
instruction.accounts.extend(transfer_hook_accounts.clone());
} else {
offchain::resolve_extra_transfer_account_metas(
&mut instruction,
|address| {
self.client
.get_account(address)
.map_ok(|opt| opt.map(|acc| acc.data))
},
self.get_address(),
)
.await
.map_err(|_| TokenError::AccountNotFound)?;
};
self.process_ixs(&[instruction], signing_keypairs).await
}
#[allow(clippy::too_many_arguments)]
pub async fn create_recipient_associated_account_and_transfer<S: Signers>(
&self,
source: &Pubkey,
destination: &Pubkey,
destination_owner: &Pubkey,
authority: &Pubkey,
amount: u64,
fee: Option<u64>,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
if *destination != self.get_associated_token_address(destination_owner) {
return Err(TokenError::AccountInvalidAssociatedAddress);
}
let mut instructions = vec![
(create_associated_token_account_idempotent(
&self.payer.pubkey(),
destination_owner,
&self.pubkey,
&self.program_id,
)),
];
if let Some(fee) = fee {
let decimals = self.decimals.ok_or(TokenError::MissingDecimals)?;
instructions.push(transfer_fee::instruction::transfer_checked_with_fee(
&self.program_id,
source,
&self.pubkey,
destination,
authority,
&multisig_signers,
amount,
decimals,
fee,
)?);
} else if let Some(decimals) = self.decimals {
instructions.push(instruction::transfer_checked(
&self.program_id,
source,
&self.pubkey,
destination,
authority,
&multisig_signers,
amount,
decimals,
)?);
} else {
#[allow(deprecated)]
instructions.push(instruction::transfer(
&self.program_id,
source,
destination,
authority,
&multisig_signers,
amount,
)?);
}
self.process_ixs(&instructions, signing_keypairs).await
}
#[allow(clippy::too_many_arguments)]
pub async fn transfer_with_fee<S: Signers>(
&self,
source: &Pubkey,
destination: &Pubkey,
authority: &Pubkey,
amount: u64,
fee: u64,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
let decimals = self.decimals.ok_or(TokenError::MissingDecimals)?;
self.process_ixs(
&[transfer_fee::instruction::transfer_checked_with_fee(
&self.program_id,
source,
&self.pubkey,
destination,
authority,
&multisig_signers,
amount,
decimals,
fee,
)?],
signing_keypairs,
)
.await
}
pub async fn burn<S: Signers>(
&self,
source: &Pubkey,
authority: &Pubkey,
amount: u64,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
let instructions = if let Some(decimals) = self.decimals {
[instruction::burn_checked(
&self.program_id,
source,
&self.pubkey,
authority,
&multisig_signers,
amount,
decimals,
)?]
} else {
[instruction::burn(
&self.program_id,
source,
&self.pubkey,
authority,
&multisig_signers,
amount,
)?]
};
self.process_ixs(&instructions, signing_keypairs).await
}
pub async fn approve<S: Signers>(
&self,
source: &Pubkey,
delegate: &Pubkey,
authority: &Pubkey,
amount: u64,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
let instructions = if let Some(decimals) = self.decimals {
[instruction::approve_checked(
&self.program_id,
source,
&self.pubkey,
delegate,
authority,
&multisig_signers,
amount,
decimals,
)?]
} else {
[instruction::approve(
&self.program_id,
source,
delegate,
authority,
&multisig_signers,
amount,
)?]
};
self.process_ixs(&instructions, signing_keypairs).await
}
pub async fn revoke<S: Signers>(
&self,
source: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[instruction::revoke(
&self.program_id,
source,
authority,
&multisig_signers,
)?],
signing_keypairs,
)
.await
}
pub async fn close_account<S: Signers>(
&self,
account: &Pubkey,
lamports_destination: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
let mut instructions = vec![instruction::close_account(
&self.program_id,
account,
lamports_destination,
authority,
&multisig_signers,
)?];
if let Ok(Some(destination_account)) = self.client.get_account(*lamports_destination).await
{
if let Ok(destination_obj) =
StateWithExtensionsOwned::<Account>::unpack(destination_account.data)
{
if destination_obj.base.is_native() {
instructions.push(instruction::sync_native(
&self.program_id,
lamports_destination,
)?);
}
}
}
self.process_ixs(&instructions, signing_keypairs).await
}
pub async fn empty_and_close_account<S: Signers>(
&self,
account_to_close: &Pubkey,
lamports_destination: &Pubkey,
tokens_destination: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
let account_state = self.get_account_info(account_to_close).await?;
let mut instructions = vec![];
if !self.is_native() && account_state.base.amount > 0 {
if let Some(decimals) = self.decimals {
instructions.push(instruction::transfer_checked(
&self.program_id,
account_to_close,
&self.pubkey,
tokens_destination,
authority,
&multisig_signers,
account_state.base.amount,
decimals,
)?);
} else {
#[allow(deprecated)]
instructions.push(instruction::transfer(
&self.program_id,
account_to_close,
tokens_destination,
authority,
&multisig_signers,
account_state.base.amount,
)?);
}
}
instructions.push(instruction::close_account(
&self.program_id,
account_to_close,
lamports_destination,
authority,
&multisig_signers,
)?);
if let Ok(Some(destination_account)) = self.client.get_account(*lamports_destination).await
{
if let Ok(destination_obj) =
StateWithExtensionsOwned::<Account>::unpack(destination_account.data)
{
if destination_obj.base.is_native() {
instructions.push(instruction::sync_native(
&self.program_id,
lamports_destination,
)?);
}
}
}
self.process_ixs(&instructions, signing_keypairs).await
}
pub async fn freeze<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[instruction::freeze_account(
&self.program_id,
account,
&self.pubkey,
authority,
&multisig_signers,
)?],
signing_keypairs,
)
.await
}
pub async fn thaw<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[instruction::thaw_account(
&self.program_id,
account,
&self.pubkey,
authority,
&multisig_signers,
)?],
signing_keypairs,
)
.await
}
pub async fn wrap<S: Signers>(
&self,
account: &Pubkey,
owner: &Pubkey,
lamports: u64,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let immutable_owner = self.program_id != spl_token::id();
let instructions = self.wrap_ixs(account, owner, lamports, immutable_owner)?;
self.process_ixs(&instructions, signing_keypairs).await
}
pub async fn wrap_with_mutable_ownership<S: Signers>(
&self,
account: &Pubkey,
owner: &Pubkey,
lamports: u64,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let instructions = self.wrap_ixs(account, owner, lamports, false)?;
self.process_ixs(&instructions, signing_keypairs).await
}
fn wrap_ixs(
&self,
account: &Pubkey,
owner: &Pubkey,
lamports: u64,
immutable_owner: bool,
) -> TokenResult<Vec<Instruction>> {
if !self.is_native() {
return Err(TokenError::AccountInvalidMint);
}
let mut instructions = vec![];
if *account == self.get_associated_token_address(owner) {
instructions.push(system_instruction::transfer(owner, account, lamports));
instructions.push(create_associated_token_account(
&self.payer.pubkey(),
owner,
&self.pubkey,
&self.program_id,
));
} else {
let extensions = if immutable_owner {
vec![ExtensionType::ImmutableOwner]
} else {
vec![]
};
let space = ExtensionType::try_calculate_account_len::<Account>(&extensions)?;
instructions.push(system_instruction::create_account(
&self.payer.pubkey(),
account,
lamports,
space as u64,
&self.program_id,
));
if immutable_owner {
instructions.push(instruction::initialize_immutable_owner(
&self.program_id,
account,
)?)
}
instructions.push(instruction::initialize_account(
&self.program_id,
account,
&self.pubkey,
owner,
)?);
};
Ok(instructions)
}
pub async fn sync_native(&self, account: &Pubkey) -> TokenResult<T::Output> {
self.process_ixs::<[&dyn Signer; 0]>(
&[instruction::sync_native(&self.program_id, account)?],
&[],
)
.await
}
pub async fn set_transfer_fee<S: Signers>(
&self,
authority: &Pubkey,
transfer_fee_basis_points: u16,
maximum_fee: u64,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[transfer_fee::instruction::set_transfer_fee(
&self.program_id,
&self.pubkey,
authority,
&multisig_signers,
transfer_fee_basis_points,
maximum_fee,
)?],
signing_keypairs,
)
.await
}
pub async fn set_default_account_state<S: Signers>(
&self,
authority: &Pubkey,
state: &AccountState,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[
default_account_state::instruction::update_default_account_state(
&self.program_id,
&self.pubkey,
authority,
&multisig_signers,
state,
)?,
],
signing_keypairs,
)
.await
}
pub async fn harvest_withheld_tokens_to_mint(
&self,
sources: &[&Pubkey],
) -> TokenResult<T::Output> {
self.process_ixs::<[&dyn Signer; 0]>(
&[transfer_fee::instruction::harvest_withheld_tokens_to_mint(
&self.program_id,
&self.pubkey,
sources,
)?],
&[],
)
.await
}
pub async fn withdraw_withheld_tokens_from_mint<S: Signers>(
&self,
destination: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[
transfer_fee::instruction::withdraw_withheld_tokens_from_mint(
&self.program_id,
&self.pubkey,
destination,
authority,
&multisig_signers,
)?,
],
signing_keypairs,
)
.await
}
pub async fn withdraw_withheld_tokens_from_accounts<S: Signers>(
&self,
destination: &Pubkey,
authority: &Pubkey,
sources: &[&Pubkey],
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[
transfer_fee::instruction::withdraw_withheld_tokens_from_accounts(
&self.program_id,
&self.pubkey,
destination,
authority,
&multisig_signers,
sources,
)?,
],
signing_keypairs,
)
.await
}
pub async fn reallocate<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
extension_types: &[ExtensionType],
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[instruction::reallocate(
&self.program_id,
account,
&self.payer.pubkey(),
authority,
&multisig_signers,
extension_types,
)?],
signing_keypairs,
)
.await
}
pub async fn enable_required_transfer_memos<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[memo_transfer::instruction::enable_required_transfer_memos(
&self.program_id,
account,
authority,
&multisig_signers,
)?],
signing_keypairs,
)
.await
}
pub async fn disable_required_transfer_memos<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[memo_transfer::instruction::disable_required_transfer_memos(
&self.program_id,
account,
authority,
&multisig_signers,
)?],
signing_keypairs,
)
.await
}
pub async fn enable_cpi_guard<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[cpi_guard::instruction::enable_cpi_guard(
&self.program_id,
account,
authority,
&multisig_signers,
)?],
signing_keypairs,
)
.await
}
pub async fn disable_cpi_guard<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[cpi_guard::instruction::disable_cpi_guard(
&self.program_id,
account,
authority,
&multisig_signers,
)?],
signing_keypairs,
)
.await
}
pub async fn update_interest_rate<S: Signers>(
&self,
authority: &Pubkey,
new_rate: i16,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[interest_bearing_mint::instruction::update_rate(
&self.program_id,
self.get_address(),
authority,
&multisig_signers,
new_rate,
)?],
signing_keypairs,
)
.await
}
pub async fn update_transfer_hook_program_id<S: Signers>(
&self,
authority: &Pubkey,
new_program_id: Option<Pubkey>,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[transfer_hook::instruction::update(
&self.program_id,
self.get_address(),
authority,
&multisig_signers,
new_program_id,
)?],
signing_keypairs,
)
.await
}
pub async fn update_metadata_address<S: Signers>(
&self,
authority: &Pubkey,
new_metadata_address: Option<Pubkey>,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[metadata_pointer::instruction::update(
&self.program_id,
self.get_address(),
authority,
&multisig_signers,
new_metadata_address,
)?],
signing_keypairs,
)
.await
}
pub async fn confidential_transfer_update_mint<S: Signers>(
&self,
authority: &Pubkey,
auto_approve_new_account: bool,
auditor_elgamal_pubkey: Option<PodElGamalPubkey>,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[confidential_transfer::instruction::update_mint(
&self.program_id,
&self.pubkey,
authority,
&multisig_signers,
auto_approve_new_account,
auditor_elgamal_pubkey,
)?],
signing_keypairs,
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn confidential_transfer_configure_token_account<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
context_state_account: Option<&Pubkey>,
maximum_pending_balance_credit_counter: Option<u64>,
elgamal_keypair: &ElGamalKeypair,
aes_key: &AeKey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
const DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER: u64 = 65536;
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
let maximum_pending_balance_credit_counter = maximum_pending_balance_credit_counter
.unwrap_or(DEFAULT_MAXIMUM_PENDING_BALANCE_CREDIT_COUNTER);
let proof_data = if context_state_account.is_some() {
None
} else {
Some(
confidential_transfer::instruction::PubkeyValidityData::new(elgamal_keypair)
.map_err(|_| TokenError::ProofGeneration)?,
)
};
let proof_location = if let Some(proof_data_temp) = proof_data.as_ref() {
ProofLocation::InstructionOffset(1.try_into().unwrap(), proof_data_temp)
} else {
let context_state_account = context_state_account.unwrap();
ProofLocation::ContextStateAccount(context_state_account)
};
let decryptable_balance = aes_key.encrypt(0);
self.process_ixs(
&confidential_transfer::instruction::configure_account(
&self.program_id,
account,
&self.pubkey,
decryptable_balance,
maximum_pending_balance_credit_counter,
authority,
&multisig_signers,
proof_location,
)?,
signing_keypairs,
)
.await
}
pub async fn confidential_transfer_approve_account<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[confidential_transfer::instruction::approve_account(
&self.program_id,
account,
&self.pubkey,
authority,
&multisig_signers,
)?],
signing_keypairs,
)
.await
}
pub async fn confidential_transfer_empty_account<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
context_state_account: Option<&Pubkey>,
account_info: Option<EmptyAccountAccountInfo>,
elgamal_keypair: &ElGamalKeypair,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
let account_info = if let Some(account_info) = account_info {
account_info
} else {
let account = self.get_account_info(account).await?;
let confidential_transfer_account =
account.get_extension::<ConfidentialTransferAccount>()?;
EmptyAccountAccountInfo::new(confidential_transfer_account)
};
let proof_data = if context_state_account.is_some() {
None
} else {
Some(
account_info
.generate_proof_data(elgamal_keypair)
.map_err(|_| TokenError::ProofGeneration)?,
)
};
let proof_location = if let Some(proof_data_temp) = proof_data.as_ref() {
ProofLocation::InstructionOffset(1.try_into().unwrap(), proof_data_temp)
} else {
let context_state_account = context_state_account.unwrap();
ProofLocation::ContextStateAccount(context_state_account)
};
self.process_ixs(
&confidential_transfer::instruction::empty_account(
&self.program_id,
account,
authority,
&multisig_signers,
proof_location,
)?,
signing_keypairs,
)
.await
}
#[cfg(feature = "proof-program")]
pub async fn confidential_transfer_get_available_balance<S: Signer>(
&self,
token_account: &Pubkey,
authority: &S,
) -> TokenResult<u64> {
let authenticated_encryption_key =
AeKey::new(authority, token_account).map_err(TokenError::Key)?;
self.confidential_transfer_get_available_balance_with_key(
token_account,
&authenticated_encryption_key,
)
.await
}
#[cfg(feature = "proof-program")]
pub async fn confidential_transfer_get_available_balance_with_key(
&self,
token_account: &Pubkey,
authenticated_encryption_key: &AeKey,
) -> TokenResult<u64> {
let state = self.get_account_info(token_account).await.unwrap();
let extension =
state.get_extension::<confidential_transfer::ConfidentialTransferAccount>()?;
let decryptable_balance_ciphertext: AeCiphertext = extension
.decryptable_available_balance
.try_into()
.map_err(TokenError::Proof)?;
let decryptable_balance = decryptable_balance_ciphertext
.decrypt(authenticated_encryption_key)
.ok_or(TokenError::AccountDecryption)?;
Ok(decryptable_balance)
}
#[cfg(feature = "proof-program")]
pub async fn confidential_transfer_get_pending_balance<S: Signer>(
&self,
token_account: &Pubkey,
authority: &S,
) -> TokenResult<u64> {
let elgamal_keypair =
ElGamalKeypair::new(authority, token_account).map_err(TokenError::Key)?;
self.confidential_transfer_get_pending_balance_with_key(token_account, &elgamal_keypair)
.await
}
#[cfg(feature = "proof-program")]
pub async fn confidential_transfer_get_pending_balance_with_key(
&self,
token_account: &Pubkey,
elgamal_keypair: &ElGamalKeypair,
) -> TokenResult<u64> {
let state = self.get_account_info(token_account).await.unwrap();
let extension =
state.get_extension::<confidential_transfer::ConfidentialTransferAccount>()?;
let pending_balance_lo = extension
.pending_balance_lo
.decrypt(&elgamal_keypair.secret)
.ok_or(TokenError::AccountDecryption)?;
let pending_balance_hi = extension
.pending_balance_hi
.decrypt(&elgamal_keypair.secret)
.ok_or(TokenError::AccountDecryption)?;
let pending_balance = pending_balance_lo
.checked_add(pending_balance_hi << confidential_transfer::PENDING_BALANCE_HI_BIT_LENGTH)
.ok_or(TokenError::AccountDecryption)?;
Ok(pending_balance)
}
#[cfg(feature = "proof-program")]
pub async fn confidential_transfer_get_withheld_amount<S: Signer>(
&self,
withdraw_withheld_authority: &S,
sources: &[&Pubkey],
) -> TokenResult<u64> {
let withdraw_withheld_authority_elgamal_keypair =
ElGamalKeypair::new(withdraw_withheld_authority, &self.pubkey)
.map_err(TokenError::Key)?;
self.confidential_transfer_get_withheld_amount_with_key(
&withdraw_withheld_authority_elgamal_keypair,
sources,
)
.await
}
#[cfg(feature = "proof-program")]
pub async fn confidential_transfer_get_withheld_amount_with_key(
&self,
withdraw_withheld_authority_elgamal_keypair: &ElGamalKeypair,
sources: &[&Pubkey],
) -> TokenResult<u64> {
let mut aggregate_withheld_amount_ciphertext = ElGamalCiphertext::default();
for &source in sources {
let state = self.get_account_info(source).await.unwrap();
let extension =
state.get_extension::<confidential_transfer::ConfidentialTransferAccount>()?;
let withheld_amount_ciphertext: ElGamalCiphertext =
extension.withheld_amount.try_into().unwrap();
aggregate_withheld_amount_ciphertext =
aggregate_withheld_amount_ciphertext + withheld_amount_ciphertext;
}
let aggregate_withheld_amount = aggregate_withheld_amount_ciphertext
.decrypt_u32(&withdraw_withheld_authority_elgamal_keypair.secret)
.ok_or(TokenError::AccountDecryption)?;
Ok(aggregate_withheld_amount)
}
#[cfg(feature = "proof-program")]
pub async fn confidential_transfer_get_elgamal_pubkey<S: Signer>(
&self,
token_account: &Pubkey,
) -> TokenResult<ElGamalPubkey> {
let state = self.get_account_info(token_account).await.unwrap();
let extension =
state.get_extension::<confidential_transfer::ConfidentialTransferAccount>()?;
let elgamal_pubkey = extension
.elgamal_pubkey
.try_into()
.map_err(TokenError::Proof)?;
Ok(elgamal_pubkey)
}
#[cfg(feature = "proof-program")]
pub async fn confidential_transfer_get_auditor_elgamal_pubkey<S: Signer>(
&self,
) -> TokenResult<Option<ElGamalPubkey>> {
let mint_state = self.get_mint_info().await.unwrap();
let ct_mint =
mint_state.get_extension::<confidential_transfer::ConfidentialTransferMint>()?;
let auditor_elgamal_pubkey: Option<ElGamalPubkey> = ct_mint.auditor_elgamal_pubkey.into();
if let Some(elgamal_pubkey) = auditor_elgamal_pubkey {
let elgamal_pubkey: ElGamalPubkey =
elgamal_pubkey.try_into().map_err(TokenError::Proof)?;
Ok(Some(elgamal_pubkey))
} else {
Ok(None)
}
}
#[cfg(feature = "proof-program")]
pub async fn confidential_transfer_get_withdraw_withheld_authority_elgamal_pubkey<S: Signer>(
&self,
) -> TokenResult<Option<ElGamalPubkey>> {
let mint_state = self.get_mint_info().await.unwrap();
let ct_mint =
mint_state.get_extension::<confidential_transfer::ConfidentialTransferMint>()?;
let withdraw_withheld_authority_elgamal_pubkey: Option<ElGamalPubkey> =
ct_mint.withdraw_withheld_authority_elgamal_pubkey.into();
if let Some(elgamal_pubkey) = withdraw_withheld_authority_elgamal_pubkey {
let elgamal_pubkey: ElGamalPubkey =
elgamal_pubkey.try_into().map_err(TokenError::Proof)?;
Ok(Some(elgamal_pubkey))
} else {
Ok(None)
}
}
pub async fn confidential_transfer_deposit<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
amount: u64,
decimals: u8,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[confidential_transfer::instruction::deposit(
&self.program_id,
account,
&self.pubkey,
amount,
decimals,
authority,
&multisig_signers,
)?],
signing_keypairs,
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn confidential_transfer_withdraw<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
context_state_account: Option<&Pubkey>,
withdraw_amount: u64,
decimals: u8,
account_info: Option<WithdrawAccountInfo>,
elgamal_keypair: &ElGamalKeypair,
aes_key: &AeKey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
let account_info = if let Some(account_info) = account_info {
account_info
} else {
let account = self.get_account_info(account).await?;
let confidential_transfer_account =
account.get_extension::<ConfidentialTransferAccount>()?;
WithdrawAccountInfo::new(confidential_transfer_account)
};
let proof_data = if context_state_account.is_some() {
None
} else {
Some(
account_info
.generate_proof_data(withdraw_amount, elgamal_keypair, aes_key)
.map_err(|_| TokenError::ProofGeneration)?,
)
};
let proof_location = if let Some(proof_data_temp) = proof_data.as_ref() {
ProofLocation::InstructionOffset(1.try_into().unwrap(), proof_data_temp)
} else {
let context_state_account = context_state_account.unwrap();
ProofLocation::ContextStateAccount(context_state_account)
};
let new_decryptable_available_balance = account_info
.new_decryptable_available_balance(withdraw_amount, aes_key)
.map_err(|_| TokenError::AccountDecryption)?;
self.process_ixs(
&confidential_transfer::instruction::withdraw(
&self.program_id,
account,
&self.pubkey,
withdraw_amount,
decimals,
new_decryptable_available_balance,
authority,
&multisig_signers,
proof_location,
)?,
signing_keypairs,
)
.await
}
#[allow(clippy::too_many_arguments)]
#[cfg(feature = "proof-program")]
pub async fn confidential_transfer_withdraw_with_key<S: Signer>(
&self,
token_account: &Pubkey,
token_authority: &S,
amount: u64,
decimals: u8,
available_balance: u64,
available_balance_ciphertext: &ElGamalCiphertext,
elgamal_keypair: &ElGamalKeypair,
authenticated_encryption_key: &AeKey,
) -> TokenResult<T::Output> {
let proof_data = confidential_transfer::instruction::WithdrawData::new(
amount,
elgamal_keypair,
available_balance,
available_balance_ciphertext,
)
.map_err(TokenError::Proof)?;
let remaining_balance = available_balance
.checked_sub(amount)
.ok_or(TokenError::NotEnoughFunds)?;
let new_decryptable_available_balance =
authenticated_encryption_key.encrypt(remaining_balance);
self.process_ixs(
&confidential_transfer::instruction::withdraw(
&self.program_id,
token_account,
&self.pubkey,
amount,
decimals,
new_decryptable_available_balance,
&token_authority.pubkey(),
&[],
&proof_data,
)?,
&[token_authority],
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn confidential_transfer_transfer<S: Signers>(
&self,
source_account: &Pubkey,
destination_account: &Pubkey,
source_authority: &Pubkey,
context_state_account: Option<&Pubkey>,
transfer_amount: u64,
account_info: Option<TransferAccountInfo>,
source_elgamal_keypair: &ElGamalKeypair,
source_aes_key: &AeKey,
destination_elgamal_pubkey: &ElGamalPubkey,
auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(source_authority, &signing_pubkeys);
let account_info = if let Some(account_info) = account_info {
account_info
} else {
let account = self.get_account_info(source_account).await?;
let confidential_transfer_account =
account.get_extension::<ConfidentialTransferAccount>()?;
TransferAccountInfo::new(confidential_transfer_account)
};
let proof_data = if context_state_account.is_some() {
None
} else {
Some(
account_info
.generate_transfer_proof_data(
transfer_amount,
source_elgamal_keypair,
source_aes_key,
destination_elgamal_pubkey,
auditor_elgamal_pubkey,
)
.map_err(|_| TokenError::ProofGeneration)?,
)
};
let proof_location = if let Some(proof_data_temp) = proof_data.as_ref() {
ProofLocation::InstructionOffset(1.try_into().unwrap(), proof_data_temp)
} else {
let context_state_account = context_state_account.unwrap();
ProofLocation::ContextStateAccount(context_state_account)
};
let new_decryptable_available_balance = account_info
.new_decryptable_available_balance(transfer_amount, source_aes_key)
.map_err(|_| TokenError::AccountDecryption)?;
self.process_ixs(
&confidential_transfer::instruction::transfer(
&self.program_id,
source_account,
&self.pubkey,
destination_account,
new_decryptable_available_balance,
source_authority,
&multisig_signers,
proof_location,
)?,
signing_keypairs,
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn confidential_transfer_transfer_with_split_proofs<S: Signer>(
&self,
source_account: &Pubkey,
destination_account: &Pubkey,
source_authority: &Pubkey,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
transfer_amount: u64,
account_info: Option<TransferAccountInfo>,
source_aes_key: &AeKey,
source_authority_keypair: &S,
source_decrypt_handles: &SourceDecryptHandles,
) -> TokenResult<T::Output> {
let account_info = if let Some(account_info) = account_info {
account_info
} else {
let account = self.get_account_info(source_account).await?;
let confidential_transfer_account =
account.get_extension::<ConfidentialTransferAccount>()?;
TransferAccountInfo::new(confidential_transfer_account)
};
let new_decryptable_available_balance = account_info
.new_decryptable_available_balance(transfer_amount, source_aes_key)
.map_err(|_| TokenError::AccountDecryption)?;
self.process_ixs(
&[
confidential_transfer::instruction::transfer_with_split_proofs(
&self.program_id,
source_account,
&self.pubkey,
destination_account,
new_decryptable_available_balance.into(),
source_authority,
context_state_accounts,
source_decrypt_handles,
)?,
],
&[source_authority_keypair],
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn confidential_transfer_transfer_with_split_proofs_in_parallel<S: Signer>(
&self,
source_account: &Pubkey,
destination_account: &Pubkey,
source_authority: &Pubkey,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
transfer_amount: u64,
account_info: Option<TransferAccountInfo>,
source_elgamal_keypair: &ElGamalKeypair,
source_aes_key: &AeKey,
destination_elgamal_pubkey: &ElGamalPubkey,
auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
source_authority_keypair: &S,
equality_proof_account_keypair: &S,
ciphertext_validity_proof_account_keypair: &S,
range_proof_account_keypair: &S,
context_state_authority_keypair: Option<&S>,
) -> TokenResult<(T::Output, T::Output)> {
let account_info = if let Some(account_info) = account_info {
account_info
} else {
let account = self.get_account_info(source_account).await?;
let confidential_transfer_account =
account.get_extension::<ConfidentialTransferAccount>()?;
TransferAccountInfo::new(confidential_transfer_account)
};
let (
equality_proof_data,
ciphertext_validity_proof_data,
range_proof_data,
source_decrypt_handles,
) = account_info
.generate_split_transfer_proof_data(
transfer_amount,
source_elgamal_keypair,
source_aes_key,
destination_elgamal_pubkey,
auditor_elgamal_pubkey,
)
.map_err(|_| TokenError::ProofGeneration)?;
let new_decryptable_available_balance = account_info
.new_decryptable_available_balance(transfer_amount, source_aes_key)
.map_err(|_| TokenError::AccountDecryption)?;
let transfer_instruction = confidential_transfer::instruction::transfer_with_split_proofs(
&self.program_id,
source_account,
&self.pubkey,
destination_account,
new_decryptable_available_balance.into(),
source_authority,
context_state_accounts,
&source_decrypt_handles,
)?;
let transfer_with_equality_and_ciphertext_validity = self
.confidential_transfer_equality_and_ciphertext_validity_proof_context_states_and_transfer_parallel(
context_state_accounts,
&equality_proof_data,
&ciphertext_validity_proof_data,
&transfer_instruction,
Some(source_authority_keypair),
equality_proof_account_keypair,
ciphertext_validity_proof_account_keypair,
context_state_authority_keypair,
);
let transfer_with_range_proof = self
.confidential_transfer_range_proof_context_states_and_transfer_parallel(
context_state_accounts,
&range_proof_data,
&transfer_instruction,
Some(source_authority_keypair),
range_proof_account_keypair,
context_state_authority_keypair,
);
try_join!(
transfer_with_equality_and_ciphertext_validity,
transfer_with_range_proof
)
}
#[allow(clippy::too_many_arguments)]
pub async fn confidential_transfer_equality_and_ciphertext_validity_proof_context_states_for_transfer<
S: Signer,
>(
&self,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
equality_proof_data: &CiphertextCommitmentEqualityProofData,
ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData,
source_authority_keypair: Option<&S>,
equality_proof_account_keypair: &S,
ciphertext_validity_proof_account_keypair: &S,
context_state_authority_keypair: Option<&S>,
) -> TokenResult<T::Output> {
self.confidential_transfer_equality_and_ciphertext_validity_proof_context_state_with_optional_transfer(
context_state_accounts,
equality_proof_data,
ciphertext_validity_proof_data,
None,
source_authority_keypair,
equality_proof_account_keypair,
ciphertext_validity_proof_account_keypair,
context_state_authority_keypair,
).await
}
#[allow(clippy::too_many_arguments)]
pub async fn confidential_transfer_equality_and_ciphertext_validity_proof_context_states_and_transfer_parallel<
S: Signer,
>(
&self,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
equality_proof_data: &CiphertextCommitmentEqualityProofData,
ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData,
transfer_instruction: &Instruction,
source_authority_keypair: Option<&S>,
equality_proof_account_keypair: &S,
ciphertext_validity_proof_account_keypair: &S,
context_state_authority_keypair: Option<&S>,
) -> TokenResult<T::Output> {
self.confidential_transfer_equality_and_ciphertext_validity_proof_context_state_with_optional_transfer(
context_state_accounts,
equality_proof_data,
ciphertext_validity_proof_data,
Some(transfer_instruction),
source_authority_keypair,
equality_proof_account_keypair,
ciphertext_validity_proof_account_keypair,
context_state_authority_keypair,
).await
}
#[allow(clippy::too_many_arguments)]
async fn confidential_transfer_equality_and_ciphertext_validity_proof_context_state_with_optional_transfer<
S: Signer,
>(
&self,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
equality_proof_data: &CiphertextCommitmentEqualityProofData,
ciphertext_validity_proof_data: &BatchedGroupedCiphertext2HandlesValidityProofData,
transfer_instruction: Option<&Instruction>,
source_authority_keypair: Option<&S>,
equality_proof_account_keypair: &S,
ciphertext_validity_proof_account_keypair: &S,
context_state_authority_keypair: Option<&S>,
) -> TokenResult<T::Output> {
let mut signers = vec![
equality_proof_account_keypair,
ciphertext_validity_proof_account_keypair,
];
if let Some(source_authority_keypair) = source_authority_keypair {
signers.push(source_authority_keypair);
}
if let Some(context_state_authority_keypair) = context_state_authority_keypair {
signers.push(context_state_authority_keypair);
}
let mut instructions = vec![];
let instruction_type = ProofInstruction::VerifyCiphertextCommitmentEquality;
let space = size_of::<ProofContextState<CiphertextCommitmentEqualityProofContext>>();
let rent = self
.client
.get_minimum_balance_for_rent_exemption(space)
.await
.map_err(TokenError::Client)?;
instructions.push(system_instruction::create_account(
&self.payer.pubkey(),
context_state_accounts.equality_proof,
rent,
space as u64,
&zk_token_proof_program::id(),
));
let equality_proof_context_state_info = ContextStateInfo {
context_state_account: context_state_accounts.equality_proof,
context_state_authority: context_state_accounts.authority,
};
instructions.push(
instruction_type
.encode_verify_proof(Some(equality_proof_context_state_info), equality_proof_data),
);
let instruction_type = ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity;
let space =
size_of::<ProofContextState<BatchedGroupedCiphertext2HandlesValidityProofContext>>();
let rent = self
.client
.get_minimum_balance_for_rent_exemption(space)
.await
.map_err(TokenError::Client)?;
instructions.push(system_instruction::create_account(
&self.payer.pubkey(),
context_state_accounts.ciphertext_validity_proof,
rent,
space as u64,
&zk_token_proof_program::id(),
));
let ciphertext_validity_proof_context_state_info = ContextStateInfo {
context_state_account: context_state_accounts.ciphertext_validity_proof,
context_state_authority: context_state_accounts.authority,
};
instructions.push(instruction_type.encode_verify_proof(
Some(ciphertext_validity_proof_context_state_info),
ciphertext_validity_proof_data,
));
if let Some(transfer_instruction) = transfer_instruction {
instructions.push(transfer_instruction.clone());
}
self.process_ixs(&instructions, &signers).await
}
pub async fn confidential_transfer_range_proof_context_state_for_transfer<S: Signer>(
&self,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
range_proof_data: &BatchedRangeProofU128Data,
source_authority_keypair: Option<&S>,
range_proof_account_keypair: &S,
context_state_authority_keypair: Option<&S>,
) -> TokenResult<T::Output> {
self.confidential_transfer_range_proof_context_state_with_optional_transfer(
context_state_accounts,
range_proof_data,
None,
source_authority_keypair,
range_proof_account_keypair,
context_state_authority_keypair,
)
.await
}
pub async fn confidential_transfer_range_proof_context_states_and_transfer_parallel<
S: Signer,
>(
&self,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
range_proof_data: &BatchedRangeProofU128Data,
transfer_instruction: &Instruction,
source_authority_keypair: Option<&S>,
range_proof_account_keypair: &S,
context_state_authority_keypair: Option<&S>,
) -> TokenResult<T::Output> {
self.confidential_transfer_range_proof_context_state_with_optional_transfer(
context_state_accounts,
range_proof_data,
Some(transfer_instruction),
source_authority_keypair,
range_proof_account_keypair,
context_state_authority_keypair,
)
.await
}
async fn confidential_transfer_range_proof_context_state_with_optional_transfer<S: Signer>(
&self,
context_state_accounts: TransferSplitContextStateAccounts<'_>,
range_proof_data: &BatchedRangeProofU128Data,
transfer_instruction: Option<&Instruction>,
source_authority_keypair: Option<&S>,
range_proof_account_keypair: &S,
context_state_authority_keypair: Option<&S>,
) -> TokenResult<T::Output> {
let mut signers = vec![range_proof_account_keypair];
if let Some(source_authority_keypair) = source_authority_keypair {
signers.push(source_authority_keypair);
}
if let Some(context_state_authority_keypair) = context_state_authority_keypair {
signers.push(context_state_authority_keypair);
}
let instruction_type = ProofInstruction::VerifyBatchedRangeProofU128;
let space = size_of::<ProofContextState<BatchedRangeProofContext>>();
let rent = self
.client
.get_minimum_balance_for_rent_exemption(space)
.await
.map_err(TokenError::Client)?;
let range_proof_context_state_info = ContextStateInfo {
context_state_account: context_state_accounts.range_proof,
context_state_authority: context_state_accounts.authority,
};
let mut instructions = vec![
system_instruction::create_account(
&self.payer.pubkey(),
context_state_accounts.range_proof,
rent,
space as u64,
&zk_token_proof_program::id(),
),
instruction_type
.encode_verify_proof(Some(range_proof_context_state_info), range_proof_data),
];
if let Some(transfer_instruction) = transfer_instruction {
instructions.push(transfer_instruction.clone());
}
self.process_ixs(&instructions, &signers).await
}
pub async fn confidential_transfer_close_context_state<S: Signer>(
&self,
context_state_account: &Pubkey,
lamport_destination_account: &Pubkey,
context_state_authority: &S,
) -> TokenResult<T::Output> {
let context_state_info = ContextStateInfo {
context_state_account,
context_state_authority: &context_state_authority.pubkey(),
};
self.process_ixs(
&[zk_token_proof_instruction::close_context_state(
context_state_info,
lamport_destination_account,
)],
&[context_state_authority],
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn confidential_transfer_transfer_with_fee<S: Signers>(
&self,
source_account: &Pubkey,
destination_account: &Pubkey,
source_authority: &Pubkey,
context_state_account: Option<&Pubkey>,
transfer_amount: u64,
account_info: Option<TransferAccountInfo>,
source_elgamal_keypair: &ElGamalKeypair,
source_aes_key: &AeKey,
destination_elgamal_pubkey: &ElGamalPubkey,
auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
withdraw_withheld_authority_elgamal_pubkey: &ElGamalPubkey,
fee_rate_basis_points: u16,
maximum_fee: u64,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(source_authority, &signing_pubkeys);
let account_info = if let Some(account_info) = account_info {
account_info
} else {
let account = self.get_account_info(source_account).await?;
let confidential_transfer_account =
account.get_extension::<ConfidentialTransferAccount>()?;
TransferAccountInfo::new(confidential_transfer_account)
};
let proof_data = if context_state_account.is_some() {
None
} else {
Some(
account_info
.generate_transfer_with_fee_proof_data(
transfer_amount,
source_elgamal_keypair,
source_aes_key,
destination_elgamal_pubkey,
auditor_elgamal_pubkey,
withdraw_withheld_authority_elgamal_pubkey,
fee_rate_basis_points,
maximum_fee,
)
.map_err(|_| TokenError::ProofGeneration)?,
)
};
let proof_location = if let Some(proof_data_temp) = proof_data.as_ref() {
ProofLocation::InstructionOffset(1.try_into().unwrap(), proof_data_temp)
} else {
let context_state_account = context_state_account.unwrap();
ProofLocation::ContextStateAccount(context_state_account)
};
let new_decryptable_available_balance = account_info
.new_decryptable_available_balance(transfer_amount, source_aes_key)
.map_err(|_| TokenError::AccountDecryption)?;
self.process_ixs(
&confidential_transfer::instruction::transfer_with_fee(
&self.program_id,
source_account,
destination_account,
&self.pubkey,
new_decryptable_available_balance,
source_authority,
&multisig_signers,
proof_location,
)?,
signing_keypairs,
)
.await
}
pub async fn confidential_transfer_apply_pending_balance<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
account_info: Option<ApplyPendingBalanceAccountInfo>,
elgamal_secret_key: &ElGamalSecretKey,
aes_key: &AeKey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
let account_info = if let Some(account_info) = account_info {
account_info
} else {
let account = self.get_account_info(account).await?;
let confidential_transfer_account =
account.get_extension::<ConfidentialTransferAccount>()?;
ApplyPendingBalanceAccountInfo::new(confidential_transfer_account)
};
let expected_pending_balance_credit_counter = account_info.pending_balance_credit_counter();
let new_decryptable_available_balance = account_info
.new_decryptable_available_balance(elgamal_secret_key, aes_key)
.map_err(|_| TokenError::AccountDecryption)?;
self.process_ixs(
&[confidential_transfer::instruction::apply_pending_balance(
&self.program_id,
account,
expected_pending_balance_credit_counter,
new_decryptable_available_balance,
authority,
&multisig_signers,
)?],
signing_keypairs,
)
.await
}
pub async fn confidential_transfer_enable_confidential_credits<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[
confidential_transfer::instruction::enable_confidential_credits(
&self.program_id,
account,
authority,
&multisig_signers,
)?,
],
signing_keypairs,
)
.await
}
pub async fn confidential_transfer_disable_confidential_credits<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[
confidential_transfer::instruction::disable_confidential_credits(
&self.program_id,
account,
authority,
&multisig_signers,
)?,
],
signing_keypairs,
)
.await
}
pub async fn confidential_transfer_enable_non_confidential_credits<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[
confidential_transfer::instruction::enable_non_confidential_credits(
&self.program_id,
account,
authority,
&multisig_signers,
)?,
],
signing_keypairs,
)
.await
}
pub async fn confidential_transfer_disable_non_confidential_credits<S: Signers>(
&self,
account: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[
confidential_transfer::instruction::disable_non_confidential_credits(
&self.program_id,
account,
authority,
&multisig_signers,
)?,
],
signing_keypairs,
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn confidential_transfer_withdraw_withheld_tokens_from_mint<S: Signers>(
&self,
destination_account: &Pubkey,
withdraw_withheld_authority: &Pubkey,
context_state_account: Option<&Pubkey>,
withheld_tokens_info: Option<WithheldTokensInfo>,
withdraw_withheld_authority_elgamal_keypair: &ElGamalKeypair,
destination_elgamal_pubkey: &ElGamalPubkey,
new_decryptable_available_balance: &DecryptableBalance,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers =
self.get_multisig_signers(withdraw_withheld_authority, &signing_pubkeys);
let account_info = if let Some(account_info) = withheld_tokens_info {
account_info
} else {
let mint_info = self.get_mint_info().await?;
let confidential_transfer_fee_config =
mint_info.get_extension::<ConfidentialTransferFeeConfig>()?;
WithheldTokensInfo::new(&confidential_transfer_fee_config.withheld_amount)
};
let proof_data = if context_state_account.is_some() {
None
} else {
Some(
account_info
.generate_proof_data(
withdraw_withheld_authority_elgamal_keypair,
destination_elgamal_pubkey,
)
.map_err(|_| TokenError::ProofGeneration)?,
)
};
let proof_location = if let Some(proof_data_temp) = proof_data.as_ref() {
ProofLocation::InstructionOffset(1.try_into().unwrap(), proof_data_temp)
} else {
let context_state_account = context_state_account.unwrap();
ProofLocation::ContextStateAccount(context_state_account)
};
self.process_ixs(
&confidential_transfer_fee::instruction::withdraw_withheld_tokens_from_mint(
&self.program_id,
&self.pubkey,
destination_account,
new_decryptable_available_balance,
withdraw_withheld_authority,
&multisig_signers,
proof_location,
)?,
signing_keypairs,
)
.await
}
#[allow(clippy::too_many_arguments)]
pub async fn confidential_transfer_withdraw_withheld_tokens_from_accounts<S: Signers>(
&self,
destination_account: &Pubkey,
withdraw_withheld_authority: &Pubkey,
context_state_account: Option<&Pubkey>,
withheld_tokens_info: Option<WithheldTokensInfo>,
withdraw_withheld_authority_elgamal_keypair: &ElGamalKeypair,
destination_elgamal_pubkey: &ElGamalPubkey,
new_decryptable_available_balance: &DecryptableBalance,
sources: &[&Pubkey],
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers =
self.get_multisig_signers(withdraw_withheld_authority, &signing_pubkeys);
let account_info = if let Some(account_info) = withheld_tokens_info {
account_info
} else {
let futures = sources.iter().map(|source| self.get_account_info(source));
let sources_extensions = join_all(futures).await;
let mut aggregate_withheld_amount = ElGamalCiphertext::default();
for source_extension in sources_extensions {
let withheld_amount: ElGamalCiphertext = source_extension?
.get_extension::<ConfidentialTransferFeeAmount>()?
.withheld_amount
.try_into()
.map_err(|_| TokenError::AccountDecryption)?;
aggregate_withheld_amount = aggregate_withheld_amount + withheld_amount;
}
WithheldTokensInfo::new(&aggregate_withheld_amount.into())
};
let proof_data = if context_state_account.is_some() {
None
} else {
Some(
account_info
.generate_proof_data(
withdraw_withheld_authority_elgamal_keypair,
destination_elgamal_pubkey,
)
.map_err(|_| TokenError::ProofGeneration)?,
)
};
let proof_location = if let Some(proof_data_temp) = proof_data.as_ref() {
ProofLocation::InstructionOffset(1.try_into().unwrap(), proof_data_temp)
} else {
let context_state_account = context_state_account.unwrap();
ProofLocation::ContextStateAccount(context_state_account)
};
self.process_ixs(
&confidential_transfer_fee::instruction::withdraw_withheld_tokens_from_accounts(
&self.program_id,
&self.pubkey,
destination_account,
new_decryptable_available_balance,
withdraw_withheld_authority,
&multisig_signers,
sources,
proof_location,
)?,
signing_keypairs,
)
.await
}
pub async fn confidential_transfer_harvest_withheld_tokens_to_mint(
&self,
sources: &[&Pubkey],
) -> TokenResult<T::Output> {
self.process_ixs::<[&dyn Signer; 0]>(
&[
confidential_transfer_fee::instruction::harvest_withheld_tokens_to_mint(
&self.program_id,
&self.pubkey,
sources,
)?,
],
&[],
)
.await
}
pub async fn confidential_transfer_enable_harvest_to_mint<S: Signers>(
&self,
withdraw_withheld_authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers =
self.get_multisig_signers(withdraw_withheld_authority, &signing_pubkeys);
self.process_ixs(
&[
confidential_transfer_fee::instruction::enable_harvest_to_mint(
&self.program_id,
&self.pubkey,
withdraw_withheld_authority,
&multisig_signers,
)?,
],
signing_keypairs,
)
.await
}
pub async fn confidential_transfer_disable_harvest_to_mint<S: Signers>(
&self,
withdraw_withheld_authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers =
self.get_multisig_signers(withdraw_withheld_authority, &signing_pubkeys);
self.process_ixs(
&[
confidential_transfer_fee::instruction::disable_harvest_to_mint(
&self.program_id,
&self.pubkey,
withdraw_withheld_authority,
&multisig_signers,
)?,
],
signing_keypairs,
)
.await
}
pub async fn withdraw_excess_lamports<S: Signers>(
&self,
source: &Pubkey,
destination: &Pubkey,
authority: &Pubkey,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let signing_pubkeys = signing_keypairs.pubkeys();
let multisig_signers = self.get_multisig_signers(authority, &signing_pubkeys);
self.process_ixs(
&[spl_token_2022::instruction::withdraw_excess_lamports(
&self.program_id,
source,
destination,
authority,
&multisig_signers,
)?],
signing_keypairs,
)
.await
}
pub async fn token_metadata_initialize<S: Signers>(
&self,
update_authority: &Pubkey,
mint_authority: &Pubkey,
name: String,
symbol: String,
uri: String,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
self.process_ixs(
&[spl_token_metadata_interface::instruction::initialize(
&self.program_id,
&self.pubkey,
update_authority,
&self.pubkey,
mint_authority,
name,
symbol,
uri,
)],
signing_keypairs,
)
.await
}
async fn get_additional_rent_for_new_metadata(
&self,
token_metadata: &TokenMetadata,
) -> TokenResult<u64> {
let account = self.get_account(self.pubkey).await?;
let account_lamports = account.lamports;
let mint_state = self.unpack_mint_info(account)?;
let new_account_len = mint_state.try_get_new_account_len(token_metadata)?;
let new_rent_exempt_minimum = self
.client
.get_minimum_balance_for_rent_exemption(new_account_len)
.await
.map_err(TokenError::Client)?;
Ok(new_rent_exempt_minimum.saturating_sub(account_lamports))
}
#[allow(clippy::too_many_arguments)]
pub async fn token_metadata_initialize_with_rent_transfer<S: Signers>(
&self,
payer: &Pubkey,
update_authority: &Pubkey,
mint_authority: &Pubkey,
name: String,
symbol: String,
uri: String,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let token_metadata = TokenMetadata {
name,
symbol,
uri,
..Default::default()
};
let additional_lamports = self
.get_additional_rent_for_new_metadata(&token_metadata)
.await?;
let mut instructions = vec![];
if additional_lamports > 0 {
instructions.push(system_instruction::transfer(
payer,
&self.pubkey,
additional_lamports,
));
}
instructions.push(spl_token_metadata_interface::instruction::initialize(
&self.program_id,
&self.pubkey,
update_authority,
&self.pubkey,
mint_authority,
token_metadata.name,
token_metadata.symbol,
token_metadata.uri,
));
self.process_ixs(&instructions, signing_keypairs).await
}
pub async fn token_metadata_update_field<S: Signers>(
&self,
update_authority: &Pubkey,
field: Field,
value: String,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
self.process_ixs(
&[spl_token_metadata_interface::instruction::update_field(
&self.program_id,
&self.pubkey,
update_authority,
field,
value,
)],
signing_keypairs,
)
.await
}
async fn get_additional_rent_for_updated_metadata(
&self,
field: Field,
value: String,
) -> TokenResult<u64> {
let account = self.get_account(self.pubkey).await?;
let account_lamports = account.lamports;
let mint_state = self.unpack_mint_info(account)?;
let mut token_metadata = mint_state.get_variable_len_extension::<TokenMetadata>()?;
token_metadata.update(field, value);
let new_account_len = mint_state.try_get_new_account_len(&token_metadata)?;
let new_rent_exempt_minimum = self
.client
.get_minimum_balance_for_rent_exemption(new_account_len)
.await
.map_err(TokenError::Client)?;
Ok(new_rent_exempt_minimum.saturating_sub(account_lamports))
}
#[allow(clippy::too_many_arguments)]
pub async fn token_metadata_update_field_with_rent_transfer<S: Signers>(
&self,
payer: &Pubkey,
update_authority: &Pubkey,
field: Field,
value: String,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
let additional_lamports = self
.get_additional_rent_for_updated_metadata(field.clone(), value.clone())
.await?;
let mut instructions = vec![];
if additional_lamports > 0 {
instructions.push(system_instruction::transfer(
payer,
&self.pubkey,
additional_lamports,
));
}
instructions.push(spl_token_metadata_interface::instruction::update_field(
&self.program_id,
&self.pubkey,
update_authority,
field,
value,
));
self.process_ixs(&instructions, signing_keypairs).await
}
pub async fn token_metadata_update_authority<S: Signers>(
&self,
current_authority: &Pubkey,
new_authority: Option<Pubkey>,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
self.process_ixs(
&[spl_token_metadata_interface::instruction::update_authority(
&self.program_id,
&self.pubkey,
current_authority,
new_authority.try_into()?,
)],
signing_keypairs,
)
.await
}
pub async fn token_metadata_remove_key<S: Signers>(
&self,
update_authority: &Pubkey,
key: String,
idempotent: bool,
signing_keypairs: &S,
) -> TokenResult<T::Output> {
self.process_ixs(
&[spl_token_metadata_interface::instruction::remove_key(
&self.program_id,
&self.pubkey,
update_authority,
key,
idempotent,
)],
signing_keypairs,
)
.await
}
}