See /concepts/lifecycle for the end-to-end flow (policy → trigger → bond → wait/sell decision).
The trigger condition
For every active flash shield, the drop is measured against the strike the policy stored at purchase: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 |
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
What’s signed (EIP-712)
- The signature recovers to
LuminaOracleV2.oracleKey()(the only allowed signer). block.timestamp - payload.timestamp ≤ MAX_PROOF_AGE(24 hours, audit fix M-8).- The nonce is strictly greater than the last accepted nonce for the asset (replay protection).
strike == policy.strike(no oracle drift between purchase and trigger).(strike - price) / strike ≥ TRIGGER_DROP_BPS(drop condition).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:ShieldKeepervia 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}/triggerwraps the oracle request + submission.
Why a signed proof (vs. fully on-chain Chainlink reads in the shield)
Two reasons:- 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.
- Cost. Pulling a Chainlink price into shield storage on every trigger would inflate gas. EIP-712 proofs are ~200 bytes of calldata.