solana_gpt_oracle/
lib.rs

1use anchor_lang::prelude::ProgramError;
2use anchor_lang::prelude::*;
3use anchor_lang::solana_program::instruction::Instruction;
4use anchor_lang::solana_program::program::invoke_signed;
5
6declare_id!("LLMrieZMpbJFwN52WgmBNMxYojrpRVYXdC1RCweEbab");
7
8#[program]
9pub mod solana_gpt_oracle {
10    use super::*;
11
12    pub fn initialize(_ctx: Context<Initialize>) -> Result<()> {
13        Ok(())
14    }
15
16    pub fn create_llm_context(ctx: Context<CreateLlmContext>, text: String) -> Result<()> {
17        let context_account = &mut ctx.accounts.context_account;
18        context_account.text = text;
19        ctx.accounts.counter.count += 1;
20        Ok(())
21    }
22
23    pub fn interact_with_llm(
24        ctx: Context<InteractWithLlm>,
25        text: String,
26        callback_program_id: Pubkey,
27        callback_discriminator: [u8; 8],
28        account_metas: Option<Vec<AccountMeta>>,
29    ) -> Result<()> {
30        let interaction = &mut ctx.accounts.interaction;
31        interaction.context = ctx.accounts.context_account.key();
32        interaction.user = ctx.accounts.payer.key();
33        interaction.text = text;
34        interaction.callback_program_id = callback_program_id;
35        interaction.callback_discriminator = callback_discriminator;
36        interaction.callback_account_metas = account_metas.unwrap_or_default();
37        Ok(())
38    }
39
40    pub fn callback_from_llm<'info>(
41        ctx: Context<'_, '_, '_, 'info, CallbackFromLlm<'info>>,
42        response: String,
43    ) -> Result<()> {
44        let response_data = [
45            ctx.accounts.interaction.callback_discriminator.to_vec(),
46            response.try_to_vec()?,
47        ]
48        .concat();
49
50        // Prepare accounts metas
51        let mut accounts_metas: Vec<anchor_lang::solana_program::instruction::AccountMeta> =
52            vec![anchor_lang::solana_program::instruction::AccountMeta {
53                pubkey: ctx.accounts.identity.key(),
54                is_signer: true,
55                is_writable: false,
56            }];
57        accounts_metas.extend(
58            ctx.accounts
59                .interaction
60                .callback_account_metas
61                .iter()
62                .map(
63                    |meta| anchor_lang::solana_program::instruction::AccountMeta {
64                        pubkey: meta.pubkey,
65                        is_signer: meta.is_signer,
66                        is_writable: meta.is_writable,
67                    },
68                ),
69        );
70
71        // Verify payer is not in remaining accounts
72        if ctx
73            .remaining_accounts
74            .iter()
75            .any(|acc| acc.key().eq(&ctx.accounts.payer.key()))
76        {
77            return Err(ProgramError::InvalidAccountData.into());
78        }
79
80        // CPI to the callback program
81        let instruction = Instruction {
82            program_id: ctx.accounts.program.key(),
83            accounts: accounts_metas,
84            data: response_data.to_vec(),
85        };
86        let mut remaining_accounts: Vec<AccountInfo<'info>> = ctx.remaining_accounts.to_vec();
87        remaining_accounts.push(ctx.accounts.identity.to_account_info());
88        remaining_accounts.push(ctx.accounts.program.to_account_info());
89        let identity_bump = ctx.bumps.identity;
90        invoke_signed(
91            &instruction,
92            &remaining_accounts,
93            &[&[b"identity", &[identity_bump]]],
94        )?;
95        Ok(())
96    }
97
98    pub fn callback_from_oracle(ctx: Context<CallbackFromOracle>, response: String) -> Result<()> {
99        if !ctx.accounts.identity.to_account_info().is_signer {
100            return Err(ProgramError::InvalidAccountData.into());
101        }
102        msg!("Callback response: {:?}", response);
103        Ok(())
104    }
105}
106
107/// Contexts
108
109#[derive(Accounts)]
110pub struct Initialize<'info> {
111    #[account(mut)]
112    pub payer: Signer<'info>,
113    #[account(
114        init,
115        payer = payer,
116        space = 8,
117        seeds = [b"identity"],
118        bump
119    )]
120    pub identity: Account<'info, Identity>,
121    #[account(
122        init,
123        payer = payer,
124        space = 8 + 32,
125        seeds = [b"counter"],
126        bump
127    )]
128    pub counter: Account<'info, Counter>,
129    pub system_program: Program<'info, System>,
130}
131
132#[derive(Accounts)]
133#[instruction(text: String)]
134pub struct CreateLlmContext<'info> {
135    #[account(mut)]
136    pub payer: Signer<'info>,
137    #[account(mut,
138        seeds = [b"counter"],
139        bump
140    )]
141    pub counter: Account<'info, Counter>,
142    #[account(
143        init,
144        payer = payer,
145        space = 8 + text.as_bytes().len() + 8,
146        seeds = [ContextAccount::seed(), &counter.count.to_le_bytes()],
147        bump
148    )]
149    pub context_account: Account<'info, ContextAccount>,
150    pub system_program: Program<'info, System>,
151}
152
153#[derive(Accounts)]
154#[instruction(text: String, callback_program_id: Pubkey, callback_discriminator: [u8; 8], account_metas: Option<Vec<AccountMeta>>)]
155pub struct InteractWithLlm<'info> {
156    #[account(mut)]
157    pub payer: Signer<'info>,
158    #[account(
159        init,
160        payer = payer,
161        space = 120 + text.as_bytes().len() + account_metas.as_ref().map_or(0, |m| m.len()) * AccountMeta::size(),
162        seeds = [Interaction::seed(), payer.key().as_ref(), context_account.key().as_ref()],
163        bump
164    )]
165    pub interaction: Account<'info, Interaction>,
166    /// CHECK: we accept any context
167    pub context_account: Account<'info, ContextAccount>,
168    pub system_program: Program<'info, System>,
169}
170
171#[derive(Accounts)]
172pub struct CallbackFromLlm<'info> {
173    #[account(mut)]
174    pub payer: Signer<'info>,
175    #[account(seeds = [b"identity"], bump)]
176    pub identity: Account<'info, Identity>,
177    /// CHECK: we accept any context
178    #[account(mut, close = payer)]
179    pub interaction: Account<'info, Interaction>,
180    /// CHECK: the callback program
181    pub program: AccountInfo<'info>,
182}
183
184#[derive(Accounts)]
185pub struct CallbackFromOracle<'info> {
186    #[account(seeds = [b"identity"], bump)]
187    pub identity: Account<'info, Identity>,
188}
189
190/// Accounts
191
192#[account]
193pub struct ContextAccount {
194    pub text: String,
195}
196
197impl ContextAccount {
198    pub fn seed() -> &'static [u8] {
199        b"context"
200    }
201}
202
203#[account]
204#[derive(Debug)]
205pub struct Interaction {
206    pub context: Pubkey,
207    pub user: Pubkey,
208    pub text: String,
209    pub callback_program_id: Pubkey,
210    pub callback_discriminator: [u8; 8],
211    pub callback_account_metas: Vec<AccountMeta>,
212}
213
214impl Interaction {
215    pub fn seed() -> &'static [u8] {
216        b"interaction"
217    }
218}
219
220#[derive(InitSpace, AnchorSerialize, AnchorDeserialize, Clone, Debug)]
221pub struct AccountMeta {
222    pub pubkey: Pubkey,
223    pub is_signer: bool,
224    pub is_writable: bool,
225}
226
227impl AccountMeta {
228    pub fn size() -> usize {
229        8 + AccountMeta::INIT_SPACE
230    }
231}
232
233#[account]
234pub struct Counter {
235    pub count: u32,
236}
237
238#[account]
239pub struct Identity {}