Join our community of builders on

Telegram!Telegram

Context Rules

Source Code

Context rules function like routing tables for authorization. For each context, they specify scope, lifetime, and the conditions (signers and policies) that must be satisfied before execution proceeds.

Structure

A context rule contains the following components:

ID

Unique identifier for the rule within the smart account.

Name

Human-readable description of the rule's purpose (e.g., "Admin Access", "DeFi Session").

Context Type

Defines the scope where the rule applies:

  • Default: Applies to any context. Used for admin-like authorization that spans all operations.
  • CallContract(Address): Applies to specific contract calls. Useful for scoped permissions like session logins to a particular dApp.
  • CreateContract(BytesN<32>): Applies to contract deployments with a specific WASM hash. Enables control over which contracts can be deployed.

Valid Until

Optional expiration defined by a ledger sequence. Rules with expiration automatically become invalid after the specified ledger, enabling time-limited permissions like 24-hour sessions.

Signers

List of authorized signers (maximum 15 per rule). Signers can be either delegated (any Soroban address) or external (using verifier contracts).

For detailed documentation on signers, see Signers.

Policies

List of policy contracts (maximum 5 per rule). Policies act as enforcement modules that perform read-only prechecks and state-changing enforcement logic.

For detailed documentation on policies, see Policies.

Key Properties

Requirement Flexibility

Each rule must contain at least one signer OR one policy. This enables pure policy-based authorization (like a spending limit without signature checks) or pure signature-based authorization (like an n-of-n multisig).

Multiple Rules Per Context

Multiple rules can exist for the same context type with different signer sets and policies. This allows progressive authorization models where different combinations of credentials grant access to the same operations.

Explicit Rule Selection

Rules are not iterated or auto-discovered. Instead, off-chain clients specify exactly which context rule to use for each operation via the context_rule_ids field in AuthPayload. This explicit selection prevents downgrade attacks where an attacker could force the system to fall back to a weaker rule.

Automatic Expiration

Expired rules are rejected during authorization evaluation.

Context Rule Limits

  • Maximum signers per context rule: 15
  • Maximum policies per context rule: 5
  • Maximum context rule name size: 20 bytes
  • Maximum external signer key size: 256 bytes

There is no upper limit on the total number of context rules per smart account.

Authorization Matching

During authorization, the framework:

  1. Reads the context_rule_ids from the AuthPayload (one rule ID per auth context)
  2. Looks up each rule directly by ID
  3. Validates the rule is not expired and matches the context type
  4. Authenticates signers and enforces policies, or fails if conditions aren't met

For detailed documentation on the authorization flow, see Authorization Flow.

Example Configuration

use soroban_sdk::{map, vec, Env, String}
use stellar_accounts::smart_account::{self as smart_account, ContextRuleType};

// This rule applies to all contexts and requires 2-of-3 signatures from Alice, Bob, or Dave.
smart_account::add_context_rule(
    e,
    ContextRuleType::Default,
    String::from_str(e, "Sudo"),
    None, // No expiration
    vec![
        e,
        Signer::External(bls_verifier, alice_key),
        Signer::External(bls_verifier, bob_key),
        Signer::Delegated(dave_addr)
    ],
    map![
        e,
        (threshold_policy, threshold_params) // 2-of-3 Threshold
    ],
);

// This rule applies only to calls to the USDC contract, expires in 1 year,
// requires a dapp1 key signature, and enforces spending limits.
smart_account::add_context_rule(
    e,
    ContextRuleType::CallContract(usdc_addr),
    String::from_str(e, "Dapp1 Subscription"),
    Some(current_ledger + 1_year),
    vec![
        e,
        Signer::External(ed25519_verifier, dapp1_key)
    ],
    map![
        e,
        (spending_limit_policy, spending_params)
    ],
);

// This rule applies only to calls to the dApp contract, expires in 7 days,
// requires a dapp2 key signature, and enforces rate limiting and time window policies.
smart_account::add_context_rule(
    e,
    ContextRuleType::CallContract(dapp_addr),
    String::from_str(e, "Dapp2 Session"),
    Some(current_ledger + 7_days),
    vec![
        e,
        Signer::External(ed25519_verifier, dapp2_key)
    ],
    map![
        e,
        (rate_limit_policy, rate_limit_params),
        (time_window_policy, time_window_params)
    ],
);

// This rule applies only to calls to a specific contract, expires in 12 hours,
// requires an AI agent key signature, and enforces volume caps.
smart_account::add_context_rule(
    e,
    ContextRuleType::CallContract(some_addr),
    String::from_str(e, "AI Agent"),
    Some(current_ledger + 12_hours),
    vec![
        e,
        Signer::External(secp256r1_verifier, agent_key)
    ],
    map![
        e,
        (volume_cap_policy, volume_cap_params)
    ],
);

See Also