rialo-cdk 0.2.0-alpha.0

Rialo CDK - A comprehensive toolkit for building with the Rialo blockchain
Documentation
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
// Copyright (c) Subzero Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use serde::{Deserialize, Serialize};

use crate::{
    error::{Result, RialoError},
    keyring::Keyring,
    rpc::types::Pubkey,
    transaction::instruction::{self, Instruction},
};

/// A fully-signed transaction compatible with Rialo blockchain
///
/// This structure represents a transaction that has been completely assembled and signed,
/// ready for submission to the network. It contains both the transaction message (which
/// specifies the operations to perform) and the cryptographic signatures that authorize it.
#[derive(Debug, Serialize, Deserialize)]
pub struct Transaction {
    /// A vector of signatures, one per required signer in the transaction's `Message`
    /// The signatures appear in the same order as the signed keys in the Message accounts
    pub signatures: Vec<Vec<u8>>,
    /// The encoded transaction message containing all instructions and account references
    pub message: Vec<u8>,
}

impl Transaction {
    /// Create a new transaction with empty signatures
    pub fn new(message: Vec<u8>, num_signatures: usize) -> Self {
        Self {
            signatures: vec![vec![0u8; 64]; num_signatures],
            message,
        }
    }

    /// Serialize the transaction to wire format bytes
    pub fn serialize_wired(&self) -> Result<Vec<u8>> {
        serialize_transaction(self)
    }

    /// Deserialize a transaction from wire format bytes
    pub fn deserialize_wired(data: &[u8]) -> Result<Self> {
        deserialize_transaction(data)
    }

    /// Add a signature at the specified index
    pub fn add_signature(&mut self, index: usize, signature: Vec<u8>) -> Result<()> {
        if signature.len() != 64 {
            return Err(RialoError::Transaction(
                "Signature must be 64 bytes long".to_string(),
            ));
        }
        if index >= self.signatures.len() {
            return Err(RialoError::Transaction(format!(
                "Signature index {} out of bounds (max: {})",
                index,
                self.signatures.len() - 1
            )));
        }
        self.signatures[index] = signature;
        Ok(())
    }

    /// Add a signature for the given public key
    pub fn add_signature_for_pubkey(&mut self, pubkey: &Pubkey, signature: Vec<u8>) -> Result<()> {
        let message_header = parse_message_header(&self.message)?;
        let account_keys = parse_account_keys(&self.message, &message_header)?;

        // Find the index of the pubkey in signer accounts
        let signer_index = account_keys
            .iter()
            .take(message_header.num_required_signatures as usize)
            .position(|key| key == pubkey)
            .ok_or_else(|| {
                RialoError::Transaction(format!("Public key {} not found as a signer", pubkey))
            })?;

        self.add_signature(signer_index, signature)
    }

    /// Clear all signatures
    pub fn clear_signatures(&mut self) {
        for sig in &mut self.signatures {
            sig.fill(0);
        }
    }

    /// Get the number of signatures
    pub fn signature_count(&self) -> usize {
        self.signatures.len()
    }

    /// Get a signature by index
    pub fn get_signature(&self, index: usize) -> Option<&Vec<u8>> {
        self.signatures.get(index)
    }

    /// Check if all signatures are present (non-zero)
    pub fn is_fully_signed(&self) -> bool {
        self.signatures
            .iter()
            .all(|sig| sig.iter().any(|&b| b != 0))
    }

    /// Get the message bytes
    pub fn message(&self) -> &[u8] {
        &self.message
    }

    /// Get the parsed message with all instructions and accounts resolved
    ///
    /// This method provides access to the transaction's instructions,
    /// allowing inspection of side effects before signing.
    ///
    /// # Returns
    ///
    /// A deserialized message with all instructions and their data
    pub fn deserialize_message(&self) -> Result<DeserializedMessage> {
        deserialize_message(&self.message)
    }
}

