Signless & Gasless Sessions
About
Many production scenarios require delegated execution with a narrow permission scope. Examples include mobile flows, relayer-based UX, temporary automation, and game or application sessions. A direct transfer of full control to a delegate key creates unnecessary risk. A repeated requirement for direct user signatures creates unnecessary friction.
The Signless & Gasless Sessions pattern introduces a constrained delegation layer for Sails-based programs on Vara. A session key receives permission to execute a limited set of actions on behalf of an original account for a limited period. Runtime validation enforces key ownership, expiration, and action allowlisting.
The pattern in this repository uses a reusable session-management crate, signless-gasless-session-service, together with a generated session subsystem produced by the generate_session_system!(ActionsForSession) macro. The application-facing integration surface remains small: a business method accepts an optional session_for_account parameter and resolves the effective account through generated validation logic.
Why the pattern matters
A session-based model allows delegated execution without granting unlimited authority to a secondary key. It also avoids the need for repeated direct signatures for every user action. The result is a more practical authorization model for applications that require temporary access, restricted permissions, and low-friction execution.
This approach is especially useful when session infrastructure must remain isolated from domain logic while still supporting both direct session creation and delegated setup through signed authorization.
Pattern overview
The pattern consists of four logical parts:
-
Original account
The principal account on whose behalf an operation is executed. -
Session key
The delegate account allowed to submit a restricted set of calls. -
Generated session subsystem
Storage, service methods, configuration, validation helpers, events, and errors generated by the session-service macro. -
Business service
Domain logic that relies on session validation only when delegated execution is requested.
A practical consequence follows from that split: domain code remains compact, while authorization rules remain centralized and consistent.
Architecture
Main components
ActionsForSession
A domain-specific enum defines the actions eligible for delegation. Only actions represented in that enum may pass runtime validation.
Typical shape:
#[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq)]
#[codec(crate = sails_rs::scale_codec)]
#[scale_info(crate = sails_rs::scale_info)]
pub enum ActionsForSession {
IncreaseCounter,
}The enum represents the authorization boundary. Any mismatch between an executed method and the corresponding action variant weakens the security model.
generate_session_system!(ActionsForSession)
The session-service repository provides a convenience macro that generates the reusable session layer after declaration of the action enum. The generated subsystem includes session storage, service methods, configuration types, session and signature data structures, typed errors, events, and helpers for effective-account resolution.
This macro removes repetitive boilerplate and standardizes storage layout, creation flow, deletion flow, and validation semantics across applications.
Session storage
The generated storage keeps session records and configuration. Each session record contains, at minimum:
-
approved session key
-
expiration timestamp
-
allowed actions
-
derived block-based expiration marker for delayed deletion
Business storage
Business storage remains independent from session infrastructure. The demo pattern uses a minimal counter state, but the same integration approach applies to balances, game state, order placement, staking actions, or administrative workflows.
Generated session subsystem
The signless-gasless-session-service repository is designed as a reusable dependency:
[dependencies]
session-service = { git = "https://github.com/gear-foundation/signless-gasless-session-service.git" }Minimal integration shape:
#![no_std]
use sails_rs::prelude::*;
use session_service::*;
#[derive(Debug, Clone, Encode, Decode, TypeInfo, PartialEq, Eq)]
#[codec(crate = sails_rs::scale_codec)]
#[scale_info(crate = sails_rs::scale_info)]
pub enum ActionsForSession {
IncreaseCounter,
}
generate_session_system!(ActionsForSession);
#[derive(Default)]
pub struct CounterStorage {
pub counter: u64,
}
pub struct Program {
/// Storage used by the generated session system (sessions map + config)
session_storage: RefCell<SessionStorage>,
/// Storage used by the business logic (the demo counter)
counter_storage: RefCell<CounterStorage>,
}
#[sails_rs::program]
impl Program {
pub async fn new(session_config: SessionConfig) -> Self {
Self {
session_storage: RefCell::new(SessionStorage::new(session_config)),
counter_storage: RefCell::new(CounterStorage::default()),
}
}
pub fn signless_gasless(&self) -> ServiceUsingSession<'_> {
ServiceUsingSession::new(&self.session_storage, &self.counter_storage)
}
pub fn session(&self) -> SessionService<'_> {
SessionService::new(&self.session_storage)
}
}Session model
A session represents a temporary grant from an original account to a session key.
Required properties
- Approved key — the delegate key allowed to submit the delegated call.
- Allowed actions — an explicit allowlist of action variants.
- Timestamp-based expiration — runtime rejection after the configured lifetime ends.
- Block-based expiration marker — a scheduling aid for delayed deletion through self-messaging.
Configuration
SessionConfig governs operational behavior.
Typical fields include:
-
gas_to_delete_session -
minimum_session_duration_ms -
ms_per_block
Those values support safe session cleanup and duration validation.
Effective-account resolution
A business method supporting delegated execution accepts an optional account parameter:
pub fn increase_counter(
&mut self,
session_for_account: Option<ActorId>,
) -> Result<String, SessionError>The business method then resolves the effective account through generated validation logic.
Conceptual flow:
Validation cases
When session_for_account is None, the call follows the normal execution path. In that case, the effective account is simply msg::source(), and no session lookup is required.
When session_for_account is Some(account), the program switches to delegated validation. The session must exist for the specified account, remain non-expired, include the requested action in its allowlist, and match the immediate caller through the stored session key. Only a successful validation produces the original account for downstream business logic.
Session creation modes
Two creation modes exist in the pattern.
Signature-based session creation
Use case: delegated or relayed setup without a direct on-chain call from the original account.
High-level model:
-
original account authorizes session creation off-chain;
-
session key or relayer submits
create_session(...); -
contract verifies the expected signature payload;
-
contract stores the session and emits
SessionCreated.
Architecture:
No-signature session creation
Use case: direct setup by the original account in a standard wallet flow.
High-level model:
-
original account sends
create_session(...)directly; -
provided key becomes the approved session key;
-
no off-chain signature verification occurs;
-
contract stores the session and emits
SessionCreated.
Architecture:
Operational result:
-
msg::source()acts as the original account; -
the provided key acts as the delegate;
-
delegated usage starts immediately after successful creation.
Integration in business logic
A business method should remain focused on domain behavior. Session handling should be limited to effective-account resolution and action binding.
Recommended structure:
#[sails_rs::service]
impl<'a> ServiceUsingSession<'a> {
/// Increments a counter, with optional session-based delegation
#[export(route = "increase_counter_with_possibility_of_sessions", unwrap_result)]
pub fn increase_counter(
&mut self,
session_for_account: Option<ActorId>,
) -> Result<String, SessionError> {
let msg_src = msg::source();
// Resolve the “original” account for which this call is executed
let original_addr = {
let storage = self.get_session_storage();
storage.get_original_address(
&msg_src,
&session_for_account,
ActionsForSession::IncreaseCounter,
)?
};
// Business logic: increment the counter
let new_value = {
let mut c = self.get_counter_storage_mut();
c.counter = c.counter.saturating_add(1);
c.counter
};
Ok(format!(
"Original address: {original_addr}; counter: {new_value}!"
))
}
}Recommended properties of that structure:
-
no duplication of session validation logic in domain methods;
-
explicit binding between the method and one action variant;
-
clear separation between authorization and state mutation.
Session lifecycle
Creation
A successful session creation flow stores:
- delegate key
- duration-derived expiration
- action allowlist
- block marker used for cleanup
A SessionCreated event confirms successful activation.
Usage
A delegated business call remains valid only when all validation checks pass. A rejected session produces an error before domain mutation.
Deletion
The pattern supports both manual deletion and delayed automatic cleanup.
Deletion paths:
- explicit deletion requested through session service methods;
- scheduled self-message after expiration.
That cleanup design requires enough gas for the delayed deletion path and a coherent ms_per_block approximation.
Security requirements
- Bind each delegated method to the correct
ActionsForSessionvariant. - Keep the action set narrow and explicit.
- Preserve exact signature payload compatibility in signature-based flows.
- Treat session validation as an authorization layer, not a replacement for domain checks.
- Configure expiration and cleanup parameters carefully.
Practical guidance for production systems
Recommended practices for production use:
- Keep
ActionsForSessionsmall and domain-specific. - Use short session durations by default.
- Add explicit revocation flows where user control is important.
- Isolate session infrastructure from business storage and business logic.
- Cover negative scenarios in integration tests before deployment.
Typical use cases include mobile applications, relayer-based UX, game sessions, and temporary delegated actions with limited scope.
Source code
Pattern repository:
Reusable session-service repository:
Related documentation:
Summary
The Signless & Gasless Sessions pattern provides a reusable model for temporary and restricted delegated execution in Sails-based contracts on Vara.
The generated session subsystem reduces boilerplate, centralizes validation, and keeps business methods compact. The result is a cleaner separation between session authorization and domain logic, with a structure suitable for production-grade contract systems.