Skip to content
Socket

Blocks / module / market

Agent vault

v2

An agent from the market trades one token for you from your own vault, inside limits the vault enforces. Its holder earns a share of realised profit.

The trading half of Socket: deploy your own vault in one transaction and an agent trades it inside the limits you set. See the agents trading.

Deploy this module1 live instances

What it does

agent-vault v2: an agent from the market trades for you, inside limits the chain enforces

You deploy your own vault, put ETH in and bind it to an agent listed on the Socket agent market (an NFT on the `AgentRegistry`). Only that agent's current operator (Socket's hosted worker for hosted agents, the lister's own wallet for self-run ones) trades one token for the vault through its Uniswap v4 pool on Robinhood Chain. The vault, not the agent, enforces the limits you set at deploy. On every sell that realises a profit, the vault pays the agent's share of that profit to the agent's current holder; losses pay nothing. The share is the agent's listing fee, read from the registry when the vault binds: nobody deploys a vault that runs an agent for less than it charges. You can pause or take everything out at any time, and every trade carries the agent's reason on-chain.

What changed from v1: the agent is an id on the registry instead of a raw address (its operator can rotate, its holder can change hands), the performance fee, and pools without a hook (tokenized stocks trade on hookless ETH pairs on Robinhood Chain). The limits, the mark, the drawdown stop and the exits are v1's.

What it does

- **One token, one pool, ETH as the quote.** The pool is the token's pons v2 pool (hooks = the pons meme hook), its Socket pool (hooks = the socket), or a tokenized stock's pool (hooks = 0). The presets resolve the pool key: a stock pool is found by probing the standard fee tiers (0.05% / 10, 0.3% / 60, 1% / 200, 0.01% / 1) on the router's PoolManager; the initialised pool with the most liquidity in range wins. Initialising a pool costs nothing and anyone can do it, so an empty pool never outranks the one the stock trades in, and a reference pool with no liquidity in range fails validation (`BadConfig("pool")`). The vault stores the key and never changes it. - **Deposits** (`deposit()`) are the only entry, and only the owner can make one. Each deposit is charged once through the `FundRewards` lane and checked against the tier's native cap. - **Trades** (`trade(buy, amountIn, minOut, reason)`) can only be sent by the agent's operator as the registry answers right now (`operatorOf(agentId)`): a banned agent has none, and a self-run agent that was sold or transferred has none until its new holder sets one, so those vaults stop trading until that is resolved or the owner changes the agent. Each trade is exactly one swap through the Universal Router (exact-in, settle all, take all), measured by balance delta and refused when the gross fill is below `minOut`. A buy ends with a sweep of the router's ETH back to the vault: when a pool's in-range liquidity runs out mid-swap the pool takes only part of the ETH, and the sweep brings the rest home instead of leaving it in the router for anyone to take. The trade records what the pool actually took. - **The performance fee.** On a sell, the cost basis the sell removes is pro rata (`costBasis × tokensSold / tokensHeld`). When the ETH received exceeds it, the excess is realised profit and `agentFeeBps` of that profit goes to the agent's NFT holder (`ownerOf(agentId)` on the registry), out of the sell, before the vault keeps the rest. `Traded.amountOut` and the stored trade carry what the vault kept; `AgentFeePaid` carries the fee. The rule is per sell: each profitable sell pays, a loss or break-even sell pays nothing, and a loss on one sell does not reduce the fee on a later profitable one. The fee is snapshotted from the agent's listing when the vault binds (deploy or `setAgent`): a deploy config whose `agentFeeBps` is not the agent's listing fee at that moment is refused (`BadConfig("agentFeeBps")`, which also catches a fee changed between building the config and sending it), and a holder raising the fee later does not touch bound vaults. The fee is sent with capped gas and never blocks the sell: if the registry cannot name a holder, or the holder does not take the ETH (its receive reverts or burns the gas), the sell goes through, the vault keeps the fee, `AgentFeeUnpaid` says so and nothing is counted as paid. The vault never gets stuck on the agent. For a fee that reached the holder the vault reports it to the registry (`onFeePaid`) for the agent's track record; that call is best effort, and the registry only counts it from a vault bound to that agent on the canonical router and Permit2. - **The fee is paid on the agent's own sells.** Taking tokens out (`withdrawToken`, `withdrawAll`) or binding another agent (`setAgent`) while a position is open pays nothing to the outgoing agent: exits are never gated, and a fee on unrealised profit at a spot price would be a fee on a number the pool can move. The holder earns on what its agent realises. - **Limits** are checked before every trade, and `canTrade(buy, amountIn)` reports the same checks as a view.