/// Builder for creating and signing blockchain transactions
///
/// The `TransactionBuilder` provides a fluent interface for constructing transactions
/// step by step. It handles the complex logic of account collection, deduplication,
/// and sorting according to blockchain rules, as well as proper serialization into
/// the wire format.
///
/// # Examples
///
/// ```
/// # use rialo_cdk::transaction::TransactionBuilder;
/// # use rialo_cdk::keyring::Keyring;
/// # use rialo_cdk::rpc::types::{Pubkey, Hash};
/// # use std::str::FromStr;
/// # fn example(keyring: &Keyring) -> Result<(), Box<dyn std::error::Error>> {
/// let fee_payer = Pubkey::from_str("FGda4aQty5NKU2as4AV7ob8J9FdPWWG2tYnTWsolWUBX").unwrap();
/// let valid_from = std::time::SystemTime::now()
///    .duration_since(std::time::UNIX_EPOCH)
///    .expect("Time went backwards")
///    .as_millis() as i64;
/// let from = Pubkey::from_str("FGda4aQty5NKU2as4AV7ob8J9FdPWWG2tYnTWsolWUBX").unwrap();
/// let to = Pubkey::from_str("AQTdzvx3HCf1TMwU9CZfSKyj2jJkdmNQWM9tFMrUhmiv").unwrap();
/// let transaction = TransactionBuilder::new(fee_payer, valid_from)
/// .add_transfer_instruction(&from, &to, 1000000000)
/// .sign(keyring)?;
/// # Ok(())
/// # }
/// ```
pub struct TransactionBuilder {
    /// The account that will pay for this transaction
    fee_payer: Pubkey,
    /// Transaction valid from in milliseconds since Unix epoch
    valid_from: i64,
    /// Collection of instructions to be executed
    instructions: Vec<Instruction>,
    /// Optional account index to use for signing (defaults to primary account)
    account_index: Option<usize>,
}

impl TransactionBuilder {
    /// Creates a new transaction builder with the specified fee_payer and valid_from
    ///
    /// # Arguments
    ///
    /// * `fee_payer` - The account that will pay for transaction fees (base58-encoded public key)
    /// * `valid_from` - The Transaction valid from in milliseconds since Unix epoch
    ///
    /// # Returns
    ///
    /// A new `TransactionBuilder` instance
    pub fn new(fee_payer: Pubkey, valid_from: i64) -> Self {
        Self {
            fee_payer,
            valid_from,
            instructions: Vec::new(),
            account_index: None,
        }
    }

    /// Adds an instruction to the transaction
    ///
    /// # Arguments
    ///
    /// * `instruction` - The instruction to add
    ///
    /// # Returns
    ///
    /// Self reference for method chaining
    pub fn add_instruction(&mut self, instruction: Instruction) -> &mut Self {
        self.instructions.push(instruction);
        self
    }

    /// Adds a token transfer instruction to the transaction
    ///
    /// # Arguments
    ///
    /// * `from` - The sender's account (base58-encoded public key)
    /// * `to` - The recipient's account (base58-encoded public key)
    /// * `kelvin` - The amount to transfer in kelvin (the smallest unit)
    ///
    /// # Returns
    ///
    /// Self reference for method chaining
    pub fn add_transfer_instruction(
        &mut self,
        from: &Pubkey,
        to: &Pubkey,
        kelvin: u64,
    ) -> &mut Self {
        let instruction = instruction::transfer_instruction(from, to, kelvin);
        self.add_instruction(instruction)
    }

    /// Specifies which account in the wallet should be used for signing
    ///
    /// # Arguments
    ///
    /// * `account_index` - The index of the account to use for signing
    ///
    /// # Returns
    ///
    /// Self reference for method chaining
    pub fn with_account_index(&mut self, account_index: usize) -> &mut Self {
        self.account_index = Some(account_index);
        self
    }

