Skip to main content

thru_base/
txn_tools.rs

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