From f4146a3ed4c299055627b25e0ff3471c321a4b2d Mon Sep 17 00:00:00 2001 From: Josh Hardy Date: Tue, 14 Apr 2026 16:46:36 +0000 Subject: [PATCH] feat: add --tokens flag to list deployment-specific tokens `raindex strategy-builder --tokens --strategy --deployment --registry ` emits a markdown table of all tokens registered for that specific deployment (symbol, name, address, decimals). This avoids bloating --describe output with potentially hundreds of tokens per deployment. --describe will reference this command so an agent only fetches the token list for the deployment it actually needs. --- .../cli/src/commands/strategy_builder/mod.rs | 18 ++ .../src/commands/strategy_builder/tokens.rs | 167 ++++++++++++++++++ 2 files changed, 185 insertions(+) create mode 100644 crates/cli/src/commands/strategy_builder/tokens.rs diff --git a/crates/cli/src/commands/strategy_builder/mod.rs b/crates/cli/src/commands/strategy_builder/mod.rs index 61a5c3a55e..d41cef3ac5 100644 --- a/crates/cli/src/commands/strategy_builder/mod.rs +++ b/crates/cli/src/commands/strategy_builder/mod.rs @@ -1,5 +1,6 @@ mod interactive; mod select; +mod tokens; use crate::execute::Execute; use alloy::primitives::hex; @@ -20,6 +21,12 @@ pub struct StrategyBuilder { #[arg(short, long, help = "Interactive mode — guided strategy deployment")] interactive: bool, + #[arg( + long, + help = "List all tokens registered for --strategy + --deployment as markdown" + )] + tokens: bool, + #[arg(long, help = "Order/strategy key from the registry")] strategy: Option, @@ -74,6 +81,17 @@ impl Execute for StrategyBuilder { if self.interactive { return interactive::run_interactive(&self.registry).await; } + if self.tokens { + let strategy = self + .strategy + .as_ref() + .ok_or_else(|| anyhow::anyhow!("--strategy is required with --tokens"))?; + let deployment = self + .deployment + .as_ref() + .ok_or_else(|| anyhow::anyhow!("--deployment is required with --tokens"))?; + return tokens::run_tokens(&self.registry, strategy, deployment).await; + } let strategy = self .strategy diff --git a/crates/cli/src/commands/strategy_builder/tokens.rs b/crates/cli/src/commands/strategy_builder/tokens.rs new file mode 100644 index 0000000000..91d323440d --- /dev/null +++ b/crates/cli/src/commands/strategy_builder/tokens.rs @@ -0,0 +1,167 @@ +//! `--tokens` mode: lists all tokens available for `--select-token` on a +//! given strategy + deployment, as markdown. + +use anyhow::Result; +use rain_orderbook_common::raindex_order_builder::RaindexOrderBuilder; +use rain_orderbook_js_api::registry::DotrainRegistry; +use std::fmt::Write; + +pub async fn run_tokens(registry_url: &str, strategy: &str, deployment: &str) -> Result<()> { + let registry = DotrainRegistry::new(registry_url.to_string()) + .await + .map_err(|err| anyhow::anyhow!("{}", err.to_readable_msg()))?; + + let dotrain = registry + .orders() + .0 + .get(strategy) + .ok_or_else(|| { + let available = registry.get_order_keys().unwrap_or_default(); + anyhow::anyhow!("strategy '{strategy}' not found. Available: {available:?}") + })? + .clone(); + + let settings = registry_settings(®istry); + + let builder = + RaindexOrderBuilder::new_with_deployment(dotrain, settings, deployment.to_string()) + .await + .map_err(|err| anyhow::anyhow!("{}", err.to_readable_msg()))?; + + let tokens = builder + .get_all_tokens(None) + .await + .map_err(|err| anyhow::anyhow!("{}", err.to_readable_msg()))?; + + let mut out = String::new(); + writeln!(out, "# Available tokens — `{strategy}` / `{deployment}`")?; + writeln!(out)?; + + if tokens.is_empty() { + writeln!(out, "_No tokens registered for this deployment._")?; + } else { + writeln!( + out, + "{} tokens. Use any address as the value of a `--select-token KEY=
` flag.", + tokens.len() + )?; + writeln!(out)?; + writeln!(out, "| Symbol | Name | Address | Decimals |")?; + writeln!(out, "|--------|------|---------|----------|")?; + for token in tokens { + writeln!( + out, + "| `{}` | {} | `{}` | {} |", + token.symbol, token.name, token.address, token.decimals + )?; + } + } + + println!("{out}"); + Ok(()) +} + +fn registry_settings(registry: &DotrainRegistry) -> Option> { + let content = registry.settings(); + if content.is_empty() { + None + } else { + Some(vec![content]) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[tokio::test] + async fn lists_tokens_for_a_deployment() { + let server = httpmock::MockServer::start(); + + let settings = r#"version: 4 +networks: + base: + rpcs: + - https://base-rpc.publicnode.com + chain-id: 8453 + network-id: 8453 + currency: ETH +orderbooks: + base: + address: 0xe522cB4a5fCb2eb31a52Ff41a4653d85A4fd7C9D + network: base +deployers: + base: + address: 0xd905B56949284Bb1d28eeFC05be78Af69cCf3668 + network: base +tokens: + test-usdc: + network: base + address: 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 + decimals: 6 + label: USD Coin + symbol: USDC +"#; + + let strategy = r#"version: 4 +orders: + base: + orderbook: base + inputs: + - token: token1 + outputs: + - token: token2 +scenarios: + base: + orderbook: base + runs: 1 + bindings: {} +deployments: + base: + order: base + scenario: base +builder: + name: Test + description: Test + short-description: Test + deployments: + base: + name: Base + description: Test + deposits: [] + fields: [] + select-tokens: + - key: token1 + - key: token2 +--- +#calculate-io +max-output: max-positive-value(), +io: 1; +#handle-io +:; +#handle-add-order +:; +"#; + + let settings_url = format!("{}/settings.yaml", server.base_url()); + let strategy_url = format!("{}/test.rain", server.base_url()); + let registry_url = format!("{}/registry", server.base_url()); + + server.mock(|when, then| { + when.method(httpmock::Method::GET).path("/registry"); + then.status(200) + .body(format!("{settings_url}\ntest {strategy_url}\n")); + }); + server.mock(|when, then| { + when.method(httpmock::Method::GET).path("/settings.yaml"); + then.status(200).body(settings); + }); + server.mock(|when, then| { + when.method(httpmock::Method::GET).path("/test.rain"); + then.status(200).body(strategy); + }); + + let result = run_tokens(®istry_url, "test", "base").await; + assert!(result.is_ok(), "tokens failed: {result:?}"); + } +}