Architecture

Three account types. Six instructions. One trait. The whole program fits in a single Rust module.

Accounts

Registry PDA

A single global account. Holds the admin authority and lifetime counters for every room ever created (total_rooms, total_evacuated, total_disarmed, total_expired).

Seeds: ["saferoom_registry"].

SafeRoom PDA

One per (owner, room_id) pair. Stores the trigger configuration and action template that the room will execute on evacuation. Once armed, this account's fields are immutable except for status and triggered_slot.

Seeds: ["safe_room", owner_pubkey, room_id_le_bytes].

Key fields:

  • status: u8ARMED (0), EVACUATED (1), DISARMED (2), EXPIRED (3).
  • trigger_kindPRICE_BELOW / PRICE_ABOVE / TIME_AFTER.
  • trigger_oracle_kindCEDA (custom devnet) / PYTH / NONE (time triggers).
  • trigger_feed_id: [u8; 32] — for Pyth, the 32-byte feed id from Hermes.
  • trigger_threshold: i64 — price (Pyth-scaled) or slot number.
  • action_kindTOKEN_TRANSFER or TOKEN_BURN.
  • action_destination: Pubkey — where the vault drains.
  • action_amount: u64 — exact amount the program will move.

Vault PDA

One per room. Owned by the SPL Token program at the chain level, but the authority is the room PDA. Holds the locked tokens.

Seeds: ["vault", room_pubkey].

Created by create_safe_room with init + token::mint = action_mint + token::authority = room. The room signs as the authority using its seeds.

Instructions

ixwhowhat
initialize_registryadmin oncescaffolds the Registry PDA
create_safe_roomroom ownerdeposits tokens into vault, arms the trigger
trigger_safe_roomanyoneverifies trigger, drains vault, flips status to EVACUATED
disarm_safe_roomroom ownercancels armed room, refunds vault
expire_safe_roomanyone, after deadlinereclaims vault to owner once past expiry_slot
close_safe_roomroom owner, terminal onlycloses accounts, returns rent

Permission model

  • The owner controls disarm and close. Nothing else.
  • The program controls the vault. There is no admin path that can move user funds.
  • Anyone can trigger or expire. Permissionless by construction: the worst a malicious responder can do is fire a trigger that is already true — which is exactly what you wanted to happen.

Atomicity

trigger_safe_room does all of the following inside one Solana transaction:

  1. Validates the room is ARMED and not past expiry_slot.
  2. Reads the oracle account, verifies ownership and feed_id, checks staleness.
  3. Compares the on-chain price (or slot) to trigger_threshold.
  4. Signs as the room PDA and CPI-calls token::transfer or token::burn.
  5. Flips status to EVACUATED, stamps triggered_slot, bumps registry counters.

If any step fails, the entire transaction reverts. There is no halfway state where the vault is empty but the status is still ARMED.

Composability

trigger_safe_room does not care who calls it, and action_destination does not have to be the room owner. That makes the primitive composable:

  • A lending market can register itself as the destination so liquidations route back into a repay action.
  • A treasury contract can be the destination of a panic-button vault.
  • A vesting protocol can use TIME_AFTER triggers as cliff aborts.
  • A keeper network can earn fees by being the first responder to trigger any room.