squads_multisig/
client.rs

1use solana_client::nonblocking::rpc_client::RpcClient;
2
3pub use squads_multisig_program::accounts::BatchAccountsClose as BatchAccountsCloseAccounts;
4pub use squads_multisig_program::accounts::ConfigTransactionAccountsClose as ConfigTransactionAccountsCloseAccounts;
5pub use squads_multisig_program::accounts::ConfigTransactionCreate as ConfigTransactionCreateAccounts;
6pub use squads_multisig_program::accounts::ConfigTransactionExecute as ConfigTransactionExecuteAccounts;
7pub use squads_multisig_program::accounts::MultisigCreateV2 as MultisigCreateAccountsV2;
8pub use squads_multisig_program::accounts::ProposalCreate as ProposalCreateAccounts;
9pub use squads_multisig_program::accounts::ProposalVote as ProposalVoteAccounts;
10pub use squads_multisig_program::accounts::SpendingLimitUse as SpendingLimitUseAccounts;
11pub use squads_multisig_program::accounts::VaultBatchTransactionAccountClose as VaultBatchTransactionAccountCloseAccounts;
12pub use squads_multisig_program::accounts::VaultTransactionAccountsClose as VaultTransactionAccountsCloseAccounts;
13pub use squads_multisig_program::accounts::VaultTransactionCreate as VaultTransactionCreateAccounts;
14pub use squads_multisig_program::accounts::VaultTransactionExecute as VaultTransactionExecuteAccounts;
15use squads_multisig_program::anchor_lang::AnchorSerialize;
16pub use squads_multisig_program::instruction::ConfigTransactionAccountsClose as ConfigTransactionAccountsCloseData;
17pub use squads_multisig_program::instruction::ConfigTransactionCreate as ConfigTransactionCreateData;
18pub use squads_multisig_program::instruction::ConfigTransactionExecute as ConfigTransactionExecuteData;
19pub use squads_multisig_program::instruction::MultisigCreate as MultisigCreateData;
20pub use squads_multisig_program::instruction::MultisigCreateV2 as MultisigCreateDataV2;
21pub use squads_multisig_program::instruction::ProposalApprove as ProposalApproveData;
22pub use squads_multisig_program::instruction::ProposalCancel as ProposalCancelData;
23pub use squads_multisig_program::instruction::ProposalCreate as ProposalCreateData;
24pub use squads_multisig_program::instruction::SpendingLimitUse as SpendingLimitUseData;
25pub use squads_multisig_program::instruction::VaultTransactionAccountsClose as VaultTransactionAccountsCloseData;
26pub use squads_multisig_program::instruction::VaultTransactionCreate as VaultTransactionCreateData;
27pub use squads_multisig_program::instruction::VaultTransactionExecute as VaultTransactionExecuteData;
28pub use squads_multisig_program::instructions::ConfigTransactionCreateArgs;
29pub use squads_multisig_program::instructions::MultisigCreateArgsV2;
30pub use squads_multisig_program::instructions::ProposalCreateArgs;
31pub use squads_multisig_program::instructions::ProposalVoteArgs;
32pub use squads_multisig_program::instructions::SpendingLimitUseArgs;
33pub use squads_multisig_program::instructions::VaultTransactionCreateArgs;
34use squads_multisig_program::TransactionMessage;
35
36use crate::anchor_lang::prelude::Pubkey;
37use crate::anchor_lang::AccountDeserialize;
38use crate::anchor_lang::{
39    solana_program::instruction::Instruction, InstructionData, ToAccountMetas,
40};
41use crate::client::utils::IntoAccountMetas;
42use crate::error::ClientError;
43use crate::pda::get_vault_pda;
44use crate::solana_program::address_lookup_table_account::AddressLookupTableAccount;
45use crate::solana_program::instruction::AccountMeta;
46use crate::state::{Multisig, SpendingLimit};
47use crate::vault_transaction::{Error, VaultTransactionMessageExt};
48use crate::ClientResult;
49
50/// Gets a `Multisig` account from the chain.
51pub async fn get_multisig(rpc_client: &RpcClient, multisig_key: &Pubkey) -> ClientResult<Multisig> {
52    let multisig_account = rpc_client.get_account(multisig_key).await?;
53
54    let multisig = Multisig::try_deserialize(&mut multisig_account.data.as_slice())
55        .map_err(|_| ClientError::DeserializationError)?;
56
57    Ok(multisig)
58}
59
60/// Gets a `SpendingLimit` account from the chain.
61pub async fn get_spending_limit(
62    rpc_client: &RpcClient,
63    spending_limit_key: &Pubkey,
64) -> ClientResult<SpendingLimit> {
65    let spending_limit_account = rpc_client.get_account(spending_limit_key).await?;
66
67    let spending_limit =
68        SpendingLimit::try_deserialize(&mut spending_limit_account.data.as_slice())
69            .map_err(|_| ClientError::DeserializationError)?;
70
71    Ok(spending_limit)
72}
73
74/// Creates a new multisig config transaction.
75/// Example:
76/// ```
77/// use squads_multisig::anchor_lang::error::ComparedValues::Pubkeys;
78/// use squads_multisig::solana_program::pubkey::Pubkey;
79/// use squads_multisig::solana_program::system_program;
80/// use squads_multisig::state::{ConfigAction, Member, Permissions, Permission};
81/// use squads_multisig::client::{
82///     MultisigCreateAccountsV2,
83///     MultisigCreateArgsV2,
84///     multisig_create_v2
85/// };
86///
87/// let ix = multisig_create_v2(
88///     MultisigCreateAccountsV2 {
89///         program_config: Pubkey::new_unique(),
90///         treasury: Pubkey::new_unique(),
91///         multisig: Pubkey::new_unique(),
92///         create_key: Pubkey::new_unique(),
93///         creator: Pubkey::new_unique(),
94///         system_program: system_program::id(),
95///     },
96///     MultisigCreateArgsV2 {
97///         members: vec![
98///             Member {
99///                 key: Pubkey::new_unique(),
100///                 permissions: Permissions::from_vec(&[Permission::Initiate, Permission::Vote, Permission::Execute]),
101///             }
102///         ],
103///         threshold: 1,
104///         time_lock: 0,
105///         config_authority: None,
106///         rent_collector: None,
107///         memo: Some("Deploy my own Squad".to_string()),
108///     },
109///     Some(squads_multisig_program::ID)
110/// );
111/// ```
112///
113pub fn multisig_create_v2(
114    accounts: MultisigCreateAccountsV2,
115    args: MultisigCreateArgsV2,
116    program_id: Option<Pubkey>,
117) -> Instruction {
118    Instruction {
119        accounts: accounts.to_account_metas(Some(false)),
120        data: MultisigCreateDataV2 { args }.data(),
121        program_id: program_id.unwrap_or(squads_multisig_program::ID),
122    }
123}
124
125/// Creates a new multisig config transaction.
126/// Example:
127/// ```
128/// use squads_multisig::solana_program::pubkey::Pubkey;
129/// use squads_multisig::solana_program::system_program;
130/// use squads_multisig::state::ConfigAction;
131/// use squads_multisig::client::{
132///     ConfigTransactionCreateAccounts,
133///     ConfigTransactionCreateArgs,
134///     config_transaction_create
135/// };
136///
137/// let ix = config_transaction_create(
138///     ConfigTransactionCreateAccounts {
139///         multisig: Pubkey::new_unique(),
140///         creator: Pubkey::new_unique(),
141///         rent_payer: Pubkey::new_unique(),
142///         transaction: Pubkey::new_unique(),
143///         system_program: system_program::id(),
144///     },
145///     ConfigTransactionCreateArgs {
146///         actions: vec![ConfigAction::ChangeThreshold { new_threshold: 2 }],
147///         memo: None,
148///     },
149///     Some(squads_multisig_program::ID)
150/// );
151/// ```
152///
153pub fn config_transaction_create(
154    accounts: ConfigTransactionCreateAccounts,
155    args: ConfigTransactionCreateArgs,
156    program_id: Option<Pubkey>,
157) -> Instruction {
158    Instruction {
159        accounts: accounts.to_account_metas(Some(false)),
160        data: ConfigTransactionCreateData { args }.data(),
161        program_id: program_id.unwrap_or(squads_multisig_program::ID),
162    }
163}
164
165/// Executes a multisig config transaction.
166/// Example:
167/// ```
168/// use squads_multisig::solana_program::pubkey::Pubkey;
169/// use squads_multisig::solana_program::system_program;
170/// use squads_multisig::state::ConfigAction;
171/// use squads_multisig::client::{
172///     ConfigTransactionExecuteAccounts,
173///     config_transaction_execute
174/// };
175///
176/// let ix = config_transaction_execute(
177///     ConfigTransactionExecuteAccounts {
178///         multisig: Pubkey::new_unique(),
179///         member: Pubkey::new_unique(),
180///         proposal: Pubkey::new_unique(),
181///         transaction: Pubkey::new_unique(),
182///         rent_payer: None,
183///         system_program: None,
184///     },
185///     vec![],
186///     Some(squads_multisig_program::ID)
187/// );
188/// ```
189pub fn config_transaction_execute(
190    accounts: ConfigTransactionExecuteAccounts,
191    spending_limit_accounts: Vec<Pubkey>,
192    program_id: Option<Pubkey>,
193) -> Instruction {
194    let program_id = program_id.unwrap_or(squads_multisig_program::ID);
195
196    let account_metas = [
197        accounts.into_account_metas(program_id),
198        // Spending Limit accounts are optional and are passed as remaining_accounts
199        // if the Config Transaction adds or removes some.
200        spending_limit_accounts
201            .into_iter()
202            .map(|key| AccountMeta::new(key, false))
203            .collect(),
204    ]
205    .concat();
206
207    Instruction {
208        accounts: account_metas,
209        data: ConfigTransactionExecuteData.data(),
210        program_id,
211    }
212}
213
214/// Creates a new multisig proposal.
215/// Example:
216/// ```
217/// use squads_multisig::solana_program::pubkey::Pubkey;
218/// use squads_multisig::solana_program::system_program;
219/// use squads_multisig::state::ConfigAction;
220/// use squads_multisig::client::{
221///     ProposalCreateAccounts,
222///     ProposalCreateArgs,
223///     proposal_create
224/// };
225///
226/// let ix = proposal_create(
227///     ProposalCreateAccounts {
228///         multisig: Pubkey::new_unique(),
229///         creator: Pubkey::new_unique(),
230///         proposal: Pubkey::new_unique(),
231///         rent_payer: Pubkey::new_unique(),
232///         system_program: system_program::id(),
233///     },
234///     ProposalCreateArgs {
235///         transaction_index: 0,
236///             draft: false,
237///     },
238///     Some(squads_multisig_program::ID)
239/// );
240/// ```
241///
242pub fn proposal_create(
243    accounts: ProposalCreateAccounts,
244    args: ProposalCreateArgs,
245    program_id: Option<Pubkey>,
246) -> Instruction {
247    Instruction {
248        accounts: accounts.to_account_metas(Some(false)),
249        data: ProposalCreateData { args }.data(),
250        program_id: program_id.unwrap_or(squads_multisig_program::ID),
251    }
252}
253
254/// Votes "approve" on a multisig proposal.
255/// Example:
256/// ```
257/// use squads_multisig::solana_program::pubkey::Pubkey;
258/// use squads_multisig::client::{
259///     ProposalVoteAccounts,
260///     ProposalVoteArgs,
261///     proposal_approve,
262/// };
263///
264/// let ix = proposal_approve(
265///     ProposalVoteAccounts {
266///         multisig: Pubkey::new_unique(),
267///         proposal: Pubkey::new_unique(),
268///         member: Pubkey::new_unique(),
269///     },
270///     ProposalVoteArgs { memo: None },
271///     Some(squads_multisig_program::ID)
272/// );
273/// ```
274pub fn proposal_approve(
275    accounts: ProposalVoteAccounts,
276    args: ProposalVoteArgs,
277    program_id: Option<Pubkey>,
278) -> Instruction {
279    Instruction {
280        accounts: accounts.to_account_metas(Some(false)),
281        data: ProposalApproveData { args }.data(),
282        program_id: program_id.unwrap_or(squads_multisig_program::ID),
283    }
284}
285
286/// Votes "cancel" on a multisig proposal.
287/// Example:
288/// ```
289/// use squads_multisig::solana_program::pubkey::Pubkey;
290/// use squads_multisig::client::{
291///     ProposalVoteAccounts,
292///     ProposalVoteArgs,
293///     proposal_cancel,
294/// };
295///
296/// let ix = proposal_cancel(
297///     ProposalVoteAccounts {
298///         multisig: Pubkey::new_unique(),
299///         proposal: Pubkey::new_unique(),
300///         member: Pubkey::new_unique(),
301///     },
302///     ProposalVoteArgs { memo: None },
303///     Some(squads_multisig_program::ID)
304/// );
305/// ```
306pub fn proposal_cancel(
307    accounts: ProposalVoteAccounts,
308    args: ProposalVoteArgs,
309    program_id: Option<Pubkey>,
310) -> Instruction {
311    Instruction {
312        accounts: accounts.to_account_metas(Some(false)),
313        data: ProposalCancelData { args }.data(),
314        program_id: program_id.unwrap_or(squads_multisig_program::ID),
315    }
316}
317
318/// Use a Spending Limit to transfer tokens from a multisig vault to a destination account.
319/// Example:
320/// ```
321/// use squads_multisig::solana_program::pubkey::Pubkey;
322/// use squads_multisig::solana_program::system_program;
323/// use squads_multisig::solana_program::native_token::LAMPORTS_PER_SOL;
324/// use squads_multisig::client::{
325///     SpendingLimitUseAccounts,
326///     SpendingLimitUseArgs,
327///     spending_limit_use,
328/// };
329///
330/// let ix = spending_limit_use(
331///     SpendingLimitUseAccounts {
332///         multisig: Pubkey::new_unique(),
333///         member: Pubkey::new_unique(),
334///         spending_limit: Pubkey::new_unique(),
335///         vault: Pubkey::new_unique(),
336///         destination: Pubkey::new_unique(),
337///         system_program: Some(system_program::id()),
338///         mint: None,
339///         vault_token_account: None,
340///         destination_token_account: None,
341///         token_program: None,
342///     },
343///     SpendingLimitUseArgs {
344///         amount: 1 * LAMPORTS_PER_SOL,
345///         decimals: 9,
346///         memo: None
347///     },
348///     None,
349/// );
350/// ```
351///
352pub fn spending_limit_use(
353    accounts: SpendingLimitUseAccounts,
354    args: SpendingLimitUseArgs,
355    program_id: Option<Pubkey>,
356) -> Instruction {
357    let program_id = program_id.unwrap_or(squads_multisig_program::ID);
358
359    Instruction {
360        accounts: accounts.into_account_metas(program_id),
361        data: SpendingLimitUseData { args }.data(),
362        program_id,
363    }
364}
365
366/// Creates a new vault transaction.
367/// Example:
368/// ```
369/// use squads_multisig::anchor_lang::AnchorSerialize;
370/// use squads_multisig::solana_program::pubkey::Pubkey;
371/// use squads_multisig::solana_program::{system_instruction, system_program};
372/// use squads_multisig::client::{
373///     VaultTransactionCreateAccounts,
374///     VaultTransactionCreateArgs,
375///     vault_transaction_create,
376/// };
377/// use squads_multisig::pda::get_vault_pda;
378/// use squads_multisig::vault_transaction::VaultTransactionMessageExt;
379/// use squads_multisig_program::TransactionMessage;
380///
381/// let multisig = Pubkey::new_unique();
382/// let vault_index = 0;
383/// let vault_pda = get_vault_pda(&multisig, vault_index, None).0;
384///
385/// // Create a vault transaction that includes 1 instruction - SOL transfer from the default vault.
386/// let message = TransactionMessage::try_compile(
387///     &vault_pda,
388///     &[system_instruction::transfer(&vault_pda, &Pubkey::new_unique(), 1_000_000)],
389///     &[]
390/// ).unwrap();
391///
392/// let ix = vault_transaction_create(
393///     VaultTransactionCreateAccounts {
394///         multisig,
395///         transaction: Pubkey::new_unique(),
396///         creator: Pubkey::new_unique(),
397///         rent_payer: Pubkey::new_unique(),
398///         system_program: system_program::id(),
399///     },
400///     vault_index,
401///     0,
402///     &message,
403///     None,
404///     None,
405/// );
406/// ```
407pub fn vault_transaction_create(
408    accounts: VaultTransactionCreateAccounts,
409    vault_index: u8,
410    num_ephemeral_signers: u8,
411    message: &TransactionMessage,
412    memo: Option<String>,
413    program_id: Option<Pubkey>,
414) -> Instruction {
415    let args = VaultTransactionCreateArgs {
416        vault_index,
417        ephemeral_signers: num_ephemeral_signers,
418        transaction_message: message.try_to_vec().unwrap(),
419        memo,
420    };
421
422    Instruction {
423        accounts: accounts.to_account_metas(Some(false)),
424        data: VaultTransactionCreateData { args }.data(),
425        program_id: program_id.unwrap_or(squads_multisig_program::ID),
426    }
427}
428
429/// Executes a vault transaction.
430/// Example:
431/// ```
432/// use squads_multisig::anchor_lang::AnchorSerialize;
433/// use squads_multisig::solana_program::pubkey::Pubkey;
434/// use squads_multisig::solana_program::{system_instruction, system_program};
435/// use squads_multisig::client::{
436///     VaultTransactionExecuteAccounts,
437///     vault_transaction_execute
438/// };
439/// use squads_multisig::pda::get_vault_pda;
440/// use squads_multisig::vault_transaction::VaultTransactionMessageExt;
441/// use squads_multisig_program::TransactionMessage;
442///
443/// let multisig = Pubkey::new_unique();
444/// let vault_index = 0;
445/// let vault_pda = get_vault_pda(&multisig, vault_index, None).0;
446///
447/// // Create a vault transaction that includes 1 instruction - SOL transfer from the default vault.
448/// let message = TransactionMessage::try_compile(
449///     &vault_pda,
450///     &[system_instruction::transfer(&vault_pda, &Pubkey::new_unique(), 1_000_000)],
451///     &[]
452/// ).unwrap();
453///
454/// let ix = vault_transaction_execute(
455///     VaultTransactionExecuteAccounts {
456///         multisig,
457///         transaction: Pubkey::new_unique(),
458///         member: Pubkey::new_unique(),
459///         proposal: Pubkey::new_unique(),
460///     },
461///     0,
462///     0,
463///     &message,
464///     &[],
465///     None,
466/// );
467///
468/// ```
469pub fn vault_transaction_execute(
470    accounts: VaultTransactionExecuteAccounts,
471    vault_index: u8,
472    num_ephemeral_signers: u8,
473    message: &TransactionMessage,
474    address_lookup_table_accounts: &[AddressLookupTableAccount],
475    program_id: Option<Pubkey>,
476) -> ClientResult<Instruction> {
477    let program_id = program_id.unwrap_or(squads_multisig_program::ID);
478
479    let vault_pda = get_vault_pda(&accounts.multisig, vault_index, Some(&program_id)).0;
480
481    let accounts_for_execute = message
482        .get_accounts_for_execute(
483            &vault_pda,
484            &accounts.transaction,
485            &address_lookup_table_accounts,
486            num_ephemeral_signers,
487            &program_id,
488        )
489        .map_err(|err| match err {
490            Error::InvalidAddressLookupTableAccount => {
491                ClientError::InvalidAddressLookupTableAccount
492            }
493            Error::InvalidTransactionMessage => ClientError::InvalidTransactionMessage,
494        })?;
495
496    let mut accounts = accounts.to_account_metas(Some(false));
497    // Append the accounts required for executing the inner instructions.
498    accounts.extend(accounts_for_execute.into_iter());
499
500    Ok(Instruction {
501        accounts,
502        data: VaultTransactionExecuteData {}.data(),
503        program_id,
504    })
505}
506
507/// Closes a `ConfigTransaction` and the corresponding `Proposal`.
508/// `transaction` can be closed if either:
509/// - the `proposal` is in a terminal state: `Executed`, `Rejected`, or `Cancelled`.
510/// - the `proposal` is stale.
511///
512/// Example:
513/// ```
514/// use squads_multisig::solana_program::{pubkey::Pubkey, system_program};
515/// use squads_multisig::client::{
516///    ConfigTransactionAccountsCloseAccounts,
517///    config_transaction_accounts_close
518/// };
519///
520/// let ix = config_transaction_accounts_close(
521///     ConfigTransactionAccountsCloseAccounts {
522///         multisig: Pubkey::new_unique(),
523///         proposal: Pubkey::new_unique(),
524///         transaction: Pubkey::new_unique(),
525///         rent_collector: Pubkey::new_unique(),
526///         system_program: system_program::id(),
527///     },
528///     None,
529/// );
530pub fn config_transaction_accounts_close(
531    accounts: ConfigTransactionAccountsCloseAccounts,
532    program_id: Option<Pubkey>,
533) -> Instruction {
534    Instruction {
535        accounts: accounts.to_account_metas(Some(false)),
536        data: ConfigTransactionAccountsCloseData {}.data(),
537        program_id: program_id.unwrap_or(squads_multisig_program::ID),
538    }
539}
540
541/// Closes a `VaultTransaction` and the corresponding `Proposal`.
542/// `transaction` can be closed if either:
543/// - the `proposal` is in a terminal state: `Executed`, `Rejected`, or `Cancelled`.
544/// - the `proposal` is stale and not `Approved`.
545///
546/// Example:
547/// ```
548/// use squads_multisig::solana_program::{pubkey::Pubkey, system_program};
549/// use squads_multisig::client::{
550///     VaultTransactionAccountsCloseAccounts,
551///     vault_transaction_accounts_close
552/// };
553///
554/// let ix = vault_transaction_accounts_close(
555///     VaultTransactionAccountsCloseAccounts {
556///         multisig: Pubkey::new_unique(),
557///         proposal: Pubkey::new_unique(),
558///         transaction: Pubkey::new_unique(),
559///         rent_collector: Pubkey::new_unique(),
560///         system_program: system_program::id(),
561///     },
562///     None,
563/// );
564/// ```
565pub fn vault_transaction_accounts_close(
566    accounts: VaultTransactionAccountsCloseAccounts,
567    program_id: Option<Pubkey>,
568) -> Instruction {
569    Instruction {
570        accounts: accounts.to_account_metas(Some(false)),
571        data: VaultTransactionAccountsCloseData {}.data(),
572        program_id: program_id.unwrap_or(squads_multisig_program::ID),
573    }
574}
575
576pub mod utils {
577    use squads_multisig_program::accounts::{ConfigTransactionExecute, SpendingLimitUse};
578
579    use crate::solana_program::instruction::AccountMeta;
580    use crate::solana_program::pubkey::Pubkey;
581
582    /// A fix for the auto derived anchor `ToAccountMetas` trait.
583    /// The anchor one works incorrectly with Option accounts when program ID is different from the canonical one.
584    pub trait IntoAccountMetas {
585        fn into_account_metas(self, program_id: Pubkey) -> Vec<AccountMeta>;
586    }
587
588    impl IntoAccountMetas for ConfigTransactionExecute {
589        fn into_account_metas(self, program_id: Pubkey) -> Vec<AccountMeta> {
590            vec![
591                AccountMeta::new(self.multisig, false),
592                AccountMeta::new_readonly(self.member, true),
593                AccountMeta::new(self.proposal, false),
594                AccountMeta::new(self.transaction, false),
595                if let Some(rent_payer) = self.rent_payer {
596                    AccountMeta::new(rent_payer, true)
597                } else {
598                    AccountMeta::new_readonly(program_id, false)
599                },
600                if let Some(system_program) = self.system_program {
601                    AccountMeta::new_readonly(system_program, false)
602                } else {
603                    AccountMeta::new_readonly(program_id, false)
604                },
605            ]
606        }
607    }
608
609    impl IntoAccountMetas for SpendingLimitUse {
610        fn into_account_metas(self, program_id: Pubkey) -> Vec<AccountMeta> {
611            vec![
612                AccountMeta::new_readonly(self.multisig, false),
613                AccountMeta::new_readonly(self.member, true),
614                AccountMeta::new(self.spending_limit, false),
615                AccountMeta::new(self.vault, false),
616                AccountMeta::new(self.destination, false),
617                if let Some(system_program) = self.system_program {
618                    AccountMeta::new_readonly(system_program, false)
619                } else {
620                    AccountMeta::new_readonly(program_id, false)
621                },
622                if let Some(mint) = self.mint {
623                    AccountMeta::new_readonly(mint, false)
624                } else {
625                    AccountMeta::new_readonly(program_id, false)
626                },
627                if let Some(vault_token_account) = self.vault_token_account {
628                    AccountMeta::new(vault_token_account, false)
629                } else {
630                    AccountMeta::new_readonly(program_id, false)
631                },
632                if let Some(destination_token_account) = self.destination_token_account {
633                    AccountMeta::new(destination_token_account, false)
634                } else {
635                    AccountMeta::new_readonly(program_id, false)
636                },
637                if let Some(token_program) = self.token_program {
638                    AccountMeta::new_readonly(token_program, false)
639                } else {
640                    AccountMeta::new_readonly(program_id, false)
641                },
642            ]
643        }
644    }
645
646    #[cfg(test)]
647    mod test {
648        use crate::anchor_lang::prelude::Pubkey;
649        use crate::anchor_lang::ToAccountMetas;
650        use crate::client::utils::IntoAccountMetas;
651        use squads_multisig_program::accounts::SpendingLimitUse;
652
653        #[test]
654        fn spending_limit_use_into_account_metas_matches_anchor_implementation() {
655            let accounts = SpendingLimitUse {
656                multisig: Pubkey::new_unique(),
657                member: Pubkey::new_unique(),
658                spending_limit: Pubkey::new_unique(),
659                vault: Pubkey::new_unique(),
660                destination: Pubkey::new_unique(),
661                system_program: Some(Pubkey::new_unique()),
662                mint: Some(Pubkey::new_unique()),
663                vault_token_account: Some(Pubkey::new_unique()),
664                destination_token_account: Some(Pubkey::new_unique()),
665                token_program: Some(Pubkey::new_unique()),
666            };
667
668            // When program_id is the canonical one our implementation should match the anchor one.
669            let anchor_metas = accounts.to_account_metas(Some(false));
670            let sdk_metas = accounts.into_account_metas(squads_multisig_program::ID);
671
672            assert_eq!(anchor_metas, sdk_metas);
673        }
674
675        #[test]
676        fn config_transaction_execute_into_account_metas_matches_anchor_implementation() {
677            let accounts = squads_multisig_program::accounts::ConfigTransactionExecute {
678                multisig: Pubkey::new_unique(),
679                member: Pubkey::new_unique(),
680                proposal: Pubkey::new_unique(),
681                transaction: Pubkey::new_unique(),
682                rent_payer: Some(Pubkey::new_unique()),
683                system_program: Some(Pubkey::new_unique()),
684            };
685
686            // When program_id is the canonical one our implementation should match the anchor one.
687            let anchor_metas = accounts.to_account_metas(Some(false));
688            let sdk_metas = accounts.into_account_metas(squads_multisig_program::ID);
689
690            assert_eq!(anchor_metas, sdk_metas);
691        }
692    }
693}