Skip to content

[sim] Agent-created markets: MarketRegistry, on-chain deployment discovery, registry-authoritative scoring #40

Description

@adachi-440

Motivation

Agents can only trade venues the environment deploys. The market set is fixed at block 0, so "where should liquidity live" is never a decision an agent makes, and market design is not a skill the competition can reward.

The blocker is not deployment. example/agents/runtime/deploy.ts already lets a participant deploy with their own key, and the Uniswap V3 / Balancer / Curve factories are all deployed (deployer/deployments/deployments.json). The two things missing are that nothing tells the other agents a new market exists, and nothing values a position held in one — value parked in an agent-created pool is invisible to core/src/realtime/reconstruct.ts and silently evaporates from the score.

Proposal

Distribute the set of agent-deployed contracts on-chain the same way the fair price is distributed: the coordinator observes off-chain, writes to a contract every block, and every agent reads the same list.

Design decisions (fixed)

  • MarketRegistry is the second instance of the PriceFeed pattern, not a new concept. Owner-gated writes from the environment's admin wallet, an event per entry, count() / all() / isRegistered() reads, ~50 lines in contracts/ (PriceFeed.sol is 68). It inherits that pattern's one-block distribution lag, which is already documented and accepted ("the write tx lands in the next block, so the information is one block late — it applies equally to everyone"). The creator knows about its own market one block before anyone else; that head start is the incentive to build markets and is intended.
  • Detection is coordinator-side and log-first. For the known factories (Uniswap V3 PoolCreated, Balancer PoolRegistered, Curve pool deployment) subscribe to factory logs — that yields address, tokens and fee directly, with no classification heuristics. The coordinator already sweeps the caught-up block range in a single getLogs for the GMX keeper (core/src/realtime/coordinator.ts:898); add filters there. For arbitrary contracts, scan the block's transactions for to === null and derive the address with viem's getContractAddress({ from, nonce }) — no receipt fetch, which also sidesteps the broken eth_getBlockReceipts on the Arbitrum fork. That scan misses CREATE/CREATE2 from inside a contract; cover it with a fallback that watches transfers of known tokens and flags unseen recipients whose eth_getCode is non-empty (a pool must receive real tokens to be tradable, so funding — not deployment — is the detectable moment; the same definition ADR 0014 already uses for vuln pools).
  • Everything is recorded; only some of it is verified. Each entry carries a kind (uniswapV3Pool / balancerWeightedPool / curvePlainPool / curveTwocryptoPool / erc20 / unknown), the creator, the tokens where known, the runtime codehash, and a verified flag. Verified means it came out of a known factory, so its implementation is environment-owned canonical code. Everything else is listed as unknown / unverified — discoverable, but carrying no safety claim.
  • The registry is a discovery mechanism, not a verification mechanism. It guarantees only that something exists. Dry-running the interaction stays the primary defence (ADR 0014 §4), and the recorded codehash lets an agent detect a swap-out between observation and execution. Losing money to an unverified contract is a legitimate loss and is not made whole.
  • Expose it through observationFor, not only on-chain. Rule agents can call the registry or read logs directly, but prompt-driven agents only ever see the observation, and the default Quick Start roster ships trading agents in prompt mode (ADR 0015 §2). A discovery feature only agent.ts can use would exclude the default configuration.
  • The registry is the scoring authority. reconstruct.ts values LP positions in registered pools and nothing else. This closes the evaporation hole by construction: what cannot be enumerated cannot be scored, and what is scored is exactly what the registry lists.
  • A token with no fair price is marked at realizable exit value, and at zero for its issuer. Agents may deploy and list their own ERC-20. Marking such a token at its pool spot price would let an agent mint a token, trade against itself in a thin pool it also owns, and manufacture unbounded score at no cost — the quote asset round-trips straight back to them. Marking instead at what selling the whole balance into the pool would actually return (a full-size quoteExactInput / get_dy, not balance × spot) bounds the credited value by the real assets sitting in the pool, so fabricated value has a hard ceiling. QuoterV2, BalancerQueries and Curve's get_dy are all already deployed and exposed in constants.local.ts.
    • The issuer's own holding is excluded outright — the registry records the creator — because the issuer also holds the LP position backing that pool, and crediting both would double-count the same reserves.
    • Every other holder marks at realizable value. That is also what makes a rug victim's accounting correct: valuing the token at zero for all holders would confiscate the portion they could still recover from the pool, overstating the loss. Rug pulls stay a viable strategy while remaining a zero-sum transfer rather than value creation.
    • A TWAP does not substitute for this. An asset with no external fair value has no arbitrageur to push a manipulated price back, so holding an inflated mark across the averaging window is cheap. Time-averaging only helps when an outside force corrects the price.
  • Agent-created markets are excluded from the no-arb guard. core/src/realtime/noArb.ts fails the run at startup and warns on persistent cross-venue spreads. A thin agent-made pool is supposed to sit away from fair; only environment-deployed venues belong in that check.
  • The environment does not make markets in them. Flow bots and oracle updates stay on environment venues. Price discovery inside an agent-created market is the agents' problem.
  • Nothing here is chain-specific. All of it is ordinary deploys and ordinary txs, so the [sim] Run the environment on a real L2 (cheatcode-free mode) #33 / [infra] OP Stack chain for real-L2 Eris runs: approach selection & Eris-side interface #35 migration to our own chain is unaffected. The registry could later be promoted to a genesis predeploy, but it does not need to be.

