ETH$3,420
Trading/terminal

The trading terminal

One entry point, one approval, every venue: how buy() and sell() execute, price, and account for a trade.

Every trade on hood.dev goes through one contract, V4TradeManager. It takes the platform fee, hands the swap to a venue adapter, checks what actually came back, and pays you. Venues live behind an append-only adapter registry, so the manager address — and the approval you gave it — never changes when a new DEX is added.

#buy and sell

solidityV4TradeManager
function buy(
    address token,
    Route calldata route,
    uint256 minAmountOut,
    uint256 deadline,
    address ref,             // referrer, or address(0)
    bytes calldata refData   // unused today; forward-compat
) external payable returns (uint256 tokenOut);

function sell(
    address token,
    uint256 amountIn,
    Route calldata route,
    uint256 minEthOut,       // checked against what YOU receive, post-fee
    uint256 deadline,
    address ref,
    bytes calldata refData
) external returns (uint256 ethToUser);

Buys are ETH-in: send msg.value, the fee comes off the top, the remainder is routed, and tokens are delivered directly to you — they are never staged in the manager. Sells are the mirror image: tokens are pulled from you, swapped, and the ETH minus the fee is sent to your address in the same transaction.

#Approvals

You approve one address, once
Adapters never see your allowance and never hold funds between transactions. The manager pulls your tokens, hands them to the adapter for the duration of the call, and the adapter ends the transaction with a zero balance. That is exactly why new venues can ship without anyone re-approving anything.

Approve V4TradeManager for the token you want to sell. Buys need no approval at all — they are payable.

If you are trading through a hood.dev wallet, the approval and the trade are both signed inside the enclave without prompting you, by a key whose policy allowlist includes this manager and excludes arbitrary destinations — so one-click trading costs you no custody. That mechanism is on Wallets & keys.

#How the manager accounts for a trade

Adapter return values are informational. The manager measures its own balances around every adapter call and uses the deltas, which is what makes the accounting hold across venues that behave differently:

  • Sell proceeds are what actually arrived. The ETH credited to you is the manager’s balance delta, not a number the adapter claimed.
  • Unspent buy ETH is refunded, whoever produced it. A bonding-curve venue that crosses its threshold mid-fill hands ETH back; the manager forwards it to you and emits EthRefunded. You never have to ask for it.
  • Fee-on-transfer tokens cannot desync it. Sells measure the token balance before and after the pull, and the adapter trades whatever survived the hop.
  • The manager ends every trade with zero balances. It is not a custodian and holds nothing between transactions.

#Slippage and deadlines

ParameterChecked how
minAmountOut (buy)The manager re-checks the tokens delivered against your floor and reverts SlippageExceeded — independently of any venue-level check.
minEthOut (sell)Checked against the post-fee ETH you receive, not the pre-fee amount. What you set is what you get, or the trade reverts.
deadlineUnix seconds. Past it, the call reverts Expired before anything moves.

#Quoting

There is no separate quoter contract, and deliberately so — a quoter is a second implementation that can drift from the executor. Instead, simulate the real trade with eth_call and a zero floor. A buy simulation returns the tokens that would be delivered; a sell simulation returns the post-fee ETH that would be sent. What you simulate is the code that will run.

tsquote a buy with viem
const tokenOut = await publicClient.simulateContract({
  address: TRADE_MANAGER,
  abi: tradeManagerAbi,
  functionName: 'buy',
  args: [token, route, 0n, deadline, zeroAddress, '0x'],
  value: ethIn,
  account: trader,
}).then((r) => r.result)
Gas budgets vary a lot by venue
Plain V2/V3 swaps are ordinary. Bonding-curve venues are heavy — budget at least 5M gas for a Virtuals trade — and hooked multi-hop V4 routes run 1.3M+ at two hops. Simulate with a generous limit or your quote will fail for the wrong reason.

#Events

soliditywhat an indexer listens for
event TradeExecuted(
    address indexed user,
    address indexed token,
    bool    indexed isBuy,
    uint8   dex,          // the dexId that executed it
    uint256 ethAmount,    // gross ETH side: buys = msg.value, sells = ETH out pre-fee
    uint256 tokenAmount,
    uint256 fee,          // TOTAL platform fee (protocol + referral)
    address ref,          // address(0) if none
    uint256 refFee        // the referral portion of the fee above
);

event EthRefunded(address indexed user, uint256 amount);

These two events are all trade history, per-wallet PnL and the leaderboard are built from (see Data & indexing). Because ethAmount is the gross side and fee is broken out, net cost is derivable without reading receipts.

#Common revert reasons

ErrorMeans
ExpiredThe deadline passed before inclusion.
SlippageExceededOutput fell below your floor — retry with a fresh quote or a wider tolerance.
NoSwapOutputThe sell produced zero ETH. Usually an unroutable or dead pool.
UnknownDex(dexId)No adapter is registered under that dexId. Your route is malformed or stale.
DexPaused(dexId)That venue is temporarily disabled. Route elsewhere.
PartialFill()A V4 hop could not consume its full input — the size exceeds usable liquidity.
BrokenChain()A multi-hop route does not connect end to end. Rebuild the path.

Every contract in the system uses custom errors, so decode revert data against the ABI rather than string-matching. Amounts everywhere are raw base units.

#Fees and hooks

The platform fee is 1% of the ETH side with a 2% hard cap — full breakdown on Fees. It is pushed as native ETH to the manager’s fee receiver with the trader’s address attached, which is what makes cash back possible without touching the trading path: the live receiver credits 25% of each fee back to the wallet that paid it and banks the rest. Any referral share is pushed to the ReferralManager in the same call, before the receiver sees the remainder.

The receiver is swappable by the manager’s owner (setFeeReceiver) and is the one moving part in the fee path — the manager binding inside each receiver is immutable, and no receiver has ever been able to reach a trader’s funds, only the fee it was handed.

The manager also has an optional tradeTracker hook for future reward accounting. It is unset today, and when set its reverts are swallowed — a broken tracker can never break trading.

#Deployments

V4TradeManager
The trading entry point. This is the only contract you ever approve.
CashBackFeeReceiver
The manager's live fee destination. Credits 25% of every trade fee back to the trader who paid it; the rest is protocol revenue.
ReferralManager
Accrues referral ETH; referrers claim(to) whenever they like.
chain 4663·on-chain values read 2026-07-29·Blockscout