    /// Builds the transaction message without signing it
    ///
    /// This method compiles all instructions into a single transaction message,
    /// collects and sorts all referenced accounts according to blockchain rules,
    /// and serializes the result into the wire format.
    ///
    /// # Returns
    ///
    /// The serialized transaction message as a byte vector
    ///
    /// # Errors
    ///
    /// Returns an error if message construction or serialization fails
    pub fn build(&self) -> Result<Vec<u8>> {
        // Collect all unique accounts from instructions
        let mut account_metas = Vec::new();
        let mut account_indices = std::collections::HashMap::new();

        // Always add payer as the first account (it's a signer and writable)
        account_metas.push(AccountMeta {
            pubkey: self.fee_payer,
            is_signer: true,
            is_writable: true,
        });
        account_indices.insert(self.fee_payer, 0);

        // Process all accounts from instructions
        for instruction in &self.instructions {
            // Add accounts from instruction
            for account_meta in &instruction.accounts {
                if let std::collections::hash_map::Entry::Vacant(e) =
                    account_indices.entry(account_meta.pubkey)
                {
                    e.insert(account_metas.len());
                    account_metas.push(AccountMeta {
                        pubkey: account_meta.pubkey,
                        is_signer: account_meta.is_signer,
                        is_writable: account_meta.is_writable,
                    });
                } else {
                    // Update existing account's signer and writable flags
                    let index = *account_indices.get(&account_meta.pubkey).unwrap();
                    let existing = &mut account_metas[index];
                    existing.is_signer |= account_meta.is_signer;
                    existing.is_writable |= account_meta.is_writable;
                }
            }

            // Add program ID if not already included
            if let std::collections::hash_map::Entry::Vacant(e) =
                account_indices.entry(instruction.program_id)
            {
                e.insert(account_metas.len());
                account_metas.push(AccountMeta {
                    pubkey: instruction.program_id,
                    is_signer: false,
                    is_writable: false,
                });
            }
        }

        // Sort accounts according to Solana rules:
        // 1. Signer + writable
        // 2. Signer + readonly
        // 3. Non-signer + writable
        // 4. Non-signer + readonly
        let mut pubkey_to_meta = std::collections::HashMap::new();
        for meta in &account_metas {
            pubkey_to_meta.insert(meta.pubkey, meta.clone());
        }

        // Sort keys first.
        // - This logic is similar to that in CompiledKeys::try_into_message_components().
        // - The first serialized key is considered to be the fee_payer (regardless
        //   of whether it is the sender, see e.g. SanitizedMessage::fee_payer()),
        // - For that reason explicitly force fee_payer to be first (the sort order
        //   provides some of that, minus the a.cmp(b) comparison).
        let mut keys_except_payer: Vec<Pubkey> = account_metas
            .iter()
            .skip(1)
            .map(|meta| meta.pubkey)
            .collect();
        keys_except_payer.sort_by(|a, b| {
            let a_meta = pubkey_to_meta.get(a).unwrap();
            let b_meta = pubkey_to_meta.get(b).unwrap();

            match (
                a_meta.is_signer,
                a_meta.is_writable,
                b_meta.is_signer,
                b_meta.is_writable,
            ) {
                // Signer + writable accounts first
                (true, true, true, false) => std::cmp::Ordering::Less,
                (true, false, true, true) => std::cmp::Ordering::Greater,

                // All signers before non-signers
                (true, _, false, _) => std::cmp::Ordering::Less,
                (false, _, true, _) => std::cmp::Ordering::Greater,

                // For same signer status, writable before readonly
                (true, true, true, true) | (false, true, false, true) => a.cmp(b),
                (true, false, true, false) | (false, false, false, false) => a.cmp(b),
                (false, true, false, false) => std::cmp::Ordering::Less,
                (false, false, false, true) => std::cmp::Ordering::Greater,
            }
        });
        let mut sorted_keys = vec![self.fee_payer];
        sorted_keys.extend(keys_except_payer);

        // Rebuild account_metas in sorted order and recreate indices map
        account_indices.clear();
        account_metas.clear();
        for (i, key) in sorted_keys.iter().enumerate() {
            let meta = pubkey_to_meta.get(key).unwrap();
            account_metas.push(meta.clone());
            account_indices.insert(*key, i);
        }

        // Create compiled instructions
        let compiled_instructions = self
            .instructions
            .iter()
            .map(|ix| {
                let program_idx = *account_indices.get(&ix.program_id).ok_or_else(|| {
                    RialoError::Transaction(format!(
                        "Program ID not found in accounts: {}",
                        ix.program_id
                    ))
                })?;

                let account_indices = ix
                    .accounts
                    .iter()
                    .map(|meta| {
                        account_indices.get(&meta.pubkey).copied().ok_or_else(|| {
                            RialoError::Transaction(format!("Account not found: {}", meta.pubkey))
                        })
                    })
                    .collect::<Result<Vec<_>>>()?;

                Ok(CompiledInstruction {
                    program_idx: program_idx as u8,
                    accounts: account_indices.into_iter().map(|idx| idx as u8).collect(),
                    data: ix.data.clone(),
                })
            })
            .collect::<Result<Vec<_>>>()?;

        // Count required signatures and read-only accounts
        let num_required_signatures = account_metas.iter().filter(|a| a.is_signer).count() as u8;
        let num_readonly_signed_accounts = account_metas
            .iter()
            .filter(|a| a.is_signer && !a.is_writable)
            .count() as u8;
        let num_readonly_unsigned_accounts = account_metas
            .iter()
            .filter(|a| !a.is_signer && !a.is_writable)
            .count() as u8;

        // Create message
        let message = Message {
            header: MessageHeader {
                num_required_signatures,
                num_readonly_signed_accounts,
                num_readonly_unsigned_accounts,
            },
            account_keys: account_metas.iter().map(|a| a.pubkey).collect(),
            valid_from: self.valid_from,
            instructions: compiled_instructions,
        };

        // Serialize message using borsh or bincode - here we'll use a basic binary format
        let message_bytes = serialize_message(&message)?;

        Ok(message_bytes)
    }

