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