Skip to main content

DEX Hook Contracts

The DobDex smart contract suite is built around DobPegHook, a Uniswap V4 Custom Accounting Hook that intercepts swap transactions and replaces AMM-based pricing with oracle-based settlement. The hook is supported by a set of specialized contracts that handle vaulting, oracle feeds, liquidity management, and routing.

Architecture

                          Uniswap V4 PoolManager
|
DobPegHook (beforeSwap)
/ | \
DobValidatorRegistry DobRwaVault DobLPRegistry
|
DobSwapRouter
|
Users

For chains without Uniswap V4:

              DobDirectSwap (standalone router)
/ | \
DobValidatorRegistry DobRwaVault DobLPRegistry

DobPegHook

The core contract. Implements the Uniswap V4 IHooks interface and intercepts swaps via the beforeSwap callback.

Hook Architecture

Uniswap V4 hooks are contracts that the PoolManager calls at specific points during a swap's lifecycle. DobPegHook uses the Custom Accounting pattern, which means it returns a BeforeSwapDelta that overrides the AMM's calculated amounts.

contract DobPegHook is BaseHook {
IDobValidatorRegistry public validatorRegistry;
IDobRwaVault public vault;
IDobLPRegistry public lpRegistry;

uint256 constant MAX_ORACLE_DELAY = 1 days;

function beforeSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata hookData
) external override returns (bytes4, BeforeSwapDelta, uint24) {
// 1. Identify the RWA token in the pool
address rwaToken = identifyRwaToken(key);

// 2. Read oracle price
uint256 oraclePrice = validatorRegistry.getAssetPrice(rwaToken);

// 3. Check freshness
uint256 lastUpdate = validatorRegistry.lastUpdated(rwaToken);
require(block.timestamp - lastUpdate < MAX_ORACLE_DELAY, "Oracle stale");

// 4. Calculate output at oracle price
(int256 specified, int256 unspecified) = calculateDelta(params, oraclePrice);

// 5. Return custom delta (skips AMM curve)
BeforeSwapDelta delta = toBeforeSwapDelta(specified, unspecified);
return (this.beforeSwap.selector, delta, 0);
}
}

Key Behaviors

BehaviorDescription
AMM bypassThe BeforeSwapDelta tells the PoolManager to use hook-calculated amounts instead of the bonding curve
Oracle enforcementEvery swap reads from DobValidatorRegistry; stale prices block the swap
Vault interactionRWA tokens are moved through DobRwaVault for proper accounting
LP matchingLiquidation events route through DobLPRegistry for FIFO matching

Hook Permissions

DobPegHook registers the following hook permissions with the V4 PoolManager:

HookEnabledPurpose
beforeInitializeYesValidate pool configuration
beforeSwapYesCustom accounting (core logic)
afterSwapNo-
beforeDonateNo-
afterDonateNo-

DobRwaVault

Escrow contract for RWA tokens during swaps and liquidations.

contract DobRwaVault {
// Holds RWA tokens that back outstanding positions
mapping(address => uint256) public rwaBalances;

// Deposit RWA tokens into the vault
function deposit(address rwaToken, uint256 amount) external;

// Withdraw RWA tokens from the vault (authorized callers only)
function withdraw(address rwaToken, address to, uint256 amount) external;

// Get the vault's balance of a specific RWA token
function getBalance(address rwaToken) external view returns (uint256);
}

The vault is the single source of truth for RWA token custody. During a swap:

  1. Sell RWA: User's tokens go into the vault; USDC comes out from LP liquidity
  2. Buy RWA: User's USDC goes to LP liquidity; tokens come out of the vault
  3. Liquidation: Seller's tokens go to matched LPs; USDC goes to seller

DobValidatorRegistry

On-chain oracle for RWA asset prices. Bridges DobValidator's off-chain appraisal data to on-chain pricing.

contract DobValidatorRegistry {
struct AssetInfo {
uint256 price; // Price in USDC (with precision)
uint256 lastUpdated; // Timestamp of last price update
bool isRegistered; // Whether the asset is known
}

mapping(address => AssetInfo) public assets;

// Set price (authorized validators only)
function setAssetPrice(address rwaToken, uint256 price) external onlyValidator;

// Read price (anyone)
function getAssetPrice(address rwaToken) external view returns (uint256);

// Check when price was last updated
function lastUpdated(address rwaToken) external view returns (uint256);

// Register a new asset
function registerAsset(address rwaToken) external onlyAdmin;
}

Oracle Update Flow

DobValidator approves submission
--> Backend calls DobValidatorRegistry.setAssetPrice()
--> On-chain price updated with current timestamp
--> DobPegHook reads new price on next swap

Staleness Protection

DobPegHook checks lastUpdated() against MAX_ORACLE_DELAY (1 day). If the price is stale, swaps are blocked until a fresh update is written.

DobLPRegistry

Manages liquidity node positions and handles FIFO matching for liquidation events.