    /// Builds and signs the transaction with the wallet's default or specified account
    ///
    /// # Arguments
    ///
    /// * `wallet` - The wallet to use for signing
    ///
    /// # Returns
    ///
    /// The serialized, signed transaction as a byte vector ready for submission
    ///
    /// # Errors
    ///
    /// Returns an error if message building, signing, or serialization fails
    pub fn sign(&self, keyring: &Keyring) -> Result<Vec<u8>> {
        // Build the transaction message
        let message_bytes = self.build()?;

        // Sign the message with the specified keypair index or default keypair
        let signature = match self.account_index {
            Some(index) => keyring.sign_with_keypair(&message_bytes, index as u32)?,
            None => keyring.sign(&message_bytes),
        };

        // Create transaction structure
        let transaction = Transaction {
            signatures: vec![signature.to_bytes().to_vec()],
            message: message_bytes.clone(),
        };

        // Serialize the full transaction
        let transaction_bytes = serialize_transaction(&transaction)?;

        Ok(transaction_bytes)
    }

    /// Builds and signs the transaction with a specific keypair from the keyring
    ///
    /// # Arguments
    ///
    /// * `keyring` - The keyring to use for signing
    /// * `keypair_index` - The specific keypair index to use for signing
    ///
    /// # Returns
    ///
    /// The serialized, signed transaction as a byte vector ready for submission
    ///
    /// # Errors
    ///
    /// Returns an error if message building, signing, or serialization fails
    pub fn sign_with_keypair(&self, keyring: &Keyring, keypair_index: u32) -> Result<Vec<u8>> {
        // Build the transaction message
        let message_bytes = self.build()?;

        // Sign the message with the specified keypair
        let signature = keyring.sign_with_keypair(&message_bytes, keypair_index)?;

        // Create transaction structure
        let transaction = Transaction {
            signatures: vec![signature.to_bytes().to_vec()],
            message: message_bytes.clone(),
        };

        // Serialize the full transaction using Solana wire format
        let transaction_bytes = serialize_transaction(&transaction)?;

        Ok(transaction_bytes)
    }

    /// Builds and signs the transaction with a specific account from the wallet
    ///
    /// # Deprecated
    ///
    /// Use `sign_with_keypair` instead.
    #[deprecated(since = "0.2.0", note = "Use sign_with_keypair instead")]
    pub fn sign_with_account(&self, keyring: &Keyring, account_index: u32) -> Result<Vec<u8>> {
        self.sign_with_keypair(keyring, account_index)
    }