Scope

  • contractsMarketRegistry.sol.
  • core — factory-log filters and CREATE scanning in the coordinator's per-block sweep; a batched register write. Cap registrations per block and carry the overflow into the next block: deployment is already gas-costly for the agent under ADR 0011, but the registry write is paid by the environment, so a deploy-spam agent must not be able to inflate that cost without bound.
  • sdk (constants) — expose the factories. The LocalDeployment type in sdk/src/constants.local.ts currently carries only poolWethUsdc500 / swapRouter / nonfungiblePositionManager / quoterV2 for Uniswap, vault / queries / pool / poolId for Balancer, and pool + indices for Curve, while deployments.json already holds uniswapV3.factory, balancerV2.weightedPoolFactory, curve.factory and curve.twocryptoFactory. Map them in gen:local-constants.
  • sdk (actions) — pool creation for all three factories, plus ERC-20 deployment for agents listing their own token.
  • sdk (observation) — registry entries, with enough per-entry detail to decide whether to interact.
  • scoringreconstruct.ts values LP positions in registered pools. This depends on [sim] Scoring black hole: positions outside the registered market set are valued at exactly zero #41: Balancer BPT and Curve LP-token valuation do not exist, and Uniswap V3 positions outside the registered market set are currently valued at exactly zero. With [sim] Scoring black hole: positions outside the registered market set are valued at exactly zero #41 landed, the work remaining here is sourcing the pool set from the registry instead of from MARKET_LEGS.

Reference agents (required)

A capability nobody uses is not exercised, and this one needs both sides — a market that gets created and a market that gets found. Ship example/agents/market-launcher/ carrying both agent.ts and prompt.md (every bundled agent provides both ways of running, ADR 0015 §2), plus a roster entry in config/example.yaml.

prompt.md cannot be a stub. The committed config/example.yaml roster ships trading agents in prompt mode, so the Quick Start default drives this through the LLM path. The prompt must describe the registry observation fields and the decisions they support well enough that a model can act on them without reading agent.ts.

  • market-launcher (new) — create a pool through a factory, seed it, and manage it: pick a pair and fee tier the environment does not already provide, size the initial liquidity, and decide when to pull. Exercises the creation side and, in phase 3, the rug side.
  • discovery-arb / discovery-arb-verify (extend the existing ADR 0014 agents) — source candidates from the registry rather than only from the vuln factory, so the discovery and verification sides are exercised against agent-created markets too. The verified flag is exactly the signal discovery-arb-verify should gate on.

