skip to content

timidan

blockchain engineer

Building Murmur: An Autonomous DeFi Agent in 9 Days

Mar 27, 202611 min read
defiautonomous agentsopenservsantimentvenice aiuniswapfilecoinensx402synthesis hackathon
Murmur — Listen. Trade. Repeat.

Building Murmur: an autonomous DeFi agent in 9 days

The problem with trading bots

Most trading bots are black boxes. You hand them capital, they take positions, and when things go sideways you get a PnL chart and some vague excuse. Not an explanation of what the system saw, how it reasoned, or who gave it permission to act.

That always bothered me. I've watched people lose money to bots they couldn't interrogate. No audit trail, no risk controls you could actually read, no way to ask "why did you buy that?" and get a real answer. The state just lives inside some process, invisible.

So I built Murmur.

It hears the market before the market hears itself.


What Murmur does

Loading diagram...

Every two minutes, Murmur runs a nine-stage cycle. I'll keep this brief because the interesting parts are in the integrations.

  1. Scout: pull signals from Santiment
  2. Analyst: normalize raw metrics into comparable scores
  3. Strategist: score three playbooks against current market state
  4. Deliberation: ask Venice AI for a bounded action
  5. Risk Gate: run 14 deterministic policy checks
  6. Quote: get a Uniswap V3 quote
  7. Execute: trade through a per-user TradeVault
  8. Attest: pin receipt to Filecoin, register hash via ERC-8004
  9. Broadcast: push state to the dashboard over WebSocket

Three playbooks: Early Narrative Breakout, Euphoria Fade, Capitulation Rebound. The thesis behind all of them: buy when attention is rising before price reprices, and onchain behavior agrees.


The build: 9 days, 9 sponsor tracks

I built Murmur solo during The Synthesis Hackathon 2026. It ended up spanning nine sponsor tracks, which sounds clean in retrospect. In practice it meant every layer of the system had to connect to a real external dependency. I didn't want sponsor logos in a README. I wanted each one doing real work in the loop.

Day 1 was the skeleton: signals in, scoring, decision shape, risk gate shape, execution stub, receipt stub. Days 2 through 5 were the integrations that would determine if Murmur was actually real or just a nice diagram: Santiment, Venice, Uniswap, Filecoin. Days 6 through 8 went into the dashboard, OpenServ, x402, and the kind of cleanup nobody talks about but every deploy falls apart without. Day 9 was bugs, deploying on Render, and killing anything non-essential.

Build the core loop first. Get it working end to end with stubs. Then layer integrations on top, one at a time.

Each sponsor attachment point becomes obvious once the loop has explicit handoffs. Without that, I'd have spent the whole hackathon debugging half-finished edges between tools that didn't know about each other.

Loading diagram...

The integrations

Santiment — the signal stack

Santiment is where Murmur starts listening. Nine metrics across eight assets: social dominance, weighted sentiment, exchange inflow, exchange outflow, age consumed, daily active addresses, network growth, MVRV, whale transaction count. The main loop runs every two minutes but the Santiment cache only refreshes every 30 — no point burning API budget on data that barely changes at execution cadence.

The part that took real work was batching. Santiment's GraphQL API lets you query multiple metrics, but you have to build a multi-alias query pattern yourself. One alias per metric, one batched query per asset, then unpack the aliased results into the signal map that the rest of the pipeline consumes. When it works, it's clean.

When it doesn't work is where the integration earns its keep. If the batch query fails, Murmur drops to sequential fetches automatically. Retries with exponential backoff. Null time series treated as normal, not exceptional, because not every metric means something for every asset.

The happy path takes an afternoon. Reliability takes the rest of the hackathon.

Venice AI — private inference

I used llama-3.3-70b through Venice's OpenAI-compatible API. Privacy was the draw. If your trading signals and deliberation context are the actual product, you should probably care whether your inference provider is logging your requests. Venice doesn't retain data. That fit.

The model doesn't get to improvise. It receives constrained context — scored candidates, normalized signals, playbook evidence — and returns one of four actions: buy, reduce, exit, hold. Plus confidence, time horizon, thesis, invalidation condition, and risks. I want the LLM resolving ambiguity inside a tight box. Not inventing trading strategy on the fly.

Setup was painless. Swap the base URL and API key, keep the chat-completions pattern, 60-second timeout, three retries. I validate the JSON response aggressively and default to hold if anything goes wrong. Honestly, a private inference provider only matters if the operational surface is boring enough that you stop thinking about it.

