Skip to content

feat: Add price ratio functionality to DotrainOrderGuide#2084

Closed
big14way wants to merge 1 commit intorainlanguage:mainfrom
big14way:feature/price-ratio-functionality
Closed

feat: Add price ratio functionality to DotrainOrderGuide#2084
big14way wants to merge 1 commit intorainlanguage:mainfrom
big14way:feature/price-ratio-functionality

Conversation

@big14way
Copy link
Copy Markdown

@big14way big14way commented Aug 14, 2025

  • Add support for ${io-ratio(input, output)} expressions in YAML configurations
  • Extend Context system to parse and resolve price ratio expressions
  • Add price callback support to DotrainOrderGui for external price fetching
  • Implement comprehensive error handling for price callback failures
  • Add backward-compatible API with newWithDeploymentAndPriceCallback method
  • Include complete documentation and examples for price ratio usage
  • Support dynamic field names, descriptions, and default values with market prices

Resolves #1998: Ability to reference price ratios in field names/descriptions and for default field values

Summary by CodeRabbit

  • New Features

    • Dynamic price ratio resolution in the GUI using an external price callback.
    • YAML support for ${io-ratio(...)} expressions across names, descriptions, and defaults.
    • Optional pricing support when creating the GUI and when restoring from saved state.
    • Interpolated default values for GUI fields.
  • Documentation

    • Added README section, comprehensive guide, JavaScript examples, and a YAML example demonstrating dynamic pricing.
  • Tests

    • Added tests for price resolution, callback handling, and error cases.
  • Chores

    • Updated external dependency reference.

- Add support for ${io-ratio(input, output)} expressions in YAML configurations
- Extend Context system to parse and resolve price ratio expressions
- Add price callback support to DotrainOrderGui for external price fetching
- Implement comprehensive error handling for price callback failures
- Add backward-compatible API with newWithDeploymentAndPriceCallback method
- Include complete documentation and examples for price ratio usage
- Support dynamic field names, descriptions, and default values with market prices

Resolves rainlanguage#1998: Ability to reference price ratios in field names/descriptions and for default field values
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Aug 14, 2025

Walkthrough

Adds dynamic price ratio support to DotrainOrderGui via a JavaScript price callback and YAML ${io-ratio(input, output)} expressions. Implements placeholder parsing/resolution, updates constructors and state restoration to accept a price callback, interpolates defaults in settings, and adds documentation, examples, and tests. Includes a submodule pointer update.

Changes

Cohort / File(s) Change Summary
GUI price-callback core
crates/js_api/src/gui/mod.rs
Adds price_callback field, new constructor with price callback, resolves PRICE_RATIO placeholders via JS Promise, integrates into deployment processing, and adds tests/docs.
GUI state restoration with price-callback
crates/js_api/src/gui/state_management.rs
Adds new_from_state_with_price_callback; updates existing new_from_state to delegate; wires price_callback through restoration.
YAML context: io-ratio support
crates/settings/src/yaml/context.rs
Adds Context.price_callback, PriceCallback type, new ContextError variants, io-ratio(...) parsing and placeholder emission, propagation across contexts, and tests.
GUI settings interpolation
crates/settings/src/gui.rs
Interpolates field default values using context.interpolate; preserves signatures.
Docs
README.md, docs/price-ratio-functionality.md
Documents io-ratio usage, price-callback constructors, examples, error handling, and migration.
Examples
examples/price-callback-examples.js, examples/price-ratio-example.yaml
Adds JS price callback implementations, caching/fallback patterns, and a comprehensive YAML example using ${io-ratio(...)}.
Submodule
lib/rain.orderbook.interface
Updates submodule reference to a new commit.

Sequence Diagram(s)

sequenceDiagram
  participant App as App (JS)
  participant GUI as DotrainOrderGui (WASM)
  participant Ctx as YAML Context
  participant CB as priceCallback (JS)

  App->>GUI: newWithDeploymentAndPriceCallback(dotrain, deployment, stateCb, priceCb)
  GUI->>Ctx: Build Context (with price_callback id)
  Ctx-->>GUI: Parsed deployment with ${io-ratio(...)} placeholders
  GUI->>GUI: resolve_price_ratios(...) on fields/names/descriptions/defaults
  loop For each io-ratio(input, output)
    GUI->>CB: priceCallback(inputAddr, outputAddr)
    CB-->>GUI: Promise -> "priceString"
    GUI->>GUI: Substitute placeholder with priceString
  end
  GUI-->>App: Initialized GUI instance
Loading
sequenceDiagram
  participant App as App (JS)
  participant GUI as DotrainOrderGui (WASM)
  participant Restore as State Restoration

  App->>GUI: newFromStateWithPriceCallback(dotrain, serialized, stateCb, priceCb)
  GUI->>Restore: Decode + verify + rebuild GUI state
  Restore-->>GUI: State with price_callback wired
  GUI-->>App: Restored GUI instance
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Assessment against linked issues

