diff --git a/drosera-trap-simulator/README.md b/drosera-trap-simulator/README.md new file mode 100644 index 0000000..c8fb35d --- /dev/null +++ b/drosera-trap-simulator/README.md @@ -0,0 +1,13 @@ +# Drosera Trap Simulator + +A simple UI to simulate, test, and visualize Drosera Trap logic. + +### ๐Ÿ”ง Features +- Write custom trap logic +- Simulate test events +- View real-time results +- Leaderboard with active trap deployers (Proof-of-Trap โ€“ Cadet) + +Built with **Vite + React + TailwindCSS**. + +> ๐Ÿงช Deployed version: [https://drosera-gamma.vercel.app](https://drosera-gamma.vercel.app/) diff --git a/drosera-trap-simulator/drosera-logo.png b/drosera-trap-simulator/drosera-logo.png new file mode 100644 index 0000000..a126364 Binary files /dev/null and b/drosera-trap-simulator/drosera-logo.png differ diff --git a/drosera-trap-simulator/drosera.png b/drosera-trap-simulator/drosera.png new file mode 100644 index 0000000..469b525 Binary files /dev/null and b/drosera-trap-simulator/drosera.png differ diff --git a/drosera-trap-simulator/eslint.config.js b/drosera-trap-simulator/eslint.config.js new file mode 100644 index 0000000..092408a --- /dev/null +++ b/drosera-trap-simulator/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) diff --git a/drosera-trap-simulator/index.html b/drosera-trap-simulator/index.html new file mode 100644 index 0000000..55c027e --- /dev/null +++ b/drosera-trap-simulator/index.html @@ -0,0 +1,13 @@ + + + + + + + Drosera Simulator + + +
+ + + diff --git a/drosera-trap-simulator/package.json b/drosera-trap-simulator/package.json new file mode 100644 index 0000000..637584b --- /dev/null +++ b/drosera-trap-simulator/package.json @@ -0,0 +1,35 @@ +{ + "name": "drosera-trap-simulator", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@monaco-editor/react": "^4.7.0", + "ethers": "^6.14.3", + "react": "^19.1.0", + "react-dom": "^19.1.0" + }, + "devDependencies": { + "@eslint/js": "^9.25.0", + "@tailwindcss/postcss": "^4.1.8", + "@types/react": "^19.1.2", + "@types/react-dom": "^19.1.2", + "@vitejs/plugin-react": "^4.4.1", + "autoprefixer": "^10.4.21", + "eslint": "^9.25.0", + "eslint-plugin-react-hooks": "^5.2.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^16.0.0", + "postcss": "^8.5.4", + "tailwindcss": "^3.4.1", + "typescript": "~5.8.3", + "typescript-eslint": "^8.30.1", + "vite": "^6.3.5" + } +} diff --git a/drosera-trap-simulator/postcss.config.cjs b/drosera-trap-simulator/postcss.config.cjs new file mode 100644 index 0000000..a03e681 --- /dev/null +++ b/drosera-trap-simulator/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/drosera-trap-simulator/public/drosera-logo.png b/drosera-trap-simulator/public/drosera-logo.png new file mode 100644 index 0000000..a126364 Binary files /dev/null and b/drosera-trap-simulator/public/drosera-logo.png differ diff --git a/drosera-trap-simulator/public/vite.svg b/drosera-trap-simulator/public/vite.svg new file mode 100644 index 0000000..e7b8dfb --- /dev/null +++ b/drosera-trap-simulator/public/vite.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/drosera-trap-simulator/src/App.css b/drosera-trap-simulator/src/App.css new file mode 100644 index 0000000..b9d355d --- /dev/null +++ b/drosera-trap-simulator/src/App.css @@ -0,0 +1,42 @@ +#root { + max-width: 1280px; + margin: 0 auto; + padding: 2rem; + text-align: center; +} + +.logo { + height: 6em; + padding: 1.5em; + will-change: filter; + transition: filter 300ms; +} +.logo:hover { + filter: drop-shadow(0 0 2em #646cffaa); +} +.logo.react:hover { + filter: drop-shadow(0 0 2em #61dafbaa); +} + +@keyframes logo-spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +@media (prefers-reduced-motion: no-preference) { + a:nth-of-type(2) .logo { + animation: logo-spin infinite 20s linear; + } +} + +.card { + padding: 2em; +} + +.read-the-docs { + color: #888; +} diff --git a/drosera-trap-simulator/src/App.tsx b/drosera-trap-simulator/src/App.tsx new file mode 100644 index 0000000..52130f4 --- /dev/null +++ b/drosera-trap-simulator/src/App.tsx @@ -0,0 +1,235 @@ +// Drosera Trap Simulator with Tabs for LiveBoard +import React, { useState, useRef } from 'react'; +import Editor from '@monaco-editor/react'; +import './index.css'; +import LiveBoard from './LiveBoard'; + +function App() { + const trapPresets = [ + { + label: "๐Ÿšจ Bridge Exploit", + logic: `async function trap(event) { + if (event.type === "bridge" && event.amount > 100000 && event.to === "0xdeadbeef") { + return "๐Ÿšจ Potential bridge exploit!"; + } +}`, + event: `{ + "type": "bridge", + "amount": 150000, + "to": "0xdeadbeef", + "timestamp": ${Date.now()} +}` + }, + { + label: "๐ŸŒ€ ETH High Slippage", + logic: `async function trap(event) { + if (event.token === "ETH" && event.amount > 1000 && event.slippage > 5) { + return "๐ŸŒ€ Large ETH swap with high slippage!"; + } +}`, + event: `{ + "type": "swap", + "token": "ETH", + "amount": 1200, + "slippage": 7, + "timestamp": ${Date.now()} +}` + }, + { + label: "๐ŸŒ’ Off-Hour Transaction", + logic: `async function trap(event) { + const hour = new Date(event.timestamp).getUTCHours(); + if (hour >= 0 && hour <= 3 && event.amount > 10000) { + return "๐ŸŒ’ Suspicious large txn during off-hours!"; + } +}`, + event: `{ + "amount": 12000, + "timestamp": ${(() => { + const d = new Date(); + d.setUTCHours(Math.floor(Math.random() * 4), 0, 0, 0); + return d.getTime(); + })()} +}` + }, + { + label: "๐Ÿ“‰ Oracle Price Drop", + logic: `async function trap(event) { + if (event.asset === "BTC" && event.oraclePrice < 20000) { + return "๐Ÿ“‰ Oracle price dropped significantly!"; + } +}`, + event: `{ + "type": "oracle", + "asset": "BTC", + "oraclePrice": 19500, + "timestamp": ${Date.now()} +}` + }, + { + label: "โš”๏ธ AVS Slashing", + logic: `async function trap(event) { + if (event.avs === "staking" && event.slashAmount > 5000) { + return "โš”๏ธ AVS slashing triggered!"; + } +}`, + event: `{ + "type": "slashing", + "avs": "staking", + "slashAmount": 6000, + "timestamp": ${Date.now()} +}` + }, + { + label: "๐Ÿ›‘ DEX Liquidity Drop", + logic: `async function trap(event) { + if (event.pool === "DEX-XYZ" && event.liquidityUSD < 1000000) { + return "๐Ÿ›‘ Liquidity has dropped dangerously low on DEX!"; + } +}`, + event: `{ + "type": "liquidity", + "pool": "DEX-XYZ", + "liquidityUSD": 800000, + "timestamp": ${Date.now()} +}` + }, + { + label: "๐Ÿฆ Lending Collateral Event", + logic: `async function trap(event) { + if (event.collateralRatio < 1.2 && event.loanId) { + return "๐Ÿฆ Lending collateral ratio below threshold!"; + } +}`, + event: `{ + "type": "loan", + "loanId": "LN-1234", + "collateralRatio": 1.1, + "timestamp": ${Date.now()} +}` + } + ]; + + const presetIndexRef = useRef(0); + const [trapLogic, setTrapLogic] = useState(trapPresets[0].logic); + const [simulatedEvent, setSimulatedEvent] = useState(trapPresets[0].event); + const [output, setOutput] = useState<{ triggered: boolean; message: string } | null>(null); + const [activeTab, setActiveTab] = useState<'simulator' | 'liveboard'>('simulator'); + + const handleRunTrap = async () => { + try { + const event = JSON.parse(simulatedEvent); + const fullCode = `(async () => { ${trapLogic}; return await trap(${JSON.stringify(event)}); })()`; + const result = await eval(fullCode); + + if (result) { + setOutput({ triggered: true, message: result }); + } else { + setOutput({ triggered: false, message: "Trap NOT TRIGGERED" }); + } + } catch (error: any) { + setOutput({ triggered: false, message: `Error: ${error.message}` }); + } + }; + + const generateTrapAndEvent = () => { + presetIndexRef.current = (presetIndexRef.current + 1) % trapPresets.length; + const preset = trapPresets[presetIndexRef.current]; + setTrapLogic(preset.logic); + setSimulatedEvent(preset.event); + setOutput(null); + }; + + const handleDropdownChange = (e: React.ChangeEvent) => { + const index = Number(e.target.value); + const preset = trapPresets[index]; + presetIndexRef.current = index; + setTrapLogic(preset.logic); + setSimulatedEvent(preset.event); + setOutput(null); + }; + + return ( +
+
+ Drosera Logo +

Drosera Trap Simulator (Unofficial)

+
+ +
+ + +
+ + {activeTab === 'simulator' ? ( + <> +
+
+

๐Ÿง  Trap Logic

+ setTrapLogic(val || '')} + theme="vs-dark" + /> +
+
+

๐Ÿงฑ Simulated Event

+ setSimulatedEvent(val || '')} + theme="vs-dark" + /> +
+
+ +
+ + (or) +
+ +
+ + +
+ + {output && ( +
+ Result: + {output.message} +
+ )} + + ) : ( + + )} + + +
+ ); +} + +export default App; diff --git a/drosera-trap-simulator/src/LiveBoard.tsx b/drosera-trap-simulator/src/LiveBoard.tsx new file mode 100644 index 0000000..f58e90b --- /dev/null +++ b/drosera-trap-simulator/src/LiveBoard.tsx @@ -0,0 +1,89 @@ +// src/LiveBoard.tsx +import React, { useEffect, useState } from 'react'; +import { JsonRpcProvider, Contract } from 'ethers'; + +const LiveBoard: React.FC = () => { + const [names, setNames] = useState([]); + const [loading, setLoading] = useState(true); + const [page, setPage] = useState(1); + const [search, setSearch] = useState(''); + const pageSize = 25; + + useEffect(() => { + const fetchNames = async () => { + try { + const provider = new JsonRpcProvider("https://ethereum-holesky-rpc.publicnode.com/"); + const abi = [ + "function getDiscordNamesBatch(uint256 start, uint256 end) view returns (string[])" + ]; + const contract = new Contract( + "0x4608Afa7f277C8E0BE232232265850d1cDeB600E", + abi, + provider + ); + + const result = await contract.getDiscordNamesBatch(0, 2000); + const filtered = result.filter((name: string) => name && name !== "DISCORD_USERNAME"); + setNames(filtered); + } catch (err) { + console.error("Failed to fetch names", err); + } finally { + setLoading(false); + } + }; + + fetchNames(); + }, []); + + const filtered = names.filter(name => name.toLowerCase().includes(search.toLowerCase())); + const paginated = filtered.slice((page - 1) * pageSize, page * pageSize); + + return ( +
+

