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