1use anchor_lang::prelude::*;
2use anchor_lang::solana_program::native_token::LAMPORTS_PER_SOL;
3use anchor_lang::system_program;
4
5#[cfg(feature = "no-entrypoint")]
6pub use session_keys_macros::*;
7
8declare_id!("KeyspM2ssCJbqUhQ4k7sveSiY4WjnYsrXkC8oDbwde5");
9
10
11#[cfg(not(feature = "no-entrypoint"))]
12solana_security_txt::security_txt! {
13 name: "gpl_session",
14 project_url: "https://magicblock.gg",
15 contacts: "email:dev@magicblock.gg,twitter:@magicblock",
16 policy: "",
17 preferred_languages: "en",
18 source_code: "https://github.com/magicblock-labs"
19}
20
21#[program]
22pub mod gpl_session {
23 use super::*;
24
25 pub fn create_session(
27 ctx: Context<CreateSessionToken>,
28 top_up: Option<bool>,
29 valid_until: Option<i64>,
30 lamports: Option<u64>,
31 ) -> Result<()> {
32 let top_up = top_up.unwrap_or(false);
34 let valid_until = valid_until.unwrap_or(Clock::get()?.unix_timestamp + 60 * 60 * 1);
36 create_session_token_handler(ctx, top_up, valid_until, lamports)
37 }
38
39 pub fn revoke_session(ctx: Context<RevokeSessionToken>) -> Result<()> {
41 revoke_session_token_handler(ctx)
42 }
43}
44
45#[derive(Accounts)]
47pub struct CreateSessionToken<'info> {
48 #[account(
49 init,
50 seeds = [
51 SessionToken::SEED_PREFIX.as_bytes(),
52 target_program.key().as_ref(),
53 session_signer.key().as_ref(),
54 authority.key().as_ref()
55 ],
56 bump,
57 payer = authority,
58 space = SessionToken::LEN
59 )]
60 pub session_token: Account<'info, SessionToken>,
61
62 #[account(mut)]
63 pub session_signer: Signer<'info>,
64 #[account(mut)]
65 pub authority: Signer<'info>,
66
67 #[account(executable)]
69 pub target_program: AccountInfo<'info>,
70
71 pub system_program: Program<'info, System>,
72}
73
74pub fn create_session_token_handler(
76 ctx: Context<CreateSessionToken>,
77 top_up: bool,
78 valid_until: i64,
79 lamports: Option<u64>,
80) -> Result<()> {
81 require!(
83 valid_until <= Clock::get()?.unix_timestamp + (60 * 60 * 24 * 7),
84 SessionError::ValidityTooLong
85 );
86
87 let session_token = &mut ctx.accounts.session_token;
88 session_token.set_inner(SessionToken {
89 authority: ctx.accounts.authority.key(),
90 target_program: ctx.accounts.target_program.key(),
91 session_signer: ctx.accounts.session_signer.key(),
92 valid_until,
93 });
94
95 if top_up {
98 system_program::transfer(
99 CpiContext::new(
100 ctx.accounts.system_program.to_account_info(),
101 system_program::Transfer {
102 from: ctx.accounts.authority.to_account_info(),
103 to: ctx.accounts.session_signer.to_account_info(),
104 },
105 ),
106 lamports.unwrap_or(LAMPORTS_PER_SOL / 100),
107 )?;
108 }
109
110 Ok(())
111}
112
113#[derive(Accounts)]
122pub struct RevokeSessionToken<'info> {
123 #[account(
124 mut,
125 seeds = [
126 SessionToken::SEED_PREFIX.as_bytes(),
127 session_token.target_program.key().as_ref(),
128 session_token.session_signer.key().as_ref(),
129 session_token.authority.key().as_ref()
130 ],
131 bump,
132 has_one = authority,
133 close = authority,
134 )]
135 pub session_token: Account<'info, SessionToken>,
136
137 #[account(mut)]
138 pub authority: SystemAccount<'info>,
140
141 pub system_program: Program<'info, System>,
142}
143
144pub fn revoke_session_token_handler(_: Context<RevokeSessionToken>) -> Result<()> {
146 Ok(())
147}
148
149pub struct ValidityChecker<'info> {
150 pub session_token: Account<'info, SessionToken>,
151 pub session_signer: Signer<'info>,
152 pub authority: Pubkey,
153 pub target_program: Pubkey,
154}
155
156#[account]
158#[derive(Copy)]
159pub struct SessionToken {
160 pub authority: Pubkey,
161 pub target_program: Pubkey,
162 pub session_signer: Pubkey,
163 pub valid_until: i64,
164}
165
166impl SessionToken {
167 pub const LEN: usize = 8 + std::mem::size_of::<Self>();
168 pub const SEED_PREFIX: &'static str = "session_token";
169
170 fn is_expired(&self) -> Result<bool> {
171 let now = Clock::get()?.unix_timestamp;
172 Ok(now < self.valid_until)
173 }
174
175 pub fn validate(&self, ctx: ValidityChecker) -> Result<bool> {
177 let target_program = ctx.target_program;
178 let session_signer = ctx.session_signer.key();
179 let authority = ctx.authority.key();
180
181 let seeds = &[
183 SessionToken::SEED_PREFIX.as_bytes(),
184 target_program.as_ref(),
185 session_signer.as_ref(),
186 authority.as_ref(),
187 ];
188
189 let (pda, _) = Pubkey::find_program_address(seeds, &crate::id());
190
191 require_eq!(pda, ctx.session_token.key(), SessionError::InvalidToken);
192
193 self.is_expired()
195 }
196}
197
198pub trait Session<'info> {
199 fn session_token(&self) -> Option<Account<'info, SessionToken>>;
200 fn session_signer(&self) -> Signer<'info>;
201 fn session_authority(&self) -> Pubkey;
202 fn target_program(&self) -> Pubkey;
203
204 fn is_valid(&self) -> Result<bool> {
205 let session_token = self.session_token().ok_or(SessionError::NoToken)?;
206 let validity_ctx = ValidityChecker {
207 session_token: session_token.clone(),
208 session_signer: self.session_signer(),
209 authority: self.session_authority(),
210 target_program: self.target_program(),
211 };
212 session_token.validate(validity_ctx)
214 }
215}
216
217#[error_code]
218pub enum SessionError {
219 #[msg("Requested validity is too long")]
220 ValidityTooLong,
221 #[msg("Invalid session token")]
222 InvalidToken,
223 #[msg("No session token provided")]
224 NoToken,
225}