Phases

  1. MarketRegistry + Uniswap V3 factory detection + observation exposure + createPool action + registry-authoritative scoring.
  2. Balancer and Curve factories (their LP valuation comes from [sim] Scoring black hole: positions outside the registered market set are valued at exactly zero #41, not from here).
  3. Arbitrary CREATE recording (kind=unknown, verified=false), agent-deployed ERC-20 listing, and rug pulls as a sanctioned strategy.

Open points

  • Registration cap and ordering. What the per-block cap should be, and whether overflow is FIFO or prioritised by kind (a pool from a known factory is more useful to publish promptly than an unknown contract).
  • ERC-20 classification. ERC-165 does not cover ERC-20, so classifying a freshly deployed token means static-calling name() / symbol() / decimals() and accepting the heuristic, or only recording a token once a known factory pairs it into a pool.
  • Rug-pull scoring semantics. Once phase 3 lands, a run's outcome can be dominated by one successful rug. Whether that is the intended discrimination signal or needs a bound should be decided from live runs, not up front.
  • check:strategy surface. Deployment is currently unrestricted by the cheatcode static check. If arbitrary deployment becomes a first-class capability, the entry gate should state explicitly what is and is not allowed rather than leaving it implicit.

Roadmap position — steps 4 and 6 of 7

Blocked by #41 (positions outside the registered market set score as zero). Registry-authoritative scoring is a special case of the adapter-driven historical valuation proposed there, and the realizable-exit marking this issue relies on is the same principle #41 needs for #38 and #39.

This issue splits across the sequence: phase 1 is step 4, once #41 has landed and #38 has established the realizable-exit pattern. Phases 2–3 are step 6, last, because arbitrary deployment and sanctioned rug pulls open the largest design surface and will generate follow-on rule decisions.

  1. [sim] Scoring black hole: positions outside the registered market set are valued at exactly zero #41 + [sim] Position PnL accounting omits feeGrowthInside — concentrated LPs invisible until collectFees #21 — scoring correctness (same function; fix together)
  2. Clarify the repository license #37 — repository license (independent, cheap, unblocks [sim] CDP stablecoin venue: fork Liquity V1 core (Troves, Stability Pool, redemption arb) #39)
  3. [sim] Liquid staking token (LST) venue: wstETH-style vault + LST/WETH secondary market #38 — LST venue, most self-contained; establishes the realizable-exit pattern
  4. [sim] Agent-created markets: MarketRegistry, on-chain deployment discovery, registry-authoritative scoring #40 phase 1this issue — MarketRegistry + Uniswap V3 factory + observation exposure
  5. [sim] CDP stablecoin venue: fork Liquity V1 core (Troves, Stability Pool, redemption arb) #39 — Liquity venue (largest; needs Clarify the repository license #37 and [sim] Scoring black hole: positions outside the registered market set are valued at exactly zero #41)
  6. [sim] Agent-created markets: MarketRegistry, on-chain deployment discovery, registry-authoritative scoring #40 phases 2–3this issue — Balancer/Curve factories, arbitrary deployment, rug pulls
  7. [sim] Stablecoin depeg: price registry stables from a market instead of asserting $1 (eUSD first) #27 / [sim] Seed-driven whale orderflow bursts #28 / [sim] Mid-run new token launch event #29 — regime variety once the venue set is stable. [sim] Mid-run new token launch event #29 can reuse this registry rather than adding a second discovery path.

Independent tracks: #33 / #35 / #36 (own chain — nothing here is chain-specific, so no ordering constraint), #20#34 (strategy). #31 / #32 (observability) should not land before #41.

Design details to be worked out at implementation time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestsim-envSimulation environment work (core/sdk/deployer, scoring, events)

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions