Work / Cryptoopen brief

StopCheck: an audit of the dreamDEX perp exchange and a tool against the stop-loss trap we found

What it was

A trader sets a stop loss on the entire position, adds to it, and the app keeps saying 'Entire'. When the price hits the stop, only the old size closes and the rest is left unprotected. The loss estimate in the dialog is also computed on the old size, so the trap is almost impossible to notice.

What we did

In one day we went through the exchange from the app down to the contracts: SDK, bot kit, docs. Every finding became a reproducible report with transaction hashes, checked first against 447 reports by others so as not to duplicate. For the key finding we built StopCheck: a static page reads the chain and shows how much of a position is really covered by a stop, and a bot closes the uncovered remainder.

What the client got

21 reports, a public repository and a live demo with hashes: the stop closed half the position at 19:34:35, the bot closed the other half at 19:34:38. A scan of 463 exchange accounts found 19 traders with a partially covered position, from 6% to 99.6%, and 129 with stops but no position. Infrastructure cost $0.

ClientdreamDEX, a perp exchange on Somnia. Open brief on DoraHacks
My roleExchange testing, 21 reports with transaction hashes, checker and bot code, web page, video, post copy
StatusTool is live, reports sent to the exchange team, brief results still ahead
Result19 of 463 exchange accounts with a partially covered position, from 6% to 99.6%, found by the scan · Open
Rejected: 99.6% coverage was rounded to '100%' right next to 'the rest has no stop', so the card contradicted itself. Reconstruction, not a screenshot
Rejected: 99.6% coverage was rounded to '100%' right next to 'the rest has no stop', so the card contradicted itself. Reconstruction, not a screenshot
Accepted: partial coverage is never rounded up. The number, the list of problems and the per-position bars all say the same thing
Accepted: partial coverage is never rounded up. The number, the list of problems and the per-position bars all say the same thing

The task

dreamDEX, a perp exchange on the Somnia network, opened a brief with two tracks: reports on problems found, paid per accepted report, and an MVP, a useful tool on top of the exchange. For us it was income plus an audit case for a DeFi product, a service that can be sold to other exchanges.

By the time we came in, the brief already had 447 reports from others, so the obvious had long been found. We had to look for what others had missed and prove every finding with a transaction hash, not a description.

Why, and what the idea was

The key finding from live tests in the app: a trader sets TP/SL in ‘Entire’ mode, meaning the whole position, then adds to the position, and the app keeps saying ‘Entire’. When the price reaches the stop, only the old size closes and the rest is left unprotected. The loss estimate in the dialog is also computed on the old size, so it is nearly impossible to notice until it is too late.

Then came a fork: build the MVP on our own finding, or make yet another ‘guardian’ bot, of which the brief already had five. We ran the decision through a council of five AI advisors and went with our finding. That is how StopCheck came about: paste a wallet address, sign nothing, the page reads the chain and shows what percentage of the position is really covered by a stop. Next to it, a bot that closes the uncovered remainder when the stop fires or the price reaches it.

A second council went over the whole plan just before publishing: soften the posts, never display other people’s positions, add a shareable result card and a 50-second video.

How it went

When What happened
Day 1, afternoon Registration, whitelist ticket, first reports via the SDK and bot kit
Day 1, evening Review of 447 reports by others: 8 of our topics had been filed earlier. New findings cross-checked before writing up
Day 1, evening Live tests in the app: reports 019, 020, 022 found
Day 1, night Council of 5 AI advisors: build the MVP on our own finding, not a sixth ‘guardian’
Day 1, night StopCheck: public repository, page, bot, live demo with hashes, scan of 463 accounts
Day 1, night Second council on the whole plan: softer posts, no other people’s positions, result card, 50 s video

The whole job fit into one day.

Tools and method

Reading: the @somnia-chain/markets-sdk SDK; positions and stop sizes come from the chain, the indexer only hints where to look. The page is static on GitHub Pages, asks for no keys and signs nothing. Built with esbuild, TypeScript and viem.

Bot: a linked key (the exchange’s own ‘Link a bot’ feature) and perp-core from the official bot kit, closing via placeOrderFor. The protocol does not let a bot place stops on the owner’s behalf, so the bot watches on its own. It tells a triggered stop from a cancelled one by the status in the indexer.

Reports: 21 of them across the app, SDK, bot kit, docs and contracts, each reproducible with transaction hashes. They live in a separate repository, dreamdex-perps-feedback. The 50-second video was assembled in ffmpeg from real screenshots captured in headless Chrome.

Result

The page at dmitriytopka.github.io/stopcheck: paste an address, get a card saying ‘the stop covers 50% of the position’, a list of problems and coverage bars per position, with Copy link and Share on X buttons. The code is open: github.com/DmitriyTopka/stopcheck.

A live demo on chain, both transactions linked in the README: the stop closed half the position at 19:34:35, the bot closed the other half at 19:34:38. Without the bot, half the position would have stayed open with no stop.

A scan of 463 exchange accounts: 19 with a partial stop loss, from 6% to 99.6%; 129 with stops but no position; 6.19 STT waiting to be claimed across 30 accounts. We do not publish other people’s addresses, that was a council decision.

Infrastructure cost $0: GitHub Pages and our own server. Testnet gas: the bot spent about 0.013 STT. The weak spots, stated plainly: no users yet, the bot needs its own server, and if the exchange fixes finding 019 the bot will be needed less often, while the checker stays useful.

Mistakes and what we took from them

A 0.138 BTC position instead of 15 USDso.In a test, '15' went into the percentage field, and I hit confirm without reading the dialog. Closed within a minute, minus 5.46 paper USDso. Rule: read the confirmation dialog separately before clicking and check 'Order value'.
99.6% rounded to '100%'.Right next to it stood 'the rest has no stop', and the card contradicted itself. Fixed: partial coverage is never rounded up.
We started publishing reports 1 to 3 days after others.8 of our topics were already in someone else's reports. Rule: in a brief that pays the first to file, start on opening day.

Tools

TypeScript@somnia-chain/markets-sdkviemdreamdex-bot-kitesbuildGitHub PagesChrome headlessffmpegClaude Code