Objective Addressed Explanation
Specify ${io-ratio(input, output)} in YAML for names/descriptions/defaults (#1998)
Inject JS price callback when creating DotrainOrderGui (#1998)
Resolve io-ratio using callback, including error handling (#1998)

Assessment against linked issues: Out-of-scope changes

Code Change Explanation
Submodule pointer update (lib/rain.orderbook.interface) Not referenced in the objective; unrelated to io-ratio or GUI price callback functionality.

Possibly related PRs

Suggested labels

rust, test

Suggested reviewers

  • findolor
  • 0xgleb
  • hardyjosh

Tip

🔌 Remote MCP (Model Context Protocol) integration is now available!

Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 21

🔭 Outside diff range comments (5)
crates/settings/src/gui.rs (1)

842-858: Default interpolation should degrade gracefully when no price callback is provided

Parsing GUI YAML currently fails if a default contains an io-ratio expression and the Context has no price callback. Catch ContextError::NoPriceCallback and preserve the raw template (so non-GUI consumers can parse) while still propagating other interpolation errors.

Files to change:

  • crates/settings/src/gui.rs — fields parsing (around the interpolation block, ~lines 815–860)
  • Consider also guarding name/description interpolations in the same area (lines ~815–825) if you want to avoid premature failures there as well.

Suggested patch:

-                        let default = optional_string(field_yaml, "default");
-                        let interpolated_default = default.map(|default| context.interpolate(&default)).transpose()?;
+                        let default = optional_string(field_yaml, "default");
+                        let interpolated_default = match default {
+                            Some(d) if d.contains("${") => match context.interpolate(&d) {
+                                Ok(s) => Some(s),
+                                // If price callback isn't available at parse time, keep the raw template.
+                                Err(crate::yaml::context::ContextError::NoPriceCallback) => Some(d),
+                                Err(e) => return Err(e.into()),
+                            },
+                            Some(d) => Some(d),
+                            None => None,
+                        };

If desirable, apply the same pattern to interpolated_name/interpolated_description to avoid similar failures there.

crates/js_api/src/gui/state_management.rs (3)

1-11: Missing imports for base64 and flate2 types used in this module.

URL_SAFE, GzEncoder, and GzDecoder are referenced but not imported here. use super::* won’t bring these into scope because they are not re-exported from the parent module.

Apply this diff near the top of the file:

 use wasm_bindgen::JsValue;
 
+use base64::{engine::general_purpose::URL_SAFE, Engine};
+use flate2::{read::GzDecoder, write::GzEncoder, Compression};

Also applies to: 185-191, 266-268


331-337: Improve naming clarity for destructured tuple (nit).

is_input is a VaultType, not a boolean. Consider renaming to vault_type, and index to token_key to more clearly represent what’s passed to update_vault_id.

-        for ((is_input, index), vault_id) in state.vault_ids {
+        for ((vault_type, token_key), vault_id) in state.vault_ids {
             dotrain_order_gui
                 .dotrain_order
                 .dotrain_yaml()
                 .get_order_for_gui_deployment(&order_key, &state.selected_deployment)
-                .and_then(|mut order| order.update_vault_id(is_input, index, vault_id))?;
+                .and_then(|mut order| order.update_vault_id(vault_type, token_key, vault_id))?;
         }

423-439: get_all_gui_config returns unresolved ${io-ratio(...)} — add a price-resolution variant

getAllGuiConfig currently calls self.get_current_deployment() (returns unresolved values). There is an async resolver get_current_deployment_with_price_resolution at crates/js_api/src/gui/mod.rs:881 that will resolve ${io-ratio(...)} when a price_callback is present.

Files/locations to update:

  • crates/js_api/src/gui/state_management.rs — get_all_gui_config (lines ~423–439): either
    • make this method async and call get_current_deployment_with_price_resolution().await when self.price_callback.is_some(), or
    • add a new async wasm-exported method (e.g., getAllGuiConfigWithPriceResolution) that awaits get_current_deployment_with_price_resolution and returns the resolved AllGuiConfig (non-breaking).
  • crates/js_api/src/gui/mod.rs:881 — contains existing async resolver to use.
  • crates/js_api/src/gui/field_values.rs (get_all_field_definitions, ~line 345) — ensure it consumes the resolved deployment if you change get_all_gui_config to use the resolved deployment.

Recommendation: prefer adding a new async wasm method getAllGuiConfigWithPriceResolution (non-breaking). I can prepare a patch for either approach — tell me which you prefer.

crates/js_api/src/gui/mod.rs (1)

174-193: Signature mismatch: new_with_deployment doc/tests pass a price_callback, but the function accepts only 3 params.

Tests and examples call new_with_deployment with four arguments (including price_callback), but the function signature exposes only three. Fix by adding the optional price_callback parameter and forwarding it to new_with_deployment_and_price_callback.

Apply this diff:

 pub async fn new_with_deployment(
     #[wasm_export(param_description = "Complete dotrain YAML content with all configurations")]
     dotrain: String,
     #[wasm_export(
         param_description = "Key of the deployment to activate (must exist in YAML)"
     )]
     selected_deployment: String,
     #[wasm_export(param_description = "Optional function called on state changes. \
         After a state change (deposit, field value, vault id, select token, etc.), the callback is called with the new state. \
         This is useful for auto-saving the state of the GUI across sessions.")]
     state_update_callback: Option<js_sys::Function>,
-) -> Result<DotrainOrderGui, GuiError> {
-    Self::new_with_deployment_and_price_callback(
-        dotrain,
-        selected_deployment,
-        state_update_callback,
-        None,
-    )
+) -> Result<DotrainOrderGui, GuiError> {
+    Self::new_with_deployment_and_price_callback(
+        dotrain,
+        selected_deployment,
+        state_update_callback,
+        price_callback,
+    )
     .await
 }

And add the new parameter:

-    state_update_callback: Option<js_sys::Function>,
+    state_update_callback: Option<js_sys::Function>,
+    #[wasm_export(param_description = "Optional function for fetching price ratios. \
+        Should accept (inputTokenAddress, outputTokenAddress) and return a Promise<string> with the price ratio.")]
+    price_callback: Option<js_sys::Function>,

Also applies to: 1522-1551, 1533-1542

📜 Review details

Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these settings in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between f41a7be and 572f6b7.

📒 Files selected for processing (9)
  • README.md (1 hunks)
  • crates/js_api/src/gui/mod.rs (12 hunks)
  • crates/js_api/src/gui/state_management.rs (2 hunks)
  • crates/settings/src/gui.rs (2 hunks)
  • crates/settings/src/yaml/context.rs (9 hunks)
  • docs/price-ratio-functionality.md (1 hunks)
  • examples/price-callback-examples.js (1 hunks)
  • examples/price-ratio-example.yaml (1 hunks)
  • lib/rain.orderbook.interface (1 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-07-04T09:02:57.301Z
Learnt from: findolor
PR: rainlanguage/rain.orderbook#1956
File: crates/common/src/fuzz/mod.rs:64-64
Timestamp: 2025-07-04T09:02:57.301Z
Learning: In rainlanguage/rain.orderbook, user findolor prefers to limit type consistency changes to only the parts directly related to the current work scope. For example, when updating chain_id fields from u64 to u32 in fuzz-related code, unrelated files like tauri-app wallet commands can remain as u64 if they serve different purposes and aren't part of the current changes.

Applied to files:

  • lib/rain.orderbook.interface
📚 Learning: 2025-07-16T10:40:05.717Z
Learnt from: findolor
PR: rainlanguage/rain.orderbook#2000
File: crates/common/src/raindex_client/vaults.rs:183-183
Timestamp: 2025-07-16T10:40:05.717Z
Learning: In the rainlanguage/rain.orderbook codebase, user findolor considers breaking changes from Option<U256> to U256 for required fields like decimals in RaindexVaultToken to be acceptable and safe, even when they affect multiple usage sites across the codebase.

Applied to files:

  • lib/rain.orderbook.interface
📚 Learning: 2025-06-18T12:54:47.807Z
Learnt from: findolor
PR: rainlanguage/rain.orderbook#1938
File: crates/js_api/src/raindex/orders.rs:1-11
Timestamp: 2025-06-18T12:54:47.807Z
Learning: In the rain.orderbook repository, findolor prefers to keep unused imports in place when they will be used in upcoming follow-up PRs, rather than removing and re-adding them during incremental development.

Applied to files:

  • lib/rain.orderbook.interface
📚 Learning: 2025-05-19T13:40:56.080Z
Learnt from: findolor
PR: rainlanguage/rain.orderbook#1858
File: crates/subgraph/src/orderbook_client/mod.rs:54-58
Timestamp: 2025-05-19T13:40:56.080Z
Learning: The `wasm_bindgen_utils` crate in the Rain Orderbook project handles conditional compilation for `JsValue` and `JsError` internally, allowing `impl From<Error> for JsValue` to work on non-WASM targets without explicit cfg guards.

Applied to files:

  • crates/js_api/src/gui/mod.rs
🧬 Code Graph Analysis (3)
crates/settings/src/yaml/context.rs (2)
crates/settings/src/yaml/dotrain.rs (1)
  • new (79-107)
examples/price-callback-examples.js (1)
  • result (203-212)
crates/js_api/src/gui/state_management.rs (1)
crates/common/src/dotrain_order.rs (1)
  • dotrain (360-362)
crates/js_api/src/gui/mod.rs (2)
examples/price-callback-examples.js (1)
  • result (203-212)
crates/settings/src/yaml/context.rs (1)
  • parts (108-111)
🪛 LanguageTool
docs/price-ratio-functionality.md

[grammar] ~1-~1: Use correct spacing
Context: ...e Ratio Functionality in DotrainOrderGui ## Overview The DotrainOrderGui now suppor...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~3-~3: Use correct spacing
Context: ...ionality in DotrainOrderGui ## Overview The DotrainOrderGui now supports dynamic...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~5-~5: Use correct spacing
Context: ...rategies more dynamic and user-friendly. ## Key Features - **Dynamic Price Resoluti...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~7-~7: Use correct spacing
Context: ...amic and user-friendly. ## Key Features - Dynamic Price Resolution: Automaticall...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~9-~9: There might be a mistake here.
Context: ...ch current market prices for token pairs - YAML Integration: Use `${io-ratio(inpu...

(QB_NEW_EN_OTHER)


[grammar] ~12-~12: There might be a mistake here.
Context: ...rations continue to work without changes ## Usage ### JavaScript Integration When ...

(QB_NEW_EN_OTHER)


[grammar] ~14-~14: Use correct spacing
Context: ...ntinue to work without changes ## Usage ### JavaScript Integration When creating a ...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~16-~16: Use correct spacing
Context: ...es ## Usage ### JavaScript Integration When creating a DotrainOrderGui instance...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~18-~18: Use correct spacing
Context: ...ance, provide a price callback function: javascript // Define your price callback function const priceCallback = async (inputTokenAddress, outputTokenAddress) => { try { // Example using SushiSwap API const response = await fetch( `https://api.sushi.com/price/${inputTokenAddress}/${outputTokenAddress}` ); const data = await response.json(); return data.ratio.toString(); } catch (error) { throw new Error(`Failed to fetch price: ${error.message}`); } }; // Create GUI instance with price callback const result = await DotrainOrderGui.newWithDeploymentAndPriceCallback( dotrainYaml, "mainnet-deployment", (serializedState) => { localStorage.setItem('orderState', serializedState); }, priceCallback ); if (result.error) { console.error("Failed to initialize GUI:", result.error.readableMsg); } else { const gui = result.value; // Use the GUI instance... } ### YAML Configuration Examples #### 1. Def...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~53-~53: Use correct spacing
Context: .... } ### YAML Configuration Examples #### 1. Default Field Values yaml deploym...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~55-~55: Use correct spacing
Context: ...n Examples #### 1. Default Field Values yaml deployments: auction-dca: name: Auction DCA Strategy description: Dollar-cost averaging using auctions fields: - binding: initial-io name: Kickoff ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol} description: | The initial ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol} to kickoff the first auction. This ratio is calculated in the same way as the baseline ratio. It must be greater than the baseline ratio, regardless of what you are selling or buying. default: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} #### 2. Field Names and Descriptions ```yaml...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~73-~73: Use correct spacing
Context: ...`` #### 2. Field Names and Descriptions yaml deployments: limit-order: name: Limit Order description: Deploy a limit order fields: - binding: fixed-io name: ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol} description: | Fixed exchange rate (${order.inputs.0.token.symbol} received per 1 ${order.outputs.0.token.symbol} sold). Current market price: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} Set your desired rate above or below the current market price. #### 3. Complex Strategy Example ```yaml dep...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~91-~91: Use correct spacing
Context: ...e. #### 3. Complex Strategy Example yaml deployments: dsf-strategy: name: Dutch Auction Strategy description: Automated Dutch auction with dynamic pricing fields: - binding: start-ratio name: Starting ${order.inputs.0.token.symbol}/${order.outputs.0.token.symbol} Ratio description: | Starting price for the Dutch auction. Current market rate: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} Recommended: Set 5-10% above market rate for better execution. default: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} - binding: end-ratio name: Ending ${order.inputs.0.token.symbol}/${order.outputs.0.token.symbol} Ratio description: | Minimum acceptable price for the Dutch auction. Current market rate: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} Recommended: Set at or slightly below market rate. ``` ## Price Callback Implementation ### Requi...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~119-~119: Use correct spacing
Context: ...e. ``` ## Price Callback Implementation ### Requirements Your price callback functi...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~121-~121: Use correct spacing
Context: ...allback Implementation ### Requirements Your price callback function must: 1. *...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~123-~123: Use correct spacing
Context: ...ents Your price callback function must: 1. Accept two parameters: `inputTokenAddr...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~125-~125: There might be a mistake here.
Context: ... and outputTokenAddress (both strings) 2. Return a Promise: That resolves to a s...

(QB_NEW_EN_OTHER)


[grammar] ~127-~127: There might be a mistake here.
Context: ... meaningful error messages for debugging ### Example Implementations #### Using Sush...

(QB_NEW_EN_OTHER)


[grammar] ~129-~129: Use correct spacing
Context: ...r debugging ### Example Implementations #### Using SushiSwap API ```javascript const...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~131-~131: Use correct spacing
Context: ...mplementations #### Using SushiSwap API javascript const sushiPriceCallback = async (inputTokenAddress, outputTokenAddress) => { const response = await fetch( `https://api.sushi.com/price/${inputTokenAddress}/${outputTokenAddress}` ); if (!response.ok) { throw new Error(`SushiSwap API error: ${response.status}`); } const data = await response.json(); return data.price.toString(); }; #### Using 1inch API ```javascript const one...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~148-~148: Use correct spacing
Context: ...toString(); }; #### Using 1inch API javascript const oneInchPriceCallback = async (inputTokenAddress, outputTokenAddress) => { const response = await fetch( https://api.1inch.io/v5.0/1/quote?fromTokenAddress=${inputTokenAddress}&toTokenAddress=${outputTokenAddress}&amount=1000000000000000000 ); const data = await response.json(); const ratio = data.toTokenAmount / data.fromTokenAmount; return ratio.toString(); }; ``` #### Using Multiple Sources with Fallback ``...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~162-~162: Use correct spacing
Context: ...### Using Multiple Sources with Fallback javascript const robustPriceCallback = async (inputTokenAddress, outputTokenAddress) => { const sources = [ () => fetchFromSushiSwap(inputTokenAddress, outputTokenAddress), () => fetchFromUniswap(inputTokenAddress, outputTokenAddress), () => fetchFromOneInch(inputTokenAddress, outputTokenAddress), ]; for (const source of sources) { try { return await source(); } catch (error) { console.warn(`Price source failed: ${error.message}`); } } throw new Error('All price sources failed'); }; ## Error Handling The system provides comp...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~184-~184: Use correct spacing
Context: ...rces failed'); }; ``` ## Error Handling The system provides comprehensive error ...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~186-~186: Use correct spacing
Context: ...m provides comprehensive error handling: ### Common Error Types 1. **No Price Callba...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~188-~188: Use correct spacing
Context: ... error handling: ### Common Error Types 1. No Price Callback: When `${io-ratio(.....

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~190-~190: There might be a mistake here.
Context: ...)}` is used but no callback was provided 2. Invalid Token Addresses: When token ad...

(QB_NEW_EN)


[grammar] ~191-~191: There might be a mistake here.
Context: ...n token addresses are empty or malformed 3. Network Errors: When the price callbac...

(QB_NEW_EN)


[grammar] ~192-~192: There might be a mistake here.
Context: ...ice callback fails due to network issues 4. Invalid Response: When the callback re...

(QB_NEW_EN)


[grammar] ~193-~193: There might be a mistake here.
Context: ...lback returns non-string or empty values 5. Invalid Expression: When the `io-ratio...

