Skip to main content

thru_base/
txn_tools.rs

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