Liquidity-Node Pricing
DobDex does not use a constant-product bonding curve. It also does not promise zero slippage on every direction of every trade — the marketing label "zero-slippage" was inaccurate. The real mechanism has two distinct paths:
- Buys (USDC → dobRWA): priced 1:1 against the oracle USD peg in the vault.
- Sells (dobRWA → USDC): priced by Liquidity Nodes (LPs) at each LP's own discount, filled FIFO. Optionally augmented by hook USDC reserves.
When the validator enables liquidation mode for an asset, sells follow a liquidation path with a protocol-mandated penalty.
This page describes the actual code paths in Dobhooks/contracts/src/DobPegHook.sol and DobLPRegistry.sol.
The hook intercepts every swap
DobPegHook is a Uniswap V4 hook that uses the V4 Custom Accounting (NoOp) pattern. The pool exists for routing, but the hook's _beforeSwap returns a BeforeSwapDelta so the AMM math is bypassed:
function _beforeSwap(
address sender,
PoolKey calldata key,
SwapParams calldata params,
bytes calldata hookData
) internal override returns (bytes4, BeforeSwapDelta, uint24) {
if (paused) revert ContractPaused();
require(params.amountSpecified < 0, "Only exact-input swaps supported");
// ...routes USDC->dobRWA at peg, or dobRWA->USDC through LPs
}
hookData carries the RWA token address and an optional minAmountOut for slippage protection (SlippageExceeded revert).
Buy path — USDC -> dobRWA
amountOut = amountIn // 1:1 against the peg
The hook mints ERC-6909 USDC claims, settles dobRWA out of the pool, and emits PegSwap(sender, false, amountIn, amountOut). There is no LP fill on the buy side and no fee on buys (the protocol fee protocolFeeBps only applies on sells; swapFeeBps only applies on sells too).
Sell path — dobRWA -> USDC
The behaviour depends on the validator registry's liquidation flag for the RWA token.
Liquidation mode disabled (normal sell)
function _handleNormalSell(address rwaToken, uint256 amountIn) internal returns (uint256 amountOut) {
uint256 fee = swapFeeBps > 0 ? (amountIn * swapFeeBps) / 10000 : 0;
uint256 idealOut = amountIn - fee;
if (fee > 0) totalLpUsdc += fee;
// ...
(uint256 lpFilled, uint256 lpDobRwa) = lpRegistry.queryAndFillAtMarket(
rwaToken, oraclePrice, idealOut
);
// ...
}
- The hook charges the LP swap fee (
swapFeeBps) up front; the fee accrues to the LP pool. - It then calls
DobLPRegistry.queryAndFillAtMarket(rwaToken, oraclePrice, idealOut). Each LP fills at their ownminPenaltyBps— i.e. the LP's chosen discount is the seller's price (the seller pays that discount). lpOnlyMode[rwaToken] == trueskips hook reserves entirely; the sell reverts withInsufficientLiquidityif LPs cannot cover the full amount.lpOnlyMode == falsefallback: hook USDC reserves cover the remainder (LP-pool USDC is protected viaavailableForSells = balanceOf(this) - totalLpUsdc).
Liquidation mode enabled
(bool enabled, uint16 penaltyBps, uint256 cap, uint256 liquidatedAmount)
= registry.getLiquidationParams(rwaToken);
if (enabled) {
if (liquidatedAmount + amountIn > cap) revert LiquidationCapExceeded();
// global cap check
amountOut = (amountIn * (10000 - penaltyBps)) / 10000;
penaltyAmount = amountIn - amountOut;
registry.recordLiquidation(rwaToken, amountIn);
(uint256 lpFilled, uint256 lpDobRwa) = lpRegistry.queryAndFill(
rwaToken, oraclePrice, penaltyBps, amountOut
);
emit LiquidationSwap(sender, rwaToken, amountIn, amountOut, penaltyAmount);
}
- Penalty is set protocol-wide (
penaltyBps), not per-LP. - Per-asset and global liquidation caps are enforced by the hook (see
LiquidationCapExceededandGlobalLiquidationCapExceeded). - The user-facing liquidation entrypoint is the hook itself — there is no separate
liquidate(...)function onDobDirectSwap. Liquidation is just a sell that the registry has flagged. - LP fills go through
queryAndFill, which only fills LPs whoseminPenaltyBps <= penaltyBps.
Oracle integration
The DobValidatorRegistry returns (priceUsd, updatedAt) via getPrice(token). Every sell path checks staleness against vault.maxOracleDelay() (the registry constant MAX_ORACLE_DELAY = 1 days is the canonical max). Stale prices revert with OracleStale.
(uint256 oraclePrice, uint48 updatedAt) = registry.getPrice(rwaToken);
if (block.timestamp - updatedAt > vault.maxOracleDelay()) revert OracleStale();
Cross-chain sync from Unichain Sepolia is delivered by the Reactive Network contracts (see DEX Hook Contracts).
Where slippage actually shows up
| Trade | Pricing | Slippage exposure |
|---|---|---|
| Buy (USDC → dobRWA) | Oracle peg, 1:1 | None on the price; staleness can revert. |
| Sell, no liquidation | Each LP's own minPenaltyBps | Yes — the seller pays the LP discount on the LP-filled portion. The hook protects against worst-case via minAmountOut. |
| Sell, liquidation enabled | Protocol penaltyBps | Yes — the seller knowingly pays penaltyBps to exit immediately. |
Calling this "zero-slippage" was misleading. The accurate framing is: buys settle at the oracle peg; sells settle at LP-quoted discounts.
What chains run which path
- Uniswap V4 chains (Arbitrum Sepolia, Base Sepolia, Unichain Sepolia): the full
DobPegHookflow. - Robinhood Chain Testnet (46630) has no Uniswap V4 — uses
DobDirectSwap, a 1:1 dUSDC↔USDC peg with a permissionless USDC LP pool. The full LP-priced exit flow is V4-only at the time of writing.