(QB_NEW_EN)


[grammar] ~194-~194: Use correct spacing
Context: ... When the io-ratio syntax is incorrect ### Error Messages All errors include user-...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~196-~196: Use correct spacing
Context: ... syntax is incorrect ### Error Messages All errors include user-friendly message...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~198-~198: Use correct spacing
Context: ...l errors include user-friendly messages: javascript // Example error handling try { const gui = await DotrainOrderGui.newWithDeploymentAndPriceCallback( yaml, deployment, stateCallback, priceCallback ); } catch (error) { if (error.message.includes('Price callback')) { console.error('Price fetching failed:', error.message); // Show user-friendly message about network connectivity } else { console.error('GUI initialization failed:', error.message); } } ## Best Practices ### 1. Robust Price Call...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~216-~216: Use correct spacing
Context: ...r.message); } } ``` ## Best Practices ### 1. Robust Price Callbacks - Implement t...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~218-~218: Use correct spacing
Context: ...Practices ### 1. Robust Price Callbacks - Implement timeout handling - Use multipl...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~220-~220: There might be a mistake here.
Context: ... Callbacks - Implement timeout handling - Use multiple price sources with fallback...

(QB_NEW_EN_OTHER)


[grammar] ~223-~223: There might be a mistake here.
Context: ... token addresses before making API calls ### 2. YAML Configuration - Always provide ...

(QB_NEW_EN_OTHER)


[grammar] ~225-~225: Use correct spacing
Context: ...ing API calls ### 2. YAML Configuration - Always provide fallback values for criti...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~227-~227: There might be a mistake here.
Context: ...vide fallback values for critical fields - Use descriptive field names and descript...

(QB_NEW_EN_OTHER)


[grammar] ~230-~230: There might be a mistake here.
Context: ...imiting when using multiple price ratios ### 3. User Experience - Show loading state...

(QB_NEW_EN_OTHER)


[grammar] ~232-~232: Use correct spacing
Context: ...ple price ratios ### 3. User Experience - Show loading states while fetching price...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~234-~234: There might be a mistake here.
Context: ...how loading states while fetching prices - Provide clear error messages for price f...

(QB_NEW_EN_OTHER)


[grammar] ~235-~235: There might be a mistake here.
Context: ...- Provide clear error messages for price fetching failures - Allow users to manua...

(QB_NEW_EN_OTHER)


[grammar] ~235-~235: There might be a mistake here.
Context: ...ror messages for price fetching failures - Allow users to manually override price r...

(QB_NEW_EN_OTHER)


[grammar] ~237-~237: There might be a mistake here.
Context: ...ful price fetches to improve performance ## Migration Guide ### From Hardcoded Valu...

(QB_NEW_EN_OTHER)


[grammar] ~239-~239: Use correct spacing
Context: ... improve performance ## Migration Guide ### From Hardcoded Values Before: ```ya...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~241-~241: Use correct spacing
Context: ...gration Guide ### From Hardcoded Values Before: ```yaml - binding: initial-io ...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~243-~243: Use correct spacing
Context: ...de ### From Hardcoded Values Before: yaml - binding: initial-io name: Initial USDC per WETH default: "1800.0" After: ```yaml - binding: initial-io ...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~250-~250: Use correct spacing
Context: ...r WETH default: "1800.0" **After:**yaml - binding: initial-io name: Initial ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol} description: Current market price: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} default: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} ``` ### Updating JavaScript Code Before: ``...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~258-~258: Use correct spacing
Context: ...ress)} ### Updating JavaScript Code **Before:**javascript const gui = awai...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~260-~260: Use correct spacing
Context: ... ### Updating JavaScript Code Before: javascript const gui = await DotrainOrderGui.newWithDeployment(yaml, deployment, stateCallback); After: ```javascript const gui = await...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~265-~265: Use correct spacing
Context: ...eployment, stateCallback); **After:**javascript const gui = await DotrainOrderGui.newWithDeploymentAndPriceCallback( yaml, deployment, stateCallback, priceCallback ); ``` ## Troubleshooting ### Common Issues 1. *...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~272-~272: Use correct spacing
Context: ...priceCallback ); ``` ## Troubleshooting ### Common Issues 1. **"Price callback not ...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~274-~274: Use correct spacing
Context: ...` ## Troubleshooting ### Common Issues 1. "Price callback not available": Ensure...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~276-~276: There might be a mistake here.
Context: ...hDeploymentAndPriceCallbackinstead ofnewWithDeployment` 2. "Invalid io-ratio expression": Check t...

(QB_NEW_EN)


[grammar] ~277-~277: There might be a mistake here.
Context: ...valid io-ratio expression"**: Check the syntax: ${io-ratio(input_path, output_path)} 3. "Price callback failed": Check network...

(QB_NEW_EN_OTHER)


[grammar] ~278-~278: There might be a mistake here.
Context: ...k network connectivity and API endpoints 4. Empty price values: Ensure your callba...

(QB_NEW_EN_OTHER)


[grammar] ~279-~279: There might be a mistake here.
Context: ...your callback returns a non-empty string ### Debug Tips - Test your price callback f...

(QB_NEW_EN_OTHER)


[grammar] ~281-~281: Use correct spacing
Context: ...turns a non-empty string ### Debug Tips - Test your price callback function indepe...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~283-~283: There might be a mistake here.
Context: ...ur price callback function independently - Use browser developer tools to inspect n...

(QB_NEW_EN_OTHER)


[grammar] ~285-~285: There might be a mistake here.
Context: ...per tools to inspect network requests - Check console for detailed error messages - V...

(QB_NEW_EN)


[grammar] ~285-~285: There might be a mistake here.
Context: ...heck console for detailed error messages - Validate token addresses are correct for...

(QB_NEW_EN_OTHER)


[grammar] ~286-~286: Insert the missing word
Context: ...ror messages - Validate token addresses are correct format (0x...)

(QB_NEW_EN_OTHER_ERROR_IDS_32)


[grammar] ~286-~286: There might be a mistake here.
Context: ...ken addresses are correct format (0x...)

(QB_NEW_EN_OTHER)

README.md

[grammar] ~26-~26: Use correct spacing
Context: ... frontends for Raindex. ## New Features ### 🎯 Dynamic Price Ratio Support The Dotr...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~28-~28: There might be a mistake here.
Context: ...ures ### 🎯 Dynamic Price Ratio Support The DotrainOrderGui now supports dynamic ...

(QB_NEW_EN)


[grammar] ~30-~30: Use correct spacing
Context: ...market prices without hardcoding values. Key Benefits: - **Automatic Price Fetc...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~32-~32: There might be a mistake here.
Context: ...hout hardcoding values. Key Benefits: - Automatic Price Fetching: Strategies c...

(QB_NEW_EN)


[grammar] ~33-~33: There might be a mistake here.
Context: ... automatically use current market prices - User-Friendly Defaults: No more manual...

(QB_NEW_EN_OTHER)


[grammar] ~34-~34: There might be a mistake here.
Context: ...: No more manual price lookups for users - Flexible Integration: Works with any p...

(QB_NEW_EN_OTHER)


[grammar] ~35-~35: There might be a mistake here.
Context: ... API (SushiSwap, 1inch, CoinGecko, etc.) - Error Handling: Comprehensive error ha...

(QB_NEW_EN_OTHER)


