A self-funding strategy factory on Circle's Arc. An agent invents trading strategies, proves them out-of-sample, commits the survivors on-chain before they touch capital, and pays for its own thinking — per LLM call, per data pull, per backtest — in USDC.
Users can submit strategies too, as Python or as a sentence. Both doors lead to the same gauntlet.
pnpm install && pnpm demo
No Circle key. No Anthropic key. No StableFX KYB. No funded wallet. No network. Everything that would need one has a mock behind the same interface, and every simulated component is badged as such in the UI rather than quietly implied to be real.
Strategy creation is gated. People have ideas — "buy EURC when it dips 20 bps below fair value" — and no infrastructure to express, test or run them safely. Nectar takes that sentence, compiles it, reads back what the code actually does, and gives you an honest walk-forward report.
Backtests are unverifiable. Every trading product says "backtested returns of
X%". There is no way to check that the deployed code is the code that was
tested, or that the window wasn't cherry-picked. Nectar commits keccak256(source)
plus the dataset fingerprint and the out-of-sample metrics to a registry contract
before a strategy is allowed near capital. Anyone can re-hash the published
module and check it.
Autonomous agents can't sustain themselves. An AI trading agent burns inference and market data continuously. On traditional rails, per-call payment is impossible — minimum fees dwarf per-call value — so agents run on prepaid accounts with no cost discipline. Arc's Nanopayments make $0.002 per inference a real transfer, which turns "is this agent economically viable?" into a number you can watch move.
flowchart TB
subgraph intake["Two intake doors"]
forge["<b>Forge</b><br/>Claude, or a 12-family<br/>template library + mutation"]
user["<b>/submit</b><br/>Python paste, or plain English<br/>→ compile → <i>read-back</i> → confirm"]
end
subgraph gauntlet["One gauntlet — identical for both"]
direction TB
validate["<b>AST allowlist</b><br/>no imports · no I/O · no dunders<br/>no eval · no unbounded loops"]
sandbox["<b>Sandboxed subprocess</b><br/>rlimits: CPU · memory · fsize=0<br/>poisoned module table · no network"]
backtest["<b>Walk-forward backtest</b><br/>out-of-sample only · signal at i fills at i+1<br/>spread + sqrt impact + USDC gas"]
hurdle{"<b>Beats USYC + costs?</b><br/>read on-chain, not configured"}
paper["<b>Paper arena</b><br/>live feed, simulated fills<br/>must keep its backtest character"]
end
subgraph chain["Arc"]
registry[("<b>StrategyRegistry</b><br/>codeHash · datasetHash<br/>OOS metrics · origin · status")]
guard[("<b>PolicyGuard</b><br/>venue allowlist · balance cap<br/>session TTL · kill switch")]
wallet["<b>Per-strategy wallet</b><br/>bound to the hash<br/>capped at $100 / 24h"]
venues["<b>Venues</b><br/>SpotVenue stub · StableFX RFQ"]
nano[("<b>Nanopayments</b><br/>batched micro-USDC<br/>one event per call")]
end
subgraph meter["The meter — everything above pays it"]
x402["<b>x402 gateway</b><br/>inference $0.002 · data $0.0005<br/>backtest $0.05"]
ledger["<b>Cost of Intelligence</b><br/>spend vs live P&L"]
end
forge --> validate
user --> validate
validate -->|rejected| mortem["<b>Post-mortem</b><br/>fed back to the Forge"]
validate --> sandbox --> backtest --> hurdle
hurdle -->|no| mortem
hurdle -->|yes| paper
paper -->|diverges| mortem
paper -->|confirms| registry
registry --> wallet
guard -.->|authorises every trade| wallet
wallet --> venues
venues -->|breach drawdown| demote["<b>StrategyDemoted</b><br/>reason published · wallet swept"]
demote --> mortem
mortem -.->|negative evidence| forge
x402 -.-> forge
x402 -.-> backtest
x402 -.-> user
x402 --> nano --> ledger
| Path | What it is |
|---|---|
packages/strategy-runtime |
Python, stdlib only. The Strategy interface, AST validator, rlimit-jailed sandbox, walk-forward backtester, cost models, paper arena, template library. Exposed over HTTP — that boundary is also the metered surface. |
packages/contracts |
Foundry. StrategyRegistry, PolicyGuard, SpotVenue, Nanopayments, plus decimals-faithful test tokens for local runs. |
apps/agent |
Node/TS. Forge, evaluation gate, graduator, live harness, x402 meter, venue routers, REST + WebSocket API, SQLite population DB. |
apps/web |
Next.js 14. Factory floor, strategy pages, submit flow, leaderboard, Cost of Intelligence ledger. |
scripts |
demo, demo:submit, demo:demote, demo:security, deploy, seed. |
Everything is built around this one contract. A strategy is pure decision logic — the harness owns all I/O, venue routing, sizing limits and signing. That separation is what makes LLM generation reliable, user submission safe, and sandboxing tractable.
class Strategy:
params: dict # tunables, with declared param_ranges
universe: list[str] # ["USDC/EURC", "ETH/USD"]
hypothesis: str # what effect do you claim exists?
def on_tick(self, market: MarketState) -> list[Order]: ...
def on_fill(self, fill: Fill) -> None: ...MarketState carries prices, RFQ quotes, positions, cash, equity and a set of
strictly causal features (zscore_20, rsi_14, vol_60, …). A strategy cannot
import anything, cannot reach the network or disk, and cannot name a venue.
Generated code and user code are treated as exactly as untrusted as each other. One security model, no privileged path.
| Layer | Contains | Demonstrated by | |
|---|---|---|---|
| 1 | AST allowlist — no imports, no eval/exec/open/getattr, no dunder attribute access, no try/with/lambda, bounded loops and complexity |
careless and casual malice, cheaply, before execution | pnpm demo:security |
| 2 | Sandboxed subprocess — RLIMIT_CPU, RLIMIT_AS, RLIMIT_FSIZE=0, RLIMIT_NPROC, own process group, wall-clock kill, scrubbed __builtins__, poisoned sys.modules |
anything that compiles — CPU bombs, allocation bombs, novel escapes | pnpm demo:security |
| 3 | PolicyGuard, on-chain — venue allowlist, cumulative balance cap, session TTL, global kill switch | capital | pnpm demo:security, forge test |
Layers 1 and 2 contain code. Layer 3 contains money — it is the only guarantee that survives an escape nobody anticipated, because the guard is what authorises the trade, and it reverts.
No strategy touches live capital unless, in this order:
keccak256(source)+ dataset hash + OOS metrics are written toStrategyRegistry;- a dedicated wallet is provisioned and its
PolicyGuardpolicy is set; - only then does the registry mark the strategy
Liveand bind the wallet.
There is never an instant where a funded wallet exists without a policy binding it. No record → no wallet → no capital, structurally rather than procedurally.
Verify any published strategy yourself:
curl -s localhost:8787/api/verify -H 'content-type: application/json' \
-d '{"source": "<the exact module>", "datasetHash": "0x…"}'
# → { registered, datasetMatches, status, oosSharpe }Per-strategy P&L is that wallet's on-chain history. Not a number we compute and ask you to believe.
- Walk-forward, out-of-sample only. Train windows are never scored; the reported equity curve is the concatenation of test segments.
- No lookahead, structurally. Features at bar
iare computed from bars0..i; an order emitted at barifills at bari+1. Both are asserted by tests that scramble future data and require the report to be byte-identical. - Costs charged in full. StableFX-calibrated spread, square-root market impact, and gas — which on Arc is denominated in USDC and therefore exactly modellable rather than hand-waved.
- Overfitting is penalised.
fitness = OOS_Sharpe − complexity_penalty − instability_penalty, where instability is the spread of per-window Sharpes. - The hurdle is read from the chain. A strategy must beat live USYC APY plus its own modelled costs, and stay inside the drawdown mandate.
- Every report is labelled
SIMULATED — not indicative of future returnsand stamped with the dataset hash.
On the data. With no network, the dataset generator produces deterministic synthetic series carrying documented structure (Ornstein-Uhlenbeck reversion in USDC/EURC, AR(1) momentum in ETH/USD). This is deliberate and disclosed: a pure random walk has no edge, so every strategy would score zero minus costs and the factory would be selecting on noise — the pipeline would run but prove nothing. Costs, out-of-sample discipline and the hurdle all still apply, and badly parameterised strategies still fail. Set a reachable
PYTH_HERMES_URLand real Pyth history is used instead.
pnpm install
pnpm demo # everything, from nothing1 · The floor — http://localhost:3000
Candidates flow left to right: intake → backtesting → paper arena → live → post-mortem. The event feed narrates every decision. Note the rejected column: each entry carries why, and the Forge reads those before its next generation.
2 · A strategy that made it — click any card in Live
Hypothesis, the exact module that was hashed, the walk-forward report with per-window results, the registry record, the policy-capped wallet, and its fills with transaction hashes.
3 · Submit your own — http://localhost:3000/submit
pnpm demo:submit # or use the UIType a sentence. Watch the read-back — it describes what the generated code actually does, derived from the code rather than echoed from your description. That step is the point: it catches a misunderstanding before you pay for a backtest and long before you start trusting its numbers. Then pay $0.05 through the same meter the agent pays.
4 · The economics — http://localhost:3000/ledger
Cost of Intelligence against live P&L, itemised per call, with real on-chain Nanopayments settlements.
5 · Containment
pnpm demo:security # all three layers, proven
pnpm demo:demote # a live strategy breaches its mandateThe demotion publishes StrategyDemoted(codeHash, reason) with the reason string
intact, revokes and sweeps the wallet, and writes a post-mortem back into the
population. A registry that kept only its winners would be exactly the selective
disclosure this project exists to argue against.
Every variable is optional. With an empty .env the whole thing runs.
See .env.example for the full annotated list.
| Variable | Default | What changes when you set it |
|---|---|---|
CHAIN_MODE |
anvil |
arc targets Arc testnet and the real USDC/EURC/USYC predeploys |
AGENT_PRIVATE_KEY |
anvil account #0 | Required for CHAIN_MODE=arc. Must hold testnet USDC |
ANTHROPIC_API_KEY |
— | Forge switches from the template library to real Claude generation, mutation, crossover, plain-English compilation and LLM read-backs |
CIRCLE_API_KEY |
— | Per-strategy wallets become developer-controlled Circle SCAs instead of derived local keys |
STABLEFX_API_KEY |
— | FX leg uses real StableFX RFQ instead of the local market maker |
PYTH_HERMES_URL |
Hermes | Real Pyth history and live prices; unreachable → synthetic, labelled |
PAPER_SESSION_MINUTES |
2 |
Paper confirmation length. Production would be days |
MAX_DRAWDOWN_BPS |
1200 |
Mandate cap; breach ⇒ demotion |
STRATEGY_WALLET_CAP_USDC |
100 |
PolicyGuard balance cap per strategy |
MAX_LIVE_STRATEGIES |
8 |
Live concurrency. A legibility limit — PolicyGuard is the safety one |
PROMOTION_MIN_SHARPE |
0.60 |
Floor, on top of the on-chain USYC hurdle |
NANOPAY_SETTLE_BATCH |
5 |
IOUs per settlement transaction |
The app never crashes on a missing optional key; it degrades to the mock and says so at boot and in the UI.
# Arc testnet
CHAIN_MODE=arc
AGENT_PRIVATE_KEY=0x… # fund from the Arc faucet
pnpm deploy:contracts
# Circle developer-controlled wallets
CIRCLE_API_KEY=…
CIRCLE_ENTITY_SECRET=…
CIRCLE_WALLET_SET_ID=…
pnpm add -w @circle-fin/developer-controlled-wallets # optional peer
# Live LLM Forge
ANTHROPIC_API_KEY=sk-ant-…
# StableFX — requires KYB approval; the mock RFQ implements the same interface
STABLEFX_API_KEY=…Arc addresses: USDC 0x3600000000000000000000000000000000000000 ·
EURC 0x89B50855Aa3bE2F677cD6303Cec089B5F319D72a ·
USYC 0xe9185F0c5F296Ed1797AaE4238D26CCaBEadb86C ·
FxEscrow 0x867650F5eAe8df91445971f14d89fd84F0C9a9f8 ·
Permit2 0x000000000022D473030F116dDEE9F6B43aC78BA3 ·
explorer https://testnet.arcscan.app
Arc's native gas asset is USDC with 18 decimals, while the USDC ERC-20 interface at
0x3600…0000reports 6. Both are called "USDC" and they are 1e12 apart. A single hardcoded1e18in the token path is a trillion-fold error, and it is silent, because every number involved still looks plausible.Nothing in Nectar hardcodes a scale factor —
decimals()is read at boot and everything scales from it.packages/contracts/test/Decimals.t.solandapps/agent/src/lib/decimals.test.tsboth assert the discipline, and the local test tokens take decimals as a constructor argument specifically so anvil runs reproduce the trap rather than hiding it.
pnpm test # everything
pnpm test:contracts # forge — registry, PolicyGuard, decimals
pnpm test:py # pytest — validator, sandbox, walk-forward, no-lookahead
pnpm test:agent # vitest — decimals, diversity guard, promotion gateThe tests worth reading, because they encode the claims:
test_backtest.py::test_report_unchanged_when_future_bars_are_scrambled— no lookahead, end to end.test_backtest.py::test_signal_fills_at_next_bar_not_current— a strategy cannot trade a price it has already seen.test_sandbox.py::test_cpu_bomb_is_killed_by_rlimit— containment past the validator.PolicyGuard.t.sol::test_non_allowlisted_venue_is_blocked— the chain refusing to sign.StrategyRegistry.t.sol::test_cannot_bind_wallet_without_registration— no record, no capital.Decimals.t.sol::test_one_dollar_differs_by_1e12_between_representations— the trap, pinned.diversity.test.ts— why a parametric variant is admitted and a duplicate is not.
- Venue liquidity is synthetic.
SpotVenueis a constant-product stub with operator-seeded reserves, labelledSIMULATEDeverywhere it surfaces. What is real is the chain from code hash → registry → policy-capped wallet → signed, settled receipt. Dressing up the depth would undercut the only part worth trusting. - No user funds, ever. Users buy a backtest of their own strategy — a software service. Live execution trades only the operator's testnet capital. "Run your strategy with your own wallet" needs a legal wrapper this build does not have.
- The mock RFQ has no counterparty. It quotes, widens with size, signs and expires like the real thing, and the backtester is calibrated against the same spreads — but nobody is on the other side.
- Nothing here promises profitability. The product is the pipeline and its verifiability. A registry record is a proof of process, not of performance.
Design decisions and the reasoning behind them, including the ones that deviate
from the original plan, are in DECISIONS.md.