https://github.com/namankumar/private-defi-agent
https://github.com/namankumar/private-defi-agent
Last synced: 15 days ago
JSON representation
- Host: GitHub
- URL: https://github.com/namankumar/private-defi-agent
- Owner: namankumar
- Created: 2026-04-23T19:11:06.000Z (about 2 months ago)
- Default Branch: main
- Last Pushed: 2026-04-23T19:15:13.000Z (about 2 months ago)
- Last Synced: 2026-04-23T21:18:40.991Z (about 2 months ago)
- Language: Python
- Size: 34.2 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# private-defi-agent
A Python runtime for an LLM-owned cross-chain DeFi agent. The agent reads on-chain state, asks Claude to decide what to do next, and executes against Aleo and Polygon. It runs on a one-minute cron. State persists to disk between ticks, so restarts are transparent.
## How it works
Every tick runs the same loop:
```
observe → policy → LLM decides → validate → execute → persist
```
**Observe.** Read pool state from Aleo, yield position from Polygon (Morpho vault), participant registry from disk.
**Policy.** Deterministically compute which actions are legal given the current phase. The LLM never sees a blank canvas. It picks from a pre-computed set.
**LLM decides.** Claude receives the observation, the set of actions allowed in the current phase, and an economic summary. Returns one action with a reason, risk note, and expected outcome.
**Validate.** Guardrails re-check the decision before execution. If the action is illegal, the amount is missing, or a live operation is already in flight, the agent pauses for human review.
**Execute.** Aleo or Polygon adapter executes the chosen action on-chain.
**Persist.** State and decision written to JSON. Agent exits. Next cron tick starts fresh.
---
## Why agent
We chose state machines over prompt chains because the constraint space for DeFi execution is finite and enumerable. A policy engine can prove it won't produce an illegal trade. A prompt chain can make the same mistake in a new way every time. The model chooses from legal actions. The policy engine defines them.
A deterministic script could handle the happy path: bridge when funded, deposit to Morpho, withdraw when yield threshold is met. The LLM earns its keep in two places a script can't handle well:
**Economic reasoning under uncertainty.** Should the agent withdraw from Morpho now? That depends on current yield bps, gas price, expected bridge time, and how close the timeout is. These tradeoffs don't reduce to a threshold. Claude weighs them and explains the call, including what it's uncertain about.
**Ambiguous chain state.** What if a bridge confirmation is partial? What if pool total is above threshold but the bridge interface isn't deployed yet? A script throws an exception or proceeds blindly. The agent pauses, writes a human-readable reason, and waits for acknowledgment.
**Protocol extensibility.** A script encodes one strategy: deposit to Morpho, withdraw when yield hits the threshold. Adding a new protocol means rewriting the script. The agent separates the concerns differently: the policy engine defines what actions are legal, the LLM reasons about which one is wise. Adding a new protocol means adding new actions to the policy layer. The reasoning handles the rest. Polymarket is a compelling next case: the agent could evaluate prediction market odds, size positions against the yield baseline, and exit before resolution. Same architecture, different policy.
```json
// Example pause entry in the decision log:
{
"phase": "PAUSED_REVIEW",
"decision": "NOOP",
"result": "Guardrail rejection: Live bridge_out operation already in progress"
}
```
---
## The LLM interface
Claude receives a structured JSON prompt on every tick. Here is the full system prompt:
```
You are the operator of a cross-chain DeFi pool.
Choose exactly one action from LEGAL_ACTIONS.
Never invent balances, tx hashes, or phase transitions.
Never invent an action not present in LEGAL_ACTIONS.
Prefer NOOP over risky progression.
Return strict JSON with keys: action, reason, risk_note, expected_outcome.
```
"Prefer NOOP over risky progression" is a reliability constraint, not a style hint. It means the agent will wait through many NOOPs rather than take an aggressive action it's uncertain about.
**Example input** (what Claude receives):
```json
{
"goal": "Bridge pool funds to Polygon, deposit to Morpho for yield, bridge back with profit, distribute to depositors.",
"state": {
"phase": "FUNDS_ON_POLYGON",
"last_successful_action": "INITIATE_BRIDGE_OUT",
"last_observed_balances": {
"leo_pool": 0,
"evm_contract": 5000000,
"morpho_vault": 0
}
},
"observation": {
"pool_total_base_units": 0,
"evm_held_usdc_base_units": 5000000,
"evm_total_shares": 0,
"evm_current_value_base_units": 0,
"gas_price_gwei": 14
},
"legal_actions": ["NOOP", "DEPOSIT_TO_MORPHO"],
"payloads": {
"DEPOSIT_TO_MORPHO": { "amount_base_units": 5000000 }
},
"economic_summary": {
"bridge_eligible": true,
"unrealized_yield_bps": 0,
"withdraw_eligible": false
}
}
```
**Example output** (what Claude returns):
```json
{
"action": "DEPOSIT_TO_MORPHO",
"reason": "Funds confirmed on Polygon (5 USDC). Bridge is complete. Depositing to Morpho is the correct next step — no reason to hold idle.",
"risk_note": "Deposit is irreversible until the withdraw cycle completes. Gas at 14 gwei is acceptable. Morpho share price risk is present but expected.",
"expected_outcome": "Phase transitions to MORPHO_DEPOSITED. Morpho shares minted. Yield accrual begins."
}
```
The `reason`, `risk_note`, and `expected_outcome` fields are persisted to `agent_state.json` on every tick, creating a full audit trail of the agent's reasoning.
---
## Phase state machine
```
IDLE
└─► POOL_REGISTERED
└─► POOL_FUNDED
└─► BRIDGE_OUT_PENDING
└─► FUNDS_ON_POLYGON
└─► MORPHO_DEPOSITED
└─► WITHDRAW_PENDING
└─► BRIDGE_BACK_PENDING
└─► READY_TO_DISTRIBUTE
└─► COMPLETED
PAUSED_REVIEW ◄── reachable from any phase on guardrail failure or timeout
```
Phase transitions are driven by on-chain observation, not by the LLM. The agent never trusts its own previous state over what the chain actually shows.
---
## Reliability
Several independent layers prevent catastrophic failure:
| Layer | Mechanism |
|---|---|
| Constrained decisions | LLM picks from pre-computed legal actions only |
| Guardrail validation | Decision re-validated before execution |
| Heuristic fallback | If Anthropic is unavailable, deterministic priority order runs instead |
| PAUSED_REVIEW phase | Ambiguous state or rejection → pause + human acknowledgment |
| Timeout detection | Pending operations that exceed `BRIDGE_TIMEOUT_SECS` trigger a pause |
| File lock | Cron overlaps rejected. Only one agent run at a time. |
| Safe reads | Chain reads that fail return zero/none rather than crashing |
| File-based state | `agent_state.json` is human-inspectable and editable mid-run |
---
## Decision trace
`docs/sample-run.json` contains a realistic end-to-end run showing the agent progressing from IDLE through MORPHO_DEPOSITED, including one NOOP tick while waiting for bridge confirmation. Each entry shows the phase, legal actions offered, Claude's choice, and the result.
---
## Install
```bash
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
Requires the [Leo CLI](https://developer.aleo.org/leo/installation) for account generation.
## Quick start (dry run, no funds)
```bash
AGENT_LLM_MODE=heuristic python3 -m agent status
AGENT_LLM_MODE=heuristic python3 -m agent run
cat agent_state.json
```
`heuristic` mode runs without Anthropic configured. The agent executes the same loop using a deterministic priority order instead of Claude.
## Environment
```bash
# LLM
ANTHROPIC_API_KEY=
ANTHROPIC_MODEL=claude-sonnet-4-5
AGENT_LLM_MODE=auto # auto | anthropic | heuristic
# Aleo
LEO_PROGRAM_ID= # your Leo program ID
LEO_POOL_ID= # your pool ID
ALEO_API_BASE=https://api.explorer.provable.com/v2
ALEO_NETWORK=mainnet
ENABLE_ALEO_MUTATIONS=false # keep false until Leo contract is ready
# Polygon
POLYGON_RPC_URL=
EVM_CONTRACT_ADDRESS=
POLYGON_PRIVATE_KEY=
# Thresholds
MIN_BRIDGE_OUT_AMOUNT=500000000 # 500 USDC (6 decimals)
MIN_NET_YIELD_BPS=50
BRIDGE_TIMEOUT_SECS=1800
```
## CLI
```bash
python3 -m agent run # full controller pass
python3 -m agent status # read-only snapshot, no LLM call
python3 -m agent resume # clear pause state after review
python3 -m agent acknowledge-pause # mark pause reviewed
python3 -m agent withdraw-request # trigger admin withdraw
python3 -m agent force-withdraw # from MORPHO_DEPOSITED only
python3 -m agent force-distribute # from READY_TO_DISTRIBUTE only
```
## Cron
```bash
* * * * * cd /path/to/repo && python3 -m agent run >> /tmp/agent.log 2>&1
```
File lock prevents overlapping invocations.
## Tests
```bash
python3 -m unittest discover -s tests -t .
```
## Current limitations
- Bridge-out, receive-state, and distribute are pending Leo contract updates. Aleo writes disabled by default.
- `agent_state.json` is designed for single-operator use; multi-operator would need a proper store
- In-memory rate limiting on API reads; no retry backoff on chain RPC failures yet