solana_transaction_status_wasm/
extract_memos.rs1use solana_message::AccountKeys;
2use solana_message::Message;
3use solana_message::SanitizedMessage;
4use solana_message::compiled_instruction::CompiledInstruction;
5use solana_pubkey::Pubkey;
6
7use crate::VersionedTransactionWithStatusMeta;
8use crate::parse_instruction::parse_memo_data;
9
10pub fn extract_and_fmt_memos<T: ExtractMemos>(message: &T) -> Option<String> {
11 let memos = message.extract_memos();
12 if memos.is_empty() {
13 None
14 } else {
15 Some(memos.join("; "))
16 }
17}
18
19fn extract_and_fmt_memo_data(data: &[u8]) -> String {
20 let memo_len = data.len();
21 let parsed_memo = parse_memo_data(data).unwrap_or_else(|_| "(unparseable)".to_string());
22 format!("[{memo_len}] {parsed_memo}")
23}
24
25pub trait ExtractMemos {
26 fn extract_memos(&self) -> Vec<String>;
27}
28
29impl ExtractMemos for Message {
30 fn extract_memos(&self) -> Vec<String> {
31 extract_memos_inner(
32 &AccountKeys::new(&self.account_keys, None),
33 &self.instructions,
34 )
35 }
36}
37
38impl ExtractMemos for SanitizedMessage {
39 fn extract_memos(&self) -> Vec<String> {
40 extract_memos_inner(&self.account_keys(), self.instructions())
41 }
42}
43
44impl ExtractMemos for VersionedTransactionWithStatusMeta {
45 fn extract_memos(&self) -> Vec<String> {
46 extract_memos_inner(
47 &self.account_keys(),
48 self.transaction.message.instructions(),
49 )
50 }
51}
52
53enum KeyType<'a> {
54 MemoProgram,
55 OtherProgram,
56 Unknown(&'a Pubkey),
57}
58
59fn extract_memos_inner(
60 account_keys: &AccountKeys,
61 instructions: &[CompiledInstruction],
62) -> Vec<String> {
63 let mut account_keys: Vec<KeyType> = account_keys.iter().map(KeyType::Unknown).collect();
64 instructions
65 .iter()
66 .filter_map(|ix| {
67 let index = ix.program_id_index as usize;
68 let key_type = account_keys.get(index)?;
69 let memo_data = match key_type {
70 KeyType::MemoProgram => Some(&ix.data),
71 KeyType::OtherProgram => None,
72 KeyType::Unknown(program_id) => {
73 if **program_id == spl_memo_interface::v1::id()
74 || **program_id == spl_memo_interface::v3::id()
75 {
76 account_keys[index] = KeyType::MemoProgram;
77 Some(&ix.data)
78 } else {
79 account_keys[index] = KeyType::OtherProgram;
80 None
81 }
82 }
83 }?;
84 Some(extract_and_fmt_memo_data(memo_data))
85 })
86 .collect()
87}
88
89#[cfg(test)]
90mod test {
91 use super::*;
92
93 #[test]
94 fn test_extract_memos_inner() {
95 let fee_payer = Pubkey::new_unique();
96 let another_program_id = Pubkey::new_unique();
97 let memo0 = "Test memo";
98 let memo1 = "🦖";
99 let expected_memos = vec![
100 format!("[{}] {}", memo0.len(), memo0),
101 format!("[{}] {}", memo1.len(), memo1),
102 ];
103 let memo_instructions = vec![
104 CompiledInstruction {
105 program_id_index: 1,
106 accounts: vec![],
107 data: memo0.as_bytes().to_vec(),
108 },
109 CompiledInstruction {
110 program_id_index: 2,
111 accounts: vec![],
112 data: memo1.as_bytes().to_vec(),
113 },
114 CompiledInstruction {
115 program_id_index: 3,
116 accounts: vec![],
117 data: memo1.as_bytes().to_vec(),
118 },
119 ];
120 let static_keys = vec![
121 fee_payer,
122 spl_memo_interface::v1::id(),
123 another_program_id,
124 spl_memo_interface::v3::id(),
125 ];
126 let account_keys = AccountKeys::new(&static_keys, None);
127
128 assert_eq!(
129 extract_memos_inner(&account_keys, &memo_instructions),
130 expected_memos
131 );
132 }
133}