1894 lines
62 KiB
Rust
1894 lines
62 KiB
Rust
use {
|
|
crate::client::{ProgramClient, ProgramClientError, SendTransaction},
|
|
solana_program_test::tokio::time,
|
|
solana_sdk::{
|
|
account::Account as BaseAccount,
|
|
epoch_info::EpochInfo,
|
|
hash::Hash,
|
|
instruction::Instruction,
|
|
message::Message,
|
|
program_error::ProgramError,
|
|
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,
|
|
},
|
|
spl_token_2022::{
|
|
extension::{
|
|
confidential_transfer, default_account_state, interest_bearing_mint, memo_transfer,
|
|
transfer_fee, ExtensionType, StateWithExtensionsOwned,
|
|
},
|
|
instruction, native_mint,
|
|
solana_zk_token_sdk::{
|
|
encryption::{auth_encryption::*, elgamal::*},
|
|
errors::ProofError,
|
|
instruction::transfer_with_fee::FeeParameters,
|
|
},
|
|
state::{Account, AccountState, Mint},
|
|
},
|
|
std::{
|
|
convert::TryInto,
|
|
fmt, io,
|
|
sync::{Arc, RwLock},
|
|
time::{Duration, Instant},
|
|
},
|
|
thiserror::Error,
|
|
};
|
|
|
|
#[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("proof error: {0}")]
|
|
Proof(ProofError),
|
|
#[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,
|
|
}
|
|
impl PartialEq for TokenError {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
match (self, other) {
|
|
// TODO not great, but workable for tests
|
|
// currently missing: proof error, signer error
|
|
(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::MaximumDepositTransferAmountExceeded,
|
|
Self::MaximumDepositTransferAmountExceeded,
|
|
) => true,
|
|
(Self::AccountDecryption, Self::AccountDecryption) => true,
|
|
(Self::NotEnoughFunds, Self::NotEnoughFunds) => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Encapsulates initializing an extension
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum ExtensionInitializationParams {
|
|
ConfidentialTransferMint {
|
|
ct_mint: confidential_transfer::ConfidentialTransferMint,
|
|
},
|
|
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,
|
|
},
|
|
}
|
|
impl ExtensionInitializationParams {
|
|
/// Get the extension type associated with the init params
|
|
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,
|
|
}
|
|
}
|
|
/// Generate an appropriate initialization instruction for the given mint
|
|
pub fn instruction(
|
|
self,
|
|
token_program_id: &Pubkey,
|
|
mint: &Pubkey,
|
|
) -> Result<Instruction, ProgramError> {
|
|
match self {
|
|
Self::ConfidentialTransferMint { ct_mint } => {
|
|
confidential_transfer::instruction::initialize_mint(
|
|
token_program_id,
|
|
mint,
|
|
&ct_mint,
|
|
)
|
|
}
|
|
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,
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
pub type TokenResult<T> = Result<T, TokenError>;
|
|
|
|
pub struct Token<T> {
|
|
client: Arc<dyn ProgramClient<T>>,
|
|
pubkey: Pubkey, /*token mint*/
|
|
payer: Arc<dyn Signer>,
|
|
program_id: Pubkey,
|
|
nonce_account: Option<Pubkey>,
|
|
nonce_authority: Option<Pubkey>,
|
|
memo: Arc<RwLock<Option<String>>>,
|
|
}
|
|
|
|
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("payer", &self.payer.pubkey())
|
|
.field("memo", &self.memo.read().unwrap())
|
|
.finish()
|
|
}
|
|
}
|
|
|
|
impl<T> Token<T>
|
|
where
|
|
T: SendTransaction,
|
|
{
|
|
pub fn new(
|
|
client: Arc<dyn ProgramClient<T>>,
|
|
program_id: &Pubkey,
|
|
address: &Pubkey,
|
|
payer: Arc<dyn Signer>,
|
|
) -> Self {
|
|
Token {
|
|
client,
|
|
pubkey: *address,
|
|
payer,
|
|
program_id: *program_id,
|
|
nonce_account: None,
|
|
nonce_authority: None,
|
|
memo: Arc::new(RwLock::new(None)),
|
|
}
|
|
}
|
|
|
|
/// Get token address.
|
|
pub fn get_address(&self) -> &Pubkey {
|
|
&self.pubkey
|
|
}
|
|
|
|
pub fn with_payer(&self, payer: Arc<dyn Signer>) -> Token<T> {
|
|
Token {
|
|
client: Arc::clone(&self.client),
|
|
pubkey: self.pubkey,
|
|
payer,
|
|
program_id: self.program_id,
|
|
nonce_account: self.nonce_account,
|
|
nonce_authority: self.nonce_authority,
|
|
memo: Arc::new(RwLock::new(None)),
|
|
}
|
|
}
|
|
|
|
pub fn with_nonce(&self, nonce_account: &Pubkey, nonce_authority: &Pubkey) -> Token<T> {
|
|
Token {
|
|
client: Arc::clone(&self.client),
|
|
pubkey: self.pubkey,
|
|
payer: self.payer.clone(),
|
|
program_id: self.program_id,
|
|
nonce_account: Some(*nonce_account),
|
|
nonce_authority: Some(*nonce_authority),
|
|
memo: Arc::new(RwLock::new(None)),
|
|
}
|
|
}
|
|
|
|
pub fn with_memo<M: AsRef<str>>(&self, memo: M) -> &Self {
|
|
let mut w_memo = self.memo.write().unwrap();
|
|
*w_memo = Some(memo.as_ref().to_string());
|
|
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<S: Signers>(
|
|
&self,
|
|
authority: &Pubkey,
|
|
signing_keypairs: &S,
|
|
) -> Vec<Pubkey> {
|
|
let signing_pubkeys = signing_keypairs.pubkeys();
|
|
|
|
if signing_pubkeys == [*authority] {
|
|
vec![]
|
|
} else {
|
|
signing_pubkeys
|
|
}
|
|
}
|
|
|
|
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() {
|
|
instructions.push(spl_memo::build_memo(memo.as_bytes(), &[&payer_key]));
|
|
}
|
|
}
|
|
|
|
instructions.extend_from_slice(token_instructions);
|
|
|
|
let latest_blockhash = self
|
|
.client
|
|
.get_latest_blockhash()
|
|
.await
|
|
.map_err(TokenError::Client)?;
|
|
|
|
let message = if let (Some(nonce_account), Some(nonce_authority)) =
|
|
(self.nonce_account, self.nonce_authority)
|
|
{
|
|
let mut message = Message::new_with_nonce(
|
|
token_instructions.to_vec(),
|
|
fee_payer,
|
|
&nonce_account,
|
|
&nonce_authority,
|
|
);
|
|
message.recent_blockhash = latest_blockhash;
|
|
message
|
|
} else {
|
|
Message::new_with_blockhash(&instructions, fee_payer, &latest_blockhash)
|
|
};
|
|
|
|
let mut transaction = Transaction::new_unsigned(message);
|
|
|
|
transaction
|
|
.try_partial_sign(&vec![self.payer.clone()], latest_blockhash)
|
|
.map_err(|error| TokenError::Client(error.into()))?;
|
|
transaction
|
|
.try_partial_sign(signing_keypairs, latest_blockhash)
|
|
.map_err(|error| TokenError::Client(error.into()))?;
|
|
|
|
Ok(transaction)
|
|
}
|
|
|
|
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>,
|
|
decimals: u8,
|
|
extension_initialization_params: Vec<ExtensionInitializationParams>,
|
|
signing_keypairs: &S,
|
|
) -> TokenResult<T::Output> {
|
|
let extension_types = extension_initialization_params
|
|
.iter()
|
|
.map(|e| e.extension())
|
|
.collect::<Vec<_>>();
|
|
let space = ExtensionType::get_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
|
|
}
|
|
|
|
/// Create native mint
|
|
pub async fn create_native_mint(
|
|
client: Arc<dyn ProgramClient<T>>,
|
|
program_id: &Pubkey,
|
|
payer: Arc<dyn Signer>,
|
|
) -> TokenResult<Self> {
|
|
let token = Self::new(client, program_id, &native_mint::id(), payer);
|
|
token
|
|
.process_ixs::<[&dyn Signer; 0]>(
|
|
&[instruction::create_native_mint(
|
|
program_id,
|
|
&token.payer.pubkey(),
|
|
)?],
|
|
&[],
|
|
)
|
|
.await?;
|
|
|
|
Ok(token)
|
|
}
|
|
|
|
/// Get the address for the associated token account.
|
|
pub fn get_associated_token_address(&self, owner: &Pubkey) -> Pubkey {
|
|
get_associated_token_address_with_program_id(owner, &self.pubkey, &self.program_id)
|
|
}
|
|
|
|
/// Create and initialize the associated account.
|
|
pub async fn create_associated_token_account(&self, owner: &Pubkey) -> TokenResult<Pubkey> {
|
|
self.process_ixs::<[&dyn Signer; 0]>(
|
|
&[create_associated_token_account(
|
|
&self.payer.pubkey(),
|
|
owner,
|
|
&self.pubkey,
|
|
&self.program_id,
|
|
)],
|
|
&[],
|
|
)
|
|
.await
|
|
.map(|_| self.get_associated_token_address(owner))
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
/// Create and initialize a new token account.
|
|
pub async fn create_auxiliary_token_account(
|
|
&self,
|
|
account: &dyn Signer,
|
|
owner: &Pubkey,
|
|
) -> TokenResult<Pubkey> {
|
|
self.create_auxiliary_token_account_with_extension_space(account, owner, vec![])
|
|
.await
|
|
}
|
|
|
|
/// Create and initialize a new token account.
|
|
pub async fn create_auxiliary_token_account_with_extension_space(
|
|
&self,
|
|
account: &dyn Signer,
|
|
owner: &Pubkey,
|
|
extensions: Vec<ExtensionType>,
|
|
) -> TokenResult<Pubkey> {
|
|
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::get_account_len::<Account>(&required_extensions);
|
|
self.process_ixs(
|
|
&[
|
|
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,
|
|
),
|
|
instruction::initialize_account(
|
|
&self.program_id,
|
|
&account.pubkey(),
|
|
&self.pubkey,
|
|
owner,
|
|
)?,
|
|
],
|
|
&vec![account],
|
|
)
|
|
.await
|
|
.map(|_| account.pubkey())
|
|
.map_err(Into::into)
|
|
}
|
|
|
|
/// Retrieve a raw account
|
|
pub async fn get_account(&self, account: &Pubkey) -> TokenResult<BaseAccount> {
|
|
self.client
|
|
.get_account(*account)
|
|
.await
|
|
.map_err(TokenError::Client)?
|
|
.ok_or(TokenError::AccountNotFound)
|
|
}
|
|
|
|
/// Retrive mint information.
|
|
pub async fn get_mint_info(&self) -> TokenResult<StateWithExtensionsOwned<Mint>> {
|
|
let account = self.get_account(&self.pubkey).await?;
|
|
if account.owner != self.program_id {
|
|
return Err(TokenError::AccountInvalidOwner);
|
|
}
|
|
|
|
StateWithExtensionsOwned::<Mint>::unpack(account.data).map_err(Into::into)
|
|
}
|
|
|
|
/// Retrieve account information.
|
|
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)
|
|
}
|
|
|
|
/// Retrieve the associated account or create one if not found.
|
|
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),
|
|
// AccountInvalidOwner is possible if account already received some lamports.
|
|
Err(TokenError::AccountNotFound) | Err(TokenError::AccountInvalidOwner) => {
|
|
self.create_associated_token_account(owner).await?;
|
|
self.get_account_info(&account).await
|
|
}
|
|
Err(error) => Err(error),
|
|
}
|
|
}
|
|
|
|
/// Assign a new authority to the account.
|
|
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 multisig_signers = self.get_multisig_signers(authority, signing_keypairs);
|
|
|
|
self.process_ixs(
|
|
&[instruction::set_authority(
|
|
&self.program_id,
|
|
account,
|
|
new_authority,
|
|
authority_type,
|
|
authority,
|
|
&multisig_signers.iter().collect::<Vec<_>>(),
|
|
)?],
|
|
signing_keypairs,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Mint new tokens
|
|
pub async fn mint_to<S: Signers>(
|
|
&self,
|
|
destination: &Pubkey,
|
|
authority: &Pubkey,
|
|
amount: u64,
|
|
decimals: Option<u8>,
|
|
signing_keypairs: &S,
|
|
) -> TokenResult<T::Output> {
|
|
let multisig_signers = self.get_multisig_signers(authority, signing_keypairs);
|
|
|
|
let instructions = if let Some(decimals) = decimals {
|
|
[instruction::mint_to_checked(
|
|
&self.program_id,
|
|
&self.pubkey,
|
|
destination,
|
|
authority,
|
|
&multisig_signers.iter().collect::<Vec<_>>(),
|
|
amount,
|
|
decimals,
|
|
)?]
|
|
} else {
|
|
[instruction::mint_to(
|
|
&self.program_id,
|
|
&self.pubkey,
|
|
destination,
|
|
authority,
|
|
&multisig_signers.iter().collect::<Vec<_>>(),
|
|
amount,
|
|
)?]
|
|
};
|
|
|
|
self.process_ixs(&instructions, signing_keypairs).await
|
|
}
|
|
|
|
/// Transfer tokens to another account
|
|
pub async fn transfer_unchecked<S: Signer>(
|
|
&self,
|
|
source: &Pubkey,
|
|
destination: &Pubkey,
|
|
authority: &S,
|
|
amount: u64,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
#[allow(deprecated)]
|
|
&[instruction::transfer(
|
|
&self.program_id,
|
|
source,
|
|
destination,
|
|
&authority.pubkey(),
|
|
&[],
|
|
amount,
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Transfer tokens to another account
|
|
pub async fn transfer_checked<S: Signer>(
|
|
&self,
|
|
source: &Pubkey,
|
|
destination: &Pubkey,
|
|
authority: &S,
|
|
amount: u64,
|
|
decimals: u8,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[instruction::transfer_checked(
|
|
&self.program_id,
|
|
source,
|
|
&self.pubkey,
|
|
destination,
|
|
&authority.pubkey(),
|
|
&[],
|
|
amount,
|
|
decimals,
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Transfer tokens to another account, given an expected fee
|
|
pub async fn transfer_checked_with_fee<S: Signer>(
|
|
&self,
|
|
source: &Pubkey,
|
|
destination: &Pubkey,
|
|
authority: &S,
|
|
amount: u64,
|
|
decimals: u8,
|
|
fee: u64,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[transfer_fee::instruction::transfer_checked_with_fee(
|
|
&self.program_id,
|
|
source,
|
|
&self.pubkey,
|
|
destination,
|
|
&authority.pubkey(),
|
|
&[],
|
|
amount,
|
|
decimals,
|
|
fee,
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Burn tokens from account
|
|
pub async fn burn<S: Signers>(
|
|
&self,
|
|
source: &Pubkey,
|
|
authority: &Pubkey,
|
|
amount: u64,
|
|
decimals: Option<u8>,
|
|
signing_keypairs: &S,
|
|
) -> TokenResult<T::Output> {
|
|
let multisig_signers = self.get_multisig_signers(authority, signing_keypairs);
|
|
|
|
let instructions = if let Some(decimals) = decimals {
|
|
[instruction::burn_checked(
|
|
&self.program_id,
|
|
source,
|
|
&self.pubkey,
|
|
authority,
|
|
&multisig_signers.iter().collect::<Vec<_>>(),
|
|
amount,
|
|
decimals,
|
|
)?]
|
|
} else {
|
|
[instruction::burn(
|
|
&self.program_id,
|
|
source,
|
|
&self.pubkey,
|
|
authority,
|
|
&multisig_signers.iter().collect::<Vec<_>>(),
|
|
amount,
|
|
)?]
|
|
};
|
|
|
|
self.process_ixs(&instructions, signing_keypairs).await
|
|
}
|
|
|
|
/// Approve a delegate to spend tokens
|
|
pub async fn approve<S: Signer>(
|
|
&self,
|
|
source: &Pubkey,
|
|
delegate: &Pubkey,
|
|
authority: &S,
|
|
amount: u64,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[instruction::approve(
|
|
&self.program_id,
|
|
source,
|
|
delegate,
|
|
&authority.pubkey(),
|
|
&[],
|
|
amount,
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Approve a delegate to spend tokens, with decimal check
|
|
pub async fn approve_checked<S: Signer>(
|
|
&self,
|
|
source: &Pubkey,
|
|
delegate: &Pubkey,
|
|
authority: &S,
|
|
amount: u64,
|
|
decimals: u8,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[instruction::approve_checked(
|
|
&self.program_id,
|
|
source,
|
|
&self.pubkey,
|
|
delegate,
|
|
&authority.pubkey(),
|
|
&[],
|
|
amount,
|
|
decimals,
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Revoke a delegate
|
|
pub async fn revoke<S: Signer>(
|
|
&self,
|
|
source: &Pubkey,
|
|
authority: &S,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[instruction::revoke(
|
|
&self.program_id,
|
|
source,
|
|
&authority.pubkey(),
|
|
&[],
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Close account into another
|
|
pub async fn close_account<S: Signers>(
|
|
&self,
|
|
account: &Pubkey,
|
|
destination: &Pubkey,
|
|
authority: &Pubkey,
|
|
signing_keypairs: &S,
|
|
) -> TokenResult<T::Output> {
|
|
let multisig_signers = self.get_multisig_signers(authority, signing_keypairs);
|
|
|
|
let mut instructions = vec![instruction::close_account(
|
|
&self.program_id,
|
|
account,
|
|
destination,
|
|
authority,
|
|
&multisig_signers.iter().collect::<Vec<_>>(),
|
|
)?];
|
|
|
|
if let Ok(Some(destination_account)) = self.client.get_account(*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, destination)?);
|
|
}
|
|
}
|
|
}
|
|
|
|
self.process_ixs(&instructions, signing_keypairs).await
|
|
}
|
|
|
|
/// Freeze a token account
|
|
pub async fn freeze_account<S: Signer>(
|
|
&self,
|
|
account: &Pubkey,
|
|
authority: &S,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[instruction::freeze_account(
|
|
&self.program_id,
|
|
account,
|
|
&self.pubkey,
|
|
&authority.pubkey(),
|
|
&[],
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Thaw / unfreeze a token account
|
|
pub async fn thaw_account<S: Signer>(
|
|
&self,
|
|
account: &Pubkey,
|
|
authority: &S,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[instruction::thaw_account(
|
|
&self.program_id,
|
|
account,
|
|
&self.pubkey,
|
|
&authority.pubkey(),
|
|
&[],
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Sync native account lamports
|
|
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
|
|
}
|
|
|
|
/// Set transfer fee
|
|
pub async fn set_transfer_fee<S: Signer>(
|
|
&self,
|
|
authority: &S,
|
|
transfer_fee_basis_points: u16,
|
|
maximum_fee: u64,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[transfer_fee::instruction::set_transfer_fee(
|
|
&self.program_id,
|
|
&self.pubkey,
|
|
&authority.pubkey(),
|
|
&[],
|
|
transfer_fee_basis_points,
|
|
maximum_fee,
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Set default account state on mint
|
|
pub async fn set_default_account_state<S: Signer>(
|
|
&self,
|
|
authority: &S,
|
|
state: &AccountState,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[
|
|
default_account_state::instruction::update_default_account_state(
|
|
&self.program_id,
|
|
&self.pubkey,
|
|
&authority.pubkey(),
|
|
&[],
|
|
state,
|
|
)?,
|
|
],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Harvest withheld tokens to mint
|
|
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
|
|
}
|
|
|
|
/// Withdraw withheld tokens from mint
|
|
pub async fn withdraw_withheld_tokens_from_mint<S: Signer>(
|
|
&self,
|
|
destination: &Pubkey,
|
|
authority: &S,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[
|
|
transfer_fee::instruction::withdraw_withheld_tokens_from_mint(
|
|
&self.program_id,
|
|
&self.pubkey,
|
|
destination,
|
|
&authority.pubkey(),
|
|
&[],
|
|
)?,
|
|
],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Withdraw withheld tokens from accounts
|
|
pub async fn withdraw_withheld_tokens_from_accounts<S: Signer>(
|
|
&self,
|
|
destination: &Pubkey,
|
|
authority: &S,
|
|
sources: &[&Pubkey],
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[
|
|
transfer_fee::instruction::withdraw_withheld_tokens_from_accounts(
|
|
&self.program_id,
|
|
&self.pubkey,
|
|
destination,
|
|
&authority.pubkey(),
|
|
&[],
|
|
sources,
|
|
)?,
|
|
],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Reallocate a token account to be large enough for a set of ExtensionTypes
|
|
pub async fn reallocate<S: Signer>(
|
|
&self,
|
|
account: &Pubkey,
|
|
authority: &S,
|
|
extension_types: &[ExtensionType],
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[instruction::reallocate(
|
|
&self.program_id,
|
|
account,
|
|
&self.payer.pubkey(),
|
|
&authority.pubkey(),
|
|
&[],
|
|
extension_types,
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Require memos on transfers into this account
|
|
pub async fn enable_required_transfer_memos<S: Signer>(
|
|
&self,
|
|
account: &Pubkey,
|
|
authority: &S,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[memo_transfer::instruction::enable_required_transfer_memos(
|
|
&self.program_id,
|
|
account,
|
|
&authority.pubkey(),
|
|
&[],
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Stop requiring memos on transfers into this account
|
|
pub async fn disable_required_transfer_memos<S: Signer>(
|
|
&self,
|
|
account: &Pubkey,
|
|
authority: &S,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[memo_transfer::instruction::disable_required_transfer_memos(
|
|
&self.program_id,
|
|
account,
|
|
&authority.pubkey(),
|
|
&[],
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Update interest rate
|
|
pub async fn update_interest_rate<S: Signer>(
|
|
&self,
|
|
authority: &S,
|
|
new_rate: i16,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[interest_bearing_mint::instruction::update_rate(
|
|
&self.program_id,
|
|
self.get_address(),
|
|
&authority.pubkey(),
|
|
&[],
|
|
new_rate,
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Update confidential transfer mint
|
|
pub async fn confidential_transfer_update_mint<S: Signer>(
|
|
&self,
|
|
authority: &S,
|
|
new_ct_mint: confidential_transfer::ConfidentialTransferMint,
|
|
new_authority: Option<&S>,
|
|
) -> TokenResult<T::Output> {
|
|
let mut signers = vec![authority];
|
|
if let Some(new_authority) = new_authority {
|
|
signers.push(new_authority);
|
|
}
|
|
self.process_ixs(
|
|
&[confidential_transfer::instruction::update_mint(
|
|
&self.program_id,
|
|
&self.pubkey,
|
|
&new_ct_mint,
|
|
&authority.pubkey(),
|
|
)?],
|
|
&signers,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Configures confidential transfers for a token account
|
|
pub async fn confidential_transfer_configure_token_account<S: Signer>(
|
|
&self,
|
|
token_account: &Pubkey,
|
|
authority: &S,
|
|
) -> TokenResult<T::Output> {
|
|
let maximum_pending_balance_credit_counter =
|
|
2 << confidential_transfer::MAXIMUM_DEPOSIT_TRANSFER_AMOUNT_BIT_LENGTH;
|
|
|
|
self.confidential_transfer_configure_token_account_with_pending_counter(
|
|
token_account,
|
|
authority,
|
|
maximum_pending_balance_credit_counter,
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn confidential_transfer_configure_token_account_with_pending_counter<S: Signer>(
|
|
&self,
|
|
token_account: &Pubkey,
|
|
authority: &S,
|
|
maximum_pending_balance_credit_counter: u64,
|
|
) -> TokenResult<T::Output> {
|
|
let elgamal_pubkey = ElGamalKeypair::new(authority, token_account)
|
|
.map_err(TokenError::Key)?
|
|
.public;
|
|
let decryptable_zero_balance = AeKey::new(authority, token_account)
|
|
.map_err(TokenError::Key)?
|
|
.encrypt(0);
|
|
|
|
self.confidential_transfer_configure_token_account_with_pending_counter_and_keypair(
|
|
token_account,
|
|
authority,
|
|
maximum_pending_balance_credit_counter,
|
|
elgamal_pubkey,
|
|
decryptable_zero_balance,
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn confidential_transfer_configure_token_account_with_pending_counter_and_keypair<
|
|
S: Signer,
|
|
>(
|
|
&self,
|
|
token_account: &Pubkey,
|
|
authority: &S,
|
|
maximum_pending_balance_credit_counter: u64,
|
|
elgamal_pubkey: ElGamalPubkey,
|
|
decryptable_zero_balance: AeCiphertext,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[confidential_transfer::instruction::configure_account(
|
|
&self.program_id,
|
|
token_account,
|
|
&self.pubkey,
|
|
elgamal_pubkey.into(),
|
|
decryptable_zero_balance,
|
|
maximum_pending_balance_credit_counter,
|
|
&authority.pubkey(),
|
|
&[],
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Approves a token account for confidential transfers
|
|
pub async fn confidential_transfer_approve_account<S: Signer>(
|
|
&self,
|
|
token_account: &Pubkey,
|
|
authority: &S,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[confidential_transfer::instruction::approve_account(
|
|
&self.program_id,
|
|
token_account,
|
|
&self.pubkey,
|
|
&authority.pubkey(),
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Prepare a token account with the confidential transfer extension for closing
|
|
pub async fn confidential_transfer_empty_account<S: Signer>(
|
|
&self,
|
|
token_account: &Pubkey,
|
|
authority: &S,
|
|
) -> TokenResult<T::Output> {
|
|
let elgamal_keypair =
|
|
ElGamalKeypair::new(authority, token_account).map_err(TokenError::Key)?;
|
|
self.confidential_transfer_empty_account_with_keypair(
|
|
token_account,
|
|
authority,
|
|
&elgamal_keypair,
|
|
)
|
|
.await
|
|
}
|
|
|
|
pub async fn confidential_transfer_empty_account_with_keypair<S: Signer>(
|
|
&self,
|
|
token_account: &Pubkey,
|
|
authority: &S,
|
|
elgamal_keypair: &ElGamalKeypair,
|
|
) -> TokenResult<T::Output> {
|
|
let state = self.get_account_info(token_account).await.unwrap();
|
|
let extension =
|
|
state.get_extension::<confidential_transfer::ConfidentialTransferAccount>()?;
|
|
|
|
let proof_data = confidential_transfer::instruction::CloseAccountData::new(
|
|
elgamal_keypair,
|
|
&extension.available_balance.try_into().unwrap(),
|
|
)
|
|
.map_err(TokenError::Proof)?;
|
|
|
|
self.process_ixs(
|
|
&confidential_transfer::instruction::empty_account(
|
|
&self.program_id,
|
|
token_account,
|
|
&authority.pubkey(),
|
|
&[],
|
|
&proof_data,
|
|
)?,
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Fetch and decrypt the available balance of a confidential token account using the uniquely
|
|
/// derived decryption key from a signer
|
|
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
|
|
}
|
|
|
|
/// Fetch and decrypt the available balance of a confidential token account using a custom
|
|
/// decryption key
|
|
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)
|
|
}
|
|
|
|
/// Fetch and decrypt the pending balance of a confidential token account using the uniquely
|
|
/// derived decryption key from a signer
|
|
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
|
|
}
|
|
|
|
/// Fetch and decrypt the pending balance of a confidential token account using a custom
|
|
/// decryption key
|
|
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>()?;
|
|
|
|
// decrypt pending balance
|
|
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)
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
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)
|
|
}
|
|
|
|
/// Fetch the ElGamal public key associated with a confidential token account
|
|
pub async fn confidential_transfer_get_encryption_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 encryption_pubkey = extension
|
|
.encryption_pubkey
|
|
.try_into()
|
|
.map_err(TokenError::Proof)?;
|
|
|
|
Ok(encryption_pubkey)
|
|
}
|
|
|
|
/// Fetch the ElGamal pubkey key of the auditor associated with a confidential token mint
|
|
pub async fn confidential_transfer_get_auditor_encryption_pubkey<S: Signer>(
|
|
&self,
|
|
) -> TokenResult<ElGamalPubkey> {
|
|
let mint_state = self.get_mint_info().await.unwrap();
|
|
let ct_mint =
|
|
mint_state.get_extension::<confidential_transfer::ConfidentialTransferMint>()?;
|
|
let auditor_pubkey = ct_mint
|
|
.auditor_encryption_pubkey
|
|
.try_into()
|
|
.map_err(TokenError::Proof)?;
|
|
|
|
Ok(auditor_pubkey)
|
|
}
|
|
|
|
/// Fetch the ElGamal pubkey key of the withdraw withheld authority associated with a
|
|
/// confidential token mint
|
|
pub async fn confidential_transfer_get_withdraw_withheld_authority_encryption_pubkey<
|
|
S: Signer,
|
|
>(
|
|
&self,
|
|
) -> TokenResult<ElGamalPubkey> {
|
|
let mint_state = self.get_mint_info().await.unwrap();
|
|
let ct_mint =
|
|
mint_state.get_extension::<confidential_transfer::ConfidentialTransferMint>()?;
|
|
let auditor_pubkey = ct_mint
|
|
.withdraw_withheld_authority_encryption_pubkey
|
|
.try_into()
|
|
.map_err(TokenError::Proof)?;
|
|
|
|
Ok(auditor_pubkey)
|
|
}
|
|
|
|
/// Deposit SPL Tokens into the pending balance of a confidential token account
|
|
pub async fn confidential_transfer_deposit<S: Signer>(
|
|
&self,
|
|
source_token_account: &Pubkey,
|
|
destination_token_account: &Pubkey,
|
|
source_token_authority: &S,
|
|
amount: u64,
|
|
decimals: u8,
|
|
) -> TokenResult<T::Output> {
|
|
if amount >> confidential_transfer::MAXIMUM_DEPOSIT_TRANSFER_AMOUNT_BIT_LENGTH != 0 {
|
|
return Err(TokenError::MaximumDepositTransferAmountExceeded);
|
|
}
|
|
|
|
self.process_ixs(
|
|
&[confidential_transfer::instruction::deposit(
|
|
&self.program_id,
|
|
source_token_account,
|
|
&self.pubkey,
|
|
destination_token_account,
|
|
amount,
|
|
decimals,
|
|
&source_token_authority.pubkey(),
|
|
&[],
|
|
)?],
|
|
&[source_token_authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Withdraw SPL Tokens from the available balance of a confidential token account using the
|
|
/// uniquely derived decryption key from a signer
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn confidential_transfer_withdraw<S: Signer>(
|
|
&self,
|
|
source_token_account: &Pubkey,
|
|
destination_token_account: &Pubkey,
|
|
source_token_authority: &S,
|
|
amount: u64,
|
|
source_available_balance: u64,
|
|
source_available_balance_ciphertext: &ElGamalCiphertext,
|
|
decimals: u8,
|
|
) -> TokenResult<T::Output> {
|
|
let source_elgamal_keypair =
|
|
ElGamalKeypair::new(source_token_authority, source_token_account)
|
|
.map_err(TokenError::Key)?;
|
|
let source_authenticated_encryption_key =
|
|
AeKey::new(source_token_authority, source_token_account).map_err(TokenError::Key)?;
|
|
|
|
self.confidential_transfer_withdraw_with_key(
|
|
source_token_account,
|
|
destination_token_account,
|
|
source_token_authority,
|
|
amount,
|
|
decimals,
|
|
source_available_balance,
|
|
source_available_balance_ciphertext,
|
|
&source_elgamal_keypair,
|
|
&source_authenticated_encryption_key,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Withdraw SPL Tokens from the available balance of a confidential token account using custom
|
|
/// keys
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn confidential_transfer_withdraw_with_key<S: Signer>(
|
|
&self,
|
|
source_token_account: &Pubkey,
|
|
destination_token_account: &Pubkey,
|
|
source_token_authority: &S,
|
|
amount: u64,
|
|
decimals: u8,
|
|
source_available_balance: u64,
|
|
source_available_balance_ciphertext: &ElGamalCiphertext,
|
|
source_elgamal_keypair: &ElGamalKeypair,
|
|
source_authenticated_encryption_key: &AeKey,
|
|
) -> TokenResult<T::Output> {
|
|
let proof_data = confidential_transfer::instruction::WithdrawData::new(
|
|
amount,
|
|
source_elgamal_keypair,
|
|
source_available_balance,
|
|
source_available_balance_ciphertext,
|
|
)
|
|
.map_err(TokenError::Proof)?;
|
|
|
|
let source_remaining_balance = source_available_balance
|
|
.checked_sub(amount)
|
|
.ok_or(TokenError::NotEnoughFunds)?;
|
|
let new_source_decryptable_available_balance =
|
|
source_authenticated_encryption_key.encrypt(source_remaining_balance);
|
|
|
|
self.process_ixs(
|
|
&confidential_transfer::instruction::withdraw(
|
|
&self.program_id,
|
|
source_token_account,
|
|
destination_token_account,
|
|
&self.pubkey,
|
|
amount,
|
|
decimals,
|
|
new_source_decryptable_available_balance,
|
|
&source_token_authority.pubkey(),
|
|
&[],
|
|
&proof_data,
|
|
)?,
|
|
&[source_token_authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Transfer tokens confidentially using the uniquely derived decryption keys from a signer
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn confidential_transfer_transfer<S: Signer>(
|
|
&self,
|
|
source_token_account: &Pubkey,
|
|
destination_token_account: &Pubkey,
|
|
source_token_authority: &S,
|
|
amount: u64,
|
|
source_available_balance: u64,
|
|
source_available_balance_ciphertext: &ElGamalCiphertext,
|
|
destination_elgamal_pubkey: &ElGamalPubkey,
|
|
auditor_elgamal_pubkey: &ElGamalPubkey,
|
|
) -> TokenResult<T::Output> {
|
|
let source_elgamal_keypair =
|
|
ElGamalKeypair::new(source_token_authority, source_token_account)
|
|
.map_err(TokenError::Key)?;
|
|
let source_authenticated_encryption_key =
|
|
AeKey::new(source_token_authority, source_token_account).map_err(TokenError::Key)?;
|
|
|
|
self.confidential_transfer_transfer_with_key(
|
|
source_token_account,
|
|
destination_token_account,
|
|
source_token_authority,
|
|
amount,
|
|
source_available_balance,
|
|
source_available_balance_ciphertext,
|
|
destination_elgamal_pubkey,
|
|
auditor_elgamal_pubkey,
|
|
&source_elgamal_keypair,
|
|
&source_authenticated_encryption_key,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Transfer tokens confidentially using custom decryption keys
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn confidential_transfer_transfer_with_key<S: Signer>(
|
|
&self,
|
|
source_token_account: &Pubkey,
|
|
destination_token_account: &Pubkey,
|
|
source_token_authority: &S,
|
|
amount: u64,
|
|
source_available_balance: u64,
|
|
source_available_balance_ciphertext: &ElGamalCiphertext,
|
|
destination_elgamal_pubkey: &ElGamalPubkey,
|
|
auditor_elgamal_pubkey: &ElGamalPubkey,
|
|
source_elgamal_keypair: &ElGamalKeypair,
|
|
source_authenticated_encryption_key: &AeKey,
|
|
) -> TokenResult<T::Output> {
|
|
if amount >> confidential_transfer::MAXIMUM_DEPOSIT_TRANSFER_AMOUNT_BIT_LENGTH != 0 {
|
|
return Err(TokenError::MaximumDepositTransferAmountExceeded);
|
|
}
|
|
|
|
let proof_data = confidential_transfer::instruction::TransferData::new(
|
|
amount,
|
|
(
|
|
source_available_balance,
|
|
source_available_balance_ciphertext,
|
|
),
|
|
source_elgamal_keypair,
|
|
(destination_elgamal_pubkey, auditor_elgamal_pubkey),
|
|
)
|
|
.map_err(TokenError::Proof)?;
|
|
|
|
let source_remaining_balance = source_available_balance
|
|
.checked_sub(amount)
|
|
.ok_or(TokenError::NotEnoughFunds)?;
|
|
let new_source_available_balance =
|
|
source_authenticated_encryption_key.encrypt(source_remaining_balance);
|
|
|
|
self.process_ixs(
|
|
&confidential_transfer::instruction::transfer(
|
|
&self.program_id,
|
|
source_token_account,
|
|
destination_token_account,
|
|
&self.pubkey,
|
|
new_source_available_balance,
|
|
&source_token_authority.pubkey(),
|
|
&[],
|
|
&proof_data,
|
|
)?,
|
|
&[source_token_authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Transfer tokens confidentially with fee using the uniquely derived decryption keys from a
|
|
/// signer
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn confidential_transfer_transfer_with_fee<S: Signer>(
|
|
&self,
|
|
source_token_account: &Pubkey,
|
|
destination_token_account: &Pubkey,
|
|
source_token_authority: &S,
|
|
amount: u64,
|
|
source_available_balance: u64,
|
|
source_available_balance_ciphertext: &ElGamalCiphertext,
|
|
destination_elgamal_pubkey: &ElGamalPubkey,
|
|
auditor_elgamal_pubkey: &ElGamalPubkey,
|
|
withdraw_withheld_authority_elgamal_pubkey: &ElGamalPubkey,
|
|
epoch_info: &EpochInfo,
|
|
) -> TokenResult<T::Output> {
|
|
let source_elgamal_keypair =
|
|
ElGamalKeypair::new(source_token_authority, source_token_account)
|
|
.map_err(TokenError::Key)?;
|
|
let source_authenticated_encryption_key =
|
|
AeKey::new(source_token_authority, source_token_account).map_err(TokenError::Key)?;
|
|
|
|
self.confidential_transfer_transfer_with_fee_with_key(
|
|
source_token_account,
|
|
destination_token_account,
|
|
source_token_authority,
|
|
amount,
|
|
source_available_balance,
|
|
source_available_balance_ciphertext,
|
|
destination_elgamal_pubkey,
|
|
auditor_elgamal_pubkey,
|
|
withdraw_withheld_authority_elgamal_pubkey,
|
|
&source_elgamal_keypair,
|
|
&source_authenticated_encryption_key,
|
|
epoch_info,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Transfer tokens confidential with fee using custom decryption keys
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn confidential_transfer_transfer_with_fee_with_key<S: Signer>(
|
|
&self,
|
|
source_token_account: &Pubkey,
|
|
destination_token_account: &Pubkey,
|
|
source_token_authority: &S,
|
|
amount: u64,
|
|
source_available_balance: u64,
|
|
source_available_balance_ciphertext: &ElGamalCiphertext,
|
|
destination_elgamal_pubkey: &ElGamalPubkey,
|
|
auditor_elgamal_pubkey: &ElGamalPubkey,
|
|
withdraw_withheld_authority_elgamal_pubkey: &ElGamalPubkey,
|
|
source_elgamal_keypair: &ElGamalKeypair,
|
|
source_authenticated_encryption_key: &AeKey,
|
|
epoch_info: &EpochInfo,
|
|
) -> TokenResult<T::Output> {
|
|
if amount >> confidential_transfer::MAXIMUM_DEPOSIT_TRANSFER_AMOUNT_BIT_LENGTH != 0 {
|
|
return Err(TokenError::MaximumDepositTransferAmountExceeded);
|
|
}
|
|
|
|
// TODO: take transfer fee params as input
|
|
let mint_state = self.get_mint_info().await.unwrap();
|
|
let transfer_fee_config = mint_state
|
|
.get_extension::<transfer_fee::TransferFeeConfig>()
|
|
.unwrap();
|
|
let fee_parameters = transfer_fee_config.get_epoch_fee(epoch_info.epoch);
|
|
|
|
let proof_data = confidential_transfer::instruction::TransferWithFeeData::new(
|
|
amount,
|
|
(
|
|
source_available_balance,
|
|
source_available_balance_ciphertext,
|
|
),
|
|
source_elgamal_keypair,
|
|
(destination_elgamal_pubkey, auditor_elgamal_pubkey),
|
|
FeeParameters {
|
|
fee_rate_basis_points: u16::from(fee_parameters.transfer_fee_basis_points),
|
|
maximum_fee: u64::from(fee_parameters.maximum_fee),
|
|
},
|
|
withdraw_withheld_authority_elgamal_pubkey,
|
|
)
|
|
.map_err(TokenError::Proof)?;
|
|
|
|
let source_remaining_balance = source_available_balance
|
|
.checked_sub(amount)
|
|
.ok_or(TokenError::NotEnoughFunds)?;
|
|
let new_source_decryptable_balance =
|
|
source_authenticated_encryption_key.encrypt(source_remaining_balance);
|
|
|
|
self.process_ixs(
|
|
&confidential_transfer::instruction::transfer_with_fee(
|
|
&self.program_id,
|
|
source_token_account,
|
|
destination_token_account,
|
|
&self.pubkey,
|
|
new_source_decryptable_balance,
|
|
&source_token_authority.pubkey(),
|
|
&[],
|
|
&proof_data,
|
|
)?,
|
|
&[source_token_authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Applies the confidential transfer pending balance to the available balance using the
|
|
/// uniquely derived decryption key
|
|
pub async fn confidential_transfer_apply_pending_balance<S: Signer>(
|
|
&self,
|
|
token_account: &Pubkey,
|
|
authority: &S,
|
|
available_balance: u64,
|
|
pending_balance: u64,
|
|
expected_pending_balance_credit_counter: u64,
|
|
) -> TokenResult<T::Output> {
|
|
let authenticated_encryption_key =
|
|
AeKey::new(authority, token_account).map_err(TokenError::Key)?;
|
|
|
|
self.confidential_transfer_apply_pending_balance_with_key(
|
|
token_account,
|
|
authority,
|
|
available_balance,
|
|
pending_balance,
|
|
expected_pending_balance_credit_counter,
|
|
&authenticated_encryption_key,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Applies the confidential transfer pending balance to the available balance using a custom
|
|
/// decryption key
|
|
pub async fn confidential_transfer_apply_pending_balance_with_key<S: Signer>(
|
|
&self,
|
|
token_account: &Pubkey,
|
|
authority: &S,
|
|
available_balance: u64,
|
|
pending_balance: u64,
|
|
expected_pending_balance_credit_counter: u64,
|
|
authenticated_encryption_key: &AeKey,
|
|
) -> TokenResult<T::Output> {
|
|
let new_decryptable_balance = available_balance.checked_add(pending_balance).unwrap();
|
|
let new_decryptable_balance_ciphertext =
|
|
authenticated_encryption_key.encrypt(new_decryptable_balance);
|
|
|
|
self.process_ixs(
|
|
&[confidential_transfer::instruction::apply_pending_balance(
|
|
&self.program_id,
|
|
token_account,
|
|
expected_pending_balance_credit_counter,
|
|
new_decryptable_balance_ciphertext,
|
|
&authority.pubkey(),
|
|
&[],
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Enable confidential transfer `Deposit` and `Transfer` instructions for a token account
|
|
pub async fn confidential_transfer_enable_balance_credits<S: Signer>(
|
|
&self,
|
|
token_account: &Pubkey,
|
|
authority: &S,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[confidential_transfer::instruction::enable_balance_credits(
|
|
&self.program_id,
|
|
token_account,
|
|
&authority.pubkey(),
|
|
&[],
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Disable confidential transfer `Deposit` and `Transfer` instructions for a token account
|
|
pub async fn confidential_transfer_disable_balance_credits<S: Signer>(
|
|
&self,
|
|
token_account: &Pubkey,
|
|
authority: &S,
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs(
|
|
&[confidential_transfer::instruction::disable_balance_credits(
|
|
&self.program_id,
|
|
token_account,
|
|
&authority.pubkey(),
|
|
&[],
|
|
)?],
|
|
&[authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Withdraw withheld confidential tokens from mint using the uniquely derived decryption key
|
|
pub async fn confidential_transfer_withdraw_withheld_tokens_from_mint<S: Signer>(
|
|
&self,
|
|
withdraw_withheld_authority: &S,
|
|
destination_token_account: &Pubkey,
|
|
destination_elgamal_pubkey: &ElGamalPubkey,
|
|
withheld_amount: u64,
|
|
withheld_amount_ciphertext: &ElGamalCiphertext,
|
|
) -> TokenResult<T::Output> {
|
|
// derive withheld authority elgamal key
|
|
let withdraw_withheld_authority_elgamal_keypair =
|
|
ElGamalKeypair::new(withdraw_withheld_authority, &self.pubkey)
|
|
.map_err(TokenError::Key)?;
|
|
|
|
self.confidential_transfer_withdraw_withheld_tokens_from_mint_with_key(
|
|
withdraw_withheld_authority,
|
|
destination_token_account,
|
|
destination_elgamal_pubkey,
|
|
withheld_amount,
|
|
withheld_amount_ciphertext,
|
|
&withdraw_withheld_authority_elgamal_keypair,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Withdraw withheld confidential tokens from mint using a custom decryption key
|
|
pub async fn confidential_transfer_withdraw_withheld_tokens_from_mint_with_key<S: Signer>(
|
|
&self,
|
|
withdraw_withheld_authority: &S,
|
|
destination_token_account: &Pubkey,
|
|
destination_elgamal_pubkey: &ElGamalPubkey,
|
|
withheld_amount: u64,
|
|
withheld_amount_ciphertext: &ElGamalCiphertext,
|
|
withdraw_withheld_authority_elgamal_keypair: &ElGamalKeypair,
|
|
) -> TokenResult<T::Output> {
|
|
let proof_data = confidential_transfer::instruction::WithdrawWithheldTokensData::new(
|
|
withdraw_withheld_authority_elgamal_keypair,
|
|
destination_elgamal_pubkey,
|
|
withheld_amount_ciphertext,
|
|
withheld_amount,
|
|
)
|
|
.map_err(TokenError::Proof)?;
|
|
|
|
self.process_ixs(
|
|
&confidential_transfer::instruction::withdraw_withheld_tokens_from_mint(
|
|
&self.program_id,
|
|
&self.pubkey,
|
|
destination_token_account,
|
|
&withdraw_withheld_authority.pubkey(),
|
|
&[],
|
|
&proof_data,
|
|
)?,
|
|
&[withdraw_withheld_authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Withdraw withheld confidential tokens from accounts using the uniquely derived decryption
|
|
/// key
|
|
pub async fn confidential_transfer_withdraw_withheld_tokens_from_accounts<S: Signer>(
|
|
&self,
|
|
withdraw_withheld_authority: &S,
|
|
destination_token_account: &Pubkey,
|
|
destination_elgamal_pubkey: &ElGamalPubkey,
|
|
aggregate_withheld_amount: u64,
|
|
aggregate_withheld_amount_ciphertext: &ElGamalCiphertext,
|
|
sources: &[&Pubkey],
|
|
) -> TokenResult<T::Output> {
|
|
let withdraw_withheld_authority_elgamal_keypair =
|
|
ElGamalKeypair::new(withdraw_withheld_authority, &self.pubkey)
|
|
.map_err(TokenError::Key)?;
|
|
|
|
self.confidential_transfer_withdraw_withheld_tokens_from_accounts_with_key(
|
|
withdraw_withheld_authority,
|
|
destination_token_account,
|
|
destination_elgamal_pubkey,
|
|
aggregate_withheld_amount,
|
|
aggregate_withheld_amount_ciphertext,
|
|
&withdraw_withheld_authority_elgamal_keypair,
|
|
sources,
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Withdraw withheld confidential tokens from accounts using a custom decryption key
|
|
#[allow(clippy::too_many_arguments)]
|
|
pub async fn confidential_transfer_withdraw_withheld_tokens_from_accounts_with_key<
|
|
S: Signer,
|
|
>(
|
|
&self,
|
|
withdraw_withheld_authority: &S,
|
|
destination_token_account: &Pubkey,
|
|
destination_elgamal_pubkey: &ElGamalPubkey,
|
|
aggregate_withheld_amount: u64,
|
|
aggregate_withheld_amount_ciphertext: &ElGamalCiphertext,
|
|
withdraw_withheld_authority_elgamal_keypair: &ElGamalKeypair,
|
|
sources: &[&Pubkey],
|
|
) -> TokenResult<T::Output> {
|
|
let proof_data = confidential_transfer::instruction::WithdrawWithheldTokensData::new(
|
|
withdraw_withheld_authority_elgamal_keypair,
|
|
destination_elgamal_pubkey,
|
|
aggregate_withheld_amount_ciphertext,
|
|
aggregate_withheld_amount,
|
|
)
|
|
.map_err(TokenError::Proof)?;
|
|
|
|
self.process_ixs(
|
|
&confidential_transfer::instruction::withdraw_withheld_tokens_from_accounts(
|
|
&self.program_id,
|
|
&self.pubkey,
|
|
destination_token_account,
|
|
&withdraw_withheld_authority.pubkey(),
|
|
&[],
|
|
sources,
|
|
&proof_data,
|
|
)?,
|
|
&[withdraw_withheld_authority],
|
|
)
|
|
.await
|
|
}
|
|
|
|
/// Harvest withheld confidential tokens to mint
|
|
pub async fn confidential_transfer_harvest_withheld_tokens_to_mint(
|
|
&self,
|
|
sources: &[&Pubkey],
|
|
) -> TokenResult<T::Output> {
|
|
self.process_ixs::<[&dyn Signer; 0]>(
|
|
&[
|
|
confidential_transfer::instruction::harvest_withheld_tokens_to_mint(
|
|
&self.program_id,
|
|
&self.pubkey,
|
|
sources,
|
|
)?,
|
|
],
|
|
&[],
|
|
)
|
|
.await
|
|
}
|
|
}
|