Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions drosera-trap-simulator/README.md
Original file line number Diff line number Diff line change
@@ -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/)
Binary file added drosera-trap-simulator/drosera-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added drosera-trap-simulator/drosera.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 28 additions & 0 deletions drosera-trap-simulator/eslint.config.js
Original file line number Diff line number Diff line change
@@ -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 },
],
},
},
)
13 changes: 13 additions & 0 deletions drosera-trap-simulator/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/drosera.png" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Drosera Simulator</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
35 changes: 35 additions & 0 deletions drosera-trap-simulator/package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
6 changes: 6 additions & 0 deletions drosera-trap-simulator/postcss.config.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
}
Binary file added drosera-trap-simulator/public/drosera-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions drosera-trap-simulator/public/vite.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
42 changes: 42 additions & 0 deletions drosera-trap-simulator/src/App.css
Original file line number Diff line number Diff line change
@@ -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;
}
235 changes: 235 additions & 0 deletions drosera-trap-simulator/src/App.tsx
Original file line number Diff line number Diff line change
@@ -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<HTMLSelectElement>) => {
const index = Number(e.target.value);
const preset = trapPresets[index];
presetIndexRef.current = index;
setTrapLogic(preset.logic);
setSimulatedEvent(preset.event);
setOutput(null);
};

return (
<div className="bg-gray-900 min-h-screen text-white p-6 font-mono">
<div className="flex items-center justify-center mb-6">
<img src="/drosera-logo.png" alt="Drosera Logo" className="w-8 h-8 mr-3" />
<h1 className="text-3xl font-bold">Drosera Trap Simulator <span className="text-sm text-gray-400 ml-2">(Unofficial)</span></h1>
</div>

<div className="flex justify-center gap-4 mb-6">
<button onClick={() => setActiveTab('simulator')} className={`px-4 py-2 rounded ${activeTab === 'simulator' ? 'bg-indigo-600' : 'bg-gray-700'}`}>Simulator</button>
<button onClick={() => setActiveTab('liveboard')} className={`px-4 py-2 rounded ${activeTab === 'liveboard' ? 'bg-indigo-600' : 'bg-gray-700'}`}>Proof-of-Trap</button>
</div>

{activeTab === 'simulator' ? (
<>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
<div>
<h2 className="text-xl mb-2">🧠 Trap Logic</h2>
<Editor
height="300px"
defaultLanguage="javascript"
value={trapLogic}
onChange={(val) => setTrapLogic(val || '')}
theme="vs-dark"
/>
</div>
<div>
<h2 className="text-xl mb-2">🧱 Simulated Event</h2>
<Editor
height="300px"
defaultLanguage="json"
value={simulatedEvent}
onChange={(val) => setSimulatedEvent(val || '')}
theme="vs-dark"
/>
</div>
</div>

<div className="flex flex-col items-center mb-4">
<select
className="bg-gray-800 text-white p-2 rounded mb-1"
onChange={handleDropdownChange}
value={presetIndexRef.current}
>
{trapPresets.map((preset, index) => (
<option key={index} value={index}>{preset.label}</option>
))}
</select>
<span className="text-gray-300">(or)</span>
</div>

<div className="flex flex-wrap gap-4 justify-center mb-4">
<button
onClick={generateTrapAndEvent}
className="bg-indigo-600 hover:bg-indigo-700 px-4 py-2 rounded text-white font-bold"
>
πŸ§ͺ Generate Trap + Event
</button>
<button
onClick={handleRunTrap}
className="bg-green-600 hover:bg-green-700 px-4 py-2 rounded text-white font-bold"
>
πŸš€ Run Trap
</button>
</div>

{output && (
<div className="bg-gray-800 p-4 rounded text-lg">
<span className="font-bold">Result:</span>
<span className={output.triggered ? "text-green-400" : "text-pink-400"}> {output.message}</span>
</div>
)}
</>
) : (
<LiveBoard />
)}

<footer className="mt-8 text-center text-sm text-gray-400">
Developed by <a href="https://x.com/xtestnet" className="underline text-blue-400">@xtestnet</a>
</footer>
</div>
);
}

export default App;
Loading