[grammar] ~36-~36: Use correct spacing
Context: ...ling for network issues and invalid data Quick Example: ```yaml fields: - bin...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~38-~38: Use correct spacing
Context: ...ssues and invalid data Quick Example: yaml fields: - binding: initial-io name: Kickoff ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol} description: Current market price: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} default: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} javascript const priceCallback = async (inputTokenAddress, outputTokenAddress) => { const response = await fetch(`/api/price/${inputTokenAddress}/${outputTokenAddress}`); return (await response.json()).ratio.toString(); }; const gui = await DotrainOrderGui.newWithDeploymentAndPriceCallback( yaml, deployment, stateCallback, priceCallback ); 📚 Learn More: See [docs/price-ratio...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~58-~58: There might be a mistake here.
Context: ...(examples/) for implementation examples. ## Setup for local development ### Environm...

(QB_NEW_EN)

🪛 markdownlint-cli2 (0.17.2)
docs/price-ratio-functionality.md

244-244: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


251-251: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


261-261: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)


266-266: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

README.md

33-33: Lists should be surrounded by blank lines

(MD032, blanks-around-lists)


39-39: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🪛 YAMLlint (1.37.1)
examples/price-ratio-example.yaml

[error] 47-47: trailing spaces

(trailing-spaces)


[error] 49-49: trailing spaces

(trailing-spaces)


[error] 50-50: trailing spaces

(trailing-spaces)


[error] 52-52: trailing spaces

(trailing-spaces)


[error] 60-60: trailing spaces

(trailing-spaces)


[error] 62-62: trailing spaces

(trailing-spaces)


[error] 83-83: trailing spaces

(trailing-spaces)


[error] 85-85: trailing spaces

(trailing-spaces)


[error] 87-87: trailing spaces

(trailing-spaces)


[error] 94-94: trailing spaces

(trailing-spaces)


[error] 95-95: trailing spaces

(trailing-spaces)


[error] 100-100: trailing spaces

(trailing-spaces)


[error] 110-110: trailing spaces

(trailing-spaces)


[error] 112-112: trailing spaces

(trailing-spaces)


[error] 120-120: trailing spaces

(trailing-spaces)


[error] 122-122: trailing spaces

(trailing-spaces)


[error] 130-130: trailing spaces

(trailing-spaces)


[error] 131-131: trailing spaces

(trailing-spaces)


[error] 147-147: trailing spaces

(trailing-spaces)


[error] 149-149: trailing spaces

(trailing-spaces)


[error] 157-157: trailing spaces

(trailing-spaces)


[error] 158-158: trailing spaces

(trailing-spaces)


[error] 174-174: trailing spaces

(trailing-spaces)


[error] 175-175: trailing spaces

(trailing-spaces)


[error] 182-182: trailing spaces

(trailing-spaces)


[error] 184-184: trailing spaces

(trailing-spaces)


[error] 193-193: trailing spaces

(trailing-spaces)


[error] 194-194: trailing spaces

(trailing-spaces)


[error] 202-202: trailing spaces

(trailing-spaces)


[error] 203-203: trailing spaces

(trailing-spaces)


[error] 205-205: trailing spaces

(trailing-spaces)

🔇 Additional comments (9)
lib/rain.orderbook.interface (2)

1-1: Submodule pointer bump only — LGTM

This is a straightforward submodule commit update with no direct code changes in the superproject.


1-1: Confirm the submodule bump is intentional and aligned with the ${io-ratio(...)} work

I checked the lib/rain.orderbook.interface submodule and the referenced commit 9d6d8cc:

  • .gitmodules contains lib/rain.orderbook.interface (submodule present).
  • git submodule status shows lib/rain.orderbook.interface at 9d6d8cc2405031ff31ee2db75b15b74ef968c88c.
  • GitHub commit: rainlanguage/rain.raindex.interface@9d6d8cc — Merge PR #38, message "bump i9r int". Files changed: lib/rain.interpreter.interface (lib/rain.math.float changed +1 −1).

I did not see explicit interface/docs changes for the ${io-ratio(...)} feature in that commit. Please confirm this bump was intentional and that the required interface/docs updates for the price-ratio/io-ratio work are included (or point me to the expected commit/tag). I can also cross-check the submodule commit vs the expected tag/branch and summarize relevant changes for the PR release notes if you’d like.

README.md (1)

53-56: API usage looks consistent with the new constructor

The example correctly uses DotrainOrderGui.newWithDeploymentAndPriceCallback and shows the expected priceCallback shape returning a string. Good addition.

crates/settings/src/yaml/context.rs (2)

23-29: Context now carries a price callback — good extension point

Adding PriceCallback and storing it in Context enables downstream layers (JS API) to drive dynamic resolution. The struct is minimal and forward-compatible.


301-305: LGTM: Early detection of io-ratio(...) expressions

Short-circuiting io-ratio(...) before the dot-path resolution is a clean way to extend the grammar without impacting existing paths.

crates/js_api/src/gui/state_management.rs (2)

220-229: Good: Backward-compatible delegating constructor preserved.

new_from_state correctly delegates to new_from_state_with_price_callback with None, preserving the old API while enabling the new one.


255-297: Wiring price_callback during state restoration looks correct.

The new new_from_state_with_price_callback accepts an optional JS function and stores it on the restored DotrainOrderGui instance. This aligns with the new constructor path in mod.rs.

crates/js_api/src/gui/mod.rs (2)

149-163: Docs for newWithDeployment are now accurate, provided the signature is updated.

Once the extra parameter is added per earlier comment, the example passing priceCallback here will be correct and helpful.


1555-1570: Update tests to expect Err when PRICE_RATIO_PLACEHOLDER is present and no price callback is provided

Current behavior (verified): resolve_price_ratios returns the input unchanged when self.price_callback is None and the tests reflect this lenient behavior. If you change resolve_price_ratios to return an error when placeholders exist and no callback is provided, update tests as follows.

  • File: crates/js_api/src/gui/mod.rs — test_price_ratio_placeholder_resolution (≈ lines 1555–1570)

    • Replace:
      let input_with_placeholder = "Price: PRICE_RATIO_PLACEHOLDER:0x123:0x456";
      let result = gui.resolve_price_ratios(input_with_placeholder).await;
      assert!(result.is_ok());
      assert_eq!(result.unwrap(), input_with_placeholder);
      
    • With:
      let input_with_placeholder = "Price: PRICE_RATIO_PLACEHOLDER:0x123:0x456";
      let result = gui.resolve_price_ratios(input_with_placeholder).await;
      assert!(result.is_err());
      
  • File: crates/js_api/src/gui/mod.rs — test_price_ratio_error_handling (≈ lines 1588–1601)

    • No change required; this test already asserts Err for malformed/empty placeholders.

Comment on lines +686 to +784
/// Resolves price ratio placeholders in a string by calling the JavaScript price callback.
///
/// This method looks for PRICE_RATIO_PLACEHOLDER patterns and replaces them with actual
/// price data fetched via the JavaScript callback function.
pub async fn resolve_price_ratios(&self, input: &str) -> Result<String, GuiError> {
if let Some(price_callback) = &self.price_callback {
let mut result = input.to_string();

// Find all PRICE_RATIO_PLACEHOLDER patterns
while let Some(start) = result.find("PRICE_RATIO_PLACEHOLDER:") {
if let Some(end) = result[start..].find(' ') {
let end = start + end;
let placeholder = &result[start..end];

// Extract input and output addresses from placeholder
let parts: Vec<&str> = placeholder.split(':').collect();
if parts.len() == 3 {
let input_address = parts[1];
let output_address = parts[2];

// Validate addresses before calling callback
if input_address.is_empty() || output_address.is_empty() {
return Err(GuiError::JsError(
"Invalid token addresses in price ratio expression".to_string(),
));
}

// Call the JavaScript price callback with error handling
match self
.call_price_callback(input_address, output_address)
.await
{
Ok(price_ratio) => {
// Validate the returned price ratio
if price_ratio.is_empty() {
return Err(GuiError::JsError(
"Price callback returned empty value".to_string(),
));
}

// Replace the placeholder with the actual price
result.replace_range(start..end, &price_ratio);
}
Err(e) => {
// Return the error instead of replacing with error text
return Err(e);
}
}
} else {
return Err(GuiError::JsError(format!(
"Invalid price ratio placeholder format: {}",
placeholder
)));
}
} else {
// No space found, placeholder might be at the end of string
let placeholder = &result[start..];
let parts: Vec<&str> = placeholder.split(':').collect();
if parts.len() == 3 {
let input_address = parts[1];
let output_address = parts[2];

// Validate addresses
if input_address.is_empty() || output_address.is_empty() {
return Err(GuiError::JsError(
"Invalid token addresses in price ratio expression".to_string(),
));
}

match self
.call_price_callback(input_address, output_address)
.await
{
Ok(price_ratio) => {
if price_ratio.is_empty() {
return Err(GuiError::JsError(
"Price callback returned empty value".to_string(),
));
}
result.replace_range(start.., &price_ratio);
}
Err(e) => return Err(e),
}
} else {
return Err(GuiError::JsError(format!(
"Invalid price ratio placeholder format: {}",
placeholder
)));
}
break;
}
}

Ok(result)
} else {
// No price callback available, return input as-is
Ok(input.to_string())
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

⚠️ Potential issue

Handle placeholders when no price callback is provided and make parsing more robust.

Current behavior silently returns the input unchanged when no callback is provided, even if placeholders exist. Docs specify an error should be raised in that case. Also, parsing assumes placeholders end at a space, which is brittle (punctuation, line breaks, or end-of-string aren’t handled well).

  • If PRICE_RATIO_PLACEHOLDER: is present and no callback: return a meaningful error.
  • Improve end-of-placeholder detection by stopping at whitespace or common punctuation.

Apply this diff:

-    pub async fn resolve_price_ratios(&self, input: &str) -> Result<String, GuiError> {
-        if let Some(price_callback) = &self.price_callback {
+    pub async fn resolve_price_ratios(&self, input: &str) -> Result<String, GuiError> {
+        let sentinel = "PRICE_RATIO_PLACEHOLDER:";
+        let has_placeholder = input.contains(sentinel);
+        if self.price_callback.is_none() {
+            return if has_placeholder {
+                Err(GuiError::JsError(
+                    "Price callback not provided but price ratio placeholder found. \
+                     Use newWithDeploymentAndPriceCallback or newFromStateWithPriceCallback."
+                        .to_string(),
+                ))
+            } else {
+                Ok(input.to_string())
+            };
+        }
+        if let Some(_cb) = &self.price_callback {
             let mut result = input.to_string();
 
             // Find all PRICE_RATIO_PLACEHOLDER patterns
-            while let Some(start) = result.find("PRICE_RATIO_PLACEHOLDER:") {
-                if let Some(end) = result[start..].find(' ') {
-                    let end = start + end;
+            while let Some(start) = result.find(sentinel) {
+                // Find the end by scanning until whitespace or common punctuation
+                let rel_end = result[start..]
+                    .char_indices()
+                    .find(|(_, ch)| ch.is_whitespace() || matches!(ch, ')' | ']' | '}' | ',' | ';'))
+                    .map(|(i, _)| i);
+                if let Some(rel_end) = rel_end {
+                    let end = start + rel_end;
                     let placeholder = &result[start..end];
 
                     // Extract input and output addresses from placeholder
                     let parts: Vec<&str> = placeholder.split(':').collect();
                     if parts.len() == 3 {
                         let input_address = parts[1];
                         let output_address = parts[2];
 
                         // Validate addresses before calling callback
                         if input_address.is_empty() || output_address.is_empty() {
                             return Err(GuiError::JsError(
                                 "Invalid token addresses in price ratio expression".to_string(),
                             ));
                         }
 
                         // Call the JavaScript price callback with error handling
-                        match self
+                        match self
                             .call_price_callback(input_address, output_address)
                             .await
                         {
                             Ok(price_ratio) => {
                                 // Validate the returned price ratio
                                 if price_ratio.is_empty() {
                                     return Err(GuiError::JsError(
                                         "Price callback returned empty value".to_string(),
                                     ));
                                 }
 
                                 // Replace the placeholder with the actual price
                                 result.replace_range(start..end, &price_ratio);
                             }
                             Err(e) => {
                                 // Return the error instead of replacing with error text
                                 return Err(e);
                             }
                         }
                     } else {
                         return Err(GuiError::JsError(format!(
                             "Invalid price ratio placeholder format: {}",
                             placeholder
                         )));
                     }
                 } else {
-                    // No space found, placeholder might be at the end of string
+                    // Placeholder extends to end of string
                     let placeholder = &result[start..];
                     let parts: Vec<&str> = placeholder.split(':').collect();
                     if parts.len() == 3 {
                         let input_address = parts[1];
                         let output_address = parts[2];
 
                         // Validate addresses
                         if input_address.is_empty() || output_address.is_empty() {
                             return Err(GuiError::JsError(
                                 "Invalid token addresses in price ratio expression".to_string(),
                             ));
                         }
 
                         match self
                             .call_price_callback(input_address, output_address)
                             .await
                         {
                             Ok(price_ratio) => {
                                 if price_ratio.is_empty() {
                                     return Err(GuiError::JsError(
                                         "Price callback returned empty value".to_string(),
                                     ));
                                 }
                                 result.replace_range(start.., &price_ratio);
                             }
                             Err(e) => return Err(e),
                         }
                     } else {
                         return Err(GuiError::JsError(format!(
                             "Invalid price ratio placeholder format: {}",
                             placeholder
                         )));
                     }
-                    break;
+                    break;
                 }
             }
 
             Ok(result)
-        } else {
-            // No price callback available, return input as-is
-            Ok(input.to_string())
-        }
+        }
     }
📝 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.

Suggested change
/// Resolves price ratio placeholders in a string by calling the JavaScript price callback.
///
/// This method looks for PRICE_RATIO_PLACEHOLDER patterns and replaces them with actual
/// price data fetched via the JavaScript callback function.
pub async fn resolve_price_ratios(&self, input: &str) -> Result<String, GuiError> {
if let Some(price_callback) = &self.price_callback {
let mut result = input.to_string();
// Find all PRICE_RATIO_PLACEHOLDER patterns
while let Some(start) = result.find("PRICE_RATIO_PLACEHOLDER:") {
if let Some(end) = result[start..].find(' ') {
let end = start + end;
let placeholder = &result[start..end];
// Extract input and output addresses from placeholder
let parts: Vec<&str> = placeholder.split(':').collect();
if parts.len() == 3 {
let input_address = parts[1];
let output_address = parts[2];
// Validate addresses before calling callback
if input_address.is_empty() || output_address.is_empty() {
return Err(GuiError::JsError(
"Invalid token addresses in price ratio expression".to_string(),
));
}
// Call the JavaScript price callback with error handling
match self
.call_price_callback(input_address, output_address)
.await
{
Ok(price_ratio) => {
// Validate the returned price ratio
if price_ratio.is_empty() {
return Err(GuiError::JsError(
"Price callback returned empty value".to_string(),
));
}
// Replace the placeholder with the actual price
result.replace_range(start..end, &price_ratio);
}
Err(e) => {
// Return the error instead of replacing with error text
return Err(e);
}
}
} else {
return Err(GuiError::JsError(format!(
"Invalid price ratio placeholder format: {}",
placeholder
)));
}
} else {
// No space found, placeholder might be at the end of string
let placeholder = &result[start..];
let parts: Vec<&str> = placeholder.split(':').collect();
if parts.len() == 3 {
let input_address = parts[1];
let output_address = parts[2];
// Validate addresses
if input_address.is_empty() || output_address.is_empty() {
return Err(GuiError::JsError(
"Invalid token addresses in price ratio expression".to_string(),
));
}
match self
.call_price_callback(input_address, output_address)
.await
{
Ok(price_ratio) => {
if price_ratio.is_empty() {
return Err(GuiError::JsError(
"Price callback returned empty value".to_string(),
));
}
result.replace_range(start.., &price_ratio);
}
Err(e) => return Err(e),
}
} else {
return Err(GuiError::JsError(format!(
"Invalid price ratio placeholder format: {}",
placeholder
)));
}
break;
}
}
Ok(result)
} else {
// No price callback available, return input as-is
Ok(input.to_string())
}
}
/// Resolves price ratio placeholders in a string by calling the JavaScript price callback.
///
/// This method looks for PRICE_RATIO_PLACEHOLDER patterns and replaces them with actual
/// price data fetched via the JavaScript callback function.
pub async fn resolve_price_ratios(&self, input: &str) -> Result<String, GuiError> {
let sentinel = "PRICE_RATIO_PLACEHOLDER:";
let has_placeholder = input.contains(sentinel);
if self.price_callback.is_none() {
return if has_placeholder {
Err(GuiError::JsError(
"Price callback not provided but price ratio placeholder found. \
Use newWithDeploymentAndPriceCallback or newFromStateWithPriceCallback."
.to_string(),
))
} else {
Ok(input.to_string())
};
}
if let Some(_cb) = &self.price_callback {
let mut result = input.to_string();
// Find all PRICE_RATIO_PLACEHOLDER patterns
while let Some(start) = result.find(sentinel) {
// Find the end by scanning until whitespace or common punctuation
let rel_end = result[start..]
.char_indices()
.find(|(_, ch)| ch.is_whitespace() || matches!(ch, ')' | ']' | '}' | ',' | ';'))
.map(|(i, _)| i);
if let Some(rel_end) = rel_end {
let end = start + rel_end;
let placeholder = &result[start..end];
// Extract input and output addresses from placeholder
let parts: Vec<&str> = placeholder.split(':').collect();
if parts.len() == 3 {
let input_address = parts[1];
let output_address = parts[2];
// Validate addresses before calling callback
if input_address.is_empty() || output_address.is_empty() {
return Err(GuiError::JsError(
"Invalid token addresses in price ratio expression".to_string(),
));
}
// Call the JavaScript price callback with error handling
match self
.call_price_callback(input_address, output_address)
.await
{
Ok(price_ratio) => {
// Validate the returned price ratio
if price_ratio.is_empty() {
return Err(GuiError::JsError(
"Price callback returned empty value".to_string(),
));
}
// Replace the placeholder with the actual price
result.replace_range(start..end, &price_ratio);
}
Err(e) => {
// Return the error instead of replacing with error text
return Err(e);
}
}
} else {
return Err(GuiError::JsError(format!(
"Invalid price ratio placeholder format: {}",
placeholder
)));
}
} else {
// Placeholder extends to end of string
let placeholder = &result[start..];
let parts: Vec<&str> = placeholder.split(':').collect();
if parts.len() == 3 {
let input_address = parts[1];
let output_address = parts[2];
// Validate addresses
if input_address.is_empty() || output_address.is_empty() {
return Err(GuiError::JsError(
"Invalid token addresses in price ratio expression".to_string(),
));
}
match self
.call_price_callback(input_address, output_address)
.await
{
Ok(price_ratio) => {
if price_ratio.is_empty() {
return Err(GuiError::JsError(
"Price callback returned empty value".to_string(),
));
}
result.replace_range(start.., &price_ratio);
}
Err(e) => return Err(e),
}
} else {
return Err(GuiError::JsError(format!(
"Invalid price ratio placeholder format: {}",
placeholder
)));
}
break;
}
}
Ok(result)
}
}

Comment on lines +786 to +793
/// Calls the JavaScript price callback function to fetch a price ratio.
///
/// This method handles all the complexity of calling JavaScript from Rust, including:
/// - Parameter validation
/// - Promise handling
/// - Error conversion and user-friendly messages
/// - Timeout handling (if the promise never resolves)
async fn call_price_callback(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Docstring claims timeout handling that isn’t implemented.

The method comment lists “Timeout handling (if the promise never resolves)”, but there is no timeout. Either implement a timeout or remove the claim.

Minimal fix (remove the bullet):

     /// - Parameter validation
     /// - Promise handling
     /// - Error conversion and user-friendly messages
-    /// - Timeout handling (if the promise never resolves)

Optional: I can add a Promise.race-based timeout using gloo_timers for WASM targets if desired.

📝 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.

Suggested change
/// Calls the JavaScript price callback function to fetch a price ratio.
///
/// This method handles all the complexity of calling JavaScript from Rust, including:
/// - Parameter validation
/// - Promise handling
/// - Error conversion and user-friendly messages
/// - Timeout handling (if the promise never resolves)
async fn call_price_callback(
/// Calls the JavaScript price callback function to fetch a price ratio.
///
/// This method handles all the complexity of calling JavaScript from Rust, including:
/// - Parameter validation
/// - Promise handling
/// - Error conversion and user-friendly messages
async fn call_price_callback(
🤖 Prompt for AI Agents
In crates/js_api/src/gui/mod.rs around lines 786 to 793, the docstring
incorrectly claims "Timeout handling (if the promise never resolves)" though the
function does not implement any timeout; remove that bullet from the method
comment to keep docs accurate, or if you prefer to add timeout behavior
implement a Promise.race-based timeout for the JS Promise (e.g., use gloo_timers
for WASM to create a timeout future that races the callback promise and
rejects/returns an error on timeout), convert the timeout error into the same
Rust error path, and update the docstring to reflect the actual timeout
semantics.

Comment on lines +833 to +867
// Convert to Promise and await with error handling
let future = JsFuture::from(js_sys::Promise::from(promise));
let result = future.await.map_err(|e| {
let error_msg = if let Some(js_error) = e.dyn_ref::<js_sys::Error>() {
format!("Price callback promise rejected: {}", js_error.message())
} else if let Some(error_str) = e.as_string() {
format!("Price callback failed: {}", error_str)
} else {
format!("Price callback promise failed with unknown error: {:?}", e)
};
GuiError::JsError(error_msg)
})?;

// Convert result to string with validation
let price_str = result.as_string().ok_or_else(|| {
GuiError::JsError(format!(
"Price callback must return a string, got: {:?}",
result
))
})?;

// Validate the returned price string
if price_str.trim().is_empty() {
return Err(GuiError::JsError(
"Price callback returned empty or whitespace-only string".to_string(),
));
}

// Basic validation that the result looks like a number
if price_str.parse::<f64>().is_err() {
return Err(GuiError::JsError(format!(
"Price callback returned invalid number format: '{}'",
price_str
)));
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Validation: enforcing string return type is good; consider accepting numbers too.

Today the callback must resolve to a string. Some users may naturally return a number. Consider accepting numbers by converting via as_f64 or to_string() if the value is numeric, then applying the same validation.

If you want, I can propose a patch that accepts both string and number returns with strict validation.

🤖 Prompt for AI Agents
In crates/js_api/src/gui/mod.rs around lines 833 to 867, the code only accepts a
JS string result from the price callback; update it to also accept numeric JS
values: first try result.as_string(), if Some use that; else if let Some(n) =
result.as_f64() convert n.to_string() to price_str; otherwise return the
existing JsError about expected string/number. After converting, run the same
trim/empty and parse::<f64>() validations as currently implemented. Keep the
existing Promise error mapping unchanged.

Comment on lines +877 to +903
/// Gets the current deployment with price ratio resolution applied to field definitions.
///
/// This method processes the deployment configuration and resolves any price ratio
/// placeholders in field names, descriptions, and default values.
pub async fn get_current_deployment_with_price_resolution(
&self,
) -> Result<GuiDeploymentCfg, GuiError> {
let mut deployment = self.get_current_deployment()?;

// Process each field to resolve price ratios
for field in &mut deployment.fields {
// Resolve price ratios in field name
field.name = self.resolve_price_ratios(&field.name).await?;

// Resolve price ratios in field description
if let Some(description) = &field.description {
field.description = Some(self.resolve_price_ratios(description).await?);
}

// Resolve price ratios in default value
if let Some(default) = &field.default {
field.default = Some(self.resolve_price_ratios(default).await?);
}
}

Ok(deployment)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

⚠️ Potential issue

Export get_current_deployment_with_price_resolution to JS.

This method is currently not wasm-exported, so UIs cannot consume the resolved configuration. Either export it or integrate price resolution into existing getters.

Apply this diff:

-    pub async fn get_current_deployment_with_price_resolution(
+    #[wasm_export(
+        js_name = "getCurrentDeploymentWithPriceResolution",
+        unchecked_return_type = "GuiDeploymentCfg",
+        return_description = "Active deployment with price-ratio placeholders resolved"
+    )]
+    pub async fn get_current_deployment_with_price_resolution(
         &self,
     ) -> Result<GuiDeploymentCfg, GuiError> {
📝 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.

Suggested change
/// Gets the current deployment with price ratio resolution applied to field definitions.
///
/// This method processes the deployment configuration and resolves any price ratio
/// placeholders in field names, descriptions, and default values.
pub async fn get_current_deployment_with_price_resolution(
&self,
) -> Result<GuiDeploymentCfg, GuiError> {
let mut deployment = self.get_current_deployment()?;
// Process each field to resolve price ratios
for field in &mut deployment.fields {
// Resolve price ratios in field name
field.name = self.resolve_price_ratios(&field.name).await?;
// Resolve price ratios in field description
if let Some(description) = &field.description {
field.description = Some(self.resolve_price_ratios(description).await?);
}
// Resolve price ratios in default value
if let Some(default) = &field.default {
field.default = Some(self.resolve_price_ratios(default).await?);
}
}
Ok(deployment)
}
/// Gets the current deployment with price ratio resolution applied to field definitions.
///
/// This method processes the deployment configuration and resolves any price ratio
/// placeholders in field names, descriptions, and default values.
#[wasm_export(
js_name = "getCurrentDeploymentWithPriceResolution",
unchecked_return_type = "GuiDeploymentCfg",
return_description = "Active deployment with price-ratio placeholders resolved"
)]
pub async fn get_current_deployment_with_price_resolution(
&self,
) -> Result<GuiDeploymentCfg, GuiError> {
let mut deployment = self.get_current_deployment()?;
// Process each field to resolve price ratios
for field in &mut deployment.fields {
// Resolve price ratios in field name
field.name = self.resolve_price_ratios(&field.name).await?;
// Resolve price ratios in field description
if let Some(description) = &field.description {
field.description = Some(self.resolve_price_ratios(description).await?);
}
// Resolve price ratios in default value
if let Some(default) = &field.default {
field.default = Some(self.resolve_price_ratios(default).await?);
}
}
Ok(deployment)
}
🤖 Prompt for AI Agents
In crates/js_api/src/gui/mod.rs around lines 877 to 903, the async method
get_current_deployment_with_price_resolution is not exported to JS; add a
wasm-bindgen wrapper that returns a JS Promise/JsValue so the UI can call it.
Create a new #[wasm_bindgen] public function (or method on the exported JS
class) that calls the existing async Rust method via
wasm_bindgen_futures::future_to_promise (or by converting the async Result to a
JsValue with serde_wasm_bindgen), returning a Promise and mapping Rust errors to
JsValue/Error; do not change the existing internal logic—only add the small
exported wrapper to bridge async Result<GuiDeploymentCfg, GuiError> into a
JS-friendly Promise/JsValue.

Comment on lines +60 to 66
ContextError::NoPriceCallback =>
"Price callback is not available. Please provide a price callback function when creating the DotrainOrderGui instance.".to_string(),
ContextError::PriceCallbackError(error) =>
format!("Failed to fetch price data: {}. Please check your price callback implementation and network connectivity.", error),
ContextError::InvalidIoRatioExpression(expr) =>
format!("Invalid io-ratio expression '{}'. Expected format: io-ratio(input_address, output_address)", expr),
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Clarify io-ratio error message to reflect accepted argument types

The message implies literal addresses are expected, but the implementation currently expects path expressions (e.g., order.inputs.0.token.address). Either accept literal 0x addresses in code (recommended) or adjust the message.

Proposed message update if keeping path-only:

-Invalid io-ratio expression '{}'. Expected format: io-ratio(input_address, output_address)
+Invalid io-ratio expression '{}'. Expected format: io-ratio(<path to input address>, <path to output address>), e.g., io-ratio(order.inputs.0.token.address, order.outputs.0.token.address)

Alternatively, see the next comment for enabling literal 0x addresses in code.

📝 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.

Suggested change
ContextError::NoPriceCallback =>
"Price callback is not available. Please provide a price callback function when creating the DotrainOrderGui instance.".to_string(),
ContextError::PriceCallbackError(error) =>
format!("Failed to fetch price data: {}. Please check your price callback implementation and network connectivity.", error),
ContextError::InvalidIoRatioExpression(expr) =>
format!("Invalid io-ratio expression '{}'. Expected format: io-ratio(input_address, output_address)", expr),
}
ContextError::NoPriceCallback =>
"Price callback is not available. Please provide a price callback function when creating the DotrainOrderGui instance.".to_string(),
ContextError::PriceCallbackError(error) =>
format!("Failed to fetch price data: {}. Please check your price callback implementation and network connectivity.", error),
ContextError::InvalidIoRatioExpression(expr) =>
format!("Invalid io-ratio expression '{}'. Expected format: io-ratio(<path to input address>, <path to output address>), e.g., io-ratio(order.inputs.0.token.address, order.outputs.0.token.address)", expr),
}
🤖 Prompt for AI Agents
In crates/settings/src/yaml/context.rs around lines 60 to 66, the io-ratio error
message incorrectly implies literal 0x addresses are expected; update the
message to state that the expression must be a path expression (e.g.,
"order.inputs.0.token.address") and show the expected format
"io-ratio(input_path, output_path)"; alternatively (preferred) modify the
parsing logic to also accept literal 0x addresses by detecting hex-address
tokens and normalizing them into the same internal representation, then adjust
the error text to mention both supported forms and include short examples for
each.

tokens:
usdc:
network: mainnet
address: 0xA0b86a33E6441b8e776f89d2b5B977c737C5e5b5
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Replace incorrect USDC mainnet address

The USDC address on Ethereum mainnet should be 0xA0b86991C6218B36C1d19D4a2e9Eb0cE3606eB48 (checksummed). The current value is invalid and will cause price lookups to fail.

-    address: 0xA0b86a33E6441b8e776f89d2b5B977c737C5e5b5
+    address: 0xA0b86991C6218B36C1d19D4a2e9Eb0cE3606eB48

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Searching repo for the addresses and USDC mentions..."
rg -n --hidden --no-ignore -S "A0b86a33|A0b86991|USDC" || true

echo
echo "If examples/price-ratio-example.yaml exists, show its contents (first 200 lines):"
if [ -f "examples/price-ratio-example.yaml" ]; then
  sed -n '1,200p' examples/price-ratio-example.yaml
else
  echo "examples/price-ratio-example.yaml not found"
fi

Length of output: 14329


Replace incorrect USDC mainnet address in examples

The USDC address 0xA0b86a33E6441b8e776f89d2b5B977c737C5e5b5 is a typo and will break price lookups. Replace it with the correct checksummed mainnet address: 0xA0b86991C6218B36C1d19D4a2e9Eb0cE3606eB48.

Files to update:

  • examples/price-ratio-example.yaml:13 — address field
  • examples/price-callback-examples.js:232 — USDC constant

Note: the correct address is already used elsewhere (e.g., crates/js_api/src/gui/mod.rs:1462).

Diffs:

--- examples/price-ratio-example.yaml
-    address: 0xA0b86a33E6441b8e776f89d2b5B977c737C5e5b5
+    address: 0xA0b86991C6218B36C1d19D4a2e9Eb0cE3606eB48
--- examples/price-callback-examples.js
-  const USDC = '0xA0b86a33E6441b8e776f89d2b5B977c737C5e5b5';
+  const USDC = '0xA0b86991C6218B36C1d19D4a2e9Eb0cE3606eB48';
📝 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.

Suggested change
address: 0xA0b86a33E6441b8e776f89d2b5B977c737C5e5b5
address: 0xA0b86991C6218B36C1d19D4a2e9Eb0cE3606eB48
🤖 Prompt for AI Agents
In examples/price-ratio-example.yaml around line 13, the USDC mainnet address is
a typo (0xA0b86a33E6441b8e776f89d2b5B977c737C5e5b5); replace that value with the
correct checksummed mainnet address 0xA0b86991C6218B36C1d19D4a2e9Eb0cE3606eB48
so price lookups work correctly and match the address used elsewhere in the
repo.

Comment on lines +34 to +134
deployments:
# Example 1: Auction DCA Strategy with dynamic pricing
auction-dca:
name: Auction DCA Strategy
description: |
Dollar-cost averaging strategy using Dutch auctions.
Automatically starts auctions at current market prices.
order: auction-order
fields:
- binding: initial-io
name: Kickoff ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol}
description: |
The initial ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol} ratio to start the first auction.

Current market price: ${io-ratio(order.inputs.0.address, order.outputs.0.address)}

This should be set higher than the baseline ratio to ensure the auction starts
at a favorable price and drops down to the baseline over time.

Recommended: Set 2-5% above current market price.
default: ${io-ratio(order.inputs.0.address, order.outputs.0.address)}

- binding: baseline-io
name: Baseline ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol}
description: |
The minimum acceptable ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol} ratio.

Current market price: ${io-ratio(order.inputs.0.address, order.outputs.0.address)}

Auctions will drop from the initial ratio down to this baseline ratio.
Set this at or slightly below current market price.

- binding: auction-duration
name: Auction Duration (seconds)
description: How long each auction should run before reaching the baseline price.
default: "3600"

# Example 2: Limit Order with price reference
limit-order:
name: Limit Order
description: |
Simple limit order with current market price reference.
Set your desired exchange rate above or below market price.
order: auction-order
fields:
- binding: fixed-io
name: ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol} Rate
description: |
Fixed exchange rate for this limit order.

You will receive ${order.inputs.0.token.symbol} for every 1 ${order.outputs.0.token.symbol} you sell.

📊 Current market price: ${io-ratio(order.inputs.0.address, order.outputs.0.address)}

💡 Tip: Set above market price to sell at a premium, or below to buy at a discount.

- binding: amount
name: ${order.outputs.0.token.symbol} Amount to Sell
description: |
Amount of ${order.outputs.0.token.symbol} you want to sell.

At current market price (${io-ratio(order.inputs.0.address, order.outputs.0.address)}),
this would get you approximately [amount × current_price] ${order.inputs.0.token.symbol}.

# Example 3: Advanced Dutch Auction Strategy
dutch-auction-strategy:
name: Dutch Auction Strategy
description: |
Advanced Dutch auction with multiple price points and dynamic pricing.
Perfect for large orders that need to be executed over time.
order: auction-order
fields:
- binding: start-premium
name: Starting Premium (%)
description: |
Percentage above current market price to start the auction.

Current market price: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} ${order.inputs.0.token.symbol}/${order.outputs.0.token.symbol}

Example: 10% means auction starts at 110% of current market price.
default: "5"

- binding: end-discount
name: Ending Discount (%)
description: |
Percentage below current market price to end the auction.

Current market price: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} ${order.inputs.0.token.symbol}/${order.outputs.0.token.symbol}

Example: 2% means auction ends at 98% of current market price.
default: "2"

- binding: auction-frequency
name: Auction Frequency (hours)
description: |
How often to start new auctions.

With current market price at ${io-ratio(order.inputs.0.address, order.outputs.0.address)},
more frequent auctions may capture better price movements.
default: "24"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Remove trailing spaces flagged by yamllint

There are numerous trailing spaces in multi-line descriptions (e.g., Lines 47, 49, 50, 52, 60, 62, 83, 85, 87, 94, 95, 100, 110, 112, 120, 122, 130, 131, 147, 149, 157, 158, 174, 175, 182, 184, 193, 194, 202, 203, 205). These can cause noisy diffs and lint failures.

Recommendation:

  • Strip trailing whitespace from the entire file.
  • If you intend to create blank lines in a block scalar (description: |), leave empty lines without spaces, not lines containing spaces.

If helpful, I can provide a script to normalize this.

🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 47-47: trailing spaces

(trailing-spaces)


[error] 49-49: trailing spaces

(trailing-spaces)


[error] 50-50: trailing spaces

(trailing-spaces)


[error] 52-52: trailing spaces

(trailing-spaces)


[error] 60-60: trailing spaces

(trailing-spaces)


[error] 62-62: trailing spaces

(trailing-spaces)


[error] 83-83: trailing spaces

(trailing-spaces)


[error] 85-85: trailing spaces

(trailing-spaces)


[error] 87-87: trailing spaces

(trailing-spaces)


[error] 94-94: trailing spaces

(trailing-spaces)


[error] 95-95: trailing spaces

(trailing-spaces)


[error] 100-100: trailing spaces

(trailing-spaces)


[error] 110-110: trailing spaces

(trailing-spaces)


[error] 112-112: trailing spaces

(trailing-spaces)


[error] 120-120: trailing spaces

(trailing-spaces)


[error] 122-122: trailing spaces

(trailing-spaces)


[error] 130-130: trailing spaces

(trailing-spaces)


[error] 131-131: trailing spaces

(trailing-spaces)

🤖 Prompt for AI Agents
In examples/price-ratio-example.yaml around lines 34 to 134, multiple multi-line
description block lines contain trailing spaces which trigger yamllint failures;
remove all trailing whitespace across the file (especially inside every
description: | block), ensure intentionally blank lines contain zero characters
(no spaces), and then re-save the file so the diff only reflects substantive
changes; run a whitespace normalization (trim trailing spaces) before committing
to prevent future noisy diffs.

Comment on lines +198 to +206
gui:
name: Dynamic Pricing Strategies
description: |
Advanced trading strategies with real-time price integration.

All strategies automatically fetch current market prices to provide
intelligent defaults and helpful context for your trading decisions.

Current ${order.inputs.0.token.symbol}/${order.outputs.0.token.symbol} market rate: ${io-ratio(order.inputs.0.address, order.outputs.0.address)}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Expressions in gui.description won’t be interpolated by current parser

The current implementation interpolates field-level name/description/defaults, but not the top-level gui.name/gui.description. The expressions here will remain literal strings at runtime.

Options:

  • Update crates/settings/src/gui.rs to interpolate gui.name and gui.description similarly to field-level values.
  • Or, remove expressions from gui.description in the example to avoid misleading users.

Would you like me to propose a code change to interpolate top-level GUI fields, or adjust the example?


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repo root: $(pwd)"
echo
echo "Searching for files related to gui and interpolation..."

# Find gui.rs if present
fd --hidden --follow --max-depth 4 'gui.rs' || true

# Show the example file mentioned in the review (if present)
echo
echo "---- examples/price-ratio-example.yaml (if present) ----"
rg --line-number --hidden --no-ignore --max-filesize 1M "price-ratio-example.yaml" || true
if [ -f "examples/price-ratio-example.yaml" ]; then
  sed -n '1,260p' examples/price-ratio-example.yaml
else
  echo "examples/price-ratio-example.yaml not found"
fi

echo
echo "---- Searching for crates/settings/src/gui.rs and related code ----"
rg --line-number --hidden --no-ignore --max-filesize 1M "crates/settings/src/gui.rs" || true
if [ -f "crates/settings/src/gui.rs" ]; then
  sed -n '1,240p' crates/settings/src/gui.rs
else
  echo "crates/settings/src/gui.rs not found"
fi

echo
echo "---- Searching repository for interpolation-related symbols ----"
rg --line-number --hidden --no-ignore --max-filesize 1M "interpolat|interpolate|render_template|render_template|Template|expand_vars|expand_|\\$\\{" || true

echo
echo "---- Searching for code that handles field-level name/description/defaults ----"
rg --line-number --hidden --no-ignore --max-filesize 1M "description" | sed -n '1,200p' || true

echo
echo "---- Searching for io-ratio or similar example helpers ----"
rg --line-number --hidden --no-ignore --max-filesize 1M "io-ratio|io_ratio|ioRatio" || true

echo
echo "Done."

Length of output: 130340


🏁 Script executed:

#!/bin/bash
set -euo pipefail
echo "Showing crates/settings/src/gui.rs lines 760-880"
nl -ba crates/settings/src/gui.rs | sed -n '760,880p'

Length of output: 273


Top-level gui.name/gui.description are not interpolated — please fix or update the example

Short: field-level names/descriptions/defaults are run through the YAML Context.interpolate (see crates/settings/src/gui.rs where field interpolation uses context.interpolate), but GuiConfigSourceCfg::try_into_gui returns GuiCfg with name/description copied directly (no interpolation). As a result the ${...} expressions in examples/price-ratio-example.yaml (gui.name/gui.description) will be literal at runtime.

Files to inspect/fix:

  • crates/settings/src/gui.rs
    • Problem: GuiConfigSourceCfg::try_into_gui returns GuiCfg { name: self.name, description: self.description, ... } without calling Context::interpolate.
    • Evidence: field-level interpolation exists in the same file (context.interpolate called for field.name/field.description/field.default around the field-processing code).
    • Fix options:
      • Interpolate top-level GUI fields in this conversion (call context.interpolate(&self.name) and interpolate description) — or accept/construct a Context here and reuse the same interpolation logic used for fields.
      • OR perform interpolation higher where a Context is already available (e.g., in the YAML expansion/parse flow) before building GuiCfg.
  • crates/settings/src/yaml/context.rs
    • Use Context::interpolate / resolve_io_ratio (already supports ${io-ratio(...)}) for top-level fields.
  • examples/price-ratio-example.yaml (lines ~198-206)
    • The gui.description currently contains expressions that will not be evaluated. Either remove these expressions or keep them but ensure the code path interpolates top-level fields.

Problem snippet (from example):

gui:
  name: Dynamic Pricing Strategies
  description: |
    Advanced trading strategies with real-time price integration.
    
    All strategies automatically fetch current market prices to provide 
    intelligent defaults and helpful context for your trading decisions.
    
    Current ${order.inputs.0.token.symbol}/${order.outputs.0.token.symbol} market rate: ${io-ratio(order.inputs.0.address, order.outputs.0.address)}

Would you like me to:

  • propose the code change to interpolate top-level GUI fields (patch in crates/settings/src/gui.rs or the YAML parsing flow), or
  • update the example to remove/clarify the interpolated expressions?
🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 202-202: trailing spaces

(trailing-spaces)


[error] 203-203: trailing spaces

(trailing-spaces)


[error] 205-205: trailing spaces

(trailing-spaces)

@@ -1 +1 @@
Subproject commit 46ec53183a0ae214847e30bf0bf10ec819b34233
Subproject commit 9d6d8cc2405031ff31ee2db75b15b74ef968c88c
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Ensure CI and local workflows fetch submodules

If CI or dev setup doesn’t already fetch submodules, this bump will not take effect in builds/tests. Make sure actions/checkout (or equivalent) has submodules enabled.

For GitHub Actions, the typical configuration is:

- uses: actions/checkout@v4
  with:
    submodules: recursive
    fetch-depth: 0

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== Top-level files ==="
ls -la | sed -n '1,200p' || true

echo
echo "=== Check for .gitmodules ==="
if [ -f .gitmodules ]; then
  echo ".gitmodules FOUND:"
  sed -n '1,200p' .gitmodules
else
  echo ".gitmodules NOT found"
fi

echo
echo "=== Search for 'actions/checkout' occurrences ==="
rg -n "actions/checkout" || true

echo
echo "=== Search for 'submodules' keyword occurrences ==="
rg -n "submodules" || true

echo
echo "=== Search for 'git submodule' occurrences ==="
rg -n "git submodule" || true

echo
echo "=== Search for 'fetch-depth' occurrences ==="
rg -n "fetch-depth" || true

echo
echo "=== Show GitHub workflow files if present ==="
if [ -d .github/workflows ]; then
  for f in .github/workflows/*.{yml,yaml}; do
    [ -e "$f" ] || continue
    echo
    echo "---- $f ----"
    sed -n '1,300p' "$f"
  done
else
  echo ".github/workflows directory NOT present"
fi

echo
echo "=== Search for other CI configs (circleci, gitlab-ci) ==="
rg -n "gitlab-ci|circleci|workflows" || true

Length of output: 49142


Enable submodules on the remaining checkout step

Most workflows already fetch submodules (submodules: recursive + fetch-depth: 0). Update the checkout in the one workflow that doesn't.

Files to change:

  • .github/workflows/npm-package-release.yml — the checkout step currently only sets ssh-key; add submodules and fetch-depth.

Replace this:

  • uses: actions/checkout@v4
    with:
    ssh-key: ${{ secrets.PUBLISH_PRIVATE_KEY }}

With this:

  • uses: actions/checkout@v4
    with:
    ssh-key: ${{ secrets.PUBLISH_PRIVATE_KEY }}
    submodules: recursive
    fetch-depth: 0
🤖 Prompt for AI Agents
In lib/rain.orderbook.interface around lines 1 to 1, update the GitHub Actions
checkout step in .github/workflows/npm-package-release.yml (currently only sets
ssh-key) to also enable submodules and full history; change the
actions/checkout@v4 step to include with: ssh-key: ${{
secrets.PUBLISH_PRIVATE_KEY }}, submodules: recursive, and fetch-depth: 0 so
submodules are fetched and not shallow-cloned.

Comment thread README.md
Comment on lines +32 to +56
**Key Benefits:**
- **Automatic Price Fetching**: Strategies can automatically use current market prices
- **User-Friendly Defaults**: No more manual price lookups for users
- **Flexible Integration**: Works with any price API (SushiSwap, 1inch, CoinGecko, etc.)
- **Error Handling**: Comprehensive error handling for network issues and invalid data

**Quick Example:**
```yaml
fields:
- binding: initial-io
name: Kickoff ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol}
description: Current market price: ${io-ratio(order.inputs.0.address, order.outputs.0.address)}
default: ${io-ratio(order.inputs.0.address, order.outputs.0.address)}
```

```javascript
const priceCallback = async (inputTokenAddress, outputTokenAddress) => {
const response = await fetch(`/api/price/${inputTokenAddress}/${outputTokenAddress}`);
return (await response.json()).ratio.toString();
};

