Contract 0eb4038ea129be586d5b324c62c672c8203547b61146f17452693e587a9f92d8

โ† Back to Index ๐Ÿ“ฅ Download WASM

Meta

cliver 26.0.0#60f7458e7ecffddf2f2d91dc6d0d2db4fab03ecc
rssdk_spec_shaking 2
rssdkver 26.1.0#175aa41306f383057a8cdfc84b68d931664fc34e
rsver 1.94.1

Instances

  • CB6SOTBREWNAYNUYKIXTNTAYDKBOBIH4MUDRU36X326NVYQDBCMIKIBP
  • CBSRLN5UW2BLYVQW44OCNDAQM47WLZKZWLHIQIPBMU2AQMWU7ZRSQAR6
  • CBYCKENHGIAFYYXAEIJ5WW6TEMAXVIAO7BYHBZBUPKU3KOOD7KQXNEZW

Interface

Permissionless TTL maintenance for named, already-existing guardians and requests โ€” extending TTL creates no authority (ยง16.1). Also covers any pending threshold change and any pending removal for the given guardians: those are only bumped otherwise at propose_* time, so a proposal left unapplied long enough after its delay elapses could archive before apply_* is ever called, silently losing an already-authorized governance action rather than just delaying it (independent security review finding). bump_ttl no-ops on keys that don't exist, so calling this with no pending changes is safe. Initialized/Admin/GuardianThreshold/PendingGuardianThreshold live in instance storage (one shared TTL, refreshed automatically by ensure_initialized on every call that reaches it), so a single extend_ttl call below covers all of them together.

fn extend_ttl(
    env: soroban_sdk::Env,
    guardians: soroban_sdk::Vec,
    request_ids: soroban_sdk::Vec>,
)
fn initialize(
    env: soroban_sdk::Env,
    admin: soroban_sdk::Address,
    guardian_threshold: u32,
) -> Result<(), RecoveryManagerError>
fn is_guardian(env: soroban_sdk::Env, guardian: soroban_sdk::Address) -> bool

Registers a guardian. The guardian is recorded immediately (is_guardian reflects it right away), but its approval does not count toward any threshold until GUARDIAN_ACTIVATION_DELAY_LEDGERS have passed โ€” see the constant's doc comment for why. Removing a guardian, unlike adding one, is itself timelocked โ€” see propose_remove_guardian.

fn add_guardian(
    env: soroban_sdk::Env,
    guardian: soroban_sdk::Address,
) -> Result<(), RecoveryManagerError>
fn contract_name(env: soroban_sdk::Env) -> soroban_sdk::Symbol

Opens a recovery request. Callable by admin/owner or by any currently-active guardian โ€” this is a deliberate fix, not the original design: requiring the admin's own authorization to open a recovery request means recovery is unusable in exactly the scenario it exists for (the owner's key is lost or destroyed, not merely "compromised but still controllable by its holder"). A guardian who suspects the owner is compromised, or who is trying to help an owner who has genuinely lost access, can start the process unassisted; approve_recovery's threshold and finalize_recovery's timelock are what actually gate the outcome, not who was allowed to open the request.

replacement_signers/replacement_policies must describe a satisfiable context rule (at least one signer or one policy) โ€” see RecoveryRequest's doc comment for why recovery needs to carry this at all, not just replacement_owner. Rejected fast, here, if replacement_signers is empty, rather than letting a genuinely unsatisfiable recovery r

fn open_recovery(
    env: soroban_sdk::Env,
    caller: soroban_sdk::Address,
    request_id: soroban_sdk::BytesN<32>,
    replacement_owner: soroban_sdk::Address,
    replacement_signers: soroban_sdk::Vec,
    replacement_policies: soroban_sdk::Map,
    earliest_ledger: u32,
) -> Result<(), RecoveryManagerError>
fn request_status(
    env: soroban_sdk::Env,
    request_id: soroban_sdk::BytesN<32>,
) -> Result
fn cancel_recovery(
    env: soroban_sdk::Env,
    request_id: soroban_sdk::BytesN<32>,
) -> Result<(), RecoveryManagerError>
fn approve_recovery(
    env: soroban_sdk::Env,
    request_id: soroban_sdk::BytesN<32>,
    guardian: soroban_sdk::Address,
) -> Result<(), RecoveryManagerError>
fn finalize_recovery(
    env: soroban_sdk::Env,
    request_id: soroban_sdk::BytesN<32>,
) -> Result

