solana_transaction_status_wasm/
extract_memos.rs

1use 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}