const gui = await DotrainOrderGui.newWithDeploymentAndPriceCallback(
yaml, deployment, stateCallback, priceCallback
);
```
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick (assertive)

Fix markdown lint (MD031/MD032) and spacing around lists and code fences

Lists and fenced code blocks should be surrounded by blank lines. This also resolves the spacing issues flagged by LanguageTool.

Apply the following to improve formatting and lint compliance:

-**Key Benefits:**
-- **Automatic Price Fetching**: Strategies can automatically use current market prices
-- **User-Friendly Defaults**: No more manual price lookups for users
-- **Flexible Integration**: Works with any price API (SushiSwap, 1inch, CoinGecko, etc.)
-- **Error Handling**: Comprehensive error handling for network issues and invalid data
+**Key Benefits:**
+
+- **Automatic Price Fetching**: Strategies can automatically use current market prices
+- **User-Friendly Defaults**: No more manual price lookups for users
+- **Flexible Integration**: Works with any price API (SushiSwap, 1inch, CoinGecko, etc.)
+- **Error Handling**: Comprehensive error handling for network issues and invalid data
-**Quick Example:**
-```yaml
+**Quick Example:**
+
+```yaml
 fields:
   - binding: initial-io
     name: Kickoff ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol}
     description: Current market price: ${io-ratio(order.inputs.0.address, order.outputs.0.address)}
     default: ${io-ratio(order.inputs.0.address, order.outputs.0.address)}

-javascript + +javascript
const priceCallback = async (inputTokenAddress, outputTokenAddress) => {
const response = await fetch(/api/price/${inputTokenAddress}/${outputTokenAddress});
return (await response.json()).ratio.toString();
};

const gui = await DotrainOrderGui.newWithDeploymentAndPriceCallback(
yaml, deployment, stateCallback, priceCallback
);

Also consider adding a brief note clarifying io-ratio direction (input per 1 output) to avoid ambiguity for users.

🧰 Tools
🪛 LanguageTool

[grammar] ~32-~32: There might be a mistake here.
Context: ...hout hardcoding values. Key Benefits: - Automatic Price Fetching: Strategies c...

(QB_NEW_EN)


[grammar] ~33-~33: There might be a mistake here.
Context: ... automatically use current market prices - User-Friendly Defaults: No more manual...

(QB_NEW_EN_OTHER)


[grammar] ~34-~34: There might be a mistake here.
Context: ...: No more manual price lookups for users - Flexible Integration: Works with any p...

(QB_NEW_EN_OTHER)


[grammar] ~35-~35: There might be a mistake here.
Context: ... API (SushiSwap, 1inch, CoinGecko, etc.) - Error Handling: Comprehensive error ha...

(QB_NEW_EN_OTHER)


[grammar] ~36-~36: Use correct spacing
Context: ...ling for network issues and invalid data Quick Example: ```yaml fields: - bin...

