light_client/interface/
light_program_interface.rs1use std::fmt::Debug;
10
11use light_sdk::interface::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 self.interface.as_compressed_account()
131 }
132
133 #[must_use]
135 pub fn hash(&self) -> Option<[u8; 32]> {
136 self.interface.hash()
137 }
138
139 #[inline]
141 #[must_use]
142 pub fn data(&self) -> &[u8] {
143 self.interface.data()
144 }
145}
146
147#[derive(Clone, Debug)]
149pub enum AccountSpec<V> {
150 Pda(PdaSpec<V>),
152 Ata(TokenAccountInterface),
154 Mint(AccountInterface),
156}
157
158impl<V> AccountSpec<V> {
159 #[inline]
160 #[must_use]
161 pub fn is_cold(&self) -> bool {
162 match self {
163 Self::Pda(s) => s.is_cold(),
164 Self::Ata(s) => s.is_cold(),
165 Self::Mint(s) => s.is_cold(),
166 }
167 }
168
169 #[inline]
170 #[must_use]
171 pub fn is_hot(&self) -> bool {
172 !self.is_cold()
173 }
174
175 #[must_use]
176 pub fn pubkey(&self) -> Pubkey {
177 match self {
178 Self::Pda(s) => s.address(),
179 Self::Ata(s) => s.key,
180 Self::Mint(s) => s.key,
181 }
182 }
183}
184
185impl<V> From<PdaSpec<V>> for AccountSpec<V> {
186 fn from(spec: PdaSpec<V>) -> Self {
187 Self::Pda(spec)
188 }
189}
190
191impl From<TokenAccountInterface> for AccountSpec<()> {
192 fn from(interface: TokenAccountInterface) -> Self {
193 Self::Ata(interface)
194 }
195}
196
197impl From<AccountInterface> for AccountSpec<()> {
198 fn from(interface: AccountInterface) -> Self {
199 Self::Mint(interface)
200 }
201}
202
203#[inline]
205#[must_use]
206pub fn any_cold<V>(specs: &[AccountSpec<V>]) -> bool {
207 specs.iter().any(|s| s.is_cold())
208}
209
210#[inline]
212#[must_use]
213pub fn all_hot<V>(specs: &[AccountSpec<V>]) -> bool {
214 specs.iter().all(|s| s.is_hot())
215}
216
217pub trait LightProgramInterface: Sized {
219 type Variant: Pack + Clone + Debug;
221
222 type Instruction;
224
225 type Error: std::error::Error;
227
228 #[must_use]
230 fn program_id(&self) -> Pubkey;
231
232 fn from_keyed_accounts(accounts: &[AccountInterface]) -> Result<Self, Self::Error>;
234
235 #[must_use]
237 fn get_accounts_to_update(&self, ix: &Self::Instruction) -> Vec<AccountToFetch>;
238
239 fn update(&mut self, accounts: &[AccountInterface]) -> Result<(), Self::Error>;
241
242 #[must_use]
244 fn get_all_specs(&self) -> Vec<AccountSpec<Self::Variant>>;
245
246 #[must_use]
248 fn get_specs_for_instruction(&self, ix: &Self::Instruction) -> Vec<AccountSpec<Self::Variant>>;
249
250 #[must_use]
252 fn get_cold_specs(&self) -> Vec<AccountSpec<Self::Variant>> {
253 self.get_all_specs()
254 .into_iter()
255 .filter(|s| s.is_cold())
256 .collect()
257 }
258
259 #[must_use]
261 fn get_cold_specs_for_instruction(
262 &self,
263 ix: &Self::Instruction,
264 ) -> Vec<AccountSpec<Self::Variant>> {
265 self.get_specs_for_instruction(ix)
266 .into_iter()
267 .filter(|s| s.is_cold())
268 .collect()
269 }
270
271 #[must_use]
273 fn needs_loading(&self, ix: &Self::Instruction) -> bool {
274 any_cold(&self.get_specs_for_instruction(ix))
275 }
276}
277
278#[inline]
280#[must_use]
281pub fn discriminator(data: &[u8]) -> Option<[u8; 8]> {
282 data.get(..8).and_then(|s| s.try_into().ok())
283}
284
285#[inline]
287#[must_use]
288pub fn matches_discriminator(data: &[u8], disc: &[u8; 8]) -> bool {
289 discriminator(data) == Some(*disc)
290}