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