Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,76 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

## 0.21.0 — 2026-09-17

Robinhood Chain becomes the plugin's primary chain surface (chain 4663):
agent-proof launches through the Clawnch RHC router (Bags.fm), fee claims and
RHC-aware links — with every silent Base fallback replaced by a loud error.
First PyPI release since 0.18.2; includes the 0.19.0 and 0.20.0 changes
documented below.

### Fixed — dependency pin: mcp>=1.0,<2

The MCP 2.x SDK renamed the low-level tool API (`Tool.inputSchema`,
`Server.list_tools`) that `clawmes/mcp_server/` targets. CI resolved the
unbounded `mcp>=1.0` to 2.x and the MCP server tests failed. The dependency
(dev extra and the production `[mcp]` extra) is now pinned to the 1.x line
until the server is ported to 2.x.

### Fixed — Robinhood Chain was blocked by clawmes' own network allowlist

The RHC RPC defaults shipped in `services/rpc.py` (4663 / 46630) were never
added to `lib/http._DEFAULT_ALLOWLIST`, so every RHC call raised
`NetworkAllowlistError` before leaving the process — the plugin blocked its own
endpoints. Fixes:

- `lib/http.py` — allowlist now carries the RHC hosts: both official RPCs
(`rpc.mainnet.chain.robinhood.com`, `rpc.testnet.chain.robinhood.com`), the
Blockscout explorers (mainnet + testnet) and the `bags.fm` trade surface.
- `services/endpoint_allowlist.py` — new `blocked_hosts()` diagnostic: returns
the hosts no allowlist layer permits (defaults ∪ session set).
- `services/rpc.py` — startup self-check (`blocked_default_endpoints()` /
`blocked_user_endpoints()`) so a shipped default that the allowlist blocks is
logged loudly at start instead of failing at first use. User-configured
overrides on non-allowlisted hosts now warn with the `/allow` remediation.

### Added — Robinhood Chain launch surface in the Clawnch service

`services/clawnch.py` now covers the RHC launch router (Bags.fm), not just the
Base/Clanker HTTP flow:

- `rh_ticket()` — `POST /api/robinhood/ticket`: unsigned `launch()` tx +
EIP-712 ticket bound to the registered agent wallet. Refuses to return a
ticket whose `chainId`/`meta.chain` isn't RHC.
- `rh_confirm_launch()` / `rh_deposit_launch()` — `POST /api/robinhood/launch`
`mode="confirm"` (record a ticket-path launch) / `mode="deposit"` (launch
from a verified ETH deposit).
- `rh_claimable()` / `rh_claim()` — `GET/POST /api/robinhood/claim`: claimable
fees + the unsigned `BagsFeeShare.claim(true)` tx. A 403 `not_claimer` is no
longer flattened into `no_credentials`.
- `rh_launches()` — `GET /api/robinhood/launches`, rows decorated with
bags.fm + Blockscout links.
- RHC-aware links (`rh_trade_url` / `rh_explorer_token_url` /
`rh_explorer_tx_url`), `lib/ui_artifacts.bags_url()` on chain 4663, and
`rh_token_info()` / `get_burn_config(chain="robinhood")` for the RHC $CLAWNCH
token (`0x6a50F139F3eD4C9c7bDa0D067c5Ed09De1EEBbeA` — no burn on this chain).

### Changed — no silent wrong-chain fallbacks

- `deploy(chain="robinhood")` and `prepare_deploy(chain="robinhood")` raise
`unsupported_chain` (the clawn.ch server routes `/api/prepare/deploy` by its
own env, ignoring the `chain` query param — a Robinhood request could have
come back as Base calldata). RHC launches must use the router actions.
- `tools/clawnch_launch.py` — new `rh_ticket` / `rh_confirm` / `rh_deposit` /
`rh_token` actions; `chain` / `from_address` / `tx_hash` / `deposit_tx_hash`
are now declared in the schema (previously read by the handler but absent from
the schema); the launch chain id is read from `data.chainId`/`meta.chain`
instead of defaulting every response to Base.
- `tools/clawnch_fees.py` — new `rh_launches` / `rh_claimable` / `rh_claim`
actions.
- `services/explorer.py` — RHC (4663/46630) now fails with an explicit
Blockscout-is-not-Etherscan message instead of a generic unknown-chain error.

## 0.20.0 — 2026-07-06