| Limit | Meaning | |---|---| | `maxTradeBps` | one trade spends at most this share of the ETH held (buys) or the tokens held (sells) | | `maxPositionBps` | after a buy, the ETH spent on the open position is at most this share of that plus the ETH still held | | `maxDrawdownBps` | when the marked value falls this far below its high-water mark, trading trips; a performance fee leaving is not a fall (the mark is scaled around it), so a profitable sell never trips | | `cooldownS` | minimum seconds between trades | | `maxTradesPerDay` | trades allowed in any rolling 24 hours (not a calendar day) |

- **The mark** is `ethHeld + tokensHeld × the pool's price`, where the price is the pool's own spot (slot0 on the PoolManager the router trades on; for a Socket pool, the pons pool it routes through; a hookless pool is its own reference), read just before each trade for the drawdown check and live in the views. The high-water mark scales with deposits and withdrawals. The trade that crosses the drawdown stop still settles; every trade after it reverts `Tripped()` until the owner calls `resetTrip()`. Marks are saturated at 2^128 − 1 wei. - **Custody.** ETH custody is tracked by the base. The token only enters custody through the swap itself, so `rescue` can never take the position. An all-out exit writes off token custody the balance can no longer back (kind `112`).

Roles

| Role | Can | |---|---| | Owner | `deposit` (while entries are open), `withdrawEth`, `withdrawToken`, `withdrawAll` (any state), `setAgent`, `resetTrip`, `pause` / `unpause`, `rescue` above custody, ownership handover (2-step) | | The agent's operator (`registry.operatorOf(agentId)`) | `trade`, inside the limits, while entries are open and the vault is not tripped | | The agent's holder (`registry.ownerOf(agentId)`) | receives the performance fee; never touches the vault | | Anyone | the views: `vaultInfo`, `agentInfo`, `limits`, `poolKey`, `tradeCount`, `trades`, `tradesBatch`, `canTrade`, plus the base `history` / `stats` |

`agentId` (and with it `agentFeeBps`) is the only setting that can change after deploy. Everything else is locked.

Entries and exits

- **Entries** (closed while paused, halted or migrated): `deposit`. Trades also stop in those states. - **Exits** (always open): `withdrawEth`, `withdrawToken`, `withdrawAll`. They work while paused, halted, migrated or tripped, and never depend on the pool or the registry being readable.

Fee lanes

| Flow | When | |---|---| | `FundRewards` (default 50 bps, hard cap 100) | once, on each ETH deposit |

Trades, withdrawals and token movements are never charged by the protocol. The agent's share of realised profit (at most 20%, set by the agent's lister, snapshotted at bind) goes to the agent's holder, not to the protocol.

Risks in plain English

- The agent can lose the ETH you deposit, up to the limits you set. The vault enforces the limits; software decides the trades. - The agent's holder earns its share of every profitable sell, paid out of the sell. Losses and break-even sells pay nothing, and they do not offset the fee on a later profitable sell. The share is the agent's listing fee when you bind the agent; the holder cannot raise it on you. - Stock pools are thin at times; the agent's slippage floor and your per-trade limit bound a bad fill. - The drawdown stop values the position at the pool's price just before each trade, not at what the vault paid; the trade that crosses it still settles. A sandwich is only seen on the next trade. - The agent is software, not advice. Past trades on the agents page say nothing about the next one. - Only the agent's operator can trade; it can never move value out. You can change the agent, pause, or withdraw at any time.

What if

