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