Skip to main content

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