Context Rules
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.CreateContractExternalRef(Address, String): Applies to contract deployments from the executable reference that an owner contract stores under a tag (Protocol 28, CAP-85).
With CreateContractExternalRef(owner, tag), the WASM hash is resolved at deployment time and the owner contract can change it later, so the rule trusts the owner contract to point the tag at acceptable code. A deployment is matched by how its executable is referenced, not by the code it resolves to: a CreateContract(hash) rule does not cover a deployment through an external reference, even if that reference resolves to the same hash.
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.
By default, the SmartAccount management functions (adding, updating or removing context rules, signers and policies) require authorization from the smart account itself, so they need a valid rule that matches a call to the account: a Default rule, or a CallContract rule for the account's own address. The library does not ensure that such a rule remains. add_context_rule and update_context_rule_valid_until accept any expiry that is not already in the past, and remove_context_rule accepts the last such rule. Once no valid rule covers calls to the account, nothing can authorize a management function again, including adding a new rule, and the account is locked permanently. Rules scoped to other contracts keep working, but cannot restore access. Before setting a rule's expiry or removing a rule, check that another valid rule still covers the account itself.
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:
- Reads the
context_rule_idsfrom theAuthPayload(one rule ID per auth context) - Looks up each rule directly by ID
- Validates the rule is not expired and matches the context type
- 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, Signer};
// ~5 seconds per ledger
const DAY_IN_LEDGERS: u32 = 17_280;
let current_ledger = e.ledger().sequence();
// 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.clone(), 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 + 365 * DAY_IN_LEDGERS),
&vec![
e,
Signer::External(ed25519_verifier.clone(), 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 * DAY_IN_LEDGERS),
&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 + DAY_IN_LEDGERS / 2),
&vec![
e,
Signer::External(secp256r1_verifier, agent_key)
],
&map![
e,
(volume_cap_policy, volume_cap_params)
],
);