(QB_NEW_EN_OTHER_ERROR_IDS_5)


[grammar] ~38-~38: Use correct spacing
Context: ...ssues and invalid data Quick Example: yaml fields: - binding: initial-io name: Kickoff ${order.inputs.0.token.symbol} per ${order.outputs.0.token.symbol} description: Current market price: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} default: ${io-ratio(order.inputs.0.address, order.outputs.0.address)} javascript const priceCallback = async (inputTokenAddress, outputTokenAddress) => { const response = await fetch(`/api/price/${inputTokenAddress}/${outputTokenAddress}`); return (await response.json()).ratio.toString(); }; const gui = await DotrainOrderGui.newWithDeploymentAndPriceCallback( yaml, deployment, stateCallback, priceCallback ); 📚 Learn More: See [docs/price-ratio...

(QB_NEW_EN_OTHER_ERROR_IDS_5)

🪛 markdownlint-cli2 (0.17.2)

33-33: Lists should be surrounded by blank lines

(MD032, blanks-around-lists)


39-39: Fenced code blocks should be surrounded by blank lines

(MD031, blanks-around-fences)

🤖 Prompt for AI Agents
In README.md around lines 32 to 56, the markdown blocks and lists lack
surrounding blank lines causing MD031/MD032 lint errors; fix by adding a blank
line before and after the "**Quick Example:**" heading, add a blank line between
the list of Key Benefits and the code fence, ensure there is a blank line
immediately before each ```yaml and ```javascript fence and immediately after
each closing fence, and optionally add a short single-line note clarifying the
io-ratio direction (e.g., "io-ratio returns input amount per 1 output") right
after the yaml example.

@hardyjosh hardyjosh requested a review from findolor August 28, 2025 15:36
Copy link
Copy Markdown
Collaborator

@findolor findolor left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@big14way Thanks for the PR. We use AI analysis to apply improvements and catch issues so could you take a look at all the AI comments posted in this PR and reply to them?

You don't have to agree with every comment but sometimes it outlines issues that have been overlooked. If you can sweep and reply to each (not needed/will do) that would be great. I can review the PR after.

@findolor findolor closed this Apr 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Ability to reference price ratios in field names/descriptions and for default field values in DotrainOrderGui yaml

2 participants