Request to refund 37,875 USDC.e burned by self-disputing 86 poisoned requests

I run an automated proposer on Polygon, 0xa0b6210f5b522b97bbce9b8e617627105c0e3891. On 10-11 August I proposed on 88 price requests that existed only to trap bonds. All 88 are disputed now, and I’ve commented in every ER thread. I’m asking the DAO to return the 37,875 USDC.e that gets burned to the treasury as a result.

What happened
Someone copied live Polymarket markets in bulk. Between blocks 91786519 and 91812941 — about fifteen hours on 10-11 August — 7,605 duplicate requests went onto the oracle, one transaction each, each aimed at a different market. My bot proposed on 88 of them, which is 1.2% of the batch.

A question’s identity is the hash of its ancillary data, and Polymarket’s ancillary ends with
the address that created it. Copy a real question, add one more address on the end, and the hash changes: the oracle sees a new question even though every word describing the market is
identical. The extra address is 0d14e9a9bb646ff0b23f610dd3a50fc9f40cd336, and it’s on all
7,605 of them. Real Polymarket questions have one creator address at the end. These have two.

The 88 that reached me went in through Polymarket’s own adapter at 0x65070BE9…, with zero
reward and a challenge window of a billion seconds. That’s 31.7 years, so they’d have become
settleable in April 2058. Real Polymarket sports requests that week ran 600, 900 or 1800
seconds.

Everything else in them is real, because it was copied from something real: the market_id
points at an actual market, the titles are real fixtures, the resolution rules and p1/p2/p3
mapping are the genuine ones word for word. I’ve since read all 88 in full and there’s nothing
hidden in the text. The only difference is that extra address at the end, which nobody was
looking at.

My bot answered all 88 correctly and bonded each one before I noticed anything was wrong. That
cost 55,250 USDC.e: 45 requests at 750 (a 500 bond plus the 250 final fee) and 43 at 500 (a 250
bond plus the same fee).

Two things you can check without taking my word for it
Here’s the full list, all 88, each one paired with the genuine request it copies:
The 88 poisoned requests, and the real markets they copy - dispute-details - Google Docs .
Open any row’s two links side by side and the Explorer sorts them out for you: the genuine
request says Creation source: Polymarket with a 600-1800 second challenge period, the
duplicate says Creation source: Manual, reward 0, challenge period 1000000000 seconds, and no event slug.

The real markets settled the same way I proposed.
For each one I found the genuine Polymarket request for the same `market_id` and read its settled price off the oracle. On all 86 I disputed myself, the real market resolved to the outcome I’d proposed on the copy. Every one. The ER comment in each thread links the genuine request, so checking any of them takes a minute.

The copy always came first, or at the same moment.
In 45 cases the fake request was created before the real one; in the other 41 they landed in the same second. Never afterwards. That’s why my proposer never saw the genuine question: it keeps one request per market, the copy got there first, and the real one had nowhere to go. Of 4,247 genuine requests it handled that week, none share a market_id with these 88.

Where things stand
An undisputed proposal can’t be settled until its challenge window closes, so disputing was the
only way to get any of it back. I disputed 86 myself, at another 54,250 in bonds. Being on both
sides, the vote doesn’t change what comes back to me, and I’ve said so in each of those threads.
70,625 returns and 37,875 burns.

The remaining two were front-run out of the mempool by
0x76df1a5792d4d31a57b72951373c69f734462316. Those are real disputes with a counterparty, the loser pays the burn, and I’m not asking for anything on them. They’re outside this request.

Why I’m asking
Disputing has to cost something or self-disputing would be free, and the burn also pays for
voter time and the gas the DAO rebates. I’m not arguing against either.

But the deterrent is aimed at someone who disputes when they didn’t have to. I didn’t have that
option: 86 disputes were the only way out of a 31-year lock, on bonds I didn’t choose, against
requests that read as genuine right up to the extra address at the end. You can’t deter
behaviour that has no alternative. Here it just moves money.

My part in this
The 31-year window trapped the money, but it isn’t why I walked in. My bot listened to
RequestPrice events and worked out which market each one belonged to by reading the market_id: string out of the question text. That string was genuine, copied from a genuine question, so it matched perfectly. Nothing on my side checked whether Polymarket had actually pointed at the question I was reading. That one’s on me and I won’t pretend otherwise.

I’ve turned it around since. Markets now start from Polymarket’s own record, which publishes a
questionID and a resolving adapter per market, and the bot reads that exact question on chain
instead of trusting whatever arrives in the event stream. A fabricated question can’t get in
that way, because nothing on Polymarket’s side points at it.

I’ve written that up so nobody else has to learn it the expensive way:
Binding oracle requests to real Polymarket markets - proposer-guide - Google Docs .
It covers how to bind a market to its canonical question, a one-line check for anyone who’d
rather not rewrite their pipeline, and the log query that tells you whether you’re already
holding one of these.

The numbers
The 86 fall into two groups, differing only in the size of the request’s bond:
45 proposals cost 750 usdc.e each to propose and 750 usdc.e to dispute.
41 proposals cost 500 usdc.e each to propose and 500 usdc.e to dispute.
Totals:
bonded for proposals:54,250 usdc.e
bonded again to dispute them: 54,250 usdc.e
returned at settlement: 70,625 usdc.e
burned to the treasury: 37,875 usdc.e

All of it comes from getRequest and from the ProposePrice, DisputePrice and Settle logs, not
my own records. Every row behind these totals is in the document linked above, with the propose
and dispute transaction for each one, so the arithmetic can be rebuilt from the chain without
going through me.

What I’m asking for
Transfer 37,875 USDC.e to 0xa0b6210f5b522b97bbce9b8e617627105c0e3891. Nothing else: no
protocol change, no parameter change, no new rule.

Posting here first for feedback. If there’s support I’ll open a Snapshot vote with the 5-day
close and post the 4,000 UMA bond, then an on-chain vote for the transfer.

4 Likes

This request seems reasonable to me. The evidence is unusually well documented, and the fact that all 86 self-disputes resolved to the same outcomes as the genuine requests makes the case much stronger. You have also acknowledged the gap in your own bot and already changed the architecture so it validates the questionID or adapter instead of trusting the marketID in arbitrary requests.

I like how you explicitly acknowledged that your bot relied on market_id and how you fixed it which makes the request more credible.

I would 100% support taking this to Snapshot and letting UMA governance decide it. I believe a one-off reimbursement is reasonable without requiring any protocol level rule change.

2 Likes