Skip to main content
Program ID: 2uYGW3fQUGfhrwVbkupdasXBpRPfGYBGTLUdaPTXU9vP x0-guard is the protocol’s policy enforcement layer. It implements the SPL Transfer Hook interface, meaning every Token-2022 transfer is intercepted and validated against the sender’s AgentPolicy before the transfer can settle.

How It Works

When a Token-2022 TransferChecked instruction executes on a mint configured with x0-guard as its transfer hook, Solana automatically invokes x0-guard’s validate_transfer handler. The guard loads the agent’s policy PDA and checks:
  1. Policy is activeis_active must be true
  2. Amount within single-transaction limit — If max_single_transaction is set, the amount must not exceed it
  3. Rolling window budget — The cumulative spend in the last 24 hours plus this amount must not exceed daily_limit
  4. Whitelist membership — If a whitelist is configured, the recipient must pass verification
  5. Minimum transfer amount — Amount must be ≥ 100 micro-units (anti-dust)
If any check fails, the transfer is rejected at the runtime level — the entire transaction reverts.

Instructions

initialize_policy

Creates a new AgentPolicy PDA for an owner-agent pair.
initialize_policy(
    daily_limit: u64,
    whitelist_mode: WhitelistMode,
    whitelist_data: Option<Vec<u8>>,
    privacy_level: PrivacyLevel
)
ParameterTypeDescription
daily_limitu64Maximum spend in a 24-hour rolling window (micro-units)
whitelist_modeWhitelistModeNone, Merkle, Bloom, or Domain
whitelist_dataOption<Vec<u8>>Merkle root (32 bytes), Bloom filter (4096 bytes), or domain prefixes
privacy_levelPrivacyLevelPublic or Confidential { auditor: Option<Pubkey> }

update_policy

Updates an existing policy. Rate-limited to one update per ~5 minutes (POLICY_UPDATE_COOLDOWN_SLOTS = 750).
update_policy(
    new_daily_limit: Option<u64>,
    new_whitelist_mode: Option<WhitelistMode>,
    new_whitelist_data: Option<Vec<u8>>,
    new_privacy_level: Option<PrivacyLevel>,
    new_auditor_key: Option<Pubkey>,
    new_max_single_transaction: Option<u64>
)

update_agent_signer

Rotates the agent’s signing key. The old key is immediately invalidated.
update_agent_signer(new_agent_signer: Pubkey)

revoke_agent_authority

Emergency revocation — immediately invalidates the agent’s key and sets is_active = false. Only the policy owner can call this.

set_policy_active

Pauses or unpauses an agent policy without destroying it.
set_policy_active(is_active: bool)

validate_transfer

The Transfer Hook entry point. Called automatically by Token-2022 on every transfer. Not callable directly by users.
validate_transfer(amount: u64, merkle_proof: Option<Vec<[u8; 32]>>)
Records a Blink (human-approval request) generation event. Rate-limited to MAX_BLINKS_PER_HOUR = 3.
record_blink(amount: u64, recipient: Pubkey, reason: String)

get_current_spend

View instruction — returns the current rolling window spend, remaining allowance, and oldest entry expiry.

State Accounts

AgentPolicy

pub struct AgentPolicy {
    pub version: u8,
    pub owner: Pubkey,              // Wallet that created the policy
    pub agent_signer: Pubkey,       // Delegated agent hot-key
    pub daily_limit: u64,           // Max 24h rolling window spend
    pub max_single_transaction: Option<u64>,
    pub rolling_window: Vec<SpendingEntry>,
    pub privacy_level: PrivacyLevel,
    pub whitelist_mode: WhitelistMode,
    pub whitelist_data: Vec<u8>,
    pub auditor_key: Option<Pubkey>,
    pub blinks_this_hour: u8,
    pub blink_hour_start: i64,
    pub is_active: bool,
    pub bump: u8,
    pub require_delegation: bool,
    pub bound_token_account: Option<Pubkey>,
    pub last_update_slot: u64,      // Rate-limiting
}
PDA Seeds: ["agent_policy", owner, agent_signer]

SpendingEntry

Each entry in the rolling window tracks one transfer:
pub struct SpendingEntry {
    pub amount: u64,      // Transfer amount in micro-units
    pub timestamp: i64,   // Unix timestamp of transfer
}

Rolling Window Algorithm

The spend limit uses a sliding 24-hour window rather than a fixed daily reset:
  1. On each transfer, expired entries (older than ROLLING_WINDOW_SECONDS = 86,400) are pruned
  2. The sum of remaining entries is computed as current_spend
  3. If current_spend + amount > daily_limit, the transfer is rejected
  4. On success, a new SpendingEntry is appended
  5. Maximum entries capped at MAX_ROLLING_WINDOW_ENTRIES = 144 (~one per 10 minutes)
This prevents the “midnight boundary” attack where an agent spends the daily limit at 23:59 and again at 00:01.

Whitelist Modes

No whitelist — the agent can transfer to any recipient.

Privacy Levels

LevelBehavior
PublicStandard Token-2022 transfer. Amount and parties are visible on-chain.
ConfidentialUses Token-2022 ConfidentialTransfer extension. Amounts are encrypted with Twisted ElGamal. Optional auditor pubkey can decrypt for compliance.

Events Emitted

EventWhen
PolicyCreatedNew policy initialized
PolicyUpdatedPolicy parameters changed
AgentRevokedAgent authority revoked
TransferValidatedTransfer passed policy check
TransferRejectedTransfer failed policy check
BlinkGeneratedBlink approval request recorded
WhitelistUpdatedWhitelist data changed
Last modified on February 8, 2026