> ## Documentation Index
> Fetch the complete documentation index at: https://docs.lumina-org.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Triggers

> Chainlink BTC/ETH feed, 3 confirmations 60s apart, drop vs strike, time window, ShieldKeeper automation.

<Note>
  See [/concepts/lifecycle](/concepts/lifecycle) for the end-to-end flow (policy → trigger → bond → wait/sell decision).
</Note>

A **trigger** is the on-chain proof that a policy's parametric condition
fired. In V5.4 the source of truth is **Chainlink BTC/USD + ETH/USD on
Base mainnet**; the shields require **3 confirmations 60 seconds apart**
and enforce a **time window** and a **drop calculation vs the strike
snapshotted at purchase**.

## The trigger condition

For every active flash shield, the drop is measured against the strike
the policy stored at purchase:

```
drop_bps = (strike - currentPrice) / strike  × 10_000
fired    = drop_bps >= TRIGGER_DROP_BPS AND
           block.timestamp <= policy.startedAt + durationSeconds
```

Per-product `TRIGGER_DROP_BPS`:

| Product       | TRIGGER\_DROP\_BPS | Window |
| ------------- | ------------------ | ------ |
| Flash BTC 1h  | `250`  (2.5%)      | 1 h    |
| Flash BTC 24h | `600`  (6%)        | 24 h   |
| Flash BTC 48h | `1000` (10%)       | 48 h   |
| Flash ETH 1h  | `400`  (4%)        | 1 h    |
| Flash ETH 24h | `850`  (8.5%)      | 24 h   |
| Flash ETH 48h | `1400` (14%)       | 48 h   |

If the cover window closes before a valid trigger is accepted, the policy
expires — no refund.

## Oracle: 3 confirmations 60s apart

`LuminaOracleV2` reads the Chainlink feed three times, **at least 60
seconds between reads**, and only signs a trigger payload if all three
reads agree the threshold has been crossed. This filters out single-block
spikes (printer errors, sandwich attacks against the feed, momentary
flash-loan oracle distortions).

The oracle also checks the **Base L2 sequencer uptime feed** — if the
sequencer is down or in the grace period, no trigger is signed. Users
can't react during sequencer outages, so triggering during one would be
unfair.

## Sequence diagram

```mermaid theme={null}
sequenceDiagram
  participant Agent
  participant Keeper as ShieldKeeper (Chainlink Automation)
  participant Oracle as LuminaOracleV2
  participant Adapter as FlashShieldAdapter
  participant Shield as BaseFlashShield
  participant PM as PolicyManagerV2
  participant Vault as BondVault
  participant CB as ClaimBond

  Keeper->>Oracle: read Chainlink (3× 60s apart) + sequencer up?
  Oracle->>Oracle: aggregate + sign EIP-712 payload
  Keeper->>Adapter: submitTrigger(payload, signature)
  Adapter->>Shield: delegate to BaseFlashShield
  Shield->>Shield: verify EIP-712 signature
  Shield->>Shield: drop_bps >= TRIGGER_DROP_BPS?
  Shield->>Shield: within durationSeconds?
  Shield->>PM: mark policy triggered
  Shield->>Vault: mint(buyer, $800 per $1k)
  Vault->>CB: ERC-1155 #epochId minted
  CB-->>Agent: BondIssued event
```

## What's signed (EIP-712)

```
struct PriceProof {
  bytes32 asset;            // keccak256("BTC") or keccak256("ETH")
  uint256 strike;           // policy.strike (18 decimals)
  uint256 price;            // observed price (18 decimals)
  uint256 timestamp;        // unix seconds; ≤ MAX_PROOF_AGE old
  uint256 nonce;            // per-asset replay counter
  uint256 policyId;         // the policy this proof targets
}
```

The shield verifies:

1. The signature recovers to `LuminaOracleV2.oracleKey()` (the only
   allowed signer).
2. `block.timestamp - payload.timestamp ≤ MAX_PROOF_AGE` (24 hours,
   audit fix M-8).
3. The nonce is strictly greater than the last accepted nonce for the
   asset (replay protection).
4. `strike == policy.strike` (no oracle drift between purchase and trigger).
5. `(strike - price) / strike ≥ TRIGGER_DROP_BPS` (drop condition).
6. `block.timestamp ≤ policy.startedAt + durationSeconds` (window).

## Who can submit a trigger

Anyone — the signature is what gives the trigger weight, not the
submitter. In practice:

* **`ShieldKeeper` via Chainlink Automation.** A registered keeper that
  scans active policies, fetches signed payloads from the oracle, and
  submits triggers in batches. **Hard cap: 10 policies per upkeep call**
  (gas guardrail; large epochs are paginated across multiple upkeeps).
* **Direct agent submission.** An agent watching the feed can submit
  their own trigger if they prefer.
* **API convenience.** `POST /api/v1/policies/{id}/trigger` wraps the
  oracle request + submission.

## Why a signed proof (vs. fully on-chain Chainlink reads in the shield)

Two reasons:

1. **Aggregation.** The "3 confirmations 60s apart" rule needs three
   reads spaced in time; doing that purely in the shield contract would
   require keeping state for partial reads across blocks. Cleaner to
   aggregate off-chain and sign the result.
2. **Cost.** Pulling a Chainlink price into shield storage on every
   trigger would inflate gas. EIP-712 proofs are \~200 bytes of calldata.

The oracle still uses **Chainlink BTC/USD + ETH/USD on Base mainnet** as
its upstream feeds — agents just see a signed proof of the
already-aggregated value.

## See also

* [Shields](/concepts/shields) — per-product drop thresholds + addresses.
* [Adapters](/concepts/adapters) — why submitTrigger goes through the adapter first.