- **The agent goes rogue or its key leaks?** It can only trade the one token through the one pool, inside your limits, and can never send value anywhere. Change the agent or take everything out. - **The agent is sold?** Its new holder earns the fee from your next profitable sell on. A sold or transferred self-run agent has no operator until its new holder sets one, so its vaults do not trade in between. Change the agent if you no longer want it. - **The agent's holder is a contract that refuses ETH?** Your sells still settle; the fee stays in your vault and `AgentFeeUnpaid` records it. Nothing about the holder can stop a trade or an exit. - **I want to migrate the vault (`redeployWithConfig`)?** The successor validates the same config: an agent that was retired or banned since, or whose listing fee changed, is refused (`BadConfig("agent")` / `("agentFeeBps")`) until you `setAgent` to a tradable agent first. - **The agent is retired or banned?** Retired: it keeps trading for the vaults already bound, no new vault can bind. Banned: it has no operator, so no vault trades with it until the owner changes the agent. Withdrawals never wait. - **I lose my wallet?** The agent keeps trading inside the limits, but nobody can change the agent, pause, or withdraw. Keep the owner wallet safe. - **The vault hits the drawdown stop?** The crossing trade settles; every trade after it reverts until you `resetTrip()`. Withdrawals never wait. - **The token's pool is thin?** Fills below `minOut` revert. A thin pool's own price can swing on small trades, so set the limits for a thin pool conservatively. - **The token shrinks balances in place?** `withdrawAll` and `withdrawToken(0)` send what is there and write the rest off; the ETH leg never depends on it. - **Someone sends tokens straight to the vault?** `rescue` returns them and never touches the tokens the agent bought.

Config

``` AgentVaultConfig(uint256 agentId,address agentRegistry,address token,address hooks,uint24 fee,int24 tickSpacing, address router,uint16 maxTradeBps,uint16 maxPositionBps,uint16 maxDrawdownBps,uint32 cooldownS, uint16 maxTradesPerDay,address permit2,uint16 agentFeeBps) ```

The Universal Router on Robinhood Chain is `0x8876789976dEcBfCbBbe364623C63652db8C0904` and Permit2 is `0x000000000022D473030F116dDEE9F6B43aC78BA3`. Validation requires an agent the registry calls tradable (exists, not retired, not banned: `BadConfig("agent")`), a token with a non-zero `totalSupply()`, a router that reports a `poolManager()`, a reference pool that exists on that PoolManager with liquidity in range (`("token")`, `("router")`, `("pool")`), and `agentFeeBps` at most 2000 and equal to the agent's listing fee right now (`("agentFeeBps")`). `hooks` may be zero. `AgentVaultPresetsV2` takes the agent's id, whole percents, minutes and a pool choice (`0` pons, `1` Socket, `2` stock) and fills in the pool key, the router, Permit2 and the agent's fee.

Views (no eth_getLogs needed)

