Change: The Order Type That Unlocks Parallel Market Making

A native order modification primitive designed for parallel execution on Monad.

Every perpetual DEX faces the same problem: when price moves, market makers need to update their quotes. The faster the chain, the more this matters, and the more it can go wrong.

The Problem Nobody Talks About

On every on-chain order book today, requoting works the same way: cancel your old order, post a new one. Two operations. This seems fine until you look at what actually happens inside the EVM.

Cancel deallocates an order ID. Post allocates a new one. Both write to the same global data structure — an index that tracks which IDs are in use. When ten market makers requote in the same block, all ten of them write to this same storage slot. On a parallel-execution chain like Monad, this creates a bottleneck: the execution engine detects the conflict, throws out its parallel work, and runs every requote one after another. Serial. Slow.

Worse, each Cancel removes that market maker's liquidity before the Post restores it at the new price. During the cascade, the book thins out. A taker arriving mid-block might see 40% of normal depth, wide spreads, and bad fills — exactly when volatility is highest and liquidity matters most.

The block-by-block sequence makes this concrete. Three market makers receive the same oracle update and race to requote. Because all three touch the global ID index, Monad serializes them — each re-executed after the previous one commits:

Figure 1 : Post/Cancel Regime — 3 MMs requoting after oracle move SERIAL
Oracle Update
Cancel Order
Post Order
Taker Order
Block N t = 0ms
Oracle Price Update$85,000 → $85,100
MM-A
ON BOOK @ $85,000
MM-B
ON BOOK @ $84,990
MM-C
ON BOOK @ $84,980
Bid Depth
3/3 MMs quoting (stale)
Block N+1, TX1 t = 400ms
MM-A (highest priority fee)
Cancel @ $85,000free ID • update Idx16 • remove lock
NOT ON BOOK
Post @ $85,090alloc ID • update Idx16 • add lock
ON BOOK @ $85,090
MM-B
STALE @ $84,990
MM-C
STALE @ $84,980
Bid Depth
2/3 MMs quoting bids
Block N+1, TX2 re-executed (conflict)
MM-B (re-exec: Idx16 conflict)
Cancel @ $84,990free ID • update Idx16 • remove lock
NOT ON BOOK
Post @ $85,080alloc ID • update Idx16 • add lock
ON BOOK @ $85,080
MM-C
STALE @ $84,980
Bid Depth
1/3 MMs quoting bids
Block N+1, TX3 re-executed (conflict)
MM-C (re-exec: Idx16 conflict)
Cancel @ $84,980free ID • update Idx16 • remove lock
NOT ON BOOK
Post @ $85,070alloc ID • update Idx16 • add lock
ON BOOK @ $85,070
Bid Depth (final)
3/3 MMs requoted
Taker arriving mid-block (between TX1 and TX3): sees only 1–2 stale bids, wide spread, degraded depth. Each Cancel removes an MM from the book before its Post restores it at the new price. All 3 MMs are serialized through Index16Bit.root and numOrders conflicts.

What Perpl Does Differently

Perpl's exchange contract has a native Change operation. Instead of cancel + post, a market maker submits a single transaction that modifies their order in place: new price, new size, same order ID.

This sounds like a small optimization. It isn't.

Because Change reuses the existing order ID, it never touches the global ID index. It never increments or decrements the order counter. The only storage it writes to is the market maker's own order slot and the price level it's moving to. Each market maker's Change is completely independent of every other market maker's Change.

On Monad, this means they all execute in parallel — and because no MM ever leaves the book, a taker sees an atomic transition from old prices to new prices with zero intermediate state:

Figure 2 : Change Regime — 3 MMs requoting after oracle move PARALLEL
Oracle Update
Change Order
Taker Order
Block N t = 0ms
Oracle Price Update$85,000 → $85,100
MM-A
ON BOOK @ $85,000
MM-B
ON BOOK @ $84,990
MM-C
ON BOOK @ $84,980
Bid Depth
3/3 MMs quoting (stale)
Block N+1 t = 400ms — all execute in parallel
MM-A
Change$85,000 → $85,090
ON BOOK @ $85,090
MM-B
Change$84,990 → $85,080
ON BOOK @ $85,080
MM-C
Change$84,980 → $85,070
ON BOOK @ $85,070
Bid Depth (instant transition)
3/3 MMs requoted simultaneously
Block N+2 t = 800ms
Taker Buy Orderhits full depth at new prices
MM-A
ON BOOK @ $85,090
MM-B
ON BOOK @ $85,080
MM-C
ON BOOK @ $85,070
Bid Depth
3/3 MMs • full depth • fresh prices
Taker arriving at any point: sees either the full old book or the full new book. Never a partially-drained state. No MM is ever absent from the book. Zero Index16Bit or numOrders contention — all MMs execute in true parallel.

The reason lies in which storage slots each operation touches. Post/Cancel conflicts on three global structures shared across all market makers. Change touches only the market maker's own private slot:

Figure 3 : State Contention Map — why Change is parallel

Post/Cancel : Shared State Conflicts

Index16Bit.root
A, B, C all write
Index16Bit.leaf[i]
A, B, C write (varies)
OrderBook.slot1 (numOrders)
A, B, C all write
idOrderMap[orderId]
per-MM (private)
orderLockMap[lockId]
per-MM (private)
priceLevelOrderIds[price]
per-price (private)
3 global conflict points → FULLY SERIAL execution

