Solidity contracts for the Elytra security agent. Foundry project.
Currently contains:
ElytraOracle.sol— onchain index of the latest Elytra security score for any address. A convenience lookup that points to the canonical EAS attestation backing the score.ElytraGateRegistry.sol— reference consumer of the oracle. Wraps it with an immutable policy (minScore, reviewCeiling, maxStaleness) and exposes strict + lenient gates (isAllowed,isNotBlocked,requireAllowed,requireNotBlocked). Drop-in one-liner for vaults, routers, allowlists.ElytraGateLib.sol— pure library version of the gate (no state, cheaper to embed).ElytraGateHook.sol— reference Uniswap v4 hook usingElytraGateLib. Uses inline minimal shims so it compiles without v4-core; replace shims with realv4-coreimports for a production-ready hook.
| Contract | Address |
|---|---|
ElytraOracle |
0x7823Ef5aD87F4851eD4b7E39ef0E227509901a7b |
ElytraGateRegistry |
0x6243d745e8cd212146176313ABB052C4541553f2 |
Registry policy: minScore=55, reviewCeiling=80, maxStaleness=7 days.
- A read-cheap mapping
(address) → (score, easUid, timestamp, chainId)any Base contract can query in a singlestaticcall. - A write-restricted index. Only the immutable
attesterwallet (the Elytra agent's signer,0x3bfb…1fc2on Base mainnet) canpublishScore. - A pointer back to the EAS attestation that produced the score, so anyone reading the oracle can verify the underlying evidence on base.easscan.org.
- An overwriting index: each
publishScorereplaces the previous score for that address. History lives on EAS, not here.
- Not a safety guarantee. A non-zero score does not mean an address is safe to interact with. The score is a heuristic.
- Not an approval. Elytra does not endorse, certify, or audit any contract published in this index.
- Not an audit. Reading from
ElytraOracleis not a substitute for a human security audit. - Not a price feed. This is a code-scan signal, not a market signal.
- Not real-time. The score is only as fresh as the most recent paid Elytra scan of that address.
- Not append-only. Older scores are overwritten in place. For the durable history, query EAS by recipient address.
✅ "Latest Elytra score for this address, published onchain on Base" ✅ "Onchain pointer to the EAS attestation that backs the latest score" ✅ "Updated when our scanner re-runs and detects a material change" ✅ "Restricted writer: only the Elytra agent wallet can publish scores"
❌ Do not write: "safe", "verified safe", "approved", "audited", "Elytra-certified", "real-time", "prevents hacks".
struct Score {
uint8 score; // 0..100 heuristic
bytes32 easUid; // backing EAS attestation UID on Base
uint64 timestamp; // block.timestamp of the publish
uint32 chainId; // chain ID of the scanned address
}
address public immutable attester;
function publishScore(address target, uint32 chainId, uint8 score, bytes32 easUid) external;
function scoreOf(address target) external view returns (Score memory);
event Scored(address indexed target, uint32 indexed chainId, uint8 score, bytes32 easUid);
error NotAttester();
error ZeroAttester();
error ZeroTarget();
error ZeroUid();
error ScoreTooHigh();Caller convention for readers: if (scoreOf(addr).timestamp == 0) →
never scanned by Elytra, not "score = 0".
- 1 slot for the immutable
attester(not stored, baked into code). mapping(address => Score)— each populated entry occupies 3 storage slots per the declared struct field order (uint8cannot pack withbytes32;uint64+uint32packs into one slot).- A future v1.1 could reorder to (
bytes32 easUid, uint8 score, uint64 timestamp, uint32 chainId) to compress to 2 slots, saving ~22.1k gas on first write and ~5k on overwrite. Deferred — the current cost is already sub-cent on Base.
These contracts compile and test under Foundry. Install Foundry separately — this package does not pin or fetch it.
# from packages/contracts/
forge install foundry-rs/forge-std --no-commit
forge build
forge test
forge test --gas-report
forge fmt --check# inside packages/contracts/
# Run every test (constructor, happy path, reverts, boundary, fuzz)
forge test -vv
# Same plus a gas-cost table for ElytraOracle
forge test --gas-report
# Snapshot gas to .gas-snapshot for diff review on future PRs
forge snapshot
# Format Solidity sources
forge fmtDeployment is intentionally not automated here. Follow the deploy runbook when you're ready, and only after:
forge test --gas-reportpasses locally- A deploy to Base Sepolia (chain 84532) confirms real gas numbers
- Mainnet target gas at the current Base fee is within the budget set in the Oracle Phase 2 economics doc
Mainnet deploy (Base 8453):
# Constructor arg: the existing Elytra agent wallet, the same one that
# signs EAS attestations (X402_WALLET_ADDRESS in packages/web/.env).
forge create \
--rpc-url base \
--private-key "$X402_WALLET_KEY" \
--constructor-args "$X402_WALLET_ADDRESS" \
src/ElytraOracle.sol:ElytraOracle
# Then verify on Basescan via Etherscan V2:
forge verify-contract \
--chain base \
--constructor-args $(cast abi-encode "constructor(address)" "$X402_WALLET_ADDRESS") \
<DEPLOYED_ADDRESS> \
src/ElytraOracle.sol:ElytraOracleSave the deployed address as ELYTRA_ORACLE_ADDRESS in
/home/ubuntu/elytrasec/packages/web/.env. The web app reads it from there.
- v1 (current): single immutable attester, overwriting index, no batching, no rotation, no history. Score-change-only publishing logic lives in the web app, not the contract — keeping the contract minimal and auditable.
- Not audited. Internal review only. Suitable for early-launch use given the small surface and the limited damage radius of a writer key compromise (attacker could publish bogus scores; cannot drain funds, mint tokens, or escalate privileges; readers can fall back to the underlying EAS attestation).
MIT.