    /// Builds the transaction as a Transaction struct (unsigned)
    ///
    /// This method creates a Transaction object with empty signatures that can be
    /// serialized, signed manually, and deserialized later.
    ///
    /// # Returns
    ///
    /// A Transaction struct with the message and empty signatures
    ///
    /// # Errors
    ///
    /// Returns an error if message construction fails
    pub fn build_transaction(&self) -> Result<Transaction> {
        // Build the transaction message
        let message_bytes = self.build()?;

        // Parse the message to determine number of required signatures
        let message_header = parse_message_header(&message_bytes)?;
        let num_signatures = message_header.num_required_signatures as usize;

        // Create transaction with empty signatures
        Ok(Transaction::new(message_bytes, num_signatures))
    }
}

/// Internal structure representing an account reference in a transaction
///
/// Each account used in a transaction needs metadata about whether it's
/// signing the transaction and whether it's being written to.
#[derive(Clone, Debug)]
struct AccountMeta {
    /// The account's public key (base58-encoded)
    pubkey: Pubkey,
    /// Whether this account is a signer of the transaction
    is_signer: bool,
    /// Whether this account's data will be modified by the transaction
    is_writable: bool,
}

/// Header information for a transaction message
///
/// The header contains counts of different types of accounts in the transaction,
/// which is used during verification and execution.
#[derive(Debug, Clone)]
pub struct MessageHeader {
    /// Number of signatures required for this transaction
    pub num_required_signatures: u8,
    /// Number of read-only signed accounts
    pub num_readonly_signed_accounts: u8,
    /// Number of read-only unsigned accounts
    pub num_readonly_unsigned_accounts: u8,
}

/// Deserialized representation of a transaction message
///
/// This structure contains the fully parsed transaction message with all
/// instructions and account information accessible for inspection.
#[derive(Debug, Clone)]
pub struct DeserializedMessage {
    /// Message header with signature and account type counts
    pub header: MessageHeader,
    /// Ordered list of all accounts referenced by this transaction
    pub account_keys: Vec<Pubkey>,
    /// List of instructions to execute in sequence
    pub instructions: Vec<DeserializedInstruction>,
}

/// A deserialized instruction with resolved account references
///
/// Unlike CompiledInstruction which uses indices, this structure
/// contains the actual public keys for easier inspection.
#[derive(Debug, Clone)]
pub struct DeserializedInstruction {
    /// The program that will execute this instruction
    pub program_id: Pubkey,
    /// The accounts used by this instruction
    pub accounts: Vec<Pubkey>,
    /// Program-specific instruction data
    pub data: Vec<u8>,
}

/// A compiled instruction in the transaction format
///
/// This is the binary representation of an instruction, with accounts
/// referenced by their index in the message's account list.
#[derive(Debug, Clone)]
struct CompiledInstruction {
    /// Index of the program account in the message's account list
    program_idx: u8,
    /// Indices of the accounts used by this instruction
    accounts: Vec<u8>,
    /// Program-specific instruction data
    data: Vec<u8>,
}

/// Complete transaction message ready for serialization
///
/// A message contains all the information needed for transaction execution,
/// including account references, a transaction valid from, and instructions.
#[derive(Debug)]
struct Message {
    /// Message header with signature and account type counts
    header: MessageHeader,
    /// Ordered list of all accounts referenced by this transaction
    account_keys: Vec<Pubkey>,
    /// Transaction valid from in milliseconds since Unix epoch
    valid_from: i64,
    /// List of instructions to execute in sequence
    instructions: Vec<CompiledInstruction>,
}

