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
16 changes: 12 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ rome facts balance hadrian 0x… # native (gas-token) balance for an address
rome facts programs devnet # Solana program ids for a network
rome cookbook cpi-recipe # the CPI account-rules + SDK encoders (grounded addresses)
rome cookbook patterns lending # which example repo + guide fits a goal
rome call hadrian 0x… "balanceOf(address) returns (uint256)" 0x… # read a contract (no key)

# actions — sign on-chain, need ROME_EVM_KEY (never a flag/log/MCP):
rome deploy hadrian ./out/Store.json # deploy a compiled artifact
rome send hadrian 0x… "set(uint256)" 42 # write via submitRomeTx
rome fund hadrian --from base-sepolia --amount 1 # bridge USDC → Rome gas (CCTP, "from home")
rome bridge hadrian --from base-sepolia --amount 1 --intent wrapper # USDC → wUSDC on Rome
```

Chains resolve by id, name, or slug (`200010`, `hadrian`, `200010-hadrian`). Output is JSON — pipe it to `jq` or read it in an agent:
Expand Down Expand Up @@ -67,11 +74,12 @@ More recipes — agent (MCP), shell, and CI integration — in [`docs/GUIDES.md`

The client (Claude Code / Claude Desktop / Cursor / …) spawns `rome mcp` as a child process on demand, talks to it over stdin/stdout, and shuts it down when the session ends — no port, no hosting, no process manager. It exposes each capability as a tool (`facts_chain`, `facts_gas`, `cookbook_cpi_recipe`, …), is **read-only and holds no keys** — safe to wire into any agent; it can never sign a transaction or leak a secret. Your app always does the signing, via [`@rome-protocol/sdk`](https://github.com/rome-protocol/rome-sdk-ts). See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md#running-it--cli-vs-mcp-server) for the lifecycle.

## What it is — and isn't (v1)
## What it is — two layers

- **v1 is grounding**: facts + cookbook, read-only. It kills hallucination and points at the right pattern.
- It does **not** deploy or sign. Deploy with Foundry / Hardhat or [`create-rome-app`](https://github.com/rome-protocol/create-rome-app); write from your app via the SDK.
- Facts come from [`@rome-protocol/registry`](https://github.com/rome-protocol/rome-registry) and the chain's RPC; the CPI recipe's precompile addresses come from the SDK — nothing is hardcoded here.
- **Grounding — read-only, on both CLI + MCP**: `facts` + `cookbook` + `call`. Kills hallucination, routes you to the right pattern, reads contracts. Holds no keys — safe to wire into any agent.
- **Actions — CLI-only, key-gated, never on MCP**: `deploy` / `send` (contracts) and `fund` / `bridge` (the "from home" on-ramp: bridge USDC in as gas or wUSDC via CCTP). These sign, so they read the key from the environment (`ROME_EVM_KEY`) — never a flag, never logged, never through the MCP server. Every action prints what it did; funding previews with `--dry-run`.
- Everything is sourced from [`@rome-protocol/registry`](https://github.com/rome-protocol/rome-registry) + the chain's RPC + the SDK's `@rome-protocol/sdk/bridge` — nothing chain-specific is hardcoded.
- Still orchestrates, doesn't replace: heavy contract builds stay in Foundry / Hardhat; scaffolding is [`create-rome-app`](https://github.com/rome-protocol/create-rome-app); library writes use [`@rome-protocol/sdk`](https://github.com/rome-protocol/rome-sdk-ts).

## Development

Expand Down
38 changes: 38 additions & 0 deletions docs/GUIDES.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ Concrete, copy-paste recipes: real commands with real output, and how to fold `r
- [Every command, with real output](#every-command-with-real-output)
- [Integrate into an AI agent (MCP)](#integrate-into-an-ai-agent-mcp)
- [The agent grounding loop](#the-agent-grounding-loop)
- [Fund a wallet from another chain (`fund` / `bridge`)](#fund-a-wallet-from-another-chain-fund--bridge)
- [Shell & scripting recipes](#shell--scripting-recipes)
- [Use it in CI](#use-it-in-ci)
- [End-to-end: build a price-reading contract, grounded by rome](#end-to-end-build-a-price-reading-contract-grounded-by-rome)
Expand Down Expand Up @@ -151,6 +152,43 @@ The agent now writes against `aerarium`'s pattern with Hadrian's real RPC and th

---

## Fund a wallet from another chain (`fund` / `bridge`)

The **from-home** path: you hold USDC on another chain and want onto Rome. `fund` bridges it in as **native gas**; `bridge --intent wrapper` brings it in as **wUSDC**. Both use Circle CCTP, orchestrated through [`@rome-protocol/sdk`](https://github.com/rome-protocol/rome-sdk-ts)'s `bridge` module — you sign only the source-chain burn; Rome's sponsor pays the settle.

These are **actions**: CLI-only, and they read `ROME_EVM_KEY` from the environment (never a flag, never logged, never on MCP).

**Preview first with `--dry-run`** — quotes the route and shows exactly what you'd sign, spending nothing:

```console
$ rome fund hadrian --from base-sepolia --amount 0.5 --dry-run
{
"dryRun": true,
"route": "usdc-cctp-to-rome",
"amountIn": "500000",
"amountOut": "500000",
"fee": { "bps": 0, "absolute": "0", "asset": "USDC" },
"etaSeconds": 1100,
"plannedTxs": [
{ "stepN": 1, "to": "0x036CbD…dCF7e", "description": "Approve TokenMessenger to spend USDC" },
{ "stepN": 1, "to": "0x8FE6B9…2DAA", "description": "Burn USDC via CCTP, mintRecipient = user's Rome account" }
]
}
```

Drop `--dry-run` to execute: `rome` signs + broadcasts the two source txs, signs the trustless-settle authorization (gas intent only), registers the transfer, then polls to completion — printing the transfer id, outcome, and source tx hashes.

```bash
rome fund hadrian --from base-sepolia --amount 0.5 # → native gas on Rome
rome bridge hadrian --from base-sepolia --amount 0.5 --intent wrapper # → wUSDC on Rome
```

Supported source chains come from the registry's bridge config for the target Rome chain — Base Sepolia, Arbitrum Sepolia, Polygon Amoy, Avalanche Fuji, Monad Testnet, Sepolia. Resolve a source by id, name, or slug (`84532`, `"base sepolia"`, `base-sepolia`). CCTP standard attestation takes ~15–20 min, so a real transfer isn't instant; the command polls until it lands.

> The bridge-api base defaults to the devnet orchestrator; override with `--bridge-api <url>` or `ROME_BRIDGE_API`.

---

## Shell & scripting recipes

`rome` prints JSON — pipe it to `jq`.
Expand Down
40 changes: 37 additions & 3 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,32 @@ export function cliCommandTable(): string[] {
return CAPABILITIES.map((c) => c.cliPath);
}

/**
* Split a command's trailing argv into ordered positionals and `--flag` values.
* `--name value` → { name: value }; a bare `--name` (end, or followed by another
* `--flag`) → { name: "true" } (boolean). Positionals keep their order.
*/
export function parseRest(rest: string[]): { positionals: string[]; flags: Record<string, string> } {
const positionals: string[] = [];
const flags: Record<string, string> = {};
for (let i = 0; i < rest.length; i++) {
const t = rest[i];
if (t.startsWith("--")) {
const name = t.slice(2);
const next = rest[i + 1];
if (next !== undefined && !next.startsWith("--")) {
flags[name] = next;
i++;
} else {
flags[name] = "true";
}
} else {
positionals.push(t);
}
}
return { positionals, flags };
}

function helpText(): string {
const lines = [
"rome — Rome Protocol dev CLI + MCP server",
Expand Down Expand Up @@ -53,10 +79,18 @@ export async function main(argv: string[]): Promise<number | void> {
}
const { cap, rest } = resolved;

// Each declared arg can be given positionally OR as `--name value`; bare `--name`
// is a boolean flag ("true"). Positionals fill declared args left-to-right; any
// extra `--flag` is carried through so handlers can read it.
const { positionals, flags } = parseRest(rest);
const argObj: Record<string, string> = {};
cap.args.forEach((spec, i) => {
if (rest[i] != null) argObj[spec.name] = rest[i];
});
let pi = 0;
for (const spec of cap.args) {
if (flags[spec.name] !== undefined) argObj[spec.name] = flags[spec.name];
else if (positionals[pi] !== undefined) argObj[spec.name] = positionals[pi++];
}
for (const [k, v] of Object.entries(flags)) if (!(k in argObj)) argObj[k] = v;

const missing = cap.args.filter((s) => s.required && argObj[s.name] == null);
if (missing.length) {
console.error(`Missing required: ${missing.map((m) => m.name).join(", ")}`);
Expand Down
Loading
Loading