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