Recomputes, from current guardian registrations, how many of this request's historical approvers are still valid right now. This is the number finalize_recovery actually checks against the threshold โ€” never the raw approvers.len().

fn live_approval_count(
    env: soroban_sdk::Env,
    request_id: soroban_sdk::BytesN<32>,
) -> Result

Permissionless view of the current freeze epoch โ€” see request_guardian_freeze's doc comment for why this is a monotonic counter rather than a consumable flag. smart_account:: apply_guardian_freeze compares this against the last epoch it applied to decide whether there is a genuinely new, not-yet-applied freeze request.

fn guardian_freeze_epoch(env: soroban_sdk::Env) -> u32

Applies a pending guardian removal once its delay has elapsed. Permissionless: the authorization decision (the admin proposing this specific removal) already happened, so when an already-authorized removal lands needs no further gate. Does not touch any open request's stored approvers list โ€” that history is preserved as an audit trail โ€” but the removed guardian's past approval stops counting toward live_approval_count / finalize_recovery from this point on, because both recompute against current guardian registration rather than trusting the historical approval.

fn apply_guardian_removal(
    env: soroban_sdk::Env,
    guardian: soroban_sdk::Address,
) -> Result<(), RecoveryManagerError>

Applies a pending guardian threshold change once its delay has elapsed. Permissionless: the authorization decision (the admin proposing this specific change) already happened, so when an already-authorized change lands needs no further gate. Doesn't call ensure_initialized (there is nothing to permission-check), so it explicitly extends the instance TTL itself on this write, the same way smart_account::apply_adapter_change does for its own instance-storage write.

fn apply_threshold_change(env: soroban_sdk::Env) -> Result<(), RecoveryManagerError>

Cancels a pending guardian removal before it takes effect. Admin-gated, letting a legitimate admin walk back an erroneous or no-longer-wanted proposal before its delay elapses.

fn cancel_guardian_removal(
    env: soroban_sdk::Env,
    guardian: soroban_sdk::Address,
) -> Result<(), RecoveryManagerError>

Cancels a pending guardian threshold change before it takes effect. Admin-gated, letting a legitimate admin walk back an erroneous or no-longer-wanted proposal before its delay elapses.

fn cancel_threshold_change(env: soroban_sdk::Env) -> Result<(), RecoveryManagerError>

Proposes removing a guardian. Does not take effect immediately โ€” see apply_guardian_removal. The guardian remains fully active (still counted by is_guardian, live_approval_count, and able to approve requests or request a freeze) until the proposal is applied. Independent security review finding: immediate removal under single-admin control meant a compromised admin could dismantle the guardian set with no delay for anyone to notice.

fn propose_remove_guardian(
    env: soroban_sdk::Env,
    guardian: soroban_sdk::Address,
) -> Result<(), RecoveryManagerError>

Real gap this session found: docs/TECHNICAL_ARCHITECTURE.md ยง12.7 documents the canonical recovery workflow as starting with "Guardian ... triggers freeze" โ€” but smart_account::freeze() was owner-only, with no guardian-facing path at all. A single active guardian requesting freeze is enough to raise the flag (unlike recovery itself, which needs quorum + timelock to replace authority, stopping active bleeding while recovery is still pending doesn't need the same bar โ€” the worst case is an unnecessary pause, not a loss of funds or authority). smart_account::apply_guardian_freeze pulls this epoch the same way apply_recovery pulls a finalized request: permissionlessly, and this contract has no knowledge of, or dependency on, the treasury that pulls it.

Stores a monotonically increasing epoch rather than a boolean flag. Security review finding on an earlier revision of this function: a plain bool, consumed (cleared) by a permissionless consume_guardian_freeze_request call, let any third party

fn request_guardian_freeze(
    env: soroban_sdk::Env,
    guardian: soroban_sdk::Address,
) -> Result<(), RecoveryManagerError>

Proposes a new guardian threshold. Does not take effect immediately โ€” see apply_threshold_change. Once applied, it takes effect for any request not yet finalized: finalize_recovery always reads the threshold fresh rather than the value in effect when the request was opened or approved.

fn propose_threshold_change(
    env: soroban_sdk::Env,
    new_threshold: u32,
) -> Result<(), RecoveryManagerError>

Imports

WebAssembly Text (WAT) โ–ถ