Lattice is a Solidity library of modular contract modules built on top of the
diamond-lib EIP-2535 Diamond Standard
framework. Most modules ship as a stateless facet + a library with ERC-7201 namespaced
storage, so they can be cut into a single Diamond proxy without storage collisions.
The core is an OpenZeppelin Contracts v5-style module set ported into the Diamond facet pattern. The current tree also includes smart-account, cross-chain, DeFi adapter, oracle, ENS, marketplace, privacy/ZK, and upgrade-governance modules. It is a Foundry project consumed as a Forge dependency; there is no application or canonical deployment of its own.
Unaudited. Use at your own risk. Lattice re-implements OpenZeppelin and adapts external protocol interfaces, account standards, bridge/oracle integrations, and ZK/privacy primitives. It has not been audited and carries no warranty. It has not received the review that the upstream libraries it mirrors or composes with have. Do not deploy it to mainnet with funds at risk without your own independent audit, especially modules that custody assets, verify proofs, bridge messages, or authorize upgrades. Licensed under MIT.
| Area | Modules |
|---|---|
access/ |
AccessControl, AccessControlEnumerable, AccessControlTimed, AccessManager (+ AccessManaged, AccessManagerStandalone), Ownable |
accounts/ |
Diamond smart-account building blocks — two modular-account flavors (see below), each in its own subfolder. accounts/erc7579/: AccountDiamond, Account7702Diamond, AccountFactory, AccountInit, AccountSigner, ERC7821Executor, ERC7579ModuleConfig. accounts/erc6900/: ModularAccount6900, AccountFactory6900, AccountInit6900, ERC6900ModuleManager, ERC6900Executor, ERC6900Validation, ERC6900Signature, ERC6900AccountView (+ reference modules modules/SingleSignerValidation, modules/SpendingLimit). Shared base + standalone account types (accounts/): ERC4337Validation, ERC1271Signature (the ERC-4337/1271 base both flavors build on), plus the single-facet standalone types ERC6551Account (token-bound) and SessionKey |
amm/ |
ConstantProduct |
crosschain/ |
Message gateways: CCIPGatewayAdapter, AxelarGatewayAdapter, WormholeGatewayAdapter, LayerZeroGatewayAdapter, HyperlaneGatewayAdapter, ZetaChainGatewayAdapter (hub-routed), HyperbridgeGatewayAdapter (proof-verified), L2ToL2/L1ToL2CrossDomainMessengerGatewayAdapter (OP). Token rails: CCTPBridgeAdapter (burn/mint), AcrossBridgeAdapter (intent), StargateBridgeAdapter (pooled), BridgeERC20, BridgeERC7802, SuperchainETHBridgeAdapter. Non-EVM: StarknetGatewayAdapter (felt252, L1↔L2). Composition: ERC7786OpenBridge (M-of-N), CrosschainLink, ChainRegistry (one-action fan-out), CrosschainTimelockHandler. See CROSSCHAIN.md for the adapter-shape reference + off-chain dependency matrix |
defi/ |
AaveV3Adapter, AggregatorExecAdapter, CompoundV3Adapter, CurveStableSwapAdapter, ERC4626Adapter, GovernedVault, LidoAdapter, StrategyManager, UniswapV3Adapter, VaultCore, WETHUnwrapper |
ens/ |
ENSResolver, ENSReverseClaimer, ENSSubnameIssuer |
governance/ |
Governor (+ GovernorStandalone), TimelockController (+ TimelockControllerStandalone), Votes, GovernedDiamondCut, SafeDiamondCut, GovernedSafeDiamondCut, SafeHarborAdopter |
oracles/ |
API3Adapter, API3QRNGAdapter, BandAdapter, ChainlinkAdapter, ChainlinkAutomationAdapter, ChainlinkCREAdapter, ChainlinkVRF, ChronicleAdapter, DIAAdapter, GelatoAutomateAdapter, GelatoVRFAdapter, PythAdapter, PythEntropyAdapter, RedStoneAdapter, TWAPOracle, TellorAdapter |
privacy/ |
CommitReveal, ERC5564Announcer, ERC6538Registry, Groth16Verifier, PlonkVerifier, PrivateVoting, Semaphore, ShieldedPool |
security/ |
Pausable, ReentrancyGuard, RateLimiter, CircuitBreaker, EmergencyStop, InvariantChecker |
tokens/ |
One subfolder per standard (base + extensions flat inside, <std>/libraries/ for logic). tokens/ERC20/: ERC20 (+ Burnable, Capped, Crosschain, Permit, Votes). tokens/ERC721/: ERC721 (+ URIStorage). tokens/ERC1155/, tokens/ERC2981/, tokens/ERC4626/, tokens/ERC7802/. MarketplaceZone sits at the tokens/ root (a Seaport zone enforcing the issuer's own token policy, not a token standard) |
utils/ |
EIP712, Multicall, Nonces, VestingWallet (+ VestingWalletStandalone) |
Utility libraries (src/utils/libraries/) — pure logic with no own storage, facet,
or interface: Base64, Bytes, Calldata, Checkpoints, ECDSA, EnumerableSet,
InterestRate, InteroperableAddress, P256, Panic, ShortStrings,
SignatureChecker, Strings, TimelockLib, UniswapV3FullRangeMath, WebAuthn, plus
module helpers such as EIP712Lib, MulticallLib, NoncesLib, and VestingWalletLib.
src/interfaces/external/ vendors minimal third-party ABIs used by adapters and standards
integrations, grouped per vendor (circle/, chainlink/, layerzero/, …; pure ERC/EIP standard
interfaces under ercs/). The canonical storage/interface registry is
STORAGE_REGISTRY.md, and
test/unit/StorageSlotVerificationTest.t.sol re-derives every registered slot and checks
global uniqueness.
Lattice ships two modular smart-account flavors, both built on the shared Diamond core
(DiamondCut / Loupe / ERC-165 / AccessControl). They are separate blueprints — you pick one
per account; the two facet sets are never cut into the same Diamond. Both target ERC-4337 and
ERC-1271, and both are written fresh in the three-layer facet pattern (the ERC-6900 reference
implementation is GPL and is not a dependency — only minimal interfaces are vendored into
src/interfaces/external/).
- ERC-7579 (
AccountDiamondproxy) — the four fixed module types (validator 1, executor 2, fallback 3, hook 4). A per-op validator is selected by the top 20 bytes ofuserOp.nonce; one global hook wraps execution; a fallback registry is layered under the facet map. - ERC-6900 (
ModularAccount6900proxy) — inspired by EIP-2535 itself, so it maps onto the Diamond most naturally. Validation is a richerModuleEntity(address ‖ uint32 entityId) with per-validation pre-validation hooks and per-selector pre/post execution hooks — the standardized form of a session-key permission. Execution modules are dispatched by CALL (so they run in their own storage), layered under the facet map.
| Dimension | ERC-7579 | ERC-6900 |
|---|---|---|
| Proxy | AccountDiamond |
ModularAccount6900 |
| Factory / init | AccountFactory / AccountInit |
AccountFactory6900 / AccountInit6900 |
| Module identity | module address (per type) | ModuleEntity = address ‖ uint32 entityId |
| Module kinds | 4 fixed types | validation / validation-hook / execution / execution-hook modules |
| userOp validator selection | top 20 bytes of userOp.nonce |
first 24 bytes of userOp.signature |
| Validation scope | by module type | global (per-selector opt-in) or a per-selector allowlist |
| Hooks | one global pre/post hook | per-validation pre-validation hooks + per-selector pre/post exec hooks |
| Execution extension | fallback handlers (CALL or DELEGATECALL) | execution-function registry, dispatched by CALL (own storage) |
| Session-key permissions | SessionKey library (ad hoc) |
a validation + attached hooks (standardized) |
| Introspection | DiamondLoupe |
IERC6900AccountView (getExecutionData / getValidationData) |
| Config authority | account-self or admin | account-self or admin (config is admin-gated, not validation-gated) |
The ERC-6900 facets: ERC6900ModuleManager (install/uninstall validations + executions),
ERC6900Executor (execute / executeBatch / executeWithRuntimeValidation plus the proxy's
execution-module dispatch), ERC6900Validation (ERC-4337 validateUserOp), ERC6900Signature
(ERC-1271, ERC-7739-bound to the account's domain so signatures can't be replayed across
accounts), and ERC6900AccountView (the loupe). Reference modules SingleSignerValidation and
SpendingLimit demonstrate the validation and execution-hook module shapes.
Diamond facets must be stateless — proxy state lives in the Diamond, not the facet — so facet modules are split into three files with strict responsibilities:
src/interfaces/<area>/IFoo.sol # ABI, custom errors, events. Wide pragma (>=0.8.4)
▲ # interfaces mirror the module's <area> folder
src/<area>/libraries/FooLib.sol # ALL logic + ERC-7201 storage + __Foo_init(...)
▲ # storage read via a single FooStorage() -> hardcoded slot
src/<area>/Foo.sol # stateless facet: virtual fns that forward to FooLib
// src/<area>/Foo.sol — facet: no state, no logic, just delegation
contract Foo is IFoo {
function bar(uint256 x) external virtual returns (uint256) {
return FooLib.bar(x);
}
}A handful of utility libraries (see above) skip this split: they are pure logic with
no own ERC-7201 slot, no interface file, and no facet — the consuming module owns any
storage struct they operate on. A few contracts are standalone rather than facets where the
standard or deployment model requires it, for example AccountFactory, GovernorStandalone,
TimelockControllerStandalone, AccessManagerStandalone, and VestingWalletStandalone.
Install as a Forge dependency:
forge install dadadave80/lattice
git submodule update --init --recursive # diamond-lib + forge-std submodulesAdd the remappings (mirror of this repo's remappings.txt / foundry.toml):
@lattice/=lib/lattice/src/
@diamond/=lib/diamond-lib/src/
forge-std/=lib/forge-std/src/
Because facets have no constructors, proxy state is set up through Lattice's vendored
Initializable mixin over InitializableLib (both moved into Lattice at diamond-lib v0.3.0).
Inherit the mixin and guard the init entrypoint with the initializer modifier — it wraps the body
in preInitializer()/postInitializer(slot), so nested constructor-initializers finalize exactly
once. reinitializer(version) and onlyInitializing are available for upgrades and init-only
helpers:
import {AccessControl} from "@lattice/access/AccessControl.sol";
import {AccessControlLib} from "@lattice/access/libraries/AccessControlLib.sol";
import {Initializable} from "@lattice/utils/Initializable.sol";
contract MyAccessControlled is AccessControl, Initializable {
function initialize(address _admin) external initializer {
AccessControlLib.__AccessControl_init(_admin); // module init (gated by checkInitializing)
}
}Note: init contracts delegatecalled during diamondCut (the *Init.sol pattern) carry NO guard of
their own — they already run inside Lattice.initialize's initializer scope, and a nested
guard reverts outside a constructor context.
When adding new modules, be deliberate about caller semantics. Some existing modules use
msg.sender directly because they authenticate protocol callbacks, Safe calls, EntryPoint
calls, or Diamond self-dispatch. If a module is intended to support forwarded calls, use the
project's established caller-resolution pattern consistently through the library layer.
A reference deployment of the self-governed, ENS-named ERC-4626 vault recipe
(DeployGovernedVaultENS) — one diamond hosting the
share token, vault, vote checkpoints, Governor, TimelockController, EmergencyStop, and a governed
upgrade path, with no external admin: the diamond administers itself, so a passed, timelock-executed
shareholder proposal is the only way to upgrade or reconfigure it.
| Diamond (vault) | 0x7a498c34A8Dc3B6502889C21218Da0F8696b7bb6 |
| Primary ENS name | milestone1vault.lattice.studio.eth (forward + reverse) |
| Underlying asset | TestnetAsset (open faucet: mint(address,uint256)) |
All 14 facets are source-verified on Etherscan: ERC165Facet · AccessControl · TimelockController · ERC20 · ERC4626 · VaultCore · Votes · ERC20Votes · Governor · GovernedVault · DiamondLoupeFacet · EmergencyStop · GovernedDiamondCut · ENSReverseClaimer · (init: GovernedVaultENSInit)
Reproduce against any fresh testnet in one command (asset 0x0 auto-deploys a faucet asset; the diamond
claims its ENS reverse record at init):
forge script script/base/defi/DeployGovernedVaultENS.s.sol --tc DeployGovernedVaultENS \
--rpc-url sepolia --account <keystore> --broadcast \
--sig "run(((address,string,string,uint8,uint256,uint48,uint32,uint256,uint256),address,string))" \
"((0x0000000000000000000000000000000000000000,\"Governed Vault Share\",\"gVLT\",0,300,60,600,0,4),<ReverseRegistrar>,\"<name>\")" \
--verify --etherscan-api-key "$ETHERSCAN_API_KEY"The broadcast run log for this deployment is committed at
broadcast/DeployGovernedVaultENS.s.sol/11155111/run-latest.json.
After deployment the diamond governed itself on-chain: a shareholder proposal — deposit → propose →
vote → timelock queue → execute — froze the loupe/cut selector set and reasserted the ENS name through the
diamond's own Governor + TimelockController, with every step cranked by
script/config/governance-demo-loop.sh. Execution proof:
ProposalExecuted tx
(run log: governanceDemo-latest.json).
Three live demos drive real USDC through Lattice diamonds with Circle's CCTP v2, with Arc testnet as the source chain — Arc's sub-second finality means Iris attests in seconds, not minutes:
- Arc-hub transfer (
make demo-cctp): one hub diamond on Arc burns USDC toward BOTH destinations (Ethereum Sepolia + Base Sepolia); each attested message is relayed and minted on the destination. One command per run: setup → burn → attest → relay → verify, unattended. - Hook showcase (
make demo-cctp-hook): programmable USDC. A burn on Arc carries the Lattice hook envelope (HOOK_MAGIC ‖ vault ‖ beneficiary); relaying on Base Sepolia through the destination diamond'srelayMessageWithHookmints to aCCTPHookVaultand, in the same tx, the diamond'sCCTPHookExecutorcredits the beneficiary — one attested message both moves funds and executes logic. - Position-style receipt NFT (
make demo-cctp-receipt): USDC is minted directly to the Base recipient while the same attested relay mints a fully on-chainCCTPHookReceiptshowing the net amount, CCTP source domain, source contract, original recipient, route, and delivery time. The transferable NFT is immutable proof of delivery — it never custodies, controls, or redeems the USDC. - Round trip (
make demo-cctp-roundtrip): USDC moves Arc → Base and back, through Lattice diamonds on both ends of both legs. Outbound attests in seconds (Arc finality); the return leg attests after Base Sepolia's L1 finality (~13–19 min on the free tier — the run journal makes Ctrl-C safe, re-run to resume). The return mint into Arc iscast-sent through the hub'srelayMessage: the Arc node executes the native-USDC precompile that local simulation cannot.
| Arc source hub (transfer demo) | 0xfc937CD3d175b890fF668f95fdED5CB4D9247d68 |
| Mint tx — Ethereum Sepolia | 0xff2326…39aea |
| Mint tx — Base Sepolia | 0xf72700…736d3 |
| Hook demo — Arc hub diamond | 0x6ca99B6179eAc891E3aCD4008b610fcE66F63E2d |
| Hook demo — Base destination diamond | 0x957259C5AEAa521c9DcFaEb6692C25ae53F349f1 |
Auto-credit vault (CCTPHookVault) |
0xe8e10843Ab41B2c359D02eA091b6772C43b05b1f |
| Burn-with-hook tx (Arc) | 0xc9ba15…a77a4 |
| Relay tx — mint + hook, one tx (Base) | 0x7f82f3…b5d00 — emits Credited(0x11Cf…eC00, 1000000, 26, hub) |
| Real-attestation replay test | test/fork/CCTPHookDemoFork.t.sol replays the captured fixture through the live Base diamond on a pinned fork |
Receipt NFT (CCTPHookReceipt) |
0x6De791…71a65 |
| Receipt demo burn (Arc) | 0x7a923b…c6f07 |
| Receipt relay — direct USDC + NFT (Base) | 0xbdcd52…5a8a7 — grant-video run; mints 5 USDC and receipt #4 to 0xDAda…C751 |
| Receipt real-attestation replay | test/fork/CCTPHookReceiptDemoFork.t.sol replays the captured receipt fixture |
| Broadcast evidence | broadcast/multi/ (setups) · broadcast/CCTPHookDemo.s.sol/84532/ (hook relay) · broadcast/CCTPUSDCDemo.s.sol/ (transfer relays) |
All demo contracts are Sourcify-verified (exact_match) on both chains.
Anyone can run the hook demo — deployment is separate from the demo, and by default it runs against the live contracts above, so all you need is a funded signer: Arc testnet USDC (the asset AND Arc's gas token, from https://faucet.circle.com) plus a little Base Sepolia ETH for relay gas (any Base Sepolia faucet):
make demo-cctp-hook PRIVATE_KEY=0x<testnet-key> # or KEYSTORE=<foundry-keystore-name>The receipt demo is intentionally separate from the vault demo. Deploy its NFT once against the existing
Base destination diamond, then run it directly or choose Receipt NFT inside make demo:
make deploy-cctp-receipt PRIVATE_KEY=0x<testnet-key>
make demo-cctp-receipt PRIVATE_KEY=0x<testnet-key>CCTPHookReceipt renders its JSON and SVG entirely on-chain. Its source contract field is Circle's
attested CCTP message sender (normally the Arc hub diamond), not an asserted source-user wallet. The live
deployment and first receipt relay are linked in the evidence table above.
To deploy your own stack instead (once — ONE deployment serves ALL the demos: the Arc hub is
registered for Ethereum Sepolia and Base Sepolia, make demo-cctp adopts it as its transfer hub,
make demo-cctp-hook gets its hub + diamond + vault, and the Base diamond carries the Arc return
registration make demo-cctp-roundtrip burns back through):
make deploy-cctp PRIVATE_KEY=0x<testnet-key>See the Makefile demos section for the full auth matrix (KEYSTORE= / PRIVATE_KEY= /
raw FORGE_AUTH=).
forge build # compile
forge test # run all tests
forge test --match-contract AccessControl -vvv # verbose, by contract
forge test --match-contract StorageSlotVerificationTest # verify ERC-7201/ERC-165 slots
forge fmt # format (CI runs `forge fmt --check`)
forge snapshot # gas snapshotsThe suite currently includes unit, integration, fork, fuzz, invariant, and gas tests. CI
runs with FOUNDRY_PROFILE=ci (optimizer_runs = 1_000_000, via_ir = false). A green
local forge test does not guarantee CI passes if optimizer behavior diverges — run
FOUNDRY_PROFILE=ci forge build --sizes before pushing if you touch hot paths.
src/
├── access/ # AccessControl(+Enumerable,+Timed), AccessManager(+Managed,+Standalone), Ownable
├── accounts/ # Diamond smart accounts — erc7579/ & erc6900/ flavor subfolders + shared (ERC-4337/1271/6551, session keys)
├── amm/ # ConstantProduct
├── crosschain/ # per-vendor adapter folders (circle/, layerzero/, …), each self-contained (facet+Init+Lib); generic modules at root, shared libs in libraries/
├── defi/ # Aave, Compound, Curve, Lido, Uniswap V3, ERC4626 adapters, AggregatorExec, GovernedVault, WETHUnwrapper
├── ens/ # ENS resolver, reverse claimer, subname issuer
├── governance/ # Governor, timelock, governed/Safe diamond cuts, Safe Harbor adoption
├── oracles/ # per-vendor adapter folders (chainlink/, pyth/, redstone/, …, uniswap/ TWAP), each self-contained (facet+Init+Lib)
├── privacy/ # Commit-reveal, stealth address standards, Groth16/PLONK, Semaphore, shielded pool
├── security/ # Pausable, ReentrancyGuard, RateLimiter, CircuitBreaker, EmergencyStop, InvariantChecker
├── tokens/ # per-standard subfolders ERC20/ ERC721/ ERC1155/ ERC2981/ ERC4626/ ERC7802/ (base+extensions); MarketplaceZone at root
├── utils/ # EIP712, Multicall, Nonces, VestingWallet(+Standalone)
│ └── libraries/ # Crypto, encoding, strings, checkpoints, math, multicall/nonces/vesting helpers
└── interfaces/ # I<Module>.sol mirrored into per-<area> subfolders
└── external/ # vendored third-party ABIs, one folder per vendor (circle/, chainlink/, …, ercs/)
Each <area>/ also contains a libraries/ subfolder holding the <Module>Lib.sol
logic libraries for that area.
MIT