Skip to main content

ctrl

Build on-chain automation on Base via CTRL - DCA, price-gated swaps, launchpad sniping, or whale-follow that run autonomously after one EIP-5792 wallet signature, bounded by preset caps.

Source facts

Repository
aeonfun/aeon-agent
Last source activity
November 4, 2026 at 01:06
Detected SKILL.md language
English
Stars
12
Forks
4

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
type
Skill
name
CTRL
category
crypto
description
Build on-chain automation on Base via CTRL - DCA, price-gated swaps, launchpad sniping, or whale-follow that run autonomously after one EIP-5792 wallet signature, bounded by preset caps.
var
tags
["crypto","automation","base","defi"]
capabilities
["external_api","writes_external_host","onchain_writes","sends_notifications"]
> **${var}** — Natural-language description of the workflow to build, e.g. `DCA 0.005 ETH into USDC every week`. Required. If empty, log `CTRL_NO_INTENT` and exit cleanly (no notify). CTRL runs on **Base mainnet** only. It compiles a `trigger → action` graph into a V3 vault, the wallet signs **once** — an EIP-5792 batch deploys the vault and registers spending rules — and a Render-hosted keeper polls every ~5s and executes from there. The security boundary is the user's signature at activate-time, not an API key at create-time, so the create + read REST endpoints are anonymous. Activation requires a real wallet session, which the user provides by opening the CTRL activate landing page in their browser. Read the last 2 days of `memory/logs/` so a re-run can reference an existing workflow id instead of provisioning a new one. ## Config - API root = `https://ctrl.build/api/mcp`. No key required for the endpoints used here. - Chain = Base mainnet (chainId `8453`). Always send `"chain": "base"` and `"network": "mainnet"` — CTRL does NOT support Ethereum, Arbitrum, Solana, or any other chain. If the user asks for another chain, log `CTRL_CHAIN_UNSUPPORTED` and exit before creating anything. - Sensible defaults the agent should adopt if the user does not state them: per-swap ≤ `0.01 ETH`, per-day ≤ `0.1 ETH`, slippage ≤ `1%` for stable-pair DCA, `15%` only for launchpad sniping. Caps are signed at activate-time, never embedded in the workflow body. ## Steps ### 1. Read the live block catalog CTRL exposes 24 blocks across four categories (triggers, actions, conditions, utilities). Every key under each node's `data.config` MUST match a `fields[].key` in the catalog — invented keys are silently dropped by the keeper. ```bash curl -m 10 -s "https://ctrl.build/api/mcp/block-catalog" \ | jq '{ triggers: [.triggers[].id], actions: [.actions[].id], conditions: [.conditions[].id], utilities: [.utilities[].id] }' ``` Pick the trigger + action ids that match `${var}`. The most common shapes: - Recurring schedule → `time.interval` (config: `minutes`). The only schedule trigger today — express weekly as `minutes: 10080`, daily as `1440`, hourly as `60`. There is no cron / day-of-week trigger yet. - Price gate → `price.above` / `price.below` (config: `token`, `threshold`) - New token launch → `pool.created` (config: `launchpad`, `safetyEnabled: true` for GoPlus honeypot/tax/score gating) - Swap action → `cypher.swap` (config: `tokenIn`, `tokenOut`, `amount`, `slippage`) - Telegram alert → `notify.telegram` (config: `message`, `severity`) If nothing in the catalog matches the intent, log `CTRL_NO_BLOCK_MATCH` and notify the user that the intent is not supported yet. ### 2. Compose the workflow graph The workflow is a ReactFlow-style `{ nodes, edges }` graph. Each node carries BOTH a top-level `blockType + blockSubtype` (for the create-time validator) AND a `data.blockId + data.subtype` (for the keeper). The two are redundant by design — get them all from the catalog row. Minimum viable weekly DCA — buy 0.005 ETH of USDC every 10080 minutes, 1% slippage. Step 3 sends this body; never write it to a file with `cat > body.json <<'JSON'` (a `>` redirect and a JSON heredoc body are both refused in this run): ```json { "name": "Weekly DCA — ETH to USDC", "description": "DCA 0.005 ETH into USDC weekly via CTRL", "chain": "base", "network": "mainnet", "workflow_data": { "nodes": [ { "id": "t1", "type": "trigger", "blockType": "trigger", "blockSubtype": "interval", "position": { "x": 200, "y": 200 }, "data": { "blockId": "time.interval", "subtype": "interval", "label": "Every week", "config": { "minutes": 10080 } } }, { "id": "a1", "type": "action", "blockType": "action", "blockSubtype": "swap", "position": { "x": 500, "y": 200 }, "data": { "blockId": "cypher.swap", "subtype": "swap", "label": "Buy USDC", "config": { "tokenIn": "ETH", "tokenOut": "USDC", "amount": 0.005, "slippage": 1, "useNativeETH": true } } } ], "edges": [ { "id": "e1", "source": "t1", "target": "a1" } ] } } ``` Units the catalog is explicit about: - `amount` is in **token units** (e.g. `0.005` ETH when `tokenIn` = `ETH`). Beta cap: ≤ 1 ETH-equivalent per swap. - `slippage` is **percent**, range `0.1–99`. Snipe flows force ≥ 10. For stable-pair DCA pick 0.5–2; for memecoin snipes 10–15. - The trigger's `minutes` is an integer ≥ 1. ### 3. Create the workflow (draft, anonymous) One call: the step 2 body pasted in as a literal `jq -nc` filter (a JSON object is a valid jq filter; keep it free of single quotes), piped straight into curl, the response saved with `-o`: ```bash jq -nc '{"name": "...", "description": "...", "chain": "base", "network": "mainnet", "workflow_data": {"nodes": [...], "edges": [...]}}' \ | curl -m 15 -s -o /tmp/ctrl-create.json -w 'http=%{http_code}\n' -X POST "https://ctrl.build/api/mcp/workflows" \ -H "Content-Type: application/json" --data-binary @- ``` Then read the id with one literal `jq` call (no `WID=$(...)` assignment, no `[ ]` test): ```bash jq -r '.workflow.id // "CTRL_CREATE_FAILED"' /tmp/ctrl-create.json ``` If it prints `CTRL_CREATE_FAILED` (or the call above was not `http=2xx`), log `CTRL_CREATE_FAILED` and stop. Otherwise the printed value is WID: write it into every later command literally. The response is `{ "workflow": { "id", "name", "status", "created_at" } }`. The draft's `user_id` stays NULL until the wallet that signs the activation batch claims it. Drafts that are never activated auto-prune. ### 4. Optional pre-flight — check vault state `<USER_WALLET>` below is the operator's connected wallet address (the vault owner, a `0x…` address) — read it from `memory/` or ask the user once. Before sending them to sign, surface the vault preview so they know whether a deploy is included and how much ETH to fund: One call, the wallet address written in literally for `<USER_WALLET>`: ```bash curl -s "https://ctrl.build/api/mcp/vault-status?wallet=<USER_WALLET>" | jq '{ vaultExists, vaultAddress, predictedVaultAddress, ethBalance: .balances.ethDecimal, wethBalance: .balances.wethDecimal, ready, warnings }' ``` If `vaultExists` is `false`, the activate batch will deploy the V3 vault in the same transaction list — fine, but the user must fund it (the user picks `depositEth` on the activate page). ### 5. Hand the user the activate landing page CTRL's activate endpoint (`POST /api/mcp/activate/<id>`) **requires wallet auth** to encode the EIP-5792 batch — the agent cannot call it from a server sandbox. Instead, construct the landing-page URL and notify the user. The landing page handles wallet-connect, lets the user adjust caps, calls activate from their authenticated session, then hands the returned `transactions[]` to their wallet's EIP-5792 `wallet_sendCalls` flow. The activate URL (ACTIVATE_URL below) is `https://ctrl.build/activate/<WID>`, with the id step 3 printed written in. No shell call is needed to build it. Send via `./notify`. Keep it under 4000 chars and put the URL last so it stays clickable: ``` *CTRL workflow drafted — ready to sign* ${var} Chain: Base mainnet Suggested caps: 0.01 ETH/swap · 0.1 ETH/day (editable on the activate page) One wallet signature deploys the vault + registers the spending rule. The keeper runs every trigger after that. ${ACTIVATE_URL} ``` The user opens the URL, connects a wallet, optionally edits `maxPerSwapEth` / `maxPerDayEth` / `depositEth` / `expiryDays`, and signs ONE EIP-5792 batch. From that moment the keeper takes over. ### 6. Log Append to `memory/logs/${today}.md`: ``` ## ctrl - Intent: ${var} - Workflow id: ${WID} - Chain: base / mainnet - Trigger: time.interval / price.above / pool.created / ... - Action: cypher.swap → USDC - Activate URL sent: ${ACTIVATE_URL} - Status: pending-signature ``` End-states: `CTRL_OK`, `CTRL_NO_INTENT`, `CTRL_NO_BLOCK_MATCH`, `CTRL_CREATE_FAILED`, `CTRL_CHAIN_UNSUPPORTED`, `CTRL_REJECTED_ADVICE`. ## Follow-up reads Once a workflow is live, both endpoints work anonymously by id / wallet: ```bash # Vault address, balances, active spending rules, ready flag. curl -s "https://ctrl.build/api/mcp/vault-status?wallet=<USER_WALLET>" | jq '.activeRules' # Last N executions for this workflow — surfaces tx hashes, error messages, gas spent. curl -s "https://ctrl.build/api/mcp/execution-logs?workflow_id=<WID>" | jq '.logs[0:5]' ``` Use these to write a one-line health line in a follow-up notify ("3 executions in last 24h, last trigger 2h ago, vault balance 0.034 ETH"). ## Withdrawing from the vault To pull funds back out of the vault, `POST /api/mcp/vault-withdraw`. Like activate, the agent never signs — it returns an EIP-5792 `transactions[]` batch + a `signUrl` the user opens in their wallet. `token` defaults to `ETH`; pass `WETH` or a `0x` token address for other assets; omit `amount` to withdraw the full balance. ```bash curl -s -X POST "https://ctrl.build/api/mcp/vault-withdraw" \ -H "Content-Type: application/json" \ -d '{"wallet": "<USER_WALLET>", "token": "ETH", "amount": "0.05"}' \ | jq '{ vaultAddress, amount, signUrl }' ``` Notify the user with the returned `signUrl` — they sign one transaction and the funds land back in their wallet. ## Surfaces This skill uses CTRL's **anonymous REST API** (`/api/mcp/*`, no key) — the canonical surface for wallet-native agents, since the wallet signature at activate-time is the trust boundary. CTRL also runs a key-gated JSON-RPC MCP server at the same base for desktop clients (Cursor, Claude Desktop) where tool execution needs an `sk_ctrl_` key; both drive identical workflows. For an aeon skill, stick with REST. ## Network note `/api/mcp/block-catalog`, `/api/mcp/workflows` (POST), `/api/mcp/vault-status`, and `/api/mcp/execution-logs` are public over HTTPS with no auth headers. `/api/mcp/activate/<id>` is NOT public — never try to call it from the agent; it requires a wallet-authenticated session and is what the landing page invokes for the user. ## Constraints - **Never auto-sign.** The wallet popup on the activate landing page is the trust boundary; the agent only hands the user the URL. - **Never embed caps in the workflow body.** `vaultCaps` is not a workflow field — caps are set when the user signs activate (`maxPerSwapEth`, `maxPerDayEth`, decimal strings). - **Match slippage to the use case.** Catalog default is 15 (= 15%) which is correct for sniping and disastrous for stable-pair DCA. For DCA / ETH↔USDC use 0.5–2. For memecoin snipes 10–15. - **No trade advice.** If the intent is "buy X because it will moon", refuse and log `CTRL_REJECTED_ADVICE`. CTRL is execution infrastructure, not a signal source. - **One workflow per invocation.** Multi-step strategies → chain blocks inside one workflow (`trigger → swap → notify`), not multiple skill runs. - **Base only.** Always set `chain: "base"`, `network: "mainnet"`. Any other chain → log `CTRL_CHAIN_UNSUPPORTED` and exit. Note: the create endpoint will accept other chain strings (it does not validate), so the agent itself is the gate. ## Resources - App — https://ctrl.build - Docs — https://ctrl.build/docs - MCP hub — https://ctrl.build/mcp - Source — https://github.com/CTRLabs/ctrl-skill
View on GitHub