palinurus_core/
response.rs1use crate::base58::Pubkey;
11use crate::durable_nonce::{parse_nonce_account, NonceState, NonceVersion};
12use crate::rpc::{AccountInfo, TxSummary};
13
14#[derive(Clone, Copy, PartialEq, Eq, Debug)]
16pub enum AccountKind {
17 Nonce,
19 Attestation,
21 Other,
23}
24
25pub fn estimate_tokens(s: &str) -> usize {
29 s.chars().count() / 4
30}
31
32pub fn short_pubkey(pk: &Pubkey) -> String {
34 let s = pk.to_string();
35 let bytes = s.as_bytes();
36 if bytes.len() <= 8 {
37 return s;
38 }
39 format!("{}…{}", &s[..4], &s[s.len() - 4..])
40}
41
42pub fn shape_account_info(info: &AccountInfo, kind: AccountKind) -> String {
44 match kind {
45 AccountKind::Nonce => shape_nonce_account(info),
46 AccountKind::Attestation => format!(
47 "Attestation account owner={} len={}B lamports={} data[0:8]={}",
48 short_pubkey(&info.owner),
49 info.data.len(),
50 info.lamports,
51 hex_prefix(&info.data, 8),
52 ),
53 AccountKind::Other => format!(
54 "Account owner={} len={}B lamports={} executable={} data[0:8]={}",
55 short_pubkey(&info.owner),
56 info.data.len(),
57 info.lamports,
58 info.executable,
59 hex_prefix(&info.data, 8),
60 ),
61 }
62}
63
64fn shape_nonce_account(info: &AccountInfo) -> String {
65 match parse_nonce_account(&info.data) {
66 Ok(nonce) => match nonce.version {
67 NonceVersion::Legacy => "Nonce: legacy (unsupported for versioned tx)".to_string(),
68 NonceVersion::Current => match nonce.state {
69 NonceState::Uninitialized => "Nonce: current/uninitialized".to_string(),
70 NonceState::Initialized(d) => format!(
71 "Nonce: current/initialized auth={} nonce={} lamports_per_sig={}",
72 short_pubkey(&d.authority),
73 short_pubkey(&Pubkey::from_bytes(d.durable_nonce)),
74 d.lamports_per_signature,
75 ),
76 },
77 },
78 Err(e) => format!(
79 "Nonce: parse error ({:?}) owner={} len={}B data[0:8]={}",
80 e,
81 short_pubkey(&info.owner),
82 info.data.len(),
83 hex_prefix(&info.data, 8),
84 ),
85 }
86}
87
88pub fn shape_signatures(sigs: &[TxSummary]) -> String {
91 if sigs.is_empty() {
92 return "No recent transactions.".to_string();
93 }
94 let total = sigs.len();
95 const SHOW: usize = 5;
96 let head: Vec<String> = sigs.iter().take(SHOW).map(|t| {
97 let status = match &t.err {
98 None => "ok".to_string(),
99 Some(e) => format!("FAIL:{}", truncate(e, 20)),
100 };
101 let sig_short = truncate(&t.signature, 8);
102 format!("{}(slot {}, {})", sig_short, t.slot, status)
103 }).collect();
104 let mut out = format!("{} txs: {}", total, head.join(" | "));
105 if total > SHOW {
106 out.push_str(&format!(" | +{} more", total - SHOW));
107 }
108 out
109}
110
111pub fn shape_attestation_result(sig: &str, pda: &Pubkey, nonce: &Pubkey) -> String {
113 format!(
114 "Attested → PDA {} (nonce {}) sig {}",
115 short_pubkey(pda),
116 short_pubkey(nonce),
117 truncate(sig, 12),
118 )
119}
120
121fn hex_prefix(data: &[u8], n: usize) -> String {
124 let n = n.min(data.len());
125 let mut s = String::with_capacity(n * 2);
126 for b in data.iter().take(n) {
127 s.push_str(&format!("{:02x}", b));
128 }
129 if data.len() > n {
130 s.push('…');
131 }
132 s
133}
134
135fn truncate(s: &str, max: usize) -> String {
136 if s.chars().count() <= max {
137 return s.to_string();
138 }
139 let head: String = s.chars().take(max).collect();
140 format!("{head}…")
141}