GPL Session
==============
Manage sessions in your Solana Anchor Programs.
# Installation
```bash
cargo add session-keys --features no-entrypoint
```
# Usage
The latest version uses `SessionTokenV2`. `SessionToken` (V1) is still supported for backwards compatibility — see [V1 usage](#v1-backwards-compatible) below.
1. Import the dependencies
```rust
use session_keys::{SessionError, SessionTokenV2, session_auth_or, Session};
```
2. Derive the `Session` trait on your instruction struct. The macro auto-detects V1 vs V2 from the `session_token` field type.
```rust
#[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>,
.....
}
```
3. 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.
```rust
#[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:
```rust
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`:
```rust
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
- [magicblock-engine-examples / session-keys](https://github.com/magicblock-labs/magicblock-engine-examples/tree/main/session-keys) — Anchor counter program using `SessionTokenV2` with the ephemeral rollups SDK, plus tests and a React frontend.
- [KamikazeJoe](https://github.com/magicblock-labs/Kamikaze-Joe) — a game using session-keys.