GPL Session
Manage sessions in your Solana Anchor Programs.
Installation
cargo add session-keys --features no-entrypoint
Usage
The latest version uses SessionTokenV2. SessionToken (V1) is still supported for backwards compatibility — see V1 usage below.
- Import the dependencies
use session_keys::{SessionError, SessionTokenV2, session_auth_or, Session};
- Derive the
Session trait on your instruction struct. The macro auto-detects V1 vs V2 from the session_token field type.
#[derive(Accounts, Session)]
pub struct Instruction<'info> {
.....
pub user: Account<'info, User>,
#[session(
// The ephemeral keypair signing the transaction
signer = signer,
// The authority of the user account which must have created the session
authority = user.authority.key()
)]
pub session_token: Option<Account<'info, SessionTokenV2>>,
#[account(mut)]
pub signer: Signer<'info>,
.....
}
- Add the
session_auth_or macro to your instruction handler with fallback logic on who the instruction should validate the signer when sessions are not present and an appropriate ErrorCode. If you've used require*! macros in anchor_lang you already know how this works.
#[session_auth_or(
ctx.accounts.user.authority.key() == ctx.accounts.authority.key(),
ErrorCode
)]
pub fn ix_handler(ctx: Context<Instruction>,) -> Result<()> {
.....
}
V1 (backwards-compatible)
To keep using SessionToken (V1), import SessionToken instead of SessionTokenV2 and use it as the field type:
use session_keys::{SessionError, SessionToken, session_auth_or, Session};
#[derive(Accounts, Session)]
pub struct Instruction<'info> {
...
pub session_token: Option<Account<'info, SessionToken>>,
...
}
#[derive(Session)] picks the right trait implementation based on the field type. You can also use #[derive(SessionV2)] to require V2 explicitly.
Full V2 example
A complete Anchor instruction using SessionTokenV2:
use anchor_lang::prelude::*;
use session_keys::{session_auth_or, Session, SessionError, SessionTokenV2};
declare_id!("YourProgramID11111111111111111111111111111111");
#[program]
pub mod my_program {
use super::*;
pub fn initialize(ctx: Context<Initialize>) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.authority = *ctx.accounts.user.key;
counter.count = 0;
Ok(())
}
#[session_auth_or(
ctx.accounts.counter.authority.key() == ctx.accounts.signer.key(),
SessionError::InvalidToken
)]
pub fn increment(ctx: Context<Increment>) -> Result<()> {
ctx.accounts.counter.count += 1;
Ok(())
}
}
#[derive(Accounts)]
pub struct Initialize<'info> {
#[account(mut)]
pub user: Signer<'info>,
#[account(
init,
payer = user,
space = 8 + 32 + 8,
seeds = [b"counter", user.key().as_ref()],
bump,
)]
pub counter: Account<'info, Counter>,
pub system_program: Program<'info, System>,
}
#[derive(Accounts, Session)]
pub struct Increment<'info> {
#[account(mut)]
pub signer: Signer<'info>,
#[account(
mut,
seeds = [b"counter", counter.authority.key().as_ref()],
bump,
)]
pub counter: Account<'info, Counter>,
#[session(
signer = signer,
authority = counter.authority.key()
)]
pub session_token: Option<Account<'info, SessionTokenV2>>,
}
#[account]
pub struct Counter {
pub authority: Pubkey,
pub count: u64,
}
More examples