light_client/interface/
light_program_interface.rs1use std::fmt::Debug;
9
10use light_account::Pack;
11use light_token::instruction::derive_token_ata;
12use solana_pubkey::Pubkey;
13
14use super::{AccountInterface, TokenAccountInterface};
15use crate::indexer::CompressedAccount;
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
19pub enum AccountToFetch {
20 Pda { address: Pubkey, program_id: Pubkey },
22 Token { address: Pubkey },
24 Ata { wallet_owner: Pubkey, mint: Pubkey },
26 Mint { address: Pubkey },
28}
29
30impl AccountToFetch {
31 pub fn pda(address: Pubkey, program_id: Pubkey) -> Self {
32 Self::Pda {
33 address,
34 program_id,
35 }
36 }
37
38 pub fn token(address: Pubkey) -> Self {
39 Self::Token { address }
40 }
41
42 pub fn ata(wallet_owner: Pubkey, mint: Pubkey) -> Self {
43 Self::Ata { wallet_owner, mint }
44 }
45
46 pub fn mint(address: Pubkey) -> Self {
47 Self::Mint { address }
48 }
49
50 #[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),
57 Self::Mint { address } => *address,
58 }
59 }
60}
61
62#[derive(Clone, Debug)]
66pub struct PdaSpec<V> {
67 pub interface: AccountInterface,
69 pub variant: V,
71 pub program_id: Pubkey,
73}
74
75impl<V> PdaSpec<V> {
76 #[must_use]
78 pub fn new(interface: AccountInterface, variant: V, program_id: Pubkey) -> Self {
79 Self {
80 interface,
81 variant,
82 program_id,
83 }
84 }
85
86 #[inline]
88 #[must_use]
89 pub fn address(&self) -> Pubkey {
90 self.interface.key
91 }
92
93 #[inline]
95 #[must_use]
96 pub fn program_id(&self) -> Pubkey {
97 self.program_id
98 }
99
100 #[inline]
102 #[must_use]
103 pub fn is_cold(&self) -> bool {
104 self.interface.is_cold()
105 }
106
107 #[inline]
109 #[must_use]
110 pub fn is_hot(&self) -> bool {
111 self.interface.is_hot()
112 }
113
114 #[must_use]
116 pub fn compressed(&self) -> Option<&CompressedAccount> {
117 self.interface.cold.as_ref()
118 }
119
120 #[must_use]
122 pub fn hash(&self) -> Option<[u8; 32]> {
123 self.interface.hash()
124 }
125
126 #[inline]
128 #[must_use]
129 pub fn data(&self) -> &[u8] {
130 self.interface.data()
131 }
132}
133
134#[derive(Clone, Debug)]
136pub enum AccountSpec<V> {
137 Pda(PdaSpec<V>),
139 Ata(Box<TokenAccountInterface>),
141 Mint(AccountInterface),
143}
144
145impl<V> AccountSpec<V> {
146 #[inline]
147 #[must_use]
148 pub fn is_cold(&self) -> bool {
149 match self {
150 Self::Pda(s) => s.is_cold(),
151 Self::Ata(s) => s.is_cold(),
152 Self::Mint(s) => s.is_cold(),
153 }
154 }
155
156 #[inline]
157 #[must_use]
158 pub fn is_hot(&self) -> bool {
159 !self.is_cold()
160 }
161
162 #[must_use]
163 pub fn pubkey(&self) -> Pubkey {
164 match self {
165 Self::Pda(s) => s.address(),
166 Self::Ata(s) => s.key,
167 Self::Mint(s) => s.key,
168 }
169 }
170}
171
172impl<V> From<PdaSpec<V>> for AccountSpec<V> {
173 fn from(spec: PdaSpec<V>) -> Self {
174 Self::Pda(spec)
175 }
176}
177
178impl From<TokenAccountInterface> for AccountSpec<()> {
179 fn from(interface: TokenAccountInterface) -> Self {
180 Self::Ata(Box::new(interface))
181 }
182}
183
184impl From<AccountInterface> for AccountSpec<()> {
185 fn from(interface: AccountInterface) -> Self {
186 Self::Mint(interface)
187 }
188}
189
190#[inline]
192#[must_use]
193pub fn any_cold<V>(specs: &[AccountSpec<V>]) -> bool {
194 specs.iter().any(|s| s.is_cold())
195}
196
197#[inline]
199#[must_use]
200pub fn all_hot<V>(specs: &[AccountSpec<V>]) -> bool {
201 specs.iter().all(|s| s.is_hot())
202}
203
204pub trait LightProgramInterface: Sized {
206 type Variant: Pack<solana_instruction::AccountMeta> + Clone + Debug;
208
209 type Instruction;
211
212 type Error: std::error::Error;
214
215 #[must_use]
217 fn program_id(&self) -> Pubkey;
218
219 fn from_keyed_accounts(accounts: &[AccountInterface]) -> Result<Self, Self::Error>;
221
222 #[must_use]
224 fn get_accounts_to_update(&self, ix: &Self::Instruction) -> Vec<AccountToFetch>;
225
226 fn update(&mut self, accounts: &[AccountInterface]) -> Result<(), Self::Error>;
228
229 #[must_use]
231 fn get_all_specs(&self) -> Vec<AccountSpec<Self::Variant>>;
232
233 #[must_use]
235 fn get_specs_for_instruction(&self, ix: &Self::Instruction) -> Vec<AccountSpec<Self::Variant>>;
236
237 #[must_use]
239 fn get_cold_specs(&self) -> Vec<AccountSpec<Self::Variant>> {
240 self.get_all_specs()
241 .into_iter()
242 .filter(|s| s.is_cold())
243 .collect()
244 }
245
246 #[must_use]
248 fn get_cold_specs_for_instruction(
249 &self,
250 ix: &Self::Instruction,
251 ) -> Vec<AccountSpec<Self::Variant>> {
252 self.get_specs_for_instruction(ix)
253 .into_iter()
254 .filter(|s| s.is_cold())
255 .collect()
256 }
257
258 #[must_use]
260 fn needs_loading(&self, ix: &Self::Instruction) -> bool {
261 any_cold(&self.get_specs_for_instruction(ix))
262 }
263}
264
265#[inline]
267#[must_use]
268pub fn discriminator(data: &[u8]) -> Option<[u8; 8]> {
269 data.get(..8).and_then(|s| s.try_into().ok())
270}
271
272#[inline]
274#[must_use]
275pub fn matches_discriminator(data: &[u8], disc: &[u8; 8]) -> bool {
276 discriminator(data) == Some(*disc)
277}