> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polynode.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Nonce Exploit Research

> Live research on the Polymarket settlement exploit — methodology, on-chain evidence, attacker identification, and detection.

polynode monitors every Polymarket settlement in real time, including ones that fail. This page documents our ongoing research into settlement failures caused by the `incrementNonce()` exploit on Polymarket's V1 CTF Exchange.

All data on this page is from Polygon mainnet. Every transaction hash is real and verifiable.

<Note>
  **Update — 2026-04-17: Fixed in V2.** The V2 CTF Exchange Order struct removes the `nonce` field entirely (along with `taker`, `expiration`, and `feeRateBps`). With no on-chain nonce to manipulate, `incrementNonce()` no longer exists as an attack surface. V2 cutover is scheduled for April 29, 2026 — at that point, Variant 1 of this exploit becomes structurally impossible. See the [V2 Migration Guide](/guides/v2-migration) for the full Order struct changes.
</Note>

## Background

Polymarket uses a hybrid architecture: orders are matched off-chain by the CLOB (Central Limit Order Book), then settled on-chain by an operator calling `matchOrders` on the CTF Exchange contract. There is a 2-3 second window between when the operator builds the settlement transaction and when it lands in a block. This window is the attack surface.

## The exploit

The CTF Exchange contract has a public function called `incrementNonce()` (selector `0x627cdcb9`). Anyone can call it on their own address for less than \$0.10 in gas. When called, it increments the caller's on-chain nonce, which instantly invalidates every outstanding order they have on the exchange.

The attack:

1. The attacker places orders on **both sides** of a market (YES and NO)
2. One side gets matched by the CLOB
3. Before the operator's `matchOrders` transaction lands on-chain, the attacker calls `incrementNonce()` on the CTF Exchange
4. The `matchOrders` transaction reverts because the attacker's order nonce no longer matches their on-chain nonce
5. Since `matchOrders` is **atomic** (no try/catch per order), one invalid order kills the entire batch — every other order in that transaction also fails
6. The counterparties who were matched against the attacker get nothing. Their trades evaporate.
7. Polymarket's off-chain system removes all affected orders from the book

The attacker keeps their winning side and loses nothing on their losing side. The cost is \~\$0.10 per cycle.

## Two attack variants

Our research identified two distinct methods that cause settlement failures:

### Variant 1: Nonce flip

The attacker calls `incrementNonce()` directly on the CTF Exchange contract. This is the canonical exploit described in public disclosures. The on-chain call is permanent and auditable — every instance can be traced to a specific wallet and block.

### Variant 2: Balance drain

The attacker transfers their USDC out of their wallet between order matching and on-chain settlement. When the operator's `matchOrders` transaction executes, the token transfer fails with `"ERC20: transfer amount exceeds balance"`. The effect is identical — the entire atomic batch reverts, killing all orders in the transaction.

This variant does not leave an `incrementNonce()` trace. It manifests as a standard ERC-20 balance error. Polymarket's CLOB validates balances at order placement time, so a balance error at settlement time indicates the funds were moved deliberately in the \~2 second window between matching and settlement.

Both variants produce the same outcome: ghost fills. A trade appears to match off-chain but never settles on-chain.

## Live audit: April 9, 2026

### Settlement failure rate

We ran 10 consecutive 30-second monitoring sessions collecting every `matchOrders` transaction from polynode's settlement stream and checking each receipt for revert status.

| Metric                    | Value                  |
| ------------------------- | ---------------------- |
| **Timestamp**             | 2026-04-09 \~06:15 UTC |
| **Collection**            | 10 rounds x 30 seconds |
| **Total matchOrders TXs** | 9,294                  |
| **Succeeded**             | 9,263                  |
| **Reverted**              | 31                     |
| **Overall failure rate**  | 0.334%                 |

A separate scan of 1,000 consecutive blocks (\~33 minutes, blocks 85296314–85297314) found 66,638 total `matchOrders` transactions with 100 reverts (0.150% failure rate).