### Added — EIP-7710 / EIP-7715 on-chain delegation
Expand Down
2 changes: 1 addition & 1 deletion clawmes/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@
* Tooling that does not want to incur a full package import
"""

__version__ = "0.20.0"
__version__ = "0.21.0"
18 changes: 18 additions & 0 deletions clawmes/lib/chains.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,24 @@ class Chain:
block_explorer_url="https://blastscan.io",
is_l2=True,
),
4663: Chain(
chain_id=4663,
name="Robinhood Chain",
short_name="robinhood",
native_symbol="ETH",
native_decimals=18,
block_explorer_url="https://robinhoodchain.blockscout.com",
is_l2=True,
),
46630: Chain(
chain_id=46630,
name="Robinhood Chain Testnet",
short_name="robinhood-testnet",
native_symbol="ETH",
native_decimals=18,
block_explorer_url="https://robinhoodchain-testnet.blockscout.com",
is_l2=True,
),
}


Expand Down
17 changes: 17 additions & 0 deletions clawmes/lib/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@
"arb1.arbitrum.io",
"mainnet.optimism.io",
"polygon-rpc.com",
# Robinhood Chain (Arbitrum Orbit L2) — official public RPCs, both
# mainnet (4663) and testnet (46630). These match the defaults in
# ``clawmes.services.rpc._DEFAULT_ENDPOINTS``: a default endpoint that
# isn't allowlisted here is a self-inflicted outage — every call is
# rejected by our own allowlist before it leaves the process.
# ``RpcService.blocked_default_endpoints()`` fails loudly if the two
# lists ever drift apart again.
"rpc.mainnet.chain.robinhood.com",
"rpc.testnet.chain.robinhood.com",
# Price feeds + market data
"api.coingecko.com",
"api.dexscreener.com",
Expand All @@ -61,6 +70,14 @@
"api.arbiscan.io",
"api.optimistic.etherscan.io",
"api.polygonscan.com",
# Robinhood Chain block explorer (Blockscout family). Used for the
# explorer links emitted on RHC launches; allowlisting it also keeps
# read-only Blockscout API calls unblocked if a surface adds them.
"robinhoodchain.blockscout.com",
"robinhoodchain-testnet.blockscout.com",
# Bags.fm — the Robinhood Chain launchpad + trade surface
# (token pages + the RHC token-metadata host clawn.ch builds on).
"bags.fm",
# Lending / yield
"aave-api-v3.aave.com",
"api.lido.fi",
Expand Down
21 changes: 19 additions & 2 deletions clawmes/lib/ui_artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,11 @@
_CLANKER_BASE_URL = "https://clanker.world/clanker"
_DEXSCREENER_BASE_URL = "https://dexscreener.com"

# Bags.fm is the Robinhood Chain launchpad + trade surface (the RHC
# counterpart of Clanker's token page).
_BAGS_CHAIN_ID = 4663
_BAGS_TOKEN_URL_BASE = "https://bags.fm/token"


def _is_tx_hash(value: str) -> bool:
"""True for a ``0x`` + 64 hex-char transaction hash."""
Expand Down Expand Up @@ -114,6 +119,13 @@ def clanker_url(token: str, chain_id: int = _CLANKER_CHAIN_ID) -> str | None:
return f"{_CLANKER_BASE_URL}/{token}"


def bags_url(token: str, chain_id: int = _BAGS_CHAIN_ID) -> str | None:
"""Bags.fm token page URL (Robinhood Chain only), or None otherwise."""
if chain_id != _BAGS_CHAIN_ID or not _is_address(token):
return None
return f"{_BAGS_TOKEN_URL_BASE}/{token}"


def enrich_tx_links(details: dict[str, Any], *, tx_hash: str, chain_id: int) -> dict[str, Any]:
"""Add an ``explorer_url`` for ``tx_hash`` to ``details`` (in place).

Expand All @@ -136,8 +148,9 @@ def enrich_token_links(
) -> dict[str, Any]:
"""Add market/explorer links for a token to ``details`` (in place).

Adds ``dexscreener_url`` and ``token_explorer_url`` (and ``clanker_url`` on
Base when ``include_clanker``). Existing keys are preserved. Returns
Adds ``dexscreener_url`` and ``token_explorer_url`` (plus
``clanker_url`` on Base — and ``bags_url`` on Robinhood Chain — when
``include_clanker``). Existing keys are preserved. Returns
``details`` for chaining.
"""
dex = dexscreener_url(token, chain_id)
Expand All @@ -153,4 +166,8 @@ def enrich_token_links(
if clank and "clanker_url" not in details:
details["clanker_url"] = clank

bags = bags_url(token, chain_id)
if bags and "bags_url" not in details:
details["bags_url"] = bags

return details
2 changes: 1 addition & 1 deletion clawmes/plugin.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: clawmes
version: 0.20.0
version: 0.21.0
description: Hermes Agent for crypto. Wallet, swaps, DeFi, launches, automation.
author: Clawnch
kind: standalone
Expand Down
Loading
Loading