TTL-Legacy uses Stellar's Time-to-Live (TTL) and state archival features to automate inheritance without manual intervention.
Each vault tracks:
last_check_in: Timestamp of last owner check-incheck_in_interval: Duration (seconds) before vault expires
pub fn is_expired(env: Env, vault_id: u64) -> bool {
let vault = Self::load_vault(&env, vault_id);
let current_time = env.ledger().timestamp();
current_time >= vault.last_check_in + vault.check_in_interval
}- Owner calls
check_in(vault_id) - Contract updates
last_check_into current timestamp - TTL countdown resets
- Anyone calls
trigger_release(vault_id) - Contract checks
is_expired() - If expired: transfers funds to beneficiary
- If not expired: returns
ContractError::NotExpired
Soroban archives inactive contract state to reduce costs. TTL-Legacy extends TTL on:
- Vault creation
- Check-ins
- Deposits
- Withdrawals
This ensures vault data remains accessible while the owner is active.
If an owner stops all activity, the vault's persistent storage entry will eventually be archived by the Soroban network. Archived entries are not deleted — they can be restored by re-extending their TTL.
Operators can snapshot vault state before archival using off-chain tooling. The snapshot is stored under DataKey::ArchivedVault(vault_id) and can be queried:
get_archived_vault_info(vault_id) -> Option<ArchivedVaultInfo>Returns Some(ArchivedVaultInfo) if a snapshot exists, None if the vault is live or was never snapshotted.
Anyone can restore an archived vault by calling:
restore_vault(vault_id)This re-extends the persistent entry TTL so the vault becomes accessible again. It also removes any stale archived-info snapshot and emits a v_restore event.
trigger_release automatically attempts to restore an archived vault before transferring funds. If an archived-info snapshot is present, the vault entry TTL is extended before the release logic runs. This ensures beneficiaries can always trigger release without a separate manual restoration step.
trigger_release(vault_id)
└─ try_restore_archived_vault() ← extends TTL if snapshot present
└─ load_vault()
└─ is_expired() check
└─ transfer funds to beneficiary
Beneficiaries can delegate their role to another address, creating a chain of custody.
The beneficiary (or the current delegate) can call:
delegate_beneficiary_role(vault_id, delegate_address)This updates the delegation chain and emits a del_ben event.
The trigger_release function automatically attempts restoration if the vault is archived.
Vault owners can temporarily borrow TTL from another vault they own during emergencies:
borrow_ttl(borrower_vault_id, lender_vault_id, caller, borrow_seconds) -> Result<(), ContractError>
repay_ttl_borrow(borrower_vault_id, caller) -> Result<(), ContractError>
get_ttl_borrow(borrower_vault_id) -> Option<TtlBorrowRecord>- The lender vault's
last_check_inis reduced byborrow_seconds(shortening its TTL) - The borrower vault's
last_check_inis extended byborrow_seconds(pushing its expiry forward) - A
TtlBorrowRecordis stored on-chain for auditability - The borrow can be repaid to restore the lender's TTL
- Events:
ttl_bor(borrow created),ttl_rep(borrow repaid)
To prevent storage abuse from excessive check-ins, a minimum cooldown can be enforced:
set_min_checkin_cooldown(cooldown_seconds) // admin-only
get_min_checkin_cooldown() -> u64
get_last_checkin_time(vault_id) -> Option<u64>- Default cooldown: 60 seconds
- Set to 0 to disable rate limiting
- Check-ins within the cooldown window return
CheckInTooFrequent(error 54) - Event:
ci_rlemitted when the cooldown setting is updated
Owners can voluntarily shorten their vault's remaining TTL to make it expire sooner:
accelerate_ttl_decay(vault_id, caller, accelerate_by_seconds) -> Result<(), ContractError>- Reduces
last_check_inbyaccelerate_by_seconds, moving the expiry deadline forward - Capped at 30 days (
MAX_ACCELERATE_SECONDS = 2_592_000) per call - Cannot push expiry to the current time or past (must leave ≥ 1 second remaining)
- Returns
InsufficientTtlToAccelerate(error 55) if the remaining TTL is too small - Event:
ttl_accwith(accelerated_seconds, new_remaining_ttl)
Check-ins can include geographic location metadata for security and anomaly detection:
check_in_with_geo(
vault_id, caller, passkey_hash,
latitude_micro, longitude_micro, country_code
) -> Result<(), ContractError>
get_geo_checkin_log(vault_id) -> Vec<GeoCheckInEntry>latitude_micro/longitude_microare in microdegrees (e.g.37_422_000= 37.422°)country_codeis an ISO 3166-1 alpha-2 string (e.g."US")- All standard
check_invalidations apply (owner auth, rate limiting, passkey expiry, TTL cap) - Location history is stored persistently under
CheckInGeoLog(vault_id)and queryable on-chain - Off-chain indexers can detect anomalies (e.g. check-in from unexpected country)
- Events:
ci_geo(location + timestamp) andcheck_in(standard check-in event)