1use anchor_lang::prelude::ProgramError;
2use anchor_lang::prelude::*;
3use anchor_lang::solana_program::instruction::Instruction;
4use anchor_lang::solana_program::program::invoke_signed;
5use ephemeral_rollups_sdk::anchor::{delegate, ephemeral};
6use ephemeral_rollups_sdk::cpi::DelegateConfig;
7
8declare_id!("LLMrieZMpbJFwN52WgmBNMxYojrpRVYXdC1RCweEbab");
9
10const ORACLE_IDENTITY: Pubkey = pubkey!("A1ooMmN1fz6LbEFrjh6GukFS2ZeRYFzdyFjeafyyS7Ca");
11
12#[ephemeral]
13#[program]
14pub mod solana_gpt_oracle {
15 use super::*;
16
17 pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
18 Ok(())
19 }
20
21 pub fn create_llm_context(ctx: Context<CreateLlmContext>, text: String) -> Result<()> {
22 let context_account = &mut ctx.accounts.context_account;
23 context_account.text = text;
24 ctx.accounts.counter.count += 1;
25 Ok(())
26 }
27
28 pub fn interact_with_llm(
29 ctx: Context<InteractWithLlm>,
30 text: String,
31 callback_program_id: Pubkey,
32 callback_discriminator: [u8; 8],
33 account_metas: Option<Vec<AccountMeta>>,
34 ) -> Result<()> {
35 let interaction = &mut ctx.accounts.interaction;
36 let current_len = interaction.to_account_info().data_len();
37 let space = Interaction::space(&text, account_metas.as_ref().map_or(0, |m| m.len()));
38 let rent = Rent::get()?;
39
40 let mut additional_rent = rent.minimum_balance(space);
41
42 let interaction_info = interaction.to_account_info();
43 let payer_info = ctx.accounts.payer.to_account_info();
44 let system_program_info = ctx.accounts.system_program.to_account_info();
45
46 if interaction_info.owner.eq(&anchor_lang::system_program::ID) {
47 let create_instruction =
48 anchor_lang::solana_program::system_instruction::create_account(
49 &ctx.accounts.payer.key(),
50 &interaction.key(),
51 additional_rent,
52 space as u64,
53 &crate::ID,
54 );
55
56 let payer = ctx.accounts.payer.key();
57 let context_account = ctx.accounts.context_account.key();
58 let signer_seeds: &[&[&[u8]]] = &[&[
59 Interaction::seed(),
60 payer.as_ref(),
61 context_account.as_ref(),
62 &[ctx.bumps.interaction],
63 ]];
64
65 anchor_lang::solana_program::program::invoke_signed(
66 &create_instruction,
67 &[
68 payer_info.clone(),
69 interaction_info.clone(),
70 system_program_info.clone(),
71 ],
72 signer_seeds,
73 )?;
74 } else {
75 additional_rent = additional_rent.saturating_sub(rent.minimum_balance(current_len));
76 interaction_info.realloc(space, false)?;
77 if additional_rent > 0 {
78 let cpi_context = CpiContext::new(
79 system_program_info,
80 anchor_lang::system_program::Transfer {
81 from: payer_info.clone(),
82 to: interaction_info.clone(),
83 },
84 );
85 anchor_lang::system_program::transfer(cpi_context, additional_rent)?;
86 }
87 }
88
89 let mut interaction_data = interaction.try_borrow_mut_data()?;
90 let mut interaction =
91 Interaction::try_deserialize_unchecked(&mut interaction_data.as_ref())
92 .unwrap_or_default();
93
94 interaction.context = ctx.accounts.context_account.key();
95 interaction.user = ctx.accounts.payer.key();
96 interaction.text = text;
97 interaction.callback_program_id = callback_program_id;
98 interaction.callback_discriminator = callback_discriminator;
99 interaction.callback_account_metas = account_metas.unwrap_or_default();
100 interaction.is_processed = false;
101
102 interaction.try_serialize(&mut interaction_data.as_mut())?;
103 Ok(())
104 }
105
106 pub fn callback_from_llm<'info>(
107 ctx: Context<'_, '_, '_, 'info, CallbackFromLlm<'info>>,
108 response: String,
109 ) -> Result<()> {
110 let response_data = [
111 ctx.accounts.interaction.callback_discriminator.to_vec(),
112 response.try_to_vec()?,
113 ]
114 .concat();
115
116 let mut accounts_metas: Vec<anchor_lang::solana_program::instruction::AccountMeta> =
118 vec![anchor_lang::solana_program::instruction::AccountMeta {
119 pubkey: ctx.accounts.identity.key(),
120 is_signer: true,
121 is_writable: false,
122 }];
123 accounts_metas.extend(
124 ctx.accounts
125 .interaction
126 .callback_account_metas
127 .iter()
128 .map(
129 |meta| anchor_lang::solana_program::instruction::AccountMeta {
130 pubkey: meta.pubkey,
131 is_signer: meta.is_signer,
132 is_writable: meta.is_writable,
133 },
134 ),
135 );
136
137 if ctx
139 .remaining_accounts
140 .iter()
141 .any(|acc| acc.key().eq(&ctx.accounts.payer.key()))
142 {
143 return Err(ProgramError::InvalidAccountData.into());
144 }
145
146 ctx.accounts.interaction.is_processed = true;
148
149 let instruction = Instruction {
151 program_id: ctx.accounts.program.key(),
152 accounts: accounts_metas,
153 data: response_data.to_vec(),
154 };
155 let mut remaining_accounts: Vec<AccountInfo<'info>> = ctx.remaining_accounts.to_vec();
156 remaining_accounts.push(ctx.accounts.identity.to_account_info());
157 remaining_accounts.push(ctx.accounts.program.to_account_info());
158 let identity_bump = ctx.bumps.identity;
159 invoke_signed(
160 &instruction,
161 &remaining_accounts,
162 &[&[b"identity", &[identity_bump]]],
163 )?;
164 Ok(())
165 }
166
167 pub fn callback_from_oracle(ctx: Context<CallbackFromOracle>, response: String) -> Result<()> {
168 if !ctx.accounts.identity.to_account_info().is_signer {
169 return Err(ProgramError::InvalidAccountData.into());
170 }
171 msg!("Callback response: {:?}", response);
172 Ok(())
173 }
174
175 pub fn delegate_interaction(ctx: Context<DelegateInteraction>) -> Result<()> {
176 ctx.accounts.delegate_interaction(
177 &ctx.accounts.payer,
178 &[
179 Interaction::seed(),
180 &ctx.accounts.payer.key().to_bytes(),
181 &ctx.accounts.context_account.key().to_bytes(),
182 ],
183 DelegateConfig::default(),
184 )?;
185 Ok(())
186 }
187}
188
189#[derive(Accounts)]
192pub struct Initialize<'info> {
193 #[account(mut)]
194 pub payer: Signer<'info>,
195 #[account(
196 init,
197 payer = payer,
198 space = 8,
199 seeds = [b"identity"],
200 bump
201 )]
202 pub identity: Account<'info, Identity>,
203 #[account(
204 init,
205 payer = payer,
206 space = 8 + 32,
207 seeds = [b"counter"],
208 bump
209 )]
210 pub counter: Account<'info, Counter>,
211 pub system_program: Program<'info, System>,
212}
213
214#[derive(Accounts)]
215#[instruction(text: String)]
216pub struct CreateLlmContext<'info> {
217 #[account(mut)]
218 pub payer: Signer<'info>,
219 #[account(mut,
220 seeds = [b"counter"],
221 bump
222 )]
223 pub counter: Account<'info, Counter>,
224 #[account(
225 init,
226 payer = payer,
227 space = 8 + text.as_bytes().len() + 8,
228 seeds = [ContextAccount::seed(), &counter.count.to_le_bytes()],
229 bump
230 )]
231 pub context_account: Account<'info, ContextAccount>,
232 pub system_program: Program<'info, System>,
233}
234
235#[derive(Accounts)]
236#[instruction(text: String, callback_program_id: Pubkey, callback_discriminator: [u8; 8], account_metas: Option<Vec<AccountMeta>>)]
237pub struct InteractWithLlm<'info> {
238 #[account(mut)]
239 pub payer: Signer<'info>,
240 #[account(
242 mut,
243 seeds = [Interaction::seed(), payer.key().as_ref(), context_account.key().as_ref()],
244 bump
245 )]
246 pub interaction: AccountInfo<'info>,
247 pub context_account: Account<'info, ContextAccount>,
249 pub system_program: Program<'info, System>,
250}
251
252#[derive(Accounts)]
253pub struct CallbackFromLlm<'info> {
254 #[account(mut, address = ORACLE_IDENTITY)]
255 pub payer: Signer<'info>,
256 #[account(seeds = [b"identity"], bump)]
257 pub identity: Account<'info, Identity>,
258 #[account(mut)]
260 pub interaction: Account<'info, Interaction>,
261 pub program: AccountInfo<'info>,
263}
264
265#[derive(Accounts)]
266pub struct CallbackFromOracle<'info> {
267 #[account(seeds = [b"identity"], bump)]
268 pub identity: Account<'info, Identity>,
269}
270
271#[delegate]
272#[derive(Accounts)]
273pub struct DelegateInteraction<'info> {
274 #[account(mut)]
275 pub payer: Signer<'info>,
276 #[account(
278 mut, del,
279 seeds = [Interaction::seed(), payer.key().as_ref(), context_account.key().as_ref()],
280 bump
281 )]
282 pub interaction: AccountInfo<'info>,
283 pub context_account: Account<'info, ContextAccount>,
285}
286
287#[account]
290pub struct ContextAccount {
291 pub text: String,
292}
293
294impl ContextAccount {
295 pub fn seed() -> &'static [u8] {
296 b"test-context"
297 }
298}
299
300#[account]
301#[derive(Default, Debug)]
302pub struct Interaction {
303 pub context: Pubkey,
304 pub user: Pubkey,
305 pub text: String,
306 pub callback_program_id: Pubkey,
307 pub callback_discriminator: [u8; 8],
308 pub callback_account_metas: Vec<AccountMeta>,
309 pub is_processed: bool,
310}
311
312impl Interaction {
313 pub fn seed() -> &'static [u8] {
314 b"interaction"
315 }
316
317 pub fn space(text: &String, account_metas_len: usize) -> usize {
318 121 + text.as_bytes().len() + account_metas_len * AccountMeta::size()
319 }
320}
321
322#[derive(InitSpace, AnchorSerialize, AnchorDeserialize, Clone, Debug)]
323pub struct AccountMeta {
324 pub pubkey: Pubkey,
325 pub is_signer: bool,
326 pub is_writable: bool,
327}
328
329impl AccountMeta {
330 pub fn size() -> usize {
331 8 + AccountMeta::INIT_SPACE
332 }
333}
334
335#[account]
336pub struct Counter {
337 pub count: u32,
338}
339
340#[account]
341pub struct Identity {}