Change : No Shared State

Index16Bit.root
NOT TOUCHED
Index16Bit.leaf[i]
NOT TOUCHED
OrderBook.slot1 (numOrders)
NOT TOUCHED
idOrderMap[orderId]
per-MM (private)
account.lockedBalCNS
per-MM (private)
priceLevelOrderIds[price]
per-price (private)
0 global conflict points → FULLY PARALLEL execution
Red dots are storage slots shared across all market makers — any write creates a conflict that forces Monad to re-execute the conflicting transactions serially. Green dots are per-market-maker private slots that can be written simultaneously with no conflict.

What This Looks Like

Cancel / Post

  • 10 steps to requote 5 MMs
  • Executed serially (state conflicts)
  • Depth drops to ~60%
  • Each MM absent for 2 steps
  • ~120–160k gas per requote

Change

  • 1 step to requote 5 MMs
  • Executed in parallel (no conflicts)
  • Depth stays at 100%
  • No MM ever absent
  • ~50–70k gas per requote

With ten market makers, cancel/post drops depth below 40%. Change still holds at 100%.

The gap compounds with scale. At 10 MMs and 20 requotes per block, post/cancel forces each transaction to wait behind all the ones that conflicted before it — 20 sequential re-executions at worst. Change batches them all into a handful of parallel passes:

Figure 4 : Parallel Executor Timeline — 10 MMs, 20 requotes per block

Post/Cancel : Serial Pipeline

Each tx conflicts with previous via Index16Bit.root. Re-execution on every step.

t=0
TX1 (MM-A bid)
t=1
TX2 re-exec
t=2
TX3 re-exec
t=3
TX4 re-exec
...
TX5–TX20
Wall time
20 sequential steps
Gas used
~2.8M gas (140k each)
Parallelism
1x

Change : Parallel Pipeline

No shared slots between MMs at different prices. All execute in first pass.

t=0
TX1
TX2
TX3
TX4
t=1
TX5
TX6
TX7
TX8
t=2
TX9
TX10
TX11
TX12
t=3
TX13
TX14
TX15
TX16
t=4
TX17
TX18
TX19
TX20
Wall time
5 parallel batches
Gas used
~1.2M gas (60k each)
Parallelism
~4x
The throughput gap is not additive — it grows with market maker count. At 5 MMs the post/cancel overhead is annoying; at 50 it saturates the block.

For a taker watching from the outside, the serial drain of post/cancel looks like temporary illiquidity during every volatility event. Change eliminates the intermediate state entirely:

Figure 5 : Book Depth During Requote Storm — taker perspective

Post/Cancel : Progressive Thinning

Before
A+B+C
stale
After TX1
B+C
67% depth
After TX2
C only
33% depth
Final
A'+B'+C'
100% depth

Change : Atomic Transition

Before
A+B+C
stale
Transition (parallel, atomic)
no intermediate state
always 100% depth
After
A'+B'+C'
100% depth
Post/cancel creates a predictable illiquidity window during every oracle move. The worse the volatility, the more MMs requote simultaneously, and the deeper the liquidity trough. Change makes the trough impossible by construction.

Why This Matters

Monad's architecture (400ms blocks, parallel execution, 200M gas per block) is built for high-throughput applications. But parallel execution only helps when transactions don't fight over shared state. Cancel/post turns every requote into a fight over the same global storage slots, collapsing Monad's parallelism back to serial execution.

Change is designed for this architecture. It keeps each market maker's state isolated, letting Monad do what it's built to do: run everything at once.

The result is an order book that stays deep through volatility, gives takers better fills, and lets market makers quote tighter spreads with lower gas costs. Not because of a faster chain, but because the contract was designed to use the chain correctly.

MetricPost/CancelChange
ExecutionSerial (1 MM at a time)Parallel (all at once)
Depth during requote40–80%100%
Steps for 5 MMs101
Gas per requote (Monad)~120–160k~50–70k
State conflictsN (fully connected)0 (disconnected)
Liquidity gapYesNever

The operational impact extends beyond on-chain execution. When a market maker builds a post/cancel transaction, they face a dependency problem: the Post can't be assembled until the Cancel's outcome is known — which balance will be freed, which order ID will be allocated. Change has no such dependency. The order ID is unchanged and the locked balance stays put. The market maker builds and fires in fewer steps with a tighter gas estimate:

Figure 6 : MM React Pipeline — transaction construction dependency chain

Post/Cancel : Dependency Chain

See oracle update
Compute new price
Build Cancel tx
Need: current orderId, perpId
Build Post tx
Need: balance AFTER cancel (unknown), new orderId (unknown)
Batch both ops
gas_limit must cover worst case of 2 ops
Submit
1 tx, 2 ops, higher gas, fragile balance estimation

Change : No Dependencies

See oracle update
Compute new price
Build Change tx
orderId known (unchanged), balance known (unchanged)
Submit
1 tx, 1 op, tight gas_limit, no speculation needed
Fewer steps, no dependency chain, lower latency
The dependency chain in post/cancel also introduces a race condition: if the Cancel lands but the Post is outbid or reverts, the market maker is momentarily unhedged with no quote on the book. Change is atomic — either the order moves to the new price or nothing happens.
▶ Run the simulation
Sources: Perpl smart contracts • Monad docs (docs.monad.xyz) • Perpl docs (docs.perpl.xyz)