spl_token_2022_interface/extension/confidential_transfer/
instruction.rs

1pub use solana_zk_sdk::zk_elgamal_proof_program::{
2    instruction::ProofInstruction, proof_data::*, state::ProofContextState,
3};
4#[cfg(feature = "serde")]
5use {
6    crate::serialization::{aeciphertext_fromstr, elgamalciphertext_fromstr},
7    serde::{Deserialize, Serialize},
8};
9use {
10    crate::{
11        check_program_account,
12        extension::confidential_transfer::*,
13        instruction::{encode_instruction, TokenInstruction},
14    },
15    bytemuck::Zeroable,
16    num_enum::{IntoPrimitive, TryFromPrimitive},
17    solana_instruction::{AccountMeta, Instruction},
18    solana_program_error::ProgramError,
19    solana_pubkey::Pubkey,
20    solana_sdk_ids::{system_program, sysvar},
21    spl_token_confidential_transfer_proof_extraction::instruction::ProofLocation,
22};
23
24/// Confidential Transfer extension instructions
25#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
26#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
27#[derive(Clone, Copy, Debug, TryFromPrimitive, IntoPrimitive)]
28#[repr(u8)]
29pub enum ConfidentialTransferInstruction {
30    /// Initializes confidential transfers for a mint.
31    ///
32    /// The `ConfidentialTransferInstruction::InitializeMint` instruction
33    /// requires no signers and MUST be included within the same Transaction
34    /// as `TokenInstruction::InitializeMint`. Otherwise another party can
35    /// initialize the configuration.
36    ///
37    /// The instruction fails if the `TokenInstruction::InitializeMint`
38    /// instruction has already executed for the mint.
39    ///
40    /// Accounts expected by this instruction:
41    ///
42    ///   0. `[writable]` The SPL Token mint.
43    ///
44    /// Data expected by this instruction:
45    ///   `InitializeMintData`
46    InitializeMint,
47
48    /// Updates the confidential transfer mint configuration for a mint.
49    ///
50    /// Use `TokenInstruction::SetAuthority` to update the confidential transfer
51    /// mint authority.
52    ///
53    /// Accounts expected by this instruction:
54    ///
55    ///   0. `[writable]` The SPL Token mint.
56    ///   1. `[signer]` Confidential transfer mint authority.
57    ///
58    /// Data expected by this instruction:
59    ///   `UpdateMintData`
60    UpdateMint,
61
62    /// Configures confidential transfers for a token account.
63    ///
64    /// The instruction fails if the confidential transfers are already
65    /// configured, or if the mint was not initialized with confidential
66    /// transfer support.
67    ///
68    /// The instruction fails if the `TokenInstruction::InitializeAccount`
69    /// instruction has not yet successfully executed for the token account.
70    ///
71    /// Upon success, confidential and non-confidential deposits and transfers
72    /// are enabled. Use the `DisableConfidentialCredits` and
73    /// `DisableNonConfidentialCredits` instructions to disable.
74    ///
75    /// In order for this instruction to be successfully processed, it must be
76    /// accompanied by the `VerifyPubkeyValidity` instruction of the
77    /// `zk_elgamal_proof` program in the same transaction or the address of a
78    /// context state account for the proof must be provided.
79    ///
80    /// Accounts expected by this instruction:
81    ///
82    ///   * Single owner/delegate
83    ///   0. `[writeable]` The SPL Token account.
84    ///   1. `[]` The corresponding SPL Token mint.
85    ///   2. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in
86    ///      the same transaction or context state account if
87    ///      `VerifyPubkeyValidity` is pre-verified into a context state
88    ///      account.
89    ///   3. `[signer]` The single source account owner.
90    ///
91    ///   * Multisignature owner/delegate
92    ///   0. `[writeable]` The SPL Token account.
93    ///   1. `[]` The corresponding SPL Token mint.
94    ///   2. `[]` Instructions sysvar if `VerifyPubkeyValidity` is included in
95    ///      the same transaction or context state account if
96    ///      `VerifyPubkeyValidity` is pre-verified into a context state
97    ///      account.
98    ///   3. `[]` The multisig source account owner.
99    ///   4. .. `[signer]` Required M signer accounts for the SPL Token Multisig
100    ///      account.
101    ///
102    /// Data expected by this instruction:
103    ///   `ConfigureAccountInstructionData`
104    ConfigureAccount,
105
106    /// Approves a token account for confidential transfers.
107    ///
108    /// Approval is only required when the
109    /// `ConfidentialTransferMint::approve_new_accounts` field is set in the
110    /// SPL Token mint.  This instruction must be executed after the account
111    /// owner configures their account for confidential transfers with
112    /// `ConfidentialTransferInstruction::ConfigureAccount`.
113    ///
114    /// Accounts expected by this instruction:
115    ///
116    ///   0. `[writable]` The SPL Token account to approve.
117    ///   1. `[]` The SPL Token mint.
118    ///   2. `[signer]` Confidential transfer mint authority.
119    ///
120    /// Data expected by this instruction:
121    ///   None
122    ApproveAccount,
123
124    /// Empty the available balance in a confidential token account.
125    ///
126    /// A token account that is extended for confidential transfers can only be
127    /// closed if the pending and available balance ciphertexts are emptied.
128    /// The pending balance can be emptied
129    /// via the `ConfidentialTransferInstruction::ApplyPendingBalance`
130    /// instruction. Use the `ConfidentialTransferInstruction::EmptyAccount`
131    /// instruction to empty the available balance ciphertext.
132    ///
133    /// Note that a newly configured account is always empty, so this
134    /// instruction is not required prior to account closing if no
135    /// instructions beyond
136    /// `ConfidentialTransferInstruction::ConfigureAccount` have affected the
137    /// token account.
138    ///
139    /// In order for this instruction to be successfully processed, it must be
140    /// accompanied by the `VerifyZeroCiphertext` instruction of the
141    /// `zk_elgamal_proof` program in the same transaction or the address of a
142    /// context state account for the proof must be provided.
143    ///
144    ///   * Single owner/delegate
145    ///   0. `[writable]` The SPL Token account.
146    ///   1. `[]` Instructions sysvar if `VerifyZeroCiphertext` is included in
147    ///      the same transaction or context state account if
148    ///      `VerifyZeroCiphertext` is pre-verified into a context state
149    ///      account.
150    ///   2. `[signer]` The single account owner.
151    ///
152    ///   * Multisignature owner/delegate
153    ///   0. `[writable]` The SPL Token account.
154    ///   1. `[]` Instructions sysvar if `VerifyZeroCiphertext` is included in
155    ///      the same transaction or context state account if
156    ///      `VerifyZeroCiphertext` is pre-verified into a context state
157    ///      account.
158    ///   2. `[]` The multisig account owner.
159    ///   3. .. `[signer]` Required M signer accounts for the SPL Token Multisig
160    ///      account.
161    ///
162    /// Data expected by this instruction:
163    ///   `EmptyAccountInstructionData`
164    EmptyAccount,
165
166    /// Deposit SPL Tokens into the pending balance of a confidential token
167    /// account.
168    ///
169    /// The account owner can then invoke the `ApplyPendingBalance` instruction
170    /// to roll the deposit into their available balance at a time of their
171    /// choosing.
172    ///
173    /// Fails if the source or destination accounts are frozen.
174    /// Fails if the associated mint is extended as `NonTransferable`.
175    /// Fails if the associated mint is extended as `ConfidentialMintBurn`.
176    /// Fails if the associated mint is paused with the `Pausable` extension.
177    ///
178    /// Accounts expected by this instruction:
179    ///
180    ///   * Single owner/delegate
181    ///   0. `[writable]` The SPL Token account.
182    ///   1. `[]` The token mint.
183    ///   2. `[signer]` The single account owner or delegate.
184    ///
185    ///   * Multisignature owner/delegate
186    ///   0. `[writable]` The SPL Token account.
187    ///   1. `[]` The token mint.
188    ///   2. `[]` The multisig account owner or delegate.
189    ///   3. .. `[signer]` Required M signer accounts for the SPL Token Multisig
190    ///      account.
191    ///
192    /// Data expected by this instruction:
193    ///   `DepositInstructionData`
194    Deposit,
195
196    /// Withdraw SPL Tokens from the available balance of a confidential token
197    /// account.
198    ///
199    /// In order for this instruction to be successfully processed, it must be
200    /// accompanied by the following list of `zk_elgamal_proof` program
201    /// instructions:
202    ///
203    /// - `VerifyCiphertextCommitmentEquality`
204    /// - `VerifyBatchedRangeProofU64`
205    ///
206    /// These instructions can be accompanied in the same transaction or can be
207    /// pre-verified into a context state account, in which case, only their
208    /// context state account address need to be provided.
209    ///
210    /// Fails if the source or destination accounts are frozen.
211    /// Fails if the associated mint is extended as `NonTransferable`.
212    /// Fails if the associated mint is extended as `ConfidentialMintBurn`.
213    /// Fails if the associated mint is paused with the `Pausable` extension.
214    ///
215    /// Accounts expected by this instruction:
216    ///
217    ///   * Single owner/delegate
218    ///   0. `[writable]` The SPL Token account.
219    ///   1. `[]` The token mint.
220    ///   2. `[]` (Optional) Instructions sysvar if at least one of the
221    ///      `zk_elgamal_proof` instructions are included in the same
222    ///      transaction.
223    ///   3. `[]` (Optional) Equality proof context state account.
224    ///   4. `[]` (Optional) Range proof context state account.
225    ///   5. `[signer]` The single source account owner.
226    ///
227    ///   * Multisignature owner/delegate
228    ///   0. `[writable]` The SPL Token account.
229    ///   1. `[]` The token mint.
230    ///   2. `[]` (Optional) Instructions sysvar if at least one of the
231    ///      `zk_elgamal_proof` instructions are included in the same
232    ///      transaction.
233    ///   3. `[]` (Optional) Equality proof context state account.
234    ///   4. `[]` (Optional) Range proof context state account.
235    ///   5. `[]` The multisig  source account owner.
236    ///   6. .. `[signer]` Required M signer accounts for the SPL Token Multisig
237    ///      account.
238    ///
239    /// Data expected by this instruction:
240    ///   `WithdrawInstructionData`
241    Withdraw,
242
243    /// Transfer tokens confidentially.
244    ///
245    /// In order for this instruction to be successfully processed, it must be
246    /// accompanied by the following list of `zk_elgamal_proof` program
247    /// instructions:
248    ///
249    /// - `VerifyCiphertextCommitmentEquality`
250    /// - `VerifyBatchedGroupedCiphertext3HandlesValidity`
251    /// - `VerifyBatchedRangeProofU128`
252    ///
253    /// These instructions can be accompanied in the same transaction or can be
254    /// pre-verified into a context state account, in which case, only their
255    /// context state account addresses need to be provided.
256    ///
257    /// Fails if the associated mint is extended as `NonTransferable`.
258    ///
259    ///   * Single owner/delegate
260    ///   1. `[writable]` The source SPL Token account.
261    ///   2. `[]` The token mint.
262    ///   3. `[writable]` The destination SPL Token account.
263    ///   4. `[]` (Optional) Instructions sysvar if at least one of the
264    ///      `zk_elgamal_proof` instructions are included in the same
265    ///      transaction.
266    ///   5. `[]` (Optional) Equality proof context state account.
267    ///   6. `[]` (Optional) Ciphertext validity context state account.
268    ///   7. `[]` (Optional) Range proof context state account.
269    ///   8. `[signer]` The single source account owner.
270    ///
271    ///   * Multisignature owner/delegate
272    ///   1. `[writable]` The source SPL Token account.
273    ///   2. `[]` The token mint.
274    ///   3. `[writable]` The destination SPL Token account.
275    ///   4. `[]` (Optional) Instructions sysvar if at least one of the
276    ///      `zk_elgamal_proof` instructions are included in the same
277    ///      transaction.
278    ///   5. `[]` (Optional) Equality proof context state account.
279    ///   6. `[]` (Optional) Ciphertext validity proof context state account.
280    ///   7. `[]` (Optional) Range proof context state account.
281    ///   8. `[]` The multisig  source account owner.
282    ///   9. .. `[signer]` Required M signer accounts for the SPL Token Multisig
283    ///      account.
284    ///
285    /// Data expected by this instruction:
286    ///   `TransferInstructionData`
287    Transfer,
288
289    /// Applies the pending balance to the available balance, based on the
290    /// history of `Deposit` and/or `Transfer` instructions.
291    ///
292    /// After submitting `ApplyPendingBalance`, the client should compare
293    /// `ConfidentialTransferAccount::expected_pending_balance_credit_counter`
294    /// with
295    /// `ConfidentialTransferAccount::actual_applied_pending_balance_instructions`.  If they are
296    /// equal then the
297    /// `ConfidentialTransferAccount::decryptable_available_balance` is
298    /// consistent with `ConfidentialTransferAccount::available_balance`. If
299    /// they differ then there is more pending balance to be applied.
300    ///
301    /// Account expected by this instruction:
302    ///
303    ///   * Single owner/delegate
304    ///   0. `[writable]` The SPL Token account.
305    ///   1. `[signer]` The single account owner.
306    ///
307    ///   * Multisignature owner/delegate
308    ///   0. `[writable]` The SPL Token account.
309    ///   1. `[]` The multisig account owner.
310    ///   2. .. `[signer]` Required M signer accounts for the SPL Token Multisig
311    ///      account.
312    ///
313    /// Data expected by this instruction:
314    ///   `ApplyPendingBalanceData`
315    ApplyPendingBalance,
316
317    /// Configure a confidential extension account to accept incoming
318    /// confidential transfers.
319    ///
320    /// Accounts expected by this instruction:
321    ///
322    ///   * Single owner/delegate
323    ///   0. `[writable]` The SPL Token account.
324    ///   1. `[signer]` Single authority.
325    ///
326    ///   * Multisignature owner/delegate
327    ///   0. `[writable]` The SPL Token account.
328    ///   1. `[]` Multisig authority.
329    ///   2. .. `[signer]` Required M signer accounts for the SPL Token Multisig
330    ///      account.
331    ///
332    /// Data expected by this instruction:
333    ///   None
334    EnableConfidentialCredits,
335
336    /// Configure a confidential extension account to reject any incoming
337    /// confidential transfers.
338    ///
339    /// If the `allow_non_confidential_credits` field is `true`, then the base
340    /// account can still receive non-confidential transfers.
341    ///
342    /// This instruction can be used to disable confidential payments after a
343    /// token account has already been extended for confidential transfers.
344    ///
345    /// Accounts expected by this instruction:
346    ///
347    ///   * Single owner/delegate
348    ///   0. `[writable]` The SPL Token account.
349    ///   1. `[signer]` The single account owner.
350    ///
351    ///   * Multisignature owner/delegate
352    ///   0. `[writable]` The SPL Token account.
353    ///   1. `[]` The multisig account owner.
354    ///   2. .. `[signer]` Required M signer accounts for the SPL Token Multisig
355    ///      account.
356    ///
357    /// Data expected by this instruction:
358    ///   None
359    DisableConfidentialCredits,
360
361    /// Configure an account with the confidential extension to accept incoming
362    /// non-confidential transfers.
363    ///
364    /// Accounts expected by this instruction:
365    ///
366    ///   * Single owner/delegate
367    ///   0. `[writable]` The SPL Token account.
368    ///   1. `[signer]` The single account owner.
369    ///
370    ///   * Multisignature owner/delegate
371    ///   0. `[writable]` The SPL Token account.
372    ///   1. `[]` The multisig account owner.
373    ///   2. .. `[signer]` Required M signer accounts for the SPL Token Multisig
374    ///      account.
375    ///
376    /// Data expected by this instruction:
377    ///   None
378    EnableNonConfidentialCredits,
379
380    /// Configure an account with the confidential extension to reject any
381    /// incoming non-confidential transfers.
382    ///
383    /// This instruction can be used to configure a confidential extension
384    /// account to exclusively receive confidential payments.
385    ///
386    /// Accounts expected by this instruction:
387    ///
388    ///   * Single owner/delegate
389    ///   0. `[writable]` The SPL Token account.
390    ///   1. `[signer]` The single account owner.
391    ///
392    ///   * Multisignature owner/delegate
393    ///   0. `[writable]` The SPL Token account.
394    ///   1. `[]` The multisig account owner.
395    ///   2. .. `[signer]` Required M signer accounts for the SPL Token Multisig
396    ///      account.
397    ///
398    /// Data expected by this instruction:
399    ///   None
400    DisableNonConfidentialCredits,
401
402    /// Transfer tokens confidentially with fee.
403    ///
404    /// In order for this instruction to be successfully processed, it must be
405    /// accompanied by the following list of `zk_elgamal_proof` program
406    /// instructions:
407    ///
408    /// - `VerifyCiphertextCommitmentEquality`
409    /// - `VerifyBatchedGroupedCiphertext3HandlesValidity` (transfer amount
410    ///   ciphertext)
411    /// - `VerifyPercentageWithFee`
412    /// - `VerifyBatchedGroupedCiphertext2HandlesValidity` (fee ciphertext)
413    /// - `VerifyBatchedRangeProofU256`
414    ///
415    /// These instructions can be accompanied in the same transaction or can be
416    /// pre-verified into a context state account, in which case, only their
417    /// context state account addresses need to be provided.
418    ///
419    /// The same restrictions for the `Transfer` applies to
420    /// `TransferWithFee`. Namely, the instruction fails if the
421    /// associated mint is extended as `NonTransferable`.
422    ///
423    ///   * Transfer without fee
424    ///   1. `[writable]` The source SPL Token account.
425    ///   2. `[]` The token mint.
426    ///   3. `[writable]` The destination SPL Token account.
427    ///   4. `[]` (Optional) Instructions sysvar if at least one of the
428    ///      `zk_elgamal_proof` instructions are included in the same
429    ///      transaction.
430    ///   5. `[]` (Optional) Equality proof context state account.
431    ///   6. `[]` (Optional) Transfer amount ciphertext validity proof context
432    ///      state account.
433    ///   7. `[]` (Optional) Fee sigma proof context state account.
434    ///   8. `[]` (Optional) Fee ciphertext validity proof context state
435    ///      account.
436    ///   9. `[]` (Optional) Range proof context state account.
437    ///   10. `[signer]` The source account owner.
438    ///
439    ///   * Transfer with fee
440    ///   1. `[writable]` The source SPL Token account.
441    ///   2. `[]` The token mint.
442    ///   3. `[writable]` The destination SPL Token account.
443    ///   4. `[]` (Optional) Instructions sysvar if at least one of the
444    ///      `zk_elgamal_proof` instructions are included in the same
445    ///      transaction.
446    ///   5. `[]` (Optional) Equality proof context state account.
447    ///   6. `[]` (Optional) Transfer amount ciphertext validity proof context
448    ///      state account.
449    ///   7. `[]` (Optional) Fee sigma proof context state account.
450    ///   8. `[]` (Optional) Fee ciphertext validity proof context state
451    ///      account.
452    ///   9. `[]` (Optional) Range proof context state account.
453    ///   10. `[]` The multisig  source account owner.
454    ///   11. .. `[signer]` Required M signer accounts for the SPL Token
455    ///       Multisig
456    ///
457    /// Data expected by this instruction:
458    ///   `TransferWithFeeInstructionData`
459    TransferWithFee,
460
461    /// Configures confidential transfers for a token account.
462    ///
463    /// This instruction is identical to the `ConfigureAccount` account except
464    /// that a valid `ElGamalRegistry` account is expected in place of the
465    /// `VerifyPubkeyValidity` proof.
466    ///
467    /// An `ElGamalRegistry` account is valid if it shares the same owner with
468    /// the token account. If a valid `ElGamalRegistry` account is provided,
469    /// then the program skips the verification of the ElGamal pubkey
470    /// validity proof as well as the token owner signature.
471    ///
472    /// If the token account is not large enough to include the new
473    /// confidential transfer extension, then optionally reallocate the
474    /// account to increase the data size. To reallocate, a payer account to
475    /// fund the reallocation and the system account should be included in the
476    /// instruction.
477    ///
478    /// Accounts expected by this instruction:
479    ///
480    ///   * Single owner/delegate
481    ///   0. `[writable]` The SPL Token account.
482    ///   1. `[]` The corresponding SPL Token mint.
483    ///   2. `[]` The ElGamal registry account.
484    ///   3. `[signer, writable]` (Optional) The payer account to fund
485    ///      reallocation
486    ///   4. `[]` (Optional) System program for reallocation funding
487    ///
488    /// Data expected by this instruction:
489    ///   None
490    ConfigureAccountWithRegistry,
491}
492
493/// Data expected by `ConfidentialTransferInstruction::InitializeMint`
494#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
495#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
496#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
497#[repr(C)]
498pub struct InitializeMintData {
499    /// Authority to modify the `ConfidentialTransferMint` configuration and to
500    /// approve new accounts.
501    pub authority: OptionalNonZeroPubkey,
502    /// Determines if newly configured accounts must be approved by the
503    /// `authority` before they may be used by the user.
504    pub auto_approve_new_accounts: PodBool,
505    /// New authority to decode any transfer amount in a confidential transfer.
506    pub auditor_elgamal_pubkey: OptionalNonZeroElGamalPubkey,
507}
508
509/// Data expected by `ConfidentialTransferInstruction::UpdateMint`
510#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
511#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
512#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
513#[repr(C)]
514pub struct UpdateMintData {
515    /// Determines if newly configured accounts must be approved by the
516    /// `authority` before they may be used by the user.
517    pub auto_approve_new_accounts: PodBool,
518    /// New authority to decode any transfer amount in a confidential transfer.
519    pub auditor_elgamal_pubkey: OptionalNonZeroElGamalPubkey,
520}
521
522/// Data expected by `ConfidentialTransferInstruction::ConfigureAccount`
523#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
524#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
525#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
526#[repr(C)]
527pub struct ConfigureAccountInstructionData {
528    /// The decryptable balance (always 0) once the configure account succeeds
529    #[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
530    pub decryptable_zero_balance: DecryptableBalance,
531    /// The maximum number of deposits and transfers that an account can receive
532    /// before the `ApplyPendingBalance` is executed
533    pub maximum_pending_balance_credit_counter: PodU64,
534    /// Relative location of the `ProofInstruction::ZeroCiphertextProof`
535    /// instruction to the `ConfigureAccount` instruction in the
536    /// transaction. If the offset is `0`, then use a context state account
537    /// for the proof.
538    pub proof_instruction_offset: i8,
539}
540
541/// Data expected by `ConfidentialTransferInstruction::EmptyAccount`
542#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
543#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
544#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
545#[repr(C)]
546pub struct EmptyAccountInstructionData {
547    /// Relative location of the `ProofInstruction::VerifyCloseAccount`
548    /// instruction to the `EmptyAccount` instruction in the transaction. If
549    /// the offset is `0`, then use a context state account for the proof.
550    pub proof_instruction_offset: i8,
551}
552
553/// Data expected by `ConfidentialTransferInstruction::Deposit`
554#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
555#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
556#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
557#[repr(C)]
558pub struct DepositInstructionData {
559    /// The amount of tokens to deposit
560    pub amount: PodU64,
561    /// Expected number of base 10 digits to the right of the decimal place
562    pub decimals: u8,
563}
564
565/// Data expected by `ConfidentialTransferInstruction::Withdraw`
566#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
567#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
568#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
569#[repr(C)]
570pub struct WithdrawInstructionData {
571    /// The amount of tokens to withdraw
572    pub amount: PodU64,
573    /// Expected number of base 10 digits to the right of the decimal place
574    pub decimals: u8,
575    /// The new decryptable balance if the withdrawal succeeds
576    #[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
577    pub new_decryptable_available_balance: DecryptableBalance,
578    /// Relative location of the
579    /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction
580    /// to the `Withdraw` instruction in the transaction. If the offset is
581    /// `0`, then use a context state account for the proof.
582    pub equality_proof_instruction_offset: i8,
583    /// Relative location of the `ProofInstruction::BatchedRangeProofU64`
584    /// instruction to the `Withdraw` instruction in the transaction. If the
585    /// offset is `0`, then use a context state account for the proof.
586    pub range_proof_instruction_offset: i8,
587}
588
589/// Data expected by `ConfidentialTransferInstruction::Transfer`
590#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
591#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
592#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
593#[repr(C)]
594pub struct TransferInstructionData {
595    /// The new source decryptable balance if the transfer succeeds
596    #[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
597    pub new_source_decryptable_available_balance: DecryptableBalance,
598    /// The transfer amount encrypted under the auditor ElGamal public key
599    #[cfg_attr(feature = "serde", serde(with = "elgamalciphertext_fromstr"))]
600    pub transfer_amount_auditor_ciphertext_lo: PodElGamalCiphertext,
601    /// The transfer amount encrypted under the auditor ElGamal public key
602    #[cfg_attr(feature = "serde", serde(with = "elgamalciphertext_fromstr"))]
603    pub transfer_amount_auditor_ciphertext_hi: PodElGamalCiphertext,
604    /// Relative location of the
605    /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction
606    /// to the `Transfer` instruction in the transaction. If the offset is
607    /// `0`, then use a context state account for the proof.
608    pub equality_proof_instruction_offset: i8,
609    /// Relative location of the
610    /// `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity`
611    /// instruction to the `Transfer` instruction in the transaction. If the
612    /// offset is `0`, then use a context state account for the proof.
613    pub ciphertext_validity_proof_instruction_offset: i8,
614    /// Relative location of the `ProofInstruction::BatchedRangeProofU128Data`
615    /// instruction to the `Transfer` instruction in the transaction. If the
616    /// offset is `0`, then use a context state account for the proof.
617    pub range_proof_instruction_offset: i8,
618}
619
620/// Data expected by `ConfidentialTransferInstruction::ApplyPendingBalance`
621#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
622#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))]
623#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
624#[repr(C)]
625pub struct ApplyPendingBalanceData {
626    /// The expected number of pending balance credits since the last successful
627    /// `ApplyPendingBalance` instruction
628    pub expected_pending_balance_credit_counter: PodU64,
629    /// The new decryptable balance if the pending balance is applied
630    /// successfully
631    #[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
632    pub new_decryptable_available_balance: DecryptableBalance,
633}
634
635/// Data expected by `ConfidentialTransferInstruction::TransferWithFee`
636#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
637#[derive(Clone, Copy, Debug, PartialEq, Pod, Zeroable)]
638#[repr(C)]
639pub struct TransferWithFeeInstructionData {
640    /// The new source decryptable balance if the transfer succeeds
641    #[cfg_attr(feature = "serde", serde(with = "aeciphertext_fromstr"))]
642    pub new_source_decryptable_available_balance: DecryptableBalance,
643    /// The transfer amount encrypted under the auditor ElGamal public key
644    #[cfg_attr(feature = "serde", serde(with = "elgamalciphertext_fromstr"))]
645    pub transfer_amount_auditor_ciphertext_lo: PodElGamalCiphertext,
646    /// The transfer amount encrypted under the auditor ElGamal public key
647    #[cfg_attr(feature = "serde", serde(with = "elgamalciphertext_fromstr"))]
648    pub transfer_amount_auditor_ciphertext_hi: PodElGamalCiphertext,
649    /// Relative location of the
650    /// `ProofInstruction::VerifyCiphertextCommitmentEquality` instruction
651    /// to the `TransferWithFee` instruction in the transaction. If the offset
652    /// is `0`, then use a context state account for the proof.
653    pub equality_proof_instruction_offset: i8,
654    /// Relative location of the
655    /// `ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity`
656    /// instruction to the `TransferWithFee` instruction in the transaction.
657    /// If the offset is `0`, then use a context state account for the
658    /// proof.
659    pub transfer_amount_ciphertext_validity_proof_instruction_offset: i8,
660    /// Relative location of the `ProofInstruction::VerifyPercentageWithFee`
661    /// instruction to the `TransferWithFee` instruction in the transaction.
662    /// If the offset is `0`, then use a context state account for the
663    /// proof.
664    pub fee_sigma_proof_instruction_offset: i8,
665    /// Relative location of the
666    /// `ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity`
667    /// instruction to the `TransferWithFee` instruction in the transaction.
668    /// If the offset is `0`, then use a context state account for the
669    /// proof.
670    pub fee_ciphertext_validity_proof_instruction_offset: i8,
671    /// Relative location of the `ProofInstruction::BatchedRangeProofU256Data`
672    /// instruction to the `TransferWithFee` instruction in the transaction.
673    /// If the offset is `0`, then use a context state account for the
674    /// proof.
675    pub range_proof_instruction_offset: i8,
676}
677
678/// Create a `InitializeMint` instruction
679pub fn initialize_mint(
680    token_program_id: &Pubkey,
681    mint: &Pubkey,
682    authority: Option<Pubkey>,
683    auto_approve_new_accounts: bool,
684    auditor_elgamal_pubkey: Option<PodElGamalPubkey>,
685) -> Result<Instruction, ProgramError> {
686    check_program_account(token_program_id)?;
687    let accounts = vec![AccountMeta::new(*mint, false)];
688
689    Ok(encode_instruction(
690        token_program_id,
691        accounts,
692        TokenInstruction::ConfidentialTransferExtension,
693        ConfidentialTransferInstruction::InitializeMint,
694        &InitializeMintData {
695            authority: authority.try_into()?,
696            auto_approve_new_accounts: auto_approve_new_accounts.into(),
697            auditor_elgamal_pubkey: auditor_elgamal_pubkey.try_into()?,
698        },
699    ))
700}
701
702/// Create a `UpdateMint` instruction
703pub fn update_mint(
704    token_program_id: &Pubkey,
705    mint: &Pubkey,
706    authority: &Pubkey,
707    multisig_signers: &[&Pubkey],
708    auto_approve_new_accounts: bool,
709    auditor_elgamal_pubkey: Option<PodElGamalPubkey>,
710) -> Result<Instruction, ProgramError> {
711    check_program_account(token_program_id)?;
712    let mut accounts = vec![
713        AccountMeta::new(*mint, false),
714        AccountMeta::new_readonly(*authority, multisig_signers.is_empty()),
715    ];
716    for multisig_signer in multisig_signers.iter() {
717        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
718    }
719    Ok(encode_instruction(
720        token_program_id,
721        accounts,
722        TokenInstruction::ConfidentialTransferExtension,
723        ConfidentialTransferInstruction::UpdateMint,
724        &UpdateMintData {
725            auto_approve_new_accounts: auto_approve_new_accounts.into(),
726            auditor_elgamal_pubkey: auditor_elgamal_pubkey.try_into()?,
727        },
728    ))
729}
730
731/// Create a `ConfigureAccount` instruction
732///
733/// This instruction is suitable for use with a cross-program `invoke`
734#[allow(clippy::too_many_arguments)]
735pub fn inner_configure_account(
736    token_program_id: &Pubkey,
737    token_account: &Pubkey,
738    mint: &Pubkey,
739    decryptable_zero_balance: &DecryptableBalance,
740    maximum_pending_balance_credit_counter: u64,
741    authority: &Pubkey,
742    multisig_signers: &[&Pubkey],
743    proof_data_location: ProofLocation<PubkeyValidityProofData>,
744) -> Result<Instruction, ProgramError> {
745    check_program_account(token_program_id)?;
746
747    let mut accounts = vec![
748        AccountMeta::new(*token_account, false),
749        AccountMeta::new_readonly(*mint, false),
750    ];
751
752    let proof_instruction_offset = match proof_data_location {
753        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
754            accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
755            proof_instruction_offset.into()
756        }
757        ProofLocation::ContextStateAccount(context_state_account) => {
758            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
759            0
760        }
761    };
762
763    accounts.push(AccountMeta::new_readonly(
764        *authority,
765        multisig_signers.is_empty(),
766    ));
767
768    for multisig_signer in multisig_signers.iter() {
769        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
770    }
771
772    Ok(encode_instruction(
773        token_program_id,
774        accounts,
775        TokenInstruction::ConfidentialTransferExtension,
776        ConfidentialTransferInstruction::ConfigureAccount,
777        &ConfigureAccountInstructionData {
778            decryptable_zero_balance: *decryptable_zero_balance,
779            maximum_pending_balance_credit_counter: maximum_pending_balance_credit_counter.into(),
780            proof_instruction_offset,
781        },
782    ))
783}
784
785/// Create a `ConfigureAccount` instruction
786#[allow(clippy::too_many_arguments)]
787pub fn configure_account(
788    token_program_id: &Pubkey,
789    token_account: &Pubkey,
790    mint: &Pubkey,
791    decryptable_zero_balance: &DecryptableBalance,
792    maximum_pending_balance_credit_counter: u64,
793    authority: &Pubkey,
794    multisig_signers: &[&Pubkey],
795    proof_data_location: ProofLocation<PubkeyValidityProofData>,
796) -> Result<Vec<Instruction>, ProgramError> {
797    let mut instructions = vec![inner_configure_account(
798        token_program_id,
799        token_account,
800        mint,
801        decryptable_zero_balance,
802        maximum_pending_balance_credit_counter,
803        authority,
804        multisig_signers,
805        proof_data_location,
806    )?];
807
808    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
809        proof_data_location
810    {
811        // This constructor appends the proof instruction right after the
812        // `ConfigureAccount` instruction. This means that the proof instruction
813        // offset must be always be 1. To use an arbitrary proof instruction
814        // offset, use the `inner_configure_account` constructor.
815        let proof_instruction_offset: i8 = proof_instruction_offset.into();
816        if proof_instruction_offset != 1 {
817            return Err(TokenError::InvalidProofInstructionOffset.into());
818        }
819        instructions
820            .push(ProofInstruction::VerifyPubkeyValidity.encode_verify_proof(None, proof_data));
821    }
822
823    Ok(instructions)
824}
825
826/// Create an `ApproveAccount` instruction
827pub fn approve_account(
828    token_program_id: &Pubkey,
829    account_to_approve: &Pubkey,
830    mint: &Pubkey,
831    authority: &Pubkey,
832    multisig_signers: &[&Pubkey],
833) -> Result<Instruction, ProgramError> {
834    check_program_account(token_program_id)?;
835    let mut accounts = vec![
836        AccountMeta::new(*account_to_approve, false),
837        AccountMeta::new_readonly(*mint, false),
838        AccountMeta::new_readonly(*authority, multisig_signers.is_empty()),
839    ];
840    for multisig_signer in multisig_signers.iter() {
841        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
842    }
843    Ok(encode_instruction(
844        token_program_id,
845        accounts,
846        TokenInstruction::ConfidentialTransferExtension,
847        ConfidentialTransferInstruction::ApproveAccount,
848        &(),
849    ))
850}
851
852/// Create an inner `EmptyAccount` instruction
853///
854/// This instruction is suitable for use with a cross-program `invoke`
855pub fn inner_empty_account(
856    token_program_id: &Pubkey,
857    token_account: &Pubkey,
858    authority: &Pubkey,
859    multisig_signers: &[&Pubkey],
860    proof_data_location: ProofLocation<ZeroCiphertextProofData>,
861) -> Result<Instruction, ProgramError> {
862    check_program_account(token_program_id)?;
863    let mut accounts = vec![AccountMeta::new(*token_account, false)];
864
865    let proof_instruction_offset = match proof_data_location {
866        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
867            accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
868            proof_instruction_offset.into()
869        }
870        ProofLocation::ContextStateAccount(context_state_account) => {
871            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
872            0
873        }
874    };
875
876    accounts.push(AccountMeta::new_readonly(
877        *authority,
878        multisig_signers.is_empty(),
879    ));
880
881    for multisig_signer in multisig_signers.iter() {
882        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
883    }
884
885    Ok(encode_instruction(
886        token_program_id,
887        accounts,
888        TokenInstruction::ConfidentialTransferExtension,
889        ConfidentialTransferInstruction::EmptyAccount,
890        &EmptyAccountInstructionData {
891            proof_instruction_offset,
892        },
893    ))
894}
895
896/// Create a `EmptyAccount` instruction
897pub fn empty_account(
898    token_program_id: &Pubkey,
899    token_account: &Pubkey,
900    authority: &Pubkey,
901    multisig_signers: &[&Pubkey],
902    proof_data_location: ProofLocation<ZeroCiphertextProofData>,
903) -> Result<Vec<Instruction>, ProgramError> {
904    let mut instructions = vec![inner_empty_account(
905        token_program_id,
906        token_account,
907        authority,
908        multisig_signers,
909        proof_data_location,
910    )?];
911
912    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
913        proof_data_location
914    {
915        // This constructor appends the proof instruction right after the `EmptyAccount`
916        // instruction. This means that the proof instruction offset must be always be
917        // 1. To use an arbitrary proof instruction offset, use the
918        // `inner_empty_account` constructor.
919        let proof_instruction_offset: i8 = proof_instruction_offset.into();
920        if proof_instruction_offset != 1 {
921            return Err(TokenError::InvalidProofInstructionOffset.into());
922        }
923        instructions
924            .push(ProofInstruction::VerifyZeroCiphertext.encode_verify_proof(None, proof_data));
925    };
926
927    Ok(instructions)
928}
929
930/// Create a `Deposit` instruction
931#[allow(clippy::too_many_arguments)]
932pub fn deposit(
933    token_program_id: &Pubkey,
934    token_account: &Pubkey,
935    mint: &Pubkey,
936    amount: u64,
937    decimals: u8,
938    authority: &Pubkey,
939    multisig_signers: &[&Pubkey],
940) -> Result<Instruction, ProgramError> {
941    check_program_account(token_program_id)?;
942    let mut accounts = vec![
943        AccountMeta::new(*token_account, false),
944        AccountMeta::new_readonly(*mint, false),
945        AccountMeta::new_readonly(*authority, multisig_signers.is_empty()),
946    ];
947
948    for multisig_signer in multisig_signers.iter() {
949        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
950    }
951
952    Ok(encode_instruction(
953        token_program_id,
954        accounts,
955        TokenInstruction::ConfidentialTransferExtension,
956        ConfidentialTransferInstruction::Deposit,
957        &DepositInstructionData {
958            amount: amount.into(),
959            decimals,
960        },
961    ))
962}
963
964/// Create a inner `Withdraw` instruction
965///
966/// This instruction is suitable for use with a cross-program `invoke`
967#[allow(clippy::too_many_arguments)]
968pub fn inner_withdraw(
969    token_program_id: &Pubkey,
970    token_account: &Pubkey,
971    mint: &Pubkey,
972    amount: u64,
973    decimals: u8,
974    new_decryptable_available_balance: &DecryptableBalance,
975    authority: &Pubkey,
976    multisig_signers: &[&Pubkey],
977    equality_proof_data_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
978    range_proof_data_location: ProofLocation<BatchedRangeProofU64Data>,
979) -> Result<Instruction, ProgramError> {
980    check_program_account(token_program_id)?;
981    let mut accounts = vec![
982        AccountMeta::new(*token_account, false),
983        AccountMeta::new_readonly(*mint, false),
984    ];
985
986    // if at least one of the proof locations is an instruction offset, sysvar
987    // account is needed
988    if equality_proof_data_location.is_instruction_offset()
989        || range_proof_data_location.is_instruction_offset()
990    {
991        accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
992    }
993
994    let equality_proof_instruction_offset = match equality_proof_data_location {
995        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
996            proof_instruction_offset.into()
997        }
998        ProofLocation::ContextStateAccount(context_state_account) => {
999            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
1000            0
1001        }
1002    };
1003
1004    let range_proof_instruction_offset = match range_proof_data_location {
1005        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
1006            proof_instruction_offset.into()
1007        }
1008        ProofLocation::ContextStateAccount(context_state_account) => {
1009            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
1010            0
1011        }
1012    };
1013
1014    accounts.push(AccountMeta::new_readonly(
1015        *authority,
1016        multisig_signers.is_empty(),
1017    ));
1018
1019    for multisig_signer in multisig_signers.iter() {
1020        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
1021    }
1022
1023    Ok(encode_instruction(
1024        token_program_id,
1025        accounts,
1026        TokenInstruction::ConfidentialTransferExtension,
1027        ConfidentialTransferInstruction::Withdraw,
1028        &WithdrawInstructionData {
1029            amount: amount.into(),
1030            decimals,
1031            new_decryptable_available_balance: *new_decryptable_available_balance,
1032            equality_proof_instruction_offset,
1033            range_proof_instruction_offset,
1034        },
1035    ))
1036}
1037
1038/// Create a `Withdraw` instruction
1039#[allow(clippy::too_many_arguments)]
1040pub fn withdraw(
1041    token_program_id: &Pubkey,
1042    token_account: &Pubkey,
1043    mint: &Pubkey,
1044    amount: u64,
1045    decimals: u8,
1046    new_decryptable_available_balance: &DecryptableBalance,
1047    authority: &Pubkey,
1048    multisig_signers: &[&Pubkey],
1049    equality_proof_data_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
1050    range_proof_data_location: ProofLocation<BatchedRangeProofU64Data>,
1051) -> Result<Vec<Instruction>, ProgramError> {
1052    let mut instructions = vec![inner_withdraw(
1053        token_program_id,
1054        token_account,
1055        mint,
1056        amount,
1057        decimals,
1058        new_decryptable_available_balance,
1059        authority,
1060        multisig_signers,
1061        equality_proof_data_location,
1062        range_proof_data_location,
1063    )?];
1064
1065    let mut expected_instruction_offset = 1;
1066
1067    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
1068        equality_proof_data_location
1069    {
1070        let proof_instruction_offset: i8 = proof_instruction_offset.into();
1071        if proof_instruction_offset != expected_instruction_offset {
1072            return Err(TokenError::InvalidProofInstructionOffset.into());
1073        }
1074        instructions.push(
1075            ProofInstruction::VerifyCiphertextCommitmentEquality
1076                .encode_verify_proof(None, proof_data),
1077        );
1078        expected_instruction_offset += 1;
1079    };
1080
1081    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
1082        range_proof_data_location
1083    {
1084        let proof_instruction_offset: i8 = proof_instruction_offset.into();
1085        if proof_instruction_offset != expected_instruction_offset {
1086            return Err(TokenError::InvalidProofInstructionOffset.into());
1087        }
1088        instructions.push(
1089            ProofInstruction::VerifyBatchedRangeProofU64.encode_verify_proof(None, proof_data),
1090        );
1091    };
1092
1093    Ok(instructions)
1094}
1095
1096/// Create an inner `Transfer` instruction
1097///
1098/// This instruction is suitable for use with a cross-program `invoke`
1099#[allow(clippy::too_many_arguments)]
1100pub fn inner_transfer(
1101    token_program_id: &Pubkey,
1102    source_token_account: &Pubkey,
1103    mint: &Pubkey,
1104    destination_token_account: &Pubkey,
1105    new_source_decryptable_available_balance: &DecryptableBalance,
1106    transfer_amount_auditor_ciphertext_lo: &PodElGamalCiphertext,
1107    transfer_amount_auditor_ciphertext_hi: &PodElGamalCiphertext,
1108    authority: &Pubkey,
1109    multisig_signers: &[&Pubkey],
1110    equality_proof_data_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
1111    ciphertext_validity_proof_data_location: ProofLocation<
1112        BatchedGroupedCiphertext3HandlesValidityProofData,
1113    >,
1114    range_proof_data_location: ProofLocation<BatchedRangeProofU128Data>,
1115) -> Result<Instruction, ProgramError> {
1116    check_program_account(token_program_id)?;
1117    let mut accounts = vec![
1118        AccountMeta::new(*source_token_account, false),
1119        AccountMeta::new_readonly(*mint, false),
1120        AccountMeta::new(*destination_token_account, false),
1121    ];
1122
1123    // if at least one of the proof locations is an instruction offset, sysvar
1124    // account is needed
1125    if equality_proof_data_location.is_instruction_offset()
1126        || ciphertext_validity_proof_data_location.is_instruction_offset()
1127        || range_proof_data_location.is_instruction_offset()
1128    {
1129        accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
1130    }
1131
1132    let equality_proof_instruction_offset = match equality_proof_data_location {
1133        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
1134            proof_instruction_offset.into()
1135        }
1136        ProofLocation::ContextStateAccount(context_state_account) => {
1137            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
1138            0
1139        }
1140    };
1141
1142    let ciphertext_validity_proof_instruction_offset = match ciphertext_validity_proof_data_location
1143    {
1144        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
1145            proof_instruction_offset.into()
1146        }
1147        ProofLocation::ContextStateAccount(context_state_account) => {
1148            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
1149            0
1150        }
1151    };
1152
1153    let range_proof_instruction_offset = match range_proof_data_location {
1154        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
1155            proof_instruction_offset.into()
1156        }
1157        ProofLocation::ContextStateAccount(context_state_account) => {
1158            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
1159            0
1160        }
1161    };
1162
1163    accounts.push(AccountMeta::new_readonly(
1164        *authority,
1165        multisig_signers.is_empty(),
1166    ));
1167
1168    for multisig_signer in multisig_signers.iter() {
1169        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
1170    }
1171
1172    Ok(encode_instruction(
1173        token_program_id,
1174        accounts,
1175        TokenInstruction::ConfidentialTransferExtension,
1176        ConfidentialTransferInstruction::Transfer,
1177        &TransferInstructionData {
1178            new_source_decryptable_available_balance: *new_source_decryptable_available_balance,
1179            transfer_amount_auditor_ciphertext_lo: *transfer_amount_auditor_ciphertext_lo,
1180            transfer_amount_auditor_ciphertext_hi: *transfer_amount_auditor_ciphertext_hi,
1181            equality_proof_instruction_offset,
1182            ciphertext_validity_proof_instruction_offset,
1183            range_proof_instruction_offset,
1184        },
1185    ))
1186}
1187
1188/// Create a `Transfer` instruction
1189#[allow(clippy::too_many_arguments)]
1190pub fn transfer(
1191    token_program_id: &Pubkey,
1192    source_token_account: &Pubkey,
1193    mint: &Pubkey,
1194    destination_token_account: &Pubkey,
1195    new_source_decryptable_available_balance: &DecryptableBalance,
1196    transfer_amount_auditor_ciphertext_lo: &PodElGamalCiphertext,
1197    transfer_amount_auditor_ciphertext_hi: &PodElGamalCiphertext,
1198    authority: &Pubkey,
1199    multisig_signers: &[&Pubkey],
1200    equality_proof_data_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
1201    ciphertext_validity_proof_data_location: ProofLocation<
1202        BatchedGroupedCiphertext3HandlesValidityProofData,
1203    >,
1204    range_proof_data_location: ProofLocation<BatchedRangeProofU128Data>,
1205) -> Result<Vec<Instruction>, ProgramError> {
1206    let mut instructions = vec![inner_transfer(
1207        token_program_id,
1208        source_token_account,
1209        mint,
1210        destination_token_account,
1211        new_source_decryptable_available_balance,
1212        transfer_amount_auditor_ciphertext_lo,
1213        transfer_amount_auditor_ciphertext_hi,
1214        authority,
1215        multisig_signers,
1216        equality_proof_data_location,
1217        ciphertext_validity_proof_data_location,
1218        range_proof_data_location,
1219    )?];
1220
1221    let mut expected_instruction_offset = 1;
1222
1223    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
1224        equality_proof_data_location
1225    {
1226        let proof_instruction_offset: i8 = proof_instruction_offset.into();
1227        if proof_instruction_offset != expected_instruction_offset {
1228            return Err(TokenError::InvalidProofInstructionOffset.into());
1229        }
1230        instructions.push(
1231            ProofInstruction::VerifyCiphertextCommitmentEquality
1232                .encode_verify_proof(None, proof_data),
1233        );
1234        expected_instruction_offset += 1;
1235    }
1236
1237    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
1238        ciphertext_validity_proof_data_location
1239    {
1240        let proof_instruction_offset: i8 = proof_instruction_offset.into();
1241        if proof_instruction_offset != expected_instruction_offset {
1242            return Err(TokenError::InvalidProofInstructionOffset.into());
1243        }
1244        instructions.push(
1245            ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity
1246                .encode_verify_proof(None, proof_data),
1247        );
1248        expected_instruction_offset += 1;
1249    }
1250
1251    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
1252        range_proof_data_location
1253    {
1254        let proof_instruction_offset: i8 = proof_instruction_offset.into();
1255        if proof_instruction_offset != expected_instruction_offset {
1256            return Err(TokenError::InvalidProofInstructionOffset.into());
1257        }
1258        instructions.push(
1259            ProofInstruction::VerifyBatchedRangeProofU128.encode_verify_proof(None, proof_data),
1260        );
1261    }
1262
1263    Ok(instructions)
1264}
1265
1266/// Create a inner `ApplyPendingBalance` instruction
1267///
1268/// This instruction is suitable for use with a cross-program `invoke`
1269pub fn inner_apply_pending_balance(
1270    token_program_id: &Pubkey,
1271    token_account: &Pubkey,
1272    expected_pending_balance_credit_counter: u64,
1273    new_decryptable_available_balance: &DecryptableBalance,
1274    authority: &Pubkey,
1275    multisig_signers: &[&Pubkey],
1276) -> Result<Instruction, ProgramError> {
1277    check_program_account(token_program_id)?;
1278    let mut accounts = vec![
1279        AccountMeta::new(*token_account, false),
1280        AccountMeta::new_readonly(*authority, multisig_signers.is_empty()),
1281    ];
1282
1283    for multisig_signer in multisig_signers.iter() {
1284        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
1285    }
1286
1287    Ok(encode_instruction(
1288        token_program_id,
1289        accounts,
1290        TokenInstruction::ConfidentialTransferExtension,
1291        ConfidentialTransferInstruction::ApplyPendingBalance,
1292        &ApplyPendingBalanceData {
1293            expected_pending_balance_credit_counter: expected_pending_balance_credit_counter.into(),
1294            new_decryptable_available_balance: *new_decryptable_available_balance,
1295        },
1296    ))
1297}
1298
1299/// Create a `ApplyPendingBalance` instruction
1300pub fn apply_pending_balance(
1301    token_program_id: &Pubkey,
1302    token_account: &Pubkey,
1303    pending_balance_instructions: u64,
1304    new_decryptable_available_balance: &DecryptableBalance,
1305    authority: &Pubkey,
1306    multisig_signers: &[&Pubkey],
1307) -> Result<Instruction, ProgramError> {
1308    inner_apply_pending_balance(
1309        token_program_id,
1310        token_account,
1311        pending_balance_instructions,
1312        new_decryptable_available_balance,
1313        authority,
1314        multisig_signers,
1315    ) // calls check_program_account
1316}
1317
1318fn enable_or_disable_balance_credits(
1319    instruction: ConfidentialTransferInstruction,
1320    token_program_id: &Pubkey,
1321    token_account: &Pubkey,
1322    authority: &Pubkey,
1323    multisig_signers: &[&Pubkey],
1324) -> Result<Instruction, ProgramError> {
1325    check_program_account(token_program_id)?;
1326    let mut accounts = vec![
1327        AccountMeta::new(*token_account, false),
1328        AccountMeta::new_readonly(*authority, multisig_signers.is_empty()),
1329    ];
1330
1331    for multisig_signer in multisig_signers.iter() {
1332        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
1333    }
1334
1335    Ok(encode_instruction(
1336        token_program_id,
1337        accounts,
1338        TokenInstruction::ConfidentialTransferExtension,
1339        instruction,
1340        &(),
1341    ))
1342}
1343
1344/// Create a `EnableConfidentialCredits` instruction
1345pub fn enable_confidential_credits(
1346    token_program_id: &Pubkey,
1347    token_account: &Pubkey,
1348    authority: &Pubkey,
1349    multisig_signers: &[&Pubkey],
1350) -> Result<Instruction, ProgramError> {
1351    enable_or_disable_balance_credits(
1352        ConfidentialTransferInstruction::EnableConfidentialCredits,
1353        token_program_id,
1354        token_account,
1355        authority,
1356        multisig_signers,
1357    )
1358}
1359
1360/// Create a `DisableConfidentialCredits` instruction
1361pub fn disable_confidential_credits(
1362    token_program_id: &Pubkey,
1363    token_account: &Pubkey,
1364    authority: &Pubkey,
1365    multisig_signers: &[&Pubkey],
1366) -> Result<Instruction, ProgramError> {
1367    enable_or_disable_balance_credits(
1368        ConfidentialTransferInstruction::DisableConfidentialCredits,
1369        token_program_id,
1370        token_account,
1371        authority,
1372        multisig_signers,
1373    )
1374}
1375
1376/// Create a `EnableNonConfidentialCredits` instruction
1377pub fn enable_non_confidential_credits(
1378    token_program_id: &Pubkey,
1379    token_account: &Pubkey,
1380    authority: &Pubkey,
1381    multisig_signers: &[&Pubkey],
1382) -> Result<Instruction, ProgramError> {
1383    enable_or_disable_balance_credits(
1384        ConfidentialTransferInstruction::EnableNonConfidentialCredits,
1385        token_program_id,
1386        token_account,
1387        authority,
1388        multisig_signers,
1389    )
1390}
1391
1392/// Create a `DisableNonConfidentialCredits` instruction
1393pub fn disable_non_confidential_credits(
1394    token_program_id: &Pubkey,
1395    token_account: &Pubkey,
1396    authority: &Pubkey,
1397    multisig_signers: &[&Pubkey],
1398) -> Result<Instruction, ProgramError> {
1399    enable_or_disable_balance_credits(
1400        ConfidentialTransferInstruction::DisableNonConfidentialCredits,
1401        token_program_id,
1402        token_account,
1403        authority,
1404        multisig_signers,
1405    )
1406}
1407
1408/// Create an inner `TransferWithFee` instruction
1409///
1410/// This instruction is suitable for use with a cross-program `invoke`
1411#[allow(clippy::too_many_arguments)]
1412pub fn inner_transfer_with_fee(
1413    token_program_id: &Pubkey,
1414    source_token_account: &Pubkey,
1415    mint: &Pubkey,
1416    destination_token_account: &Pubkey,
1417    new_source_decryptable_available_balance: &DecryptableBalance,
1418    transfer_amount_auditor_ciphertext_lo: &PodElGamalCiphertext,
1419    transfer_amount_auditor_ciphertext_hi: &PodElGamalCiphertext,
1420    authority: &Pubkey,
1421    multisig_signers: &[&Pubkey],
1422    equality_proof_data_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
1423    transfer_amount_ciphertext_validity_proof_data_location: ProofLocation<
1424        BatchedGroupedCiphertext3HandlesValidityProofData,
1425    >,
1426    fee_sigma_proof_data_location: ProofLocation<PercentageWithCapProofData>,
1427    fee_ciphertext_validity_proof_data_location: ProofLocation<
1428        BatchedGroupedCiphertext2HandlesValidityProofData,
1429    >,
1430    range_proof_data_location: ProofLocation<BatchedRangeProofU256Data>,
1431) -> Result<Instruction, ProgramError> {
1432    check_program_account(token_program_id)?;
1433    let mut accounts = vec![
1434        AccountMeta::new(*source_token_account, false),
1435        AccountMeta::new_readonly(*mint, false),
1436        AccountMeta::new(*destination_token_account, false),
1437    ];
1438
1439    // if at least one of the proof locations is an instruction offset, sysvar
1440    // account is needed
1441    if equality_proof_data_location.is_instruction_offset()
1442        || transfer_amount_ciphertext_validity_proof_data_location.is_instruction_offset()
1443        || fee_sigma_proof_data_location.is_instruction_offset()
1444        || fee_ciphertext_validity_proof_data_location.is_instruction_offset()
1445        || range_proof_data_location.is_instruction_offset()
1446    {
1447        accounts.push(AccountMeta::new_readonly(sysvar::instructions::id(), false));
1448    }
1449
1450    let equality_proof_instruction_offset = match equality_proof_data_location {
1451        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
1452            proof_instruction_offset.into()
1453        }
1454        ProofLocation::ContextStateAccount(context_state_account) => {
1455            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
1456            0
1457        }
1458    };
1459
1460    let transfer_amount_ciphertext_validity_proof_instruction_offset =
1461        match transfer_amount_ciphertext_validity_proof_data_location {
1462            ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
1463                proof_instruction_offset.into()
1464            }
1465            ProofLocation::ContextStateAccount(context_state_account) => {
1466                accounts.push(AccountMeta::new_readonly(*context_state_account, false));
1467                0
1468            }
1469        };
1470
1471    let fee_sigma_proof_instruction_offset = match fee_sigma_proof_data_location {
1472        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
1473            proof_instruction_offset.into()
1474        }
1475        ProofLocation::ContextStateAccount(context_state_account) => {
1476            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
1477            0
1478        }
1479    };
1480
1481    let fee_ciphertext_validity_proof_instruction_offset =
1482        match fee_ciphertext_validity_proof_data_location {
1483            ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
1484                proof_instruction_offset.into()
1485            }
1486            ProofLocation::ContextStateAccount(context_state_account) => {
1487                accounts.push(AccountMeta::new_readonly(*context_state_account, false));
1488                0
1489            }
1490        };
1491
1492    let range_proof_instruction_offset = match range_proof_data_location {
1493        ProofLocation::InstructionOffset(proof_instruction_offset, _) => {
1494            proof_instruction_offset.into()
1495        }
1496        ProofLocation::ContextStateAccount(context_state_account) => {
1497            accounts.push(AccountMeta::new_readonly(*context_state_account, false));
1498            0
1499        }
1500    };
1501
1502    accounts.push(AccountMeta::new_readonly(
1503        *authority,
1504        multisig_signers.is_empty(),
1505    ));
1506
1507    for multisig_signer in multisig_signers.iter() {
1508        accounts.push(AccountMeta::new_readonly(**multisig_signer, true));
1509    }
1510
1511    Ok(encode_instruction(
1512        token_program_id,
1513        accounts,
1514        TokenInstruction::ConfidentialTransferExtension,
1515        ConfidentialTransferInstruction::TransferWithFee,
1516        &TransferWithFeeInstructionData {
1517            new_source_decryptable_available_balance: *new_source_decryptable_available_balance,
1518            transfer_amount_auditor_ciphertext_lo: *transfer_amount_auditor_ciphertext_lo,
1519            transfer_amount_auditor_ciphertext_hi: *transfer_amount_auditor_ciphertext_hi,
1520            equality_proof_instruction_offset,
1521            transfer_amount_ciphertext_validity_proof_instruction_offset,
1522            fee_sigma_proof_instruction_offset,
1523            fee_ciphertext_validity_proof_instruction_offset,
1524            range_proof_instruction_offset,
1525        },
1526    ))
1527}
1528
1529/// Create a `TransferWithFee` instruction
1530#[allow(clippy::too_many_arguments)]
1531pub fn transfer_with_fee(
1532    token_program_id: &Pubkey,
1533    source_token_account: &Pubkey,
1534    mint: &Pubkey,
1535    destination_token_account: &Pubkey,
1536    new_source_decryptable_available_balance: &DecryptableBalance,
1537    transfer_amount_auditor_ciphertext_lo: &PodElGamalCiphertext,
1538    transfer_amount_auditor_ciphertext_hi: &PodElGamalCiphertext,
1539    authority: &Pubkey,
1540    multisig_signers: &[&Pubkey],
1541    equality_proof_data_location: ProofLocation<CiphertextCommitmentEqualityProofData>,
1542    transfer_amount_ciphertext_validity_proof_data_location: ProofLocation<
1543        BatchedGroupedCiphertext3HandlesValidityProofData,
1544    >,
1545    fee_sigma_proof_data_location: ProofLocation<PercentageWithCapProofData>,
1546    fee_ciphertext_validity_proof_data_location: ProofLocation<
1547        BatchedGroupedCiphertext2HandlesValidityProofData,
1548    >,
1549    range_proof_data_location: ProofLocation<BatchedRangeProofU256Data>,
1550) -> Result<Vec<Instruction>, ProgramError> {
1551    let mut instructions = vec![inner_transfer_with_fee(
1552        token_program_id,
1553        source_token_account,
1554        mint,
1555        destination_token_account,
1556        new_source_decryptable_available_balance,
1557        transfer_amount_auditor_ciphertext_lo,
1558        transfer_amount_auditor_ciphertext_hi,
1559        authority,
1560        multisig_signers,
1561        equality_proof_data_location,
1562        transfer_amount_ciphertext_validity_proof_data_location,
1563        fee_sigma_proof_data_location,
1564        fee_ciphertext_validity_proof_data_location,
1565        range_proof_data_location,
1566    )?];
1567
1568    let mut expected_instruction_offset = 1;
1569
1570    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
1571        equality_proof_data_location
1572    {
1573        let proof_instruction_offset: i8 = proof_instruction_offset.into();
1574        if proof_instruction_offset != expected_instruction_offset {
1575            return Err(TokenError::InvalidProofInstructionOffset.into());
1576        }
1577        instructions.push(
1578            ProofInstruction::VerifyCiphertextCommitmentEquality
1579                .encode_verify_proof(None, proof_data),
1580        );
1581        expected_instruction_offset += 1;
1582    }
1583
1584    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
1585        transfer_amount_ciphertext_validity_proof_data_location
1586    {
1587        let proof_instruction_offset: i8 = proof_instruction_offset.into();
1588        if proof_instruction_offset != expected_instruction_offset {
1589            return Err(TokenError::InvalidProofInstructionOffset.into());
1590        }
1591        instructions.push(
1592            ProofInstruction::VerifyBatchedGroupedCiphertext3HandlesValidity
1593                .encode_verify_proof(None, proof_data),
1594        );
1595        expected_instruction_offset += 1;
1596    }
1597
1598    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
1599        fee_sigma_proof_data_location
1600    {
1601        let proof_instruction_offset: i8 = proof_instruction_offset.into();
1602        if proof_instruction_offset != expected_instruction_offset {
1603            return Err(TokenError::InvalidProofInstructionOffset.into());
1604        }
1605        instructions
1606            .push(ProofInstruction::VerifyPercentageWithCap.encode_verify_proof(None, proof_data));
1607        expected_instruction_offset += 1;
1608    }
1609
1610    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
1611        fee_ciphertext_validity_proof_data_location
1612    {
1613        let proof_instruction_offset: i8 = proof_instruction_offset.into();
1614        if proof_instruction_offset != expected_instruction_offset {
1615            return Err(TokenError::InvalidProofInstructionOffset.into());
1616        }
1617        instructions.push(
1618            ProofInstruction::VerifyBatchedGroupedCiphertext2HandlesValidity
1619                .encode_verify_proof(None, proof_data),
1620        );
1621        expected_instruction_offset += 1;
1622    }
1623
1624    if let ProofLocation::InstructionOffset(proof_instruction_offset, proof_data) =
1625        range_proof_data_location
1626    {
1627        let proof_instruction_offset: i8 = proof_instruction_offset.into();
1628        if proof_instruction_offset != expected_instruction_offset {
1629            return Err(TokenError::InvalidProofInstructionOffset.into());
1630        }
1631        instructions.push(
1632            ProofInstruction::VerifyBatchedRangeProofU256.encode_verify_proof(None, proof_data),
1633        );
1634    }
1635
1636    Ok(instructions)
1637}
1638
1639/// Create a `ConfigureAccountWithRegistry` instruction
1640pub fn configure_account_with_registry(
1641    token_program_id: &Pubkey,
1642    token_account: &Pubkey,
1643    mint: &Pubkey,
1644    elgamal_registry_account: &Pubkey,
1645    payer: Option<&Pubkey>,
1646) -> Result<Instruction, ProgramError> {
1647    check_program_account(token_program_id)?;
1648    let mut accounts = vec![
1649        AccountMeta::new(*token_account, false),
1650        AccountMeta::new_readonly(*mint, false),
1651        AccountMeta::new_readonly(*elgamal_registry_account, false),
1652    ];
1653    if let Some(payer) = payer {
1654        accounts.push(AccountMeta::new(*payer, true));
1655        accounts.push(AccountMeta::new_readonly(system_program::id(), false));
1656    }
1657
1658    Ok(encode_instruction(
1659        token_program_id,
1660        accounts,
1661        TokenInstruction::ConfidentialTransferExtension,
1662        ConfidentialTransferInstruction::ConfigureAccountWithRegistry,
1663        &(),
1664    ))
1665}