Minimal, chain-agnostic interface to operate any OpenZeppelin TimelockController from the browser.
Schedule, monitor, and execute timelocked operations on any EVM chain.
Connect your wallet, paste a TimelockController address, and you're ready to go. No backend, no setup, no vendor lock-in.
- Any network. Mainnet, Sepolia, Hoodi, any L2 or custom RPC you configure.
- Any contract. Paste any
TimelockControlleraddress. No hardcoded deployments. - No backend. Everything runs in the browser; state is persisted in
localStorage. - Non-custodial. Your wallet signs every transaction; the app never touches your keys.
- Wallet-agnostic. MetaMask, Coinbase, Rainbow, WalletConnect. Any Safe via WalletConnect.
timelock.stakely.io, hosted by Stakely, free to use.
You can also self-host your own instance (see Self-hosting).
The classic timelock flow:
schedule → wait delay → execute
- Schedule. Submit a
schedule()transaction with the target, calldata, and a delay. The operation is queued on-chain and a countdown starts. - Wait. The operation stays in
Waitingstate until the delay elapses, then becomesReady. - Execute. Any account with
EXECUTOR_ROLEcallsexecute(). Any account withCANCELLER_ROLEcan callcancel()instead.
The UI also picks up operations scheduled outside of it: the Sync chain button scans CallScheduled events so you can monitor a Timelock you don't control.
Deploy your own instance in minutes:
git clone https://github.com/stakely/timelock-ui.git
cd timelock-ui
npm install
npm run build
# Serve dist/ with any static host: Vercel, Netlify, Nginx, IPFS, …For local development:
cp .env.example .env # set your WalletConnect Project ID (optional)
npm run dev # http://localhost:5173
npm run preview # preview the production buildCopy .env.example to .env and set your own Project ID (free at cloud.reown.com):
VITE_WC_PROJECT_ID=your_project_idThe app falls back to a bundled public ID if the variable is not set. Fine for local dev, but use your own for production.
| Package | Role |
|---|---|
| Vite + React 19 + TypeScript | Core framework |
| wagmi v2 + viem | On-chain reads & writes |
| RainbowKit | Wallet connection |
| TailwindCSS v4 | Styling |
| TanStack Query | Async state / caching |
| React Router v7 | Client-side routing |
| Lucide React | Icons |
All state lives in localStorage under the tl-ui:* namespace:
| Key | Contents |
|---|---|
tl-ui:networks |
Configured networks (chainId, RPC, explorer) |
tl-ui:timelocks |
Configured timelock contracts |
tl-ui:active-timelock |
Currently selected timelock address |
tl-ui:operations:<chainId>:<address> |
Operations per timelock |
tl-ui:sync-cursor:<chainId>:<address> |
Last block scanned for incremental sync |
Clearing site data resets the app completely.
Pull requests are welcome. The codebase is small and self-contained: no Solidity, no backend, just a frontend talking to the chain through viem.
For bugs or feature requests, open an issue at github.com/stakely/timelock-ui/issues.
npm run lint # ESLint
npm run build # type-check + production build