skills: add CTRL — on-chain automation on Base#353
Conversation
| "config": { | ||
| "tokenIn": "ETH", | ||
| "tokenOut": "USDC", | ||
| "amount": 0.01, |
There was a problem hiding this comment.
[ISSUE] Example DCA uses amount: 0.01 ETH but the default maxPerSwap: 0.005 ETH cap (line 65 of this file) means the CTRL keeper will silently reject every single swap — the vault signs successfully but never executes, giving the user no error feedback.
| "tokenIn": "ETH", | ||
| "tokenOut": "USDC", | ||
| "amount": 0.01, | ||
| "slippage": 15 |
There was a problem hiding this comment.
[ISSUE] slippage: 15 has no documented unit — if interpreted as percent (15%), every swap is wide open to sandwich attacks; if BPS (0.15%), it's fine. The block catalog likely defines this field, but the skill never says so, leaving every future cypher.swap workflow with a 100× ambiguity in slippage tolerance.
NeronCrypto
left a comment
There was a problem hiding this comment.
Verdict: discussion-needed — 2 issues in the example workflow block the feature from working correctly.
Findings (mirrored as inline comments):
- [ISSUE] skills/ctrl/SKILL.md:59 —
amount: 0.01 ETHexceeds the defaultmaxPerSwap: 0.005 ETHcap; the CTRL keeper will silently reject every swap, making the example DCA non-functional despite a successful wallet signature. - [ISSUE] skills/ctrl/SKILL.md:60 —
slippage: 15has no documented unit (% vs BPS = 100× difference in sandwich exposure); implementors must infer the unit from the external block catalog, which is never referenced here.
|
Thanks for this — CTRL is a genuinely cool fit for aeon, the API is live, and the safety model (agent never signs, only hands back an EIP-5792 1. Invalid capability value (blocker). capabilities: [external_api, writes_external_host, onchain_writes, sends_notifications]( 2. Missing registration (blocker). Adding a skill here touches four files — see the merged 3. Worked example doesn't match the live catalog. I diffed the SKILL.md against
4. Nice-to-have (security): before forwarding the returned Fix 1–3 and I'll merge. Appreciate the contribution! |
|
|
||
| ```bash | ||
| WORKFLOW=$(curl -m 15 -s -X POST "https://ctrl.build/api/mcp/workflows" \ | ||
| -H "Content-Type: application/json" -d @body.json) |
There was a problem hiding this comment.
[ISSUE] curl -d @body.json references a file that is never written — the skill composes the workflow JSON in step 2 as a conceptual block, but step 3 jumps straight to curl ... -d @body.json with no printf '%s' "$WORKFLOW_JSON" > body.json preceding it. An agent executing this literally will get curl: (26) Failed to open/read local data from file/application and fall into the error branch, never creating the draft. Add an explicit write step before the curl.
| ```bash | ||
| BATCH=$(curl -m 15 -s -X POST "https://ctrl.build/api/mcp/activate/${WID}" \ | ||
| -H "Content-Type: application/json") | ||
| SIGN_URL=$(printf '%s' "$BATCH" | jq -r '.signUrl') |
There was a problem hiding this comment.
[ISSUE] No null/empty guard on SIGN_URL — if the activate endpoint returns unexpected JSON or the field is absent, jq -r '.signUrl' outputs the string "null", and step 5 sends that unclickable string to the user with no error signal. Add [ -n "$SIGN_URL" ] && [ "$SIGN_URL" != "null" ] || { echo "CTRL_ACTIVATE_FAILED"; exit 1; } immediately after this line, mirroring the WID check in step 3.
NeronCrypto
left a comment
There was a problem hiding this comment.
Verdict: discussion-needed
Two steps reference state that is never set up, leading to silent failure paths.
Findings (mirrored as inline comments):
- [ISSUE] skills/ctrl/SKILL.md:75 —
curl -d @body.jsonreferences a file that step 2 never writes; executing this literally yieldscurl: (26)and the workflow is never created. Add an explicitprintf '%s' "$WORKFLOW_JSON" > body.jsonbefore the curl. - [ISSUE] skills/ctrl/SKILL.md:87 —
SIGN_URLhas no null/empty guard afterjq -r '.signUrl'; if activate returns unexpected JSON, user receives the literal string"null"as the sign URL with no error. Add the same[ -n "$SIGN_URL" ] && [ "$SIGN_URL" != "null" ] || ...pattern used forWIDin step 3.
|
pushed fixes for all three blockers + neron's notes:
on the signUrl spoofing note — the skill never parses a returned url, it builds thanks for the thorough review. |
|
Hey @daxaur — still keen to land this. I did another deep pass today; the design is right (agent never signs, one-shot signUrl, conservative caps) and the API checks out live. What's left: From my last review (still pending):
New asks:
Separately — I added the CTRL MCP and tried it. Push those and I'll merge promptly. |
|
Hey @daxaur — following up on my note above. Heads up that this now also has a merge conflict with |
|
rebased onto latest main (the registry files drift fast). everything from your last pass is in now:
also fixed the two things you flagged on the MCP server itself: auth failures now return proper JSON-RPC error objects ( should be clean to merge now. thanks for the thorough reviews! 🩶 |
Adds an on-chain automation skill that compiles natural-language intents (DCA, price-gated swaps, launchpad sniping) into a CTRL workflow on Base. The wallet signs an EIP-5792 batch once; the CTRL keeper handles every trigger after, bounded by the per-swap and per-day caps the user signs. The REST surface at /api/mcp is anonymous — the security boundary is the wallet signature at activate-time, not API auth at create-time. Agents never hold keys; activation returns a hosted signUrl the user opens in their wallet.
…ed example - capabilities: drop invalid on_chain → [external_api, writes_external_host, onchain_writes, sends_notifications] (matches docs/CAPABILITIES.md) - register the skill the way the rest of the repo does: aeon.yml entry (enabled: false, workflow_dispatch), generate-skills-json category, and the regenerated skills.json entry. was undiscoverable before. - worked example now matches the live /api/mcp/block-catalog: time.interval instead of the nonexistent time.cron, .actions[] in the jq, buy amount 0.005 under the 0.01 cap, slippage pinned to percent. - write body.json explicitly before the create curl, and the activate URL is built as https://ctrl.build/activate/<id> so there's no returned signUrl to spoof.
379eee4 to
6666ee7
Compare
Adds
skills/ctrl/SKILL.md— an on-chain automation skill that turns natural-language intents into CTRL workflows on Base.What CTRL does
maxPerSwapandmaxPerDaycaps the user signed.What the skill does
Given a natural-language intent like "DCA 0.01 ETH into USDC every Monday at 14:00 UTC", the skill:
GET /api/mcp/block-catalog)POST /api/mcp/workflows)POST /api/mcp/activate/<id>)signUrlvia./notify— the agent never holds keysTrust boundary
The REST surface at
/api/mcpis intentionally anonymous — the security boundary is the wallet signature at activate-time, not API auth at create-time. Drafts that never get signed auto-prune.Resources