soroban_rs/
account.rs

1//! # Soroban Account Management
2//!
3//! This module provides types and functionality for handling Stellar accounts in Soroban,
4//! including transaction signing for both single and multi-signature (multisig) accounts.
5//!
6//! ## Features
7//!
8//! - Account sequence number tracking and management
9//! - Single and multi-signature account support
10//! - Transaction signing with authorization control
11//! - Account configuration (thresholds, weights, signers)
12//!
13//! ## Example
14//!
15//! ```rust,no_run
16//! use soroban_rs::{Account, Env, EnvConfigs, Signer};
17//! use ed25519_dalek::SigningKey;
18//!
19//! // Example private key (32 bytes)
20//! let private_key_bytes: [u8; 32] = [
21//!     1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
22//!     26, 27, 28, 29, 30, 31, 32,
23//! ];
24//!
25//! // Create a signer from a secret key
26//! let signing_key = SigningKey::from_bytes(&private_key_bytes);
27//! let signer = Signer::new(signing_key);
28//!
29//! // Single-signature account
30//! let account = Account::single(signer);
31//! ```
32use crate::{error::SorobanHelperError, guard::Guard, Env, Signer, TransactionBuilder};
33use std::fmt;
34use stellar_strkey::ed25519::PublicKey;
35use stellar_xdr::curr::{
36    AccountEntry, AccountId, DecoratedSignature, Hash, Operation, OperationBody, SetOptionsOp,
37    Signer as XdrSigner, SignerKey, Transaction, TransactionEnvelope, TransactionV1Envelope, VecM,
38};
39
40/// Represents a transaction sequence number for a Stellar account.
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
42pub struct AccountSequence(i64);
43
44impl AccountSequence {
45    /// Creates a new sequence number with the specified value.
46    ///
47    /// # Parameters
48    ///
49    /// * `val` - The i64 sequence number value
50    pub fn new(val: i64) -> Self {
51        Self(val)
52    }
53
54    /// Returns a new SequenceNumber incremented by one.
55    pub fn next(&self) -> Self {
56        Self(self.0 + 1)
57    }
58
59    /// Increments the sequence number and returns the new value.
60    ///
61    /// Unlike next() which leaves the original value unchanged, this method
62    /// replaces the original sequence number.
63    pub fn increment(self) -> Self {
64        self.next()
65    }
66
67    /// Returns the raw i64 sequence number.
68    pub fn value(self) -> i64 {
69        self.0
70    }
71}
72
73impl fmt::Display for AccountSequence {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        write!(f, "{}", self.0)
76    }
77}
78
79impl From<i64> for AccountSequence {
80    fn from(val: i64) -> Self {
81        Self(val)
82    }
83}
84
85impl From<AccountSequence> for i64 {
86    fn from(seq: AccountSequence) -> Self {
87        seq.0
88    }
89}
90
91/// Configuration options for setting up or modifying a Stellar account.
92///
93/// Used to configure thresholds and signers for an account. This is particularly
94/// useful for creating or modifying multisig accounts with specific
95/// threshold requirements.
96///
97/// # Example
98///
99/// ```rust,no_run
100/// use soroban_rs::AccountConfig;
101/// use stellar_strkey::ed25519::PublicKey;
102///
103/// let config = AccountConfig::new()
104///     .with_master_weight(10)
105///     .with_thresholds(1, 5, 10)
106///     .add_signer(PublicKey::from_string("PUBLIC KEY").unwrap(), 5);
107/// ```
108#[derive(Default, Debug, Clone)]
109pub struct AccountConfig {
110    /// Weight assigned to the master key (account owner)
111    master_weight: Option<u32>,
112    /// Threshold for low security operations
113    low_threshold: Option<u32>,
114    /// Threshold for medium security operations
115    med_threshold: Option<u32>,
116    /// Threshold for high security operations
117    high_threshold: Option<u32>,
118    /// Additional signers with their respective weights
119    signers: Vec<(PublicKey, u32)>,
120}
121
122impl AccountConfig {
123    /// Creates a new empty account configuration.
124    pub fn new() -> Self {
125        Self::default()
126    }
127
128    /// Sets the master key weight for the account.
129    ///
130    /// # Parameters
131    ///
132    /// * `weight` - The weight to assign to the master key
133    ///   Set to 0 to prevent the master key from being used for signing
134    pub fn with_master_weight(mut self, weight: u32) -> Self {
135        self.master_weight = Some(weight);
136        self
137    }
138
139    /// Sets the threshold values for low, medium, and high security operations.
140    ///
141    /// # Parameters
142    ///
143    /// * `low` - Threshold for low security operations (e.g., setting options)
144    /// * `med` - Threshold for medium security operations (e.g., payments)
145    /// * `high` - Threshold for high security operations (e.g., account merge)
146    pub fn with_thresholds(mut self, low: u32, med: u32, high: u32) -> Self {
147        self.low_threshold = Some(low);
148        self.med_threshold = Some(med);
149        self.high_threshold = Some(high);
150        self
151    }
152
153    /// Adds a new signer with the specified weight.
154    ///
155    /// # Parameters
156    ///
157    /// * `key` - The public key of the signer to add
158    /// * `weight` - The weight to assign to this signer
159    pub fn add_signer(mut self, key: PublicKey, weight: u32) -> Self {
160        self.signers.push((key, weight));
161        self
162    }
163
164    /// Helper function to create a signer operation
165    fn create_signer_operation(&self, public_key: &PublicKey, weight: u32) -> Operation {
166        let signer_key = SignerKey::Ed25519(public_key.0.into());
167        Operation {
168            source_account: None,
169            body: OperationBody::SetOptions(SetOptionsOp {
170                inflation_dest: None,
171                clear_flags: None,
172                set_flags: None,
173                master_weight: None,
174                low_threshold: None,
175                med_threshold: None,
176                high_threshold: None,
177                home_domain: None,
178                signer: Some(XdrSigner {
179                    key: signer_key,
180                    weight,
181                }),
182            }),
183        }
184    }
185
186    /// Helper function to create a thresholds operation
187    fn create_thresholds_operation(&self) -> Option<Operation> {
188        let has_thresholds = self.master_weight.is_some()
189            || self.low_threshold.is_some()
190            || self.med_threshold.is_some()
191            || self.high_threshold.is_some();
192
193        if !has_thresholds {
194            return None;
195        }
196
197        Some(Operation {
198            source_account: None,
199            body: OperationBody::SetOptions(SetOptionsOp {
200                inflation_dest: None,
201                clear_flags: None,
202                set_flags: None,
203                master_weight: self.master_weight,
204                low_threshold: self.low_threshold,
205                med_threshold: self.med_threshold,
206                high_threshold: self.high_threshold,
207                home_domain: None,
208                signer: None,
209            }),
210        })
211    }
212}
213
214/// Represents a single-signature account.
215#[derive(Clone)]
216pub struct SingleAccount {
217    /// The account's identifier
218    account_id: AccountId,
219    /// Signer associated with this account
220    signer: Box<Signer>,
221    /// List of guards associated with this account
222    pub guards: Vec<Guard>,
223}
224
225impl SingleAccount {
226    /// Creates a new single-signature account
227    ///
228    /// # Parameters
229    ///
230    /// * `signer` - The signer for this account
231    /// * `authorized_calls` - The number of calls to authorize
232    pub fn new(signer: Signer) -> Self {
233        Self {
234            account_id: signer.account_id(),
235            signer: Box::new(signer),
236            guards: Vec::new(),
237        }
238    }
239}
240
241impl fmt::Display for SingleAccount {
242    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243        write!(f, "SingleAccount({})", self.account_id.0)
244    }
245}
246
247/// Represents a multisig account.
248#[derive(Clone)]
249pub struct MultisigAccount {
250    /// The account's identifier
251    account_id: AccountId,
252    /// Signers associated with this account
253    pub signers: Vec<Signer>,
254    /// List of guards associated with this account
255    pub guards: Vec<Guard>,
256}
257
258impl MultisigAccount {
259    /// Creates a new multisig account
260    ///
261    /// # Parameters
262    ///
263    /// * `account_id` - The identifier for this account
264    /// * `signers` - A vector of signers for this account
265    /// * `authorized_calls` - The number of calls to authorize
266    pub fn new(account_id: AccountId, signers: Vec<Signer>) -> Self {
267        Self {
268            account_id,
269            signers,
270            guards: Vec::new(),
271        }
272    }
273}
274
275impl fmt::Display for MultisigAccount {
276    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
277        write!(
278            f,
279            "MultisigAccount({}, {} signers)",
280            self.account_id.0,
281            self.signers.len()
282        )
283    }
284}
285
286/// Represents either a single-signature or multisig account.
287///
288/// This is the main account type used for interacting with the Stellar network.
289/// It provides methods for signing transactions, configuring account settings,
290/// and managing sequence numbers.
291#[derive(Clone)]
292pub enum Account {
293    /// Single-signature account with one key pair
294    KeyPair(SingleAccount),
295    /// Multi-signature account
296    Multisig(MultisigAccount),
297}
298
299impl fmt::Display for Account {
300    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
301        match self {
302            Self::KeyPair(account) => write!(f, "{}", account),
303            Self::Multisig(account) => write!(f, "{}", account),
304        }
305    }
306}
307
308impl From<SingleAccount> for Account {
309    fn from(account: SingleAccount) -> Self {
310        Self::KeyPair(account)
311    }
312}
313
314impl From<MultisigAccount> for Account {
315    fn from(account: MultisigAccount) -> Self {
316        Self::Multisig(account)
317    }
318}
319
320impl From<Signer> for Account {
321    fn from(signer: Signer) -> Self {
322        Self::single(signer)
323    }
324}
325
326// Single merged implementation block for Account
327impl Account {
328    // ==== Constructors ====
329
330    /// Creates a new single-signature Account instance with the provided signer.
331    ///
332    /// # Parameters
333    ///
334    /// * `signer` - The signer for this account
335    ///
336    /// # Returns
337    ///
338    /// A new `Account` instance with the KeyPair variant
339    pub fn single(signer: Signer) -> Self {
340        Self::KeyPair(SingleAccount {
341            signer: Box::new(signer.clone()),
342            account_id: signer.account_id(),
343            guards: Vec::new(),
344        })
345    }
346
347    /// Creates a new multisig Account instance with the provided account ID and signers.
348    ///
349    /// # Parameters
350    ///
351    /// * `account_id` - The identifier for this account
352    /// * `signers` - A vector of signers for this multi-signature account
353    ///
354    /// # Returns
355    ///
356    /// A new `Account` instance with the Multisig variant
357    pub fn multisig(account_id: AccountId, signers: Vec<Signer>) -> Self {
358        Self::Multisig(MultisigAccount {
359            account_id,
360            signers,
361            guards: Vec::new(),
362        })
363    }
364
365    /// Returns the account's identifier.
366    pub fn account_id(&self) -> AccountId {
367        match self {
368            Self::KeyPair(account) => account.account_id.clone(),
369            Self::Multisig(account) => account.account_id.clone(),
370        }
371    }
372
373    /// Returns a reference to the account's signers.
374    pub fn signers(&self) -> &[Signer] {
375        match self {
376            Self::KeyPair(account) => std::slice::from_ref(&*account.signer),
377            Self::Multisig(account) => &account.signers,
378        }
379    }
380
381    /// Loads the account information from the network.
382    ///
383    /// # Parameters
384    /// * `env` - The environment to use for loading the account
385    ///
386    /// # Returns
387    /// * `AccountEntry` - The account information
388    pub async fn load(&self, env: &Env) -> Result<AccountEntry, SorobanHelperError> {
389        env.get_account(&self.account_id().to_string()).await
390    }
391
392    /// Gets the current sequence number for the account.
393    ///
394    /// # Parameters
395    ///
396    /// * `env` - The environment to use for fetching the sequence number
397    ///
398    /// # Returns
399    ///
400    /// The current sequence number wrapped in `AccountSequence`
401    pub async fn get_sequence(&self, env: &Env) -> Result<AccountSequence, SorobanHelperError> {
402        let entry = self.load(env).await?;
403        Ok(AccountSequence::from(entry.seq_num.0))
404    }
405
406    /// Retrieves the next available sequence number.
407    ///
408    /// This is useful when preparing a new transaction.
409    ///
410    /// # Parameters
411    ///
412    /// * `env` - The environment to use for fetching the sequence number
413    ///
414    /// # Returns
415    ///
416    /// The next sequence number (current + 1) wrapped in `AccountSequence`
417    pub async fn next_sequence(&self, env: &Env) -> Result<AccountSequence, SorobanHelperError> {
418        Ok(self.get_sequence(env).await?.next())
419    }
420
421    /// Adds a guard to the account.
422    ///
423    /// Guards are used to control and limit operations that can be performed with this account.
424    /// Multiple guards can be added to an account, and all guards must pass for operations to proceed.
425    ///
426    /// # Parameters
427    ///
428    /// * `guard` - The guard to add to the account
429    pub fn add_guard(&mut self, guard: Guard) {
430        match self {
431            Self::KeyPair(account) => account.guards.push(guard),
432            Self::Multisig(account) => account.guards.push(guard),
433        }
434    }
435
436    /// Checks if all guards associated with this account are satisfied.
437    ///
438    /// This method evaluates all guards attached to the account and returns
439    /// true only if all of them pass their respective checks.
440    ///
441    /// # Returns
442    ///
443    /// * `false` - If any guard fails its check
444    pub fn check_guards(&self, tx: &Transaction) -> Result<bool, SorobanHelperError> {
445        match self {
446            Self::KeyPair(account) => {
447                for guard in &account.guards {
448                    if !guard.check(tx)? {
449                        return Ok(false);
450                    }
451                }
452                Ok(true)
453            }
454            Self::Multisig(account) => {
455                for guard in &account.guards {
456                    if !guard.check(tx)? {
457                        return Ok(false);
458                    }
459                }
460                Ok(true)
461            }
462        }
463    }
464
465    /// Updates the state of all guards after an operation has been performed.
466    ///
467    /// This method should be called after a successful operation to update
468    /// the internal state of all guards (e.g., decrement remaining allowed calls).
469    pub fn update_guards(&mut self, tx: &Transaction) -> Result<(), SorobanHelperError> {
470        match self {
471            Self::KeyPair(account) => {
472                for guard in &mut account.guards {
473                    guard.update(tx)?;
474                }
475                Ok(())
476            }
477            Self::Multisig(account) => {
478                for guard in &mut account.guards {
479                    guard.update(tx)?;
480                }
481                Ok(())
482            }
483        }
484    }
485
486    /// Sign a transaction using the account's signers.
487    ///
488    /// # Parameters
489    ///
490    /// * `tx` - The transaction to sign
491    /// * `network_id` - The network ID hash
492    /// * `signers` - The signers to use
493    ///
494    /// # Returns
495    ///
496    /// A vector of decorated signatures
497    fn sign_with_tx(
498        tx: &Transaction,
499        network_id: &Hash,
500        signers: &[Signer],
501    ) -> Result<VecM<DecoratedSignature, 20>, SorobanHelperError> {
502        let signatures: Vec<DecoratedSignature> = signers
503            .iter()
504            .map(|signer| signer.sign_transaction(tx, network_id))
505            .collect::<Result<_, _>>()
506            .map_err(|e| SorobanHelperError::XdrEncodingFailed(e.to_string()))?;
507        signatures.try_into().map_err(|_| {
508            SorobanHelperError::XdrEncodingFailed("Failed to convert signatures to XDR".to_string())
509        })
510    }
511
512    /// Signs a transaction, ensuring the account still has authorized calls.
513    ///
514    /// Decrements the authorized call counter when successful.
515    ///
516    /// # Parameters
517    ///
518    /// * `tx` - The transaction to sign
519    /// * `network_id` - The network ID hash
520    ///
521    /// # Returns
522    ///
523    /// A signed transaction envelope
524    pub fn sign_transaction(
525        &mut self,
526        tx: &Transaction,
527        network_id: &Hash,
528    ) -> Result<TransactionEnvelope, SorobanHelperError> {
529        if !self.check_guards(tx)? {
530            return Err(SorobanHelperError::Unauthorized(
531                "The transaction didn't pass one or more guards".to_string(),
532            ));
533        }
534
535        let signatures = Self::sign_with_tx(tx, network_id, self.signers())?;
536        self.update_guards(tx)?;
537
538        Ok(TransactionEnvelope::Tx(TransactionV1Envelope {
539            tx: tx.clone(),
540            signatures,
541        }))
542    }
543
544    /// Signs a transaction envelope by appending new signatures.
545    ///
546    /// # Parameters
547    ///
548    /// * `tx_envelope` - The transaction envelope to sign
549    /// * `network_id` - The network ID hash
550    ///
551    /// # Returns
552    ///
553    /// A transaction envelope with the new signatures appended
554    pub fn sign_transaction_envelope(
555        &mut self,
556        tx_envelope: &TransactionEnvelope,
557        network_id: &Hash,
558    ) -> Result<TransactionEnvelope, SorobanHelperError> {
559        let tx_v1 = match tx_envelope {
560            TransactionEnvelope::Tx(tx_v1) => tx_v1,
561            _ => {
562                return Err(SorobanHelperError::XdrEncodingFailed(
563                    "Invalid transaction envelope".to_string(),
564                ));
565            }
566        };
567
568        if !self.check_guards(&tx_v1.tx)? {
569            return Err(SorobanHelperError::Unauthorized(
570                "The transaction didn't pass one or more guards".to_string(),
571            ));
572        }
573
574        let prev_signatures = tx_v1.signatures.clone();
575        let new_signatures = Self::sign_with_signers(&tx_v1.tx, network_id, self.signers())?;
576
577        let mut all_signatures = prev_signatures.to_vec();
578        all_signatures.extend(new_signatures.to_vec());
579        let signatures: VecM<DecoratedSignature, 20> = all_signatures.try_into().map_err(|_| {
580            SorobanHelperError::XdrEncodingFailed(
581                "Too many signatures for XDR vector (max 20)".to_string(),
582            )
583        })?;
584
585        self.update_guards(&tx_v1.tx)?;
586
587        Ok(TransactionEnvelope::Tx(TransactionV1Envelope {
588            tx: tx_v1.tx.clone(),
589            signatures,
590        }))
591    }
592
593    /// Configures the account by building and signing a transaction that sets options.
594    ///
595    /// This can be used to add signers, set thresholds, and modify other account settings.
596    ///
597    /// # Parameters
598    ///
599    /// * `env` - The environment to use for transaction building
600    /// * `config` - The account configuration to apply
601    ///
602    /// # Returns
603    ///
604    /// A signed transaction envelope containing the set options operations
605    pub async fn configure(
606        mut self,
607        env: &Env,
608        config: AccountConfig,
609    ) -> Result<TransactionEnvelope, SorobanHelperError> {
610        let mut tx = TransactionBuilder::new(&self, env);
611
612        // Add set options operation for each signer configuration
613        for (public_key, weight) in &config.signers {
614            tx = tx.add_operation(config.create_signer_operation(public_key, *weight));
615        }
616
617        // Add thresholds if any are specified
618        if let Some(op) = config.create_thresholds_operation() {
619            tx = tx.add_operation(op);
620        }
621
622        let tx = tx.simulate_and_build(env, &self).await?;
623        self.sign_transaction(&tx, &env.network_id())
624    }
625
626    /// Signs a transaction without checking or decrementing the authorized_calls counter.
627    ///
628    /// This method bypasses authorization checks and should be used with caution.
629    ///
630    /// # Parameters
631    ///
632    /// * `tx` - The transaction to sign
633    /// * `network_id` - The network ID hash
634    ///
635    /// # Returns
636    ///
637    /// A signed transaction envelope
638    pub fn sign_transaction_unsafe(
639        &self,
640        tx: &Transaction,
641        network_id: &Hash,
642    ) -> Result<TransactionEnvelope, SorobanHelperError> {
643        let signatures = Self::sign_with_signers(tx, network_id, self.signers())?;
644
645        Ok(TransactionEnvelope::Tx(TransactionV1Envelope {
646            tx: tx.clone(),
647            signatures,
648        }))
649    }
650
651    /// Sign a transaction using the account's signers.
652    ///
653    /// # Parameters
654    ///
655    /// * `tx` - The transaction to sign
656    /// * `network_id` - The network ID hash
657    /// * `signers` - The signers to use
658    ///
659    /// # Returns
660    ///
661    /// A vector of decorated signatures
662    pub fn sign_with_signers(
663        tx: &Transaction,
664        network_id: &Hash,
665        signers: &[Signer],
666    ) -> Result<VecM<DecoratedSignature, 20>, SorobanHelperError> {
667        if signers.is_empty() {
668            return Err(SorobanHelperError::SigningFailed(
669                "No signers provided".to_string(),
670            ));
671        }
672
673        let signatures: Vec<DecoratedSignature> = signers
674            .iter()
675            .map(|signer| signer.sign_transaction(tx, network_id))
676            .collect::<Result<_, _>>()?;
677
678        signatures.try_into().map_err(|_| {
679            SorobanHelperError::XdrEncodingFailed(
680                "Too many signatures for XDR vector (max 20)".to_string(),
681            )
682        })
683    }
684}
685
686#[cfg(test)]
687mod test {
688    use stellar_xdr::curr::{OperationBody, Signer as XdrSigner, SignerKey, TransactionEnvelope};
689
690    use crate::account::AccountSequence;
691    use crate::guard::Guard;
692    use crate::mock::{all_signers, mock_env, mock_signer1, mock_signer3};
693    use crate::{
694        Account, AccountConfig, MultisigAccount, SingleAccount, SorobanHelperError,
695        TransactionBuilder,
696    };
697
698    #[tokio::test]
699    async fn load_account() {
700        let env = mock_env(None, None, None);
701
702        // Test single account operations
703        let account = Account::single(mock_signer1());
704
705        // Test account loading
706        let entry = account.load(&env).await;
707
708        let expected_account_id = mock_signer1().account_id().0.to_string();
709        let res_account_id = entry.unwrap().account_id.0.to_string();
710
711        assert_eq!(expected_account_id, res_account_id);
712    }
713
714    #[tokio::test]
715    async fn multisig() {
716        let env = mock_env(None, None, None);
717
718        // Test single account operations
719        let account = Account::multisig(mock_signer3().account_id(), all_signers());
720
721        // Test account loading
722        let entry = account.load(&env).await;
723
724        let expected_account_id = mock_signer3().account_id().0.to_string();
725        let res_account_id = entry.unwrap().account_id.0.to_string();
726
727        let signers = account.signers();
728
729        for (i, sig) in signers.iter().enumerate() {
730            assert_eq!(sig.account_id(), all_signers()[i].account_id())
731        }
732
733        assert_eq!(expected_account_id, res_account_id);
734    }
735
736    #[tokio::test]
737    async fn sign_transaction() {
738        let env = mock_env(None, None, None);
739
740        // Test single account operations
741        let mut account = Account::single(mock_signer1());
742
743        // Test sign transaction
744        let tx = TransactionBuilder::new(&account, &env)
745            .build()
746            .await
747            .unwrap();
748
749        let authorized_calls = Guard::NumberOfAllowedCalls(1);
750        account.add_guard(authorized_calls.clone());
751        let signed_tx = account.sign_transaction(&tx, &env.network_id());
752
753        assert!(signed_tx.is_ok());
754
755        // when exceeded number of authorized calls
756        let signed_tx_2 = account.sign_transaction(&tx, &env.network_id());
757        assert!(signed_tx_2.is_err());
758        // asserts it throws the right error
759        assert_eq!(
760            signed_tx_2.err().unwrap(),
761            SorobanHelperError::Unauthorized(
762                "The transaction didn't pass one or more guards".to_string()
763            )
764        );
765    }
766
767    #[tokio::test]
768    async fn sign_transaction_unsafe() {
769        let env = mock_env(None, None, None);
770
771        // Test single account operations
772        let mut account = Account::single(mock_signer1());
773
774        // Test sign transaction
775        let tx = TransactionBuilder::new(&account, &env)
776            .build()
777            .await
778            .unwrap();
779
780        // no authorized calls
781        let authorized_calls = Guard::NumberOfAllowedCalls(0);
782        account.add_guard(authorized_calls);
783
784        // sign unsafe does not check the remaining authorized calls.
785        let signed_tx = account.sign_transaction_unsafe(&tx, &env.network_id());
786
787        assert!(signed_tx.is_ok());
788    }
789
790    #[test]
791    fn test_display_account_sequence() {
792        let seq = AccountSequence::new(42);
793        assert_eq!(seq.to_string(), "42");
794    }
795
796    #[test]
797    fn test_account_sequence_conversions() {
798        // Test From<i64> for AccountSequence
799        let seq_from_i64 = crate::account::AccountSequence::from(123_i64);
800        assert_eq!(seq_from_i64.value(), 123);
801
802        // Test From<AccountSequence> for i64
803        let seq = crate::account::AccountSequence::new(456);
804        let i64_from_seq: i64 = seq.into();
805        assert_eq!(i64_from_seq, 456);
806    }
807
808    #[test]
809    fn test_account_config_default() {
810        let config = AccountConfig::default();
811
812        assert_eq!(config.master_weight, None);
813        assert_eq!(config.low_threshold, None);
814        assert_eq!(config.med_threshold, None);
815        assert_eq!(config.high_threshold, None);
816        assert!(config.signers.is_empty());
817
818        let config_new = AccountConfig::new();
819        assert_eq!(config.master_weight, config_new.master_weight);
820        assert_eq!(config.low_threshold, config_new.low_threshold);
821        assert_eq!(config.med_threshold, config_new.med_threshold);
822        assert_eq!(config.high_threshold, config_new.high_threshold);
823        assert_eq!(config.signers.len(), config_new.signers.len());
824    }
825
826    #[test]
827    fn test_account_display_implementations() {
828        let signer = mock_signer1();
829        let single_account = SingleAccount::new(signer.clone());
830        let expected_single_display = format!("SingleAccount({})", signer.account_id().0);
831        assert_eq!(single_account.to_string(), expected_single_display);
832
833        let signers = all_signers();
834        let account_id = mock_signer3().account_id();
835        let multisig_account = MultisigAccount::new(account_id.clone(), signers.clone());
836        let expected_multisig_display = format!(
837            "MultisigAccount({}, {} signers)",
838            account_id.0,
839            signers.len()
840        );
841        assert_eq!(multisig_account.to_string(), expected_multisig_display);
842
843        let account_single = Account::single(mock_signer1());
844        let account_multisig = Account::multisig(mock_signer3().account_id(), all_signers());
845
846        assert!(account_single.to_string().starts_with("SingleAccount"));
847        assert!(account_multisig.to_string().starts_with("MultisigAccount"));
848    }
849
850    #[test]
851    fn test_account_from_implementations() {
852        let signer1 = mock_signer1();
853        let single_account = SingleAccount::new(signer1);
854        let expected_id = single_account.account_id.0.to_string();
855        let account_from_single: Account = single_account.into();
856
857        assert!(matches!(account_from_single, Account::KeyPair(_)));
858
859        assert!(
860            matches!(account_from_single, Account::KeyPair(inner) if inner.account_id.0.to_string() == expected_id)
861        );
862
863        let signers = all_signers();
864        let account_id = mock_signer3().account_id();
865
866        let expected_multi_id = account_id.0.to_string();
867        let expected_signers_len = signers.len();
868
869        let multisig_account = MultisigAccount::new(account_id, signers);
870        let account_from_multisig: Account = multisig_account.into();
871
872        assert!(matches!(account_from_multisig, Account::Multisig(_)));
873
874        assert!(matches!(account_from_multisig, Account::Multisig(inner) if
875            inner.account_id.0.to_string() == expected_multi_id &&
876            inner.signers.len() == expected_signers_len
877        ));
878
879        let signer2 = mock_signer1();
880        let expected_signer_id = signer2.account_id().0.to_string();
881        let account_from_signer: Account = signer2.into();
882
883        assert!(matches!(account_from_signer, Account::KeyPair(_)));
884
885        assert!(
886            matches!(account_from_signer, Account::KeyPair(inner) if inner.account_id.0.to_string() == expected_signer_id)
887        );
888    }
889
890    #[tokio::test]
891    async fn test_next_sequence() {
892        let env = mock_env(None, None, None);
893        let account = Account::single(mock_signer1());
894
895        let current_seq = account.get_sequence(&env).await.unwrap();
896
897        let next_seq = account.next_sequence(&env).await.unwrap();
898        assert_eq!(next_seq.value(), current_seq.value() + 1);
899
900        let second_current = account.get_sequence(&env).await.unwrap();
901        let second_next = account.next_sequence(&env).await.unwrap();
902        assert_eq!(second_current.value(), current_seq.value());
903
904        assert_eq!(second_next.value(), second_current.value() + 1);
905    }
906
907    #[test]
908    fn test_create_thresholds_operation() {
909        let empty_config = AccountConfig::new();
910        assert!(empty_config.create_thresholds_operation().is_none());
911
912        let master_weight_config = AccountConfig::new().with_master_weight(5);
913        assert!(master_weight_config.create_thresholds_operation().is_some());
914
915        let thresholds_config = AccountConfig::new().with_thresholds(1, 2, 3);
916        assert!(thresholds_config.create_thresholds_operation().is_some());
917
918        let low_threshold_config = AccountConfig::new().with_thresholds(1, 0, 0);
919        let low_op = low_threshold_config.create_thresholds_operation().unwrap();
920        if let OperationBody::SetOptions(op_body) = &low_op.body {
921            assert_eq!(op_body.low_threshold, Some(1));
922            assert_eq!(op_body.med_threshold, Some(0));
923            assert_eq!(op_body.high_threshold, Some(0));
924        }
925
926        let med_threshold_config = AccountConfig::new().with_thresholds(0, 2, 0);
927        let med_op = med_threshold_config.create_thresholds_operation().unwrap();
928        if let OperationBody::SetOptions(op_body) = &med_op.body {
929            assert_eq!(op_body.low_threshold, Some(0));
930            assert_eq!(op_body.med_threshold, Some(2));
931            assert_eq!(op_body.high_threshold, Some(0));
932        }
933
934        let high_threshold_config = AccountConfig::new().with_thresholds(0, 0, 3);
935        let high_op = high_threshold_config.create_thresholds_operation().unwrap();
936        if let OperationBody::SetOptions(op_body) = &high_op.body {
937            assert_eq!(op_body.low_threshold, Some(0));
938            assert_eq!(op_body.med_threshold, Some(0));
939            assert_eq!(op_body.high_threshold, Some(3));
940        }
941    }
942
943    #[tokio::test]
944    async fn test_configure() {
945        let env = mock_env(None, None, None);
946
947        let account = Account::single(mock_signer1());
948        let config = AccountConfig::new()
949            .with_master_weight(10)
950            .with_thresholds(1, 2, 3)
951            .add_signer(mock_signer3().public_key(), 5);
952
953        let tx = account.configure(&env, config).await.unwrap();
954
955        assert!(matches!(tx, TransactionEnvelope::Tx(_)));
956
957        let tx_env = match tx {
958            TransactionEnvelope::Tx(env) => env,
959            _ => return, // Will not reach here due to previous assertion
960        };
961
962        assert_eq!(tx_env.tx.operations.len(), 2);
963
964        assert!(matches!(
965            tx_env.tx.operations[0].body,
966            OperationBody::SetOptions(_)
967        ));
968
969        if let OperationBody::SetOptions(op0_body) = &tx_env.tx.operations[0].body {
970            assert_eq!(
971                op0_body.signer,
972                Some(XdrSigner {
973                    key: SignerKey::Ed25519(mock_signer3().public_key().0.into()),
974                    weight: 5
975                })
976            );
977        }
978
979        assert!(matches!(
980            tx_env.tx.operations[1].body,
981            OperationBody::SetOptions(_)
982        ));
983
984        if let OperationBody::SetOptions(op1_body) = &tx_env.tx.operations[1].body {
985            assert_eq!(op1_body.master_weight, Some(10));
986            assert_eq!(op1_body.low_threshold, Some(1));
987            assert_eq!(op1_body.med_threshold, Some(2));
988            assert_eq!(op1_body.high_threshold, Some(3));
989        }
990    }
991
992    #[tokio::test]
993    async fn test_sign_transaction_envelope() {
994        let env = mock_env(None, None, None);
995
996        let mut first_account = Account::single(mock_signer1());
997        let mut second_account = Account::single(mock_signer3());
998
999        let authorized_calls = Guard::NumberOfAllowedCalls(1);
1000        first_account.add_guard(authorized_calls.clone());
1001        second_account.add_guard(authorized_calls);
1002
1003        let tx = TransactionBuilder::new(&first_account, &env)
1004            .build()
1005            .await
1006            .unwrap();
1007
1008        let first_signed_envelope = first_account
1009            .sign_transaction(&tx, &env.network_id())
1010            .unwrap();
1011
1012        assert!(matches!(first_signed_envelope, TransactionEnvelope::Tx(_)));
1013
1014        let first_envelope_signatures = match &first_signed_envelope {
1015            TransactionEnvelope::Tx(tx_v1) => &tx_v1.signatures,
1016            _ => &[][..], // Empty slice as fallback, assertion below will fail if this happens
1017        };
1018        assert_eq!(first_envelope_signatures.len(), 1); // First envelope should have exactly one signature
1019
1020        let final_envelope = second_account
1021            .sign_transaction_envelope(&first_signed_envelope, &env.network_id())
1022            .unwrap();
1023
1024        assert!(matches!(final_envelope, TransactionEnvelope::Tx(_)));
1025
1026        let final_signatures = match &final_envelope {
1027            TransactionEnvelope::Tx(tx_v1) => &tx_v1.signatures,
1028            _ => &[][..], // Empty slice as fallback, assertion below will fail if this happens
1029        };
1030        assert_eq!(final_signatures.len(), 2); // Final envelope should have exactly two signatures
1031
1032        let first_public_key = mock_signer1().public_key();
1033        let second_public_key = mock_signer3().public_key();
1034        assert_eq!(final_signatures[0].hint.0, &first_public_key.0[28..32]); // First signature should match first account's public key
1035        assert_eq!(final_signatures[1].hint.0, &second_public_key.0[28..32]); // Second signature should match second account's public key
1036    }
1037}