The rate fluctuates between 0% and 1% depending on whether short-term crypto markets are actively resolving.

### Per-round breakdown

| Round | TXs   | Reverted | Rate   |
| ----- | ----- | -------- | ------ |
| 1     | 1,091 | 2        | 0.183% |
| 2     | 792   | 2        | 0.253% |
| 3     | 1,313 | 2        | 0.152% |
| 4     | 1,684 | 7        | 0.416% |
| 5     | 683   | 7        | 1.025% |
| 6     | 557   | 4        | 0.718% |
| 7     | 513   | 2        | 0.390% |
| 8     | 541   | 3        | 0.555% |
| 9     | 1,252 | 2        | 0.160% |
| 10    | 868   | 0        | 0.000% |

### Market category breakdown

| Category                             | Reverts | Percentage |
| ------------------------------------ | ------- | ---------- |
| Short-term crypto (BTC/ETH 5–15 min) | 29      | 93.5%      |
| Other crypto                         | 1       | 3.2%       |
| Other (politics)                     | 1       | 3.2%       |

93.5% of all settlement failures occur in short-term crypto markets. These are the 5-minute and 15-minute BTC/ETH "up or down" markets where the outcome becomes clear in the final seconds, giving the attacker a window to invalidate their losing orders before settlement.

### Revert cause analysis

We traced every reverted transaction using `trace_transaction` to decode the exact revert reason.

| Revert reason                            | Count | Variant       |
| ---------------------------------------- | ----- | ------------- |
| `ERC20: transfer amount exceeds balance` | 20    | Balance drain |
| `SafeMath: subtraction overflow`         | 11    | Balance drain |
| Total                                    | 31    |               |

All 31 reverts in this sample produced balance-related errors at the ERC-20 transfer level, consistent with both the nonce flip (which cascades into balance errors when the atomic batch unwinds) and the balance drain variant.

### Attacker identification

We scanned 1,800 consecutive blocks (\~1 hour) for all calls to `incrementNonce()` (selector `0x627cdcb9`) on the CTF Exchange contract (`0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E`).

**21 `incrementNonce()` calls found in 1 hour. Two wallets responsible for all of them.**

#### Attacker 1: `0xe5f5f9bf130f4e0947ab9e79b2ee0ab3f33885f5`

* **12 calls** in 1 hour
* Calls `incrementNonce()` **directly** on the CTF Exchange (no proxy)
* Regular \~150 block cadence (\~5 minutes), matching BTC/ETH short-term market resolution cycles
* No Polymarket profile found for this address
* **Directly caused 7 of 31 reverted settlements** in our audit (confirmed via same-block correlation)

Nonce flip blocks: 85296283, 85296433, 85296583, 85296733, 85296883, 85297033, 85297183, 85297333, 85297483, 85297633, 85297783, 85297933

At block **85297633**, this wallet called `incrementNonce()` in the same block as 3 reverted BTC Up/Down settlements, and 4 more reverted in the next 1–3 blocks as the operator's queued transactions hit the invalidated nonce.

#### Attacker 2: `0xfd29d745edaf724bae8ade4f7b3a3465eed3b905`

* **7 calls** in 1 hour
* Calls `incrementNonce()` through a **Safe proxy wallet** at `0xfc64b6660ed1...`
* Similar \~5 minute cadence
* Has a Polymarket account and profile page
* Attribution to specific reverts pending (proxy routing makes block-level correlation less precise)

Nonce flip blocks (via proxy): 85296604, 85296825, 85297016, 85297484, 85297770, 85297921, 85297985

#### Additional callers

Two wallets (`0x2df3626c...`, `0xa2b2a759...`) made single `incrementNonce()` calls during the scan period. These may be legitimate order cancellations rather than exploit activity.

### Attribution: nonce flip to settlement failure

For the 31 reverted settlements in our audit, we cross-referenced each revert block against the `incrementNonce()` call log:

