Non-custodial ERC-20 token locking with multi-signature governance, air-gapped signing (EIP-712 + QR codes), on-chain price conditions, and progressive vesting.
Deploy your own Locker smart contracts on any EVM chain and register them on lockerprotocol.com.
- Overview
- Architecture
- Smart Contracts
- Quick Start
- Testing
- Deploy on Mainnet
- Register on Locker Protocol
- Supported Chains
- Security
- License
Locker Protocol allows any entity to deploy its own vault infrastructure using a modular smart contract architecture. A group of signers (3 to 20) manages locked assets via multi-signature (M-of-N) logic with offline signing support (EIP-712 + QR codes).
- Multi-Signature Security β M-of-N threshold signing, 1 to 20 signers
- Air-Gapped Signing β Sign transactions offline via EIP-712 + animated QR codes
- Flexible Lock Conditions β Time-based, price-based (Uniswap V2/V3 oracle), or both
- Progressive Vesting β Token-denominated or percentage schedules (optional accumulation)
- Replay Protection β Per-operation nonces prevent signature reuse
- Symmetric Authority β Immobilising funds costs the same quorum as releasing them
- Chain-Aware Asset Registries β Canonical stablecoin/WETH sets keyed by chain id
- Launch Suite β Stablecoin-only presale and allocation rounds with on-chain raise-lock proofs
- Multi-Chain β Deploy on Ethereum, BSC, Polygon, Arbitrum, Base, and more
graph TD
subgraph "Your Deployment"
LC["LockerContract<br/>(Entry Point)"]
LM["LockManager<br/>(Lock Storage & Conditions)"]
SM["SignerManager<br/>(Authorized Signers)"]
VH["ValidationHandler<br/>(EIP-712 Multi-Sig Engine)"]
VM["VestingManager<br/>(Progressive Release)"]
PC["PriceCalculator<br/>(Uniswap V2/V3 Oracle)"]
end
subgraph "Launch Suite (optional)"
LF["LaunchFactory<br/>(Canonical Rails per Chain)"]
PR["PresaleRound<br/>(Stablecoin-Only Sale)"]
AR["AllocationRound<br/>(Merkle Distribution)"]
end
subgraph "Locker Protocol Platform"
UI["lockerprotocol.com<br/>(Management UI)"]
end
LC --> LM
LC --> SM
LC --> VH
LC --> VM
LM --> PC
VM --> PC
UI -->|"Add Custom Locker"| LC
LF -->|"deploys"| PR
LF -->|"deploys"| AR
PR -->|"codehash check + raise-lock"| LC
The Launch Suite is optional: a Locker deployment is complete and fully usable without it.
When used, a PresaleRound proves its "the raise is locked" claim by pointing at a lock
inside the team's own LockerContract, and the factory verifies that locker is genuine
before the round can be created.
All contracts use Solidity ^0.8.20 with OpenZeppelin primitives.
| Contract | Role |
|---|---|
LockerContract |
Central entry point - delegates to all modules, manages nonces |
LockManager |
Lock storage, time/price condition evaluation, token accounting |
SignerManager |
Authorized signer list, M-of-N threshold management |
ValidationHandler |
Multi-sig engine, EIP-712 signature verification via ecrecover |
VestingManager |
Progressive token release with token-denominated schedules |
PriceCalculator |
On-chain price oracle - queries Uniswap V2 reserves and V3 slots |
FullMath |
Safe math library for price calculations |
| Contract | Role |
|---|---|
LaunchFactory |
Per-chain source of truth - holds the canonical rails, deploys rounds |
PresaleRound |
Stablecoin-only sale with tranches, refunds, and a verified raise-lock |
AllocationRound |
Merkle-based allocation and claiming |
LaunchVestingCore |
Shared vesting maths for both round types |
LaunchRoundDeployers |
Split deployers - keeps the factory under the EIP-170 size limit |
| File | Role |
|---|---|
LockerContractStructs.sol |
Shared data structures (CreateLockParams, SignatureParams) |
LockerInternal.sol |
Internal logic (operation validation, batch approve) |
LockerLockOperations.sol |
Lock/unlock operations with signature verification |
LockerSignerOperations.sol |
Threshold update with multi-sig |
LockerViewFunctions.sol |
Read-only view functions |
All mocks live in contracts/mocks/ and are never deployed to a real network.
| Contract | Usage |
|---|---|
ERC20Mock |
Standard ERC-20 token for testing |
ERC20MockWithDecimals |
ERC-20 with custom decimals |
ERC20MutableDecimals |
Token that changes decimals() between calls |
ERC20USDTMock |
USDT simulation (non-standard approve) |
ERC20FeeMock |
Fee-on-transfer token, including a fee switched on mid-flight |
ERC20Blacklistable |
USDC/USDT-style blacklist and pause switches |
MockUniswapV2Pair |
Uniswap V2 pool simulation |
MockUniswapV3Pool |
Uniswap V3 pool simulation |
MockBrokenPool |
Pool returning malformed or zero-liquidity data |
MockAggregatorV3 |
Chainlink-style feed (legacy oracle paths) |
WETH9Mock |
Wrapped-native mock |
FullMathMock |
Direct harness for the FullMath library |
ForceSend |
Forces native coin into a contract with no receive() |
ReentrantUnlockToken |
Reentrancy attempt on the unlock path |
ReentrantPresaleAttacker |
Reentrancy attempt on presale refunds |
RogueTeamLocker |
Hand-written locker impostor (wrong codehash) |
FakeLocker |
Locker-shaped contract failing the consistency probe |
FakeVestingManagerForAnchor |
Canonical-shell locker wired to a hostile VestingManager |
PermissiveSignerManagerMock |
SignerManager that approves everything |
AliasedStableForRescue |
Token aliasing a canonical stablecoin address |
ForwardingBeneficiary |
Beneficiary that forwards value on receipt |
| Tool | Version | Check |
|---|---|---|
| Node.js | 22+ (see .nvmrc) |
node --version |
| NVM | Latest | nvm --version |
| Git | Latest | git --version |
# Clone the repository
git clone https://github.com/locker-protocol/locker-protocol-smart-contracts.git
cd locker-smart-contracts
# Use the correct Node version
nvm use
# Install dependencies
npm install
# Compile contracts
npx hardhat compile# Terminal 1: Start a local Hardhat node
npx hardhat node
# Terminal 2: Deploy all contracts
npx hardhat run scripts/deploy-local.js --network localhostThis deploys:
- All 6 core contracts (LockerContract, LockManager, SignerManager, ValidationHandler, VestingManager, PriceCalculator)
- Mock ERC-20 tokens (WETH, USDT, USDC, DAI)
Deployed addresses are saved to deployed-addresses.json.
The test suite contains 91 test files covering all contract functionality - from core locking and vesting to multi-signature governance, replay protection, the Launch Suite, and adversarial security scenarios.
A large share of them are adversarial: they mount a concrete attack and pass only when it
is blocked. Green there means "the hole is closed", not "the feature works" - each one
fails loudly (ATTACK SUCCEEDED) while its hole is open, so it stays the regression test for
its own fix.
Tests run on a local Hardhat node with automated setup, compilation, and teardown.
# Run all tests
npm test
# Run a specific test
cd test
./run_tests.sh 01 # By number
./run_tests.sh basic # By keyword
./run_tests.sh 01 03 05 # Multiple tests| ID | Test | Description |
|---|---|---|
| 00 | 00-setup.js |
Deploy contracts, configure signers and tokens (always runs first) |
| 01 | 01-basic-lock.js |
Create a lock, add tokens, verify balances and lock state |
| 02 | 02-price-lock.js |
Create locks with on-chain price conditions (Uniswap oracles) |
| 07 | 07-lock-history.js |
Verify lock history tracking and event logs |
| 30 | 30-get-all-locks.js |
Retrieve and paginate all locks across many tokens (stress test) |
| 40 | 40-multilock-surplus.js |
Multi-lock operations and surplus token handling |
| 44 | 44-zero-duration-lock.js |
Zero-duration locks are immediately time-unlockable |
| 90 | 90-lock-closure-with-history.js |
Closing a lock is constant-cost regardless of accumulated history; duration bound |
| 91 | 91-lock-closure-and-duration-bounds.js |
Regular and vesting closure cost, history retention, and MAX_LOCK_DURATION bounds |
| 72 | 72-lock-pagination-and-counter.js |
Lock counter and paginated enumeration |
| 101 | 101-accounting-sweep-and-pagination.js |
Token accounting under sweeps, pagination invariants |
| 64 | 64-permissionless-topup.js |
addToLock is open by design and can only pull the caller's own funds |
| 63 | 63-fee-on-transfer-credit.js |
Fee-on-transfer tokens are credited by what actually arrived |
| 70 | 70-topup-overflow-gainloss.js |
Top-up overflow safety and gain/loss pool consistency |
| ID | Test | Description |
|---|---|---|
| 05 | 05-price-conditions.js |
UPSIDE / DOWNSIDE price condition logic |
| 09 | 09-target-price-immutable.js |
Verify target price cannot be modified after lock creation |
| 31 | 31-exhaustive-prices.js |
Exhaustive price calculation scenarios (edge cases) |
| 32 | 32-reference-pools.js |
Reference pool configurations for multiple DEX versions |
| 42 | 42-threshold-v3-inverse.js |
Uniswap V3 inverse pair math and threshold edge cases |
| 51 | 51-downside-ethpair-paths.js |
DOWNSIDE direction combined with ETH-routed pairs |
| 56 | 56-pricecalculator-edges.js |
PriceCalculator edge branches |
| 57 | 57-fullmath-edges.js |
FullMath edge coverage |
| 62 | 62-time-backstop-price-lock.js |
The time backstop still applies to a price-locked lock |
| 67 | 67-v3-price-underflow.js |
V3 price underflow is symmetric - no silent zero |
| ID | Test | Description |
|---|---|---|
| 10 | 10-eip712-unlock.js |
EIP-712 typed signature unlock flow |
| 18 | 18-vesting-unlock-sig.js |
Progressive vesting with multi-sig unlock |
| 54 | 54-vesting-negatives.js |
Rejected vesting configurations |
| 68 | 68-vesting-duration-guard.js |
A schedule that could never complete before unlockTime is refused |
| 71 | 71-vesting-percent-and-history.js |
Percentage mode resolved against a frozen basis, plus claim history |
| 92 | 92-vesting-percent-cap-grief.js |
Dust top-ups cannot inflate the rate or void a signed ceiling |
| 100 | 100-vesting-price-cross.js |
Vesting interacting with price conditions |
| 104 | 104-archive-direction-and-decimals.js |
Lock archive direction and live decimals() reads |
| ID | Test | Description |
|---|---|---|
| 14 | 14-batch-signers.js |
Batch add/remove signers |
| 15 | 15-threshold-update.js |
Update M-of-N threshold with signatures |
| 16 | 16-create-lock-sig.js |
Lock creation requiring multi-sig approval |
| 34 | 34-threshold-under-5.js |
Threshold configurations with fewer than 5 signers |
| 73 | 73-genesis-threshold-invariant.js |
threshold <= signers holds from construction onward (anti-freeze) |
| 98 | 98-threshold-one-money-paths.js |
Every money path under a 1-of-1 configuration |
| ID | Test | Description |
|---|---|---|
| 04 | 04-lock-security.js |
Unauthorized access, invalid inputs, boundary checks |
| 06 | 06-lock-rescue.js |
Multi-sig token rescue with guards against active locks |
| 43 | 43-rescue-lock-lifecycle.js |
Rescue interaction with the full lock lifecycle |
| 45 | 45-rescue-native.js |
Multi-sig native coin rescue (force-sent funds) |
| 36 | 36-signature-malleability.js |
EIP-712 signature malleability protection (ECDSA s value) |
| 38 | 38-lockmanager-price-coverage.js |
LockManager price logic edge cases |
| 39 | 39-signer-access-coverage.js |
Signer access control exhaustive coverage |
| 35 | 35-v3-math-overflow.js |
Uniswap V3 math overflow protection |
| 58 | 58-entrypoint-lockdown-matrix.js |
Exhaustive matrix: every entry point against a non-signer |
| 59 | 59-residual-revert-paths.js |
Residual revert paths not covered elsewhere |
| 60 | 60-adversarial-fund-theft.js |
Direct attempts to walk funds out of a locker |
| 61 | 61-hardening-regression.js |
Regressions for previously closed holes |
| 74 | 74-rogue-signer-rescue-block.js |
A lone signer cannot force a rescue |
| 75 | 75-v3-price-quantum-zero.js |
A V3 pool truncating to a usable zero price |
| 93 | 93-module-coherence-invariant.js |
Modules must all point at the same peers (ERR_010/ERR_011) |
| 95 | 95-ethpair-counterasset.js |
The counter-asset of an ETH-routed pair must really be WETH |
| 96 | 96-signer-freeze-via-surplus-sweep.js |
Freezing funds costs the same quorum as releasing them |
| 97 | 97-price-gate-cross-defeats.js |
Four price-gate defeats, closed by the canonical stablecoin registry |
| 99 | 99-launch-module-substitution.js |
A canonical shell wired to a hostile module is rejected |
| 103 | 103-signature-encoding-negatives.js |
Malformed signature encodings |
| 47 | 47-operations-surface-removed.js |
Removed operation surface stays removed; onlyLocker gating |
| 48 | 48-lockercontract-negatives.js |
LockerContract negative paths |
| 50 | 50-lockmanager-negatives.js |
LockManager negative paths and edge coverage |
| 52 | 52-validationhandler-negatives.js |
ValidationHandler negative paths and exact revert reasons |
| 53 | 53-signermanager-negatives.js |
SignerManager negative paths and neutral getters |
| 55 | 55-libops-reverts.js |
Library-level revert coverage |
| 66 | 66-signer-bounds-swap.js |
Progressive onboarding, anti-freeze invariant, 1β20 bounds |
| ID | Test | Description |
|---|---|---|
| 23 | 23-threshold-replay-protection.js |
Replay protection on threshold updates |
| 25 | 25-batch-signers-replay.js |
Replay protection on batch signer operations |
| 26 | 26-unlock-replay.js |
Replay protection on unlock transactions |
| 27 | 27-create-lock-replay.js |
Replay protection on lock creation |
| 46 | 46-rescue-replay.js |
Replay protection on token and native rescues |
| 49 | 49-batch-update-and-replay.js |
Batch signer update guards + on-chain replay protection |
| ID | Test | Description |
|---|---|---|
| 76 | 76-allocation-round.js |
Merkle allocation round: finalize, claim, vesting |
| 77 | 77-allocation-scale.js |
Allocation at scale (large trees) |
| 78 | 78-presale-round.js |
Presale tranches, soft cap, settlement, refunds |
| 79 | 79-presale-raiselock.js |
Raise-lock creation, execution, and retargeting |
| 85 | 85-launch-vault-flow.js |
Air-gapped vault flow for round creation |
| 86 | 86-launch-global-stats.js |
Cross-round aggregate statistics |
| 94 | 94-presale-leg-isolation.js |
One blacklisted stablecoin must not strand the whole raise |
| 102 | 102-launch-edge-states.js |
Round edge states and transitions |
Each of these mounts real attacks and passes only when every one is blocked.
| ID | Test | Description |
|---|---|---|
| 80 | 80-launch-adversarial.js |
Round 1 - impostor locker, vesting-drained lock target, proceeds sweep |
| 81 | 81-launch-adversarial-2.js |
Round 2 - the price door, cap-table pollution |
| 82 | 82-launch-adversarial-3.js |
Round 3 - fee-switch tokens, verifier verdicts, forgeable history |
| 83 | 83-launch-adversarial-derived.js |
Round 4 - derived attacks and TOCTOU on the verified horizon |
| 84 | 84-launch-adversarial-actors.js |
Round 5 - investor, team, and code-author threat models |
| 93 | 93-module-coherence-invariant.js |
Round 6 - split module wiring behind canonical bytecode |
| 99 | 99-launch-module-substitution.js |
Round 6 - module substitution behind a canonical codehash |
The deployment process is entirely configured via environment variables. You can deploy to any preset EVM chain (with default public RPCs or optional custom RPC overrides) or to any generic EVM chain by specifying its RPC URL and Chain ID.
Create your .env file by copying the template:
cp .env.example .envOpen .env and fill in the values:
# ------------------------------------------------------------
# 1. Deployer Credentials (Provide EITHER Private Key OR Mnemonic)
# ------------------------------------------------------------
# If both are set, PRIVATE_KEY takes precedence.
PRIVATE_KEY=0xYourPrivateKeyHere
# OR:
MNEMONIC=word1 word2 word3 ... word12
# ------------------------------------------------------------
# 2. Locker Parameters
# ------------------------------------------------------------
# INITIAL_SIGNERS: Comma-separated list of authorized multi-sig signers (3 to 20).
INITIAL_SIGNERS=0xSigner1,0xSigner2,0xSigner3
# THRESHOLD: Number of required signer approvals (must be >= 3 and <= number of signers).
THRESHOLD=3
# ------------------------------------------------------------
# 3. Uniswap Price Condition / WETH Configuration
# ------------------------------------------------------------
# WETH_ADDRESS: Required for on-chain price evaluation (PriceCalculator).
# Set to the WETH or wrapped native token address of your target chain (see table below).
WETH_ADDRESS=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
# CUSTOM_WETH_ADDRESSES: Optional comma-separated list of additional
# wrapped-native token addresses, registered immutably at deploy time.
# CUSTOM_WETH_ADDRESSES=0xToken1,0xToken2
# ------------------------------------------------------------
# 4. Target Network Configuration (Only required for --network custom / mainnet)
# ------------------------------------------------------------
RPC_URL=https://your-custom-rpc-url-here
CHAIN_ID=1
# ------------------------------------------------------------
# 5. Optional Preset Network RPC Overrides (e.g. for --network base)
# ------------------------------------------------------------
# BASE_RPC_URL=https://mainnet.base.org
# BSC_RPC_URL=https://binance.llamarpc.com
# POLYGON_RPC_URL=https://polygon.llamarpc.com
# ARBITRUM_RPC_URL=https://arbitrum.llamarpc.com
# OPTIMISM_RPC_URL=https://optimism.llamarpc.com
# SEPOLIA_RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
# AMOY_RPC_URL=https://rpc-amoy.polygon.technology
# BSC_TESTNET_RPC_URL=https://bsc-testnet-rpc.publicnode.comCompile the contracts using Hardhat:
npx hardhat compile --forceDeploy the contracts to the network of your choice.
You can deploy directly to standard networks configured in hardhat.config.js. If you haven't set custom RPC overrides in .env, the script will automatically use public endpoints:
# Mainnets
npx hardhat run scripts/deploy.js --network base
npx hardhat run scripts/deploy.js --network arbitrum
npx hardhat run scripts/deploy.js --network bsc
npx hardhat run scripts/deploy.js --network polygon
npx hardhat run scripts/deploy.js --network ethereum
npx hardhat run scripts/deploy.js --network optimism
# Testnets
npx hardhat run scripts/deploy.js --network sepolia
npx hardhat run scripts/deploy.js --network amoy
npx hardhat run scripts/deploy.js --network bscTestnetTo deploy to any EVM chain not preset above, configure RPC_URL and CHAIN_ID in .env, and run the deployment targeting the custom network:
npx hardhat run scripts/deploy.js --network customThe deployment script executes the following steps:
- Validates Configuration β Checks for active RPC, balance, WETH address validity, signer count (3-20), and threshold rules.
- Deploys Core Modules β Deploys
PriceCalculator,ValidationHandler,LockManager,SignerManager, andVestingManager. - Deploys Locker Entry Point β Deploys
LockerContractusing the module addresses, initial signers, and threshold. - Saves Deployment Metadata β Generates a
deployed-addresses-<chainId>.jsonmetadata file containing all deployed addresses and deployment parameters. - Prints Integration Guide β Outputs step-by-step instructions to register your new Locker on the protocol.
Once your contracts are deployed, register your Locker on the platform:
- Go to lockerprotocol.com
- Connect your wallet (MetaMask) on the correct network
- Click "Add Custom Locker"
- Paste your LockerContract address (the one labelled as the entry point in the deployment output)
- Give your Locker a name (optional)
- Click "Add Locker"
Your Locker is now visible and manageable through the Locker Protocol interface.
| Chain | Chain ID | WETH Address |
|---|---|---|
| Ethereum | 1 |
0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 |
| BSC | 56 |
0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c |
| Polygon | 137 |
0x7ceB23fD6bC0adD59E62ac25578270cFf1b9f619 |
| Arbitrum | 42161 |
0x82aF49447D8a07e3bd95BD0d56f35241523fBab1 |
| Optimism | 10 |
0x4200000000000000000000000000000000000006 |
| Base | 8453 |
0x4200000000000000000000000000000000000006 |
| Avalanche | 43114 |
0xB31f66AA3C1e785363F0875A1B74E27b85FD66c7 |
| Linea | 59144 |
0xe5D7C2a44FfDDf6b295A15c148167daaAf5Cf34f |
| zkSync Era | 324 |
0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91 |
| Robinhood | 4663 |
0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73 |
All critical operations require M-of-N multi-signature approval:
- Unlock tokens - requires threshold signatures
- Rescue tokens or native funds - requires threshold signatures + nonce
- Update signers - requires threshold signatures + nonce
- Update threshold - requires threshold signatures + nonce
Every signed operation includes a per-operation nonce that is incremented after execution, making signature replay impossible.
Private keys can be kept on a completely offline device. Signature requests and responses are exchanged via animated QR codes using the BC-UR protocol.
ReentrancyGuard- protection against reentrancy attacksSafeERC20- safe token transfer handling- Checks-Effects-Interactions pattern throughout
Releasing funds and immobilising them cost the same authority. Crediting a token's
rescuable surplus into a lock is a quorum-gated SweepSurplus operation - the exact
complement of rescueToken over the same quantity. It used to be a lone-signer call,
which let one signer sweep a token's surplus into a price-free century-long lock and
freeze it against the full quorum.
isStablecoin and isWETH are keyed by block.chainid: on any chain, only that chain's
own entries can match, and everything else falls through to the immutable per-deployment
list configured at construction.
This is not defensive styling. A flat cross-chain address set is only as strong as the key
management of every project in it: a CREATE address is keccak(rlp(sender, nonce)) with
no chain component, so anyone holding a deployer's key can claim the same address on any
chain where that account is still at the matching nonce. That has already happened to a
major stablecoin - one well-known DAI address is genuine on two chains and
attacker-controlled on a third, where the code sitting there is not even an ERC-20. An
address only identifies an asset together with its chain id.
Both registries are immutable after construction, on purpose: a standing power to extend
what counts as a dollar is a standing power to mint a fake one. Adopting a new canonical
stablecoin therefore requires redeploying PriceCalculator.
A PresaleRound that advertises "the raise is locked" verifies that claim on-chain before
the round can exist:
- the team's locker must match the canonical codehash, and so must the two modules that
carry the promise (
VestingManager,LockManager) - pinning the shell alone is not enough, since its modules are constructor arguments held in ordinary storage; - the lock's release time is read through
earliestReleaseTime, which accounts for every door (time, price, and vesting), not justunlockTime; - both checks run again at
executeRaiseLockand on every retarget, so substituting a module after creation is caught too.
If you find a vulnerability, please see SECURITY.md for reporting instructions.
locker-smart-contracts/
βββ contracts/ # Solidity smart contracts
β βββ LockerContract.sol # Entry point
β βββ LockManager.sol # Lock storage & conditions
β βββ SignerManager.sol # Signer management
β βββ ValidationHandler.sol # EIP-712 multi-sig engine
β βββ VestingManager.sol # Progressive vesting
β βββ PriceCalculator.sol # Uniswap V2/V3 oracle + canonical asset registries
β βββ FullMath.sol # Math library
β βββ LaunchFactory.sol # Launch Suite - per-chain canonical rails
β βββ PresaleRound.sol # Launch Suite - stablecoin-only sale
β βββ AllocationRound.sol # Launch Suite - merkle allocation
β βββ mocks/ # Test-only contracts
βββ scripts/
β βββ deploy-local.js # Local testnet deployment
β βββ deploy.js # Mainnet deployment
β βββ verify-round.mjs # Off-chain verifier for a finalized round
βββ test/
β βββ tests/ # 91 test files
β βββ core/utils.js # Test utilities
β βββ run_tests.sh # Test runner
βββ hardhat.config.js # Hardhat configuration
βββ package.json # Dependencies
βββ .env.example # Environment template
βββ LICENSE # Proprietary License
βββ SECURITY.md # Security policy
Solidity: 0.8.20
Optimizer: enabled (runs: 1)
viaIR: true
This project is licensed under a proprietary non-commercial license. You are permitted to use, copy, modify, and run the software for non-commercial purposes only. Commercial use, reproduction, redistribution, or commercialization is strictly prohibited. See the LICENSE file for details.
Website: lockerprotocol.com Β· Security: SECURITY.md