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;
8use std::collections::HashMap;
9
10/// No-op program identifier (32-byte array with 0x03 in the last byte)
11pub const NOOP_PROGRAM: [u8; 32] = {
12    let mut arr = [0u8; 32];
13    arr[31] = 0x03;
14    arr
15};
16pub const SYSTEM_PROGRAM: [u8; 32] = {
17    let mut arr = [0u8; 32];
18    arr[31] = 0x01;
19    arr
20};
21pub const EOA_PROGRAM: [u8; 32] = {
22    let arr = [0u8; 32];
23    arr
24};
25
26pub const UPLOADER_PROGRAM: [u8; 32] = {
27    let mut arr = [0u8; 32];
28    arr[31] = 0x02;
29    arr
30};
31pub const FAUCET_PROGRAM: [u8; 32] = {
32    let mut arr = [0u8; 32];
33    arr[31] = 0xFA;
34    arr
35};
36
37#[derive(Debug, Clone)]
38pub struct TransactionBuilder {
39    // fee_payer: FdPubkey,
40    // program: FdPubkey,
41    // fee: u64,
42    // nonce: u64,
43}
44
45impl TransactionBuilder {
46    /// Build balance transfer transaction
47    pub fn build_create_with_fee_payer_proof(
48        fee_payer: TnPubkey,
49        start_slot: u64,
50        fee_payer_state_proof: &StateProof,
51    ) -> Result<Transaction> {
52        let tx = Transaction::new(fee_payer, NOOP_PROGRAM, 0, 0)
53            .with_fee_payer_state_proof(fee_payer_state_proof)
54            .with_start_slot(start_slot)
55            .with_expiry_after(100)
56            .with_compute_units(10_000)
57            .with_memory_units(10_000)
58            .with_state_units(10_000);
59        Ok(tx)
60    }
61
62    /// Build balance transfer transaction for EOA program (tn_eoa_program.c)
63    ///
64    /// This creates a transaction that calls the TRANSFER instruction of the EOA program,
65    /// which transfers balance from one account to another.
66    ///
67    /// # Arguments
68    /// * `fee_payer` - The account paying the transaction fee (also the from_account for the transfer)
69    /// * `program` - The EOA program pubkey (typically EOA_PROGRAM constant = all zeros)
70    /// * `to_account` - The destination account receiving the transfer
71    /// * `amount` - The amount to transfer
72    /// * `fee` - Transaction fee
73    /// * `nonce` - Account nonce
74    /// * `start_slot` - Starting slot for transaction validity
75    pub fn build_transfer(
76        fee_payer: TnPubkey,
77        program: TnPubkey,
78        to_account: TnPubkey,
79        amount: u64,
80        fee: u64,
81        nonce: u64,
82        start_slot: u64,
83    ) -> Result<Transaction> {
84        // Create transfer instruction data for EOA program
85        // Account layout: [0: fee_payer/from_account, 1: program, 2: to_account]
86        let from_account_idx = 0u16; // fee_payer is also the from_account
87        let to_account_idx = 2u16; // to_account added via add_rw_account
88        let instruction_data =
89            build_transfer_instruction(from_account_idx, to_account_idx, amount)?;
90
91        let tx = Transaction::new(fee_payer, program, fee, nonce)
92            .with_start_slot(start_slot)
93            .add_rw_account(to_account) // Destination account (receives transfer)
94            .with_instructions(instruction_data)
95            .with_expiry_after(100)
96            .with_compute_units(10000)
97            .with_memory_units(10000)
98            .with_state_units(10000);
99
100        Ok(tx)
101    }
102
103    /// Build regular account creation transaction (with optional state proof)
104    pub fn build_create_account(
105        fee_payer: TnPubkey,
106        program: TnPubkey,
107        target_account: TnPubkey,
108        seed: &str,
109        state_proof: Option<&[u8]>,
110        fee: u64,
111        nonce: u64,
112        start_slot: u64,
113    ) -> Result<Transaction> {
114        // Account layout: [0: fee_payer, 1: program, 2: target_account]
115        let target_account_idx = 2u16; // target_account added via add_rw_account
116        let instruction_data =
117            build_create_account_instruction(target_account_idx, seed, state_proof)?;
118
119        let tx = Transaction::new(fee_payer, program, fee, nonce)
120            .with_start_slot(start_slot)
121            .add_rw_account(target_account)
122            .with_instructions(instruction_data)
123            .with_expiry_after(100)
124            .with_compute_units(10_000)
125            .with_memory_units(10_000)
126            .with_state_units(10_000);
127
128        Ok(tx)
129    }
130
131    /// Build account creation transaction
132    pub fn build_create_ephemeral_account(
133        fee_payer: TnPubkey,
134        program: TnPubkey,
135        target_account: TnPubkey,
136        seed: &[u8; 32],
137        fee: u64,
138        nonce: u64,
139        start_slot: u64,
140    ) -> Result<Transaction> {
141        // Account layout: [0: fee_payer, 1: program, 2: target_account]
142        let target_account_idx = 2u16; // target_account added via add_rw_account
143        let instruction_data = build_ephemeral_account_instruction(target_account_idx, seed)?;
144
145        let tx = Transaction::new(fee_payer, program, fee, nonce)
146            .with_start_slot(start_slot)
147            .add_rw_account(target_account)
148            .with_instructions(instruction_data)
149            .with_expiry_after(100)
150            .with_compute_units(50_000)
151            .with_memory_units(10_000)
152            .with_state_units(10_000);
153        Ok(tx)
154    }
155
156    /// Build account resize transaction
157    pub fn build_resize_account(
158        fee_payer: TnPubkey,
159        program: TnPubkey,
160        target_account: TnPubkey,
161        new_size: u64,
162        fee: u64,
163        nonce: u64,
164        start_slot: u64,
165    ) -> Result<Transaction> {
166        // Account layout: [0: fee_payer, 1: program, 2: target_account]
167        let target_account_idx = 2u16; // target_account added via add_rw_account
168        let instruction_data = build_resize_instruction(target_account_idx, new_size)?;
169
170        let tx = Transaction::new(fee_payer, program, fee, nonce)
171            .with_start_slot(start_slot)
172            .with_expiry_after(100)
173            .with_compute_units(100032)
174            .with_state_units(1 + new_size.checked_div(4096).unwrap() as u16)
175            .with_memory_units(10000)
176            .add_rw_account(target_account)
177            .with_instructions(instruction_data)
178            .with_expiry_after(100)
179            .with_compute_units(10_000 + 2 * new_size as u32)
180            .with_memory_units(10_000)
181            .with_state_units(10_000);
182
183        Ok(tx)
184    }
185
186    /// Build account compression transaction
187    pub fn build_compress_account(
188        fee_payer: TnPubkey,
189        program: TnPubkey,
190        target_account: TnPubkey,
191        state_proof: &[u8],
192        fee: u64,
193        nonce: u64,
194        start_slot: u64,
195        account_size: u32,
196    ) -> Result<Transaction> {
197        // Account layout: [0: fee_payer, 1: program, 2: target_account]
198        let target_account_idx = 2u16; // target_account added via add_rw_account
199        let instruction_data = build_compress_instruction(target_account_idx, state_proof)?;
200
201        let tx = Transaction::new(fee_payer, program, fee, nonce)
202            .with_start_slot(start_slot)
203            .with_may_compress_account()
204            .add_rw_account(target_account)
205            .with_instructions(instruction_data)
206            .with_expiry_after(100)
207            .with_compute_units(100_300 + account_size * 2)
208            .with_memory_units(10000)
209            .with_state_units(10000);
210
211        Ok(tx)
212    }
213
214    /// Build account decompression transaction
215    pub fn build_decompress_account(
216        fee_payer: TnPubkey,
217        program: TnPubkey,
218        target_account: TnPubkey,
219        account_data: &[u8],
220        state_proof: &[u8],
221        fee: u64,
222        nonce: u64,
223        start_slot: u64,
224    ) -> Result<Transaction> {
225        // Account layout: [0: fee_payer, 1: program, 2: target_account]
226        let target_account_idx = 2u16; // target_account added via add_rw_account
227        let instruction_data =
228            build_decompress_instruction(target_account_idx, account_data, state_proof)?;
229
230        let tx = Transaction::new(fee_payer, program, fee, nonce)
231            .with_start_slot(start_slot)
232            .add_rw_account(target_account)
233            .with_instructions(instruction_data)
234            .with_compute_units(100_300 + account_data.len() as u32 * 2)
235            .with_state_units(10_000)
236            .with_memory_units(10_000)
237            .with_expiry_after(100);
238        Ok(tx)
239    }
240
241    /// Build data write transaction
242    pub fn build_write_data(
243        fee_payer: TnPubkey,
244        program: TnPubkey,
245        target_account: TnPubkey,
246        offset: u16,
247        data: &[u8],
248        fee: u64,
249        nonce: u64,
250        start_slot: u64,
251    ) -> Result<Transaction> {
252        // Account layout: [0: fee_payer, 1: program, 2: target_account]
253        let target_account_idx = 2u16; // target_account added via add_rw_account
254        let instruction_data = build_write_instruction(target_account_idx, offset, data)?;
255
256        let tx = Transaction::new(fee_payer, program, fee, nonce)
257            .with_start_slot(start_slot)
258            .with_expiry_after(100)
259            .with_compute_units(100045)
260            .with_state_units(10000)
261            .with_memory_units(10000)
262            .add_rw_account(target_account)
263            .with_instructions(instruction_data);
264
265        Ok(tx)
266    }
267}
268
269/// Build transfer instruction for EOA program (tn_eoa_program.c)
270///
271/// Instruction format (matching tn_eoa_instruction_t and tn_eoa_transfer_args_t):
272/// - Discriminant: u32 (4 bytes) = TN_EOA_INSTRUCTION_TRANSFER (1)
273/// - Amount: u64 (8 bytes)
274/// - From account index: u16 (2 bytes)
275/// - To account index: u16 (2 bytes)
276/// Total: 16 bytes
277fn build_transfer_instruction(
278    from_account_idx: u16,
279    to_account_idx: u16,
280    amount: u64,
281) -> Result<Vec<u8>> {
282    let mut instruction = Vec::new();
283
284    // TN_EOA_INSTRUCTION_TRANSFER = 1 (u32, 4 bytes little-endian)
285    instruction.extend_from_slice(&1u32.to_le_bytes());
286
287    // tn_eoa_transfer_args_t structure:
288    // - amount (u64, 8 bytes little-endian)
289    instruction.extend_from_slice(&amount.to_le_bytes());
290
291    // - from_account_idx (u16, 2 bytes little-endian)
292    instruction.extend_from_slice(&from_account_idx.to_le_bytes());
293
294    // - to_account_idx (u16, 2 bytes little-endian)
295    instruction.extend_from_slice(&to_account_idx.to_le_bytes());
296
297    Ok(instruction)
298}
299
300/// Build regular account creation instruction (TN_SYS_PROG_DISCRIMINANT_ACCOUNT_CREATE = 0x00)
301fn build_create_account_instruction(
302    target_account_idx: u16,
303    seed: &str,
304    state_proof: Option<&[u8]>,
305) -> Result<Vec<u8>> {
306    let mut instruction = Vec::new();
307
308    // TN_SYS_PROG_DISCRIMINANT_ACCOUNT_CREATE = 0x00
309    instruction.push(0x00);
310
311    // Target account index (little-endian u16) - matching tn_system_program_account_create_args_t
312    instruction.extend_from_slice(&target_account_idx.to_le_bytes());
313
314    // Seed should be hex-decoded (to match addrtool behavior)
315    let seed_bytes =
316        hex::decode(seed).map_err(|e| anyhow::anyhow!("Failed to decode hex seed: {}", e))?;
317
318    // Seed length (little-endian u64) - matching tn_system_program_account_create_args_t.seed_len
319    instruction.extend_from_slice(&(seed_bytes.len() as u64).to_le_bytes());
320
321    // has_proof flag (1 byte) - matching tn_system_program_account_create_args_t.has_proof
322    let has_proof = state_proof.is_some();
323    instruction.push(if has_proof { 1u8 } else { 0u8 });
324
325    // Seed data (seed_len bytes follow)
326    instruction.extend_from_slice(&seed_bytes);
327
328    // Proof data (if present, proof follows seed)
329    if let Some(proof) = state_proof {
330        instruction.extend_from_slice(proof);
331    }
332
333    Ok(instruction)
334}
335
336/// Build ephemeral account creation instruction
337fn build_ephemeral_account_instruction(
338    target_account_idx: u16,
339    seed: &[u8; 32],
340) -> Result<Vec<u8>> {
341    let mut instruction = Vec::new();
342
343    // TN_SYS_PROG_DISCRIMINANT_ACCOUNT_CREATE_EPHEMERAL = 01
344    instruction.push(0x01);
345
346    // Target account index (little-endian u16)
347    instruction.extend_from_slice(&target_account_idx.to_le_bytes());
348
349    // Seed length (little-endian u64)
350    instruction.extend_from_slice(&(seed.len() as u64).to_le_bytes());
351
352    // Seed data
353    instruction.extend_from_slice(seed);
354
355    Ok(instruction)
356}
357
358/// Build account resize instruction
359fn build_resize_instruction(target_account_idx: u16, new_size: u64) -> Result<Vec<u8>> {
360    let mut instruction = Vec::new();
361
362    // TN_SYS_PROG_DISCRIMINANT_ACCOUNT_RESIZE = 04
363    instruction.push(0x04);
364
365    // Target account index (little-endian u16)
366    instruction.extend_from_slice(&target_account_idx.to_le_bytes());
367
368    // New size (little-endian u64)
369    instruction.extend_from_slice(&new_size.to_le_bytes());
370
371    Ok(instruction)
372}
373
374/// Build data write instruction
375fn build_write_instruction(target_account_idx: u16, offset: u16, data: &[u8]) -> Result<Vec<u8>> {
376    let mut instruction = Vec::new();
377
378    // TN_SYS_PROG_DISCRIMINANT_WRITE = C8
379    instruction.push(0xC8);
380
381    // Target account index (little-endian u16)
382    instruction.extend_from_slice(&target_account_idx.to_le_bytes());
383
384    // Offset (little-endian u16)
385    instruction.extend_from_slice(&offset.to_le_bytes());
386
387    // Data length (little-endian u16)
388    instruction.extend_from_slice(&(data.len() as u16).to_le_bytes());
389
390    // Data
391    instruction.extend_from_slice(data);
392
393    Ok(instruction)
394}
395
396/// Build account compression instruction
397fn build_compress_instruction(target_account_idx: u16, state_proof: &[u8]) -> Result<Vec<u8>> {
398    let mut instruction = Vec::new();
399
400    // TN_SYS_PROG_DISCRIMINANT_ACCOUNT_COMPRESS - based on C test, this appears to be different from other discriminants
401    // Looking at the C test pattern and other system discriminants, compression is likely 0x05
402    instruction.push(0x05);
403
404    // Target account index (little-endian u16)
405    instruction.extend_from_slice(&target_account_idx.to_le_bytes());
406
407    // State proof bytes
408    instruction.extend_from_slice(state_proof);
409
410    Ok(instruction)
411}
412
413fn build_decompress_instruction(
414    target_account_idx: u16,
415    account_data: &[u8],
416    state_proof: &[u8],
417) -> Result<Vec<u8>> {
418    let mut instruction = Vec::new();
419
420    // TN_SYS_PROG_DISCRIMINANT_ACCOUNT_DECOMPRESS = 0x06
421    instruction.push(0x06);
422
423    // tn_system_program_account_decompress_args_t: account_idx (u16) + data_len (u64)
424    instruction.extend_from_slice(&target_account_idx.to_le_bytes());
425    instruction.extend_from_slice(&(account_data.len() as u64).to_le_bytes());
426
427    // Account data
428    instruction.extend_from_slice(account_data);
429
430    // State proof bytes
431    instruction.extend_from_slice(state_proof);
432
433    Ok(instruction)
434}
435
436/// Generate ephemeral account address from seed
437/// This replaces the `addrtool --ephemeral` functionality
438/// Based on create_program_defined_account_address from tn_vm_syscalls.c
439/// Note: For ephemeral accounts, the owner is always the system program (all zeros)
440pub fn generate_ephemeral_address(seed: &str) -> Result<String> {
441    // Owner is always system program (all zeros) for ephemeral accounts
442    let owner_pubkey = [0u8; 32];
443
444    // Convert seed string to hex bytes (addrtool expects hex-encoded seed)
445    let seed_bytes =
446        hex::decode(seed).map_err(|e| anyhow::anyhow!("Failed to decode hex seed: {}", e))?;
447
448    // Pad or truncate to exactly 32 bytes (matching C implementation)
449    let mut seed_32 = [0u8; 32];
450    let copy_len = std::cmp::min(seed_bytes.len(), 32);
451    seed_32[..copy_len].copy_from_slice(&seed_bytes[..copy_len]);
452
453    // Use the new implementation from tn_public_address
454    Ok(
455        crate::tn_public_address::create_program_defined_account_address_string(
456            &owner_pubkey,
457            true, // is_ephemeral = true
458            &seed_32,
459        ),
460    )
461}
462
463pub fn generate_system_derived_address(seed: &str, is_ephemeral: bool) -> Result<String> {
464    // Convert seed string to hex bytes (addrtool expects hex-encoded seed)
465    let seed_bytes =
466        hex::decode(seed).map_err(|e| anyhow::anyhow!("Failed to decode hex seed: {}", e))?;
467
468    let pubkey = generate_derived_address(&seed_bytes, &[0u8; 32], is_ephemeral)?;
469
470    Ok(tn_pubkey_to_address_string(&pubkey))
471}
472
473pub fn generate_derived_address(
474    seed: &[u8],
475    owner_pubkey: &[u8; 32],
476    is_ephemeral: bool,
477) -> Result<[u8; 32]> {
478    use sha2::{Digest, Sha256};
479
480    // Create SHA256 hasher
481    let mut hasher = Sha256::new();
482
483    // Hash owner pubkey (32 bytes) - system program
484    hasher.update(&owner_pubkey);
485
486    // Hash is_ephemeral flag (1 byte)
487    hasher.update(&[is_ephemeral as u8]);
488
489    // Hash seed bytes
490    hasher.update(&seed);
491
492    // Finalize hash to get 32-byte result
493    Ok(hasher.finalize().into())
494}
495
496#[cfg(test)]
497mod tests {
498    use super::*;
499
500    #[test]
501    fn test_ephemeral_address_generation() {
502        // Test with hex-encoded seeds (as addrtool expects)
503        let hex_seed1 = hex::encode("test_seed_123");
504        let hex_seed2 = hex::encode("test_seed_123");
505        let hex_seed3 = hex::encode("different_seed");
506
507        let addr1 = generate_ephemeral_address(&hex_seed1).unwrap();
508        let addr2 = generate_ephemeral_address(&hex_seed2).unwrap();
509        let addr3 = generate_ephemeral_address(&hex_seed3).unwrap();
510
511        // Same inputs should produce same address
512        assert_eq!(addr1, addr2);
513
514        // Different seeds should produce different addresses
515        assert_ne!(addr1, addr3);
516
517        // All addresses should be ta... format
518        assert!(addr1.starts_with("ta"));
519        assert!(addr2.starts_with("ta"));
520        assert!(addr3.starts_with("ta"));
521
522        // All addresses should be 46 characters
523        assert_eq!(addr1.len(), 46);
524        assert_eq!(addr2.len(), 46);
525        assert_eq!(addr3.len(), 46);
526    }
527
528    #[test]
529    fn test_eoa_transfer_instruction_format() {
530        // Test that the transfer instruction matches the EOA program's expected format
531        let from_idx = 0u16;
532        let to_idx = 2u16;
533        let amount = 1000u64;
534
535        let instruction = build_transfer_instruction(from_idx, to_idx, amount).unwrap();
536
537        // Expected format (matching tn_eoa_program.c):
538        // - Discriminant: u32 (4 bytes) = 1
539        // - Amount: u64 (8 bytes)
540        // - From account index: u16 (2 bytes)
541        // - To account index: u16 (2 bytes)
542        // Total: 16 bytes
543
544        assert_eq!(instruction.len(), 16, "Instruction should be 16 bytes");
545
546        // Check discriminant (TN_EOA_INSTRUCTION_TRANSFER = 1)
547        let discriminant = u32::from_le_bytes([
548            instruction[0],
549            instruction[1],
550            instruction[2],
551            instruction[3],
552        ]);
553        assert_eq!(discriminant, 1, "Discriminant should be 1 for TRANSFER");
554
555        // Check amount
556        let parsed_amount = u64::from_le_bytes([
557            instruction[4],
558            instruction[5],
559            instruction[6],
560            instruction[7],
561            instruction[8],
562            instruction[9],
563            instruction[10],
564            instruction[11],
565        ]);
566        assert_eq!(parsed_amount, amount, "Amount should match input");
567
568        // Check from_account_idx
569        let parsed_from = u16::from_le_bytes([instruction[12], instruction[13]]);
570        assert_eq!(parsed_from, from_idx, "From index should match input");
571
572        // Check to_account_idx
573        let parsed_to = u16::from_le_bytes([instruction[14], instruction[15]]);
574        assert_eq!(parsed_to, to_idx, "To index should match input");
575    }
576
577    #[test]
578    fn test_faucet_deposit_instruction_layout_with_fee_payer_depositor() {
579        let fee_payer = [1u8; 32];
580        let faucet_program = FAUCET_PROGRAM;
581        let faucet_account = [2u8; 32];
582        let depositor_account = fee_payer;
583        let amount = 500u64;
584
585        let tx = TransactionBuilder::build_faucet_deposit(
586            fee_payer,
587            faucet_program,
588            faucet_account,
589            depositor_account,
590            EOA_PROGRAM,
591            amount,
592            0,
593            42,
594            100,
595        )
596        .expect("build faucet deposit");
597
598        let rw_accs = tx.rw_accs.expect("rw accounts must exist");
599        assert_eq!(rw_accs.len(), 1);
600        assert_eq!(rw_accs[0], faucet_account);
601
602        let ro_accs = tx.r_accs.expect("ro accounts must exist");
603        assert_eq!(ro_accs.len(), 1);
604        assert_eq!(ro_accs[0], EOA_PROGRAM);
605
606        let instruction = tx.instructions.expect("instruction bytes must exist");
607        assert_eq!(instruction.len(), 18, "Deposit instruction must be 18 bytes");
608
609        let discriminant =
610            u32::from_le_bytes([instruction[0], instruction[1], instruction[2], instruction[3]]);
611        assert_eq!(discriminant, 0, "Deposit discriminant should be 0");
612
613        let faucet_idx = u16::from_le_bytes([instruction[4], instruction[5]]);
614        let depositor_idx = u16::from_le_bytes([instruction[6], instruction[7]]);
615        let eoa_idx = u16::from_le_bytes([instruction[8], instruction[9]]);
616        let parsed_amount = u64::from_le_bytes([
617            instruction[10],
618            instruction[11],
619            instruction[12],
620            instruction[13],
621            instruction[14],
622            instruction[15],
623            instruction[16],
624            instruction[17],
625        ]);
626
627        assert_eq!(faucet_idx, 2, "Faucet account should be first RW account");
628        assert_eq!(depositor_idx, 0, "Depositor shares the fee payer index");
629        assert_eq!(eoa_idx, 3, "EOA program should follow RW accounts");
630        assert_eq!(parsed_amount, amount, "Amount should match input");
631    }
632
633    #[test]
634    fn test_build_token_initialize_mint() {
635        // Create test keypairs and addresses
636        let fee_payer = [1u8; 32];
637        let token_program = [2u8; 32];
638        let mint_account = [3u8; 32];
639        let creator = [4u8; 32];
640        let mint_authority = [5u8; 32];
641        let freeze_authority = [6u8; 32];
642
643        let decimals = 9u8;
644        let ticker = "TEST";
645        let seed = [7u8; 32];
646        let state_proof = vec![8u8; 64];
647
648        // Test with freeze authority
649        let result = TransactionBuilder::build_token_initialize_mint(
650            fee_payer,
651            token_program,
652            mint_account,
653            creator,
654            mint_authority,
655            Some(freeze_authority),
656            decimals,
657            ticker,
658            seed,
659            state_proof.clone(),
660            1000, // fee
661            1,    // nonce
662            100,  // start_slot
663        );
664
665        assert!(result.is_ok(), "Should build valid transaction with freeze authority");
666        let tx = result.unwrap();
667        assert!(tx.instructions.is_some(), "Transaction should have instructions");
668
669        // Test without freeze authority
670        let result_no_freeze = TransactionBuilder::build_token_initialize_mint(
671            fee_payer,
672            token_program,
673            mint_account,
674            creator,
675            mint_authority,
676            None,
677            decimals,
678            ticker,
679            seed,
680            state_proof,
681            1000,
682            1,
683            100,
684        );
685
686        assert!(result_no_freeze.is_ok(), "Should build valid transaction without freeze authority");
687    }
688
689    #[test]
690    fn test_build_token_initialize_mint_instruction_format() {
691        let mint_account_idx = 2u16;
692        let decimals = 9u8;
693        let creator = [1u8; 32];
694        let mint_authority = [2u8; 32];
695        let freeze_authority = [3u8; 32];
696        let ticker = "TST";
697        let seed = [4u8; 32];
698        let state_proof = vec![5u8; 10];
699
700        let instruction = build_token_initialize_mint_instruction(
701            mint_account_idx,
702            decimals,
703            creator,
704            mint_authority,
705            Some(freeze_authority),
706            ticker,
707            seed,
708            state_proof.clone(),
709        )
710        .unwrap();
711
712        // Verify instruction structure
713        // Tag (1) + mint_account_idx (2) + decimals (1) + creator (32) + mint_authority (32) + 
714        // freeze_authority (32) + has_freeze_authority (1) + ticker_len (1) + ticker_bytes (8) + seed (32) + proof
715        let expected_min_size = 1 + 2 + 1 + 32 + 32 + 32 + 1 + 1 + 8 + 32 + state_proof.len();
716        assert_eq!(instruction.len(), expected_min_size);
717
718        // Verify tag
719        assert_eq!(instruction[0], 0, "First byte should be InitializeMint tag (0)");
720
721        // Verify mint account index
722        let parsed_idx = u16::from_le_bytes([instruction[1], instruction[2]]);
723        assert_eq!(parsed_idx, mint_account_idx);
724
725        // Verify decimals
726        assert_eq!(instruction[3], decimals);
727
728        // Verify creator is at correct position (bytes 4-35)
729        assert_eq!(&instruction[4..36], &creator);
730
731        // Verify mint_authority is at correct position (bytes 36-67)
732        assert_eq!(&instruction[36..68], &mint_authority);
733
734        // Verify freeze_authority is at correct position (bytes 68-99)
735        assert_eq!(&instruction[68..100], &freeze_authority);
736
737        // Verify has_freeze_authority flag
738        assert_eq!(instruction[100], 1);
739    }
740
741    #[test]
742    fn test_token_initialize_mint_creator_vs_mint_authority() {
743        // Test that creator and mint_authority can be different
744        let fee_payer = [1u8; 32];
745        let token_program = [2u8; 32];
746        let mint_account = [3u8; 32];
747        let creator = [4u8; 32];
748        let mint_authority = [5u8; 32]; // Different from creator
749        let seed = [6u8; 32];
750        let state_proof = vec![7u8; 32];
751
752        let result = TransactionBuilder::build_token_initialize_mint(
753            fee_payer,
754            token_program,
755            mint_account,
756            creator,
757            mint_authority,
758            None,
759            9,
760            "TEST",
761            seed,
762            state_proof,
763            1000,
764            1,
765            100,
766        );
767
768        assert!(result.is_ok(), "Should allow different creator and mint_authority");
769
770        // Test that creator and mint_authority can be the same
771        let result_same = TransactionBuilder::build_token_initialize_mint(
772            fee_payer,
773            token_program,
774            mint_account,
775            creator,
776            creator, // Same as creator
777            None,
778            9,
779            "TEST",
780            seed,
781            vec![7u8; 32],
782            1000,
783            1,
784            100,
785        );
786
787        assert!(result_same.is_ok(), "Should allow same creator and mint_authority");
788    }
789}
790
791/// Uploader program instruction discriminants
792pub const TN_UPLOADER_PROGRAM_INSTRUCTION_CREATE: u32 = 0x00;
793pub const TN_UPLOADER_PROGRAM_INSTRUCTION_WRITE: u32 = 0x01;
794pub const TN_UPLOADER_PROGRAM_INSTRUCTION_DESTROY: u32 = 0x02;
795pub const TN_UPLOADER_PROGRAM_INSTRUCTION_FINALIZE: u32 = 0x03;
796
797/// Uploader program CREATE instruction arguments (matches C struct)
798#[repr(C, packed)]
799#[derive(Debug, Clone, Copy)]
800pub struct UploaderCreateArgs {
801    pub buffer_account_idx: u16,
802    pub meta_account_idx: u16,
803    pub authority_account_idx: u16,
804    pub buffer_account_sz: u32,
805    pub expected_account_hash: [u8; 32],
806    pub seed_len: u32,
807    // seed bytes follow
808}
809
810/// Uploader program WRITE instruction arguments (matches C struct)
811#[repr(C, packed)]
812#[derive(Debug, Clone, Copy)]
813pub struct UploaderWriteArgs {
814    pub buffer_account_idx: u16,
815    pub meta_account_idx: u16,
816    pub data_len: u32,
817    pub data_offset: u32,
818    // data bytes follow
819}
820
821/// Uploader program FINALIZE instruction arguments (matches C struct)
822#[repr(C, packed)]
823#[derive(Debug, Clone, Copy)]
824pub struct UploaderFinalizeArgs {
825    pub buffer_account_idx: u16,
826    pub meta_account_idx: u16,
827    pub expected_account_hash: [u8; 32],
828}
829
830/// Uploader program DESTROY instruction arguments (matches C struct)
831#[repr(C, packed)]
832#[derive(Debug, Clone, Copy)]
833pub struct UploaderDestroyArgs {
834    pub buffer_account_idx: u16,
835    pub meta_account_idx: u16,
836}
837
838/// Manager program instruction discriminants (matches C defines)
839pub const MANAGER_INSTRUCTION_CREATE_PERMANENT: u8 = 0x00;
840pub const MANAGER_INSTRUCTION_CREATE_EPHEMERAL: u8 = 0x01;
841pub const MANAGER_INSTRUCTION_UPGRADE: u8 = 0x02;
842pub const MANAGER_INSTRUCTION_SET_PAUSE: u8 = 0x03;
843pub const MANAGER_INSTRUCTION_DESTROY: u8 = 0x04;
844pub const MANAGER_INSTRUCTION_FINALIZE: u8 = 0x05;
845pub const MANAGER_INSTRUCTION_SET_AUTHORITY: u8 = 0x06;
846pub const MANAGER_INSTRUCTION_CLAIM_AUTHORITY: u8 = 0x07;
847
848pub const ABI_MANAGER_INSTRUCTION_CREATE_PERMANENT: u8 = 0x00;
849pub const ABI_MANAGER_INSTRUCTION_CREATE_EPHEMERAL: u8 = 0x01;
850pub const ABI_MANAGER_INSTRUCTION_UPGRADE: u8 = 0x02;
851pub const ABI_MANAGER_INSTRUCTION_CLOSE: u8 = 0x03;
852pub const ABI_MANAGER_INSTRUCTION_FINALIZE: u8 = 0x04;
853
854/// Manager program header arguments (matches C struct)
855#[repr(C, packed)]
856#[derive(Debug, Clone, Copy)]
857pub struct ManagerHeaderArgs {
858    pub discriminant: u8,
859    pub meta_account_idx: u16,
860    pub program_account_idx: u16,
861}
862
863/// Manager program CREATE instruction arguments (matches C struct)
864#[repr(C, packed)]
865#[derive(Debug, Clone, Copy)]
866pub struct ManagerCreateArgs {
867    pub discriminant: u8,
868    pub meta_account_idx: u16,
869    pub program_account_idx: u16,
870    pub srcbuf_account_idx: u16,
871    pub srcbuf_offset: u32,
872    pub srcbuf_size: u32,
873    pub authority_account_idx: u16,
874    pub seed_len: u32,
875    // seed bytes and proof bytes follow
876}
877
878/// Manager program UPGRADE instruction arguments (matches C struct)
879#[repr(C, packed)]
880#[derive(Debug, Clone, Copy)]
881pub struct ManagerUpgradeArgs {
882    pub discriminant: u8,
883    pub meta_account_idx: u16,
884    pub program_account_idx: u16,
885    pub srcbuf_account_idx: u16,
886    pub srcbuf_offset: u32,
887    pub srcbuf_size: u32,
888}
889
890/// ABI manager program CREATE instruction arguments (matches C struct)
891#[repr(C, packed)]
892#[derive(Debug, Clone, Copy)]
893pub struct AbiManagerCreateArgs {
894    pub program_meta_account_idx: u16,
895    pub abi_account_idx: u16,
896    pub srcbuf_account_idx: u16,
897    pub srcbuf_offset: u32,
898    pub srcbuf_size: u32,
899    pub authority_account_idx: u16,
900    pub seed: [u8; 32],  // Fixed 32-byte seed
901}
902
903/// ABI manager program UPGRADE instruction arguments (matches C struct)
904#[repr(C, packed)]
905#[derive(Debug, Clone, Copy)]
906pub struct AbiManagerUpgradeArgs {
907    pub program_meta_account_idx: u16,
908    pub abi_account_idx: u16,
909    pub srcbuf_account_idx: u16,
910    pub srcbuf_offset: u32,
911    pub srcbuf_size: u32,
912    pub authority_account_idx: u16,
913}
914
915/// ABI manager program FINALIZE instruction arguments (matches C struct)
916#[repr(C, packed)]
917#[derive(Debug, Clone, Copy)]
918pub struct AbiManagerFinalizeArgs {
919    pub program_meta_account_idx: u16,
920    pub abi_account_idx: u16,
921    pub authority_account_idx: u16,
922}
923
924/// ABI manager program CLOSE instruction arguments (matches C struct)
925#[repr(C, packed)]
926#[derive(Debug, Clone, Copy)]
927pub struct AbiManagerCloseArgs {
928    pub program_meta_account_idx: u16,
929    pub abi_account_idx: u16,
930    pub authority_account_idx: u16,
931}
932
933/// Manager program SET_PAUSE instruction arguments (matches C struct)
934#[repr(C, packed)]
935#[derive(Debug, Clone, Copy)]
936pub struct ManagerSetPauseArgs {
937    pub discriminant: u8,
938    pub meta_account_idx: u16,
939    pub program_account_idx: u16,
940    pub is_paused: u8,
941}
942
943/// Manager program SET_AUTHORITY instruction arguments (matches C struct)
944#[repr(C, packed)]
945#[derive(Debug, Clone, Copy)]
946pub struct ManagerSetAuthorityArgs {
947    pub discriminant: u8,
948    pub meta_account_idx: u16,
949    pub program_account_idx: u16,
950    pub authority_candidate: [u8; 32],
951}
952
953/// Test uploader program instruction discriminants (matches C defines)
954pub const TN_TEST_UPLOADER_PROGRAM_DISCRIMINANT_CREATE: u8 = 0x00;
955pub const TN_TEST_UPLOADER_PROGRAM_DISCRIMINANT_WRITE: u8 = 0x01;
956
957/// Test uploader program CREATE instruction arguments (matches C struct)
958#[repr(C, packed)]
959#[derive(Debug, Clone, Copy)]
960pub struct TestUploaderCreateArgs {
961    pub account_idx: u16,
962    pub is_ephemeral: u8,
963    pub account_sz: u32,
964    pub seed_len: u32,
965    // seed bytes follow, then optional state proof
966}
967
968/// Test uploader program WRITE instruction arguments (matches C struct)
969#[repr(C, packed)]
970#[derive(Debug, Clone, Copy)]
971pub struct TestUploaderWriteArgs {
972    pub target_account_idx: u16,
973    pub target_offset: u32,
974    pub data_len: u32,
975    // data bytes follow
976}
977
978/// System program DECOMPRESS2 instruction arguments (matches C struct)
979#[repr(C, packed)]
980#[derive(Debug, Clone, Copy)]
981pub struct SystemProgramDecompress2Args {
982    pub target_account_idx: u16,
983    pub meta_account_idx: u16,
984    pub data_account_idx: u16,
985    pub data_offset: u32,
986}
987
988impl TransactionBuilder {
989    /// Build uploader program CREATE transaction
990    pub fn build_uploader_create(
991        fee_payer: TnPubkey,
992        uploader_program: TnPubkey,
993        meta_account: TnPubkey,
994        buffer_account: TnPubkey,
995        buffer_size: u32,
996        expected_hash: [u8; 32],
997        seed: &[u8],
998        fee: u64,
999        nonce: u64,
1000        start_slot: u64,
1001    ) -> Result<Transaction> {
1002        // Account layout: [0: fee_payer, 1: uploader_program, 2: meta_account, 3: buffer_account]
1003        let authority_account_idx = 0u16;
1004
1005        let mut tx = Transaction::new(fee_payer, uploader_program, fee, nonce)
1006            .with_start_slot(start_slot)
1007            .with_expiry_after(10)
1008            .with_compute_units(50_000 + 2 * buffer_size as u32)
1009            .with_memory_units(10_000)
1010            .with_state_units(10_000);
1011
1012        let mut meta_account_idx = 2u16;
1013        let mut buffer_account_idx = 3u16;
1014        if meta_account > buffer_account {
1015            meta_account_idx = 3u16;
1016            buffer_account_idx = 2u16;
1017            tx = tx
1018                .add_rw_account(buffer_account)
1019                .add_rw_account(meta_account)
1020        } else {
1021            tx = tx
1022                .add_rw_account(meta_account)
1023                .add_rw_account(buffer_account)
1024        }
1025
1026        let instruction_data = build_uploader_create_instruction(
1027            buffer_account_idx,
1028            meta_account_idx,
1029            authority_account_idx,
1030            buffer_size,
1031            expected_hash,
1032            seed,
1033        )?;
1034
1035        tx = tx.with_instructions(instruction_data);
1036
1037        Ok(tx)
1038    }
1039
1040    /// Build uploader program WRITE transaction
1041    pub fn build_uploader_write(
1042        fee_payer: TnPubkey,
1043        uploader_program: TnPubkey,
1044        meta_account: TnPubkey,
1045        buffer_account: TnPubkey,
1046        data: &[u8],
1047        offset: u32,
1048        fee: u64,
1049        nonce: u64,
1050        start_slot: u64,
1051    ) -> Result<Transaction> {
1052        // Account layout: [0: fee_payer, 1: uploader_program, 2: meta_account, 3: buffer_account]
1053        let mut tx = Transaction::new(fee_payer, uploader_program, fee, nonce)
1054            .with_start_slot(start_slot)
1055            .with_expiry_after(10000)
1056            .with_compute_units(500_000_000)
1057            .with_memory_units(5000)
1058            .with_state_units(5000);
1059
1060        let mut meta_account_idx = 2u16;
1061        let mut buffer_account_idx = 3u16;
1062        if meta_account > buffer_account {
1063            meta_account_idx = 3u16;
1064            buffer_account_idx = 2u16;
1065            tx = tx
1066                .add_rw_account(buffer_account)
1067                .add_rw_account(meta_account)
1068        } else {
1069            tx = tx
1070                .add_rw_account(meta_account)
1071                .add_rw_account(buffer_account)
1072        }
1073
1074        let instruction_data =
1075            build_uploader_write_instruction(buffer_account_idx, meta_account_idx, data, offset)?;
1076
1077        tx = tx.with_instructions(instruction_data);
1078
1079        Ok(tx)
1080    }
1081
1082    /// Build uploader program FINALIZE transaction
1083    pub fn build_uploader_finalize(
1084        fee_payer: TnPubkey,
1085        uploader_program: TnPubkey,
1086        meta_account: TnPubkey,
1087        buffer_account: TnPubkey,
1088        buffer_size: u32,
1089        expected_hash: [u8; 32],
1090        fee: u64,
1091        nonce: u64,
1092        start_slot: u64,
1093    ) -> Result<Transaction> {
1094        let mut tx = Transaction::new(fee_payer, uploader_program, fee, nonce)
1095            .with_start_slot(start_slot)
1096            .with_expiry_after(10000)
1097            .with_compute_units(50_000 + 200 * buffer_size as u32)
1098            .with_memory_units(5000)
1099            .with_state_units(5000);
1100
1101        // Account layout: [0: fee_payer, 1: uploader_program, 2: meta_account, 3: buffer_account]
1102        let mut meta_account_idx = 2u16;
1103        let mut buffer_account_idx = 3u16;
1104        if meta_account > buffer_account {
1105            meta_account_idx = 3u16;
1106            buffer_account_idx = 2u16;
1107            tx = tx
1108                .add_rw_account(buffer_account)
1109                .add_rw_account(meta_account)
1110        } else {
1111            tx = tx
1112                .add_rw_account(meta_account)
1113                .add_rw_account(buffer_account)
1114        }
1115
1116        let instruction_data = build_uploader_finalize_instruction(
1117            buffer_account_idx,
1118            meta_account_idx,
1119            expected_hash,
1120        )?;
1121
1122        tx = tx.with_instructions(instruction_data);
1123
1124        Ok(tx)
1125    }
1126
1127    /// Build uploader program DESTROY transaction
1128    pub fn build_uploader_destroy(
1129        fee_payer: TnPubkey,
1130        uploader_program: TnPubkey,
1131        meta_account: TnPubkey,
1132        buffer_account: TnPubkey,
1133        fee: u64,
1134        nonce: u64,
1135        start_slot: u64,
1136    ) -> Result<Transaction> {
1137        let mut tx = Transaction::new(fee_payer, uploader_program, fee, nonce)
1138            .with_start_slot(start_slot)
1139            .with_expiry_after(10000)
1140            .with_compute_units(50000)
1141            .with_memory_units(5000)
1142            .with_state_units(5000);
1143
1144        // Account layout: [0: fee_payer, 1: uploader_program, 2: meta_account, 3: buffer_account]
1145        let mut meta_account_idx = 2u16;
1146        let mut buffer_account_idx = 3u16;
1147        if meta_account > buffer_account {
1148            meta_account_idx = 3u16;
1149            buffer_account_idx = 2u16;
1150            tx = tx
1151                .add_rw_account(buffer_account)
1152                .add_rw_account(meta_account)
1153        } else {
1154            tx = tx
1155                .add_rw_account(meta_account)
1156                .add_rw_account(buffer_account)
1157        }
1158
1159        let instruction_data =
1160            build_uploader_destroy_instruction(buffer_account_idx, meta_account_idx)?;
1161
1162        tx = tx.with_instructions(instruction_data);
1163        Ok(tx)
1164    }
1165
1166    /// Build manager program CREATE transaction
1167    pub fn build_manager_create(
1168        fee_payer: TnPubkey,
1169        manager_program: TnPubkey,
1170        meta_account: TnPubkey,
1171        program_account: TnPubkey,
1172        srcbuf_account: TnPubkey,
1173        authority_account: TnPubkey,
1174        srcbuf_offset: u32,
1175        srcbuf_size: u32,
1176        seed: &[u8],
1177        is_ephemeral: bool,
1178        meta_proof: Option<&[u8]>,
1179        program_proof: Option<&[u8]>,
1180        fee: u64,
1181        nonce: u64,
1182        start_slot: u64,
1183    ) -> Result<Transaction> {
1184        let mut tx = Transaction::new(fee_payer, manager_program, fee, nonce)
1185            .with_start_slot(start_slot)
1186            .with_expiry_after(10000)
1187            .with_compute_units(500_000_000)
1188            .with_memory_units(5000)
1189            .with_state_units(5000);
1190
1191        // Check if authority_account is the same as fee_payer
1192        let authority_is_fee_payer = authority_account == fee_payer;
1193
1194        // Separate accounts by access type and sort each group by pubkey
1195        let mut rw_accounts = vec![(meta_account, "meta"), (program_account, "program")];
1196
1197        let mut r_accounts = vec![(srcbuf_account, "srcbuf")];
1198
1199        // Only add authority_account if it's different from fee_payer
1200        if !authority_is_fee_payer {
1201            r_accounts.push((authority_account, "authority"));
1202        }
1203
1204        // Sort read-write accounts by pubkey
1205        rw_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1206
1207        // Sort read-only accounts by pubkey
1208        r_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1209
1210        // Combine sorted accounts: read-write first, then read-only
1211        let mut accounts = rw_accounts;
1212        accounts.extend(r_accounts);
1213
1214        let mut meta_account_idx = 0u16;
1215        let mut program_account_idx = 0u16;
1216        let mut srcbuf_account_idx = 0u16;
1217        let mut authority_account_idx = if authority_is_fee_payer {
1218            0u16 // Use fee_payer index (0) when authority is the same as fee_payer
1219        } else {
1220            0u16 // Will be set in the loop below
1221        };
1222
1223        for (i, (account, account_type)) in accounts.iter().enumerate() {
1224            let idx = (i + 2) as u16; // Skip fee_payer (0) and program (1)
1225            match *account_type {
1226                "meta" => {
1227                    meta_account_idx = idx;
1228                    tx = tx.add_rw_account(*account);
1229                }
1230                "program" => {
1231                    program_account_idx = idx;
1232                    tx = tx.add_rw_account(*account);
1233                }
1234                "srcbuf" => {
1235                    srcbuf_account_idx = idx;
1236                    tx = tx.add_r_account(*account);
1237                }
1238                "authority" => {
1239                    authority_account_idx = idx;
1240                    tx = tx.add_r_account(*account);
1241                }
1242                _ => unreachable!(),
1243            }
1244        }
1245
1246        let discriminant = if is_ephemeral {
1247            MANAGER_INSTRUCTION_CREATE_EPHEMERAL
1248        } else {
1249            MANAGER_INSTRUCTION_CREATE_PERMANENT
1250        };
1251
1252        // Concatenate proofs if both are provided (for permanent programs)
1253        let combined_proof = if let (Some(meta), Some(program)) = (meta_proof, program_proof) {
1254            let mut combined = Vec::with_capacity(meta.len() + program.len());
1255            combined.extend_from_slice(meta);
1256            combined.extend_from_slice(program);
1257            Some(combined)
1258        } else {
1259            None
1260        };
1261
1262        let instruction_data = build_manager_create_instruction(
1263            discriminant,
1264            meta_account_idx,
1265            program_account_idx,
1266            srcbuf_account_idx,
1267            authority_account_idx,
1268            srcbuf_offset,
1269            srcbuf_size,
1270            seed,
1271            combined_proof.as_deref(),
1272        )?;
1273
1274        tx = tx.with_instructions(instruction_data);
1275        Ok(tx)
1276    }
1277
1278    /// Build ABI manager program CREATE transaction
1279    #[allow(clippy::too_many_arguments)]
1280    pub fn build_abi_manager_create(
1281        fee_payer: TnPubkey,
1282        abi_manager_program: TnPubkey,
1283        program_meta_account: TnPubkey,
1284        abi_account: TnPubkey,
1285        srcbuf_account: TnPubkey,
1286        authority_account: TnPubkey,
1287        srcbuf_offset: u32,
1288        srcbuf_size: u32,
1289        seed: &[u8],
1290        is_ephemeral: bool,
1291        abi_proof: Option<&[u8]>,
1292        fee: u64,
1293        nonce: u64,
1294        start_slot: u64,
1295    ) -> Result<Transaction> {
1296        let mut tx = Transaction::new(fee_payer, abi_manager_program, fee, nonce)
1297            .with_start_slot(start_slot)
1298            .with_expiry_after(10000)
1299            .with_compute_units(500_000_000)
1300            .with_memory_units(5000)
1301            .with_state_units(5000);
1302
1303        let authority_is_fee_payer = authority_account == fee_payer;
1304
1305        let mut rw_accounts = vec![(program_meta_account, "meta"), (abi_account, "abi")];
1306
1307        let mut r_accounts = vec![(srcbuf_account, "srcbuf")];
1308
1309        if !authority_is_fee_payer {
1310            r_accounts.push((authority_account, "authority"));
1311        }
1312
1313        rw_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1314        r_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1315
1316        let mut accounts = rw_accounts;
1317        accounts.extend(r_accounts);
1318
1319        let mut program_meta_account_idx = 0u16;
1320        let mut abi_account_idx = 0u16;
1321        let mut srcbuf_account_idx = 0u16;
1322        let mut authority_account_idx = 0u16;
1323
1324        for (i, (account, account_type)) in accounts.iter().enumerate() {
1325            let idx = (i + 2) as u16;
1326            match *account_type {
1327                "meta" => {
1328                    program_meta_account_idx = idx;
1329                    tx = tx.add_rw_account(*account);
1330                }
1331                "abi" => {
1332                    abi_account_idx = idx;
1333                    tx = tx.add_rw_account(*account);
1334                }
1335                "srcbuf" => {
1336                    srcbuf_account_idx = idx;
1337                    tx = tx.add_r_account(*account);
1338                }
1339                "authority" => {
1340                    authority_account_idx = idx;
1341                    tx = tx.add_r_account(*account);
1342                }
1343                _ => unreachable!(),
1344            }
1345        }
1346
1347        let discriminant = if is_ephemeral {
1348            ABI_MANAGER_INSTRUCTION_CREATE_EPHEMERAL
1349        } else {
1350            ABI_MANAGER_INSTRUCTION_CREATE_PERMANENT
1351        };
1352
1353        let instruction_data = build_abi_manager_create_instruction(
1354            discriminant,
1355            program_meta_account_idx,
1356            abi_account_idx,
1357            srcbuf_account_idx,
1358            srcbuf_offset,
1359            srcbuf_size,
1360            authority_account_idx,
1361            seed,
1362            abi_proof,
1363        )?;
1364
1365        tx = tx.with_instructions(instruction_data);
1366        Ok(tx)
1367    }
1368
1369    /// Build ABI manager program UPGRADE transaction
1370    #[allow(clippy::too_many_arguments)]
1371    pub fn build_abi_manager_upgrade(
1372        fee_payer: TnPubkey,
1373        abi_manager_program: TnPubkey,
1374        program_meta_account: TnPubkey,
1375        abi_account: TnPubkey,
1376        srcbuf_account: TnPubkey,
1377        authority_account: TnPubkey,
1378        srcbuf_offset: u32,
1379        srcbuf_size: u32,
1380        fee: u64,
1381        nonce: u64,
1382        start_slot: u64,
1383    ) -> Result<Transaction> {
1384        let mut tx = Transaction::new(fee_payer, abi_manager_program, fee, nonce)
1385            .with_start_slot(start_slot)
1386            .with_expiry_after(10000)
1387            .with_compute_units(500_000_000)
1388            .with_memory_units(5000)
1389            .with_state_units(5000);
1390
1391        let authority_is_fee_payer = authority_account == fee_payer;
1392
1393        let mut rw_accounts = vec![(program_meta_account, "meta"), (abi_account, "abi")];
1394        let mut r_accounts = vec![(srcbuf_account, "srcbuf")];
1395
1396        if !authority_is_fee_payer {
1397            r_accounts.push((authority_account, "authority"));
1398        }
1399
1400        rw_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1401        r_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1402
1403        let mut accounts = rw_accounts;
1404        accounts.extend(r_accounts);
1405
1406        let mut program_meta_account_idx = 0u16;
1407        let mut abi_account_idx = 0u16;
1408        let mut srcbuf_account_idx = 0u16;
1409        let mut authority_account_idx = 0u16;
1410
1411        for (i, (account, account_type)) in accounts.iter().enumerate() {
1412            let idx = (i + 2) as u16;
1413            match *account_type {
1414                "meta" => {
1415                    program_meta_account_idx = idx;
1416                    tx = tx.add_rw_account(*account);
1417                }
1418                "abi" => {
1419                    abi_account_idx = idx;
1420                    tx = tx.add_rw_account(*account);
1421                }
1422                "srcbuf" => {
1423                    srcbuf_account_idx = idx;
1424                    tx = tx.add_r_account(*account);
1425                }
1426                "authority" => {
1427                    authority_account_idx = idx;
1428                    tx = tx.add_r_account(*account);
1429                }
1430                _ => unreachable!(),
1431            }
1432        }
1433
1434        let authority_idx = if authority_is_fee_payer {
1435            0u16
1436        } else {
1437            authority_account_idx
1438        };
1439
1440        let instruction_data = build_abi_manager_upgrade_instruction(
1441            program_meta_account_idx,
1442            abi_account_idx,
1443            srcbuf_account_idx,
1444            srcbuf_offset,
1445            srcbuf_size,
1446            authority_idx,
1447        )?;
1448
1449        tx = tx.with_instructions(instruction_data);
1450        Ok(tx)
1451    }
1452
1453    /// Build ABI manager program FINALIZE transaction
1454    pub fn build_abi_manager_finalize(
1455        fee_payer: TnPubkey,
1456        abi_manager_program: TnPubkey,
1457        program_meta_account: TnPubkey,
1458        abi_account: TnPubkey,
1459        authority_account: TnPubkey,
1460        fee: u64,
1461        nonce: u64,
1462        start_slot: u64,
1463    ) -> Result<Transaction> {
1464        let mut tx = Transaction::new(fee_payer, abi_manager_program, fee, nonce)
1465            .with_start_slot(start_slot)
1466            .with_expiry_after(10000)
1467            .with_compute_units(500_000_000)
1468            .with_memory_units(5000)
1469            .with_state_units(5000);
1470
1471        let authority_is_fee_payer = authority_account == fee_payer;
1472
1473        let mut rw_accounts = vec![(program_meta_account, "meta"), (abi_account, "abi")];
1474        let mut r_accounts = Vec::new();
1475
1476        if !authority_is_fee_payer {
1477            r_accounts.push((authority_account, "authority"));
1478        }
1479
1480        rw_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1481        r_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1482
1483        let mut accounts = rw_accounts;
1484        accounts.extend(r_accounts);
1485
1486        let mut program_meta_account_idx = 0u16;
1487        let mut abi_account_idx = 0u16;
1488        let mut authority_account_idx = 0u16;
1489
1490        for (i, (account, account_type)) in accounts.iter().enumerate() {
1491            let idx = (i + 2) as u16;
1492            match *account_type {
1493                "meta" => {
1494                    program_meta_account_idx = idx;
1495                    tx = tx.add_rw_account(*account);
1496                }
1497                "abi" => {
1498                    abi_account_idx = idx;
1499                    tx = tx.add_rw_account(*account);
1500                }
1501                "authority" => {
1502                    authority_account_idx = idx;
1503                    tx = tx.add_r_account(*account);
1504                }
1505                _ => unreachable!(),
1506            }
1507        }
1508
1509        let authority_idx = if authority_is_fee_payer {
1510            0u16
1511        } else {
1512            authority_account_idx
1513        };
1514
1515        let instruction_data = build_abi_manager_finalize_instruction(
1516            program_meta_account_idx,
1517            abi_account_idx,
1518            authority_idx,
1519        )?;
1520
1521        tx = tx.with_instructions(instruction_data);
1522        Ok(tx)
1523    }
1524
1525    /// Build ABI manager program CLOSE transaction
1526    pub fn build_abi_manager_close(
1527        fee_payer: TnPubkey,
1528        abi_manager_program: TnPubkey,
1529        program_meta_account: TnPubkey,
1530        abi_account: TnPubkey,
1531        authority_account: TnPubkey,
1532        fee: u64,
1533        nonce: u64,
1534        start_slot: u64,
1535    ) -> Result<Transaction> {
1536        let mut tx = Transaction::new(fee_payer, abi_manager_program, fee, nonce)
1537            .with_start_slot(start_slot)
1538            .with_expiry_after(10000)
1539            .with_compute_units(500_000_000)
1540            .with_memory_units(5000)
1541            .with_state_units(5000);
1542
1543        let authority_is_fee_payer = authority_account == fee_payer;
1544
1545        let mut rw_accounts = vec![(program_meta_account, "meta"), (abi_account, "abi")];
1546        let mut r_accounts = Vec::new();
1547
1548        if !authority_is_fee_payer {
1549            r_accounts.push((authority_account, "authority"));
1550        }
1551
1552        rw_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1553        r_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1554
1555        let mut accounts = rw_accounts;
1556        accounts.extend(r_accounts);
1557
1558        let mut program_meta_account_idx = 0u16;
1559        let mut abi_account_idx = 0u16;
1560        let mut authority_account_idx = 0u16;
1561
1562        for (i, (account, account_type)) in accounts.iter().enumerate() {
1563            let idx = (i + 2) as u16;
1564            match *account_type {
1565                "meta" => {
1566                    program_meta_account_idx = idx;
1567                    tx = tx.add_rw_account(*account);
1568                }
1569                "abi" => {
1570                    abi_account_idx = idx;
1571                    tx = tx.add_rw_account(*account);
1572                }
1573                "authority" => {
1574                    authority_account_idx = idx;
1575                    tx = tx.add_r_account(*account);
1576                }
1577                _ => unreachable!(),
1578            }
1579        }
1580
1581        let authority_idx = if authority_is_fee_payer {
1582            0u16
1583        } else {
1584            authority_account_idx
1585        };
1586
1587        let instruction_data = build_abi_manager_close_instruction(
1588            program_meta_account_idx,
1589            abi_account_idx,
1590            authority_idx,
1591        )?;
1592
1593        tx = tx.with_instructions(instruction_data);
1594        Ok(tx)
1595    }
1596
1597    /// Build manager program UPGRADE transaction
1598    pub fn build_manager_upgrade(
1599        fee_payer: TnPubkey,
1600        manager_program: TnPubkey,
1601        meta_account: TnPubkey,
1602        program_account: TnPubkey,
1603        srcbuf_account: TnPubkey,
1604        srcbuf_offset: u32,
1605        srcbuf_size: u32,
1606        fee: u64,
1607        nonce: u64,
1608        start_slot: u64,
1609    ) -> Result<Transaction> {
1610        let mut tx = Transaction::new(fee_payer, manager_program, fee, nonce)
1611            .with_start_slot(start_slot)
1612            .with_expiry_after(10000)
1613            .with_compute_units(500_000_000)
1614            .with_memory_units(5000)
1615            .with_state_units(5000);
1616
1617        // Separate accounts by access type and sort each group by pubkey
1618        let mut rw_accounts = vec![(meta_account, "meta"), (program_account, "program")];
1619
1620        let mut r_accounts = vec![(srcbuf_account, "srcbuf")];
1621
1622        // Sort read-write accounts by pubkey
1623        rw_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1624
1625        // Sort read-only accounts by pubkey
1626        r_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1627
1628        // Combine sorted accounts: read-write first, then read-only
1629        let mut accounts = rw_accounts;
1630        accounts.extend(r_accounts);
1631
1632        let mut meta_account_idx = 0u16;
1633        let mut program_account_idx = 0u16;
1634        let mut srcbuf_account_idx = 0u16;
1635
1636        for (i, (account, account_type)) in accounts.iter().enumerate() {
1637            let idx = (i + 2) as u16; // Skip fee_payer (0) and program (1)
1638            match *account_type {
1639                "meta" => {
1640                    meta_account_idx = idx;
1641                    tx = tx.add_rw_account(*account);
1642                }
1643                "program" => {
1644                    program_account_idx = idx;
1645                    tx = tx.add_rw_account(*account);
1646                }
1647                "srcbuf" => {
1648                    srcbuf_account_idx = idx;
1649                    tx = tx.add_r_account(*account);
1650                }
1651                _ => unreachable!(),
1652            }
1653        }
1654
1655        let instruction_data = build_manager_upgrade_instruction(
1656            meta_account_idx,
1657            program_account_idx,
1658            srcbuf_account_idx,
1659            srcbuf_offset,
1660            srcbuf_size,
1661        )?;
1662
1663        tx = tx.with_instructions(instruction_data);
1664        Ok(tx)
1665    }
1666
1667    /// Build manager program SET_PAUSE transaction
1668    pub fn build_manager_set_pause(
1669        fee_payer: TnPubkey,
1670        manager_program: TnPubkey,
1671        meta_account: TnPubkey,
1672        program_account: TnPubkey,
1673        is_paused: bool,
1674        fee: u64,
1675        nonce: u64,
1676        start_slot: u64,
1677    ) -> Result<Transaction> {
1678        let mut tx = Transaction::new(fee_payer, manager_program, fee, nonce)
1679            .with_start_slot(start_slot)
1680            .with_expiry_after(10000)
1681            .with_compute_units(100_000_000)
1682            .with_memory_units(5000)
1683            .with_state_units(5000);
1684
1685        // Add accounts in sorted order
1686        let mut accounts = vec![(meta_account, "meta"), (program_account, "program")];
1687        accounts.sort_by(|a, b| a.0.cmp(&b.0));
1688
1689        let mut meta_account_idx = 0u16;
1690        let mut program_account_idx = 0u16;
1691
1692        for (i, (account, account_type)) in accounts.iter().enumerate() {
1693            let idx = (i + 2) as u16;
1694            match *account_type {
1695                "meta" => {
1696                    meta_account_idx = idx;
1697                    tx = tx.add_rw_account(*account);
1698                }
1699                "program" => {
1700                    program_account_idx = idx;
1701                    tx = tx.add_rw_account(*account);
1702                }
1703                _ => unreachable!(),
1704            }
1705        }
1706
1707        let instruction_data =
1708            build_manager_set_pause_instruction(meta_account_idx, program_account_idx, is_paused)?;
1709
1710        tx = tx.with_instructions(instruction_data);
1711        Ok(tx)
1712    }
1713
1714    /// Build manager program simple transactions (DESTROY, FINALIZE, CLAIM_AUTHORITY)
1715    pub fn build_manager_simple(
1716        fee_payer: TnPubkey,
1717        manager_program: TnPubkey,
1718        meta_account: TnPubkey,
1719        program_account: TnPubkey,
1720        instruction_type: u8,
1721        fee: u64,
1722        nonce: u64,
1723        start_slot: u64,
1724    ) -> Result<Transaction> {
1725        let mut tx = Transaction::new(fee_payer, manager_program, fee, nonce)
1726            .with_start_slot(start_slot)
1727            .with_expiry_after(10000)
1728            .with_compute_units(100_000_000)
1729            .with_memory_units(5000)
1730            .with_state_units(5000);
1731
1732        // Add accounts in sorted order
1733        let mut accounts = vec![(meta_account, "meta"), (program_account, "program")];
1734        accounts.sort_by(|a, b| a.0.cmp(&b.0));
1735
1736        let mut meta_account_idx = 0u16;
1737        let mut program_account_idx = 0u16;
1738
1739        for (i, (account, account_type)) in accounts.iter().enumerate() {
1740            let idx = (i + 2) as u16;
1741            match *account_type {
1742                "meta" => {
1743                    meta_account_idx = idx;
1744                    tx = tx.add_rw_account(*account);
1745                }
1746                "program" => {
1747                    program_account_idx = idx;
1748                    tx = tx.add_rw_account(*account);
1749                }
1750                _ => unreachable!(),
1751            }
1752        }
1753
1754        let instruction_data = build_manager_header_instruction(
1755            instruction_type,
1756            meta_account_idx,
1757            program_account_idx,
1758        )?;
1759
1760        tx = tx.with_instructions(instruction_data);
1761        Ok(tx)
1762    }
1763
1764    /// Build manager program SET_AUTHORITY transaction
1765    pub fn build_manager_set_authority(
1766        fee_payer: TnPubkey,
1767        manager_program: TnPubkey,
1768        meta_account: TnPubkey,
1769        program_account: TnPubkey,
1770        authority_candidate: [u8; 32],
1771        fee: u64,
1772        nonce: u64,
1773        start_slot: u64,
1774    ) -> Result<Transaction> {
1775        let mut tx = Transaction::new(fee_payer, manager_program, fee, nonce)
1776            .with_start_slot(start_slot)
1777            .with_expiry_after(10000)
1778            .with_compute_units(100_000_000)
1779            .with_memory_units(5000)
1780            .with_state_units(5000);
1781
1782        // Add accounts in sorted order
1783        let mut accounts = vec![(meta_account, "meta"), (program_account, "program")];
1784        accounts.sort_by(|a, b| a.0.cmp(&b.0));
1785
1786        let mut meta_account_idx = 0u16;
1787        let mut program_account_idx = 0u16;
1788
1789        for (i, (account, account_type)) in accounts.iter().enumerate() {
1790            let idx = (i + 2) as u16;
1791            match *account_type {
1792                "meta" => {
1793                    meta_account_idx = idx;
1794                    tx = tx.add_rw_account(*account);
1795                }
1796                "program" => {
1797                    program_account_idx = idx;
1798                    tx = tx.add_rw_account(*account);
1799                }
1800                _ => unreachable!(),
1801            }
1802        }
1803
1804        let instruction_data = build_manager_set_authority_instruction(
1805            meta_account_idx,
1806            program_account_idx,
1807            authority_candidate,
1808        )?;
1809
1810        tx = tx.with_instructions(instruction_data);
1811        Ok(tx)
1812    }
1813
1814    /// Build test uploader program CREATE transaction
1815    pub fn build_test_uploader_create(
1816        fee_payer: TnPubkey,
1817        test_uploader_program: TnPubkey,
1818        target_account: TnPubkey,
1819        account_sz: u32,
1820        seed: &[u8],
1821        is_ephemeral: bool,
1822        state_proof: Option<&[u8]>,
1823        fee: u64,
1824        nonce: u64,
1825        start_slot: u64,
1826    ) -> Result<Transaction> {
1827        // Account layout: [0: fee_payer, 1: test_uploader_program, 2: target_account]
1828        let target_account_idx = 2u16;
1829
1830        let tx = Transaction::new(fee_payer, test_uploader_program, fee, nonce)
1831            .with_start_slot(start_slot)
1832            .with_expiry_after(100)
1833            .with_compute_units(100_000 + account_sz)
1834            .with_memory_units(10_000)
1835            .with_state_units(10_000)
1836            .add_rw_account(target_account);
1837
1838        let instruction_data = build_test_uploader_create_instruction(
1839            target_account_idx,
1840            account_sz,
1841            seed,
1842            is_ephemeral,
1843            state_proof,
1844        )?;
1845
1846        let tx = tx.with_instructions(instruction_data);
1847        Ok(tx)
1848    }
1849
1850    /// Build test uploader program WRITE transaction
1851    pub fn build_test_uploader_write(
1852        fee_payer: TnPubkey,
1853        test_uploader_program: TnPubkey,
1854        target_account: TnPubkey,
1855        offset: u32,
1856        data: &[u8],
1857        fee: u64,
1858        nonce: u64,
1859        start_slot: u64,
1860    ) -> Result<Transaction> {
1861        // Account layout: [0: fee_payer, 1: test_uploader_program, 2: target_account]
1862        let target_account_idx = 2u16;
1863
1864        let tx = Transaction::new(fee_payer, test_uploader_program, fee, nonce)
1865            .with_start_slot(start_slot)
1866            .with_expiry_after(10_000)
1867            .with_compute_units(100_000 + 18 * data.len() as u32)
1868            .with_memory_units(10_000)
1869            .with_state_units(10_000)
1870            .add_rw_account(target_account);
1871
1872        let instruction_data =
1873            build_test_uploader_write_instruction(target_account_idx, offset, data)?;
1874
1875        let tx = tx.with_instructions(instruction_data);
1876        Ok(tx)
1877    }
1878
1879    /// Build account decompression transaction using DECOMPRESS2 (separate meta and data accounts)
1880    pub fn build_decompress2(
1881        fee_payer: TnPubkey,
1882        program: TnPubkey,
1883        target_account: TnPubkey,
1884        meta_account: TnPubkey,
1885        data_account: TnPubkey,
1886        data_offset: u32,
1887        state_proof: &[u8],
1888        fee: u64,
1889        nonce: u64,
1890        start_slot: u64,
1891        data_sz: u32,
1892    ) -> Result<Transaction> {
1893        // Account layout: [0: fee_payer, 1: program, 2: target_account, 3+: meta/data accounts]
1894        let mut tx = Transaction::new(fee_payer, program, fee, nonce)
1895            .with_start_slot(start_slot)
1896            .with_expiry_after(100)
1897            .with_compute_units(10_000 + 2 * data_sz)
1898            .with_memory_units(10_000)
1899            .with_state_units(10);
1900
1901        // Add target account (read-write)
1902        let target_account_idx = 2u16;
1903        tx = tx.add_rw_account(target_account);
1904
1905        let mut meta_account_idx = 0u16;
1906        let mut data_account_idx = 0u16;
1907
1908        // Handle meta and data accounts - if they're the same, add only once; if different, add both and sort
1909        if meta_account == data_account {
1910            // Same account for both meta and data
1911            let account_idx = 3u16;
1912            meta_account_idx = account_idx;
1913            data_account_idx = account_idx;
1914            tx = tx.add_r_account(meta_account);
1915        } else {
1916            // Different accounts - add both and sort by pubkey
1917            let mut read_accounts = vec![(meta_account, "meta"), (data_account, "data")];
1918            read_accounts.sort_by(|a, b| a.0.cmp(&b.0));
1919
1920            for (i, (account, account_type)) in read_accounts.iter().enumerate() {
1921                let idx = (3 + i) as u16; // Start from index 3
1922                match *account_type {
1923                    "meta" => {
1924                        meta_account_idx = idx;
1925                        tx = tx.add_r_account(*account);
1926                    }
1927                    "data" => {
1928                        data_account_idx = idx;
1929                        tx = tx.add_r_account(*account);
1930                    }
1931                    _ => unreachable!(),
1932                }
1933            }
1934        }
1935
1936        let instruction_data = build_decompress2_instruction(
1937            target_account_idx,
1938            meta_account_idx,
1939            data_account_idx,
1940            data_offset,
1941            state_proof,
1942        )?;
1943
1944        tx = tx.with_instructions(instruction_data);
1945        Ok(tx)
1946    }
1947}
1948
1949/// Build uploader CREATE instruction data
1950fn build_uploader_create_instruction(
1951    buffer_account_idx: u16,
1952    meta_account_idx: u16,
1953    authority_account_idx: u16,
1954    buffer_size: u32,
1955    expected_hash: [u8; 32],
1956    seed: &[u8],
1957) -> Result<Vec<u8>> {
1958    let mut instruction = Vec::new();
1959
1960    // Discriminant (4 bytes, little-endian)
1961    instruction.extend_from_slice(&TN_UPLOADER_PROGRAM_INSTRUCTION_CREATE.to_le_bytes());
1962
1963    // Create args struct
1964    let args = UploaderCreateArgs {
1965        buffer_account_idx,
1966        meta_account_idx,
1967        authority_account_idx,
1968        buffer_account_sz: buffer_size,
1969        expected_account_hash: expected_hash,
1970        seed_len: seed.len() as u32,
1971    };
1972
1973    // Serialize args (unsafe due to packed struct)
1974    let args_bytes = unsafe {
1975        std::slice::from_raw_parts(
1976            &args as *const _ as *const u8,
1977            std::mem::size_of::<UploaderCreateArgs>(),
1978        )
1979    };
1980    instruction.extend_from_slice(args_bytes);
1981
1982    // Append seed bytes
1983    instruction.extend_from_slice(seed);
1984
1985    Ok(instruction)
1986}
1987
1988/// Build uploader WRITE instruction data
1989fn build_uploader_write_instruction(
1990    buffer_account_idx: u16,
1991    meta_account_idx: u16,
1992    data: &[u8],
1993    offset: u32,
1994) -> Result<Vec<u8>> {
1995    let mut instruction = Vec::new();
1996
1997    // Discriminant (4 bytes, little-endian)
1998    instruction.extend_from_slice(&TN_UPLOADER_PROGRAM_INSTRUCTION_WRITE.to_le_bytes());
1999
2000    // Write args
2001    let args = UploaderWriteArgs {
2002        buffer_account_idx,
2003        meta_account_idx,
2004        data_len: data.len() as u32,
2005        data_offset: offset,
2006    };
2007
2008    // Serialize args
2009    let args_bytes = unsafe {
2010        std::slice::from_raw_parts(
2011            &args as *const _ as *const u8,
2012            std::mem::size_of::<UploaderWriteArgs>(),
2013        )
2014    };
2015    instruction.extend_from_slice(args_bytes);
2016
2017    // Append data
2018    instruction.extend_from_slice(data);
2019
2020    Ok(instruction)
2021}
2022
2023/// Build uploader FINALIZE instruction data
2024fn build_uploader_finalize_instruction(
2025    buffer_account_idx: u16,
2026    meta_account_idx: u16,
2027    expected_hash: [u8; 32],
2028) -> Result<Vec<u8>> {
2029    let mut instruction = Vec::new();
2030
2031    // Discriminant (4 bytes, little-endian)
2032    instruction.extend_from_slice(&TN_UPLOADER_PROGRAM_INSTRUCTION_FINALIZE.to_le_bytes());
2033
2034    // Finalize args
2035    let args = UploaderFinalizeArgs {
2036        buffer_account_idx,
2037        meta_account_idx,
2038        expected_account_hash: expected_hash,
2039    };
2040
2041    // Serialize args
2042    let args_bytes = unsafe {
2043        std::slice::from_raw_parts(
2044            &args as *const _ as *const u8,
2045            std::mem::size_of::<UploaderFinalizeArgs>(),
2046        )
2047    };
2048    instruction.extend_from_slice(args_bytes);
2049
2050    Ok(instruction)
2051}
2052
2053/// Build uploader DESTROY instruction data
2054fn build_uploader_destroy_instruction(
2055    buffer_account_idx: u16,
2056    meta_account_idx: u16,
2057) -> Result<Vec<u8>> {
2058    let mut instruction = Vec::new();
2059
2060    // Discriminant (4 bytes, little-endian)
2061    instruction.extend_from_slice(&TN_UPLOADER_PROGRAM_INSTRUCTION_DESTROY.to_le_bytes());
2062
2063    // Destroy args
2064    let args = UploaderDestroyArgs {
2065        buffer_account_idx,
2066        meta_account_idx,
2067    };
2068
2069    // Serialize args
2070    let args_bytes = unsafe {
2071        std::slice::from_raw_parts(
2072            &args as *const _ as *const u8,
2073            std::mem::size_of::<UploaderDestroyArgs>(),
2074        )
2075    };
2076    instruction.extend_from_slice(args_bytes);
2077
2078    Ok(instruction)
2079}
2080
2081/// Build manager CREATE instruction data
2082fn build_manager_create_instruction(
2083    discriminant: u8,
2084    meta_account_idx: u16,
2085    program_account_idx: u16,
2086    srcbuf_account_idx: u16,
2087    authority_account_idx: u16,
2088    srcbuf_offset: u32,
2089    srcbuf_size: u32,
2090    seed: &[u8],
2091    proof: Option<&[u8]>,
2092) -> Result<Vec<u8>> {
2093    let mut instruction = Vec::new();
2094
2095    // Create args
2096    let args = ManagerCreateArgs {
2097        discriminant,
2098        meta_account_idx,
2099        program_account_idx,
2100        srcbuf_account_idx,
2101        srcbuf_offset,
2102        srcbuf_size,
2103        authority_account_idx,
2104        seed_len: seed.len() as u32,
2105    };
2106
2107    // Serialize args
2108    let args_bytes = unsafe {
2109        std::slice::from_raw_parts(
2110            &args as *const ManagerCreateArgs as *const u8,
2111            std::mem::size_of::<ManagerCreateArgs>(),
2112        )
2113    };
2114    instruction.extend_from_slice(args_bytes);
2115
2116    // Add seed bytes
2117    instruction.extend_from_slice(seed);
2118
2119    // Add proof bytes (only for permanent accounts)
2120    if let Some(proof_bytes) = proof {
2121        instruction.extend_from_slice(proof_bytes);
2122    }
2123
2124    Ok(instruction)
2125}
2126
2127/// Build ABI manager CREATE instruction data
2128fn build_abi_manager_create_instruction(
2129    discriminant: u8,
2130    program_meta_account_idx: u16,
2131    abi_account_idx: u16,
2132    srcbuf_account_idx: u16,
2133    srcbuf_offset: u32,
2134    srcbuf_size: u32,
2135    authority_account_idx: u16,
2136    seed: &[u8],
2137    proof: Option<&[u8]>,
2138) -> Result<Vec<u8>> {
2139    let mut instruction = Vec::new();
2140
2141    // Prepare 32-byte seed: pad with zeros or hash if too long
2142    let mut seed_bytes = [0u8; 32];
2143    if seed.len() <= 32 {
2144        seed_bytes[..seed.len()].copy_from_slice(seed);
2145    } else {
2146        // Hash the seed if it's longer than 32 bytes
2147        use sha2::{Digest, Sha256};
2148        let hash = Sha256::digest(seed);
2149        seed_bytes.copy_from_slice(&hash);
2150    }
2151
2152    // Write discriminant byte first
2153    instruction.push(discriminant);
2154
2155    let args = AbiManagerCreateArgs {
2156        program_meta_account_idx,
2157        abi_account_idx,
2158        srcbuf_account_idx,
2159        srcbuf_offset,
2160        srcbuf_size,
2161        authority_account_idx,
2162        seed: seed_bytes,
2163    };
2164
2165    let args_bytes = unsafe {
2166        std::slice::from_raw_parts(
2167            &args as *const AbiManagerCreateArgs as *const u8,
2168            std::mem::size_of::<AbiManagerCreateArgs>(),
2169        )
2170    };
2171
2172    instruction.extend_from_slice(args_bytes);
2173    // Note: seed is now part of the struct, no need to append separately
2174
2175    if let Some(proof_bytes) = proof {
2176        instruction.extend_from_slice(proof_bytes);
2177    }
2178
2179    Ok(instruction)
2180}
2181
2182fn build_abi_manager_upgrade_instruction(
2183    program_meta_account_idx: u16,
2184    abi_account_idx: u16,
2185    srcbuf_account_idx: u16,
2186    srcbuf_offset: u32,
2187    srcbuf_size: u32,
2188    authority_account_idx: u16,
2189) -> Result<Vec<u8>> {
2190    let mut instruction = Vec::new();
2191
2192    // Write discriminant byte first
2193    instruction.push(ABI_MANAGER_INSTRUCTION_UPGRADE);
2194
2195    let args = AbiManagerUpgradeArgs {
2196        program_meta_account_idx,
2197        abi_account_idx,
2198        srcbuf_account_idx,
2199        srcbuf_offset,
2200        srcbuf_size,
2201        authority_account_idx,
2202    };
2203
2204    let args_bytes = unsafe {
2205        std::slice::from_raw_parts(
2206            &args as *const AbiManagerUpgradeArgs as *const u8,
2207            std::mem::size_of::<AbiManagerUpgradeArgs>(),
2208        )
2209    };
2210
2211    instruction.extend_from_slice(args_bytes);
2212    Ok(instruction)
2213}
2214
2215fn build_abi_manager_finalize_instruction(
2216    program_meta_account_idx: u16,
2217    abi_account_idx: u16,
2218    authority_account_idx: u16,
2219) -> Result<Vec<u8>> {
2220    let mut instruction = Vec::new();
2221
2222    // Write discriminant byte first
2223    instruction.push(ABI_MANAGER_INSTRUCTION_FINALIZE);
2224
2225    let args = AbiManagerFinalizeArgs {
2226        program_meta_account_idx,
2227        abi_account_idx,
2228        authority_account_idx,
2229    };
2230
2231    let args_bytes = unsafe {
2232        std::slice::from_raw_parts(
2233            &args as *const AbiManagerFinalizeArgs as *const u8,
2234            std::mem::size_of::<AbiManagerFinalizeArgs>(),
2235        )
2236    };
2237
2238    instruction.extend_from_slice(args_bytes);
2239    Ok(instruction)
2240}
2241
2242fn build_abi_manager_close_instruction(
2243    program_meta_account_idx: u16,
2244    abi_account_idx: u16,
2245    authority_account_idx: u16,
2246) -> Result<Vec<u8>> {
2247    let mut instruction = Vec::new();
2248
2249    // Write discriminant byte first
2250    instruction.push(ABI_MANAGER_INSTRUCTION_CLOSE);
2251
2252    let args = AbiManagerCloseArgs {
2253        program_meta_account_idx,
2254        abi_account_idx,
2255        authority_account_idx,
2256    };
2257
2258    let args_bytes = unsafe {
2259        std::slice::from_raw_parts(
2260            &args as *const AbiManagerCloseArgs as *const u8,
2261            std::mem::size_of::<AbiManagerCloseArgs>(),
2262        )
2263    };
2264
2265    instruction.extend_from_slice(args_bytes);
2266    Ok(instruction)
2267}
2268
2269/// Build manager UPGRADE instruction data
2270fn build_manager_upgrade_instruction(
2271    meta_account_idx: u16,
2272    program_account_idx: u16,
2273    srcbuf_account_idx: u16,
2274    srcbuf_offset: u32,
2275    srcbuf_size: u32,
2276) -> Result<Vec<u8>> {
2277    let mut instruction = Vec::new();
2278
2279    let args = ManagerUpgradeArgs {
2280        discriminant: MANAGER_INSTRUCTION_UPGRADE,
2281        meta_account_idx,
2282        program_account_idx,
2283        srcbuf_account_idx,
2284        srcbuf_offset,
2285        srcbuf_size,
2286    };
2287
2288    let args_bytes = unsafe {
2289        std::slice::from_raw_parts(
2290            &args as *const ManagerUpgradeArgs as *const u8,
2291            std::mem::size_of::<ManagerUpgradeArgs>(),
2292        )
2293    };
2294    instruction.extend_from_slice(args_bytes);
2295
2296    Ok(instruction)
2297}
2298
2299/// Build manager SET_PAUSE instruction data
2300fn build_manager_set_pause_instruction(
2301    meta_account_idx: u16,
2302    program_account_idx: u16,
2303    is_paused: bool,
2304) -> Result<Vec<u8>> {
2305    let mut instruction = Vec::new();
2306
2307    let args = ManagerSetPauseArgs {
2308        discriminant: MANAGER_INSTRUCTION_SET_PAUSE,
2309        meta_account_idx,
2310        program_account_idx,
2311        is_paused: if is_paused { 1 } else { 0 },
2312    };
2313
2314    let args_bytes = unsafe {
2315        std::slice::from_raw_parts(
2316            &args as *const ManagerSetPauseArgs as *const u8,
2317            std::mem::size_of::<ManagerSetPauseArgs>(),
2318        )
2319    };
2320    instruction.extend_from_slice(args_bytes);
2321
2322    Ok(instruction)
2323}
2324
2325/// Build manager header-only instruction data (DESTROY, FINALIZE, CLAIM_AUTHORITY)
2326fn build_manager_header_instruction(
2327    discriminant: u8,
2328    meta_account_idx: u16,
2329    program_account_idx: u16,
2330) -> Result<Vec<u8>> {
2331    let mut instruction = Vec::new();
2332
2333    let args = ManagerHeaderArgs {
2334        discriminant,
2335        meta_account_idx,
2336        program_account_idx,
2337    };
2338
2339    let args_bytes = unsafe {
2340        std::slice::from_raw_parts(
2341            &args as *const ManagerHeaderArgs as *const u8,
2342            std::mem::size_of::<ManagerHeaderArgs>(),
2343        )
2344    };
2345    instruction.extend_from_slice(args_bytes);
2346
2347    Ok(instruction)
2348}
2349
2350/// Build manager SET_AUTHORITY instruction data
2351fn build_manager_set_authority_instruction(
2352    meta_account_idx: u16,
2353    program_account_idx: u16,
2354    authority_candidate: [u8; 32],
2355) -> Result<Vec<u8>> {
2356    let mut instruction = Vec::new();
2357
2358    let args = ManagerSetAuthorityArgs {
2359        discriminant: MANAGER_INSTRUCTION_SET_AUTHORITY,
2360        meta_account_idx,
2361        program_account_idx,
2362        authority_candidate,
2363    };
2364
2365    let args_bytes = unsafe {
2366        std::slice::from_raw_parts(
2367            &args as *const ManagerSetAuthorityArgs as *const u8,
2368            std::mem::size_of::<ManagerSetAuthorityArgs>(),
2369        )
2370    };
2371    instruction.extend_from_slice(args_bytes);
2372
2373    Ok(instruction)
2374}
2375
2376/// Build test uploader CREATE instruction data
2377fn build_test_uploader_create_instruction(
2378    account_idx: u16,
2379    account_sz: u32,
2380    seed: &[u8],
2381    is_ephemeral: bool,
2382    state_proof: Option<&[u8]>,
2383) -> Result<Vec<u8>> {
2384    let mut instruction = Vec::new();
2385
2386    // Discriminant (1 byte)
2387    instruction.push(TN_TEST_UPLOADER_PROGRAM_DISCRIMINANT_CREATE);
2388
2389    // Create args struct
2390    let args = TestUploaderCreateArgs {
2391        account_idx,
2392        is_ephemeral: if is_ephemeral { 1u8 } else { 0u8 },
2393        account_sz,
2394        seed_len: seed.len() as u32,
2395    };
2396
2397    // Serialize args (unsafe due to packed struct)
2398    let args_bytes = unsafe {
2399        std::slice::from_raw_parts(
2400            &args as *const _ as *const u8,
2401            std::mem::size_of::<TestUploaderCreateArgs>(),
2402        )
2403    };
2404    instruction.extend_from_slice(args_bytes);
2405
2406    // Append seed bytes
2407    instruction.extend_from_slice(seed);
2408
2409    // Append state proof if provided (for non-ephemeral accounts)
2410    if let Some(proof) = state_proof {
2411        instruction.extend_from_slice(proof);
2412    }
2413
2414    Ok(instruction)
2415}
2416
2417/// Build test uploader WRITE instruction data
2418fn build_test_uploader_write_instruction(
2419    target_account_idx: u16,
2420    target_offset: u32,
2421    data: &[u8],
2422) -> Result<Vec<u8>> {
2423    let mut instruction = Vec::new();
2424
2425    // Discriminant (1 byte)
2426    instruction.push(TN_TEST_UPLOADER_PROGRAM_DISCRIMINANT_WRITE);
2427
2428    // Write args
2429    let args = TestUploaderWriteArgs {
2430        target_account_idx,
2431        target_offset,
2432        data_len: data.len() as u32,
2433    };
2434
2435    // Serialize args
2436    let args_bytes = unsafe {
2437        std::slice::from_raw_parts(
2438            &args as *const _ as *const u8,
2439            std::mem::size_of::<TestUploaderWriteArgs>(),
2440        )
2441    };
2442    instruction.extend_from_slice(args_bytes);
2443
2444    // Append data
2445    instruction.extend_from_slice(data);
2446
2447    Ok(instruction)
2448}
2449
2450/// Build system program DECOMPRESS2 instruction data
2451pub fn build_decompress2_instruction(
2452    target_account_idx: u16,
2453    meta_account_idx: u16,
2454    data_account_idx: u16,
2455    data_offset: u32,
2456    state_proof: &[u8],
2457) -> Result<Vec<u8>> {
2458    let mut instruction = Vec::new();
2459
2460    // Discriminant (1 byte) - TN_SYS_PROG_DISCRIMINANT_ACCOUNT_DECOMPRESS2 = 0x08
2461    instruction.push(0x08);
2462
2463    // DECOMPRESS2 args
2464    let args = SystemProgramDecompress2Args {
2465        target_account_idx,
2466        meta_account_idx,
2467        data_account_idx,
2468        data_offset,
2469    };
2470
2471    // Serialize args
2472    let args_bytes = unsafe {
2473        std::slice::from_raw_parts(
2474            &args as *const _ as *const u8,
2475            std::mem::size_of::<SystemProgramDecompress2Args>(),
2476        )
2477    };
2478    instruction.extend_from_slice(args_bytes);
2479
2480    // Append state proof bytes
2481    instruction.extend_from_slice(state_proof);
2482
2483    Ok(instruction)
2484}
2485
2486/// Token program instruction discriminants
2487pub const TOKEN_INSTRUCTION_INITIALIZE_MINT: u8 = 0x00;
2488pub const TOKEN_INSTRUCTION_INITIALIZE_ACCOUNT: u8 = 0x01;
2489pub const TOKEN_INSTRUCTION_TRANSFER: u8 = 0x02;
2490pub const TOKEN_INSTRUCTION_MINT_TO: u8 = 0x03;
2491pub const TOKEN_INSTRUCTION_BURN: u8 = 0x04;
2492pub const TOKEN_INSTRUCTION_CLOSE_ACCOUNT: u8 = 0x05;
2493pub const TOKEN_INSTRUCTION_FREEZE_ACCOUNT: u8 = 0x06;
2494pub const TOKEN_INSTRUCTION_THAW_ACCOUNT: u8 = 0x07;
2495
2496/* WTHRU instruction discriminants */
2497pub const TN_WTHRU_INSTRUCTION_INITIALIZE_MINT: u32 = 0;
2498pub const TN_WTHRU_INSTRUCTION_DEPOSIT: u32 = 1;
2499pub const TN_WTHRU_INSTRUCTION_WITHDRAW: u32 = 2;
2500// lease, resolve (lil library), show lease info, 
2501// cost to lease, renew, record management, listing records for domain, 
2502// subdomain management 
2503
2504/* Name service instruction discriminants */
2505pub const TN_NAME_SERVICE_INSTRUCTION_INITIALIZE_ROOT: u32 = 0;
2506pub const TN_NAME_SERVICE_INSTRUCTION_REGISTER_SUBDOMAIN: u32 = 1;
2507pub const TN_NAME_SERVICE_INSTRUCTION_APPEND_RECORD: u32 = 2;
2508pub const TN_NAME_SERVICE_INSTRUCTION_DELETE_RECORD: u32 = 3;
2509pub const TN_NAME_SERVICE_INSTRUCTION_UNREGISTER: u32 = 4;
2510
2511/* Name service proof discriminants */
2512pub const TN_NAME_SERVICE_PROOF_INLINE: u32 = 0;
2513
2514/* Name service limits */
2515pub const TN_NAME_SERVICE_MAX_DOMAIN_LENGTH: usize = 64;
2516pub const TN_NAME_SERVICE_MAX_KEY_LENGTH: usize = 32;
2517pub const TN_NAME_SERVICE_MAX_VALUE_LENGTH: usize = 256;
2518
2519// Thru registrar instruction types (u32 discriminants)
2520pub const TN_THRU_REGISTRAR_INSTRUCTION_INITIALIZE_REGISTRY: u32 = 0;
2521pub const TN_THRU_REGISTRAR_INSTRUCTION_PURCHASE_DOMAIN: u32 = 1;
2522pub const TN_THRU_REGISTRAR_INSTRUCTION_RENEW_LEASE: u32 = 2;
2523pub const TN_THRU_REGISTRAR_INSTRUCTION_CLAIM_EXPIRED_DOMAIN: u32 = 3;
2524
2525/// Helper function to add sorted accounts and return their indices
2526fn add_sorted_accounts(tx: Transaction, accounts: &[(TnPubkey, bool)]) -> (Transaction, Vec<u16>) {
2527    // Separate RW and RO accounts, sort each group separately
2528    let mut rw_accounts: Vec<_> = accounts.iter().enumerate()
2529        .filter(|(_, (_, writable))| *writable)
2530        .collect();
2531    let mut ro_accounts: Vec<_> = accounts.iter().enumerate()
2532        .filter(|(_, (_, writable))| !*writable)
2533        .collect();
2534    
2535    // Sort each group by pubkey
2536    rw_accounts.sort_by(|a, b| a.1.0.cmp(&b.1.0));
2537    ro_accounts.sort_by(|a, b| a.1.0.cmp(&b.1.0));
2538
2539    let mut updated_tx = tx;
2540    let mut indices = vec![0u16; accounts.len()];
2541    let mut seen: HashMap<TnPubkey, u16> = HashMap::new();
2542    seen.insert(updated_tx.fee_payer, 0u16);
2543    seen.insert(updated_tx.program, 1u16);
2544
2545    let mut next_idx = 2u16;
2546
2547    // Process RW accounts first (in sorted order)
2548    for (i, (account, _)) in rw_accounts.iter() {
2549        if let Some(idx) = seen.get(account) {
2550            indices[*i] = *idx;
2551            continue;
2552        }
2553
2554        let account_idx = next_idx;
2555        next_idx = next_idx.saturating_add(1);
2556        seen.insert(*account, account_idx);
2557        indices[*i] = account_idx;
2558
2559        updated_tx = updated_tx.add_rw_account(*account);
2560    }
2561
2562    // Then process RO accounts (in sorted order)
2563    for (i, (account, _)) in ro_accounts.iter() {
2564        if let Some(idx) = seen.get(account) {
2565            indices[*i] = *idx;
2566            continue;
2567        }
2568
2569        let account_idx = next_idx;
2570        next_idx = next_idx.saturating_add(1);
2571        seen.insert(*account, account_idx);
2572        indices[*i] = account_idx;
2573
2574        updated_tx = updated_tx.add_r_account(*account);
2575    }
2576
2577    (updated_tx, indices)
2578}
2579
2580/// Helper function to get authority index (0 if fee_payer, else add as readonly)
2581fn add_sorted_rw_accounts(mut tx: Transaction, accounts: &[TnPubkey]) -> (Transaction, Vec<u16>) {
2582    if accounts.is_empty() {
2583        return (tx, Vec::new());
2584    }
2585
2586    let mut sorted: Vec<(usize, TnPubkey)> = accounts.iter().cloned().enumerate().collect();
2587    sorted.sort_by(|a, b| a.1.cmp(&b.1));
2588
2589    let mut indices = vec![0u16; accounts.len()];
2590    for (pos, (orig_idx, account)) in sorted.into_iter().enumerate() {
2591        let idx = (2 + pos) as u16;
2592        indices[orig_idx] = idx;
2593        tx = tx.add_rw_account(account);
2594    }
2595
2596    (tx, indices)
2597}
2598
2599fn add_sorted_ro_accounts(
2600    mut tx: Transaction,
2601    base_idx: u16,
2602    accounts: &[TnPubkey],
2603) -> (Transaction, Vec<u16>) {
2604    if accounts.is_empty() {
2605        return (tx, Vec::new());
2606    }
2607
2608    let mut sorted: Vec<(usize, TnPubkey)> = accounts.iter().cloned().enumerate().collect();
2609    sorted.sort_by(|a, b| a.1.cmp(&b.1));
2610
2611    let mut indices = vec![0u16; accounts.len()];
2612    for (pos, (orig_idx, account)) in sorted.into_iter().enumerate() {
2613        let idx = base_idx + pos as u16;
2614        indices[orig_idx] = idx;
2615        tx = tx.add_r_account(account);
2616    }
2617
2618    (tx, indices)
2619}
2620
2621impl TransactionBuilder {
2622    /// Build token program InitializeMint transaction
2623    pub fn build_token_initialize_mint(
2624        fee_payer: TnPubkey,
2625        token_program: TnPubkey,
2626        mint_account: TnPubkey,
2627        creator: TnPubkey,
2628        mint_authority: TnPubkey,
2629        freeze_authority: Option<TnPubkey>,
2630        decimals: u8,
2631        ticker: &str,
2632        seed: [u8; 32],
2633        state_proof: Vec<u8>,
2634        fee: u64,
2635        nonce: u64,
2636        start_slot: u64,
2637    ) -> Result<Transaction> {
2638        let base_tx =
2639            Transaction::new(fee_payer, token_program, fee, nonce).with_start_slot(start_slot);
2640        let (tx, indices) = add_sorted_rw_accounts(base_tx, &[mint_account]);
2641        let mint_account_idx = indices[0];
2642
2643        let instruction_data = build_token_initialize_mint_instruction(
2644            mint_account_idx,
2645            decimals,
2646            creator,
2647            mint_authority,
2648            freeze_authority,
2649            ticker,
2650            seed,
2651            state_proof,
2652        )?;
2653
2654        let tx = tx
2655            .with_instructions(instruction_data)
2656            .with_expiry_after(100)
2657            .with_compute_units(300_000)
2658            .with_state_units(10_000)
2659            .with_memory_units(10_000);
2660
2661        Ok(tx)
2662    }
2663
2664    /// Build token program InitializeAccount transaction
2665    pub fn build_token_initialize_account(
2666        fee_payer: TnPubkey,
2667        token_program: TnPubkey,
2668        token_account: TnPubkey,
2669        mint_account: TnPubkey,
2670        owner: TnPubkey,
2671        seed: [u8; 32],
2672        state_proof: Vec<u8>,
2673        fee: u64,
2674        nonce: u64,
2675        start_slot: u64,
2676    ) -> Result<Transaction> {
2677        let owner_is_fee_payer = owner == fee_payer;
2678
2679        let mut rw_accounts = vec![token_account];
2680        rw_accounts.sort();
2681
2682        let mut ro_accounts = vec![mint_account];
2683        if !owner_is_fee_payer {
2684            ro_accounts.push(owner);
2685            ro_accounts.sort();
2686        }
2687
2688        let mut tx =
2689            Transaction::new(fee_payer, token_program, fee, nonce).with_start_slot(start_slot);
2690
2691        let mut token_account_idx = 0u16;
2692        for (i, account) in rw_accounts.iter().enumerate() {
2693            let idx = (2 + i) as u16;
2694            if *account == token_account {
2695                token_account_idx = idx;
2696            }
2697            tx = tx.add_rw_account(*account);
2698        }
2699
2700        let base_ro_idx = 2 + rw_accounts.len() as u16;
2701        let mut mint_account_idx = 0u16;
2702        let mut owner_account_idx = if owner_is_fee_payer { 0u16 } else { 0u16 };
2703        for (i, account) in ro_accounts.iter().enumerate() {
2704            let idx = base_ro_idx + i as u16;
2705            if *account == mint_account {
2706                mint_account_idx = idx;
2707            } else if !owner_is_fee_payer && *account == owner {
2708                owner_account_idx = idx;
2709            }
2710            tx = tx.add_r_account(*account);
2711        }
2712
2713        let instruction_data = build_token_initialize_account_instruction(
2714            token_account_idx,
2715            mint_account_idx,
2716            owner_account_idx,
2717            seed,
2718            state_proof,
2719        )?;
2720
2721        let tx = tx
2722            .with_instructions(instruction_data)
2723            .with_expiry_after(100)
2724            .with_compute_units(300_000)
2725            .with_state_units(10_000)
2726            .with_memory_units(10_000);
2727
2728        Ok(tx)
2729    }
2730
2731    /// Build token program Transfer transaction
2732    pub fn build_token_transfer(
2733        fee_payer: TnPubkey,
2734        token_program: TnPubkey,
2735        source_account: TnPubkey,
2736        dest_account: TnPubkey,
2737        _authority: TnPubkey,
2738        amount: u64,
2739        fee: u64,
2740        nonce: u64,
2741        start_slot: u64,
2742    ) -> Result<Transaction> {
2743        let mut tx = Transaction::new(fee_payer, token_program, fee, nonce)
2744            .with_start_slot(start_slot)
2745            .with_expiry_after(100)
2746            .with_compute_units(300_000)
2747            .with_state_units(10_000)
2748            .with_memory_units(10_000);
2749
2750        let is_self_transfer = source_account == dest_account;
2751        let (source_account_idx, dest_account_idx) = if is_self_transfer {
2752            // For self-transfers, add the account only once and use same index
2753            tx = tx.add_rw_account(source_account);
2754            (2u16, 2u16)
2755        } else {
2756            // Add source and dest accounts in sorted order
2757            let accounts = &[(source_account, true), (dest_account, true)];
2758            let (updated_tx, indices) = add_sorted_accounts(tx, accounts);
2759            tx = updated_tx;
2760            (indices[0], indices[1])
2761        };
2762
2763        // Note: For transfers, the authority (source account owner) must sign the transaction
2764        // The token program will verify the signature matches the source account owner
2765
2766        let instruction_data =
2767            build_token_transfer_instruction(source_account_idx, dest_account_idx, amount)?;
2768
2769        Ok(tx.with_instructions(instruction_data))
2770    }
2771
2772    /// Build token program MintTo transaction
2773    pub fn build_token_mint_to(
2774        fee_payer: TnPubkey,
2775        token_program: TnPubkey,
2776        mint_account: TnPubkey,
2777        dest_account: TnPubkey,
2778        authority: TnPubkey,
2779        amount: u64,
2780        fee: u64,
2781        nonce: u64,
2782        start_slot: u64,
2783    ) -> Result<Transaction> {
2784        let base_tx = Transaction::new(fee_payer, token_program, fee, nonce)
2785            .with_start_slot(start_slot)
2786            .with_expiry_after(100)
2787            .with_compute_units(300_000)
2788            .with_state_units(10_000)
2789            .with_memory_units(10_000);
2790
2791        let (tx_after_rw, rw_indices) =
2792            add_sorted_rw_accounts(base_tx, &[mint_account, dest_account]);
2793        let mint_account_idx = rw_indices[0];
2794        let dest_account_idx = rw_indices[1];
2795
2796        let mut tx = tx_after_rw;
2797        let authority_account_idx = if authority == fee_payer {
2798            0u16
2799        } else {
2800            let base_ro_idx = 2 + rw_indices.len() as u16;
2801            let (tx_after_ro, ro_indices) = add_sorted_ro_accounts(tx, base_ro_idx, &[authority]);
2802            tx = tx_after_ro;
2803            ro_indices[0]
2804        };
2805
2806        let instruction_data = build_token_mint_to_instruction(
2807            mint_account_idx,
2808            dest_account_idx,
2809            authority_account_idx,
2810            amount,
2811        )?;
2812
2813        Ok(tx.with_instructions(instruction_data))
2814    }
2815
2816    /// Build token program Burn transaction
2817    pub fn build_token_burn(
2818        fee_payer: TnPubkey,
2819        token_program: TnPubkey,
2820        token_account: TnPubkey,
2821        mint_account: TnPubkey,
2822        authority: TnPubkey,
2823        amount: u64,
2824        fee: u64,
2825        nonce: u64,
2826        start_slot: u64,
2827    ) -> Result<Transaction> {
2828        let base_tx = Transaction::new(fee_payer, token_program, fee, nonce)
2829            .with_start_slot(start_slot)
2830            .with_expiry_after(100)
2831            .with_compute_units(300_000)
2832            .with_state_units(10_000)
2833            .with_memory_units(10_000);
2834
2835        let (tx_after_rw, rw_indices) =
2836            add_sorted_rw_accounts(base_tx, &[token_account, mint_account]);
2837        let token_account_idx = rw_indices[0];
2838        let mint_account_idx = rw_indices[1];
2839
2840        let mut tx = tx_after_rw;
2841        let authority_account_idx = if authority == fee_payer {
2842            0u16
2843        } else {
2844            let base_ro_idx = 2 + rw_indices.len() as u16;
2845            let (tx_after_ro, ro_indices) = add_sorted_ro_accounts(tx, base_ro_idx, &[authority]);
2846            tx = tx_after_ro;
2847            ro_indices[0]
2848        };
2849
2850        let instruction_data = build_token_burn_instruction(
2851            token_account_idx,
2852            mint_account_idx,
2853            authority_account_idx,
2854            amount,
2855        )?;
2856
2857        Ok(tx.with_instructions(instruction_data))
2858    }
2859
2860    /// Build token program FreezeAccount transaction
2861    pub fn build_token_freeze_account(
2862        fee_payer: TnPubkey,
2863        token_program: TnPubkey,
2864        token_account: TnPubkey,
2865        mint_account: TnPubkey,
2866        authority: TnPubkey,
2867        fee: u64,
2868        nonce: u64,
2869        start_slot: u64,
2870    ) -> Result<Transaction> {
2871        let base_tx = Transaction::new(fee_payer, token_program, fee, nonce)
2872            .with_start_slot(start_slot)
2873            .with_expiry_after(100)
2874            .with_compute_units(300_000)
2875            .with_state_units(10_000)
2876            .with_memory_units(10_000);
2877
2878        let (tx_after_rw, rw_indices) =
2879            add_sorted_rw_accounts(base_tx, &[token_account, mint_account]);
2880        let token_account_idx = rw_indices[0];
2881        let mint_account_idx = rw_indices[1];
2882
2883        let mut tx = tx_after_rw;
2884        let authority_account_idx = if authority == fee_payer {
2885            0u16
2886        } else {
2887            let base_ro_idx = 2 + rw_indices.len() as u16;
2888            let (tx_after_ro, ro_indices) = add_sorted_ro_accounts(tx, base_ro_idx, &[authority]);
2889            tx = tx_after_ro;
2890            ro_indices[0]
2891        };
2892
2893        let instruction_data = build_token_freeze_account_instruction(
2894            token_account_idx,
2895            mint_account_idx,
2896            authority_account_idx,
2897        )?;
2898
2899        Ok(tx.with_instructions(instruction_data))
2900    }
2901
2902    /// Build token program ThawAccount transaction
2903    pub fn build_token_thaw_account(
2904        fee_payer: TnPubkey,
2905        token_program: TnPubkey,
2906        token_account: TnPubkey,
2907        mint_account: TnPubkey,
2908        authority: TnPubkey,
2909        fee: u64,
2910        nonce: u64,
2911        start_slot: u64,
2912    ) -> Result<Transaction> {
2913        let base_tx = Transaction::new(fee_payer, token_program, fee, nonce)
2914            .with_start_slot(start_slot)
2915            .with_expiry_after(100)
2916            .with_compute_units(300_000)
2917            .with_state_units(10_000)
2918            .with_memory_units(10_000);
2919
2920        let (tx_after_rw, rw_indices) =
2921            add_sorted_rw_accounts(base_tx, &[token_account, mint_account]);
2922        let token_account_idx = rw_indices[0];
2923        let mint_account_idx = rw_indices[1];
2924
2925        let mut tx = tx_after_rw;
2926        let authority_account_idx = if authority == fee_payer {
2927            0u16
2928        } else {
2929            let base_ro_idx = 2 + rw_indices.len() as u16;
2930            let (tx_after_ro, ro_indices) = add_sorted_ro_accounts(tx, base_ro_idx, &[authority]);
2931            tx = tx_after_ro;
2932            ro_indices[0]
2933        };
2934
2935        let instruction_data = build_token_thaw_account_instruction(
2936            token_account_idx,
2937            mint_account_idx,
2938            authority_account_idx,
2939        )?;
2940
2941        Ok(tx.with_instructions(instruction_data))
2942    }
2943
2944    /// Build token program CloseAccount transaction
2945    pub fn build_token_close_account(
2946        fee_payer: TnPubkey,
2947        token_program: TnPubkey,
2948        token_account: TnPubkey,
2949        destination: TnPubkey,
2950        authority: TnPubkey,
2951        fee: u64,
2952        nonce: u64,
2953        start_slot: u64,
2954    ) -> Result<Transaction> {
2955        let base_tx = Transaction::new(fee_payer, token_program, fee, nonce)
2956            .with_start_slot(start_slot)
2957            .with_expiry_after(100)
2958            .with_compute_units(300_000)
2959            .with_state_units(10_000)
2960            .with_memory_units(10_000);
2961
2962        let mut rw_accounts = vec![token_account];
2963        let destination_in_accounts = destination != fee_payer;
2964        if destination_in_accounts {
2965            rw_accounts.push(destination);
2966        }
2967
2968        let (tx_after_rw, rw_indices) = add_sorted_rw_accounts(base_tx, &rw_accounts);
2969        let token_account_idx = rw_indices[0];
2970        let destination_idx = if destination_in_accounts {
2971            rw_indices[1]
2972        } else {
2973            0u16
2974        };
2975
2976        let mut tx = tx_after_rw;
2977        let authority_account_idx = if authority == fee_payer {
2978            0u16
2979        } else {
2980            let base_ro_idx = 2 + rw_indices.len() as u16;
2981            let (tx_after_ro, ro_indices) = add_sorted_ro_accounts(tx, base_ro_idx, &[authority]);
2982            tx = tx_after_ro;
2983            ro_indices[0]
2984        };
2985
2986        let instruction_data = build_token_close_account_instruction(
2987            token_account_idx,
2988            destination_idx,
2989            authority_account_idx,
2990        )?;
2991
2992        Ok(tx.with_instructions(instruction_data))
2993    }
2994
2995    /// Build WTHRU initialize transaction
2996    pub fn build_wthru_initialize_mint(
2997        fee_payer: TnPubkey,
2998        wthru_program: TnPubkey,
2999        token_program: TnPubkey,
3000        mint_account: TnPubkey,
3001        vault_account: TnPubkey,
3002        decimals: u8,
3003        mint_seed: [u8; 32],
3004        mint_proof: Vec<u8>,
3005        vault_proof: Vec<u8>,
3006        fee: u64,
3007        nonce: u64,
3008        start_slot: u64,
3009    ) -> Result<Transaction> {
3010        let mut tx = Transaction::new(fee_payer, wthru_program, fee, nonce)
3011            .with_start_slot(start_slot)
3012            .with_expiry_after(100)
3013            .with_compute_units(500_000)
3014            .with_state_units(10_000)
3015            .with_memory_units(10_000);
3016
3017        let accounts = [
3018            (mint_account, true),
3019            (vault_account, true),
3020            (token_program, false),
3021        ];
3022
3023        let (tx_with_accounts, indices) = add_sorted_accounts(tx, &accounts);
3024        tx = tx_with_accounts;
3025
3026        let mint_account_idx = indices[0];
3027        let vault_account_idx = indices[1];
3028        let token_program_idx = indices[2];
3029
3030        let instruction_data = build_wthru_initialize_mint_instruction(
3031            token_program_idx,
3032            mint_account_idx,
3033            vault_account_idx,
3034            decimals,
3035            mint_seed,
3036            mint_proof,
3037            vault_proof,
3038        )?;
3039
3040        Ok(tx.with_instructions(instruction_data))
3041    }
3042
3043    /// Build WTHRU deposit transaction
3044    pub fn build_wthru_deposit(
3045        fee_payer: TnPubkey,
3046        wthru_program: TnPubkey,
3047        token_program: TnPubkey,
3048        mint_account: TnPubkey,
3049        vault_account: TnPubkey,
3050        dest_token_account: TnPubkey,
3051        fee: u64,
3052        nonce: u64,
3053        start_slot: u64,
3054    ) -> Result<Transaction> {
3055        let mut tx = Transaction::new(fee_payer, wthru_program, fee, nonce)
3056            .with_start_slot(start_slot)
3057            .with_expiry_after(100)
3058            .with_compute_units(400_000)
3059            .with_state_units(10_000)
3060            .with_memory_units(10_000);
3061
3062        let accounts = [
3063            (mint_account, true),
3064            (vault_account, true),
3065            (dest_token_account, true),
3066            (token_program, false),
3067        ];
3068
3069        let (tx_with_accounts, indices) = add_sorted_accounts(tx, &accounts);
3070        tx = tx_with_accounts;
3071
3072        let mint_account_idx = indices[0];
3073        let vault_account_idx = indices[1];
3074        let dest_account_idx = indices[2];
3075        let token_program_idx = indices[3];
3076
3077        let instruction_data = build_wthru_deposit_instruction(
3078            token_program_idx,
3079            vault_account_idx,
3080            mint_account_idx,
3081            dest_account_idx,
3082        )?;
3083
3084        Ok(tx.with_instructions(instruction_data))
3085    }
3086
3087    /// Build WTHRU withdraw transaction
3088    pub fn build_wthru_withdraw(
3089        fee_payer: TnPubkey,
3090        wthru_program: TnPubkey,
3091        token_program: TnPubkey,
3092        mint_account: TnPubkey,
3093        vault_account: TnPubkey,
3094        wthru_token_account: TnPubkey,
3095        recipient_account: TnPubkey,
3096        amount: u64,
3097        fee: u64,
3098        nonce: u64,
3099        start_slot: u64,
3100    ) -> Result<Transaction> {
3101        let mut tx = Transaction::new(fee_payer, wthru_program, fee, nonce)
3102            .with_start_slot(start_slot)
3103            .with_expiry_after(100)
3104            .with_compute_units(400_000)
3105            .with_state_units(10_000)
3106            .with_memory_units(10_000);
3107
3108        let accounts = [
3109            (mint_account, true),
3110            (vault_account, true),
3111            (wthru_token_account, true),
3112            (recipient_account, true),
3113            (token_program, false),
3114        ];
3115
3116        let (tx_with_accounts, indices) = add_sorted_accounts(tx, &accounts);
3117        tx = tx_with_accounts;
3118
3119        let mint_account_idx = indices[0];
3120        let vault_account_idx = indices[1];
3121        let token_account_idx = indices[2];
3122        let recipient_account_idx = indices[3];
3123        let token_program_idx = indices[4];
3124
3125        let owner_account_idx = 0u16; // fee payer/owner
3126
3127        let instruction_data = build_wthru_withdraw_instruction(
3128            token_program_idx,
3129            vault_account_idx,
3130            mint_account_idx,
3131            token_account_idx,
3132            owner_account_idx,
3133            recipient_account_idx,
3134            amount,
3135        )?;
3136
3137        Ok(tx.with_instructions(instruction_data))
3138    }
3139
3140    /// Build faucet program Deposit transaction
3141    /// The faucet program will invoke the EOA program to transfer from depositor to faucet account
3142    pub fn build_faucet_deposit(
3143        fee_payer: TnPubkey,
3144        faucet_program: TnPubkey,
3145        faucet_account: TnPubkey,
3146        depositor_account: TnPubkey,
3147        eoa_program: TnPubkey,
3148        amount: u64,
3149        fee: u64,
3150        nonce: u64,
3151        start_slot: u64,
3152    ) -> Result<Transaction> {
3153        let tx = Transaction::new(fee_payer, faucet_program, fee, nonce)
3154            .with_start_slot(start_slot)
3155            .with_expiry_after(100)
3156            .with_compute_units(300_000)
3157            .with_state_units(10_000)
3158            .with_memory_units(10_000);
3159
3160        let (tx, depositor_account_idx) = Self::ensure_rw_account(tx, depositor_account);
3161        let (tx, faucet_account_idx) = Self::ensure_rw_account(tx, faucet_account);
3162        let (tx, eoa_program_idx) = Self::ensure_ro_account(tx, eoa_program);
3163
3164        let instruction_data = build_faucet_deposit_instruction(
3165            faucet_account_idx,
3166            depositor_account_idx,
3167            eoa_program_idx,
3168            amount,
3169        )?;
3170
3171        Ok(tx.with_instructions(instruction_data))
3172    }
3173
3174    fn resolve_account_index(tx: &Transaction, target: &TnPubkey) -> Option<u16> {
3175        if *target == tx.fee_payer {
3176            return Some(0u16);
3177        }
3178
3179        if *target == tx.program {
3180            return Some(1u16);
3181        }
3182
3183        if let Some(ref rw) = tx.rw_accs {
3184            if let Some(pos) = rw.iter().position(|acc| acc == target) {
3185                return Some(2u16 + pos as u16);
3186            }
3187        }
3188
3189        if let Some(ref ro) = tx.r_accs {
3190            let base = 2u16 + tx.rw_accs.as_ref().map_or(0u16, |v| v.len() as u16);
3191            if let Some(pos) = ro.iter().position(|acc| acc == target) {
3192                return Some(base + pos as u16);
3193            }
3194        }
3195
3196        None
3197    }
3198
3199    fn ensure_rw_account(mut tx: Transaction, account: TnPubkey) -> (Transaction, u16) {
3200        if let Some(idx) = Self::resolve_account_index(&tx, &account) {
3201            return (tx, idx);
3202        }
3203
3204        tx = tx.add_rw_account(account);
3205        let idx = Self::resolve_account_index(&tx, &account)
3206            .expect("read-write account index should exist after insertion");
3207
3208        (tx, idx)
3209    }
3210
3211    fn ensure_ro_account(mut tx: Transaction, account: TnPubkey) -> (Transaction, u16) {
3212        if let Some(idx) = Self::resolve_account_index(&tx, &account) {
3213            return (tx, idx);
3214        }
3215
3216        tx = tx.add_r_account(account);
3217        let idx = Self::resolve_account_index(&tx, &account)
3218            .expect("read-only account index should exist after insertion");
3219
3220        (tx, idx)
3221    }
3222
3223    /// Build faucet program Withdraw transaction
3224    pub fn build_faucet_withdraw(
3225        fee_payer: TnPubkey,
3226        faucet_program: TnPubkey,
3227        faucet_account: TnPubkey,
3228        recipient_account: TnPubkey,
3229        amount: u64,
3230        fee: u64,
3231        nonce: u64,
3232        start_slot: u64,
3233    ) -> Result<Transaction> {
3234        let mut tx = Transaction::new(fee_payer, faucet_program, fee, nonce)
3235            .with_start_slot(start_slot)
3236            .with_expiry_after(100)
3237            .with_compute_units(300_000)
3238            .with_state_units(10_000)
3239            .with_memory_units(10_000);
3240
3241        // Determine account indices, handling duplicates with fee_payer and between accounts
3242        // Check for duplicates first
3243        let faucet_is_fee_payer = faucet_account == fee_payer;
3244        let recipient_is_fee_payer = recipient_account == fee_payer;
3245        let recipient_is_faucet = recipient_account == faucet_account;
3246
3247        let (faucet_account_idx, recipient_account_idx) = if faucet_is_fee_payer && recipient_is_fee_payer {
3248            // Both are fee_payer (same account)
3249            (0u16, 0u16)
3250        } else if faucet_is_fee_payer {
3251            // Faucet is fee_payer, recipient is different
3252            tx = tx.add_rw_account(recipient_account);
3253            (0u16, 2u16)
3254        } else if recipient_is_fee_payer {
3255            // Recipient is fee_payer, faucet is different
3256            tx = tx.add_rw_account(faucet_account);
3257            (2u16, 0u16)
3258        } else if recipient_is_faucet {
3259            // Both are same account (but not fee_payer)
3260            tx = tx.add_rw_account(faucet_account);
3261            (2u16, 2u16)
3262        } else {
3263            // Both are different accounts, add in sorted order
3264            if faucet_account < recipient_account {
3265                tx = tx.add_rw_account(faucet_account);
3266                tx = tx.add_rw_account(recipient_account);
3267                (2u16, 3u16)
3268            } else {
3269                tx = tx.add_rw_account(recipient_account);
3270                tx = tx.add_rw_account(faucet_account);
3271                (3u16, 2u16)
3272            }
3273        };
3274
3275        let instruction_data = build_faucet_withdraw_instruction(
3276            faucet_account_idx,
3277            recipient_account_idx,
3278            amount,
3279        )?;
3280
3281        Ok(tx.with_instructions(instruction_data))
3282    }
3283
3284    /// Build name service InitializeRoot transaction
3285    pub fn build_name_service_initialize_root(
3286        fee_payer: TnPubkey,
3287        name_service_program: TnPubkey,
3288        registrar_account: TnPubkey,
3289        authority_account: TnPubkey,
3290        root_name: &str,
3291        state_proof: Vec<u8>,
3292        fee: u64,
3293        nonce: u64,
3294        start_slot: u64,
3295    ) -> Result<Transaction> {
3296        let mut tx = Transaction::new(fee_payer, name_service_program, fee, nonce)
3297            .with_start_slot(start_slot)
3298            .with_expiry_after(100)
3299            .with_compute_units(500_000)
3300            .with_state_units(10_000)
3301            .with_memory_units(10_000);
3302
3303        let accounts = [(registrar_account, true), (authority_account, false)];
3304        let (tx_with_accounts, indices) = add_sorted_accounts(tx, &accounts);
3305        tx = tx_with_accounts;
3306
3307        let registrar_account_idx = indices[0];
3308        let authority_account_idx = indices[1];
3309
3310        let instruction_data = build_name_service_initialize_root_instruction(
3311            registrar_account_idx,
3312            authority_account_idx,
3313            root_name,
3314            state_proof,
3315        )?;
3316
3317        Ok(tx.with_instructions(instruction_data))
3318    }
3319
3320    /// Build name service RegisterSubdomain transaction
3321    pub fn build_name_service_register_subdomain(
3322        fee_payer: TnPubkey,
3323        name_service_program: TnPubkey,
3324        domain_account: TnPubkey,
3325        parent_account: TnPubkey,
3326        owner_account: TnPubkey,
3327        authority_account: TnPubkey,
3328        domain_name: &str,
3329        state_proof: Vec<u8>,
3330        fee: u64,
3331        nonce: u64,
3332        start_slot: u64,
3333    ) -> Result<Transaction> {
3334        let mut tx = Transaction::new(fee_payer, name_service_program, fee, nonce)
3335            .with_start_slot(start_slot)
3336            .with_expiry_after(100)
3337            .with_compute_units(500_000)
3338            .with_state_units(10_000)
3339            .with_memory_units(10_000);
3340
3341        let accounts = [
3342            (domain_account, true),
3343            (parent_account, true),
3344            (owner_account, false),
3345            (authority_account, false),
3346        ];
3347        let (tx_with_accounts, indices) = add_sorted_accounts(tx, &accounts);
3348        tx = tx_with_accounts;
3349
3350        let domain_account_idx = indices[0];
3351        let parent_account_idx = indices[1];
3352        let owner_account_idx = indices[2];
3353        let authority_account_idx = indices[3];
3354
3355        let instruction_data = build_name_service_register_subdomain_instruction(
3356            domain_account_idx,
3357            parent_account_idx,
3358            owner_account_idx,
3359            authority_account_idx,
3360            domain_name,
3361            state_proof,
3362        )?;
3363
3364        Ok(tx.with_instructions(instruction_data))
3365    }
3366
3367    /// Build name service AppendRecord transaction
3368    pub fn build_name_service_append_record(
3369        fee_payer: TnPubkey,
3370        name_service_program: TnPubkey,
3371        domain_account: TnPubkey,
3372        owner_account: TnPubkey,
3373        key: &[u8],
3374        value: &[u8],
3375        fee: u64,
3376        nonce: u64,
3377        start_slot: u64,
3378    ) -> Result<Transaction> {
3379        let mut tx = Transaction::new(fee_payer, name_service_program, fee, nonce)
3380            .with_start_slot(start_slot)
3381            .with_expiry_after(100)
3382            .with_compute_units(250_000)
3383            .with_state_units(10_000)
3384            .with_memory_units(10_000);
3385
3386        let accounts = [(domain_account, true), (owner_account, false)];
3387        let (tx_with_accounts, indices) = add_sorted_accounts(tx, &accounts);
3388        tx = tx_with_accounts;
3389
3390        let domain_account_idx = indices[0];
3391        let owner_account_idx = indices[1];
3392
3393        let instruction_data = build_name_service_append_record_instruction(
3394            domain_account_idx,
3395            owner_account_idx,
3396            key,
3397            value,
3398        )?;
3399
3400        Ok(tx.with_instructions(instruction_data))
3401    }
3402
3403    /// Build name service DeleteRecord transaction
3404    pub fn build_name_service_delete_record(
3405        fee_payer: TnPubkey,
3406        name_service_program: TnPubkey,
3407        domain_account: TnPubkey,
3408        owner_account: TnPubkey,
3409        key: &[u8],
3410        fee: u64,
3411        nonce: u64,
3412        start_slot: u64,
3413    ) -> Result<Transaction> {
3414        let mut tx = Transaction::new(fee_payer, name_service_program, fee, nonce)
3415            .with_start_slot(start_slot)
3416            .with_expiry_after(100)
3417            .with_compute_units(200_000)
3418            .with_state_units(10_000)
3419            .with_memory_units(10_000);
3420
3421        let accounts = [(domain_account, true), (owner_account, false)];
3422        let (tx_with_accounts, indices) = add_sorted_accounts(tx, &accounts);
3423        tx = tx_with_accounts;
3424
3425        let domain_account_idx = indices[0];
3426        let owner_account_idx = indices[1];
3427
3428        let instruction_data = build_name_service_delete_record_instruction(
3429            domain_account_idx,
3430            owner_account_idx,
3431            key,
3432        )?;
3433
3434        Ok(tx.with_instructions(instruction_data))
3435    }
3436
3437    /// Build name service UnregisterSubdomain transaction
3438    pub fn build_name_service_unregister_subdomain(
3439        fee_payer: TnPubkey,
3440        name_service_program: TnPubkey,
3441        domain_account: TnPubkey,
3442        owner_account: TnPubkey,
3443        fee: u64,
3444        nonce: u64,
3445        start_slot: u64,
3446    ) -> Result<Transaction> {
3447        let mut tx = Transaction::new(fee_payer, name_service_program, fee, nonce)
3448            .with_start_slot(start_slot)
3449            .with_expiry_after(100)
3450            .with_compute_units(200_000)
3451            .with_state_units(10_000)
3452            .with_memory_units(10_000);
3453
3454        let accounts = [(domain_account, true), (owner_account, false)];
3455        let (tx_with_accounts, indices) = add_sorted_accounts(tx, &accounts);
3456        tx = tx_with_accounts;
3457
3458        let domain_account_idx = indices[0];
3459        let owner_account_idx = indices[1];
3460
3461        let instruction_data = build_name_service_unregister_subdomain_instruction(
3462            domain_account_idx,
3463            owner_account_idx,
3464        )?;
3465
3466        Ok(tx.with_instructions(instruction_data))
3467    }
3468
3469    /// Build thru registrar InitializeRegistry transaction
3470    pub fn build_thru_registrar_initialize_registry(
3471        fee_payer: TnPubkey,
3472        thru_registrar_program: TnPubkey,
3473        config_account: TnPubkey,
3474        name_service_program: TnPubkey,
3475        root_registrar_account: TnPubkey,
3476        treasurer_account: TnPubkey,
3477        token_mint_account: TnPubkey,
3478        token_program: TnPubkey,
3479        root_domain_name: &str,
3480        price_per_year: u64,
3481        config_proof: Vec<u8>,
3482        registrar_proof: Vec<u8>,
3483        fee: u64,
3484        nonce: u64,
3485        start_slot: u64,
3486    ) -> Result<Transaction> {
3487        let mut tx = Transaction::new(fee_payer, thru_registrar_program, fee, nonce)
3488            .with_start_slot(start_slot)
3489            .with_expiry_after(100)
3490            .with_compute_units(500_000)
3491            .with_state_units(10_000)
3492            .with_memory_units(10_000);
3493
3494        // Add accounts in sorted order (read-write first, then read-only)
3495        // Registrar must be writable because the base name service CPI creates/resizes it.
3496        let mut rw_accounts = vec![config_account, root_registrar_account];
3497        rw_accounts.sort();
3498
3499        let mut ro_accounts = vec![
3500            name_service_program,
3501            treasurer_account,
3502            token_mint_account,
3503            token_program,
3504        ];
3505        ro_accounts.sort();
3506
3507        // Add RW accounts
3508        let mut config_account_idx = 0u16;
3509        let mut root_registrar_account_idx = 0u16;
3510        for (i, account) in rw_accounts.iter().enumerate() {
3511            let idx = (2 + i) as u16;
3512            if *account == config_account {
3513                config_account_idx = idx;
3514            } else if *account == root_registrar_account {
3515                root_registrar_account_idx = idx;
3516            }
3517            tx = tx.add_rw_account(*account);
3518        }
3519
3520        // Add RO accounts
3521        let base_ro_idx = 2 + rw_accounts.len() as u16;
3522        let mut name_service_program_idx = 0u16;
3523        let mut treasurer_account_idx = 0u16;
3524        let mut token_mint_account_idx = 0u16;
3525        let mut token_program_idx = 0u16;
3526
3527        for (i, account) in ro_accounts.iter().enumerate() {
3528            let idx = base_ro_idx + i as u16;
3529            if *account == name_service_program {
3530                name_service_program_idx = idx;
3531            } else if *account == root_registrar_account {
3532                root_registrar_account_idx = idx;
3533            } else if *account == treasurer_account {
3534                treasurer_account_idx = idx;
3535            } else if *account == token_mint_account {
3536                token_mint_account_idx = idx;
3537            } else if *account == token_program {
3538                token_program_idx = idx;
3539            }
3540            tx = tx.add_r_account(*account);
3541        }
3542
3543        let instruction_data = build_thru_registrar_initialize_registry_instruction(
3544            config_account_idx,
3545            name_service_program_idx,
3546            root_registrar_account_idx,
3547            treasurer_account_idx,
3548            token_mint_account_idx,
3549            token_program_idx,
3550            root_domain_name,
3551            price_per_year,
3552            config_proof,
3553            registrar_proof,
3554        )?;
3555
3556        Ok(tx.with_instructions(instruction_data))
3557    }
3558
3559    /// Build thru registrar PurchaseDomain transaction
3560    pub fn build_thru_registrar_purchase_domain(
3561        fee_payer: TnPubkey,
3562        thru_registrar_program: TnPubkey,
3563        config_account: TnPubkey,
3564        lease_account: TnPubkey,
3565        domain_account: TnPubkey,
3566        name_service_program: TnPubkey,
3567        root_registrar_account: TnPubkey,
3568        treasurer_account: TnPubkey,
3569        payer_token_account: TnPubkey,
3570        token_mint_account: TnPubkey,
3571        token_program: TnPubkey,
3572        domain_name: &str,
3573        years: u8,
3574        lease_proof: Vec<u8>,
3575        domain_proof: Vec<u8>,
3576        fee: u64,
3577        nonce: u64,
3578        start_slot: u64,
3579    ) -> Result<Transaction> {
3580        let mut tx = Transaction::new(fee_payer, thru_registrar_program, fee, nonce)
3581            .with_start_slot(start_slot)
3582            .with_expiry_after(100)
3583            .with_compute_units(500_000)
3584            .with_state_units(10_000)
3585            .with_memory_units(10_000);
3586
3587        // Add accounts in sorted order
3588        // Token accounts must be writable for transfer; lease/domain are created; root registrar and config are updated via CPI.
3589        let mut rw_accounts = vec![
3590            config_account,
3591            lease_account,
3592            domain_account,
3593            treasurer_account,
3594            payer_token_account,
3595            root_registrar_account,
3596        ];
3597        rw_accounts.sort();
3598
3599        let mut ro_accounts = vec![
3600            name_service_program,
3601            token_mint_account,
3602            token_program,
3603        ];
3604        ro_accounts.sort();
3605
3606        // Add RW accounts
3607        let mut config_account_idx = 0u16;
3608        let mut lease_account_idx = 0u16;
3609        let mut domain_account_idx = 0u16;
3610        let mut treasurer_account_idx = 0u16;
3611        let mut payer_token_account_idx = 0u16;
3612        let mut root_registrar_account_idx = 0u16;
3613        for (i, account) in rw_accounts.iter().enumerate() {
3614            let idx = (2 + i) as u16;
3615            if *account == config_account {
3616                config_account_idx = idx;
3617            } else if *account == lease_account {
3618                lease_account_idx = idx;
3619            } else if *account == domain_account {
3620                domain_account_idx = idx;
3621            } else if *account == treasurer_account {
3622                treasurer_account_idx = idx;
3623            } else if *account == payer_token_account {
3624                payer_token_account_idx = idx;
3625            } else if *account == root_registrar_account {
3626                root_registrar_account_idx = idx;
3627            }
3628            tx = tx.add_rw_account(*account);
3629        }
3630
3631        // Add RO accounts
3632        let base_ro_idx = 2 + rw_accounts.len() as u16;
3633        let mut name_service_program_idx = 0u16;
3634        let mut token_mint_account_idx = 0u16;
3635        let mut token_program_idx = 0u16;
3636
3637        for (i, account) in ro_accounts.iter().enumerate() {
3638            let idx = base_ro_idx + i as u16;
3639            if *account == config_account {
3640                config_account_idx = idx; // Should remain zero; config moved to RW set
3641            } else if *account == name_service_program {
3642                name_service_program_idx = idx;
3643            } else if *account == root_registrar_account {
3644                root_registrar_account_idx = idx;
3645            } else if *account == treasurer_account {
3646                treasurer_account_idx = idx;
3647            } else if *account == payer_token_account {
3648                payer_token_account_idx = idx;
3649            } else if *account == token_mint_account {
3650                token_mint_account_idx = idx;
3651            } else if *account == token_program {
3652                token_program_idx = idx;
3653            }
3654            tx = tx.add_r_account(*account);
3655        }
3656
3657        let instruction_data = build_thru_registrar_purchase_domain_instruction(
3658            config_account_idx,
3659            lease_account_idx,
3660            domain_account_idx,
3661            name_service_program_idx,
3662            root_registrar_account_idx,
3663            treasurer_account_idx,
3664            payer_token_account_idx,
3665            token_mint_account_idx,
3666            token_program_idx,
3667            domain_name,
3668            years,
3669            lease_proof,
3670            domain_proof,
3671        )?;
3672
3673        Ok(tx.with_instructions(instruction_data))
3674    }
3675
3676    /// Build thru registrar RenewLease transaction
3677    pub fn build_thru_registrar_renew_lease(
3678        fee_payer: TnPubkey,
3679        thru_registrar_program: TnPubkey,
3680        config_account: TnPubkey,
3681        lease_account: TnPubkey,
3682        treasurer_account: TnPubkey,
3683        payer_token_account: TnPubkey,
3684        token_mint_account: TnPubkey,
3685        token_program: TnPubkey,
3686        years: u8,
3687        fee: u64,
3688        nonce: u64,
3689        start_slot: u64,
3690    ) -> Result<Transaction> {
3691        let mut tx = Transaction::new(fee_payer, thru_registrar_program, fee, nonce)
3692            .with_start_slot(start_slot)
3693            .with_expiry_after(100)
3694            .with_compute_units(300_000)
3695            .with_state_units(10_000)
3696            .with_memory_units(10_000);
3697
3698        // Add accounts in sorted order
3699        // Token accounts must be writable for transfer.
3700        let mut rw_accounts = vec![lease_account, treasurer_account, payer_token_account];
3701        rw_accounts.sort();
3702
3703        let mut ro_accounts = vec![
3704            config_account,
3705            token_mint_account,
3706            token_program,
3707        ];
3708        ro_accounts.sort();
3709
3710        // Add RW accounts
3711        let mut lease_account_idx = 0u16;
3712        let mut treasurer_account_idx = 0u16;
3713        let mut payer_token_account_idx = 0u16;
3714        for (i, account) in rw_accounts.iter().enumerate() {
3715            let idx = (2 + i) as u16;
3716            if *account == lease_account {
3717                lease_account_idx = idx;
3718            } else if *account == treasurer_account {
3719                treasurer_account_idx = idx;
3720            } else if *account == payer_token_account {
3721                payer_token_account_idx = idx;
3722            }
3723            tx = tx.add_rw_account(*account);
3724        }
3725
3726        // Add RO accounts
3727        let base_ro_idx = 2 + rw_accounts.len() as u16;
3728        let mut config_account_idx = 0u16;
3729        let mut token_mint_account_idx = 0u16;
3730        let mut token_program_idx = 0u16;
3731
3732        for (i, account) in ro_accounts.iter().enumerate() {
3733            let idx = base_ro_idx + i as u16;
3734            if *account == config_account {
3735                config_account_idx = idx;
3736            } else if *account == token_mint_account {
3737                token_mint_account_idx = idx;
3738            } else if *account == token_program {
3739                token_program_idx = idx;
3740            }
3741            tx = tx.add_r_account(*account);
3742        }
3743
3744        let instruction_data = build_thru_registrar_renew_lease_instruction(
3745            config_account_idx,
3746            lease_account_idx,
3747            treasurer_account_idx,
3748            payer_token_account_idx,
3749            token_mint_account_idx,
3750            token_program_idx,
3751            years,
3752        )?;
3753
3754        Ok(tx.with_instructions(instruction_data))
3755    }
3756
3757    /// Build thru registrar ClaimExpiredDomain transaction
3758    pub fn build_thru_registrar_claim_expired_domain(
3759        fee_payer: TnPubkey,
3760        thru_registrar_program: TnPubkey,
3761        config_account: TnPubkey,
3762        lease_account: TnPubkey,
3763        treasurer_account: TnPubkey,
3764        payer_token_account: TnPubkey,
3765        token_mint_account: TnPubkey,
3766        token_program: TnPubkey,
3767        years: u8,
3768        fee: u64,
3769        nonce: u64,
3770        start_slot: u64,
3771    ) -> Result<Transaction> {
3772        let mut tx = Transaction::new(fee_payer, thru_registrar_program, fee, nonce)
3773            .with_start_slot(start_slot)
3774            .with_expiry_after(100)
3775            .with_compute_units(300_000)
3776            .with_state_units(10_000)
3777            .with_memory_units(10_000);
3778
3779        // Add accounts in sorted order
3780        // Token accounts must be writable for transfer.
3781        let mut rw_accounts = vec![lease_account, treasurer_account, payer_token_account];
3782        rw_accounts.sort();
3783
3784        let mut ro_accounts = vec![
3785            config_account,
3786            token_mint_account,
3787            token_program,
3788        ];
3789        ro_accounts.sort();
3790
3791        // Add RW accounts
3792        let mut lease_account_idx = 0u16;
3793        let mut treasurer_account_idx = 0u16;
3794        let mut payer_token_account_idx = 0u16;
3795        for (i, account) in rw_accounts.iter().enumerate() {
3796            let idx = (2 + i) as u16;
3797            if *account == lease_account {
3798                lease_account_idx = idx;
3799            } else if *account == treasurer_account {
3800                treasurer_account_idx = idx;
3801            } else if *account == payer_token_account {
3802                payer_token_account_idx = idx;
3803            }
3804            tx = tx.add_rw_account(*account);
3805        }
3806
3807        // Add RO accounts
3808        let base_ro_idx = 2 + rw_accounts.len() as u16;
3809        let mut config_account_idx = 0u16;
3810        let mut token_mint_account_idx = 0u16;
3811        let mut token_program_idx = 0u16;
3812
3813        for (i, account) in ro_accounts.iter().enumerate() {
3814            let idx = base_ro_idx + i as u16;
3815            if *account == config_account {
3816                config_account_idx = idx;
3817            } else if *account == token_mint_account {
3818                token_mint_account_idx = idx;
3819            } else if *account == token_program {
3820                token_program_idx = idx;
3821            }
3822            tx = tx.add_r_account(*account);
3823        }
3824
3825        let instruction_data = build_thru_registrar_claim_expired_domain_instruction(
3826            config_account_idx,
3827            lease_account_idx,
3828            treasurer_account_idx,
3829            payer_token_account_idx,
3830            token_mint_account_idx,
3831            token_program_idx,
3832            years,
3833        )?;
3834
3835        Ok(tx.with_instructions(instruction_data))
3836    }
3837}
3838
3839/// Build token InitializeMint instruction data
3840fn build_token_initialize_mint_instruction(
3841    mint_account_idx: u16,
3842    decimals: u8,
3843    creator: TnPubkey,
3844    mint_authority: TnPubkey,
3845    freeze_authority: Option<TnPubkey>,
3846    ticker: &str,
3847    seed: [u8; 32],
3848    state_proof: Vec<u8>,
3849) -> Result<Vec<u8>> {
3850    let mut instruction_data = Vec::new();
3851
3852    // Instruction tag
3853    instruction_data.push(TOKEN_INSTRUCTION_INITIALIZE_MINT);
3854
3855    // mint_account_index (u16)
3856    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
3857
3858    // decimals (u8)
3859    instruction_data.push(decimals);
3860
3861    // creator (32 bytes)
3862    instruction_data.extend_from_slice(&creator);
3863
3864    // mint_authority (32 bytes)
3865    instruction_data.extend_from_slice(&mint_authority);
3866
3867    // freeze_authority (32 bytes) and has_freeze_authority flag
3868    let (freeze_auth, has_freeze_auth) = match freeze_authority {
3869        Some(auth) => (auth, 1u8),
3870        None => ([0u8; 32], 0u8),
3871    };
3872    instruction_data.extend_from_slice(&freeze_auth);
3873    instruction_data.push(has_freeze_auth);
3874
3875    // ticker_len and ticker_bytes (max 8 bytes)
3876    let ticker_bytes = ticker.as_bytes();
3877    if ticker_bytes.len() > 8 {
3878        return Err(anyhow::anyhow!("Ticker must be 8 characters or less"));
3879    }
3880
3881    instruction_data.push(ticker_bytes.len() as u8);
3882    let mut ticker_padded = [0u8; 8];
3883    ticker_padded[..ticker_bytes.len()].copy_from_slice(ticker_bytes);
3884    instruction_data.extend_from_slice(&ticker_padded);
3885
3886    // seed (32 bytes)
3887    instruction_data.extend_from_slice(&seed);
3888
3889    // state proof (variable length)
3890    instruction_data.extend_from_slice(&state_proof);
3891
3892    Ok(instruction_data)
3893}
3894
3895/// Build token InitializeAccount instruction data
3896fn build_token_initialize_account_instruction(
3897    token_account_idx: u16,
3898    mint_account_idx: u16,
3899    owner_account_idx: u16,
3900    seed: [u8; 32],
3901    state_proof: Vec<u8>,
3902) -> Result<Vec<u8>> {
3903    let mut instruction_data = Vec::new();
3904
3905    // Instruction tag
3906    instruction_data.push(TOKEN_INSTRUCTION_INITIALIZE_ACCOUNT);
3907
3908    // token_account_index (u16)
3909    instruction_data.extend_from_slice(&token_account_idx.to_le_bytes());
3910
3911    // mint_account_index (u16)
3912    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
3913
3914    // owner_account_index (u16)
3915    instruction_data.extend_from_slice(&owner_account_idx.to_le_bytes());
3916
3917    // seed (32 bytes)
3918    instruction_data.extend_from_slice(&seed);
3919
3920    // state proof (variable length)
3921    instruction_data.extend_from_slice(&state_proof);
3922
3923    Ok(instruction_data)
3924}
3925
3926/// Build token Transfer instruction data
3927fn build_token_transfer_instruction(
3928    source_account_idx: u16,
3929    dest_account_idx: u16,
3930    amount: u64,
3931) -> Result<Vec<u8>> {
3932    let mut instruction_data = Vec::new();
3933
3934    // Instruction tag
3935    instruction_data.push(TOKEN_INSTRUCTION_TRANSFER);
3936
3937    // source_account_index (u16)
3938    instruction_data.extend_from_slice(&source_account_idx.to_le_bytes());
3939
3940    // dest_account_index (u16)
3941    instruction_data.extend_from_slice(&dest_account_idx.to_le_bytes());
3942
3943    // amount (u64)
3944    instruction_data.extend_from_slice(&amount.to_le_bytes());
3945
3946    Ok(instruction_data)
3947}
3948
3949/// Build token MintTo instruction data
3950fn build_token_mint_to_instruction(
3951    mint_account_idx: u16,
3952    dest_account_idx: u16,
3953    authority_idx: u16,
3954    amount: u64,
3955) -> Result<Vec<u8>> {
3956    let mut instruction_data = Vec::new();
3957
3958    // Instruction tag
3959    instruction_data.push(TOKEN_INSTRUCTION_MINT_TO);
3960
3961    // mint_account_index (u16)
3962    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
3963
3964    // dest_account_index (u16)
3965    instruction_data.extend_from_slice(&dest_account_idx.to_le_bytes());
3966
3967    // authority_index (u16)
3968    instruction_data.extend_from_slice(&authority_idx.to_le_bytes());
3969
3970    // amount (u64)
3971    instruction_data.extend_from_slice(&amount.to_le_bytes());
3972
3973    Ok(instruction_data)
3974}
3975
3976/// Build token Burn instruction data
3977fn build_token_burn_instruction(
3978    token_account_idx: u16,
3979    mint_account_idx: u16,
3980    authority_idx: u16,
3981    amount: u64,
3982) -> Result<Vec<u8>> {
3983    let mut instruction_data = Vec::new();
3984
3985    // Instruction tag
3986    instruction_data.push(TOKEN_INSTRUCTION_BURN);
3987
3988    // token_account_index (u16)
3989    instruction_data.extend_from_slice(&token_account_idx.to_le_bytes());
3990
3991    // mint_account_index (u16)
3992    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
3993
3994    // authority_index (u16)
3995    instruction_data.extend_from_slice(&authority_idx.to_le_bytes());
3996
3997    // amount (u64)
3998    instruction_data.extend_from_slice(&amount.to_le_bytes());
3999
4000    Ok(instruction_data)
4001}
4002
4003/// Build token FreezeAccount instruction data
4004fn build_token_freeze_account_instruction(
4005    token_account_idx: u16,
4006    mint_account_idx: u16,
4007    authority_idx: u16,
4008) -> Result<Vec<u8>> {
4009    let mut instruction_data = Vec::new();
4010
4011    // Instruction tag
4012    instruction_data.push(TOKEN_INSTRUCTION_FREEZE_ACCOUNT);
4013
4014    // token_account_index (u16)
4015    instruction_data.extend_from_slice(&token_account_idx.to_le_bytes());
4016
4017    // mint_account_index (u16)
4018    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
4019
4020    // authority_index (u16)
4021    instruction_data.extend_from_slice(&authority_idx.to_le_bytes());
4022
4023    Ok(instruction_data)
4024}
4025
4026/// Build token ThawAccount instruction data
4027fn build_token_thaw_account_instruction(
4028    token_account_idx: u16,
4029    mint_account_idx: u16,
4030    authority_idx: u16,
4031) -> Result<Vec<u8>> {
4032    let mut instruction_data = Vec::new();
4033
4034    // Instruction tag
4035    instruction_data.push(TOKEN_INSTRUCTION_THAW_ACCOUNT);
4036
4037    // token_account_index (u16)
4038    instruction_data.extend_from_slice(&token_account_idx.to_le_bytes());
4039
4040    // mint_account_index (u16)
4041    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
4042
4043    // authority_index (u16)
4044    instruction_data.extend_from_slice(&authority_idx.to_le_bytes());
4045
4046    Ok(instruction_data)
4047}
4048
4049/// Build token CloseAccount instruction data
4050fn build_token_close_account_instruction(
4051    token_account_idx: u16,
4052    destination_idx: u16,
4053    authority_idx: u16,
4054) -> Result<Vec<u8>> {
4055    let mut instruction_data = Vec::new();
4056
4057    // Instruction tag
4058    instruction_data.push(TOKEN_INSTRUCTION_CLOSE_ACCOUNT);
4059
4060    // token_account_index (u16)
4061    instruction_data.extend_from_slice(&token_account_idx.to_le_bytes());
4062
4063    // destination_index (u16)
4064    instruction_data.extend_from_slice(&destination_idx.to_le_bytes());
4065
4066    // authority_index (u16)
4067    instruction_data.extend_from_slice(&authority_idx.to_le_bytes());
4068
4069    Ok(instruction_data)
4070}
4071
4072fn build_wthru_initialize_mint_instruction(
4073    token_program_idx: u16,
4074    mint_account_idx: u16,
4075    vault_account_idx: u16,
4076    decimals: u8,
4077    mint_seed: [u8; 32],
4078    mint_proof: Vec<u8>,
4079    vault_proof: Vec<u8>,
4080) -> Result<Vec<u8>> {
4081    let mint_proof_len =
4082        u64::try_from(mint_proof.len()).map_err(|_| anyhow::anyhow!("mint proof too large"))?;
4083    let vault_proof_len =
4084        u64::try_from(vault_proof.len()).map_err(|_| anyhow::anyhow!("vault proof too large"))?;
4085
4086    let mut instruction_data = Vec::new();
4087    instruction_data.extend_from_slice(&TN_WTHRU_INSTRUCTION_INITIALIZE_MINT.to_le_bytes());
4088    instruction_data.extend_from_slice(&token_program_idx.to_le_bytes());
4089    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
4090    instruction_data.extend_from_slice(&vault_account_idx.to_le_bytes());
4091    instruction_data.push(decimals);
4092    instruction_data.extend_from_slice(&mint_seed);
4093    instruction_data.extend_from_slice(&mint_proof_len.to_le_bytes());
4094    instruction_data.extend_from_slice(&vault_proof_len.to_le_bytes());
4095    instruction_data.extend_from_slice(&mint_proof);
4096    instruction_data.extend_from_slice(&vault_proof);
4097
4098    Ok(instruction_data)
4099}
4100
4101fn build_wthru_deposit_instruction(
4102    token_program_idx: u16,
4103    vault_account_idx: u16,
4104    mint_account_idx: u16,
4105    dest_account_idx: u16,
4106) -> Result<Vec<u8>> {
4107    let mut instruction_data = Vec::new();
4108    instruction_data.extend_from_slice(&TN_WTHRU_INSTRUCTION_DEPOSIT.to_le_bytes());
4109    instruction_data.extend_from_slice(&token_program_idx.to_le_bytes());
4110    instruction_data.extend_from_slice(&vault_account_idx.to_le_bytes());
4111    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
4112    instruction_data.extend_from_slice(&dest_account_idx.to_le_bytes());
4113
4114    Ok(instruction_data)
4115}
4116
4117fn build_wthru_withdraw_instruction(
4118    token_program_idx: u16,
4119    vault_account_idx: u16,
4120    mint_account_idx: u16,
4121    wthru_token_account_idx: u16,
4122    owner_account_idx: u16,
4123    recipient_account_idx: u16,
4124    amount: u64,
4125) -> Result<Vec<u8>> {
4126    let mut instruction_data = Vec::new();
4127    instruction_data.extend_from_slice(&TN_WTHRU_INSTRUCTION_WITHDRAW.to_le_bytes());
4128    instruction_data.extend_from_slice(&token_program_idx.to_le_bytes());
4129    instruction_data.extend_from_slice(&vault_account_idx.to_le_bytes());
4130    instruction_data.extend_from_slice(&mint_account_idx.to_le_bytes());
4131    instruction_data.extend_from_slice(&wthru_token_account_idx.to_le_bytes());
4132    instruction_data.extend_from_slice(&owner_account_idx.to_le_bytes());
4133    instruction_data.extend_from_slice(&recipient_account_idx.to_le_bytes());
4134    instruction_data.extend_from_slice(&amount.to_le_bytes());
4135
4136    Ok(instruction_data)
4137}
4138
4139/// Build faucet Deposit instruction data
4140fn build_faucet_deposit_instruction(
4141    faucet_account_idx: u16,
4142    depositor_account_idx: u16,
4143    eoa_program_idx: u16,
4144    amount: u64,
4145) -> Result<Vec<u8>> {
4146    let mut instruction_data = Vec::new();
4147
4148    // Discriminant: TN_FAUCET_INSTRUCTION_DEPOSIT = 0 (u32, 4 bytes little-endian)
4149    instruction_data.extend_from_slice(&0u32.to_le_bytes());
4150
4151    // tn_faucet_deposit_args_t structure:
4152    // - faucet_account_idx (u16, 2 bytes little-endian)
4153    instruction_data.extend_from_slice(&faucet_account_idx.to_le_bytes());
4154
4155    // - depositor_account_idx (u16, 2 bytes little-endian)
4156    instruction_data.extend_from_slice(&depositor_account_idx.to_le_bytes());
4157
4158    // - eoa_program_idx (u16, 2 bytes little-endian)
4159    instruction_data.extend_from_slice(&eoa_program_idx.to_le_bytes());
4160
4161    // - amount (u64, 8 bytes little-endian)
4162    instruction_data.extend_from_slice(&amount.to_le_bytes());
4163
4164    Ok(instruction_data)
4165}
4166
4167/// Build faucet Withdraw instruction data
4168fn build_faucet_withdraw_instruction(
4169    faucet_account_idx: u16,
4170    recipient_account_idx: u16,
4171    amount: u64,
4172) -> Result<Vec<u8>> {
4173    let mut instruction_data = Vec::new();
4174
4175    // Discriminant: TN_FAUCET_INSTRUCTION_WITHDRAW = 1 (u32, 4 bytes little-endian)
4176    instruction_data.extend_from_slice(&1u32.to_le_bytes());
4177
4178    // tn_faucet_withdraw_args_t structure:
4179    // - faucet_account_idx (u16, 2 bytes little-endian)
4180    instruction_data.extend_from_slice(&faucet_account_idx.to_le_bytes());
4181
4182    // - recipient_account_idx (u16, 2 bytes little-endian)
4183    instruction_data.extend_from_slice(&recipient_account_idx.to_le_bytes());
4184
4185    // - amount (u64, 8 bytes little-endian)
4186    instruction_data.extend_from_slice(&amount.to_le_bytes());
4187
4188    Ok(instruction_data)
4189}
4190
4191#[repr(C, packed)]
4192struct NameServiceInitializeRootArgs {
4193    registrar_account_idx: u16,
4194    authority_account_idx: u16,
4195    root_name: [u8; TN_NAME_SERVICE_MAX_DOMAIN_LENGTH],
4196    root_name_length: u32,
4197}
4198
4199#[repr(C, packed)]
4200struct NameServiceRegisterSubdomainArgs {
4201    domain_account_idx: u16,
4202    parent_account_idx: u16,
4203    owner_account_idx: u16,
4204    authority_account_idx: u16,
4205    name: [u8; TN_NAME_SERVICE_MAX_DOMAIN_LENGTH],
4206    name_length: u32,
4207}
4208
4209#[repr(C, packed)]
4210struct NameServiceAppendRecordArgs {
4211    domain_account_idx: u16,
4212    owner_account_idx: u16,
4213    key_length: u32,
4214    key: [u8; TN_NAME_SERVICE_MAX_KEY_LENGTH],
4215    value_length: u32,
4216    value: [u8; TN_NAME_SERVICE_MAX_VALUE_LENGTH],
4217}
4218
4219#[repr(C, packed)]
4220struct NameServiceDeleteRecordArgs {
4221    domain_account_idx: u16,
4222    owner_account_idx: u16,
4223    key_length: u32,
4224    key: [u8; TN_NAME_SERVICE_MAX_KEY_LENGTH],
4225}
4226
4227#[repr(C, packed)]
4228struct NameServiceUnregisterSubdomainArgs {
4229    domain_account_idx: u16,
4230    owner_account_idx: u16,
4231}
4232
4233/// Build name service InitializeRoot instruction data
4234fn build_name_service_initialize_root_instruction(
4235    registrar_account_idx: u16,
4236    authority_account_idx: u16,
4237    root_name: &str,
4238    state_proof: Vec<u8>,
4239) -> Result<Vec<u8>> {
4240    let root_name_bytes = root_name.as_bytes();
4241    if root_name_bytes.is_empty()
4242        || root_name_bytes.len() > TN_NAME_SERVICE_MAX_DOMAIN_LENGTH
4243    {
4244        return Err(anyhow::anyhow!(
4245            "Root name length must be between 1 and {}",
4246            TN_NAME_SERVICE_MAX_DOMAIN_LENGTH
4247        ));
4248    }
4249
4250    let mut args = NameServiceInitializeRootArgs {
4251        registrar_account_idx,
4252        authority_account_idx,
4253        root_name: [0u8; TN_NAME_SERVICE_MAX_DOMAIN_LENGTH],
4254        root_name_length: root_name_bytes.len() as u32,
4255    };
4256    args.root_name[..root_name_bytes.len()].copy_from_slice(root_name_bytes);
4257
4258    let mut instruction_data = Vec::new();
4259    instruction_data.extend_from_slice(&TN_NAME_SERVICE_INSTRUCTION_INITIALIZE_ROOT.to_le_bytes());
4260
4261    let args_bytes = unsafe {
4262        std::slice::from_raw_parts(
4263            &args as *const _ as *const u8,
4264            std::mem::size_of::<NameServiceInitializeRootArgs>(),
4265        )
4266    };
4267    instruction_data.extend_from_slice(args_bytes);
4268
4269    instruction_data.extend_from_slice(&TN_NAME_SERVICE_PROOF_INLINE.to_le_bytes());
4270    instruction_data.extend_from_slice(&state_proof);
4271
4272    Ok(instruction_data)
4273}
4274
4275/// Build name service RegisterSubdomain instruction data
4276fn build_name_service_register_subdomain_instruction(
4277    domain_account_idx: u16,
4278    parent_account_idx: u16,
4279    owner_account_idx: u16,
4280    authority_account_idx: u16,
4281    domain_name: &str,
4282    state_proof: Vec<u8>,
4283) -> Result<Vec<u8>> {
4284    let domain_bytes = domain_name.as_bytes();
4285    if domain_bytes.is_empty()
4286        || domain_bytes.len() > TN_NAME_SERVICE_MAX_DOMAIN_LENGTH
4287    {
4288        return Err(anyhow::anyhow!(
4289            "Domain name length must be between 1 and {}",
4290            TN_NAME_SERVICE_MAX_DOMAIN_LENGTH
4291        ));
4292    }
4293
4294    let mut args = NameServiceRegisterSubdomainArgs {
4295        domain_account_idx,
4296        parent_account_idx,
4297        owner_account_idx,
4298        authority_account_idx,
4299        name: [0u8; TN_NAME_SERVICE_MAX_DOMAIN_LENGTH],
4300        name_length: domain_bytes.len() as u32,
4301    };
4302    args.name[..domain_bytes.len()].copy_from_slice(domain_bytes);
4303
4304    let mut instruction_data = Vec::new();
4305    instruction_data.extend_from_slice(&TN_NAME_SERVICE_INSTRUCTION_REGISTER_SUBDOMAIN.to_le_bytes());
4306
4307    let args_bytes = unsafe {
4308        std::slice::from_raw_parts(
4309            &args as *const _ as *const u8,
4310            std::mem::size_of::<NameServiceRegisterSubdomainArgs>(),
4311        )
4312    };
4313    instruction_data.extend_from_slice(args_bytes);
4314
4315    instruction_data.extend_from_slice(&TN_NAME_SERVICE_PROOF_INLINE.to_le_bytes());
4316    instruction_data.extend_from_slice(&state_proof);
4317
4318    Ok(instruction_data)
4319}
4320
4321/// Build name service AppendRecord instruction data
4322fn build_name_service_append_record_instruction(
4323    domain_account_idx: u16,
4324    owner_account_idx: u16,
4325    key: &[u8],
4326    value: &[u8],
4327) -> Result<Vec<u8>> {
4328    if key.is_empty() || key.len() > TN_NAME_SERVICE_MAX_KEY_LENGTH {
4329        return Err(anyhow::anyhow!(
4330            "Key length must be between 1 and {} bytes",
4331            TN_NAME_SERVICE_MAX_KEY_LENGTH
4332        ));
4333    }
4334    if value.len() > TN_NAME_SERVICE_MAX_VALUE_LENGTH {
4335        return Err(anyhow::anyhow!(
4336            "Value length must be <= {} bytes",
4337            TN_NAME_SERVICE_MAX_VALUE_LENGTH
4338        ));
4339    }
4340
4341    let mut args = NameServiceAppendRecordArgs {
4342        domain_account_idx,
4343        owner_account_idx,
4344        key_length: key.len() as u32,
4345        key: [0u8; TN_NAME_SERVICE_MAX_KEY_LENGTH],
4346        value_length: value.len() as u32,
4347        value: [0u8; TN_NAME_SERVICE_MAX_VALUE_LENGTH],
4348    };
4349    args.key[..key.len()].copy_from_slice(key);
4350    args.value[..value.len()].copy_from_slice(value);
4351
4352    let mut instruction_data = Vec::new();
4353    instruction_data.extend_from_slice(&TN_NAME_SERVICE_INSTRUCTION_APPEND_RECORD.to_le_bytes());
4354
4355    let args_bytes = unsafe {
4356        std::slice::from_raw_parts(
4357            &args as *const _ as *const u8,
4358            std::mem::size_of::<NameServiceAppendRecordArgs>(),
4359        )
4360    };
4361    instruction_data.extend_from_slice(args_bytes);
4362
4363    Ok(instruction_data)
4364}
4365
4366/// Build name service DeleteRecord instruction data
4367fn build_name_service_delete_record_instruction(
4368    domain_account_idx: u16,
4369    owner_account_idx: u16,
4370    key: &[u8],
4371) -> Result<Vec<u8>> {
4372    if key.is_empty() || key.len() > TN_NAME_SERVICE_MAX_KEY_LENGTH {
4373        return Err(anyhow::anyhow!(
4374            "Key length must be between 1 and {} bytes",
4375            TN_NAME_SERVICE_MAX_KEY_LENGTH
4376        ));
4377    }
4378
4379    let mut args = NameServiceDeleteRecordArgs {
4380        domain_account_idx,
4381        owner_account_idx,
4382        key_length: key.len() as u32,
4383        key: [0u8; TN_NAME_SERVICE_MAX_KEY_LENGTH],
4384    };
4385    args.key[..key.len()].copy_from_slice(key);
4386
4387    let mut instruction_data = Vec::new();
4388    instruction_data.extend_from_slice(&TN_NAME_SERVICE_INSTRUCTION_DELETE_RECORD.to_le_bytes());
4389
4390    let args_bytes = unsafe {
4391        std::slice::from_raw_parts(
4392            &args as *const _ as *const u8,
4393            std::mem::size_of::<NameServiceDeleteRecordArgs>(),
4394        )
4395    };
4396    instruction_data.extend_from_slice(args_bytes);
4397
4398    Ok(instruction_data)
4399}
4400
4401/// Build name service UnregisterSubdomain instruction data
4402fn build_name_service_unregister_subdomain_instruction(
4403    domain_account_idx: u16,
4404    owner_account_idx: u16,
4405) -> Result<Vec<u8>> {
4406    let args = NameServiceUnregisterSubdomainArgs {
4407        domain_account_idx,
4408        owner_account_idx,
4409    };
4410
4411    let mut instruction_data = Vec::new();
4412    instruction_data.extend_from_slice(&TN_NAME_SERVICE_INSTRUCTION_UNREGISTER.to_le_bytes());
4413
4414    let args_bytes = unsafe {
4415        std::slice::from_raw_parts(
4416            &args as *const _ as *const u8,
4417            std::mem::size_of::<NameServiceUnregisterSubdomainArgs>(),
4418        )
4419    };
4420    instruction_data.extend_from_slice(args_bytes);
4421
4422    Ok(instruction_data)
4423}
4424
4425/// Build thru registrar InitializeRegistry instruction data
4426fn build_thru_registrar_initialize_registry_instruction(
4427    config_account_idx: u16,
4428    name_service_program_idx: u16,
4429    root_registrar_account_idx: u16,
4430    treasurer_account_idx: u16,
4431    token_mint_account_idx: u16,
4432    token_program_idx: u16,
4433    root_domain_name: &str,
4434    price_per_year: u64,
4435    config_proof: Vec<u8>,
4436    registrar_proof: Vec<u8>,
4437) -> Result<Vec<u8>> {
4438    let mut instruction_data = Vec::new();
4439
4440    // Discriminant: TN_THRU_REGISTRAR_INSTRUCTION_INITIALIZE_REGISTRY = 0 (u32, 4 bytes little-endian)
4441    instruction_data.extend_from_slice(&TN_THRU_REGISTRAR_INSTRUCTION_INITIALIZE_REGISTRY.to_le_bytes());
4442
4443    // tn_thru_registrar_initialize_registry_args_t structure:
4444    // - config_account_idx (u16, 2 bytes little-endian)
4445    instruction_data.extend_from_slice(&config_account_idx.to_le_bytes());
4446    // - name_service_program_account_idx (u16, 2 bytes little-endian)
4447    instruction_data.extend_from_slice(&name_service_program_idx.to_le_bytes());
4448    // - root_registrar_account_idx (u16, 2 bytes little-endian)
4449    instruction_data.extend_from_slice(&root_registrar_account_idx.to_le_bytes());
4450    // - treasurer_account_idx (u16, 2 bytes little-endian)
4451    instruction_data.extend_from_slice(&treasurer_account_idx.to_le_bytes());
4452    // - token_mint_account_idx (u16, 2 bytes little-endian)
4453    instruction_data.extend_from_slice(&token_mint_account_idx.to_le_bytes());
4454    // - token_program_account_idx (u16, 2 bytes little-endian)
4455    instruction_data.extend_from_slice(&token_program_idx.to_le_bytes());
4456    // - root_domain_name (64 bytes, padded with zeros)
4457    let domain_bytes = root_domain_name.as_bytes();
4458    if domain_bytes.len() > 64 {
4459        return Err(anyhow::anyhow!("Root domain name must be 64 characters or less"));
4460    }
4461    let mut domain_padded = [0u8; 64];
4462    domain_padded[..domain_bytes.len()].copy_from_slice(domain_bytes);
4463    instruction_data.extend_from_slice(&domain_padded);
4464    // - root_domain_name_length (u32, 4 bytes little-endian)
4465    instruction_data.extend_from_slice(&(domain_bytes.len() as u32).to_le_bytes());
4466    // - price_per_year (u64, 8 bytes little-endian)
4467    instruction_data.extend_from_slice(&price_per_year.to_le_bytes());
4468
4469    // Variable-length proofs follow:
4470    // - config_proof (variable length)
4471    instruction_data.extend_from_slice(&config_proof);
4472    // - registrar_proof (variable length)
4473    instruction_data.extend_from_slice(&registrar_proof);
4474
4475    Ok(instruction_data)
4476}
4477
4478/// Build thru registrar PurchaseDomain instruction data
4479fn build_thru_registrar_purchase_domain_instruction(
4480    config_account_idx: u16,
4481    lease_account_idx: u16,
4482    domain_account_idx: u16,
4483    name_service_program_idx: u16,
4484    root_registrar_account_idx: u16,
4485    treasurer_account_idx: u16,
4486    payer_token_account_idx: u16,
4487    token_mint_account_idx: u16,
4488    token_program_idx: u16,
4489    domain_name: &str,
4490    years: u8,
4491    lease_proof: Vec<u8>,
4492    domain_proof: Vec<u8>,
4493) -> Result<Vec<u8>> {
4494    let mut instruction_data = Vec::new();
4495
4496    // Discriminant: TN_THRU_REGISTRAR_INSTRUCTION_PURCHASE_DOMAIN = 1 (u32, 4 bytes little-endian)
4497    instruction_data.extend_from_slice(&TN_THRU_REGISTRAR_INSTRUCTION_PURCHASE_DOMAIN.to_le_bytes());
4498
4499    // tn_thru_registrar_purchase_domain_args_t structure:
4500    // - config_account_idx (u16, 2 bytes little-endian)
4501    instruction_data.extend_from_slice(&config_account_idx.to_le_bytes());
4502    // - lease_account_idx (u16, 2 bytes little-endian)
4503    instruction_data.extend_from_slice(&lease_account_idx.to_le_bytes());
4504    // - domain_account_idx (u16, 2 bytes little-endian)
4505    instruction_data.extend_from_slice(&domain_account_idx.to_le_bytes());
4506    // - name_service_program_account_idx (u16, 2 bytes little-endian)
4507    instruction_data.extend_from_slice(&name_service_program_idx.to_le_bytes());
4508    // - root_registrar_account_idx (u16, 2 bytes little-endian)
4509    instruction_data.extend_from_slice(&root_registrar_account_idx.to_le_bytes());
4510    // - treasurer_account_idx (u16, 2 bytes little-endian)
4511    instruction_data.extend_from_slice(&treasurer_account_idx.to_le_bytes());
4512    // - payer_token_account_idx (u16, 2 bytes little-endian)
4513    instruction_data.extend_from_slice(&payer_token_account_idx.to_le_bytes());
4514    // - token_mint_account_idx (u16, 2 bytes little-endian)
4515    instruction_data.extend_from_slice(&token_mint_account_idx.to_le_bytes());
4516    // - token_program_account_idx (u16, 2 bytes little-endian)
4517    instruction_data.extend_from_slice(&token_program_idx.to_le_bytes());
4518    // - domain_name (64 bytes, padded with zeros)
4519    let domain_bytes = domain_name.as_bytes();
4520    if domain_bytes.len() > 64 {
4521        return Err(anyhow::anyhow!("Domain name must be 64 characters or less"));
4522    }
4523    let mut domain_padded = [0u8; 64];
4524    domain_padded[..domain_bytes.len()].copy_from_slice(domain_bytes);
4525    instruction_data.extend_from_slice(&domain_padded);
4526    // - domain_name_length (u32, 4 bytes little-endian)
4527    instruction_data.extend_from_slice(&(domain_bytes.len() as u32).to_le_bytes());
4528    // - years (u8, 1 byte)
4529    instruction_data.push(years);
4530
4531    // Variable-length proofs follow:
4532    // - lease_proof (variable length)
4533    instruction_data.extend_from_slice(&lease_proof);
4534    // - domain_proof (variable length)
4535    instruction_data.extend_from_slice(&domain_proof);
4536
4537    Ok(instruction_data)
4538}
4539
4540/// Build thru registrar RenewLease instruction data
4541fn build_thru_registrar_renew_lease_instruction(
4542    config_account_idx: u16,
4543    lease_account_idx: u16,
4544    treasurer_account_idx: u16,
4545    payer_token_account_idx: u16,
4546    token_mint_account_idx: u16,
4547    token_program_idx: u16,
4548    years: u8,
4549) -> Result<Vec<u8>> {
4550    let mut instruction_data = Vec::new();
4551
4552    // Discriminant: TN_THRU_REGISTRAR_INSTRUCTION_RENEW_LEASE = 2 (u32, 4 bytes little-endian)
4553    instruction_data.extend_from_slice(&TN_THRU_REGISTRAR_INSTRUCTION_RENEW_LEASE.to_le_bytes());
4554
4555    // tn_thru_registrar_renew_lease_args_t structure:
4556    // - config_account_idx (u16, 2 bytes little-endian)
4557    instruction_data.extend_from_slice(&config_account_idx.to_le_bytes());
4558    // - lease_account_idx (u16, 2 bytes little-endian)
4559    instruction_data.extend_from_slice(&lease_account_idx.to_le_bytes());
4560    // - treasurer_account_idx (u16, 2 bytes little-endian)
4561    instruction_data.extend_from_slice(&treasurer_account_idx.to_le_bytes());
4562    // - payer_token_account_idx (u16, 2 bytes little-endian)
4563    instruction_data.extend_from_slice(&payer_token_account_idx.to_le_bytes());
4564    // - token_mint_account_idx (u16, 2 bytes little-endian)
4565    instruction_data.extend_from_slice(&token_mint_account_idx.to_le_bytes());
4566    // - token_program_account_idx (u16, 2 bytes little-endian)
4567    instruction_data.extend_from_slice(&token_program_idx.to_le_bytes());
4568    // - years (u8, 1 byte)
4569    instruction_data.push(years);
4570
4571    Ok(instruction_data)
4572}
4573
4574/// Build thru registrar ClaimExpiredDomain instruction data
4575fn build_thru_registrar_claim_expired_domain_instruction(
4576    config_account_idx: u16,
4577    lease_account_idx: u16,
4578    treasurer_account_idx: u16,
4579    payer_token_account_idx: u16,
4580    token_mint_account_idx: u16,
4581    token_program_idx: u16,
4582    years: u8,
4583) -> Result<Vec<u8>> {
4584    let mut instruction_data = Vec::new();
4585
4586    // Discriminant: TN_THRU_REGISTRAR_INSTRUCTION_CLAIM_EXPIRED_DOMAIN = 3 (u32, 4 bytes little-endian)
4587    instruction_data.extend_from_slice(&TN_THRU_REGISTRAR_INSTRUCTION_CLAIM_EXPIRED_DOMAIN.to_le_bytes());
4588
4589    // tn_thru_registrar_claim_expired_domain_args_t structure:
4590    // - config_account_idx (u16, 2 bytes little-endian)
4591    instruction_data.extend_from_slice(&config_account_idx.to_le_bytes());
4592    // - lease_account_idx (u16, 2 bytes little-endian)
4593    instruction_data.extend_from_slice(&lease_account_idx.to_le_bytes());
4594    // - treasurer_account_idx (u16, 2 bytes little-endian)
4595    instruction_data.extend_from_slice(&treasurer_account_idx.to_le_bytes());
4596    // - payer_token_account_idx (u16, 2 bytes little-endian)
4597    instruction_data.extend_from_slice(&payer_token_account_idx.to_le_bytes());
4598    // - token_mint_account_idx (u16, 2 bytes little-endian)
4599    instruction_data.extend_from_slice(&token_mint_account_idx.to_le_bytes());
4600    // - token_program_account_idx (u16, 2 bytes little-endian)
4601    instruction_data.extend_from_slice(&token_program_idx.to_le_bytes());
4602    // - years (u8, 1 byte)
4603    instruction_data.push(years);
4604
4605    Ok(instruction_data)
4606}