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
| Behavior | Description |
|---|---|
| AMM bypass | The BeforeSwapDelta tells the PoolManager to use hook-calculated amounts instead of the bonding curve |
| Oracle enforcement | Every swap reads from DobValidatorRegistry; stale prices block the swap |
| Vault interaction | RWA tokens are moved through DobRwaVault for proper accounting |
| LP matching | Liquidation events route through DobLPRegistry for FIFO matching |
Hook Permissions
DobPegHook registers the following hook permissions with the V4 PoolManager:
| Hook | Enabled | Purpose |
|---|---|---|
beforeInitialize | Yes | Validate pool configuration |
beforeSwap | Yes | Custom accounting (core logic) |
afterSwap | No | - |
beforeDonate | No | - |
afterDonate | No | - |
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:
- Sell RWA: User's tokens go into the vault; USDC comes out from LP liquidity
- Buy RWA: User's USDC goes to LP liquidity; tokens come out of the vault
- 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
| Chain | Contract | Reason |
|---|---|---|
| Robinhood Chain | DobDirectSwap | Uniswap 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:
| Category | Test Count | Coverage |
|---|---|---|
| Hook behavior | ~15 | beforeSwap interception, delta calculation |
| Oracle integration | ~10 | Price reads, staleness, circuit breaker |
| LP management | ~15 | Deposits, withdrawals, delays, limits |
| Liquidation matching | ~15 | FIFO order, partial fills, caps |
| Vault accounting | ~10 | Deposits, withdrawals, balance tracking |
| Edge cases | ~12 | Zero amounts, unauthorized calls, overflow |
Deployment Status
| Contract | Arbitrum Sepolia | Base Sepolia | Robinhood Chain |
|---|---|---|---|
| DobPegHook | Deployed | Deployed | N/A |
| DobRwaVault | Deployed | Deployed | Deployed |
| DobValidatorRegistry | Deployed | Deployed | Deployed |
| DobLPRegistry | Deployed | Deployed | Deployed |
| DobDirectSwap | N/A | N/A | Deployed |
| DobSwapRouter | Deployed | Deployed | N/A |
| DobTokenFactory | Deployed | Deployed | Deployed |