thru_base/
txn_tools.rs

1use crate::{
2    StateProof,
3    tn_public_address::tn_pubkey_to_address_string,
4    txn_lib::{TnPubkey, Transaction},
5};
6use anyhow::Result;
7use hex;
8
9#[derive(Debug, Clone)]
10pub struct TransactionBuilder {
11    // fee_payer: FdPubkey,
12    // program: FdPubkey,
13    // fee: u64,
14    // nonce: u64,
15}
16
17impl TransactionBuilder {
18    /// Build balance transfer transaction
19    pub fn build_create_with_fee_payer_proof(
20        fee_payer: TnPubkey,
21        start_slot: u64,
22        fee_payer_state_proof: &StateProof,
23    ) -> Result<Transaction> {
24        let mut noop_program = [0u8; 32];
25        noop_program[31] = 0x02;
26        let tx = Transaction::new(fee_payer, noop_program, 0, 0)
27            .with_fee_payer_state_proof(fee_payer_state_proof)
28            .with_start_slot(start_slot)
29            .with_expiry_after(100)
30            .with_compute_units(10_000)
31            .with_memory_units(10_000)
32            .with_state_units(10_000);
33        Ok(tx)
34    }
35
36    /// Build balance transfer transaction
37    pub fn build_transfer(
38        fee_payer: TnPubkey,
39        program: TnPubkey,
40        to_account: TnPubkey,
41        amount: u64,
42        fee: u64,
43        nonce: u64,
44        start_slot: u64,
45    ) -> Result<Transaction> {
46        // Create transfer instruction data
47        // Account layout: [0: fee_payer/from_account, 1: program, 2: to_account]
48        let from_account_idx = 0u16; // fee_payer is also the from_account
49        let to_account_idx = 2u16; // to_account added via add_rw_account
50        let instruction_data =
51            build_transfer_instruction(from_account_idx, to_account_idx, amount)?;
52
53        let tx = Transaction::new(fee_payer, program, fee, nonce)
54            .with_start_slot(start_slot)
55            .add_rw_account(to_account) // Alice account (receives transfer)
56            .with_instructions(instruction_data)
57            .with_expiry_after(100)
58            .with_compute_units(10000)
59            .with_memory_units(10000)
60            .with_state_units(10000);
61
62        Ok(tx)
63    }
64
65    /// Build regular account creation transaction (with optional state proof)
66    pub fn build_create_account(
67        fee_payer: TnPubkey,
68        program: TnPubkey,
69        target_account: TnPubkey,
70        seed: &str,
71        state_proof: Option<&[u8]>,
72        fee: u64,
73        nonce: u64,
74        start_slot: u64,
75    ) -> Result<Transaction> {
76        // Account layout: [0: fee_payer, 1: program, 2: target_account]
77        let target_account_idx = 2u16; // target_account added via add_rw_account
78        let instruction_data =
79            build_create_account_instruction(target_account_idx, seed, state_proof)?;
80
81        let tx = Transaction::new(fee_payer, program, fee, nonce)
82            .with_start_slot(start_slot)
83            .add_rw_account(target_account)
84            .with_instructions(instruction_data)
85            .with_expiry_after(100)
86            .with_compute_units(10_000)
87            .with_memory_units(10_000)
88            .with_state_units(10_000);
89
90        Ok(tx)
91    }
92
93    /// Build account creation transaction
94    pub fn build_create_ephemeral_account(
95        fee_payer: TnPubkey,
96        program: TnPubkey,
97        target_account: TnPubkey,
98        seed: &[u8; 32],
99        fee: u64,
100        nonce: u64,
101        start_slot: u64,
102    ) -> Result<Transaction> {
103        // Account layout: [0: fee_payer, 1: program, 2: target_account]
104        let target_account_idx = 2u16; // target_account added via add_rw_account
105        let instruction_data = build_ephemeral_account_instruction(target_account_idx, seed)?;
106
107        let tx = Transaction::new(fee_payer, program, fee, nonce)
108            .with_start_slot(start_slot)
109            .add_rw_account(target_account)
110            .with_instructions(instruction_data)
111            .with_expiry_after(100)
112            .with_compute_units(50_000)
113            .with_memory_units(10_000)
114            .with_state_units(10_000);
115        Ok(tx)
116    }
117
118    /// Build account resize transaction
119    pub fn build_resize_account(
120        fee_payer: TnPubkey,
121        program: TnPubkey,
122        target_account: TnPubkey,
123        new_size: u64,
124        fee: u64,
125        nonce: u64,
126        start_slot: u64,
127    ) -> Result<Transaction> {
128        // Account layout: [0: fee_payer, 1: program, 2: target_account]
129        let target_account_idx = 2u16; // target_account added via add_rw_account
130        let instruction_data = build_resize_instruction(target_account_idx, new_size)?;
131
132        let tx = Transaction::new(fee_payer, program, fee, nonce)
133            .with_start_slot(start_slot)
134            .with_expiry_after(100)
135            .with_compute_units(100032)
136            .with_state_units(1 + new_size.checked_div(4096).unwrap() as u16)
137            .with_memory_units(10000)
138            .add_rw_account(target_account)
139            .with_instructions(instruction_data)
140            .with_expiry_after(100)
141            .with_compute_units(10_000 + 2 * new_size as u32)
142            .with_memory_units(10_000)
143            .with_state_units(10_000);
144
145        Ok(tx)
146    }
147
148    /// Build account compression transaction
149    pub fn build_compress_account(
150        fee_payer: TnPubkey,
151        program: TnPubkey,
152        target_account: TnPubkey,
153        state_proof: &[u8],
154        fee: u64,
155        nonce: u64,
156        start_slot: u64,
157        account_size: u32,
158    ) -> Result<Transaction> {
159        // Account layout: [0: fee_payer, 1: program, 2: target_account]
160        let target_account_idx = 2u16; // target_account added via add_rw_account
161        let instruction_data = build_compress_instruction(target_account_idx, state_proof)?;
162
163        let tx = Transaction::new(fee_payer, program, fee, nonce)
164            .with_start_slot(start_slot)
165            .with_may_compress_account()
166            .add_rw_account(target_account)
167            .with_instructions(instruction_data)
168            .with_expiry_after(100)
169            .with_compute_units(100_300 + account_size * 2)
170            .with_memory_units(10000)
171            .with_state_units(10000);
172
173        Ok(tx)
174    }
175
176    /// Build account decompression transaction
177    pub fn build_decompress_account(
178        fee_payer: TnPubkey,
179        program: TnPubkey,
180        target_account: TnPubkey,
181        account_data: &[u8],
182        state_proof: &[u8],
183        fee: u64,
184        nonce: u64,
185        start_slot: u64,
186    ) -> Result<Transaction> {
187        // Account layout: [0: fee_payer, 1: program, 2: target_account]
188        let target_account_idx = 2u16; // target_account added via add_rw_account
189        let instruction_data =
190            build_decompress_instruction(target_account_idx, account_data, state_proof)?;
191
192        let tx = Transaction::new(fee_payer, program, fee, nonce)
193            .with_start_slot(start_slot)
194            .add_rw_account(target_account)
195            .with_instructions(instruction_data)
196            .with_compute_units(100_300 + account_data.len() as u32 * 2)
197            .with_state_units(10_000)
198            .with_memory_units(10_000)
199            .with_expiry_after(100);
200        Ok(tx)
201    }
202
203    /// Build data write transaction
204    pub fn build_write_data(
205        fee_payer: TnPubkey,
206        program: TnPubkey,
207        target_account: TnPubkey,
208        offset: u16,
209        data: &[u8],
210        fee: u64,
211        nonce: u64,
212        start_slot: u64,
213    ) -> Result<Transaction> {
214        // Account layout: [0: fee_payer, 1: program, 2: target_account]
215        let target_account_idx = 2u16; // target_account added via add_rw_account
216        let instruction_data = build_write_instruction(target_account_idx, offset, data)?;
217
218        let tx = Transaction::new(fee_payer, program, fee, nonce)
219            .with_start_slot(start_slot)
220            .with_expiry_after(100)
221            .with_compute_units(100045)
222            .with_state_units(10000)
223            .with_memory_units(10000)
224            .add_rw_account(target_account)
225            .with_instructions(instruction_data);
226
227        Ok(tx)
228    }
229}
230
231/// Build transfer instruction matching shell script format
232fn build_transfer_instruction(
233    from_account_idx: u16,
234    to_account_idx: u16,
235    amount: u64,
236) -> Result<Vec<u8>> {
237    let mut instruction = Vec::new();
238
239    // TI_DISCRIMINANT = 03
240    instruction.push(0x03);
241
242    // TI_FROM_PUBKEY_IDX (little-endian u16)
243    instruction.extend_from_slice(&from_account_idx.to_le_bytes());
244
245    // TI_TO_PUBKEY_IDX (little-endian u16)
246    instruction.extend_from_slice(&to_account_idx.to_le_bytes());
247
248    // Amount in little-endian u64
249    instruction.extend_from_slice(&amount.to_le_bytes());
250
251    Ok(instruction)
252}
253
254/// Build regular account creation instruction (TN_SYS_PROG_DISCRIMINANT_ACCOUNT_CREATE = 0x00)
255fn build_create_account_instruction(
256    target_account_idx: u16,
257    seed: &str,
258    state_proof: Option<&[u8]>,
259) -> Result<Vec<u8>> {
260    let mut instruction = Vec::new();
261
262    // TN_SYS_PROG_DISCRIMINANT_ACCOUNT_CREATE = 0x00
263    instruction.push(0x00);
264
265    // Target account index (little-endian u16) - matching tn_system_program_account_create_args_t
266    instruction.extend_from_slice(&target_account_idx.to_le_bytes());
267
268    // Seed should be hex-decoded (to match addrtool behavior)
269    let seed_bytes =
270        hex::decode(seed).map_err(|e| anyhow::anyhow!("Failed to decode hex seed: {}", e))?;
271
272    // Seed length (little-endian u64) - matching tn_system_program_account_create_args_t.seed_len
273    instruction.extend_from_slice(&(seed_bytes.len() as u64).to_le_bytes());
274
275    // has_proof flag (1 byte) - matching tn_system_program_account_create_args_t.has_proof
276    let has_proof = state_proof.is_some();
277    instruction.push(if has_proof { 1u8 } else { 0u8 });
278
279    // Seed data (seed_len bytes follow)
280    instruction.extend_from_slice(&seed_bytes);
281
282    // Proof data (if present, proof follows seed)
283    if let Some(proof) = state_proof {
284        instruction.extend_from_slice(proof);
285    }
286
287    Ok(instruction)
288}
289
290/// Build ephemeral account creation instruction
291fn build_ephemeral_account_instruction(
292    target_account_idx: u16,
293    seed: &[u8; 32],
294) -> Result<Vec<u8>> {
295    let mut instruction = Vec::new();
296
297    // TN_SYS_PROG_DISCRIMINANT_ACCOUNT_CREATE_EPHEMERAL = 01
298    instruction.push(0x01);
299
300    // Target account index (little-endian u16)
301    instruction.extend_from_slice(&target_account_idx.to_le_bytes());
302
303    // Seed length (little-endian u64)
304    instruction.extend_from_slice(&(seed.len() as u64).to_le_bytes());
305
306    // Seed data
307    instruction.extend_from_slice(seed);
308
309    Ok(instruction)
310}
311
312/// Build account resize instruction
313fn build_resize_instruction(target_account_idx: u16, new_size: u64) -> Result<Vec<u8>> {
314    let mut instruction = Vec::new();
315
316    // TN_SYS_PROG_DISCRIMINANT_ACCOUNT_RESIZE = 04
317    instruction.push(0x04);
318
319    // Target account index (little-endian u16)
320    instruction.extend_from_slice(&target_account_idx.to_le_bytes());
321
322    // New size (little-endian u64)
323    instruction.extend_from_slice(&new_size.to_le_bytes());
324
325    Ok(instruction)
326}
327
328/// Build data write instruction
329fn build_write_instruction(target_account_idx: u16, offset: u16, data: &[u8]) -> Result<Vec<u8>> {
330    let mut instruction = Vec::new();
331
332    // TN_SYS_PROG_DISCRIMINANT_WRITE = C8
333    instruction.push(0xC8);
334
335    // Target account index (little-endian u16)
336    instruction.extend_from_slice(&target_account_idx.to_le_bytes());
337
338    // Offset (little-endian u16)
339    instruction.extend_from_slice(&offset.to_le_bytes());
340
341    // Data length (little-endian u16)
342    instruction.extend_from_slice(&(data.len() as u16).to_le_bytes());
343
344    // Data
345    instruction.extend_from_slice(data);
346
347    Ok(instruction)
348}
349
350/// Build account compression instruction
351fn build_compress_instruction(target_account_idx: u16, state_proof: &[u8]) -> Result<Vec<u8>> {
352    let mut instruction = Vec::new();
353
354    // TN_SYS_PROG_DISCRIMINANT_ACCOUNT_COMPRESS - based on C test, this appears to be different from other discriminants
355    // Looking at the C test pattern and other system discriminants, compression is likely 0x05
356    instruction.push(0x05);
357
358    // Target account index (little-endian u16)
359    instruction.extend_from_slice(&target_account_idx.to_le_bytes());
360
361    // State proof bytes
362    instruction.extend_from_slice(state_proof);
363
364    Ok(instruction)
365}
366
367fn build_decompress_instruction(
368    target_account_idx: u16,
369    account_data: &[u8],
370    state_proof: &[u8],
371) -> Result<Vec<u8>> {
372    let mut instruction = Vec::new();
373
374    // TN_SYS_PROG_DISCRIMINANT_ACCOUNT_DECOMPRESS = 0x06
375    instruction.push(0x06);
376
377    // tn_system_program_account_decompress_args_t: account_idx (u16) + data_len (u64)
378    instruction.extend_from_slice(&target_account_idx.to_le_bytes());
379    instruction.extend_from_slice(&(account_data.len() as u64).to_le_bytes());
380
381    // Account data
382    instruction.extend_from_slice(account_data);
383
384    // State proof bytes
385    instruction.extend_from_slice(state_proof);
386
387    Ok(instruction)
388}
389
390/// Generate ephemeral account address from seed
391/// This replaces the `addrtool --ephemeral` functionality
392/// Based on create_program_defined_account_address from tn_vm_syscalls.c
393/// Note: For ephemeral accounts, the owner is always the system program (all zeros)
394pub fn generate_ephemeral_address(seed: &str) -> Result<String> {
395    // Owner is always system program (all zeros) for ephemeral accounts
396    let owner_pubkey = [0u8; 32];
397
398    // Convert seed string to hex bytes (addrtool expects hex-encoded seed)
399    let seed_bytes =
400        hex::decode(seed).map_err(|e| anyhow::anyhow!("Failed to decode hex seed: {}", e))?;
401
402    // Pad or truncate to exactly 32 bytes (matching C implementation)
403    let mut seed_32 = [0u8; 32];
404    let copy_len = std::cmp::min(seed_bytes.len(), 32);
405    seed_32[..copy_len].copy_from_slice(&seed_bytes[..copy_len]);
406
407    // Use the new implementation from tn_public_address
408    Ok(
409        crate::tn_public_address::create_program_defined_account_address_string(
410            &owner_pubkey,
411            true, // is_ephemeral = true
412            &seed_32,
413        ),
414    )
415}
416
417pub fn generate_system_derived_address(seed: &str, is_ephemeral: bool) -> Result<String> {
418    // Convert seed string to hex bytes (addrtool expects hex-encoded seed)
419    let seed_bytes =
420        hex::decode(seed).map_err(|e| anyhow::anyhow!("Failed to decode hex seed: {}", e))?;
421
422    let pubkey = generate_derived_address(&seed_bytes, &[0u8; 32], is_ephemeral)?;
423
424    Ok(tn_pubkey_to_address_string(&pubkey))
425}
426
427pub fn generate_derived_address(
428    seed: &[u8],
429    owner_pubkey: &[u8; 32],
430    is_ephemeral: bool,
431) -> Result<[u8; 32]> {
432    use sha2::{Digest, Sha256};
433
434    // Create SHA256 hasher
435    let mut hasher = Sha256::new();
436
437    // Hash owner pubkey (32 bytes) - system program
438    hasher.update(&owner_pubkey);
439
440    // Hash is_ephemeral flag (1 byte)
441    hasher.update(&[is_ephemeral as u8]);
442
443    // Hash seed bytes
444    hasher.update(&seed);
445
446    // Finalize hash to get 32-byte result
447    Ok(hasher.finalize().into())
448}
449
450#[cfg(test)]
451mod tests {
452    use super::*;
453
454    #[test]
455    fn test_ephemeral_address_generation() {
456        // Test with hex-encoded seeds (as addrtool expects)
457        let hex_seed1 = hex::encode("test_seed_123");
458        let hex_seed2 = hex::encode("test_seed_123");
459        let hex_seed3 = hex::encode("different_seed");
460
461        let addr1 = generate_ephemeral_address(&hex_seed1).unwrap();
462        let addr2 = generate_ephemeral_address(&hex_seed2).unwrap();
463        let addr3 = generate_ephemeral_address(&hex_seed3).unwrap();
464
465        // Same inputs should produce same address
466        assert_eq!(addr1, addr2);
467
468        // Different seeds should produce different addresses
469        assert_ne!(addr1, addr3);
470
471        // All addresses should be ta... format
472        assert!(addr1.starts_with("ta"));
473        assert!(addr2.starts_with("ta"));
474        assert!(addr3.starts_with("ta"));
475
476        // All addresses should be 46 characters
477        assert_eq!(addr1.len(), 46);
478        assert_eq!(addr2.len(), 46);
479        assert_eq!(addr3.len(), 46);
480    }
481}
482
483/// Uploader program instruction discriminants
484pub const TN_UPLOADER_PROGRAM_INSTRUCTION_CREATE: u32 = 0x00;
485pub const TN_UPLOADER_PROGRAM_INSTRUCTION_WRITE: u32 = 0x01;
486pub const TN_UPLOADER_PROGRAM_INSTRUCTION_DESTROY: u32 = 0x02;
487pub const TN_UPLOADER_PROGRAM_INSTRUCTION_FINALIZE: u32 = 0x03;
488
489/// Uploader program CREATE instruction arguments (matches C struct)
490#[repr(C, packed)]
491#[derive(Debug, Clone, Copy)]
492pub struct UploaderCreateArgs {
493    pub buffer_account_idx: u16,
494    pub meta_account_idx: u16,
495    pub authority_account_idx: u16,
496    pub buffer_account_sz: u32,
497    pub expected_account_hash: [u8; 32],
498    pub seed_len: u32,
499    // seed bytes follow
500}
501
502/// Uploader program WRITE instruction arguments (matches C struct)
503#[repr(C, packed)]
504#[derive(Debug, Clone, Copy)]
505pub struct UploaderWriteArgs {
506    pub buffer_account_idx: u16,
507    pub meta_account_idx: u16,
508    pub data_len: u32,
509    pub data_offset: u32,
510    // data bytes follow
511}
512
513/// Uploader program FINALIZE instruction arguments (matches C struct)
514#[repr(C, packed)]
515#[derive(Debug, Clone, Copy)]
516pub struct UploaderFinalizeArgs {
517    pub buffer_account_idx: u16,
518    pub meta_account_idx: u16,
519    pub expected_account_hash: [u8; 32],
520}
521
522/// Uploader program DESTROY instruction arguments (matches C struct)
523#[repr(C, packed)]
524#[derive(Debug, Clone, Copy)]
525pub struct UploaderDestroyArgs {
526    pub buffer_account_idx: u16,
527    pub meta_account_idx: u16,
528}
529
530/// Manager program instruction discriminants (matches C defines)
531pub const MANAGER_INSTRUCTION_CREATE_PERMANENT: u8 = 0x00;
532pub const MANAGER_INSTRUCTION_CREATE_EPHEMERAL: u8 = 0x01;
533pub const MANAGER_INSTRUCTION_UPGRADE: u8 = 0x02;
534pub const MANAGER_INSTRUCTION_SET_PAUSE: u8 = 0x03;
535pub const MANAGER_INSTRUCTION_DESTROY: u8 = 0x04;
536pub const MANAGER_INSTRUCTION_FINALIZE: u8 = 0x05;
537pub const MANAGER_INSTRUCTION_SET_AUTHORITY: u8 = 0x06;
538pub const MANAGER_INSTRUCTION_CLAIM_AUTHORITY: u8 = 0x07;
539
540/// Manager program header arguments (matches C struct)
541#[repr(C, packed)]
542#[derive(Debug, Clone, Copy)]
543pub struct ManagerHeaderArgs {
544    pub discriminant: u8,
545    pub meta_account_idx: u16,
546    pub program_account_idx: u16,
547}
548
549/// Manager program CREATE instruction arguments (matches C struct)
550#[repr(C, packed)]
551#[derive(Debug, Clone, Copy)]
552pub struct ManagerCreateArgs {
553    pub discriminant: u8,
554    pub meta_account_idx: u16,
555    pub program_account_idx: u16,
556    pub srcbuf_account_idx: u16,
557    pub srcbuf_offset: u32,
558    pub srcbuf_size: u32,
559    pub authority_account_idx: u16,
560    pub seed_len: u32,
561    // seed bytes and proof bytes follow
562}
563
564/// Manager program UPGRADE instruction arguments (matches C struct)
565#[repr(C, packed)]
566#[derive(Debug, Clone, Copy)]
567pub struct ManagerUpgradeArgs {
568    pub discriminant: u8,
569    pub meta_account_idx: u16,
570    pub program_account_idx: u16,
571    pub srcbuf_account_idx: u16,
572    pub srcbuf_offset: u32,
573    pub srcbuf_size: u32,
574}
575
576/// Manager program SET_PAUSE instruction arguments (matches C struct)
577#[repr(C, packed)]
578#[derive(Debug, Clone, Copy)]
579pub struct ManagerSetPauseArgs {
580    pub discriminant: u8,
581    pub meta_account_idx: u16,
582    pub program_account_idx: u16,
583    pub is_paused: u8,
584}
585
586/// Manager program SET_AUTHORITY instruction arguments (matches C struct)
587#[repr(C, packed)]
588#[derive(Debug, Clone, Copy)]
589pub struct ManagerSetAuthorityArgs {
590    pub discriminant: u8,
591    pub meta_account_idx: u16,
592    pub program_account_idx: u16,
593    pub authority_candidate: [u8; 32],
594}
595
596/// Test uploader program instruction discriminants (matches C defines)
597pub const TN_TEST_UPLOADER_PROGRAM_DISCRIMINANT_CREATE: u8 = 0x00;
598pub const TN_TEST_UPLOADER_PROGRAM_DISCRIMINANT_WRITE: u8 = 0x01;
599
600/// Test uploader program CREATE instruction arguments (matches C struct)
601#[repr(C, packed)]
602#[derive(Debug, Clone, Copy)]
603pub struct TestUploaderCreateArgs {
604    pub account_idx: u16,
605    pub is_ephemeral: u8,
606    pub account_sz: u32,
607    pub seed_len: u32,
608    // seed bytes follow, then optional state proof
609}
610
611/// Test uploader program WRITE instruction arguments (matches C struct)
612#[repr(C, packed)]
613#[derive(Debug, Clone, Copy)]
614pub struct TestUploaderWriteArgs {
615    pub target_account_idx: u16,
616    pub target_offset: u32,
617    pub data_len: u32,
618    // data bytes follow
619}
620
621/// System program DECOMPRESS2 instruction arguments (matches C struct)
622#[repr(C, packed)]
623#[derive(Debug, Clone, Copy)]
624pub struct SystemProgramDecompress2Args {
625    pub target_account_idx: u16,
626    pub meta_account_idx: u16,
627    pub data_account_idx: u16,
628    pub data_offset: u32,
629}
630
631impl TransactionBuilder {
632    /// Build uploader program CREATE transaction
633    pub fn build_uploader_create(
634        fee_payer: TnPubkey,
635        uploader_program: TnPubkey,
636        meta_account: TnPubkey,
637        buffer_account: TnPubkey,
638        buffer_size: u32,
639        expected_hash: [u8; 32],
640        seed: &[u8],
641        fee: u64,
642        nonce: u64,
643        start_slot: u64,
644    ) -> Result<Transaction> {
645        // Account layout: [0: fee_payer, 1: uploader_program, 2: meta_account, 3: buffer_account]
646        let authority_account_idx = 0u16;
647
648        let mut tx = Transaction::new(fee_payer, uploader_program, fee, nonce)
649            .with_start_slot(start_slot)
650            .with_expiry_after(10)
651            .with_compute_units(50_000 + 2 * buffer_size as u32)
652            .with_memory_units(10_000)
653            .with_state_units(10_000);
654
655        let mut meta_account_idx = 2u16;
656        let mut buffer_account_idx = 3u16;
657        if meta_account > buffer_account {
658            meta_account_idx = 3u16;
659            buffer_account_idx = 2u16;
660            tx = tx
661                .add_rw_account(buffer_account)
662                .add_rw_account(meta_account)
663        } else {
664            tx = tx
665                .add_rw_account(meta_account)
666                .add_rw_account(buffer_account)
667        }
668
669        let instruction_data = build_uploader_create_instruction(
670            buffer_account_idx,
671            meta_account_idx,
672            authority_account_idx,
673            buffer_size,
674            expected_hash,
675            seed,
676        )?;
677
678        tx = tx.with_instructions(instruction_data);
679
680        Ok(tx)
681    }
682
683    /// Build uploader program WRITE transaction
684    pub fn build_uploader_write(
685        fee_payer: TnPubkey,
686        uploader_program: TnPubkey,
687        meta_account: TnPubkey,
688        buffer_account: TnPubkey,
689        data: &[u8],
690        offset: u32,
691        fee: u64,
692        nonce: u64,
693        start_slot: u64,
694    ) -> Result<Transaction> {
695        // Account layout: [0: fee_payer, 1: uploader_program, 2: meta_account, 3: buffer_account]
696        let mut tx = Transaction::new(fee_payer, uploader_program, fee, nonce)
697            .with_start_slot(start_slot)
698            .with_expiry_after(10000)
699            .with_compute_units(500_000_000)
700            .with_memory_units(5000)
701            .with_state_units(5000);
702
703        let mut meta_account_idx = 2u16;
704        let mut buffer_account_idx = 3u16;
705        if meta_account > buffer_account {
706            meta_account_idx = 3u16;
707            buffer_account_idx = 2u16;
708            tx = tx
709                .add_rw_account(buffer_account)
710                .add_rw_account(meta_account)
711        } else {
712            tx = tx
713                .add_rw_account(meta_account)
714                .add_rw_account(buffer_account)
715        }
716
717        let instruction_data =
718            build_uploader_write_instruction(buffer_account_idx, meta_account_idx, data, offset)?;
719
720        tx = tx.with_instructions(instruction_data);
721
722        Ok(tx)
723    }
724
725    /// Build uploader program FINALIZE transaction
726    pub fn build_uploader_finalize(
727        fee_payer: TnPubkey,
728        uploader_program: TnPubkey,
729        meta_account: TnPubkey,
730        buffer_account: TnPubkey,
731        buffer_size: u32,
732        expected_hash: [u8; 32],
733        fee: u64,
734        nonce: u64,
735        start_slot: u64,
736    ) -> Result<Transaction> {
737        let mut tx = Transaction::new(fee_payer, uploader_program, fee, nonce)
738            .with_start_slot(start_slot)
739            .with_expiry_after(10000)
740            .with_compute_units(50_000 + 180 * buffer_size as u32)
741            .with_memory_units(5000)
742            .with_state_units(5000);
743
744        // Account layout: [0: fee_payer, 1: uploader_program, 2: meta_account, 3: buffer_account]
745        let mut meta_account_idx = 2u16;
746        let mut buffer_account_idx = 3u16;
747        if meta_account > buffer_account {
748            meta_account_idx = 3u16;
749            buffer_account_idx = 2u16;
750            tx = tx
751                .add_rw_account(buffer_account)
752                .add_rw_account(meta_account)
753        } else {
754            tx = tx
755                .add_rw_account(meta_account)
756                .add_rw_account(buffer_account)
757        }
758
759        let instruction_data = build_uploader_finalize_instruction(
760            buffer_account_idx,
761            meta_account_idx,
762            expected_hash,
763        )?;
764
765        tx = tx.with_instructions(instruction_data);
766
767        Ok(tx)
768    }
769
770    /// Build uploader program DESTROY transaction
771    pub fn build_uploader_destroy(
772        fee_payer: TnPubkey,
773        uploader_program: TnPubkey,
774        meta_account: TnPubkey,
775        buffer_account: TnPubkey,
776        fee: u64,
777        nonce: u64,
778        start_slot: u64,
779    ) -> Result<Transaction> {
780        let mut tx = Transaction::new(fee_payer, uploader_program, fee, nonce)
781            .with_start_slot(start_slot)
782            .with_expiry_after(10000)
783            .with_compute_units(50000)
784            .with_memory_units(5000)
785            .with_state_units(5000);
786
787        // Account layout: [0: fee_payer, 1: uploader_program, 2: meta_account, 3: buffer_account]
788        let mut meta_account_idx = 2u16;
789        let mut buffer_account_idx = 3u16;
790        if meta_account > buffer_account {
791            meta_account_idx = 3u16;
792            buffer_account_idx = 2u16;
793            tx = tx
794                .add_rw_account(buffer_account)
795                .add_rw_account(meta_account)
796        } else {
797            tx = tx
798                .add_rw_account(meta_account)
799                .add_rw_account(buffer_account)
800        }
801
802        let instruction_data =
803            build_uploader_destroy_instruction(buffer_account_idx, meta_account_idx)?;
804
805        tx = tx.with_instructions(instruction_data);
806        Ok(tx)
807    }
808
809    /// Build manager program CREATE transaction
810    pub fn build_manager_create(
811        fee_payer: TnPubkey,
812        manager_program: TnPubkey,
813        meta_account: TnPubkey,
814        program_account: TnPubkey,
815        srcbuf_account: TnPubkey,
816        authority_account: TnPubkey,
817        srcbuf_offset: u32,
818        srcbuf_size: u32,
819        seed: &[u8],
820        is_ephemeral: bool,
821        meta_proof: Option<&[u8]>,
822        program_proof: Option<&[u8]>,
823        fee: u64,
824        nonce: u64,
825        start_slot: u64,
826    ) -> Result<Transaction> {
827        let mut tx = Transaction::new(fee_payer, manager_program, fee, nonce)
828            .with_start_slot(start_slot)
829            .with_expiry_after(10000)
830            .with_compute_units(500_000_000)
831            .with_memory_units(5000)
832            .with_state_units(5000);
833
834        // Check if authority_account is the same as fee_payer
835        let authority_is_fee_payer = authority_account == fee_payer;
836
837        // Separate accounts by access type and sort each group by pubkey
838        let mut rw_accounts = vec![(meta_account, "meta"), (program_account, "program")];
839
840        let mut r_accounts = vec![(srcbuf_account, "srcbuf")];
841
842        // Only add authority_account if it's different from fee_payer
843        if !authority_is_fee_payer {
844            r_accounts.push((authority_account, "authority"));
845        }
846
847        // Sort read-write accounts by pubkey
848        rw_accounts.sort_by(|a, b| a.0.cmp(&b.0));
849
850        // Sort read-only accounts by pubkey
851        r_accounts.sort_by(|a, b| a.0.cmp(&b.0));
852
853        // Combine sorted accounts: read-write first, then read-only
854        let mut accounts = rw_accounts;
855        accounts.extend(r_accounts);
856
857        let mut meta_account_idx = 0u16;
858        let mut program_account_idx = 0u16;
859        let mut srcbuf_account_idx = 0u16;
860        let mut authority_account_idx = if authority_is_fee_payer {
861            0u16 // Use fee_payer index (0) when authority is the same as fee_payer
862        } else {
863            0u16 // Will be set in the loop below
864        };
865
866        for (i, (account, account_type)) in accounts.iter().enumerate() {
867            let idx = (i + 2) as u16; // Skip fee_payer (0) and program (1)
868            match *account_type {
869                "meta" => {
870                    meta_account_idx = idx;
871                    tx = tx.add_rw_account(*account);
872                }
873                "program" => {
874                    program_account_idx = idx;
875                    tx = tx.add_rw_account(*account);
876                }
877                "srcbuf" => {
878                    srcbuf_account_idx = idx;
879                    tx = tx.add_r_account(*account);
880                }
881                "authority" => {
882                    authority_account_idx = idx;
883                    tx = tx.add_r_account(*account);
884                }
885                _ => unreachable!(),
886            }
887        }
888
889        let discriminant = if is_ephemeral {
890            MANAGER_INSTRUCTION_CREATE_EPHEMERAL
891        } else {
892            MANAGER_INSTRUCTION_CREATE_PERMANENT
893        };
894
895        // Concatenate proofs if both are provided (for permanent programs)
896        let combined_proof = if let (Some(meta), Some(program)) = (meta_proof, program_proof) {
897            let mut combined = Vec::with_capacity(meta.len() + program.len());
898            combined.extend_from_slice(meta);
899            combined.extend_from_slice(program);
900            Some(combined)
901        } else {
902            None
903        };
904
905        let instruction_data = build_manager_create_instruction(
906            discriminant,
907            meta_account_idx,
908            program_account_idx,
909            srcbuf_account_idx,
910            authority_account_idx,
911            srcbuf_offset,
912            srcbuf_size,
913            seed,
914            combined_proof.as_deref(),
915        )?;
916
917        tx = tx.with_instructions(instruction_data);
918        Ok(tx)
919    }
920
921    /// Build manager program UPGRADE transaction
922    pub fn build_manager_upgrade(
923        fee_payer: TnPubkey,
924        manager_program: TnPubkey,
925        meta_account: TnPubkey,
926        program_account: TnPubkey,
927        srcbuf_account: TnPubkey,
928        srcbuf_offset: u32,
929        srcbuf_size: u32,
930        fee: u64,
931        nonce: u64,
932        start_slot: u64,
933    ) -> Result<Transaction> {
934        let mut tx = Transaction::new(fee_payer, manager_program, fee, nonce)
935            .with_start_slot(start_slot)
936            .with_expiry_after(10000)
937            .with_compute_units(500_000_000)
938            .with_memory_units(5000)
939            .with_state_units(5000);
940
941        // Separate accounts by access type and sort each group by pubkey
942        let mut rw_accounts = vec![(meta_account, "meta"), (program_account, "program")];
943
944        let mut r_accounts = vec![(srcbuf_account, "srcbuf")];
945
946        // Sort read-write accounts by pubkey
947        rw_accounts.sort_by(|a, b| a.0.cmp(&b.0));
948
949        // Sort read-only accounts by pubkey
950        r_accounts.sort_by(|a, b| a.0.cmp(&b.0));
951
952        // Combine sorted accounts: read-write first, then read-only
953        let mut accounts = rw_accounts;
954        accounts.extend(r_accounts);
955
956        let mut meta_account_idx = 0u16;
957        let mut program_account_idx = 0u16;
958        let mut srcbuf_account_idx = 0u16;
959
960        for (i, (account, account_type)) in accounts.iter().enumerate() {
961            let idx = (i + 2) as u16; // Skip fee_payer (0) and program (1)
962            match *account_type {
963                "meta" => {
964                    meta_account_idx = idx;
965                    tx = tx.add_rw_account(*account);
966                }
967                "program" => {
968                    program_account_idx = idx;
969                    tx = tx.add_rw_account(*account);
970                }
971                "srcbuf" => {
972                    srcbuf_account_idx = idx;
973                    tx = tx.add_r_account(*account);
974                }
975                _ => unreachable!(),
976            }
977        }
978
979        let instruction_data = build_manager_upgrade_instruction(
980            meta_account_idx,
981            program_account_idx,
982            srcbuf_account_idx,
983            srcbuf_offset,
984            srcbuf_size,
985        )?;
986
987        tx = tx.with_instructions(instruction_data);
988        Ok(tx)
989    }
990
991    /// Build manager program SET_PAUSE transaction
992    pub fn build_manager_set_pause(
993        fee_payer: TnPubkey,
994        manager_program: TnPubkey,
995        meta_account: TnPubkey,
996        program_account: TnPubkey,
997        is_paused: bool,
998        fee: u64,
999        nonce: u64,
1000        start_slot: u64,
1001    ) -> Result<Transaction> {
1002        let mut tx = Transaction::new(fee_payer, manager_program, fee, nonce)
1003            .with_start_slot(start_slot)
1004            .with_expiry_after(10000)
1005            .with_compute_units(100_000_000)
1006            .with_memory_units(5000)
1007            .with_state_units(5000);
1008
1009        // Add accounts in sorted order
1010        let mut accounts = vec![(meta_account, "meta"), (program_account, "program")];
1011        accounts.sort_by(|a, b| a.0.cmp(&b.0));
1012
1013        let mut meta_account_idx = 0u16;
1014        let mut program_account_idx = 0u16;
1015
1016        for (i, (account, account_type)) in accounts.iter().enumerate() {
1017            let idx = (i + 2) as u16;
1018            match *account_type {
1019                "meta" => {
1020                    meta_account_idx = idx;
1021                    tx = tx.add_rw_account(*account);
1022                }
1023                "program" => {
1024                    program_account_idx = idx;
1025                    tx = tx.add_rw_account(*account);
1026                }
1027                _ => unreachable!(),
1028            }
1029        }
1030
1031        let instruction_data =
1032            build_manager_set_pause_instruction(meta_account_idx, program_account_idx, is_paused)?;
1033
1034        tx = tx.with_instructions(instruction_data);
1035        Ok(tx)
1036    }
1037
1038    /// Build manager program simple transactions (DESTROY, FINALIZE, CLAIM_AUTHORITY)
1039    pub fn build_manager_simple(
1040        fee_payer: TnPubkey,
1041        manager_program: TnPubkey,
1042        meta_account: TnPubkey,
1043        program_account: TnPubkey,
1044        instruction_type: u8,
1045        fee: u64,
1046        nonce: u64,
1047        start_slot: u64,
1048    ) -> Result<Transaction> {
1049        let mut tx = Transaction::new(fee_payer, manager_program, fee, nonce)
1050            .with_start_slot(start_slot)
1051            .with_expiry_after(10000)
1052            .with_compute_units(100_000_000)
1053            .with_memory_units(5000)
1054            .with_state_units(5000);
1055
1056        // Add accounts in sorted order
1057        let mut accounts = vec![(meta_account, "meta"), (program_account, "program")];
1058        accounts.sort_by(|a, b| a.0.cmp(&b.0));
1059
1060        let mut meta_account_idx = 0u16;
1061        let mut program_account_idx = 0u16;
1062
1063        for (i, (account, account_type)) in accounts.iter().enumerate() {
1064            let idx = (i + 2) as u16;
1065            match *account_type {
1066                "meta" => {
1067                    meta_account_idx = idx;
1068                    tx = tx.add_rw_account(*account);
1069                }
1070                "program" => {
1071                    program_account_idx = idx;
1072                    tx = tx.add_rw_account(*account);
1073                }
1074                _ => unreachable!(),
1075            }
1076        }
1077
1078        let instruction_data = build_manager_header_instruction(
1079            instruction_type,
1080            meta_account_idx,
1081            program_account_idx,
1082        )?;
1083
1084        tx = tx.with_instructions(instruction_data);
1085        Ok(tx)
1086    }
1087
1088    /// Build manager program SET_AUTHORITY transaction
1089    pub fn build_manager_set_authority(
1090        fee_payer: TnPubkey,
1091        manager_program: TnPubkey,
1092        meta_account: TnPubkey,
1093        program_account: TnPubkey,
1094        authority_candidate: [u8; 32],
1095        fee: u64,
1096        nonce: u64,
1097        start_slot: u64,
1098    ) -> Result<Transaction> {
1099        let mut tx = Transaction::new(fee_payer, manager_program, fee, nonce)
1100            .with_start_slot(start_slot)
1101            .with_expiry_after(10000)
1102            .with_compute_units(100_000_000)
1103            .with_memory_units(5000)
1104            .with_state_units(5000);
1105
1106        // Add accounts in sorted order
1107        let mut accounts = vec![(meta_account, "meta"), (program_account, "program")];
1108        accounts.sort_by(|a, b| a.0.cmp(&b.0));
1109
1110        let mut meta_account_idx = 0u16;
1111        let mut program_account_idx = 0u16;
1112
1113        for (i, (account, account_type)) in accounts.iter().enumerate() {
1114            let idx = (i + 2) as u16;
1115            match *account_type {
1116                "meta" => {
1117                    meta_account_idx = idx;
1118                    tx = tx.add_rw_account(*account);
1119                }
1120                "program" => {
1121                    program_account_idx = idx;
1122                    tx = tx.add_rw_account(*account);
1123                }
1124                _ => unreachable!(),
1125            }
1126        }
1127
1128        let instruction_data = build_manager_set_authority_instruction(
1129            meta_account_idx,
1130            program_account_idx,
1131            authority_candidate,
1132        )?;
1133
1134        tx = tx.with_instructions(instruction_data);
1135        Ok(tx)
1136    }
1137
1138    /// Build test uploader program CREATE transaction
1139    pub fn build_test_uploader_create(
1140        fee_payer: TnPubkey,
1141        test_uploader_program: TnPubkey,
1142        target_account: TnPubkey,
1143        account_sz: u32,
1144        seed: &[u8],
1145        is_ephemeral: bool,
1146        state_proof: Option<&[u8]>,
1147        fee: u64,
1148        nonce: u64,
1149        start_slot: u64,
1150    ) -> Result<Transaction> {
1151        // Account layout: [0: fee_payer, 1: test_uploader_program, 2: target_account]
1152        let target_account_idx = 2u16;
1153
1154        let tx = Transaction::new(fee_payer, test_uploader_program, fee, nonce)
1155            .with_start_slot(start_slot)
1156            .with_expiry_after(100)
1157            .with_compute_units(100_000 + account_sz)
1158            .with_memory_units(10_000)
1159            .with_state_units(10_000)
1160            .add_rw_account(target_account);
1161
1162        let instruction_data = build_test_uploader_create_instruction(
1163            target_account_idx,
1164            account_sz,
1165            seed,
1166            is_ephemeral,
1167            state_proof,
1168        )?;
1169
1170        let tx = tx.with_instructions(instruction_data);
1171        Ok(tx)
1172    }
1173
1174    /// Build test uploader program WRITE transaction
1175    pub fn build_test_uploader_write(
1176        fee_payer: TnPubkey,
1177        test_uploader_program: TnPubkey,
1178        target_account: TnPubkey,
1179        offset: u32,
1180        data: &[u8],
1181        fee: u64,
1182        nonce: u64,
1183        start_slot: u64,
1184    ) -> Result<Transaction> {
1185        // Account layout: [0: fee_payer, 1: test_uploader_program, 2: target_account]
1186        let target_account_idx = 2u16;
1187
1188        let tx = Transaction::new(fee_payer, test_uploader_program, fee, nonce)
1189            .with_start_slot(start_slot)
1190            .with_expiry_after(10_000)
1191            .with_compute_units(100_000 + 18 * data.len() as u32)
1192            .with_memory_units(10_000)
1193            .with_state_units(10_000)
1194            .add_rw_account(target_account);
1195
1196        let instruction_data =
1197            build_test_uploader_write_instruction(target_account_idx, offset, data)?;
1198
1199        let tx = tx.with_instructions(instruction_data);
1200        Ok(tx)
1201    }
1202
1203    /// Build account decompression transaction using DECOMPRESS2 (separate meta and data accounts)
1204    pub fn build_decompress2(
1205        fee_payer: TnPubkey,
1206        program: TnPubkey,
1207        target_account: TnPubkey,
1208        meta_account: TnPubkey,
1209        data_account: TnPubkey,
1210        data_offset: u32,
1211        state_proof: &[u8],
1212        fee: u64,
1213        nonce: u64,
1214        start_slot: u64,
1215        data_sz: u32,
1216    ) -> Result<Transaction> {
1217        // Account layout: [0: fee_payer, 1: program, 2: target_account, 3+: meta/data accounts]
1218        let mut tx = Transaction::new(fee_payer, program, fee, nonce)
1219            .with_start_slot(start_slot)
1220            .with_expiry_after(100)
1221            .with_compute_units(10_000 + 2 * data_sz)
1222            .with_memory_units(10_000)
1223            .with_state_units(10);
1224
1225        // Add target account (read-write)
1226        let target_account_idx = 2u16;
1227        tx = tx.add_rw_account(target_account);
1228
1229        let mut meta_account_idx = 0u16;
1230        let mut data_account_idx = 0u16;
1231
1232        // Handle meta and data accounts - if they're the same, add only once; if different, add both and sort
1233        if meta_account == data_account {
1234            // Same account for both meta and data
1235            let account_idx = 3u16;
1236            meta_account_idx = account_idx;
1237            data_account_idx = account_idx;
1238            tx = tx.add_r_account(meta_account);
1239        } else {
1240            // Different accounts - add both and sort by pubkey
1241            let mut read_accounts = vec![(meta_account, "meta"), (data_account, "data")];
1242            read_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1243
1244            for (i, (account, account_type)) in read_accounts.iter().enumerate() {
1245                let idx = (3 + i) as u16; // Start from index 3
1246                match *account_type {
1247                    "meta" => {
1248                        meta_account_idx = idx;
1249                        tx = tx.add_r_account(*account);
1250                    }
1251                    "data" => {
1252                        data_account_idx = idx;
1253                        tx = tx.add_r_account(*account);
1254                    }
1255                    _ => unreachable!(),
1256                }
1257            }
1258        }
1259
1260        let instruction_data = build_decompress2_instruction(
1261            target_account_idx,
1262            meta_account_idx,
1263            data_account_idx,
1264            data_offset,
1265            state_proof,
1266        )?;
1267
1268        tx = tx.with_instructions(instruction_data);
1269        Ok(tx)
1270    }
1271}
1272
1273/// Build uploader CREATE instruction data
1274fn build_uploader_create_instruction(
1275    buffer_account_idx: u16,
1276    meta_account_idx: u16,
1277    authority_account_idx: u16,
1278    buffer_size: u32,
1279    expected_hash: [u8; 32],
1280    seed: &[u8],
1281) -> Result<Vec<u8>> {
1282    let mut instruction = Vec::new();
1283
1284    // Discriminant (4 bytes, little-endian)
1285    instruction.extend_from_slice(&TN_UPLOADER_PROGRAM_INSTRUCTION_CREATE.to_le_bytes());
1286
1287    // Create args struct
1288    let args = UploaderCreateArgs {
1289        buffer_account_idx,
1290        meta_account_idx,
1291        authority_account_idx,
1292        buffer_account_sz: buffer_size,
1293        expected_account_hash: expected_hash,
1294        seed_len: seed.len() as u32,
1295    };
1296
1297    // Serialize args (unsafe due to packed struct)
1298    let args_bytes = unsafe {
1299        std::slice::from_raw_parts(
1300            &args as *const _ as *const u8,
1301            std::mem::size_of::<UploaderCreateArgs>(),
1302        )
1303    };
1304    instruction.extend_from_slice(args_bytes);
1305
1306    // Append seed bytes
1307    instruction.extend_from_slice(seed);
1308
1309    Ok(instruction)
1310}
1311
1312/// Build uploader WRITE instruction data
1313fn build_uploader_write_instruction(
1314    buffer_account_idx: u16,
1315    meta_account_idx: u16,
1316    data: &[u8],
1317    offset: u32,
1318) -> Result<Vec<u8>> {
1319    let mut instruction = Vec::new();
1320
1321    // Discriminant (4 bytes, little-endian)
1322    instruction.extend_from_slice(&TN_UPLOADER_PROGRAM_INSTRUCTION_WRITE.to_le_bytes());
1323
1324    // Write args
1325    let args = UploaderWriteArgs {
1326        buffer_account_idx,
1327        meta_account_idx,
1328        data_len: data.len() as u32,
1329        data_offset: offset,
1330    };
1331
1332    // Serialize args
1333    let args_bytes = unsafe {
1334        std::slice::from_raw_parts(
1335            &args as *const _ as *const u8,
1336            std::mem::size_of::<UploaderWriteArgs>(),
1337        )
1338    };
1339    instruction.extend_from_slice(args_bytes);
1340
1341    // Append data
1342    instruction.extend_from_slice(data);
1343
1344    Ok(instruction)
1345}
1346
1347/// Build uploader FINALIZE instruction data
1348fn build_uploader_finalize_instruction(
1349    buffer_account_idx: u16,
1350    meta_account_idx: u16,
1351    expected_hash: [u8; 32],
1352) -> Result<Vec<u8>> {
1353    let mut instruction = Vec::new();
1354
1355    // Discriminant (4 bytes, little-endian)
1356    instruction.extend_from_slice(&TN_UPLOADER_PROGRAM_INSTRUCTION_FINALIZE.to_le_bytes());
1357
1358    // Finalize args
1359    let args = UploaderFinalizeArgs {
1360        buffer_account_idx,
1361        meta_account_idx,
1362        expected_account_hash: expected_hash,
1363    };
1364
1365    // Serialize args
1366    let args_bytes = unsafe {
1367        std::slice::from_raw_parts(
1368            &args as *const _ as *const u8,
1369            std::mem::size_of::<UploaderFinalizeArgs>(),
1370        )
1371    };
1372    instruction.extend_from_slice(args_bytes);
1373
1374    Ok(instruction)
1375}
1376
1377/// Build uploader DESTROY instruction data
1378fn build_uploader_destroy_instruction(
1379    buffer_account_idx: u16,
1380    meta_account_idx: u16,
1381) -> Result<Vec<u8>> {
1382    let mut instruction = Vec::new();
1383
1384    // Discriminant (4 bytes, little-endian)
1385    instruction.extend_from_slice(&TN_UPLOADER_PROGRAM_INSTRUCTION_DESTROY.to_le_bytes());
1386
1387    // Destroy args
1388    let args = UploaderDestroyArgs {
1389        buffer_account_idx,
1390        meta_account_idx,
1391    };
1392
1393    // Serialize args
1394    let args_bytes = unsafe {
1395        std::slice::from_raw_parts(
1396            &args as *const _ as *const u8,
1397            std::mem::size_of::<UploaderDestroyArgs>(),
1398        )
1399    };
1400    instruction.extend_from_slice(args_bytes);
1401
1402    Ok(instruction)
1403}
1404
1405/// Build manager CREATE instruction data
1406fn build_manager_create_instruction(
1407    discriminant: u8,
1408    meta_account_idx: u16,
1409    program_account_idx: u16,
1410    srcbuf_account_idx: u16,
1411    authority_account_idx: u16,
1412    srcbuf_offset: u32,
1413    srcbuf_size: u32,
1414    seed: &[u8],
1415    proof: Option<&[u8]>,
1416) -> Result<Vec<u8>> {
1417    let mut instruction = Vec::new();
1418
1419    // Create args
1420    let args = ManagerCreateArgs {
1421        discriminant,
1422        meta_account_idx,
1423        program_account_idx,
1424        srcbuf_account_idx,
1425        srcbuf_offset,
1426        srcbuf_size,
1427        authority_account_idx,
1428        seed_len: seed.len() as u32,
1429    };
1430
1431    // Serialize args
1432    let args_bytes = unsafe {
1433        std::slice::from_raw_parts(
1434            &args as *const ManagerCreateArgs as *const u8,
1435            std::mem::size_of::<ManagerCreateArgs>(),
1436        )
1437    };
1438    instruction.extend_from_slice(args_bytes);
1439
1440    // Add seed bytes
1441    instruction.extend_from_slice(seed);
1442
1443    // Add proof bytes (only for permanent accounts)
1444    if let Some(proof_bytes) = proof {
1445        instruction.extend_from_slice(proof_bytes);
1446    }
1447
1448    Ok(instruction)
1449}
1450
1451/// Build manager UPGRADE instruction data
1452fn build_manager_upgrade_instruction(
1453    meta_account_idx: u16,
1454    program_account_idx: u16,
1455    srcbuf_account_idx: u16,
1456    srcbuf_offset: u32,
1457    srcbuf_size: u32,
1458) -> Result<Vec<u8>> {
1459    let mut instruction = Vec::new();
1460
1461    let args = ManagerUpgradeArgs {
1462        discriminant: MANAGER_INSTRUCTION_UPGRADE,
1463        meta_account_idx,
1464        program_account_idx,
1465        srcbuf_account_idx,
1466        srcbuf_offset,
1467        srcbuf_size,
1468    };
1469
1470    let args_bytes = unsafe {
1471        std::slice::from_raw_parts(
1472            &args as *const ManagerUpgradeArgs as *const u8,
1473            std::mem::size_of::<ManagerUpgradeArgs>(),
1474        )
1475    };
1476    instruction.extend_from_slice(args_bytes);
1477
1478    Ok(instruction)
1479}
1480
1481/// Build manager SET_PAUSE instruction data
1482fn build_manager_set_pause_instruction(
1483    meta_account_idx: u16,
1484    program_account_idx: u16,
1485    is_paused: bool,
1486) -> Result<Vec<u8>> {
1487    let mut instruction = Vec::new();
1488
1489    let args = ManagerSetPauseArgs {
1490        discriminant: MANAGER_INSTRUCTION_SET_PAUSE,
1491        meta_account_idx,
1492        program_account_idx,
1493        is_paused: if is_paused { 1 } else { 0 },
1494    };
1495
1496    let args_bytes = unsafe {
1497        std::slice::from_raw_parts(
1498            &args as *const ManagerSetPauseArgs as *const u8,
1499            std::mem::size_of::<ManagerSetPauseArgs>(),
1500        )
1501    };
1502    instruction.extend_from_slice(args_bytes);
1503
1504    Ok(instruction)
1505}
1506
1507/// Build manager header-only instruction data (DESTROY, FINALIZE, CLAIM_AUTHORITY)
1508fn build_manager_header_instruction(
1509    discriminant: u8,
1510    meta_account_idx: u16,
1511    program_account_idx: u16,
1512) -> Result<Vec<u8>> {
1513    let mut instruction = Vec::new();
1514
1515    let args = ManagerHeaderArgs {
1516        discriminant,
1517        meta_account_idx,
1518        program_account_idx,
1519    };
1520
1521    let args_bytes = unsafe {
1522        std::slice::from_raw_parts(
1523            &args as *const ManagerHeaderArgs as *const u8,
1524            std::mem::size_of::<ManagerHeaderArgs>(),
1525        )
1526    };
1527    instruction.extend_from_slice(args_bytes);
1528
1529    Ok(instruction)
1530}
1531
1532/// Build manager SET_AUTHORITY instruction data
1533fn build_manager_set_authority_instruction(
1534    meta_account_idx: u16,
1535    program_account_idx: u16,
1536    authority_candidate: [u8; 32],
1537) -> Result<Vec<u8>> {
1538    let mut instruction = Vec::new();
1539
1540    let args = ManagerSetAuthorityArgs {
1541        discriminant: MANAGER_INSTRUCTION_SET_AUTHORITY,
1542        meta_account_idx,
1543        program_account_idx,
1544        authority_candidate,
1545    };
1546
1547    let args_bytes = unsafe {
1548        std::slice::from_raw_parts(
1549            &args as *const ManagerSetAuthorityArgs as *const u8,
1550            std::mem::size_of::<ManagerSetAuthorityArgs>(),
1551        )
1552    };
1553    instruction.extend_from_slice(args_bytes);
1554
1555    Ok(instruction)
1556}
1557
1558/// Build test uploader CREATE instruction data
1559fn build_test_uploader_create_instruction(
1560    account_idx: u16,
1561    account_sz: u32,
1562    seed: &[u8],
1563    is_ephemeral: bool,
1564    state_proof: Option<&[u8]>,
1565) -> Result<Vec<u8>> {
1566    let mut instruction = Vec::new();
1567
1568    // Discriminant (1 byte)
1569    instruction.push(TN_TEST_UPLOADER_PROGRAM_DISCRIMINANT_CREATE);
1570
1571    // Create args struct
1572    let args = TestUploaderCreateArgs {
1573        account_idx,
1574        is_ephemeral: if is_ephemeral { 1u8 } else { 0u8 },
1575        account_sz,
1576        seed_len: seed.len() as u32,
1577    };
1578
1579    // Serialize args (unsafe due to packed struct)
1580    let args_bytes = unsafe {
1581        std::slice::from_raw_parts(
1582            &args as *const _ as *const u8,
1583            std::mem::size_of::<TestUploaderCreateArgs>(),
1584        )
1585    };
1586    instruction.extend_from_slice(args_bytes);
1587
1588    // Append seed bytes
1589    instruction.extend_from_slice(seed);
1590
1591    // Append state proof if provided (for non-ephemeral accounts)
1592    if let Some(proof) = state_proof {
1593        instruction.extend_from_slice(proof);
1594    }
1595
1596    Ok(instruction)
1597}
1598
1599/// Build test uploader WRITE instruction data
1600fn build_test_uploader_write_instruction(
1601    target_account_idx: u16,
1602    target_offset: u32,
1603    data: &[u8],
1604) -> Result<Vec<u8>> {
1605    let mut instruction = Vec::new();
1606
1607    // Discriminant (1 byte)
1608    instruction.push(TN_TEST_UPLOADER_PROGRAM_DISCRIMINANT_WRITE);
1609
1610    // Write args
1611    let args = TestUploaderWriteArgs {
1612        target_account_idx,
1613        target_offset,
1614        data_len: data.len() as u32,
1615    };
1616
1617    // Serialize args
1618    let args_bytes = unsafe {
1619        std::slice::from_raw_parts(
1620            &args as *const _ as *const u8,
1621            std::mem::size_of::<TestUploaderWriteArgs>(),
1622        )
1623    };
1624    instruction.extend_from_slice(args_bytes);
1625
1626    // Append data
1627    instruction.extend_from_slice(data);
1628
1629    Ok(instruction)
1630}
1631
1632/// Build system program DECOMPRESS2 instruction data
1633pub fn build_decompress2_instruction(
1634    target_account_idx: u16,
1635    meta_account_idx: u16,
1636    data_account_idx: u16,
1637    data_offset: u32,
1638    state_proof: &[u8],
1639) -> Result<Vec<u8>> {
1640    let mut instruction = Vec::new();
1641
1642    // Discriminant (1 byte) - TN_SYS_PROG_DISCRIMINANT_ACCOUNT_DECOMPRESS2 = 0x08
1643    instruction.push(0x08);
1644
1645    // DECOMPRESS2 args
1646    let args = SystemProgramDecompress2Args {
1647        target_account_idx,
1648        meta_account_idx,
1649        data_account_idx,
1650        data_offset,
1651    };
1652
1653    // Serialize args
1654    let args_bytes = unsafe {
1655        std::slice::from_raw_parts(
1656            &args as *const _ as *const u8,
1657            std::mem::size_of::<SystemProgramDecompress2Args>(),
1658        )
1659    };
1660    instruction.extend_from_slice(args_bytes);
1661
1662    // Append state proof bytes
1663    instruction.extend_from_slice(state_proof);
1664
1665    Ok(instruction)
1666}
1667
1668/// Token program instruction discriminants
1669pub const TOKEN_INSTRUCTION_INITIALIZE_MINT: u8 = 0x00;
1670pub const TOKEN_INSTRUCTION_INITIALIZE_ACCOUNT: u8 = 0x01;
1671pub const TOKEN_INSTRUCTION_TRANSFER: u8 = 0x02;
1672pub const TOKEN_INSTRUCTION_MINT_TO: u8 = 0x03;
1673pub const TOKEN_INSTRUCTION_BURN: u8 = 0x04;
1674pub const TOKEN_INSTRUCTION_CLOSE_ACCOUNT: u8 = 0x05;
1675pub const TOKEN_INSTRUCTION_FREEZE_ACCOUNT: u8 = 0x06;
1676pub const TOKEN_INSTRUCTION_THAW_ACCOUNT: u8 = 0x07;
1677
1678/// Helper function to add sorted accounts and return their indices
1679fn add_sorted_accounts(tx: Transaction, accounts: &[(TnPubkey, bool)]) -> (Transaction, Vec<u16>) {
1680    let mut sorted_accounts: Vec<_> = accounts.iter().enumerate().collect();
1681    sorted_accounts.sort_by(|a, b| a.1.0.cmp(&b.1.0));
1682
1683    let mut updated_tx = tx;
1684    let mut indices = vec![0u16; accounts.len()];
1685
1686    for (original_idx, (i, (account, writable))) in sorted_accounts.iter().enumerate() {
1687        let account_idx = (original_idx + 2) as u16; // Skip fee_payer(0) and program(1)
1688        indices[*i] = account_idx;
1689
1690        if *writable {
1691            updated_tx = updated_tx.add_rw_account(*account);
1692        } else {
1693            updated_tx = updated_tx.add_r_account(*account);
1694        }
1695    }
1696
1697    (updated_tx, indices)
1698}
1699
1700/// Helper function to get authority index (0 if fee_payer, else add as readonly)
1701fn add_authority_account(
1702    mut tx: Transaction,
1703    authority: TnPubkey,
1704    fee_payer: TnPubkey,
1705    next_idx: u16,
1706) -> (Transaction, u16) {
1707    if authority == fee_payer {
1708        (tx, 0u16)
1709    } else {
1710        tx = tx.add_r_account(authority);
1711        (tx, next_idx)
1712    }
1713}
1714
1715impl TransactionBuilder {
1716    /// Build token program InitializeMint transaction
1717    pub fn build_token_initialize_mint(
1718        fee_payer: TnPubkey,
1719        token_program: TnPubkey,
1720        mint_account: TnPubkey,
1721        mint_authority: TnPubkey,
1722        freeze_authority: Option<TnPubkey>,
1723        decimals: u8,
1724        ticker: &str,
1725        seed: [u8; 32],
1726        state_proof: Vec<u8>,
1727        fee: u64,
1728        nonce: u64,
1729        start_slot: u64,
1730    ) -> Result<Transaction> {
1731        // Account layout: [0: fee_payer, 1: token_program, 2: mint_account]
1732        let mint_account_idx = 2u16;
1733
1734        let instruction_data = build_token_initialize_mint_instruction(
1735            mint_account_idx,
1736            decimals,
1737            mint_authority,
1738            freeze_authority,
1739            ticker,
1740            seed,
1741            state_proof,
1742        )?;
1743
1744        let tx = Transaction::new(fee_payer, token_program, fee, nonce)
1745            .with_start_slot(start_slot)
1746            .add_rw_account(mint_account)
1747            .with_instructions(instruction_data)
1748            .with_expiry_after(100)
1749            .with_compute_units(300_000)
1750            .with_state_units(10_000)
1751            .with_memory_units(10_000);
1752
1753        Ok(tx)
1754    }
1755
1756    /// Build token program InitializeAccount transaction
1757    pub fn build_token_initialize_account(
1758        fee_payer: TnPubkey,
1759        token_program: TnPubkey,
1760        token_account: TnPubkey,
1761        mint_account: TnPubkey,
1762        owner: TnPubkey,
1763        seed: [u8; 32],
1764        state_proof: Vec<u8>,
1765        fee: u64,
1766        nonce: u64,
1767        start_slot: u64,
1768    ) -> Result<Transaction> {
1769        // Account layout: [0: fee_payer, 1: token_program, 2: token_account, 3: mint_account, 4?: owner]
1770        let token_account_idx = 2u16;
1771        let mint_account_idx = 3u16;
1772
1773        // Handle case where owner is the same as fee_payer
1774        let owner_is_fee_payer = owner == fee_payer;
1775        let owner_account_idx = if owner_is_fee_payer {
1776            0u16 // Use fee_payer index
1777        } else {
1778            4u16 // Add as separate account
1779        };
1780
1781        let instruction_data = build_token_initialize_account_instruction(
1782            token_account_idx,
1783            mint_account_idx,
1784            owner_account_idx,
1785            seed,
1786            state_proof,
1787        )?;
1788
1789        let mut tx = Transaction::new(fee_payer, token_program, fee, nonce)
1790            .with_start_slot(start_slot)
1791            .add_rw_account(token_account)
1792            .add_r_account(mint_account);
1793
1794        // Only add owner account if it's different from fee_payer
1795        if !owner_is_fee_payer {
1796            tx = tx.add_r_account(owner);
1797        }
1798
1799        tx = tx
1800            .with_instructions(instruction_data)
1801            .with_expiry_after(100)
1802            .with_compute_units(300_000)
1803            .with_state_units(10_000)
1804            .with_memory_units(10_000);
1805
1806        Ok(tx)
1807    }
1808
1809    /// Build token program Transfer transaction
1810    pub fn build_token_transfer(
1811        fee_payer: TnPubkey,
1812        token_program: TnPubkey,
1813        source_account: TnPubkey,
1814        dest_account: TnPubkey,
1815        authority: TnPubkey,
1816        amount: u64,
1817        fee: u64,
1818        nonce: u64,
1819        start_slot: u64,
1820    ) -> Result<Transaction> {
1821        let mut tx = Transaction::new(fee_payer, token_program, fee, nonce)
1822            .with_start_slot(start_slot)
1823            .with_expiry_after(100)
1824            .with_compute_units(300_000)
1825            .with_state_units(10_000)
1826            .with_memory_units(10_000);
1827
1828        let is_self_transfer = source_account == dest_account;
1829        let (source_account_idx, dest_account_idx) = if is_self_transfer {
1830            // For self-transfers, add the account only once and use same index
1831            tx = tx.add_rw_account(source_account);
1832            (2u16, 2u16)
1833        } else {
1834            // Add source and dest accounts in sorted order
1835            let accounts = &[(source_account, true), (dest_account, true)];
1836            let (updated_tx, indices) = add_sorted_accounts(tx, accounts);
1837            tx = updated_tx;
1838            (indices[0], indices[1])
1839        };
1840
1841        // Note: For transfers, the authority (source account owner) must sign the transaction
1842        // The token program will verify the signature matches the source account owner
1843
1844        let instruction_data =
1845            build_token_transfer_instruction(source_account_idx, dest_account_idx, amount)?;
1846
1847        Ok(tx.with_instructions(instruction_data))
1848    }
1849
1850    /// Build token program MintTo transaction
1851    pub fn build_token_mint_to(
1852        fee_payer: TnPubkey,
1853        token_program: TnPubkey,
1854        mint_account: TnPubkey,
1855        dest_account: TnPubkey,
1856        authority: TnPubkey,
1857        amount: u64,
1858        fee: u64,
1859        nonce: u64,
1860        start_slot: u64,
1861    ) -> Result<Transaction> {
1862        let tx = Transaction::new(fee_payer, token_program, fee, nonce)
1863            .with_start_slot(start_slot)
1864            .with_expiry_after(100)
1865            .with_compute_units(300_000)
1866            .with_state_units(10_000)
1867            .with_memory_units(10_000);
1868
1869        // Both mint and destination accounts need to be writable for mint operations
1870        let accounts = &[(mint_account, true), (dest_account, true)];
1871        let (tx, indices) = add_sorted_accounts(tx, accounts);
1872        let (mint_account_idx, dest_account_idx) = (indices[0], indices[1]);
1873
1874        let (tx, authority_account_idx) = add_authority_account(tx, authority, fee_payer, 4);
1875
1876        let instruction_data = build_token_mint_to_instruction(
1877            mint_account_idx,
1878            dest_account_idx,
1879            authority_account_idx,
1880            amount,
1881        )?;
1882
1883        Ok(tx.with_instructions(instruction_data))
1884    }
1885
1886    /// Build token program Burn transaction
1887    pub fn build_token_burn(
1888        fee_payer: TnPubkey,
1889        token_program: TnPubkey,
1890        token_account: TnPubkey,
1891        mint_account: TnPubkey,
1892        authority: TnPubkey,
1893        amount: u64,
1894        fee: u64,
1895        nonce: u64,
1896        start_slot: u64,
1897    ) -> Result<Transaction> {
1898        let tx = Transaction::new(fee_payer, token_program, fee, nonce)
1899            .with_start_slot(start_slot)
1900            .with_expiry_after(100)
1901            .with_compute_units(300_000)
1902            .with_state_units(10_000)
1903            .with_memory_units(10_000);
1904
1905        // Both token and mint accounts need to be writable for burn operations
1906        let accounts = &[(token_account, true), (mint_account, true)];
1907        let (tx, indices) = add_sorted_accounts(tx, accounts);
1908        let (token_account_idx, mint_account_idx) = (indices[0], indices[1]);
1909
1910        let (tx, authority_account_idx) = add_authority_account(tx, authority, fee_payer, 4);
1911
1912        let instruction_data = build_token_burn_instruction(
1913            token_account_idx,
1914            mint_account_idx,
1915            authority_account_idx,
1916            amount,
1917        )?;
1918
1919        Ok(tx.with_instructions(instruction_data))
1920    }
1921
1922    /// Build token program FreezeAccount transaction
1923    pub fn build_token_freeze_account(
1924        fee_payer: TnPubkey,
1925        token_program: TnPubkey,
1926        token_account: TnPubkey,
1927        mint_account: TnPubkey,
1928        authority: TnPubkey,
1929        fee: u64,
1930        nonce: u64,
1931        start_slot: u64,
1932    ) -> Result<Transaction> {
1933        let tx = Transaction::new(fee_payer, token_program, fee, nonce)
1934            .with_start_slot(start_slot)
1935            .with_expiry_after(100)
1936            .with_compute_units(300_000)
1937            .with_state_units(10_000)
1938            .with_memory_units(10_000);
1939
1940        // Both token and mint accounts need to be writable for freeze operations
1941        let accounts = &[(token_account, true), (mint_account, true)];
1942        let (tx, indices) = add_sorted_accounts(tx, accounts);
1943        let (token_account_idx, mint_account_idx) = (indices[0], indices[1]);
1944
1945        let (tx, authority_account_idx) = add_authority_account(tx, authority, fee_payer, 4);
1946
1947        let instruction_data = build_token_freeze_account_instruction(
1948            token_account_idx,
1949            mint_account_idx,
1950            authority_account_idx,
1951        )?;
1952
1953        Ok(tx.with_instructions(instruction_data))
1954    }
1955
1956    /// Build token program ThawAccount transaction
1957    pub fn build_token_thaw_account(
1958        fee_payer: TnPubkey,
1959        token_program: TnPubkey,
1960        token_account: TnPubkey,
1961        mint_account: TnPubkey,
1962        authority: TnPubkey,
1963        fee: u64,
1964        nonce: u64,
1965        start_slot: u64,
1966    ) -> Result<Transaction> {
1967        let tx = Transaction::new(fee_payer, token_program, fee, nonce)
1968            .with_start_slot(start_slot)
1969            .with_expiry_after(100)
1970            .with_compute_units(300_000)
1971            .with_state_units(10_000)
1972            .with_memory_units(10_000);
1973
1974        // Both token and mint accounts need to be writable for thaw operations
1975        let accounts = &[(token_account, true), (mint_account, true)];
1976        let (tx, indices) = add_sorted_accounts(tx, accounts);
1977        let (token_account_idx, mint_account_idx) = (indices[0], indices[1]);
1978
1979        let (tx, authority_account_idx) = add_authority_account(tx, authority, fee_payer, 4);
1980
1981        let instruction_data = build_token_thaw_account_instruction(
1982            token_account_idx,
1983            mint_account_idx,
1984            authority_account_idx,
1985        )?;
1986
1987        Ok(tx.with_instructions(instruction_data))
1988    }
1989
1990    /// Build token program CloseAccount transaction
1991    pub fn build_token_close_account(
1992        fee_payer: TnPubkey,
1993        token_program: TnPubkey,
1994        token_account: TnPubkey,
1995        destination: TnPubkey,
1996        authority: TnPubkey,
1997        fee: u64,
1998        nonce: u64,
1999        start_slot: u64,
2000    ) -> Result<Transaction> {
2001        let tx = Transaction::new(fee_payer, token_program, fee, nonce)
2002            .with_start_slot(start_slot)
2003            .with_expiry_after(100)
2004            .with_compute_units(300_000)
2005            .with_state_units(10_000)
2006            .with_memory_units(10_000);
2007
2008        // Both token and destination accounts need to be writable
2009        let accounts = &[(token_account, true), (destination, true)];
2010        let (tx, indices) = add_sorted_accounts(tx, accounts);
2011        let (token_account_idx, destination_idx) = (indices[0], indices[1]);
2012
2013        let (tx, authority_account_idx) = add_authority_account(tx, authority, fee_payer, 4);
2014
2015        let instruction_data = build_token_close_account_instruction(
2016            token_account_idx,
2017            destination_idx,
2018            authority_account_idx,
2019        )?;
2020
2021        Ok(tx.with_instructions(instruction_data))
2022    }
2023}
2024
2025/// Build token InitializeMint instruction data
2026fn build_token_initialize_mint_instruction(
2027    mint_account_idx: u16,
2028    decimals: u8,
2029    mint_authority: TnPubkey,
2030    freeze_authority: Option<TnPubkey>,
2031    ticker: &str,
2032    seed: [u8; 32],
2033    state_proof: Vec<u8>,
2034) -> Result<Vec<u8>> {
2035    let mut instruction_data = Vec::new();
2036
2037    // Instruction tag
2038    instruction_data.push(TOKEN_INSTRUCTION_INITIALIZE_MINT);
2039
2040    // mint_account_index (u16)
2041    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
2042
2043    // decimals (u8)
2044    instruction_data.push(decimals);
2045
2046    // mint_authority (32 bytes)
2047    instruction_data.extend_from_slice(&mint_authority);
2048
2049    // freeze_authority (32 bytes) and has_freeze_authority flag
2050    let (freeze_auth, has_freeze_auth) = match freeze_authority {
2051        Some(auth) => (auth, 1u8),
2052        None => ([0u8; 32], 0u8),
2053    };
2054    instruction_data.extend_from_slice(&freeze_auth);
2055    instruction_data.push(has_freeze_auth);
2056
2057    // ticker_len and ticker_bytes (max 8 bytes)
2058    let ticker_bytes = ticker.as_bytes();
2059    if ticker_bytes.len() > 8 {
2060        return Err(anyhow::anyhow!("Ticker must be 8 characters or less"));
2061    }
2062
2063    instruction_data.push(ticker_bytes.len() as u8);
2064    let mut ticker_padded = [0u8; 8];
2065    ticker_padded[..ticker_bytes.len()].copy_from_slice(ticker_bytes);
2066    instruction_data.extend_from_slice(&ticker_padded);
2067
2068    // seed (32 bytes)
2069    instruction_data.extend_from_slice(&seed);
2070
2071    // state proof (variable length)
2072    instruction_data.extend_from_slice(&state_proof);
2073
2074    Ok(instruction_data)
2075}
2076
2077/// Build token InitializeAccount instruction data
2078fn build_token_initialize_account_instruction(
2079    token_account_idx: u16,
2080    mint_account_idx: u16,
2081    owner_account_idx: u16,
2082    seed: [u8; 32],
2083    state_proof: Vec<u8>,
2084) -> Result<Vec<u8>> {
2085    let mut instruction_data = Vec::new();
2086
2087    // Instruction tag
2088    instruction_data.push(TOKEN_INSTRUCTION_INITIALIZE_ACCOUNT);
2089
2090    // token_account_index (u16)
2091    instruction_data.extend_from_slice(&token_account_idx.to_le_bytes());
2092
2093    // mint_account_index (u16)
2094    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
2095
2096    // owner_account_index (u16)
2097    instruction_data.extend_from_slice(&owner_account_idx.to_le_bytes());
2098
2099    // seed (32 bytes)
2100    instruction_data.extend_from_slice(&seed);
2101
2102    // state proof (variable length)
2103    instruction_data.extend_from_slice(&state_proof);
2104
2105    Ok(instruction_data)
2106}
2107
2108/// Build token Transfer instruction data
2109fn build_token_transfer_instruction(
2110    source_account_idx: u16,
2111    dest_account_idx: u16,
2112    amount: u64,
2113) -> Result<Vec<u8>> {
2114    let mut instruction_data = Vec::new();
2115
2116    // Instruction tag
2117    instruction_data.push(TOKEN_INSTRUCTION_TRANSFER);
2118
2119    // source_account_index (u16)
2120    instruction_data.extend_from_slice(&source_account_idx.to_le_bytes());
2121
2122    // dest_account_index (u16)
2123    instruction_data.extend_from_slice(&dest_account_idx.to_le_bytes());
2124
2125    // amount (u64)
2126    instruction_data.extend_from_slice(&amount.to_le_bytes());
2127
2128    Ok(instruction_data)
2129}
2130
2131/// Build token MintTo instruction data
2132fn build_token_mint_to_instruction(
2133    mint_account_idx: u16,
2134    dest_account_idx: u16,
2135    authority_idx: u16,
2136    amount: u64,
2137) -> Result<Vec<u8>> {
2138    let mut instruction_data = Vec::new();
2139
2140    // Instruction tag
2141    instruction_data.push(TOKEN_INSTRUCTION_MINT_TO);
2142
2143    // mint_account_index (u16)
2144    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
2145
2146    // dest_account_index (u16)
2147    instruction_data.extend_from_slice(&dest_account_idx.to_le_bytes());
2148
2149    // authority_index (u16)
2150    instruction_data.extend_from_slice(&authority_idx.to_le_bytes());
2151
2152    // amount (u64)
2153    instruction_data.extend_from_slice(&amount.to_le_bytes());
2154
2155    Ok(instruction_data)
2156}
2157
2158/// Build token Burn instruction data
2159fn build_token_burn_instruction(
2160    token_account_idx: u16,
2161    mint_account_idx: u16,
2162    authority_idx: u16,
2163    amount: u64,
2164) -> Result<Vec<u8>> {
2165    let mut instruction_data = Vec::new();
2166
2167    // Instruction tag
2168    instruction_data.push(TOKEN_INSTRUCTION_BURN);
2169
2170    // token_account_index (u16)
2171    instruction_data.extend_from_slice(&token_account_idx.to_le_bytes());
2172
2173    // mint_account_index (u16)
2174    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
2175
2176    // authority_index (u16)
2177    instruction_data.extend_from_slice(&authority_idx.to_le_bytes());
2178
2179    // amount (u64)
2180    instruction_data.extend_from_slice(&amount.to_le_bytes());
2181
2182    Ok(instruction_data)
2183}
2184
2185/// Build token FreezeAccount instruction data
2186fn build_token_freeze_account_instruction(
2187    token_account_idx: u16,
2188    mint_account_idx: u16,
2189    authority_idx: u16,
2190) -> Result<Vec<u8>> {
2191    let mut instruction_data = Vec::new();
2192
2193    // Instruction tag
2194    instruction_data.push(TOKEN_INSTRUCTION_FREEZE_ACCOUNT);
2195
2196    // token_account_index (u16)
2197    instruction_data.extend_from_slice(&token_account_idx.to_le_bytes());
2198
2199    // mint_account_index (u16)
2200    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
2201
2202    // authority_index (u16)
2203    instruction_data.extend_from_slice(&authority_idx.to_le_bytes());
2204
2205    Ok(instruction_data)
2206}
2207
2208/// Build token ThawAccount instruction data
2209fn build_token_thaw_account_instruction(
2210    token_account_idx: u16,
2211    mint_account_idx: u16,
2212    authority_idx: u16,
2213) -> Result<Vec<u8>> {
2214    let mut instruction_data = Vec::new();
2215
2216    // Instruction tag
2217    instruction_data.push(TOKEN_INSTRUCTION_THAW_ACCOUNT);
2218
2219    // token_account_index (u16)
2220    instruction_data.extend_from_slice(&token_account_idx.to_le_bytes());
2221
2222    // mint_account_index (u16)
2223    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
2224
2225    // authority_index (u16)
2226    instruction_data.extend_from_slice(&authority_idx.to_le_bytes());
2227
2228    Ok(instruction_data)
2229}
2230
2231/// Build token CloseAccount instruction data
2232fn build_token_close_account_instruction(
2233    token_account_idx: u16,
2234    destination_idx: u16,
2235    authority_idx: u16,
2236) -> Result<Vec<u8>> {
2237    let mut instruction_data = Vec::new();
2238
2239    // Instruction tag
2240    instruction_data.push(TOKEN_INSTRUCTION_CLOSE_ACCOUNT);
2241
2242    // token_account_index (u16)
2243    instruction_data.extend_from_slice(&token_account_idx.to_le_bytes());
2244
2245    // destination_index (u16)
2246    instruction_data.extend_from_slice(&destination_idx.to_le_bytes());
2247
2248    // authority_index (u16)
2249    instruction_data.extend_from_slice(&authority_idx.to_le_bytes());
2250
2251    Ok(instruction_data)
2252}