session-keys 3.1.1

Gum Session Protocol (GPL Session)
Documentation

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.

  1. Import the dependencies
use session_keys::{SessionError, SessionTokenV2, session_auth_or, Session};
  1. 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()
    )]
    // Session Tokens are passed as optional accounts
    pub session_token: Option<Account<'info, SessionTokenV2>>,

    #[account(mut)]
    pub signer: Signer<'info>,
    .....
}
  1. 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(())
    }

    /// Either the session signer (via SessionTokenV2) or the original authority
    /// can call this. If no session token is provided, the fallback check runs.
    #[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