light_client/interface/
light_program_interface.rs1use std::fmt::Debug;
10
11use light_account::Pack;
12use light_token::instruction::derive_token_ata;
13use solana_pubkey::Pubkey;
14
15use super::{AccountInterface, TokenAccountInterface};
16use crate::indexer::{CompressedAccount, CompressedTokenAccount};
17
18#[derive(Debug, Clone, PartialEq, Eq, Hash)]
20pub enum AccountToFetch {
21 Pda { address: Pubkey, program_id: Pubkey },
23 Token { address: Pubkey },
25 Ata { wallet_owner: Pubkey, mint: Pubkey },
27 Mint { address: Pubkey },
29}
30
31impl AccountToFetch {
32 pub fn pda(address: Pubkey, program_id: Pubkey) -> Self {
33 Self::Pda {
34 address,
35 program_id,
36 }
37 }
38
39 pub fn token(address: Pubkey) -> Self {
40 Self::Token { address }
41 }
42
43 pub fn ata(wallet_owner: Pubkey, mint: Pubkey) -> Self {
44 Self::Ata { wallet_owner, mint }
45 }
46
47 pub fn mint(address: Pubkey) -> Self {
48 Self::Mint { address }
49 }
50
51 #[must_use]
52 pub fn pubkey(&self) -> Pubkey {
53 match self {
54 Self::Pda { address, .. } => *address,
55 Self::Token { address } => *address,
56 Self::Ata { wallet_owner, mint } => derive_token_ata(wallet_owner, mint).0,
57 Self::Mint { address } => *address,
58 }
59 }
60}
61
62#[derive(Clone, Debug)]
68pub enum ColdContext {
69 Account(CompressedAccount),
71 Token(CompressedTokenAccount),
73}
74
75#[derive(Clone, Debug)]
79pub struct PdaSpec<V> {
80 pub interface: AccountInterface,
82 pub variant: V,
84 pub program_id: Pubkey,
86}
87
88impl<V> PdaSpec<V> {
89 #[must_use]
91 pub fn new(interface: AccountInterface, variant: V, program_id: Pubkey) -> Self {
92 Self {
93 interface,
94 variant,
95 program_id,
96 }
97 }
98
99 #[inline]
101 #[must_use]
102 pub fn address(&self) -> Pubkey {
103 self.interface.key
104 }
105
106 #[inline]
108 #[must_use]
109 pub fn program_id(&self) -> Pubkey {
110 self.program_id
111 }
112
113 #[inline]
115 #[must_use]
116 pub fn is_cold(&self) -> bool {
117 self.interface.is_cold()
118 }
119
120 #[inline]
122 #[must_use]
123 pub fn is_hot(&self) -> bool {
124 self.interface.is_hot()
125 }
126
127 #[must_use]
129 pub fn compressed(&self) -> Option<&CompressedAccount> {
130 match &self.interface.cold {
131 Some(ColdContext::Account(c)) => Some(c),
132 Some(ColdContext::Token(c)) => Some(&c.account),
133 None => None,
134 }
135 }
136
137 #[must_use]
139 pub fn compressed_token(&self) -> Option<&CompressedTokenAccount> {
140 self.interface.as_compressed_token()
141 }
142
143 #[must_use]
145 pub fn is_token_pda(&self) -> bool {
146 self.interface.as_compressed_token().is_some()
147 }
148
149 #[must_use]
151 pub fn hash(&self) -> Option<[u8; 32]> {
152 self.interface.hash()
153 }
154
155 #[inline]
157 #[must_use]
158 pub fn data(&self) -> &[u8] {
159 self.interface.data()
160 }
161}
162
163#[derive(Clone, Debug)]
165pub enum AccountSpec<V> {
166 Pda(PdaSpec<V>),
168 Ata(TokenAccountInterface),
170 Mint(AccountInterface),
172}
173
174impl<V> AccountSpec<V> {
175 #[inline]
176 #[must_use]
177 pub fn is_cold(&self) -> bool {
178 match self {
179 Self::Pda(s) => s.is_cold(),
180 Self::Ata(s) => s.is_cold(),
181 Self::Mint(s) => s.is_cold(),
182 }
183 }
184
185 #[inline]
186 #[must_use]
187 pub fn is_hot(&self) -> bool {
188 !self.is_cold()
189 }
190
191 #[must_use]
192 pub fn pubkey(&self) -> Pubkey {
193 match self {
194 Self::Pda(s) => s.address(),
195 Self::Ata(s) => s.key,
196 Self::Mint(s) => s.key,
197 }
198 }
199}
200
201impl<V> From<PdaSpec<V>> for AccountSpec<V> {
202 fn from(spec: PdaSpec<V>) -> Self {
203 Self::Pda(spec)
204 }
205}
206
207impl From<TokenAccountInterface> for AccountSpec<()> {
208 fn from(interface: TokenAccountInterface) -> Self {
209 Self::Ata(interface)
210 }
211}
212
213impl From<AccountInterface> for AccountSpec<()> {
214 fn from(interface: AccountInterface) -> Self {
215 Self::Mint(interface)
216 }
217}
218
219#[inline]
221#[must_use]
222pub fn any_cold<V>(specs: &[AccountSpec<V>]) -> bool {
223 specs.iter().any(|s| s.is_cold())
224}
225
226#[inline]
228#[must_use]
229pub fn all_hot<V>(specs: &[AccountSpec<V>]) -> bool {
230 specs.iter().all(|s| s.is_hot())
231}
232
233pub trait LightProgramInterface: Sized {
235 type Variant: Pack<solana_instruction::AccountMeta> + Clone + Debug;
237
238 type Instruction;
240
241 type Error: std::error::Error;
243
244 #[must_use]
246 fn program_id(&self) -> Pubkey;
247
248 fn from_keyed_accounts(accounts: &[AccountInterface]) -> Result<Self, Self::Error>;
250
251 #[must_use]
253 fn get_accounts_to_update(&self, ix: &Self::Instruction) -> Vec<AccountToFetch>;
254
255 fn update(&mut self, accounts: &[AccountInterface]) -> Result<(), Self::Error>;
257
258 #[must_use]
260 fn get_all_specs(&self) -> Vec<AccountSpec<Self::Variant>>;
261
262 #[must_use]
264 fn get_specs_for_instruction(&self, ix: &Self::Instruction) -> Vec<AccountSpec<Self::Variant>>;
265
266 #[must_use]
268 fn get_cold_specs(&self) -> Vec<AccountSpec<Self::Variant>> {
269 self.get_all_specs()
270 .into_iter()
271 .filter(|s| s.is_cold())
272 .collect()
273 }
274
275 #[must_use]
277 fn get_cold_specs_for_instruction(
278 &self,
279 ix: &Self::Instruction,
280 ) -> Vec<AccountSpec<Self::Variant>> {
281 self.get_specs_for_instruction(ix)
282 .into_iter()
283 .filter(|s| s.is_cold())
284 .collect()
285 }
286
287 #[must_use]
289 fn needs_loading(&self, ix: &Self::Instruction) -> bool {
290 any_cold(&self.get_specs_for_instruction(ix))
291 }
292}
293
294#[inline]
296#[must_use]
297pub fn discriminator(data: &[u8]) -> Option<[u8; 8]> {
298 data.get(..8).and_then(|s| s.try_into().ok())
299}
300
301#[inline]
303#[must_use]
304pub fn matches_discriminator(data: &[u8], disc: &[u8; 8]) -> bool {
305 discriminator(data) == Some(*disc)
306}