Uniswap on Base — the execution layer

Execution runs on Uniswap V3 on Base Sepolia, but the custody model is the interesting part. Murmur doesn't trade from a hot wallet holding user funds. Each user gets a TradeVault deployed by a VaultFactory on first deposit. The user owns that vault. Murmur only has permission to call executeTrade() within onchain limits. Want out? Withdraw, pause, revoke. No need to ask the agent.

Quoting was fiddlier than I expected. I needed pool-fee resolution across 500, 3000, and 10000 bps tiers with fallback if the preferred route didn't exist. Slippage configurable up to 1%, amountOutMinimum computed before execution, quotes rejected if price impact blows through the limit. In agent systems, the "obvious" execution details are exactly where sloppy implementations leak money.

viem made the chain interaction smooth. Read balances, quote, simulate, submit — all without fighting the tooling. And delegating execution to the vault kept the trust boundary tight. Users keep their funds, the agent gets a narrow permission, the control plane lives in contract limits. Not promises.

Filecoin + ERC-8004 — the audit trail

Every Murmur cycle produces a decision receipt. I treated these as real artifacts, not debug logs. Each one contains the full signal snapshot, playbook scores, LLM rationale, risk gate results, and execution metadata like the tx hash. If Murmur acts, there should be a durable object explaining why.

The storage works like this: canonicalize the receipt JSON, hash it with keccak256, upload the payload to Filecoin through Lighthouse, register the hash on-chain via ERC-8004. You get content-addressed storage for the full payload and a chain-anchored reference saying this exact decision existed at this time under this agent identity.

Lighthouse's upload API was simple. The harder decision was philosophical. I didn't want "receipts" to mean telemetry that only I can inspect. I wanted them to mean something a third party could pull up and challenge. The question I kept asking myself: did the agent reason through this trade, or did it just YOLO?

Did the agent reason through this trade, or did it just YOLO? Murmur's answer is a retrievable artifact and an onchain attestation. Not a pinky promise.

ENS — agent identity

Small integration, but it punches above its weight. I resolve the agent wallet to an ENS name on Mainnet and surface that in the dashboard header. A named agent feels different from a hex string. It's cosmetic until you start thinking about agents as long-lived services. Then it starts feeling like the thin layer that makes the agent legible as a real actor, sitting on top of the heavier pieces like vaults and receipts.

Merit / x402 — paid API

I exposed Murmur's outputs as pay-per-request endpoints through x402. Public endpoint: /api/agent (what Murmur is). Paid: /api/receipts/latest and /api/signals/latest (attested receipts, scored assets, treasury snapshot, risk state).

The shift that made this work was conceptual. Once I stopped treating receipts and signal snapshots as internal logs, the API design got cleaner. They weren't implementation leftovers. They were products. The agent's intelligence, purchasable at the boundary where it's already most useful.


Loading diagram...

OpenServ — the coordination layer

This is the integration that changed how I think about Murmur. Before OpenServ, I had an autonomous trading loop with decent internal structure. But it was still a closed worker: receive task, do something internally, return result. After wiring in OpenServ, I started thinking about the agent as a queryable service with live state. Different mental model. And honestly, for autonomous finance, the more useful one.

Here's why I care about the distinction. Task routing isn't coordination. If another agent can invoke Murmur but can't see what Murmur is holding, what regime it's reading, whether the risk gate is blocking trades, or how much treasury capacity is left — that's blind delegation. Fire-and-forget. Fine for proving a call happened. Not fine when the thing on the other end is managing positions and making real decisions.

I exposed three capabilities, each one mapped to a question another agent would actually want to ask:

typescript
get_market_regime()
get_latest_receipt()
run_analysis(windowDays: number, topN: number)

get_market_regime returns current regime (bullish/bearish/neutral), scored assets, treasury state, latest risk gate result, and the most recent decision. get_latest_receipt hands back the newest attested receipt with its Filecoin CID so another agent can follow the audit trail without scraping a dashboard. run_analysis triggers the Santiment scoring pipeline on demand with custom parameters — makes Murmur usable as a live analysis service, not just a status page. All three return structured JSON. Agents should consume state directly, not parse prose.

That "questions other agents would ask" framing is what made OpenServ click for me. I didn't start from internal modules and figure out what to expose. I started from the outside. What would a useful collaborator need to know? What regime are you in? What did you just decide? Can you rescore these assets on a different window? Once I framed it that way, the API design stopped feeling like plumbing and started feeling obvious.