/// Serializes a transaction message into the wire format
///
/// # Arguments
///
/// * `message` - The transaction message to serialize
///
/// # Returns
///
/// The serialized message as a byte vector
///
/// # Errors
///
/// Returns an error if serialization fails
fn serialize_message(message: &Message) -> Result<Vec<u8>> {
    let mut buffer = Vec::new();

    // Serialize header (3 bytes)
    buffer.push(message.header.num_required_signatures);
    buffer.push(message.header.num_readonly_signed_accounts);
    buffer.push(message.header.num_readonly_unsigned_accounts);

    // Serialize account keys (compact-array)
    serialize_compact_array(&mut buffer, &message.account_keys, |buf, key: &Pubkey| {
        // Pubkey is already a 32-byte array, just copy the bytes
        buf.extend_from_slice(key.as_ref());
        Ok(())
    })?;

    // Serialize valid_from (8 bytes, little-endian i64)
    buffer.extend_from_slice(&message.valid_from.to_le_bytes());

    // Serialize instructions (compact-array)
    serialize_compact_array(&mut buffer, &message.instructions, |buf, instruction| {
        // Program ID index (1 byte)
        buf.push(instruction.program_idx);

        // Account indices (compact-array of bytes)
        serialize_compact_array(buf, &instruction.accounts, |b, &idx| {
            b.push(idx);
            Ok(())
        })?;

        // Instruction data (compact-array of bytes)
        serialize_compact_array(buf, &instruction.data, |b, &byte| {
            b.push(byte);
            Ok(())
        })?;

        Ok(())
    })?;

    Ok(buffer)
}

/// Serializes a complete signed transaction into the wire format
///
/// # Arguments
///
/// * `transaction` - The signed transaction to serialize
///
/// # Returns
///
/// The serialized transaction as a byte vector
///
/// # Errors
///
/// Returns an error if serialization fails
fn serialize_transaction(transaction: &Transaction) -> Result<Vec<u8>> {
    let mut buffer = Vec::new();

    // Serialize signatures (compact-array)
    serialize_compact_array(&mut buffer, &transaction.signatures, |buf, sig| {
        if sig.len() != 64 {
            return Err(RialoError::Transaction(
                "Invalid signature length".to_string(),
            ));
        }
        buf.extend_from_slice(sig);
        Ok(())
    })?;

    // Append message
    buffer.extend_from_slice(&transaction.message);

    Ok(buffer)
}

/// Helper function to serialize arrays with a compact-u16 length prefix
///
/// This implementation supports the Solana wire format, which uses a variable-length
/// encoding for array lengths to save space.
///
/// # Arguments
///
/// * `buffer` - The output buffer to write to
/// * `items` - The items to serialize
/// * `serialize_item` - Function to serialize each item
///
/// # Returns
///
/// Ok(()) if serialization succeeds
///
/// # Errors
///
/// Returns an error if serialization fails or the array is too large
fn serialize_compact_array<T, F>(
    buffer: &mut Vec<u8>,
    items: &[T],
    mut serialize_item: F,
) -> Result<()>
where
    F: FnMut(&mut Vec<u8>, &T) -> Result<()>,
{
    // Write compact-u16 length prefix
    let len = items.len();
    if len > 0x7FFF {
        return Err(RialoError::Transaction(
            "Array too large (max 32767 items)".to_string(),
        ));
    }

    // Encode compact-u16 following standard variable-length encoding:
    // - If the value is < 128, it's encoded as a single byte
    // - If the value is >= 128, it's encoded as two bytes:
    //   - First byte: (value & 0x7F) | 0x80 (sets the high bit)
    //   - Second byte: value >> 7
    if len < 128 {
        buffer.push(len as u8);
    } else {
        let lower_seven_bits = (len & 0x7F) as u8;
        let upper_bits = ((len >> 7) & 0x7F) as u8;
        buffer.push(lower_seven_bits | 0x80); // Set high bit to indicate multi-byte encoding
        buffer.push(upper_bits);
    }

    // Serialize each item
    for item in items {
        serialize_item(buffer, item)?;
    }

    Ok(())
}

/// Deserializes a complete signed transaction from the wire format
///
/// # Arguments
///
/// * `data` - The serialized transaction bytes
///
/// # Returns
///
/// The deserialized transaction or an error if deserialization fails
///
/// # Errors
///
/// Returns an error if the data is malformed or doesn't contain a valid transaction
fn deserialize_transaction(data: &[u8]) -> Result<Transaction> {
    if data.is_empty() {
        return Err(RialoError::Transaction(
            "Empty transaction data".to_string(),
        ));
    }

    let mut cursor = 0;

    // Deserialize signatures
    let (signatures, new_cursor) = deserialize_compact_array(data, cursor, |data, cursor| {
        if data.len() < cursor + 64 {
            return Err(RialoError::Transaction(
                "Insufficient data for signature".to_string(),
            ));
        }
        let signature = data[cursor..cursor + 64].to_vec();
        Ok((signature, cursor + 64))
    })?;
    cursor = new_cursor;

    // Remaining data is the message
    if cursor >= data.len() {
        return Err(RialoError::Transaction("No message data found".to_string()));
    }
    let message = data[cursor..].to_vec();

    Ok(Transaction {
        signatures,
        message,
    })
}

/// Helper function to deserialize a compact array
///
/// # Arguments
///
/// * `data` - The input data
/// * `cursor` - Current position in the data
/// * `deserialize_item` - Function to deserialize each item
///
/// # Returns
///
/// A tuple containing the deserialized items and the new cursor position
fn deserialize_compact_array<T, F>(
    data: &[u8],
    mut cursor: usize,
    mut deserialize_item: F,
) -> Result<(Vec<T>, usize)>
where
    F: FnMut(&[u8], usize) -> Result<(T, usize)>,
{
    // Read compact-u16 length prefix
    if cursor >= data.len() {
        return Err(RialoError::Transaction(
            "Insufficient data for array length".to_string(),
        ));
    }

    let first_byte = data[cursor];
    cursor += 1;

    let len = if first_byte & 0x80 == 0 {
        // Single byte encoding
        first_byte as usize
    } else {
        // Two byte encoding
        if cursor >= data.len() {
            return Err(RialoError::Transaction(
                "Insufficient data for array length continuation".to_string(),
            ));
        }
        let second_byte = data[cursor];
        cursor += 1;
        ((first_byte & 0x7F) as usize) | ((second_byte as usize) << 7)
    };

    // Deserialize each item
    let mut items = Vec::with_capacity(len);
    for _ in 0..len {
        let (item, new_cursor) = deserialize_item(data, cursor)?;
        items.push(item);
        cursor = new_cursor;
    }

    Ok((items, cursor))
}

/// Parse the message header from message bytes
///
/// # Arguments
///
/// * `message` - The message bytes
///
/// # Returns
///
/// The parsed message header
pub fn parse_message_header(message: &[u8]) -> Result<MessageHeader> {
    if message.len() < 3 {
        return Err(RialoError::Transaction(
            "Message too short for header".to_string(),
        ));
    }

    Ok(MessageHeader {
        num_required_signatures: message[0],
        num_readonly_signed_accounts: message[1],
        num_readonly_unsigned_accounts: message[2],
    })
}

/// Parse account keys from message bytes
///
/// # Arguments
///
/// * `message` - The message bytes
/// * `header` - The parsed message header
///
/// # Returns
///
/// The list of account public keys in order
pub fn parse_account_keys(message: &[u8], header: &MessageHeader) -> Result<Vec<Pubkey>> {
    let cursor = 3; // Skip header

    // Deserialize account keys
    let (account_keys, _) = deserialize_compact_array(message, cursor, |data, cursor| {
        if data.len() < cursor + 32 {
            return Err(RialoError::Transaction(
                "Insufficient data for pubkey".to_string(),
            ));
        }
        let mut pubkey_bytes = [0u8; 32];
        pubkey_bytes.copy_from_slice(&data[cursor..cursor + 32]);
        Ok((Pubkey::new_from_array(pubkey_bytes), cursor + 32))
    })?;

    // Validate that we have the expected number of signers
    let expected_signers = header.num_required_signatures as usize;
    if account_keys.len() < expected_signers {
        return Err(RialoError::Transaction(format!(
            "Not enough account keys for signers: expected {}, got {}",
            expected_signers,
            account_keys.len()
        )));
    }

    Ok(account_keys)
}

/// Deserialize a complete transaction message from wire format
///
/// This function fully parses a serialized message, providing access to all
/// transaction details including instruction data for side effect analysis.
///
/// # Arguments
///
/// * `data` - The serialized message bytes
///
/// # Returns
///
/// A fully deserialized message with resolved account references
///
/// # Example
///
/// ```
/// # use rialo_cdk::transaction::builder::deserialize_message;
/// # fn example(message_bytes: Vec<u8>) -> Result<(), Box<dyn std::error::Error>> {
/// let deserialized = deserialize_message(&message_bytes)?;
/// for instruction in &deserialized.instructions {
///     println!("Program: {}", instruction.program_id);
///     println!("Accounts: {:?}", instruction.accounts);
///     println!("Data: {:?}", instruction.data);
/// }
/// # Ok(())
/// # }
/// ```
pub fn deserialize_message(data: &[u8]) -> Result<DeserializedMessage> {
    if data.len() < 3 {
        return Err(RialoError::Transaction(
            "Message too short for header".to_string(),
        ));
    }

    // Parse header
    let header = parse_message_header(data)?;
    let mut cursor = 3;

    // Parse account keys
    let (account_keys, new_cursor) = deserialize_compact_array(data, cursor, |data, cursor| {
        if data.len() < cursor + 32 {
            return Err(RialoError::Transaction(
                "Insufficient data for pubkey".to_string(),
            ));
        }
        let mut pubkey_bytes = [0u8; 32];
        pubkey_bytes.copy_from_slice(&data[cursor..cursor + 32]);
        Ok((Pubkey::new_from_array(pubkey_bytes), cursor + 32))
    })?;
    cursor = new_cursor;

    // Parse valid_from (8 bytes, little-endian i64)
    if data.len() < cursor + 8 {
        return Err(RialoError::Transaction(
            "Insufficient data for valid_from".to_string(),
        ));
    }
    let mut valid_from_bytes = [0u8; 8];
    valid_from_bytes.copy_from_slice(&data[cursor..cursor + 8]);
    let _valid_from = i64::from_le_bytes(valid_from_bytes);
    cursor += 8;

    // Parse instructions
    let (compiled_instructions, _) = deserialize_compact_array(data, cursor, |data, cursor| {
        if data.len() < cursor + 1 {
            return Err(RialoError::Transaction(
                "Insufficient data for program index".to_string(),
            ));
        }
        let program_idx = data[cursor];
        let mut cursor = cursor + 1;

        // Parse account indices
        let (accounts, new_cursor) = deserialize_compact_array(data, cursor, |data, cursor| {
            if data.len() < cursor + 1 {
                return Err(RialoError::Transaction(
                    "Insufficient data for account index".to_string(),
                ));
            }
            Ok((data[cursor], cursor + 1))
        })?;
        cursor = new_cursor;

        // Parse instruction data
        let (data_bytes, new_cursor) = deserialize_compact_array(data, cursor, |data, cursor| {
            if data.len() < cursor + 1 {
                return Err(RialoError::Transaction(
                    "Insufficient data for instruction data".to_string(),
                ));
            }
            Ok((data[cursor], cursor + 1))
        })?;
        cursor = new_cursor;

        Ok((
            CompiledInstruction {
                program_idx,
                accounts,
                data: data_bytes,
            },
            cursor,
        ))
    })?;

    // Convert compiled instructions to deserialized instructions with resolved pubkeys
    let instructions = compiled_instructions
        .into_iter()
        .map(|compiled| {
            // Resolve program ID
            let program_id = *account_keys
                .get(compiled.program_idx as usize)
                .ok_or_else(|| {
                    RialoError::Transaction(format!(
                        "Invalid program index: {}",
                        compiled.program_idx
                    ))
                })?;

            // Resolve account pubkeys
            let accounts = compiled
                .accounts
                .iter()
                .map(|&idx| {
                    account_keys.get(idx as usize).copied().ok_or_else(|| {
                        RialoError::Transaction(format!("Invalid account index: {}", idx))
                    })
                })
                .collect::<Result<Vec<_>>>()?;

            Ok(DeserializedInstruction {
                program_id,
                accounts,
                data: compiled.data,
            })
        })
        .collect::<Result<Vec<_>>>()?;

    Ok(DeserializedMessage {
        header,
        account_keys,
        instructions,
    })
}