๐Ÿ… Proof-of-Trap โ€“ Cadet LiveBoard

+ + { setSearch(e.target.value); setPage(1); }} + className="mb-4 w-full p-2 rounded bg-gray-700 text-white" + /> + + {loading ? ( +

Loading real-time data from Drosera testnet...

+ ) : ( + filtered.length > 0 ? ( + <> +
    + {paginated.map((name, idx) => ( +
  • {name}
  • + ))} +
+
+ + Page {page} of {Math.ceil(filtered.length / pageSize)} + +
+ + ) : ( +

No active responders found.

+ ) + )} +
+ ); +}; + +export default LiveBoard; diff --git a/drosera-trap-simulator/src/assets/react.svg b/drosera-trap-simulator/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/drosera-trap-simulator/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/drosera-trap-simulator/src/index.css b/drosera-trap-simulator/src/index.css new file mode 100644 index 0000000..dce418d --- /dev/null +++ b/drosera-trap-simulator/src/index.css @@ -0,0 +1,7 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + @apply bg-gray-900 text-white; +} diff --git a/drosera-trap-simulator/src/main.tsx b/drosera-trap-simulator/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/drosera-trap-simulator/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/drosera-trap-simulator/src/vite-env.d.ts b/drosera-trap-simulator/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/drosera-trap-simulator/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/drosera-trap-simulator/tailwind.config.cjs b/drosera-trap-simulator/tailwind.config.cjs new file mode 100644 index 0000000..579846a --- /dev/null +++ b/drosera-trap-simulator/tailwind.config.cjs @@ -0,0 +1,7 @@ +module.exports = { + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], + theme: { + extend: {}, + }, + plugins: [], +}; diff --git a/drosera-trap-simulator/tsconfig.app.json b/drosera-trap-simulator/tsconfig.app.json new file mode 100644 index 0000000..c9ccbd4 --- /dev/null +++ b/drosera-trap-simulator/tsconfig.app.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/drosera-trap-simulator/tsconfig.json b/drosera-trap-simulator/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/drosera-trap-simulator/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/drosera-trap-simulator/tsconfig.node.json b/drosera-trap-simulator/tsconfig.node.json new file mode 100644 index 0000000..9728af2 --- /dev/null +++ b/drosera-trap-simulator/tsconfig.node.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/drosera-trap-simulator/vite.config.ts b/drosera-trap-simulator/vite.config.ts new file mode 100644 index 0000000..8b0f57b --- /dev/null +++ b/drosera-trap-simulator/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite' +import react from '@vitejs/plugin-react' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], +})