solana_extra_wasm/account_decoder/
mod.rs1pub mod parse_token;
2pub mod parse_token_extension;
3
4use std::str::FromStr;
5
6use serde_json::Value;
7use solana_sdk::{
8 account::{ReadableAccount, WritableAccount},
9 clock::Epoch,
10 instruction::InstructionError,
11 pubkey::Pubkey,
12};
13use thiserror::Error;
16
17pub type StringAmount = String;
18pub type StringDecimals = String;
19
20#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
21#[serde(rename_all = "camelCase")]
22pub struct UiDataSliceConfig {
23 pub offset: usize,
24 pub length: usize,
25}
26
27fn slice_data(data: &[u8], data_slice_config: Option<UiDataSliceConfig>) -> &[u8] {
28 if let Some(UiDataSliceConfig { offset, length }) = data_slice_config {
29 if offset >= data.len() {
30 &[]
31 } else if length > data.len() - offset {
32 &data[offset..]
33 } else {
34 &data[offset..offset + length]
35 }
36 } else {
37 data
38 }
39}
40
41#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq)]
43#[serde(rename_all = "camelCase")]
44pub struct UiAccount {
45 pub lamports: u64,
46 pub data: UiAccountData,
47 pub owner: String,
48 pub executable: bool,
49 pub rent_epoch: Epoch,
50}
51
52#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
53#[serde(rename_all = "camelCase", untagged)]
54pub enum UiAccountData {
55 LegacyBinary(String), Json(ParsedAccount),
57 Binary(String, UiAccountEncoding),
58}
59
60#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, Hash)]
61#[serde(rename_all = "camelCase")]
62pub enum UiAccountEncoding {
63 Binary, Base58,
65 Base64,
66 JsonParsed,
67 }
71
72pub const MAX_BASE58_BYTES: usize = 128;
73
74impl UiAccount {
75 fn encode_bs58<T: ReadableAccount>(
76 account: &T,
77 data_slice_config: Option<UiDataSliceConfig>,
78 ) -> String {
79 if account.data().len() <= MAX_BASE58_BYTES {
80 bs58::encode(slice_data(account.data(), data_slice_config)).into_string()
81 } else {
82 "error: data too large for bs58 encoding".to_string()
83 }
84 }
85
86 pub fn encode<T: ReadableAccount>(
87 pubkey: &Pubkey,
88 account: &T,
89 encoding: UiAccountEncoding,
90 additional_data: Option<AccountAdditionalData>,
91 data_slice_config: Option<UiDataSliceConfig>,
92 ) -> Self {
93 let data = match encoding {
94 UiAccountEncoding::Binary => {
95 let data = Self::encode_bs58(account, data_slice_config);
96 UiAccountData::LegacyBinary(data)
97 }
98 UiAccountEncoding::Base58 => {
99 let data = Self::encode_bs58(account, data_slice_config);
100 UiAccountData::Binary(data, encoding)
101 }
102 UiAccountEncoding::Base64 => UiAccountData::Binary(
103 base64::encode(slice_data(account.data(), data_slice_config)),
104 encoding,
105 ),
106 UiAccountEncoding::JsonParsed => {
121 if let Ok(parsed_data) =
122 parse_account_data(pubkey, account.owner(), account.data(), additional_data)
123 {
124 UiAccountData::Json(parsed_data)
125 } else {
126 UiAccountData::Binary(base64::encode(account.data()), UiAccountEncoding::Base64)
127 }
128 }
129 };
130 UiAccount {
131 lamports: account.lamports(),
132 data,
133 owner: account.owner().to_string(),
134 executable: account.executable(),
135 rent_epoch: account.rent_epoch(),
136 }
137 }
138
139 pub fn decode<T: WritableAccount>(&self) -> Option<T> {
140 let data = match &self.data {
141 UiAccountData::Json(_) => None,
142 UiAccountData::LegacyBinary(blob) => bs58::decode(blob).into_vec().ok(),
143 UiAccountData::Binary(blob, encoding) => match encoding {
144 UiAccountEncoding::Base58 => bs58::decode(blob).into_vec().ok(),
145 UiAccountEncoding::Base64 => base64::decode(blob).ok(),
146 UiAccountEncoding::Binary | UiAccountEncoding::JsonParsed => None,
155 },
156 }?;
157 Some(T::create(
158 self.lamports,
159 data,
160 Pubkey::from_str(&self.owner).ok()?,
161 self.executable,
162 self.rent_epoch,
163 ))
164 }
165}
166
167#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
168#[serde(rename_all = "camelCase")]
169pub struct ParsedAccount {
170 pub program: String,
171 pub parsed: Value,
172 pub space: u64,
173}
174
175#[derive(Default)]
176pub struct AccountAdditionalData {
177 pub spl_token_decimals: Option<u8>,
178}
179
180#[derive(Debug, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub enum ParsableAccount {
183 BpfUpgradeableLoader,
184 Config,
185 Nonce,
186 SplToken,
187 SplToken2022,
188 Stake,
189 Sysvar,
190 Vote,
191}
192
193#[derive(Error, Debug)]
194pub enum ParseAccountError {
195 #[error("{0:?} account not parsable")]
196 AccountNotParsable(ParsableAccount),
197
198 #[error("Program not parsable")]
199 ProgramNotParsable,
200
201 #[error("Additional data required to parse: {0}")]
202 AdditionalDataMissing(String),
203
204 #[error("Instruction error")]
205 InstructionError(#[from] InstructionError),
206
207 #[error("Serde json error")]
208 SerdeJsonError(#[from] serde_json::error::Error),
209}
210
211pub fn parse_account_data(
212 _pubkey: &Pubkey,
213 _program_id: &Pubkey,
214 _data: &[u8],
215 _additional_data: Option<AccountAdditionalData>,
216) -> Result<ParsedAccount, ParseAccountError> {
217 Err(ParseAccountError::ProgramNotParsable)
219 }