The cleanest choice I made was putting a hard boundary between Murmur's internals and what OpenServ can see:

typescript
interface OpenServRuntime {
  getReceipts: () => DecisionReceipt[];
  getLastScoredAssets: () => ScoredAsset[];
  getLastRiskGate: () => RiskGateResult | null;
  getTreasuryState: () => TreasuryState | null;
  getCycleCount: () => number;
  getDecisionLog: () => DecisionLogEntry[];
}

Capabilities read from this surface, not from random variables inside the loop. The coordination layer gets a stable contract even if the trading loop changes internally. And I control exactly which state becomes externally queryable, instead of accidentally leaking prompt context or private config.

The SDK itself got out of my way, which is the best thing I can say about a tool during a hackathon. @openserv-labs/sdk — define capability names, descriptions, zod schemas, pass a system prompt, set the port. Done. The OpenAI-compatible pattern felt familiar because the rest of Murmur already used structured prompts and typed payloads. I also liked that run_analysis enforces sane ranges on windowDays and topN through the schema. Schema discipline separates a multi-agent service from a pile of function calls.

Just as important: the failure path. If OpenServ fails to start, Murmur keeps running. API key missing? Keeps running. Coordination layer throws during startup? Trading loop still boots, dashboard still broadcasts, receipts still get written. I was deliberate about this. OpenServ is how other agents talk to Murmur. It's not what makes Murmur think. In finance, a metadata outage should never change trade logic. Graceful degradation, not hard dependency.

What keeps me interested in all this — and I mean genuinely interested, not hackathon-pitch interested — is the idea of agents that can actually reason about each other's state. Not just trigger each other and wait for a 200 OK. I want agents that ask real questions, compare exposure, check liquidity conditions, look at each other's recent decisions before acting. Murmur's OpenServ integration is the first version of that idea. What I want to see next isn't more demos of registering capabilities. It's other agents calling Murmur's capabilities because the state behind them is actually worth querying.

What I'd tell developers building with OpenServ

Loading diagram...

Think capabilities, not endpoints. Start from the outside: what would another agent need to know about your system? If you design from internal modules outward, you end up exposing implementation detail instead of value.

Build your core loop first. Add OpenServ after the agent already works on its own. Your service should still make decisions and enforce policy without the coordination layer. If OpenServ becomes a hard dependency before you have something worth exposing, you'll spend your time debugging plumbing.

The SDK works well. What I'd want next: more examples from real projects. Projects with state, long-running loops, failure handling. The hello-world examples help on day one, but they stop helping right around the point where an agent gets interesting.

What would keep developers coming back: seeing agents use each other outside of staged demos. A live registry of agent capabilities you can browse. More examples where agents inspect each other's state instead of just passing tasks around. I want to see what Murmur looks like as one node in a network of agents that actually talk to each other.


Risk gates — why determinism matters

The risk gate is the real authority in Murmur's system. Before any trade executes, 14 checks run:

text
allowlist          delegation_expiry    data_freshness
max_notional       daily_turnover       delegation_cap
cooldown           max_positions        concentration
action_validity    min_confidence       confidence_vs_size
min_liquidity      usdc_balance

The defaults are deliberately conservative: max notional $500, daily turnover $1000, cooldown 2 minutes, max positions 5, concentration capped at 25%, minimum confidence 35%, minimum pool liquidity $50k.

I also added fast-lane exits that bypass the LLM entirely. If exchange inflows spike, age consumed jumps, sentiment hits extreme euphoria, or social dominance goes anomalous, Murmur doesn't need a language model to sit there and think about it. It needs to reduce or exit. Some decisions don't deserve deliberation.

A good rationale isn't a permission slip. The LLM can explain why a trade looks attractive. It doesn't get to decide whether the system is allowed to take that risk.

Policy becomes code, code becomes the authority. That's the only arrangement I'm comfortable with when an autonomous agent is touching real capital.


Closing

Murmur started as a hackathon project. I didn't build it like one. Non-custodial vaults, deterministic risk gates, verifiable receipts, a coordination layer that exposes state instead of hiding it. The architecture already points somewhere real.

Autonomous agents are already trading. The interesting question isn't whether they will. It's whether you can pull up what they did, read why they did it, and verify that the constraints you set were actually enforced.

That's the problem I wanted Murmur to solve.