feat: add strategy-builder CLI subcommand#2544
feat: add strategy-builder CLI subcommand#2544hardyjosh wants to merge 6 commits into2026-02-23-js-api-consolidationfrom
Conversation
WalkthroughAdds a new Changes
Sequence DiagramsequenceDiagram
participant User as User/CLI
participant CLI as StrategyBuilder
participant HTTP as HTTP Client
participant Registry as Registry Server
participant OrderBuilder as RaindexOrderBuilder
participant Output as Stdout
User->>CLI: invoke with registry, strategy, deployment, owner, bindings
CLI->>HTTP: GET registry URL
HTTP->>Registry: request registry
Registry-->>HTTP: registry text
HTTP-->>CLI: registry data
CLI->>OrderBuilder: new_with_deployment(registry.settings?, deployment)
CLI->>OrderBuilder: set_field_value(...) for each binding
CLI->>OrderBuilder: set_select_token(...) async for each select
CLI->>OrderBuilder: set_deposit(...) async for each deposit
OrderBuilder-->>CLI: get_deployment_transaction_args(owner) -> (approvals, calldata, meta_call?)
CLI->>Output: print approvals
CLI->>Output: print address:calldata
CLI->>Output: print optional meta-call
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/cli/src/commands/strategy_builder.rs`:
- Around line 48-55: parse_key_value_pairs currently silently overwrites
duplicate keys; update it to detect duplicate keys and return an error instead
of replacing the previous entry. In the function parse_key_value_pairs(args:
&[String]) -> Result<HashMap<String, String>>, after splitting into key/value
check whether map.contains_key(&key.to_string()) (or lookups using &key) and if
present return an anyhow::anyhow! error indicating a duplicate override for that
key and the original arg; only insert when the key is new. Ensure the function
returns an Err on duplicates so callers cannot proceed with silent collisions.
- Around line 86-89: The parser currently uses line.split_once(' ') which fails
on tabs or multiple/mixed whitespace; change the logic that extracts key and
url_str from each line to use line.split_whitespace() (collect into fields),
validate that fields.len() == 2 and return the same anyhow::anyhow!("invalid
registry line (expected 'key url'): {line}') error if not, then set key =
fields[0] and url_str = fields[1] before calling
order_urls.insert(key.to_string(), url_str.trim().to_string()) so the code
accepts any whitespace delimiter.
- Around line 59-65: The fetch_text function currently calls reqwest::get
without a timeout and can block indefinitely; change it to build and reuse a
reqwest::Client with an explicit timeout (e.g., Duration::from_secs(10)) and
perform client.get(url.as_str()).send().await instead of reqwest::get, ensuring
you propagate errors as before; update fetch_text to accept or create that
client (or create a local client with Client::builder().timeout(...).build()?)
and use response.text().await? to return the body. Also add the necessary
std::time::Duration import and preserve the existing HTTP error handling around
response.status() and anyhow::bail! when non-success.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c75248ce-047a-4e27-b189-fdb73b5de209
📒 Files selected for processing (4)
crates/cli/Cargo.tomlcrates/cli/src/commands/mod.rscrates/cli/src/commands/strategy_builder.rscrates/cli/src/lib.rs
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
♻️ Duplicate comments (1)
crates/cli/src/commands/strategy_builder.rs (1)
48-60:⚠️ Potential issue | 🟡 MinorReject empty
KEYvalues duringKEY=VALUEparsing.
=valuecurrently passes parsing and creates an empty key, which degrades CLI validation and error clarity. Validate non-empty trimmed keys at parse time and add a test for it.Suggested patch
fn parse_key_value_pairs(args: &[String]) -> Result<HashMap<String, String>> { let mut map = HashMap::new(); for arg in args { - let (key, value) = arg + let (raw_key, value) = arg .split_once('=') .ok_or_else(|| anyhow::anyhow!("expected KEY=VALUE, got: {arg}"))?; + let key = raw_key.trim(); + if key.is_empty() { + anyhow::bail!("expected non-empty KEY in KEY=VALUE, got: {arg}"); + } if map.contains_key(key) { anyhow::bail!("duplicate key: {key}"); } map.insert(key.to_string(), value.to_string()); } Ok(map) } @@ fn parse_key_value_pairs_duplicate_key_fails() { let args = vec!["key=first".to_string(), "key=second".to_string()]; let err = parse_key_value_pairs(&args).unwrap_err().to_string(); assert!(err.contains("duplicate key: key"), "got: {err}"); } + + #[test] + fn parse_key_value_pairs_empty_key_fails() { + let args = vec!["=value".to_string()]; + let err = parse_key_value_pairs(&args).unwrap_err().to_string(); + assert!(err.contains("expected non-empty KEY"), "got: {err}"); + } }Also applies to: 165-202
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@crates/cli/src/commands/strategy_builder.rs` around lines 48 - 60, parse_key_value_pairs accepts inputs like "=value" producing empty keys; update the function (parse_key_value_pairs) to trim the key part, reject empty keys by returning an error (e.g., bail or anyhow::anyhow! with a clear message referencing the original arg), and keep the duplicate-key check and insertion behavior. Apply the same non-empty-trimmed-key validation to the other parsing occurrence in this file (the duplicate block around the later function), and add a unit test that asserts parsing "=value" (and keys with only whitespace) returns an error.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@crates/cli/src/commands/strategy_builder.rs`:
- Around line 48-60: parse_key_value_pairs accepts inputs like "=value"
producing empty keys; update the function (parse_key_value_pairs) to trim the
key part, reject empty keys by returning an error (e.g., bail or anyhow::anyhow!
with a clear message referencing the original arg), and keep the duplicate-key
check and insertion behavior. Apply the same non-empty-trimmed-key validation to
the other parsing occurrence in this file (the duplicate block around the later
function), and add a unit test that asserts parsing "=value" (and keys with only
whitespace) returns an error.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 5ccb4e7f-ad28-409d-a17f-fce8982ea92f
📒 Files selected for processing (2)
crates/cli/Cargo.tomlcrates/cli/src/commands/strategy_builder.rs
Adds a new CLI subcommand that generates deployment calldata from a
remote registry strategy. Fetches the registry file, resolves the
strategy's dotrain + settings, creates a RaindexOrderBuilder, applies
field/token/deposit overrides from flags, and outputs `<to>:<calldata>`
lines to stdout for piping into a transaction submitter.
Usage:
raindex strategy-builder \
--registry <url> --strategy <name> --deployment <key> \
--owner <addr> \
--set-field binding=value \
--select-token slot=addr \
--set-deposit token=amount
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace manual registry/settings/order fetching with DotrainRegistry from js_api, which already handles all of that. The CLI now depends on rain_orderbook_js_api (compiles natively since PR #2455) and uses DotrainRegistry::new(url) to fetch the registry, then extracts dotrain content and settings to create the RaindexOrderBuilder.
Some token list JSON files (e.g., st0x.registry) don't include the logoURI field. Making it optional prevents deserialization failures. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ba92f20 to
9a5eb91
Compare
|
Warning This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
This stack of pull requests is managed by Graphite. Learn more about stacking. |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@crates/cli/src/commands/strategy_builder.rs`:
- Around line 77-82: The error message builds its "available" list from
registry.get_order_keys() which relies on map iteration order and yields
non-deterministic text; change the code that assigns available (from
registry.get_order_keys().unwrap_or_default()) to collect into a Vec, sort it
(e.g., sort_unstable or sort()), and then use that sorted Vec in the anyhow!
message so the "Available: {:?}" output is deterministic; update the variable
named available near the anyhow! call in strategy_builder.rs accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 81056df2-7093-4391-9355-e31e7fee657e
📒 Files selected for processing (5)
crates/cli/Cargo.tomlcrates/cli/src/commands/mod.rscrates/cli/src/commands/strategy_builder.rscrates/cli/src/lib.rscrates/settings/src/remote/tokens.rs
| let available = registry.get_order_keys().unwrap_or_default(); | ||
| anyhow::anyhow!( | ||
| "strategy '{}' not found in registry. Available: {:?}", | ||
| self.strategy, | ||
| available | ||
| ) |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Make “available strategies” output deterministic in not-found errors.
Line 77 currently uses map key iteration order, which can produce unstable error text. Sorting before formatting improves reproducibility (especially for tests and user support).
Proposed refinement
- let available = registry.get_order_keys().unwrap_or_default();
+ let mut available = registry.get_order_keys().unwrap_or_default();
+ available.sort();
anyhow::anyhow!(
"strategy '{}' not found in registry. Available: {:?}",
self.strategy,
available
)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| let available = registry.get_order_keys().unwrap_or_default(); | |
| anyhow::anyhow!( | |
| "strategy '{}' not found in registry. Available: {:?}", | |
| self.strategy, | |
| available | |
| ) | |
| let mut available = registry.get_order_keys().unwrap_or_default(); | |
| available.sort(); | |
| anyhow::anyhow!( | |
| "strategy '{}' not found in registry. Available: {:?}", | |
| self.strategy, | |
| available | |
| ) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@crates/cli/src/commands/strategy_builder.rs` around lines 77 - 82, The error
message builds its "available" list from registry.get_order_keys() which relies
on map iteration order and yields non-deterministic text; change the code that
assigns available (from registry.get_order_keys().unwrap_or_default()) to
collect into a Vec, sort it (e.g., sort_unstable or sort()), and then use that
sorted Vec in the anyhow! message so the "Available: {:?}" output is
deterministic; update the variable named available near the anyhow! call in
strategy_builder.rs accordingly.

Motivation
The Raindex orderbook protocol has a rich GUI builder flow in the webapp for configuring and deploying strategies. There is currently no way to do the same from a terminal or from a non-interactive agent — the webapp is the only entry point. That forces anyone automating a deployment (CI, scripts, AI agents) to either drive the browser or hand-roll the calldata.
Solution
Add a
strategy-buildersubcommand to the raindex CLI that generates deployment calldata from a remote registry strategy.Outputs one
<address>:<calldata>line per transaction on stdout — approvals first, then the deployment multicall, then optional metaboard meta emission. Each line is one signable transaction.Implementation reuses
RaindexOrderBuilderfrom the common crate (same object the webapp drives) andDotrainRegistryfrom the js_api crate, so the CLI and webapp use identical resolution semantics.Follow-up PRs in this stack add
--interactive(#2546),--tokens(#2549), the template-fallback operator (#2551), and--describe(#2548).Checks
Summary by CodeRabbit
New Features
strategy-buildercommand that generates deployment calldata from registry strategies with configurable field bindings, token selections, and deposit amounts.Improvements