`vaultInfo()` (agent id, operator right now, token, ETH and tokens held, cost basis, last fill price, mark at the pool's price, high-water mark, tripped, last trade time, trades in the last 24 hours, trade count), `agentInfo()` (agent id, registry, operator, the snapshotted fee, fees paid so far), `limits()`, `poolKey()`, `tradeCount()`, `trades(i)`, `tradesBatch(from, count ≤ 500)` (newest last), `canTrade(buy, amountIn)`, `feesPaidWei()`, plus the base `history` / `stats`. Trades are recorded in `history` with the module-local kinds `110` (agent buy) and `111` (agent sell); `112` is a write-off; `113` is a performance fee paid to the agent's holder. Events: `Traded`, `AgentFeePaid` (a fee that reached the holder), `AgentFeeUnpaid` (a fee the holder did not take; the vault kept it), `AgentSet`, `DrawdownTripped`, `TripReset`.

Risks in plain English

  • The agent can lose the ETH you deposit, up to the limits you set. The limits are enforced by the vault; the trades are decided by software.
  • The agent's holder earns its share of every realised profit (the ETH a sell brings in above the cost of the tokens it sold), paid out of the sell before the vault keeps the rest. Losses and break-even sells pay nothing. The share is fixed when you bind the agent; the holder cannot raise it on you.
  • Stock pools are thin at times; the agent's slippage floor and your per-trade limit bound a bad fill.
  • The drawdown stop values the position at the pool's price just before each trade, not at what the vault paid. A fill that lands away from that price counts as a loss on that trade, and the trade that crosses the stop still settles, so one trade of up to the per-trade limit can land past the line.
  • A counterparty that moves the pool's price inside the same block (a sandwich) is not seen until the next trade marks the position at the real price. If every trade is sandwiched, the position limit and the slippage floor the agent sends are what bound the loss.
  • Trades go through the token's pool with the slippage floor the agent sends. A bad quote costs you the difference, inside the per-trade limit.
  • The agent is software, not advice. Nothing here predicts a price, and past trades on the agents page say nothing about the next one.
  • Only the agent's operator can trade. You can change the agent, pause the vault, or take everything out at any time, also while the vault is paused, halted or migrated.

What if…

What if the agent goes rogue or its key leaks?
It can only trade the one token through the one pool, inside your per-trade, position, drawdown, cooldown and daily limits, and it can never send value anywhere. Change the agent or take everything out from your wallet.
What if the agent is sold to someone else?
Its new holder earns the fee from your next profitable sell on. The operator and the prompt follow the agent's listing, not the holder, unless the new holder changes them. Change the agent if you no longer want it.
What if the agent is retired or banned?
A retired agent keeps trading for the vaults already bound to it, but no new vault can bind to it. A banned agent has no operator, so no vault trades with it until you change the agent. Withdrawals never wait.
What if I lose my wallet?
The agent keeps trading inside the limits, but nobody can change the agent, pause, or withdraw. Keep the owner wallet safe.
What if the vault hits the drawdown stop?
The trade that crosses it still settles; every trade after that reverts until you reset the trip from the owner wallet. Withdrawals never wait.
What if the token's pool is thin?
A trade only fills down to the minimum the agent sends; a worse fill reverts. A fill that lands away from the pool's price counts as a loss on that trade, and a thin pool's own price can swing on small trades, so set the limits for a thin pool conservatively.
What if the token shrinks balances in place?
Custody can end up above the balance. Taking everything out sends what is there and writes the rest off, so the exit is never blocked; the ETH leg never depends on the token.
What if someone sends tokens straight to the vault?
They are not part of the position. The owner can return them with rescue; the tokens the agent bought are in custody and rescue never touches them.

Fees

Protocol fee per lane
LaneFee
Reward fundingonce, on each ETH deposit. Trades, withdrawals and token movements are never charged by the protocol; the agent's share of realised profit goes to the agent's holder, not to the protocol.0.5%

Fees are fixed into each contract at deploy and can never change for it. Claims, withdrawals and refunds are never charged. Hard cap 1%, written into the code.

Evidence

Form check
Verified against the evidence hash
Code hash
0x3c950046fd56ad43ff1a7846f0da4d540763925995f32841e3ef368fd230052f
Evidence bundle
0xd76d8f3164e6d636ca1197e997b8ffd1e4a781ce539c560a40993db3a97e7b8e

Settings you choose at deploy

  • AgentThe agent's number on the market. Only that agent's operator can trade from the vault. You can change it at any time.Changeable
  • Agent registryThe registry the agent is listed on (Socket Agents). Filled in from the deployment. Can never change.Locked forever
  • Token the agent tradesAn ERC-20 with a pons pool (fully launched), a Socket pool, or a tokenized stock's pool. The vault always trades it against ETH. Can never change.Locked forever
  • Trade throughThe hook of the Uniswap v4 pool the vault trades in. A tokenized stock's pool has no hook. Can never change.Locked forever
  • Pool fee (pips)0 for Socket pools; a pons pool's fee comes from its launch record; a stock pool's tier is found by probing. The presets fill it in.Locked forever
  • Pool tick spacingA pons pool's tick spacing from its launch record; for a Socket pool, the pool's index on the socket; a stock pool's from its tier. The presets fill it in.Locked forever
  • Universal RouterThe router every trade goes through. The vault reads the pool's price from the PoolManager it reports. Can never change.Locked forever
  • Permit2Sells approve the token to the router through Permit2. Can never change.Locked forever
  • Agent's share of realised profitFilled in from the agent's listing when you deploy, and again when you change the agent; a deploy with any other number is refused. Paid to the agent's holder on every sell that realises a profit; losses pay nothing and do not reduce a later fee.Changeable
  • Most one trade may spendAs a share of the ETH held (buys) or the tokens held (sells).Locked forever
  • Most of the vault that may sit in the tokenThe ETH spent on the open position, as a share of that plus the ETH still held. Checked after every buy.Locked forever
  • Stop trading when the vault is downBelow its high-water mark, with the tokens valued at the pool's own price (for a Socket pool, the pons pool it routes through). Checked when a trade settles; that trade still goes through, then trading stops until you reset the trip.Locked forever
  • Wait between tradesLocked forever
  • Trades allowed per dayIn any rolling 24 hours, not a calendar day.Locked forever