| Attribution              | Count | Notes                                                                                                     |
| ------------------------ | ----- | --------------------------------------------------------------------------------------------------------- |
| **Confirmed nonce flip** | 7     | `incrementNonce()` call in same block or 1–3 blocks before revert. All from Attacker 1.                   |
| **Unattributed**         | 24    | No nearby `incrementNonce()` call. Likely balance drain variant or nonce flip from undetected proxy call. |

The 7 confirmed nonce-flip reverts all occurred in BTC Up/Down 2:10–2:15AM ET markets within blocks 85297633–85297636. The 24 unattributed reverts are concentrated in ETH Up/Down markets and revert with balance errors, consistent with the balance drain variant.

### Sample reverted transactions

```
BTC Up/Down 2:10-2:15AM — Nonce flip confirmed (Attacker 1, block 85297633):
  0x6694bb41b21ec924d29e8126659db6be34bfa057a9a32f7abbc2fc773855a031
  0x727488a9527e0b140e2907e51385d3ef3c1df07bdfa54e2de5bda867b8daeb8a
  0x5e20b0d3d6bb59dfa981c75b2e0900c1a9474d5127f4859c88b9a0cd5469ddda

ETH Up/Down 2:15-2:30AM — Balance drain (unattributed):
  0x1ba2ed39c77e767ae2b000f521e14e618187746520c4385092f083d4871d158d
  0x56aec1b48fc1c6e0d7c87e7533d66b4d00537979ab9fb0ce0c87e9a6f6b6f5e4
  0xf595c73f55dd2008f5a83855fd44b277ca4cf3468f8e19e155d1cdec584445c6
```

## How polynode detects ghost fills

polynode detects settlements from the mempool 3–5 seconds before on-chain confirmation. When a `matchOrders` transaction is detected, a pending `settlement` event is emitted. When the block confirms:

* If the receipt shows `status: 0x1` (success): a `status_update` event is emitted with `confirmed_fills` containing exact on-chain execution data
* If the receipt shows `status: 0x0` (reverted): no `status_update` is emitted. The pending settlement was a ghost fill.

To detect ghost fills, track pending settlements that never receive a `status_update`:

```javascript theme={null}
const pending = new Map();

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  if (msg.type === "settlement" && msg.data.status === "pending") {
    pending.set(msg.data.tx_hash, {
      ...msg.data,
      detected_at: Date.now(),
    });
  }

  if (msg.type === "status_update") {
    // Confirmed on-chain — not a ghost fill
    pending.delete(msg.data.tx_hash);
  }
};

// Check for ghost fills every 5 seconds
setInterval(() => {
  const now = Date.now();
  for (const [txHash, data] of pending) {
    if (now - data.detected_at > 15000) {
      console.log("Ghost fill:", txHash, data.market_title);
      pending.delete(txHash);
    }
  }
}, 5000);
```

For verified trade data, use the `confirmed_fills` field on `status_update` events. See the [Trade Tracking Guide](/guides/trade-tracking) for details on using pending settlements vs confirmed fills.

## Methodology

All data was collected using polynode's live settlement stream and Polygon RPC.

**Settlement failure rate:** Subscribe to settlements via WebSocket, collect unique `tx_hash` values for a fixed period, wait for block confirmations, batch-query receipts, check `receipt.status`.

**Revert cause analysis:** Use `trace_transaction` on reverted TXs to decode the revert reason string from the deepest failing call.

**Attacker identification:** Scan all transactions in the block range for calls to `incrementNonce()` (selector `0x627cdcb9`) targeting the CTF Exchange contract (`0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E`) or the NegRisk CTF Exchange. Both direct calls and proxy calls (where the selector appears inside `execTransaction` calldata) are detected.

**Attribution:** Cross-reference `incrementNonce()` call blocks against reverted settlement blocks. A revert is "confirmed" if a nonce flip occurred in the same block or 1–3 blocks prior.

No mock data is used. All transaction hashes, block numbers, and wallet addresses are from Polygon mainnet.