contract DobLPRegistry {
struct LPNode {
address owner;
uint256 amount; // USDC deposited
uint256 minOraclePrice; // Minimum asset price to back
uint256 minPenaltyBps; // Minimum liquidation penalty
uint256 maxExposure; // Maximum total exposure
uint256 currentExposure; // Current exposure
uint256 depositedAt; // Timestamp for MIN_BACKING_AGE
bool withdrawalRequested; // Withdrawal pending
uint256 withdrawalRequestedAt; // For WITHDRAWAL_DELAY
}

uint256 constant MIN_BACKING_AGE = 1 hours;
uint256 constant WITHDRAWAL_DELAY = 24 hours;
uint256 constant RESERVE_BPS = 3300; // 33%
uint256 constant MAX_BACKERS = 50;

// Deposit liquidity
function depositLiquidity(
uint256 amount,
uint256 minOraclePrice,
uint256 minPenaltyBps,
uint256 maxExposure
) external returns (uint256 nodeId);

// Request withdrawal (starts 24h timer)
function requestWithdrawal(uint256 nodeId) external;

// Execute withdrawal (after delay)
function executeWithdrawal(uint256 nodeId) external;

// Match LPs for a liquidation (called by DobPegHook/DobDirectSwap)
function matchLPs(
address rwaToken,
uint256 amount,
uint256 oraclePrice,
uint256 penaltyBps
) external returns (MatchResult[] memory);
}

See Liquidity Nodes for detailed LP mechanics.

DobDirectSwap

Lightweight swap router for chains that do not have Uniswap V4 deployed. Provides the same oracle-based pricing without the hook infrastructure.

contract DobDirectSwap {
IDobValidatorRegistry public validatorRegistry;
IDobRwaVault public vault;
IDobLPRegistry public lpRegistry;

uint256 constant MAX_ORACLE_DELAY = 1 days;

// Swap RWA tokens for USDC or vice versa
function swap(
address rwaToken,
address paymentToken,
uint256 amountIn,
bool isRwaToPayment
) external returns (uint256 amountOut);

// Liquidate at penalty
function liquidate(
address rwaToken,
uint256 amount
) external returns (uint256 usdcReceived);
}

DobDirectSwap reads from the same DobValidatorRegistry and DobLPRegistry as DobPegHook. The only difference is the execution path -- direct calls instead of V4 hook callbacks.

Deployment

ChainContractReason
Robinhood ChainDobDirectSwapUniswap V4 not deployed on this chain

DobSwapRouter

User-facing router contract that provides a clean interface for interacting with Uniswap V4 pools that use DobPegHook.

contract DobSwapRouter {
IPoolManager public poolManager;

// Swap through V4 pool with DobPegHook
function swapExactInput(
PoolKey calldata key,
uint256 amountIn,
uint256 minAmountOut,
bytes calldata hookData
) external returns (uint256 amountOut);

// Swap for exact output through V4 pool
function swapExactOutput(
PoolKey calldata key,
uint256 amountOut,
uint256 maxAmountIn,
bytes calldata hookData
) external returns (uint256 amountIn);
}

The router handles token approvals, swap parameter encoding, and result decoding so that users and frontend integrations do not need to interact with the V4 PoolManager directly.

DobTokenFactory

Factory contract for creating wrapped RWA tokens (dRWA tokens) that are compatible with the DobDex ecosystem.

contract DobTokenFactory {
// Create a new dRWA token
function createToken(
string calldata name,
string calldata symbol,
address underlyingAsset
) external returns (address tokenAddress);
}

dRWA tokens are standard ERC-20 tokens that represent claims on real-world assets. They are registered in the DobValidatorRegistry for oracle pricing and can be traded through DobPegHook or DobDirectSwap.

Contract Interactions

Normal Swap Flow

1. User calls DobSwapRouter.swapExactInput()
2. Router calls PoolManager.swap()
3. PoolManager calls DobPegHook.beforeSwap()
4. DobPegHook reads DobValidatorRegistry.getAssetPrice()
5. DobPegHook calculates output at oracle price
6. DobPegHook returns BeforeSwapDelta
7. PoolManager uses delta instead of AMM math
8. DobRwaVault handles token transfers
9. User receives exact oracle-priced output

Liquidation Flow

1. Seller calls DobDirectSwap.liquidate() or triggers via hook
2. System reads oracle price and penalty params
3. DobLPRegistry.matchLPs() finds eligible LPs (FIFO)
4. Seller receives USDC minus penalty
5. Matched LPs receive RWA tokens at discount
6. DobRwaVault updates balances

Test Coverage

The full contract suite has 77 tests passing, covering:

CategoryTest CountCoverage
Hook behavior~15beforeSwap interception, delta calculation
Oracle integration~10Price reads, staleness, circuit breaker
LP management~15Deposits, withdrawals, delays, limits
Liquidation matching~15FIFO order, partial fills, caps
Vault accounting~10Deposits, withdrawals, balance tracking
Edge cases~12Zero amounts, unauthorized calls, overflow

Deployment Status

ContractArbitrum SepoliaBase SepoliaRobinhood Chain
DobPegHookDeployedDeployedN/A
DobRwaVaultDeployedDeployedDeployed
DobValidatorRegistryDeployedDeployedDeployed
DobLPRegistryDeployedDeployedDeployed
DobDirectSwapN/AN/ADeployed
DobSwapRouterDeployedDeployedN/A
DobTokenFactoryDeployedDeployedDeployed